- 31 Oct 2025
In Solana, a Cross-Program Invocation (CPI) allows one program to call another program on the blockchain. This mechanism helps developers build modular and composable smart contracts, where programs can reuse logic instead of duplicating code.
A CPI happens when a Solana program sends an instruction to another program. The called program runs its logic and returns control to the original program. This is similar to a function call in traditional programming, but across different deployed smart contracts on Solana.
For example, your custom program might:
This example shows how a Solana program can transfer SOL by calling the System Program using Anchor.
use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer};
#[program]
pub mod cpi_example {
use super::*;
pub fn send_sol(ctx: Context, amount: u64) -> Result<()> {
let cpi_ctx = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
Transfer {
from: ctx.accounts.sender.to_account_info(),
to: ctx.accounts.receiver.to_account_info(),
},
);
transfer(cpi_ctx, amount)
}
}
#[derive(Accounts)]
pub struct SendSol<'info> {
#[account(mut)]
pub sender: Signer<'info>,
#[account(mut)]
pub receiver: SystemAccount<'info>,
pub system_program: Program<'info, System>,
}
This demonstrates a program using the System Program’s transfer instruction to send SOL on behalf of the sender account.
Here’s the same concept written in native Solana Rust, using invoke() directly.
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
program::invoke,
system_instruction,
pubkey::Pubkey,
};
pub fn process_transfer(
_program_id: &Pubkey,
accounts: &[AccountInfo],
amount: u64,
) -> ProgramResult {
let from = &accounts[0];
let to = &accounts[1];
let system_program = &accounts[2];
let ix = system_instruction::transfer(from.key, to.key, amount);
invoke(&ix, &[from.clone(), to.clone(), system_program.clone()])
}
This version doesn’t use Anchor’s helpers — instead, it manually constructs and calls the system instruction.
If your program owns a Program Derived Address (PDA) and it needs to sign the CPI, use invoke_signed() with seed values for verification.
let seeds: &[&[u8]] = &[b"seed", &[bump]];
invoke_signed(
&ix,
&[pda_account.clone(), receiver_account.clone(), system_program.clone()],
&[seeds],
)?;
This allows your PDA to act as a signer during the CPI.
invoke() for standard CPIs and invoke_signed() when PDAs are involved.Cross-Program Invocations make Solana programs flexible, reusable, and powerful. By calling other on-chain programs, developers can combine multiple smart contracts in one transaction, creating complex decentralized apps with cleaner, modular code.
It stands for Cross-Program Invocation — when one Solana program calls another program to perform an action, such as transferring tokens or managing accounts.
invoke() is used for normal calls, while invoke_signed() is used when a Program Derived Address (PDA) needs to act as a signer.
Yes, as long as the caller provides the correct accounts and permissions required by the target program.
CPIs consume more compute units than direct instructions because they involve multiple program calls, but they enable more flexibility and reusability.
They allow modular, composable applications — a key feature of Solana’s ecosystem. Without CPIs, programs would be limited to their own isolated logic.