PDA 账户

摘要

通过 invoke_signed 及 PDA 的 seed,可以创建 PDA 账户。只有 拥有该 PDA 的程序才能为其签名。Anchor 的 init 约束可自动化 PDA 账户的创建。

通过 invoke_signed 进行 PDA 签名

当程序需要在 CPI 期间代表 PDA 进行签名时,会使用 invoke_signed 及 PDA 的 seed。运行时会验证这些 seed 是否能根据调用程序的 ID推导出预期的 PDA,从而确保只有拥有该 PDA 的程序才能签名。完整的验证流程请参见 PDA 签名

创建 PDA 账户

推导 PDA 和在 PDA 上创建账户是两个独立的操作。在推导出地址后,必须显式创建该账户。

要在 PDA 上创建账户,推导程序需要调用 System Program 的 create_account 指令,并通过 invoke_signed 传递 PDA 的 seed,以便运行时验证程序对该地址的权限。

下面的示例使用 Anchor framework 通过程序派生地址创建新账户。该程序包含一个 initialize 指令用于创建新账户,该账户将存储用于推导 PDA 的 user addressbump seed

Program
use anchor_lang::prelude::*;
declare_id!("75GJVCJNhaukaa2vCCqhreY31gaphv7XTScBChmr1ueR");
#[program]
pub mod pda_account {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let account_data = &mut ctx.accounts.pda_account;
// store the address of the `user`
account_data.user = *ctx.accounts.user.key;
// store the canonical bump
account_data.bump = ctx.bumps.pda_account;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
// define the seeds to derive the PDA
seeds = [b"data", user.key().as_ref()],
// use the canonical bump
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE // 8 bytes for Anchor account discriminator
)]
pub pda_account: Account<'info, DataAccount>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct DataAccount {
pub user: Pubkey,
pub bump: u8,
}

init 约束会告知 Anchor 调用 System Program 使用 PDA 作为地址创建新账户。用于创建 PDA 的 seed 包括:

  • 固定字符串:"data"
  • 指令中提供的用户账户地址
  • 标准的 bump seed

在此示例中,bump 约束未被赋值,因此 Anchor 会使用 find_program_address 来推导 PDA 并查找 bump。

pda_account
#[account(
init,
seeds = [b"data", user.key().as_ref()],
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE // 8 bytes for Anchor account discriminator
)]
pub pda_account: Account<'info, DataAccount>,

下面的测试文件包含一个交易,该交易会调用 initialize 指令,以使用程序派生地址创建新账户。该文件还包含 推导 PDA 的代码。

该示例还展示了如何 fetch 即将创建的新账户。

Test
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PdaAccount } from "../target/types/pda_account";
import { PublicKey } from "@solana/web3.js";
describe("pda-account", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.PdaAccount as Program<PdaAccount>;
const user = provider.wallet as anchor.Wallet;
// Derive the PDA address using the seeds specified on the program
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});
});

如果你再次使用相同的 initialize 指令和相同的 user 地址 seed 调用, 该交易将会失败。原因是派生地址上已经存在一个账户。

Is this page helpful?

Table of Contents

Edit Page

管理者

©️ 2026 Solana 基金会版权所有
取得联系