cascade-vault on-chain reference.
Anchor program at FxausMaH3TWJkUeVPpLbYY55rVMztSKr6KtZ1jRFdrRw (devnet). Eight instructions covering vault lifecycle, deposit / withdraw, and cycle execution. Every state transition is enforced on-chain.
initialize_vault
Admin-only. Creates the Vault PDA, lp_mint, and asset_vault token account in one transaction.
pub fn initialize_vault(
ctx: Context<InitializeVault>,
config: VaultConfig,
) -> Result<()>;
// VaultConfig fields:
// monitor_authority Pubkey allowed to call deploy_lp / exit_lp / update_cluster_signal
// stop_loss_bps u16 (default 500 = 5%)
// recovery_bps_min u16 (default 150 = 1.5%)
// timeout_slots u64 (default 4500 = ~30 min)
// max_deploy_pct u16 (default 2000 = 20% of vault per deploy)
// performance_fee_bps u16 (default 2000 = 20% of cycle profit)
// withdraw_fee_bps u16 (default 10 = 0.1%)deposit
User-signed. Transfers asset into the vault, mints LP tokens pro-rata to NAV. Valid only when state is Idle.
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()>;
// LP tokens minted:
// first deposit: amount (1:1 bootstrap)
// subsequent deposits: amount * lp_supply / total_assetswithdraw
User-signed. Burns LP tokens, returns asset pro-rata minus withdraw fee. Valid only when state is Idle.
pub fn withdraw(ctx: Context<Withdraw>, lp_amount: u64) -> Result<()>;
// Asset returned to depositor:
// asset_to_return = lp_amount * total_assets / lp_supply
// withdraw_fee = asset_to_return * withdraw_fee_bps / 10000
// net_to_depositor = asset_to_return - withdraw_fee
// withdraw_fee routes to fee_treasury.update_cluster_signal
Monitor-only. Pushes a freshly computed cluster signal into vault state. The signal is checked again at deploy time.
pub fn update_cluster_signal(
ctx: Context<UpdateCluster>,
signal: ClusterSignal,
) -> Result<()>;
// Requires:
// ctx.accounts.authority == vault.monitor_authority
// signal.computed_at_slot >= current_slot - 5 (freshness)
// signal.confidence >= 60 (basic floor)deploy_lp
Monitor-only. Transitions Idle → Deployed. In MVP this is a pure state transition; venue CPI (Raydium CLMM / Orca / Meteora) is stubbed pending follow-up.
pub fn deploy_lp(
ctx: Context<DeployLp>,
deploy_amount: u64,
deploy_price: u64,
range_low: u64,
range_high: u64,
venue: u8,
) -> Result<()>;
// Requires:
// state == Idle
// authority == vault.monitor_authority
// deploy_amount <= total_assets * max_deploy_pct / 10000
// cluster.confidence >= 80
// deploy_price in [cluster.trigger_price_low, cluster.trigger_price_high]exit_lp
Monitor-only. Transitions Deployed → Idle on recovery. Computes P&L, accrues performance fee.
pub fn exit_lp(ctx: Context<ExitLp>, exit_price: u64) -> Result<()>;
// Requires:
// state == Deployed
// authority == vault.monitor_authority
// exit_price >= deploy_price * (1 + recovery_bps_min / 10000)stop_loss_crank
Permissionless. Anyone can call when spot is 5% below deploy price.
pub fn stop_loss_crank(
ctx: Context<StopLossCrank>,
oracle_price: u64,
) -> Result<()>;
// Requires:
// state == Deployed
// oracle_price <= deploy_price * (1 - stop_loss_bps / 10000)
// MVP: oracle_price supplied by cranker. Production: read Pyth on-chain.timeout_crank
Permissionless. Anyone can call after the configured slot count.
pub fn timeout_crank(
ctx: Context<TimeoutCrank>,
current_price: u64,
) -> Result<()>;
// Requires:
// state == Deployed
// clock.slot - deployed_at_slot >= timeout_slotsEvents
Every state transition emits an event for off-chain indexing:
VaultInitializedDeposited/WithdrawnClusterSignalUpdatedDeployed/PositionClosed(withreason: Recovery / StopLoss / Timeout)