4、ERC721与ERC1155标准:非同质化代币(NFT)基础、批量代币标准、应用场景分析

4.1 从ERC20到ERC721:为什么需要非同质化?

聊到NFT,很多人第一反应是头像、图片、天价拍卖。但作为开发者,我们得先搞清楚一个根本问题:为什么有了ERC20,还要搞个ERC721?

ERC20是标准的同质化代币。你手里的一个USDT和我手里的一个USDT,完全等价,可以互换。这在货币、积分场景下没问题。但现实世界中,很多东西是独一无二的——比如房产证、艺术品、游戏里的稀有装备。

我早期参与过一个游戏项目,想把游戏里的“屠龙刀”做成代币。如果用ERC20,那所有屠龙刀都一样,玩家根本分不清哪把是+10的、哪把是+15的。这显然不合理。所以,ERC721的核心思想就是:每个代币都有一个唯一的ID

核心区别一句话总结:

  • ERC20:所有代币都一样,像人民币。
  • ERC721:每个代币都不同,像身份证号。

4.2 ERC721标准核心接口

ERC721的接口定义在OpenZeppelin的库中,我建议你直接用它,别自己手写。手写容易漏掉关键函数,我在审计中见过不少因为没实现safeTransferFrom导致资产卡死的案例。

核心函数就这几个:

// 查询某个地址拥有多少个NFT
function balanceOf(address owner) external view returns (uint256 balance);

// 查询某个tokenId的拥有者
function ownerOf(uint256 tokenId) external view returns (address owner);

// 安全转账(推荐使用这个)
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

// 普通转账(不推荐,容易丢资产)
function transferFrom(address from, address to, uint256 tokenId) external;

// 授权
function approve(address to, uint256 tokenId) external;

// 批量授权
function setApprovalForAll(address operator, bool _approved) external;

嗯,这里有个坑。很多新手会问:safeTransferFromtransferFrom有什么区别?

说白了,safeTransferFrom会检查接收方是不是合约。如果是合约,它会调用接收方的onERC721Received函数,确保对方能处理NFT。如果对方没实现这个接口,交易就会回滚。这能防止NFT被转到一个无法取出的合约里——我曾经见过一个项目,因为用了transferFrom,导致价值几百万的NFT卡死在合约里,最后只能硬分叉解决

避坑指南:

我建议你永远优先使用safeTransferFrom。除非你明确知道接收方是EOA(外部账户),否则别用transferFrom

4.3 ERC721元数据扩展

光有ID还不够,你得告诉用户这个NFT长什么样。ERC721的元数据扩展(Metadata Extension)定义了tokenURI函数:

function tokenURI(uint256 tokenId) external view returns (string memory);

这个函数返回一个JSON格式的URI,通常指向IPFS或中心化服务器。JSON结构大概长这样:

{
  "name": "屠龙刀 #1",
  "description": "一把传说中的武器",
  "image": "ipfs://QmX...",
  "attributes": [
    {"trait_type": "攻击力", "value": 100},
    {"trait_type": "稀有度", "value": "传说"}
  ]
}

我个人习惯把元数据存在IPFS上,因为去中心化。但要注意,IPFS的地址是固定的,一旦上传就不能修改。如果你需要动态更新元数据(比如游戏装备升级后攻击力变化),那就得用中心化服务器或者自己写个可升级的合约。

4.4 ERC1155:批量代币标准

好,现在问题来了。假设你做一个游戏,里面有100种装备,每种装备有1000个。如果用ERC721,你得部署100个合约,或者在一个合约里管理10万个tokenId。这太浪费gas了。

ERC1155就是来解决这个问题的。它把同质化和非同质化代币统一到一个合约里。说白了,ERC1155可以同时管理多种代币,每种代币有自己的ID,而且支持批量转账。

核心接口:

// 批量查询余额
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);

// 批量转账
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;

你看,balanceOfBatch一次能查多个地址、多种代币的余额。这在游戏里特别有用——比如一次查询玩家所有装备的数量。

我的经验:

如果你要发一个系列(比如10000个PFP头像),用ERC721就够了。但如果你要做游戏、道具系统,ERC1155是更好的选择。它节省gas,而且代码更简洁。

4.5 应用场景分析

我整理了一个表格,方便你对比:

场景 推荐标准 原因
数字艺术品(单个) ERC721 每个作品独一无二,不需要批量管理
游戏道具(多种类、多数量) ERC1155 批量操作省gas,管理方便
房地产产权 ERC721 每个房产证唯一,需要单独追踪
会员卡/门票 ERC1155 同一种门票可以发多张,支持批量发放
合成资产(如多个NFT合成一个) ERC1155 支持同质化与非同质化混合管理

你想想看,为什么现在很多GameFi项目都用ERC1155?因为游戏里经常要批量发装备、批量销毁、批量转移。如果用ERC721,一次只能操作一个,gas费高得吓人。ERC1155一次能处理256种代币,效率提升巨大。

4.6 实战中的注意事项

最后,分享几个我在实际开发中踩过的坑:

  • 小心重入攻击safeTransferFromsafeBatchTransferFrom会调用外部合约。如果接收方是恶意合约,它可能在onERC721Received里再次调用你的转账函数。记得加上重入锁。
  • URI存储位置:别把元数据直接存在链上,太贵了。用IPFS或者Arweave。如果必须动态更新,考虑用中心化服务器+签名验证。
  • 批量转账的数组长度:ERC1155的safeBatchTransferFrom要求idsamounts数组长度一致。我见过有人传错长度,导致部分代币没转出去。
  • 授权管理setApprovalForAll是全局授权,要提醒用户谨慎操作。我建议在UI上明确提示“授权后该合约可以操作你所有NFT”。

总结一下:

ERC721适合做独一无二的数字藏品,ERC1155适合做批量管理的游戏道具。选哪个,取决于你的业务场景。别盲目跟风,想清楚再动手。

下一章,我们会深入讲解如何用OpenZeppelin快速搭建一个安全的NFT合约。到时候我会手把手带你写一个完整的例子。