NFT 市场(如 OpenSea / Blur / LooksRare)的核心是一个链上/链下混合的订单撮合系统:卖方上架(NFT + 期望价格),买方按价成交,平台在成交时自动拆分版税(Royalty)给创作者。
18.6.1 市场架构:两种撮合模型
graph TD
Seller[卖方] --> List[上架 Listing]
Buyers[买方] --> Order[报价 Offer]
List --> Engine{撮合引擎}
Order --> Engine
Engine -->|链下+签名| OB[链下订单簿<br/>订单以 EIP-712 签名,<br/>成交时上链执行]
Engine -->|完全链上| OnChain[链上订单<br/>订单存于合约 storage]
OB --> Match[成交执行]
OnChain --> Match
Match --> Royalty[版税拆分]
Match --> Escrow[资金结算]
| 模型 | 订单存储 | Gas | 灵活性 | 代表 |
|---|---|---|---|---|
| 链下签名订单(Off-chain order) | 链下数据库,链上仅校验签名 | 仅成交付 Gas | 高(取消免费、可批量) | OpenSea / Blur / LooksRare |
| 链上订单(On-chain listing) | 合约 storage | 上架/成交都付 Gas | 低(但更去信任) | 早期 CryptoPunks / 部分 1/1 平台 |
链下订单的信任假设:平台数据库不可被篡改地保存签名,但攻击者理论上可以重放签名——因此链上必须校验 nonce(订单序号)与截止时间,防止过期或已取消订单被执行。
18.6.2 链下签名订单:EIP-712 与成交校验
一张"链下订单"本质是一段被签名者签名的结构化数据。成交时,合约校验:
- 签名者确实是 NFT 当前 owner;
- 订单未过期(
deadline > now)且 nonce 未使用; - 价格不低于
price且买方已支付; - 版税按
creatorFeeBps拆分给创作者(如有)。
typescript
// 链下订单撮合:签名校验 + 版税拆分的数学(纯 TS + BigInt,无外部库)
type Address = string;
interface Listing {
seller: Address; // 卖家
nft: Address; // NFT 合约地址
tokenId: bigint; // 具体 NFT 编号
price: bigint; // 成交价(wei)
royaltyBps: bigint; // 版税(1/10000)
deadline: bigint; // 截止时间戳
nonce: bigint; // 防重放序号
}
class Marketplace {
usedNonces: Set<string>; // 已消费的 (seller:nft:tokenId:nonce)
creatorRoyalty: Map<string, bigint>; // nft合约 => 版税bps(项目方预设)
constructor() {
this.usedNonces = new Set();
this.creatorRoyalty = new Map();
}
// 成交结算:返回 [买方支付, 卖家所得, 创作者版税]
fillOrder(
order: Listing,
fundsDeposited: bigint,
_signatureValid: boolean
): [bigint, bigint, bigint] {
if (!_signatureValid) throw new Error("签名无效");
if (fundsDeposited < order.price) throw new Error("支付不足");
const nonceKey = `{order.nft}:{order.nonce}`;
if (this.usedNonces.has(nonceKey)) throw new Error("订单已执行/取消");
this.usedNonces.add(nonceKey);
const royalty = (order.price * order.royaltyBps) / 10000n; // 版税
const sellerNet = order.price - royalty; // 卖家净得
return [order.price, sellerNet, royalty]; // [买方支付, 卖家, 创作者]
}
}
// 验证:售价 1 ETH,版税 5% => 版税 0.05 ETH,卖家净得 0.95 ETH
const WEI = 10n ** 18n;
const mk = new Marketplace();
const order: Listing = {
seller: "0xSeller",
nft: "0xNft",
tokenId: 1n,
price: 1n * WEI,
royaltyBps: 500n, // 5%
deadline: 2000n,
nonce: 0n,
};
const [pay, seller, creator] = mk.fillOrder(order, 1n * WEI, true);
console.log(
"版税 =", Number(creator) / Number(WEI), "ETH,",
"卖家净得 =", Number(seller) / Number(WEI), "ETH"
);
// 输出:版税 = 0.05 ETH 卖家净得 = 0.95 ETH18.6.3 版税的实现与争议
版税(Royalty)在成交时强制拆分给创作者。历史上由市场自觉执行,2023 年后各平台政策分化(OpenSea 默认强制、Blur 部分可选、部分平台设上限)。
| 方案 | 谁执行 | 优点 | 缺点 |
|---|---|---|---|
| 市场级强制 | 市场合约 | 简单、创作者收益稳定 | 平台可关闭;不同市场规则不一 |
| 合约级 ERC-2981 | NFT 合约 royaltyInfo() | 链上原语,不可绕过 | 需 NFT 合约支持 |
| 可编程版税(如 on-chain royalties / 黑名单市场) | 转移时校验 | 最强保障 | 影响互操作性、抗 FUD |
ERC-2981 接口:
solidity
interface IERC2981 {
/// @notice 返回给定 tokenId/售价下的版税接收者与金额
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external view returns (address receiver, uint256 royaltyAmount);
}18.6.4 攻击面与安全清单
| 风险 | 描述 | 防御 |
|---|---|---|
| 签名重放 | 同一订单在另一市场重复执行 | nonce + 市场地址进入签名域(EIP-712 domain) |
| 订单过期 | 卖家忘了下架,价格已失真 | deadline 校验 |
| 版税绕过 | 跨平台/私下交易绕过版税 | ERC-2981 + 市场强制 |
| 虚假 NFT 合约 | 高仿同名合约钓鱼 | 合约地址白名单校验 |
| 前端钓鱼 | 恶意签名(授权/上架)请求 | 明确提示每次签名含义 |
| 礼包/批量成交偏差 | 多 token 打包成交价格偏差 | 单笔逐项校验 |
18.6.5 小结
3 个关键认知:① NFT 市场 = 签名订单 + 链上成交校验 + 版税拆分三件事;② 链下订单省 Gas 但信任平台,链上订单去信任但贵;③ 版税是创作经济的核心,需从"市场自觉"走向"合约原语"。
, 前往 → ch18 总结 |*
评论
0评论加载中…