ERC-721是以太坊上非同质化代币(NFT)的核心标准,定义了NFT的所有权、转移和授权规则。本文将通过一个简化版的ERC-721合约源码(基于Solidity),逐层剖析其实现逻辑、核心难点和关键设计思想。ERC-721的核心功能所有权与余额管理每个NFT通过唯一的to
ERC-721 是以太坊上非同质化代币(NFT)的核心标准,定义了 NFT 的所有权、转移和授权规则。本文将通过一个简化版的 ERC-721 合约源码(基于 Solidity),逐层剖析其实现逻辑、核心难点和关键设计思想。
每个 NFT 通过唯一的 tokenId
标识所有权,合约通过以下数据结构实现:
mapping(uint256 => address) internal _ownerOf; // TokenID → 所有者地址
mapping(address => uint256) internal _balanceOf; // 地址 → 持有代币数量
ownerOf(uint256 id)
方法验证代币存在性后返回所有者。balanceOf(address owner)
确保查询地址非零后返回余额。ERC-721 支持两种授权模式:
approve()
设置)。setApprovalForAll()
设置)。// 单代币授权映射
mapping(uint256 => address) internal _approvals;
// 全局授权映射
mapping(address => mapping(address => bool)) public isApprovedForAll;
代币转移是 ERC-721 最核心的功能,分为两种模式:
transferFrom()
直接更新所有权。safeTransferFrom()
额外检查接收方是否为合约,并验证其能否处理 NFT。// 基础转账逻辑(简化版)
function transferFrom(address from, address to, uint256 id) public {
require(_isApprovedOrOwner(from, msg.sender, id), "未授权");
_balanceOf[from]--;
_balanceOf[to]++;
_ownerOf[id] = to;
delete _approvals[id]; // 清除单代币授权
emit Transfer(from, to, id);
}
在转账或授权时,需验证调用者是否具备以下任一权限:
这一逻辑通过 _isApprovedOrOwner()
实现:
function _isApprovedOrOwner(address owner, address spender, uint256 id)
internal view returns (bool)
{
return spender == owner
|| isApprovedForAll[owner][spender]
|| spender == _approvals[id];
}
当接收方(to
)为合约时,必须调用其 onERC721Received
方法,并验证返回值是否为预定义的魔法值 0x150b7a02
(即 IERC721Receiver.onERC721Received.selector
)。这是为了防止代币误转入无法处理的合约。
// 安全转账逻辑片段
require(
to.code.length == 0 || // 接收方为普通地址
IERC721Receiver(to).onERC721Received(...) == IERC721Receiver.onERC721Received.selector,
"不安全的接收方"
);
ERC-721 必须实现 ERC-165 标准
,声明支持的接口。这是为了与其他合约(如市场平台)兼容,使其能够自动检测合约功能。
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return interfaceId == type(IERC721).interfaceId
|| interfaceId == type(IERC165).interfaceId;
}
在 safeTransferFrom
方法中,遵循 Checks-Effects-Interactions 模式:
_mint()
方法要求接收方地址非零,防止无效铸造。_burn()
将代币所有者设为零地址,并触发 Transfer
事件。所有关键操作必须触发对应事件,以便链下监听:
Transfer(address from, address to, uint256 id)
Approval(address owner, address spender, uint256 id)
ApprovalForAll(address owner, address operator, bool approved)
ownerOf()
和 getApproved()
中检查 owner != address(0)
。_mint()
检查 _ownerOf[id] == address(0)
。_mint
和 _burn
标记为 internal
,需通过包装函数(如 MyNFT.mint()
)调用,便于添加权限控制(如仅允许合约所有者调用)。tokenURI(uint256 id)
方法以支持链上/链下元数据。totalSupply()
和 tokenByIndex()
方法,支持代币遍历。// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
interface IERC165 {
function supportsInterface(bytes4 interfaceID)
external
view
returns (bool);
}
interface IERC721 is IERC165 {
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 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 getApproved(uint256 tokenId)
external
view
returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator)
external
view
returns (bool);
}
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
contract ERC721 is IERC721 {
// 定义事件
event Transfer(
address indexed from, address indexed to, uint256 indexed id
);
event Approval(
address indexed owner, address indexed spender, uint256 indexed id
);
event ApprovalForAll(
address indexed owner, address indexed operator, bool approved
);
// 记录每个NFT与其所有者的映射关系
mapping(uint256 => address) internal _ownerOf;
// 记录每个地址所持有的代币数量
mapping(address => uint256) internal _balanceOf;
// 记录每个代币被授权的地址
mapping(uint256 => address) internal _approvals;
// 全局授权映射
mapping(address => mapping(address => bool)) public isApprovedForAll;
// 实现 IERC165 接口的方法
function supportsInterface(bytes4 interfaceId)
external
pure
returns (bool)
{
return interfaceId == type(IERC721).interfaceId
|| interfaceId == type(IERC165).interfaceId;
}
// 根据tokenId 获取其所有者
function ownerOf(uint256 id) external view returns (address owner) {
owner = _ownerOf[id];
require(owner != address(0), "token doesn't exist");
}
// 获取账户余额
function balanceOf(address owner) external view returns (uint256) {
require(owner != address(0), "owner = zero address");
return _balanceOf[owner];
}
function setApprovalForAll(address operator, bool approved) external {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved); // 触发ApprovalForAll 事件
}
function approve(address spender, uint256 id) external {
address owner = _ownerOf[id];
require(
msg.sender == owner || isApprovedForAll[owner][msg.sender],
"not authorized"
);
_approvals[id] = spender; //更新代币授权
emit Approval(owner, spender, id); // 触发 Approval 事件
}
function getApproved(uint256 id) external view returns (address) {
require(_ownerOf[id] != address(0), "token doesn't exist");
return _approvals[id];
}
function _isApprovedOrOwner(address owner, address spender, uint256 id)
internal
view
returns (bool)
{
return (
spender == owner || isApprovedForAll[owner][spender]
|| spender == _approvals[id]
);
}
function transferFrom(address from, address to, uint256 id) public {
require(from == _ownerOf[id], "from != owner");
require(to != address(0), "transfer to zero address");
require(_isApprovedOrOwner(from, msg.sender, id), "not authorized");
_balanceOf[from]--;
_balanceOf[to]++;
_ownerOf[id] = to;
delete _approvals[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(address from, address to, uint256 id) external {
transferFrom(from, to, id);
require(
to.code.length == 0
|| IERC721Receiver(to).onERC721Received(msg.sender, from, id, "")
== IERC721Receiver.onERC721Received.selector,
"unsafe recipient"
);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) external {
transferFrom(from, to, id);
require(
to.code.length == 0
|| IERC721Receiver(to).onERC721Received(msg.sender, from, id, data)
== IERC721Receiver.onERC721Received.selector,
"unsafe recipient"
);
}
function _mint(address to, uint256 id) internal {
require(to != address(0), "mint to zero address");
require(_ownerOf[id] == address(0), "already minted");
_balanceOf[to]++;
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal {
address owner = _ownerOf[id];
require(owner != address(0), "not minted");
_balanceOf[owner] -= 1;
delete _ownerOf[id];
delete _approvals[id];
emit Transfer(owner, address(0), id);
}
}
contract MyNFT is ERC721 {
function mint(address to, uint256 id) external {
_mint(to, id);
}
function burn(uint256 id) external {
require(msg.sender == _ownerOf[id], "not owner");
_burn(id);
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!