- 示例工程
- 区块链
【免费下载链接】Dapp-Learning
Dapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.
Wyvern 是一阶(first-order)去中心化交换协议,曾是 OpenSea 等 NFT 市场撮合交易的核心底层。本文以仓库内 WyvernProtocolV3-cn.md 的翻译文档为主体,结合配套的英文原版与仓库内的实战示例代码,系统讲解 Wyvern 的协议组件、Order 订单结构、订单构造/授权/匹配全流程,以及它相对 0x、EtherDelta 等零阶协议的设计差异。读完本文,你将掌握一阶交换协议的订单谓词模型、广义部分填充、链下签名授权等关键机制,并能理解 OpenSea 早期交易撮合背后的技术原理。
说明:本文主体翻译自 Wyvern Protocol V3 官方文档,原文位于仓库 nft/opensea/WyvernProtocolV3.md,中文版位于 nft/opensea/WyvernProtocolV3-cn.md。中文版遵循"翻译中不明确的词汇会将原词汇跟随在翻译词汇后"的双语标注约定,本文沿用该风格。
一、协议组件:从零阶到一阶的范式跃迁
Wyvern 是一阶(first-order)去中心化交换协议。理解这个概念,需要先看它要对比的"零阶(zeroeth-order)"协议。
概述:订单表达能力的本质差异
可比的现有零阶协议包括 EtherDelta、0x、Dexy 等。零阶协议的共同特点是:每个订单指定了两个离散资产的所需交易——通常是两个代币,附带特定的兑换比率和最大数量。也就是说,零阶订单在"资产对"层面定义交易,表达力受限于"币种 A 换币种 B"这种固定形态。
Wyvern 的订单则完全不同:订单被定义为"状态转换的谓词(predicate)"。具体而言,一个订单是一个函数,它接收三个输入并映射到布尔值(订单是否匹配):
| 输入 | 含义 |
|---|---|
| 订单构建者(maker)发出的调用 | 订单创建者愿意执行的操作 |
| 交易另一方发出的调用 | 交易对手方(counterparty)执行的操作 |
| 订单元数据 | Ether 数量、时间戳、匹配地址等信息 |
这个函数返回true/false来决定订单是否允许匹配,以及匹配时填充(fill)多少。
由于这些谓词是任意的——以太坊上可表示的任何资产或资产的任意组合都可以用 Wyvern 订单进行交换——因此文档明确指出:Wyvern 可以实例化(instantiate)上述所有零阶协议。换句话说,Wyvern 的表达能力是零阶协议的超集,0x、EtherDelta 能表达的订单,Wyvern 都能表达,且能表达它们无法表达的更复杂交易。
协议优势与劣势
文档对 Wyvern 的取舍给出了清晰的评估:
优势(Advantages)
- 非常灵活:可以表达任何简单订单协议能表达的订单,以及很多它们不能表达的订单;
- 接近最优的 gas 效率:大多数 gas 消耗发生在实际调用和 calldata 谓词中,核心撮合逻辑本身开销极小;
- 安全友好(Security-conducive):协议组件彼此隔离,核心协议保持最小化,攻击面被压缩在可审查的边界内。
劣势(Disadvantages)
- 对开发者不太友好,且容易被滥用:谓词逻辑的自由度过高,构造和校验订单的出错概率大于固定模板式的零阶协议;
- 用户级工具支持不佳:例如 MetaMask 等钱包在展示签名消息时体验较差,因为订单签名是任意数据而非标准化的交易意图。
这些特性决定了 Wyvern 的定位:它是面向"专业订单簿/中继者(relayer)"的底层协议,而非面向终端用户的开箱即用产品——这也解释了为什么它的主要落地场景是 OpenSea 这类由专业团队维护的市场。
二、订单架构(Order Schema)
Wyvern 订单的数据结构如下(所有字段都会被签名):
struct Order { address registry; address maker; address staticTarget; bytes4 staticSelector; bytes staticExtradata; uint256 maximumFill; uint256 listingTime; uint256 expirationTime; uint256 salt; }各字段的含义与用途,文档中给出了完整的对照表:
| 字段 | 类型 | 用途 |
|---|---|---|
| registry | address | 用来调用的注册表(记录如何调用) |
| maker | address | 订单创建者,将执行调用的一方 |
| staticTarget | address | 谓词函数的目标地址 |
| staticSelector | bytes4 | 谓词函数的 Selector(函数签名的 hash) |
| staticExtradata | bytes | 谓词函数的额外数据 |
| maximumFill | uint256 | 订单无法再被匹配的最大填充量 |
| listingTime | uint256 | 上架时间,在此之前订单无法被匹配 |
| expirationTime | uint256 | 过期时间,在此之后订单无法被匹配 |
| salt | uint256 | 盐值,用于哈希去重 |
所有字段都会被签名覆盖(All fields are signed over)。这意味着任何字段的篡改都会导致签名校验失败,从而保证订单从构造、传播到最终上链撮合的全过程不可被中间人改动。salt字段的引入,使得同一 maker 可以生成大量内容相同但哈希不同的订单而不会互相冲突,也便于取消旧订单后重新签名新订单。
从结构上可以看出 Wyvern 的"一阶"设计落点:订单本身不记录要交换什么资产、什么价格,只记录"谓词函数在哪里(staticTarget+staticSelector)、谓词需要哪些额外数据(staticExtradata)、以及订单的生命周期与填充约束(maximumFill、listingTime、expirationTime)"。真正的资产转移逻辑全部委托给谓词函数,这是它表达力远超零阶协议的结构性原因。
三、构造一个订单
资产注册(Asserting registry)
订单创建者会检查自己和交易伙伴都使用了有效的注册中心(registry)。注册中心负责记录"如何调用",是订单执行时的调用上下文来源。需要注意的是,尽管注册中心需要在交易所合约(Exchange contract)的白名单中,文档也提示:订单创建者最好自行校验注册中心的有效性,而不是完全依赖交易所的白名单——这是协议安全性设计的一部分(注册中心被隔离为独立组件,核心协议保持最小化)。
资产 calldata(Asserting calldata)
一个订单的大部分逻辑集中在构造"调用 + 反调用"的谓词上。每个订单的静态回调(static callback,即谓词函数)会接收到以下全部参数:
- 调用(call)的所有参数;
- 交易对手方的调用(countercall)参数;
- 订单元数据(Ether 数量、时间戳、匹配地址)。
谓词函数必须据此决定:是否允许该订单被匹配,以及如果允许,填充多少。
Call:订单创建者的调用
第一次调用由订单创建者通过其代理合约(proxy contract)执行。静态回调接收这次调用的全部参数——调用目标(target)、调用类型(CALL或DELEGATECALL)、调用数据(calldata)——并必须验证该调用确实是订单创建者愿意执行的调用,例如"转出某个特定资产或一系列资产"。
这里有一个值得注意的架构细节:maker 并不是直接用其 EOA 地址签名转账,而是通过代理合约执行调用。代理合约的存在使得"签名授权"与"实际执行"解耦——maker 在链下签名订单,撮合时由代理合约代表 maker 执行经谓词验证过的调用,这正是 Wyvern 支持链下订单簿的基础。
Countercall:交易对手方的调用
第二次调用由交易对手方执行,源码中为了表述方便称之为反调用(countercall)。静态回调同样接收反调用的全部参数——反调用目标、反调用类型(CALL或DELEGATECALL)、反调用数据——并必须验证该调用是订单创建者愿意作为回报而接受的调用,例如"转入一个特定资产或一系列资产"。
把 Call 与 Countercall 放在一起看,Wyvern 订单的匹配本质就是:谓词同时验证"我给出的"和"我收到的"两个动作,两个动作都通过后,一笔原子化交易(atomicMatch)才能成立。任何一方动作不符合预期,整个匹配都会被拒绝。
断言状态(Asserting state)
谓词除了校验 calldata 本身,还可以采用状态断言的方式:静态调用在调用之后执行(若静态调用失败,整个交易会被 revert 回滚),因此你可以不校验 calldata 的属性,而是断言特定的状态已经发生变更——例如"某个账户现在确实拥有了某个资产"。
文档同时给出了重要的实践警告:这种方式的推理更复杂,且可能导致意外结果——如果状态因其他原因发生了变更(例如你正打算购买的资产恰好被别人赠送到了你的地址),断言也可能错误地通过。因此,状态断言仅在特殊场景下推荐使用,例如在合约上放置"不变量被违背即触发"的 bug 赏金(bug bounty)。
订单元数据(Metadata)
匹配时,谓词函数还能读到完整的订单元数据,包括:
- 订单的上架时间(listing time);
- 订单的过期时间(expiration time);
- 对手订单的上架时间(counterorder listing time);
- 调用中传递的 Ether 数量(如有);
- 当前订单的填充值(current order fill value);
- 匹配地址(matching address)。
元数据把"时间窗口、价格(Ether 量)、撮合双方身份、已填充进度"等撮合所需的上下文全部交给谓词函数裁决,使得谓词可以对"这笔匹配发生的时间是否合法、价格是否达标、是否由指定地址撮合"做精细化校验。
广义部分填充(Generalized Partial Fill)
Wyvern 的订单天然支持部分填充(partial fill),机制如下:
- 订单在签名时指定一个最大填充量(
maximumFill); - 静态调用在订单匹配时返回一个无符号整数(
uint),用于指定更新后的填充值——即本次匹配后订单的新填充进度; - 只要累计填充未超过
maximumFill,订单就可以被多次部分匹配。
此外,文档强调了一个关键能力:订单的当前填充值可以由订单创建者通过一笔交易手动设置。这带来两个实际用途:
- 取消订单:maker 可以将填充值直接设置为
maximumFill,使订单立即变为"不可再匹配",等效于取消; - 隐式授权:将订单填充设置为非空值(nonzero)会隐式授权该订单——因为部分填充订单的授权会被缓存,以避免不必要的重复签名校验。也就是说,一旦订单处于部分填充状态,后续匹配不再要求重新验签,从而节省 gas。
这一设计将"授权"与"填充进度"绑定在一起,是 Wyvern 在 gas 效率上的又一个精巧取舍。
四、订单授权(Authorizing an order)
订单必须始终由maker地址授权——maker 拥有将执行调用的代理合约。文档给出了三种授权方式:签名消息授权、预授权、匹配时授权。
签名消息授权(Signed message)
这是最常见的授权方式:在链下对订单哈希(order hash)签名。它的优势是零成本(costless)——任意数量的订单都可以被签名、存储、索引,并发布到网站或自动化订单簿上。
由于链下签名没有链上状态,文档特别给出了一个实用技巧:为了避免取消"不再需要"的订单的麻烦,maker 可以给订单设置较近的过期时间,并且仅在希望继续求购/求售期间重新签名新订单。过期时间到了订单自然失效,无需额外取消交易。
预授权(Pre-approval)
另一种方式是通过向WyvernExchange合约发送一笔交易来完成授权。这种方式对由智能合约构造的订单尤其有意义——合约本身无法在链下签名消息。链上授权会发出事件(event),订单簿可以轻松索引该事件,将订单收录进自己的数据库。
匹配时授权(Match-time approval)
最后一种方式是临时构造订单(通常是去匹配一个已经签名或已授权的现存订单),并在匹配时直接授权——做法很简单:从订单的maker地址发出匹配交易即可。如果 maker 打算自己发送匹配这笔订单的交易,这种方式就很方便,而且能省一点 gas——因为发出交易本身就隐含了对 calldata 的验证,无需额外的签名检查。
三种方式覆盖了不同的使用场景:面向个人的链下签名(成本最低、可规模化)、面向合约的链上预授权(可被索引)、面向自撮合的匹配时授权(最省事)。
五、匹配订单(Matching orders)
构造匹配 calldata
匹配用的 calldata 可以在链下以任意方式构造。协议并不关心最终 calldata 是如何得到的,只关心它是否满足订单的谓词函数。在实际运行中,订单簿维护者(中继者 relayer)通常会随订单一起存储额外的元数据,用于构造可能的匹配 calldata——也就是说,"如何找到匹配对手单并组装 calldata"是订单簿层面的竞争点,而不是协议层面的固定流程。
协议的不对称性(Asymmetries)
Wyvern 在设计上尽量保持对称性:订单不需要固定处于某个"方向"(买/卖),也不需要限制自己只能匹配另一侧的订单。但协议存在两处不可避免的不对称:
调用顺序(Call ordering)
第一处不对称是顺序:总有一个调用要先执行,而先执行的调用可能改变第二个调用的结果。规则很简单:传入atomicMatch的第一个调用先执行。因此,构造匹配 calldata 时必须考虑调用顺序对结果的影响。
Ether 特例(Special-cased Ether)
第二处不对称来自Ether 的特殊性:由于以太坊设计限制,Ether 是一种"内建(wired-in)"资产(不同于 ERC20 代币),只能由账户通过该账户发起的交易转出。为了易用性,Wyvern 对 Ether 做了最大程度的特例支持:订单的匹配者(matcher)可以选择在匹配交易中附带 value,这些 Ether 会被转给交易对手方,同时作为参数传入谓词函数——谓词可以据此断言"例如发送了特定数量的 Ether"。这实质上为"用 ETH 购买 NFT"这类常见场景提供了原生支持。
杂项:禁止自匹配
还有一条重要的安全规则:订单不能自匹配(self-match);不过,同一个 maker 发出的两个不同订单之间可以互相匹配。禁止自匹配避免了同一订单自我撮合产生虚假成交(wash trading)的路径,而允许同 maker 的订单互相匹配则为"自我对敲式的合理操作"保留了空间。
六、仓库实践印证:OpenSea 与 Wyvern 的落地
Wyvern 协议最广为人知的应用是 OpenSea。在 Dapp-Learning 仓库中,可以找到多处与 Wyvern 直接相关的实践印证:
OpenSea 目录的文档体系
仓库在 nft/opensea/readme.md 中维护了 OpenSea 专题目录,其中明确列出了 Wyvern Protocol V3 的 GitHub 仓库、官网文档以及本站翻译(即本文主体 WyvernProtocolV3-cn.md)。该索引还记录了工作进度:协议翻译、协议流程图、OpenSea 站的工作流程,并附有"NFT 及 OpenSea 交易背后的技术分享"参考链接——说明仓库将 Wyvern 协议视作理解 OpenSea 交易底层的关键一环。
链上合约地址的代码佐证
仓库的实战示例 nft/Practical-Applications/examples/nft-analytics.js(NFT 数据分析示例)在初始化分析器时,将marketplaceAddress配置为主网上的OpenSea Wyvern Exchange v1 合约地址:
// 初始化分析器(以Bored Ape Yacht Club为例) const analytics = new NFTAnalytics({ nftContractAddress: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", // BAYC合约地址 marketplaceAddress: "0x7Be8076f4EA4A4AD08075C2508e481d6C946D12b", // OpenSea Wyvern Exchange v1 rpcUrl: "https://mainnet.infura.io/v3/YOUR_INFURA_KEY", startBlock: 12287507, // BAYC部署区块 collectionName: "Bored Ape Yacht Club" });该示例通过监听市场合约的销售事件(ItemSold)来拉取 NFT 的交易历史、统计交易量与价格趋势(见 nft-analytics.js 的事件过滤与日志处理逻辑)。这从链上数据层面印证了:Wyvern Exchange 合约确实承担了 OpenSea 早期 NFT 撮合与成交事件上链的职责,其部署地址被社区工具广泛引用为 NFT 市场数据的标准数据源。
与 NFT 标准的关系
从资产类型上看,Wyvern 订单通过谓词可以表达任意资产,NFT 只是其中一种应用。仓库的 nft/readme.md 系统整理了 NFT 的标准基础(EIP-721、EIP-1155 及其 OpenZeppelin 实现)和安全事件分析,可以与本文结合阅读:理解 NFT 资产的"唯一性、不可分割性、可批量管理"特性,有助于理解 Wyvern 谓词如何通过"断言特定 Token ID 的转移"来实现 NFT 订单。同时,仓库 nft/Practical-Applications 下的 NFT 跨链、身份验证、流动性解决方案等实战文章,进一步展示了在 NFT 资产之上构建应用的各种延伸方向。
七、要点回顾:Wyvern 核心概念速查
- 一阶协议:订单 = 谓词(maker 调用、counterparty 调用、元数据 → 布尔值),可表达任意以太坊资产组合,可实例化 0x、EtherDelta 等零阶协议。
- Order 结构:
registry、maker、staticTarget、staticSelector、staticExtradata、maximumFill、listingTime、expirationTime、salt,全部字段签名覆盖。 - 谓词三要素:Call(maker 经代理合约执行)、Countercall(对手方执行)、Metadata(时间、Ether、填充值、匹配地址)。
- 两种断言方式:断言 calldata 属性(常规)、断言状态变更(特殊场景,如 bug 赏金)。
- 广义部分填充:签名最大填充量,静态调用返回更新后的填充值;maker 可手动设置填充值来取消订单,非空填充值隐式授权并缓存授权以省 gas。
- 三种授权方式:链下签名消息(零成本、可规模化)、链上预授权(合约友好、可索引事件)、匹配时授权(maker 自撮合、省 gas)。
- 匹配规则:匹配 calldata 由订单簿链下任意构造;第一个传入
atomicMatch的调用先执行;Ether 通过匹配交易的 value 特例支持;订单禁止自匹配,但同 maker 的不同订单可互配。 - 落地印证:OpenSea Wyvern Exchange v1 合约地址
0x7Be8076f4EA4A4AD08075C2508e481d6C946D12b被仓库 NFT 分析示例作为标准市场数据源引用。
Wyvern Protocol V3 以"任意谓词 + 最小化核心协议"的设计,把交易表达的自由度推到了以太坊上可行的极致,同时以代理合约、链下签名、部分填充缓存等机制将 gas 开销与安全风险控制在合理范围。理解它的订单模型,是深入理解 OpenSea 撮合逻辑和现代 NFT 市场架构的重要一步;想继续深入,可对照阅读仓库中的英文原版文档与OpenSea 专题目录,并结合 NFT 数据分析示例 做链上数据验证。
- 示例工程
- 区块链
【免费下载链接】Dapp-Learning
Dapp learning project for developers at all stages. Becoming and cultivating sovereign individuals. Nonprofit organization.
相关推荐
Dapp-Learning 深度解读:Aave V2 借贷协议合约架构、核心交互与清算机制
Dapp Learning 深度解读:Aave V2 借贷协议合约架构、核心交互与清算机制 本文基于 Dapp Learning 开源仓库的 Aave 专题文档
示例工程区块链订单簿去中心化交易所深度解析:EtherDelta 合约源码与 Solidity 0.8 升级实战(Dapp-Learning)
订单簿去中心化交易所深度解析:EtherDelta 合约源码与 Solidity 0.8 升级实战(Dapp Learning) 导读 本文以 Dapp Lea
示例工程区块链0x 协议深度解读:以 0x API 构建去中心化交易与聚合流动性(附 Dapp-Learning 仓库实战)
0x 协议深度解读:以 0x API 构建去中心化交易与聚合流动性(附 Dapp Learning 仓库实战) 0x 协议是一个面向以太坊生态的开源去中心化交易
示例工程区块链
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考