机器接口¶
本页定义脚本、CI 流水线和 AI 智能体调用 wallet-cli 时应遵循的接口规范,包括 JSON 响应结构、
退出码、错误码和敏感信息处理方式。除非另有说明,这些约定均受 wallet-cli.result.v1 的稳定性承诺保护。
调用约定¶
wallet-cli <command> -o json [--network <id|alias>] [--timeout <ms>] [--account <id|label>]
- 始终传
-o json。文本输出是给人看的,不提供任何稳定性承诺。 - JSON 模式下,stdout 上只有一个终态帧——也就是结果响应体。除此之外绝不会有任何东西写到 stdout。诊断信息一律走 stderr。
- 运行时间较长的
x402和bai命令还会在 stderr 上报告进度:text 模式下是⏳ …行,JSON 模式下每一步一行{"type":"activity","message":"…"}。它们只是提示性的——可以忽略,也可以展示给用户,但绝不要用它们来判断结果。 - 每一次 RPC / 设备调用都受
--timeout限制(毫秒,默认取config.timeoutMs,内置 60000)。 --network接收规范的 CAIP-2 id(tron:3448148188、eip155:11155111)或简短别名(nile、sepolia、bsc)。namespace 不等于链家族:eip155寻址的是evm家族。别名只在选择网络的那一刻解析一次;下游任何环节都看不到它,响应中的chain.network始终报告规范 id。脚本里请优先用规范 id——别名只是本地配置项,随时可能被重新指向。- CAIP-2 之前使用的那几个 TRON id(
tron:mainnet、tron:nile、tron:shasta)作为永久别名保留,因此既有的调用方式仍然可用。但输出是另一回事:chain.network、chain.chainId、networks列表中的id,以及各config键,现在报告的都是 CAIP-2 id,所以凡是按字符串匹配、或用tron:nile作 map 键的使用方都需要更新。
能力发现¶
wallet-cli --json-schema
wallet-cli --json-schema tron
wallet-cli tx send --json-schema
一次调用就返回整个接口面:tool、version、globalFlags、errorCodes 和 commands[]。每个命令条目带有 id、kind、path、usage、summary、requires(network / auth / wallet)、capability、examples,以及描述其输入的 JSON Schema inputSchema;链上命令还会声明 families。这是了解本 CLI 的推荐方式;请不要去抓 --help。带上家族参数可以把目录限定到某一个链家族。
链家族¶
一个网络属于某一个链家族——tron 或 evm,而正是它决定了哪些命令、哪些参数适用:
- 命令会声明它服务于哪些家族(目录中的
families)。在另一个家族的网络上调用它,会在任何节点调用之前就以family_mismatch失败,退出码2。 - 参数也可能只属于某一个家族(
--asset-id和--permission-id是 TRON 的,--gas-limit和--nonce是 EVM 的)。把其中之一用在另一个家族上是invalid_option,退出码2。--help会给它们打上(TRON only)/(EVM only)标记。 - 只要账户持有密钥,它就不绑定家族——seed 账户或私钥账户同时拥有 TRON 和 EVM 两个地址。仅观察账户和 Ledger 账户只有一个地址,因此也只属于一个家族;在不匹配的网络上选中它,同样是
family_mismatch。
命令家族与参数家族的校验是静态的——它们只取决于命令、参数和所选网络——因此智能体可以直接从目录判断,无需发起调用。而账户家族是否兼容还取决于所选的钱包账户:持有密钥的账户两个家族都能用,仅观察账户和 Ledger 账户则绑定在其中一个上。
启动时的钱包数据升级¶
每条命令在运行前都会检查持久化的钱包 schema。--help、--version、--json-schema 以及不带参数的 wallet-cli 是例外:它们从不读取钱包数据,因此跳过该检查。如果 schema 已经过时,启动关卡会先完成升级,而你输入的那条命令会被刻意跳过、不予执行。过程写入 stderr;结果是一个退出码为 0 的成功响应,其 command 为 migration:
| 字段 | 含义 |
|---|---|
upgraded |
文件被改写时为 true,用户拒绝时为 false |
cancelled |
仅在用户拒绝升级时为 true |
files[] |
含 path、from、to 和 backup(用户拒绝升级时没有 backup,因为什么都没写) |
originalCommandExecuted |
恒为 false——查看这个结果之后,请重新执行你原来的命令 |
在这个边界上结束本次调用,正是为了避免脚本化命令或动用资金的命令,在一次预料之外的持久状态变更之后继续往下跑。
在交互式终端里,升级会先征求同意。随后,seed 和私钥类的迁移会要求输入 master password;Ledger 和仅观察类的迁移则不需要。非交互式的 Ledger / 仅观察迁移是自动进行的,而需要密码的那一类必须提供 --password-stdin,否则以 migration_required 失败、退出码 2——因为需要改变的是这次调用本身,所以它属于用法错误;该错误码专门保留给无法进行的升级。拒绝升级不算失败:它是退出码 0,带 upgraded: false、cancelled: true,且不写入任何文件或备份。
退出码¶
| 退出码 | 含义 | JSON 响应 |
|---|---|---|
0 |
成功 | success: true |
1 |
执行失败——运行时错误:RPC 失败、超时、链上拒绝、钱包错误 | success: false |
2 |
用法错误——参数写错、缺少必填选项、取值非法、family 不匹配 | success: false |
退出码只有以上三种,含义固定。在 JSON 模式下,非零退出码总会同时在 stdout 输出错误响应。
JSON 响应结构¶
Schema id:wallet-cli.result.v1。
成功:
{
"schema": "wallet-cli.result.v1",
"success": true,
"command": "account.balance",
"data": { "address": "TMSgJxtPw29AFEHMXsjGo4kWV7UwbCToHJ", "balance": "1976489000", "decimals": 6, "symbol": "TRX" },
"meta": { "durationMs": 1114, "warnings": [] },
"chain": { "family": "tron", "network": "tron:3448148188", "chainId": "3448148188" }
}
出错:
{
"schema": "wallet-cli.result.v1",
"success": false,
"command": "tx.info",
"error": { "code": "rpc_error", "message": "TRON getTransaction failed: Transaction not found" },
"meta": { "durationMs": 1033, "warnings": [] },
"chain": { "family": "tron", "network": "tron:3448148188", "chainId": "3448148188" }
}
| 字段 | 类型 | 出现时机 | 说明 |
|---|---|---|---|
schema |
"wallet-cli.result.v1" |
始终 | schema 版本标识;调用方应据此选择解析逻辑 |
success |
boolean | 始终 | 与退出码一致(true ⇔ 0) |
command |
string | 始终 | 规范命令 id,例如 tx.send、list。它标识的是操作,而不是你输入的字面词:backup --records 报告为 backup.records,import keystore 报告为 import.keystore |
data |
object/array | 仅成功时 | 命令返回的数据;见各命令的参考页 |
error.code |
string | 仅出错时 | 机器可读;见错误码 |
error.message |
string | 仅出错时 | 面向人类可读;不稳定——绝不要解析它。当一次链上拒绝被归类到某个错误码时,它的内容形如 <category>: <what the node said>,因此终端里能看到节点自己给出的数字或 revert 原因;同一段节点原文也会出现在 details.nodeMessage(TRON,已脱敏)或 details.providerMessage(TronLink)中,并与 details.nodeCode / details.providerCode 一同给出 |
error.details |
object | 可选 | 可用时提供的结构化附加信息 |
meta.durationMs |
number | 始终 | 实际耗时(毫秒) |
meta.warnings |
(string \| {code, message})[] |
始终 | 非致命提示;元素类型并不统一——见下文 |
meta.pagination |
object | 仅返回窗口的命令 | offset / limit / total;当命令返回一个分页窗口时出现——见分页 |
chain |
object | 选定了网络时 | family / network / chainId。每条链上命令都有;本地命令若其策略会解析出一个网络,也会有——目前是 backup、current 和 list,它们把选定网络或默认网络当作链家族/展示用的选择器,并不访问节点。network: "none" 的命令(config、networks、contact、encoding、address、create、import 等)不带它;它的存在并不意味着访问过节点 |
编码规则:bigint 值序列化为十进制字符串(例如 "balance": "1976489000"),二进制序列化为 hex。以 bigint 或协议 int64 表示的金额是字符串,但 feeSun、multiSignFeeSun、energyUsed、netUsed 这类有界的计数和费用可能以 JSON 数字返回。请以各命令的字段表为准,不要把所有金额强行当成同一种类型。
读取 meta.warnings¶
每个条目可能是普通字符串,也可能是 {code, message} 对象。需要程序据此分支处理的警告使用对象形式——
目前包括 permission update 的安全警告和确认后的检查;其他警告使用
普通字符串。展示前请先统一处理两种形式,不要假设所有元素类型相同:
# 供人工阅读的文本——两种形式都适用
jq -r '.meta.warnings[] | if type == "string" then . else .message end'
# 针对特定情况分支——仅对象形式适用
jq -e '.meta.warnings[] | select(type == "object" and .code == "owner_lockout")' >/dev/null && exit 1
假定元素是字符串的辅助写法(.meta.warnings | join("\n")、Array.prototype.join)在遇到对象形式时会报错或打印出 [object Object]。警告的 code 取值在 v1 内是稳定且只增的——可能出现新的 code,已有的 code 会保持原有含义。警告的 message 文本不稳定;请像对待 error.message 一样,绝不要解析它。
分页¶
返回 offset/limit 窗口的命令会把它放在 meta.pagination 中报告,绝不会塞进 data 里。目前这类命令有 asset list、exchange list、proposal list、backup --records、x402 provider-list、bai recharge-orders 和 bai usage-records;某条命令也可能只是把 --limit 当作结果条数上限,从而不给出分页元数据:
| 键 | 类型 | 含义 |
|---|---|---|
offset |
number | 本页的起始索引——回显 --offset |
limit |
number | null | 每页大小;null = 不限(未给 --limit) |
total |
number | null | 匹配记录的总数;null 表示不存在这个计数,而不是"被省略了" |
这三个键始终存在,因此 null 是唯一的「未知」信号,也就不必再去区分「字段缺席」和「值为 null」。按游标翻页的服务还可能多出两个:hasMore(布尔)和 nextCursor(字符串,用 --cursor 传回)——目前只有 bai usage-records,它的 total 恒为 null。
对于由 TRON 分页节点端点提供的命令——asset list 和 exchange list——total 永久为 null。这些端点不返回计数,而要算出一个计数就意味着传输全部记录(主网上有 5,187 个资产,2.7 MB)。请一直翻页直到返回一个不足整页的结果为止,而不要去和总数比较:
offset=0
while :; do
page=$(wallet-cli asset list --limit 50 --offset "$offset" -o json)
n=$(jq '.data.assets | length' <<<"$page")
jq -c '.data.assets[]' <<<"$page"
[ "$n" -lt 50 ] && break
offset=$((offset + 50))
done
对本地有限数据集分页的命令(backup --records),以及先取回全部数据再在客户端分页的命令(proposal list),会报告 total。
text 模式会为同一页数据添加标题(Assets (limit 50, offset 0)、Proposals (showing 2 of 4)、Backup records (showing 3 of 12)),但 text 输出不属于稳定接口,请使用并解析 -o json。
错误码¶
退出码是稳定接口:2 表示命令参数或调用方式有误(直接重试仍会失败),1 表示执行过程中发生
网络、设备、链或钱包错误。error.code 用于进一步区分具体原因;程序应先按退出码分类,再按需要处理 error.code。
持续维护的错误码索引是公开的,位于能力发现目录的 errorCodes 下:
wallet-cli --json-schema | jq '.errorCodes'
每个条目都是对象,而不是单独的字符串:
{ "rpc_error": { "exit": 1, "retry": "same", "meaning": "the node answered with an error" } }
exit 是某个错误码退出状态的权威来源;下面的表格是手写的,并由测试与它核对。少数错误码带的是 "either":它们确实会在两侧出现,实际退出状态以进程真正返回的那个为准。
retry 回答的是「接下来怎么办」:same——立即重试完全相同的命令(节点或服务的偶发抖动);later——同样的命令会成功,但现在还不行,需要先退避一段时间(锁定期、提现间隔、限流);changed——必须先修改请求再重试(提高手续费、用新的 nonce 重新构建);never——原样重试不可能成功,必须改变命令之外的某些东西。所有退出码为 2 的错误码,按定义都是 never。
retry 描述的是错误,而不是命令。timeout 和 rpc_error 标为 same,是因为对大多数调用来说这是对的——节点根本没有动作,重发不会有代价。但是,一条可能已经广播出交易的命令(tx send 以及提交路径上的其他命令)也可能在节点已经受理该交易、而响应还没回来的时候撞上 timeout 或 rpc_error。这种情况下结果是未知,而不是失败;重发并不是在重试原来那个请求:它会构建并签名一笔新的交易,在 TRON 上就是第二笔互不相同的转账。对于解析网络 id 或读取余额时发生的 timeout / rpc_error,retry: "same" 是对的;但它不是盲目重发广播的许可。做决定之前,请先用 tx status 对账。
这份索引是本版本暴露出来的机器可读目录。请把它当作能力发现的辅助手段,而不是一个封闭枚举:有少数代码路径会在运行时动态选择错误码字符串,因此实际响应中仍可能出现 errorCodes 里没有的 code。下面的表格是高频子集,便于阅读。v1 内仍可能新增 code;有两个字符串(invalid_value、aborted)会根据抛出位置的不同而出现在两种退出码下——所以请始终兼容未知错误码,并回退到对应的退出码类别。
退出码 2(用法——修正调用方式)下的常见错误码:
| 错误码 | 含义 |
|---|---|
usage_error |
由参数解析器本身抛出——yargs 的用法失败,或位置参数过多。更具体的问题各有自己的 code:未知参数是 invalid_option,缺少必填参数是 missing_option,取值校验或跨字段规则失败是 invalid_value |
family_mismatch |
该命令、该账户、该收款方或该原始交易不属于所选网络所在的链家族 |
missing_option |
未提供某个必填参数 |
invalid_option |
某个参数被用在了非法组合中,或者它只属于另一个链家族 |
invalid_value |
某个参数取值未通过校验(例如 config defaultOutput xml) |
invalid_permission |
权限文档或所选的权限组对该操作而言不合法 |
invalid_amount |
金额格式错误或超出范围 |
weak_password |
master password 未达策略要求(≥8 个字符;大写 + 小写 + 数字 + 特殊字符) |
tty_required |
需要交互式提示,但没有挂载 TTY——请在终端中运行,或在该命令提供 stdin 标志时改用它 |
missing_network / unsupported_network |
调用方显式要求注册表解析一个空的网络 id,或者给出的规范 id / 别名未知。普通链上命令在省略 --network 时使用 config.defaultNetwork,其内置值为 tron:728126428 |
unsupported_network_capability |
所选网络不提供该命令所需的能力 |
limit_exceeded |
某个有界输入(文件大小、列表长度、分页大小)超出了上限 |
unknown_command |
没有这条命令 |
output_exists |
目标文件已存在且绝不会被覆盖(backup --out、address generate --out)。这是确定性的——用同一路径重试永远会失败 |
file_not_found |
某个参数指定的输入文件不存在(contract deploy --artifact / --code-file、contract create2 --code-file) |
keystore_not_found |
import keystore:给定路径上没有文件 |
invalid_keystore |
import keystore:不是合法的 Web3 V3 keystore——JSON 有误、version ≠ 3、使用了不支持的 cipher/KDF,或解密结果不是 32 字节私钥 |
invalid_config |
config.yaml 无法读取或不是合法的 YAML——请修复或删除该文件。具体解析错误会被隐藏,因为错误信息可能引用包含凭据的原始行 |
insecure_config |
config.yaml 保存了服务凭据,但它是符号链接或对同组/所有人可读——请对它执行 chmod 600(仅 POSIX;Windows 上不强制) |
account_not_found |
本地没有该 id、标签或地址对应的账户 |
contact_not_found / already_exists |
不存在该名称的联系人,或该联系人名称/地址已被占用 |
token_not_in_book / token_is_official / token_already_listed |
token 地址簿相关的状况 |
unsupported_token |
所选服务方或该命令不支持这个 token |
insufficient_voting_power |
请求的票数超出该账户可用的投票权 |
gasfree_credentials_missing / tronlink_credentials_missing |
未配置所需的服务凭据(用 config 设置) |
unknown_parameter |
不存在该名称或 id 的链参数(proposal create --set) |
invalid_asset_name |
TRC10 名称或缩写不在 1–32 个可见 ASCII 字符范围内 |
migration_required |
持久化的钱包数据需要升级,但本次调用因无法获得 master password 而不能完成升级——请在终端中重新运行,或用 --password-stdin 管道传入。参见启动时的钱包数据升级 |
seed_not_found |
derive 指向的账户或 id 不是 HD 钱包——而是私钥、Ledger 或仅观察账户 |
bai_credentials_missing |
未配置 B.AI API key。请用 config baiApiKey --api-key-stdin 设置 |
ambiguous_account |
--account <address> 匹配到多个账户,且这些账户在当前链家族中无法归并为同一个等效签名者;error.details 中包含候选项——参见 error.details.matches |
退出码 1(执行——运行时失败)下的常见错误码:
| 错误码 | 含义 |
|---|---|
rpc_error |
节点拒绝了请求或请求执行失败——可能是一次 TRON API 调用,也可能是 eth_estimateGas 之类的 JSON-RPC 方法 |
invalid_node_response |
节点响应与请求或协议不一致:TRC10/exchange 记录的 ID 不是请求值、precision 超出 0..6、汇率参数不是正 int32、EVM JSON-RPC 响应同时缺少 result 和 error,或最新区块查询没有返回区块。由于这些数据会影响签名金额,命令会停止执行,不会继续使用异常值;列表查询则丢弃异常记录并保留本页其他结果 |
timeout |
等待网络或设备时被中止(超过 --timeout) |
auth_required |
所需的凭据不可用——软件账户的 master password,或者 Ledger app / 设备未就绪 |
auth_failed |
master password 错误(解密失败) |
signing_rejected / transaction_rejected |
签名或广播被拒绝(设备或链) |
watch_only_no_signer |
该账户是仅观察账户,无法签名 |
invalid_mnemonic / invalid_private_key |
存储层校验拒绝了格式错误的助记词或私钥;交互式导入通常会在提示处当场发现并要求重输 |
token_metadata_unavailable |
无法从所选网络读取必需的 token 元数据。该错误可能对应两种退出码:大多数场景返回退出码 1;但在 TRON 上,如果 tx send 遇到合约不返回 decimals() 且地址簿中也没有对应条目,则返回退出码 2——此时必须修改调用参数或配置 |
wrong_device_seed |
连接的 Ledger 与已注册的账户不匹配 |
tx_integrity / invalid_transaction |
预签名交易未通过完整性 / 合法性检查 |
insufficient_balance / insufficient_token_balance |
TRX / token 不足以覆盖金额加手续费 |
provider_error |
节点或外部服务返回了 CLI 无法安全使用的数据——例如格式错误、自相矛盾或超出取值范围的响应(TRON 权限数据、链参数、本地 TronWeb 构建未暴露的 protobuf 编解码器,以及 GasFree / TronLink 的载荷)、请求失败,或者 GasFree / TronLink 返回错误状态码。TronLink 的所有非 404 状态码都归到这里,包括 429 |
provider_rate_limited |
某个外部服务返回了 HTTP 429。GasFree:若它发送了 Retry-After 头,error.details.retryAfter 会带上它。x402 facilitator 或 endpoint:对方发送时给出 error.details.retryAfterSeconds,并附带下文的付款明细——结算过程中的 429 是 paymentStatus: "unknown"。B.AI:error.details.httpStatus: 429。TronLink 的 429 则归为 provider_error |
tx_expired |
签名收齐之前交易就已过期(TRON) |
chain_id_mismatch |
该 EVM 交易是为另一条链构建的,与所选网络不符 |
nonce_too_low |
该 EVM 交易的 nonce 已经被一笔已入块的交易用掉了 |
history_not_supported |
该端点不支持 TronGrid 历史查询(account history,TRON) |
not_found |
所寻址的对象不存在——例如未激活的账户、交易、区块,或 GasFree / TronLink 资源 |
proposal_not_found / contract_not_found / asset_not_found / exchange_not_found |
链上不存在与该提案 ID、合约地址、TRC10 引用或交易对 ID 对应的对象 |
ambiguous_asset_name |
某个 TRC10 名称匹配到多个 token;error.details 中带有候选项——见 error.details.matches |
ledger_unsupported |
所选的 Ledger app 无法为该交易类型签名——请求会在访问设备前被拒绝(TRON 的账户激活、账户 id、asset 写操作、合约部署与治理、witness 写操作,以及 stake cancel-unfreeze) |
not_a_witness / already_witness / not_proposal_owner |
治理身份不满足该操作的规则 |
already_approved / not_approved / proposal_expired / already_canceled |
提案投票的状态条件 |
account_not_active / account_already_active / name_already_set / id_already_set / chain_parameter_unavailable |
账户激活 / 名称 / id 相关的状况,或者 witness create 无法读取 getAccountUpgradeCost |
not_contract_deployer |
该账户不是目标合约的部署者 |
already_issued_asset / not_an_issuer |
该账户已经发行过 TRC10,或者从未发行过 |
not_in_ico_window / self_participation |
TRC10 ICO 参与条件 |
no_frozen_supply / not_yet_unfreezable |
没有冻结的部分,或还没有到期的部分(asset unfreeze) |
not_exchange_creator / token_not_in_exchange / exchange_closed / same_token |
交易对的访问权限与状态条件 |
insufficient_reserve |
exchange withdraw:撤出量超过交易对对应一侧的储备 |
precision_loss / slippage_exceeded / exchange_trading_disabled |
根据有限白名单识别出的节点拒绝原因——金额无法按储备比例精确换算、回报低于下限,或该网络不接受 Bancor 交易 |
not_exportable |
该账户不持有可导出的密钥材料(仅观察或 Ledger)——backup |
wrong_keystore_password |
import keystore:文件自身的密码不对(区别于 auth_failed,后者指的是 master password)。mac 缺失或不是 hex 的文件属于 invalid_keystore,而不是密码错误——hex 不区分大小写 |
legacy_derivation |
4.13.1 之前创建的钱包中,1 号或更靠后的 TRON 账户使用的派生路径,本版本已不再用于签名。以该 TRON 地址签名、或用 derive 向其钱包添加账户时抛出。参见出现 legacy_derivation 后如何找回地址 |
derivation_mismatch |
某账户存储的地址与其种子的任何派生路径都对不上——wallets.json 与加密保险库互相矛盾 |
execution_reverted |
合约 revert 了该调用;原始 revert 数据在 error.details.revertData 中 |
not_authorized |
该账户无权执行此操作。8004 update / transfer / approve:该账户既不是 Agent 的所有者,也不是被允许执行此操作的操作者——这是在签名之前由注册表的 revert 解码得出的 |
agent_not_found |
所选网络的注册表中没有该 id 的 Agent——用于 8004 show,以及 8004 的各写入命令(在签名之前由注册表的 revert 解码得出) |
amount_exceeds_limit / no_matching_requirement |
x402 pay,无论是否加 --dry-run:价格高于 --max-amount / --max-raw-amount,或端点给出的支付路由中没有一条与所选网络、--token、--asset 或 --scheme 匹配。均在签名之前抛出(details.paymentStatus: "not_sent") |
gasfree_insufficient_balance / gasfree_not_activated / gasfree_asset_unsupported |
一笔 exact_gasfree 的 x402 付款:GasFree 账户不足以覆盖付款额加最高手续费、尚未激活,或不持有所选的 token 合约(请检查网络、token 和 --gasfree-relay)。在发出任何东西之前就抛出(details.paymentStatus: "not_sent") |
permit2_allowance_required / approval_reset_required |
x402 付款需要先有 token 授权额度,或者该 token 需要先把额度重置为零才能设置新值 |
fee_cap_exceeded / payer_mismatch / signed_payload_mismatch |
x402 授权在签名前或签名后被拒绝:GasFree 手续费超过上限、载荷中的付款方是另一个地址,或签名覆盖的结构与请求的不一致 |
invalid_settlement / invalid_x402_response |
付费后的响应带有无效的结算回执,或某个 x402 响应无法解码 |
provider_not_found / catalog_schema_unsupported |
x402 provider-show / endpoint-list:没有该名称的服务方;或下载到的目录使用了本版本无法读取的版本 |
port_in_use |
x402 serve / roundtrip:请求的本地端口已被占用。x402 serve --daemon 下,后台服务器启动失败(含端口被占用的情形)会报告为 provider_error,并带上 error.details.logFile |
response_too_large |
远端响应超过了 CLI 的大小上限 |
bai_auth_failed / bai_rejected |
B.AI 拒绝了该 API key,或出于可识别的业务原因拒绝了某项操作(error.details.reason) |
internal_error |
预期之外的内部失败;消息刻意保持通用 |
预期之外的异常会先经过脱敏处理,再以 internal_error 和通用消息返回,避免第三方库回显的敏感信息
进入响应。上面两张表仅用于方便阅读;持续维护的能力发现索引是 --json-schema 返回的 errorCodes,
但该索引也不保证列出解析器可能返回的所有错误码。
x402 与 B.AI 的支付细节¶
付款失败时,error.details 中会带上额外字段,便于脚本判断钱是否可能已经出去了:
| 字段 | 含义 |
|---|---|
phase |
失败发生在哪一步:request、challenge、create_payment、sign、payment_request、verify 或 settle |
httpStatus |
失败请求的 HTTP 状态码(如果有的话) |
paymentStatus |
not_sent——什么都没发出去;unknown——无法确认结果,因此再次付款前请先对账;settled——收到了结算回执,但所购买的资源未能交付 |
settled / delivered |
付款是否已结算,以及资源是否已送达 |
retryPayment |
false 表示不要为了补救而再付一次。它优先于错误码自带的通用 retry 值 |
candidateTxHash / candidateNetwork |
一笔可能就是该付款的交易。它是对账用的线索,而不是付款已完成的证据 |
approval |
TRON:失败前已签名的那笔一次性 Permit2 approve——{txId, token, spender, allowance, feeLimitSun, status},其中 status 为 submitted、confirmed 或 exported。它不是付款,不要当作付款上报。如果它旁边还有 paymentStatus: "not_sent",则说明链上已存在该授权额度,但并未生成任何付款授权 |
请把 paymentStatus: "unknown" 当作可能已付。 举例来说,从一个没有 token 余额的账户发起 exact 付款,当前会以 provider_error 失败,带 phase: "create_payment" 和 paymentStatus: "unknown"——尽管实际上什么都没发出去。
error.details.matches¶
部分错误表示输入存在歧义,需要调用方从多个候选项中选择,而不是简单重试。这类错误会把候选项放在
error.details.matches 中,其值是字段结构一致的对象数组:
{"code":"ambiguous_asset_name","message":"2 TRC10 tokens are named MyToken; re-run with the id","details":{"name":"MyToken","assetIds":["1000123","1000488"],"matches":[{"assetId":"1000123","issuerAddress":"TQkXm4vN...","totalSupply":"1000000000000000","precision":6},{"assetId":"1000488","issuerAddress":"TZx9kP2m...","totalSupply":"5000000000","precision":2}]}}
ambiguous_account 是这套约定的第二位成员,也是更常遇到的那个——任何 --account <address> 只要在当前操作所属的家族下无法解析为唯一一个可互换的签名者,都会返回它:
{"code":"ambiguous_account","message":"address T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb matches 2 accounts; address it by accountId","details":{"address":"T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb","accountIds":["wlt_a1b2c.0","wlt_d3e4f.0"],"matches":[{"accountId":"wlt_a1b2c.0","label":"main","type":"seed","index":0},{"accountId":"wlt_d3e4f.0","label":"cold","type":"watch","index":null}]}}
matches 是通用字段,并不限定于某个错误码。任何带有该字段的错误都应按相同方式处理。text 模式会在
stderr 的 error [...] 行下方以表格显示候选项。matches 中的数量保留最小单位,与对应成功响应的
表示方式一致;某行包含 precision 时,text 表格会换算为便于阅读的数值。
与它并列,错误还可能携带一个只包含标识符的标量列表,供你据此重试——就是上面的 assetIds。脚本请优先用它;matches 的存在是为了让人能分辨这些候选项。
敏感信息处理¶
wallet-cli 绝不从命令行参数、也不从专用的敏感信息环境变量中读取密码、助记词或私钥:参数和导出的变量会泄漏进 shell 历史、进程列表和 CI 日志。只有两条通道:
- stdin 标志——
--password-stdin用于 master password,--tx-stdin/--message-stdin用于较大的载荷。每次运行只能有一个*-stdin标志消费 stdin。(助记词和私钥没有 stdin 通道——import mnemonic/import private-key/change-password只能交互执行,走隐藏的 TTY 输入。) - 交互式 TTY 提示——只出现在那些自己声明为交互式的命令上:
create、各个import子命令、backup、change-password,以及delete的确认。其他地方有没有终端都一样:tx send、contract *、stake *、message sign之类从不弹出提示,缺少 master password 一律是auth_required(退出码1),与是否挂载 TTY 无关。
示例中的 shell 变量只是 shell 一侧为管道准备的数据来源;wallet-cli 并不会去读它们。请让它们保持在进程内、生命周期尽量短,也不要长期 export。
# 非交互式解锁
printf '%s' "$MASTER_PASSWORD_FROM_YOUR_VAULT" | wallet-cli tx send \
--to TSx72ViULFepRGCS4PM5dP4FqD1d8qggCc --amount 1 \
--network tron:3448148188 --password-stdin -o json
脚本安全:绝不要把"已提交"当作"已确认"¶
错误判断交易是否成功可能造成资产损失。请遵循以下规则:
- 广播类(✍️)命令默认在提交后就返回,而不是等待确认。
data是一个扁平对象,其中包含表示操作类型的kind(send、stake-freeze、permission-update、account-activate、proposal-create、asset-issue、exchange-trade等)、stage和txId;submitted阶段不包含区块、手续费或执行结果(这些字段只有在--wait确认后才会出现):
{ "kind": "send", "stage": "submitted", "txId": "7d9b6a08…", "rawAmount": "1000000", "to": "TSx72…" }
由链分配的 id 只有随确认才会出现。 新提案的 proposalId、TRC10 的 assetId、交易对的 exchangeId 在提交时并不存在——它们不会出现在提交回执中,而是在 --wait(或之后的查询)在链上看到该交易之后才出现。创建这些对象的脚本必须等待。
Agent id 同理:8004 register 会立刻把请求的 uri 放进 data.identity,而只有在能读到已确认的注册事件时,才补上 identity.agentId(十进制字符串)。8004 update 报告 identity.agentId、oldURI 和 requestedURI,确认之后再加上 newURI;8004 transfer 报告 agentId、oldOwner 和 requestedOwner,确认之后再加上 newOwner。8004 approve 报告 agentId 和 operator(使用 --revoke 时为零地址)。8004 add-operator 和 remove-operator 报告 operator 和 requestedApproval,确认之后再加上从注册表读回的 approved。
- 想阻塞到结果已知为止,请传
--wait(轮询直到 confirmed/failed,上限由--wait-timeout控制,默认 60000 毫秒;触及上限时返回提交回执)。
--wait 通过 data.stage 报告交易结果,而不是通过 success。 如果交易已提交并入块,但链上执行失败,CLI 调用本身仍算成功:响应中的 success 为 true、退出码为 0,而 data.stage 为 "failed"。退出码表示 CLI 是否完成了请求,并不表示链上执行是否成功。因此使用 --wait 后,程序必须检查 data.stage(confirmed / failed / submitted),再决定如何记录本次操作。
- 或者你自己用
tx status轮询,它有一个四状态模型:
data.state |
含义 | 是否终态? |
|---|---|---|
confirmed |
已入块,且能拿到执行结果 / 回执(带有 blockNumber) |
是 |
failed |
已入块但被 revert / 拒绝 | 是 |
pending |
节点已看到,但尚无执行结果 / 回执 | 否——继续轮询 |
not_found |
所查询的端点不认识它 | 否——继续轮询并对账;不要臆断为失败 |
data.confirmed 和 data.failed 以布尔值提供,便于直接分支。
confirmed表示已入块并取得回执,不表示已最终确定。当这个区别重要时,请另行验证——TRON 上查询 SolidityNode 视图,EVM 上检查 finalized 区块。如果截止时间到达时状态仍为
pending或not_found,交易结果仍然未知。不要将其记录为失败;在通过外部方式对账之前,不要自动重发。
GasFree 转账是个例外。 gasfree transfer 提交给的是一个服务提供方,而不是直接提交给节点:提交回执中带的是 traceId(而不是 txId),进度遵循提供方的状态——WAITING → INPROGRESS → CONFIRMING → SUCCEED / FAILED。请用 --wait 或 gasfree trace <traceId> 跟踪它,而不是 tx status;txId 只有在提供方把它送上链之后才会出现。
#!/usr/bin/env bash
set -euo pipefail
deadline=$((SECONDS + 90))
txid=$(
printf '%s' "$PW" |
wallet-cli tx send --to T... --amount 1 --network tron:3448148188 --password-stdin -o json |
jq -er '.data.txId'
)
while (( SECONDS < deadline )); do
state=$(
wallet-cli tx status --txid "$txid" --network tron:3448148188 -o json |
jq -er '.data.state'
)
case "$state" in
confirmed) exit 0 ;;
failed)
echo "transaction failed: $txid" >&2
exit 1
;;
pending|not_found) sleep 3 ;;
*)
echo "unexpected transaction state: $state" >&2
exit 1
;;
esac
done
echo "transaction outcome unknown after deadline: $txid" >&2
exit 1
- 批量操作:每条命令对应一笔交易和一个退出码。默认的安全做法是遇到第一个失败就停止;如果选择继续,请逐项记录 txid,并在报告成功前用
tx status逐一核对。
稳定性承诺(v1)¶
只要 schema 是 wallet-cli.result.v1,以下内容保证稳定:
- 上表中的响应字段名称与语义;
- 0/1/2 的退出码映射;
- JSON 模式下 stdout 只输出一个完整 JSON 对象的约定;
- 已有的
error.code取值保持其含义(可能新增 code); - 规范命令 id 和网络 id(
tron:728126428、tron:3448148188、tron:2494104990、eip155:1、eip155:56、eip155:11155111、eip155:97、eip155:8453、eip155:84532)。
网络别名属于配置,不属于接口约定:它们可以在本地被重新指向,因此脚本应当传规范 id。
不在承诺范围内:text 模式输出、error.message 的措辞、字段顺序、meta.durationMs 的取值,以及任何在命令参考页上标记为尽力而为(best-effort)的字段(例如 account portfolio 中的 priceUsd)。