SPL代币账户概览在开始之前,了解Solana代币计划账户的几个组件的工作原理会很有帮助:MintID和关联代币账户。MintID每个SPL代币都有一个唯一的铸币ID,可以将其与任何其他类型的代币区分开来。例如,USDCSPL代币铸币ID为[EPjFWdd5AufqSSqe
<!--StartFragment-->
在开始之前,了解 Solana 代币计划账户的几个组件的工作原理会很有帮助:Mint ID 和关联代币账户。
每个 SPL 代币都有一个唯一的铸币 ID,可以将其与任何其他类型的代币区分开来。例如,$USDC SPL 代币铸币 ID 为[EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v],$SAMO 铸币 ID 为[7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU]。
值得注意的是,每个 NFT 都有一个唯一的铸币地址(这在一定程度上使其不可替代)。例如,Famous Fox #4679 的铸币 ID 为GS2AtoEoL9DgaYg9xC7cUumftB7vb2CPMrczZCKgJv1Y],Famous Fox #6562 的铸币 ID 为7FTdQdMqkk5Xc2oFsYR88BuJt2yyCPReTpqr3viH6b6C。正如您将在稍后看到的,这是这两个代币之间的显著区别。
Solana 代币计划从用户的主要系统帐户地址和代币铸币地址派生出“代币帐户密钥,允许用户为他们拥有的每个代币创建一个主代币帐户”。该帐户称为关联代币帐户或“ATA”。实际上,ATA 是与用户和特定代币铸币厂相关联的唯一帐户。例如,假设我是帐户“DEMO...1234”的所有者。我可以拥有多个 ATA——我持有的每个代币铸币厂都有一个 ATA(例如,每个 $USDC、$SAMO 等都有一个唯一的 ATA,如下所示)。
代币转移必须始终在与同一铸币地址关联的两个 ATA 之间进行。这意味着我们无法将 $USDC 从我的 ATA 发送到您的 $SAMO ATA(见下面的示例)。
正如您稍后将在我们的代码中看到的那样,每个帐户并非都拥有每个铸币的 ATA。如果用户之前没有与代币交互,则发送者必须“创建”它并存入必要的租金以使帐户保持活跃。
这个概念一开始可能有点难懂,但它很重要,所以花点时间确保你理解了这一点!我发现在 Solana Explorer 的代币详细信息页面上浏览我的钱包很有帮助 ?display=detail 。你可以看到你的每个 ATA、它们的帐户 ID 和关联的 Mint ID:
如果您想进一步了解 Solana SPL 代币程序库,请访问spl.solana.com。
准备好开始编码了吗?我也是!
使用以下命令在终端中创建一个新的项目目录:
mkdir spl-transfer
cd spl-transfer
为你的应用创建一个文件app.ts:
echo > app.ts
使用“是”标志初始化您的项目以使用新包的默认值:
yarn init --yes
#or
npm init --yes
创建一个启用了 .json 导入的tsconfig.json :
tsc -init --resolveJsonModule true
我们需要为本次练习添加 Solana Web3 和 SPL Token 库。在您的终端中输入:
yarn add @solana/web3.js@1 @solana/spl-token
#or
npm install @solana/web3.js@1 @solana/spl-token
我们需要完成的第一个任务是创建一个带有钱包的帐户并为其注资。我们将使用下面的便捷工具自动生成一个新钱包并向其中空投 1 SOL。(如果您更喜欢手动方式,也可以使用Keypair.generate()
和功能来实现这一点)。requestAirdrop()
🔑使用 Devnet SOL 生成新钱包
创建新钱包
成功生成密钥对后,您会注意到两个新常量:secret
和FROM_KEYPAIR
,密钥对。secret
是一个 32 字节数组,用于生成公钥和私钥。FROM_KEYPAIR
是一个密钥对实例,用于签署交易(我们空投了一些 devnet SOL 来支付 gas 费用)。如果还没有,请确保将其添加到代码中其他常量下方。
在导入下方,粘贴您的新机密,并添加:
import { Keypair } from "@solana/web3.js";
const secret = [0,...,0]; // 👈 Replace with your secret
const FROM_KEYPAIR = Keypair.fromSecretKey(new Uint8Array(secret));
console.log(`My public key is: ${FROM_KEYPAIR.publicKey.toString()}.`);
用于简化调试的日志
您现在可以访问 RPC 端点的日志,从而帮助您更有效地解决问题。如果您在 RPC 调用中遇到问题,只需检查 QuickNode 仪表板中的日志即可快速识别和解决问题。
要完成本指南,您需要一些 Devnet 上的 SPL 代币。有多种方法可以获取 Devnet SPL 代币 - 以下列出几种。
在继续之前,您应该能够进入 Solana Explorer 并查看您的钱包在 devnet 上有 SOL 余额和至少一个 SPL 代币。它看起来应该像这样:
让我们开始吧。
打开app.ts,并将以下导入粘贴到第 1 行:
import { getOrCreateAssociatedTokenAccount, createTransferInstruction } from "@solana/spl-token";
import { Connection, Keypair, ParsedAccountData, PublicKey, sendAndConfirmTransaction, Transaction } from "@solana/web3.js";
除了我们在上一步中创建的钱包之外,我们还从 Solana Web3 库中导入了一些基本的方法和类。
要在 Solana 上构建,您需要一个 API 端点来连接网络。欢迎您使用公共节点或部署和管理自己的基础设施;但是,如果您希望响应时间缩短 8 倍,您可以将繁重的工作交给我们。了解为什么 Solana 上超过 50% 的项目选择 QuickNode 并在此处]注册免费帐户。我们将使用 Solana Devnet 节点。
复制 HTTP 提供程序链接:
在app.ts中的导入语句下,声明您的 RPC 并建立与 Solana 的连接:
const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);
您需要声明几个变量来运行脚本:您的目标帐户(您希望向其转移代币的所有者)、您要转移的代币的铸币地址以及要转移的代币数量。添加以下声明SOLANA_CONNECTION
:
const DESTINATION_WALLET = 'DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H';
const MINT_ADDRESS = 'DoJuta7joTSuuoozqQtjtnASRYiVsT435gh4srh5LLGK'; //You must change this value!
const TRANSFER_AMOUNT = 1;
将其替换MINT_ADDRESS
为您计划发送的代币的铸币地址。如果您不知道代币的铸币地址,可以前往并单击复制图标以获取您的铸币地址。将其粘贴到MINT_ADDRESS变量中。
注意:如果愿意,您还可以更新您的 DESTINATION_WALLET 地址和 TRANSFER_AMOUNT ,但这两个值都适用于此示例。
如果您之前创建过可替代的 SPL 代币,那么您就会知道小数很重要。由于链将代币供应表示为整数值,因此我们必须根据代币元数据中分配的小数位数来转换代币数量(例如,如果铸币厂有三个小数,我们必须发送 1,000 个单位才能有效传输一个代币 [1,000 / 10**3])。
要获取小数,我们将在SOLANA_CONNECTION上调用**getParsedAccountInfo并传递令牌MINT_ADDRESS。由于该方法可以返回多种类型,因此我们需要将类型声明为ParsedAccountData才能有效地解析数据。在声明下方添加此函数:**
async function getNumberDecimals(mintAddress: string):Promise<number> {
const info = await SOLANA_CONNECTION.getParsedAccountInfo(new PublicKey(MINT_ADDRESS));
const result = (info.value?.data as ParsedAccountData).parsed.info.decimals as number;
return result;
}
从技术上讲,如果您知道代币铸币的小数位数(您可以在 Solana Explorer 的代币页面上找到它),则不需要这个。尽管如此,我们还是希望将其包括在内,因为它允许您使用不同的铸币修改代码,而无需更改其他变量。
让我们开始有趣的事情吧!
创建一个新的异步函数sendTokens:
async function sendTokens() {
}
首先,我们需要获取源账户和目标账户的关联代币账户 (ATA)。请记住,每个账户必须有一个将钱包与铸币厂关联的 ATA;一个 ATA 只能为一个铸币厂持有代币;发送方和接收方关联代币账户的铸币厂必须相同。
我们将使用getOrCreateAssociatedTokenAccount为交易的每一方获取 ATA(如果不存在则创建)。由于我们可能需要为这些交易创建关联账户,我们的付款人可能必须向新 ATA 注入少量 SOL 以支付租金。在sendTokens中,将步骤 1 添加到您的代码中以获取我们的源账户(从技术上讲,因为我们知道我们已经有 SPL 代币——我们已经有一个 ATA,所以我们也可以使用getAssociatedTokenAddress):
console.log(`Sending ${TRANSFER_AMOUNT} ${(MINT_ADDRESS)} from ${(FROM_KEYPAIR.publicKey.toString())} to ${(DESTINATION_WALLET)}.`)
//Step 1
console.log(`1 - Getting Source Token Account`);
let sourceAccount = await getOrCreateAssociatedTokenAccount(
SOLANA_CONNECTION,
FROM_KEYPAIR,
new PublicKey(MINT_ADDRESS),
FROM_KEYPAIR.publicKey
);
console.log(` Source Account: ${sourceAccount.address.toString()}`);
我们将对我们的目标账户执行相同的操作。添加下面的步骤 2 以获取或创建我们的目标账户(将向其发送令牌的收件人的 ATA):
//Step 2
console.log(`2 - Getting Destination Token Account`);
let destinationAccount = await getOrCreateAssociatedTokenAccount(
SOLANA_CONNECTION,
FROM_KEYPAIR,
new PublicKey(MINT_ADDRESS),
new PublicKey(DESTINATION_WALLET)
);
console.log(` Destination Account: ${destinationAccount.address.toString()}`);
只需要再提供一条信息即可完成我们的交易。还记得我们创建的getNumberDecimals函数吗?让我们调用它并将返回值存储在常量numberDecimals中:
//Step 3
console.log(`3 - Fetching Number of Decimals for Mint: ${MINT_ADDRESS}`);
const numberDecimals = await getNumberDecimals(MINT_ADDRESS);
console.log(` Number of Decimals: ${numberDecimals}`);
最后,我们需要组装并发送我们的交易。在步骤 3 下方添加以下指令组装:
//Step 4
console.log(`4 - Creating and Sending Transaction`);
const tx = new Transaction();
tx.add(createTransferInstruction(
sourceAccount.address,
destinationAccount.address,
FROM_KEYPAIR.publicKey,
TRANSFER_AMOUNT * Math.pow(10, numberDecimals)
))
我们在这里所做的是告诉我们的应用程序将TRANSFER_AMOUNT代币(请注意,我们必须乘以 10 的**numberDecimals次方)从我们获取的sourceAccount转移到我们获取的目标帐户**。
为了达到最佳实践,我们应该在发送交易之前获取最近的区块哈希。在sendTokens函数的第 4 步末尾添加此代码片段:
const latestBlockHash = await SOLANA_CONNECTION.getLatestBlockhash('confirmed');
tx.recentBlockhash = await latestBlockHash.blockhash;
const signature = await sendAndConfirmTransaction(SOLANA_CONNECTION,tx,[FROM_KEYPAIR]);
console.log(
'\x1b[32m', //Green Text
` Transaction Success!🎉`,
`\n https://explorer.solana.com/tx/${signature}?cluster=devnet`
);
不要介意那个 console.log——那只是一些让我们的成功以绿色文本显示的技巧。
最后,在代码末尾调用sendTokens:
sendTokens();
在你的终端中,输入以下命令运行代码:
ts-node app.ts
恭喜!如果你按照上述步骤操作,你应该会看到类似这样的内容:
太棒了!现在您知道如何将 SPL 代币从一个用户转移到另一个用户了!想更上一层楼吗?以下是一些可以进一步深入的方法:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!