From 64f6e0247a4b04f6b805e6ec797113909536fafb Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Fri, 9 Jun 2023 16:49:52 +0200 Subject: [PATCH 01/15] ERC721 Multi-Metadata Extension --- EIPS/erc-multimetadata.md | 136 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 EIPS/erc-multimetadata.md diff --git a/EIPS/erc-multimetadata.md b/EIPS/erc-multimetadata.md new file mode 100644 index 00000000000000..40cda780a71bb0 --- /dev/null +++ b/EIPS/erc-multimetadata.md @@ -0,0 +1,136 @@ +--- +title: ERC721 Multi-Metadata Extension +description: "This EIP proposes an extension to the ERC721 standards to support multiple metadata URIs per token via a new tokenURIs method that returns the pinned metadata index and a list of metadata URIs." +author: 0xG (@0xGh) +discussions-to: +status: Draft +type: Standards Track +category: ERC +created: 2023-06-09 +requires: EIP-165, EIP-721 +--- + +## Abstract + +This EIP proposes an extension to the ERC721 standards to support multiple metadata URIs per token. It introduces a new interface, IERC721MultiMetadata, which provides methods for accessing the metadata URIs associated with a token, including a pinned URI index and a list of all metadata URIs. The extension is designed to be backward compatible with existing ERC721Metadata implementations. + +## Motivation + +The current ERC721 standards allow for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. + +## Specification + +The IERC721MultiMetadata interface extends the existing IERC721 interface and introduces two additional methods: + +```solidity +interface IERC721MultiMetadata is IERC721 { + function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris); + function pinTokenURI(uint256 tokenId, uint256 index) external; + function unpinTokenURI(uint256 tokenId) external; + function isPinnedTokenURI(uint256 tokenId) external view returns (bool pinned); +} +``` + +The `tokenURIs` function returns a tuple containing the pinned URI index and a list of all metadata URIs associated with the specified token. The `pinTokenURI` function allows the contract owner to designate a specific metadata URI as the pinned URI for a token. + +The `tokenURI` function defined in the ERC721 standard can be modified to return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. + +See the [Implementation](#Implementation) section for an example. + +## Rationale + +The `tokenURIs` function returns both the pinned URI index and the list of all metadata URIs to provide flexibility in accessing the metadata. 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. + +Depending on the implementation, the `pinTokenURI` function allows the contract owner or token owner to specify a particular metadata URI index for a token. This enables the selection of a preferred URI by index from the list of available metadata. + +## Backward Compatibility + +This extension is designed to be backward compatible with existing ERC721 contracts. The modified `tokenURI` function can either return the last URI in the list returned by tokenURIs or the pinned URI, depending on the implementation. + +## Security Considerations + +Care should be taken when implementing the extension to ensure that the metadata URIs are properly handled and validated. It is crucial to prevent malicious actors from manipulating or injecting harmful metadata. + +## Test Cases + +Test cases should be provided to cover the functionality of the IERC721MultiMetadata interface, including retrieving the pinned URI, retrieving the list of metadata URIs, and pinning/unpinning a specific metadata URI for a token. Additionally, tests should be performed to validate the backward compatibility of existing ERC721 contracts. + +## Implementation + +An open-source reference implementation of the IERC721MultiMetadata interface can be provided, demonstrating how to extend an existing ERC721 contract to support multi-metadata functionality. This reference implementation can serve as a guide for developers looking to implement the extension in their own contracts. + +```solidity +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./IERC721MultiMetadata.sol"; + +contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { + mapping(uint256 => string[]) _tokenURIs; + mapping(uint256 => uint256) _pinnedURIIndices; + mapping(uint256 => bool) _isPinnedTokenURI; + + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} + + // Returns the pinned URI index or the last token URI index (length - 1). + function _getTokenURIIndex(uint256 tokenId) internal view returns (uint256) { + return _isPinnedTokenURI[tokenId] ? _pinnedURIIndices[tokenId] : _tokenURIs[tokenId].length - 1; + } + + // Implementation of ERC721.tokenURI for backwards compatibility. + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + _requireMinted(tokenId); + + string memory uri = _tokenURIs[_getTokenURIIndex(tokenId)]; + + // Revert if no URI is found for the token. + require(bytes(uri).length > 0, "ERC721: not URI found"); + return uri; + } + + // Retrieves the pinned URI index and the list of all metadata URIs associated with the specified token. + function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris) { + _requireMinted(tokenId); + return (_getTokenURIIndex(tokenId), _tokenURIs[tokenId]); + } + + // Sets a specific metadata URI as the pinned URI for a token. + function pinTokenURI(uint256 tokenId, uint256 index) external { + require(msg.sender == ownerOf(tokenId), "Unauthorized"); + _pinnedURIIndices[tokenId] = index; + _isPinnedTokenURI[tokenId] = true; + // Optionally emit token uri update (see EIP-4906) + } + + // Unsets the pinned URI for a token. + function unpinTokenURI(uint256 tokenId) external { + require(msg.sender == ownerOf(tokenId), "Unauthorized"); + _pinnedURIIndices[tokenId] = 0; + _isPinnedTokenURI[tokenId] = false; + // Optionally emit token uri update (see EIP-4906) + } + + // Checks if a token has a pinned URI. + function isPinnedTokenURI(uint256 tokenId) external view returns (bool isPinned) { + require(msg.sender == ownerOf(tokenId), "Unauthorized"); + return _isPinnedTokenURI[tokenId]; + } + + // Sets a specific metadata URI for a token at the given index. + function setUri(uint256 tokenId, uint256 index, string calldata uri) external onlyOwner { + _tokenURIs[tokenId][index] = uri; + // Optionally emit token uri update (see EIP-4906) + } + + // Overrides supportsInterface to include IERC721MultiMetadata interface support. + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { + return ( + interfaceId == type(IERC721MultiMetadata).interfaceId || + super.supportsInterface(interfaceId) + ); + } +} +``` + +## Copyright + +This work is licensed under the MIT License. From 97215691c6894c07cf87a4953bd8b8c66aea2c66 Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:04:55 +0200 Subject: [PATCH 02/15] Add discussions-to URL --- EIPS/erc-multimetadata.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/erc-multimetadata.md b/EIPS/erc-multimetadata.md index 40cda780a71bb0..65686be272671f 100644 --- a/EIPS/erc-multimetadata.md +++ b/EIPS/erc-multimetadata.md @@ -2,7 +2,7 @@ title: ERC721 Multi-Metadata Extension description: "This EIP proposes an extension to the ERC721 standards to support multiple metadata URIs per token via a new tokenURIs method that returns the pinned metadata index and a list of metadata URIs." author: 0xG (@0xGh) -discussions-to: +discussions-to: https://ethereum-magicians.org/t/erc721-multi-metadata-extension/14629 status: Draft type: Standards Track category: ERC From 35299751cf21ed7e7b3c5a13cb94dae4ec5b6a37 Mon Sep 17 00:00:00 2001 From: 0xGh <107277922+0xGh@users.noreply.github.com> Date: Sat, 10 Jun 2023 09:57:56 +0200 Subject: [PATCH 03/15] Add assigned EIP number Co-authored-by: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> --- EIPS/erc-multimetadata.md | 1 + 1 file changed, 1 insertion(+) diff --git a/EIPS/erc-multimetadata.md b/EIPS/erc-multimetadata.md index 65686be272671f..75312c5595a2c7 100644 --- a/EIPS/erc-multimetadata.md +++ b/EIPS/erc-multimetadata.md @@ -1,4 +1,5 @@ --- +eip: 7160 title: ERC721 Multi-Metadata Extension description: "This EIP proposes an extension to the ERC721 standards to support multiple metadata URIs per token via a new tokenURIs method that returns the pinned metadata index and a list of metadata URIs." author: 0xG (@0xGh) From 086a59ccfcec2f811d4cef1586005e033568549e Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Sat, 10 Jun 2023 10:08:23 +0200 Subject: [PATCH 04/15] Rename to match EIP number and fix lint errors --- EIPS/{erc-multimetadata.md => eip-7160.md} | 36 ++++++++++------------ 1 file changed, 16 insertions(+), 20 deletions(-) rename EIPS/{erc-multimetadata.md => eip-7160.md} (65%) diff --git a/EIPS/erc-multimetadata.md b/EIPS/eip-7160.md similarity index 65% rename from EIPS/erc-multimetadata.md rename to EIPS/eip-7160.md index 75312c5595a2c7..697dc055f46d26 100644 --- a/EIPS/erc-multimetadata.md +++ b/EIPS/eip-7160.md @@ -1,27 +1,27 @@ --- eip: 7160 -title: ERC721 Multi-Metadata Extension -description: "This EIP proposes an extension to the ERC721 standards to support multiple metadata URIs per token via a new tokenURIs method that returns the pinned metadata index and a list of metadata URIs." +title: ERC-721 Multi-Metadata Extension +description: "This EIP proposes an extension to the ERC-721 to support multiple metadata URIs per token." author: 0xG (@0xGh) discussions-to: https://ethereum-magicians.org/t/erc721-multi-metadata-extension/14629 status: Draft type: Standards Track category: ERC created: 2023-06-09 -requires: EIP-165, EIP-721 +requires: 165, 721 --- ## Abstract -This EIP proposes an extension to the ERC721 standards to support multiple metadata URIs per token. It introduces a new interface, IERC721MultiMetadata, which provides methods for accessing the metadata URIs associated with a token, including a pinned URI index and a list of all metadata URIs. The extension is designed to be backward compatible with existing ERC721Metadata implementations. +This EIP proposes an extension to the [ERC-721](./eip-721.md) standards to support multiple metadata URIs per token. It introduces a new interface, `IERC721MultiMetadata`, which provides methods for accessing the metadata URIs associated with a token, including a pinned URI index and a list of all metadata URIs. The extension is designed to be backward compatible with existing `ERC721Metadata` implementations. ## Motivation -The current ERC721 standards allow for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. +The current [ERC-721](./eip-721.md) standards allow for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. ## Specification -The IERC721MultiMetadata interface extends the existing IERC721 interface and introduces two additional methods: +The `IERC721MultiMetadata` interface extends the existing `IERC721` interface and introduces two additional methods: ```solidity interface IERC721MultiMetadata is IERC721 { @@ -34,9 +34,9 @@ interface IERC721MultiMetadata is IERC721 { The `tokenURIs` function returns a tuple containing the pinned URI index and a list of all metadata URIs associated with the specified token. The `pinTokenURI` function allows the contract owner to designate a specific metadata URI as the pinned URI for a token. -The `tokenURI` function defined in the ERC721 standard can be modified to return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. +The `tokenURI` function defined in the ERC-721 standard can be modified to return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. -See the [Implementation](#Implementation) section for an example. +See the [Implementation](#reference-implementation) section for an example. ## Rationale @@ -44,21 +44,13 @@ The `tokenURIs` function returns both the pinned URI index and the list of all m Depending on the implementation, the `pinTokenURI` function allows the contract owner or token owner to specify a particular metadata URI index for a token. This enables the selection of a preferred URI by index from the list of available metadata. -## Backward Compatibility +## Backwards Compatibility -This extension is designed to be backward compatible with existing ERC721 contracts. The modified `tokenURI` function can either return the last URI in the list returned by tokenURIs or the pinned URI, depending on the implementation. +This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The modified `tokenURI` function can either return the last URI in the list returned by tokenURIs or the pinned URI, depending on the implementation. -## Security Considerations - -Care should be taken when implementing the extension to ensure that the metadata URIs are properly handled and validated. It is crucial to prevent malicious actors from manipulating or injecting harmful metadata. - -## Test Cases - -Test cases should be provided to cover the functionality of the IERC721MultiMetadata interface, including retrieving the pinned URI, retrieving the list of metadata URIs, and pinning/unpinning a specific metadata URI for a token. Additionally, tests should be performed to validate the backward compatibility of existing ERC721 contracts. +## Reference Implementation -## Implementation - -An open-source reference implementation of the IERC721MultiMetadata interface can be provided, demonstrating how to extend an existing ERC721 contract to support multi-metadata functionality. This reference implementation can serve as a guide for developers looking to implement the extension in their own contracts. +An open-source reference implementation of the `IERC721MultiMetadata` interface can be provided, demonstrating how to extend an existing [ERC-721](./eip-721.md) contract to support multi-metadata functionality. This reference implementation can serve as a guide for developers looking to implement the extension in their own contracts. ```solidity import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; @@ -132,6 +124,10 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { } ``` +## Security Considerations + +Care should be taken when implementing the extension to ensure that the metadata URIs are properly handled and validated. It is crucial to prevent malicious actors from manipulating or injecting harmful metadata. + ## Copyright This work is licensed under the MIT License. From 1f9d99aafe358a301613becaca57fe4bfef3f82e Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Fri, 23 Jun 2023 09:43:47 +0200 Subject: [PATCH 05/15] Add pin events and mpeyfuss as co-author --- EIPS/eip-7160.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 697dc055f46d26..08cd4c0f8c28ed 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -2,7 +2,7 @@ eip: 7160 title: ERC-721 Multi-Metadata Extension description: "This EIP proposes an extension to the ERC-721 to support multiple metadata URIs per token." -author: 0xG (@0xGh) +author: 0xG (@0xGh), Marco Peyfuss (@mpeyfuss) discussions-to: https://ethereum-magicians.org/t/erc721-multi-metadata-extension/14629 status: Draft type: Standards Track @@ -25,10 +25,13 @@ The `IERC721MultiMetadata` interface extends the existing `IERC721` interface an ```solidity interface IERC721MultiMetadata is IERC721 { + event TokenUriPinned(uint256 indexed tokenId, uint256 indexed index, address indexed sender); + event TokenUriUnpinned(uint256 indexed tokenId, address indexed sender); + function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris); function pinTokenURI(uint256 tokenId, uint256 index) external; function unpinTokenURI(uint256 tokenId) external; - function isPinnedTokenURI(uint256 tokenId) external view returns (bool pinned); + function hasPinnedTokenURI(uint256 tokenId) external view returns (bool pinned); } ``` @@ -36,6 +39,8 @@ The `tokenURIs` function returns a tuple containing the pinned URI index and a l The `tokenURI` function defined in the ERC-721 standard can be modified to return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. +When pinning or unpinning URIs it is highly encouraged to implement [EIP-4906](./eip-4906) and emit a `MetadataUpdate` event. + See the [Implementation](#reference-implementation) section for an example. ## Rationale @@ -60,13 +65,13 @@ import "./IERC721MultiMetadata.sol"; contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { mapping(uint256 => string[]) _tokenURIs; mapping(uint256 => uint256) _pinnedURIIndices; - mapping(uint256 => bool) _isPinnedTokenURI; + mapping(uint256 => bool) _hasPinnedTokenURI; constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} // Returns the pinned URI index or the last token URI index (length - 1). function _getTokenURIIndex(uint256 tokenId) internal view returns (uint256) { - return _isPinnedTokenURI[tokenId] ? _pinnedURIIndices[tokenId] : _tokenURIs[tokenId].length - 1; + return _hasPinnedTokenURI[tokenId] ? _pinnedURIIndices[tokenId] : _tokenURIs[tokenId].length - 1; } // Implementation of ERC721.tokenURI for backwards compatibility. @@ -90,28 +95,34 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { function pinTokenURI(uint256 tokenId, uint256 index) external { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = index; - _isPinnedTokenURI[tokenId] = true; - // Optionally emit token uri update (see EIP-4906) + _hasPinnedTokenURI[tokenId] = true; + + // Emit a MetadataUpdate event (see EIP-4906). + emit MetadataUpdate(tokenId); } // Unsets the pinned URI for a token. function unpinTokenURI(uint256 tokenId) external { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = 0; - _isPinnedTokenURI[tokenId] = false; - // Optionally emit token uri update (see EIP-4906) + _hasPinnedTokenURI[tokenId] = false; + + // Emit a MetadataUpdate event (see EIP-4906). + emit MetadataUpdate(tokenId); } // Checks if a token has a pinned URI. - function isPinnedTokenURI(uint256 tokenId) external view returns (bool isPinned) { + function hasPinnedTokenURI(uint256 tokenId) external view returns (bool isPinned) { require(msg.sender == ownerOf(tokenId), "Unauthorized"); - return _isPinnedTokenURI[tokenId]; + return _hasPinnedTokenURI[tokenId]; } // Sets a specific metadata URI for a token at the given index. function setUri(uint256 tokenId, uint256 index, string calldata uri) external onlyOwner { _tokenURIs[tokenId][index] = uri; - // Optionally emit token uri update (see EIP-4906) + + // Emit a MetadataUpdate event (see EIP-4906). + emit MetadataUpdate(tokenId); } // Overrides supportsInterface to include IERC721MultiMetadata interface support. From b739024c8979fd94fd1e0ebba2b4df5bcbbf9dc3 Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:27:07 +0200 Subject: [PATCH 06/15] Typos and fix sample implementation --- EIPS/eip-7160.md | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 08cd4c0f8c28ed..6f813c8289eeb2 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -13,11 +13,11 @@ requires: 165, 721 ## Abstract -This EIP proposes an extension to the [ERC-721](./eip-721.md) standards to support multiple metadata URIs per token. It introduces a new interface, `IERC721MultiMetadata`, which provides methods for accessing the metadata URIs associated with a token, including a pinned URI index and a list of all metadata URIs. The extension is designed to be backward compatible with existing `ERC721Metadata` implementations. +This EIP proposes an extension to the [ERC-721](./eip-721.md) standard to support multiple metadata URIs per token. It introduces a new interface, `IERC721MultiMetadata`, which provides methods for accessing the metadata URIs associated with a token, including a pinned URI index and a list of all metadata URIs. The extension is designed to be backward compatible with existing `ERC721Metadata` implementations. ## Motivation -The current [ERC-721](./eip-721.md) standards allow for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. +The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. ## Specification @@ -35,39 +35,52 @@ interface IERC721MultiMetadata is IERC721 { } ``` -The `tokenURIs` function returns a tuple containing the pinned URI index and a list of all metadata URIs associated with the specified token. The `pinTokenURI` function allows the contract owner to designate a specific metadata URI as the pinned URI for a token. +The `tokenURIs` function returns a tuple containing the pinned URI index and a list of all metadata URIs associated with the specified token. -The `tokenURI` function defined in the ERC-721 standard can be modified to return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. +The `pinTokenURI` function allows the contract owner to designate a specific metadata URI as the pinned URI for a token. -When pinning or unpinning URIs it is highly encouraged to implement [EIP-4906](./eip-4906) and emit a `MetadataUpdate` event. +The `unpinTokenURI` function allows the contract owner to remove the pin on a specific URI index. + +The `tokenURI` function defined in the ERC-721 standard must return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. + +When pinning or unpinning URIs it is highly encouraged to implement [EIP-4906](./eip-4906.md) and emit a `MetadataUpdate` event. See the [Implementation](#reference-implementation) section for an example. ## Rationale -The `tokenURIs` function returns both the pinned URI index and the list of all metadata URIs to provide flexibility in accessing the metadata. 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. +The `tokenURIs` function returns both the pinned URI index and the list of all metadata URIs to provide flexibility in accessing the metadata. -Depending on the implementation, the `pinTokenURI` function allows the contract owner or token owner to specify a particular metadata URI index for a token. This enables the selection of a preferred URI by index from the list of available metadata. +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. + +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. ## Backwards Compatibility -This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The modified `tokenURI` function can either return the last URI in the list returned by tokenURIs or the pinned URI, depending on the implementation. +This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The implementation of the `tokenURI` method must either return the last URI in the list returned by tokenURIs or the pinned URI. ## Reference Implementation An open-source reference implementation of the `IERC721MultiMetadata` interface can be provided, demonstrating how to extend an existing [ERC-721](./eip-721.md) contract to support multi-metadata functionality. This reference implementation can serve as a guide for developers looking to implement the extension in their own contracts. ```solidity +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/interfaces/IERC4906.sol"; import "./IERC721MultiMetadata.sol"; -contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { +contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { mapping(uint256 => string[]) _tokenURIs; mapping(uint256 => uint256) _pinnedURIIndices; mapping(uint256 => bool) _hasPinnedTokenURI; - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) { + _mint(msg.sender, 1); + } // Returns the pinned URI index or the last token URI index (length - 1). function _getTokenURIIndex(uint256 tokenId) internal view returns (uint256) { @@ -78,7 +91,9 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { _requireMinted(tokenId); - string memory uri = _tokenURIs[_getTokenURIIndex(tokenId)]; + uint256 index = _getTokenURIIndex(tokenId); + string[] memory uris = _tokenURIs[tokenId]; + string memory uri = uris[index]; // Revert if no URI is found for the token. require(bytes(uri).length > 0, "ERC721: not URI found"); @@ -97,6 +112,8 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { _pinnedURIIndices[tokenId] = index; _hasPinnedTokenURI[tokenId] = true; + emit TokenUriPinned(tokenId, index, msg.sender); + // Emit a MetadataUpdate event (see EIP-4906). emit MetadataUpdate(tokenId); } @@ -107,6 +124,8 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { _pinnedURIIndices[tokenId] = 0; _hasPinnedTokenURI[tokenId] = false; + emit TokenUriUnpinned(tokenId, msg.sender); + // Emit a MetadataUpdate event (see EIP-4906). emit MetadataUpdate(tokenId); } @@ -119,7 +138,11 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata { // Sets a specific metadata URI for a token at the given index. function setUri(uint256 tokenId, uint256 index, string calldata uri) external onlyOwner { - _tokenURIs[tokenId][index] = uri; + if (_tokenURIs[tokenId].length > index) { + _tokenURIs[tokenId][index] = uri; + } else { + _tokenURIs[tokenId].push(uri); + } // Emit a MetadataUpdate event (see EIP-4906). emit MetadataUpdate(tokenId); From 4f812c47bd73cd3e0d661ac6cbd4590561a180e2 Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Fri, 23 Jun 2023 18:46:35 +0200 Subject: [PATCH 07/15] Fix events in example implementation --- EIPS/eip-7160.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 6f813c8289eeb2..f8ecc6ce1b8938 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -43,7 +43,7 @@ The `unpinTokenURI` function allows the contract owner to remove the pin on a sp The `tokenURI` function defined in the ERC-721 standard must return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. -When pinning or unpinning URIs it is highly encouraged to implement [EIP-4906](./eip-4906.md) and emit a `MetadataUpdate` event. +When adding new URIs it is highly encouraged to implement [EIP-4906](./eip-4906.md) and emit a `MetadataUpdate` event. See the [Implementation](#reference-implementation) section for an example. @@ -111,11 +111,7 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = index; _hasPinnedTokenURI[tokenId] = true; - emit TokenUriPinned(tokenId, index, msg.sender); - - // Emit a MetadataUpdate event (see EIP-4906). - emit MetadataUpdate(tokenId); } // Unsets the pinned URI for a token. @@ -123,11 +119,7 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = 0; _hasPinnedTokenURI[tokenId] = false; - emit TokenUriUnpinned(tokenId, msg.sender); - - // Emit a MetadataUpdate event (see EIP-4906). - emit MetadataUpdate(tokenId); } // Checks if a token has a pinned URI. @@ -158,10 +150,6 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { } ``` -## Security Considerations - -Care should be taken when implementing the extension to ensure that the metadata URIs are properly handled and validated. It is crucial to prevent malicious actors from manipulating or injecting harmful metadata. - ## Copyright This work is licensed under the MIT License. From 8722f0ef1796326604b0a6d404f0a00475dd70c1 Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Sun, 25 Jun 2023 10:40:46 +0200 Subject: [PATCH 08/15] Add some clarifications --- EIPS/eip-7160.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index f8ecc6ce1b8938..67063a8bae7a68 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -21,7 +21,7 @@ The current [ERC-721](./eip-721.md) standard allows for a single metadata URI pe ## Specification -The `IERC721MultiMetadata` interface extends the existing `IERC721` interface and introduces two additional methods: +The `IERC721MultiMetadata` interface extends the existing `IERC721` interface and introduces additional methods and events: ```solidity interface IERC721MultiMetadata is IERC721 { @@ -37,11 +37,13 @@ interface IERC721MultiMetadata is IERC721 { The `tokenURIs` function returns a tuple containing the pinned URI index and a list of all metadata URIs associated with the specified token. +`tokenURIs` is the core method of a multi-metadata token and the additional methods and events are meant to provide a standard interface to dapps to perform pinning and unpinning actions. + The `pinTokenURI` function allows the contract owner to designate a specific metadata URI as the pinned URI for a token. The `unpinTokenURI` function allows the contract owner to remove the pin on a specific URI index. -The `tokenURI` function defined in the ERC-721 standard must return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backward compatibility with existing contracts and applications that rely on the single metadata URI. +The `tokenURI` function defined in the ERC-721 standard must return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backwards compatibility with existing contracts and applications that rely on the single metadata URI. When adding new URIs it is highly encouraged to implement [EIP-4906](./eip-4906.md) and emit a `MetadataUpdate` event. @@ -55,6 +57,8 @@ The pinned URI can be used as a default or primary URI for the token, while the 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. +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. + ## Backwards Compatibility This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The implementation of the `tokenURI` method must either return the last URI in the list returned by tokenURIs or the pinned URI. @@ -150,6 +154,10 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { } ``` +## Security Considerations + +This extension does not poses new security risks outside of those that might arise from existing token URI handling practices. + ## Copyright This work is licensed under the MIT License. From 4c35f00eb8b60ec713f32b7161e4d25457f106f3 Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Sun, 25 Jun 2023 10:45:59 +0200 Subject: [PATCH 09/15] Fix ERC-4906 reference --- EIPS/eip-7160.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 67063a8bae7a68..3d4e6d9bb1e289 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -45,7 +45,7 @@ The `unpinTokenURI` function allows the contract owner to remove the pin on a sp The `tokenURI` function defined in the ERC-721 standard must return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backwards compatibility with existing contracts and applications that rely on the single metadata URI. -When adding new URIs it is highly encouraged to implement [EIP-4906](./eip-4906.md) and emit a `MetadataUpdate` event. +When adding new URIs it is highly encouraged to implement [ERC-4906](./eip-4906.md) and emit a `MetadataUpdate` event. See the [Implementation](#reference-implementation) section for an example. From 7ef5c5282c05d803649260561cb0fd451a55cf45 Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Tue, 27 Jun 2023 21:13:50 +0200 Subject: [PATCH 10/15] Address feedback --- EIPS/eip-7160.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 3d4e6d9bb1e289..eb404e70895e12 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -1,7 +1,7 @@ --- eip: 7160 title: ERC-721 Multi-Metadata Extension -description: "This EIP proposes an extension to the ERC-721 to support multiple metadata URIs per token." +description: "Multiple metadata URIs per token, with the option to pin a primary URI." author: 0xG (@0xGh), Marco Peyfuss (@mpeyfuss) discussions-to: https://ethereum-magicians.org/t/erc721-multi-metadata-extension/14629 status: Draft @@ -19,12 +19,14 @@ This EIP proposes an extension to the [ERC-721](./eip-721.md) standard to suppor The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. +The primary reason for having a multi-metadata standard versus existing `tokenURI` with some token-specific functions to change the active URI is that dapps and marketplaces don't have a mechanism to infer and display all the URIs in a carousel, gallery, list, etc. The non-pinned URIs might be equally important: imagine a colaborative token with multiple artworks per token. + ## Specification -The `IERC721MultiMetadata` interface extends the existing `IERC721` interface and introduces additional methods and events: +The `IERC721MultiMetadata` interface: ```solidity -interface IERC721MultiMetadata is IERC721 { +interface IERC721MultiMetadata /* is IERC721 */ { event TokenUriPinned(uint256 indexed tokenId, uint256 indexed index, address indexed sender); event TokenUriUnpinned(uint256 indexed tokenId, address indexed sender); @@ -68,13 +70,13 @@ This extension is designed to be backward compatible with existing [ERC-721](./e An open-source reference implementation of the `IERC721MultiMetadata` interface can be provided, demonstrating how to extend an existing [ERC-721](./eip-721.md) contract to support multi-metadata functionality. This reference implementation can serve as a guide for developers looking to implement the extension in their own contracts. ```solidity -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.19; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/interfaces/IERC4906.sol"; +import "./token/ERC721/ERC721.sol"; +import "./access/Ownable.sol"; +import "./interfaces/IERC4906.sol"; import "./IERC721MultiMetadata.sol"; contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { @@ -160,4 +162,4 @@ This extension does not poses new security risks outside of those that might ari ## Copyright -This work is licensed under the MIT License. +Copyright and related rights waived via [CC0](../LICENSE.md). From 6a66fc62c05c8b5333cfbe9a4dc265bf5c5efa9a Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Tue, 27 Jun 2023 21:19:01 +0200 Subject: [PATCH 11/15] Improve motivation wording --- EIPS/eip-7160.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index eb404e70895e12..c290f8fec861eb 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -19,7 +19,7 @@ This EIP proposes an extension to the [ERC-721](./eip-721.md) standard to suppor The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. -The primary reason for having a multi-metadata standard versus existing `tokenURI` with some token-specific functions to change the active URI is that dapps and marketplaces don't have a mechanism to infer and display all the URIs in a carousel, gallery, list, etc. The non-pinned URIs might be equally important: imagine a colaborative token with multiple artworks per token. +The primary reason for having a multi-metadata standard versus using the existing `tokenURI` method with some token-specific functions to change the active URI is that dapps and marketplaces don't have a mechanism to infer and display all the token URIs. Furthermore the non-pinned URIs might be equally important: imagine a colaborative token with multiple artworks per token. Multi-metadata tokens can be displayed in a carousel, gallery, list, etc. ## Specification From edee4d45bb3f124c8baebd369cd5d4dd86b88c98 Mon Sep 17 00:00:00 2001 From: Marco Peyfuss Date: Fri, 7 Jul 2023 22:39:32 +0200 Subject: [PATCH 12/15] General updates that we can discuss - added natspec to the interface and reference implementation - made use of keywords from RFC 2119 - listed out potential use cases rather than in one sentence - general updates to wording - removed reference that the default method of handling unpinned uris is defined as the latest uri as that is highly dependent on the application - added interface id as is - improved rationale section - removed token ownership check in the reference implementation from `hasPinnedTokenUri` as that is a view function and doesn't need that check - added imports from openzeppelin in the reference implementation - change linearzation of parent contracts in reference implementation - added Ownable constructor explicitly in the reference implementation - updated security considerations as there are some to be aware of but nothing out of the ordinary --- EIPS/eip-7160.md | 97 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index c290f8fec861eb..c4554a744fa451 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -17,42 +17,77 @@ This EIP proposes an extension to the [ERC-721](./eip-721.md) standard to suppor ## Motivation -The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token. However, there are use cases where multiple metadata URIs are desirable, such as when a token represents a collection of (cycling) assets with individual metadata, historic token metadata, collaborative and multi-artist tokens, evolving tokens. This extension enables such use cases by introducing the concept of multi-metadata support. +The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token with the `ERC721Metadata` implementation. However, there are use cases where multiple metadata URIs are desirable. Some example use cases are listed below: +- A token represents a collection of (cycling) assets with individual metadata +- An on-chain history of revisions to token metadata +- Appending metadata with different aspect ratios so that it can be displayed properly on all screens +- Dynamic & evolving metadata +- Collaborative and multi-artist tokens -The primary reason for having a multi-metadata standard versus using the existing `tokenURI` method with some token-specific functions to change the active URI is that dapps and marketplaces don't have a mechanism to infer and display all the token URIs. Furthermore the non-pinned URIs might be equally important: imagine a colaborative token with multiple artworks per token. Multi-metadata tokens can be displayed in a carousel, gallery, list, etc. +This extension enables such use cases by introducing the concept of multi-metadata support. + +The primary reason for having a multi-metadata standard in addition to the existing `ERC721Metadata` standard is that dapps and marketplaces don't have a mechanism to infer and display all the token URIs. Giving a standard way for marketplaces to offer collectors a way to pin/unpin one of the metadata choices also enables quick & easy adoption of this functionality. These features ## Specification -The `IERC721MultiMetadata` interface: +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. + +**The multi-metadata extension is OPTIONAL for EIP-721 contracts and it is RECOMMENDED to be used in conjuction with the `EIP-4906` standard if implemented** ```solidity -interface IERC721MultiMetadata /* is IERC721 */ { +/// @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 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); + + /// @dev This event emits when a token uri is unpinned and is + /// useful for indexing purposes. event TokenUriUnpinned(uint256 indexed tokenId, address indexed sender); + /// @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 + /// @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); + + /// @notice Pin a specific token uri for a particular token + /// @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 + /// @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 + /// @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); } ``` -The `tokenURIs` function returns a tuple containing the pinned URI index and a list of all metadata URIs associated with the specified token. - -`tokenURIs` is the core method of a multi-metadata token and the additional methods and events are meant to provide a standard interface to dapps to perform pinning and unpinning actions. +The `TokenUriPinned` event MUST be emitted when pinning a token uri with the `pinTokenUri` function. -The `pinTokenURI` function allows the contract owner to designate a specific metadata URI as the pinned URI for a token. +The `TokenUriUnpinned` event MUST be emitted when unpinning a token uri with the `unpinTokenUri` function. -The `unpinTokenURI` function allows the contract owner to remove the pin on a specific URI index. +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 standard must return the pinned URI or the last URI in the list returned by `tokenURIs` when there is not pinned URI. This ensures backwards compatibility with existing contracts and applications that rely on the single metadata URI. +The `supportsInterface` method MUST return true when called with 0x06e1bc5b. -When adding new URIs it is highly encouraged to implement [ERC-4906](./eip-4906.md) and emit a `MetadataUpdate` event. +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. 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. 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. @@ -63,7 +98,7 @@ When unpinned, it is recommended to return the last URI for the token. However t ## Backwards Compatibility -This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The implementation of the `tokenURI` method must either return the last URI in the list returned by tokenURIs or the pinned URI. +This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The implementation of the `tokenURI` method must either return the pinned token uri (if pinned) or some default uri (if unpinned). ## Reference Implementation @@ -74,26 +109,27 @@ An open-source reference implementation of the `IERC721MultiMetadata` interface pragma solidity ^0.8.19; -import "./token/ERC721/ERC721.sol"; -import "./access/Ownable.sol"; -import "./interfaces/IERC4906.sol"; -import "./IERC721MultiMetadata.sol"; +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"; -contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { - mapping(uint256 => string[]) _tokenURIs; - mapping(uint256 => uint256) _pinnedURIIndices; - mapping(uint256 => bool) _hasPinnedTokenURI; +contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { + mapping(uint256 => string[]) private _tokenURIs; + mapping(uint256 => uint256) private _pinnedURIIndices; + mapping(uint256 => bool) private _hasPinnedTokenURI; - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) { + constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) Ownable() { _mint(msg.sender, 1); } - // Returns the pinned URI index or the last token URI index (length - 1). + // @notice Returns the pinned URI index or the last token URI index (length - 1). function _getTokenURIIndex(uint256 tokenId) internal view returns (uint256) { return _hasPinnedTokenURI[tokenId] ? _pinnedURIIndices[tokenId] : _tokenURIs[tokenId].length - 1; } - // Implementation of ERC721.tokenURI for backwards compatibility. + // @notice Implementation of ERC721.tokenURI for backwards compatibility. + // @inheritdoc ERC721.tokenURI function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { _requireMinted(tokenId); @@ -106,13 +142,13 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { return uri; } - // Retrieves the pinned URI index and the list of all metadata URIs associated with the specified token. + /// @inheritdoc IERC721MultiMetadata.tokenURIs function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris) { _requireMinted(tokenId); return (_getTokenURIIndex(tokenId), _tokenURIs[tokenId]); } - // Sets a specific metadata URI as the pinned URI for a token. + /// @inheritdoc IERC721MultiMetadata.pinTokenURI function pinTokenURI(uint256 tokenId, uint256 index) external { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = index; @@ -120,7 +156,7 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { emit TokenUriPinned(tokenId, index, msg.sender); } - // Unsets the pinned URI for a token. + /// @inheritdoc IERC721MultiMetadata.unpinTokenURI function unpinTokenURI(uint256 tokenId) external { require(msg.sender == ownerOf(tokenId), "Unauthorized"); _pinnedURIIndices[tokenId] = 0; @@ -128,13 +164,12 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { emit TokenUriUnpinned(tokenId, msg.sender); } - // Checks if a token has a pinned URI. + /// @inheritdoc IERC721MultiMetadata.hasPinnedTokenURI function hasPinnedTokenURI(uint256 tokenId) external view returns (bool isPinned) { - require(msg.sender == ownerOf(tokenId), "Unauthorized"); return _hasPinnedTokenURI[tokenId]; } - // Sets a specific metadata URI for a token at the given index. + /// @notice Sets a specific metadata URI for a token at the given index. function setUri(uint256 tokenId, uint256 index, string calldata uri) external onlyOwner { if (_tokenURIs[tokenId].length > index) { _tokenURIs[tokenId][index] = uri; @@ -158,7 +193,9 @@ contract MultiMetadata is Ownable, ERC721, IERC721MultiMetadata, IERC4906 { ## Security Considerations -This extension does not poses new security risks outside of those that might arise from existing token URI handling practices. +Care should be taken when specifying access controls for state changing events, such as those that allow uris to be added to tokens +and those specified in this standard: the `pinTokenUri` and `unpinTokenUri` functions. This is up to the developers to specify +as each application may have different requirements to allow for pinning and unpinning. ## Copyright From e16d784b50d923a162418823b2551d74b3d31dbb Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Sat, 8 Jul 2023 18:50:06 +0200 Subject: [PATCH 13/15] Fix typos --- EIPS/eip-7160.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index c4554a744fa451..88b05c6fa8ddc1 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -18,32 +18,33 @@ This EIP proposes an extension to the [ERC-721](./eip-721.md) standard to suppor ## Motivation The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token with the `ERC721Metadata` implementation. However, there are use cases where multiple metadata URIs are desirable. Some example use cases are listed below: + - A token represents a collection of (cycling) assets with individual metadata - An on-chain history of revisions to token metadata - Appending metadata with different aspect ratios so that it can be displayed properly on all screens -- Dynamic & evolving metadata +- Dynamic and evolving metadata - Collaborative and multi-artist tokens This extension enables such use cases by introducing the concept of multi-metadata support. -The primary reason for having a multi-metadata standard in addition to the existing `ERC721Metadata` standard is that dapps and marketplaces don't have a mechanism to infer and display all the token URIs. Giving a standard way for marketplaces to offer collectors a way to pin/unpin one of the metadata choices also enables quick & easy adoption of this functionality. These features +The primary reason for having a multi-metadata standard in addition to the existing `ERC721Metadata` standard is that dapps and marketplaces don't have a mechanism to infer and display all the token URIs. Giving a standard way for marketplaces to offer collectors a way to pin/unpin one of the metadata choices also enables quick and easy adoption of this functionality. ## Specification The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. -**The multi-metadata extension is OPTIONAL for EIP-721 contracts and it is RECOMMENDED to be used in conjuction with the `EIP-4906` standard if implemented** +**The multi-metadata extension is OPTIONAL for EIP-721 contracts and it is RECOMMENDED to be used in conjuction with the `EIP-4906` standard if implemented**. ```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 This event emits when a token uri is pinned and is + /// @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); - /// @dev This event emits when a token uri is unpinned and is + /// @dev This event emits when a token uri is unpinned and is /// useful for indexing purposes. event TokenUriUnpinned(uint256 indexed tokenId, address indexed sender); @@ -78,7 +79,7 @@ The `TokenUriUnpinned` event MUST be emitted when unpinning a token uri with the 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 `supportsInterface` method MUST return true when called with 0x06e1bc5b. +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. @@ -195,7 +196,7 @@ contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { Care should be taken when specifying access controls for state changing events, such as those that allow uris to be added to tokens and those specified in this standard: the `pinTokenUri` and `unpinTokenUri` functions. This is up to the developers to specify -as each application may have different requirements to allow for pinning and unpinning. +as each application may have different requirements to allow for pinning and unpinning. ## Copyright From 7aab08abe5252c19cee7c10798cd977dd948ef0e Mon Sep 17 00:00:00 2001 From: 0xGh <0xGh@users.noreply.github.com> Date: Sat, 8 Jul 2023 18:52:47 +0200 Subject: [PATCH 14/15] Fix ERC references --- EIPS/eip-7160.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 88b05c6fa8ddc1..827bf874c2ec4d 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -33,7 +33,7 @@ The primary reason for having a multi-metadata standard in addition to the exist The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. -**The multi-metadata extension is OPTIONAL for EIP-721 contracts and it is RECOMMENDED to be used in conjuction with the `EIP-4906` standard if implemented**. +**The multi-metadata extension is OPTIONAL for [ERC-721](./eip-721.md) contracts and it is RECOMMENDED to be used in conjuction with the [ERC-4906](./eip-4906.md) standard if implemented**. ```solidity /// @title EIP-721 Multi-Metdata Extension From af32fb50015dc4c9465ffd89c3dca435ea9e64a7 Mon Sep 17 00:00:00 2001 From: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:15:41 -0400 Subject: [PATCH 15/15] Update EIPS/eip-7160.md --- EIPS/eip-7160.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 827bf874c2ec4d..8427bfef0c012f 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -1,7 +1,7 @@ --- eip: 7160 title: ERC-721 Multi-Metadata Extension -description: "Multiple metadata URIs per token, with the option to pin a primary URI." +description: Multiple metadata URIs per token, with the option to pin a primary URI. author: 0xG (@0xGh), Marco Peyfuss (@mpeyfuss) discussions-to: https://ethereum-magicians.org/t/erc721-multi-metadata-extension/14629 status: Draft