NFT 与数字资产标准:ERC-721、ERC-1155 与元数据存储

深入解析 NFT 的核心标准 ERC-721 与 ERC-1155 的设计差异、元数据存储方案(IPFS/Arweave/链上)、铸造机制及主流 NFT 协议架构。

NFT(Non-Fungible Token,非同质化代币)是区块链上独一无二的数字资产标准。从数字艺术到游戏道具、从域名到房地产代币化,NFT 打开了数字所有权的新范式。本文将详解核心标准、元数据存储方案以及铸造和交易机制。

一、ERC-721:唯一的 NFT 标准

接口定义

interface IERC721 {
    // 余额与所有权
    function balanceOf(address owner) external view returns (uint256);
    function ownerOf(uint256 tokenId) external view returns (address);
    
    // 授权管理
    function approve(address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address);
    function setApprovalForAll(address operator, bool approved) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    
    // 转账
    function transferFrom(address from, address to, uint256 tokenId) external;
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
    
    // 事件
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}

💡 indexed 参数最多 3 个。Token ID 被索引意味着前端可以通过 ID 直接过滤相关事件。

safeTransferFrom 为什么重要?

function safeTransferFrom(address from, address to, uint256 tokenId) external {
    transferFrom(from, to, tokenId);
    // 检查接收方是否为合约,且是否实现了 IERC721Receiver
    require(
        to.code.length == 0 ||  // EOA
        IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, "") == 
            IERC721Receiver.onERC721Received.selector,
        "Transfer to non ERC721Receiver implementer"
    );
}

若向未实现 onERC721Received 的合约转账 NFT,资产将永久锁定。safeTransfer 强制要求接收合约正确处理 NFT。

OpenZeppelin 实现骨架

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    
    constructor() ERC721("MyNFT", "MNFT") {}
    
    function mint(address to, string memory uri) external onlyOwner returns (uint256) {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
        return tokenId;
    }
    
    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }
    
    function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
        return super.tokenURI(tokenId);
    }
}

二、ERC-1155:多代币标准

为什么需要 ERC-1155?

游戏中一个角色可能有:

  • 1 把独一无二的剑(ERC-721)
  • 100 个同样的药水(ERC-20)
  • 5 个同样的盔甲(半同质化)

为每种资产部署单独合约成本极高。ERC-1155 允许一个合约管理多种代币(每种代币有自己的 ID 和供应量)。

interface IERC1155 {
    // 查询余额
    function balanceOf(address account, uint256 id) external view returns (uint256);
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) 
        external view returns (uint256[] memory);
    
    // 授权与转账
    function setApprovalForAll(address operator, bool approved) external;
    function isApprovedForAll(address account, address operator) external view returns (bool);
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
    function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, 
        uint256[] calldata amounts, bytes calldata data) external;
    
    // 元数据
    function uri(uint256 id) external view returns (string memory);
}

ERC-721 vs ERC-1155

特性ERC-721ERC-1155
供应量每个 Token ID = 1每个 Token ID 可任意数量
Gas 效率单代币低批量操作极优( batchTransfer )
元数据tokenURI()uri()(统一或差异化)
半同质化支持
适用场景艺术品、PFP、域名游戏资产、门票、会员

三、元数据存储方案

元数据 JSON 标准

{
    "name": "CryptoKitty #12345",
    "description": "A cute crypto-collectible cat",
    "image": "ipfs://QmXYZ.../image.png",
    "attributes": [
        { "trait_type": "Background", "value": "Blue" },
        { "trait_type": "Eyes", "value": "Green", "display_type": "number" },
        { "trait_type": "Level", "value": 5, "max_value": 10 }
    ],
    "animation_url": "ipfs://QmABC.../animation.glb",
    "external_url": "https://example.com/nft/12345"
}

存储方案对比

方案持久性成本去中心化速度适用场景
IPFS高(需 pin)是(但 gateway 可审查)中等主流选择
Arweave永久一次性付费中等高价值资产
链上永久极高完全小数据 / 核心逻辑
中心化服务器依赖服务商不推荐
Filecoin较慢IPFS 的激励层

IPFS 实践

# 安装 IPFS CLI
ipfs init
ipfs daemon

# 上传文件
ipfs add image.png
# 返回: added QmXYZ... image.png

# 固定(pin)到 Pinata/Infura 防止垃圾回收
curl -X POST "https://api.pinata.cloud/pinning/pinByHash" \
  -H "Authorization: Bearer YOUR_JWT" \
  -d '{"hashToPin":"QmXYZ..."}'
// 合约中使用 IPFS 链接
function tokenURI(uint256 tokenId) public view override returns (string memory) {
    return string(abi.encodePacked("ipfs://", _tokenCIDs[tokenId]));
}

💡 链上只需存储固定的 IPFS CID(Qm...),实际内容通过 IPFS 网关(如 https://ipfs.io/ipfs/Qm...)或自己的节点获取。

四、铸造(Mint)机制

常见铸造模式

模式描述Gas 成本代表项目
Owner Mint项目方铸造后分发给用户项目方承担大多数 PFP
用户 Mint用户支付 Gas 自行铸造用户承担大多数生成艺术
Lazy Mint用户购买时代币才上链买家承担OpenSea shared store
Airdrop项目方主动分发项目方承担社区空投
Free Claim满足条件的用户免费铸造用户承担 Gas社区奖励

可验证随机性(链上生成)

对于生成艺术 NFT,需要链上随机决定属性:

import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

contract RandomNFT is VRFConsumerBaseV2 {
    function requestRandomWords() external returns (uint256 requestId) {
        return COORDINATOR.requestRandomWords(
            keyHash, subscriptionId, requestConfirmations, callbackGasLimit, numWords
        );
    }
    
    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        uint256 tokenId = requestIdToTokenId[requestId];
        // 使用随机数生成属性
        uint256 random = randomWords[0];
        attributes[tokenId] = Attribute({
            background: random % 10,
            eyes: (random / 10) % 10,
            ...
        });
    }
}

⚠️ 绝不要使用 block.timestampblock.numberblockhash 作为随机源——矿工/验证人可操纵。

五、NFT 市场与版税

版税机制(EIP-2981)

interface IERC2981 {
    function royaltyInfo(uint256 tokenId, uint256 salePrice) 
        external view returns (address receiver, uint256 royaltyAmount);
}

// 实现示例:固定 5% 版税
function royaltyInfo(uint256, uint256 salePrice) external view 
    returns (address receiver, uint256 royaltyAmount) {
    return (artistAddress, salePrice * 500 / 10000);  // 5%
}

⚠️ 注意:OpenSea、Blur 等市场已逐步转向可选版税模式,EIP-2981 的强制执行能力在减弱。

主要市场对比

市场特点版税策略
OpenSea最大综合市场可选版税,最低 0.5%
Blur专业交易者零平台费、可选版税
Magic EdenSolana/多链可选
Foundation高端艺术强制版税 10%
Zora创作者优先低费用、模块化

六、本章小结

NFT 标准从 ERC-721 的唯一性到 ERC-1155 的高效批量管理,为数字资产提供了坚实的技术基础。元数据存储是 NFT 持久性的关键——IPFS 是当前的主流选择,而 Arweave 则为高价值资产提供了永久存储方案。在开发 NFT 项目时,随机性来源的安全性、Gas 优化(特别是 ERC-1155 的批量操作)以及市场兼容性都是需要重点考虑的因素。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「区块链 Web3」更多文章

  1. Web3 全栈 DApp 开发实战:从前端到智能合约的完整链路
  2. 企业级区块链:Hyperledger Fabric 架构与链码开发
  3. 区块链安全:合约审计、攻击模式与防御体系