Program reference
The Aqueduct Anchor program on Solana: its accounts and PDAs, all 23 instructions by who may call them, events, errors and constants, generated from the IDL.
On this page
Overview#
Aqueduct is a single Anchor 1.0 program. Its program ID is BweEZpNS6bhcwjxgEgScgZSWRSkKC9tqmC2PpfZdFbhm on devnet, deployed with the upgradeable loader; the upgrade authority is the admin key. The tables on this page are generated from the IDL in packages/sdk/src/idl/aqueduct.json when the docs are built, so they match the deployed interface.
| Count | |
|---|---|
| Instructions | 23 |
| Account types | 4 |
| Events | 9 |
| Custom errors | 19 |
Accounts#
All state lives in four account types. Amounts are in base units (6 decimals); rates are fixed point scaled by 1018.
Config#
admin- Type
pubkey- Notes
- Creates vaults, reserves and strategies, sets caps and fees, rotates keeper and treasury, pauses. Changed only through `propose_admin` + `accept_admin`.
keeper- Type
pubkey- Notes
- Can rebalance between idle and whitelisted strategies and update simulated APYs. Cannot withdraw.
treasury- Type
pubkey- Notes
- Receives management- and performance-fee shares.
paused- Type
bool- Notes
- Blocks deposits and allocations. Never blocks withdrawals, deallocations or fee collection.
bump- Type
u8
Vault#
config- Type
pubkey
asset_mint- Type
pubkey- Notes
- The deposited asset (USDC on mainnet, the program's test mint on devnet).
share_mint- Type
pubkey- Notes
- Vault share mint, PDA `["shares", vault]`, same decimals as the asset.
idle- Type
pubkey- Notes
- Uninvested assets, PDA `["idle", vault]`. Deposits land here and withdrawals pay from here.
tier- Type
u8- Notes
- 0 Bedrock (conservative), 1 Rapids (higher yield). Only used as a seed and label.
fee_bps- Type
u16- Notes
- Annual management fee on assets.
deposit_cap- Type
u64- Notes
- Max total assets in base units, 0 = unlimited.
last_fee_ts- Type
i64- Notes
- Last time the management fee was accrued (advanced only once a whole share is due).
pending_fee_shares- Type
u64- Notes
- Accrued management + performance fees, in shares, not yet minted to the treasury.
strategy_count- Type
u8
strategies- Type
[pubkey; 8]- Notes
- Strategy PDAs in index order; the first `strategy_count` are live. Strategies can be disabled but never removed.
lifetime_deposits- Type
u64- Notes
- Running totals for dashboards only; saturating, never used in share math.
lifetime_withdrawals- Type
u64
bump- Type
u8
share_bump- Type
u8
idle_bump- Type
u8
perf_fee_bps- Type
u16- Notes
- Share of yield above the high-water mark taken as a performance fee.
high_water_mark- Type
u128- Notes
- Highest share price (assets per share, RATE_SCALE) on which a performance fee has been charged. 0 = not yet set; the first accrual starts it at the current price.
Strategy#
vault- Type
pubkey
index- Type
u8
kind- Type
StrategyKind
chain- Type
u8- Notes
- 0 solana, 1 ethereum, 2 base, 3 arbitrum
risk_score- Type
u8- Notes
- 1 (safest) .. 10
max_alloc_bps- Type
u16- Notes
- Hard cap on this strategy's value as a share of vault total assets, checked on `allocate`.
enabled- Type
bool- Notes
- Disabled strategies take no new allocations; their positions can still be withdrawn.
reserve- Type
pubkey- Notes
- The simulated reserve holding this strategy's funds.
position_shares- Type
u64- Notes
- Reserve shares held by this strategy.
name- Type
[u8; 32]
venue- Type
[u8; 32]
bump- Type
u8
Reserve#
asset_mint- Type
pubkey
token_account- Type
pubkey- Notes
- Token account holding the reserve's assets, PDA `["reserve_vault", reserve]`, owned by the reserve.
name_seed- Type
[u8; 32]- Notes
- The `id` seed, zero-padded, and its length.
name_len- Type
u8
apy_bps- Type
u32- Notes
- Simple annual rate, set by the keeper to mirror the real venue (max 100%).
exchange_rate- Type
u128- Notes
- Assets per reserve share, scaled by RATE_SCALE.
total_shares- Type
u64
last_update- Type
i64
bump- Type
u8
token_bump- Type
u8
PDAs#
Every account is a program-derived address. The SDK exports one helper per seed (configPda, vaultPda, …).
- Config
- Seeds
"config"- Purpose
- One per program: admin, keeper, treasury, paused flag.
- Example (devnet)
- DyhV…kqmJ
- Vault
- Seeds
"vault", asset_mint, [tier]- Purpose
- One per asset and tier (Bedrock = 0, Rapids = 1).
- Example (devnet)
- AvFA…cggt
- Share mint
- Seeds
"shares", vault- Purpose
- SPL mint of the vault's shares; the vault PDA is its authority.
- Example (devnet)
- HE2n…yMzW
- Idle account
- Seeds
"idle", vault- Purpose
- Token account holding the vault's undeployed USDC.
- Example (devnet)
- 8q8Z…9eiL
- Strategy
- Seeds
"strategy", vault, [index]- Purpose
- One per whitelisted venue, index 0 to 7.
- Example (devnet)
- Ecc4…6Wyx
- Reserve
- Seeds
"reserve", asset_mint, id- Purpose
- Devnet simulated venue; `id` is the registry strategy id.
- Example (devnet)
- 6voY…Ujc1
- Reserve vault
- Seeds
"reserve_vault", reserve- Purpose
- Token account holding a reserve's USDC.
- Example (devnet)
- FKWY…ypKH
- Mint authority
- Seeds
"mint_auth"- Purpose
- Mint authority of the test USDC mint (faucet and interest backing).
- Example (devnet)
- 79h1…8eB3
Instructions#
Grouped by the signer the program requires. Amounts are base units; index is a strategy's position in its vault.
User instructions#
depositamount: u64Accrues every reserve and the fees, then mints shares at the current share price. Blocked while paused or when the deposit would exceed the vault cap.
deposit_checkedamount: u64min_shares_out: u64faucetamount: u64Devnet only. Mints test USDC to the caller, at most 10,000 per call.
withdrawshares: u64Burns shares for USDC at the current share price. If idle is short, pulls the rest from strategies in the same transaction, Solana venues first. Never blocked by pause.
withdraw_checkedshares: u64min_assets_out: u64
Keeper instructions#
allocateindex: u8amount: u64Moves idle USDC into a strategy. Rejected if the position would exceed the strategy's cap against total assets, if the strategy is disabled, or while paused. Cross-chain strategies burn a simulated 1 bp bridge fee.
deallocateindex: u8amount: u64Moves USDC from a strategy back to idle. `u64::MAX` exits the whole position. Allowed while paused so the keeper can always de-risk.
set_reserve_apyapy_bps: u32Devnet only. Accrues a simulated reserve at its old rate, then sets the new APY (at most 10,000 bps).
Permissionless instructions#
accept_adminaccrue_reserveDevnet only. Advances a simulated reserve's exchange rate and mints the interest into it so it stays fully backed.
collect_feesAccrues fees on fresh totals and mints the vault's pending fee shares to the treasury's share account. Anyone can call it; the keeper does so every hour.
Neither can move funds anywhere new: fee shares only go to the treasury in the config, and accrued interest only into the reserve.
Admin instructions#
add_strategyindex: u8name: stringvenue: stringchain: u8risk_score: u8max_alloc_bps: u16Whitelists a reserve as the vault's next strategy (at most 8). Risk score 1 to 10, cap at most 10,000 bps.
cancel_admin_proposalcreate_reserveid: stringapy_bps: u32Devnet only. Creates a simulated venue and its token account.
create_vaulttier: u8fee_bps: u16perf_fee_bps: u16deposit_cap: u64Creates a vault with its share mint and idle account. Management fee at most 200 bps a year, performance fee at most 3,000 bps.
initialize_configkeeper: pubkeytreasury: pubkeyCreates the program config. The signer becomes the admin.
migrate_vaultGrows a vault created before the performance fee existed to the current layout. Idempotent.
propose_adminnew_admin: pubkeyset_keeperkeeper: pubkeyRotates the keeper key.
set_pausedpaused: boolPauses or unpauses deposits and allocations. Withdrawals, deallocations and fee sweeps keep working.
set_treasurytreasury: pubkeyChanges the account that receives fee shares.
update_strategymax_alloc_bps: u16risk_score: u8enabled: boolChanges a strategy's cap and risk score, or disables it. A disabled strategy receives nothing new but can still be drained.
update_vaultdeposit_cap: u64fee_bps: u16perf_fee_bps: u16Changes the deposit cap and fees (same limits). Management fee accrued so far is settled at the old rate first.
Strategy accounts#
Every instruction that values a vault (deposit, withdraw, allocate, deallocate, collect_fees) takes the vault's strategies as remaining accounts, as triplets in vault order:
[strategy 0, reserve 0, reserve token 0, strategy 1, reserve 1, reserve token 1, …] all writableThe program requires exactly 3 × strategy_count accounts and checks that each strategy is the vault's, in the right slot, owned by the program and matched to its reserve and token account. Anything else fails with StrategyAccountsMismatch. Without this, a caller could omit a strategy to make the vault look smaller and buy shares cheaply. The SDK's strategyMetas(vault) builds the list.
The fixed accounts of a deposit or withdrawal, in order:
| # | Account | Flags |
|---|---|---|
| 0 | user | signer, writable |
| 1 | config | PDA |
| 2 | vault | writable |
| 3 | asset_mint | writable |
| 4 | share_mint | writable |
| 5 | idle | writable |
| 6 | user_asset | writable, PDA |
| 7 | user_shares | writable, PDA |
| 8 | mint_auth | PDA |
| 9 | token_program | program |
| 10 | associated_token_program | program |
| 11 | system_program | program |
Events#
The program emits Anchor events, readable from transaction logs:
| Event | Fields |
|---|---|
AdminProposed | admin: pubkey, new_admin: pubkey |
ApyUpdated | reserve: pubkey, apy_bps: u32 |
ConfigUpdated | admin: pubkey, keeper: pubkey, treasury: pubkey, paused: bool |
Deposited | vault: pubkey, user: pubkey, assets: u64, shares: u64, total_assets: u64 |
FeesCollected | vault: pubkey, treasury_shares: pubkey, shares: u64 |
Rebalanced | vault: pubkey, strategy_index: u8, allocate: bool, amount: u64, bridge_fee: u64, strategy_value: u64, total_assets: u64 |
StrategyConfigured | vault: pubkey, strategy: pubkey, index: u8, reserve: pubkey, chain: u8, risk_score: u8, max_alloc_bps: u16, enabled: bool |
VaultConfigured | vault: pubkey, asset_mint: pubkey, tier: u8, fee_bps: u16, perf_fee_bps: u16, deposit_cap: u64 |
Withdrawn | vault: pubkey, user: pubkey, assets: u64, shares: u64, total_assets: u64 |
Errors#
- 6000
- Name
Paused- Message
- Protocol is paused
- 6001
- Name
Unauthorized- Message
- Signer is not authorised for this action
- 6002
- Name
ZeroAmount- Message
- Amount must be greater than zero
- 6003
- Name
DepositCapExceeded- Message
- Deposit would exceed the vault cap
- 6004
- Name
StrategyCapExceeded- Message
- Allocation would exceed the strategy's risk cap
- 6005
- Name
TooManyStrategies- Message
- Vault already has the maximum number of strategies
- 6006
- Name
BadStrategyIndex- Message
- Strategy index is out of range, or not the next free slot when adding
- 6007
- Name
StrategyAccountsMismatch- Message
- Strategy accounts passed do not match the vault
- 6008
- Name
StrategyDisabled- Message
- Strategy is disabled
- 6009
- Name
InsufficientIdle- Message
- Not enough idle liquidity
- 6010
- Name
InsufficientLiquidity- Message
- Not enough liquidity to cover withdrawal
- 6011
- Name
InsufficientPosition- Message
- Strategy position is smaller than requested amount
- 6012
- Name
NameTooLong- Message
- Name is too long (max 32 bytes)
- 6013
- Name
InvalidParameter- Message
- Parameter out of range
- 6014
- Name
FaucetLimit- Message
- Faucet amount exceeds per-call maximum
- 6015
- Name
MathOverflow- Message
- Math overflow
- 6016
- Name
ZeroAssetsOut- Message
- Withdrawal would return zero assets
- 6017
- Name
SlippageExceeded- Message
- Output is below the caller's minimum (slippage bound)
- 6018
- Name
AllocationTooSmall- Message
- Allocation is too small to buy any reserve shares after the bridge fee
Wallets show program errors as hex, such as custom program error: 0x1773. Convert to decimal (0x1773 = 6003) and look it up above: 6003 is DepositCapExceeded. See SDK errors for doing this in code.
Constants#
| Constant | Value | Meaning |
|---|---|---|
MAX_STRATEGIES | 8 | Strategies per vault |
VIRTUAL_OFFSET | 1,000 | Virtual shares and assets in the share math, base units |
RATE_SCALE | 1018 | Fixed-point scale of exchange rates and the high-water mark |
BRIDGE_FEE_BPS | 1 | Simulated CCTP fee per cross-chain move, bps |
FAUCET_MAX | 10,000 USDC | Per faucet call |
MAX_FEE_BPS | 200 | Management fee limit, bps per year |
MAX_PERF_FEE_BPS | 3,000 | Performance fee limit, bps of yield |
MAX_APY_BPS | 10,000 | Simulated reserve APY limit |
NAME_LEN | 32 | Bytes in a strategy or venue name and a reserve id |
Build and test#
From a checkout of the repository, with the Solana and Anchor CLIs installed (Rust 1.89, Anchor 1.0):
anchor build
pnpm --filter @aqueduct/sdk sync-idl # copy the new IDL and types into the SDK
# SDK integration tests against a local validator with the program loaded
solana-test-validator --reset --bpf-program BweEZpNS6bhcwjxgEgScgZSWRSkKC9tqmC2PpfZdFbhm target/deploy/aqueduct.so
RPC_URL=http://127.0.0.1:8899 pnpm --filter @aqueduct/sdk testPending upgrade#
A new program version is built and tested but not deployed. It adds deposit_checked and withdraw_checked, which take a minimum number of shares or USDC out and fail below it, and a two-step admin handover (propose_admin, accept_admin, cancel_admin_proposal). Admin actions and fee sweeps also emit events. The existing instructions keep their signatures. None of this is callable on devnet until the program is upgraded.
programs/aqueduct/src/lib.rs, programs/aqueduct/src/state.rs, programs/aqueduct/src/constants.rs and packages/sdk/src/idl/aqueduct.json. If the code and this page disagree, the code wins.