进阶

交易构建最佳实践

BoltTx 已经把交易投递做到物理极限 —— 但要想真的落得快,交易本身的构建也要合格。本页总结了那些无论投递路径多优秀、都可能让交易失败、停滞或被丢弃的典型错误。

计算单元预算(Compute Units)

每笔 Solana 交易都有一个隐式的计算单元(CU)预算。若不显式调用 ComputeBudgetProgram.setComputeUnitLimit,默认按每条指令 200,000 CU 计算(整笔交易上限为 1,400,000 CU)。Swap、多跳 DEX 路由以及复杂的 DeFi 交互通常需要显式设置 CU 上限 —— 依赖默认值会让你的交易即使成功投递,也在链上执行失败。

先模拟,再设定

在最新 blockhash 下用 simulateTransaction 测量实际 CU 消耗,然后把上限设为该值的 1.2 倍 —— 留出足够余量应对模拟到落块之间的价格或流动性状态变化。

不要默认申请最大值

"以防万一" 地申请 1.4M CU 会让你的交易更大、调度器排队优先级更低。只申请你真正需要的量。

设置 CU 上限
import { ComputeBudgetProgram } from "@solana/web3.js";

// 1. Simulate first to measure real usage
const sim = await connection.simulateTransaction(transaction);
const actualCU = sim.value.unitsConsumed ?? 200_000;

// 2. Set limit to 1.2x measured
transaction.add(
  ComputeBudgetProgram.setComputeUnitLimit({
    units: Math.ceil(actualCU * 1.2),
  })
);

Priority Fee 与 Tip 的区别

这是两个完全独立的概念 —— 你可以同时用、都不用,或只用其中之一。理解它们的区别对成本优化很重要。

BoltTx Tip

必须向 BoltTx Tip 地址转的一笔 SOL —— 用于投递并解锁你的套餐档位。这不是 Solana 协议层的费用,而是 BoltTx 的用量计量方式。

Solana Priority Fee

通过 setComputeUnitPrice 设置的可选计算单元价格。它告诉 Solana 验证节点在拥堵的 slot 里优先处理你的交易。BoltTx 不要求你设置,但在高峰期有助于落块。

建议:BoltTx Tip 始终要加(必需)。只有在网络拥堵或你要争抢某个特定 slot 时才加 priority fee —— 否则就是白花钱。

Address Lookup Tables(ALT)

Solana 交易有 1232 字节的硬上限。复杂交易(多跳 Swap、批量操作)很容易撞到这个上限,因为每个引用的账户要占 32 字节。Address Lookup Table 允许你用 1 字节的索引来引用账户 —— 一个 ALT 可以把 1200 字节的交易压缩到 400 字节。

当你的交易引用超过约 20 个账户,或看到 "Transaction too large" 错误时,就该用 ALT 了。

配合 ALT 使用 versioned transaction
import {
  PublicKey,
  TransactionMessage,
  VersionedTransaction,
  AddressLookupTableAccount,
} from "@solana/web3.js";

// Fetch your ALT(s) — each can hold up to 256 addresses
const alt = await connection
  .getAddressLookupTable(new PublicKey("YOUR_ALT_ADDRESS"))
  .then(r => r.value!);

// Build a v0 message that references the ALT
const message = new TransactionMessage({
  payerKey: payer.publicKey,
  recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
  instructions: [ /* your instructions */ ],
}).compileToV0Message([alt]);

const tx = new VersionedTransaction(message);
tx.sign([payer]);

// tx.serialize() now produces a much smaller payload

模拟通过但落块失败

一个常见的困惑:simulateTransaction 返回成功,但提交后链上执行却失败了。这几乎总是因为模拟到执行之间链上状态发生了变化。

  • 池子价格变动 —— 你的滑点检查不再通过。
  • 其他交易消耗了你原本想用的流动性。
  • 签名账户的余额变了(例如并发交易把 SOL 耗到租金豁免线之下)。
  • 从模拟到落块之间 blockhash 过期了。

修复:用最新 blockhash 模拟,之后立刻提交;Swap 类交易始终保留充足的滑点容忍度。在 BoltTx 上,落块通常在 1 秒内 —— 从模拟到执行的间隔一般只有一两个 slot。

交易体积排查

如果交易因体积过大被拒,按以下顺序排查:

  1. 数一下引用的唯一账户个数。每个 32 字节。
  2. 数一下签名数量。每个签名 64 字节,加 1 字节索引。
  3. 把所有指令的 data 字节数累加 —— 某些程序会序列化较大的数据(复杂 Swap 路由尤其如此)。
  4. 如果总和超过 1232 字节,把最庞大的账户列表挪到 ALT,改用 v0 versioned transaction 重新提交。

Blockhash 过期

普通交易引用一个近期 blockhash,必须在 150 个 slot(按 400ms/slot 算约 60 秒)内落块。开启 preflight 时,blockhash 过期会以 BlockhashNotFound 的 RPC 错误返回;若 skip_preflight=true,交易会在链上被静默丢弃而不回传错误。以下场景尤其常见:

  • 你在很早之前就签了名(例如在手机上签名,再在后端提交)。
  • 投递路径延迟很高 —— 交易到达链上时已经太迟。
  • 重试失败的交易时没有刷新 blockhash。

修复:签名前立刻重新拉取 blockhash。如果场景需要长期有效,请切换到 Durable Nonce.

提交前清单

  • CU 上限 = 模拟值 × 1.2,而不是默认的 200k。
  • 已包含 BoltTx Tip 指令,且金额不低于套餐最低要求。
  • Blockhash 是 30 秒内拉取的。
  • 交易体积 < 1232 字节,或者你已在用 ALT + versioned transaction。
  • 所有必需的签名者都已签名。
  • 模拟时使用的 blockhash 与准备提交的 blockhash 一致。

延伸阅读