CPI avec signataires PDA

Résumé

Utilisez invoke_signed lorsque le programme appelant doit signer au nom d'un PDA qu'il possède. Le runtime dérive les clés publiques PDA à partir des graines de signataire fournies et les ajoute à l'ensemble des signataires valides avant la vérification des privilèges.

CPI avec signataires PDA

Lorsqu'un CPI nécessite un signataire PDA, utilisez invoke_signed avec les graines de signataire utilisées pour dériver le PDA. Pour plus de détails sur la façon dont le runtime vérifie les signatures PDA, consultez Signature PDA.

Invoke signed
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
// --snip--
invoke_signed_unchecked(instruction, account_infos, signers_seeds)
}

Les exemples ci-dessous effectuent un CPI avec des signataires PDA en utilisant Anchor et Rust natif. Chaque exemple inclut une seule instruction pour transférer des SOL d'un PDA vers un compte destinataire, en utilisant un CPI signé par le PDA.

Anchor

Les exemples suivants montrent deux approches pour implémenter des CPI dans un programme Anchor. Les exemples sont fonctionnellement équivalents, mais démontrent différents niveaux d'abstraction.

  • Exemple 1 : utilise le CpiContext d'Anchor et une fonction auxiliaire.
  • Exemple 2 : utilise la fonction system_instruction::transfer de la crate solana_program. (L'exemple 1 est une abstraction de cette implémentation.)
  • Exemple 3 : construit l'instruction CPI manuellement. Cette approche est utile lorsqu'il n'y a pas de crate disponible pour aider à construire l'instruction que vous souhaitez invoquer.
use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer};
declare_id!("BrcdB9sV7z9DvF9rDHG263HUxXgJM3iCQdF36TcxbFEn");
#[program]
pub mod cpi {
use super::*;
pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {
let from_pubkey = ctx.accounts.pda_account.to_account_info();
let to_pubkey = ctx.accounts.recipient.to_account_info();
let program_id = ctx.accounts.system_program.to_account_info();
let seed = to_pubkey.key();
let bump_seed = ctx.bumps.pda_account;
let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];
let cpi_context = CpiContext::new(
program_id,
Transfer {
from: from_pubkey,
to: to_pubkey,
},
)
.with_signer(signer_seeds);
transfer(cpi_context, amount)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct SolTransfer<'info> {
#[account(
mut,
seeds = [b"pda", recipient.key().as_ref()],
bump,
)]
pda_account: SystemAccount<'info>,
#[account(mut)]
recipient: SystemAccount<'info>,
system_program: Program<'info, System>,
}

Rust

L'exemple ci-dessous effectue un CPI avec des signataires PDA depuis un programme écrit en Rust natif. Il comprend une seule instruction qui transfère des SOL d'un compte PDA vers un autre. Le CPI est signé par le compte PDA. (Le fichier de test utilise LiteSVM pour tester le programme.)

use borsh::BorshDeserialize;
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
system_instruction,
};
// Declare program entrypoint
entrypoint!(process_instruction);
// Define program instructions
#[derive(BorshDeserialize)]
enum ProgramInstruction {
SolTransfer { amount: u64 },
}
impl ProgramInstruction {
fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
Self::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)
}
}
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// Deserialize instruction data
let instruction = ProgramInstruction::unpack(instruction_data)?;
// Process instruction
match instruction {
ProgramInstruction::SolTransfer { amount } => {
// Parse accounts
let [pda_account_info, recipient_info, system_program_info] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Derive PDA and verify it matches the account provided by client
let recipient_pubkey = recipient_info.key;
let seeds = &[b"pda", recipient_pubkey.as_ref()];
let (expected_pda, bump_seed) = Pubkey::find_program_address(seeds, program_id);
if expected_pda != *pda_account_info.key {
return Err(ProgramError::InvalidArgument);
}
// Create the transfer instruction
let transfer_ix = system_instruction::transfer(
pda_account_info.key,
recipient_info.key,
amount,
);
// Create signer seeds for PDA
let signer_seeds: &[&[&[u8]]] = &[&[b"pda", recipient_pubkey.as_ref(), &[bump_seed]]];
// Invoke the transfer instruction with PDA as signer
invoke_signed(
&transfer_ix,
&[
pda_account_info.clone(),
recipient_info.clone(),
system_program_info.clone(),
],
signer_seeds,
)?;
Ok(())
}
}
}

Is this page helpful?

Table des matières

Modifier la page

Géré par

© 2026 Fondation Solana.
Tous droits réservés.
Restez connecté