Treasury Floor
On this page
The Treasury records a reserve value and provides a bounded NOTE redemption path. Marked reserve value is not the same as immediately available USDG or a market-price guarantee.
Reserves
fact The Treasury holds USDG, whitelisted Stock Tokens, COUPON legs and NOTE. reserveValueQuote() values everything except NOTE in USDG:
Asset listing checklist. Only plain ERC-20 tokens are eligible as series underlyings (NoteCore.createSeries, RollPolicy.setTemplate) and as Treasury stock assets (addStockAsset): 18 decimals, no fee on transfer, no rebasing or elastic supply, no transfer pause that can outlast a series, and a uiMultiplier-aware feed. NoteCore._burnAndPay delivers the nominal stockOut computed from the books without a recipient balance-delta check, so a token that deducts a fee on transfer would deliver less stock than the core reports and the shortfall would fall on later redeemers.
Robinhood Stock Tokens meet the checklist; any new listing must be verified against it before governance registers the asset.
| Asset | Valuation |
|---|---|
| USDG | Par |
| Stock Tokens | Chainlink-style feed (addStockAsset), reverts IncompleteRound(token) if the latest round has updatedAt == 0 or answeredInRound < roundId, or InvalidPrice if the answer is not positive. A print older than maxFeedStaleness (26 hours by default) is not used directly: the module asks the oracle adapter for the market-calendar reference price (the official close of the last completed session, at most seven days back), so a Friday close read at the weekend or over a holiday still values, and reverts StaleFeed only when neither is available. In reserveValueQuote a stale asset whose holding is worth at most staleDustQuote (one whole quote unit by default, a permissioned setting) is carried at zero instead of reverting, so a dust balance of a delisted asset cannot freeze redemptions or the floors. The same dust rule applies to COUPON leg holdings: a Live leg whose feed has no print under maxFeedStaleness and no reference close is carried at zero in reserveValueQuote when its unit balance is at most staleDustQuote (COUPON units are quote-denominated with a factor capped at 1, so units bounds the holding's value); a larger holding still reverts StaleFeed(feed). withdrawLegs moves such dust out against the daily budget at par, and isLegStaleDust(seriesId) mirrors isStaleDust(token). A feed whose latestRoundData reverts or returns malformed data is treated like a stale feed: the reference close is used when available, otherwise the asset is stale (StaleFeed, or zero when the holding is dust). The same checks apply wherever the asset is valued: valueOf, reserveValueQuote, previewRedeem, withdrawERC20 and purchaseStock. isReferencePriced(token) reports whether the reference close currently stands in for a stale or unreadable print; the bond depository uses it to refuse stock-quoted purchases while the market is shut |
| COUPON legs | legValueFactorWad: par (1 USDG per unit) when Autocalled, Matured unbreached or Unwound; while Live with the barrier intact on both the last observation and the latest fresh print, mark + (1 − mark) × min(1, (mark − barrier) / legBufferBps) with mark = min(1, liveMark / s0), liveMark = min(last observed price, latest fresh print within 30 minutes) and legBufferBps default 15% (setLegBufferBps), so a leg reaches par only once it sits at least the buffer above the barrier; while Live with the barrier breached mark; Matured breached at min(1, min(lastPrice, usablePrice) / s0), where usablePrice is the latest print under maxFeedStaleness or, failing that, the market-calendar reference close; when neither is available the maturity print alone is used, so the mark follows the stock down between maturity and harvestSeries and never exceeds the maturity print; zero in any other status. Series must be registered with registerLegSeries (owner or BondDepository). Accrued coupons and terminal payouts are pulled by the permissionless harvestSeries(seriesId), which measures the Treasury's quote and stock balance deltas around the core call and reports those measured amounts in SeriesHarvested(seriesId, couponOut, unitsRedeemed, quoteOut, stockOut); indexers can rely on stockOut as delivered stock, not nominal stock. When harvestSeries receives stock from a breached maturity or an unwound series, the Treasury registers that token as a Stock asset with the series' price feed if it is not registered already (AssetAdded), so the received stock is part of the reserve value and the withdrawal budget from the next block on. NOTE is never registered. Stock arriving by direct transfer remains unregistered until addStockAsset |
| NOTE | Excluded (it is the liability side) |
Floor and Redemption
fact Floor and Redemption Calculation
redeem(noteAmount, minQuoteOut) burns the caller’s NOTE and pays previewRedeem in USDG, provided it does not exceed dailyRedemptionRemaining() and the Treasury holds enough USDG (InsufficientLiquidity otherwise). The limiter is a leaky bucket: redemptionUsed() is the usage recorded at the last redemption, amortised linearly to zero over REDEMPTION_WINDOW = 1 day, so over any span t the Treasury pays at most cap × (1 + t / 1 day) and never more than cap in one burst.
| Parameter | Value | Hard maximum |
|---|---|---|
redemptionHaircutBps | 0 in DeployToken.s.sol | 5,000 (50%) |
dailyRedemptionCapBps | 200 (2%) in DeployToken.s.sol, as PROTOCOL_SPEC.md specifies | 10,000 |
maxFeedStaleness | 26 hours | - |
fact DeployToken.s.sol passes the two values in the order initialize expects (daily cap, then haircut) and reverts with TreasuryParamMismatch if the deployed Treasury does not read them back, so redemption is live from deployment at a 2% daily cap with no haircut. Both can be changed by the owner with setRedemptionParams(haircutBps, dailyCapBps). The examples below use a 2% haircut and 2% daily cap and are illustrative.
Worked Example
illustrative Reserves: 3,000,000 USDG + 4,000 NVDA tokens at 180.00 = 720,000 + 280,000 USDG of COUPON legs at par. reserveValueQuote = 4,000,000. Total NOTE supply 12,000,000 of which 2,000,000 is held by the Treasury; circulating 10,000,000.
| Quantity | Value |
|---|---|
| Floor | 4,000,000 / 10,000,000 = 0.400 USDG per NOTE |
| Redemption of 100,000 NOTE at 2% haircut | 100,000 × 0.400 × 0.98 = 39,200 USDG |
| Daily allowance | 2% × 4,000,000 = 80,000 USDG |
| Redemptions possible today | 80,000 / 0.392 ≈ 204,081 NOTE |
| After redeeming 100,000 NOTE | reserves 3,960,800; circulating 9,900,000; floor 0.40008 |
Redemption at a haircut slightly raises the floor for remaining holders, because the redeemer leaves 2% of their share behind.
Why a Cap and a Haircut
- The daily cap prevents a run from draining the Treasury’s liquid USDG in one block while Stock Tokens and legs, which are less liquid, remain. Redeemers queue across days and the Treasury can rebalance.
- The haircut makes redemption a floor rather than an arbitrage engine: NOTE has to trade below
floor × (1 − haircut)before burning beats selling. - Haircut recapture. The haircut a redemption leaves behind stays in the reserve and therefore accrues to the remaining holders, including a redeemer who keeps part of a position. Splitting a redemption into tranches recaptures a share of the haircut through the later tranches; the closed form for a holder redeeming a fraction
fof the circulating supply inkequal tranches at haircuthis a saving that grows withkand is bounded byf × hof the redeemed value. The Treasury removes this within a window: everyredeemaddsgross − quoteOuttohaircutRetainedStored, decayed linearly to zero overREDEMPTION_WINDOWfromlastRedemptionAt(the same leaky-bucket shape asredemptionUsed), andpreviewRedeemvalues againstreserveValueQuote() − retainedHaircutQuote(). Within one window tranche splitting recaptures nothing; across windows the recapture is bounded by the daily cap times the haircut. Reserve value per circulating NOTE is non-decreasing after every redemption. - Liquidity:
redeempays only USDG. If USDG on hand is insufficient the call reverts; governance can sell Stock Tokens or wait for legs to settle. The floor is a valuation, not a guarantee of same-day liquidity for all reserves.
Reserve Flows
| Inflow | Source |
|---|---|
| Bond proceeds | BondDepository (USDG, Stock Tokens, COUPON legs) |
| 10% of protocol fees | RevenueRouter |
| Desk returns | COUPON coupons and principal from Desk positions |
| Stock Tokens bought from the Desk at feed value | Desk.liquidateToTreasury → Treasury.purchaseStock |
| Outflow | Authority |
|---|---|
| NOTE redemption | Permissionless, capped |
| USDG paid to the Desk for Stock Tokens | purchaseStock (Desk only, at feed value, StockPurchased); the Treasury's balance change must equal the amount sent, else TransferShortfall(expected, received) |
withdrawERC20(token, to, amount), withdrawLegs(seriesId, to, units) | governance owner only (Withdrawn, LegsWithdrawn); every registered reserve asset, enabled or disabled, and COUPON legs are valued through the reserve mark and consume the daily redemption budget shared with redeem (DailyCapExceeded); only tokens never registered leave the budget untouched. Withdrawing a disabled asset therefore needs a working feed; setAssetFeed(token, feed) (owner, Stock assets only, else AssetNotEnabled; feed decimals at most 18, else InvalidFeed; AssetFeedUpdated(token, previous, current)) replaces a feed that no longer prices |
mintNote(to, amount) | governance owner, within epoch cap (NoteMinted) |
Governance withdrawal authority is the main trust assumption on the floor; see Governance for governance and guardian arrangements.
Keep exploring, or revisit this topic from the navigation.