Account Structure
The ksUSD vault uses two on-chain account types: a single Vault PDA and one WithdrawalRequest PDA per queued withdrawal.
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.
Source: the vault account definition
Anchor client name:
program.account.vault.fetch(vaultPda)andprogram.account.withdrawalRequest.fetch(requestPda).
Vault (singleton)
PDA seed: [b"vault"] · authority for ksUSD mint, vault USDC ATA, vault jitoSOL ATA, reserve USDC ATA, Phoenix trader account, and lend / reserve cToken ATAs.
Identity
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
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
kamino_lend_reserve
Pubkey
Pinned Kamino USDC reserve (set by enable_lending)
vault_lend_collateral_ata
Pubkey
Vault's cToken ATA for Kamino lending
reserve_lend_collateral_ata
Pubkey
Reserve fund's cToken ATA — distinct so reserve yield tracks separately
kamino_lending_initialized
bool
true after enable_lending succeeds
usdc_lent_kamino
u64
USDC base units deposited at Kamino from vault main ATA
reserve_lent_kamino
u64
USDC base units deposited at Kamino from reserve ATA
reserve_ata
Pubkey
Reserve fund USDC ATA (vault-PDA-owned). Pinned at init.
Oracles (set by set_oracles)
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)
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
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
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
reserve_fund_usdc
u64
Reserve fund balance in USDC base units (reserve_ata balance + lent portion)
Position state
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
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
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)
reserve_skim_bps
u16
500
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
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
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
_reserved
[u8; 10]
Forward compatibility
PositionMode (enum)
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).
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_owedagainst the current share priceTakes 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)
Related
Instructions · Events · Errors
Quick start — getting a Program instance set up
Last updated