Minted NFTs of an ERC721 contract having different metadata

I’m trying to implement ERC721 in such a way that copies of a NFTs by the same contract can each represent different metadata.
For example the copy of id 0 has the metadata A, while the copy of id 1 has the metadata B.

I assume this is possible since otherwise I don’t understand how can projects like cryptokitties manage to have a huge amount of NFTs representing different products without creating a unique contract for each unique product.

How can this be done? I’m trying to follow guides but they all input the metadata at contract creation so every minted copy will represent the same metadata.

1 Like

Hello @LSparkzwz welcome to the community :slight_smile:

In Contract’s ERC721 documentation you can see an example of an NFT representing different items in a game. It simply sets the tokenURI on each mint.

// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract GameItem is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("GameItem", "ITM") {}

    function awardItem(address player, string memory tokenURI)
        public
        returns (uint256)
    {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}
1 Like

Thank you.
If I want to set some other unique data besides the uri, is this the correct approach?
I pretty much copied the uri functions in ERC721URIStorage. Also if I’m not mistaken once the NFT is minted this way there’s no way to change the uri and this extra data right? Since the set functions are internal and not used somewhere else in the contract.

// contracts/GameItem.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract GameItem is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    
    mapping (uint256 => string) private _extraData;

    constructor() ERC721("GameItem", "ITM") {}
    
    function _setExtraData(uint256 tokenId, string memory extraData) internal virtual {
        require(_exists(tokenId), "extraData set of nonexistent token");
        _extraData[tokenId] = extraData;
    }
    
    function extraData(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "extraData query for nonexistent token");

        string memory _extraData = _extraData[tokenId];

        return _extraData;
    }


    function awardItem(address player, string memory tokenURI,  string memory extraData)
        public
        returns (uint256)
    {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);
        
        _setExtraData(newItemId, extraData);

        return newItemId;
    }
}
1 Like

Hi @LSparkzwz,

Unless you need the metadata on chain, you may want to store it in decentralized storage to reduce minting costs (e.g. IPFS)