How ERC-721 Enumerable Works

Why ERC721 Enumerable?

As you may know, Standard ERC721 lacks built-in functionalities for efficiently listing all tokens owned by a specific address or enumerating all tokens in circulation. This poses a significant obstacle for applications like marketplaces, games, or collectible platforms that necessitate such features.

Let's say we wanted to find all NFTs held by a specific wallet address within an ERC721 contract. We will need to call the BalanceOf() function on the address to first get the number of NFTs owned by the address. Afterward, loop over the tokenIDs in the contract and call the ownerOf() function on each of the tokenIDs. However, executing this method under a significant volume of NFTs is deemed computationally expensive.

In Solidity, arrays incur additional gas costs due to implicit length checks whenever indexed, meaning for each index i, it verifies if i < array.length. This check contributes to increased gas consumption when working with arrays. Conversely, when using a mapping to emulate an array, this check is bypassed, resulting in gas savings. But, they are also limited because they don’t have the “length property” to track total NFTs in contracts. This is where the ERC721 Enumerable extension comes in.

What ERC721 Enumerable Does

The ERC721Enumerable extension resolves these limitations by introducing supplementary data structures and functions, facilitating the listing of tokens while adhering to the ERC721 standard. It basically tracks all the tokenIDs using the data structures _allTokens and _allTokensIndex and also all tokenIDs owned by an address using the data structures _ownedTokens and _ownedTokensIndex.

The Data Structures of ERC721Enumerable:

_allTokens array: This private array stores the IDs of all tokens currently in circulation. For example, _allTokens might contain values such as [1, 2, 3, 4, 5, 1337].

_allTokensIndex mapping: Similar to _ownedTokensIndex, this mapping is employed to set and retrieve the position of a token ID within the _allTokens array. So, instead of looping through the _allTokens array to locate the index corresponding to a tokenID, it uses the tokenID directly as a key to retrieve its index within the _allTokens array.

_ownedTokens mapping: It has a nested mapping (i.e., owner -> index -> tokenID) that sets and retrieves the index of a token ID for a given address. For instance, _ownedTokens[Bob][index0] might correspond to Bob owning token ID 25.

_ownedTokensIndex mapping: _ownedTokensIndex is a mapping that correlates tokenIDs to their respective indices within the _ownedTokens mapping for a specific user.

ERC721Enumerable Public Functions

As seen in the snippet above, the data structures mentioned above are private but can be interfaced with the following public functions: totalSupply(), tokenByIndex(index), and tokenOfOwnerByIndex(owner, index).

totalSupply(): The function returns the length of the _allTokens array. That translates to the total number of NFTs in a contract. Also, each token has a valid owner address that is not address(0).

tokenByIndex(): The function returns the tokenID of an NFT that is stored at the provided index.

tokenOfOwnerByIndex(): The function returns the tokenID of an NFT stored at a particular index in the owner's list of tokens.

Link to the article