peard
The market side

Quote modes and coverage

What the reward vault physically holds, and why that one answer decides everything else.


QuoteMode is chosen once, at create_market, and it decides exactly one question: what does the reward vault physically hold? Everything else follows from that answer.

QuoteMode::Denominated  // the vault holds dollars
QuoteMode::Native       // the vault holds the pairable's own asset
DenominatedNative
Vault holdsGlobal.usd_mintPairable.asset_mint
A unit isa dollar amount at the printone whole quote token
Price read on accrualyes, live_priceno
Price read on claimyes, live_priceno
coverage()floatsreturns S_ONE unconditionally
Stale or frozen pairableblocks accrual, claims, fulfilmentblocks nothing but the chart
Fulfilment valveavailablerefused, FulfillmentNeedsDenominated
Inbox and convert_inboxnot applicablethe fallback path

What decides which

create_market decides, and it refuses rather than warns.

QuoteMode::Native => {
    require!(pairable.grade == Grade::Hard, Err::NativeNeedsHardGrade);
    require!(pairable.asset_mint == quote_mint, Err::WrongAssetMint);
}
QuoteMode::Denominated => {
    require!(global.usd_mint == quote_mint, Err::WrongUsdMint);
}

Two separate checks on the Native side, and both earn their place. The grade check keeps the mode honest. The mint check keeps it pointed at the address the registry actually vetted rather than at a lookalike trading under the same ticker, which is the failure the registry spends several paragraphs on: there are a dozen mints called SPYx and one of them has real money behind it.

The Denominated side pins the dollar globally rather than per market. Global.usd_mint is EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v on mainnet.

On devnet it is Frgfj3XfvXeCjZb82NvbFxfGN9tf7cQjkes31ar3p1nA, symbol dUSD, a stand-in this project minted because devnet has no USDC. Anything denominated against it on devnet is denominated against a token whose mint authority we hold, and the app enforces saying so: standIn() in app/src/lib/provenance.ts flags every such figure.

Coverage in Denominated mode

In Denominated, units are computed at accrual and settled at claim, at two different prices. The gap between them is the entire exposure, and coverage is the number that bounds it.

pub fn coverage(&self, price_twap: u64) -> u128 {
    if self.quote_mode == QuoteMode::Native { return S_ONE; }
    let owed = self.owed_usdc(price_twap) as u128;
    if owed == 0 { return S_ONE; }
    ((self.holder_usdc() as u128 * S_ONE) / owed).min(S_ONE)
}

Read the numerator carefully. It is holder_usdc(), not the vault balance and not available_usdc(). Three subtractions get you there:

vault_usdc
  - fulfillment_reserved_usdc   =  available_usdc()   // a promised bottle is not buffer
  - protocol_fees_usdc          =  holder_usdc()      // the cut never backed a claim

The second subtraction is the sharper of the two. The protocol's cut was split off before any units were credited, so counting it makes coverage read high and then step down the instant governance sweeps, which hands a first-mover advantage to whoever claims before the sweep.

The cap at S_ONE is deliberate. A vault holding more than it owes pays face value and banks the surplus, and that surplus is exactly what funds the holder who claims into a dear print later. Coverage sitting above 1.00 is the healthy state, not an accounting error.

Coverage in Native mode

coverage returns S_ONE, and the comment in state.rs says why that is not an optimisation but the definition of the mode: the vault holds the very thing it owes, so there is no drift to absorb and nothing to prorate.

A unit is one whole quote token, and the conversion is pure rescaling:

pub fn native_units_from_amount(&self, amount: u64) -> u128 {
    (amount as u128 * UNIT_ONE) / self.quote_one()   // quote_one = 10^quote_decimals
}

No price appears anywhere in that. Which is why deposit_fees, sweep_fees and claim all begin with the same three lines:

let price = match market.quote_mode {
    QuoteMode::Native => 0,
    QuoteMode::Denominated => live_price(&pairable, now)?,
};

live_price is the gate: not frozen, price non-zero, not stale. A Native market never calls it, so a frozen oracle stops that market's chart and nothing else.

Total coverage is a different number from cash coverage

total_coverage folds in a market's synced backing value. coverage does not, and holder_usdc still bounds every payout.

Pears in a warehouse do not pay dollars until somebody sells them, so folding them into the ratio that governs USDC claims would let a market read fully covered while holding no money, which is the same overstatement as counting the protocol's own accrued cut. Backing is for display and for deciding whether physical delivery is on the table.

No market has non-zero backing today. Global.backing_program is Pubkey::default() on every cluster, so no attestation can satisfy the owner pin. See Backing by real things.

The inbox, and why it is an offer rather than a swap

A Native market's pool pays dollars and its vault holds the asset, so something has to bridge them. The obvious move is a Jupiter CPI, and it is the wrong one: the dollars land in a PDA's account, so the program would have to sign the route itself, which means a hard dependency on one venue and a large new attack surface inside a program whose entire claim is that it contains no venue.

So it does not swap. convert_inbox posts a standing offer: deliver the asset at the oracle price less a bounty, take the dollars. Filling it is somebody else's trade and their route is their problem, which puts Jupiter in relayer/src/convert.ts where it can be replaced without touching a deployed program.

Three properties make it safe:

  • Refusing costs nothing. A frozen, stale or missing price refuses the conversion, and the dollars simply wait in the inbox. The oracle appears here and still nowhere in a trading path.
  • Every remainder lands on the vault's side. Both divisions round up. At the size a fee claim arrives in that is dust; at one base unit it is the difference between an offer and a faucet.
  • It credits nobody. The vault gets heavier and sweep_fees notices, which keeps the protocol cut and the accumulator step on one path.

The bounty is one protocol-wide number capped at MAX_CONVERT_BOUNTY_BPS of 500bps, not a per-market setting. It only has to beat a swap fee, and a per-market knob is a per-market way to get it wrong.

convert_inbox is the Meteora-path fallback rather than the main path. Quoting the peard_amm curve in the asset itself means fees arrive already denominated in what a Native vault holds, so there is no inbox, no conversion, and no price read on any path.

Token-2022 is not optional

Thirty-five of the 44 hard entries are Token-2022, read off mainnet on 2026-08-25, so the program speaks token_interface rather than spl_token: InterfaceAccount throughout and transfer_checked on every leg. That costs nothing on the nine classic-SPL entries (wSOL, USDe, USDY, syrupUSDC, EURC, PRIME, ONYC, PST, eUSX) and is the only way to touch the other 35. Three consequences that were not obvious until the mints were actually read:

scaledUiAmountConfig is safe in value and wrong in vocabulary

This is the sharpest edge, because it breaks nothing and misleads everything.

It is the stock-split mechanism. The mint carries a multiplier, and when the issuer changes it, the raw amounts every account holds do not move while every displayed balance rescales. That is how xStocks pay dividends and process splits without minting a single token. SPYx sat at 1.005714 effective 2026-06-18, AAPLx at 1.003269 effective 2026-08-08.

native_units_from_amount counts raw base units over 10^decimals, so it calls one raw token one share. It is not. It is 1.0057 shares on SPYx, and the gap widens every dividend.

The fix is deliberately not to track the multiplier on chain. It would drift, it would need a mint read on every accrual, and it would put an issuer-controlled number inside a settlement path. Raw is the invariant: the vault holds raw and pays raw, so no value is ever miscounted, and because a holder's claim is denominated in raw the dividend accrues to them automatically with nothing to distribute.

"Shares" is a view, and the UI owes the multiplier when it prints one. Fourteen registry entries carry scaledUiAmountConfig today, across all three issuers: every Backpack Securities equity in the registry, three Backed mints (KO, PLTR, STRC), and Ondo's SLVon.