DEV Community

Cover image for How to Implement a Whitelist in NFT contracts using Merkle Tree
Abdul Azeez V
Abdul Azeez V

Posted on

How to Implement a Whitelist in NFT contracts using Merkle Tree

Story

I was recently trying to make a fan club nft for a web3 community. The project required that only members could mint a limited NFT collection, so I needed to implement a whitelist for the minting function. Here are some solutions I found online:

  • Store the whitelist onchain in nft contract
  • Store whitelist in serverside and call nft minting from there only by a preselected wallet.
  • Using merkle tree implementation

I chose the third one cause it has more advantages over others.

Undrestanding merkle tree

Merkle tree

Merkle tree is a tree data structure where each non-leaf node is a hash of it's child nodes

How it is solving our problem ?

If we generate a merkle tree using our whitelist we only need to store the single merkle root hash onchain, not the entire list. By just using the merkle root hash and the merkle proof created on server side, we can verify wether the address is in whitelist or not.

This method reduces the amount of data stored onchain and gas fee needed for the operation.

Read to undrestand more: link

Merkle tree white list implemetation

Logic

First, create the whitelist database in the server and compute merkle root hash of the whitelist.

import { ethers } from 'ethers'
import { MerkleTree } from 'merkletreejs'

// Your whitelist from database
const whitelist = [
  '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
  '0x90F79bf6EB2c4f870365E785982E1f101E93b906',
  '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65',
  '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc',
  '0x976EA74026E726554dB657fA54763abd0C3a0aa9',
]

const { keccak256 } = ethers.utils
let leaves = whitelist.map((addr) => keccak256(addr))
const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true })

// We need to store this hash in NFT contract
const merkleRootHash = merkleTree.getHexRoot()

// 0x09485889b804a49c9e383c7966a2c480ab28a13a8345c4ebe0886a7478c0b73d
Enter fullscreen mode Exit fullscreen mode

Create a NFT contract using any of the NFT standards (ERC721, ERC1155 etc). Add functions that can verify the merkle proof.


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

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

contract MerkleTreeWhitelistContract is Ownable {

    // merkle root hash computed before
    bytes32 public merkleRoot = 0x09485889b804a49c9e383c7966a2c480ab28a13a8345c4ebe0886a7478c0b73d;

    // function to change metkle root after deployment
    function setMerkleRoot(bytes32 merkleRootHash) external onlyOwner
    {
        merkleRoot = merkleRootHash;
    }

    // function to verify merkle proof
    function verifyAddress(bytes32[] calldata _merkleProof) private 
    view returns (bool) {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        return MerkleProof.verify(_merkleProof, merkleRoot, leaf);
    }

    // White list checker function
    function whitelistFunc(bytes32[] calldata _merkleProof) external
    {
        require(verifyAddress(_merkleProof), "INVALID_PROOF");

        // Do some useful stuff
    }
}
Enter fullscreen mode Exit fullscreen mode

When the user clicks a button on your website, you send the request to your server with the user's address. If the user is in the whitelist, create the Merkle proof on your server:

import { ethers } from 'ethers'
import { MerkleTree } from 'merkletreejs'

export default async function handler() {

  // whitelist stored in database
  const whitelist = [
    '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
    '0x90F79bf6EB2c4f870365E785982E1f101E93b906',
    '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65',
    '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc',
    '0x976EA74026E726554dB657fA54763abd0C3a0aa9',
  ]

  // Variable to store merkle proof
  let proof = []

  const userWalletAddress = 'PASTE_USER_WALLET_ADDRESS_HERE'

  if (whitelist.includes(userWalletAddress)) {
    const { keccak256 } = ethers.utils
    let leaves = whitelist.map((addr) => keccak256(addr))
    const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true })
    let hashedAddress = keccak256(userWalletAddress)
    proof = merkleTree.getHexProof(hashedAddress)
  }

  // Send this proof to NFT contract along with other data
}
Enter fullscreen mode Exit fullscreen mode

Using this merkle proof and merkle root already stored onchain, the contract can verify whether the address allowed to mint the nft operation or not.

Problem solved 😻

Github | LinkedIn

Top comments (0)