CPIs with Anchor
Cross Program Invocations (CPI) refer to the process of one program invoking instructions of another program, which enables the composability of programs on Solana.
This section will cover the basics of implementing CPIs in an Anchor program, using a simple SOL transfer instruction as a practical example. Once you understand the basics of how to implement a CPI, you can apply the same concepts for any instruction.
Cross Program Invocations
Let's examine a program that implements a CPI to the System Program's transfer instruction. Here is the example program on Solana Playground.
The lib.rs
file includes a single sol_transfer
instruction. When the
sol_transfer
instruction on the Anchor program is invoked, the program
internally invokes the transfer instruction of the System Program.
The cpi.test.ts
file shows how to invoke the Anchor program's sol_transfer
instruction and logs a link to the transaction details on SolanaFM.
You can build, deploy, and run the test for this example on Playground to view the transaction details on the SolanaFM explorer.
The transaction details will show that the Anchor program was first invoked (instruction 1), which then invokes the System Program (instruction 1.1), resulting in a successful SOL transfer.
Transaction Details
Example 1 Explanation
Implementing a CPI follows the same pattern as building an instruction to add to a transaction. When implementing a CPI, we must specify the program ID, accounts, and instruction data for the instruction being called.
The System Program's transfer instruction requires two accounts:
from
: The account sending SOL.to
: The account receiving SOL.
In the example program, the SolTransfer
struct specifies the accounts required
by the transfer instruction. The System Program is also included because the CPI
invokes the System Program.
The following tabs present three approaches to implementing Cross Program Invocations (CPIs), each at a different level of abstraction. All examples are functionally equivalent. The main purpose is to illustrate the implementation details of the CPI.
The sol_transfer
instruction included in the example code shows a typical
approach for constructing CPIs using the Anchor framework.
This approach involves creating a
CpiContext
,
which includes the program_id
and accounts required for the instruction being
called, followed by a helper function (transfer
) to invoke a specific
instruction.
The cpi_context
variable specifies the program ID (System Program) and
accounts (sender and recipient) required by the transfer instruction.
The cpi_context
and amount
are then passed into the transfer
function to
execute the CPI invoking the transfer instruction of the System Program.
Here is a reference program on Solana Playground which includes all 3 examples.
Cross Program Invocations with PDA Signers
Next, let's examine a program that implements a CPI to the System Program's transfer instruction where the sender is a Program Derived Address (PDA) that must be "signed" for by the program. Here is the example program on Solana Playground.
The lib.rs
file includes the following program with a single sol_transfer
instruction.
The cpi.test.ts
file shows how to invoke the Anchor program's sol_transfer
instruction and logs a link to the transaction details on SolanaFM.
It shows how to derive the PDA using the seeds specified in the program:
The first step in this example is to fund the PDA account with a basic SOL transfer from the Playground wallet.
Once the PDA is funded with SOL, invoke the sol_transfer
instruction. This
instruction transfers SOL from the PDA account back to the wallet
account via
a CPI to the System Program, which is "signed" for by the program.
You can build, deploy, and run the test to view the transaction details on the SolanaFM explorer.
The transaction details will show that the custom program was first invoked (instruction 1), which then invokes the System Program (instruction 1.1), resulting in a successful SOL transfer.
Transaction Details
Example 2 Explanation
In the example code, the SolTransfer
struct specifies the accounts required by
the transfer instruction.
The sender is a PDA that the program must sign for. The seeds
to derive the
address for the pda_account
include the hardcoded string "pda" and the address
of the recipient
account. This means the address for the pda_account
is
unique for each recipient
.
The Javascript equivalent to derive the PDA is included in the test file.
The following tabs present two approaches to implementing Cross Program Invocations (CPIs), each at a different level of abstraction. Both examples are functionally equivalent. The main purpose is to illustrate the implementation details of the CPI.
The sol_transfer
instruction included in the example code shows a typical
approach for constructing CPIs using the Anchor framework.
This approach involves creating a
CpiContext
,
which includes the program_id
and accounts required for the instruction being
called, followed by a helper function (transfer
) to invoke a specific
instruction.
When signing with PDAs, the seeds and bump seed are included in the
cpi_context
as signer_seeds
using with_signer()
. The bump seed for a PDA
can be accessed using ctx.bumps
followed by the name of the PDA account.
The cpi_context
and amount
are then passed into the transfer
function to
execute the CPI.
When the CPI is processed, the Solana runtime will validate that the provided seeds and caller program ID derive a valid PDA. The PDA is then added as a signer on the invocation. This mechanism allows for programs to sign for PDAs that are derived from their program ID.
Here is a reference program on Solana Playground which includes both examples.