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

基于Solana Anchor框架实现双人竞猜合约的技术咨询

双人SOL竞猜合约实现方案(Anchor框架)

核心逻辑拆解

要实现你需要的功能,核心是用**PDA(程序派生账户)**作为SOL的托管容器,分两个阶段处理:

  • 存入阶段:两名玩家分别向PDA转入等额SOL,合约记录存入状态
  • 结算阶段:触发条件满足后,将PDA内的总SOL转给指定获胜者

具体实现步骤

1. 定义游戏状态结构体

在Anchor程序里定义GameAccount,用来存储游戏关键信息:

  • 约定的单份投注金额
  • 两名玩家的公钥
  • 双方的存入状态
  • 获胜者公钥
  • 游戏是否已结算

2. 初始化游戏指令

创建PDA作为托管账户,初始化游戏状态,这里用玩家A的公钥作为PDA种子的一部分,确保每个游戏实例唯一。

3. 玩家存入指令

  • 校验游戏未结算、当前玩家未存入
  • 若玩家是未登记的第二名玩家,自动登记其公钥
  • 通过CPI调用系统程序,将玩家的SOL转入PDA
  • 更新对应玩家的存入状态

4. 结算指令

  • 校验游戏未结算且双方已完成存入
  • 校验获胜者是两名玩家之一
  • 将PDA内的所有SOL(两份投注金额之和)转给获胜者
  • 标记游戏已结算

关键代码片段

use anchor_lang::prelude::*;

declare_id!("YourProgramIDHere");

#[program]
pub mod two_player_bet {
    use super::*;

    // 初始化游戏,玩家A发起,指定投注金额
    pub fn initialize(ctx: Context<Initialize>, bet_amount: u64) -> Result<()> {
        let game = &mut ctx.accounts.game;
        game.bet_amount = bet_amount;
        game.player_a = ctx.accounts.player.key();
        game.player_b = Pubkey::default();
        game.a_deposited = false;
        game.b_deposited = false;
        game.winner = Pubkey::default();
        game.settled = false;
        Ok(())
    }

    // 玩家存入SOL
    pub fn deposit(ctx: Context<Deposit>) -> Result<()> {
        let game = &mut ctx.accounts.game;
        let player = ctx.accounts.player.key();

        // 基础状态校验
        require!(!game.settled, GameError::GameSettled);
        
        // 处理玩家身份
        if player == game.player_a {
            require!(!game.a_deposited, GameError::AlreadyDeposited);
            game.a_deposited = true;
        } else if game.player_b == Pubkey::default() {
            game.player_b = player;
            game.b_deposited = true;
        } else if player == game.player_b {
            require!(!game.b_deposited, GameError::AlreadyDeposited);
            game.b_deposited = true;
        } else {
            return err!(GameError::InvalidPlayer);
        }

        // 转账SOL到PDA
        anchor_lang::system_program::transfer(
            CpiContext::new(
                ctx.accounts.system_program.to_account_info(),
                anchor_lang::solana_program::system_instruction::transfer(
                    &player,
                    &game.key(),
                    game.bet_amount,
                ),
            ),
        )?;

        Ok(())
    }

    // 结算游戏,转总金额给获胜者
    pub fn settle(ctx: Context<Settle>, winner: Pubkey) -> Result<()> {
        let game = &mut ctx.accounts.game;

        // 校验条件
        require!(!game.settled, GameError::GameSettled);
        require!(game.a_deposited && game.b_deposited, GameError::NotAllDeposited);
        require!(winner == game.player_a || winner == game.player_b, GameError::InvalidWinner);

        // 转移总金额给获胜者
        let total = game.bet_amount * 2;
        let game_lamports = &mut ctx.accounts.game.to_account_info().lamports.borrow_mut();
        let winner_lamports = &mut ctx.accounts.winner.lamports.borrow_mut();
        
        *game_lamports -= total;
        *winner_lamports += total;

        // 更新状态
        game.winner = winner;
        game.settled = true;
        Ok(())
    }
}

// 账户结构体定义
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = player,
        space = 8 + 8 + 32 + 32 + 1 + 1 + 32 + 1,
        seeds = [b"bet", player.key().as_ref()],
        bump
    )]
    pub game: Account<'info, GameAccount>,
    #[account(mut)]
    pub player: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Deposit<'info> {
    #[account(mut, seeds = [b"bet", game.player_a.as_ref()], bump)]
    pub game: Account<'info, GameAccount>,
    #[account(mut)]
    pub player: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Settle<'info> {
    #[account(mut, seeds = [b"bet", game.player_a.as_ref()], bump)]
    pub game: Account<'info, GameAccount>,
    #[account(mut)]
    pub winner: AccountInfo<'info>,
}

#[account]
pub struct GameAccount {
    pub bet_amount: u64,       // 单份投注金额(Lamports)
    pub player_a: Pubkey,      // 玩家A公钥
    pub player_b: Pubkey,      // 玩家B公钥
    pub a_deposited: bool,     // 玩家A是否已存入
    pub b_deposited: bool,     // 玩家B是否已存入
    pub winner: Pubkey,        // 获胜者公钥
    pub settled: bool,         // 游戏是否已结算
}

// 错误码定义
#[error_code]
pub enum GameError {
    #[msg("游戏已结算")]
    GameSettled,
    #[msg("该玩家已完成存入")]
    AlreadyDeposited,
    #[msg("无效玩家")]
    InvalidPlayer,
    #[msg("未完成全部存入")]
    NotAllDeposited,
    #[msg("无效获胜者")]
    InvalidWinner,
}

重点注意事项

  • PDA安全性:必须用固定的seeds生成PDA,确保只有合约能控制该账户的资金
  • 状态校验:每个指令都要严格校验游戏状态,防止重复存入、提前结算等非法操作
  • 转账处理:直接操作账户的lamports时要确保金额计算正确,或者用Anchor提供的transferCPI宏简化操作
  • 权限控制:如果结算需要特定权限(比如裁判、或者双方签名确认),可以在Settle账户结构体中添加对应的签名者校验

学习资源推荐

  • Anchor官方文档:重点研读PDA派生账户和CPI调用章节,理解账户初始化、签名验证的底层逻辑
  • Solana官方编程模型:掌握Solana账户模型、Lamport转移的核心规则
  • Anchor官方示例项目:从basic-0到basic-4的基础示例,学习状态管理和指令编写的规范

内容的提问来源于stack exchange,提问作者dev1800

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 15:36:11