CPI Basics in Anchor
8 examples to get you started with Anchor CPIs - 5 basic and 3 intermediate.
Prerequisites
- anchor-lang 0.32.1 and anchor-spl 0.32.1 in program
Cargo.toml - Understanding of Instruction Handlers
- Program accounts for System, Token, or custom targets in accounts struct
Basic Examples
1. CpiContext::new for SOL transfer
System program CPI moves lamports between accounts.
use anchor_lang::system_program::{self, Transfer};
system_program::transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
},
),
lamports,
)?;- Program account is first argument to CpiContext::new
- Inner struct lists account metas for the CPI
- Use
?to propagate ProgramError
2. SPL token transfer CPI
anchor-spl wraps token program invoke.
use anchor_spl::token::{self, Transfer};
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.from_ata.to_account_info(),
to: ctx.accounts.to_ata.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
amount,
)?;- Authority must sign or be PDA with with_signer
- Token accounts must be mut
- Include Program<Token> in accounts
3. with_signer for PDA authority
PDAs sign CPIs via seed slices.
let seeds = &[b"vault".as_ref(), &[ctx.bumps.vault]];
let signer = &[&seeds[..]];
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
accounts,
signer,
),
amount,
)?;- Seeds must match PDA constraints
- Use ctx.bumps when available
- Authority account is the PDA
4. CpiContext with signer
Add seeds to an existing context with .with_signer.
let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_accounts,
);
token::transfer(cpi_ctx.with_signer(signer), amount)?;- Useful when signer seeds computed conditionally
- Same semantics as new_with_signer
- Prefer one style per codebase
5. Include program accounts
Pin CPI program IDs in accounts struct.
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,- Prevents program substitution attacks
- Required for Anchor CPI helpers
- Use Interface for Token-2022
Intermediate Examples
6. declare_program! CPI module
Anchor 0.32 generates CPI clients from external IDL.
// lib.rs
declare_program!(other_program);
pub fn call_other(ctx: Context<CallOther>) -> Result<()> {
other_program::cpi::do_something(
CpiContext::new(
ctx.accounts.other_program.to_account_info(),
other_program::cpi::accounts::DoSomething { /* ... */ },
),
)?;
Ok(())
}- Place IDL JSON in workspace for declare_program!
- Generated module under cpi::
- Type-safe account structs for CPI
Related: CPI to Custom Programs
7. CpiContext remaining accounts
Forward extra accounts to CPI target.
let mut cpi_ctx = CpiContext::new(program, accounts);
cpi_ctx = cpi_ctx.with_remaining_accounts(ctx.remaining_accounts.to_vec());
other_program::cpi::advanced(cpi_ctx)?;- Target program must expect extras
- Validate before forwarding
- Order must match callee
Related: Passing Remaining Accounts
8. Error propagation
CPI failures return as ProgramError in caller.
token::transfer(cpi_ctx, amount)
.map_err(|_| MyError::TransferFailed)?;- Map opaque errors to custom #[error_code]
- Log before return for debugging
- Simulate txs to see inner errors
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.