EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8418: Itemized Non-Fungible Token

Extends ERC-721 so each token records which item it is, with a write-once supply cap enforced per item

Authors
Created2026-09-01
Discussion Linkhttps://ethereum-magicians.org/t/erc-8418-itemized-non-fungible-token/29693
Requires

Markdown

https://raw.githubusercontent.com/leedoe/ERCs/refs...

EIP-GPT summary

Contents
AbstractMotivationAn identifier does not say what the token isERC-721 caps the collection, not the itemThe two are one mechanismWhere existing standards fall shortSpecificationBehavioral requirementsERC-165 interface detectionOptional extension: edition numbersRationaleNot encoding the item in tokenIdNot recording the cap in tokenURI metadataWhy on-chain enumeration is not part of this ERCWhy edition numbers are an extension rather than core1-based edition numbersWhy all three supply counts are exposedWhy there is no uncapped itemWhy 0 is reserved for non-existenceWhy the cap is immutableWhy token identifier allocation is unspecifiedWhy a token identifier is never reusedThis ERC constrains minting without defining itBackwards CompatibilityFor consumersFor existing collectionsTest CasesReference ImplementationSecurity ConsiderationsMax supply integrityScope of the supply guaranteeCounter bounds at the maximum capItem creation controlItem immutability and downstream cachingBurned token dataCopyright

Abstract

This ERC extends ERC-721 with a second level of identity. A token belongs to an item — the kind of thing it is — and the contract holds a write-once supply cap per item, refusing any mint that would exceed it.

ERC-721 gives a token an identifier and an owner. It does not say what the token is, and any supply it tracks covers the whole collection rather than the items within it. This ERC adds both: the item recorded per token, and per-item supply, cumulative mint and burn counts that anyone can read to verify how many tokens of that item may ever exist.

An optional extension adds edition numbers — a token's position within its item — for implementations that need "42 of 100" on-chain.

Motivation

This ERC comes from putting a live game's items on-chain. Two properties the game already relied on turned out to have no representation in ERC-721.

An identifier does not say what the token is

In ERC-721 a token is a number and an owner. Nothing on-chain distinguishes a Flame Sword from a Blue Potion. A game has thousands of items, and most of its rules are stated in terms of item rather than instance: what a recipe consumes, what a set bonus counts, what an exchange accepts. Off-chain that mapping lives in the game's database, and tokenURI publishes it for display. On-chain there is nothing to read.

Projects work around this in ways that do not interoperate. Some pack the item into the upper bits of tokenId under a private scheme, which bounds both ranges and requires every reader to know the layout. Others expose a getter under whichever name they picked — itemId, classId, item, series — so every consumer writes a bespoke adapter. A deployed system known to the author declares a three-function interface — ownerOf, transferFrom, and its own such getter — purely so one contract can read another's classification. None of these generalise.

ERC-721 caps the collection, not the item

An edition size belongs to an item. "One hundred Flame Swords" is a statement about an item, and ERC-721 has no place to put it. totalSupply, where implemented, counts the whole collection and says nothing about how many of one item exist.

So the number lives in metadata, where nothing enforces it. A JSON file claiming "maxSupply": 100 is served from a host the issuer controls, can be rewritten, and cannot be fetched or parsed by a contract in the first place. The cap exists as a promise.

That gap matters most when the cap is an input to something rather than a label. Where a per-item cap is registered with a contract that prices a collection against a fungible token, the cap becomes a term in a pricing formula, and a cap the issuer can quietly raise reprices everyone's holdings. An interface standard should be able to express that constraint, and a counterparty should be able to verify it.

The two are one mechanism

A cap has to be charged against something. The contract must know at mint time which item the new token belongs to, and must expose that association afterwards so the count can be audited. Recording the item is what makes the cap enforceable; exposing the item is what makes it verifiable.

Reading the item then becomes useful on its own — a marketplace can group by it, a lending protocol can price scarcity by it, an index can weight by it — but this ERC standardises it because supply enforcement requires it.

Two properties make the cap meaningful, and both are requirements here rather than implementation choices. The cap bounds cumulative mints, not circulating supply: if burning returned capacity, an issuer could mint, burn and mint again indefinitely under a cap advertised as fixed. And the cap is write-once: a cap that can be raised after creation is worth no more than the metadata it replaces.

Where existing standards fall short

StandardLimitation for this use case
ERC-721No items, so nothing to classify by and no per-item cap to enforce. Collection-wide supply says nothing about the scarcity of one item.
ERC-1155Has per-id supply, but tokens sharing an id are indistinguishable — balanceOf(owner, id) returns a quantity, so two of them cannot be approved or transferred independently or carry different provenance. No edition number, no individual identity, no standard cap. An enhanced sword cannot differ from an unenhanced one of the same kind.
ERC-3525Provides slot plus value, but its core feature is splitting and merging value, designed for financial instruments. It carries no supply cap semantics, and fractionalising a game item or a ticket is meaningless.
ERC-3440Caps a limited edition run and fixes it on set, which is the closest existing behaviour. The cap is one run per contract: a collection holding a thousand kinds of item needs a thousand contracts. Its subject is artist signatures for provenance rather than classification, and it exposes no way to ask which kind a token is.
ERC-7496Puts arbitrary traits on-chain, so an issuer could store a kind under a trait key. Traits are mutable by design and carry no supply accounting, so a cap stored as a trait is a number the issuer can rewrite — the same guarantee metadata already fails to give.

The shared gap is level. Where a cap exists at all, it covers the contract. This ERC puts it one level down: one contract holds many items, each with its own enforced cap, and any caller can ask which item a token is and how many of that item may ever exist.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Every compliant contract MUST implement IERC8418, ERC-721, and ERC-165.

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

interface IERC8418 /* is IERC721, IERC165 */ {
    /// @notice MUST be emitted when a new item is created.
    /// @param itemId The identifier of the newly created item.
    /// @param maxSupply The maximum cumulative mints for this item.
    ///        MUST be in the range 1 to `type(uint256).max`. MUST NOT be 0.
    event ItemCreated(uint256 indexed itemId, uint256 maxSupply);

    /// @notice MUST be emitted in the same transaction as the ERC-721 Transfer
    ///         event whose `from` is the zero address.
    /// @param tokenId The minted token.
    /// @param itemId The item the token belongs to.
    event TokenMinted(uint256 indexed tokenId, uint256 indexed itemId);

    /// @notice Returns the item identifier of a token.
    /// @dev MUST revert if `tokenId` does not exist.
    function tokenItemId(uint256 tokenId) external view returns (uint256);

    /// @notice Returns whether an item has been created.
    function itemExists(uint256 itemId) external view returns (bool);

    /// @notice Returns the maximum cumulative mints for an item.
    /// @dev MUST revert if `itemId` does not exist. Never returns 0.
    function itemMaxSupply(uint256 itemId) external view returns (uint256);

    /// @notice Returns the number of tokens currently in circulation for an item.
    /// @dev MUST revert if `itemId` does not exist. MUST NOT count burned tokens.
    function itemSupply(uint256 itemId) external view returns (uint256);

    /// @notice Returns the total number of tokens ever minted for an item.
    /// @dev MUST revert if `itemId` does not exist.
    ///      MUST equal `itemSupply(itemId) + itemBurnedCount(itemId)`.
    function itemTotalMinted(uint256 itemId) external view returns (uint256);

    /// @notice Returns the number of burned tokens for an item.
    /// @dev MUST revert if `itemId` does not exist.
    function itemBurnedCount(uint256 itemId) external view returns (uint256);
}

Behavioral requirements

  1. Item assignment happens at mint and only at mint. A token MUST be assigned its item in the same transaction it is minted, and tokenItemId MUST NOT change for as long as the token exists. An implementation MUST NOT expose a path that assigns an item to an existing token, including one that has never had an item. Assignment after the fact would let an issuer choose which cap a token counts against once its scarcity is known.

  2. Supply invariant. For any existing item t, itemTotalMinted(t) == itemSupply(t) + itemBurnedCount(t) MUST hold at all times.

  3. Max supply is enforced. itemTotalMinted(t) <= itemMaxSupply(t) MUST hold at all times. Any mint that would violate this MUST revert. There is no exemption: every existing item has a cap, and the largest one expressible is type(uint256).max.

  4. Max supply is immutable. Once an item is created, itemMaxSupply for that item MUST NOT change. An implementation MUST NOT expose any path that raises or lowers a cap after creation. A mutable cap voids the scarcity guarantee this ERC exists to provide.

  5. Zero is reserved. itemMaxSupply MUST NOT be 0 for an existing item. A stored value of 0 denotes an item that has not been created, which is how itemExists is determined.

  6. Burned tokens revert. tokenItemId MUST revert for a burned token, matching ERC-721's behaviour for ownerOf. In this ERC a token is minted when an ERC-721 Transfer with from equal to the zero address is emitted for it, and burned when a Transfer with to equal to the zero address is emitted for it.

  7. Token identifiers are never reused. Within a compliant contract, a tokenId that has ever been minted MUST NOT be minted again, whether or not the token it identified has since been burned. A tokenId therefore denotes one token of one item for the life of the contract. This ERC fixes the guarantee and leaves the method to the implementer: allocating identifiers from a counter that never decreases satisfies it structurally, and an implementation that accepts identifiers from the caller MUST reject any it has already issued.

ERC-165 interface detection

Compliant contracts MUST return true from supportsInterface for the IERC8418 interface identifier and for 0x80ac58cd (ERC-721).

Optional extension: edition numbers

An implementation MAY expose a token's position within its item.

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

interface IERC8418Edition /* is IERC8418 */ {
    /// @notice Returns the edition number of a token within its item.
    /// @dev MUST revert if `tokenId` does not exist.
    function tokenEdition(uint256 tokenId) external view returns (uint256);
}

Where implemented:

  • Edition numbers MUST start at 1 and MUST increment by 1 for each mint within the item.
  • An edition number MUST NOT be reused after the corresponding token is burned.
  • tokenEdition MUST revert for burned tokens.

Rationale

Not encoding the item in tokenId

Packing itemId into the upper bits of tokenId is a common pattern, but no standard defines which bits mean what. Other contracts cannot decode an item without knowing the project's scheme, and bit-packing bounds the ranges of both values. A tokenItemId(tokenId) call is interoperable regardless of internal storage.

Not recording the cap in tokenURI metadata

tokenURI returns a URI to off-chain JSON. Contracts cannot fetch and parse it, the server can change or disappear, and nothing on-chain enforces that the metadata matches contract state. itemMaxSupply gives a guarantee metadata cannot.

Why on-chain enumeration is not part of this ERC

Walking every item, or every token of an item, is the kind of query an indexer answers cheaply and a contract answers badly. Maintaining the arrays costs gas on every mint and burn, and reading them at any real supply approaches the block gas limit, so the getter a consumer was promised stops working exactly when the collection gets large enough to need it. ItemCreated and TokenMinted carry the same information to an off-chain indexer at no on-chain cost. ERC-721 Enumerable exists and is widely skipped for these reasons; this ERC does not repeat it.

Why edition numbers are an extension rather than core

The core guarantee — an item recorded per token and a verifiable cap on that item — is complete without edition numbers. The cap is enforced through itemTotalMinted, and individual identity comes from ERC-721 itself. An implementation that does not need "42 of 100" should not pay a storage slot per token for it.

The extension exists rather than being dropped entirely because the number is only interpretable if everyone computes it the same way. Left unspecified, one collection numbers from 0, another reuses the numbers of burned tokens, a third numbers across the whole collection instead of within an item. A consumer reading "edition 42" could not tell what it means. Fixing the name, the base and the reuse rule is what makes the value portable.

1-based edition numbers

Physical collectibles, art prints, and tickets number editions from 1 ("Edition 1 of 100"). Zero-based numbering would conflict with that convention in every user-facing surface.

Why all three supply counts are exposed

Requirement 2 ties the three together — itemTotalMinted == itemSupply + itemBurnedCount — so any one of them is derivable from the other two, and a smaller interface could drop one.

They are all exposed because an implementation that enforces the cap correctly already stores all three. Circulating supply falls on a burn while the cumulative count does not, so the two have to be tracked separately, and the burn count is the difference the implementation is already maintaining. Adding the getter costs no storage and no additional write; omitting it would save nothing on the issuing side while making every consumer that wants the number spend a second external call and a subtraction.

The three also answer different questions. itemSupply is how many exist now, which is what a wallet or marketplace displays. itemTotalMinted is how many were ever issued, which is what the cap constrains. itemBurnedCount is how many were destroyed, which is what a holder reading scarcity after a burn event wants. Forcing a consumer to reconstruct one of these invites the wrong subtraction against the wrong pair, and that error is silent.

Why there is no uncapped item

An uncapped item cannot be implemented. itemTotalMinted is a uint256, so no item can ever hold more than type(uint256).max tokens; the counter bounds issuance whether or not the interface names a cap. "Unlimited" would therefore be a label on a limit that already exists.

Treating type(uint256).max as an ordinary cap rather than a sentinel removes a branch from every implementation and a case from every consumer. Requirement 3 holds uniformly, the mint guard is one comparison with no exemption, and a reader never has to ask whether the number it just read is a quantity or a flag. An issuer who wants an item to be practically unbounded sets the cap to type(uint256).max and gets exactly the semantics the name suggests.

Why 0 is reserved for non-existence

An unset entry in a mapping reads as 0, so 0 is already the natural encoding of "this item was never created". Assigning it any other meaning would require a separate existence flag in storage, and a caller that skipped itemExists would silently read an uncreated item as whatever that meaning was. Reserving 0 keeps the unsafe reading impossible and lets itemExists be derived from the same slot the cap lives in.

Why the cap is immutable

The point of putting itemMaxSupply on-chain is that another contract can read an item's issuance bound without trusting the issuer to report it honestly. A cap the issuer can raise later provides no more assurance than metadata, which is the problem this ERC set out to solve. Lowering a cap is equally unsafe: it can be set below itemTotalMinted, breaking requirement 3 retroactively.

Why token identifier allocation is unspecified

Implementations may assign tokenId sequentially or accept it from the caller. Caller-assigned identifiers let a system preserve a token's identity across chains or align it with an identifier space that already exists off-chain; sequential assignment is simpler and removes collision handling. Both satisfy this interface, and the choice does not affect how a consumer reads item information, so this ERC does not constrain it.

Requirement 7 constrains the one aspect of allocation that a consumer can observe — whether an identifier can come back meaning something else — and leaves the rest open. A sequential allocator gets it for free; a caller-assigned one pays a check against the identifiers it has already issued.

Why a token identifier is never reused

Requirement 6 already makes a burned token unreadable, and requirement 1 already fixes an item at mint. Neither prevents identifier 42 from being burned and minted again under a different item, and that sequence is what breaks consumers that hold no state of their own.

An ERC-721 marketplace binds an order to (collection, tokenId), because under ERC-721 that pair is the token. A signed offer for token 42 made while 42 was a rare item stays valid by its own terms after 42 is burned and re-minted as a common one, and the contract settling it has no way to notice: it cannot read past events, and every value it can read at execution describes the new token. The holder of the offer gets the item they signed for in name and a different one in fact.

Requiring the identifier to be retired closes this at the source rather than asking every integrator to defend against it. The alternative — permitting reuse and telling integrators to bind the expected itemId into signed orders and check it at execution — works only for integrators that have been told, and the collections it fails on are indistinguishable from the outside. A guarantee that holds for plain ERC-721 tooling is worth more than one that holds for tooling updated to know about this ERC, because the first is what a compliant token meets in the wild.

The cost falls on issuance and is bounded. An implementation that allocates from a counter pays nothing, because an identifier it has never issued cannot be one it has issued before. One that accepts identifiers from the caller keeps a record of what it has issued, which is one slot per token where the association is not already kept for the life of the contract. Neither pays anything in identifier space: tokenId is a uint256, and retiring one per burn cannot exhaust it.

This ERC constrains minting without defining it

ERC-721 has no mint function. Issuance is a convention — a Transfer from the zero address — and every implementation supplies its own entry point. This ERC therefore adds a requirement to an operation ERC-721 leaves open rather than changing one it defines: a token must receive its item as it comes into existence. Nothing ERC-721 specifies behaves differently under this ERC, which is why a compliant token remains usable by tooling that has never heard of it.

The constraint is what makes the supply counters trustworthy. If an item could be attached later, the count for an item would reflect the issuer's subsequent choices rather than what was issued, and itemTotalMinted would stop being a record of issuance.

Issuance policy itself stays open. Operator-restricted, publicly payable and burn-prohibited implementations all satisfy this ERC, which standardises the read interface and leaves write operations to implementers as ERC-721 does.

Backwards Compatibility

For consumers

This ERC is backwards compatible with ERC-721. A compliant token is an ERC-721 token with additional view functions and one additional event. Transfer, approval and ownership behave exactly as ERC-721 specifies, so existing marketplaces, wallets and indexers continue to work against a compliant collection without changes; they ignore the item information until they are updated to read it.

For existing collections

A collection already deployed as a plain ERC-721 cannot become compliant by upgrading in place.

Its tokens were issued before the contract had any notion of an item, so nothing recorded which item each one belongs to. A proxy upgrade can add the storage and the getters, but every existing token would then read as belonging to no item, and filling those values in is precisely what requirement 1 forbids: an item is assigned in the transaction that mints the token, and those mints are in the past.

There are two paths for a collection that wants these guarantees. It can issue a new contract for future issuance and leave the old one alone. Or it can migrate: burn each existing token in the old contract and mint its replacement in the new compliant one, assigning the item in that mint transaction. The second path satisfies requirement 1, and it is visible — the burn and the mint appear on-chain as Transfer events to and from the zero address in the two contracts, so any holder or integrator can see that the collection's items were assigned during a migration rather than at original issuance, and judge the collection accordingly.

Requirement 7 is not an obstacle to either path, since it binds identifiers within a single contract and the replacements are minted in a contract that has issued none. A migration that wants to carry identifiers across can reuse the old tokenId values in the new contract unchanged.

Test Cases

The following cases capture the behavioral requirements. T denotes an item with maxSupply = 3.

#SequenceExpected
1createItem(T, 3)itemExists(T) == true, itemMaxSupply(T) == 3, itemSupply(T) == 0
2mint 3 tokens of TitemSupply(T) == 3; itemTotalMinted(T) == 3
3mint a 4th token of Treverts (requirement 3)
4burn the second minted tokenitemSupply(T) == 2, itemBurnedCount(T) == 1, itemTotalMinted(T) == 3
5tokenItemId on the burned tokenreverts (requirement 6)
6mint another token of Treverts — itemTotalMinted is already at maxSupply (requirement 3)
7itemMaxSupply(U) for uncreated Ureverts
8item with maxSupply == type(uint256).maxmints until the counter is exhausted; no special case, invariant in requirement 2 holds throughout
9createItem(T, 0)reverts (requirement 5)
10Edition extension: burn edition 2, then mintthe new token is edition 4, never 2
11item V with maxSupply == 2: mint tokenId 7, burn it, mint tokenId 7 againreverts (requirement 7), although itemTotalMinted(V) == 1 leaves capacity

Case 6 is the case implementations most often get wrong: burning MUST NOT free capacity, because maxSupply bounds cumulative mints, not circulating supply.

Case 11 separates the two rules. The mint is refused for the identifier, not for the cap: V has capacity remaining, and minting a token of V under any unused identifier succeeds.

Reference Implementation

The getters are mechanical and are omitted. What follows is the part an implementation can get wrong: the storage that makes the invariant hold, the write-once cap, and the mint guard.

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

// maxSupply >= supply + burnedCount, and maxSupply != 0 for an existing item
struct Item {
    uint256 maxSupply;
    uint256 supply;
    uint256 burnedCount;
}

mapping(uint256 itemId => Item) private _items;
mapping(uint256 tokenId => uint256) private _tokenItemId;

// Set on mint and never cleared, so a burned identifier stays retired.
// An implementation that allocates tokenId from a monotonic counter satisfies
// requirement 7 without this mapping. One that wants the check without the extra
// slot can store itemId + 1 in _tokenItemId, keep it after burn, and read a
// non-zero entry as "issued" — this version keeps the two separate to read plainly.
mapping(uint256 tokenId => bool) private _tokenIdUsed;

function createItem(uint256 itemId, uint256 maxSupply) external onlyOwner {
    if (maxSupply == 0) revert ZeroMaxSupply();              // requirement 5
    if (_items[itemId].maxSupply != 0) revert ItemExists();  // requirement 4: write-once
    _items[itemId].maxSupply = maxSupply;
    emit ItemCreated(itemId, maxSupply);
}

function mint(address to, uint256 itemId, uint256 tokenId) external onlyOwner {
    Item storage i = _items[itemId];
    if (i.maxSupply == 0) revert ItemDoesNotExist();
    if (_tokenIdUsed[tokenId]) revert TokenIdUsed();         // requirement 7

    // Compare before incrementing. `supply + burnedCount` is the cumulative mint
    // count, so burning never returns capacity. Incrementing first and checking
    // afterwards wraps at maxSupply == type(uint256).max.
    if (i.supply + i.burnedCount >= i.maxSupply) revert MaxSupplyReached();

    unchecked { i.supply += 1; }
    _tokenIdUsed[tokenId] = true;
    _tokenItemId[tokenId] = itemId;
    _safeMint(to, tokenId);                       // emits Transfer from address(0)

    emit TokenMinted(tokenId, itemId);
}

function burn(uint256 tokenId) external {
    uint256 itemId = _tokenItemId[tokenId];
    unchecked {
        _items[itemId].supply -= 1;
        _items[itemId].burnedCount += 1;   // cumulative count is unchanged
    }
    delete _tokenItemId[tokenId];    // _tokenIdUsed[tokenId] stays set
    _burn(tokenId);
}

Security Considerations

Max supply integrity

itemMaxSupply bounds cumulative mints, not circulating supply. Burning MUST NOT free capacity. An implementation that reduces the cumulative count on burn allows unbounded issuance of an item advertised as capped, breaking the scarcity guarantee that is the point of this ERC. The invariant itemTotalMinted <= itemMaxSupply MUST hold under reentrancy and batch operations.

The cap MUST also be write-once. An implementation that lets an operator raise a cap after creation gives a consumer no more assurance than off-chain metadata; one that lets a cap be lowered can put it below itemTotalMinted, breaking the invariant for tokens already issued. Upgradeable implementations MUST treat the cap storage as immutable across upgrades, since a proxy that rewrites it defeats the check even when the current implementation has no setter.

Scope of the supply guarantee

This ERC bounds cumulative mints per itemId. It places no bound on how many items an issuer creates, and it says nothing about the relationship between an item and anything outside the contract. An issuer that has exhausted an item's cap can create a second item that is materially identical and mint under that one.

A consumer that treats an item as a proxy for a kind of thing — a marketplace grouping listings, a lending protocol pricing collateral — is relying on the issuer's item taxonomy rather than on this ERC. What this ERC makes verifiable is narrower and exact: for a given itemId, the cap was fixed at creation and no mint has ever exceeded it. A claim about the scarcity of the underlying kind has to be established out of band.

Counter bounds at the maximum cap

At itemMaxSupply == type(uint256).max the cumulative mint count can approach the width of the type. Implementations MUST keep itemSupply + itemBurnedCount from wrapping: the guard has to compare before incrementing, so the cumulative count stops at the cap rather than passing it. An implementation that increments first and checks afterwards can wrap to zero at this cap and reopen unbounded issuance on an item, which is the one place where the largest cap behaves differently from a small one.

Item creation control

Item identifiers SHOULD be assigned through a controlled mechanism — sequential assignment or a permissioned creator. An implementation that allows arbitrary callers to create items permits an attacker to occupy identifiers that the issuer intended to use, or to create items with misleading supply caps under a contract users trust.

Item immutability and downstream caching

A token's item is fixed for the life of that token, and implementations MUST NOT provide any path that reassigns it, including through upgrades that rewrite token storage. Downstream contracts may cache tokenItemId for a token that exists and use it for access control, pricing, or collateral valuation.

Requirement 7 makes that cache safe to hold. A tokenId is retired when its token is burned, so the identifier never comes back under another item and a cached association cannot go stale. A consumer that reads tokenItemId once and stores the result is reading a value that is correct for the life of the contract. tokenEdition behaves the same way where the extension is implemented, since edition numbers are never reused either.

What a consumer MUST still track is existence. A cached item survives the burn; the token does not. Access control, pricing and collateral valuation MUST be keyed on the token still existing — ownerOf reverts for a burned token, and ERC-721 Transfer to the zero address reports the burn — because a stale entry that says the token exists is a live exposure even when the item it names is right. Requirement 7 removes the substitution risk, not the need to notice a burn.

This guarantee is contract-local. Requirement 7 binds identifiers within one compliant contract and says nothing about another contract that happens to use the same numbers, so a cache keyed on tokenId alone rather than (collection, tokenId) is unsafe for reasons this ERC does not address.

Burned token data

tokenItemId reverts for burned tokens, as does tokenEdition where the Edition extension is implemented. This matches ERC-721's behaviour for ownerOf and provides no privacy. TokenMinted is emitted for every mint and ERC-721 Transfer for every transfer, so a token's item and its full ownership history stay readable from logs after the burn. An implementation that wants burned tokens to remain queryable may expose that data through additional getters without weakening any guarantee in this ERC.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth