NFT Allowlist and Presale Mechanics 1 — Questions and Answers
Question 1: What data structure is most commonly used to implement gas-efficient NFT allowlists on Ethereum?
- Linked list of addresses
- Merkle tree (Correct answer)
- Binary search tree
- On-chain address array
Correct answer: Merkle tree
Merkle trees represent the entire allowlist with a single 32-byte root stored on-chain, while users provide off-chain proofs to verify membership.
Question 2: In a Merkle tree allowlist, what must a user provide at mint time to prove they are on the list?
- Their wallet's private key signature
- A Merkle proof consisting of sibling hashes along the tree path (Correct answer)
- The full array of all allowlisted addresses
- A signed transaction from the contract owner
Correct answer: A Merkle proof consisting of sibling hashes along the tree path
A Merkle proof is an array of sibling hashes from the leaf up to the root, enabling anyone to recompute and verify the root without storing the full list on-chain.
Question 3: Which OpenZeppelin library provides Merkle proof verification for NFT allowlists?
- @openzeppelin/contracts/utils/Arrays.sol
- @openzeppelin/contracts/utils/cryptography/MerkleProof.sol (Correct answer)
- @openzeppelin/contracts/security/ReentrancyGuard.sol
- @openzeppelin/contracts/token/ERC721/ERC721.sol
Correct answer: @openzeppelin/contracts/utils/cryptography/MerkleProof.sol
OpenZeppelin's MerkleProof.sol exposes the verify() function that checks whether a given proof correctly maps a leaf to the stored root.
Question 4: What is the primary gas advantage of Merkle tree allowlists over storing all allowed addresses in an on-chain mapping?
- Merkle proofs are computed in fewer EVM opcodes
- Only the 32-byte root is stored on-chain, regardless of how many addresses are allowlisted (Correct answer)
- Mappings require more function calls to initialize
- Merkle trees compress addresses to 16 bytes each
Correct answer: Only the 32-byte root is stored on-chain, regardless of how many addresses are allowlisted
Storing a Merkle root costs one SSTORE (32 bytes), whereas an on-chain mapping requires one storage slot per address, making large allowlists orders of magnitude cheaper with Merkle trees.
Question 5: How is a Merkle leaf typically generated from an Ethereum address in Solidity for allowlist verification?
- abi.encode(address)
- keccak256(abi.encodePacked(address)) (Correct answer)
- sha256(abi.encode(address))
- bytes32(uint256(uint160(address)))
Correct answer: keccak256(abi.encodePacked(address))
The standard convention is keccak256(abi.encodePacked(address)), which matches how JavaScript libraries like merkletreejs generate leaves, ensuring proof compatibility.
Question 6: What does the Merkle root stored in an NFT smart contract cryptographically represent?
- The hash of the first token minted from the contract
- A commitment to the entire set of allowlisted addresses that changes if any address changes (Correct answer)
- The IPFS CID of the collection's metadata folder
- The contract deployer's address encoded as bytes32
Correct answer: A commitment to the entire set of allowlisted addresses that changes if any address changes
The Merkle root is a single hash that acts as a fingerprint of every leaf in the tree; modifying any address in the allowlist produces a completely different root.
Question 7: How does verification gas cost scale as the allowlist size grows in a Merkle tree implementation?
- O(1) — constant, regardless of list size
- O(log n) — logarithmically with list size, because proof length equals tree depth (Correct answer)
- O(n) — linearly with list size
- O(n²) — quadratically due to sibling hash comparisons
Correct answer: O(log n) — logarithmically with list size, because proof length equals tree depth
A Merkle proof for a list of N addresses requires log₂(N) hashes, so verification gas grows logarithmically — a list of 1,000,000 addresses needs only ~20 hash operations.
What data structure is most commonly used to implement gas-efficient NFT allowlists on Ethereum?