Cross Program Invocation (CPI)
A Cross Program Invocation (CPI) refers to when one program invokes the instructions of another program. This mechanism allows for the composability of Solana programs.
You can think of instructions as API endpoints that a program exposes to the network and a CPI as one API internally invoking another API.
When a program initiates a Cross Program Invocation (CPI) to another program:
- The signer privileges from the initial transaction invoking the caller program (A) extend to the callee (B) program
- The callee (B) program can make further CPIs to other programs, up to a maximum depth of 4 (ex. B->C, C->D)
- The programs can "sign" on behalf of the PDAs derived from its program ID
The Solana program runtime defines a constant called
max_invoke_stack_height
, which is set to a value of 5. This represents the maximum height of the program instruction invocation stack. The stack height begins at 1 for transaction instructions, increases by 1 each time a program invokes another instruction. This setting effectively limits invocation depth for CPIs to 4.
Key Points
-
CPIs enable Solana program instructions to directly invoke instructions on another program.
-
Signer privileges from a caller program are extended to the callee program.
-
When making a CPI, programs can "sign" on behalf of PDAs derived from their own program ID.
-
The callee program can make additional CPIs to other programs, up to a maximum depth of 4.
How to write a CPI
Writing an instruction for a CPI follows the same pattern as building an instruction to add to a transaction. Under the hood, each CPI instruction must specify the following information:
- Program address: Specifies the program being invoked
- Accounts: Lists every account the instruction reads from or writes to, including other programs
- Instruction Data: Specifies which instruction on the program to invoke, plus any additional data required by the instruction (function arguments)
Depending on the program you are making the call to, there may be crates
available with helper functions for building the instruction. Programs then
execute CPIs using either one of the following functions from the
solana_program
crate:
invoke
- used when there are no PDA signersinvoke_signed
- used when the caller program needs to sign with a PDA derived from its program ID
Basic CPI
The
invoke
function is used when making a CPI that does not require PDA signers. When
making CPIs, signers provided to the caller program automatically extend to the
callee program.
pub fn invoke(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>]
) -> Result<(), ProgramError>
Here is an example program on
Solana Playground
that makes a CPI using the invoke
function to call the transfer instruction on
the System Program. You can also reference the
Basic CPI guide for further details.
CPI with PDA Signer
The
invoke_signed
function is used when making a CPI that requires PDA signers. The seeds used to
derive the signer PDAs are passed into the invoke_signed
function as
signer_seeds
.
You can reference the Program Derived Address page for details on how PDAs are derived.
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>],
signers_seeds: &[&[&[u8]]]
) -> Result<(), ProgramError>
The runtime uses the privileges granted to the caller program to determine what privileges can be extended to the callee. Privileges in this context refer to signers and writable accounts. For example, if the instruction the caller is processing contains a signer or writable account, then the caller can invoke an instruction that also contains that signer and/or writable account.
While PDAs have no private keys, they can
still act as a signer in an instruction via a CPI. To verify that a PDA is
derived from the calling program, the seeds used to generate the PDA must be
included as signers_seeds
.
When the CPI is processed, the Solana runtime
internally calls create_program_address
using the signers_seeds
and the program_id
of the calling program. If a
valid PDA is found, the address is
added as a valid signer.
Here is an example program on
Solana Playground
that makes a CPI using the invoke_signed
function to call the transfer
instruction on the System Program with a PDA signer. You can reference the
CPI with PDA Signer guide
for further details.