CIP - NFT Metadata Standard

Hey everyone!

I’m trying to form a metadata standard for NFTs with this proposal. Let me know your thoughts about this approach.

Motivation

Tokens on Cardano are a part of the ledger. Unlike on Ethereum, where metadata can be attached to a token through a smart contract, this isn’t possible on Cardano because tokens are native and Cardano uses an UTxO ledger, which makes it hard to directly attach metadata to a token.
So the link to the metadata needs to be established differently.
Cardano has the ability to send metadata in a transaction, that’s the way we can create a link between a token and the metadata. To make the metadata unique to the token, they should be appended to the same transaction, where the token forge happens:

Given a token in a EUTXOma ledger, we can ask “where did this token come from?” Since tokens
are always created in specific forging operations, we can always trace them back through their
transaction graph to their origin.

(Section 4.1 in the paper: https://hydra.iohk.io/build/5400786/download/1/eutxoma.pdf)

Considerations

That being said, we have an unique metadata link to a token and can always prove that with 100% certainty.

My assumption is now that, with the upcoming explorer APIs for multi assets it should be possible to find the forging transaction of a token by simply providing the Policy ID and the token name, in order to get the right metadata.

EDIT:
I just found the SQL DB Schema in the db-sync repository, it has a table, that confirms my idea actually works:

CREATE TABLE public.ma_tx_mint (
    id bigint NOT NULL,
    policy public.hash28type NOT NULL,
    name public.asset32type NOT NULL,
    quantity public.int65type NOT NULL,
    tx_id bigint NOT NULL
);

With SELECT tx_id FROM ma_tx_mint where ma_tx_mint.policy = <“policy”> AND ma_tx_mint.name = <“name”> I can get the forging transaction by only providing the Policy-ID and the token name.

Specification

This is the registered transaction_metadatum_label value

transaction_metadatum_label description
721 NFT Metadata

There are two different ways, how the metadata structure can be defined. One makes more use of the Cardano blockchain and the other is a more offchain approach. For both approaches we mint the example NFTs with:
Policy-ID: cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b
Names: nft0, nft1, …

On-Chain Approach:

{
  "721": {
    "cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b": {
      "nft0": {
        "id": 0,
        "name": "NFT 0",
        "image": "ipfs://ipfs/<IPFS_HASH>",
        <other properties>
      },
      "nft1": {
        "id": 1,
        "name": "NFT 1",
        "image": "ipfs://ipfs/<IPFS_HASH>",
        <other properties>
      }
      ...
    }
  }
}

Off-Chain Approach:

This could be useful, when creating a lot of NFTs or the metadata entries for a single token are big in size, were it would be too expensive to do everything on-chain.

{
  "721": {
    "cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b": {
      "offchain": "ipfs://ipfs/<IPFS_HASH>"
    }
  }
}

Fetching the link from the offchain property will give you back a json file of this format:

{
  "nft0": {
    "id": 0,
    "name": "NFT 0",
    "image": "ipfs://ipfs/<IPFS_HASH>",
    <other properties>
  },
  "nft1": {
    "id": 1,
    "name": "NFT 1",
    "image": "ipfs://ipfs/<IPFS_HASH>",
    <other properties>
  }
}

Since we have the policy and the token name, it is a quick and exact lookup in the metadata map for a specific token to get all the properties needed.

Ipfs is the preferred way to where fetch data from. Reason one is to keep everything decentralized/immutable and reason two is, that the hash is always fix in size and you can be assured that you always stay below the maximum of 64 bytes for a string (http/https is of course also an option).

These approaches also work with batch transactions, where tokens are not only forged in a single transaction, but in multiple.

9 Likes

Interesting :smiley:

A few questions: what guarantees in terms of data availability does IPFS provide? For example, if I upload a file with some token’s metadata, what’s the incentive for the hosting node(s) to keep it in the long term? If the file is not available because the node(s) hosting it go offline, how can we mitigate this?

At first glance at least, I tend to think that the first approach is more robust. Though yes, the amount of tokens that can be minted in a single transaction is more limited.


“image”: “ipfs://ipfs/<IPFS_HASH>”,

By image you mean an actual link to a picture or a link to a metadata object file (possibly containing picture)? Linking to a eg. json metadata file looks more flexible IMO.

Also, I’m not sure if I would use the 721 label, mainly because this implementation doesn’t look 100% compatible. It’s fine to have a different, custom NFT implementation though.

Final comment, I think that this approach only allows to create NFTs as completely independent tokens. However, it may be interesting to have a way to “group” tokens, for instance by the target application or the type of asset they represent. I think in ethereum this is natural because you can track a “family” of tokens directly from the factory contract. Maybe the minting policy can be used for something like this in Cardano?

2 Likes

Hey, thanks for the reply!

There are definitely some concerns about IPFS, but it is still a good solution to keep everything decentralized. Most NFTs on Ethereum link their images/metadata with IPFS. There is no incentive to keep the nodes hosted, but if the file gets distributed in the network it’s hard to get it off. Even if the file is not available anymore, you have a unique IPFS hash. So in case you have the image offline you can upload it again and it’s available again. With a centralized solution via http/https it’s not possible, because you can’t prove if the file behind a link is staying the same.
However the intention of the CIP is another, but maybe there are better solutions than IPFS. If you have one, let me know.

Yes with the image property I mean a direct link to an image (.png, .jpg, .gif, etc.). Most NFTs represent ownership of digital art. If I make it flexible, like you mention, with a json file it’s hard for wallets or other service provider to find the right information in order to display the NFT on their platform. That’s the reason, why I form this standard, so everyone uses the same approach and follows it.
I also added “<“other properties”>”, that’s where you can add any properties for your specific NFT. In my experience most NFTs have a name and an image. So that’s what a wallet for example will show, but a wallet won’t show e.g. the power property of an item {power: 5}. This is what your specific NFT platform will show then.

Also, I’m not sure if I would use the 721 label, mainly because this implementation doesn’t look 100% compatible. It’s fine to have a different, custom NFT implementation though.

I don’t know what you mean with that, especially with “doesn’t look 100% compatible”.
You can still create any token you want, but if no one follows specific rules, it will be a mess, since everyone creates tokens how he wants and no platform can display the tokens correctly, because there are hundred different implementations.

That’s not right at all, that you can only use that for independent tokens. This approach makes it even easier for groups of tokens. Let me explain:

cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft0
cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft1

These two tokens belong to the same group, but are two NFTs.
If I mint the tokens with the according metadata, my transaction will look something like this:

Tx tx1: 
    forge: {cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft0,
               cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft1}
    metadata: {
  "721": {
    "cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b": {
      "nft0": {
        "id": 0,
        "name": "NFT 0",
        "image": "ipfs://ipfs/<IPFS_HASH>",
      },
      "nft1": {
        "id": 1,
        "name": "NFT 1",
        "image": "ipfs://ipfs/<IPFS_HASH>",
      }
    }
  }
}

Minting another two NTFs of the same token group:

Tx tx2: 
    forge: {cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft2,
               cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft3}
    metadata: {
  "721": {
    "cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b": {
      "nft2": {
        "id": 2,
        "name": "NFT 2",
        "image": "ipfs://ipfs/<IPFS_HASH>",
      },
      "nft3": {
        "id": 3,
        "name": "NFT 3",
        "image": "ipfs://ipfs/<IPFS_HASH>",
      }
    }
  }
}

Now I can lookup the right metadata for the NFT I want by finding the root forge transaction. For nft0 I look up tx1 and for nft2 I look up tx2, but this doesn’t mean these tokens don’t belong to the same group. Their metadata is simply at a different place on the blockchain, but they still belong to each other, because they have the same policy.

1 Like

Hi Alessandro,
I like your approach. We have a proposal on Project Catalyst to develop a modular NFT framework that would allow anyone to mint NFTs and launch unique marketplaces. We would love to get your insight and collaborate if you’re interested.
https://cardano.ideascale.com/a/dtd/Comprehensive-NFT-Collaboration/334521-48088

1 Like

The proposal idea is great. I didn’t know that there are already so many proposals about NFTs. I think it is essential that we have a standard. The more people follow it the easier we can use NFTs on Cardano.
The metadata standard is in my opinion the most critical component. A collaboration sounds good, so we can easier spread this standard around. Just wondering if the there had been different approaches so far?

There are a ton of different NFT proposals, but most seem to focus on their individual marketplace. I think it’s great to build for niche use cases, but at this stage developing an interoperable NFT framework that others can quickly iterate on is crucial. We would love to have you help out, if you’re interested.

I mean that this proposal is not 100% compatible with ERC721, because I don’t think it covers all of the ERC721 interface (which we couldn’t achieve at the L1 level anyway because we’d need smart contracts). Also, since IOHK is working on a bridge for ERC tokens, and ERC721 may be included, using the 721 label for a different NFT implementation can be misleading. But this is just my opinion.

cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft0
cbc34df5cb851e6fe5035a438d534ffffc87af012f3ff2d4db94288b.nft1

These two tokens belong to the same group, but are two NFTs.

I think you are right :smile: I just thought that minting policies were somewhat restricted by the protocol, but after revisiting the docs it sounds more like they can be fully defined by users.

You are right. I think the confusion comes from the two type of native tokens:

  • UTXOma, multi-signature script based native tokens (FT or NFT), using very simple minting policies (for tokens or token bundles, with or without token locking feature), and the more complex
  • EUTXOma (Plutus Core’s smart contract) based native tokens, which can have normal monetary policy attached like ERC20 or ERC751, ERC1155 etc.

And I am with you. I would not bother /w forcing proper monetary policy in metadata, as metadata is attached only to address, that means it’s public key must be exposed to the public to be able to use it (I meant filtering out from the thousand or even hundred thousands tx’s metadata). But, who should own that keypair? And we can go down to the rabbit hole…

The metadata is attached to the forging transaction. So it is unique and can easily be looked up by just providing the token name and the policy. No need to expose any keys in order to filter out the right transaction. Check out this table:

CREATE TABLE public.ma_tx_mint (
    id bigint NOT NULL,
    policy public.hash28type NOT NULL,
    name public.asset32type NOT NULL,
    quantity public.int65type NOT NULL,
    tx_id bigint NOT NULL
);

it gives me back the correct tx_id by providing policy and name.
and then I can lookup the metadata with the tx_id:

CREATE TABLE public.tx_metadata (
    id bigint NOT NULL,
    key public.word64type NOT NULL,
    json jsonb,
    tx_id bigint NOT NULL,
    bytes bytea NOT NULL
);

So a wallet for example needs no extra information to fetch the right data for a token.

Well the idea is not to copy the ERC721 Token from Ethereum, I could choose another metadata label, but the idea would still be the same. I just chose it because it’s easy to remember.
So what makes the ERC721 token an NFT. It’s the fact that the each token is unique and you can get the metadata for each token. That’s the same idea I follow with my standard, but of course the interface is not the same, but the idea and functionality is.

You know that you are talking about billions of billions transactions in the not distant future, while metadata (the id the hash of the metadata to the tx body) are attached to transactions. While I said to addresses?
Because the authenticity of the metadata is based on for example HD keys, and that key (either a private or public) needs to be (to be precise) must be exposed to be sure of the authenticity of the metadata, which is required for proper (off chain) indexing that metadata. Because, anyone would be able to clone/counterfreight it and later put a little bit modified descendent metada to the chain, so how would you ensure its authenticity? Anyway, I am always prefer the Occam’s razorz methots to the complexity.

Sorry typing on tablet.

Well you can fake the metadata, that’s right, but what you can’t fake is the forging process with a certain policy. That’s why you attach the metadata to the forging transaction. Only the owner of the policy script can forge tokens. That’s how you can validate the real metadata. The one that is attached to the forging transaction, no one can fake that.

That is correct, but how the policy could be validated (computationally cheap) by the owner (not tje creator but the holder) or other wallets?
The validation should, would say must, be on chain and not by the hand of some central entity like the token controller. As anybody can say anything. ‘Do not trust but verify’ modified Russian proverb

That is why the I think the normal plutus core based monetary policies should be used. But, ttl (as I have not read your proposal), there could be some use cases where your solution would fit. But, it must be audited at least by cryptographers at the minimum.

If we make this the standard everyone knows how to find the right metadata for their tokens. People just need to agree on it (same like for ERC721). The link is fix then. It’s super easy to implement functions where you just add as argument policy and name, then the above process happens with the DB lookup.

But for plutus based tokens you do not need to do anything just an utxo with a validator and a data script, everything is done on chain.

Even as am example, when you try to forge more than in the policy the chain would not allow it.

So you mean adding for each token a data script? But doesn’t that make it more expensive moving the token around?

Sorry, I meant hash of the policy, like policy id, similar to the current utxo