Skip to content
Open app

Security and trust model

Who can do what with a vault's funds: the program's guarantees, the keeper's limited powers, the admin's broad ones, what pausing does, and the audit status.

On this page

The short version#

  • The program is non-custodial. Only a share holder can redeem shares, and withdrawals are never blocked by the pause switch.
  • The keeper is boxed in. It can move a vault's USDC between the idle account and whitelisted strategies, within each strategy's on-chain cap, and nothing else. It cannot withdraw or send funds anywhere.
  • The admin is trusted. One key is both the program's admin and its upgrade authority. An upgrade can change any rule, so today you are trusting that key completely.
  • Nothing has been independently audited. This is devnet software with test tokens.

Roles#

  • User
    Who
    Any wallet
    Powers
    Deposit, withdraw its own shares, mint test USDC from the faucet
  • Keeper
    Who
    One key stored in the program config
    Powers
    Allocate and deallocate between idle and strategies; set devnet reserve APYs
  • Admin
    Who
    One key stored in the program config, also the upgrade authority
    Powers
    Settings, vaults, strategies, pause, keeper and treasury rotation, program upgrades
  • Treasury
    Who
    One account stored in the program config
    Powers
    Receives fee shares and can redeem them like any holder
  • Anyone
    Who
    Any wallet
    Powers
    Sweep fees to the treasury, accrue a devnet reserve
The current keys are listed under Live deployment.

What the keeper can and cannot do#

The keeper signs three instructions. The program checks the signer against the keeper key in its config on each of them:

  • allocate
    index: u8amount: u64

    Moves 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.

  • deallocate
    index: u8amount: u64

    Moves 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_apy
    apy_bps: u32

    Devnet only. Accrues a simulated reserve at its old rate, then sets the new APY (at most 10,000 bps).

Keeper: 3 instructions, from the program IDL.
The keeper canThe keeper cannot
Move USDC from idle into any enabled strategy, up to its capWithdraw, transfer or burn anyone's shares
Move USDC from any strategy back to idle, including while pausedSend USDC to any account other than the vault's idle account and its strategies' reserves
Set a devnet reserve's APY between 0% and 100%Change caps, fees, the whitelist, the treasury or the pause flag
Choose not to act, or ignore its own sentinelMint shares, or move funds between vaults

So a compromised keeper key cannot steal deposits, but it can still cost depositors yield or a little principal:

  • parking everything in idle, or in the worst venues the caps allow;
  • ignoring sentinel exits;
  • churning cross-chain strategies, since each cross-chain move burns a 1 bp bridge fee and the program has no on-chain limit on how often the keeper moves;
  • on devnet, setting reserve APYs that no real venue pays.

The admin can replace a compromised keeper key at any time with set_keeper.

Admin powers#

  • add_strategy
    index: u8name: stringvenue: stringchain: u8risk_score: u8max_alloc_bps: u16

    Whitelists a reserve as the vault's next strategy (at most 8). Risk score 1 to 10, cap at most 10,000 bps.

  • cancel_admin_proposal

  • create_reserve
    id: stringapy_bps: u32

    Devnet only. Creates a simulated venue and its token account.

  • create_vault
    tier: u8fee_bps: u16perf_fee_bps: u16deposit_cap: u64

    Creates 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_config
    keeper: pubkeytreasury: pubkey

    Creates the program config. The signer becomes the admin.

  • migrate_vault

    Grows a vault created before the performance fee existed to the current layout. Idempotent.

  • propose_admin
    new_admin: pubkey

  • set_keeper
    keeper: pubkey

    Rotates the keeper key.

  • set_paused
    paused: bool

    Pauses or unpauses deposits and allocations. Withdrawals, deallocations and fee sweeps keep working.

  • set_treasury
    treasury: pubkey

    Changes the account that receives fee shares.

  • update_strategy
    max_alloc_bps: u16risk_score: u8enabled: bool

    Changes a strategy's cap and risk score, or disables it. A disabled strategy receives nothing new but can still be drained.

  • update_vault
    deposit_cap: u64fee_bps: u16perf_fee_bps: u16

    Changes the deposit cap and fees (same limits). Management fee accrued so far is settled at the old rate first.

Admin: 12 instructions, from the program IDL.

The program enforces these limits on the admin:

  • Performance fee at most 30% of yield, management fee at most 2% a year.
  • Strategy caps at most 100%, risk scores from 1 to 10, at most 8 strategies per vault.
  • A strategy can only point at a reserve for the vault's own asset, and strategies cannot be removed.
  • No admin instruction moves user funds.
The upgrade authority overrides all of it

The same key that is the admin is the program's upgrade authority on devnet. A program upgrade can replace any instruction, including the withdrawal path, so whoever holds that key could take every vault's funds. There is no multisig and no timelock today. For a mainnet launch, the upgrade authority should move to a multisig with a timelock, or the program should be made immutable.

Pause#

The admin can pause the whole protocol with set_paused. Pausing stops new money from entering the system and new risk from being taken, while everything that reduces risk keeps working:

InstructionWhile paused
depositBlocked
allocateBlocked
withdrawWorks
deallocateWorks
collect_feesWorks
faucet, accrue_reserve, set_reserve_apyWorks
Admin instructionsWorks

A paused deposit fails with error 6000 (Paused). The pause is global: it applies to both vaults at once.

Users can always exit#

withdraw does not read the pause flag, and it pulls from strategies inside the same transaction when the idle account is short. No keeper action is needed for you to leave. The only ways a withdrawal can fail are the ones that also protect other depositors:

  • the amount is zero, or so small it would pay zero USDC;
  • you do not hold the shares;
  • the strategies together cannot cover the amount (InsufficientLiquidity), which cannot happen on devnet but could on mainnet if a venue were fully utilised;
  • the program has been upgraded to behave differently, which only the upgrade authority can do.

On-chain safeguards#

  • Inflation-attack resistance. Share math uses an ERC-4626 style virtual offset of 1,000 base units, so donating to an empty vault cannot round later depositors down to zero shares.
  • Account validation. Every instruction that values a vault requires all of its strategy accounts, in order, owned by the program and matched to their reserves and token accounts. A transaction cannot leave a strategy out to misprice shares, or slip in a foreign account.
  • Fresh prices. Every deposit, withdrawal, allocation and fee sweep accrues all reserves and fees before it prices anything.
  • Fully backed reserves. Devnet interest is minted into the reserve token account as it accrues, so reserves hold every token they owe.
  • Rounding in the vault's favour, and checked arithmetic that fails instead of overflowing.
  • Fees cannot be rounded away or charged twice. Fee accrual waits until at least one share unit is due, and the high-water mark stops a recovered loss being charged again.

Off-chain trust#

Some parts of the system are trusted without on-chain enforcement:

  • Rate sources (venue APIs, DefiLlama)
    Trusted for
    APY, TVL and utilisation
    If it fails or lies
    The keeper allocates on wrong numbers, within the caps; the sentinel can be triggered or blinded
  • Keeper host
    Trusted for
    Running the keeper and holding its key
    If it fails or lies
    As a compromised keeper key, above
  • Web app host
    Trusted for
    Serving the dashboard code
    If it fails or lies
    Could ask you to sign a different transaction. Read your wallet's preview before you approve
  • Your RPC endpoint
    Trusted for
    Balances, vault data, confirmations
    If it fails or lies
    Can show wrong numbers, but cannot redirect funds: the program checks every account
  • Optional LLM classifier
    Trusted for
    A stricter second opinion
    If it fails or lies
    Can only tighten. Off in production

Audit status#

Not audited

Neither the program nor the keeper has been audited by an independent firm. Do not deposit anything of value, and do not treat devnet behaviour as a guarantee of how a mainnet version would behave.

In October 2026 the program and the off-chain code (keeper, SDK, web app and deployment) were reviewed by an automated review agent at the owner's request. These reviews are not a substitute for an independent audit. Their findings are being fixed: the keeper and SDK changes ship with their next releases, and the program changes below need an upgrade.

Needs a program upgrade, not deployed

The next program version adds deposit_checked and withdraw_checked, which fail if the shares or USDC you receive fall below a minimum you set, and a two-step admin handover, so the admin key can only be transferred to an account that accepts it. Until that upgrade is deployed, deposits and withdrawals settle at whatever the share price is when the transaction lands.

Written against programs/aqueduct/src/instructions/admin.rs, programs/aqueduct/src/instructions/keeper.rs, programs/aqueduct/src/instructions/user.rs and programs/aqueduct/src/instructions/common.rs. If the code and this page disagree, the code wins.