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.