第32集:ERC-721 NFT标准
学习目标
- 了解ERC-721 NFT标准的核心概念
- 掌握ERC-721 NFT的实现方法
- 学习NFT元数据的管理
- 了解NFT的应用场景
- 掌握NFT开发的最佳实践
核心知识点讲解
1. ERC-721 NFT标准概述
ERC-721是以太坊上的非同质化代币(NFT)标准,它定义了独特数字资产的接口。ERC-721 NFT的核心特点:
- 每个代币都是唯一的
- 代币具有不可分割性
- 代币具有所有权证明
- 代币可以包含元数据
2. ERC-721接口
ERC-721标准定义了以下接口:
interface IERC721 {
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);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}3. ERC-721实现
实现ERC-721 NFT需要:
- 实现所有必需的接口函数
- 维护代币所有权和批准记录
- 处理代币的创建和转移
- 管理代币元数据
4. NFT元数据
NFT元数据是描述代币特征的JSON数据,通常包括:
- 名称
- 描述
- 图像
- 属性
- 其他自定义数据
元数据可以存储在:
- 链上(直接存储在合约中)
- 链下(存储在IPFS或其他分布式存储)
5. NFT应用场景
NFT的主要应用场景包括:
- 数字艺术
- 收藏品
- 游戏物品
- 身份验证
- 虚拟地产
- 音乐和视频
实用案例分析
案例1:基本ERC-721 NFT实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract BasicNFT {
string public name;
string public symbol;
uint256 public tokenCounter;
mapping(uint256 => address) public ownerOf;
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
mapping(address => uint256) public balanceOf;
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);
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
tokenCounter = 0;
}
function _mint(address to) internal {
uint256 tokenId = tokenCounter;
tokenCounter += 1;
ownerOf[tokenId] = to;
balanceOf[to] += 1;
emit Transfer(address(0), to, tokenId);
}
function mint() external {
_mint(msg.sender);
}
function transferFrom(address from, address to, uint256 tokenId) external {
require(ownerOf[tokenId] == from, "Not the owner");
require(msg.sender == from || getApproved[tokenId] == msg.sender || isApprovedForAll[from][msg.sender], "Not approved");
require(to != address(0), "Transfer to zero address");
ownerOf[tokenId] = to;
balanceOf[from] -= 1;
balanceOf[to] += 1;
getApproved[tokenId] = address(0);
emit Transfer(from, to, tokenId);
}
function approve(address to, uint256 tokenId) external {
require(ownerOf[tokenId] == msg.sender, "Not the owner");
getApproved[tokenId] = to;
emit Approval(msg.sender, to, tokenId);
}
function setApprovalForAll(address operator, bool approved) external {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function safeTransferFrom(address from, address to, uint256 tokenId) external {
transferFrom(from, to, tokenId);
// 这里应该添加接收者是否支持ERC721的检查
}
}案例2:带元数据的ERC-721 NFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract NFTWithMetadata {
string public name;
string public symbol;
string public baseURI;
uint256 public tokenCounter;
mapping(uint256 => address) public ownerOf;
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
mapping(address => uint256) public balanceOf;
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);
constructor(string memory _name, string memory _symbol, string memory _baseURI) {
name = _name;
symbol = _symbol;
baseURI = _baseURI;
tokenCounter = 0;
}
function _mint(address to) internal {
uint256 tokenId = tokenCounter;
tokenCounter += 1;
ownerOf[tokenId] = to;
balanceOf[to] += 1;
emit Transfer(address(0), to, tokenId);
}
function mint() external {
_mint(msg.sender);
}
function transferFrom(address from, address to, uint256 tokenId) external {
require(ownerOf[tokenId] == from, "Not the owner");
require(msg.sender == from || getApproved[tokenId] == msg.sender || isApprovedForAll[from][msg.sender], "Not approved");
require(to != address(0), "Transfer to zero address");
ownerOf[tokenId] = to;
balanceOf[from] -= 1;
balanceOf[to] += 1;
getApproved[tokenId] = address(0);
emit Transfer(from, to, tokenId);
}
function approve(address to, uint256 tokenId) external {
require(ownerOf[tokenId] == msg.sender, "Not the owner");
getApproved[tokenId] = to;
emit Approval(msg.sender, to, tokenId);
}
function setApprovalForAll(address operator, bool approved) external {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function safeTransferFrom(address from, address to, uint256 tokenId) external {
transferFrom(from, to, tokenId);
}
// 元数据URI
function tokenURI(uint256 tokenId) external view returns (string memory) {
require(ownerOf[tokenId] != address(0), "Token does not exist");
return string(abi.encodePacked(baseURI, tokenId, ".json"));
}
}案例3:使用OpenZeppelin实现ERC-721 NFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNFT is ERC721, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
string private _baseURI;
constructor(string memory name, string memory symbol, string memory baseURI) ERC721(name, symbol) {
_baseURI = baseURI;
}
function _baseURI() internal view override returns (string memory) {
return _baseURI;
}
function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
}
}案例4:NFT元数据示例
{
"name": "CryptoPunk #1",
"description": "A unique CryptoPunk NFT",
"image": "ipfs://QmWj2MffHm753x27T1qQ6k8xwAESf87mE7c1xVnF3aV1",
"attributes": [
{
"trait_type": "Background",
"value": "Blue"
},
{
"trait_type": "Hair",
"value": "Mohawk"
},
{
"trait_type": "Eyes",
"value": "Sun Glasses"
}
]
}案例5:NFT前端交互
// 安装依赖
// npm install ethers
const { ethers } = require('ethers');
// 连接到以太坊网络
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY');
// NFT合约地址
const nftAddress = '0x...';
// ERC-721 NFT ABI
const nftABI = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function balanceOf(address) view returns (uint256)",
"function ownerOf(uint256) view returns (address)",
"function transferFrom(address, address, uint256) returns (void)",
"function safeTransferFrom(address, address, uint256) returns (void)",
"function approve(address, uint256) returns (void)",
"function setApprovalForAll(address, bool) returns (void)",
"function getApproved(uint256) view returns (address)",
"function isApprovedForAll(address, address) view returns (bool)",
"function tokenURI(uint256) view returns (string)",
"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)"
];
// 创建NFT合约实例
const nftContract = new ethers.Contract(nftAddress, nftABI, provider);
// 读取代币信息
async function getNFTInfo() {
const name = await nftContract.name();
const symbol = await nftContract.symbol();
console.log('NFT Name:', name);
console.log('NFT Symbol:', symbol);
}
// 读取账户NFT数量
async function getNFTBalance(address) {
const balance = await nftContract.balanceOf(address);
console.log('NFT Balance:', balance.toString());
}
// 读取NFT所有者
async function getNFTOwner(tokenId) {
const owner = await nftContract.ownerOf(tokenId);
console.log('NFT Owner:', owner);
}
// 读取NFT元数据
async function getNFTMetadata(tokenId) {
const tokenURI = await nftContract.tokenURI(tokenId);
console.log('Token URI:', tokenURI);
// 如果是IPFS URI,需要转换
if (tokenURI.startsWith('ipfs://')) {
const ipfsHash = tokenURI.replace('ipfs://', '');
const metadataUrl = `https://ipfs.io/ipfs/${ipfsHash}`;
// 这里可以使用fetch或axios获取元数据
// const response = await fetch(metadataUrl);
// const metadata = await response.json();
// console.log('NFT Metadata:', metadata);
}
}
// 转账NFT
async function transferNFT(from, to, tokenId) {
// 连接钱包
const wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);
const nftWithSigner = nftContract.connect(wallet);
// 转账
const tx = await nftWithSigner.transferFrom(from, to, tokenId);
await tx.wait();
console.log('NFT Transfer successful:', tx.hash);
}
// 运行示例
getNFTInfo();
getNFTBalance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045');
getNFTOwner(1);
getNFTMetadata(1);
transferNFT('0x...', '0x...', 1);ERC-721 NFT最佳实践
实现标准:
- 严格按照ERC-721标准实现接口
- 使用经过审计的库(如OpenZeppelin)
- 测试所有功能
元数据管理:
- 使用IPFS存储元数据和图像
- 确保元数据的完整性和持久性
- 考虑使用去中心化存储
安全性:
- 防止重入攻击
- 验证输入参数
- 限制敏感函数的访问
- 实现安全的转移函数
Gas优化:
- 优化存储操作
- 减少不必要的计算
- 使用合适的数据类型
部署和验证:
- 在测试网测试
- 部署到主网
- 验证合约代码
- 监控合约活动
前端集成:
- 提供清晰的用户界面
- 显示NFT元数据和图像
- 处理交易确认
- 支持不同的钱包
总结
本教程介绍了ERC-721 NFT标准的实现和应用,包括基本ERC-721 NFT的实现、带元数据的ERC-721 NFT、使用OpenZeppelin实现ERC-721 NFT,以及NFT的前端交互。通过本教程的学习,开发者可以掌握NFT的实现方法,为开发自己的NFT项目打下基础。
在实际开发中,开发者应该遵循ERC-721 NFT的最佳实践,确保NFT的安全性、可靠性和唯一性。同时,开发者还应该不断学习新的NFT标准和技术,以适应区块链生态系统的发展。