为了避免大量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文章来源:https://www.toymoban.com/news/detail-517524.html
// 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文章来源地址https://www.toymoban.com/news/detail-517524.html
// 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 ================================ */
}
到了这里,关于solidty实现默克尔树空投的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!