网站Logo 北之屿

🧩Solana Anchor 中的 PDA 计算与账户关系解析

beiyu
29
2025-08-08

什么是 PDA?

PDA(Program Derived Address)是一种由 seeds + programId 唯一确定的账户地址。
它保证:

  • 地址是确定性的(同样 seeds 每次生成相同地址);

  • 地址是不可签名的(没有私钥对应);

  • 只能由程序自己通过 invoke_signed 访问。

寻找程序的入口点

在阅读 Anchor IDL 或合约代码时,可以先搜索以下关键词来确定程序入口与账户初始化逻辑:

  • initialize

  • init

  • create

这些函数通常是账户的初始化指令,会在其中定义 PDA 的计算方式与账户关系。

分析账户定义模式

在 IDL 文件中,可以看到账户字段带有 pda 描述,例如:

{
  "name": "market",
  "writable": true,
  "pda": {
    "seeds": [
      {
        "kind": "const",
        "value": [109, 97, 114, 107, 101, 116]
      },
      {
        "kind": "arg",
        "path": "args.symbol"
      },
      {
        "kind": "account",
        "path": "config"
      }
    ]
  }
}

这部分描述了 PDA 的生成方式:
由常量、函数参数、以及其他账户地址组成。

实际计算方式

使用findProgramAddressSync方法

const [marketPDA,] = PublicKey.findProgramAddressSync(
  [
    Buffer.from([109, 97, 114, 107, 101, 116]),  // 常量 "market"
    Buffer.from("TTYL"),                         // 代币符号
    configAddress.toBuffer()                     // config 地址
  ],
  programId
);

通过Relations计算地址

当账户有 relations 字段时,说明它与其他账户存在从属关系。
这意味着:你可以通过关联账户的数据,推导出这个账户的地址。

📘 IDL 示例

{
  "name": "native_vault",
  "writable": true,
  "relations": ["market"]
}

计算步骤,通过fetch获取账户数据

// 1. 通过pda计算出 marketPDA
const [marketPDA] = PublicKey.findProgramAddressSync(
    [
        Buffer.from("market"),
        Buffer.from("TTYL"),
        configPublicKey.toBuffer()  //已知的地址
    ],
    programId
);
// 2. 获取关联账户数据
const marketAccount = await program.account.market.fetch(marketPDA);

// 1. 从账户数据中读取地址
const nativeVault = marketAccount.nativeVault;
const tokenVault = marketAccount.tokenVault;
const communityVault = marketAccount.communityVault;

关于fetch的讲解详细请查看:Anchor 中的账户读取

MarketAccount 的结构示例

marketAccount 对应的数据结构

{
  "name": "Market",
  "type": {
    "kind": "struct", 
    "fields": [
      {
        "name": "config",
        "type": "pubkey"
      },
      {
        "name": "token_mint",
        "type": "pubkey"
      },
      {
        "name": "token_vault",
        "type": "pubkey"
      },
      {
        "name": "native_vault", 
        "type": "pubkey"
      },
      {
        "name": "community_vault",
        "type": "pubkey"
      }
    ]
  }
}

💡 每个字段都是一个 PublicKey,可直接 .toBase58() 输出。

计算代币ATA地址

// 提供代币地址和钱包地址
// 查看idl中所需的TOKEN_PROGRAM_ID和ASSOCIATED_TOKEN_PROGRAM_ID是什么

const tokenRecipientPDA = await getAssociatedTokenAddress(
  new PublicKey(tokenMint),  //代币地址
  payer,   // 用户钱包地址PublicKey对象钱包
  false, // allowOwnerOffCurve
  TOKEN_PROGRAM_ID,   //这里可指定程序
  ASSOCIATED_TOKEN_PROGRAM_ID   // 这里也能指定
);

动物装饰