账户创建与管理
在深入代码之前,让我们先理解账户创建的底层机制。在 Solana 上创建账户需要完成几个步骤:分配账户空间、支付租金、初始化账户数据。如果直接使用底层 API,这些步骤需要分别处理,代码会变得冗长且容易出错。
Anchor 框架的强大之处在于它将这些底层细节封装成了简洁的声明式宏,让开发者可以专注于业务逻辑。
创建 Mint 账户
使用 Anchor 创建 Mint 账户非常简洁。Anchor 提供的宏会自动处理账户初始化、空间分配和租金支付等底层细节。让我们看一个完整的示例:
rustuse anchor_lang::prelude::*; use anchor_spl::token::{Mint, Token}; #[derive(Accounts)] pub struct CreateMint<'info> { #[account(mut)] pub payer: Signer<'info>, #[account( init, payer = payer, mint::decimals = 6, mint::authority = payer.key(), mint::freeze_authority = payer.key(), )] pub mint: Account<'info, Mint>, pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, } pub fn create_mint(ctx: Context<CreateMint>) -> Result<()> { msg!("Mint 账户已创建: {}", ctx.accounts.mint.key()); Ok(()) }
让我们逐行解析这段代码:
init约束告诉 Anchor 这是一个需要新建的账户。Anchor 会自动计算所需的空间大小、调用 System Program 创建账户、并调用 Token Program 初始化 Mint 数据payer = payer指定谁支付账户租金(约 0.00145 SOL)。Solana 的租金机制是为了防止状态膨胀,如果账户余额足够支付两年租金,则该账户将被标记为"免租"(rent-exempt),不会被系统回收mint::decimals = 6设置代币精度,6 位小数是 USDC 等稳定币的常见选择。选择合适的小数位数很重要:太少会限制代币的可分割性,太多则会增加用户界面的复杂度mint::authority设置铸币权限,持有此权限才能铸造新代币。这通常是项目方的多签钱包或治理合约mint::freeze_authority设置冻结权限,可以冻结任何持有该代币的账户。这是一把双刃剑:合规项目需要它来应对法律要求,但去中心化项目通常会放弃这个权限以增强用户信任
如果你想创建一个去中心化的代币(创建后无法增发),可以将 mint::authority 设置为 None,或者在创建后通过 set_authority 指令放弃权限。这种做法在社区驱动的项目中很常见,因为它向持有者证明了代币的稀缺性不会被人为破坏。
关于账户大小和租金
Mint 账户的大小是固定的 82 字节。按照当前的租金标准,免租所需的 SOL 约为 0.00145 SOL。这个费用由 payer 支付,成为账户余额的一部分。当账户被关闭时(如果支持关闭),这些 SOL 可以被回收。
创建 Token 账户
普通的 Token 账户需要显式指定账户地址,适用于程序需要控制的 PDA 账户:
rustuse anchor_spl::token::{Mint, Token, TokenAccount}; #[derive(Accounts)] pub struct CreateTokenAccount<'info> { #[account(mut)] pub payer: Signer<'info>, pub mint: Account<'info, Mint>, /// CHECK: 这个账户将成为 token 账户的 owner pub owner: AccountInfo<'info>, #[account( init, payer = payer, token::mint = mint, token::authority = owner, )] pub token_account: Account<'info, TokenAccount>, pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, }
创建 Associated Token Account
在实际开发中,我们更常使用 ATA,因为它的地址是确定性的:
rustuse anchor_spl::{ associated_token::AssociatedToken, token::{Mint, Token, TokenAccount}, }; #[derive(Accounts)] pub struct CreateATA<'info> { #[account(mut)] pub payer: Signer<'info>, pub mint: Account<'info, Mint>, /// CHECK: ATA 的 owner pub owner: AccountInfo<'info>, #[account( init, payer = payer, associated_token::mint = mint, associated_token::authority = owner, )] pub token_account: Account<'info, TokenAccount>, pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, }
一个常见的模式是使用 init_if_needed 约束,它会在账户不存在时自动创建:
rust#[account( init_if_needed, payer = payer, associated_token::mint = mint, associated_token::authority = owner, )] pub token_account: Account<'info, TokenAccount>,
使用 init_if_needed 需要在 Cargo.toml 中启用对应的 feature:
toml[features] default = [] cpi = ["no-entrypoint"] no-entrypoint = [] no-idl = [] no-log-ix-name = [] idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] init-if-needed = ["anchor-lang/init-if-needed"]