Skip to main content
Cross-Program Invocations (CPIs) allow your program to call instructions on other programs. This is essential for composability on Solana.

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 the CpiContext 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, use CpiContext::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, use declare_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

Build docs developers (and LLMs) love