What are CPIs?
CPIs enable your program to:- Call functions in other programs (like Token Program, System Program)
- Transfer SOL or tokens
- Create and manage accounts on behalf of PDAs
- Build complex composable applications
Making a CPI
Anchor provides theCpiContext type for making CPIs:
use anchor_spl::token::{self, Transfer};
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
};
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
);
token::transfer(cpi_context, amount)?;
Ok(())
}
CPI with signers
When a PDA needs to sign a CPI, useCpiContext::new_with_signer:
pub fn transfer_from_pda(ctx: Context<TransferFromPda>, amount: u64) -> Result<()> {
let seeds = &[
b"vault",
ctx.accounts.authority.key().as_ref(),
&[ctx.accounts.vault.bump],
];
let signer_seeds = &[&seeds[..]];
let cpi_accounts = Transfer {
from: ctx.accounts.vault_ata.to_account_info(),
to: ctx.accounts.user_ata.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
};
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
signer_seeds,
);
token::transfer(cpi_context, amount)?;
Ok(())
}
Common CPI examples
Transferring SOL
use anchor_lang::system_program;
pub fn transfer_sol(ctx: Context<TransferSol>, amount: u64) -> Result<()> {
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.to.to_account_info(),
},
);
system_program::transfer(cpi_context, amount)?;
Ok(())
}
#[derive(Accounts)]
pub struct TransferSol<'info> {
#[account(mut)]
pub from: Signer<'info>,
#[account(mut)]
pub to: SystemAccount<'info>,
pub system_program: Program<'info, System>,
}
Creating accounts
use anchor_lang::system_program;
pub fn create_account(ctx: Context<CreateAccount>) -> Result<()> {
let space = 8 + 32 + 8; // discriminator + pubkey + u64
let rent = Rent::get()?.minimum_balance(space);
let seeds = &[b"data", ctx.accounts.user.key().as_ref(), &[ctx.bumps.data]];
let signer_seeds = &[&seeds[..]];
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.system_program.to_account_info(),
system_program::CreateAccount {
from: ctx.accounts.user.to_account_info(),
to: ctx.accounts.data.to_account_info(),
},
signer_seeds,
);
system_program::create_account(
cpi_context,
rent,
space as u64,
ctx.program_id,
)?;
Ok(())
}
Minting tokens
use anchor_spl::token::{self, MintTo};
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
let seeds = &[b"mint", &[ctx.accounts.mint_authority.bump]];
let signer_seeds = &[&seeds[..]];
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.token_account.to_account_info(),
authority: ctx.accounts.mint_authority.to_account_info(),
};
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
signer_seeds,
);
token::mint_to(cpi_context, amount)?;
Ok(())
}
Using declare_program! for CPIs
For external programs, usedeclare_program! to generate type-safe CPI helpers:
use anchor_lang::prelude::*;
declare_program!(my_program);
pub fn call_other_program(ctx: Context<CallOtherProgram>, amount: u64) -> Result<()> {
my_program::cpi::initialize(
CpiContext::new(
ctx.accounts.other_program.to_account_info(),
my_program::cpi::accounts::Initialize {
account: ctx.accounts.account.to_account_info(),
user: ctx.accounts.user.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
},
),
amount,
)?;
Ok(())
}
CPI return values
Since Solana v1.16, CPIs can return values:use anchor_lang::solana_program::program::get_return_data;
pub fn call_with_return(ctx: Context<CallWithReturn>) -> Result<()> {
// Make CPI
my_program::cpi::compute_value(
CpiContext::new(
ctx.accounts.other_program.to_account_info(),
my_program::cpi::accounts::ComputeValue {
// accounts...
},
),
)?;
// Get return value
let (program_id, return_data) = get_return_data()
.ok_or(ErrorCode::NoReturnData)?;
require_keys_eq!(program_id, my_program::ID);
let value = u64::from_le_bytes(return_data.try_into().unwrap());
msg!("Returned value: {}", value);
Ok(())
}
Complete example
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token::{self, Mint, Token, TokenAccount, Transfer},
};
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod cpi_example {
use super::*;
pub fn initialize_vault(ctx: Context<InitializeVault>) -> Result<()> {
let vault = &mut ctx.accounts.vault;
vault.authority = ctx.accounts.authority.key();
vault.bump = ctx.bumps.vault;
msg!("Vault initialized");
Ok(())
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
// CPI to transfer tokens from user to vault
let cpi_accounts = Transfer {
from: ctx.accounts.user_ata.to_account_info(),
to: ctx.accounts.vault_ata.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
};
let cpi_context = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
);
token::transfer(cpi_context, amount)?;
msg!("Deposited {} tokens to vault", amount);
Ok(())
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
// CPI with PDA signer to transfer tokens from vault to user
let authority_key = ctx.accounts.authority.key();
let seeds = &[
b"vault",
authority_key.as_ref(),
&[ctx.accounts.vault.bump],
];
let signer_seeds = &[&seeds[..]];
let cpi_accounts = Transfer {
from: ctx.accounts.vault_ata.to_account_info(),
to: ctx.accounts.user_ata.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
};
let cpi_context = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
cpi_accounts,
signer_seeds,
);
token::transfer(cpi_context, amount)?;
msg!("Withdrew {} tokens from vault", amount);
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeVault<'info> {
#[account(
init,
payer = authority,
space = 8 + 32 + 1,
seeds = [b"vault", authority.key().as_ref()],
bump
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(seeds = [b"vault", authority.key().as_ref()], bump = vault.bump)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub user_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub vault_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub user: Signer<'info>,
pub authority: SystemAccount<'info>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(
seeds = [b"vault", authority.key().as_ref()],
bump = vault.bump,
has_one = authority
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub vault_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub user_ata: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
#[account]
pub struct Vault {
pub authority: Pubkey,
pub bump: u8,
}
Best practices
Validate CPI accounts - Always verify accounts passed to CPIs belong to the expected programs.
Check return values - When CPIs return data, validate the returning program ID matches expectations.
Be aware of compute limits - CPIs consume compute units from your program’s budget.
Next steps
SPL integrations
Learn about anchor-spl CPI helpers
declare_program!
Generate type-safe CPI helpers