The System & Token Programs
Include Program<'info, System>, Program<'info, Token>, and related program accounts in every instruction that CPIs to them. Anchor pins IDs to prevent program substitution.
Recipe
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,When to reach for this: Instructions create accounts, transfer SOL, or move SPL tokens.
Working Example
#[derive(Accounts)]
pub struct CreateAta<'info> {
#[account(mut)]
pub payer: Signer<'info>,
pub mint: Account<'info, Mint>,
#[account(
init,
payer = payer,
associated_token::mint = mint,
associated_token::authority = owner,
)]
pub ata: Account<'info, TokenAccount>,
pub owner: SystemAccount<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}What this demonstrates:
- Program<T> validates executable program ID
- System program needed for account allocate
- Token program required for SPL CPIs
- Associated token program for ATA creation
Deep Dive
Common Program Accounts
| Program | Anchor type |
|---|---|
| System | Program<'info, System> |
| SPL Token | Program<'info, Token> |
| Associated Token | Program<'info, AssociatedToken> |
| Token-2022 | Interface<'info, TokenInterface> |
Import from anchor_lang::system_program and anchor_spl::token.
Gotchas
- Omitting system_program on init - Init constraint fails.. Fix: Always pair init with System.
- Token program substitution - Without Program<Token>, attacker passes fake program.. Fix: Use typed Program accounts.
- Token vs Token-2022 mismatch - CPI fails.. Fix: Use Interface when supporting both.
- Wrong program in client remaining accounts - Meta order errors.. Fix: Follow IDL account order exactly.
- Using system CPI without mut payer - Lamport debit fails.. Fix: Mark payer mut.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| CpiContext with program account | Same requirement | Unchecked program AccountInfo |
| Native invoke with hardcoded IDs | Non-Anchor | Anchor Program<T> safety |
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 system token topics.
Related
- SPL Token CPIs - token CPIs
- Token & Associated Token Constraints - validation
- Creating Accounts via CPI - system CPI
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.