> 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/keystone-finance/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

```
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                              |
| `token_program`      | —          | SPL token program                       |

Math:

```
usdc_owed = shares × share_price_1e9 / 1e9
```

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

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

| 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` / `system_program` | —           | —                                                 |

### Step 2 — process (permissionless crank)

```
process_withdrawal()
```

* Anyone can call this, including the person who queued the request.
* The queue is strictly first-in, first-out. The next request processed must be `queue_processed_through + 1`; anything else reverts with `WithdrawalNotNextInQueue`.
* The payout is capped at the live share price. So 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 deposit to whoever opened it.

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

```
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](/keystone-finance/deposit.md) · [Check position & NAV](/keystone-finance/check-position.md)
* [Fees](/reference/fees.md) — no withdrawal fees on either path
* [Errors](/for-developers/errors.md)
