The Accounts Struct
Every instruction declares an accounts struct with #[derive(Accounts)]. Anchor runs constraints before your handler executes, enforcing ownership, mutability, and relationships fail-closed.
Recipe
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub from: Signer<'info>,
#[account(mut, constraint = to.owner == from.key())]
pub to: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}When to reach for this: You design any instruction that touches on-chain accounts.
Working Example
#[derive(Accounts)]
pub struct UpdateMetadata<'info> {
#[account(
mut,
seeds = [b"meta", authority.key().as_ref()],
bump = metadata.bump,
has_one = authority,
)]
pub metadata: Account<'info, Metadata>,
pub authority: Signer<'info>,
}
#[account]
pub struct Metadata {
pub authority: Pubkey,
pub bump: u8,
pub uri: String,
}What this demonstrates:
- Field types (
Signer,Account<T>,Program<T>) imply base checks - Attribute constraints add seeds, init, has_one, and custom expressions
bumpsmap is available in handlers for stored canonical bumps- Validation order matters for init and CPI safety
Deep Dive
Common Account Types
| Type | Validates |
|---|---|
Signer<'info> | Account signed the transaction |
Account<'info, T> | Owner is program, deserializes as T |
Program<'info, T> | Address matches program ID for T |
SystemAccount<'info> | Owned by system program |
UncheckedAccount<'info> | No automatic checks (use carefully) |
Constraint Categories
- Mutability -
mutfor writable accounts. - Ownership -
owner,has_one, token constraints. - Address -
address = expr, seeds/bump for PDAs. - Lifecycle -
init,init_if_needed,close.
Gotchas
- UncheckedAccount without constraints - Any account passes validation.. Fix: Add
address,owner, orconstraint. - Missing mut on writable account - Runtime failure on write.. Fix: Mark every written account
mut. - has_one on wrong field - Relationship not enforced.. Fix: Match field name to struct field on account data.
- Init without payer - Account creation fails.. Fix: Set
payer = signerand include system program. - Trusting client-provided PDAs - Wrong seeds drain funds.. Fix: Always use
seeds+bumpconstraints.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Manual validation in handler | Dynamic checks hard to express | Prefer declarative constraints for static rules |
| remaining_accounts | Variable account sets | Fixed layout known at compile time |
| AccountLoader for zero-copy | Large accounts | Small structs with Borsh |
FAQs
What Anchor version does this site use?
0.32.1 for anchor-lang, Anchor CLI, and examples in this section.
Do I need Solana CLI alongside Anchor?
Yes. Solana CLI 3.0.10 handles keypairs, airdrops, and solana program inspection.
Where does the IDL live after build?
target/idl/<program>.json in your workspace.
Can I mix UncheckedAccount with Signer?
Yes, but every unchecked field needs explicit constraints or handler checks.
How do I test without devnet?
Use anchor test with Surfpool 0.12.0 or LiteSVM 0.6.x in CI.
What is the 8-byte prefix on account data?
Anchor account discriminator; do not strip it when sizing space.
Should I commit generated IDL?
Yes, or publish on-chain IDL so clients have a canonical source.
How do I debug constraint failures?
Run with logs; Anchor prints constraint name and account index.
Does Anchor work on Agave 4.1.1?
Yes. This stack targets Agave validators with Solana CLI 3.0.10.
What should I read next in this section?
See sibling articles linked in Related for deeper accounts struct topics.
Related
- Account Types - type reference
- The #[account] Constraints - constraint catalog
- Account Validation Order - check sequence
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.