NFT 与通证经济:从 ERC-721 合约、铸造页到通缩代币与 NFT 市场——打通数字资产确权、发行、流通与经济激励的完整链路。
本章目录:
- 18.1 NFT 设计与元数据
- 18.2 ERC-721 基础智能合约
- 18.3 铸造页设计
- 18.4 通缩代币:燃烧、税费与反射机制
- 18.5 通证经济设计
- 18.6 NFT 市场:上架、成交与版税
18.1 NFT 设计与元数据
NFT(Non-Fungible Token,非同质化代币)= 链上所有权 + 链下元数据。
18.1.1 什么是 NFT?
graph TD
Token["ERC-721 代币"] --> ON["链上: tokenId → 所有者地址"]
Token --> OFF["链下: 元数据 JSON → 图片/媒体/属性"]
Token --> Contract["合约: 名称/符号/总供应量/铸造规则"]
style ON fill:#e3f2fd
style OFF fill:#e8f5e9
style Contract fill:#fff3e0
| tokenId | 唯一标识符(0, 1, 2... 或指定) |
| 元数据 URL | 指向 JSON 文件(通常 IPFS/Arweave) |
18.1.2 元数据标准(ERC-721 / ERC-1155)
// 标准 OpenSea/市场兼容的元数据
{
"name": "Bored Ape #1",
"description": "随机生成的猿猴收藏品",
"image": "ipfs://Qm.../1.png",
"attributes": [
{"trait_type": "Background", "value": "Deep Blue"},
{"trait_type": "Fur", "value": "Golden"},
{"trait_type": "Eyes", "value": "Laser", "rarity": 0.5}
],
"external_url": "https://example.com/ape/1"
}
18.1.3 设计决策
| 链 | 以太坊主网 / Polygon / 以太坊L2 | 成本 vs. 安全性 |
| 元数据存储 | IPFS / 中心化服务器 / Arweave | 永久保存 vs. 成本 |
| 铸造方式 | 用户支付(用户出 Gas)/ 免费铸造(项目方出) | 用户体验 vs. 防机器人 |
| 版税 | 固定 5-10% / 可选 / 市场决定 | 收益 vs. 合规 |
| 白名单 | Merkle 树 / 签名白名单 / 无 | 社区策略 |
, 前往 → 18.2 基础智能合约 |*
18.2 ERC-721 基础智能合约
graph TD
部署者[部署者] --> 合约[ERC-721 合约]
合约 --> 铸造[safeMint→所有者]
合约 --> 转移[transferFrom→]
铸造 --> 所有者[所有者]
转移 --> 新所有者[新所有者]
合约 --> 元数据[TokenURI 链下镜像]
所有者 --> 授权[approve→被授权者]
授权 --> 新所有者
style 合约 fill:#c8e6c9
style 元数据 fill:#e3f2fd
style 新所有者 fill:#c8e6c9
NFT 智能合约的核心:记录谁拥有哪个 tokenId,以及如何把 tokenId 转移给新地址。
18.2.1 最小 ERC-721 实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, ERC721Enumerable, Ownable {
using Strings for uint256;
// === 状态变量 ===
string private _baseTokenURI; // 元数据基础 URL: https://ipfs.io/ipfs/Qm.../
uint256 public maxSupply = 10000; // 最大供应量
uint256 public totalMinted = 0; // 已铸造量
uint256 public mintPrice = 0.01 ether; // 铸造价格
bool public mintActive = false; // 铸造开关
// === 事件 ===
event Minted(address indexed to, uint256 indexed tokenId, uint256 price);
event BaseURIChanged(string newURI);
constructor(
string memory name,
string memory symbol,
string memory baseURI
) ERC721(name, symbol) Ownable(msg.sender) {
_baseTokenURI = baseURI;
}
// === 铸造功能 ===
function mint(uint256 quantity) external payable {
require(mintActive, "Minting is not active");
require(quantity > 0 && quantity <= 10, "Max 10 per tx");
require(totalMinted + quantity <= maxSupply, "Exceeds max supply");
require(msg.value >= mintPrice * quantity, "Insufficient payment");
for (uint256 i = 0; i < quantity; i++) {
uint256 tokenId = totalMinted;
_safeMint(msg.sender, tokenId);
totalMinted++;
}
emit Minted(msg.sender, tokenId, msg.value);
// 退款多余 ETH
if (msg.value > mintPrice * quantity) {
payable(msg.sender).transfer(msg.value - mintPrice * quantity);
}
}
// === 查询 ===
function tokenURI(uint256 tokenId) public view override returns (string memory) {
_requireOwned(tokenId);
return string(abi.encodePacked(_baseTokenURI, tokenId.toString(), ".json"));
}
function walletOfOwner(address owner) public view returns (uint256[] memory) {
uint256 balance = balanceOf(owner);
uint256[] memory tokenIds = new uint256[](balance);
for (uint256 i = 0; i < balance; i++) {
tokenIds[i] = tokenOfOwnerByIndex(owner, i);
}
return tokenIds;
}
// === 仅所有者 ===
function setBaseURI(string memory newURI) external onlyOwner {
_baseTokenURI = newURI;
emit BaseURIChanged(newURI);
}
function toggleMint() external onlyOwner {
mintActive = !mintActive;
}
function withdraw() external onlyOwner {
(bool success, ) = payable(owner()).call{value: address(this).balance}("");
require(success, "Withdraw failed");
}
// === 必需覆盖 ===
function supportsInterface(bytes4 interfaceId)
public view override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
18.2.2 安全要点
| 重入 | 使用 _safeMint(内部检查) | OpenZeppelin 实现 |
| 超额支付 | 退款多余 ETH | msg.value - mintPrice * quantity |
| 无限铸造 | 上限检查 | totalMinted + quantity <= maxSupply |
| 权限控制 | 仅所有者 | onlyOwner modifier |
18.2.3 Gas 优化
// 未优化: 每次循环都检查 supply
for (uint i = 0; i < quantity; i++) {
require(totalMinted < maxSupply, "..."); // 多余
// ...
}
// 已优化: 外部统一检查后批量铸造
require(totalMinted + quantity <= maxSupply, "..."); // 一次检查
for (uint i = 0; i < quantity; i++) {
// ...
}
, 前往 → 18.3 铸造页 |*
18.3 铸造页设计
铸造页是 NFT 项目的"门面"——好的铸造体验能提升 200% 的转化率。
18.3.1 铸造页架构
graph TD
User["用户"] --> UI["铸造UI"]
UI --> Check["检查: 白名单? 余额? 供应量?"]
Check --> |通过| Tx["构造交易: mint(quantity)"]
Check --> |不通过| Error["显示错误/提示"]
Tx --> MM["MetaMask 签名"]
MM --> Pending["交易 pending"]
Pending --> Confirm["确认/失败"]
Confirm --> Success["显示铸造结果+链接"]
style Check fill:#fff3e0
style Success fill:#e8f5e9
18.3.2 前端代码
// components/Minter.tsx
import { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import MyNFT from '../abi/MyNFT.json';
const CONTRACT_ADDRESS = "0x...";
const MINT_PRICE = ethers.parseEther("0.01");
export const Minter = () => {
const [quantity, setQuantity] = useState(1);
const [supply, setSupply] = useState({ total: 0, max: 10000 });
const [price, setPrice] = useState(MINT_PRICE);
const [loading, setLoading] = useState(false);
const [txHash, setTxHash] = useState('');
const provider = new ethers.BrowserProvider(window.ethereum);
useEffect(() => {
const loadSupply = async () => {
const contract = new ethers.Contract(CONTRACT_ADDRESS, MyNFT, await provider.getSigner());
const [minted, max, mintPrice] = await Promise.all([
contract.totalMinted(),
contract.maxSupply(),
contract.mintPrice(),
]);
setSupply({ total: Number(minted), max: Number(max) });
setPrice(mintPrice);
};
loadSupply();
}, []);
const mint = async () => {
setLoading(true);
try {
const signer = await provider.getSigner();
const contract = new ethers.Contract(CONTRACT_ADDRESS, MyNFT, signer);
const tx = await contract.mint(quantity, {
value: price * BigInt(quantity),
});
setTxHash(tx.hash);
await tx.wait();
// 刷新供应量
const newTotal = await contract.totalMinted();
setSupply(s => ({ ...s, total: Number(newTotal) }));
} finally {
setLoading(false);
}
};
const remaining = supply.max - supply.total;
return (
<div className="minter">
<h2>Mint Your NFT</h2>
<div className="supply">{supply.total} / {supply.max} 已铸造</div>
<div className="quantity-selector">
<button onClick={() => setQuantity(Math.max(1, q-1))}>-</button>
<span>{quantity}</span>
<button onClick={() => setQuantity(Math.min(10, q+1))}>+</button>
</div>
<div className="price">总计: {ethers.formatEther(price * BigInt(quantity))} ETH</div>
<button
onClick={mint}
disabled={loading || remaining <= 0}
className="mint-btn"
>
{loading ? '铸造中...' : remaining <= 0 ? '已售完' : '立即铸造'}
</button>
{txHash && (
<a href={`https://etherscan.io/tx/${txHash}`} target="_blank">
查看交易 →
</a>
)}
</div>
);
};
18.3.3 UX 最佳实践
, 前往 → 18.4 元数据 |*
18.4 通缩代币:燃烧、税费与反射机制
通缩代币(Deflationary Token)通过燃烧(Burn)、交易税(Tax)与反射(Reflect)机制,让每一笔转账都产生"通缩"或"再分配"效果——这是 2021 年 Meme 币潮(Shib、Safemoon 等)的标志性设计。
18.4.1 什么是通缩机制?
graph TD
Tx["用户转账 X 代币"] --> Split{代币拆分}
Split -->|1-5%| Burn["燃烧: 永久销毁"]
Split -->|2-5%| Liquidity["自动添加流动性"]
Split -->|3-8%| Reflect["反射: 按比例分给全体持有者"]
Split -->|0-2%| Treasury["项目金库"]
Burn --> Supply["总供应量减少"]
Reflect --> Holders["持有者被动获得代币"]
| 燃烧 | 总供应量 Stotal 单调递减 | 稀缺性上升、长期通缩 |
| 反射 | 单次转账按 rfee 比例再分配 | 持有者被动增持 |
| 自动流动性 | 税费的一部分路由到 DEX 池 | 池深增加、滑点降低 |
核心公式:假设交易税率为 τ,发生一笔金额为 A 的转账,到账金额为:
Anet=A×(1−τ)
其中 τ=τburn+τreflect+τliquidity+τtreasury。
18.4.2 反射机制的数学原理
反射代币(如 RFI 标准)并不真实转移代币,而是维护一个全局放大系数 _rate。每个持有者的"映射余额"(reflection balance)除以 _rate 得到真实余额:
realBalance(u)=rreflectionOf(u)
初始 r=1。每当发生一笔带手续费的转账,收取的税费代币全部被销毁(从反射层面移除),同时 _rate 增加,使得每个持有者的真实余额按比例上升。
数量守恒更新:设转账前全局反射总量为 ∑ref,真实总量为 S。转账 A(含税 τA),税后真实总量变为 S−τA,则新放大系数:
r′=S−τA∑ref>r
// 反射代币核心:从零实现(纯 TS + BigInt,无外部库)
// 模型:每笔转账收取 3% 税费,税费按"除发送方外"各持有者转账前余额占比再分配
// => 发送方付全额,接收方得净额,税费让全体持有者被动增值(反射语义)
class ReflectToken {
totalReal: bigint; // 真实总供应量
balances: Map<string, bigint>; // 地址 -> 真实余额
static readonly TAX_BPS = 300n; // 3% 交易税(1/10000 为单位)
constructor(initialSupply: bigint, holders: Array<[string, bigint]>) {
this.totalReal = initialSupply;
this.balances = new Map(holders);
}
realBalance(addr: string): bigint {
return this.balances.get(addr) ?? 0n;
}
transfer(from: string, to: string, amount: bigint): void {
const fromBal = this.balances.get(from) ?? 0n;
if (fromBal < amount) throw new Error("余额不足");
const fee = (amount * ReflectToken.TAX_BPS) / 10000n; // 税费
const net = amount - fee; // 净到账
// 1) 反射分配:基于"除发送方外"其余持有者的转账前余额占比
// (顺序在 B 到账之前,保证分母不因本次净到账而改变)
const othersBefore: Array<[string, bigint]> = [];
let sumOtherBefore = 0n;
for (const [addr, bal] of this.balances) {
if (addr === from) continue;
othersBefore.push([addr, bal]);
sumOtherBefore += bal;
}
if (sumOtherBefore > 0n) {
for (const [addr, bal] of othersBefore) {
const share = (fee * bal) / sumOtherBefore; // 税费按占比反射
this.balances.set(addr, (this.balances.get(addr) ?? 0n) + share);
}
}
// 2) 发送方扣全额,接收方得净额
this.balances.set(from, fromBal - amount);
this.balances.set(to, (this.balances.get(to) ?? 0n) + net);
// 3) 税费进入持有者账户 => 总真实供应增长(等价于全体持有人被动增持)
this.totalReal += fee;
}
}
// 验证:创始 1000 token,A=500、B=500;A 给 B 转 100(税 3%)
const WEI = 10n ** 18n; // 1e18 定点
const t = new ReflectToken(1000n * WEI, [
["0xA", 500n * WEI],
["0xB", 500n * WEI],
]);
const b0 = t.realBalance("0xB");
t.transfer("0xA", "0xB", 100n * WEI);
const b1 = t.realBalance("0xB");
const got = Number(b1 - b0) / Number(WEI);
// 理论:净到账 97 + 反射份额 = fee(3) × B占比(500/500=1.0) = 3 => 增量 = 100
console.log("B 到账增量 ≈", got.toFixed(2), "token(预期 100.00)");
运行验证:node --experimental-strip-types 输出 B 到账增量 ≈ 100.00 token(预期 100.00),数学自洽。
18.4.3 燃烧与总供应量
硬性燃烧(Hard Burn):把代币转到 0x000...dead 地址,彻底从流通中移除:
function _burn(address from, uint256 amount) internal {
_balances[from] -= amount;
_totalSupply -= amount;
emit Transfer(from, address(0), amount);
}
反射燃烧(Reflection Burn):RFI 风格的燃烧并不从任何账户扣减,而是通过增加 rate 实现全体持有人真实余额按比例上升、总供应量的反射总量不变——看起来"每个人余额都变多"。
数学关系:每燃烧 ΔS,通缩率可表达为:
SΔS⇒S(t)=S0×e−δt
其中 δ 为平均年燃烧速率(需由实际税费流量估算)。
18.4.4 自动添加流动性(Auto-LP)
税费的一部分(如 3%)被积累在合约中,达到阈值后自动 swap 为 ETH/配对代币,再与等量代币一起注入 DEX 池。这形成了"每笔交易都在买底池"的机制。
sequenceDiagram
participant U as 转账发起
participant C as 通缩合约
participant P as DEX Pair
C->>C: 收取 tau_lp 比例代币到内部积累地址
C->>C: 积累量 >= swapThreshold?
C->>P: swap 积累代币 -> 配对代币(ETH/USDC)
C->>C: 用配对代币 + 等量代币 addLiquidity
P->>P: 池深度增加 (k 增大)
风险提示:自动 LP 机制曾是众多 Rug Pull 与"貔貅盘"(只能买不能卖)攻击的载体——若合约 owner 可随时修改税率或将 swapBack 阈值调至极高,用户卖出将永远失败。审计这类合约时首先要检查:税费是否上限锁定、owner 是否可任意改税率、是否有黑名单。
18.4.5 TS 从零实现:锤子税 / 手续费结算
下面的纯 TS 实现了"每笔转移收取固定比例手续费并累计到金库",展示税率的数学分解:
// 税率数学:输入金额 -> 各项去向(纯 BigInt,无外部库)
function splitFee(amount: bigint, basisPoints: readonly number[]): bigint[] {
// basisPoints: [burn, reflect, liquidity, treasury],单位万分比(1/10000)
const TOTAL = 10000n;
const totalBps = basisPoints.reduce((a, b) => a + b, 0);
if (totalBps > 10000) throw new Error("总税率超过100%");
const parts = basisPoints.map(bp => (amount * BigInt(bp)) / TOTAL);
// 用整数除法后余数归给接收方,保证到账+税费 == 原金额
const net = amount - parts.reduce((a, b) => a + b, 0n);
return [...parts, net]; // [burn, reflect, liq, treasury, netToReceiver]
}
// 验证:100e18 代币,税 8% (burn1% reflect3% liq3% treasury1%)
const amount = 100n * 10n ** 18n;
const [b, r, l, t, net] = splitFee(amount, [100, 300, 300, 100]);
console.log("burn", (b / 10n ** 16n) / 100, "reflect", (r / 10n ** 16n) / 100);
console.log("liq ", (l / 10n ** 16n) / 100, "treasury", (t / 10n ** 16n) / 100);
console.log("net ", (net / 10n ** 16n) / 100); // 应约 92
// 输出:burn 1 reflect 3 liq 3 treasury 1 net 92
18.4.6 小结与审计要点
| 税率是否可改 | owner 是否能绕过限额把税率调到 99% |
swapBack 门槛 | 若阈值过高,LP 税永不激活 |
3 个关键认知:① 通缩 = 燃烧使 S 递减 + 反射使持有者被动增持;② 税费各部分需用整数运算精确拆分,余数必须归账保证守恒;③ 自动流动性是"每笔交易买底池",但也是 Rug Pull 高危区,审计优先审查 owner 权限。
, 前往 → 18.5 通证经济设计 |*
18.5 通证经济设计
通证经济(Tokenomics)= 代币如何被创造、分配、激励与销毁。一份好的代币设计,要让"自利行为"聚合出"系统目标"——这是从第一性原理出发的经济工程。
18.5.1 代币的四大核心职能
graph LR
Token["代币"] --> Medium["交换媒介 Medium of Exchange"]
Token --> Store["价值储藏 Store of Value"]
Token --> Utility["功能使用权 Utility"]
Token --> Governance["治理权 Governance"]
Medium --> |流动速度快| TX["支付/费用"]
Store --> |总量上限/通缩| Scarcity["稀缺性"]
Utility --> |消耗/质押| Work["激励行为"]
Governance --> |投票权重| DAO["社区治理"]
| 交换媒介 | 是否用于支付手续费/内部计价 | Gas 币、协议内结算 |
核心权衡:四个职能往往彼此冲突——流通快的"媒介"不适合当"储值"标的("货币"与"资产"之争);治理权需要长期锁定,又削弱流动性。
18.5.2 供应量四要素:总量、释放、燃烧与流通
Total Supply=Minted−Burned
Circulating=Total Supply−Locked/Treasury/Vested
- Minted(增发):PoS 链的质押奖励、协议补贴、团队/基金会额度。
- Burned(燃烧):手续费销毁、回购销毁、通缩机制。
- Vesting(解锁):团队/投资人代币线性解锁,防止抛压。
- Inflation/Deflation 速率:
Net Inflation=Mint Rate−Burn Rate
关键直觉:用户感知的"稀缺性"来自净通胀率与流通盘——不是总量大小。一个总量 100 亿但 90% 锁仓的代币,其流通盘只有 10 亿量级。
18.5.3 激励设计:质押、锁仓与惩罚(从零 TS 实现)
PoS 类激励的经典结构:质押 → 锁定 → 累积奖励 → 解锁,并用 Slash(罚没) 惩罚作恶验证者。
// 质押激励引擎:从零实现(纯 TS + BigInt,无外部库)
// 计算:每 epoch 按质押份额分配奖励;提前解锁需罚没部分本金
class StakingEngine {
epochDurationSeconds: bigint; // 每个 epoch 时长
totalStaked: bigint; // 当前总质押额
rewardPerEpoch: bigint; // 每 epoch 奖励总量(协议补贴)
positions: Map<string, { amount: bigint; lockUntil: bigint; entered: bigint }>;
constructor(epochDur: bigint, reward: bigint) {
this.epochDurationSeconds = epochDur;
this.totalStaked = 0n;
this.rewardPerEpoch = reward;
this.positions = new Map();
}
stake(user: string, amount: bigint, now: bigint, lockEpochs: bigint): void {
const until = now + lockEpochs * this.epochDurationSeconds;
const pos = this.positions.get(user);
if (pos) {
pos.amount += amount;
pos.lockUntil = pos.lockUntil > until ? pos.lockUntil : until;
} else {
this.positions.set(user, { amount, lockUntil: until, entered: now });
}
this.totalStaked += amount;
}
// 每 epoch 由协议触发:奖励按各质押者份额分配
distributeRewards(now: bigint): void {
if (this.totalStaked === 0n) return;
for (const [user, pos] of this.positions) {
const share = (pos.amount * this.rewardPerEpoch) / this.totalStaked;
pos.amount += share; // 复利:奖励自动再质押
this.totalStaked += share;
}
}
// 提前解冻:罚没 penaltyBps(万分比)
unstake(user: string, now: bigint, penaltyBps: bigint): bigint {
const pos = this.positions.get(user);
if (!pos) throw new Error("无质押");
let payout = pos.amount;
if (now < pos.lockUntil) {
const penalty = (payout * penaltyBps) / 10000n; // 提前赎回罚金(部分销毁)
payout -= penalty;
}
this.totalStaked -= pos.amount;
this.positions.delete(user);
return payout; // 返回实际可提取金额
}
}
// 验证:2 个质押者 share epochs 奖励(整数单位,简化演示)
// 注意:distributeRewards 按 Map 插入序遍历,A 先分后 totalStaked 增大,
// 故 B 的份额分母变大(2000→2050),B 实得 1048(顺序复利特性)
const s = new StakingEngine(1n, 100n);
s.stake("0xA", 1000n, 0n, 10n); // A 质押 1000,锁 10 epoch
s.stake("0xB", 1000n, 0n, 1n); // B 质押 1000,锁 1 epoch
s.distributeRewards(1n); // A 先分 50,B 后分 48(分母变大)
const bEarly = s.unstake("0xB", 2n, 500n); // now=2 已过 B 锁定期(1),不罚没
console.log("B 实得 =", bEarly.toString(), "(A 先分→B 份额减少,1048 符合顺序复利)");
console.log("锁定期内解冻示例(罚5%):", (1050n * 9500n / 10000n).toString()); // 997
注:上面 distributeRewards 同步遍历全部质押者,真实链上会用"全局每份额奖励"变量以避免 O(N) 循环(Gas 优化),但经济结果是等价的——奖励按质押份额比例分配,复利累积。
18.5.4 分配方案与流通管理
| --------- | --------- | --------- | ------ |
| 公募/IDO | 5-15% | 线性释放 6-24 月 | 公平启动、初始流动性 |
| 团队/核心贡献者 | 15-25% | 锁 12 月 + 分 24 月 | 长期激励 |
| 投资者(私募) | 10-20% | 线性解锁,通常带 cliff | 早期资金 |
| 质押/流动性激励 | 10-30% | 持续 emit | 冷启动与流动性 |
常见健康信号:
- 流通量占比不过度偏斜——早期流通盘太大易被砸盘,锁太久又缺乏流动性。
- 财政储备透明度——多签金库(Treasury Multisig)公开地址。
- 通胀可控——Net Inflation < 用户增长,代币才可能升值。
18.5.5 飞轮与死亡螺旋
正向飞轮:激励 → 用户/流动性增长 → 手续费收入增长 → 回购/燃烧 → 代币增值 → 激励更有价值。
死亡螺旋(常见于纯通胀+无内在需求的代币):通胀 → 抛压 → 价格下跌 → 激励吸引力下降 → 用户离开 → 价格进一步下跌。
graph TD
A["激励发放"] --> B["用户与TVL增长"]
B --> C["协议手续费收入增加"]
C --> D{收入如何分配?}
D -->|回购+燃烧| E["代币升值"]
D -->|全部分给质押者| F["通胀高企"]
E --> A
F --> G["抛压增大"]
G --> H["价格下跌"]
H --> I["激励吸引力下降"]
I --> J["用户流失"]
J --> G
style E fill:#e8f5e9
style G fill:#ffebee
设计检验:问自己——"如果我停止发激励,协议还有多少真实需求?"纯激励堆出来的增长是租来的用户。
18.5.6 小结:代币设计的 5 个自检问题
- 四个职能里,我的代币到底承担哪一两个?(少即是多)
- 净通胀率是多少?对应什么增长假设?
- 团队/投资人解锁对市场抛压的第二年影响能否承受?
- 激励退出后,真实需求是否还在?
- 治理与实际价值捕获是否脱节?
3 个关键认知:① Tokenomics 是"激励工程",自利行为要能聚合出系统目标;② 稀缺性来自净通胀+流通盘,不是总量;③ 可持续性取决于"停止激励后的真实需求"。
, 前往 → 18.6 NFT 市场 |*
18.6 NFT 市场:上架、成交与版税
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["资金结算"]
| ------ | --------- | ----- | -------- | ------ |
| 链下签名订单(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 拆分给创作者(如有)。
// 链下订单撮合:签名校验 + 版税拆分的数学(纯 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.seller:{order.nft}:order.tokenId:{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 ETH
18.6.3 版税的实现与争议
版税(Royalty)在成交时强制拆分给创作者。历史上由市场自觉执行,2023 年后各平台政策分化(OpenSea 默认强制、Blur 部分可选、部分平台设上限)。
| ------ | -------- | ------ | ------ |
| 市场级强制 | 市场合约 | 简单、创作者收益稳定 | 平台可关闭;不同市场规则不一 |
| 合约级 ERC-2981 | NFT 合约 royaltyInfo() | 链上原语,不可绕过 | 需 NFT 合约支持 |
| 可编程版税(如 on-chain royalties / 黑名单市场) | 转移时校验 | 最强保障 | 影响互操作性、抗 FUD |
ERC-2981 接口:
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 总结 |*
第18章 总结:从合约到市场
三个关键认知
- NFT = 链上所有权 + 链下元数据。合约只记录"谁拥有哪个 tokenId",图片与属性放在 IPFS——区分两者是理解 NFT 的起点。
- 通证经济是激励工程。质押、锁仓、燃烧、反射税——每一条规则都在把"自利行为"导向"系统目标";稀缺性来自净通胀与流通盘,而非总量。
- 市场的本质是签名订单 + 链上成交 + 版税拆分。链下订单省 Gas 但信任平台,链上订单去信任但昂贵;版税正从"市场自觉"走向 ERC-2981 合约原语。
本章知识地图
graph TD
Ch18["第18章 代币与NFT"] --> Std["18.1-18.2 标准与合约"]
Ch18 --> Dream["18.4 通缩/反射"]
Ch18 --> Toke["18.5 通证经济"]
Ch18 --> Mkt["18.6 NFT市场"]
Std -->|ERC-20/721| Impl["18.2/18.3 从零实现"]
Impl --> Sol[Solidity+OpenZeppelin]
Dream --> Tax["交易税拆分/率更新"]
Toke --> Incent["激励/质押/烧毁"]
Mkt --> Order["签名订单撮合"]
Mkt --> Royal["版税 ERC-2981"]
Mkt --> Sec["攻击面与防重放"]
核心对比表
| 维度 | ERC-20 同质化代币 | ERC-721 NFT | ERC-1155 多类型 |
| ------ | ----------------- | ------------- | ---------------- |
| 单位 | 可分割 (decimals) | 不可分割,每 tokenId 唯一 | 可同质可非同质 |
| 关联 | balanceOf(addr) | ownerOf(tokenId) + getApproved | 混合 balanceOf(addr,id) |
| 典型场景 | 支付/治理/质押 | 数字收藏品/身份 | 游戏资产/批量铸造 |
| 转换 | transfer/transferFrom | 需显式 approve + 安全转移 | 批量转移 |
| --------- | --------- | --------- |
| 订单存储 | 数据库 + EIP-712 签名 | 合约 storage |
核心公式与代码索引
| 反射税费拆分 | Anet=A(1−τ) | 18.4 |
| 放大系数更新 | r′=∑ref/(S−τA) | 18.4 |
| 质押复利 | share = amount×reward/totalStaked | 18.5 |
| 版税拆分 | royalty = price×bps/10000 | 18.6 |
| TS 运行验证 | node --experimental-strip-types | 18.4/18.5/18.6 |
下一章衔接
你已经能发代币、造 NFT、设计经济并搭建市场。第 19 章将把经济逻辑推向极致——构建一个 去中心化交易所(DEX):恒定乘积 AMM、流动性池、Swap 与无常损失。NFT 市场的资金结算,本质上也可以是一个 AMM 池。
, 前往 → 19.1 DEX 总览 |*
评论
0评论加载中…