How the LitePSM Works
Architecture
A USDS ↔ USDC swap through the deployed wrapper flows through the USDS wrapper, the core DAI LitePSM, and the pocket. The wrapper also uses Maker's DAI and USDS join adapters to translate the DAI-denominated core route into a USDS-denominated interface:
caller ──► UsdsPsmWrapper ──► DssLitePsm (LITE-PSM-USDC-A) ──► Pocket
│ ▲
├──── DaiJoin ────────────┤
└──── UsdsJoin ───────────┘The core DssLitePsm is DAI-native: it swaps USDC against a pre-minted DAI balance. To present a USDS interface, the UsdsPsmWrapper wraps the core PSM and uses DaiJoin and UsdsJoin to move between DAI, USDS, and Vat internal balances. USDC custody lives in the Pocket, a separate address whose balance is the maximum USDC available for buy-side liquidity.
The "lite" in LitePSM refers to a design choice: the original Maker DSS PSM touched the Vat on every swap, which is expensive in gas. The LitePSM holds pre-minted DAI inside the core PSM contract, so core user swaps reduce to ERC-20 transfers plus small accounting around fees. Periodic permissionless bookkeeping calls (described below) keep the system aligned with its target accounting state.
Pre-Minted DAI Buffer
The core PSM has a buf parameter, currently 400_000_000e18, representing the target amount of unused pre-minted DAI the system is designed to keep available in most situations. buf is not the same thing as the exact current DAI balance, and fill() / trim() do not simply compare dai.balanceOf(psm) to buf.
The core contract computes its target debt as:
targetArt = gem.balanceOf(pocket) * to18ConversionFactor + bufTwo permissionless functions use this target together with the local and global debt ceilings:
fill()— mintsrush()DAI from the Maker Vat into the core PSM when additional DAI can be generated under the target and debt-ceiling constraints.trim()— burnsgush()DAI and repays Vat debt when the core PSM has excess debt or needs to reduce debt against the target.
Both functions are typically called by automated keeper jobs and are not part of the normal swap path. From an integrator's perspective, the sell-side capacity for USDC → USDS is the actual external DAI balance of the core PSM:
dai.balanceOf(address(wrapper.psm()))That balance can be below, equal to, or above buf. For large routes, read the live DAI balance and account for same-block fill(), trim(), chug(), and competing swaps.
The Pocket
USDC is not held in the LitePSM contract itself. It is held in a separate Pocket address (0x37305B...D7341), which has granted the core DssLitePsm a max allowance to move USDC during buyGem calls. This decoupling has two consequences:
- Buy-side liquidity is exactly
gem.balanceOf(pocket). Integrators should read this directly when sizing routes. - The pocket address is immutable in the deployed core PSM. The current PSM cannot reassign
pocketthrough afilecall; changing that address would require deploying or migrating to a different PSM/wrapper setup.
The Pocket is a Coinbase Custody account with a private key that was burned with a heavily witnessed key generation and burning ceremony. The idea is that the only transaction made on this Custody account was an infinite approval to the PSM contract, which allows adding and removing funds. After that transaction was complete, both Coinbase's and Sky's keys were permanently destroyed.
Fees
The PSM exposes two fee parameters in WAD precision (where 1e18 = 100%):
tin— fee applied when a user sells USDC for DAI/USDS.tout— fee applied when a user buys USDC with DAI/USDS.
Both are currently 0. Accrued fees stay in the contract as DAI and are swept to the Maker Vow (surplus buffer) by a permissionless chug() call.
Halts and Failure Modes
- Direction halt. Sky governance, or the
DssLitePsmMomemergency contract, can settinortouttotype(uint256).max(theHALTEDsentinel). When set, the corresponding direction reverts. Readtin()andtout()before quoting and reject if either equalstype(uint256).max. - Pocket depletion. If
gem.balanceOf(pocket)is below the requestedgemAmt,buyGemreverts. Check pocket balance before routing buy-side liquidity. - USDC blacklisting. USDC has an admin-controlled blacklist. If Circle blacklists the pocket or the wrapper, swaps in the affected direction revert at the USDC transfer step.
- Debt ceiling exhaustion. If the Maker Vat debt ceiling for the LitePSM ilk is exhausted,
fill()reverts and sell-side liquidity stops replenishing. Swaps continue to work against the existing buffer until it is drained. - Vat cage / global settlement assumptions.
live()is a compatibility getter that returnsvat.live(). The swap functions do not check this flag directly, and the ChainSecurity audit notes thatDssLitePsmdoes not support coordinated Global Settlement. Treat any Vat cage or settlement state as unsupported for normal integrations.
Decimals
USDC has 6 decimals. USDS and DAI have 18 decimals. The PSM uses a constant to18ConversionFactor = 10^12 to translate between them.
Both sellGem and buyGem take gemAmt in USDC decimals (6). The functions return DAI/USDS amounts in WAD (18 decimals).
Worked example — selling 1 USDC for USDS with tin = 0:
gemAmt = 1_000_000 (1 USDC, 6 decimals)
usdsOut = gemAmt * 10^12 (scale to WAD)
= 1_000_000_000_000_000_000 (1 USDS, 18 decimals)With a non-zero tin, the output is reduced proportionally:
usdsOut = gemAmt * 10^12 * (WAD - tin) / WADFor buyGem, the input USDS amount is increased by tout:
usdsIn = gemAmt * 10^12 * (WAD + tout) / WAD