链上核验清单:收据、钱包与授权,别只看表面

收据说每行都平,链说少了 0.002 BNB;钱包"看起来正常"却已被 EIP-7702 接管。四个近期样本,一张链上核验清单。

链上核验清单:收据、钱包与授权,别只看表面

一句话结论:链上没有"看起来正常"这回事。一份每一行都平、对 swap 日志精确到 wei 的回购收据,链上却少了 0.0020265546 BNB;一个在浏览器里"空投历史、短、没有标记"的普通 EOA 钱包,被 6 条 EIP-7702 授权变成了收到钱就自动转走的木偶;一次"免费"的 permit 签名,和一次无限 approve 一样能清空钱包。把四份 2026 年 10 月的新样本放在一起,可以拼出一张可复查的链上核验清单——核心只有一句:读代码,不读历史;读链,不读收据;读数,不读感觉。

本文整理:Kaminari Research 的《The receipt said everything balanced. The chain disagreed by 0.002 BNB.》与《The wallet looked normal on the explorer. Six EIP-7702 signatures said otherwise.》(dev.to,2026-10-14)、cryptostrapon 的《How "approval phishing" drains wallets without stealing a key》(dev.to,2026-10-14)、OpenClaw Cash 的《Test your agent's payment loop on a testnet before it goes live》(dev.to,2026-10-14)。立场说明:Kaminari 两篇文末在售 Agent Skill(Onchain Claim Verifier / Crypto Wallet Safety Check),cryptostrapon 有自己的检查站与题库,OpenClaw Cash 是钱包/支付服务商(其官方文档为内容来源)——本文按"机制与检查方法"引用,不构成对任何工具或平台的推荐。本文不构成投资建议。


一、收据 vs 链:0.002 BNB 教会的事

先看那个样本。某项目金库发布回购收据:每一行都闭合,费用列到小数点后六位,余额与 swap 日志"精确到 wei"地对上。但作者把链上的实际资金流重算了一遍:两次连续回购之间,有 0.0020265546 BNB 离开了金库,没有出现在收据的任何一行。

差额的真相不复杂——那是一笔发给托管钱包的 0.002 BNB gas 补助,而收据生成器在那个窗口把"gas 补助"记成了 0,只打印了支付费用。团队确认了缺口并修了生成器。真正有价值的部分不是这个 bug,而是抓住它的检查,因为那个检查谁都能重跑。作者给的规则有六条:

  1. 先把问题钉死再问链。"查一下余额"不是问题;一个可复查的问题是四件套:合约地址、函数选择器、带 ABI 编码的参数、区块号(或 latest)。这次的审计起点是:"把该地址在 A 块到 B 块之间所有 BNB 流出求和,与收据的净额行比对"——任何人拿一个节点都能重跑;
  2. 0x 不等于 0。RPC 对一次根本没执行的调用(区块范围错、provider 抽风、合约不在该链)可能返回 0x 空响应。把 0x 解码成 0,你发布的不是"余额为零",而是"我不知道"。规则:空响应 = UNVERIFIABLE(无法核验),永远不是 FALSE(否)。作者在 Base 上就撞到过:同一个历史调用,一个 provider 返回 0x,另一个返回正确结果——两个来源、一个归档节点,或者不发这个结论;
  3. 永远不要假设单位。18 位小数是脑子里的默认值,不是链上的事实。如果合约没暴露 decimals()、来源也没写明精度,诚实的输出是 no_unit_stated,而不是一个带猜测指数的数字。1000 倍的错误不是"舍入问题",是加了步骤的错误答案;
  4. 把结论绑定到取数那一刻。核验状态类主张时,证据必须带:区块号、区块哈希、原始响应、以及原始响应的摘要(对精确 JSON 做 sha256)。没有这层绑定,"我检查过"就不是证据——状态会变,收据会漂移;
  5. 最终性是数字的一部分。"已确认"在不同层含义不同:L2 交易可以几秒软确认、很久以后才批量最终确认。报告要同时写明确认层级与语义,否则下游决策继承的是歧义;
  6. "检查通过"本身也是主张。最不舒服的一点:大多数检查的结果是"干净",而"全部正常"恰恰是最容易说错的时刻——它同样需要四件套、两个来源、摘要。开头那份收据看起来就很干净,这才是重点。

二、钱包:看起来普通,可能已被"委托"

第二个样本是一个"看起来很正常"的钱包:浏览器里显示为标准 EOA、历史短、没有任何标记——典型的"先打小额测试"的对象。但测试金额在同一区块内到达又离开:收到的钱被自动转发,没有 owner 发起的交易,没有任何 approve 可以撤销。钱包被"委托"了。

这是 EIP-7702(Pectra 后上线)的机制:EOA 可以为自己设置代码。owner 签一个 authorization tuple,账户代码就变成一段委托指示符:0xef0100 || <delegate address>。账户仍然是 EOA——地址没变、私钥没变——但之后的每一次调用都执行 delegate 的代码。对钱包来说这是强大的能力(批量交易、代付 gas、社交恢复);对攻击者来说同样好用:一条签名授权,账户就变成看起来仍是普通 EOA 的木偶。

作者拉取了与该地址相关的全部 Authorization tuple(每条包含 chain id、delegate 地址、nonce、y-parity 与 r/s 签名),用 ecrecover 恢复签名者:6 条授权全部来自同一把密钥、全部指向同一个 delegate。而 delegate 的代码读完就破案:收到 value 就转发,转到一个硬编码地址,没有任何可满足的条件分支——发 0.01 ETH 进去,它就留下 0.01 ETH;之后每个"小额试试"的人也一样。

三道读取就能在转账前识破,不需要任何索引器:

  1. 读代码,不读历史:对地址做 eth_getCode。空响应 = 普通 EOA;以 0xef0100 开头 = 已被委托,紧随其后的 20 字节就是 delegate 地址。"这个地址还没有交易"什么都说明不了——被委托的账户可以是全新历史;
  2. 读 delegate 的代码:拿 delegate 地址,再读一次它的代码,问一个具体问题:普通的纯转账进来会发生什么? 它会转发吗?转给谁?目标是硬编码的吗?真正的收割逻辑写在大约 30 字节的字节码里,不在任何 UI 上;
  3. 读授权:拉取该账户的 Authorization tuple(在委托交易的 calldata 里),用 ecrecover 验证签名者。一条不是 owner 签的授权,其危险程度高于其他一切信号。

配套的一条"元规则"值得单独记住:"先打个小额测试"不是安全策略。 对一个"delegate 到收割合约"的钱包,唯一安全的金额是 0。

三、授权:免费签名不等于安全信号

第三类核验对象连交易都不需要——授权(approve)。ERC-20 的机制很直白:你调用 approve(spender, amount),此后 spender 就能用 transferFrom 把你的代币转去任何地方,不需要再问你。

风险在于:大量 DApp 为了省事,请求的是 MaxUint256(无限授权)。如果那个 spender 是骗局合约——或者是一个后来被攻破的正经合约——它可以在任何时候清空你那一种代币,而授权一直有效到你主动撤销。

典型的钓鱼流程是:假空投/假领取/克隆铸币站告诉你"验证钱包"或"领取奖励"→ 钱包弹出签名或授权请求,文案看着无害或者根本看不懂 → 你签了 → 骗子的合约拿到了 allowance → 资金可能几天后才被转走,那时你已经想不起和哪个页面有关了。还有一层隐蔽性:permit / Permit2 这类链下签名可以不付 gas 完成同样的事——所以"这次没花 gas"不是安全信号。

自己查一个授权只要十行(ethers v6):

import { ethers } from "ethers";

const ERC20 = ["function allowance(address owner, address spender) view returns (uint256)"];

const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const token = new ethers.Contract(process.env.TOKEN, ERC20, provider);

const allowance = await token.allowance(process.env.OWNER, process.env.SPENDER);
console.log(allowance === ethers.MaxUint256 ? "UNLIMITED allowance" : allowance.toString());

用自己的钱包地址、代币地址与你想检查的 spender 跑一遍:一个你不认识的地址持有无限授权,就是红旗。 处置建议也很常规:撤销不用的授权(浏览器"代币授权"页面或 撤销授权工具);能选精确额度就不选"无限";签名前读一眼文案——只要出现 approve、setApprovalForAll、permit,或一长串你解析不了的数据,就停下来;打开新站点用只放少量资金的小号钱包。

四、Agent 付款:上线前先在 testnet 把回路跑坏

前三节是"人核验链",第四节是"Agent 花链上的钱"。把支付接进 Agent 有几类错误只在真钱上出现:网络超时后重试把同一张发票付了两遍;收款地址来自过期配置;webhook 处理函数接受重放的投递——这些问题在"无法撤销的主网余额"上都看不见,最便宜的发现地点是测试网。

一个完整的演练要证明三件事:

  1. Agent 能读到发给它的钱包,看到的余额与预期一致;
  2. 转账能通过最低可转金额检查(含手续费);
  3. 你的服务器能验签,并在重试窗口内返回 2xx,让平台从不重试。

流程本身是标准的:在 Sepolia 等测试网创建钱包(创建时先持久化导出口令、再确认已保存;且关闭 key 的 live 交易开关,让这把 key 在越权时也只能碰测试网)→ 从水龙头充值并重读余额 → 发起小额转账 → 订阅 wallet.transaction.confirmed webhook。两个容易踩的细节:金额字段只能给一个(amountDisplay 或 valueBaseUnits,同时给会被拒);原生币转账会做 preflight,余额不够手续费时返回 amount_below_min_transfer 而不是广播一笔付不起的交易。

Webhook 侧按清单核对:先读原始 body 再解析 JSON;拒绝 webhook-timestamp 超过 5 分钟的投递;用 HMAC-SHA256(对 {id}.{timestamp}.{body} 做 base64)与签名头里的 v1 做常量时间比较;按 webhook-id 去重(同一条投递可能到达多次);10 秒内回 2xx——超时的投递会被重试约一天,而返回 410 会直接关掉该端点。测试网转账产生与主网完全相同的事件,这正是重点:让重复投递、过期时间戳与 410 路径先在 Sepolia 上失败,而不是在客户的发票上失败。

上线时的两条边界也要写进 checklist:权限与策略在平台侧执行,不在你的 Agent 代码里——一把开着 live 交易、却没挂策略的 key,危险程度和你本想避开的私钥一样;撤销是控制台动作——删掉 API key,钱包余额还在,因为钱包的密钥材料从未绑定到 API key 上。

五、链上核验清单

日常/例行核验

  • [ ] 钉死四件套:合约地址、函数选择器、ABI 参数、区块号;
  • [ ] 空响应不是 0:0x = UNVERIFIABLE,宁可说"不知道";关键结论两个来源或归档节点;
  • [ ] 不假设精度:没有 decimals() 或来源声明,输出 no_unit_stated;
  • [ ] 绑定证据:区块号 + 区块哈希 + 原始响应 + 摘要;"干净"也要同样标准;
  • [ ] 写清最终性:确认层级与语义随数值一起给。

付款前(人 / Agent 通用)

  • [ ] eth_getCode:0xef0100 开头就是被委托的钱包,继续读 delegate;
  • [ ] 读 delegate 的行为:纯转账进去会转发吗?转给谁?条件是什么?
  • [ ] ecrecover 验授权签名者:不是 owner 签的授权,一票否决;
  • [ ] 查 allowance:不认识的 spender + 无限额度 = 先撤销再谈;
  • [ ] 不迷信"小额测试":对疑似收割钱包,安全金额是 0;
  • [ ] permit 也要读:免 gas 的链下签名,同样能设授权。

Agent 支付上线

  • [ ] 先在 testnet 跑完整回路:读钱包 → preflight → 转账 → webhook 验签与去重;
  • [ ] 金额字段只给一个;webhook 校验时间戳、HMAC(常量时间)、webhook-id 去重、10 秒内 2xx;
  • [ ] 上线 key 单独开、限定单链单钱包、挂上策略;testnet key 留给测试套件。

系列另两篇:Agent 说"做完了",证据呢:三道交付验证门(同一套验收文化落到编码 Agent 上)与 别人发来的 3D 文件怎么验收:STEP 翻译与网格拓扑(落到 3D 资产上)。


原文链接

  • @kaminariresearch, The receipt said everything balanced. The chain disagreed by 0.002 BNB.(dev.to,2026-10-14;作者在售 Agent Skill):https://dev.to/kaminariresearch/the-receipt-said-everything-balanced-the-chain-disagreed-by-0002-bnb-2fl0
  • @kaminariresearch, The wallet looked normal on the explorer. Six EIP-7702 signatures said otherwise.(dev.to,2026-10-14):https://dev.to/kaminariresearch/the-wallet-looked-normal-on-the-explorer-six-eip-7702-signatures-said-otherwise-2lg1
  • @cryptostrapon, How "approval phishing" drains wallets without stealing a key (and how to check it in 10 lines)(dev.to,2026-10-14;作者运营检查站/题库):https://dev.to/cryptostrapon/how-approval-phishing-drains-wallets-without-stealing-a-key-and-how-to-check-it-in-10-lines-4pfl
  • @openclawcash, Test your agent's payment loop on a testnet before it goes live(dev.to,2026-10-14;官方 API 文档为内容来源):https://dev.to/openclawcash/test-your-agents-payment-loop-on-a-testnet-before-it-goes-live-3n0a

相关阅读

本文由 DefiPlot 翻译整理,转载请注明出处。

免责声明

本文仅用于学习与研究目的,不构成任何投资建议、财务建议或交易建议。文中案例均来自公开来源(作者自述、脱敏数据或服务商文档),未做独立链上复测;相关人物与项目方均带有各自立场,请自行核查并 DYOR(Do Your Own Research)。加密资产价格波动剧烈,链上交互与自动化支付存在合约、授权、钓鱼与执行风险,任何工具与流程都不能替代自己的核验;请勿使用无法承受损失的资金参与交易。

本文属于「链上数据 / 安全」专题。查看专题全部文章 →
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。