Cross-Program Invocation (CPI)
Cross-Program Invocation (CPI) lets one Arch program invoke another program during transaction execution. Use CPI for composition, shared token operations, and PDA-authorized workflows.
Basic CPI
Use arch_program::program::invoke with a real Instruction and the accounts required by the target program.
use arch_program::{
account::AccountInfo,
entrypoint::ProgramResult,
program::invoke,
};
pub fn invoke_token_transfer(
source: &AccountInfo,
destination: &AccountInfo,
authority: &AccountInfo,
amount: u64,
) -> ProgramResult {
let ix = apl_token::instruction::transfer(
&apl_token::id(),
source.key,
destination.key,
authority.key,
&[],
amount,
)?;
invoke(&ix, &[source.clone(), destination.clone(), authority.clone()])
}For APL token transfers, build the instruction through apl_token::instruction so the instruction data and account order match the token program.
CPI with PDA Signers
Use invoke_signed when the authority is a PDA owned by the calling program. Signer seeds are passed as a slice of signer groups: &[&[&[u8]]].
use arch_program::{
account::AccountInfo,
entrypoint::ProgramResult,
program::invoke_signed,
pubkey::Pubkey,
};
pub fn invoke_with_vault_authority(
program_id: &Pubkey,
vault_authority: &AccountInfo,
source: &AccountInfo,
destination: &AccountInfo,
amount: u64,
) -> ProgramResult {
let (vault_pda, bump) = Pubkey::find_program_address(&[b"vault"], program_id);
if vault_pda != *vault_authority.key {
return Err(arch_program::program_error::ProgramError::InvalidSeeds);
}
let ix = apl_token::instruction::transfer(
&apl_token::id(),
source.key,
destination.key,
vault_authority.key,
&[],
amount,
)?;
let signer_seeds: &[&[&[u8]]] = &[&[b"vault", &[bump]]];
invoke_signed(
&ix,
&[source.clone(), destination.clone(), vault_authority.clone()],
signer_seeds,
)
}Account Creation and Rent
For account creation flows, use system instructions from arch_program::system_instruction. Compute rent with arch_program::rent::minimum_rent(space) instead of Solana sysvar patterns.
use arch_program::{
pubkey::Pubkey,
rent::minimum_rent,
system_instruction,
};
pub fn create_account_ix(
payer: &Pubkey,
new_account: &Pubkey,
owner: &Pubkey,
space: u64,
) -> arch_program::instruction::Instruction {
system_instruction::create_account(
payer,
new_account,
minimum_rent(space as usize),
space,
owner,
)
}Client-Side Considerations
- Include every account the target program expects, in the order expected by that program.
- Reproduce PDA seeds and bumps exactly on the client and on-chain.
- Use target-program instruction builders where available instead of manually packing bytes.
- Keep instruction data small enough for transaction limits.
Troubleshooting
InvalidSeeds: PDA mismatch between client and program.MissingRequiredSignature: A required signer was not present or signer seeds were wrong.IncorrectProgramId: An account is not owned by the expected program.- Size or compute errors: split large workflows into smaller transactions.