I want to create an NFT contract and mint some NFTs to some addresses. I have a function as follow:

function airdrop(uint256 tokenId, address _receiver)
    onlyOwner {
    _safeMint(_receiver, tokenId); }

This function is fine but the gas fee is high since I need to call this function for several times. Is there any other way to do airdrop so that I can reduce the gas fee? I will spend the gas fee so the method of asking receivers to pay is not expected.

  • I think I can help, as I do have some experience with optimizing NFT minting. But I would need to see more of your code. For example, I would need to see the NFT structure and how you whitelisted addresses for airdrop, or how you call the function in the first place.
    – Sky
    Jun 8, 2022 at 13:29

1 Answer 1


you can use lazy mint with signatures but if you have large airdrops you should use the Merkle tree. you get the list of the addresses and need to install merkletreejs and keccak256.



const [owner, user1, user2, user3, user4] = await ethers.getSigners();
    let listOfAddresses = [
const leaf = listOfAddresses.map(addrs=> ethers.utils.solidityKeccak256(['address'], [addrs]));
const merkleTree = new MerkleTree(leaf, keccak256,{sortPairs: true})
const root = merkleTree.getHexRoot(); 
// deploy the contract and add the root of the tree
const deployExample = await instance.deploy('name', 'symbol', merkleTree.getHexRoot());

 const user4ClaimingNFT = merkleTree.getHexProof(leaf[3]);

 // when a user claim an airdrop ... 
 await instance.connect(user4).airdrop(user4.address, user4ClaimingNFT)
 // check the balance ..
 await instance.balanceOf(buyer4.address).then(res=>{
        console.log("balanceOf ", res);

in the contract :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MerkleExample is ERC721 {

bytes32 immutable public root;
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
mapping (address => bool) private isMinted;

constructor(string memory name, string memory symbol, bytes32 _root)
ERC721(name, symbol)
    root = _root;

function airdrop(address account, bytes32[] calldata proof) external
    require(account==msg.sender,"not owner" );
    // you can have some requirement by checking how many mint per address
    // or map address -> true/false something like isMinted:
    require(!isMinted[account],"already minted");
    require(_verify(_leaf(account), proof), "Invalid proof");
    isMinted[account] = true;

function safeMint(address to) private  {
    uint256 tokenId = _tokenIdCounter.current();
    _safeMint(to, tokenId);

function _leaf(address account)
public pure returns (bytes32)
    return keccak256(abi.encodePacked(account));

function _verify(bytes32 leaf, bytes32[] memory proof)
public view returns (bool)
    return MerkleProof.verify(proof, root, leaf);

Not the answer you're looking for? Browse other questions tagged or ask your own question.