x402发现字段放错层级,286个接口受阻
作者用Coinbase CDP公开校验器检查286个x402接口,发现Bazaar扩展被错误放在accepts[0]中,而校验要求它位于402响应顶层。文章据此说明,自建审计通过仍可能遗漏平台发现机制要求,影响接口被索引和买方代理发现。
AI 深度解读
286 个付费接口,内部审计跑了约 200 轮,收入仍是 $0.00。作者接入 Coinbase CDP 的公开校验器后才发现:发现信息放错了层级,大部分接口的价格低于支付下限,还有接口会重复发起结算。
这篇排障记录最有价值的地方,是把「代码能跑」「支付可用」「能被市场发现」拆开验证。修复之后,他们终于通过外部校验,但仍未被收录,也没有收入。这个差别,值得所有做付费 API 和 Agent 工具服务的人看清楚。
为什么接口已经上线,买家却找不到
作者提供约 286 个付费 x402 接口,内部审计曾给出 100/A、100/A、55/C、35/D,却始终没有成交。他们随后直接询问负责支付协调和 Bazaar 索引的 Coinbase CDP facilitator。
下面这条命令无需密钥,可以检查指定资源是否满足 CDP 的要求:
curl -X POST https://api.cdp.coinbase.com/platform/v2/x402/validate \
-H "Content-Type: application/json" \
-d '{"resource":"https://example.com/api/extract","method":"GET"}'
注意请求体中的 resource 和 method:检查对象是具体接口及其 HTTP 方法。
286 条路由全部返回 simulation.outcome: "rejected",拒绝原因是 no bazaar discovery extension found。缺失检查包括 has_bazaar_extension、bazaar.info、bazaar.info.input、bazaar.info.input.type、bazaar.info.input.method 和 bazaar.schema。
信息其实存在,只是位置错了。作者原来的写法是:
accept_obj["extensions"] = {"bazaar": bazaar_ext}
这行代码把发现信息放进了 accepts[0]。要注意的就是这个归属位置:CDP 要求 extensions.bazaar 位于 HTTP 402 响应体顶层,与 accepts 平级。
买家 Agent 按约定位置读取,拿不到信息,就无法构造发现记录。按作者描述,CDP 在资源首次成功结算后才建立索引,没有独立注册入口。于是故障串起来了:无法完成这条交易发现链路,没有结算,没有收录,也就更难被找到。
字段存在,不等于协议使用方读得到。
价格和描述也能让交易直接失败
校验器还在他们的线上接口中发现了两项硬性限制。
第一,CDP 要求最低支付金额为 $0.001,对应 1000 atomic units。作者却把 284 条路由定价为 $0.0005,对应金额 500,校验器将其标记为 severity: required。
对于经过这个 facilitator 的买家,这个价格无法支付。作者还抽样检查了 Bazaar 中 2,628 个 accepts[],低于 $0.001 的占 0.91%,中位数是 $0.01。不过,这组市场数据只能提供参照;不能据此把 CDP 的限制推广为所有 x402 支付路径的共同限制。
第二,资源描述最多允许 500 个字符,超长会导致 verify/settle 被拒。作者有 179 条描述超限,最长达到 2,171 个字符。而且 manifest 中放的是长版本,支付响应里却是另一个短版本,同一个资源出现了两套描述。
他们的处理方式是统一价格、描述和金额的生成入口,在一个位置执行限制,让 manifest 与 402 响应使用同一份结果。
对开发者来说,这比单独修几个字段更有用:发现目录和支付响应如果各自拼装,很容易出现「目录里承诺一种价格,实际支付又是另一种」的问题。
为什么编译通过,仍然可能重复扣款
检查支付响应时,作者又发现 c204 路由被支付门禁包装了两次:定义时加过装饰器,安装路由的循环里又包了一层。
他们用计数版结算替身(settle stub)测到,/api/x402-payment-challenge-conformance 和 /api/x402-buyer-payment-readiness-report 的 settle_calls=2、wrapper_depth=2;对照接口 /api/extract 则都是 1。另有六条路由存在同样问题,共八条。
作者将其判断为重复收费风险。这里需要区分证据:计数替身直接证明的是一次请求调用了两次结算,原文没有提供真实买家被扣两笔的账单。修复后,八条路由均测得 settle_calls=1,并增加启动检查,发现双重门禁就拒绝启动。
第一次修复还引入了 Python 闭包作用域错误:嵌套函数给闭包变量赋值,变量被当成局部变量,所有付费路由返回 HTTP 500。py_compile 却正常通过,六个发现入口也全部返回 200。只有实际请求付费路由才暴露问题。
最终,他们遍历了目录中全部 287 个路径:286 个返回支付门禁 402,意外返回 200 的数量为 0;顶层扩展缺失、v2 amount 缺失、描述超限、目录与线上价格不一致,也全部为 0。
外部验证展示了 /api/dns 和 /api/extract 的 valid=True、outcome=accepted,没有失败项;对照资源 example.com 则被拒,失败 21 项。这能说明校验器会区分合格与不合格输入,但不能把两个展示结果当作 286 条路由全部经过外部验证的证据。
我的判断
我赞同作者把验收交给实际接收协议的一方。内部检查仍有价值,但如果实现和检查共享同一个错误假设,再跑 200 轮也可能看不见问题。外部校验补上的,是另一套独立约束。
不过,通过校验,只能证明跨过了相应门槛,不能证明已经被收录或有人愿意购买。
作者写到第 205 轮时,累计收入仍是 $0.00。他们尚无首次成功结算,服务还运行在 trycloudflare 临时域名上。作者认为,买家签署的 EIP-3009 授权与资源 URL 绑定,域名轮换会让已授权支付难以继续使用;但原文没有展开具体绑定机制,也没有提供复现实验。
「买家路由器会把无成交记录的卖家放在成熟卖家后面」同样缺少具体实现和对照数据,只能视为作者对当前阻碍的判断。不能据此推导出换固定域名、补一笔结算,就一定会获得自然流量。
他们新增了索引存在性和收录准备度两个付费接口,分别查询发现渠道、调用官方校验器。这是把排障能力产品化的尝试,实际需求和付费意愿仍未得到证明。
能拿来做什么
- 如果你在用 Node 提供付费 API,把价格、金额和描述集中生成,让 manifest 与 HTTP 402 响应消费同一份数据,并校验目标 facilitator 的具体限制。
- 如果支付校验通过装饰器、中间件或路由安装逻辑接入,就统计一次请求实际触发了几次结算,并在启动时阻止重复挂载。
- 给 AI 编程任务补上真实请求验收:遍历付费路由、核对响应体、调用外部校验器,再放一个已知不合格的对照输入。把「协议通过」「已收录」「已有成交」分别记录。