ERC721 - NFT - all the tokens sharing the same metadata tokenURI?

TLDR: I would like all the NFTs to use the same token URI - an IPFS hash that contains JSON file with all the metadata.

Looking at the source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/538b6d21b15733601f9193af5b9f662b94f16ea1/contracts/token/ERC721/ERC721.sol#L91

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    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()))
            : '';
    }

Looks like tokenURI is individual for each token.

Does it make sense for many NFTs to share the same tokenURI?

In my opinion yes.

I could be wrong? :woman_shrugging:

Limited series of 100 artworks or passports. All of them can share the same token URI?

SHILL: https://docs.google.com/document/d/1bN4MpxCpEad-1PesnX9B3Lu_Sm57IEXQfHCoagYXB3M/edit?usp=sharing

(still working and editing, the JSON metadata on IPFS and tokenURI is the current problem I’m solving)

RELATED (DYOR)

I don’t want to deploy a directory of JSON files, I just prefer a sigle JSON file that is applicable for the NFTs.

STANDARD

/// @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);
}
  • This is the “ERC721 Metadata JSON Schema” referenced above.
{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}
1 Like

I think I’ll just override the function.

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

        return "ipfs://Qmck3GpzP8uhXgpkUUKnHMH3DZKUpwf1U9BgyvLKFNK6r6"; // 1 ?
        return "Qmck3GpzP8uhXgpkUUKnHMH3DZKUpwf1U9BgyvLKFNK6r6"; // 2 ?
        return "https://ipfs.io/ipfs/Qmck3GpzP8uhXgpkUUKnHMH3DZKUpwf1U9BgyvLKFNK6r6"; // 3 ?
    }

I think option 1 is the most preferable.

That IPFS hash will be a JSON containing all the metadata.

I think that is a sensible workaround?

Kindly welcoming a reality check.

This is a relatively new area (ground zero), standards are still being developed…

1 Like

Hi @marsxr,

You could either override tokenURI (option 1 is supported by OpenSea, rather than option 3 as it is centralized due to the gateway) or you could pre-create your metadata for each token ID and upload the directory to IPFS and have the base URI as ipfs://[Hash]/ and concatenate with the tokenID in tokenURI

That is up to infinity… Not keen. I prefer a static / fixed tokenURI and I will use option 1 as it appears natural.

EDIT / UPDATE

INFINITY

    receive() external payable { // Fallback function
        purchase();
    }

    function purchase() payable public {
        require(msg.value >= currentPrice, "Not enough ETH. Check the current price.");
        uint256 refund = msg.value - currentPrice;
        if (refund > 0) {
            (bool sent1, bytes memory data1) = payable(msg.sender).call{value: refund}("");
            require(sent1, "Failed to send ETH refund to the sender");
        }       

        // Sending to Gnosis Safe takes more than 21k gas limit on `transfer`
        // Need to use something else, see: https://solidity-by-example.org/sending-ether/
        (bool sent2, bytes memory data2) = multisig.call{value: currentPrice}("");
        require(sent2, "Failed to send ETH to the multisig");

        _mint(msg.sender, currentSerialNumber);
        emit Purchase(msg.sender, currentSerialNumber, currentPrice, false);
        currentSerialNumber++;

        if (block.timestamp > cutoffTimestamp) {
            currentPrice = currentPrice * multiplier / divisor; // * 1001 / 1000 === increase by 0.1% (no longer SafeMath, compiler by default)
        }
    }

There is a price increase but as we recycle ETH (spend and release back in circulation) there is no upper limit…

THERE IS ALSO 1 WBTC PURCHASE

    // This is inspired by Hackers Congress Paralelní Polis: final ticket available for 1 BTC
    // Network State Genesis offers *UNLIMITED* number of NFTs for 1 BTC
    // How is that even possible?
    // As we establish multiplanetary civilisation, some of the accrued money will be put back into the circulation (Bitcoin recycling)
    function purchaseWithWBTC() public {
        WBTC.transferFrom(msg.sender, multisig, 10 ** 18);
        _mint(msg.sender, currentSerialNumber);
        Purchase(msg.sender, currentSerialNumber, 0, true);
        currentSerialNumber++;
    }

Also INFINITY

1 Like