Token metadata

The link between on-chain and off-chain data

Smart contracts need only store the minimum amount of data to identify the owner of a token. "External" properties are typically stored off-chain.

Sometimes these data are stored on regular cloud services. However, for better coherence and decentralisation, many projects store the token metadata on a decentralised service called IPFS. Other decentralised storage solutions include Filecoin and Arweave.

IPFS works with content-addressable URI, which means that the URI of an object is the signature (hash) of its content. This allows us to create a strong link between the on-chain information and the off-chain metadata. tzip-12 and tzip-21 allow this by adding a token_metadata map, where the empty string key will point to a byte-encoded string representing the metadata URI (see the doc for more details)

The steps to publish metadata on IPFS are as follows

Prepare the content

A NFT usually has a media representation (image, video...). We'll use a simple PNG image here, one of a zombie, the other of a brain. Don't ask me why.

The media file can be published on IPFS, which means made available on at least a node on the network. To achieve this, either you can have an IPFS node running, or you can use pinning services such as:

When the file will be published, you will get a content identifier (CID) in return. This is used to construct the URI to access the content.

We'll use the following two images for our dApp:

If your browser cannot open these links, replace ipfs:// with https://ipfs.io/ipfs/ to use a gateway.

JSON

Now that we have our images, we can prepare the rest of the metadata. Following the token metadata standard, we end up with the following format:

{
  "name": "Brainz",
  "description": "A tasty brainz",
  "artifactUri": "ipfs://QmT2NbKvqrXTwZQXcziP3XiTawk63eWknYsGegBAGDKRAJ",
  "displayUri": "ipfs://QmT2NbKvqrXTwZQXcziP3XiTawk63eWknYsGegBAGDKRAJ",
  "thumbnailUri": "ipfs://QmT2NbKvqrXTwZQXcziP3XiTawk63eWknYsGegBAGDKRAJ",
  "symbol": "",
  "tags": ["tezos", "tutorial"],
  "creators": [],
  "formats": [
    {
      "uri": "ipfs://QmT2NbKvqrXTwZQXcziP3XiTawk63eWknYsGegBAGDKRAJ",
      "mimeType": "image/png"
    }
  ],
  "decimals": 0,
  "isBooleanAmount": false,
  "shouldPreferSymbol": false,
  "attributes": [
    {
      "name": "Alive",
      "value": true,
      "type": "boolean"
    }
  ]
}
{
  "name": "TZombie",
  "description": "A deadly undead creature",
  "artifactUri": "ipfs://QmehW5o2t2ZGUoHBYqdFbozGEWSSGrNBtcPcocaLXPpxbY",
  "displayUri": "ipfs://QmehW5o2t2ZGUoHBYqdFbozGEWSSGrNBtcPcocaLXPpxbY",
  "thumbnailUri": "ipfs://QmehW5o2t2ZGUoHBYqdFbozGEWSSGrNBtcPcocaLXPpxbY",
  "symbol": "",
  "tags": ["tezos", "tutorial"],
  "creators": [],
  "formats": [
    {
      "uri": "ipfs://QmehW5o2t2ZGUoHBYqdFbozGEWSSGrNBtcPcocaLXPpxbY",
      "mimeType": "image/png"
    }
  ],
  "decimals": 0,
  "isBooleanAmount": false,
  "shouldPreferSymbol": false,
  "attributes": [
    {
      "name": "Alive",
      "value": false,
      "type": "boolean"
    }
  ]
}

These two files are also published to IPFS. We get these two URI for our metadata:

Contract encoding

According to the standard, the two metadata URI must be set as a bytes sequence representing the URI string in the smart contract. This gives us:

  • Zombie: 697066733a2f2f516d546d65517a55754b37716d467337795466563254434c5a416852466d716d714a793536636b6b7a666a586939

  • Brainz: 697066733a2f2f516d53445733794257756e7977624c544c78723835784843464d6d747a5372365a55565138433375346161314d65

To generate the byte sequence from a string, we can use this Node.js one-liner:

Buffer.from("<your string>").toString('hex')

These two byte-strings are the values set in the `set_token_metadata` contract call, as we saw earlier.

Last updated