Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Merkle Proof Allowlist for NFT Minting

Hello! Welcome back to your course on Ethereum development.

In our last lesson, we secured your NFT's metadata using decentralized storage with IPFS, ensuring its permanence. Now, we'll address another common requirement for NFT projects: managing exclusive access to the minting process.

Many projects offer a pre-sale or a discounted mint price to a select group of early supporters or community members. This list of eligible users is often called an "allowlist" (or "whitelist"). The naive approach would be to store this entire list of addresses in the smart contract, but for a list of thousands, this would be prohibitively expensive in terms of gas fees for both deployment and verification.

This lesson introduces an elegant and gas-efficient solution to this problem using a powerful cryptographic tool: the Merkle tree.

Learning Outcome: By the end of this lesson, you will be able to implement an NFT mint with an allowlist using Merkle proofs.

We will cover the theory behind Merkle trees, how to generate them off-chain using JavaScript, and how to implement the on-chain verification logic in your Solidity smart contract.

1. The Core Concept: What is a Merkle Tree?

A Merkle tree is a data structure that allows you to create a single, unique cryptographic fingerprint—the Merkle root—for a large set of data. Its key feature is that you can prove that a specific piece of data belongs to the set using a small "proof," without needing to have the entire dataset on-chain. This makes it incredibly efficient for our allowlist use case.

The process involves:

  1. Taking each item in our list (e.g., an Ethereum address).
  2. Hashing each item to create a "leaf" node.
  3. Recursively pairing and hashing these nodes together until only one hash remains: the Merkle root.

For a clear, visual explanation of how a Merkle tree is constructed, let's watch the following video.

Merkle Trees and Merkle Proofs in Solidity Smart Contracts

This video from Ciara Nightingale provides an excellent conceptual breakdown of what a Merkle tree is and how the root is calculated step-by-step.

Please watch the segments 'What is a Merkle Tree?' (01:43 - 03:11) and 'Calculating the Merkle Root' (03:11 - 06:15). Focus on understanding the hierarchical hashing process, from individual data points (leaves) to intermediate nodes, and finally to the single root hash.

The key takeaway is that if any single piece of data in the original list changes, the final Merkle root will change completely. This property is what makes the structure so secure and verifiable.

2. Off-Chain: Generating the Root and Proofs

The entire Merkle tree is constructed and managed off-chain. Given your background in web development, this part of the process will feel familiar. We'll use JavaScript libraries to handle the cryptographic heavy lifting.

The workflow is:

  1. Collect Addresses: Start with your list of allowlisted addresses.
  2. Generate Leaves: Hash each address using keccak256.
  3. Build the Tree: Use a library like merkletreejs to construct the tree from the leaves and derive the root hash. This root will be passed to your smart contract's constructor.
  4. Generate Proofs: For each address on the list, generate a unique Merkle proof. This proof is an array of the "sibling" hashes that the smart contract needs to reconstruct the path to the root. These proofs are then distributed to the users (e.g., via a backend API).

The following video demonstrates this entire process using JavaScript in a simple HTML file.

How to use a Merkle Tree for gasless Whitelist/Allowlist in NFT smart contract

This video from Shobhit (Web3 Club) provides a practical, hands-on demonstration of generating a Merkle root and proofs for an allowlist using JavaScript.

Watch the segment from 08:31 to 12:30. Pay close attention to how the merkletreejs and keccak256 libraries are used. Notice the important sortPairs: true option, which is required for compatibility with OpenZeppelin's on-chain verifier.

Here is a clean code example from a blog post by Juliette Chevalier that summarizes the off-chain script.

OpenZeppelin's MerkleProof Utility: A Deep Dive

This blog post provides a concise and well-structured JavaScript snippet for generating a Merkle tree, its root, and the proofs for each address. It's a great reference for the off-chain part of the implementation.

Review the code under 'Step 1: Generate the Merkle Tree Off-chain'. This shows how to use merkletreejs and ethers to create the root and proofs you'll need for the next steps.

3. On-Chain: Verifying the Proof

Now for the Solidity part. Our smart contract only needs to do two things:

  1. Store the bytes32 Merkle root.
  2. Verify a proof provided by a user during the minting process.

We don't need to write the verification logic from scratch. OpenZeppelin provides a battle-tested MerkleProof.sol library that handles it for us. The core function is MerkleProof.verify().

MerkleProof.verify(proof, root, leaf) takes three arguments:

  • proof: The bytes32[] array of sibling hashes provided by the user.
  • root: The trusted Merkle root stored in your contract.
  • leaf: The user's hashed address, which you should calculate inside the contract for security.

The function re-calculates the root using the leaf and the proof, and returns true if it matches the root stored in the contract.

The following resource provides a complete NFT contract that uses this library for an allowlist mint.

OpenZeppelin's MerkleProof Utility: A Deep Dive

Let's look at the full Solidity implementation. This resource provides a complete AllowlistNFT.sol contract that integrates the Merkle proof verification into a mint function.

Now, focus on the Solidity code under 'Step 2: Implement the Allowlist in Your NFT Contract'. Pay attention to: How MerkleProof.sol is imported. The merkleRoot state variable set in the constructor. The allowlistMint function, especially how the leaf is generated from msg.sender and how MerkleProof.verify is used in the require statement.

A Critical Security Check

A common mistake is to let the user pass in the leaf they want to verify. This is insecure. A malicious user on the allowlist could generate a valid proof for their address and give it to a friend who is not on the list. The friend could then use that proof with their own address, but the contract would have no way to know the proof wasn't generated for them.

The correct and secure approach is to always generate the leaf inside the smart contract using msg.sender.

bytes32 leaf = keccak256(abi.encodePacked(msg.sender));

This ensures that the proof is only valid for the address that is actually calling the function. The video from Shobhit highlights this exact point.

How to use a Merkle Tree for gasless Whitelist/Allowlist in NFT smart contract

This segment of the video explains the verification process and highlights a crucial security improvement: ensuring the leaf being verified corresponds to the message sender.

Watch from 12:30 to 15:25. The presenter first identifies a bug where anyone could use a valid proof, and then fixes it by deriving the leaf from msg.sender directly within the contract. This is a vital security pattern.

4. Advanced Topic: Second Pre-image Attack

For a contract that needs to verify arbitrary data (not just addresses), there's a subtle vulnerability called a "second pre-image attack." An attacker could craft a special leaf node that is actually an intermediate hash from higher up in the tree. This could allow them to pass the verification even if their data wasn't in the original set.

The standard defense, adopted by OpenZeppelin and other libraries, is to use a different hashing scheme for leaves versus intermediate nodes. The simplest way to achieve this is to double-hash the leaves.

  • Off-chain: leaf = keccak256(keccak256(address))
  • On-chain: leaf = keccak256(keccak256(abi.encodePacked(msg.sender)))

While this isn't strictly necessary when your leaves are just msg.sender (which can't be manipulated to look like an intermediate node), it is a robust best practice. The following video explains this attack and defense in detail.

Merkle Trees and Merkle Proofs in Solidity Smart Contracts

Given your technical background, it's worth understanding this more advanced security consideration. This video explains the second pre-image attack and the double-hashing defense.

Watch the segment 'Preventing Second Pre-image Attacks' (12:38 - 17:27). This will give you a deeper understanding of the cryptographic principles that make Merkle proof verification secure.

5. The End-to-End User Flow

Let's tie everything together. Here is how a user interacts with the system:

  1. Off-Chain (Backend/API): You generate the Merkle tree from your allowlist and get the root. You deploy your NFT contract with this root.
  2. Front-End (dApp): A user connects their wallet. Your dApp sends their address (userAddress) to your backend.
  3. Backend: Your backend checks if userAddress is in the allowlist. If so, it generates the specific Merkle proof for that address and sends it back to the front-end.
  4. Front-End: The user clicks "Mint." The front-end calls the allowlistMint(merkleProof) function on the smart contract, passing the proof received from the backend.
  5. On-Chain (Contract): The allowlistMint function executes, verifies the proof against the stored merkleRoot and msg.sender, and if valid, mints the NFT.

This separation of concerns—heavy computation off-chain, lightweight verification on-chain—is a core pattern in scalable blockchain application design.

Conclusion

You have now mastered one of the most important and gas-efficient patterns for managing access control in smart contracts. By leveraging Merkle proofs, you can handle allowlists of any size while keeping on-chain costs minimal.

Key Takeaways:

  • Gas Efficiency: Merkle proofs allow you to verify membership in a large dataset by storing only a single 32-byte hash (the Merkle root) on-chain.
  • Off-Chain Generation: The Merkle tree and individual proofs are generated off-chain, typically using JavaScript libraries like merkletreejs.
  • On-Chain Verification: OpenZeppelin's MerkleProof.sol library provides a standard and secure verify function to check proofs within your contract.
  • Security is Paramount: Always derive the leaf to be verified from msg.sender inside the contract to prevent proofs from being shared or re-used by other accounts.
  • Trade-offs: Merkle proofs are ideal for static or infrequently updated lists. For highly dynamic lists, other methods like off-chain digital signatures might be more suitable, though they come with their own complexities.

Next Steps:

Now that you can control who mints your NFT, the next logical step is to manage the economics after the initial sale. In the next lesson, we will explore how to implement on-chain royalty mechanisms using the ERC-2981 standard, ensuring you can receive a percentage of all future secondary sales of your NFTs.

Can't find a good explanation? Sign up and we'll make it for you

Sign up