Understanding ERC721 _setTokenURI after minting

Hello!
I am an engineering student and recently I started with the wonderful world of SC. I am currently trying to learn the implementation of ERC721 and how it works and there is a thing I do not understand and I would appreciate help.
(I am taking as an example a code of an nft project from etherscan).
This project had a mint process and a reveal done a week later. I understand how the minting is done (with the function _safeMint and the modifications one may add) but I do not understand how the token URI is set or how can it be accessed and changed in the future for the reveal as I do not see any DataStructre which stores the uri data or updates it in the contract.

Full code: https://etherscan.io/address/0xd8537b0ea5b21ca39af493525a3c54b6fa8f32b3#code

Functions referencing URI:

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        baseTokenURI = baseURI;
        emit BaseURIChanged(baseURI);
    }

Mint function:

    function mint(uint256 amountOfNfts) external payable whenPublicSaleStarted {
        updatePrice();

        //Requirements here....

        for (uint256 i = 0; i < amountOfNfts; i++) {
            uint256 tokenId = numNftsMinted + 1;

            numNftsMinted += 1;
            _totalClaimed[msg.sender] += 1;
            _safeMint(msg.sender, tokenId);
        }
     //  emits here
}

And as you can see there is a _safeMint but not a _tokenURI function and I would like to unerstand how this is possible.
Thank you very much!

The contract inherits ERC721Enumerable which itself inherits ERC721. If you look at that contract, you will find:

    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())) : "";
    }

Here the token id is concatenated with the base URI. Thus the token URI doesn't need to be stored in a data structure, as it's generated dynamically.

Thank you!
Just one more question regarding the answer:
The function tokenURI is not called in the contract (at least inside the contract), is it called outside (f.e. in brownie) to update each token metadata?
Thank you!

Yes, it's called from outside the blockchain. Not exactly to "update" token metadata, just to fetch/query the metadata when it needs to be shown. Generally though services (e.g. OpenSea) will query it once and keep it in their cache.

1 Like