2.6 million ETH (~$6.5bn @ 1E = $2.5k) has been burned in the last 12months. Imagine the impact on NFTs if this wasn’t burned, but instead was preserved as liquidity for the ecosystem. When Citizens of Tajigen mints on 22nd July 2022, we’ll open-source TinyERC721: a hyper-optimized NFT contract for the community to use, and improve upon.
Because tiny optimizations at scale can make a huge difference.
To open with a bit of a trope: it’s still incredibly early in web3, and in NFTs especially. If you’re an active market participant at this point in time, you are without a shadow of a doubt, an innovator - the earliest users on the left extremity of the user adoption graph.
Being early is generally really fun if you’re innately curious & love trying things out before they become mainstream, but with new technologies it often means you have to put up with stuff like rough edges, bugs and inefficiencies.
Probably the biggest economic inefficiency (on a protocol / infra level) facing the burgeoning ethereum NFT ecosystem is excessive gas expenditure.
Although ethereum transaction volume has dropped in recent months, several thousands of ETH are still burned through gas fees every day. During the bull market in the second half of 2021, and in early 2022, we’d frequently burn between 5,000 - 10,000 ether per day, with NFTs making up ~30% of gas usage.
Putting this into perspective, the month of January 2022 alone saw more ETH burned than the entire lifetime OS trading volume of Doodles & Azuki combined. It’s important we recognise the significance of this, and how much better off we’d be if this volume was retained as ecosystem liquidity, rather than being burned away into the ether (excuse the pun).
The good news is: there’s something we can do about it.
Adopting more gas-efficient contracts can help save the NFT ecosystem hundreds of thousands of dollars a day. Optimizing for gas-efficiency benefits traders and creators alike, as ETH that’s not burnt remains available as ecosystem liquidity, likely increasing trading volumes, floor prices and other growth rates.
Thankfully, we’ve seen many projects move towards more gas-efficient NFT contracts, but.. (yes, there’s unfortunately a but here) most existing optimizations shift the gas overhead from the time of mint to future token transfers. So, whilst these changes are valuable in reducing gas-wars & excessive gas expenditure at the time of minting, they don’t always result in a net reduction of gas consumption in the long term.
By examining several existing optimization techniques, we challenged ourselves to achieve the best of both worlds: to minimize gas consumption at mint time AND reduce gas consumption in the long term.
Before we look at TinyERC721, let’s look at what contributes to gas and others who have paved the way to date.
The most important factor contributing to the gas usage of a transaction is data storage. In order to store a piece of data on-chain, you need to pay 20,000 gas for 32 bytes of storage space. With ETH at ~$1,000 and gas at 50 gwei, this corresponds to $1 (although, painful reminder, this is the lowest ETH price we’ve seen in the last 12 months).
Reducing the amount of data stored on-chain has therefore been the main focus of several gas optimization techniques we’ve seen recently.
Initially, many NFT projects used the OpenZeppelin ERC721Enumerable base contract. This contract is optimized for efficient data retrieval and provides mechanisms to efficiently retrieve the list of tokens owned by a wallet. In order to do this, it needs to store several extra pieces of data, which significantly increases the gas cost for every mint and transfer of a token. While this trade-off may be justifiable in some scenarios, the majority of NFT contracts do not require efficient on-chain token enumeration, and this extra storage space is essentially wasted.
This prompted many projects that followed to choose the OpenZeppelin ERC721 base contract which dropped support for efficient token enumeration. Removing enumerable support reduces the gas cost of minting and transferring tokens by more than 50%, but means that you either need to loop through the entire collection to retrieve the list of tokens owned by a particular wallet, or depend on off-chain indexing mechanisms. This is essentially a trade-off between write-efficiency and read-efficiency. As you don’t need to pay for gas when you only need to read data from the blockchain, it is often favorable to optimize for write-efficiency.
When a token is minted using the OpenZeppelin ERC721 base contract, the owner of each newly minted token is stored (token #42 is owned by wallet 0x123..89, token #43 is owned by wallet 0x123..89, etc) and the balance of the token owner is stored (wallet 0x123..89 owns 5 tokens). Both of these have been the focus of newer gas optimization techniques.
Whilst the official NFT specs do not define any behavior around token IDs when minting, a very common approach is to sequentially assign token IDs using an internal counter that keeps track of the number of minted tokens. This enables the batch minting optimization technique used in the ERC721A base contract by Azuki.
The main innovation of ERC721A is to store the token owner only once per batch. Let’s say I mint 5 tokens (token #42 up to #46), instead of storing my wallet address 5 times as the owner of each individual token, ERC721A will store my wallet address only once for token #42 and leave the owner of the following tokens blank. This means that, in order to determine the owner of a token, you move backwards through the list of tokens until you find a non-blank token owner. This does mean however that gas costs are shifted from minting to future token transfers, during which these blank spaces are gradually filled in.
Another approach, used by Mason and Chance in the Nuclear Nerds contract, eliminates the need to store the token balances by dynamically calculating this only when needed. This results in the lowest possible gas cost for minting a single token and also optimizes the gas cost of subsequent token transfers.
TinyERC721 builds on top of both techniques to optimize gas usage in both individual mints and token transfers, as well as when minting multiple tokens in a single batch. In combination with a number of other techniques such as data packing, reducing storage reads and eliminating redundant overflow checks (all of which we’ll discuss in future articles), TinyERC721 reduces the gas usage of ERC721A by another ~30%.
We’re excited to see how TinyERC721 performs in a live mint - we’ll know shortly as we’re using it to launch the Citizens of Tajigen on 22nd July. Till then, here are the gas figures from tests we’ve recently run.
As is often the case in software engineering, there are always trade-offs between various implementations. Our implementation makes a number of assumptions to enable the optimization techniques we are using. If these assumptions do not hold for your specific use-case, other ERC721 implementations may be a better fit. As always, DYOR on the optimal contract to use and avoid blindly copy-pasting code or using a contract ‘just because X other team did so’. Following our opening blurb, we do believe that - generally speaking - optimizing for gas-efficiency is the right approach in the vast majority of scenarios.
With that said, here are some things to consider if using TinyERC721:
Token IDs are assigned sequentially to newly minted tokens. This is the assumption that enables the ERC721A batch-minting optimization. Whilst this holds true for the vast majority of NFT projects, some projects (like LOOT) let you choose which token ID to mint.
The list of tokens owned by a specific address does not need to be queried on-chain. This is what enables us to eliminate the extra storage requirements of ERC721Enumerable.
Token balances do not need to be queries on-chain. This is what enables the Mason Chance optimization.
The number of tokens in a collection is restricted by some upper limit. Certain ethereum gateways such as infura, which is the default gateway used by Metamask, enforce a gas limit on read operations to prevent DDoS attacks. Calculating your token balance (even though it’s a read-only operation which doesn’t require you to pay for gas) will loop through the entire collection. This means that, if your collection were to start approaching 100,000 tokens, it might not be possible to query token balances through infura as it runs into this limit. This shouldn’t be a problem for the vast majority of collections that we see today (often 10,000 tokens or less), but it means that TinyERC721 may not be suited for future collections aimed at containing significantly more tokens.
Both #2 and #3 above limit the information that is efficiently available on-chain. While there are scenarios, such as staking or token-gating, in which this information might be needed, this can usually be replaced by retrieving this data off-chain and passing in arguments that can efficiently be verified on-chain. For example, in the context of token-gating, instead of checking the balance of the caller on-chain, you could pass in a token ID that is owned by the caller as an argument to the token-gated method, which can then efficiently be verified on-chain.
My co-founders and I are grateful for every builder in the space who turns up daily to think, tinker and toil to help us raise the bar collectively, and move the space forward. The work I have done on TinyERC721 is a humble extension of the efforts that others have already made. I have no greater wish than for the community to continue building upon it, so that together, we create the best future possible.
wkm, co-founder & lead eng, Citizens of Tajigen.