CPI với PDA Signers

Tóm tắt

Sử dụng invoke_signed khi chương trình gọi cần ký thay mặt cho một PDA mà nó sở hữu. Runtime sẽ suy ra các pubkey PDA từ các signer seeds được cung cấp và thêm chúng vào tập hợp các signer hợp lệ trước khi kiểm tra quyền.

CPI với PDA signers

Khi một CPI yêu cầu PDA signer, hãy sử dụng invoke_signed với signer seeds được dùng để suy ra PDA. Để biết chi tiết về cách runtime xác minh chữ ký PDA, hãy xem PDA Signing.

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

Các ví dụ dưới đây thực hiện CPI với PDA signers bằng Anchor và Native Rust. Mỗi ví dụ bao gồm một instruction duy nhất để chuyển SOL từ một PDA đến một tài khoản người nhận, sử dụng CPI được ký bởi PDA.

Anchor

Các ví dụ sau đây trình bày hai cách tiếp cận để triển khai CPI trong một chương trình Anchor. Các ví dụ này tương đương về mặt chức năng, nhưng thể hiện các mức độ trừu tượng khác nhau.

  • Ví dụ 1: Sử dụng CpiContext và hàm helper của Anchor.
  • Ví dụ 2: Sử dụng hàm system_instruction::transfer từ crate solana_program. (Ví dụ 1 là một trừu tượng hóa của cách triển khai này.)
  • Ví dụ 3: Xây dựng instruction CPI thủ công. Cách tiếp cận này hữu ích khi không có crate nào có sẵn để giúp xây dựng instruction mà bạn muốn gọi.
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

Ví dụ dưới đây thực hiện CPI với PDA signers từ một chương trình được viết bằng Native Rust. Nó bao gồm một lệnh duy nhất chuyển SOL từ tài khoản PDA sang tài khoản khác. CPI được ký bởi tài khoản PDA. (Tệp kiểm thử sử dụng LiteSVM để kiểm thử chương trình.)

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?

Mục lục

Chỉnh sửa trang

Quản lý bởi

© 2026 Solana Foundation.
Đã đăng ký bản quyền.
Kết nối