Anchor 程序:自定义铸造逻辑
1. 为什么需要自定义程序
虽然直接使用 Metaplex 提供的 Candy Machine 可以满足很多需求,但在某些特定场景下,编写自己的 Anchor 程序来处理铸造逻辑是更好的选择:
- 完全的逻辑控制:你可以实现任意复杂的白名单逻辑(例如基于链上交互历史、持有特定 Token 等)。
- 动态定价:实现荷兰拍、指数增长曲线或基于预言机的定价。
- 自定义支付:接受任意 SPL 代币支付,或实现分润逻辑(例如 50% 给金库,50% 给创作者)。
- 游戏集成:铸造不仅仅是获得 NFT,还可以触发游戏内的状态变更(如升级、解锁关卡)。
2. 核心架构设计
一个标准的 NFT Minter 程序通常包含以下几个核心组件:
- 全局配置账户 (MintConfig):存储管理员地址、价格、最大供应量、Collection 地址等全局状态。它通常是一个 PDA。
- 用户记录账户 (UserRecord):防止女巫攻击,限制每个钱包的铸造数量。也是一个 PDA。
- 金库账户 (Treasury):接收用户支付的 SOL 或代币。
铸造流程 (CPI 编排)
自定义 Minter 的核心在于编排 (Orchestration)。用户的单一交互(调用 mint_nft 指令)会在链上触发一系列连锁反应:
- 检查逻辑:验证时间、供应量、支付金额。
- 扣款:将 SOL 从用户转到金库 (System Program)。
- 铸造:调用 Token Program 铸造 1 个 Token。
- 元数据:调用 Metaplex Program 创建 Metadata 和 Master Edition。
- 验证:程序使用 PDA 签名,作为 Collection Authority 验证新铸造的 NFT。
3. 程序代码实现
以下是一个简化的 Minter 程序核心逻辑。
状态定义 (State)
rust#[account] pub struct MintConfig { pub authority: Pubkey, // 管理员 pub collection_mint: Pubkey,// 绑定的集合 pub mint_price: u64, // 价格 pub current_supply: u32, // 当前供应量 pub max_supply: u32, // 最大供应量 pub bump: u8, // PDA Bump }
铸造指令 (Mint Instruction)
这是最复杂的部分。我们需要在一个指令中完成支付、铸造、创建元数据和验证集合。
rust#[derive(Accounts)] pub struct MintNft<'info> { #[account(mut)] pub minter: Signer<'info>, // 铸造者(用户) // 全局配置 PDA #[account( mut, seeds = [b"config"], bump = config.bump )] pub config: Account<'info, MintConfig>, // 用户铸造记录 PDA (限制每个钱包铸造数) #[account( init_if_needed, payer = minter, space = 8 + 32 + 4, seeds = [b"user", minter.key().as_ref()], bump )] pub user_record: Account<'info, UserRecord>, // 新 NFT 的 Mint 账户 (由用户生成 Keypair) #[account( init, payer = minter, mint::decimals = 0, mint::authority = config, // 铸造权暂时交给程序 PDA mint::freeze_authority = config, )] pub nft_mint: Account<'info, Mint>, // ... 其他必要的账户 (Token Account, Metadata, System, Token Prog, Rent) } pub fn handler(ctx: Context<MintNft>, name: String, uri: String) -> Result<()> { let config = &mut ctx.accounts.config; // 1. 逻辑检查 require!(config.current_supply < config.max_supply, MinterError::SoldOut); // 2. 支付 (CPI to System) system_program::transfer( CpiContext::new( ctx.accounts.system_program.to_account_info(), system_program::Transfer { from: ctx.accounts.minter.to_account_info(), to: ctx.accounts.treasury.to_account_info(), } ), config.mint_price )?; // 3. 准备 PDA 签名种子 let seeds = &[b"config".as_ref(), &[config.bump]]; let signer = &[&seeds[..]]; // 4. 铸造 1 个 Token (CPI to Token) // 使用 PDA 签名,因为 Mint Authority 是 config PDA token::mint_to( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), token::MintTo { mint: ctx.accounts.nft_mint.to_account_info(), to: ctx.accounts.nft_token_account.to_account_info(), authority: config.to_account_info(), }, signer ), 1 )?; // 5. 创建元数据 (CPI to Metaplex) // 这里省略了冗长的参数构建... create_metadata_accounts_v3(..., signer)?; create_master_edition_v3(..., signer)?; // 6. 验证集合 (CPI to Metaplex) // 关键一步:程序作为 Collection Authority 自动验证 NFT set_and_verify_collection( CpiContext::new_with_signer( ctx.accounts.metadata_program.to_account_info(), SetAndVerifyCollection { metadata: ctx.accounts.metadata.to_account_info(), collection_authority: config.to_account_info(), // 程序 PDA // ... }, signer ) )?; // 7. 更新状态 config.current_supply += 1; Ok(()) }
4. 关键点:Collection Verification
在自定义程序中,Program PDA 通常持有 Collection NFT 的 Update Authority。
这就允许程序代表 Collection 对新铸造的 NFT 进行 Verify Collection 操作。
这实现了无信任铸造:用户不需要请求项目方的后端签名,只要满足链上逻辑(付钱、未超量),程序就会自动盖上"正品"的戳。