使用Anchor与SPL Token Program交互指南

Anchor 是用来开发 Solana 智能合约的高层开发框架,它基于 Rust,简化了 Solana 原生开发的繁琐流程。
SPLSolana Program Library 的缩写,它是Solana官方维护的一套标准智能合约程序集合,用来为 Solana 生态提供各种 基础功能模块,类似于以太坊里的 ERC标准合约。

创建Token Mint

首先要了解什么是Mint账户,简单来说,Mint 账户就是某个代币的“发行登记中心”,负责记录这个代币的总发行量、精度、小数位,以及谁有权限铸造(mint)或销毁(burn)它,所以它的结构就非常的清晰了,如下:

pub struct Mint {
/// 用于铸造新token的可选权限。mint权限只能在mint创建期间提供。
/// 如果没有mint权限,则mint具有固定的供应量,并且无法铸造更多的token。
pub mint_authority: COption<Pubkey>,

/// token的总供应量。
pub supply: u64,

/// 小数点右侧的十进制位数。
pub decimals: u8,

/// 如果此结构已初始化,则为`true`
pub is_initialized: bool,

/// 用于冻结token账户的可选权限。
pub freeze_authority: COption<Pubkey>,
}

然后使用anchor-spl crate中的token_interface模块来与Token ProgramToken Extension Program进行交互,以下是个简单的模板:

use anchor_spl::token_interface::{Mint, TokenInterface}; // 引入 Mint 账户类型和 Token 接口定义

#[derive(Accounts)] // 自动为该结构生成账户验证与解包逻辑
pub struct CreateMint<'info> {
#[account(mut)] // signer 是一个可变账户
pub signer: Signer<'info>, // 调用者的钱包地址,需要签名

#[account(
init, // 创建一个新账户
payer = signer, // signer 将为创建 mint 支付租金(SOL)
mint::decimals = 6, // 设置 token 的最大小数位为 6 位
mint::authority = signer.key(), // 设置 signer 为 mint 的铸造权限地址
mint::freeze_authority = signer.key(), // 设置 signer 为冻结权限地址
)]
pub mint: InterfaceAccount<'info, Mint>, // 创建一个新的 SPL Token Mint 账户(使用通用接口封装)

pub token_program: Interface<'info, TokenInterface>, // 传入的 SPL Token Program(支持多实现)
pub system_program: Program<'info, System>, // 系统程序,用于创建账户和支付租金
}

**#[account(…)]**是一个 属性宏语法块, Anchor 框架扩展定义用来给紧随其后的账户字段添加 初始化规则或约束条件,在上面模板中就是一个账户的约束。

当然在创建mint账户时,也可以使用PDA作为你的 mint::authority,那么可以让你的程序控制这个Token的铸造权限,而不是由某个外部用户来控制。创建地址如下:

#[account(
init, // 初始化一个新的账户
payer = signer, // 由 signer 支付租金(SOL)
mint::decimals = 6, // 设置代币小数位数为6
mint::authority = mint.key(), // 设置铸造权限为mint账户本身(PDA)
mint::freeze_authority = mint.key(), // 设置冻结权限为mint账户本身(PDA)
seeds = [b"mint"], // 用固定种子 "mint" 派生PDA
bump // 使用 bump 种子,防止哈希碰撞,使PDA合法
)]
pub mint: InterfaceAccount<'info, Mint>,

创建Token账户

在 Solana 的 SPL Token标准中,每种代币(对应一个 Mint 账户)都有很多 Token账户,每个 Token账户属于一个特定的用户钱包地址,专门用来保存该用户持有的该种代币的数量。它的结构如下:

/// 账户数据结构。
#[repr(C)] // 使用 C 语言的内存布局,确保在链上序列化一致性
#[derive(Clone, Copy, Debug, Default, PartialEq)] // 派生常见 trait,支持拷贝、调试、默认值等
pub struct Account {
/// 与此账户关联的 Mint(表示这是哪一种代币)
pub mint: Pubkey,

/// 此账户的所有者(钱包地址)
pub owner: Pubkey,

/// 此账户中持有的代币数量
pub amount: u64,

/// 如果 `delegate` 为 `Some`,则 `delegated_amount` 表示代理地址可使用的代币数量
pub delegate: COption<Pubkey>,

/// 此账户的状态(如:初始化、冻结)
pub state: AccountState,

/// 如果 `is_native.is_some`,说明是一个原生SOL包装账户,该值为其保持租金豁免所需的最低余额
/// 一个账户必须保持租金豁免,因此该值用于防止封装SOL(wSOL)账户余额低于该阈值
pub is_native: COption<u64>,

/// 授权代理地址可以使用的代币数量(只有当 `delegate` 存在时才生效)
pub delegated_amount: u64,

/// 可以关闭此账户的可选地址(授权关闭者)
pub close_authority: COption<Pubkey>,

///注意Pubkey 是 Solana 中的公钥(32字节地址)
}

此外,Token账户有个特定结构的Token账户——关联账户(ATA),就是某个用户的钱包地址(owner)+ 某个代币(mint)唯一对应的标准 Token 账户地址,它的用途是用于标准化存储某个钱包所持有的某种 SPL 代币的余额。

例如:钱包 A 拥有 100 个 USDC(mint = USDC 的地址) → 会有一个 ATA 专门存储这个 USDC 的余额
使用find_program_address(seed,bump)生成关联地址:

pub fn get_associated_token_address_and_bump_seed_internal(
wallet_address: &Pubkey, // 拥有者的钱包地址
token_mint_address: &Pubkey, // 代币的 mint 地址
program_id: &Pubkey, // 关联 Token 程序的 ID
token_program_id: &Pubkey, // Token 程序的 ID
) -> (Pubkey, u8) { // 返回值为关联账户的地址和 bump seed
// 使用 find_program_address 生成 PDA
Pubkey::find_program_address(
&[
&wallet_address.to_bytes(), // 所有者公钥的字节数组
&token_program_id.to_bytes(), // Token 程序的公钥字节数组
&token_mint_address.to_bytes(), // 代币 mint 地址的字节数组
],
program_id, // 与关联账户相关联的程序 ID(例如 Token Program ID)
)
}

根据用户具体的使用,有俩种方式创建Token账户

  • 当使用关联Token账户(ATA)时,使用associated_token约束.比如用户钱包

    use anchor_spl::associated_token::AssociatedToken;  // 导入 Anchor SPL 库中的关联 Token 程序相关功能
    use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; // 导入与 Token 程序相关的接口,Mint 是代币的铸造地址,TokenAccount 是代币账户,TokenInterface 是 Token 程序接口

    // --snip--(省略的部分,通常是函数或程序主体)

    #[derive(Accounts)] // 声明 Anchor 账户结构体,标记为 Accounts,用于描述与程序交互时需要的账户。
    pub struct CreateTokenAccount<'info> {
    #[account(mut)] // 表示这个账户是可变的,将会在后续的操作中修改
    pub signer: Signer<'info>, // signer 账户,表示谁发起这个操作,通常是一个钱包地址,用于支付创建账户的费用,并提供签名。

    #[account( // 账户宏,定义如何初始化 token_account,以下是初始化的相关参数
    init_if_needed, // 如果账户不存在则初始化它,如果已存在则不做任何处理
    payer = signer, // 创建该账户所需的费用将由 signer 账户支付
    associated_token::mint = mint, // 关联 Token 账户将对应的 mint 地址为指定的 mint 地址,表示 token 的种类
    associated_token::authority = signer, // 关联的 Token 账户的权限由 signer 控制,即 signer 可以操作该账户
    associated_token::token_program = token_program, // 关联 Token 程序,通常是 Solana 官方的 Token 程序
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>, // 目标账户,即创建的 Token 账户,通过 InterfaceAccount 将账户与 TokenAccount 关联
    pub mint: InterfaceAccount<'info, Mint>, // 代币的 mint 地址,表示 Token 的类型,用于标识该 Token 账户属于哪个代币种类
    pub token_program: Interface<'info, TokenInterface>, // 指定与 Token 相关的程序,通常是 Solana 官方的 Token 程序,用于处理代币操作(如转账、查询等)
    pub associated_token_program: Program<'info, AssociatedToken>, // 指定使用的关联 Token 程序,它负责管理与钱包地址关联的 Token 账户
    pub system_program: Program<'info, System>, // Solana 的系统程序,用于账户和资源管理(例如账户创建)
    }
  • 当使用非特定ATA的Token账户(如自定义PDA或使用密钥对公钥作为地址的Token账户)时,使用token约束,比如程序控制的账户

     use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; // 导入 Anchor SPL 库中的 Token 相关接口,Mint 是代币的铸造地址,TokenAccount 是代币账户,TokenInterface 是 Token 程序接口

    // --snip--(省略的部分,通常是函数或程序主体)

    #[derive(Accounts)] // 声明 Anchor 账户结构体,标记为 Accounts,用于描述与程序交互时需要的账户。
    pub struct CreateTokenAccount<'info> {
    #[account(mut)] // 表示这个账户是可变的,后续操作会修改它
    pub signer: Signer<'info>, // signer 账户,表示操作发起者,通常是一个钱包地址,提供签名并支付交易费用

    #[account( // 账户宏,定义如何初始化 token_account,以下是初始化的相关参数
    init_if_needed, // 如果账户不存在,则初始化该账户;如果已存在,则跳过初始化
    payer = signer, // 创建该账户所需的 SOL 租金由 signer 账户支付
    token::mint = mint, // 关联的 mint 地址,表示该 Token 账户的代币类型
    token::authority = token_account, // 该 Token 账户的授权者设置为 token_account,通常是签名者
    token::token_program = token_program, // 关联的 Token 程序,通常是 Solana 官方的 Token 程序
    seeds = [b"token"], // 使用种子值 `token` 来派生一个程序派生地址(PDA)
    bump // `bump` 是在计算派生地址时用于确保唯一性和合法性的附加参数
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>, // 创建的目标 Token 账户,表示该账户与 mint 地址关联
    pub mint: InterfaceAccount<'info, Mint>, // 代币的 mint 地址,表示该 Token 账户所属的代币种类
    pub token_program: Interface<'info, TokenInterface>, // 关联的 Token 程序,用于处理所有与 Token 相关的操作
    pub system_program: Program<'info, System>, // Solana 系统程序,用于创建账户等基础操作
    }

铸造代币

铸造代币是指通过调用 Token Program 上的 mint_to 指令创建新代币单位的过程。只有被指定为铸币账户上的铸币权限的地址才能铸造新代币。该指令还要求在代币接收地址上存在一个目标代币账户。
要通过 Anchor 程序铸造代币,你需要向 Token Program 或 Token Extension Program 的 指令发起跨程序调用(CPI)。
这意味着你要从程序中的指令调用 Token Program 或 Token Extension Program 上的 指令。你的程序作为中介,向代币程序传递所需的账户、指令数据和签名。

通过CPI铸造程序

MintTo结构体:

  • mint- 用于创建新代币单位的铸币账户
  • to- 用于接收新铸造代币的目标代币账户
  • authority- 拥有铸造代币权限的铸币授权
use anchor_lang::prelude::*; // 引入 Anchor 框架的基础设施
use anchor_spl::token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface}; // 引入 SPL Token 程序的接口

declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d"); // 声明当前程序的 ID,程序唯一标识符

#[program] // 程序宏,标记当前模块为 Anchor 程序
pub mod token_example {
use super::*; // 导入上面定义的内容

// mint_tokens 函数,用于铸造指定数量的代币到指定 Token 账户
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
// 构建 CPI 账户信息结构体
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(), // 代币 mint 地址
to: ctx.accounts.token_account.to_account_info(), // 目标 Token 账户
authority: ctx.accounts.signer.to_account_info(), // 执行铸币操作的授权者
};

let cpi_program = ctx.accounts.token_program.to_account_info(); // 获取关联的 Token 程序账户信息

let cpi_context = CpiContext::new(cpi_program, cpi_accounts); // 创建 CPI 上下文,包含程序和账户信息

// 调用 mint_to 函数进行铸币操作
token_interface::mint_to(cpi_context, amount)?; // 使用指定的数量铸造代币

Ok(()) // 返回 Ok,表示操作成功
}
}

// 账户结构体,用于定义 mint_tokens 函数的账户参数
#[derive(Accounts)]
pub struct MintTokens<'info> {
#[account(mut)] // signer 是可变的账户,可能会进行操作
pub signer: Signer<'info>, // 发起铸币操作的账户(签名者)

#[account(mut)] // mint 是可变的账户,代表代币种类
pub mint: InterfaceAccount<'info, Mint>, // 代币 mint 地址

#[account(mut)] // token_account 是可变的账户,表示目标 Token 账户
pub token_account: InterfaceAccount<'info, TokenAccount>, // 目标 Token 账户

pub token_program: Interface<'info, TokenInterface>, // 关联的 Token 程序
}

解释CPI相关的部分:

  • let cpi_accounts = MintTo { … };:构建一个 MintTo 结构体,表示代币铸造操作的账户信息.
  • let cpi_program = ctx.accounts.token_program.to_account_info();:获取 Token 程序的账户信息,这是代币相关操作的程序。
  • let cpi_context = CpiContext::new(cpi_program, cpi_accounts);:将 cpi_program 和 cpi_accounts 组合成一个 CPI 上下文,这将用来调用代币程序的 mint_to 函数。
  • token_interface::mint_to(cpi_context, amount)?;:调用 SPL Token 程序的 mint_to 函数来实际铸造代币,传入 CPI 上下文和指定的数量 amount。

通过CPI使用PDA铸币授权铸造代币

使用 PDA 来授权铸币的目的是确保某些操作只能由程序控制,并且可以通过程序进行管理,而不是依赖于某个签名者的私钥。具体操作时,我们通常会使用特定的种子来派生 PDA,并将它设置为 mint::authority,这样就能通过这个 PDA 来授权铸币操作。

结合之前所讲的 Token Mint部分,就好理解了。

use anchor_lang::prelude::*;  // 引入 Anchor 框架的标准库,提供基础功能
use anchor_spl::{ // 引入 anchor_spl 库,用于处理代币相关的操作
associated_token::AssociatedToken, // 用于与关联账户相关的功能
token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface}, // 处理代币的铸造、转账等
};

declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d"); // 定义程序的 ID,必须与部署合约时的 ID 匹配

#[program]
pub mod token_example { // 定义一个名为 token_example 的程序模块
use super::*; // 引入外部作用域中的所有内容

// 创建 mint 账户的函数
pub fn create_mint(ctx: Context<CreateMint>) -> Result<()> {
msg!("Created Mint Account: {:?}", ctx.accounts.mint.key()); // 输出 mint 账户的公钥
Ok(()) // 返回成功
}

// 铸造代币的函数
pub fn mint_tokens(ctx: Context<MintTokens>, amount: u64) -> Result<()> {
// 定义签名者种子(使用 "mint" 作为种子,添加 bump 值)
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];

// 定义 CPI 账户信息,用于铸造代币
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(), // mint 账户的公钥
to: ctx.accounts.token_account.to_account_info(), // 收款的 token 账户公钥
authority: ctx.accounts.mint.to_account_info(), // 铸造权限(mint 本身作为 authority)
};

let cpi_program = ctx.accounts.token_program.to_account_info(); // 引用 Token 程序
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds); // 创建 CPI 上下文,附带签名者的种子

token_interface::mint_to(cpi_context, amount)?; // 调用 mint_to 函数铸造代币
Ok(()) // 返回成功
}
}

// 定义创建 mint 账户时的参数
#[derive(Accounts)]
pub struct CreateMint<'info> {
#[account(mut)] // 表示 signer 账户是可变的,可以进行操作
pub signer: Signer<'info>, // 用于支付费用的账户(签名者)

#[account( // 定义 mint 账户的初始化配置
init, // 初始化 mint 账户
payer = signer, // 由 signer 支付费用
mint::decimals = 6, // 设置代币的小数位数为 6
mint::authority = mint, // mint 账户的 authority 设置为 mint 本身
mint::freeze_authority = mint, // mint 账户的 freeze authority 设置为 mint 本身
seeds = [b"mint"], // 使用 "mint" 字符串作为种子生成 PDA
bump // 使用 bump 值来确保 PDA 的合法性
)]
pub mint: InterfaceAccount<'info, Mint>, // mint 账户,实际存储代币的信息

pub token_program: Interface<'info, TokenInterface>, // Token 程序接口
pub system_program: Program<'info, System>, // 系统程序接口
}

// 定义铸币操作的参数
#[derive(Accounts)]
pub struct MintTokens<'info> {
#[account(mut)] // 表示 signer 账户是可变的
pub signer: Signer<'info>, // 用于支付费用的账户(签名者)

#[account( // 定义 token_account 的初始化配置
init_if_needed, // 如果 token_account 尚不存在,则进行初始化
payer = signer, // 由 signer 支付费用
associated_token::mint = mint, // 关联的 mint 账户
associated_token::authority = signer, // 关联账户的 authority 为 signer
associated_token::token_program = token_program, // 关联的 token 程序
)]
pub token_account: InterfaceAccount<'info, TokenAccount>, // 代币账户,存储持有的代币

#[account( // 定义 mint 账户的配置
mut, // mint 账户是可变的
seeds = [b"mint"], // 使用 "mint" 字符串作为种子生成 PDA
bump // 使用 bump 值来确保 PDA 的合法性
)]
pub mint: InterfaceAccount<'info, Mint>, // mint 账户,用于铸造代币

pub token_program: Interface<'info, TokenInterface>, // Token 程序接口
pub associated_token_program: Program<'info, AssociatedToken>, // 关联账户程序接口
pub system_program: Program<'info, System>, // 系统程序接口
}

转移代币

转移代币涉及将代币从一个代币账户移动到另一个共享相同铸造的代币账户。这是通过在代币程序上调用 transfer_checked 指令来完成的。只有指定为源代币账户所有者(权限)的地址可以从该账户中转移代币。

通过CPI转移代币

使用token_interface::transfer_checked函数对代币程序或代币扩展程序进行 CPI。此函数需要:

  • TransferChecked结构体,该结构体指定所需的账户:
    • mint- 指定要转移的代币类型的铸造账户
    • from- 要转移代币的源代币账户
    • to- 接收转移代币的目标代币账户
    • authority- 源代币账户的所有者
  • 要转移的代币 ,以代币的基本单位调整小数。(例如,如果铸造有 2 位小数,则 100 的数量 = 1 个代币)amount
use anchor_lang::prelude::*; // 导入 Anchor 的预定义模块
use anchor_spl::token_interface::{self, TokenAccount, TokenInterface, TransferChecked}; // 导入 Anchor SPL Token 相关模块

declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d"); // 声明程序的唯一标识符(ID)

#[program]
pub mod token_example {
use super::*;

// 转账代币的函数,`amount` 参数是转账的代币数量
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
// 获取 mint 账户的 decimals 属性,用于正确处理小数位数
let decimals = ctx.accounts.mint.decimals;

// 定义 CPI 调用所需的账户信息
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(), // 代币的 Mint 账户(发行者)
from: ctx.accounts.sender_token_account.to_account_info(), // 发送方的代币账户
to: ctx.accounts.recipient_token_account.to_account_info(), // 接收方的代币账户
authority: ctx.accounts.signer.to_account_info(), // 转账操作的授权者(签名者)
};

// 获取要调用的程序,这里是 Solana 的 Token Program
let cpi_program = ctx.accounts.token_program.to_account_info();

// 构建 CPI 上下文,将账户信息与目标程序关联
let cpi_context = CpiContext::new(cpi_program, cpi_accounts);

// 执行 CPI 调用,完成代币的转账操作
token_interface::transfer_checked(cpi_context, amount, decimals)?; // `amount` 是转账数量,`decimals` 是小数位数

// 返回成功的结果
Ok(())
}
}

// 定义账户结构,包含与转账相关的账户信息
#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)] // `signer` 是一个可变账户,表示发起转账操作的签名者
pub signer: Signer<'info>,

#[account(mut)] // `mint` 账户是代币的 Mint 账户,控制代币的创建和管理
pub mint: InterfaceAccount<'info, Mint>,

#[account(mut)] // `sender_token_account` 是发送方的代币账户,存储发送者的代币
pub sender_token_account: InterfaceAccount<'info, TokenAccount>,

#[account(mut)] // `recipient_token_account` 是接收方的代币账户,存储接收者的代币
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,

pub token_program: Interface<'info, TokenInterface>, // Token Program,用于代币操作
}

通过CPI使用PDA代币所有者转移代币

通过程序控制的 PDA 地址来充当代币账户的所有者,而程序通过 CPI 调用代币程序(Token Program)进行代币转移操作。

结合之前讲的Mint账户,铸造代币的内容,来实现通过CPI使用PDA代币所有者转移代币

use anchor_lang::prelude::*;  // 引入 Anchor 的预定义模块,用于开发程序的基础功能。
use anchor_spl::{
associated_token::AssociatedToken, // 引入 Anchor SPL 中的 Associated Token 模块,处理与 Token 账户相关的操作。
token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked}, // 引入 Token 接口相关功能。
};

declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d"); // 定义智能合约的 ID(这个 ID 是一个唯一标识符)。

#[program]
pub mod token_example { // 定义程序的模块,程序的名称为 token_example。
use super::*; // 引入外部代码(当前模块)的所有功能。

// 创建和铸造代币的功能
pub fn create_and_mint_tokens(ctx: Context<CreateAndMintTokens>, amount: u64) -> Result<()> {
// 创建铸造的种子数据,用于通过 PDA 进行签名。
let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];

// 创建 MintTo CPI 调用,表示铸造代币操作。
let cpi_accounts = MintTo {
mint: ctx.accounts.mint.to_account_info(), // 获取 mint 账户信息。
to: ctx.accounts.token_account.to_account_info(), // 获取代币账户信息,代币会铸造到此账户。
authority: ctx.accounts.mint.to_account_info(), // mint 的 authority 账户。
};

let cpi_program = ctx.accounts.token_program.to_account_info(); // 获取 token 程序的账户信息。
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds); // 创建 CPI 上下文并设置 signer。

token_interface::mint_to(cpi_context, amount)?; // 调用 mint_to 函数,铸造 `amount` 数量的代币。
Ok(()) // 返回成功。
}

// 转账代币的功能
pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
// 创建转账的种子数据,用于通过 PDA 进行签名。
let signer_seeds: &[&[&[u8]]] = &[&[b"token", &[ctx.bumps.sender_token_account]]];

// 获取待转账的数量。
let amount = ctx.accounts.sender_token_account.amount;
let decimals = ctx.accounts.mint.decimals; // 获取代币的 decimals。

// 创建 TransferChecked CPI 调用,表示代币转账操作。
let cpi_accounts = TransferChecked {
mint: ctx.accounts.mint.to_account_info(), // 获取 mint 账户信息。
from: ctx.accounts.sender_token_account.to_account_info(), // 获取发送方 token 账户信息。
to: ctx.accounts.recipient_token_account.to_account_info(), // 获取接收方 token 账户信息。
authority: ctx.accounts.sender_token_account.to_account_info(), // 获取发送方账户作为授权者。
};

let cpi_program = ctx.accounts.token_program.to_account_info(); // 获取 token 程序的账户信息。
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds); // 创建 CPI 上下文并设置 signer。

token_interface::transfer_checked(cpi_context, amount, decimals)?; // 调用 transfer_checked 函数执行转账。
Ok(()) // 返回成功。
}
}

#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>, // 发送方账户,用于支付账户创建费用和铸造代币的授权。
#[account(
init,
payer = signer,
mint::decimals = 6, // 设置代币的小数位数。
mint::authority = mint, // 设置 mint 的权限。
mint::freeze_authority = mint, // 设置冻结权限。
seeds = [b"mint"], // 使用种子 [b"mint"] 来生成 PDA。
bump // 使用 bump 来确保生成的 PDA 地址唯一。
)]
pub mint: InterfaceAccount<'info, Mint>, // 创建并初始化一个 mint 账户。

#[account(
init,
payer = signer,
token::mint = mint, // 关联创建的 mint 账户。
token::authority = token_account, // 设置 token_account 为 token 账户的权限。
seeds = [b"token"], // 使用种子 [b"token"] 来生成 PDA。
bump // 使用 bump 来确保生成的 PDA 地址唯一。
)]
pub token_account: InterfaceAccount<'info, TokenAccount>, // 创建并初始化一个 token 账户,用于接收铸造的代币。

pub token_program: Interface<'info, TokenInterface>, // token 程序的接口。
pub system_program: Program<'info, System>, // 系统程序的接口,用于账户初始化等操作。
}

#[derive(Accounts)]
pub struct TransferTokens<'info> {
#[account(mut)]
pub signer: Signer<'info>, // 发送方账户,用于授权代币转移。

#[account(
mut,
seeds = [b"mint"], // 使用种子 [b"mint"] 来生成 mint 账户的 PDA。
bump // 使用 bump 来确保生成的 PDA 地址唯一。
)]
pub mint: InterfaceAccount<'info, Mint>, // mint 账户,代表发行的代币。

#[account(
mut,
token::mint = mint, // 关联 mint 账户。
token::authority = sender_token_account, // 设置 sender_token_account 为 token 账户的权限。
seeds = [b"token"], // 使用种子 [b"token"] 来生成 PDA。
bump // 使用 bump 来确保生成的 PDA 地址唯一。
)]
pub sender_token_account: InterfaceAccount<'info, TokenAccount>, // 发送方的 token 账户。

#[account(
init_if_needed, // 如果接收方账户尚未存在,则进行初始化。
payer = signer, // 使用 signer 账户支付创建费用。
associated_token::mint = mint, // 关联 mint 账户。
associated_token::authority = signer, // 设置 signer 为接收方账户的权限。
associated_token::token_program = token_program, // 设置 token 程序。
)]
pub recipient_token_account: InterfaceAccount<'info, TokenAccount>, // 接收方的 token 账户。

pub token_program: Interface<'info, TokenInterface>, // token 程序的接口。
pub associated_token_program: Program<'info, AssociatedToken>, // 关联 token 程序接口,用于关联 token 账户。
pub system_program: Program<'info, System>, // 系统程序接口,用于账户初始化等操作。
}

以上就是一个创建Mint账户,Token账户,实现铸造代币,转移代币的过程。