第 1 步:弄清发生了什么
用签名向集群查询。searchTransactionHistory 让查询对任意时间的交易都有效。
如果是
sendTransaction 本身返回了错误,就没有可查询的签名。交易在发送前就被拒绝了。请转到发送前被拒绝。
如需在代码中检查签名,请参考等待确认的示例。
发送前被拒绝
默认情况下,sendTransaction 会先模拟交易,如果模拟失败就拒绝发送。您会收到错误 -32002,其中包含原因和程序日志:
data.logs通常会用直白的语言说明出了什么问题。这里,付款方只有 0.06 SOL,却试图转出 1,000 SOL。data.err指出哪条指令失败。InstructionError: [0, ...]是第一条指令,从零开始计数。- 自定义错误码属于失败的那个程序,而不是 Solana。System Program 的
Custom: 1表示“余额不足”,但其他程序的Custom: 1含义不同。请在该程序的错误列表(其 IDL 或源码)中查找这个数字。消息中以十六进制显示,因此0x1770就是6000,即 Anchor 程序中的第一个自定义错误。
常见拒绝原因
使用 simulateTransaction 调试
如果只想查看日志而不发送任何内容,请进行模拟。调试时,以下两个选项会让模拟有用得多:replaceRecentBlockhash: true会替换为当前的 blockhash,因此在交易的 blockhash 过期后,您仍可以继续模拟同一笔交易。它不能与sigVerify: true同时使用。sigVerify: false会跳过签名检查,因此您可以模拟未签名或部分签名的交易。
err、logs 和 unitsConsumed。用 unitsConsumed 设置准确的计算单元上限:上限越紧,您按单元支付的优先费就越划算。
已上链但失败
交易已经上链,手续费已扣除,但它的所有更改都没有生效。获取完整记录:meta.err 与模拟错误的结构相同,meta.logMessages 包含程序日志。请按发送前被拒绝中描述的方法阅读。
模拟成功的交易仍可能在链上失败,因为在此期间状态发生了变化。常见原因包括兑换时的滑点、某个账户被他人关闭或修改,或者 nonce 已被使用。请基于当前状态重新模拟以确认原因,然后留出容错空间:放宽滑点、获取新报价,或在重试时重新构建交易。
从未上链
getSignatureStatuses 返回 null:RPC 节点接受了这笔交易,但没有任何领导者将其打包。不会产生任何费用。
blockhash 已过期
交易仅在其 blockhash 之后的 150 个区块内有效。每个槽约 215 毫秒,相当于大约 30 到 35 秒。一旦超过,交易就永远无法上链,因此请停止等待并重新构建交易。 检查 blockhash 是否仍然可用:getLatestBlockhash 返回的 lastValidBlockHeight 与 blockhash 一起保存。将它与 getBlockHeight 比较:只要区块高度更低,就持续重发同一笔已签名的交易。一旦区块高度更高,这笔交易就已失效。
优先费过低
区块繁忙时,领导者会优先打包每个计算单元支付最多的交易。查看最近涉及您所操作账户的交易支付了多少:SetComputeUnitPrice 设置,并配合通过模拟得到的准确 SetComputeUnitLimit。
在传输途中被丢弃
已发送的交易可能在 RPC 节点和领导者之间丢失,在网络拥堵时尤其如此。每隔几秒重发同样的已签名字节,直到交易确认或 blockhash 过期。重发是安全的:同一个签名只能上链一次。 对于需要快速上链的交易,请通过 Apex 发送。Apex 会同时通过多条路径将每笔交易发送给即将出块的领导者,并持续重试,直到交易上链或过期。Apex 自身的错误列在 Apex 错误与速率限制中。仍未解决
请收集签名(如果交易从未获得签名,则收集 base64 交易)、包含data.logs 的完整错误、您发送时使用的区域,以及是否开启了预检。然后在 Discord 中提问。