Skip to main content

04-2-写一个免费铸造的APE

1.我们来利用ERC721来写一个免费铸造的WTF APE,总量设置为10000

只需要重写一下mint()和baseURI()函数即可。由于baseURI()设置的和BAYC一样,元数据会直接获取无聊猿的,类似RRBAYC:

// SPDX-License-Identifier: MIT
// by 0xAA
pragma solidity ^0.8.4;

import "./ERC721.sol";

contract WTFApe 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);
}
}

2.发行ERC721NFT

有了ERC721标准后,在ETH链上发行NFT变得非常简单。现在,我们发行属于我们的NFT。

在Remix上编译好ERC721合约和WTFApe合约(按照顺序),在部署栏点击下按钮,输入构造函数的参数,name_和symbol_都设为WTF,然后点击transact键进行部署。

3.ERC165与ERC721详解

上面说到,为了防止NFT被转到一个没有能力操作NFT的合约中去,目标必须正确实现ERC721TokenReceiver接口:

interface ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}

只要某个contract类型实现了上述的ERC721TokenReceiver接口(更具体而言就是实现了onERC721Received这个函数),该contract类型就对外表明了自己拥有管理NFT的能力。当然操作NFT的逻辑被实现在该合约其他的函数中。

ERC721标准在执行safeTransferFrom的时候会检查目标合约是否实现了onERC721Received函数,这是一种利用ERC165思想进行的操作。

3-1.那究竟什么是ERC165呢?

ERC165是一种对外表明自己实现了哪些接口的技术标准。就像上面所说的,实现了一个接口就表明合约拥有种特殊能力。

有一些合约与其他合约交互时,期望目标合约拥有某些功能,那么合约之间就能够通过ERC165标准对对方进行查询以检查对方是否拥有相应的能力。

3-2.以ERC721合约为例

当外部对某个合约进行检查其是否是ERC721时,怎么做? 按照这个说法,检查步骤应该是首先检查该合约是否实现了ERC165, 再检查该合约实现的其他特定接口。此时该特定接口是IERC721. IERC721的是ERC721的基本接口(为什么说基本,是因为还有其他的诸如ERC721Metadata ERC721Enumerable 这样的拓展):

/// 注意这个**0x80ac58cd**
/// **⚠⚠⚠ Note: the ERC-165 identifier for this interface is 0x80ac58cd. ⚠⚠⚠**
interface ERC721 /* is ERC165 */ {
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);

function ownerOf(uint256 _tokenId) external view returns (address);

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

function approve(address _approved, uint256 _tokenId) external payable;

function setApprovalForAll(address _operator, bool _approved) external;

function getApproved(uint256 _tokenId) external view returns (address);

function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

0x80ac58cd= bytes4(keccak256(ERC721.Transfer.selector) ^ keccak256(ERC721.Approval.selector) ^ ··· ^keccak256(ERC721.isApprovedForAll.selector)),这是ERC165规定的计算方式。

那么,类似的,能够计算出ERC165本身的接口(它的接口里只有一个 function supportsInterface(bytes4 interfaceID) external view returns (bool); 函数,对其进行bytes4(keccak256(supportsInterface.selector))

得到0x01ffc9a7。此外,ERC721还定义了一些拓展接口,比如ERC721Metadata ,长这样:

///  Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface ERC721Metadata /* is ERC721 */ {
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) external view returns (string); // 这个很重要,前端展示的小图片的链接都是这个函数返回的
}

这个0x5b5e139f 的计算就是:

bytes4(keccak256(ERC721Metadata.name.selector)^keccak256(ERC721Metadata.symbol.selector)^keccak256(ERC721Metadata.tokenURI.selector))

solamte实现的ERC721.sol是怎么完成这些ERC165要求的特性的呢?

function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}

没错就这么简单。当外界按照link1 的步骤去做检查的时候,如果外界想检查这个合约是否实现了165,好说,就是supportsInterface函数在入参是0x01ffc9a7时必须返回true,在入参是0xffffffff时,返回值必须是false。上述实现完美达成要求。

当外界想检查这个合约是否是ERC721的时候,好说,入参是0x80ac58cd 的时候表明外界想做这个检查。返回true。

并且由于该函数是virtual的。因此该合约的使用者可以继承该合约,然后继续实现ERC721Enumerable 接口。实现完里面的什么totalSupply 啊之类的函数之后,把继承的supportsInterface重实现为

function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f || // ERC165 Interface ID for ERC721Metadata
interfaceId == 0x780e9d63; // ERC165 Interface ID for ERC721Enumerable
}