Cross Program Invocations (CPIs)
A Cross-Program Invocation (CPI) is a direct call from one program into another, allowing for the composability of Solana programs. Just as any client can call any program using the JSON RPC, any program can call any other program via a CPI. CPIs essentially turn the entire Solana ecosystem into one giant API that is at your disposal as a developer.
The purpose of this section is to provide a high-level overview CPIs. Please refer to the linked resources below for more detailed explanations, examples, and walkthroughs.
tip Fact Sheet
- A Cross-Program Invocation (CPI) is a call from one program to another, targeting a specific instruction on the program being called
- CPIs allow the calling program to extend its signer privileges to the callee program
- Programs can execute CPIs using either
invoke
orinvoke_signed
within their instructions invoke
is used when all required signatures are accessible prior to invocation, without the need for PDAs to act as signersinvoke_signed
is used when PDAs from the calling program are required as signers in the CPI- After a CPI is made to another program, the callee program can make further CPIs to other programs, up to a maximum depth of 4
Deep Dive
Cross Program Invocations (CPIs) enable the composability of Solana programs, which allow developers to utilize and build on the instruction of existing programs.
To execute CPIs, use the invoke or invoke_signed function found in the solana_program
crate.
// Used when there are not signatures for PDAs needed
pub fn invoke(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>]
) -> ProgramResult
// Used when a program must provide a 'signature' for a PDA, hence the signer_seeds parameter
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>],
signers_seeds: &[&[&[u8]]]
) -> ProgramResult
To make a CPI, you must specify and construct an instruction on the program being invoked and supply a list of accounts necessary for that instruction. If a PDA is required as a signer, the signers_seeds
must also be provided when using invoke_signed
.
CPI with invoke
The invoke
function is used when making a CPI that does not require any PDAs to act as signers. When making CPIs, the Solana runtime extends the original signature passed into a program to the callee program.
invoke(
&some_instruction, // instruction to invoke
&[account_one.clone(), account_two.clone()], // accounts required by instruction
)?;
CPI with invoke_signed
To make a CPI that requires a PDA as a signer, use the invoke_signed
function and provide the necessary seeds to derive the required PDA of the calling program.
invoke_signed(
&some_instruction, // instruction to invoke
&[account_one.clone(), pda.clone()], // accounts required by instruction, where one is a pda required as signer
&[signers_seeds], // seeds to derive pda
)?;
While PDAs have no private keys of their own, they can still act as a signer in an instruction via a CPI. To verify that a PDA belongs to the calling program, the seeds used to generate the PDA required as a signer must be included in as signers_seeds
.
The Solana runtime will internally call create_program_address
using the seeds provided and the program_id
of the calling program. The resulting PDA is then compared to the addresses supplied in the instruction. If there's a match, the PDA is considered a valid signer.
CPI Instruction
Depending on the program you're making the call to, there may be a crate available with helper functions for creating the Instruction
. Many individuals and organizations create publicly available crates alongside their programs that expose these sorts of functions to simplify calling their programs.
The definition of the Instruction
type required for a CPI includes:
program_id
- the public key of the program that executes the instructionaccounts
- a list of all accounts that may be read or written to during the execution of the instructiondata
- the instruction data required by the instruction
pub struct Instruction {
pub program_id: Pubkey,
pub accounts: Vec<AccountMeta>,
pub data: Vec<u8>,
}
The AccountMeta
struct has the following definition:
pub struct AccountMeta {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}
When creating a CPI, use the following syntax to specify the AccountMeta
for each account:
AccountMeta::new
- indicates writableAccountMeta::new_readonly
- indicates not writable(pubkey, true)
- indicates account is signer(pubkey, false)
- indicates account is not signer
Here is an example:
use solana_program::instruction::AccountMeta;
let account_metas = vec![
AccountMeta::new(account1_pubkey, true),
AccountMeta::new(account2_pubkey, false),
AccountMeta::new_readonly(account3_pubkey, false),
AccountMeta::new_readonly(account4_pubkey, true),
]
CPI AccountInfo
To use invoke
and invoke_signed
, a list of account_infos
is also required. Similar to the list of AccountMeta
in the instruction, you need to include all the AccountInfo
of each account that the program you're calling will read from or write to.
For reference, the AccountInfo
struct has the following definition:
/// Account information
#[derive(Clone)]
pub struct AccountInfo<'a> {
/// Public key of the account
pub key: &'a Pubkey,
/// Was the transaction signed by this account's public key?
pub is_signer: bool,
/// Is the account writable?
pub is_writable: bool,
/// The lamports in the account. Modifiable by programs.
pub lamports: Rc<RefCell<&'a mut u64>>,
/// The data held in this account. Modifiable by programs.
pub data: Rc<RefCell<&'a mut [u8]>>,
/// Program that owns this account
pub owner: &'a Pubkey,
/// This account's data contains a loaded program (and is now read-only)
pub executable: bool,
/// The epoch at which this account will next owe rent
pub rent_epoch: Epoch,
}
You can create a copy of the AccountInfo
for each required account using the Clone trait, which is implemented for the AccountInfo struct in the solana_program
crate.
let accounts_infos = [
account_one.clone(),
account_two.clone(),
account_three.clone(),
];
While this section has provided a high-level overview of CPIs, more detailed explanations, examples, and walkthroughs can be found in the linked resources below.