Desk
On this page
The Desk pools USDG to take COUPON exposure within allocation limits. Depositors bear the underlying note risks and may have to wait for deployed capital to become available for withdrawal.
Purpose
A series is only useful if both legs fill. The Desk pools USDG and subscribes through the configured fill policy when SHIELD demand exceeds COUPON demand and the policy and caps permit a fill. Desk depositors earn the coupons (and take the note-buyer’s risk) on the notional the Desk fills.
Structure
fact Desk is an ERC-4626 vault over USDG (reconciled with contracts/src/token/Desk.sol).
| Property | Value |
|---|---|
| Deposits | Open (deposit/mint), pausable by guardian |
| NAV | Idle USDG plus marked deployed principal, held stock, retained legs and keeper rewards owed. Valuation limits are explained below |
| Fills | fillCoupon(seriesId, amount, minCouponBps), callable by the bound filler (DeskPolicy) only, only while S > D during Subscription and never past that gap. The Desk posts the amount as a reserved deposit on NoteCore (depositCouponReserved): at strike the core matches it only as far as the discovered coupon stays at or above minCouponBps and refunds the excluded part to the Desk (ReserveExcluded), which harvest collects. The filler binding changes only through the protocol governance with the published delay |
| Caps | Caps{perSeriesCapBps, perUnderlyingCapBps, totalDeployedCapBps} as bps of totalAssets; DeployToken.s.sol sets 1,000 / 2,500 / 6,000. A breach reverts CapExceeded(cap, requested, limit) with cap one of "perSeries", "perUnderlying", "totalDeployed" |
| Utilisation | utilisationBps = deployed × 1e4 / totalAssets, capped at 10,000 when marked NAV falls below the deployed principal |
| Withdrawals | Immediate up to free idle USDG after minHold; otherwise FIFO requestWithdraw(shares, receiver) queue filled by processQueue(n) as series settle and coupons are harvested. Fills credit a claimable balance that the receiver collects with claim(to) |
| Stock on breach | Held and valued at the Treasury feed; governance may liquidateToTreasury at feed price (Treasury.StockPurchased) |
| Harvest | harvest(seriesId), permissionless: claims coupons, redeems or refunds, updates deployed. The core's NothingToClaim() and NothingToRefund() reverts are treated as a zero result only when the revert payload is exactly those four bytes; any other revert, including one whose data merely starts with the same selector, is bubbled up unchanged |
fact Structure Calculation
utilisationBps feeds the bond discount: when the Desk is heavily deployed, bonds cheapen to raise Treasury reserves.
Net Asset Value
totalAssets = idleAssets + marked deployed principal + held Stock Token value + retained leg value + keeper reward owed. idleAssets is the USDG balance less totalClaimable (filled requests not yet claimed). The keeper reward the Desk has earned on NoteCore but not yet claimed (keeperOwed) is part of NAV; claiming it moves the amount into idle without changing NAV or the share price, and queue fills price it in.
Held stock is valued at the Treasury feed; when the Treasury cannot value a token it is carried at lastStockMark(token), the last good value of 1e18 base units, refreshed on every state-changing call (StockMarkStale(token, lastMark) is emitted when the fallback is used), for at most maxFeedStaleness after the mark was taken (lastStockMarkAt(token)), and at zero once the mark is older than that (StockMarkExpired(token, lastMark, markedAt)), when setStockWriteOff(token, true) has been applied or when the token was never marked.
Legs kept after a failed redemption (RedemptionFailed) stay in NAV at the Treasury leg value (retainedLegValue(), LegsRetained / LegsReleased) until a later harvest redeems them or the series is written off; the Desk records the units it held at the moment of retention (retainedUnits(seriesId)) and values min(balance, retainedUnits), so legs donated to the Desk after retention do not inflate NAV until a later successful harvest books them as cash.
While the Desk holds a stock whose feed is stale beyond the mark window and that is not written off, maxDeposit and maxMint return 0 and deposits revert, because NAV would understate the stock and dilute existing holders; exits, the queue, harvest and the owner write-off keep working, and deposits reopen when the feed returns or the stock is written off. paused() stays false in this state, so integrators read maxDeposit(user) rather than paused() to know whether deposits are open. totalAssets never reverts on a held-stock feed failure; a stale feed behind a Live or Matured series makes the Treasury leg factor, and with it totalAssets, revert StaleFeed until the feed prints again
Worked Example
illustrative Desk totalAssets 4,000,000 USDG (2,300,000 idle, 1,500,000 deployed, 200,000 held stock). NVDA series: SHIELD 900,000, COUPON 500,000. The Desk already holds 200,000 in another NVDA series.
| Constraint | Room |
|---|---|
Gap S − D | 400,000 |
| Per series 10% | 400,000 − 0 = 400,000 |
| Per underlying 25% | 1,000,000 − 200,000 = 800,000 |
| Total 60% | 2,400,000 − 1,500,000 = 900,000 |
| Idle USDG | 2,300,000 |
| Fillable | 400,000 |
After filling, D = 900,000 = S, the series matches fully at strike (subject to notionalCap), the Desk holds 400,000,000,000 raw COUPON units, representing 400,000 USDG notional, deployed = 1,900,000 and utilisation is 47.5%.
Withdrawal Queue
maxWithdraw(owner)is bounded by idle USDG net of the value of shares escrowed in open requests (previewRedeem(escrowedShares)). Withdrawing up to that amount is a normal ERC-4626withdraw/redeem, subject to the exit gates below.- Beyond that,
requestWithdraw(shares, receiver)escrows the shares in the vault and appends a request (WithdrawRequested). The shares must be worth at leastminRequestQuote()at the current NAV (default 1 USDG; the owner may set any value from 1 raw unit to 1,000 USDG viasetMinRequestQuote,MinRequestQuoteOutOfBoundsotherwise), and smaller requests revertRequestTooSmall(assets, minimum). Shares received after a request never join the request. The request is subject to the same exit gates as a direct withdrawal: it revertsExitBlocked(owner)while the owner is insideminHoldor while any settled position is waiting forharvest.cancelWithdrawRequest(id)returns the escrowed shares to the owner. - Anyone may call
processQueue(n)to walk up tonqueue entries in FIFO order (nis clamped toMAX_QUEUE_PROCESS = 50; every visited entry counts, including cancelled or filled ones, and the head is persisted past closed entries). The hold is checked once, at request time; only the harvest gate is checked again at fill: nothing fills whilehasUnharvestedSettled()is true. A transfer to a requester after the request re-stamps only the owner’s free balance and never touches the queued entry, so a gift of shares cannot make the head skip and let the next entry take the idle USDG. A request fills only whenpreviewRedeem(shares)fits inidleAssets(); the escrowed shares are burnt at the share price of that moment,WithdrawRequestFilledis emitted, and the USDG is credited toclaimable[receiver](summed intotalClaimable). No token leaves the vault during processing. - The receiver calls
claim(to)to transfer the wholeclaimablebalance (WithdrawClaimed;NothingToClaimwhen zero). Claimable USDG is excluded fromidleAssetsand therefore from NAV.
harvest and fillCoupon also process up to queueProcessLimit entries (constructor default 10, setQueueProcessLimit reverts QueueLimitTooHigh above 50). Governance may clear an open request that is at least REQUEST_CANCEL_GRACE = 7 days old with cancelRequestFor(id); the shares return to the request owner (WithdrawRequestCancelledFor) and an earlier call reverts RequestTooRecent(id). Withdrawals, queue processing and claims are not pausable; maxDeposit/maxMint return zero while paused.
Hold Period
minHold (default 1 day, setMinHold bounded to 1 second to 7 days, MinHoldUpdated) runs from lastDeposit[owner]. Mints stamp the current time.
A transfer carries the sender’s clock to the receiver as a weighted average of the receiver’s existing clock and the incoming one, weighted by the receiver’s whole position (free plus escrowed shares) against the incoming amount and rounded toward the receiver’s existing clock, so moving shares to a fresh address does not shorten the wait and a dust transfer cannot move an established clock in either direction.
Before averaging, the receiver’s previous stamp is clamped to no earlier than now − minHold, which bounds how far an unsolicited transfer can push out a direct exit: a gift of weight w against a position B delays the receiver’s direct withdrawal by at most minHold × w / (B + w).
For a 100,000 USDG position and a one-day hold, a one-hour delay costs the sender about 4,350 USDG worth of shares, which then belong to the receiver. Queued requests are unaffected (the hold is checked at request time only). Escrow moves into and out of the vault for queue requests do not change the clock.
Shared pools of dNOTE such as the Desk gauge are registered by governance with setPoolHolder(account, true) (PoolHolderSet): a registered pool is never stamped and is not a stamp source, so shares coming out of it keep the receiver’s own clock (a receiver that never held dNOTE is stamped with the current time), and staking fresh shares into an aged pool cannot shorten the hold.
Unwound Series
A series in status Unwound counts as settled: harvest(seriesId) calls the core’s withdrawUnwound for the Desk position and releases the principal.
If the core exit is not available, governance may call writeOffSeries(seriesId, legsTo) (allowed for any settled status: Autocalled, Matured, Cancelled or Unwound, NotTerminal otherwise): the position is settled where the core still allows it, the Desk’s COUPON legs are moved to legsTo when non-zero, the deployed principal for the series is released and SeriesWrittenOff(seriesId, principal, legsTo, units) is emitted, so the series no longer blocks exits.
Anything the core still books to the Desk for that series remains collectable through a later harvest.
Redemption That Cannot Pay
When a series has Autocalled or Matured and the core redemption reverts (for example the stock token refuses the transfer), harvest does not fail: it emits RedemptionFailed(seriesId, units, reason), releases the principal at zero recovery so the series leaves the active set and exits reopen, and keeps the COUPON legs in the Desk. A later harvest retries the redemption and books whatever it recovers; governance can also move the legs out with writeOffSeries(seriesId, legsTo).
Risk to Desk Depositors
The Desk is a note buyer. It earns coupons while the barrier holds and receives stock at s0 if the barrier breaks at maturity, at which point NAV reflects the feed value of that stock. Depositors bear market risk on the underlyings the Desk has filled, concentration risk bounded by the caps, and liquidity risk when most assets are deployed. Desk shares are not NOTE and carry no governance rights.
The Desk never buys SHIELD and never fills a series above its notionalCap.
Governance Surface
| Function | Effect |
|---|---|
setCaps(Caps) | Per-series, per-underlying, total caps |
setMaxFeedStaleness(seconds) | Valuation staleness bound for held stock |
setQueueProcessLimit(n) | Entries processed after harvest and fillCoupon (at most 50) |
setMinHold(seconds) | Hold period for direct and queued exits, 1 second to 7 days |
cancelRequestFor(id) | Returns the escrowed shares of an open request older than 7 days to its owner |
writeOffSeries(seriesId, legsTo) | Closes the Desk position in a settled series (Autocalled, Matured, Cancelled or Unwound) and moves its COUPON legs to legsTo |
setPoolHolder(account, pooled) | Registers a shared pool of dNOTE (a gauge) that is neither stamped nor a stamp source for the hold clock |
setStockWriteOff(token, bool) | Carries an unpriceable held stock at zero instead of its last good mark |
sweepShares(to) | Transfers dNOTE held by the vault beyond the escrow of open requests (SharesSwept) |
setFiller(filler) | Binds the single address allowed to call fillCoupon (FillerSet); zero disables fills |
fillCoupon(seriesId, amount, minCouponBps) | Filler only: subscribe to COUPON within caps as a reserved deposit with a coupon hurdle |
liquidateToTreasury(token, amount) | Sell held stock to the Treasury at feed value |
sweepToken(token, to, amount) | Recover tokens that are not part of NAV |
setGaugeController(controller) | Sets the single address allowed to steer per-underlying caps; emits GaugeControllerUpdated |
setPerUnderlyingCapFromGauge(underlying, bps) | Gauge controller only, once per week per underlying via GaugeController.syncCaps; bounds 100 to 4,000 bps; emits PerUnderlyingCapSet |
Gauge-steered Caps
fact The governed Caps.perUnderlyingCapBps is a ceiling. Each week GaugeController.syncCaps writes a per-underlying value derived from the series-gauge vote, and effectivePerUnderlyingCapBps(underlying) returns the lower of the governed cap and the gauge value (min). fillCoupon uses the effective value; an underlying with no gauge, or one that has not been synced yet, keeps the governed cap. The mechanics and a worked example are under Gauges.
The module is a UUPS proxy whose implementation can only be replaced by the 7-day upgrade governance; see Upgradeability and recovery.
Exit Guard and Marked NAV
fact Share price is computed from a marked NAV: deployed principal is valued at the Treasury’s live mark for each series (par for series not yet live) plus coupons accrued but not yet harvested, so depositing just before a harvest or withdrawing just before a mark-down no longer transfers value between holders. Direct withdraw/redeem and requestWithdraw require the shares to have been held for minHold (default 1 day, governance-settable up to 7 days) and are refused while a settled series is waiting to be harvested; queued requests are checked against the harvest gate again when they fill (the hold applies at request time only). Open requests reserve their shares, valued at the current NAV, against the idle USDG available to direct exits. Bonds read the Desk’s utilisation as of the previous block (laggedUtilisationBps).
Keep exploring, or revisit this topic from the navigation.