Seaport 的分数履约:部分成交按分子分母算,坏订单静默跳过 图 1
Seaport 的分数履约:部分成交按分子分母算,坏订单静默跳过 · 图 1

一枚 ERC-1155 挂了 10 份,买家只想收 3 份,这一档“部分成交”在基于 Seaport 的市场里不是客服问题,而是协议字段问题。Seaport 官方文档在订单履约一节给出了两条与部分数量直接相关的机制:一组用分数表达成交比例的参数(numerator 与 denominator),以及一套“跳过不可成交订单继续撮合”的批量履约规则。读懂这两条,就能解释为什么同是“部分成交”,有的订单剩下 7 份还挂着、有的整单作废。

先说分数。文档描述 matchAdvancedOrders 与 fulfillAvailableAdvancedOrders 这类“advanced”函数时写明:调用方可以随履约参数提交 numerator(分子)与 denominator(分母)的分数值来表达部分成交,并支持一个可选的 extraData,在履约受限订单类型时作为参数传给 validateOrder、在合约订单类型时作为上下文传给 generateOrder 与 ratifyOrder。也就是说,成交比例不是市场页面自己算好数字再转账,而是由履约调用当场声明,Seaport 按这个分数切分 offer 与 consideration 的数量。同时文档在 fulfill基本订单的基础路线一节列了硬性前提:走 basic order 路线时每个物品的 startAmount 必须等于 endAmount,不允许金额随时间递增递减——时间衰减价与部分成交的细粒度玩法都被挡在这条省 gas 的捷径之外。

再说跳过规则。fulfillAvailableOrders 类函数的语义是:给一组订单加一组履约,其中已取消、时间无效(超出 start/end 窗口)、或已被完全成交的订单会被跳过而不导致整批失败,并且一旦凑满 maximumFulfilled 个可成交订单,剩余订单同样跳过。这个设计对买家极其实用——批量扫货时不会因为其中一单刚被撤掉而让整笔交易 revert 白烧 gas——但它的副作用是“成交了什么要看回执”:跳过的单在交易里不留失败记录,只有成功的那些产生转移事件。核对批量采购结果,正确姿势是读交易事件日志里实际发生的 transfer,而不是把自己提交的订单列表当成成交清单。

两条机制拼起来,部分成交的“剩余部分去哪了”就有确定答案:订单的已成交数量记录在协议状态里,未成交的余量仍按原订单参数可继续被履约,直到过期、被 cancel、或被 incrementCounter 整批作废。市场界面显示的“剩余份数”,本质是对这份链上状态的读取渲染。

实操层面的防御因此很具体:接部分成交的单,先确认订单类型能否走部分履约(advanced 路径与基础路径的差别如上);用批量履约时,预期“部分成功是正常态”,以事件日志为账;时间窗口 start/end 与取消状态都会在跳过规则里起作用,挂单时把过期时间设清楚,比事后盯客服快得多。最后照例划边界:这些是 Seaport 合约层的规则,各市场在产品层可能限死“只允许整份交易”,界面不提供不等于协议不支持;协议支持也不等于你遇到的这个前端把分数参数用对了——机制与实现,两层分开核验。

把分数履约放回时间维度,还能看清 startAmount/endAmount 与其的分工。文档在 consideration 一侧写明:若 endAmount 与 startAmount 不同,实际成交金额按订单生效以来的时间线性折算——这是“价格随时间爬升/回落”的时间衰减机制,走的是同一张订单内数量随时间变化的路线;而 numerator/denominator 是单次履约请求当场声明切分比例的路线。两者容易在界面里被混叫成“部分成交”,链上语义却完全不同:时间衰减到一半时吃单,订单余量与成交记录由协议按状态推进;分数履约则是调用方主动表达“我先吃这一份”,advanced 路径的每个物品还必须能被该分数整除,除不尽直接失败。对 ERC-1155 卖家,整除约束意味着挂 7 份时可吃的比例被限定在 7 的因子结构里,市场 UI 若提供“滑杆选份数”,背后其实是在挑一个合法分数。理解这两条路线的分岔,再看到“半成交”截图时,你就能分辨它是时间推进的自然结果,还是某次调用主动切下的一块,以及剩余部分还挂在不在原订单上。

本文为机制说明,不构成任何投资建议,也不构成对任何平台、合约或标准实现的背书。文中功能与规则描述以对应版本的官方文档为准,阅读时可能存在版本滞后。

Seaport 的分数履约:部分成交按分子分母算,坏订单静默跳过 图 2
Seaport 的分数履约:部分成交按分子分母算,坏订单静默跳过 · 图 2