Accessing Accounts
Use ctx.accounts for typed fields, ctx.bumps for PDA seeds, and ctx.remaining_accounts for variable account lists passed after the fixed layout.
Recipe
pub fn route(ctx: Context<Route>) -> Result<()> {
let bump = ctx.bumps.vault;
let vault = &mut ctx.accounts.vault;
for acc in ctx.remaining_accounts.iter() {
require!(acc.owner == &crate::ID, MyError::BadRemaining);
}
Ok(())
}When to reach for this: Handlers read or mutate validated accounts and optional extras.
Working Example
pub fn swap(ctx: Context<Swap>) -> Result<()> {
let user = &ctx.accounts.user;
let pool = &mut ctx.accounts.pool;
let bump = ctx.bumps.pool;
msg!("user: {}, pool bump: {}", user.key(), bump);
for meta in ctx.remaining_accounts.iter() {
let ai = meta.to_account_info();
require!(ai.is_writable, SwapError::ReadonlyAccount);
}
Ok(())
}What this demonstrates:
- Account fields are references with lifetimes tied to Context
ctx.bumps.<name>matches seeds field nameremaining_accountsis a slice of AccountInfo- Use
to_account_info()for CPI account metas
Deep Dive
Account Mutation
&mut Account<'info, T> serializes changes at end of instruction if the account was marked mut in constraints.
remaining_accounts
Clients append accounts after the IDL order. Document expected order and validate each entry.
Gotchas
- remaining_accounts unvalidated - Critical vulnerability.. Fix: Check owner, discriminator, signer as needed.
- Wrong bump name in ctx.bumps - Field name must match seeds account field.. Fix: Use exact struct field name.
- Cloning AccountInfo unnecessarily - CU waste.. Fix: Pass references into CPI helpers.
- Assuming remaining order from IDL - Only fixed accounts are in IDL.. Fix: Publish separate docs for extras.
- Reading data before validation in other crate - Only applies to unchecked paths.. Fix: Keep validation in constraints first.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Explicit accounts vec in struct | Small fixed sets | Large dynamic routing |
| AccountLoader::load | Zero-copy reads | Standard Account<T> |
| Sysvar accounts in struct | Clock, rent | remaining_accounts for rare sysvars |
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 accessing topics.
Related
- remaining_accounts - dynamic sets
- Passing Remaining Accounts - CPI extras
- Instruction Handlers - Context overview
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.