How to Implement a Whitelist in NFT contracts using Merkle Tree
Tags: web3, nft, ethereum, blockchain
Published: 2024-05-19 10:31:23

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4co68r5juaptdblvcrt.png)

>  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](https://medium.com/coinmonks/merkle-tree-a-simple-explanation-and-implementation-48903442bc08)

## Merkle tree white list implemetation

![Logic](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qr24pjxvm21wmo75t3md.png)

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


```js
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
```

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

```js

// 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
    }
}
```

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:

```js
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
}
```

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](https://github.com/abdxzi)  |  [LinkedIn](https://www.linkedin.com/in/abdul-azeez-v/)