转走再转回,低价挂单活了过来:ERC-5008 给每枚 NFT 记一笔 nonce 图 1
转走再转回,低价挂单活了过来:ERC-5008 给每枚 NFT 记一笔 nonce · 图 1

转走再转回,低价挂单活了过来:ERC-5008 给每枚 NFT 记一笔 nonce

先讲一个真实的攻击剧本。卖家在市场签过一份低价订单,之后撤了单——页面显示订单“失效”了。可没过多久,藏品被人按那个老价格买走。中间发生了什么?有人(往往就是卖家自己)把 NFT 转到另一个钱包,再转回原地址。在一些市场机制里,这趟短途旅行会让早先挂着的旧报价重新变得可执行,于是藏品以远低于当前地板价的价格成交。ERC-5008(2022 年 4 月 10 日创建,进入过 Last Call 最终征询、截止日期为 2023 年 8 月 15 日,提案文档现状为 Last Call)想提供一个很小的零件来堵这个口子:给每枚代币记一个会变的编号。

失效靠感觉,nonce 靠事实

为什么转一圈就能“复活”旧单?不少早期市场对“订单是否还有效”的判断依赖归属关系这类间接信号:订单签发时卖家持有代币,撤单只是从索引里划掉记录,链上执行条件仍然可能满足。代币转出去,条件被打破;转回来,条件又闭合,旧签名重新变成一笔可执行凭证。用户没有工具去作废自己签过的每一张旧纸条。ERC-5008 的思路是造一个天然的计数器:给 ERC-721 加一个 nonce(uint256 tokenId) 查询函数,代币每被转移一次,这个数就变化一次,变化时广播 NonceChanged(tokenId, nonce) 事件。订单结构里把这个数登记进去,执行时对比当前值——对不上,当场拒绝。转让一圈不再是模糊的“看起来还能用”,而是硬邦邦的“nonce 已经变了,单已作废”。标准文本还有个细节耐人寻味:函数最初叫 transferCount,后来改为 nonce,因为除了转账,属性被重大修改之类的场景也可能需要让旧订单作废。

转走再转回,低价挂单活了过来:ERC-5008 给每枚 NFT 记一笔 nonce 图 2
转走再转回,低价挂单活了过来:ERC-5008 给每枚 NFT 记一笔 nonce · 图 2

接口很小,边界要画清

这份扩展的全部技术要求就三条:一个视图函数 nonce(tokenId),一个 NonceChanged 事件,以及用 ERC-165 标识 0xce03fdab 声明支持。链上留痕靠事件可回放,谁、哪枚、第几手,一目了然。但读者务必把它和“行业标配”区分开:5008 的提案状态是 Last Call,即走过最终意见征询阶段,距离 Final 仍有距离,采用面相当有限;Seaport 这类主流订单系统用的是另一套失效手段(比如按签名单独撤、或者递增账户级 counter),账户级数字管不住“同一账户的哪一枚”,代币级 nonce 才补得上这个粒度。换句话说,机制是否保护到你,取决于你挂单的市场和合约有没有真的实现它——验证办法很朴素:对目标代币合约调用 nonce 查询,报 unsupported 就是没有。

挂单前的三个动作

机制看懂之后,动作清单其实很短:挂长期订单前,确认市场撤单机制是链上生效还是仅索引生效,帮助文档会写;如果市场支持按枚失效,优先选择登记了 nonce 一类的订单形式;对自己签过的旧签名保持戒心,高价值藏品宁可“转回后重新挂新单”,也不要赌旧单自动死亡。最后照例划清边界:本文讲的是订单失效机制的技术原理,不预测任何价格走势,也不构成交易或投资建议。

与主流市场现有失效手段放在一起看

成熟订单系统并非没有防重放手段,只是颗粒度不同。以广泛使用的 Seaport 协议为例,它提供两条撤单路:按订单哈希精确作废某一单,或者递增账户级 counter 一次性作废该地址签名的全部旧单。前者的前提是订单在链上有登记,纯链下签名订单并不适用;后者杀伤范围大,撤一单等于废所有单,挂多单并行时并不好用。代币级 nonce 补的正是这个中间地带:让“作废”精确到某一件藏品,而且不需要市场记账系统参与——任何人拿一枚代币都能查它当前的 nonce,判断某笔历史交易之后它搬过几次家。两条路线并不互斥,检查成本也低:挂单前在浏览器对目标合约直接调用 nonce 函数,能返回数字的说明市场若采用这条防线有数据可用,报错误的市场则完全依赖自己的撤单系统,撤单按钮背后到底发生了什么,值得去帮助文档里确认一次。