You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a user i would like to receive an unrevealed token immediately so that i have possession of my token but also have confidence that I have a fair shot at receiving a rare token. As a user i would like my unrevealed token to reveal its artwork at some point in the future and for the developer to prove to me that it was distributed fairly so that i can verify I received the right token.
The concept of a mint reveal is an interesting one. We are basically telling our users that we are going to mutate the state of the product that they purchased, which is very assuredly against everything immutable ledgers are supposed to stand for. But, as with everything there are exceptions to every rule. This pattern arose out of expedience as a cheap alternative to guarantee fairness when pulling rares.
Generative NFT token mints are a lot like opening a pack of cards, or a variable-reward lottery and users expect that all else being equal, they will have a shot at buying in for price X and have a chance at receiving something valued at X+Y.
Of course in practice, this puts a lot of trust in the developer. In fact, it is quite common for a developer to launch a project using a mint reveal pattern and then immediately "rug" the project. In fact, some of them do it on purpose.
Consider the alternative that we do not do a mint reveal and instead we simply publish the artwork and metadata for the tokens to the blockchain before the sale starts. Any user can go and check which token is at index 0, at index, 1, at index 500 and so forth. We can see which ones might be valuable and which ones might not be. We can also determine beforehand which tokens we might get when we submit transactions, or we can bribe a miner to mine our transactions in a particular order to ensure we get the tokens we want. That's no fun.
So now that we understand why we need to use a mint reveal scheme, we can talk about how to do it.
The most simple implementation is to define two separate states, unrevealed and revealed. If the contract is unrevealed, then all calls to tokenURI return the same image (usually just a placeholder). If the state is revealed, then the actual image for a given token id is returned from the tokenURI call.
First, you will need to override the OZ tokenURI function. Then you will need to modify it internally to handle the state changes. Here's an example that demonstrates a few new solidity features that we'll go over:
function tokenURI(uint256tokenId) publicviewoverridereturns (stringmemory) {
require(_exists(tokenId), 'Token does not exist');
stringmemory revealedBaseURI = _revealedBaseURI;
returnbytes(revealedBaseURI).length>0?string(abi.encodePacked(revealedBaseURI, tokenId.toString()))
: _tokenBaseURI;
}
So what's going on here? First we use arequire clause to error out if the caller is searching for a token that doesnt exist. Next we grab a handle to a global state variable, but this could also be the result of a function call. Notice we have a memory declaration. This tells the compiler that we want to read the variable into memory. Storage access on the blockchain is extremely expensive, so we save a little gas by reading the variable from storage only once and using the memory version in our if statement.
next, we have a short-hand if statement to check if the revealedBaseURI has been set (is not null), by casting it to bytes and checking it's length.
if the revealed base has been set, then we return a representation of the URI that resolves to the token. If it has not, we return the base.
There are a few things to go over in this section. First, what is the actual content of these variables? _revealedBaseURI and _tokenBaseURI are both IPFS hashes. We will go over IPFS more in a later section but for now:
_tokenBaseURI - an ipfs CID route in the form of ipfs://QmcZyZppFdQ5MPDEtNnAa8ri3QSHtkqYCRMYv5o1dQcjZm' that points to a metadata file containing the pre-revealed data
_revealedBaseURI - an ipfs CID route in the same form that points to a folder containing metadata files named after their tokenid (e.g. /folder/0, /folder/1, /folder/500, etc.)
Why is this important?
Storing the hash of the folder containing the appropriately named token files allows us to take up a single storage slot for each type baseURI so that we do not have to store a mapping of token id's to their IFS file hashes. It keeps our code more simple, but also each storage of a 32 byte IPFS costs about $7 at current prices, so storing one for each of our 1k tokens would cost $7,000.
Second, and most importantly we are taking advantage of the abi.encodePacked function to concatenate the two strings together.
I'm sure you will be shocked to hear this, but strings are fickle in solidity. Here are a couple of articles that go into greater detail, but basically it works like this: Strings are just a convenience declaration for a variable-length byte array that contains characters (similar to other C-based languages). There is a lot of nuance to how they work and most of the time using abi.encodePacked is how you solve your problem.
So, picking up where we left off, we'll need to implement a couple more state variables in our contract and add a setter function so we can update the _revealedBaseURI. Don't forget to mark it onlyOwner!
And of course, let's write some tests!
How can you configure the contract to test the state pre-reveal and post-reveal?
The text was updated successfully, but these errors were encountered:
As a user i would like to receive an unrevealed token immediately so that i have possession of my token but also have confidence that I have a fair shot at receiving a rare token. As a user i would like my unrevealed token to reveal its artwork at some point in the future and for the developer to prove to me that it was distributed fairly so that i can verify I received the right token.
The concept of a mint reveal is an interesting one. We are basically telling our users that we are going to mutate the state of the product that they purchased, which is very assuredly against everything immutable ledgers are supposed to stand for. But, as with everything there are exceptions to every rule. This pattern arose out of expedience as a cheap alternative to guarantee fairness when pulling rares.
Generative NFT token mints are a lot like opening a pack of cards, or a variable-reward lottery and users expect that all else being equal, they will have a shot at buying in for price X and have a chance at receiving something valued at X+Y.
Of course in practice, this puts a lot of trust in the developer. In fact, it is quite common for a developer to launch a project using a mint reveal pattern and then immediately "rug" the project. In fact, some of them do it on purpose.
Consider the alternative that we do not do a mint reveal and instead we simply publish the artwork and metadata for the tokens to the blockchain before the sale starts. Any user can go and check which token is at index 0, at index, 1, at index 500 and so forth. We can see which ones might be valuable and which ones might not be. We can also determine beforehand which tokens we might get when we submit transactions, or we can bribe a miner to mine our transactions in a particular order to ensure we get the tokens we want. That's no fun.
In any case, the reason for this is because it is extremely difficult to define randomness in a smart contract cheaply. This article describes why it is so easy to predict random numbers in ethereum smart contracts and this article describes Verifiable Random Functions as a solution. There are some other options out there but all of it is either extremely expensive to use or is technically complex for our use case.
So now that we understand why we need to use a mint reveal scheme, we can talk about how to do it.
The most simple implementation is to define two separate states, unrevealed and revealed. If the contract is unrevealed, then all calls to
tokenURI
return the same image (usually just a placeholder). If the state is revealed, then the actual image for a given token id is returned from the tokenURI call.First, you will need to override the OZ
tokenURI
function. Then you will need to modify it internally to handle the state changes. Here's an example that demonstrates a few new solidity features that we'll go over:So what's going on here? First we use a
require
clause to error out if the caller is searching for a token that doesnt exist. Next we grab a handle to a global state variable, but this could also be the result of a function call. Notice we have amemory
declaration. This tells the compiler that we want to read the variable into memory. Storage access on the blockchain is extremely expensive, so we save a little gas by reading the variable from storage only once and using the memory version in our if statement.next, we have a short-hand if statement to check if the revealedBaseURI has been set (is not null), by casting it to bytes and checking it's length.
if the revealed base has been set, then we return a representation of the URI that resolves to the token. If it has not, we return the base.
There are a few things to go over in this section. First, what is the actual content of these variables?
_revealedBaseURI
and_tokenBaseURI
are both IPFS hashes. We will go over IPFS more in a later section but for now:Why is this important?
Storing the hash of the folder containing the appropriately named token files allows us to take up a single storage slot for each type baseURI so that we do not have to store a mapping of token id's to their IFS file hashes. It keeps our code more simple, but also each storage of a 32 byte IPFS costs about $7 at current prices, so storing one for each of our 1k tokens would cost $7,000.
Second, and most importantly we are taking advantage of the
abi.encodePacked
function to concatenate the two strings together.I'm sure you will be shocked to hear this, but strings are fickle in solidity. Here are a couple of articles that go into greater detail, but basically it works like this: Strings are just a convenience declaration for a variable-length byte array that contains characters (similar to other C-based languages). There is a lot of nuance to how they work and most of the time using
abi.encodePacked
is how you solve your problem.So, picking up where we left off, we'll need to implement a couple more state variables in our contract and add a setter function so we can update the
_revealedBaseURI
. Don't forget to mark itonlyOwner
!And of course, let's write some tests!
How can you configure the contract to test the state pre-reveal and post-reveal?
The text was updated successfully, but these errors were encountered: