代理找到了付费端点,解开了402响应,也把付款签了名。结果回来还是402。在签名这一步失败的x402付款,几乎总是两种情况之一:授权在结算前就过期了,或者它的nonce已经被花掉了。这两种都很容易在无意中造出来,而一旦你能看见它们,修起来也很容易。
需要先厘清一件事:EVM链上的x402付款不是一笔交易,而是一条签过名的EIP-3009 transferWithAuthorization消息。它包含六个字段:
- from:你的代理钱包
- to:付款要求里的payTo
- value:付款要求里的确切金额,以十进制字符串表示
- validAfter:该授权可被使用的最早时间戳
- validBefore:最晚时间戳
- nonce:一次性的bytes32标识符
服务器(或其facilitator)负责广播并支付gas。你的签名就是全部工作,而其中两个字段造成了几乎所有的首次尝试失败。
失败一:窗口在结算前关闭
validBefore是在结算时上链检查的,不是你签名时检查的。任何拖慢结算的事情都会吃掉你的窗口:代理在读取要求和签名之间停顿了,facilitator忙,区块慢。
经典版本是这样的:代理解开付款要求,花几秒钟“思考”(或者走一轮LLM往返),然后签名,validBefore直接抄了付款要求自带的截止时间。等交易落链,截止时间已经过去,结算回滚。
两条规则可以防住它。第一,用签名时你自己的时钟计算validBefore,以付款要求里的maxTimeoutSeconds为上限,再减去一段缓冲,六十秒是个合理的最低值。第二,最后才签名:读完要求,把代理需要做的其他事全部做完,只在准备发出付费请求时才签。带倒计时的签名是最后一步,永远不是中间产物。
镜像的失败同样存在:时钟走快了会把validAfter设到未来,授权尚未生效。如果代理运行在时间可疑的环境里,用validAfter = 0,然后去修时钟。
失败二:nonce已经被花掉
EIP-3009的nonce是一次性的。USDC合约在authorizationState[from][nonce]里追踪每一个nonce,一旦被消费,该授权就再也不能结算。两种方式会意外烧掉nonce。
其一是顺序编号。如果钱包软件把授权编成1、2、3,两个共用同一钱包的代理迟早会挑中同一个号。EIP-3009期望的是随机的32字节nonce,这样碰撞就不成问题。
其二是用同一份签名载荷重试。代理发出付费请求,连接超时,它用完全相同的载荷重试。但第一次尝试可能已经结算,或者正在结算的路上。重试于是因授权已被使用而失败,代理据此认定付款“没成功”,而它可能实际上已经付过了。
安全的做法是查询,不是重发。超时后先读订单或资源状态(多数x402 API、以及每一个行为良好的API,都提供了询问的途径)。如果必须再付一次,用全新的nonce签一份全新的载荷。绝不用同一个nonce重签,也绝不跨订单复用载荷。
发送前的60秒检查清单
在请求离开代理之前,解开你即将发送的载荷,对照付款要求逐项核对:
- to与payTo逐字符相等,包括校验和
- value与amount相等,十进制字符串,不是JSON数字,不是浮点数
- validBefore至少在六十秒之后,且在maxTimeoutSeconds之内
- 该nonce从未被这个钱包使用过(随机生成,且你此前没有发过)
- EIP-712域与付款要求中的链和资产匹配(name、version、链id、验证合约)
五项检查。跳过任何一项都会在链上大声失败,而它们没有一项在链下产生成本。
如果做完这些仍然失败,重复的402通常会在响应体里带上原因。重签之前先读它:带原因的“invalid”是服务器在告诉你,六个字段里它不同意哪一个。用新nonce把同一个错误再签一遍,只是又烧掉一次尝试。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.