DeFi 阶段四 ✅ 验证通过
| 场景 | 示例 | 代表项目 |
|---|---|---|
| 数字艺术 | 唯一画作/音乐 | Art Blocks, Sound.xyz |
| 收藏品 | 头像/卡牌 | CryptoPunks, BAYC |
| 游戏资产 | 装备/角色/土地 | Axie Infinity, Decentraland |
| 身份凭证 | 会员卡/证书 | POAP, ENS |
| 现实资产 | 房产/奢侈品 | RealT, Courtyard |
// IERC721.sol — 核心接口
interface IERC721 {
// ═══════ 查询函数 ═══════
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function tokenURI(uint256 tokenId) external view returns (string memory);
// ═══════ 转账函数 ═══════
// 安全转账(检查接收方能否处理NFT)
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) external;
// 普通转账(不检查接收方)
function transferFrom(address from, address to, uint256 tokenId) external;
// ═══════ 授权函数 ═══════
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);
// ═══════ 事件 ═══════
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的合约必须实现此接口
interface IERC721Receiver {
function onERC721Received(
address operator, address from, uint256 tokenId, bytes memory data
) external returns (bytes4);
}
onERC721Received,如果接收方返回正确的magic value(0x150b7a02),说明它能处理NFT;否则交易回滚。// contracts/CollectibleNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
/**
* @title CollectibleNFT
* @dev 带版税、可枚举的NFT收藏品
*/
contract CollectibleNFT is ERC721, ERC721URIStorage, ERC721Enumerable, Ownable {
using Strings for uint256;
uint256 public maxSupply;
uint256 public mintPrice;
uint256 private _nextTokenId;
string private _baseTokenURI;
// 版税信息 (EIP-2981)
uint256 public royaltyBasisPoints; // 基点,如500=5%
address public royaltyRecipient;
event Minted(address indexed minter, uint256 tokenId);
constructor(
string memory _name,
string memory _symbol,
uint256 _maxSupply,
uint256 _mintPrice,
uint256 _royaltyBps
) ERC721(_name, _symbol) Ownable(msg.sender) {
maxSupply = _maxSupply;
mintPrice = _mintPrice;
royaltyBasisPoints = _royaltyBps;
royaltyRecipient = msg.sender;
}
// ═══════ 铸造函数 ═══════
function mint() external payable {
require(_nextTokenId < maxSupply, "Max supply reached");
require(msg.value >= mintPrice, "Insufficient payment");
uint256 tokenId = _nextTokenId;
_nextTokenId++;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, string(abi.encodePacked(_baseTokenURI, tokenId.toString(), ".json")));
// 退回多余ETH
if (msg.value > mintPrice) {
(bool success,) = msg.sender.call{value: msg.value - mintPrice}("");
require(success, "Refund failed");
}
emit Minted(msg.sender, tokenId);
}
// ═══════ 版税 (EIP-2981) ═══════
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external view
returns (address, uint256)
{
require(_ownerOf(tokenId) != address(0), "Token not exists");
uint256 royaltyAmount = (salePrice * royaltyBasisPoints) / 10000;
return (royaltyRecipient, royaltyAmount);
}
// ═══════ 管理函数 ═══════
function setBaseURI(string memory baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
function setRoyalty(address _recipient, uint256 _bps) external onlyOwner {
require(_bps <= 1000, "Royalty too high"); // 最高10%
royaltyRecipient = _recipient;
royaltyBasisPoints = _bps;
}
function withdraw() external onlyOwner {
(bool success,) = owner().call{value: address(this).balance}("");
require(success, "Withdraw failed");
}
// ═══════ 重写函数 ═══════
function tokenURI(uint256 tokenId)
public view override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public view override(ERC721, ERC721URIStorage, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function _update(address to, uint256 tokenId, address auth)
internal override(ERC721, ERC721Enumerable)
returns (address)
{
return super._update(to, tokenId, auth);
}
}
// ERC1155核心接口
interface IERC1155 {
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] memory accounts, uint256[] memory ids) external view returns (uint256[] memory);
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes memory data) external;
function safeBatchTransferFrom(address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) external;
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
}
// 游戏物品示例
contract GameItems is ERC1155 {
uint256 public constant GOLD = 0; // 同质化:金币
uint256 public constant SWORD = 1; // 半同质化:有10把相同的剑
uint256 public constant LEGENDARY = 2; // 非同质化:唯一传说装备
constructor() ERC1155("https://game.example/api/{id}.json") {
_mint(msg.sender, GOLD, 1000000 * 10**18, ""); // 100万金币
_mint(msg.sender, SWORD, 10, ""); // 10把剑
_mint(msg.sender, LEGENDARY, 1, ""); // 1件传说装备
}
}
| 特性 | ERC721 | ERC1155 |
|---|---|---|
| 代币类型 | 仅非同质化 | 同质化+非同质化 |
| 每个ID的数量 | 始终为1 | 任意数量 |
| 批量转账 | 不支持(需多笔交易) | 支持(一笔交易多代币) |
| 合约数量 | 每种NFT一个合约 | 所有代币一个合约 |
| Gas效率 | 较低 | 较高 |
| 适用场景 | 艺术品、收藏品 | 游戏、多类型资产 |
1. safeTransferFrom防止什么问题?
2. ERC1155相比ERC721的核心优势是什么?
3. EIP-2981版税标准的作用是什么?
你已掌握NFT标准!从ERC721所有权验证到ERC1155多代币管理,从版税机制到安全转账,你拥有了创建数字资产的能力。
关键收获:
✅ ERC721接口与安全转账机制
✅ NFT合约完整实现(铸造+版税+枚举)
✅ ERC1155多代币标准与批量操作
✅ EIP-2981版税标准
✅ NFT应用场景与市场机制