0%

solidity | ERC721

在看 ERC721 之前,请先看 ERC165

例子

我们沿用 BAYC 的方式来弄 ERC721 例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract BAYC is ERC721{
uint public MAX_APES = 10000; // 总量

// 构造函数
constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){
}

//BAYC的baseURI为ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
function _baseURI() internal pure override returns (string memory) {
return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
}

// 铸造函数
function mint(address to, uint tokenId) external {
require(tokenId >= 0 && tokenId < MAX_APES, "tokenId out of range");
_mint(to, tokenId);
}
}

这里说一下细节 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 会发现其继承了

IERC721Metadata

  • import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";

合约内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.8.0;

import "../IERC721.sol";

interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);

/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);

/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}

IERC721

  • import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

合约内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

interface IERC721 is IERC165 {
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,
bytes calldata data
) external;
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);
}

IERC721事件

IERC721 包含 3 个事件,其中 TransferApproval 事件在 ERC20 中也有。

  • Transfer事件:在转账时被释放,记录代币的发出地址from,接收地址totokenid
  • Approval事件:在授权时释放,记录授权地址owner,被授权地址approvedtokenid
  • ApprovalForAll事件:在批量授权时释放,记录批量授权的发出地址owner,被授权地址operator和授权与否的approved

IERC721函数

  • balanceOf:返回某地址的NFT持有量 balance
  • ownerOf:返回某 tokenId 的主人 owner
  • transferFrom:普通转账,参数为转出地址 from,接收地址 totokenId
  • safeTransferFrom:安全转账(如果接收方是合约地址,会要求实现 ERC721Receiver 接口)。参数为转出地址 from ,接收地址 totokenId
  • approve:授权另一个地址使用你的NFT。参数为被授权地址 approvetokenId
  • 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 的能力,可以参考 ERC165IERC721Receiver 接口只包含一个 onERC721Received() 函数。

1
2
3
4
5
6
7
8
9
// ERC721接收者接口:合约必须实现这个接口来通过安全转账接收ERC721
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata data
) external returns (bytes4);
}

我们看下 ERC721 利用 _checkOnERC721Received 来确保目标合约实现了 onERC721Received() 函数(返回 onERC721Receivedselector):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function _checkOnERC721Received(
address from,
address to,
uint tokenId,
bytes memory _data
) private returns (bool) {
if (to.isContract()) {
return
IERC721Receiver(to).onERC721Received(
msg.sender,
from,
tokenId,
_data
) == IERC721Receiver.onERC721Received.selector;
} else {
return true;
}
}

有了这些之后,再看 ERC721.sol 主文件就会发现一切都很简单了。

请我喝杯咖啡吧~