Building a Fair NFT Reveal Mechanism: Commit-Reveal and Chainlink VRF
A technical checklist for implementing provably fair NFT reveal mechanics using commit-reveal schemes and Chainlink VRF to prevent front-running and sniping.
Your 10,000-piece collection is live, mint is in progress, and somewhere in the mempool is a bot that can read your contract's pending reveal transaction and pick the token IDs with the highest rarity before they land. If your reveal mechanism is naive, this is not a hypothetical — it is a predictable outcome that experienced traders will exploit and your community will notice.
Provably fair reveals are not optional for any serious NFT collection. Here is a practical technical breakdown of the two main approaches and when to use each.
Why Naive Reveals Are Exploitable
The typical naive approach looks like this: after mint completes, the team generates traits off-chain, uploads metadata to IPFS, and updates the baseURI in the contract. Token ID 1 gets traits A. Token ID 2 gets traits B.
The problem: anyone watching the mempool or the team's IPFS uploads can see what traits are assigned to which token IDs before the transaction is confirmed. They can quickly purchase specific token IDs on secondary markets — sniping the rarest tokens — before most holders know what they have.
A provably fair reveal removes the team's ability to cherry-pick rare assignments and prevents front-running by making the assignment unpredictable until a specific commitment is made.
Approach 1: Offset-Based Reveal with Commit-Reveal
This approach uses a random offset to shuffle which token ID receives which metadata entry, without rewriting all token IDs.
How It Works
- Pre-mint: The team pre-generates all token metadata and uploads it to IPFS as a numbered series (1.json through 10000.json). This is committed before any mint occurs.
- During mint: The
baseURIpoints to a placeholder. Token IDs are assigned sequentially. - Post-mint: A random
offsetvalue is generated and stored on-chain. TokenN's actual metadata is at((N + offset) % totalSupply) + 1.
The offset shifts the entire mapping. Because the metadata was committed before the mint and the offset is generated unpredictably after the mint, neither the team nor any observer can predict which token ID will receive which trait set at mint time.
Generating the Offset
The offset must come from a source that the team cannot control or predict. Do not use block.timestamp or blockhash — these can be influenced.
Options:
- Use a future
blockhashat a block number that was unknown at mint time (weak but simple) - Use Chainlink VRF (strong, verifiable, recommended for high-value collections)
Approach 2: Chainlink VRF
Chainlink VRF (Verifiable Random Function) provides cryptographically provable randomness. The random value comes from Chainlink's decentralized oracle network and is accompanied by a cryptographic proof that the value was not manipulated. Anyone can verify the proof on-chain.
Integration Steps
Step 1: Subscribe to VRF
Create a VRF subscription at vrf.chain.link and fund it with LINK tokens. Note your subscription ID.
Step 2: Inherit VRFConsumerBaseV2
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
contract MyNFT is ERC721A, VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
uint64 subscriptionId;
bytes32 keyHash;
uint256 public randomSeed;
bool public revealed;
constructor(uint64 subId, address coordinator, bytes32 _keyHash)
VRFConsumerBaseV2(coordinator)
{
COORDINATOR = VRFCoordinatorV2Interface(coordinator);
subscriptionId = subId;
keyHash = _keyHash;
}
}
Step 3: Request Randomness After Mint Closes
function requestReveal() external onlyOwner {
require(!revealed, "Already revealed");
COORDINATOR.requestRandomWords(
keyHash,
subscriptionId,
3, // request confirmations
100000, // callback gas limit
1 // number of random words
);
}
Step 4: Receive and Store
function fulfillRandomWords(uint256, uint256[] memory randomWords) internal override {
randomSeed = randomWords[0];
revealed = true;
}
Step 5: Use the Seed in tokenURI
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(revealed, "Not yet revealed");
uint256 metadataIndex = (tokenId + randomSeed) % totalSupply();
return string(abi.encodePacked(baseURI, metadataIndex.toString(), ".json"));
}
Reveal Checklist
- Metadata is uploaded and the IPFS CID is publicly committed before mint opens
- The contract's
baseURIduring mint points to a placeholder, not the real metadata - Reveal is triggered by a one-time, non-repeatable action after mint closes
- Randomness comes from Chainlink VRF or an equivalent verifiable source
- The team cannot call the reveal function more than once (guarded by a boolean flag)
- The random seed is stored on-chain and publicly verifiable
- Post-reveal,
tokenURIcorrectly computes the offset mapping - Edge cases tested: token 0, token at max supply, offset that wraps around
Provenance Hash
Before mint opens, publish a provenance hash — a SHA-256 of all metadata concatenated in sequence. This proves the metadata was fixed before the mint and was not changed afterward. Collectors can verify the provenance hash against the revealed IPFS content to confirm no swaps occurred.
Provenance hashes are a community expectation for serious collections. Store the hash in your contract and in your project documentation.
Summary
For small or low-value collections, a commit-reveal with a future blockhash offset may be sufficient. For any collection where rare traits have significant secondary market value, Chainlink VRF is the appropriate tool. The cost of the VRF request is modest; the cost of a compromised reveal in community trust is not.
If you are building a reveal mechanism and want the implementation reviewed or built to specification, reach out to Clixo. We design and implement reveal systems as part of full-stack NFT collection builds.