For the complete documentation index, see llms.txt. This page is also available as Markdown.

NAV & Share Pricing

ksUSD prices itself the way sUSDe does. Your ksUSD balance never changes. What changes is the price of each token, which drifts up as carry accrues. So you don't watch for new tokens arriving; you watch the price.

NAV, below, is net asset value: the total dollar value of everything the vault holds.


Share price

There are two NAV figures, and the difference between them is the accrued performance fee.

gross_effective_nav_usdc = cached_nav_usdc − queue_pending_usdc − reserve_fund_usdc
effective_nav_usdc       = gross_effective_nav_usdc − pending_perf_fees_usdc

share_price_1e9       = effective_nav_usdc × 1e9 / total_shares         (when total_shares > 0)
                      = 1_000_000_000 (= $1.00)                         (when total_shares = 0)
gross_share_price_1e9 = gross_effective_nav_usdc × 1e9 / total_shares

The net price (share_price_1e9) is what deposits and withdrawals use, so it's the one that matters to you. The gross price is what the high-water mark is measured against. Net is always ≤ gross.

  • cached_nav_usdc — USDC base units (6 decimals)

  • total_shares — ksUSD base units (6 decimals)

  • The 1e9 scaling keeps enough precision without needing 128-bit math beyond the intermediate step

  • Rust helpers: Vault::share_price_1e9() and Vault::gross_share_price_1e9()

Why three things come out of NAV

Subtraction
Reason

queue_pending_usdc

These shares are already burned and the vault owes real USDC against them. Leaving that money in the count would let new depositors mark the price up against a liability that's already locked.

reserve_fund_usdc

The reserve exists to absorb losses, not to pay holders. Keeping it out means the share price shows only what actually belongs to holders.

pending_perf_fees_usdc

Performance fees are set aside as they're earned, not at collection time. Carving them out here means the share price is already net of fees owed, so collecting them later doesn't step the price down, and a holder who exits first can't dodge a fee they already accrued.


What cached_nav_usdc includes

The vault recalculates cached_nav_usdc any time its state changes. It adds up:

Between updates, funding and profit still sitting open on the perp venue are not counted in cached_nav_usdc. Anyone can run settle to refresh the margin figures from the venue. For open profit and loss the keeper can also run attest_nav(new_nav_usdc), which is capped per hour by max_nav_change_bps_per_hour.


Oracle pricing

jitoSOL and SOL/USD prices come from Pyth. Every single read is checked twice before the vault will use it:

Check
Threshold
Effect

Staleness

< 5 minutes since last Pyth update

Stale → revert with StaleOracle

Confidence

conf / price < 2%

Out of band → revert with OracleConfidenceExceeded

The thresholds are tight enough to catch an oracle outage or a manipulated feed, but loose enough not to trip on ordinary market activity.


Drawdown peak

vault.peak_share_price_1e9 holds the highest share price any settle has ever seen. The drawdown guard measures how far the price has fallen from that peak:

A single bad reading can't force an emergency exit. The condition has to hold across consecutive_dd_settles_required settles in a row, which defaults to 2, and firing early reverts with DrawdownTriggerLatent.

Only the admin can reset the peak, using reset_peak; collecting fees doesn't touch it. And note that the drawdown watches the live share price, not the post-fee high-water mark used for fees.


Worked example

Initial state:

  • cached_nav_usdc = 1_000_000_000_000 ($1M)

  • total_shares = 1_000_000_000_000 (1M ksUSD, 6 decimals)

  • share_price_1e9 = 1_000_000_000 ($1.0000)

A keeper opens a normal-basis position, and over time funding earns $30,000 net. After the next settle refreshes NAV:

  • cached_nav_usdc = 1_030_000_000_000 ($1.03M)

  • total_shares = 1_000_000_000_000 (unchanged)

  • share_price_1e9 = 1_030_000_000 ($1.0300)

A new depositor sends 100 USDC:

  • Shares minted: 100_000_000 × 1e9 / 1_030_000_000 = 97_087_378.6…, truncated to 97,087,378 ksUSD base units

  • Vault state after: total_shares ≈ 1_000_097_087_378, cached_nav_usdc = 1_030_100_000_000

They end up with about 97.087 ksUSD, worth the same ~100 USDC they put in. Fewer tokens than the earlier depositor got, each one worth more.

This example ignores fees to keep the arithmetic clear. In the real path, deposit calls accrue_perf_fees before it prices the mint, so the share price is already net of fees owed and the new depositor is never charged on gains that happened before they arrived.


Last updated