Function _setTokenURI() in ERC721 is gone with pragma ^0.8.0

I see two use-cases in which _setTokenURI might be useful:

  1. If we need to build the URI considering a different name than the sequential tokenId (for instance, adding category or group prefix before the tokenId, or even using identifiers such as UUID-like).

  2. If we need to have different URIs per token type instead of having every token using the same baseURI (eg: using a path for hi-resolution pics and a different path for low-resolution pics associated with the NFT).

In summary, if the baseURI + '/' + tokenId is sufficient, I agree the current implementation should be fine. If this is not enough, then we either override function tokenURI() or use an extension if implemented.

2 Likes

@sjuanati

The latest preview (4.0.0-beta.1) includes ERC721URIStorage, an ERC721 extension, that should answer this issue.

Let us know if this is satisfactory or if you need something else.

2 Likes

I’m having trouble using the example on 4.0.0-beta.1’s documented example.

DeclarationError: Undeclared identifier.
  --> PATH/GOES/HERE/file.sol:49:9:
   |
49 |         _setTokenURI(newItemId, tokenURI);
   |         ^^^^^^^^^^^^

If I import and derive ERC721URIStorage on that file I get multiple override errors. Where should I be importing and including ERC721URIStorage? Sorry, I’m pretty new to this.

I’ve tried extension ERC721URIStorage from the latest preview (4.0.0-beta1) and everything seems to be working just fine!

@AnkerFaster I leave here a minimalistic code to try the extension, in case it helps:

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

import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.0/contracts/token/ERC721/extensions/ERC721URIStorage.sol';

contract Test is ERC721URIStorage {
    string baseURI;
    
    constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}
    
    function mint(
        address account,
        uint256 tokenId
    ) external {
        _mint(account, tokenId);
    }
    
    function setTokenURI(
        uint256 tokenId, 
        string memory tokenURI
    ) external {
        _setTokenURI(tokenId, tokenURI);
    }
    
    function setBaseURI(string memory baseURI_) external {
        baseURI = baseURI_;
    }
    
    function _baseURI() internal view override returns (string memory) {
        return baseURI;
    }
}
2 Likes

Feel free to ask for help with anything!

I imagine you did something like contract ... is ERC721, ERC721URIStorage ?

This is good, but it will result in the override errors you mentioned. They can be worked around following the recommendations in the error message, but they’re pretty verbose. If you instead inherit ERC721URIStorage only (as in @sjuanati’s post above!) you will get all of ERC721 functionality without all the override errors.

1 Like

i think the example code here needs to be updated ERC721 - OpenZeppelin Docs as it gives a setTokenURI error.

1 Like

Hi @scottsuhy,

Welcome to the community :wave:

Good spot. I have created an issue for this: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2614

You would be very welcome to create a PR to resolve.

1 Like

I wish I could @abcoathup . I’m just getting started with Solidity. Give me a few weeks and I may be ready to help on that front.

1 Like

Hi @scottsuhy,

Welcome to the world of Solidity!

It is never too early to start contributing to open source. :smile:
Updating documentation is a great way to start. My pitch is in: Solidity learning resources.

Thanks for the help! Sorry for the late reply. That cleared everything up :slight_smile: It also helped me out on another issue and another contract. I’m not used to working in heavily OOP styled code. Thanks again!

1 Like

I have the same error, now it’s OK:

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

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

contract HarddiskCafe is ERC721URIStorage{
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    mapping(string => uint8) hashes;
    
    constructor() ERC721("HarddiskCafe", "HDC") {}
  
    function awardItem(address recipient, string memory hash, string memory metadata) public returns (uint256){
        require(hashes[hash] != 1);
        hashes[hash] = 1;
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();
        _mint(recipient, newItemId);
        _setTokenURI(newItemId, metadata);
        
        return newItemId;
    }
}
2 Likes

Hi @samshi,

You can try with the Contracts Wizard: https://zpl.in/wizard

1 Like

I’m having trouble using ERC721URIStorage because I’m currently using ERC721Enumerable which both inherit from ERC721. Is there a way I can use both? It’d be great if you guys created an extension that implemented both of these into 1 file.

Take a look at Contracts Wizard and select both URI Storage and Enumerable to see how you can combine them into one contract.

1 Like

Hey @sjuanati thank you for sharing this. Could you tell me in your mint method, what is the tokenURI_ syntax? What get’s input here, the return of tokenURI method? Scott

I mixed the following features at wizard: URI storage, mintable and ownable. It gave me back:
'''
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.2;

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

contract PANA is ERC721, ERC721URIStorage, Ownable {
constructor() ERC721("PANA", "PANA") {}

function safeMint(address to, uint256 tokenId) public onlyOwner {
    _safeMint(to, tokenId);
}

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);
}
} '''

My question is: why do we have _burn function? I see it returns super.TokenURI(tokenId) which executes the same function but from the contract that is inheriting. thanks beforehand.

This is an override that is required by Solidity even though it does nothing. Solidity basically requires you to explicitly say that you want the _burn implementation from your parent.

I've been compiling with and without _burn function. Compiler specifies the functions needed to override at error message.

Hey i'm trying to implement this code in my nft cause my NFT's are dynamically minted and i'd like to set the base URI myself but i'm getting this error instead

project:/contracts/DogeWars.sol:451:25: TypeError: Data location must be "calldata" for parameter in external function, but "memory" was given.
    function setBaseURI(string memory baseURI_) external onlyOwner() {
                        ^--------------------^

Hi Frangio, i'm using the contract wizard to implement the base uri function, but i'm getting this error, do i need to set the base uri directly in the ERC721 contract i'm inheriting from?

TypeError: Function has override specified but does not override anything.
   function _baseURI() internal pure override returns (string memory) {
                                     ^------^

Compilation failed. See above.