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 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
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
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
}
}
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
}
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 😻
Top comments (0)