All articles
Smart Contracts·October 14, 2025·5 min read

Building Anchor PDA Vaults for Solana Trading Bots

Learn how to design and deploy a Program Derived Address vault using Anchor so your HFT bot can custody funds trustlessly on-chain. Covers seed derivation, bump canonicalization, and CPI-safe withdraw guards.

On-chain trading bots that hold user funds have exactly one place where the trust model either holds or collapses: the vault. Get the Program Derived Address design right and you get a custody layer that is cryptographically impossible for an operator to drain unilaterally. Get it wrong and you have a hot wallet with extra steps. Here is how we build them at TierZero, from first seed derivation through to CPI-safe withdraw guards in production.

Why PDAs Instead of a Keypair Wallet

A PDA is an account address derived deterministically from a program ID and a set of seeds. Because it falls off the ed25519 curve, no private key exists for it — only the program itself can sign on its behalf via invoke_signed. This is the entire security premise. A normal keypair held by your bot process is a single point of failure: leak the key, lose the funds. A PDA vault removes that risk entirely; the only way to move lamports or SPL tokens out is to satisfy the logic encoded in your on-chain program.

For an HFT bot on Solana, this matters more than on slower chains. Your bot may be executing hundreds of transactions per minute. The signing authority for routine market operations does not need to be — and must not be — the same key that can empty the treasury.

Seed Design and the Canonical Bump

Anchor's #[account(seeds = [...], bump)] macro hides a lot of complexity, but you need to understand what it is hiding. find_program_address iterates bump values from 255 downward until it finds one that lands off the curve. The first valid result is the canonical bump. Store it.

A minimal vault account definition:

#[account]
pub struct Vault {
    pub authority: Pubkey,   // the human or multisig that can withdraw
    pub strategy: Pubkey,    // the bot keypair allowed to trade
    pub bump: u8,
    pub locked: bool,        // emergency pause flag
}

Seeds that work well in practice: [b"vault", authority.key().as_ref(), strategy.key().as_ref()]. Including both keys in the seed means one program can manage multiple vaults — one per strategy per user — without address collision. Avoid including mutable state (e.g., a counter) in your seeds unless you specifically need multiple sequential vaults; that just creates unnecessary derivation complexity at call time.

At account initialization, capture and store the bump:

#[account(
    init,
    payer = authority,
    space = 8 + Vault::LEN,
    seeds = [b"vault", authority.key().as_ref(), strategy.key().as_ref()],
    bump
)]
pub vault: Account<'info, Vault>,

// in handler:
ctx.accounts.vault.bump = ctx.bumps.vault;

On every subsequent instruction, use bump = vault.bump rather than letting Anchor re-derive it. Re-derivation is safe but costs ~1500 compute units per call — across thousands of bot transactions daily, that adds up.

Separating Trading Authority from Withdrawal Authority

The most important architectural decision is role separation. Your vault should expose two distinct capability sets:

  • Strategy authority (strategy pubkey): signs trade instructions, can move funds into DEX positions, cannot withdraw to external accounts.
  • Vault authority (authority pubkey): the only signer that can pull lamports or tokens back to an arbitrary destination. Ideally this is a multisig (Squads is the current standard on Solana) rather than a single hot key.

Enforce this in your withdraw handler with an explicit check before any token transfer:

require!(
    ctx.accounts.authority.key() == ctx.accounts.vault.authority,
    VaultError::UnauthorizedWithdraw
);
require!(!ctx.accounts.vault.locked, VaultError::VaultLocked);

Never make the strategy key a signer on the withdraw instruction. If the bot process is compromised, the attacker gets market-making access but cannot drain the vault.

CPI Guard: Defending Against Composability Attacks

Solana's composability is powerful and dangerous. A malicious program can CPI into yours if your instruction is not gated correctly. Anchor's #[interface] and account constraint checks help, but there is one additional guard worth adding explicitly: reject calls that arrive via CPI when you do not expect them.

let ixs = ctx.accounts.instructions.to_account_info();
let current_ix = load_current_index_checked(&ixs)?;
require!(
    current_ix == 0 || !is_cpi_call(&ixs)?,
    VaultError::NoCPIAllowed
);

This uses the Instructions sysvar to detect whether your withdraw handler was invoked as a top-level instruction or via CPI. For a vault, you almost never want CPI-initiated withdrawals — if you do need them (e.g., a rebalancer program), gate them on a separate whitelist of trusted program IDs stored in the vault account.

Token Account Ownership and Rent

The vault PDA should own any associated token accounts it holds. Initialize them with the vault PDA as authority, not the strategy key. This means SPL token transfers out require invoke_signed with the vault's seeds — same enforcement as native SOL.

One practical detail: pre-fund the vault with enough lamports to cover rent-exemption for every token account it will ever open. On mainnet, rent-exemption for a token account is roughly 0.00203928 SOL. If your vault strategies span 10 different mints, reserve about 0.025 SOL in the vault for account creation overhead before the first trade fires.

Upgrade Authority and the Long-Term Trust Model

Your vault program's upgrade authority is as important as the vault logic itself. A mutable program can be replaced by whoever holds upgrade authority, which voids all on-chain guarantees. For production vaults holding material capital, the upgrade authority should be a Squads multisig requiring M-of-N signers, with a time-lock if the Squads version supports it. We typically freeze upgrade authority entirely on strategies that have been audited and are not expected to change — immutability is the strongest trust signal you can give depositors.


If you want a vault architecture that is actually production-hardened for high-frequency strategies rather than demo-grade, talk to our team. We design and operate these systems end-to-end through our bot development service.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Smart Contracts#Solana#Anchor#PDA#Trading Bots#DeFi#On-Chain