Cross Program Invocation
In this section, the CRUD program from the previous PDA section gets updated by adding Cross Program Invocations (CPIs), a feature that enables Solana programs to invoke each other.
This tutorial also shows how programs can "sign" for Program Derived Addresses (PDAs) when making Cross Program Invocations.
The update
and delete
instructions need modification to handle SOL transfers
between accounts by invoking the System Program.
The purpose of this section includes walking through the process of implementing CPIs in a Solana program using the Anchor framework, building upon the PDA concepts explored in the previous section. For more details, refer to the Cross Program Invocation page.
For reference, this link includes the final code after completing both the PDA and CPI sections.
The starter code for this section includes just the PDA section completed.
Update the Update Instruction
First, the program needs a simple "pay-to-update" mechanism by changing the
Update
struct and update
function.
Begin by updating the lib.rs
file to bring into scope items from the
system_program
module.
use anchor_lang::system_program::{transfer, Transfer};
Next, update the Update
struct to include a new account called
vault_account
. This account, controlled by the program, receives SOL from a
user when they update their message account.
#[account(mut,seeds = [b"vault", user.key().as_ref()],bump,)]pub vault_account: SystemAccount<'info>,
Next, add the CPI logic in the update
instruction to transfer 0.001 SOL from
the user's account to the vault account.
let transfer_accounts = Transfer {from: ctx.accounts.user.to_account_info(),to: ctx.accounts.vault_account.to_account_info(),};let cpi_context = CpiContext::new(ctx.accounts.system_program.to_account_info(),transfer_accounts,);transfer(cpi_context, 1_000_000)?;
Rebuild the program.
$build
Update the Delete Instruction
Now add a "refund on delete" mechanism by changing the Delete
struct and
delete
function.
First, update the Delete
struct to include the vault_account
. This
allows the transfer of any SOL in the vault back to the user when they close
their message account.
#[account(mut,seeds = [b"vault", user.key().as_ref()],bump,)]pub vault_account: SystemAccount<'info>,
Also add the system_program
as the CPI for the transfer requires invoking the
System Program.
pub system_program: Program<'info, System>,
Next, add the CPI logic in the delete
instruction to transfer SOL from the
vault account back to the user's account.
let user_key = ctx.accounts.user.key();let signer_seeds: &[&[&[u8]]] =&[&[b"vault", user_key.as_ref(), &[ctx.bumps.vault_account]]];let transfer_accounts = Transfer {from: ctx.accounts.vault_account.to_account_info(),to: ctx.accounts.user.to_account_info(),};let cpi_context = CpiContext::new(ctx.accounts.system_program.to_account_info(),transfer_accounts,).with_signer(signer_seeds);transfer(cpi_context, ctx.accounts.vault_account.lamports())?;
Note that _ctx: Context<Delete>
changes to ctx: Context<Delete>
to
use the context in the body of the function.
Rebuild the program.
$build
Redeploy Program
After making these changes, redeploy the updated program. This ensures the modified program becomes available for testing. On Solana, updating a program simply requires deploying the program at the same program ID.
Ensure your Playground wallet has devnet SOL. Get devnet SOL from the Solana Faucet.
$deploy
Update Test File
Next, update the anchor.test.ts
file to include the new vault account in the
instructions. This requires deriving the vault PDA and including it in the
update and delete instruction calls.
Derive Vault PDA
First, add the vault PDA derivation:
const [vaultPda, vaultBump] = PublicKey.findProgramAddressSync([Buffer.from("vault"), wallet.publicKey.toBuffer()],program.programId,);
Change Update Test
Then, update the update instruction to include the vaultAccount
const transactionSignature = await program.methods.update(message).accounts({messageAccount: messagePda,vaultAccount: vaultPda,}).rpc({ commitment: "confirmed" });
Change Delete Test
Then, update the delete instruction to include the vaultAccount
const transactionSignature = await program.methods.delete().accounts({messageAccount: messagePda,vaultAccount: vaultPda,}).rpc({ commitment: "confirmed" });
Rerun Test
After making these changes, run the tests to ensure everything works as expected:
$test
You can then inspect the SolanaFM links to view the transaction details, where you'll find the CPIs for the transfer instructions within the update and delete instructions.
Update CPI
Delete CPI
If you encounter any errors, you can reference the final code.
Next Steps
Congratulations on completing the Solana Quickstart guide. You've gained hands-on experience with key Solana concepts including:
- Fetching and reading data from accounts
- Building and sending transactions
- Deploying and updating Solana programs
- Working with Program Derived Addresses (PDAs)
- Making Cross-Program Invocations (CPIs)
To deepen your understanding of these concepts, check out the Core Concepts documentation which provides detailed explanations of the topics covered in this guide.
Explore More Examples
If you prefer learning by example, check out the Program Examples Repository for a variety of example programs.
Solana Playground offers a convenient feature allowing you to import or view projects using their GitHub links. For example, open this Solana Playground link to view the Anchor project from this Github repo.
Click the Import
button and enter a project name to add it to your list of
projects in Solana Playground. Once a project gets imported, all changes receive
automatic saving and persistence.
Is this page helpful?