How to: ERC-1155 ID Substitution for Token URI

I haven’t encountered any issues yet. But I’m not sure I understand how to implement ID substitution for this token standard.

If I understand correctly, the metadata URI within the 1155 contract could be a string like "https://game.example/api/item/{id}.json" set in the constructor or after deployment for all token types created from this contract.

After the contract is deployed, say we mint a token with an id of 1. Does my JSON metadata need to be hosted at "https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000001.json"?

If I were to upload a JSON file to IPFS for hosting and received an IPFS hash like QmNeHgsWvoZHDjXJwH3uBev1M69GXGBHyHYZpjZxDkkGsr to locate it at https://ipfs.io/ipfs/QmNeHgsWvoZHDjXJwH3uBev1M69GXGBHyHYZpjZxDkkGsr, would my URI be https://ipfs.io/ipfs/{id} and the ID be QmNeHgsWvoZHDjXJwH3uBev1M69GXGBHyHYZpjZxDkkGsr?

This doesn’t seem right, so I thought maybe I need to map the token id to the IPFS hash or convert the IPFS hash into hex first before appending it to the URI from the contract. But the IPFS in hex extends past the 64 hex character length and mapping ids to IPFS hashes diminishes the gas savings from using this contract in the first place.

I’m not well-versed with server requests or database storage, so the relationship between the externally hosted metadata JSON identifier and the token ID confuse me a bit. It seems the token ID is subject to the metadata identifier I get from IPFS. But I feel like I’m missing an important piece in all this.

5 Likes

Yes. Though please note it is the token ID in lowercase hexadecimal padded to 64 hex characters.

The uri can include the string {id} which clients must replace with the actual token ID, in lowercase hexadecimal (with no 0x prefix) and leading zero padded to 64 hex characters.

For token ID 2 and uri https://game.example/api/item/{id}.json clients would replace {id} with 0000000000000000000000000000000000000000000000000000000000000002 to retrieve JSON at https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json.

I have the following example:
https://abcoathup.github.io/SampleERC1155/api/token/0000000000000000000000000000000000000000000000000000000000000000.json


Unfortunately you can't use this scheme with content addressing such as IPFS.

So you either need to implement a token URI mechanism (but would need the OpenZeppelin Contracts implementation to support, see: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2154#issuecomment-657880427 otherwise you would need to create your own implementation) or you create a mechanism to redirect from the centralized URI to the decentralized IPFS hash address. I don't know if IPNS would be an option for this?

2 Likes

I see! Thank you very much. That clarifies a lot of my confusion. I didn’t realize the IPFS scheme was incompatible with the 1155 URI scheme.

I’ll look into IPNS and see if it would work for this. Otherwise, a centralized solution wouldn’t be a problem for my use at this stage.

1 Like

Hi @Jshanks21,

A single token URI using the {id} replacement is the most gas efficient but uses a centralized location based naming.

You could have your image locations in your metadata use IPFS hashes.

1 Like

Hi @abcoathup,

I love it. This was very helpful. I was trying to make 1155 ID substitution and IPFS hashes work together without being aware of this incompatibility. This was making any progress so much harder than necessary.

But now with a better understanding of how these can work together, I have a much clearer idea of what needs doing. I used the IPFS daemon to upload a directory of images I can use for tests, so I just need a centralized host for the rest of the metadata JSON.

1 Like

It is not completely true.
If the metadata is known ahead of time, you can create an ipfs folder with the filenames being the id as per the ERC1155 rules

if metadata is to be provided as part of a minting process, you can as @abcoathup mentioned implement the uri function yourself or emit the URI event. if you use uri you ll need to store the ipfs hash but can use folder to do so in batch (one hash per batch). This is how we do it for https://sandbox.game

4 Likes

That’s really clever @wighawag! Thank you for the tip. I’ll checkout the sandbox to explore this further and try implementing it myself.

2 Likes

Hi @wighawag,

Thank you :pray:

I didn’t realise that you could do [ipfs directory]/[padded token id in hex]

1 Like

Hello @abcoathup ,
sorry to revive this topic but I was wondering how to deal with the initializer of ERC1155Upgradeable when you override the uri() function.

Let’s say you use IPFS for your URI and then can’t really have a common base URI because we are in situation 2 of what @wighawag describes.

Then OZ implementation of setUri() and string private _uri are not longer relevant because each batch of the minted tokens will have its specific IPFS URI.
What should be done then? set URI with a null value? Ovveride the initalizer of ERC1155Upgradeable and getting rid off this setter?

thanks for your help

Just ran into this myself. Like @wighawag said, I used an ipfs folder with the file names as ids.

For example: I uploaded a folder called “foo” with 3 images.
The url is then:
https://gateway.pinata.cloud/ipfs/Qme92XuB3CPHqGHiXC3pSkAcctoGEfdHBpKeMziTTkZ6/foo/{id}.json, with the same hash for the 3 images in the batch.
This way they have the same base url

Hey @mpng yes I agree with that part.
But if one needs to mint tokens later, the folder won’t accept new json files unless we use IPNS. Yet in that case why bother lol.
So each batch will have a different base folder path and set_uri won’t work that well.
Alternative is to store just the IPFS:// tag but it’s not sparing much data.

Did you find solution if we want to add more files to the folder with IPFS? Or any alternatives?

I think I found a way to do this. But it is a bit hackish. The trick is to use a non-default hash for the CID that encodes to 65 characters in base16 and use the hash minus the leading "f" as token-id. Both sha3-224 and blake2b-208 should do the trick. Example with "Hello World" as content:

$ ipfs add hello.txt  --cid-version=1 --hash=blake2b-208
added bafkzvzacdjx7hmxsyps4q3kneb5vktqzfekadpj6yv34xotaxeea hello.txt

$ ipfs cid format -b=base16 bafkzvzacdjx7hmxsyps4q3kneb5vktqzfekadpj6yv34xotaxeea
f01559ae4021a6ff3b2f2c3e5c86d4d207b554e19291401bd3ec577cbba60b908
Token-ID: 01559ae4021a6ff3b2f2c3e5c86d4d207b554e19291401bd3ec577cbba60b908
Template: https://gateway.ipfs.io/ipfs/f{id}
Without gateway: ipfs://f{id}

Have not yet tested this but it should work in theory :grinning:

1 Like

Great idea and it works not only in theory :slight_smile: So no more reasons to have centralized NFTs :slight_smile:

The URI function needs to be overwritten like this:

    import "@openzeppelin/contracts/utils/Strings.sol"
    
    function uri(uint256 _tokenID) override public view returns (string memory) {
    
       string memory hexstringtokenID;
         hexstringtokenID = uint2hexstr(_tokenID);
    
    return string(
        abi.encodePacked(
        "ipfs://f0",
        hexstringtokenID)
        );
    }

Here is an [example](https://soenkeba.medium.com/truly-decentralized-nfts-by-erc-1155-b9be28db2aae).
2 Likes

This is a really cool approach, actually our team is messing with this.

From this V1 using hash blake2b-208 = we get bafkzvzacdkm3bu3t266ivacqjowxqi3hvpqsyijxhsb23rv7nj7a
then from base16 we get
f01559ae4021a99b0d373d7bc8a80504bad782367abe12c21373c83adc6bf6a7e

The question is:
From base16 and removing the "f" we get 01559ae4021a99b0d373d7bc8a80504bad782367abe12c21373c83adc6bf6a7e, then we need pass to hex from this? i mean how to handle this function in contract function mint(address account, uint256 cid).
CID is a uint256 need to hex(01559ae4021a99b0d373d7bc8a80504bad782367abe12c21373c83adc6bf6a7e) ?

NOTE: i am using python so i guess this type need to be casted to int(hex(01559ae4021a99b0d373d7bc8a80504bad782367abe12c21373c83adc6bf6a7e), 0).

Thank you :slight_smile:

Just in case that somebody has the same issue:

in python solution is :

# Format base16 => hex => int
cid = int("0x" + cid_to_uint256(cid)[1:].decode('utf-8'), 0)
logger.log.info(f"uint256: {cid}")

Thank you guys

Hi ...

this is a working example:

Just put the "0xHEXNUMBER" to call the mint function. It is then changed to base10 integer. Open Sea, etc. will call the URI function with that number which will be changed back to a hex string with the uint2heststring ...

Let me know how it goes ...

1 Like

Hey, thank you for your response. Yeah, actually i figure it out and now it working with python:
Please follow this: How to: ERC-1155 ID Substitution for Token URI - #18 by Geolffrey_Mena

Regards

Actually it can be done even simpler with the default sha256 hash if you add the static CIDv1 prefix to the URI template:

$ ipfs add hello.txt --cid-version=1
added bafkreigsvbhuxc3fbe36zd3tzwf6fr2k3vnjcg5gjxzhiwhnqiu5vackey hello.txt

$ ipfs cid format -b=base16 bafkreigsvbhuxc3fbe36zd3tzwf6fr2k3vnjcg5gjxzhiwhnqiu5vackey
f01551220d2a84f4b8b650937ec8f73cd8be2c74add5a911ba64df27458ed8229da804a26
Token-ID: d2a84f4b8b650937ec8f73cd8be2c74add5a911ba64df27458ed8229da804a26
Template: https://gateway.ipfs.io/ipfs/f01551220{id}
Without gateway: ipfs://f01551220{id}
2 Likes

Hello guys, I'm having erc1155 smart contract which is using following base url: https://token-cdn-domain/{id}.json . When i try to get the uri for token with id 5 for example i receive this: https://token-cdn-domain/5.json but in ipfs they are stored with leading zero padded to 64 hex characters length and 5 looks like this 0000000000000000000000000000000000000000000000000000000000000005.json any idea how to provide the correct uri?

1 Like