solidty实现默克尔树空投

为了避免大量gas消耗,我们将在链下要根据白名单数据生成 Merkle Tree,我们这里使用 Javascript 来完成相关工作。

首先需要安装两个依赖包:

npm install --save keccak256 merkletreejs

实例代码如下:

const {ethers} = require("ethers");
const { MerkleTree } = require('merkletreejs');
const keccak256 = require('keccak256');
async function main() {

// 白名单地址,这里采用了硬编码,实际开发应该从数据库读取
// 这里我们随机生成几个地址,作为白名单示例
let list =[{address:"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"},{address:"0x39Ef50bd29Ae125FE10C6a909E42e1C6a94Dde29"},{address:"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"},{address:"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"}];
    //叶子结点数据
  let leafs = [];
  for (let k = 0; k < list.length; k++) {
    let leaf = ethers.utils.solidityKeccak256(["address"], [list[k].address]);
    leafs.push(leaf);
  }

  //树根
  let tree = new MerkleTree(leafs, keccak256, { sort: true });
 // 打印查看 Root 数据,需要设置到合约中
  let root = tree.getHexRoot();
  console.log("root = ",root);

  const rootHash = tree.getRoot();
  console.log("rootHash = ",rootHash);
  //叶子proof
  let proofs = [];
  leafs.map((item) => {
    proofs.push(tree.getHexProof(item));
  });

  let res = [];
  for (let index = 0; index < list.length; index++) {
    res.push([list[index].address, proofs[index]]);
  }
   //组装的数据
  console.log("res = ",res);
}


main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

 首先在Airdrop 合约里面设置 root

//set root
    function setMerkleRoot(bytes32 _root) external onlyOwner {
        root = _root;
    }

 然后调用getDrop 获取空投

完整的代码如下:

Airdrop.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./NFT.sol";

contract Airdrop is Ownable {
    bytes32 root;
    mapping(address => bool) isGet;
    using SafeMath for uint256;
    constructor()  {
        
    }
    NFT public nft = NFT(0xD4Fc541236927E2EAf8F27606bD7309C1Fc2cbee) ;
    address public constant feeAddress = 0xA38f69a8177BF32c65234BA340101e9441A5BEFa;
    uint256 public nftPrice = 0.01 ether;
    uint256 public totalNft = 300;
    uint256 public mintCount = 0;
    uint256 public startTime = 1656984610;
    uint256 _startTokenId = 10;
    /* ============================================== EVENT START ================================================ */
    event GetDrop(address sender,uint256 number,uint256[] tokens,uint256 totalMoney,uint256 createTime);
    event SetNFTAddress(address sender,address oldAddress,address newAddress,uint256 createTime);
    event SetSaleTotal(address sender,uint256 oldNumber,uint256 newNumber,uint256 createTime);
    event SetStartTime(address sender,uint256 oldStartTime,uint256 newStartTime,uint256 createTime);
    event SetStartToken(address sender,uint256 oldStartToken,uint256 newStartToken,uint256 createTime);
    event SetNftPrice(address sender,uint256 oldPrice,uint256 newPrice,uint256 createTime);
    /*=============================================== EVENT END ================================================== */
    /* =================================== Mutable Functions START ================================ */
    
    //set root
    function setMerkleRoot(bytes32 _root) external onlyOwner {
        root = _root;
    }
   //varify and get drop
    function getDrop(
        address _address,
        uint256 _number,
        bytes32[] calldata _proofs
    ) external payable{
        require(startTime<=block.timestamp);
        require(isGet[_address] == false, "has got");
        uint256 totalMoney = nftPrice.mul(_number);
        require(msg.value == totalMoney,"amount fail");
        bytes32 _leaf = keccak256(abi.encodePacked(_address));
        bool _verify = MerkleProof.verify(_proofs, root, _leaf);
        require(_verify, "fail");
        isGet[_address] = true;
        uint256[] memory _tokens = buyNft(_number);
        payable(feeAddress).transfer(msg.value);
        emit GetDrop(_msgSender(),_number,_tokens,totalMoney,block.timestamp);
    }

    function setNFTAddress(address _address) public onlyOwner{
        emit SetNFTAddress(_msgSender(),address(nft),_address,block.timestamp);
        nft = NFT(_address);
    }

    function buyNft(uint256 number) internal returns(uint256[] memory){
        require(number>0,"min limit 1");
        require(number<=residual(),"Exceed maximum mint");
        mintCount = mintCount.add(number);
        uint256 tokenBase = _startTokenId;
        _startTokenId = _startTokenId.add(number);
        uint256[] memory _tokens = new uint256[](number);
        for(uint256 i=0;i<number;i++){
            uint256 tokenId = tokenBase.add(i);
            _tokens[i] = tokenId;
            nft.safeMint(_msgSender(), tokenId);
        }
        return _tokens;
    }

    

    function setTotalNft(uint256 _total) public onlyOwner{
        emit SetSaleTotal(_msgSender(),totalNft,_total,block.timestamp);
        totalNft = _total;
    }

    function setStartTime(uint256 _startTime) public onlyOwner{
        emit SetStartTime(_msgSender(),startTime,_startTime,block.timestamp);
        startTime = _startTime;
    }
    function _setStartTokenId(uint256 _startToken) public onlyOwner{
        require(_startToken > _startTokenId,"_startToken must > old number");
        emit SetStartToken(_msgSender(),_startTokenId,_startToken,block.timestamp);
        _startTokenId = _startToken;
    }
    function _setNftPrice(uint256 _price) public onlyOwner{
        require(_price > 0,"_startToken must > old number");
        emit SetNftPrice(_msgSender(),nftPrice,_price,block.timestamp);
        nftPrice = _price;
    }
     /* =================================== Mutable Functions END ================================ */
    /* ====================================== View Functions START ================================ */
    function hasGet(address _address) public view returns (bool) {
        return isGet[_address];
    }
    function residual() public view returns(uint256){
        return totalNft.sub(mintCount);
    }
    /* ====================================== View Functions END ================================ */
}

NFT.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract NFT is ERC721PresetMinterPauserAutoId {
    using Counters for Counters.Counter;
    using EnumerableSet for EnumerableSet.UintSet;
    using SafeMath for uint256;
    // Count the amount mint
    Counters.Counter private _tokenCounter;
    EnumerableSet.UintSet private _tokenList;
    // limit  mint max 10000
    uint256 constant totalNumber = 10240;
    mapping(address=>EnumerableSet.UintSet)  private userTokens;
    address immutable dead = 0x000000000000000000000000000000000000dEaD;
    //base
    string private baseTokenURI = "ipfs://QmRcb6Mpwxwnjr3d5hPiXj6vdi4hZv6WGeGQWYgib2BDpA/";
    bytes32 public constant SUPER_ROLE = keccak256("SUPER_ROLE");
    constructor() ERC721PresetMinterPauserAutoId("Hash Eagle", "Eagle", "") {
        _setupRole(SUPER_ROLE, _msgSender());
    }
    uint256 constant openTotal = 7000;
    uint256 constant foundTotal = 3240;
    EnumerableSet.UintSet _superTokens;
    EnumerableSet.UintSet _openMints;
    EnumerableSet.UintSet _foundMints;
    /* ==================================== EVENT START ======================================== */
    event SafeMint(address indexed sender,address indexed to, uint256 tokenId,  uint256 createtime);
    event ChangeURI(address indexed sender, string oldUri, string newUri, uint256 createtime);
    event Burn(address indexed sender, address owner,uint256 tokenId, uint256 createtime);
    event AddSuperId(address indexed sender, uint256[] tokenIds, uint256 createtime);
    /* ==================================== EVENT END ======================================== */

    /* ==================================== ERROR START ======================================== */
    error Unauthorized();
    /* ==================================== ERROR END ======================================== */


    /* =================================== Mutable Functions START ================================ */
    function safeMint(address _to, uint256 _tokenId) public onlyRole(MINTER_ROLE){
        require(_openMints.length()<openTotal,"total limit mint 7000");
        require(!_superTokens.contains(_tokenId),"is not super tokenId");
        _saMinit(_to,_tokenId);
        require(_openMints.add(_tokenId),"_openMints add error");
    }
    function _saMinit(address _to, uint256 _tokenId)  internal {
        require(_tokenId>0,"tokenId can not zero");
        require(mintCount()<totalNumber,"mint  limit");
        require(!_tokenList.contains(_tokenId),"cannot repeat mint");
        require(_tokenList.length()<totalNumber,"token  mint limit 10240");
        //mint(If _tokenId already exists, the call will return with an error)
        _mint(_to, _tokenId);
        
        require(userTokens[_to].add(_tokenId),"userTokens add error");
        require(_tokenList.add(_tokenId),"_tokenList add error");
        emit SafeMint(_msgSender(),_to,_tokenId,block.timestamp);
    }
    function safeMintSuperId(address _to, uint256 _tokenId) public onlyRole(SUPER_ROLE){
        require(_foundMints.length()<foundTotal,"total limit mint 3240");
        // require(_superTokens.contains(_tokenId),"is not super tokenId");
        _saMinit(_to,_tokenId);
        require(_foundMints.add(_tokenId),"_foundMints add error");
    }

    function changeURI(string calldata _tokenURI) external onlyRole(MINTER_ROLE) {
        require(keccak256(abi.encodePacked(_tokenURI)) != keccak256(abi.encodePacked(baseTokenURI)), "same tokenURI");
        emit ChangeURI( _msgSender(), baseTokenURI, _tokenURI, block.timestamp);
        baseTokenURI = _tokenURI;
    }


    function addSuperId(uint256[] calldata tokenIds) public  onlyRole(SUPER_ROLE){
        require(tokenIds.length>0 && tokenIds.length<=30 ,"length must >0 or length <=30");
        uint256 total = 0;
        total = total.add(_foundMints.length()).add(tokenIds.length).add(_superTokens.length());
        require(total<=foundTotal,"limit 3240");
        for(uint256 i=0;i<tokenIds.length;i++){
            require(tokenIds[i]>0,"tokenId can not zero");
            require(!_exists(tokenIds[i]),"It's already cast");
            require(!_superTokens.contains(tokenIds[i]),"It's already cast");
            require(_superTokens.add(tokenIds[i]),"_superTokens add error");
        }
        emit AddSuperId(_msgSender(), tokenIds, block.timestamp);
    }

    //get mint count
    function mintCount() view public returns(uint256){
        return _tokenList.length();
    }
    function mint(address /*to*/) public virtual override(ERC721PresetMinterPauserAutoId){
        revert Unauthorized();
    }

    function _burn(uint256 tokenId) internal override(ERC721) {
        address owner = ERC721.ownerOf(tokenId);
        transferFrom(owner, dead, tokenId);
        emit Burn(_msgSender(), owner,tokenId, block.timestamp);
    }
    /* =================================== Mutable Functions END ================================ */

    /* ====================================== View Functions START ================================ */
    function getMyNFT() external view returns(uint256[] memory) {
        return userTokens[_msgSender()].values();
    }
    function getSuperIds() public view  returns (uint256[] memory) {
        return _superTokens.values();
    }
    function totalSupply() public view virtual override(ERC721Enumerable) returns (uint256) {
        return totalNumber;
    }

    function getOpenMints() public view  returns (uint256[] memory) {
        return _openMints.values();
    }
    function getFoundMints() public view  returns (uint256[] memory) {
        return _foundMints.values();
    }

    function _baseURI() internal view virtual override(ERC721PresetMinterPauserAutoId) returns(string memory)
    {
        return baseTokenURI;
    }
    /* ====================================== View Functions END ================================ */
}

猜你喜欢

转载自blog.csdn.net/toume/article/details/125621683
今日推荐