在看 ERC721 之前,请先看 ERC165。
例子
我们沿用 BAYC 的方式来弄 ERC721 例子。
1 | pragma solidity ^0.8.0; |
这里说一下细节 BAYC 一共有 10000 个,序号是 0 - 9999。
其中,一个例子 https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/0 的内容如下
1 | {"image":"ipfs://QmRRPWG96cmgTn2qSzjwr2qvfNEuhunv6FNeMFGa9bx6mQ","attributes":[{"trait_type":"Earring","value":"Silver Hoop"},{"trait_type":"Background","value":"Orange"},{"trait_type":"Fur","value":"Robot"},{"trait_type":"Clothes","value":"Striped Tee"},{"trait_type":"Mouth","value":"Discomfort"},{"trait_type":"Eyes","value":"X Eyes"}]} |
里面有图片的内容还有图片的描述信息。
解析
观看 @openzeppelin/contracts/token/ERC721/ERC721.sol 会发现其继承了
ContextERC165IERC721IERC721Metadata
IERC721Metadata
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
合约内容如下
1 | pragma solidity ^0.8.0; |
IERC721
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
合约内容如下
1 | pragma solidity ^0.8.0; |
IERC721事件
IERC721 包含 3 个事件,其中 Transfer 和 Approval 事件在 ERC20 中也有。
Transfer事件:在转账时被释放,记录代币的发出地址from,接收地址to和tokenid。Approval事件:在授权时释放,记录授权地址owner,被授权地址approved和tokenid。ApprovalForAll事件:在批量授权时释放,记录批量授权的发出地址owner,被授权地址operator和授权与否的approved。
IERC721函数
balanceOf:返回某地址的NFT持有量balance。ownerOf:返回某tokenId的主人owner。transferFrom:普通转账,参数为转出地址from,接收地址to和tokenId。safeTransferFrom:安全转账(如果接收方是合约地址,会要求实现ERC721Receiver接口)。参数为转出地址from,接收地址to和tokenId。approve:授权另一个地址使用你的NFT。参数为被授权地址approve和tokenId。getApproved:查询tokenId被批准给了哪个地址。setApprovalForAll:将自己持有的该系列NFT批量授权给某个地址operator。isApprovedForAll:查询某地址的NFT是否批量授权给了另一个operator地址。safeTransferFrom:安全转账的重载函数,参数里面包含了data。
表面上 ERC721 需要实现 IERC721 的接口,但是背地里有更多更细节的实现。
ERC721 实现
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
IERC721Receiver
如果一个合约没有实现 ERC721 的相关函数,转入的 NFT 就进了黑洞,永远转不出来了。
为了防止误转账,ERC721 实现了 safeTransferFrom()安全转账函数,目标合约必须实现了 IERC721Receiver 接口才能接收 ERC721 代币,不然会revert。「实现了该接口相当于告诉外界,你有收取 ERC721 的能力,可以参考 ERC165」IERC721Receiver 接口只包含一个 onERC721Received() 函数。
1 | // ERC721接收者接口:合约必须实现这个接口来通过安全转账接收ERC721 |
我们看下 ERC721 利用 _checkOnERC721Received 来确保目标合约实现了 onERC721Received() 函数(返回 onERC721Received 的 selector):
1 | function _checkOnERC721Received( |
有了这些之后,再看 ERC721.sol 主文件就会发现一切都很简单了。