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-721 | ERC-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.timestamp、block.number或blockhash作为随机源——矿工/验证人可操纵。
五、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 Eden | Solana/多链 | 可选 |
| Foundation | 高端艺术 | 强制版税 10% |
| Zora | 创作者优先 | 低费用、模块化 |
六、本章小结
NFT 标准从 ERC-721 的唯一性到 ERC-1155 的高效批量管理,为数字资产提供了坚实的技术基础。元数据存储是 NFT 持久性的关键——IPFS 是当前的主流选择,而 Arweave 则为高价值资产提供了永久存储方案。在开发 NFT 项目时,随机性来源的安全性、Gas 优化(特别是 ERC-1155 的批量操作)以及市场兼容性都是需要重点考虑的因素。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。