SPL Token CPIs
anchor-spl exposes typed CPI functions for SPL Token: transfer, mint_to, burn, approve, and more. They wrap account metas and instruction data for the token program.
Recipe
use anchor_spl::token::{self, MintTo, Transfer};
token::mint_to(cpi_ctx, amount)?;
token::transfer(cpi_ctx, amount)?;
token::burn(cpi_ctx, amount)?;When to reach for this: Your program moves SPL tokens without reimplementing token instruction layouts.
Working Example
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(mut)]
pub user_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub vault_ata: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.user_ata.to_account_info(),
to: ctx.accounts.vault_ata.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
},
),
amount,
)?;
Ok(())
}What this demonstrates:
- MintTo requires mint authority signer or PDA
- Transfer needs mut source and dest ATAs
- Burn reduces supply from burn account
- Token-2022 uses token_interface module
Deep Dive
Token Interface (2022)
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface};
pub token_program: Interface<'info, TokenInterface>,Use when supporting both classic Token and Token-2022 programs.
Gotchas
- Authority not signer for user transfer - CPI fails.. Fix: Use Signer or with_signer for PDA.
- Mint/decimals mismatch - Amount wrong magnitude.. Fix: Validate mint on both ATAs.
- Forgot mut on ATAs - Token program rejects.. Fix: Mark token accounts mut.
- Using Token program for Token-2022 mint - Owner mismatch.. Fix: Switch to Interface types.
- Approve without revoking - Allowance linger risk.. Fix: Revoke or use exact-delegate pattern.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Raw token instruction CPI | Custom layouts | Standard SPL flows |
| Client-side token transfers only | User signs token ix | Program custody |
FAQs
Anchor 0.32.1 error format?
Custom codes in IDL plus #[msg] strings.
emit! vs emit_cpi!?
emit_cpi! costs more CU but improves capture reliability.
Should I use msg! on mainnet?
Avoid except behind feature flags.
How do clients get error codes?
From IDL via Anchor or Codama codegen.
Do events appear in IDL?
Yes, #[event] types are listed.
Can constraints return custom errors?
Yes with @ MyError::Variant.
What uses CU in logging?
Each log line and formatted string.
How to test errors?
anchor test expecting error codes.
Are logs consensus data?
Yes, but not an API contract; use events.
Next read?
See Related for spl cpi.
Related
- CPI Basics in Anchor - CpiContext
- Token & Associated Token Constraints - validation
- SPL Token Basics - token model
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.