Solana 交易错误码:Custom 6001 到底在说什么

InstructionError、自定义程序错误,以及为什么 0x1771 在不同程序里含义不同。怎么解码一次失败,以及怎么把它和「从未上链」区分开。

BoltTx Team··15 min read
solana错误码调试instruction-error交易上链rpc

一笔 Solana 交易失败了,你拿到的是类似 {"InstructionError":[2,{"Custom":6001}]} 这样的东西。它其实把发生了什么说得很清楚 —— 前提是你知道怎么读。

大部分困惑来自一件事:★自定义错误码是每个程序自己定义的,不是 Solana 定义的。★ 同一个数字,取决于是哪个程序返回的,含义完全不同。

先分清:是失败了,还是根本没上链

这两种在大多数日志里长得一模一样,解法却完全不同。

const { value } = await connection.getSignatureStatuses([sig], {
  searchTransactionHistory: true,
});

if (!value[0]) {
  // ★从未上链 —— 没有错误可以解码。★
  // 费用、路由、重试、或 blockhash 过期。
} else if (value[0].err) {
  // 上链了但失败。现在这个错误是真实且可解码的。
  console.log(JSON.stringify(value[0].err));
}

★一笔从未上链的交易没有错误码,因为它从未执行过。★ 去找一个不存在的错误,是很多人浪费掉一个下午的方式。

错误的结构

大多数失败以 InstructionError 形式返回:

{"InstructionError": [2, {"Custom": 6001}]}
                      ↑        ↑
                      │        └── 程序自己的错误码
                      └── 第几条指令失败(从 0 开始)

★索引和错误码同样重要。★ 如果你在前面插了两条 compute budget 指令,索引 2 是你的第一条真实指令 —— 不是你写的第三个东西。

type TxError = {
  InstructionError?: [number, string | { Custom: number }];
};

const err = value[0].err as TxError;
if (err.InstructionError) {
  const [index, detail] = err.InstructionError;
  const code = typeof detail === "object" ? detail.Custom : detail;
  console.log(`第 ${index} 条指令失败,错误 ${code}`);
}

运行时错误:到哪都是一个意思

这些来自 Solana 运行时,所以不管哪个程序,含义相同:

错误 含义 常见原因
InsufficientFundsForRent ★账户会掉到租金豁免线以下★ 留下的 SOL 太少
ComputeBudgetExceeded 计算单元用完了 ★CU limit 设太低★
AccountNotFound 账户不存在 ★缺 ATA★
AccountInUse 该账户本 slot 被写锁定 竞争,常常是自己造成的
AlreadyProcessed 这个签名已经上链了 ★不是错误 —— 你成功了★
BlockhashNotFound blockhash 过期或无效 取得太早
MissingRequiredSignature 某个必需签名者没签 账户被错误标成 signer
ProgramFailedToComplete 程序 panic 了 通常是程序本身的 bug

有两个值得展开。

AlreadyProcessed 不是失败。 它表示这个签名已经在链上了 —— 你重试了,而更早的某次尝试落块了。★把它当成功处理,不要当成需要重试的错误。★

AccountInUse 在高吞吐下经常是自己造成的。 你的两笔交易在同一个 slot 里写同一个账户,就会互相串行化。

如果你在解码"已上链交易"的错误、而真正的问题是交易根本不上链,免费领个 BoltTx key 改一行就能测测路由那一侧。

自定义错误:程序特有

Custom: N 由返回它的那个程序定义。没有一张通用对照表。

★Anchor 程序的用户自定义错误从 6000 开始。★ 所以 Custom: 6001 是那个程序错误枚举里的第二个:

#[error_code]
pub enum MyError {
    #[msg("Slippage tolerance exceeded")]
    SlippageExceeded,        // 6000
    #[msg("Pool is paused")]
    PoolPaused,              // 6001
}

非 Anchor 程序用它们自己选的编号。Token Program 这类原生程序有完全独立的号段。

怎么解码:

// 日志里几乎总是带着人类可读的信息。
const tx = await connection.getTransaction(sig, {
  maxSupportedTransactionVersion: 0,
});
console.log(tx?.meta?.logMessages?.slice(-10).join("\n"));
// → "Program log: AnchorError ... Error Code: SlippageExceeded. Error Number: 6000"

★先读日志,再去搜那个十六进制值。★ Anchor 会直接把错误名打出来,省掉你猜"该查哪个程序的表"的功夫。

0x1771 这个误区

这个码在 Solana 搜索结果里反复出现,而且通常配着一个很笃定的答案:它是滑点错误。

0x1771 是十六进制的 6001。在一个 Anchor 程序里,那是★第二个用户自定义错误★ —— 作者在那个位置放了什么,它就是什么。它恰好在几个流行的 swap 程序里是滑点错误,于是这个联想就传开了。

0x1770 = 6000   Anchor 第一个用户错误
0x1771 = 6001   第二个
0x1772 = 6002   第三个

★如果你在一个不是自己写的程序里碰到 0x1771,去查那个程序的错误枚举,别信江湖传说。★ 两个 swap 程序都可能返回 6001,含义却完全不同。

Token Program 的错误码

常见到值得单独列一张表,因为它们在 swap 失败里反复出现:

名称 原因
1 InsufficientFunds 代币余额不足
3 InvalidMint ★mint 和账户对不上★
4 MintMismatch 这个代币账户的 mint 不对
5 OwnerMismatch 签名者不是账户所有者

InvalidMintMintMismatch 通常是 ATA 推导的 bug★ —— 你为错误的 mint 或错误的 owner 算出了关联代币账户。

实战解码

一个覆盖大多数真实场景的小函数:

function describeError(err: unknown, logs?: string[]): string {
  const e = err as { InstructionError?: [number, unknown] };
  if (!e?.InstructionError) return JSON.stringify(err);

  const [index, detail] = e.InstructionError;

  if (typeof detail === "string") {
    // 运行时错误 —— 到哪都是一个意思。
    return `第 ${index} 条指令: ${detail}`;
  }

  const code = (detail as { Custom: number }).Custom;

  // Anchor 会在日志里打出错误名,优先用它而不是数字。
  const named = logs?.find((l) => l.includes("Error Code:"));
  if (named) return `第 ${index} 条指令: ${named.trim()}`;

  const hint = code >= 6000 ? "(Anchor 用户自定义)" : "";
  return `第 ${index} 条指令: Custom ${code} ${hint}`;
}

哪些错误值得重试

★大多数不值得。★ 重试一个确定性的失败,只会复现它,并且再付一次基础费。

错误 该重试吗 原因
BlockhashNotFound ★重建★ 旧字节已永久作废
AccountInUse 瞬时竞争
AlreadyProcessed ★不该 —— 你赢了★ 已经在链上
ComputeBudgetExceeded ★不该★ 先把 limit 调高
InsufficientFundsForRent 不该 先给账户充值
自定义滑点错误 ★看情况★ 只有放宽容忍度才有意义
MissingRequiredSignature 不该 修 account metas

★两个代价最大的错误做法:不调高 limit 就重试 ComputeBudgetExceeded,以及把 AlreadyProcessed 当成失败。★ 前者在一个必然失败的事情上烧钱;后者可能让你在第一笔已经成功的情况下,又发出去第二笔真实交易。

上链应该是什么水平

我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。

★这篇文章里的每一个错误码,都要求交易先落块。★ 如果你的失败里大多数根本查不到状态,那问题不在错误解码这一层。

BoltTx 在这里的位置

我们负责把交易送进区块。一旦落块,任何错误都是你和那个程序之间的事。

提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前,传输途中观察不到。你在本地签名,我们不托管资金、不代签、不修改交易内容。

tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。

免费领 API key,没有月费:

const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

常见问题

Solana 的 InstructionError 是什么意思? 交易里某一条具体指令失败了。第一个元素是那条指令从 0 开始的索引,第二个要么是运行时错误名,要么是由程序定义的 Custom 码。

custom program error 0x1771 是什么意思? 0x1771 十进制是 6001,是 Anchor 程序里的第二个用户自定义错误。它的含义完全取决于那个程序的错误枚举。 它在一些 swap 程序里是滑点错误,但这个数字本身没有通用含义。

Anchor 的错误码为什么从 6000 开始? Anchor 把较低的号段留给框架自己的错误,用户自定义错误从 6000 起。所以在 Anchor 程序里,6000 及以上的 Custom 码按声明顺序对应作者的错误枚举。

InsufficientFundsForRent 是什么? 某个账户会被留在租金豁免所需余额之下。通常是你转走的 SOL 太多,或者在创建账户时没有充到豁免阈值。

AlreadyProcessed 是什么意思? 这个签名已经在链上了。它不是失败 —— 是更早的某次重试落块了。当成功处理,因为继续重试可能导致你在第一笔已经成功的情况下又发一笔不同的交易。

Solana 的错误码怎么解码? 先读交易日志。Anchor 程序会直接打出错误名和编号,省掉猜的功夫。只有在拿不到日志时,才回退到"去某个程序的错误枚举里查这个数字"。

swap 里的 AccountNotFound 是什么意思? 通常是缺关联代币账户(ATA)。指令期望一个从未被创建过的代币账户 —— 这在某个地址第一次持有某个 mint 时会发生。

为什么指令索引和我写的代码对不上? 因为 compute budget 指令也算数。如果你在前面插了 setComputeUnitLimitsetComputeUnitPrice,索引 2 是你的第一条真实指令,而不是你写的第三个。

上链但报错的交易该重试吗? 通常不该。大多数错误是确定性的,重试只会复现它,并且再付一次基础费。例外是瞬时的 AccountInUse,以及放宽容忍度之后的滑点错误。

AccountInUse 是什么? 两笔交易在同一个 slot 里试图写同一个账户,于是串行化了。高吞吐下这常常是自己造成的 —— 你自己的交易在争抢同一个可写账户。

怎么区分程序错误和运行时错误? 运行时错误返回的是 InsufficientFundsForRent 这样的字符串,到哪都是一个意思。程序错误返回的是 {"Custom": N},由那个具体程序定义。

ComputeBudgetExceeded 是什么意思? 你的交易计算单元用完了。limit 对实际工作量来说太低了,常见原因是留了默认值,或者模拟时用的账户状态比生产环境简单。

为什么我的交易报 MissingRequiredSignature? 某个账户在指令里被标成了签名者,但它没有签这笔交易。通常是 account meta 的问题 —— 一个不该标 isSigner: true 的账户被标了,或者签名集合里少了某个密钥对。

去哪找一个程序的错误码? Anchor 程序看它的 IDL,或者看已公开的源码。错误枚举按声明顺序、从 6000 开始(Anchor 用户错误)。跨程序没有中央注册表。

我的交易没有错误,但也没出现在链上,怎么办? 没有错误可以解码,因为它从未执行。去看 blockhash 用了多久、费用相对当时行情如何、有没有重试到过期、以及交易是怎么被路由的。

Token Program 的错误码有哪些? 1InsufficientFunds,3InvalidMint,4MintMismatch,5OwnerMismatch。和 mint 相关的那几个,通常意味着 ATA 是按错误的 mint 或错误的 owner 推导出来的。

延伸阅读

返回博客列表