ERC721R代币标准正式发布,它在NFT智能合约中增加了去信任的退款设计,允许铸造者在给定的期限内退还按成本铸造的NFT,并获得相应退款。
Or: how to snipe all the rares if ERC721R isn’t being used!
TLDR: Here’s a link to the ERC721R repository.
Mphers was the first mfers derivative and, because it is also a Phunks derivative, I was quite interested in owning one.
Specifically, I wanted an alien. They look the coolest and there are only 8 in the 6,969 collection. And I got one!
Though it might not have been clear from the Tweet, what I meant was that I was lucky to have figured out how to 100% guarantee I would get an alien without needing any additional luck.
Read on for how I did it, how you can do it too, and, if you’re a dev, how you can prevent it from happening!
The key to minting a rare NFT is knowing the id of every rare token in advance.
For example, once I knew my alien was #4002, all I had to do was refresh the mint page until I saw #3992 had been minted and then immediately mint 10 mphers.
How did I know #4002 was an alien? Let’s retrace my steps.
First, go to the Etherscan page for the mpher contract and look up the tokenURI of a token that has already been minted, token #1:
The template for mpher metadata
As you can see, mphers, like many contracts, constructs metadata URIs by combining the token id with an IPFS hash.
The benefit of this approach is that it gives you the provenance of the entire collection in every URI and, while that URI can be changed, doing so affects everyone and is public.
By contrast, imagine if the token URI contained no provenance hash, for example https://mphers.art/api?tokenId=1
. As a collector you could never be certain that the devs weren’t silently changing #1’s metadata whenever they wanted.
However, if you have an API, you can say “if #4002 has not been minted, do not show any information about it” and you can’t do this if you go the IPFS route.
Once the metadata has been revealed (which in mpfers case was instantly), you can look up the metadata of any token, whether or not it has been minted.
Just replace the trailing “1” in the URI above with the id you want.
These metadata files give us all the attributes of the mpher with the specified id. For example, in the case of my alien:
Mpher #4002
To find the aliens, we just need to search all the metadata files for the string “alien mpher.”
Next, download the 6,969 metadata files. Here I’m using OpenSea’s IPFS gateway but you can try ipfs.io
or something else if that works better.
This snippet uses curl
to download the files 10 at a time. Downloading thousands of files quickly can be finicky so you might end up with some duplicates or errors. But if you fiddle with it you should be able to get everything (and for our purposes dupes aren’t a problem).
Now that you have the files in one directory, just grep
for the aliens:
The numbers that appear are the names of the files that contain “alien mpher” and therefore the ids of the aliens themselves.
The whole process takes less than 10 minutes. And you can use this technique on many NFTs that are minting right now.
In practice, it is not completely trivial to manually mint at the exact right moment to get the alien, especially when tokens are minting quickly. If you really want to “go big” with this approach, you should write a bot to poll totalSupply()
every second and automatically submit the mint transaction at the exact right moment.
And if you want to go “absolutely huge,” you could look for the token you need to see in the mempool before it is even minted and get your mint into the same block!
However, in my experience, the “big” approach is enough to win 99% of the time—though, interestingly, not 100% of the time.
Is one question you might be asking yourself if you’re just learning about this now.
The idea that you had zero chance at minting anything that someone using this technique also wanted is distressing.
But, did you have no chance? In a way, you had the same chance as everyone else!
Take me for example: I figured this out on my own using public information and I put it to work using free open-source tools. Anyone can do this, and in general, if you don’t investigate how a contract works before minting you are going to run into much worse issues than this.
The mpher mint was 100% fair.
Still, while it was a fair game, “snipe the alien” might not have been the game everyone wanted to play.
Instead, people might have had more fun overall playing the game of “mint lottery” where tokens were distributed by chance and it was impossible to gain an advantage over someone who was just clicking the “mint” button.
How might we do this?
For Fashion Hat Punks, my goal was to create a random minting experience without sacrificing fairness. In my view, a predictable mint is far better than an unfair one. Above all else, participants must be kept on equal footing.
Unfortunately, the most common way to create a random experience—the so-called a post-mint “reveal”— is deeply unfair. It works like this:
Here, the person setting the metadata obviously has a tremendous unfair advantage over the people who are minting because they alone determine who gets what! Unlike the mpher mint, here is a situation where you actually have no chance to compete.
But what if it is a well-known, trusted, doxxed dev team with a long track record. Are reveals okay in this case?
No! No one can be trusted with this kind of power. Even if someone isn’t consciously trying to cheat, they bring unconscious biases to the table just like everyone else. Beyond this, they might simply make a mistake and not realize it until it was too late.
You should not trust yourself either. Imagine, you do a reveal, you feel pretty good you did it correctly (nothing is 100%!), and somehow you end up with the rarest NFT. Would that not feel a tiny bit weird? Are you sure you deserve it? Personally, as an NFT developer, I would not want to be in this situation.
The bottom line is this: reveals are bad*
*UNLESS: they are done trustlessly, meaning everyone can verify their fairness without having to trust the devs (which you should never do).
To achieve a trustless reveal, you need a way of proving that the reveal was fair—typically by having the reveal happen on-chain and be powered by randomness that is verifiably outside of anyone’s control (e.g., through Chainlink).
Tubby Cats did a great job on a reveal like this and I recommend you check out their contract and launch reflections. Their reveal was also cool in that it was progressive—you didn’t have to wait for the end of the mint to learn what you got.
The downside to trustlessness in general is that it is extremely difficult to get right—@DefiLlama had this to say in his launch reflections:
When writing the contract I made it as trustless as possible, removing as much trust as possible into the team.
The reason for it is that I believe it’s important for every participant to know the rules of the game and know that they won’t be changed from under them (everyone should have complete information to make decisions, if processes are changed in the middle that creates groups of people with privileged information), while trust minimization is important because that’s the whole raison d’etre for smart contracts (and it makes it impossible to hack even if the team is compromised). However, this was a huge mistake, since it greatly reduced our flexbility and the actions we could take to correct things that happened.
And @DefiLlama is a top-tier dev. If maximizing trustlessness gave him this many headaches, imagine what it will do to you!
Therefore, my recommendation is to use a worse solution that still suffices in 99% of cases and that is much easier to implement: random token assignments.
ERC721R implements the converse of a reveal: instead of minting token ids deterministically and assigning metadata randomly, we mint token ids randomly and assign metadata deterministically.
This allows us to reveal all metadata before minting while still minimizing snipe opportunities.
To use it, copy the contract into your project directory (sorry, no NPM package yet), import
it, and use this code:
How does ERC721R work?
First, a disclaimer: unlike a trustless reveal, ERC721R is not truly random. In this sense it creates the same “game” we saw in the mpher situation where minters can use publicly-available information to compete to exploit the mint. However, in the case of ERC721R, the game is significantly more difficult.
In order to game ERC721R, you would need to be able to predict the value of a hash with these inputs:
For a normal person, this is impossible because it requires knowing the timestamp of the block of your mint and you do not have access to this information.
A miner who has control over when blocks mine (and therefore can influence the timestamp) can theoretically do this, but even then they must set the timestamp to a value in the future and whatever they’re doing depends on the hash of the previous block which expires in about 10 seconds when the next block is mined.
I believe this pseudo-randomness is “good enough,” but if there is big money on the line, it will be gamed with 100% certainty, so be careful! Of course the system it is replacing—predictable minting—will also be gamed.
The token id selection itself happens in a very clever implementation of the modern version of the Fisher–Yates shuffle algorithm that I copied from CryptoPhunksV2.
To understand it, first consider the naive solution: (the below assumes a 10,000 item collection)
This works, but it costs too much gas because changing the length of an array and storing a huge array filled with non-zero values are costly.
How can we avoid both? What if we instead started with an array containing 10,000 zeros, which is cheap to create. Now let’s use each index in that array to represent an id.
Suppose we choose index #6500 randomly—#6500 would be our token id and we would indicate index #6500 was already used by (for example) replacing the 0 in index #6500 with a 1.
But what would happen if we chose #6500 again? We would observe a 1, indicating #6500 was taken, but then what? We cannot simply “roll again” as this would make gas unpredictable and high, especially for later mints.
The genius of modern Fisher-Yates is that it gives us mechanism for selecting an available token id 100% of the time without the cost of maintaining a separate list. Here’s how it works:
numAvailableTokens
with the value 10,000.numAvailableTokens — 1
numAvailableTokens — 1
. If that value is 0, update the value at index #6500 to the last index in the array (#9999 if it’s the first token). If the last value in the array is not zero, update index #6500 to store this final non-zero value.numAvailableTokens
by 1.And there you have it! The array stays the same size and yet we are able to reliably choose an available id. Here is the Solidity code:
Unfortunately, this algorithm still uses significantly more gas than the leading sequential mint solution, ERC721A.
This is most pronounced when minting multiple tokens in one transaction—e.g., a 10 token mint costs about 5x more on ERC721R than ERC721A. That said, ERC721A has been optimized much further than ERC721R so there is probably room for improvement.
Here are your options:
Did I miss an option?! Leave a comment or hit me up @dumbnamenumbers on Twitter.
If you want to learn more, head over to the GitHub repository and check out the code! Pull requests more than welcomed—I’m sure I’ve overlooked many opportunities for gas savings.