Jacky Gu

Solana开发笔记: 使用anchor程序冻结代币

01 Jan 2025 Share to

使用anchor程序冻结代币

前面一篇中,详细介绍了如何燃烧代币,这篇介绍冻结代币。

因为mint账户的冻结权限(freeze authority)是mint账户自己,所以本操作需要在anchor程序中进行。

创建freeze token指令

instructions中添加freeze_token_account.rs,并粘贴以下代码:

use anchor_lang::prelude::*;

use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::{freeze_account, spl_token, FreezeAccount, Mint, Token, TokenAccount};

use crate::constants::MINT_SEEDS;
use crate::errors::ErrorCode;

#[derive(Accounts)]
#[instruction(token_name: String, token_symbol: String)]
pub struct FreezeTokenAccount<'info> {
    #[account(
      mut,
      seeds = [MINT_SEEDS.as_bytes(), token_name.as_bytes(), token_symbol.as_bytes()],
      bump,
      mint::authority = mint,
      mint::decimals = 9,
    )]
    pub mint: Box<Account<'info, Mint>>,
    #[account(
      init_if_needed,
      payer = user,
      associated_token::mint = mint,
      associated_token::authority = user, // destination is the ata account and user is the owner
    )]
    pub destination: Box<Account<'info, TokenAccount>>, // ata account
    #[account(mut)]
    pub user: Signer<'info>,
    pub rent: Sysvar<'info, Rent>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
    pub associated_token_program: Program<'info, AssociatedToken>,
}

impl<'info> FreezeTokenAccount<'info> {
    pub fn freeze_token_account(ctx: Context<FreezeTokenAccount>, token_name: String, token_symbol: String) -> Result<()> {
        require!(spl_token::ID == ctx.accounts.token_program.key(), ErrorCode::WrongTokenProgram);
        let seeds = &[MINT_SEEDS.as_bytes(), token_name.as_bytes(), token_symbol.as_bytes(), &[ctx.bumps.mint]];
        let signer = [&seeds[..]];

        let freeze_ctx = CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            FreezeAccount {
                account: ctx.accounts.destination.to_account_info(),
                mint: ctx.accounts.mint.to_account_info(),
                authority: ctx.accounts.mint.to_account_info(),
            },
            &signer,
        );
        freeze_account(freeze_ctx)?;
        msg!("Freeze ata successfully.");
        Ok(())
    }
}

在lib.rs中调用上面的指令

    pub fn freeze_token_account(ctx: Context<FreezeTokenAccount>, token_name: String, token_symbol: String) -> Result<()> {
        FreezeTokenAccount::freeze_token_account(ctx, token_name, token_symbol)
    }

编译部署

anchor build && anchor deploy

编写测试代码

  it("freeze token account", async () => {
    // Get user1's token account (ATA)
    const tokenAccount = await getAssociatedTokenAddress(mintAccount, user1Account.publicKey, false, TOKEN_PROGRAM_ID);

    // Prepare context for freeze instruction
    const context = {
      mint: mintAccount,
      destination: tokenAccount,
      user: user1Account.publicKey,
      tokenProgram: TOKEN_PROGRAM_ID,
    };

    // Call freeze_token_account instruction
    const tx = await program.methods
      .freezeTokenAccount(metadataParams.name, metadataParams.symbol)
      .accounts(context)
      .signers([user1Account])
      .rpc();

    console.log("Freeze token account transaction signature:", tx);

    // Optional: Verify the token account is frozen
    try {
      // Attempt to transfer tokens from a frozen account should fail
      const sourceAta = await getAssociatedTokenAddress(mintAccount, user1Account.publicKey, false, TOKEN_PROGRAM_ID);
      const destinationAta = await getAssociatedTokenAddress(mintAccount, user2Account.publicKey, false, TOKEN_PROGRAM_ID);

      const transferIx = createTransferInstruction(
        sourceAta,
        destinationAta,
        user1Account.publicKey,
        10 * Math.pow(10, 9)  // 10 tokens
      );

      const tx = new anchor.web3.Transaction().add(transferIx);
      await provider.sendAndConfirm(tx, [user1Account]);
      console.log("Transfer from frozen account unexpectedly succeeded");
    } catch (error) {
      console.log("Transfer from frozen account failed as expected:", error);
    }
  })

运行后,当测试转币时,会出错,说明成功锁定账户。