You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solana Anchor PDA托管SOL功能开发及unknown signer报错排查

问题排查与修复方案

报错直接原因

unknown signer报错是测试代码调用RPC接口时,未传入账户结构要求的必填签名者账户导致的,同时Rust程序逻辑和测试代码都存在多处不合理的地方,具体如下:

一、Rust程序存在的问题

  • AddGif账户结构体的#[instruction]宏参数和add_gif函数入参顺序、数量完全不匹配,会导致Anchor推导PDA时参数校验失败
  • 硬编码PDA的bump值为5,不符合PDA生成规则,PDA的bump必须由链上计算得出,不能硬编码
  • add_result、place_bet两个函数错误复用了AddGif账户结构,AddGif中的pool_wallet标记为init模式,只有首次创建池的时候需要初始化,后续调用这两个函数时会重复尝试初始化已存在的账户,直接报错

Rust代码修复示例

  1. 修正AddGif账户结构
#[derive(Accounts)]
// 入参顺序和add_gif函数保持完全一致
#[instruction(image_link: String, pool_name: String, pool_desc: String, win_opt:String, close: u32, verify:String, fee:u8)]
pub struct AddGif<'info> {
    #[account(mut)]
    pub base_account: Account<'info, BaseAccount>,
    // 去掉硬编码的bump=5,用bump关键字让Anchor自动计算
    #[account(init, seeds=[pool_name.as_bytes(),b"pool_wallet"], space=9000, bump, payer=user)]
    pub pool_wallet: Account<'info, PoolWallet>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>
}
  1. 新增独立的账户结构给add_result和place_bet使用
#[derive(Accounts)]
pub struct AddResult<'info> {
    #[account(mut)]
    pub base_account: Account<'info, BaseAccount>,
    // 要求调用者签名,后续可以加校验判断是否为池所有者
    pub user: Signer<'info>,
}

#[derive(Accounts)]
pub struct PlaceBet<'info> {
    #[account(mut)]
    pub base_account: Account<'info, BaseAccount>,
    /// CHECK: 后续可增加校验逻辑确保是对应池的托管地址
    #[account(mut)]
    pub pool_wallet: UncheckedAccount<'info>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}
  1. 修改对应函数的上下文参数
pub fn add_result(ctx: Context<AddResult>,result:String, pool_id:u32 ) -> ProgramResult {
    // 原有逻辑不变
}

pub fn place_bet(ctx: Context<PlaceBet>, pred: String, pool_id:u32, stake_bal:u32, user:String) -> ProgramResult {
    // 原有逻辑不变
}

二、JS测试代码存在的问题

  • 错误生成普通密钥对作为PDA托管地址:PDA是程序派生地址,没有对应私钥,不能用Keypair.generate()生成,需要用findProgramAddress推导
  • 后续RPC调用缺少必填账户:第二次调用addGif、调用addResult、placeBet时,只传了baseAccount,没有传入要求的user、pool_wallet、system_program等必填账户,直接触发签名者缺失错误

JS测试代码修复示例

  1. 调用addGif前先推导对应池的PDA地址
// 第一个池的PDA推导,seed和Rust端保持一致:poolName的字节 + b"pool_wallet"
const [poolWalletPDA1] = await anchor.web3.PublicKey.findProgramAddress(
  [Buffer.from("Test Pool"), Buffer.from("pool_wallet")],
  program.programId
);
// 调用addGif时传入推导得到的PDA,不需要添加到签名者列表
await program.rpc.addGif("insert image link here", "Test Pool","This is a test Pool", "option 1; option 2", 9897, "Verify here", 5,{
  accounts: {
    baseAccount: baseAccount.publicKey,
    poolWallet: poolWalletPDA1,
    user: provider.wallet.publicKey,
    systemProgram: SystemProgram.programId
  },
  signers: [baseAccount],
});

// 第二个池同理推导对应PDA再调用
const [poolWalletPDA2] = await anchor.web3.PublicKey.findProgramAddress(
  [Buffer.from("Test Pool 2"), Buffer.from("pool_wallet")],
  program.programId
);
await program.rpc.addGif("insert image link here 3", "Test Pool 2","This is a test Pool 2", "2option 1; 2option 2", 789897, "2Verify here", 2,{
  accounts: {
    baseAccount: baseAccount.publicKey,
    poolWallet: poolWalletPDA2,
    user: provider.wallet.publicKey,
    systemProgram: SystemProgram.programId
  },
  signers: [baseAccount],
});
  1. 补全addResult和placeBet的必填账户
await program.rpc.addResult("winner 1",0,{
  accounts: {
    baseAccount: baseAccount.publicKey,
    user: provider.wallet.publicKey
  }
});

// placeBet需要传入对应池的PDA地址
await program.rpc.placeBet("winner 1",1,125,"user",  {
  accounts: {
    baseAccount: baseAccount.publicKey,
    poolWallet: poolWalletPDA2, // 对应pool_id=1的池的PDA
    user: provider.wallet.publicKey,
    systemProgram: SystemProgram.programId
  }
})

内容的提问来源于stack exchange,提问作者Joby Ingram-Dodd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 23:15:01