如何使用 Anchor 转移 SOL 和 SPL 代币
- 原文链接:www.quicknode.com/guides...
- 译者:AI翻译官,校对:翻译小组
- 本文链接:learnblockchain.cn/article…
Anchor 是一个加速在 Solana 区块链上开发安全程序的框架。在使用 Solana 和 Anchor 时,你可能会遇到需要在账户之间发送 SOL 或 SPL 代币的情况(例如,处理用户支付到你的国库,或让用户将他们的 NFT 发送到托管账户)。本指南将引导你通过使用 Anchor 转移 SOL 和 SPL 代币的过程。我们将涵盖程序和测试所需的代码,以确保账户之间的代币无缝转移。
在本指南中,你将:
依赖项 | 版本 |
---|---|
anchor-lang | 0.26.0 |
anchor-spl | 0.26.0 |
solana-program | 1.14.12 |
spl-token | 3.5.0 |
通过访问 https://beta.solpg.io/ 在 Solana Playground 创建一个新项目。Solana Playground 是一个基于浏览器的 Solana 代码编辑器,可以让我们快速启动这个项目。你可以在自己的代码编辑器中跟随,但本指南将根据 Solana Playground 的必需步骤进行调整。首先,点击“创建新项目”:
输入项目名称“transfers”,选择“Anchor (Rust)”:
由于此项目仅用于演示目的,我们可以使用一个“临时”钱包。Solana Playground 使创建钱包变得简单。你应该看到浏览器窗口左下角显示红点“未连接”。点击它:
Solana Playground 将为你生成一个钱包(或者你可以导入自己的钱包)。可以保存以备后用,当你准备好时点击继续。一个新的钱包将被初始化并连接到 Solana 开发网络。Solana Playground 会自动向你的新钱包空投一些 SOL,但我们会请求一些额外的 SOL,以确保我们有足够的资金来部署我们的程序。在浏览器终端中,你可以使用 Solana CLI 命令。输入 solana airdrop 2
向你的钱包空投 2 SOL。你的钱包现在应已连接到开发网络,余额为 6 SOL:
你准备好了!让我们开始构建!
首先打开 lib.rs
并删除启动代码。拥有一个空白的界面后,我们可以开始构建程序。首先,我们需要导入一些依赖项。将以下内容添加到文件顶部:
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer as SplTransfer};
use solana_program::system_instruction;
declare_id!("11111111111111111111111111111111");
这些导入将允许我们使用 Anchor 框架、SPL 代币程序和系统程序。Solana Playground 在我们部署程序时将自动更新 declare_id!
。
要创建一个转移 SOL(或 lamports)的函数,我们必须为我们的转移上下文定义一个结构。将以下内容添加到你的程序中:
#[derive(Accounts)]
pub struct TransferLamports<'info> {
#[account(mut)]
pub from: Signer<'info>,
#[account(mut)]
pub to: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
该结构定义了一个将签名 transaction 并发送 SOL 的 from
账户,一个将接收 SOL 的 to
账户,以及处理转账的系统程序。属性 #[account(mut)]
表示程序将修改该账户。
接下来,我们将创建处理转账的函数。将以下内容添加到你的程序中:
#[program]
pub mod solana_lamport_transfer {
use super::*;
pub fn transfer_lamports(ctx: Context<TransferLamports>, amount: u64) -> Result<()> {
let from_account = &ctx.accounts.from;
let to_account = &ctx.accounts.to;
// Create the transfer instruction
let transfer_instruction = system_instruction::transfer(from_account.key, to_account.key, amount);
// Invoke the transfer instruction
anchor_lang::solana_program::program::invoke_signed(
&transfer_instruction,
&[
from_account.to_account_info(),
to_account.clone(),
ctx.accounts.system_program.to_account_info(),
],
&[],
)?;
Ok(())
}
}
以下是该代码片段不同部分的简要解释:
#[program]
属性将模块标记为 Anchor 程序。它生成所需的模板以定义程序的入口点,并自动处理账户验证和反序列化。
在 solana_lamport_transfer
模块内部,使用 use super::*;
导入父模块所需的项。
transfer_lamports
函数接受一个 Context 和一个 amount
作为其参数。Context 包含交易所需的账户信息,amount
是要转移的 lamports 数量。
我们创建对上下文中的 from_account
和 to_account
的引用,这些引用将用于转账。
system_instruction::transfer
函数创建一个转账指令,该指令接受 from_account
的公钥、to_account
的公钥以及要转移的 amount
作为参数。
anchor_lang::solana_program::program::invoke_signed
函数调用转账指令,并使用交易的签名者(from_account
)。它接受转账指令、from_account
、to_account
和 system_program
的账户信息数组,以及一个空数组作为签名者。
transfer_lamports
函数返回 Ok(())
以表示执行成功。
你可以通过点击 Build
按钮或在终端中输入 anchor build
来确保一切正常。如果出现错误,请检查你的代码与本指南中的代码并遵循错误响应的建议。如果你需要帮助,请随时通过 Discord 联系我们。
在我们部署程序之前,让我们添加一个第二个功能来转移 SPL 代币。首先,创建该功能的新上下文。在你的程序中 _TransferLamports_
结构下添加以下内容:
#[derive(Accounts)]
pub struct TransferSpl<'info> {
pub from: Signer<'info>,
#[account(mut)]
pub from_ata: Account<'info, TokenAccount>,
#[account(mut)]
pub to_ata: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
此结构将需要 from
钱包(我们的签名者)、来自钱包的关联代币账户(ATA)、目的钱包的 ATA 和代币程序。你不需要目的钱包的主账户,因为它将保持不变(只有其 ATA 会被修改)。现在让我们创建我们的函数。在 solana_lamport_transfer
模块中,在 transfer_lamports
指令下,添加以下内容:
pub fn transfer_spl_tokens(ctx: Context<TransferSpl>, amount: u64) -> Result<()> {
let destination = &ctx.accounts.to_ata;
let source = &ctx.accounts.from_ata;
let token_program = &ctx.accounts.token_program;
let authority = &ctx.accounts.from;
// 从承接者转账给初始化者
let cpi_accounts = SplTransfer {
from: source.to_account_info().clone(),
to: destination.to_account_info().clone(),
authority: authority.to_account_info().clone(),
};
let cpi_program = token_program.to_account_info();
token::transfer(
CpiContext::new(cpi_program, cpi_accounts),
amount
)?;
Ok(())
}
让我们逐步分析这个函数:
transfer_spl_tokens
函数接受一个 Context 和一个 amount
作为参数。TransferSpl 上下文包含我们在前一步中定义的交易所需账户信息。
我们从上下文中创建对 destination
、source
、token_program
和 authority
的引用。这些变量分别代表目的 ATA、源 ATA、代币程序和签名者的钱包。
使用 source
、destination
和 authority
的账户信息创建 SplTransfer 结构。当进行跨程序调用(CPI)到 SPL Token 程序时,此结构将提供账户信息。
使用 cpi_program
和 cpi_accounts
以及要转移的 amount
调用 token::transfer
函数。此函数执行指定 ATA 之间的实际代币转移。
我们返回 Ok(())
以表示成功执行。
继续构建你的程序,以确保一切正常工作,方法是点击“Build”或输入 anchor build
。
如果程序成功构建,你可以将其部署到 Solana 开发网络。
点击页面左侧的工具图标 🛠,然后点击“Deploy”:
这可能需要一分钟或两分钟,但完成后,你应该在浏览器终端中看到类似如下的信息:
干得好!让我们测试一下。
返回你编辑 lib.rs
文件的主文件窗口,点击页面左上角的 📑 图标。打开 anchor.test.ts
并用以下内容替换其内容:
import {
createMint,
createAssociatedTokenAccount,
mintTo,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
describe("Test transfers", () => {});
现在你可以访问 RPC 端点的日志,帮助你更有效地排除问题。如果你遇到 RPC 调用的问题,只需在你的 QuickNode 仪表板中检查日志以快速识别和解决问题。了解更多关于日志历史限制的内容,请查看 我们的定价页面。
这段代码将导入 SPL Token 程序中创建铸币、创建关联代币账户和铸造代币到关联代币账户所需的函数。这还将为我们的程序创建一个测试套件。
在你的测试套件中,添加以下代码:
it("transferLamports", async () => {
// 生成新账户的密钥对
const newAccountKp = new web3.Keypair();
// 发送交易
const data = new BN(1000000);
const tx = await pg.program.methods
.transferLamports(data)
.accounts({
from: pg.wallet.publicKey,
to: newAccountKp.publicKey,
})
.signers([pg.wallet.keypair])
.transaction();
const txHash = await web3.sendAndConfirmTransaction(pg.program.provider.connection, tx, [pg.wallet.keypair]);
console.log(`https://explorer.solana.com/tx/${txHash}?cluster=devnet`);
const newAccountBalance = await pg.program.provider.connection.getBalance(
newAccountKp.publicKey
);
assert.strictEqual(
newAccountBalance,
data.toNumber(),
"The new account should have the transferred lamports"
);
});
在这个 transferLamports
测试中发生了什么:
data
,其值设为 1,000,000 lamports (0.001 SOL)(注意:Anchor 期望我们将此值作为大数字类型传递)。pg.program.methods.transferLamports(data)
执行 Solana 程序的 transferLamports
函数。该交易使用 accounts
方法指定所用账户,其中 from
账户是测试钱包的公钥,to
账户是新生成账户的公钥。Anchor 知道我们需要系统程序,因此这里不需要传递它。signers
方法传递。transaction()
方法创建交易。sendAndConfirmTransaction()
等待交易被 确认。在我们检查新账户的余额时,确保它已更新为转移的金额是很重要的。getBalance()
获取新账户的余额,并将其存储在 newAccountBalance
变量中。assert.strictEqual
进行断言,以确认新账户的余额与转移金额匹配。只有当余额匹配预期金额时,测试才会成功。在你的 transferLamports
测试之后,但在同一个测试套件中,添加一个测试以测试你的 SPL 代币转移:
it("transferSplTokens", async () => {
// 为新账户生成密钥对
const fromKp = pg.wallet.keypair;
const toKp = new web3.Keypair();
// 创建新的铸币并初始化它
const mint = await createMint(
pg.program.provider.connection,
pg.wallet.keypair,
fromKp.publicKey,
null,
0
);
// 为新账户创建关联代币账户
const fromAta = await createAssociatedTokenAccount(
pg.program.provider.connection,
pg.wallet.keypair,
mint,
fromKp.publicKey
);
const toAta = await createAssociatedTokenAccount(
pg.program.provider.connection,
pg.wallet.keypair,
mint,
toKp.publicKey
);
// 铸造代币到 'from' 关联代币账户
const mintAmount = 1000;
await mintTo(
pg.program.provider.connection,
pg.wallet.keypair,
mint,
fromAta,
pg.wallet.keypair.publicKey,
mintAmount
);
// 发送交易
const transferAmount = new BN(500);
const tx = await pg.program.methods
.transferSplTokens(transferAmount)
.accounts({
from: fromKp.publicKey,
fromAta: fromAta,
toAta: toAta,
tokenProgram: TOKEN_PROGRAM_ID,
})
.signers([pg.wallet.keypair, fromKp])
.transaction();
const txHash = await web3.sendAndConfirmTransaction(pg.program.provider.connection, tx, [pg.wallet.keypair, fromKp]);
console.log(`https://explorer.solana.com/tx/${txHash}?cluster=devnet`);
const toTokenAccount = await pg.connection.getTokenAccountBalance(toAta);
assert.strictEqual(
toTokenAccount.value.uiAmount,
transferAmount.toNumber(),
"The 'to' token account should have the transferred tokens"
);
});
以下是 transferSplTokens
测试中发生的事情:
transferSplTokens
指令。此次交易使用的账户通过 accounts 方法指定,其中 from
帐户是测试钱包的公钥,fromAta
帐户是源关联代币账户,toAta
帐户是目标关联代币账户,而 tokenProgram
是 SPL Token 程序 ID。transaction()
方法创建交易。sendAndConfirmTransaction()
等待交易被 最终确定。这对于确保当我们检查新账户的余额时,已更新为转移的金额非常重要。getTokenAccountBalance()
获取新代币账户的余额,并将其存储在 toTokenAccount
变量中。assert.strictEqual
进行断言以确认新账户的余额与转移金额匹配。仅当余额与预期金额匹配时,测试才会成功。干得不错——让我们测试一下!按下屏幕左侧的 🧪 测试 按钮运行测试。你应该会看到两个测试都通过,如下所示:
Running tests... anchor.test.ts: Test https://explorer.solana.com/tx/5DNZm9oCtzMFSqUte6bt9tW5iW95AS77hSz8uqjZjD41rkJpCDLHRti6X7iRDrCfHRRGpMeAAePrVcKW4Qg3C9GB?cluster=devnet ✔ transferLamports (13409ms) https://explorer.solana.com/tx/3KCDqrbUonDBQSZfN8jFYZH8vo4fWgLfa3nCSWfCmeNgva3yVCeiy7QfiH9Az8U8LQpS1VKMELCH2wPDL4BcgKD6?cluster=devnet ✔ transferSplTokens (15975ms) 2 passing (29s)
就这样!干得好。
你已经使用自己的 Solana 程序实现了本地 SOL 转账和 SPL 代币转账。这是构建自己的 NFT 项目、游戏或 DeFi 应用程序的一个很好的开始。继续构建吧!
如果你卡住了,有问题,或者想聊聊你正在构建的内容,欢迎通过 Discord 或 Twitter 联系我们!
如果你对本指南有任何反馈, 告诉我们 。我们很乐意听取你的意见。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!