In version 1.0 of the CEP-78 Enhanced NFT Standard contract, tracking minted tokens consisted of a single, unbounded list that would grow in size with each additional token. As a result, gas costs would increase over time as the list must be overwritten with each new minting. The related tutorial can be found here.
In an effort to stabilize the gas costs of larger NFT collections, version 1.1 of CEP-78 includes the use of a pre-allocated page system to track ownership of NFTs within the contract.
This system stabilizes the cost for interacting with the contract, but not the mint price itself. The size of metadata for a collection, and any differences in that metadata, will still result in some fluctuation in the price for the NFT itself. However, the cost of engaging the system itself will remain stable. Users can expect to pay a higher upfront price for page allocation, but will not need to pay this cost again for any NFTs minted within that given page.
Ownership of NFTs within a CEP-78 contract is now tracked with a series of pages
, with each page tracking a range of 1,000 tokens each. When installing an instance of the CEP-78 contract, the user determines the total token supply. This, in turn, determines the maximum number of pages, i.e., for a 10,000 token collection, each account could have up to 10 pages numbering from 0-9 tracking ownership of NFTs.
A page_table
tracks which pages within a range have been allocated and set for a certain user. The size of the page table directly correlates to the total token supply, i.e. for a CEP-78 instance tracking 10,000 tokens, the page table would be 10 bits wide. For a total of 20,000 it would be 20 bits wide. The cost of the initial page table allocation depends on the overall total size of a collection, with larger collections possessing correspondingly greater gas costs. To make initial minting costs more stable across contracts, the process of allocating a page table has been shifted to the register_owner
entrypoint.
After registering as an owner, the contract creates an entry within the page_table
dictionary for the minting account or contract. This dictionary entry consists of a series of boolean
values amounting to the total number of pages in the collection. In our 10,000 token example, this would be 10 bits set to false.
Upon minting the token, the user will pay for a page allocation. This adds them to the page
dictionary, in which each entry corresponds to a specific account or contract that owns tokens within that page. That account or contract's entry in the page
dictionary will consist of 1,000 page_address
bits set to False
upon allocation, and the minting of any given token in that page will set the page_address
bit to True
.
In addition, that account or contract's page_table
will be updated by marking the corresponding page number's bit as True
.
As an example, consider a new user minting their first NFT with a given CEP-78 contract set to a maximum number of 10,000 tokens. They are minting the 2,350th token within that collection. The following sequence of events would occur:
-
The contract registers their account as an owner.
-
The contract creates a
page_table
dictionary for that account, with 10 boolean values. As the numbering system begins with0
, the third boolean value corresponding with page2
is set toTrue
. -
The account pays for allocation of page 2, creating an entry in the
Page 2
dictionary for that account. Within that entry, there are 1,000 boolean values set to false. Minting the 2,350th token in the collection sets the correspondingpage_address
boolean for 350 asTrue
. -
Any further tokens minted by this account prior to the 3,000th token being minted will not have to pay for additional page allocations. If the account mints a token at or beyond 3,000, they must pay for the corresponding page allocation. For example, if they decided to mint the 5,125th token in the collection, they would need to pay for
page 5
to be allocated to them. They would then be added to thepage 5
dictionary with thepage_address
boolean for 125 set asTrue
.
This system binds the data writing costs to a maximum size of any given page dictionary.
If the contract enables OwnerReverseLookupMode
, calling the updated_receipts
entrypoint will return a list of receipt names alongside the dictionary for the relevant pages.
Updated receipts come in the format of "{}_m{modulo}_p{}". Once again using the 2,350th token as an example, the receipt would read:
cep78_collection_m_350_p_2
You can determine the token number by multiplying the page_number
by the page_size
(1,000) and adding the modulo
.
If the NFTIdentifierMode
is set to Ordinal
, this number corresponds directly to the token ID.
If it is set to Hash
, you will need to reference the HASH_BY_INDEX
dictionary to determine the mapping of token numbers to token hashes.