CPI Basics
10 examples to get you started with CPIs - 7 basic and 3 intermediate.
Prerequisites
- Native program entrypoint familiarity.
- Account meta ordering from Working with Pubkeys.
Basic Examples
1. Simple invoke
Call System Program transfer from your program.
use solana_program::program::invoke;
use solana_program::system_instruction;
invoke(
&system_instruction::transfer(from.key, to.key, lamports),
&[from.clone(), to.clone()],
)?;- Callee program id is embedded in Instruction.
- Accounts must match callee expectations.
- Return
?to bubble CPI errors.
2. Instruction struct
Build Instruction with program id, accounts, data.
use solana_program::instruction::{AccountMeta, Instruction};
let ix = Instruction {
program_id: token_program.key(),
accounts: vec![/* AccountMeta */],
data: vec![/* opcode */],
};- Data layout must match callee program.
- Prefer helper crates (
spl-token) for encoding. - Program id wrong = IncorrectProgramId.
3. Clone AccountInfo
Invoke needs owned AccountInfo references in slice.
&[payer.clone(), vault.clone(), system_program.clone()]cloneon AccountInfo is cheap ref clone.- Order matches AccountMeta order.
- Include program accounts as readonly unless writable required.
4. CPI error propagation
Failed CPI fails your instruction.
invoke(&ix, accounts).map_err(|e| {
solana_program::msg!("cpi failed");
e
})?;- Entire transaction rolls back on CPI error.
- Map errors if you need custom codes.
- Logs may include callee program errors.
5. Program account in CPI
Pass token/system program account readonly.
let system_program = next_account_info(iter)?;
if system_program.key != &system_program::ID {
return Err(ProgramError::IncorrectProgramId);
}- Validate program ids before invoke.
- Executable accounts are program accounts.
- Never pass wrong program id.
6. Signer requirements
CPI cannot add signatures wallets did not provide.
if !payer.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}- Exception: PDAs via
invoke_signed. - Debit from user requires user signer.
- PDA debit requires program seeds.
7. Read-only CPI
Some CPIs only read callee state.
// e.g. reading oracle - still passes accounts- Writable flag must be false if not mutating.
- Over-writable metas are audit findings.
- Follow callee interface docs.
Intermediate Examples
8. invoke_signed for PDA
Program signs CPI on behalf of PDA.
invoke_signed(&ix, accounts, &[&[b"vault", &[bump]]])?;- Seeds must match PDA derivation.
- Required for token authority PDAs.
- See invoke vs invoke_signed page.
Related: invoke vs invoke_signed
9. SPL token transfer CPI
Transfer tokens with token program.
use spl_token::instruction as token_ix;
let ix = token_ix::transfer(
token_program.key,
source.key,
dest.key,
authority.key,
&[],
amount,
)?;
invoke(&ix, &[source.clone(), dest.clone(), authority.clone(), token_program.clone()])?;- Authority must sign or be PDA with invoke_signed.
- Validate mint matching across accounts.
- Use checked amount math before CPI.
Related: CPI to SPL Token
10. CPI depth awareness
CPIs can call programs that CPI again - max depth 4.
// Your program -> Token -> ... depth budget- Deep chains cost CUs.
- Design flat CPI graphs when possible.
- See CPI Depth & Limits.
Related: CPI Depth & Limits
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.