# ERC-7646: Metadata Management for NFTsw Source: https://ethereum-magicians.org/t/erc-7646-metadata-management-for-nfts/19027 Kiwi News: https://news.kiwistand.com/stories/ERC-7646-Metadata-Management-for-NFTsw?index=0x65f40af4c8dec8cbafe211c10e4c90f1089a1f8d482c0e96167371e652956de6ea494e99 Submitted by rvolz.eth on 2024-03-15 UTC, 1 upvote, 0 comments ## Summary ERC-7646 proposes a standard for managing NFT metadata, letting a token's owner and authorized addresses update its metadata after minting. The author, identified in the article as Kureev, says updatable metadata would support use cases such as provenance tracking and game assets, and dynamic NFTs tied to IP, real estate, or supply chains. The proposed MetadataManager contract stores metadata per token, lets owners authorize or revoke other addresses, and can be used with any ERC721 or ERC1155 contract. The article lists its test cases as TBD and notes that the contract relies on the NFT contract's ownerOf function, which could create security issues if implemented incorrectly. ## Article ERC-7646: Metadata Management for NFTs Hi Magicians, I’d like to propose an approach to manage metadata for NFTs (by the owner and authorized parties). In many cases (provenance tracking, game assets etc) it would be beneficial to be able to update metadata for existing NFT. I believe it will open door to many interesting applications related to dynamic nature of underlying NFT (IP, real estate, supply chains etc). Here is the PR: Add ERC: Metadata Management for NFTs by Kureev · Pull Request #293 · ethereum/ERCs · GitHub And here is the copy of the idea: Simple Summary This ERC proposes a standard for managing metadata of NFTs that allows owners and authorized addresses to update the metadata of their tokens. Abstract The MetadataManager contract provides a mapping for storing metadata of NFTs. It also provides a mechanism for authorizing other addresses to update the metadata of a token. This contract can be used in conjunction with any NFT contract that implements the ERC721 or ERC1155 standard. Motivation Currently, the metadata of an NFT is immutable once it has been minted. This is problematic for use cases where the metadata of an NFT needs to be updated afterwards. For example, an NFT representing a digital artwork might need to have its metadata updated to reflect changes in the artwork’s ownership or provenance. Specification The MetadataManager contract provides the following functions: updateMetadata(address tokenAddress, uint256 tokenId, string memory newURI): Updates the metadata of a token. The sender must be the owner of the token or authorized to update the metadata. getMetadata(address tokenAddress, uint256 tokenId): Returns the metadata of a token. If the metadata is not stored, it will try to fetch it from the NFT contract. authorize(address tokenAddress, uint256 tokenId, address authorizedAddress): Authorizes an address to update the metadata of a token. Only the owner of the token can call this function. revokeAuthorization(address tokenAddress, uint256 tokenId, address authorizedAddress): Revokes the authorization of an address to update the metadata of a token. Only the owner of the token can call this function. Rationale This ERC provides a flexible and decentralized way to manage the metadata of NFTs. It allows the metadata of an NFT to be updated after it has been minted, and it allows the owner of an NFT to delegate the ability to update the metadata to other addresses. Backwards Compatibility This ERC is fully backwards compatible with existing NFT standards. It can be used in conjunction with any NFT contract that implements the ERC721 or ERC1155 standard. Test Cases TBD Implementation // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; interface INFT { function ownerOf(uint256 tokenId) external view returns (address owner); function tokenURI(uint256 tokenId) external view returns (string memory); } contract MetadataManager { // tokenAddress => tokenId => URI of the metadata mapping(address => mapping(uint256 => string)) public tokenMetadata; // tokenAddress => tokenId => authorizedAddress => bool mapping(address => mapping(uint256 => mapping(address => bool))) public isAuthorized; // Emits when the metadata of a token is updated. event MetadataUpdated(address indexed tokenAddress, uint256 indexed tokenId, string newUri); // Emits when an address is authorized to update the metadata of a token. event AuthorizationGranted(address indexed tokenAddress, uint256 indexed tokenId, address indexed authorizedAddress); // Emits when an address is revoked from updating the metadata of a token. event AuthorizationRevoked(address indexed tokenAddress, uint256 indexed tokenId, address indexed authorizedAddress); // Throws if the sender is not the owner of the token. modifier onlyTokenOwner(address tokenAddress, uint256 tokenId) { INFT nftContract = INFT(tokenAddress); if (msg.sender != nftContract.ownerOf(tokenId)) { revert NotOwner(); } _; } /** * Updates the metadata of a token. * The sender must be the owner of the token or authorized to update the metadata. * Emits a {MetadataUpdated} event. */ function updateMetadata(address tokenAddress, uint256 tokenId, string memory newURI) public { INFT nftContract = INFT(tokenAddress); if (msg.sender != nftContract.ownerOf(tokenId) || !isAuthorized[tokenAddress][tokenId][msg.sender]) { revert NotAuthorized(); } tokenMetadata[tokenAddress][tokenId] = newURI; emit MetadataUpdated(tokenAddress, tokenId, newURI); } /** * Returns the metadata of a token. * If the metadata is not stored, it will try to fetch it from the NFT contract. * If the NFT contract does not have a tokenURI function, it will revert. */ function getMetadata(address tokenAddress, uint256 tokenId) public view returns (string memory) { string memory metadata = tokenMetadata[tokenAddress][tokenId]; if (bytes(metadata).length == 0) { try INFT(tokenAddress).tokenURI(tokenId) returns (string memory uri) { return uri; } catch { revert NoMetadata(); } } else { return metadata; } } // Authorizes an address to update the metadata of a token. function authorize(address tokenAddress, uint256 tokenId, address authorizedAddress) public onlyTokenOwner(tokenAddress, tokenId) { isAuthorized[tokenAddress][tokenId][authorizedAddress] = true; emit AuthorizationGranted(tokenAddress, tokenId, authorizedAddress); } // Revokes the authorization of an address to update the metadata of a token. function revokeAuthorization(address tokenAddress, uint256 tokenId, address authorizedAddress) public onlyTokenOwner(tokenAddress, tokenId) { isAuthorized[tokenAddress][tokenId][authorizedAddress] = false; emit AuthorizationRevoked(tokenAddress, tokenId, authorizedAddress); } error NotAuthorized(); error NotOwner(); error NoMetadata(); } Example usage // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "./MetadataManager.sol"; contract MyNFT is ERC721 { MetadataManager public metadataManager; constructor(address _metadataManager) ERC721("MyNFT", "MNFT") { metadataManager = MetadataManager(_metadataManager); } error NotOwner(); modifier onlyOwner(uint256 tokenId) { if (ownerOf(tokenId) != msg.sender) { revert NotOwner(); } _; } function mint(address to, uint256 tokenId) public { _mint(to, tokenId); } function tokenURI(uint256 tokenId) public view override returns (string memory) { return metadataManager.getMetadata(address(this), tokenId); } function updateMetadata(uint256 tokenId, string memory newURI) public onlyOwner(tokenId) { metadataManager.updateMetadata(address(this), tokenId, newURI); } function authorize(address authorizedAddress, uint256 tokenId) public onlyOwner(tokenId) { metadataManager.authorize(address(this), tokenId, authorizedAddress); } function revokeAuthorization(address authorizedAddress, uint256 tokenId) public onlyOwner(tokenId) { metadataManager.revokeAuthorization(address(this), tokenId, authorizedAddress); } } Security Considerations The MetadataManager contract relies on the ownerOf function of the NFT contract to determine the owner of a token. If the NFT contract does not implement this function correctly, it could lead to security issues.