archive about
Clouds 24°C — Kifissia, GR — #en #howto #solidity #nft

Implementing tokenURI as a separate contract

The cost is minimal, and the advantages are significant.

The metadata extension of ERC721 includes tokenURI() that returns the metadata for a specific token ID:

/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
///  Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface ERC721Metadata /* is ERC721 */ {
    /// @notice A descriptive name for a collection of NFTs in this contract
    function name() external view returns (string _name);

    /// @notice An abbreviated name for NFTs in this contract
    function symbol() external view returns (string _symbol);

    /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
    /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
    ///  3986. The URI may point to a JSON file that conforms to the "ERC721
    ///  Metadata JSON Schema".
    function tokenURI(uint256 _tokenId) external view returns (string);
}

And the Openzeppelin implementation or ERC721 implements it like this (practically, it returns "baseURI + tokenId")

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

For NiftyWalls, is decided to implement tokenURI as a separate contract.

So, the NiftyWalls implementation looks like more or less this

interface ITokenURI {
    function tokenURI(uint256 tokenId) public view returns (string memory) ;
}

contract NiftyWalls is Ownable, ERC721 {
    address public metadata;

...
...

  function tokenURI(uint256 tokenId) public view override(ERC721)returns (string memory) {
    require(_exists(tokenId), "NiftyWalls: URI query for nonexistent token");
    IToknURI meta = ITokenURI(metadata);
    string memory json = meta.idToJson(tokenId);
    return string(abi.encodePacked("data:application/json;base64,", Base64.encode(bytes(json))));
  }

...
...

  function setMetadataContract(address _metadata) public onlyOwner {
    metadata = _metadata;
  }

A very simple implementation of the TokenURI contract would be something like this.

contract TokenURI {
    string private baseURI = 'http://mysite/' ;

     function tokenURI(uint256 tokenId) public view returns (string memory) {
        return string(abi.encodePacked(baseURI, tokenId.toString())) ;
    }
}

The nice thing with this approach is that it allows you to upgrade the TokenURI contract to do interesting things in the future, without touching the NFT contract. For example:

The cost of implementing tokenURI as a separate contract is minimal, and the advantages are significant, so I expect more ERC721 contracts to implement it this way.