Skip to content
Protocol

PSM

The USDG gate — asymmetric fees, a 30 % cap that never rises, repayment in USDG, and what happens if USDG loses its peg.

The peg stability module swaps USDG against fyUSD at close to par in both directions. It is what bounds the fyUSD price from above, what provides an exit that does not depend on a DEX pool, and what lets a borrower repay in the stablecoin they actually hold.

It is a door, not a place to stay. The fees are asymmetric on purpose: cheap to enter, four times more expensive to leave, so that the module is used for arbitrage and exit rather than as a parking spot.

Fees and directions

function swapIn(uint256 usdgAmount, uint256 minFyOut) external returns (uint256);
function swapOut(uint256 fyAmount, uint256 minUsdgOut) external returns (uint256);
DirectionFeeEffect
USDG to fyUSD (swapIn)0.05%Reserve rises, fyUSD is minted to the caller, minted[PSM] rises
fyUSD to USDG (swapOut)0.20%fyUSD is burned from the caller, reserve falls

Both fees are immutable, and both become zero during Sunset (Rule R-8.1.1, R-8.2.1, R-8.7.1).

The resulting price bounds (Rule R-8.4.1):

How it is calculated

While intake capacity remains: market ceiling ≈ 1.0005 USD. While the reserve is non-empty: market floor ≈ 0.998 USD.

Guarantee

swapOut is never pausable — not by the Closer, not by any mechanism, not during a USDG depeg. Whoever holds fyUSD can always reach the reserve while the reserve exists (Rule R-8.2.1, Rule R-4.4.1).

The entry side is the one that can be frozen, because it is a mint (Rule R-8.6.1).

The 30 % cap

Entry is capped at a fixed share of the total fyUSD supply (Rule R-8.1.1):

How it is calculated

swapIn(usdg) requires minted[PSM] + usdg ≤ capShare × (fyUSD.totalSupply() + usdg), with capShare = 30%.

The condition is written on the post-mint supply, so it must be solved for the marginal mint. intakeCapacity() returns the answer directly:

function intakeCapacity() external view returns (uint256);  // max(0, capShare × supply − minted[PSM]), solved for the marginal mint

Worked example. Supply 4 000 000 fyUSD, of which 1 000 000 came from the PSM. The remaining capacity solves 1 000 000 + x = 0.30 × (4 000 000 + x), giving x = 285 714. An arbitrageur can therefore bring in roughly 286 000 USDG before the door closes.

Guarantee

The 30 % cap never rises. There is no function to raise it, during any market condition, for any reason, by anybody. A persistent premium above the ceiling is accepted as an outcome rather than answered with a parameter change: it lasts until the debt caps or the market loosen, and nobody intervenes (Rule R-1.5.1, Rule R-8.4.1).

The reason for the cap is what fyUSD is meant to be. Without it, a protocol whose stablecoin trades at a premium fills its balance sheet with USDG until fyUSD is a wrapped USDG with a CDP attached. The cap makes the ratio visible and bounds it: minted[PSM] / totalSupply is published as "fyUSD backed by USDG", and if it sits at the cap for a long period, that is the public information that the CDP side is not growing (Rule R-8.5.4).

repayWithUSDG

function repayWithUSDG(address branch, address onBehalfOf, uint256 usdgAmount, uint256 minDebtRepaid) external;

The PSM takes the USDG, mints usdgAmount × (1 − feeIn) fyUSD to itself, and calls Branch.repay as the payer (Rule R-1.1.4, Rule R-8.3.1).

Two properties make this the important function on the page:

It is exempt from the cap. minted[PSM] may exceed 30% of the supply through this route (Rule R-8.3.2). The result is over-collateralisation at the protocol level: more USDG sits in the reserve than there is PSM-minted fyUSD outstanding, so every holder can still exit one for one. Repaying debt with USDG can never make the system less solvent, so capping it would only make the exit worse.

It costs 0.05% regardless of the market price of fyUSD. A borrower who wants to repay when fyUSD trades at a premium does not have to buy fyUSD at that premium. Repaying 10 000 of debt costs 10 005 USDG whatever the DEX says.

Two related helpers:

function swapInToSP(address branch, uint256 usdgAmount, uint256 minDeposit) external;  // USDG to pool in one transaction

swapInToSP is subject to the intake cap and to the Stability Pool caps (Rule R-8.3.3).

Fee routing: the repayWithUSDG fee is minted in fyUSD and routed by the InterestRouter to the Stability Pool of the branch that was repaid, under the current regime's key. The swapIn and swapOut fees are routed to the pools of all activated branches pro rata their uncovered debt (Rule R-8.3.4, Rule R-8.5.3).

The reserve

Invariant (Rule R-8.5.1). reserve == FYUSD.minted[PSM], up to rounding, in every mode. This holds because every fee is minted in fyUSD against USDG that stays in the reserve, rather than skimmed from the reserve. There is no excess to sweep, and no sweepExcess function.

Operational target: 10% of the supply, with 5% as the operational minimum, bootstrapped by the founders through swapIn. That is a target, not a contract rule, and there is no function anywhere that enforces it (Rule R-8.5.2).

Version 0.4 raised the published aim from the bottom of that range to the top, and gave the reason (decision D88). The peg has two lines out of session: redemption and this reserve. Redemption only works while the collateral has held its value, because a redeemer burns fyUSD at par and receives collateral at pRedeem, never below the official print. The moment the collateral falls, that trade stops being worth doing and the reserve is the only line left. Sizing it at the bottom of a range is sizing it for the weekend where nothing happened.

Risk

The reserve is bootstrapped by the founders and can be drawn down by anyone through swapOut. If it empties on a weekend during which the collateral has fallen, nothing holds the fyUSD price from below until the market reopens. No mechanism refills it, and none can be added after deployment.

Guarantee

The reserve is never deployed. It is not lent to Spark, not supplied to Morpho, not placed in any third-party vault, for the entire life of version 1. An automatic deposit into a vault governed by somebody else, with no hand able to withdraw it in an incident, would be the protocol's only path to losing funds through a third party. The additional yield it would produce does not buy that (Rule R-8.5.5).

What happens in a USDG depeg

There is no USDG oracle in version 1. USDG is treated as a unit of account for the module, not as an asset with a price (Rule R-2.3.1).

During the Closer's lifetime, a depeg is answered with Closer.freeze(PSM, MINT, ≤ 72 h), which blocks swapIn, swapInToSP and repayWithUSDG, subject to the same cumulative 30-day bound as a branch freeze. swapOut and repayment in fyUSD stay open throughout (Rule R-8.6.1).

After the key expires, nothing happens. There is no mechanism, and there is no plan to add one, because adding one would mean a setter.

Risk

If USDG loses its peg permanently, holders of fyUSD are exposed through the reserve, bounded by capShare at 30% of the supply plus whatever repayWithUSDG has added. Below the peg, arbitrageurs would mint fyUSD cheaply against discounted USDG until the cap binds; above it, the reserve would be drained through swapOut at par. The exposure is bounded and disclosed. It is not hedged.

The reason this is tolerable is the shape of the cap. At 30% of the supply, a 10% USDG depeg is roughly a 3% hit to the backing of fyUSD, against a CDP side over-collateralised at 125% or more at the moment each position opened. The reason it is not comfortable is that the bound is the only defence, and the protocol has no way to add another one after deployment.

Sunset behaviour

The PSM reads BranchRegistry.sunsetAt() at every call; it has no Sunset function of its own. Once Sunset begins (Rule R-8.7.1):

  • feeIn and feeOut become zero.
  • capShare becomes 100%.
  • Any Closer freeze on intake is lifted.

That combination turns the module into an unrestricted exit at par in both directions, which is exactly what a wind-down needs. The change is one-way.

The two prices you should see

A front end shows the reserve, the remaining intake capacity, and the two implied prices 1.0005 and 0.998, so that a user can compare them against whatever a DEX is quoting (Rule R-1.5.1). When the DEX price sits inside that band, the PSM is doing its job and nobody needs to use it.

Last reviewed: 2026-09-07 · Spec v0.4