Why doesn't OpenZeppelin ERC721 contain setTokenURI?

Hi Team,

I am looking at https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol 0.8.0 version.

It seems like that it doesn’t contain the function which receives the ipfs hash and metadatauri to set on a token. There was something like this in the previous versions of it. Look here https://github.com/OpenZeppelin/openzeppelin-contracts/blob/6be5ffe54fc8982b7c1d0f45d4cb7bbd313107cd/contracts/token/ERC721/ERC721.sol#L411

Any idea why 0.8.0 version doesn’t contain such a fuction ? Is it my responsibility that I create such a function in my derived contract ? if so, why previous version of openzeppelin contained such a function in it ?

Thanks in advance.

2 Likes

Hi @novaknole! This was split off into a separate contract called ERC721URIStorage, because using storage in this way is expensive and we decided to make the default mechanism significantly cheaper.

You may find these threads useful:

2 Likes

@frangio

Thanks a lot for your reply.

How can it become signifancly cheaper ? Everyone still needs to have setTokenURI functions and things like that, so, we still will use ERC721Storage, which means that in the end, the costs are the same.

Maybe what you mean is that some people wouldn’t have to setTokenURI and just use baseURI + '/' + tokenId as the tokenURI ? is this what you mean ? if that’s so, how can this be possible ? baseURI let’s say is gateway.pinada.com/ipfs , but tokenId is the id|number so , I can’t include ipfs hash anymore in the baseURI + '/' + tokenId which means I still need to have setTokenURI functionality.

Does it make sense ?

1 Like

Yes, we think most people should try to avoid setTokenURI if possible for their use case.

If you use IFPS, your baseURI wouldn’t be gateway.pinata.cloud, it would be the URI to an IPFS directory.

For example, I just uploaded a directory to Pinata at https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/.

I can now create an ERC721 with that URL as its baseURI (or I could use a native ipfs:// protocol URI), and mint token IDs 1 and 2.

The concatenated URI for token ID 1 would then be https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/1, and you will find the metadata there.

For NFTs where new tokens are minted dynamically, it would be possible to use IPNS to update the base URI to point to an updated directory of metadata.

It’s always possible to use ERC721URIStorage with setTokenURI if this doesn’t work or someone finds it too complicated to set up.

2 Likes

@frangio It all makes sense… Appreciate your detailed answer. I have several questions though.

Some questions regarding ERC721

  1. The contract doesn’t have updateBaseURI function. is this something derived contract should implement ?
  2. seems like setting baseURI to a directory or using ipfs:// protocol URI, solves the problem and there’s no need to use setTokenURI. Any idea why setTokenURI could still be useful ? you might say that people would be free to upload their content whichever gateway they want and they would have more freedom. are there any other advantages ?
  3. What do you mean by I could use a native ipfs:// protocol URI ? how would it be possible that baseURI would stay the same in this case, and we would just append the ID’s on it for different content ?
  4. How were you able to add 1 and 2 in here https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA
    I don’t want to click mint nft on the pinata’s gateway. How will my users put numbers in there so their NFT token corresponds to the https://gateway.pinata.cloud/ipfs/hash_here/tokenIdHere ?

Questions about NFT’s concept.

  1. If Taylor fritz sings a song, creates NFT, then my friend(she) sings the same song, creates NFT too. How do I make sure that I buy the original one ? let’s say that I checked IPFS content metadata and sound is the same and I can’t distinguish who is the real taylor. So, what’s the right way for me to detect that I am buying the NFT from the real taylor fritz and not my friend’s who is hiding this fact ? The only way I can see this happening is that Taylor fritz publicly announces what her etheruem address which helps me to check if there’s NFT on her address. Is there any other way without her public announcement ? I don’t think so…

Thanks so much in advance for all your help.

1 Like

If necessary, a derived contract can add a baseURI state variable and an update function.

In some cases it might be simpler to manage each tokenURI separately. For example, suppose you mint tokens regularly, with different metadata each time, and you want to host that on IPFS. In this case it might be easier to just upload a new file to IPFS and use setTokenURI for the minted token, otherwise you would have to update the entire metadata directory on IPFS and either change IPNS to point to the new directory, or change the base URI on your contract.

ipfs:// URIs work the same way. In my example it would be ipfs://QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/1.

In the Pinata main page, when you click “Upload” it offers an option to upload a Folder, instead of a File. This is what I used.

I’m not sure who your users are or what they should be able to do, so I don’t know what the workflow would look like for them. As I said above, if you’re going to be minting NFTs dynamically, using a single base URI may not be the best option for your use case, and ERC721URIStorage with setTokenURI may be a better fit.


Yeah I don’t think there is any other way. You need to somehow trace an NFT back to its alleged author. It can be as simple as the author posting a tweet saying they have created this NFT, though then you would need some way to verify that their Twitter account was not hacked.

1 Like

Thanks a lot Francisco @frangio

Hope, I am almost there to get the full picture.

you mentioned this:

The concatenated URI for token ID 1 would then be https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/1 , and you will find the metadata there.

How did you make the url ? i understand that you uploaded a folder on pinata, but how did 1 and 2 ended up in the folder ? I don’t want to click mint token which is also a button on the pinata on the uploaded folder’s options. What would be the process in this case ?

1 Like

A post was split to a new topic: Migrate metadata to new ERC721 contract

I uploaded a folder that contained text files called 1 and 2, then they become available at the URL I shared.

I’m not sure what “mint token” button you mean, sorry!

1 Like

I was under the impression that IPFS data had to be passed as one hash per NFT. Not a link to which we add an ID value at the end. And I think this is the actual flaw in the NFT mechanism. By using an API the data is centralized and could be lost or manipulated. In the Other hand IPFS produce a hash of the content. An immutable solution, more decentralized.

So by adding an ID at the end of a IPFS (meant to be static content) It’s kind of going against the flow.
This is of course my opinion but if I was to invest in an NFT I would value the ones that are immutable. Now it’s a matter of keeping these IPFS files online and this should be the responsibility of the platform to where the fees goes.

1 Like

Hi @madeindreams,

Instead of having an IPFS hash per token ID, you can have an IPFS hash for the entire token and use the token ID to access a JSON file in the directory. This means that you need to predefine the metadata and it can’t be changed (as changing the data would change the hash).

It starts to make less sense…

as @frangio said, he uploaded a folder on pinata and the folder contained the text files 1 and 2 as the names.

Now, let’s say we mint tokenId as 1 on the smart contract. Now, if I want to get the URI of this tokenId = 1, I call tokenURI which returns BaseURI +/ + 1 . The same scenario with tokenId as 2.

Now, the problem is that before the tokenId was minted, the data was already created, and uploaded on pinata, which means that if tokenId = 8 gets minted, even before it gets minted, its data should be written to pinata.

Question 1: So, after the folder gets uploaded to pinata which contains folder which contains 1, 2, 3 files and so on, no one can add new ones, but for sure 1,2 , 3 and all other files contain different data in it. but the problem is that no one can add more numbers in the folder. Right ?
Also, @abcoathup mentions : This means that you need to predefine the metadata , but I think, we not only need to predefine the metadata, but predefine the array of metadatas , because each tokenId has one metadata and we need to have multiple metadatas defined as multiple tokens have to be minted. Right ?

Question 2: can I add new file in the folder once it’s been uploaded to pinata ? seems like I couldn’t find this option. Other than moving all the files in the new directory.

Question 3: If I use IPNS, then it means that BaseURI on the contract should be the IPNS hash and not the IPFS one, right ?

Question 4: It really loses point at some point. using setTokenURI is still an amazing solution. Can we talk about why using setTokenURI is bad because of gas costs ? if we store tokenURI as IPFS hash which is 32 bytes(1 storage slot need), I don’t see so much gas costs related to this solution. What do you think ?

1 Like

Adding more metadata means that the IPFS hash of the directory changes. So using this method, you would either need to predefine all the metadata (e.g. for your total planned supply of tokens) or have a mechanism to update the baseURI.

Adding files to a directory means the hash of the directory changes.

Yes. Though I haven’t used IPNS so you would need to try this out to see what the naming scheme is.

@abcoathup Thanks so much for your answers.

Question 1:

So using this method, you would either need to predefine all the metadata (e.g. for your total planned supply of tokens) … you said this…

Total planned supply of tokens is very vague here, because these tokens are not the same as the normal tokens. each one of them is different, so it means each file(1, 2 and so on. in the folder) will have different data/metadata in it. Which means that I would have to host a file, grab all the users’ metadatas(they will be different), then start to push to pinata or something. Do you agree ?

Question 2: Adding files to a directory means the hash of the directory changes. True to that, but the bad thing here is that the normal user(not the creator of ERC721) won’t be able to do this, because she/he doesn’t have access to pinata… Which leads to centralization point too much. Don’t you agree ?

Question 3: I guess, whatever gateway we use(other than pinata), users(not the creator of ERC721) will not be able to add new metadatas at all as they won’t have access to it, unless we create our own gateway and design it this way, but still if anyone can add new metadatas, then it’s much worse because we are giving the power of our own metadatas to anyone else… Also, if anyone can call updateBaseURI, then it gets so so so much worse, because they can just update it to a very wrong hash which leads to nothing … What do you think about these ?

Question 4: It really loses point at some point. using setTokenURI is still an amazing solution. Can we talk about why using setTokenURI is bad because of gas costs ? if we store tokenURI as IPFS hash which is 32 bytes(1 storage slot need), I don’t see so much gas costs related to this solution. What do you think ?

1 Like

Hi @novaknole,

Question 1:

So using this method, you would either need to predefine all the metadata (e.g. for your total planned supply of tokens) … you said this…

Using an IPFS hash as a baseURI would mean you would need to setup metadata in advance, or have a mechanism to update the baseURI. IPNS could be another solution (though I haven’t played with it).

This means that you can’t add metadata without doing the above.

As the token creator you create the metadata. Or is your solution that users provide their own metadata? If users provide their own metadata, then you could store this centralized or you could accept an IPFS hash.

You need to store for each tokenID so this can add up to a fair amount of storage.

You can try the different options and see what the storage/minting costs are like and if it works for your use case.

Assume that we only store the hash as bytes32 and calculate the IPFS URL with Base58 Multihash in our tokenURI view function.

Populating the hashes in a dynamic array with a for loop costs 11,260,104 gas for 1,000 items. This is barely within the current block gas limit.

The cost would be around $2 per token.

Calculation: 11,260,104 gas / 1,000 token = 11,260 gas per token = 0.001126 ether per token at 100 gwei = $2 at $1,776 for ether

1 Like

@hantuzun Thanks a lot for the follow up.

But somehow it doesn’t make sense. Why do I need to pupulate the hashes in a dynamic array ? setTokenURI function should be fairly easy, user already passes the IPFS hash to it. The only cost is to store the bytes32 hash into the storage which each user only pays for his part only.

What am I missing ?

Hi @hantuzun,

Welcome to the community :wave:

Thanks for sharing costing for creating an ERC721 with 1000 items.

Hi @novaknole,

The example was minting 1000 items with the project specifying the metadata.

If users specify the metadata and is paying for the minting, then this is a different use case, so would be ok to do. The challenge would then be dealing with any moderation on the user supplied metadata (if this was an issue).

Glad to be here Andrew, looking forward to be a part of OpenZeppelin’s community, not a free rider as most of us :slight_smile:

You’re welcome.

As @abcoathup said, the metadata, i.e., the thing that’s distributed through the contract, is usually created by the contract creator.

In my case, setTokenURI function is easy. However, tokenURI function should append hash identifier (0x1220 for sha256) to the hash, Base58 encode it, and add this multihash after a base url such as ipfs://.

1 Like

Let me note that this’s apparently costly unless needed. The way to go for most of the projects is not to store a hash per token, but instead store multiple files under a single IPFS hash such as ipfs://Qmxyz...xyz/1.json. This could be achieved by uploading a directory to IPFS.

1 Like