As the NFT area is growing every day, more new mechanics and experiments are coming to the world of priceless pixels. One of the popular approaches during NFT launches is a Reveal toggle functionality.
Reveal functionality stands for hiding the NFT picture with some placeholder image initially. In this article we will go through the process of implementing such behavior and analyze why you’d possibly use it in your collection.
Firstly, let’s dive into the theory of NFTs to define the steps needed for our Reveal. NFTs are following ERC721 standard of a non-fungible token and all of the generative collections follow ERC721Metadata
interface as well.
interface ERC721Metadata {
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) external view returns (string);
}
This interface allows us to specify name, symbol and tokenURI for each NFT in our collection. What we are interested here is tokenURI
function that is responsible for returning item’s metadata value.
So our task for implementing Reveal is the following:
tokenURI
method to return the same single metadata file for every NFT during the Hidden phase and appropriate NFT metadata after revealI won’t go deep with explaining the common NFT smart-contract, assuming that you have one already ;) If not, then have a look on ERC721A standard that provides gas-efficient minting, so your customers won’t pay a lot for transaction fee. I’ll be using ERC721A in this tutorial as well.
Let’s add the flag property to existing Contract class and initialize it with false
:
bool public revealed = false;
Then let’s add a function for changing our revealed
value and make sure it could only be called by the owner
. For doing that, I’d suggest derive from the OpenZeppelin Ownable
contract that will add onlyOwner
modifier for us.
contract HiddenReveal is ERC721A, Ownable {
bool public revealed = false;
function reveal() external onlyOwner {revealed = true;}
}
As you can see, I’m not toggling the revealed
value but just set it true
. In this way you won’t make your community nervous that you might hide their NFTs again after revealing 🙃 You can make it to toggle current value or set to the value passed into the function.
Now is the most interesting part: overriding tokenURI
function based on our revealed
flag.
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
string memory baseURI = _baseURI();
string memory metadataPointerId = !revealed ? 'unrevealed' : _toString(tokenId);
string memory result = string(abi.encodePacked(baseURI, metadataPointerId, '.json'));
return bytes(baseURI).length != 0 ? result : '';
}
tokenId
exists and revert with Custom Error if notbaseURI
value to concat it with tokenId
and get metadata URLrevealed
state and if it’s false
then use unrevealed
string instead of tokenId
, store it into metadataPointerId
baseURI
, metadataPointerId
and .json
extensionresult
is baseURI is specified
Final result for revealed=false
will be something like this, for every NFT:
https://www.mybaseurilink.com/folderipfshash/unrevealed.json
or ipfs://folderipfshash/unrevealed.json
Here is the full Smart Contract code with Hide/Reveal functionality:
contract HiddenReveal is ERC721A, Ownable {
string private _baseTokenURI;
bool public revealed = false;
function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
string memory baseURI = _baseURI();
string memory metadataPointerId = !revealed ? 'unrevealed' : _toString(tokenId);
string memory result = string(abi.encodePacked(baseURI, metadataPointerId, '.json'));
return bytes(baseURI).length != 0 ? result : '';
}
function setBaseURI(string calldata baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
function reveal() external onlyOwner {
revealed = true;
}
}
Please, note that this Smart Contract is NOT production ready, as it doesn’t have mint functions lol. But you can use it as a reference to plug functionality into your solution
After finishing with Smart Contract part, you have to set up the storage for your Unrevealed items metadata. Generally, you will just need to store a placeholder image and unrevealed.json
metadata files in a separate IPFS folder.
Then you connect your Smart Contract to the IPFS storage through calling setBaseURI
on contract with the folder’s hash.
Here is an example of unrevealed.json
:
{
"name": "@sir_fedos Hide/Reveal",
"description": "Tutorial on how to create Reveal functionality on your NFT",
"image": "ipfs://hAshToYoUrPlAcEhoLderImAge"
}
All in all, Reveal functionality is a great marketing tool and a tiny mechanic that could be added to every NFT drop with ease. Moreover, you can specify multiple reveal stages, define them as an enum and add some entertainment to the post-mint phase of your project!
Follow for more tutorials!
Also published here.