ERC-7160 NFT Multi-Metadata Extension (ERC-721 Multi-Metadata Extension)
Final · NFTs & multi-token · created 2023-06-09
An ERC-721 extension standard that lets a single NFT carry multiple metadata addresses (URIs) and pin one of them as the representative.
Why it is needed
The existing ERC-721 metadata extension (ERC721Metadata) allows only one metadata address per token. However, some use cases need multiple addresses: a single token that bundles several assets, a token that records its metadata edit history on-chain, a token that serves different images for different aspect ratios, or a token that holds a work created together by several artists. Without a standard, each project stores multiple metadata in its own way, and apps (dApps) and marketplaces have no common way to find out or display which addresses a token has. The pin and unpin feature, which lets an owner choose which of several metadata to show as the representative, also has to be implemented separately by each service, which makes wide support difficult.
What to implement
event TokenUriPinned(uint256 indexed tokenId, uint256 indexed index);: An event emitted when a specific URI of a token is pinned. It is used for indexing.event TokenUriUnpinned(uint256 indexed tokenId);: An event emitted when a token's pin is released.function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris, bool pinned);: Returns, in one call, the array of all URIs linked to the token, the index currently pointed to, and whether a URI is pinned. It must revert if the token does not exist.function pinTokenURI(uint256 tokenId, uint256 index) external;: Pins the URI at position index in the array returned by tokenURIs as the token's representative. It must emit the TokenUriPinned event.function unpinTokenURI(uint256 tokenId) external;: Releases the token's pin. The behavior after release is intentionally left for developers to decide. It must emit the TokenUriUnpinned event.function hasPinnedTokenURI(uint256 tokenId) external view returns (bool pinned);: Checks on-chain whether the token has a pinned URI. This is useful for on-chain logic that does not need the URI contents.
In depth
tokenURIs: a lookup function that returns three values at once
tokenURIs(uint256 tokenId) returns (uint256 index, string[] memory uris, bool pinned). uris is the array of all URIs linked to the token, index is the array index of the pinned URI (or the index of the default URI if nothing is pinned), and pinned indicates whether a URI is pinned. It must revert if the token does not exist. The Rationale explains that a design that returns only a string array was considered, but additional information is returned together to reduce three calls to one.
pin and unpin: state changes that require events
pinTokenURI(tokenId, index) pins the URI at position index in the tokenURIs array and MUST emit the TokenUriPinned event when called. unpinTokenURI(tokenId) releases the pin and MUST emit the TokenUriUnpinned event. Both functions must revert if the token does not exist, and they MAY additionally emit the ERC-4906 MetadataUpdate event. The specific behavior after release is intentionally left for developers to decide. The reference implementation restricts both functions so that only the token owner can call them.
Backward compatibility with the existing tokenURI
The tokenURI function of the ERC-721 metadata extension MUST return the pinned URI if the token has one, and MUST return the default URI if nothing is pinned. Thanks to this rule, existing apps that know nothing about ERC-7160 receive the correct representative metadata by calling only tokenURI. The reference implementation uses the last item of the array (length - 1) as the default URI when nothing is pinned.
Interface identifier and ERC-165
The ERC-165 identifier of this interface is 0x06e1bc5b. A call to supportsInterface(0x06e1bc5b) MUST return true. With this check, an app can know in advance whether a contract supports multi-metadata.
Adding and removing URIs is outside the standard, ERC-4906 recommended alongside
The feature for adding or removing URIs on a token MUST be implemented separately from this standard. When adding or removing, emitting the events defined in ERC-4906 is RECOMMENDED. The Rationale explains that many apps already support ERC-4906 events, that they fit the purpose of notifying that metadata has changed, and that using them avoids app problems caused by duplicate events. A party that receives the event can call tokenURIs to fetch the updated list.
Event-based indexing and use cases
By subscribing to the TokenUriPinned and TokenUriUnpinned events, an app can index which metadata to display on an event basis, without on-chain calls. Use cases cited in the source text include rotating asset collections, on-chain records of metadata edit history, metadata per aspect ratio, dynamically evolving metadata, and joint tokens by multiple artists. The pinned URI can be used as the token's representative, and the rest of the list can be shown as a gallery or media carousel.
Things to watch out for
- Access control for state-changing functions: Access control must be specified carefully for functions that change state, such as the feature that adds URIs to a token and this standard's pinTokenURI and unpinTokenURI. Which conditions allow pinning and releasing differs by application, so developers must decide.