Instruction Arguments
Arguments after Context<T> are Borsh-serialized in instruction data. Use #[instruction(...)] on accounts structs to reference args in constraints.
Recipe
#[derive(Accounts)]
#[instruction(amount: u64, recipient: Pubkey)]
pub struct Transfer<'info> { /* use amount in constraint */ }
pub fn transfer(ctx: Context<Transfer>, amount: u64, recipient: Pubkey) -> Result<()> {
require!(amount > 0, MyError::InvalidAmount);
Ok(())
}When to reach for this: Clients pass numeric or pubkey parameters beyond the account list.
Working Example
#[derive(Accounts)]
#[instruction(id: u64)]
pub struct CreatePool<'info> {
#[account(
init,
payer = payer,
space = 8 + Pool::INIT_SPACE,
seeds = [b"pool", id.to_le_bytes().as_ref()],
bump,
)]
pub pool: Account<'info, Pool>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn create_pool(ctx: Context<CreatePool>, id: u64, fee_bps: u16) -> Result<()> {
require!(fee_bps <= 10_000, PoolError::InvalidFee);
ctx.accounts.pool.fee_bps = fee_bps;
Ok(())
}What this demonstrates:
- Arg order in handler matches client serialization
#[instruction]exposes args to constraints- Validate ranges with
require!early - Pubkey args often duplicate account keys for PDA seeds
Deep Dive
Serialization
Anchor prepends 8-byte instruction discriminator, then Borsh-encodes args in handler parameter order.
Validation Tips
- Reject zero amounts and sentinel pubkeys.
- Keep arg list small to reduce CU and client bugs.
- Prefer accounts over args when the pubkey must sign or hold data.
Gotchas
- Arg order mismatch in client - Deserialization fails.. Fix: Generate clients from IDL.
- Missing #[instruction] on struct - Cannot use args in seeds.. Fix: Add attribute with same names.
- Trusting args over accounts - Arg pubkey not tied to signer.. Fix: Require matching Signer account.
- Large Vec in args - CU and size blowup.. Fix: Use remaining_accounts or separate upload pattern.
- fee or amount overflow - Unchecked math in handler.. Fix: Use
checked_add/checked_mul.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Accounts-only API | When every input is on-chain state | Client-supplied scalars |
| Instruction data manually (native) | Non-Borsh layouts | Anchor IDL workflows |
| Version byte prefix in custom layout | Migration of arg layout | Greenfield Anchor programs |
FAQs
What Anchor version is assumed?
0.32.1 throughout this section.
Can a PDA be a Signer in accounts struct?
No. Use seeds constraints and CPI signing.
Where is bump stored?
In your account struct field, set at initialization.
How do clients derive PDAs?
Use @solana/kit 7.0.0 with matching seed bytes.
What program id is used for PDAs?
Your Anchor program's declare_id address.
Do seeds include bump?
Not in seeds array; bump is separate parameter to find_program_address.
How to debug PDA failures?
Compare logged keys; verify seeds and program id client-side.
Are PDAs rent-exempt?
Yes when holding data; fund with payer on init.
Can one PDA sign multiple CPIs in one ix?
Yes with same signer seeds for each CPI.
What to read next?
See Related links for deeper args topics.
Related
- Instruction Handlers - signatures
- Seeds & Bump Constraints - args in seeds
- Building Instructions - client encoding
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.