diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 8427bfef0c012f..9e72bfdd94276e 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -37,36 +37,45 @@ The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL ```solidity /// @title EIP-721 Multi-Metdata Extension -/// @dev See https://eips.ethereum.org/EIPS/eip-721 -/// Note: the ERC-165 identifier for this interface is 0x06e1bc5b. -interface IERC721MultiMetadata /* is IERC721Metadata */ { +/// @dev The ERC-165 identifier for this interface is 0x06e1bc5b. +interface IERC7160 { + /// @dev This event emits when a token uri is pinned and is /// useful for indexing purposes. - event TokenUriPinned(uint256 indexed tokenId, uint256 indexed index, address indexed sender); + event TokenUriPinned(uint256 indexed tokenId, uint256 indexed index); /// @dev This event emits when a token uri is unpinned and is /// useful for indexing purposes. - event TokenUriUnpinned(uint256 indexed tokenId, address indexed sender); + event TokenUriUnpinned(uint256 indexed tokenId); /// @notice Get all token uris associated with a particular token - /// @dev If a token uri is pinned, the index returned should be the index in the string array + /// @dev If a token uri is pinned, the index returned SHOULD be the index in the string array + /// @dev This call MUST revert if the token does not exist /// @param tokenId The identifier for the nft /// @return index An unisgned integer that specifies which uri is pinned for a token (or the default uri if unpinned) /// @return uris A string array of all uris associated with a token - function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris); + /// @return pinned A boolean showing if the token has pinned metadata or not + function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris, bool pinned); /// @notice Pin a specific token uri for a particular token + /// @dev This call MUST revert if the token does not exist + /// @dev This call MUST emit a `TokenUriPinned` event + /// @dev This call MAY emit a `MetadataUpdate` event from ERC-4096 /// @param tokenId The identifier of the nft /// @param index The index in the string array returned from the `tokenURIs` function that should be pinned for the token function pinTokenURI(uint256 tokenId, uint256 index) external; /// @notice Unpin metadata for a particular token - /// @dev This should reset the token to the default uri + /// @dev This call MUST revert if the token does not exist + /// @dev This call MUST emit a `TokenUriUnpinned` event + /// @dev This call MAY emit a `MetadataUpdate` event from ERC-4096 + /// @dev It is up to the developer to define what this function does and is intentionally left open-ended /// @param tokenId The identifier of the nft function unpinTokenURI(uint256 tokenId) external; /// @notice Check on-chain if a token id has a pinned uri or not - /// @dev Useful for on-chain mechanics + /// @dev This call MUST revert if the token does not exist + /// @dev Useful for on-chain mechanics that don't require the tokenURIs themselves /// @param tokenId The identifier of the nft /// @return pinned A bool specifying if a token has metadata pinned or not function hasPinnedTokenURI(uint256 tokenId) external view returns (bool pinned); @@ -77,25 +86,25 @@ The `TokenUriPinned` event MUST be emitted when pinning a token uri with the `pi The `TokenUriUnpinned` event MUST be emitted when unpinning a token uri with the `unpinTokenUri` function. -The `tokenURI` function defined in the ERC-721 Metadata standard MUST return the pinned URI when a token has a pinned uri. The `tokenURI` fucntion MUST return a default uri when a token has an unpinned uri. Which uri is returned when unpinned is up to the developer and is not specified in this standard. This ensures backwards compatibility with existing contracts and applications that rely on the single metadata URI. +The `tokenURI` function defined in the ERC-721 Metadata extension MUST return the pinned URI when a token has a pinned uri. + +The `tokenURI` function defined in the ERC-721 Metadata extension MUST return a default uri when a token has an unpinned uri. The `supportsInterface` method MUST return `true` when called with `0x06e1bc5b`. -Implementing functionality to add uris to a token MUST be implemented separately from this standard. A `MetadataUpdate` or `BatchMetadataUpdate` event SHOULD be emitted when adding a uri to a token. +Implementing functionality to add or remove uris to a token MUST be implemented separately from this standard. It is RECOMMENDED that one of the event defined in [ERC-4906](./eip-4906.md) are emitted whenever uris are added or removed. See the [Implementation](#reference-implementation) section for an example. ## Rationale -The `tokenURIs` function MUST revert if the token does not exist. - -The `tokenURIs` function returns both the pinned URI index and the list of all metadata URIs to provide flexibility in accessing the metadata. +Similar terminology to [ERC-721](./eip-721.md) was used in order to keep fetching metadata familiar. The concept of pinning and unpinning metadata is introduced as it is clear that NFT owners might want to choose which piece of metadata to display. At first, we considered leaving the pinning and unpinning actions up to each developer, but realized that a standard interface for pinning and unpinning allows for dApps to easily implement universal support for multi-metadata tokens. -The pinned URI can be used as a default or primary URI for the token, while the list of metadata URIs can be used to access individual assets' metadata within the token. Marketplaces could present these as a gallery or media carousels. +We first considered whether the `tokenURIs` function should return just a string array, but added the extra information so that you could get all info desired in one call instead of potentially three calls. The pinned URI should be used as the primary URI for the token, while the list of metadata URIs can be used to access individual assets' metadata within the token. dApps could present these as a gallery or media carousels. -Depending on the implementation, the `pinTokenURI` function allows the contract owner or token owner to specify a particular fixed metadata URI index for a token. This enables the selection of a preferred URI by index from the list of available metadata. +The `TokenUriPinned` and `TokenUriUnpinned` events included in this specification can be used by dApps to index what metadata to show. This can eliminate on-chain calls and event driven architecture can be used instead. -When unpinned, it is recommended to return the last URI for the token. However the behavior in the case of unpinned tokens is at the discretion of the implementation and depends on the specific purpose of the token. +The reason why this standard recommends the use of [ERC-4906](./eip-4906.md) when adding or removing uris from a token is that there is already wide dApp support for this event and it already is what is needed - an alert to dApps that metadata for a token has been updated. We did not want to potentially cause dApp issues with duplicate events. A third party listening to this event could then call the `tokenURIs` function to get the updated metadata. ## Backwards Compatibility @@ -113,9 +122,9 @@ pragma solidity ^0.8.19; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol"; -import {IERC721MultiMetadata} from "./IERC721MultiMetadata.sol"; +import {IERC7160} from "./IERC7160.sol"; -contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { +contract MultiMetadata is ERC721, Ownable, IERC7160, IERC4906 { mapping(uint256 => string[]) private _tokenURIs; mapping(uint256 => uint256) private _pinnedURIIndices; mapping(uint256 => bool) private _hasPinnedTokenURI; @@ -144,9 +153,9 @@ contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { } /// @inheritdoc IERC721MultiMetadata.tokenURIs - function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris) { + function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris, bool pinned) { _requireMinted(tokenId); - return (_getTokenURIIndex(tokenId), _tokenURIs[tokenId]); + return (_getTokenURIIndex(tokenId), _tokenURIs[tokenId], _hasPinnedTokenURI[tokenId]); } /// @inheritdoc IERC721MultiMetadata.pinTokenURI @@ -154,7 +163,7 @@ contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = index; _hasPinnedTokenURI[tokenId] = true; - emit TokenUriPinned(tokenId, index, msg.sender); + emit TokenUriPinned(tokenId, index); } /// @inheritdoc IERC721MultiMetadata.unpinTokenURI @@ -162,11 +171,11 @@ contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = 0; _hasPinnedTokenURI[tokenId] = false; - emit TokenUriUnpinned(tokenId, msg.sender); + emit TokenUriUnpinned(tokenId); } /// @inheritdoc IERC721MultiMetadata.hasPinnedTokenURI - function hasPinnedTokenURI(uint256 tokenId) external view returns (bool isPinned) { + function hasPinnedTokenURI(uint256 tokenId) external view returns (bool pinned) { return _hasPinnedTokenURI[tokenId]; } @@ -178,14 +187,13 @@ contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { _tokenURIs[tokenId].push(uri); } - // Emit a MetadataUpdate event (see EIP-4906). emit MetadataUpdate(tokenId); } // Overrides supportsInterface to include IERC721MultiMetadata interface support. function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { return ( - interfaceId == type(IERC721MultiMetadata).interfaceId || + interfaceId == type(IERC7160).interfaceId || super.supportsInterface(interfaceId) ); }