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

Withdraw

You hand back ksUSD and get USDC. There are two normal ways to do that, plus a third that only applies if the vault is ever shut down.

Path
When to use
Speed

Instant (withdraw_instant)

Vault has enough idle USDC

One transaction

Queued (request_withdrawal + process_withdrawal)

Withdrawal larger than the liquidity buffer

Burned immediately at locked price; USDC paid out (FIFO, strict) after the keeper unwinds enough position

Wind-down (claim_wind_down)

Active only when position_mode == WindDown

Pro-rata redemption against the vault's idle USDC; no queue, no FIFO

Instant is the path almost everyone uses. The vault keeps a liquidity buffer of idle USDC on hand, 10% of what it holds by default, which is enough to cover about 99% of real withdrawals on the spot.


Instant withdrawal

withdraw_instant(shares: u64)
  • Burns shares of ksUSD

  • Pays out USDC from the vault's idle balance at the current share price

Account
Mutability
Purpose

vault

mut

Vault PDA

vault_usdc_account

mut

Vault USDC ATA — source of payout

ksusd_mint

mut

ksUSD share mint

user_ksusd_account

mut

User's ksUSD ATA (must hold ≥ shares)

user_usdc_account

mut

User's USDC destination

user

signer

Withdrawer

phoenix_trader

Phoenix trader account — read to true up the venue's contribution to NAV before pricing. Pinned to vault.phoenix_trader

sol_oracle

Pyth SOL/USD — values the perp leg for that same correction. Pinned to vault.sol_oracle

token_program

SPL token program

Math — the deposit division, run backwards:

Burn 97.09 ksUSD while a share is worth $1.05 and you get $101.94. There is no withdrawal fee and no exit penalty, so what comes back is exactly your slice of NAV at the moment the transaction lands. Like the mint, the division truncates in the vault's favour by under one base unit.

If vault_usdc_account.amount < usdc_owed, this reverts with InsufficientLiquidityBuffer. Use the queued path instead.


Queued withdrawal

This is what happens when the vault doesn't have enough idle USDC to pay you on the spot.

Your shares burn right away at the current share price, so the dollar amount you're owed is locked in at that moment and can't drift while you wait. The request is recorded in its own account. Once the keeper has unwound enough of the position to refill the buffer, anyone can trigger the payout, including you.

Step 1 — request

  • Burns shares

  • Creates a request PDA at [b"withdrawal_request", vault, queue_next_id]

  • Locks usdc_owed = shares × share_price_1e9 / 1e9 on the request record — the same math as the instant path, priced at the moment you queue

Account
Mutability
Purpose

vault

mut

Vault PDA

withdrawal_request

init

New per-request PDA (payer = user)

ksusd_mint

mut

ksUSD share mint

user_ksusd_account

mut

User's ksUSD ATA (burned from)

user_usdc_account

User's USDC destination (recorded on the request)

user

signer, mut

Requester (pays rent)

token_program

SPL token program

system_program

Required — the request PDA is created by this instruction

Step 2 — process (permissionless crank)

In practice you do nothing here — the keeper calls this for you. It checks the queue every tick (~5 minutes) and pays out whatever the vault can cover, oldest first, up to 8 requests per tick. Refilling the buffer is part of the same loop: the keeper folds what the queue owes into its idle-USDC target and pulls the shortfall back out of Kamino, and only if that is still not enough does it de-lever the position to raise the rest. If the head of the queue has been waiting more than an hour, the keeper pages an operator.

"Permissionless" is the guarantee behind that, not the normal path: the payout does not depend on the keeper being alive. If it stops, anyone can crank the queue — you, another depositor, a bot — and the money still moves. Nobody can be paid ahead of you, because the ordering is enforced on-chain rather than by whoever sends the transaction.

The mechanics:

  • Anyone may call it, including the person who queued the request.

  • Strictly first-in, first-out: the next request processed must have request_id == vault.queue_processed_through. Anything else reverts with WithdrawalNotNextInQueue, so a cranker cannot skip the head of the queue to reach their own request.

  • The payout is capped at the live share price. If the vault's value has fallen since you queued, you get your fair share of it and no more.

  • The request account closes on payout, refunding its rent to whoever opened it.

  • It is blocked while the vault is paused. Pending requests wait — the shares are already burned and the amount owed is locked — until an admin unpauses.

Account
Mutability
Purpose

vault

mut

Vault PDA

withdrawal_request

mut

The queued request (will be closed)

vault_usdc_account

mut

Vault USDC ATA — source of payout

user_usdc_account

mut

Recorded destination from the request

recipient_for_rent

mut

Original requester (rent refund destination)

cranker

signer

Anyone can crank

token_program


TypeScript — instant path

TypeScript — queued path


Wind-down path

This only applies if the vault is being shut down for good. It is not a normal operating mode.

Once the admin calls init_wind_down, the vault stops taking deposits and stops opening positions. Everyone then redeems with:

  • Pays out your share of the vault's idle USDC at the live effective_nav share price.

  • No queue, no ordering, and no rent refund.

  • Reverts unless position_mode == WindDown.


Events

  • WithdrawEvent — emitted by withdraw_instant

  • WithdrawalRequested — emitted by request_withdrawal

  • WithdrawalProcessed — emitted by process_withdrawal

  • WindDownClaimed — emitted by claim_wind_down


Last updated