> For the complete documentation index, see [llms.txt](https://docs.keystonefi.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.keystonefi.xyz/start-here/withdraw.md).

# 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

```rust
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:

```
USDC you get = ksUSD you burn × current share price

usdc_owed    = shares × share_price_1e9 / 1e9      // 1e9 undoes the price scale
```

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

```rust
request_withdrawal(shares: u64)
```

* 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)

```rust
process_withdrawal()
```

**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

```ts
const tx = await program.methods
  .withdrawInstant(new BN("25_000_000"))    // 25 ksUSD
  .accountsStrict({
    vault: vaultPda,
    vaultUsdcAccount: vaultUsdcAta,
    ksusdMint: vault.ksusdMint,
    userKsusdAccount: userKsusdAta,
    userUsdcAccount: userUsdcAta,
    user: user.publicKey,
    tokenProgram: TOKEN_PROGRAM_ID,
  })
  .rpc();
```

## TypeScript — queued path

```ts
const nextId = vault.queueNextId.toString();
const [requestPda] = PublicKey.findProgramAddressSync(
  [
    Buffer.from("withdrawal_request"),
    vaultPda.toBuffer(),
    new BN(nextId).toArrayLike(Buffer, "le", 8),
  ],
  PROGRAM_ID
);

await program.methods
  .requestWithdrawal(new BN("100_000_000_000"))   // 100,000 ksUSD
  .accountsStrict({
    vault: vaultPda,
    withdrawalRequest: requestPda,
    ksusdMint: vault.ksusdMint,
    userKsusdAccount: userKsusdAta,
    userUsdcAccount: userUsdcAta,
    user: user.publicKey,
    tokenProgram: TOKEN_PROGRAM_ID,
    systemProgram: SystemProgram.programId,
  })
  .rpc();

// Later, after the keeper has unwound enough position:
await program.methods
  .processWithdrawal()
  .accountsStrict({
    vault: vaultPda,
    withdrawalRequest: requestPda,
    vaultUsdcAccount: vaultUsdcAta,
    userUsdcAccount: userUsdcAta,
    recipientForRent: user.publicKey,
    cranker: anyWallet.publicKey,
    tokenProgram: TOKEN_PROGRAM_ID,
  })
  .rpc();
```

***

## 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:

```rust
claim_wind_down(shares: u64)
```

* 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`

***

## Related

* [Deposit](/start-here/deposit.md) · [Check position & NAV](/start-here/check-position.md)
* [Fees](/start-here/fees.md) — no withdrawal fees on either path
* [Errors](/build-on-it/quick-start/errors.md)
