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.


The whole idea, in one line

share price = the dollars the vault holds for shareholders ÷ ksUSD in circulation

That dollar figure is the vault's NAV — net asset value. Divide it by the token count and you have what one token is worth.

Two examples, and they are the only two operations there are:

  • Deposit. Put in $100 while the price is $1.03 → you get 100 ÷ 1.03 = 97.09 ksUSD.

  • Redeem. Hand back 97.09 ksUSD while the price is $1.05 → you get 97.09 × 1.05 = $101.94.

Everything below is that one division, written precisely enough to implement.


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)

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

Reading the 1e9 scale

A Solana account stores integers, not decimals, so the price is carried as dollars × 1,000,000,000. To read one, move the decimal point nine places left:

Stored share_price_1e9

Dollars per ksUSD

1_000_000_000

$1.000000

1_030_000_000

$1.030000

1_002_500_000

$1.002500

The scale is 1e9 rather than the token's own 1e6 so that a day of carry — a few hundredths of a cent per token — doesn't round away to nothing. It's wide enough to price accurately without pushing the arithmetic past a u128 intermediate.

Why three things come out of NAV

The vault holds some money that isn't shareholders'. Each subtraction takes one of those out before the division, so the price reflects only what holders actually own.

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

A vault holding $1M against 1M ksUSD, which then earns $30,000 of carry. The left column is what a holder sees; the right is what's stored on-chain.

In dollars
On-chain (base units)

Start — NAV

$1,000,000

cached_nav_usdc = 1_000_000_000_000

Start — supply

1,000,000 ksUSD

total_shares = 1_000_000_000_000

Start — price

$1.0000

share_price_1e9 = 1_000_000_000

After $30k carry — NAV

$1,030,000

cached_nav_usdc = 1_030_000_000_000

After carry — supply

unchanged

total_shares = 1_000_000_000_000

After carry — price

$1.0300

share_price_1e9 = 1_030_000_000

Nobody's balance moved when the vault earned. The price did the work.

Now a new depositor sends 100 USDC. They get $100 ÷ $1.03 = 97.087… ksUSD:

Vault after: total_shares ≈ 1_000_097_087_378, cached_nav_usdc = 1_030_100_000_000, and the price is still $1.0300 — $100 of new money bought exactly $100 of shares. The truncation rounds against the depositor by under one base unit, a millionth of a token, which is what stops a mint from nudging the price down for everyone else.

So the new holder has ~97.087 ksUSD worth the same ~$100 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