Solana 交易是一个包含一个或多个指令的容器。每个指令都是一个操作——转账代币、创建账户、调用程序。网络会按顺序且原子性地执行交易中的所有指令:要么所有指令都成功,要么整个交易失败并回滚。
这意味着你可以在一笔交易中打包多笔转账。与其为三位收款人分别发送三笔交易,不如在一笔交易中包含三条转账指令。这样更快(只需一次确认而不是三次),也更省钱(只需支付一次基础费用而不是三次)。下图展示了如何将多笔支付(在图中称为 "drops")批量打包进一笔交易,并通过多笔交易处理更大规模的批量支付。
批量支付示意图
来源: QuickNode - 如何在 Solana 上发送批量交易
下面的操作演示了如何将多条转账指令加载到一笔交易中,实现批量支付。
将多条指令批量打包进一笔交易
一笔 Solana 交易可以包含对不同收款人的多笔转账。你只需签名一次,支付一次交易费用,所有转账会一起结算。如果有任何一笔转账失败,整个交易都会被拒绝。
参见 Solana 支付原理 了解核心支付概念。
批量转账需要分别构建每条指令,然后将它们合并到一笔交易中。
以下步骤展示了核心流程。完整可运行代码请参见 演示。
派生 Token 账户
首先,为发送方和每个接收方派生 Associated Token Account (ATA) 地址。ATA 是基于钱包和 mint 的确定性地址。
const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});
创建转账指令
为每个接收方分别创建一条转账指令。每条指令需指定:
- 源 token account 地址
- 目标 token account 地址
- authority(源 token account 拥有者地址)
- 金额(以基础单位表示,需根据 mint 的 decimals 调整)
const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const transfer1Instruction = getTransferInstruction({source: senderAta,destination: recipient1Ata,authority: sender.address,amount: 250_000n});const transfer2Instruction = getTransferInstruction({source: senderAta,destination: recipient2Ata,authority: sender.address,amount: 250_000n});
作为单笔交易发送
将所有转账指令添加到一笔交易中。这样可以原子性地执行所有转账,要么全部成功,要么整个交易失败。
验证余额
批量转账完成后,使用 splToken helper 验证所有参与方的 token 余额。
const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const transfer1Instruction = getTransferInstruction({source: senderAta,destination: recipient1Ata,authority: sender.address,amount: 250_000n});const transfer2Instruction = getTransferInstruction({source: senderAta,destination: recipient2Ata,authority: sender.address,amount: 250_000n});const signature = await client.transaction.prepareAndSend({authority: sender,instructions: [transfer1Instruction, transfer2Instruction],version: 0});const splToken = client.splToken({mint: mint.address,tokenProgram: "auto"});const senderBalance = await splToken.fetchBalance(sender.address);const recipient1Balance = await splToken.fetchBalance(recipient1.address);const recipient2Balance = await splToken.fetchBalance(recipient2.address);
演示
// Generate keypairs for sender and two recipientsconst sender = (await generateKeypair()).signer;const recipient1 = (await generateKeypair()).signer;const recipient2 = (await generateKeypair()).signer;console.log("Sender Address:", sender.address);console.log("Recipient 1 Address:", recipient1.address);console.log("Recipient 2 Address:", recipient2.address);// Demo Setup: Create client, mint account, token accounts, and fund with initial tokensconst { client, mint } = await demoSetup(sender, recipient1, recipient2);console.log("\nMint Address:", mint.address);// Derive the Associated Token Accounts addresses (ATAs) for sender and recipientsconst [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient1Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient1.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipient2Ata] = await findAssociatedTokenPda({mint: mint.address,owner: recipient2.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});console.log("Sender Token Account:", senderAta.toString());console.log("Recipient 1 Token Account:", recipient1Ata.toString());console.log("Recipient 2 Token Account:", recipient2Ata.toString());// =============================================================================// Batch Token Payment Demo// =============================================================================// Create instructions to transfer tokens from sender to both recipients// Transferring 250,000 base units = 0.25 tokens (with 6 decimals) to eachconst transfer1Instruction = getTransferInstruction({source: senderAta,destination: recipient1Ata,authority: sender.address,amount: 250_000n // 0.25 tokens});const transfer2Instruction = getTransferInstruction({source: senderAta,destination: recipient2Ata,authority: sender.address,amount: 250_000n // 0.25 tokens});// Prepare and send both transfers in a single transaction using @solana/clientconst signature = await client.transaction.prepareAndSend({authority: sender,instructions: [transfer1Instruction, transfer2Instruction],version: 0});console.log("\n=== Batch Token Payment Complete ===");console.log("Transaction Signature:", signature.toString());// Fetch final token account balances using @solana/client SPL token helperconst splToken = client.splToken({mint: mint.address,tokenProgram: "auto"});const senderBalance = await splToken.fetchBalance(sender.address);const recipient1Balance = await splToken.fetchBalance(recipient1.address);const recipient2Balance = await splToken.fetchBalance(recipient2.address);console.log("\nSender Token Account Balance:", senderBalance);console.log("Recipient 1 Token Account Balance:", recipient1Balance);console.log("Recipient 2 Token Account Balance:", recipient2Balance);// =============================================================================// Demo Setup Helper Function// =============================================================================
通过交易规划实现扩展
单笔交易有大小限制——大约 1232 字节。对于大批量操作(如数百名员工的工资发放、大规模空投),会超出此限制,需要将操作拆分为多笔交易。
当然你可以自行实现交易分发逻辑,但
@solana/instruction-plans
包(Solana Kit 的一部分)可在两个层面处理此问题:
指令计划 用于定义你的操作及其顺序约束:
- 顺序执行 — 必须按顺序执行的指令
- 并行执行 — 可以以任意顺序执行的指令
- 不可拆分 — 必须保持在同一笔交易中的指令
交易计划 是由指令计划生成的。规划器会智能地将指令打包进最优大小的交易中,同时遵循你的顺序约束。最终生成的交易计划可以:
- 执行 — 签名并发送到网络,支持并行交易同时发送
- 模拟 — 在网络上进行试运行,发送前进行验证
- 序列化 — 编译为 base64,便于外部签名服务或多方协作流程
这种两层结构让你可以专注于操作本身(“先转账给 Alice,再转账给 Bob”),而库会自动处理交易大小、打包和并行执行等细节。
Is this page helpful?