LogoAnchor 中文文档

转移代币

了解如何通过 Anchor 中的跨程序调用 (CPIs) 在代币账户之间转移代币。

如何转移代币

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

代币程序代币扩展程序 具有相似的实现以实现相同的功能。

示例

要通过 Anchor 程序转移代币,你需要对代币程序或代币扩展程序上的 transfer_checked 指令进行跨程序调用 (CPI)。

这意味着你从你程序中的一个指令调用代币程序或代币扩展程序上的 transfer_checked 指令。你的程序作为中介,传递所需的账户、指令数据和签名到代币程序。

通过 CPI 转移代币

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

  1. TransferChecked 结构体,该结构体指定所需的账户:

    • mint - 指定要转移的代币类型的铸造账户
    • from - 要转移代币的源代币账户
    • to - 接收转移代币的目标代币账户
    • authority - 源代币账户的所有者
  2. 要转移的代币 amount,以代币的基本单位调整小数。(例如,如果铸造有 2 位小数,则 100 的数量 = 1 个代币)

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, TokenAccount, TokenInterface, TransferChecked};
 
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
 
#[program]
pub mod token_example {
    use super::*;
 
    pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
        let decimals = ctx.accounts.mint.decimals;
 
        let cpi_accounts = TransferChecked {
            mint: ctx.accounts.mint.to_account_info(),
            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(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
        // [!代码高亮]
        token_interface::transfer_checked(cpi_context, amount, decimals)?;
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct TransferTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(mut)]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(mut)]
    pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
    #[account(mut)]
    pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
}

至少需要以下账户:

snippet
#[derive(Accounts)]
pub struct TransferTokens<'info> {
    // 源代币账户的所有者
    #[account(mut)]
    pub signer: Signer<'info>,
    // 指定代币类型的铸造账户
    #[account(mut)]
    pub mint: InterfaceAccount<'info, Mint>,
    // 要转移代币的源代币账户
    #[account(mut)]
    pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
    // 接收代币的目标代币账户
    #[account(mut)]
    pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
    // 将处理转移的代币程序
    pub token_program: Interface<'info, TokenInterface>,
}

在指令逻辑中,使用:

  • TransferChecked 结构体来指定所需的账户
  • token_interface::transfer_checked 函数进行 CPI
snippet
pub fn transfer_tokens(ctx: Context<TransferTokens>, amount: u64) -> Result<()> {
    // 获取此铸造的位数
    let decimals = ctx.accounts.mint.decimals;
 
    // 创建带有所需账户的 TransferChecked 结构体
    let cpi_accounts = TransferChecked {
        mint: ctx.accounts.mint.to_account_info(),
        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(),
    };
 
    // 在 CPI 中被调用的程序
    let cpi_program = ctx.accounts.token_program.to_account_info();
 
     // 将账户和程序组合成 "CpiContext"
    let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
 
    // 对代币程序的 transfer_checked 指令进行 CPI
    token_interface::transfer_checked(cpi_context, amount, decimals)?;
    Ok(())
}

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

你可以创建一个以 PDA 作为所有者的代币账户。这允许你的程序通过在跨程序调用 (CPI) 中 "签署" PDA 的种子来从程序控制的代币账户转移代币。当你希望程序本身根据程序内部定义的条件控制代币转移时,这种模式非常有用。

lib.rs
use anchor_lang::prelude::*;
use anchor_spl::{
    associated_token::AssociatedToken,
    token_interface::{self, Mint, MintTo, TokenAccount, TokenInterface, TransferChecked},
};
 
declare_id!("3pX5NKLru1UBDVckynWQxsgnJeUN3N1viy36Gk9TSn8d");
 
#[program]
pub mod token_example {
    use super::*;
 
    pub fn create_and_mint_tokens(ctx: Context<CreateAndMintTokens>, amount: u64) -> Result<()> {
        let signer_seeds: &[&[&[u8]]] = &[&[b"mint", &[ctx.bumps.mint]]];
 
        let cpi_accounts = MintTo {
            mint: ctx.accounts.mint.to_account_info(),
            to: ctx.accounts.token_account.to_account_info(),
            authority: ctx.accounts.mint.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
        token_interface::mint_to(cpi_context, amount)?;
        Ok(())
    }
 
    pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
        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;
 
        let cpi_accounts = TransferChecked {
            mint: ctx.accounts.mint.to_account_info(),
            from: ctx.accounts.sender_token_account.to_account_info(),
            to: ctx.accounts.recipient_token_account.to_account_info(),
            authority: ctx.accounts.sender_token_account.to_account_info(),
        };
        let cpi_program = ctx.accounts.token_program.to_account_info();
        let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
        token_interface::transfer_checked(cpi_context, amount, decimals)?;
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        payer = signer,
        mint::decimals = 6,
        mint::authority = mint,
        mint::freeze_authority = mint,
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(
        init,
        payer = signer,
        token::mint = mint,
        token::authority = token_account,
        seeds = [b"token"],
        bump
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}
 
#[derive(Accounts)]
pub struct TransferTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        mut,
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(
        mut,
        token::mint = mint,
        token::authority = sender_token_account,
        seeds = [b"token"],
        bump
    )]
    pub sender_token_account: InterfaceAccount<'info, TokenAccount>,
    #[account(
        init_if_needed,
        payer = signer,
        associated_token::mint = mint,
        associated_token::authority = signer,
        associated_token::token_program = token_program,
    )]
    pub recipient_token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

在这个例子中,源代币账户的所有者被设置为程序派生地址 (PDA)。PDA 是使用种子 b"token" 派生的。这意味着程序本身通过这个 PDA 控制代币账户的代币转移。

snippet
#[derive(Accounts)]
pub struct CreateAndMintTokens<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    #[account(
        init,
        payer = signer,
        mint::decimals = 6,
        mint::authority = mint,
        mint::freeze_authority = mint,
        seeds = [b"mint"],
        bump
    )]
    pub mint: InterfaceAccount<'info, Mint>,
    #[account(
        init,
        payer = signer,
        token::mint = mint,
        // [!代码 单词:token_account]
        // [!代码高亮]
        token::authority = token_account,
        seeds = [b"token"],
        bump
    )]
    pub token_account: InterfaceAccount<'info, TokenAccount>,
    pub token_program: Interface<'info, TokenInterface>,
    pub system_program: Program<'info, System>,
}

要转移代币,程序必须通过包含种子和 bump 来 "签署" PDA。在创建 CPI 上下文时,通过将种子和 bump 传递给 with_signer 方法来做到这一点。

snippet
pub fn transfer_tokens(ctx: Context<TransferTokens>) -> Result<()> {
    // [!代码 单词:signer_seeds]
    // [!代码高亮]
    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;
 
    let cpi_accounts = TransferChecked {
        mint: ctx.accounts.mint.to_account_info(),
        from: ctx.accounts.sender_token_account.to_account_info(),
        to: ctx.accounts.recipient_token_account.to_account_info(),
        authority: ctx.accounts.sender_token_account.to_account_info(),
    };
    let cpi_program = ctx.accounts.token_program.to_account_info();
    // [!代码高亮:2]
    let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(signer_seeds);
    token_interface::transfer_checked(cpi_context, amount, decimals)?;
    Ok(())
}

请注意,在此示例中, 동일한 PDA 同时用作源代币账户的地址和源代币账户的所有者。

On this page

在GitHub上编辑