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.

Cross Program InvocationCross Program Invocation

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 signers
  • invoke_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.