以太坊 NFT 的 ERC-721 和 ERC-1155 有什么区别?
以太坊 NFT(非同质化代币)是部署在以太坊区块链上的独特数字资产,每个代币绑定一个全局唯一的 tokenId,不可互换也不可分割。以太坊上有两个主流 NFT 标准:ERC-721 和 ERC-1155。ERC-721 一个合约只管理一种 NFT,每次只能转移一个代币;ERC-1155 一个合约可以同时管理同质化和非同质化代币,支持批量转账,Gas 成本节省约 70%。选型依据:单一艺术品收藏用 ERC-721,游戏道具、多类型资产体系用 ERC-1155。
追问
ERC-721 和 ERC-1155 有什么区别?
| 维度 | ERC-721 | ERC-1155 |
|---|---|---|
| 代币类型 | 仅非同质化 | 同质化 + 非同质化 + 半同质化 |
| 批量转账 | 不支持,一次转一个 | 支持 safeBatchTransferFrom |
| 合约关系 | 一个合约 = 一个 NFT 集合 | 一个合约可管理多种代币 |
| Gas 成本 | 高(每笔 6-10 万 gas) | 低(批量操作省约 70%) |
| 互转能力 | NFT 之间不可互转 | FT 和 NFT 可互转 |
| 市场兼容 | OpenSea 等全平台支持 | 主流市场均已支持 |
早期项目(CryptoPunks 除外,它甚至不符合任何标准)几乎都用 ERC-721,因为 OpenSea 等市场初期只兼容 721。现在主流 NFT 市场对两个标准都已支持,Enjin、Decentraland 等游戏类项目早已转向 ERC-1155。
ERC-721 的核心接口有哪些?为什么这么设计?
四个核心函数:ownerOf(tokenId) 查所有者、balanceOf(owner) 查持有数量、transferFrom / safeTransferFrom 转移所有权、approve / setApprovalForAll 授权。
设计逻辑:每个 tokenId 全局唯一且绑定一个 owner,这就是"非同质化"的来源——不存在两个 tokenId 相同的代币。safeTransferFrom 比 transferFrom 多了一步:调用接收方的 onERC721Received 回调,确认对方能处理 NFT。没有这步,代币可能被转进一个没有提取函数的合约,永远锁死。历史上因为漏用 safe 版本导致资产损失的案例不少。
什么时候用 ERC-721,什么时候用 ERC-1155?
单一类型资产、1/1 艺术品、收藏品——ERC-721 更简单直接,生态工具链也更成熟。多类型资产体系(游戏装备分武器/防具/消耗品、赛事门票分 VIP/普通/团体)——ERC-1155 一份合约搞定,省 Gas 又省部署成本。
踩坑经验:ERC-1155 的 tokenId 语义完全由开发者自定义,合约 A 的 tokenId=1 代表武器,合约 B 的 tokenId=1 可能代表门票。跨合约交互时必须检查 tokenId 的上下文含义,否则会转错资产。
NFT 元数据存在哪里?tokenURI 返回什么?
tokenURI 返回一个指向 JSON 文件的 URI,JSON 包含 name、description、image、attributes 等字段。存储方式两种:
- 链下存储(主流):IPFS 或 Arweave,
tokenURI返回ipfs://QmHash...格式。优点是 Gas 低,缺点是依赖外部存储可用性。 - 链上存储:直接在合约里存 base64 编码的 JSON,
tokenURI返回data:application/json;base64,...。优点是元数据永不丢失,缺点是 Gas 成本极高,只适合小体量项目。
面试加分点:提一下 ERC-2981 版税标准——它定义了 royaltyInfo() 接口,让市场自动计算创作者版税,OpenSea 和 LooksRare 都已支持。
ERC-1155 的批量操作是怎么省 Gas 的?
ERC-721 转 10 个 NFT = 10 笔独立交易,每笔执行完整的 transfer 逻辑。ERC-1155 的 safeBatchTransferFrom 在一笔交易里转多个代币,共享一笔基础 Gas(约 21000),每个代币只附加少量存储读写开销。批量铸造 mintBatch 同理,一次调用铸造多种代币,省掉多次合约调用的固定开销。
实测数据:批量转 10 个代币,ERC-721 约 60-100 万 Gas,ERC-1155 约 8-15 万 Gas,差距 5-10 倍。这就是为什么游戏类项目几乎都用 ERC-1155——玩家一次性装备/卸下多件道具是高频操作。
写段代码
ERC-721 铸造关键片段:
solidityfunction safeMint(address to, string memory uri) public onlyOwner { uint256 tokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _safeMint(to, tokenId); _setTokenURI(tokenId, uri); }
ERC-1155 批量铸造:
solidityfunction mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public onlyOwner { _mintBatch(to, ids, amounts, data); }