Skip to main content

What is a Cross Program Invocation (CPI) on Solana?

Updated on
Sep 8, 2023

5 min read

Overview

In Solana programming, a Cross Program Invocation (CPI) is a way to invoke a function in another Solana program. CPIs are a powerful tool that allows you to build more complex programs by enabling you to call functions from other programs. In this guide, you will learn what CPIs are, how to use them, and how to create them using Anchor.

Recommended Prereading: Solana Fundamentals Reference Guide

Why are CPIs are important?

CPIs allow any program on Solana to call another--this is extremely valuable in making programs more composable. Composability is effectively the ability to combine different programs (or elements from other programs) to create unique value for end users. Using a CPI is like calling an API method but within your program--it is targeted to a specific instruction for a specific program. There are endless possibilities of how you might use a CPI, but here are a few examples:

  • an NFT staking program that calls a CPI to the Solana SPL token program to mint fungible tokens as rewards for staking NFTs
  • a game program that calls a CPI to a program that allows users to swap tokens (in-game assets)
  • a lending protocol that calls a CPI to a liquidity provider program to borrow tokens

These are just a few examples; do not constrain yourself to these. CPIs are a powerful tool that can be used in many ways to create unique value for end users.

How to Use CPIs

To use a CPI, you will need the following information:

  • an assembled instruction for the program you want to call, which includes:
    • the program ID of the program you want to call
    • the accounts required by the instruction
    • any data required by the instruction
  • account infos for the accounts required by the instruction
  • seeds to derive the required PDA (if approving the transaction with a PDA)

Calling a CPI requires the use of the Solana crate's invoke or invoke_signed methods to make CPIs:


  • invoke is used when the transaction is being signed by the original transaction signer (not a PDA)
pub fn invoke(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>]
) -> ProgramResult

  • invoke_signed is used when the transaction is being signed by a PDA (generated by passing the correct seeds)
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo<'_>],
signers_seeds: &[&[&[u8]]]
) -> ProgramResult

Here's an example of a CPI using the invoke method:

use borsh::BorshDeserialize;
use cross_program_invocatio_native_lever::SetPowerStatus;
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
instruction::{AccountMeta, Instruction},
program::invoke,
pubkey::Pubkey,
};

entrypoint!(pull_lever);

fn pull_lever(
_program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();
let power = next_account_info(accounts_iter)?;
let lever_program = next_account_info(accounts_iter)?;

let set_power_status_instruction = SetPowerStatus::try_from_slice(instruction_data)?;

let ix = Instruction::new_with_borsh(
*lever_program.key, // Our lever program's ID
&set_power_status_instruction, // Passing instructions through
vec![AccountMeta::new(*power.key, false)], // Just the required account for the other program
);

invoke(&ix, &[power.clone()])
}

Source: Solana Developer's Example Programs

In this example, a pull_lever function defines an instruction, ix, that passes the lever_program program public key, the instruction data (set_power_status_instruction), and a vector of account metas (in this case, just the power account). The invoke method is then called to execute the CPI with the instruction and the power account.

Using Anchor to Create CPIs

Though you can create CPIs in Anchor using the same approach we outlined above, Anchor makes CPIs even easier. Much like Anchor relies on Context to simplify calling accounts for a program, Anchor also uses a CpiContext struct to streamline the process of calling CPIs. A CpiContext can be created using the new or new_with_signer methods (if using a PDA):

// If using the transaction signer(s) 
CpiContext::new(cpi_program, cpi_accounts)
// If using a PDA
CpiContext::new_with_signer(cpi_program, cpi_accounts, seeds)

The cpi_program is the program ID of the program you want to call, and the cpi_accounts are the accounts required by the instruction. If you are using a PDA, you must also pass the seeds to derive the PDA.

Once a CpiContext is created, you can call a CPI the program's imported method:

use program_to_call;
program_to_call::function_to_call(cpi_context, instruction_data)

Here's how that same pull_lever instruction could look using Anchor:

use lever::cpi::accounts::SetPowerStatus;
use lever::program::Lever;
use lever::{self, PowerStatus};
//...
pub fn pull_lever(ctx: Context<PullLever>, name: String) -> anchor_lang::Result<()> {
lever::cpi::switch_power(
CpiContext::new(
ctx.accounts.lever_program.to_account_info(),
SetPowerStatus {
power: ctx.accounts.power.to_account_info(),
},
),
name,
)
}

*Source: Solana Developer's Example Programs

We use the switch_power method on the lever::cpi module to call the CPI. This method takes a CpiContext and the instruction data (name in this case).

One important thing to note in Anchor is that we need to add the lever program to our Cargo.toml file as a dependency. We will use the features = ["cpi"] to use not only the lever's types but also its instruction builders and cpi functions. By enabling the cpi feature, the hand program gets access to the lever::cpi module. Anchor generates this module automatically, containing tailor-made instructions builders and CPI helpers for the program.

[dependencies]
anchor-lang = "0.28.0"
lever = { path = "../lever", features = ["cpi"] }

That's it! If you want to build an example program with CPIs, check out our Guide: How to Transfer SOL and SPL Tokens Using Anchor, which will walk you through using a CPI to the SPL token program to transfer tokens. You can also check out the Resources and Examples section below for more examples.

Wrap Up

CPIs are a great way to make your program more composable and create unique value for end users. You now have the knowledge and tools to build something great! If you're stuck, have questions, or just want to talk about what you're building, drop us a line on Discord or Twitter!

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Resources and Examples

Share this guide