I have a smart contract working on rinkeby which is minting ERC721 tokens. The goal is to allow minting for a week and then to release the metadata and images after a week has passed.

When I deploy it, I use https://gateway.pinata.cloud/ipfs/ as the baseUri that I feed into the contract.

Next, im simulating a delayed metadata/image release:

The image_url metadata property is failing in validation because ipfs links use a hash and im using the integer token value. I cant see to figure out how to bridge this gap since the hash is generated outside of the contract and it looks like the contract is what is generating this link in the metadata.

Could anyone shine a light on my dilemna? Thank you! :D


You did not set the TokenURI for NFT, so when NFT gets the URI, BaseURI + TokenID is used.Look at the source code.

 * @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 _tokenURI = _tokenURIs[tokenId];
    string memory base = baseURI();

    // If there is no base URI, return the token URI.
    if (bytes(base).length == 0) {
        return _tokenURI;
    // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
    if (bytes(_tokenURI).length > 0) {
        return string(abi.encodePacked(base, _tokenURI));
    // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
    return string(abi.encodePacked(base, tokenId.toString()));

It returns the URI: https://gateway.pinata.cloud/ipfs/0

This, of course, is not ideal, the ideal URI should be: https://gateway.pinata.cloud/ipfs/Qmf43nycxRXdwdq3GqT7SKU3MvRyBvbpjzufzZR8ASkMGb

I think you see the reason for the problem here.

The next thing you need to do is call _setTokenUri to store the CID from IPFS so that you can get the correct IPFS link when reading the NFT.

 * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
 * Requirements:
 * - `tokenId` must exist.
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
    require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
    _tokenURIs[tokenId] = _tokenURI;

This function is an internal function, and you should provide an external function to use it, but security must be taken care of, or anyone can modify it!

  • Yes! Thank you! I simply was missing the last piece!
    – ogg130
    Jun 25 at 23:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.