预售是运营NFT的常见手段。
- 链上预售白名单
方法是在合约的存储中简单地包含一个地址映射变量,将每个地址映射到一个布尔值,或者每个地址映射到该地址允许的铸币数量。
- mapping(address => uint8) _allowList;
- function setAllowList(
- address[] calldata addresses,
- uint8 numAllowedToMint
- ) external onlyOwner {
- for (uint256 i = 0; i < addresses.length; i++) {
- _allowList[addresses[i]] = numAllowedToMint;
- }
- }
这种方法使用起来简单,但在配置白名单时,将会耗费大量的gas费用。
- 默克尔树
使用openzeppelin的MerkleProof.verify进行验证。
在合约中只需要存储roothash,验证时用户需传入默克尔树证明,新增叶子节点时,也只需要更新roothash。
验证通过后可领取空投,并将状态改为已领取,避免重复领取。
- import '@openzeppelin/contracts/utils/cryptography/MerkleProof.sol';
- ...
- // declare bytes32 variables to store each root (a hash)
- bytes32 public genesisMerkleRoot;
- bytes32 public authorsMerkleRoot;
- bytes32 public presaleMerkleRoot;
- ...
- // separate functions to set the roots of each individual Merkle Tree
- function setGenesisMerkleRoot(bytes32 _root) external onlyOwner {
- genesisMerkleRoot = _root;
- }
- function setAuthorsMerkleRoot(bytes32 _root) external onlyOwner {
- authorsMerkleRoot = _root;
- }
- function setPresaleMerkleRoot(bytes32 _root) external onlyOwner {
- presaleMerkleRoot = _root;
- }
- ...
- // create merkle leaves from supplied data
- function _generateGenesisMerkleLeaf(
- address _account,
- uint256 _tokenId
- ) internal pure returns (bytes32) {
- return keccak256(abi.encodePacked(_tokenId, _account));
- }
- function _generateAuthorsMerkleLeaf(
- address _account,
- uint256 _tokenCount
- ) internal pure returns (bytes32) {
- return keccak256(abi.encodePacked(_account, _tokenCount));
- }
- function _generatePresaleMerkleLeaf(
- address _account,
- uint256 _max
- ) internal pure returns (bytes32) {
- return keccak256(abi.encodePacked(_max, _account));
- }
- ...
- // function to verify that the given leaf belongs to a given tree using its root for comparison
- function _verifyMerkleLeaf(
- bytes32 _leafNode,
- bytes32 _merkleRoot,
- bytes32[] memory _proof ) internal view returns (bool) {
- return MerkleProof.verify(_proof, _merkleRoot, _leafNode);
- }
然后,调用每个 mint/claim 函数都需要使用发送者的地址来生成和验证叶子节点。 例如,当使用 for loop 铸造多个代币时
- require(
- _verifyMerkleLeaf(
- _generateGenesisMerkleLeaf(
- msg.sender,
- _tokenIds[i]),
- genesisMerkleRoot,
- _proofs[i]
- ), "Invalid proof, you don't own that Token ID");