Featured image of post NFT 标准选型与场景实践

NFT 标准选型与场景实践

ERC-721 是单一 NFT 的鼻祖,ERC-1155 是多代币标准的进化。本文从协议、Gas、交互体验讲清两者差异与选型

两个标准都叫"NFT",但它们解决的问题不同

新人接触 NFT 时常被这两个标准的并存搞晕:

  • ERC-721:CryptoPunks、Bored Ape、CryptoKitties 都是这个标准
  • ERC-1155:Decentraland 道具、Enjin 卡牌、OpenSea 创作工具

它们都是 NFT,但底层模型完全不同

ERC-721 一个合约管理一系列独一无二的 token,每个 tokenId 唯一。

ERC-1155 一个合约管理多种 token每种 token 可以有多个——既可以是 NFT,也可以是 SemiFungible,甚至 Fungible。

理解这两个标准的边界,决定了你写 NFT 项目时该选哪个、为什么选。


一、ERC-721:经典的"单一 NFT"

ERC-721 由 Dieter Shirley、William Entriken 等人于 2018 年提出(EIP-721)。核心理念——每个 token 唯一

1
2
3
4
5
6
7
interface IERC721 {
    function ownerOf(uint256 tokenId) external view returns (address);
    function balanceOf(address owner) external view returns (uint256);
    function transferFrom(address from, address to, uint256 tokenId) external;
    function approve(address to, uint256 tokenId) external;
    // ... 其他
}

每个 tokenId 对应一个独一无二的资产。一只 BAYC 猴子、一张 CryptoKitty——对应一个 tokenId。

数据结构

1
2
mapping(uint256 => address) private _owners;        // tokenId → owner
mapping(address => uint256) private _balances;      // owner → 持有数量

典型操作

1
2
3
4
5
6
7
8
// 铸造一个 token
_owners[tokenId] = to;
_balances[to] += 1;

// 转账
_owners[tokenId] = newOwner;
_balances[from] -= 1;
_balances[to] += 1;

ERC-721 的特性

  • 每个 tokenId 唯一——一个 owner,不存在"持有多少"
  • 每个 token 一段元数据——通过 tokenURI(tokenId)
  • 批量操作要循环 N 次——转 100 个 NFT 要 100 次 transferFrom

二、ERC-1155:多代币 + 半同质

ERC-1155 由 Enjin 团队 2018 年提出(EIP-1155),针对 ERC-721 的几个痛点:

痛点 1:游戏里有大量同种道具

ERC-721 模型下,“100 把同样的剑"要 100 个不同 tokenId、100 次铸造、100 次转账——Gas 成本爆炸

痛点 2:合约碎片化

每个游戏出一种 NFT 都要部署一个新合约——以太坊上充斥着 1 万个仅区分一个游戏内物品的合约。

痛点 3:不能批量

ERC-721 没有 safeBatchTransferFrom——电商场景里"一键购买 5 件商品"做不到。

ERC-1155 的解法

一个合约管理多种 id,每种 id 可以有多份

1
2
3
4
5
6
interface IERC1155 {
    function balanceOf(address account, uint256 id) external view returns (uint256);
    function balanceOfBatch(address[] accounts, uint256[] ids) external view returns (uint256[]);
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data) external;
    function safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data) external;
}

注意 balanceOf 多了 (account, id)——同一个 id 可以有多份

数据结构

1
2
mapping(uint256 => mapping(address => uint256)) private _balances;
// _balances[tokenId][owner] = 持有数量

三、能力对比表

ERC-721ERC-1155
token 模型每 id 唯一每 id 可有多份
适合 NFT✓ (amount = 1 等于 NFT)
适合 Fungible
批量转账✗(要循环)safeBatchTransferFrom
批量查询余额balanceOfBatch
Gas(批量场景)显著更低
metadatatokenURI(id)uri(id)(可用 {id} 占位符)
approve 粒度单 token全合约(setApprovalForAll)
钱包/工具支持几乎全部主流支持,部分老钱包不支持
适用场景头像、艺术、收藏品游戏道具、批量、组合资产

四、Gas 成本:ERC-1155 的杀手级优势

铸造 100 个相同道具:

1
2
3
4
5
6
7
// ERC-721
for (uint i = 0; i < 100; i++) {
    _mint(player, nextTokenId++);          // 100 次 SSTORE
}

// ERC-1155
_mint(player, ITEM_SWORD, 100, "");        // 1 次 SSTORE(balance = 100)

实测对比(铸造 100 件相同道具):

方案Gas 消耗
ERC-721~3,500,000
ERC-1155~80,000

差距 40 倍。批量转账的差距类似。

这是为什么链游、批量市场几乎都选 ERC-1155——同等业务,Gas 成本压到几十分之一。


五、适用场景

选 ERC-721 的场景

  • 每个 token 独一无二:头像、PFP、艺术品、稀有收藏
  • 每个 token 元数据完全不同:CryptoPunks 每个都不一样
  • 品牌效应:BAYC、Doodles 这种"个体认知度"项目——单合约、单 tokenId

选 ERC-1155 的场景

  • 游戏道具:剑、药水、卡牌——同种物品多份
  • 门票 / 凭证:演唱会门票每张相同但要发几千份
  • 批量操作:电商商品、物流凭证
  • 混合资产:一个合约里既有可堆叠物品,又有独一无二的稀有 NFT
  • 组合金融产品:一个合约里管理 N 种衍生品

一些灰色地带

  • 限量版艺术品(一种艺术 100 个相同复制品):ERC-1155 更合适
  • 演唱会门票(每张序列号不同):ERC-721 更合适
  • Loot 这种链上随机生成:ERC-721 元数据 on-chain 更合适

判断关键:“这种 token 会有多份吗?"——会就 ERC-1155,不会就 ERC-721。


六、metadata 的差异

ERC-721 的 tokenURI

每个 tokenId 一个 URI:

1
2
3
function tokenURI(uint256 tokenId) public view returns (string memory) {
    return string(abi.encodePacked(baseURI, tokenId.toString()));
}

返回的 JSON:

1
2
3
4
5
6
{
    "name": "BAYC #4521",
    "description": "...",
    "image": "ipfs://Qm.../4521.png",
    "attributes": [{"trait_type": "Background", "value": "Gold"}]
}

ERC-1155 的 uri

支持 {id} 占位符:

1
2
3
function uri(uint256 id) public view returns (string memory) {
    return "https://example.com/api/{id}.json";
}

客户端拿到后用 64 位 hex(补全 0x 前缀)替换 {id}——这样一个 URI 模板能服务所有 id,省心。

但实际上很多 ERC-1155 项目仍然给每个 id 单独配 URI——尤其是游戏道具,每种道具一段元数据。


七、approve 模型差异

ERC-721

  • approve(to, tokenId):授权某地址转某个 token
  • setApprovalForAll(operator, true):授权某地址操作所有 token

ERC-1155

  • 没有单 token approve——只能 setApprovalForAll
  • 这意味着用户必须信任整个 operator(如交易市场)能动他全部 1155 资产

这是 ERC-1155 在用户安全侧的劣势——授权范围更大、撤销更重要。


八、写一个 ERC-1155 合约

OpenZeppelin 提供了一行能用的实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract GameItems is ERC1155, Ownable {
    uint256 public constant SWORD     = 0;
    uint256 public constant SHIELD    = 1;
    uint256 public constant POTION    = 2;
    uint256 public constant LEGENDARY = 3;

    constructor() ERC1155("https://example.com/api/{id}.json") {
        _mint(msg.sender, SWORD,    100, "");
        _mint(msg.sender, SHIELD,   200, "");
        _mint(msg.sender, POTION,   500, "");
        _mint(msg.sender, LEGENDARY, 1, "");   // 唯一稀有 NFT
    }

    function mint(address to, uint256 id, uint256 amount) external onlyOwner {
        _mint(to, id, amount, "");
    }

    function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts)
            external onlyOwner {
        _mintBatch(to, ids, amounts, "");
    }
}

注意 LEGENDARY 的 amount = 1——这相当于在同一个合约里用 ERC-1155 表达 NFT。这是 ERC-1155 的灵活性。


九、踩坑提醒

1. safeTransferFrom 接收方要实现钩子

如果接收方是合约,必须实现 onERC721Received / onERC1155Received,否则转账 revert。

1
2
3
4
function onERC1155Received(address, address, uint256, uint256, bytes calldata)
        external pure returns (bytes4) {
    return this.onERC1155Received.selector;
}

2. ERC-1155 的 transfer 必须带 amount

1
2
3
4
5
// ERC-721 的语法
contract.transferFrom(from, to, tokenId);

// ERC-1155 的语法
contract.safeTransferFrom(from, to, id, amount, "");

NFT 性质的 token 永远 amount = 1。

3. metadata 占位符兼容

{id} 占位符是 ERC-1155 标准里规定的——实际平台支持参差不齐。OpenSea / 区块链浏览器多数支持,但小钱包可能不识别——保险起见每个 id 单独配 URI。

4. 不要混用模型

同一个项目里要么用 ERC-721 要么用 ERC-1155,不要在一个集合里两种都发。买家会困惑:

  • “为什么这个 NFT 在我的钱包里看不到?”
  • “为什么我数量是 5 但只能转 1?”

5. 老钱包可能不支持 ERC-1155

Trust Wallet、imToken 较新版本支持,但 5 年前的老钱包可能识别不出 1155——目标用户群里要有市场调研。


十、其他相关标准

NFT 生态的相关标准还有:

  • ERC-2981:版税标准——让 NFT 在二级市场卖出时自动扣版税给原作者
  • EIP-2535:Diamond 合约标准——超大合约的模块化升级
  • ERC-4907:可租赁 NFT
  • ERC-721A:Azuki 优化的批量铸造 ERC-721(Gas 比标准 721 低很多)

🔥 ERC-721A 在某种程度上是对 ERC-1155 的"反击”——保留 ERC-721 的语义但优化批量铸造 Gas,是大量 PFP 项目的选择。


小结

把全文压一句:

ERC-721 表达『每个唯一』,ERC-1155 表达『多种 + 多份』。一个合约一个项目就用 721;多类型批量场景必上 1155。

工程选型:

  • PFP / 艺术 / 收藏 → ERC-721(或 ERC-721A)
  • 游戏 / 批量 / 多种 → ERC-1155
  • 混合需求 → ERC-1155(amount=1 表 NFT,amount>1 表数量)
  • 二级市场版税 → 配 ERC-2981

NFT 生态还在演进,理解这些标准的边界比追新更重要——搞清"业务场景该用哪个”,远比"哪个最先进"实用。

使用 Hugo 构建
主题 StackJimmy 设计