It’s become a common practice when launching a new PFP style NFT collection (a la Bored Ape Yacht Club) to use a placeholder image for every NFT minted, and to only reveal the final NFTs once all NFTs have been minted.
This is an important practice because without it snipers can choose which NFTs to mint based on the rarities of the traits exposed through the metadata.
Before starting to build this feature, let’s expand on our business requirements:
- Hide the tokens and metadata until all tokens have been minted
- Send a “pre-reveal” version of the token to users once they mint
- Allow the contract owner to “reveal” all tokens in the collection
Our smart contract is responsible for returning the URL where each token’s metadata.json
file lives.
The way we can solve for the above is to create a “pre-reveal” metadata file and upload it somewhere (e.g.: https://example.org/pre-reveal.json). This “pre-reveal URI” is what we want to return for every token until the reveal takes place.
After the reveal, we want to be able to update the smart contract with a new URL that can be used to generate the correct token URIs. For example, if we’ve uploaded all our tokens to a web-server that responds to https://exmaple.org/tokens/:tokenId
, then we’ll want to update the smart contract with our baseURI (https://example.org/tokens/
) so that it can easily generate the correct tokenURI by simply appending the token ID to the baseURI.
With this knowledge, we can redefine the problem to more concrete asks of our smart contract:
- It should return a generic metadata when the collection is not yet revealed
- It should allow the contract owner to update the baseURI
- It should return the correct metadata when the token is revealed
How do we implement it?
Let’s assume that we are developing a smart contract for an NFT collection that extends off the usual OpenZeppelin ERC721
implementation.
If you inspect the ERC721.sol
contract, you’ll find that a tokenURI
function is implemented there.
Here it is, for your reference:
Their implementation (above) will automatically concatenate the base URI (returned from _baseURI()
– which we’ll need to remember for later) with the token ID being retrieved. This is great for AFTER the reveal – but for the pre-reveal we’ll need to override this function to return our pre-reveal URI:
You’ll notice that we have a couple of global variables referenced: _isRevealed
and _preRevealURI
. You can implement these however you like, but at it’s simplest you can simply define them at the top of your contract:
Next we’ll need to create a function to “reveal” the token.
The above reveal function will save the passed baseURI to a global variable called _postRevealBaseURI
and will set the _isRevealed
boolean to true
.
We’re almost done, with the _isRevealed
boolean set to true
, then the tokenURI
function that we wrote earlier will defer to the parent class’ implementation. If you remember, this implementation calls a _baseURI
function to retrieve the base URI.
If we inspect that implementation, we can see that it’s actually there to be overridden:
Let’s oblige OpenZeppelin and override the _baseURI
function to return the correct base URI!
Conclusion
You’ve learnt a simple, secure and effective method to implement an NFT pre-reveal mechanism.