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)
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);

    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {

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

I believe that you need to change this:

_setTokenURI(tokenId, uri);

To this:


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