Smart Contract Token URI doesnt contain Token ID

I am using the smart contract for erc721 standard with the following functions: burnable, mintable, ownable and URI Storage. I am using URI Storage because i want to mint token with a unique metadata easily without setting up a dictionary on ipfs. I have read that the token URI gets concatenated with the token ID but when i mint a token, setting token ID to „1“ and set my uri to my ipfs CID, the token URI i call later after the successfull mint doesnt contain the 1 at the end. Actually this is exactly what i want, since it doesnt change my CID, but i would like to understand why and when this happens.
Thanks a lot for any explaining

Please share all the relevant code (and only the relevant code).

This is the code I am using:


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Tokentest is ERC721, ERC721URIStorage, ERC721Burnable, Ownable {
    constructor() ERC721("Tokentest", "MTK") {}

    function safeMint(address to, uint256 tokenId, string memory uri)
        public
        onlyOwner
    {
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }
}

I believe that you need to change this:

_setTokenURI(tokenId, uri);

To this:

_setBaseURI(uri);

As you said, what ERC721URIStorage does is allow you full control of the tokenuri for each token.

Whenever someone wants to retreive the meta data of a token they would call tokenURI(uint256 tokenId)

Normally this function would return: string(abi.encodePacked(_baseURI, _tokenURI))
Which just means: baseUri + tokenId, where _baseURI would be set to the base url of your IPFS location.

If you use ERC721URIStorage then the token URI's are stored in a variable _tokenURIs. If the _baseURI is empty, then whenever tokenURI(tokenid) is called it will directly return the tokenUri: return _tokenURI and does not try to add the _baseURI to it.

It does mean that you when you mint a token, you will have to set the tokenuri using the _setTokenURI(uint256 tokenId, string memory _tokenURI) function with your your full tokenuri.

To explain a bit more, what "normally" happens is that if a "dApp" sees en url like ipfs://xxxxxxxxx it will go to that url and retreive a "json" file. This file contains the metadata for your token like the name + any other information like rarity and stuff like that.
However this is not required, the tokenURI is also allowed to directly return the meta data or even the image itself. This allows you to dynamicly "build" the meta information without requiring you to store this information on an external location like ipfs.

For more information about that latter you check out this article:

Thanks a lot! So that means that the Token URI only gets changed when it gets set from the base URI?

That is correct, when you use ERC721URIStorage then you set the uri one time with _setTokenURI and then remains the same for that token.

However, even without using ERC721URIStorage it would always remain the same, it will just always include the token id.

Thanks a lot for the help @the 2 of u

Follow up for my clarity. My project of 200 animations with metadata on NFT.Storage. The uploader will create the CIDs.

My metadata json files are 1.json running up to 20.json, then in groups of 20, 101.json to 120.json and this runs up to 901.json to 920.json. This is true for my image files ie 1.jpg and animation_url files, ie 1.mp4. This is why I am concerned with this thread. I have not created the uri/cid yet. In this situation will the hex code work with such titles?

This is for a Seadrop. The ERC721 in the Wizard, I think the options I want are

Mintable
auto increment IDs
Pausable
Enumerable
URI Storage

UUPS

Please confirm, I can piece things together but time wise such confirmations take down the pressure and speed up the production time.

Update
Of course I am studying further....just going to use
mintable
auto increment ids
pausable
enumerable

not going to use uri storage or uups.....I believe so far?

The file CIDs will be separate from some other nightmare I read about where the individual was creating the URIs and the hexcode had problems with his file names. I think I can dismiss that issue,

Sorry, I’m having problems understanding the question.

Someone who was deploying file names into his uris was writing individual uris

000000000000000000000000000000000010.jpg

In the process the 10 was put wrong by the computers as 1a because of the hex code.

my ipfs://xxxxxxxxxxxxxxxxxxxxxxxxxxx/10.jpg should not mess up the hex code.

or for that matter ipfs://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/10.json

I doubt this will be a problem for me.

I do not need URI storage checked off in wizard, I think.

UUPS is not necessary?

I’m not sure what you mean with deploying file names and wrong urls. Or what you mean with your ipfs url should not mess up the hex code.

The token url that the nft returns should return the location of the metadata ipfs url.
If this ipfs url is different for the first 10 nft tokens then you will have to use something like nftstorage to return the correct url for each token. If the token url’s are normal and incremental then you don’t have to use the custom urls.

Uups has nothing to do with nft (in general) so no that’s not needed. Uups or something similar is only needed if you want to upgrade/change your nft contract code after it is deployed

Forgetting the guy online elsewhere who screwed up his files names etc....

I am using the NFTStorage uploader for the images and animations to get the CIDs for both folders.

I am then hand building my json files because the file name for the images and animations are in blocks of 20. I have 10 prototype animations and 19 versions of each.

I will make one json file and populate it. Then copy it and change the /1.jpg and /1.mp4 to /2.jpg and /2.mp4. The item "name" I will change from "1 xyz" to "2 xyz".

When the 200 json files are built in the json folder I will upload that folder to NFTStorage for the baseuri.

I have not generated my animations as projects that generate 10k cards would do.

To play it safe I am going to put the "animation_url" before the "image" within each json file so that the "animation_url" lets Opensea know this is an animation NFT and not just an image to upload.

So yeah, the options you selected should be enough. You would use the uri storage extension if you want to use custom uri’s. If you use auto increment and your ipfs uri’s increment like that also then you do not need that extension.
UUPS is useful if you want to upgrade your code later but not required either.

1 Like

Sorry I had to read this a few times. I am not making custom uri's. The nftstorage uploader is making the uri's. I will be uploading an animations folder to get the cid. Then using that cid added the /number.mp4 to build the json file. The json folder when complete will be uploaded to get that cid or uri.

That build will increment like any other build by one, changing the start from 0 to 1, and go to a Max 200. The file names after the respective cid's are not of import. They do not control the process.

adding the json files will be saved as 1 to 200 because that is the incremental process.

I have two other problems to kick around.

Do I set the buy limit in the contract? Or with opensea's seadrop? I want buyers to have an option to buy up to 35 NFTs from my drop.

Can I set the prototype animations to a higher Eth price than the 19 subsequent versions of each prototype? The 19 versions get just one lower price. Do I do that in the contract or with seadrop? In other words ten prototypes and 190 versions.

Sorry I had to read this a few times. I am not making custom uri's. The nftstorage uploader is making the uri's.

Yeah thats what tripped me up initially in your post also. The OpenZeppelin wizard has the option to enable ERC721URIStorage that extension is used for allowing you to set "custom" (ipfs) url's. Thats unrelated to nft.storage :slight_smile:

Do I set the buy limit in the contract? Or with opensea's seadrop? I want buyers to have an option to buy up to 35 NFTs from my drop.
Can I set the prototype animations to a higher Eth price than the 19 subsequent versions of each prototype? The 19 versions get just one lower price. Do I do that in the contract or with seadrop? In other words ten prototypes and 190 versions.

I'm assuming you mean https://docs.opensea.io/docs/drops-on-opensea tbh im unfamilair with their system. It looks like you'll have to extend your smart contract from their ERC721SeaDrop contract as it contains additional logic, however i don't see any information on setting up a pricing structure for this.

1 Like

I am getting to my questions on the max limit and price. I am okay there.

The ERC721 SeaDrop contract is ERC721A.

I need to study how to enter my baseuri into the contract. It wont be hard. I need to stipulate Max 200 NFT. I need to add my wallet. But that might be all I need to do. Any other reminders?

Tomorrow into Wednesday i need to create all the json files by hand. Then get that uri from NFTstorage.

I wont be tinkering with the coding of the smart contract. By lining up the json files to be used incrementally this is an easy job.

I am beginning to look at the calendar for a drop date.

Deploy your NFT/contracts on testnet first and test it using https://testnets.opensea.io/
See if everything is working as expected before you deploy it to main net

1 Like