> 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/build-on-it/quick-start/accounts.md).

# Account Structure

The ksUSD vault uses two on-chain account types: a single `Vault` PDA and one `WithdrawalRequest` PDA per queued withdrawal.

{% hint style="warning" %}
v1 hedges on **Phoenix Perps** (Ellipsis Labs) with USDC margin via the **Ember** program; jitoSOL is held unlevered as the spot leg. The perp-venue account set below follows the Rise SDK (TraderHeader, PermissionAccount, GlobalConfig, PerpAssetMap) and is finalized with the integration.
{% endhint %}

> Source: [the vault account definition](https://github.com/kamwithak/keystone-contracts/blob/main/programs/keystone-finance/src/state/vault.rs)
>
> Anchor client name: `program.account.vault.fetch(vaultPda)` and `program.account.withdrawalRequest.fetch(requestPda)`.

***

## `Vault` (singleton)

PDA seed: `[b"vault"]` · authority for ksUSD mint, vault USDC ATA, vault jitoSOL ATA, Phoenix trader account, and the lend cToken ATA.

### Identity

| Field           | Type     | Meaning                                                                       |
| --------------- | -------- | ----------------------------------------------------------------------------- |
| `admin`         | `Pubkey` | Admin authority (transferable via two-step `transfer_admin` + `accept_admin`) |
| `pending_admin` | `Pubkey` | Pending admin during a transfer; `Pubkey::default()` = none pending           |
| `bump`          | `u8`     | Vault PDA bump                                                                |
| `usdc_mint`     | `Pubkey` | USDC mint (depositor token)                                                   |
| `jitosol_mint`  | `Pubkey` | jitoSOL mint (LST spot leg — held unlevered for staking)                      |
| `ksusd_mint`    | `Pubkey` | ksUSD share mint — vault PDA is mint + freeze authority                       |

### Phoenix integration

| Field                   | Type     | Meaning                                                                                                                                         |
| ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `phoenix_trader`        | `Pubkey` | The vault's Phoenix cross-margin trader PDA (authority = vault). Registered and capability-enabled off-chain via the Rise SDK, then pinned here |
| `phoenix_market`        | `Pubkey` | SOL-PERP orderbook (market) account                                                                                                             |
| `phoenix_global_config` | `Pubkey` | Phoenix GlobalConfig — constrained on every Phoenix CPI                                                                                         |
| `canonical_mint`        | `Pubkey` | Ember canonical collateral mint. USDC wraps into this 1:1 to become Phoenix margin                                                              |
| `phoenix_canonical_ata` | `Pubkey` | The vault's canonical-token ATA, i.e. its Phoenix margin token account                                                                          |
| `phoenix_perp_asset_id` | `u64`    | SOL-PERP asset id in the `PerpAssetMap` (0), used to read the signed position                                                                   |
| `phoenix_initialized`   | `bool`   | `true` after `enable_phoenix` succeeds                                                                                                          |

Trading capability itself is **not** a vault field. It lives on the Phoenix trader account as capability flags, granted by Ellipsis's `risk_authority`. A freshly registered trader can hold funds but can't post margin until that grant lands. | `phoenix_perp_asset_map` | `Pubkey` | Phoenix's perp asset map for `phoenix_market`, pinned at `enable_phoenix`. Every order instruction address-checks it | | `perp_base_lot_lamports` | `u64` | Size of one Phoenix base lot in lamports (SOL-PERP = 0.01 SOL = `10_000_000`). Converts lots ↔ SOL for hedge sizing and the fill-price bound | | `client_order_seq` | `u64` | Monotonic `client_order_id` for venue orders — unique by construction, unlike the wall-clock it replaced |

### Lending venues

| Field                        | Type     | Meaning                                                 |
| ---------------------------- | -------- | ------------------------------------------------------- |
| `kamino_lend_reserve`        | `Pubkey` | Pinned Kamino USDC reserve (set by `enable_lending`)    |
| `vault_lend_collateral_ata`  | `Pubkey` | Vault's cToken ATA for Kamino lending                   |
| `kamino_lending_initialized` | `bool`   | `true` after `enable_lending` succeeds                  |
| `usdc_lent_kamino`           | `u64`    | USDC base units deposited at Kamino from vault main ATA |

### Oracles (set by `set_oracles`)

| Field                 | Type     | Meaning                                                                                      |
| --------------------- | -------- | -------------------------------------------------------------------------------------------- |
| `sol_oracle`          | `Pubkey` | Pyth SOL/USD pull-oracle account                                                             |
| `jitosol_oracle`      | `Pubkey` | Pyth jitoSOL/USD pull-oracle account                                                         |
| `oracles_initialized` | `bool`   | `true` after `set_oracles` succeeds                                                          |
| `jito_stake_pool`     | `Pubkey` | Jito stake pool — source of the jitoSOL/SOL redemption rate the depeg guard measures against |

### NAV & share accounting (USDC base units, 6 decimals)

| Field                | Type  | Meaning                                                       |
| -------------------- | ----- | ------------------------------------------------------------- |
| `total_shares`       | `u64` | Total ksUSD outstanding (6 decimals)                          |
| `cached_nav_usdc`    | `u64` | Last computed NAV in USDC base units                          |
| `last_settle_ts`     | `i64` | Last `settle` crank timestamp                                 |
| `last_nav_attest_ts` | `i64` | Last `attest_nav` timestamp — anchors the per-hour change cap |

### Performance fee & reserve

| Field                    | Type  | Meaning                                                                                                                                                                                                                                                                                                                         |
| ------------------------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hwm_share_price_1e9`    | `u64` | High-water mark, measured against the **gross** share price (1e9 scale). Only moves up. Advances on its own at `hurdle_apr_bps`/yr — see [Fees](/start-here/fees.md)                                                                                                                                                            |
| `hurdle_apr_bps`         | `u16` | Benchmark the vault must beat before any performance fee applies. Not a fixed constant — `init-v1.ts` measures the live Kamino USDC supply APY at deploy and writes it (3.69% → 369 on the seven-day median as of 2 Sep 2026), and the keeper's `watchdog` warns when it drifts from the smoothed rate. `0` disables the hurdle |
| `last_fee_accrual_ts`    | `i64` | When `accrue_perf_fees` last ran. Drives the hurdle ratchet; `0` means "never initialised" and suppresses it                                                                                                                                                                                                                    |
| `peak_share_price_1e9`   | `u64` | Rolling peak share price (drawdown anchor). Reset only by `reset_peak`, never by fee collection                                                                                                                                                                                                                                 |
| `pending_perf_fees_usdc` | `u64` | Performance fees accrued but not yet collected. Crystallized by `accrue_perf_fees` on every price change, and **subtracted from `effective_nav_usdc`** so the share price is net of fees owed. Zeroed by `collect_fees`                                                                                                         |

### Position state

| Field                  | Type           | Meaning                                                                                                   |
| ---------------------- | -------------- | --------------------------------------------------------------------------------------------------------- |
| `position_mode`        | `PositionMode` | `Idle` / `Normal` / `WindDown`                                                                            |
| `position_base_amount` | `i64`          | Signed Phoenix position in **base lots** (negative = short SOL-PERP). SOL-PERP uses `baseLotDecimals = 2` |
| `usdc_margin_posted`   | `u64`          | USDC base units currently posted as Phoenix margin via Ember                                              |
| `last_mode_change_ts`  | `i64`          | Anchors the `min_dwell_seconds` guard                                                                     |
| `last_rebalance_ts`    | `i64`          | When `rebalance_hedge` last corrected delta                                                               |

### Withdrawal queue cursors

| Field                     | Type  | Meaning                                                     |
| ------------------------- | ----- | ----------------------------------------------------------- |
| `queue_next_id`           | `u64` | Next request\_id to assign                                  |
| `queue_processed_through` | `u64` | Smallest request\_id not yet processed (strict FIFO cursor) |
| `queue_pending_usdc`      | `u64` | Total USDC owed across pending requests                     |

### Configurable parameters

| Field                             | Type  | Default                                                                                                                                                           |
| --------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `liquidity_buffer_bps`            | `u16` | 1\_000 (10%)                                                                                                                                                      |
| `funding_threshold_normal_bps`    | `i32` | 0 (safety floor — the keeper applies the tighter dynamic threshold above it)                                                                                      |
| `perf_fee_bps`                    | `u16` | 2\_000                                                                                                                                                            |
| `hurdle_apr_bps`                  | `u16` | measured live at init (Kamino USDC supply APY). `400` in `InitializeParams::default()` is a tests/localnet fallback only — mainnet init aborts rather than use it |
| `min_margin_bps`                  | `u16` | 900 (bounds leverage at \~10x)                                                                                                                                    |
| `min_dwell_seconds`               | `u32` | 43\_200 (12 h)                                                                                                                                                    |
| `max_swap_slippage_bps`           | `u16` | 50                                                                                                                                                                |
| `emergency_close_dd_bps`          | `u16` | 500                                                                                                                                                               |
| `deposit_cap_usdc`                | `u64` | 500\_000\_000\_000 ($500k launch cap). `u64::MAX` = uncapped; `0` = paused.                                                                                       |
| `min_request_shares`              | `u64` | 1\_000\_000 (1 ksUSD)                                                                                                                                             |
| `max_pending_queue_usdc`          | `u64` | `u64::MAX` (disabled)                                                                                                                                             |
| `max_nav_change_bps_per_hour`     | `u16` | 1\_000 (±10%/hr on keeper attestations; deposits/withdrawals write `cached_nav_usdc` directly and are not capped by it)                                           |
| `funding_max_staleness_seconds`   | `u32` | 21\_600 (6 h)                                                                                                                                                     |
| `consecutive_dd_settles_required` | `u8`  | 2                                                                                                                                                                 |
| `consecutive_dd_settles_observed` | `u8`  | Running counter                                                                                                                                                   |
| `lst_depeg_bps`                   | `u16` | 500 (5%)                                                                                                                                                          |
| `max_position_base_lots`          | `u64` | Absolute ceiling on position size, in base lots. Breach reverts with `PerpShortExceedsSizeCap`                                                                    |
| `instant_buffer_bps`              | `u16` | Raw-USDC floor reserved for `withdraw_instant`, as bps of NAV                                                                                                     |
| `delta_band_bps`                  | `u16` | Delta drift, as bps of NAV, that must accumulate before `rebalance_hedge` will act. `0` disables rebalancing                                                      |
| `max_rebalance_clip_lots`         | `u64` | Per-call ceiling on a rebalance order, in base lots. `0` = unclipped — bounds market impact, not price                                                            |
| `hard_margin_floor_bps`           | `u16` | Margin health below which **anyone** may call `reduce_position` to `Margin`. `0` leaves the margin defence entirely dependent on the keeper being alive           |
| `mint_window_seconds`             | `u64` | Length of the deposit rate-limit window. `0` disables the flow cap                                                                                                |
| `mint_limit_per_window_usdc`      | `u64` | USDC that may be minted within one window — a FLOW cap, distinct from the `deposit_cap_usdc` stock cap                                                            |
| `mint_window_start_ts`            | `i64` | Start of the current mint window                                                                                                                                  |
| `minted_in_window_usdc`           | `u64` | USDC minted so far in the current window                                                                                                                          |

### Risk flags & keeper allowlist

| Field               | Type     | Meaning                                                                                                                                                                                                                    |
| ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `paused`            | `bool`   | Halts deposits + new positions; instant withdraw stays open                                                                                                                                                                |
| `authorized_keeper` | `Pubkey` | Gates `settle` / strategy opens / closes / lending cranks. **Exact match required** — `Pubkey::default()` (the post-init state) blocks every keeper-gated ix until admin runs `update_params { authorized_keeper: <key> }` |

### Smoothed funding signal

| Field                      | Type  | Meaning                                                                |
| -------------------------- | ----- | ---------------------------------------------------------------------- |
| `funding_apr_smoothed_bps` | `i32` | EMA of SOL-PERP funding, updated on every `settle`                     |
| `funding_smooth_last_ts`   | `i64` | Last EMA update timestamp. `0` sentinel = first settle hasn't run yet. |

### Reserved

| Field       | Type       | Meaning               |
| ----------- | ---------- | --------------------- |
| `_reserved` | `[u8; 10]` | Forward compatibility |

***

## `PositionMode` (enum)

| Variant    | Value | Meaning                                                                                 |
| ---------- | ----- | --------------------------------------------------------------------------------------- |
| `Idle`     | 0     | **Parked** (v1 name). No active perp position; capital in vault USDC (lent on Kamino).  |
| `Normal`   | 1     | Long jitoSOL spot (unlevered) + short SOL-PERP on Phoenix at 1× notional, USDC margin.  |
| `WindDown` | 3     | Terminal. New positions / deposits blocked; users claim pro-rata via `claim_wind_down`. |

***

## `WithdrawalRequest`

PDA seed: `[b"withdrawal_request", vault.key(), request_id_le_bytes]` · created by `request_withdrawal`, closed by `process_withdrawal` (rent refunded to the original requester).

| Field               | Type     | Meaning                                                          |
| ------------------- | -------- | ---------------------------------------------------------------- |
| `vault`             | `Pubkey` | Vault this request belongs to                                    |
| `user`              | `Pubkey` | Original requester (rent refund recipient)                       |
| `user_usdc_account` | `Pubkey` | USDC destination — must match at process time                    |
| `usdc_owed`         | `u64`    | Upper bound USDC owed at burn time (FIFO at request)             |
| `shares_burned`     | `u64`    | Shares burned at request — used for the price-at-process haircut |
| `request_id`        | `u64`    | Sequential ID assigned at enqueue                                |
| `requested_ts`      | `i64`    | Timestamp of request                                             |
| `bump`              | `u8`     | PDA bump                                                         |

`process_withdrawal` price-at-process haircut:

* Re-prices `usdc_owed` against the current share price
* Takes the **lower** of locked vs. live
* Depositors can never extract more than their pro-rata share if NAV has fallen since they queued

***

## Derivation helpers (TypeScript)

```ts
const [vaultPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("vault")],
  PROGRAM_ID
);

const [requestPda] = PublicKey.findProgramAddressSync(
  [
    Buffer.from("withdrawal_request"),
    vaultPda.toBuffer(),
    new BN(requestId).toArrayLike(Buffer, "le", 8),
  ],
  PROGRAM_ID
);

// Vault ATAs (PDA-owned)
const vaultUsdcAta    = getAssociatedTokenAddressSync(vault.usdcMint,    vaultPda, true);
const vaultJitosolAta = getAssociatedTokenAddressSync(vault.jitosolMint, vaultPda, true);
const reserveUsdcAta  = vault.reserveAta;             // pinned at init
```

***

## Related

* [Instructions](/build-on-it/quick-start/instructions.md) · [Events](/build-on-it/quick-start/events.md) · [Errors](/build-on-it/quick-start/errors.md)
* [Quick start](/build-on-it/quick-start.md) — getting a Program instance set up
