anchor 智能合约案例6 之 token_lottery

发布于:2025-07-23 ⋅ 阅读:(11) ⋅ 点赞:(0)

前言:

前面我们介绍了 solana 关于 anchor 智能合约环境搭建 配置,简单编写 发布 部署 调用 的一些落地。等等。接下来我们借着案例。详细剖析下智能合约编写。

 案例 token_lottery 介绍: 

这是一个关于抽奖的 anchor 智能合约。首先初始化 智能合约的一些配置(metadata collection)。然后用户 使用 SPL 进行抽奖 得到票据(nft)通过设置随机数 进行抽奖。最后根据合约的 获奖者  发送奖励; 视频链接 ,项目地址

 

initialize_config:
/**
    执行初始化配置
**/
pub fn initialize_config(
    ctx: Context<Initialize>,
    start: u64,
    end: u64,
    price: u64,
) -> Result<()> {
    //缓存推导 PAD 庄户的种子
    ctx.accounts.token_lottery.bump = ctx.bumps.token_lottery;
    ctx.accounts.token_lottery.start_time = start;
    ctx.accounts.token_lottery.end_time = end;
    ctx.accounts.token_lottery.ticket_price = price;
    ctx.accounts.token_lottery.authority = ctx.accounts.payer.key();
    ctx.accounts.token_lottery.lottery_pot_amount = 0;
    ctx.accounts.token_lottery.total_tickets = 0;
    // Pubkey::default() 是一个固定 全零的 32 字节公钥,固定值为 [0; 32],
    // 被初始化为全零公钥,表示尚未设置有效的所有者地址
    ctx.accounts.token_lottery.randomness_account = Pubkey::default();
    ctx.accounts.token_lottery.winner_chosen = false;

    Ok(())
}

//操作指令 关联PAD  TokenLottery 账户
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    #[account(
            init,
            payer = payer,
            space = 8 + TokenLottery::INIT_SPACE,
            seeds = [b"token_lottery".as_ref()],
            bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    pub system_program: Program<'info, System>,
}

#[account]
#[derive(InitSpace)]
pub struct TokenLottery {
    //缓存 种子,便于后期推导 PAD
    pub bump: u8,
    //胜利的 NFT 票据 ID
    pub winner: u64,
    //是否已经领取奖励
    pub winner_chosen: bool,
    //事件限制 开始时间
    pub start_time: u64,
    //事件限制 结束事件
    pub end_time: u64,
    //奖池
    pub lottery_pot_amount: u64,
    //参与价格
    pub ticket_price: u64,
    //一共参与票数
    pub total_tickets: u64,
    //授权 可以执行提交随机数 和 选出胜利者
    pub authority: Pubkey,
    //关联执行随机信息的账户
    pub randomness_account: Pubkey,
}
initialize_lottery:
/**
初始化 lottery,管理 ntf,
    创建 nft
    设置 nft metadata
**/
pub fn initialize_lottery(ctx: Context<InitializeLottery>) -> Result<()> {
    let signer_seeds: &[&[&[u8]]] =
        &[&[b"collection_mint".as_ref(), &[ctx.bumps.collection_mint]]];

    //铸造 nft
    msg!("Creating Mint Account");
    mint_to(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            MintTo {
                mint: ctx.accounts.collection_mint.to_account_info(),
                to: ctx.accounts.collection_mint_account.to_account_info(),
                authority: ctx.accounts.collection_mint.to_account_info(),
            },
            signer_seeds,
        ),
        1,
    )
    .expect("TODO: panic message");

    //设置nft 元数据
    msg!("Creating Metadata Account");
    create_metadata_accounts_v3(
        CpiContext::new_with_signer(
            ctx.accounts.metadata.to_account_info(),
            CreateMetadataAccountsV3 {
                metadata: ctx.accounts.metadata.to_account_info(),
                mint: ctx.accounts.collection_mint.to_account_info(),
                mint_authority: ctx.accounts.collection_mint.to_account_info(),
                payer: ctx.accounts.payer.to_account_info(),
                update_authority: ctx.accounts.collection_mint.to_account_info(),
                system_program: ctx.accounts.system_program.to_account_info(),
                rent: ctx.accounts.rent.to_account_info(),
            },
            signer_seeds,
        ),
        DataV2 {
            name: NAME.to_string(),
            symbol: SYMBOL.to_string(),
            uri: URI.to_string(),
            seller_fee_basis_points: 0,
            creators: Some(vec![Creator {
                address: ctx.accounts.collection_mint.key(),
                verified: false,
                share: 100,
            }]),
            collection: None,
            uses: None,
        },
        true,
        true,
        Some(CollectionDetails::V1 { size: 0 }),
    )?;

    //设置 nft 主 edition
    msg!("Create Master Edition Account");
    create_master_edition_v3(
        CpiContext::new_with_signer(
            ctx.accounts.token_metadata_program.to_account_info(),
            CreateMasterEditionV3 {
                edition: ctx.accounts.master_edition.to_account_info(),
                mint: ctx.accounts.collection_mint.to_account_info(),
                update_authority: ctx.accounts.collection_mint.to_account_info(),
                mint_authority: ctx.accounts.collection_mint.to_account_info(),
                payer: ctx.accounts.payer.to_account_info(),
                metadata: ctx.accounts.metadata.to_account_info(),
                token_program: ctx.accounts.token_program.to_account_info(),
                system_program: ctx.accounts.system_program.to_account_info(),
                rent: ctx.accounts.rent.to_account_info(),
            },
            &signer_seeds,
        ),
        Some(0),
    )?;

    msg!("Verifying Collection");
    sign_metadata(CpiContext::new_with_signer(
        ctx.accounts.token_metadata_program.to_account_info(),
        SignMetadata {
            creator: ctx.accounts.collection_mint.to_account_info(),
            metadata: ctx.accounts.metadata.to_account_info(),
        },
        &signer_seeds,
    ))?;
    Ok(())
}

#[derive(Accounts)]
pub struct InitializeLottery<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    //ntf 集合代币账户,只是个PDA账户没有存储功能 也没有 space, 并且权限设置为 自己本身
    #[account(
        init,
        payer = payer,
        mint::decimals = 0,
        mint::authority = collection_mint,
        mint::freeze_authority = collection_mint,
        seeds = [b"collection_mint".as_ref()],
        bump,
    )]
    pub collection_mint: InterfaceAccount<'info, Mint>,

    //nft 账户 只是 pad 关联 账户 不需要存储, 并且权限设置为 自己本身
    #[account(
        init,
        payer = payer,
        token::mint = collection_mint,
        token::authority = collection_mint_account,
        seeds = [b"collection_associated_token".as_ref()],
        bump,
    )]
    pub collection_mint_account: InterfaceAccount<'info, TokenAccount>,

    //nft 设置 metadata 关联  pad 账户 不需要存储,
    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    ///CHECK: 这个账户 将由 metadata 智能合约进行 检查,  "///CHECK:" 固定写法 要不然编译不通过
    pub metadata: UncheckedAccount<'info>,

    ///单一 NFT 账号配置,区别于 Normal Editions;支持单一NFT(Master Edition)或限量复制品(Normal Editions),适合不同类型的项目需求
    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
            b"edition"
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    ///CHECK: 这个账户 将由 metadata 智能合约进行 检查 "///CHECK:" 固定写法 要不然编译不通过
    pub master_edition: UncheckedAccount<'info>,

    //mint 元数据处理 系统账号
    pub token_metadata_program: Program<'info, Metadata>,
    //ATA associate_token处理 程序账号
    pub associate_token_program: Program<'info, AssociatedToken>,
    //ATA token处理 程序账号
    pub token_program: Interface<'info, TokenInterface>,
    //系统账号
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
}

buy_ticket: 
pub fn buy_ticket(ctx: Context<BuyTicket>) -> Result<()> {
    let clock = Clock::get()?;
    //名称 + 数量(Id)
    let ticket_name: String = NAME.to_owned()
        + ctx
            .accounts
            .token_lottery
            .total_tickets
            .to_string()
            .as_str();

    //判断时间 是否可以购买
    if clock.slot < ctx.accounts.token_lottery.start_time
        || clock.slot > ctx.accounts.token_lottery.end_time
    {
        return Err(ErrorCode::LotteryNotOpen.into());
    }

    //转账 支付购买费用
    system_program::transfer(
        CpiContext::new(
            ctx.accounts.system_program.to_account_info(),
            system_program::Transfer {
                from: ctx.accounts.payer.to_account_info(),
                to: ctx.accounts.token_lottery.to_account_info(),
            },
        ),
        ctx.accounts.token_lottery.ticket_price,
    )?;

    //创建票据的种子
    let signer_seeds: &[&[&[u8]]] =
        &[&[b"collection_mint".as_ref(), &[ctx.bumps.collection_mint]]];

    //铸造票据
    mint_to(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            MintTo {
                mint: ctx.accounts.ticket_mint.to_account_info(),
                to: ctx.accounts.destination.to_account_info(),
                authority: ctx.accounts.collection_mint.to_account_info(),
            },
            &signer_seeds,
        ),
        1,
    )?;

    // 创建票据元数据 NFT
    msg!("Creating Metadata Account");
    create_metadata_accounts_v3(
        CpiContext::new_with_signer(
            ctx.accounts.token_metadata_program.to_account_info(),
            CreateMetadataAccountsV3 {
                metadata: ctx.accounts.collection_metadata.to_account_info(),
                mint: ctx.accounts.ticket_mint.to_account_info(),
                mint_authority: ctx.accounts.collection_mint.to_account_info(),
                payer: ctx.accounts.payer.to_account_info(),
                update_authority: ctx.accounts.collection_mint.to_account_info(),
                system_program: ctx.accounts.system_program.to_account_info(),
                rent: ctx.accounts.rent.to_account_info(),
            },
            signer_seeds,
        ),
        DataV2 {
            name: ticket_name,
            symbol: SYMBOL.to_string(),
            uri: URI.to_string(),
            seller_fee_basis_points: 0,
            creators: None,
            collection: None,
            uses: None,
        },
        true,
        true,
        None,
    )?;

    msg!("Create Master Edition Account");
    create_master_edition_v3(
        CpiContext::new_with_signer(
            ctx.accounts.token_metadata_program.to_account_info(),
            CreateMasterEditionV3 {
                edition: ctx.accounts.collection_master_edition.to_account_info(),
                mint: ctx.accounts.ticket_mint.to_account_info(),
                update_authority: ctx.accounts.collection_mint.to_account_info(),
                mint_authority: ctx.accounts.collection_mint.to_account_info(),
                payer: ctx.accounts.payer.to_account_info(),
                metadata: ctx.accounts.collection_metadata.to_account_info(),
                token_program: ctx.accounts.token_program.to_account_info(),
                system_program: ctx.accounts.system_program.to_account_info(),
                rent: ctx.accounts.rent.to_account_info(),
            },
            &signer_seeds,
        ),
        Some(0),
    )?;

    //验证集合 作为集合的一部分
    set_and_verify_sized_collection_item(
        CpiContext::new_with_signer(
            ctx.accounts.token_metadata_program.to_account_info(),
            SetAndVerifySizedCollectionItem {
                metadata: ctx.accounts.collection_metadata.to_account_info(),
                collection_authority: ctx.accounts.collection_mint.to_account_info(),
                payer: ctx.accounts.payer.to_account_info(),
                update_authority: ctx.accounts.collection_mint.to_account_info(),
                collection_mint: ctx.accounts.collection_mint.to_account_info(),
                collection_metadata: ctx.accounts.collection_metadata.to_account_info(),
                collection_master_edition: ctx
                    .accounts
                    .collection_master_edition
                    .to_account_info(),
            },
            &signer_seeds,
        ),
        None,
    )?;

    ctx.accounts.token_lottery.total_tickets += 1;

    Ok(())
}


#[derive(Accounts)]
pub struct BuyTicket<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    //获取 token_lottery
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    //获取 token_lottery
    #[account(
        mut,
        seeds = [b"collection_mint".as_ref()],
        bump,
    )]
    pub collection_mint: InterfaceAccount<'info, Mint>,

    //创建NFT 票据
    #[account(
        init,
        payer = payer,
        seeds = [token_lottery.total_tickets.to_le_bytes().as_ref()],
        bump,
        mint::decimals = 0,
        mint::authority = collection_mint,
        mint::freeze_authority = collection_mint,
        mint::token_program = token_program,
    )]
    pub ticket_mint: InterfaceAccount<'info, Mint>,

    //购买完毕接收 nft 的账号
    #[account(
        init,
        payer = payer,
        associated_token::mint = ticket_mint,
        associated_token::authority = payer,
        associated_token::token_program = token_program,
    )]
    pub destination: InterfaceAccount<'info, TokenAccount>,

    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    /// CHECK 这个 Account 将由智能合约 进行验证
    pub collection_metadata: UncheckedAccount<'info>,

    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
            b"edition"
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    ///CHECK: 这个账户 将由 metadata 智能合约进行 检查
    pub collection_master_edition: UncheckedAccount<'info>,

    pub associated_token_program: Program<'info, AssociatedToken>,
    pub token_program: Interface<'info, TokenInterface>,
    pub token_metadata_program: Program<'info, Metadata>,
    pub system_program: Program<'info, System>,
    //租金
    pub rent: Sysvar<'info, Rent>,
}
commit_randomness:
/**
 提交随机数
**/
pub fn commit_randomness(ctx: Context<CommitRandomness>) -> Result<()> {
    let clock = Clock::get()?;
    let token_lottery = &mut ctx.accounts.token_lottery;
    //检查权限
    if ctx.accounts.payer.key() != token_lottery.authority {
        return Err(ErrorCode::NotAuthorized.into());
    }
    let randomness_data =
        RandomnessAccountData::parse(ctx.accounts.randomness_account.data.borrow()).unwrap();
    //随机数不符合要求  说明这个抽奖已经揭晓了,否则就有人提前知道谁是 赢家了
    if randomness_data.seed_slot != clock.slot - 1 {
        return Err(ErrorCode::RandomnessAlreadyRevealed.into());
    }
    token_lottery.randomness_account = ctx.accounts.randomness_account.key();
    Ok(())
}


#[derive(Accounts)]
pub struct CommitRandomness<'info> {
    //付款人
    #[account(mut)]
    pub payer: Signer<'info>,

    //获取 token_lottery
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    //创建随机账号 由 switchboard 处理
    //命令行:cargo add switchboard_on_demand
    /// CHECK  这个账号是由 Switchboard smart contract 验证
    pub randomness_account: UncheckedAccount<'info>,
    // 用到的系统账号
    pub system_program: Program<'info, System>,
}
reveal_winner:
/**
 抽奖结果
**/
pub fn reveal_winner(ctx: Context<RevealWinner>) -> Result<()> {
    let clock = Clock::get()?;
    let token_lottery = &mut ctx.accounts.token_lottery;

    if ctx.accounts.payer.key() != token_lottery.authority {
        return Err(ErrorCode::NotAuthorized.into());
    }

    //无效的随机因子
    if ctx.accounts.randomness_account.key() != token_lottery.randomness_account {
        return Err(ErrorCode::IncorrectRandomnessAccount.into());
    }

    //还没到结束时间
    if clock.slot < token_lottery.end_time {
        return Err(ErrorCode::LotteryNotCompleted.into());
    }

    let randomness_data =
        RandomnessAccountData::parse(ctx.accounts.randomness_account.data.borrow()).unwrap();

    let reveal_random_value = randomness_data
        .get_value(&clock)
        .map_err(|_| ErrorCode::RandomnessNotResolved)?;

    //选出获胜者
    let winner = reveal_random_value[0] as u64 % token_lottery.total_tickets;

    token_lottery.winner = winner;
    token_lottery.winner_chosen = true;

    Ok(())
}

#[derive(Accounts)]
pub struct RevealWinner<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    /// CHECK 随机数种子, 由 Switchboard 进行验证
    pub randomness_account: UncheckedAccount<'info>,

    pub system_program: Program<'info, System>,
}
clain_winnings:
/**
 发放奖励
**/
pub fn clain_winnings(ctx: Context<ClainWinnings>) -> Result<()> {
    //当前 token_lottery 已经选出 获奖励 者
    require!(
        ctx.accounts.token_lottery.winner_chosen,
        ErrorCode::WinnerNotChosen
    );

    //检查 metadata 是否是属于改 集合,进行验证
    require!(
        ctx.accounts
            .token_metadata
            .collection
            .as_ref()
            .unwrap()
            .verified,
        ErrorCode::NotVerified
    );

    //无效的票据
    require!(
        ctx.accounts.token_metadata.collection.as_ref().unwrap().key
            == ctx.accounts.collection_mint.key(),
        ErrorCode::IncorrectTicket
    );

    let ticket_name = NAME.to_owned() + &ctx.accounts.token_lottery.winner.to_string();
    let metadata_name = ctx.accounts.token_metadata.name.replace("\u{0}","");

    //检查票据信息
    require!(metadata_name == ticket_name, ErrorCode::IncorrectTicket);
    //检查一共对外卖出票数
    require!(ctx.accounts.ticket_account.amount > 0,ErrorCode::NoTicket);

    //转账
    **ctx.accounts.token_lottery.to_account_info().lamports.borrow_mut() -= ctx.accounts.token_lottery.lottery_pot_amount;
    **ctx.accounts.payer.to_account_info().lamports.borrow_mut() += ctx.accounts.token_lottery.lottery_pot_amount;

    ctx.accounts.token_lottery.lottery_pot_amount = 0;


    Ok(())
}


#[derive(Accounts)]
pub struct ClainWinnings<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    //关联账号
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    //关联账号
    #[account(
        seeds = [token_lottery.winner.to_le_bytes().as_ref()],
        bump,
    )]
    pub ticket_mint: InterfaceAccount<'info, Mint>,

    //关联账号
    #[account(
        seeds = [b"collection_mint".as_ref()],
        bump,
    )]
    pub collection_mint: InterfaceAccount<'info, Mint>,

    //关联 票据元数据
    #[account(
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            ticket_mint.key().as_ref(),
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    pub token_metadata: Account<'info, MetadataAccount>,

    //关联中奖账户
    #[account(
       associated_token::mint = ticket_mint,
       associated_token::authority = payer,
       associated_token::token_program = token_program,
    )]
    pub ticket_account: InterfaceAccount<'info, TokenAccount>,

    //关联票据集合
    #[account(
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref()
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    pub collection_metadata: Account<'info, MetadataAccount>,

    pub token_program: Interface<'info, TokenInterface>,
    pub token_metadata_program: Program<'info, Metadata>,
    pub system_program: Program<'info, System>,
}

 完整脚本:

use anchor_lang::prelude::*;
use anchor_lang::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token_interface::{mint_to, Mint, MintTo, TokenAccount, TokenInterface};

use anchor_spl::metadata::mpl_token_metadata::types::{CollectionDetails, Creator, DataV2};
use anchor_spl::metadata::{
    create_master_edition_v3, create_metadata_accounts_v3, set_and_verify_sized_collection_item,
    sign_metadata, CreateMasterEditionV3, CreateMetadataAccountsV3, Metadata, MetadataAccount,
    SetAndVerifySizedCollectionItem, SignMetadata,
};
use switchboard_on_demand::RandomnessAccountData;

declare_id!("J1VmbciWTjAmAygKi3VbXFnd857WxuSvSUpxhxTehipG");

#[constant]
pub const NAME: &str = "Token Lottery Ticket #";
#[constant]
pub const SYMBOL: &str = "TLT";
#[constant]
pub const URI: &str = "https://token-creator-lac.vercel.app/token_metadata.json";

#[program]
pub mod token_lottery {

    use super::*;

    /**
        执行初始化配置
    **/
    pub fn initialize_config(
        ctx: Context<Initialize>,
        start: u64,
        end: u64,
        price: u64,
    ) -> Result<()> {
        //缓存推导 PAD 庄户的种子
        ctx.accounts.token_lottery.bump = ctx.bumps.token_lottery;
        ctx.accounts.token_lottery.start_time = start;
        ctx.accounts.token_lottery.end_time = end;
        ctx.accounts.token_lottery.ticket_price = price;
        ctx.accounts.token_lottery.authority = ctx.accounts.payer.key();
        ctx.accounts.token_lottery.lottery_pot_amount = 0;
        ctx.accounts.token_lottery.total_tickets = 0;
        // Pubkey::default() 是一个固定 全零的 32 字节公钥,固定值为 [0; 32],
        // 被初始化为全零公钥,表示尚未设置有效的所有者地址
        ctx.accounts.token_lottery.randomness_account = Pubkey::default();
        ctx.accounts.token_lottery.winner_chosen = false;

        Ok(())
    }

    /**
    初始化 lottery,管理 ntf,
        创建 nft
        设置 nft metadata
    **/
    pub fn initialize_lottery(ctx: Context<InitializeLottery>) -> Result<()> {
        let signer_seeds: &[&[&[u8]]] =
            &[&[b"collection_mint".as_ref(), &[ctx.bumps.collection_mint]]];

        //铸造 nft
        msg!("Creating Mint Account");
        mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    mint: ctx.accounts.collection_mint.to_account_info(),
                    to: ctx.accounts.collection_mint_account.to_account_info(),
                    authority: ctx.accounts.collection_mint.to_account_info(),
                },
                signer_seeds,
            ),
            1,
        )
        .expect("TODO: panic message");

        //设置nft 元数据
        msg!("Creating Metadata Account");
        create_metadata_accounts_v3(
            CpiContext::new_with_signer(
                ctx.accounts.metadata.to_account_info(),
                CreateMetadataAccountsV3 {
                    metadata: ctx.accounts.metadata.to_account_info(),
                    mint: ctx.accounts.collection_mint.to_account_info(),
                    mint_authority: ctx.accounts.collection_mint.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    update_authority: ctx.accounts.collection_mint.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                signer_seeds,
            ),
            DataV2 {
                name: NAME.to_string(),
                symbol: SYMBOL.to_string(),
                uri: URI.to_string(),
                seller_fee_basis_points: 0,
                creators: Some(vec![Creator {
                    address: ctx.accounts.collection_mint.key(),
                    verified: false,
                    share: 100,
                }]),
                collection: None,
                uses: None,
            },
            true,
            true,
            Some(CollectionDetails::V1 { size: 0 }),
        )?;

        //设置 nft 主 edition
        msg!("Create Master Edition Account");
        create_master_edition_v3(
            CpiContext::new_with_signer(
                ctx.accounts.token_metadata_program.to_account_info(),
                CreateMasterEditionV3 {
                    edition: ctx.accounts.master_edition.to_account_info(),
                    mint: ctx.accounts.collection_mint.to_account_info(),
                    update_authority: ctx.accounts.collection_mint.to_account_info(),
                    mint_authority: ctx.accounts.collection_mint.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    metadata: ctx.accounts.metadata.to_account_info(),
                    token_program: ctx.accounts.token_program.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &signer_seeds,
            ),
            Some(0),
        )?;

        msg!("Verifying Collection");
        sign_metadata(CpiContext::new_with_signer(
            ctx.accounts.token_metadata_program.to_account_info(),
            SignMetadata {
                creator: ctx.accounts.collection_mint.to_account_info(),
                metadata: ctx.accounts.metadata.to_account_info(),
            },
            &signer_seeds,
        ))?;
        Ok(())
    }

    pub fn buy_ticket(ctx: Context<BuyTicket>) -> Result<()> {
        let clock = Clock::get()?;
        //名称 + 数量(Id)
        let ticket_name: String = NAME.to_owned()
            + ctx
                .accounts
                .token_lottery
                .total_tickets
                .to_string()
                .as_str();

        //判断时间 是否可以购买
        if clock.slot < ctx.accounts.token_lottery.start_time
            || clock.slot > ctx.accounts.token_lottery.end_time
        {
            return Err(ErrorCode::LotteryNotOpen.into());
        }

        //转账 支付购买费用
        system_program::transfer(
            CpiContext::new(
                ctx.accounts.system_program.to_account_info(),
                system_program::Transfer {
                    from: ctx.accounts.payer.to_account_info(),
                    to: ctx.accounts.token_lottery.to_account_info(),
                },
            ),
            ctx.accounts.token_lottery.ticket_price,
        )?;

        //创建票据的种子
        let signer_seeds: &[&[&[u8]]] =
            &[&[b"collection_mint".as_ref(), &[ctx.bumps.collection_mint]]];

        //铸造票据
        mint_to(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                MintTo {
                    mint: ctx.accounts.ticket_mint.to_account_info(),
                    to: ctx.accounts.destination.to_account_info(),
                    authority: ctx.accounts.collection_mint.to_account_info(),
                },
                &signer_seeds,
            ),
            1,
        )?;

        // 创建票据元数据 NFT
        msg!("Creating Metadata Account");
        create_metadata_accounts_v3(
            CpiContext::new_with_signer(
                ctx.accounts.token_metadata_program.to_account_info(),
                CreateMetadataAccountsV3 {
                    metadata: ctx.accounts.collection_metadata.to_account_info(),
                    mint: ctx.accounts.ticket_mint.to_account_info(),
                    mint_authority: ctx.accounts.collection_mint.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    update_authority: ctx.accounts.collection_mint.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                signer_seeds,
            ),
            DataV2 {
                name: ticket_name,
                symbol: SYMBOL.to_string(),
                uri: URI.to_string(),
                seller_fee_basis_points: 0,
                creators: None,
                collection: None,
                uses: None,
            },
            true,
            true,
            None,
        )?;

        msg!("Create Master Edition Account");
        create_master_edition_v3(
            CpiContext::new_with_signer(
                ctx.accounts.token_metadata_program.to_account_info(),
                CreateMasterEditionV3 {
                    edition: ctx.accounts.collection_master_edition.to_account_info(),
                    mint: ctx.accounts.ticket_mint.to_account_info(),
                    update_authority: ctx.accounts.collection_mint.to_account_info(),
                    mint_authority: ctx.accounts.collection_mint.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    metadata: ctx.accounts.collection_metadata.to_account_info(),
                    token_program: ctx.accounts.token_program.to_account_info(),
                    system_program: ctx.accounts.system_program.to_account_info(),
                    rent: ctx.accounts.rent.to_account_info(),
                },
                &signer_seeds,
            ),
            Some(0),
        )?;

        //验证集合 作为集合的一部分
        set_and_verify_sized_collection_item(
            CpiContext::new_with_signer(
                ctx.accounts.token_metadata_program.to_account_info(),
                SetAndVerifySizedCollectionItem {
                    metadata: ctx.accounts.collection_metadata.to_account_info(),
                    collection_authority: ctx.accounts.collection_mint.to_account_info(),
                    payer: ctx.accounts.payer.to_account_info(),
                    update_authority: ctx.accounts.collection_mint.to_account_info(),
                    collection_mint: ctx.accounts.collection_mint.to_account_info(),
                    collection_metadata: ctx.accounts.collection_metadata.to_account_info(),
                    collection_master_edition: ctx
                        .accounts
                        .collection_master_edition
                        .to_account_info(),
                },
                &signer_seeds,
            ),
            None,
        )?;

        ctx.accounts.token_lottery.total_tickets += 1;

        Ok(())
    }

    /**
     提交随机数
    **/
    pub fn commit_randomness(ctx: Context<CommitRandomness>) -> Result<()> {
        let clock = Clock::get()?;
        let token_lottery = &mut ctx.accounts.token_lottery;
        //检查权限
        if ctx.accounts.payer.key() != token_lottery.authority {
            return Err(ErrorCode::NotAuthorized.into());
        }
        let randomness_data =
            RandomnessAccountData::parse(ctx.accounts.randomness_account.data.borrow()).unwrap();
        //随机数不符合要求  说明这个抽奖已经揭晓了,否则就有人提前知道谁是 赢家了
        if randomness_data.seed_slot != clock.slot - 1 {
            return Err(ErrorCode::RandomnessAlreadyRevealed.into());
        }
        token_lottery.randomness_account = ctx.accounts.randomness_account.key();
        Ok(())
    }

    /**
     抽奖结果
    **/
    pub fn reveal_winner(ctx: Context<RevealWinner>) -> Result<()> {
        let clock = Clock::get()?;
        let token_lottery = &mut ctx.accounts.token_lottery;

        if ctx.accounts.payer.key() != token_lottery.authority {
            return Err(ErrorCode::NotAuthorized.into());
        }

        //无效的随机因子
        if ctx.accounts.randomness_account.key() != token_lottery.randomness_account {
            return Err(ErrorCode::IncorrectRandomnessAccount.into());
        }

        //还没到结束时间
        if clock.slot < token_lottery.end_time {
            return Err(ErrorCode::LotteryNotCompleted.into());
        }

        let randomness_data =
            RandomnessAccountData::parse(ctx.accounts.randomness_account.data.borrow()).unwrap();

        let reveal_random_value = randomness_data
            .get_value(&clock)
            .map_err(|_| ErrorCode::RandomnessNotResolved)?;

        //选出获胜者
        let winner = reveal_random_value[0] as u64 % token_lottery.total_tickets;

        token_lottery.winner = winner;
        token_lottery.winner_chosen = true;

        Ok(())
    }

    /**
     发放奖励
    **/
    pub fn clain_winnings(ctx: Context<ClainWinnings>) -> Result<()> {
        //当前 token_lottery 已经选出 获奖励 者
        require!(
            ctx.accounts.token_lottery.winner_chosen,
            ErrorCode::WinnerNotChosen
        );

        //检查 metadata 是否是属于改 集合,进行验证
        require!(
            ctx.accounts
                .token_metadata
                .collection
                .as_ref()
                .unwrap()
                .verified,
            ErrorCode::NotVerified
        );

        //无效的票据
        require!(
            ctx.accounts.token_metadata.collection.as_ref().unwrap().key
                == ctx.accounts.collection_mint.key(),
            ErrorCode::IncorrectTicket
        );

        let ticket_name = NAME.to_owned() + &ctx.accounts.token_lottery.winner.to_string();
        let metadata_name = ctx.accounts.token_metadata.name.replace("\u{0}", "");

        //检查票据信息
        require!(metadata_name == ticket_name, ErrorCode::IncorrectTicket);
        //检查一共对外卖出票数
        require!(ctx.accounts.ticket_account.amount > 0, ErrorCode::NoTicket);

        //转账
        **ctx
            .accounts
            .token_lottery
            .to_account_info()
            .lamports
            .borrow_mut() -= ctx.accounts.token_lottery.lottery_pot_amount;
        **ctx.accounts.payer.to_account_info().lamports.borrow_mut() +=
            ctx.accounts.token_lottery.lottery_pot_amount;

        ctx.accounts.token_lottery.lottery_pot_amount = 0;

        Ok(())
    }
}

//操作指令 关联PAD  TokenLottery 账户
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    #[account(
            init,
            payer = payer,
            space = 8 + TokenLottery::INIT_SPACE,
            seeds = [b"token_lottery".as_ref()],
            bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    pub system_program: Program<'info, System>,
}

#[account]
#[derive(InitSpace)]
pub struct TokenLottery {
    //缓存 种子,便于后期推导 PAD
    pub bump: u8,
    //胜利的 NFT 票据 ID
    pub winner: u64,
    //是否已经领取奖励
    pub winner_chosen: bool,
    //事件限制 开始时间
    pub start_time: u64,
    //事件限制 结束事件
    pub end_time: u64,
    //奖池
    pub lottery_pot_amount: u64,
    //参与价格
    pub ticket_price: u64,
    //一共参与票数
    pub total_tickets: u64,
    //授权 可以执行提交随机数 和 选出胜利者
    pub authority: Pubkey,
    //关联执行随机信息的账户
    pub randomness_account: Pubkey,
}

#[derive(Accounts)]
pub struct InitializeLottery<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    //ntf 集合代币账户,只是个PDA账户没有存储功能 也没有 space, 并且权限设置为 自己本身
    #[account(
        init,
        payer = payer,
        mint::decimals = 0,
        mint::authority = collection_mint,
        mint::freeze_authority = collection_mint,
        seeds = [b"collection_mint".as_ref()],
        bump,
    )]
    pub collection_mint: InterfaceAccount<'info, Mint>,

    //nft 账户 只是 pad 关联 账户 不需要存储, 并且权限设置为 自己本身
    #[account(
        init,
        payer = payer,
        token::mint = collection_mint,
        token::authority = collection_mint_account,
        seeds = [b"collection_associated_token".as_ref()],
        bump,
    )]
    pub collection_mint_account: InterfaceAccount<'info, TokenAccount>,

    //nft 设置 metadata 关联  pad 账户 不需要存储,
    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    ///CHECK: 这个账户 将由 metadata 智能合约进行 检查,  "///CHECK:" 固定写法 要不然编译不通过
    pub metadata: UncheckedAccount<'info>,

    ///单一 NFT 账号配置,区别于 Normal Editions;支持单一NFT(Master Edition)或限量复制品(Normal Editions),适合不同类型的项目需求
    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
            b"edition"
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    ///CHECK: 这个账户 将由 metadata 智能合约进行 检查 "///CHECK:" 固定写法 要不然编译不通过
    pub master_edition: UncheckedAccount<'info>,

    //mint 元数据处理 系统账号
    pub token_metadata_program: Program<'info, Metadata>,
    //ATA associate_token处理 程序账号
    pub associate_token_program: Program<'info, AssociatedToken>,
    //ATA token处理 程序账号
    pub token_program: Interface<'info, TokenInterface>,
    //系统账号
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
pub struct BuyTicket<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    //获取 token_lottery
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    //获取 token_lottery
    #[account(
        mut,
        seeds = [b"collection_mint".as_ref()],
        bump,
    )]
    pub collection_mint: InterfaceAccount<'info, Mint>,

    //创建NFT 票据
    #[account(
        init,
        payer = payer,
        seeds = [token_lottery.total_tickets.to_le_bytes().as_ref()],
        bump,
        mint::decimals = 0,
        mint::authority = collection_mint,
        mint::freeze_authority = collection_mint,
        mint::token_program = token_program,
    )]
    pub ticket_mint: InterfaceAccount<'info, Mint>,

    //购买完毕接收 nft 的账号
    #[account(
        init,
        payer = payer,
        associated_token::mint = ticket_mint,
        associated_token::authority = payer,
        associated_token::token_program = token_program,
    )]
    pub destination: InterfaceAccount<'info, TokenAccount>,

    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    /// CHECK 这个 Account 将由智能合约 进行验证
    pub collection_metadata: UncheckedAccount<'info>,

    #[account(
        mut,
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref(),
            b"edition"
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    ///CHECK: 这个账户 将由 metadata 智能合约进行 检查
    pub collection_master_edition: UncheckedAccount<'info>,

    pub associated_token_program: Program<'info, AssociatedToken>,
    pub token_program: Interface<'info, TokenInterface>,
    pub token_metadata_program: Program<'info, Metadata>,
    pub system_program: Program<'info, System>,
    //租金
    pub rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
pub struct CommitRandomness<'info> {
    //付款人
    #[account(mut)]
    pub payer: Signer<'info>,

    //获取 token_lottery
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    //创建随机账号 由 switchboard 处理
    //命令行:cargo add switchboard_on_demand
    /// CHECK  这个账号是由 Switchboard smart contract 验证
    pub randomness_account: UncheckedAccount<'info>,
    // 用到的系统账号
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct RevealWinner<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    /// CHECK 随机数种子, 由 Switchboard 进行验证
    pub randomness_account: UncheckedAccount<'info>,

    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct ClainWinnings<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    //关联账号
    #[account(
        mut,
        seeds = [b"token_lottery".as_ref()],
        bump = token_lottery.bump,
    )]
    pub token_lottery: Account<'info, TokenLottery>,

    //关联账号
    #[account(
        seeds = [token_lottery.winner.to_le_bytes().as_ref()],
        bump,
    )]
    pub ticket_mint: InterfaceAccount<'info, Mint>,

    //关联账号
    #[account(
        seeds = [b"collection_mint".as_ref()],
        bump,
    )]
    pub collection_mint: InterfaceAccount<'info, Mint>,

    //关联 票据元数据
    #[account(
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            ticket_mint.key().as_ref(),
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    pub token_metadata: Account<'info, MetadataAccount>,

    //关联中奖账户
    #[account(
       associated_token::mint = ticket_mint,
       associated_token::authority = payer,
       associated_token::token_program = token_program,
    )]
    pub ticket_account: InterfaceAccount<'info, TokenAccount>,

    //关联票据集合
    #[account(
        seeds = [
            b"metadata",
            token_metadata_program.key().as_ref(),
            collection_mint.key().as_ref()
        ],
        bump,
        seeds::program = token_metadata_program.key(),
    )]
    pub collection_metadata: Account<'info, MetadataAccount>,

    pub token_program: Interface<'info, TokenInterface>,
    pub token_metadata_program: Program<'info, Metadata>,
    pub system_program: Program<'info, System>,
}

#[error_code]
pub enum ErrorCode {
    #[msg("Lottery is not open")]
    LotteryNotOpen,
    #[msg("Not authorized")]
    NotAuthorized,
    #[msg("Randomness Already Revealed")]
    RandomnessAlreadyRevealed,
    #[msg("Incorrect Randomness Account")]
    IncorrectRandomnessAccount,
    #[msg("Lottery No tCompleted")]
    LotteryNotCompleted,
    #[msg("Randomness Not Resolved")]
    RandomnessNotResolved,
    #[msg("Winner Not Chosen")]
    WinnerNotChosen,
    #[msg("NotVerified")]
    NotVerified,
    #[msg("Incorrect Ticket")]
    IncorrectTicket,
    #[msg("No Ticket")]
    NoTicket,
}

 Cargo.toml 配置文件

[package]
name = "token_lottery"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "token_lottery"

[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
#idl-build = ["anchor-lang/idl-build"]
#添加anchor-spl build
idl-build = ["anchor-lang/idl-build","anchor-spl/idl-build"]
[dependencies]
anchor-lang = "0.31.1"
anchor-spl = { version =  "0.31.1",features = ["metadata"] }
switchboard-on-demand = "0.4.9"


网站公告

今日签到

点亮在社区的每一天
去签到