当前位置: 首页 > news >正文

anchor 智能合约案例6 之 token_lottery

前言:

前面我们介绍了 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 {//缓存 种子,便于后期推导 PADpub bump: u8,//胜利的 NFT 票据 IDpub 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]]];//铸造 nftmsg!("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 主 editionmsg!("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,)?;// 创建票据元数据 NFTmsg!("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]]];//铸造 nftmsg!("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 主 editionmsg!("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,)?;// 创建票据元数据 NFTmsg!("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 {//缓存 种子,便于后期推导 PADpub bump: u8,//胜利的 NFT 票据 IDpub 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"
http://www.lryc.cn/news/596233.html

相关文章:

  • 假发行业数字化突围,外贸ERP重构外协管理引擎,助力效率飞跃
  • 34、鸿蒙Harmony Next开发:使用动画-转场动画
  • Jmeter使用 - 2
  • Chrome 开发环境屏蔽 CORS 跨域限制
  • PHICOMM(斐讯)N1盒子 - Armbian25.05(Debian 12)刷入U盘/EMMC
  • SQL 中 JOIN 顺序对性能的影响
  • FastDFS 6.11.0 单机环境搭建与测试(附 Nginx 集成)+ docker构建+k8s启动文件
  • 浏览器地址栏输入URL回车后白屏分析
  • Jenkins接口自动化测试(构建)平台搭建
  • Apache Ignite 中事务的使用方式和机制
  • Excel工具
  • ROS个人笔记
  • Qt Creator集成开发环境使用指南
  • K 近邻算法(K-Nearest Neighbors, KNN)详解及案例
  • 聊聊原生 CSS 变量:让样式更灵活的“魔法”
  • 大模型推理环境安装过程中踩坑记录
  • 野外具身视觉跟踪:北大团队TrackVLA让AI视觉跟踪进化到2.0时代
  • Springboot使用外部的Servelt容器(最简单的方式)
  • 1-bit AI 基础设施:第 1.1 部分 —— 在 CPU 上实现快速且无损的 BitNet b1.58 推理
  • ubuntu24.04安装CUDA、VLLM、Pytorch等并部署Qwen3-8B-AWQ【50系显卡通用】
  • proxmox 解决docker容器MongoDB创建报错MongoDB 5.0+ requires a CPU with AVX support
  • Leetcode力扣解题记录--第73题(矩阵置零)
  • Leetcode题解:209长度最小的子数组,掌握滑动窗口从此开始!!!
  • Vue中最简单的PDF引入方法及优缺点分析
  • Gradio, Streamlit, Dash:AI应用开发的效率之选
  • 配置https ssl证书生成
  • 拓展三字棋
  • ansible 批量 scp 和 load 镜像
  • 2025 年 7 月 21 日 AI 日报
  • 位运算符的妙用