Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIP-0068 | Datum Metadata Standard #299

Merged
merged 9 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 241 additions & 0 deletions CIP-0068/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
---
CIP: 68
Title: Datum Metadata Standard
Authors: Alessandro Konrad <alessandro.konrad@live.de>, Thomas Vellekoop <thomas.vellekoop@iohk.io>
Comments-URI:
Status: Proposed
Type: Informational
Created: 2022-07-13
Post-History:
License: CC-BY-4.0
---

## Abstract

This proposal defines a metadata standard for native assets making use of output datums not only for NFTs but any asset class.

## Motivation

This proposal addresses a few shortcomings of [CIP-0025](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0025):

- Lack of programmability;
- Difficult metadata update / evolution;
- Non-inspectable metadata from within Plutus validators...

Besides these shortcomings CIP-0025 has some [flaws](https://github.com/cardano-foundation/CIPs/pull/85#issuecomment-1054123645) in its design.
For people unaware of CIP-0025 or want to use a different way of minting or want to use a different metadata format/mechanism you open up a protocol to metadata spoofing, because this standard is so established and metadata in minting transactions are interpreted by most platforms by default. Since this standard is not enforced at the protocol level there is no guarantee everyone will be aware of it or follow the rules. At the same time you limit and constraint the capabilities of the ledger if everyone was forced to follow the rules of CIP-0025.

This standard tackles all these problems and offers many more advantages, not only for NFTs, but also for any asset class that may follow. Additionally, this CIP will introduce a way to classify tokens so that third parties like wallets can easily know what the kind of token it is.


## Specification

### Considerations

The basic idea is to have two assets issued, where one references the other. We call these two a `reference NFT` and an `user token`, where the `user token` can be an NFT, FT or any other asset class that is transferable and represents any value. So, the `user token` is the actual asset that lives in a user's wallet.

To find the metadata for the `user token` you need to look for the output, where the `reference NFT` is locked in. How this is done concretely will become clear below. Moreover, this output contains a datum, which holds the metadata. The advantage of this approach is that the issuer of the assets can decide how the transaction output with the `reference NFT` is locked and further handled. If the issuer wants complete immutable metadata, the `reference NFT` can be locked at the address of an unspendable script. Similarly, if the issuer wants the NFTs/FTs to evolve or wants a mechanism to update the metadata, the `reference NFT` can be locked at the address of a script with arbitrary logic that the issuer decides.

Lastly and most importantly, with this construction, the metadata can be used by a PlutusV2 script with the use of reference inputs [CIP-0031](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0031). This will drive further innovation in the token space.

### Reference NFT label

This is the registered `asset_name_label` value

| asset_name_label | class | description |
| --------------------------- | ------------ | --------------------------------------------------------- |
| 100 | NFT | Reference NFT locked at a script containing the datum |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table comes a bit out of nowhere at this stage. There has been no mention of the asset name thus far. I feel like the concept / specification should be introduced earlier and that registry information like this should be left in annex / at the of the specification.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true because it's part of CIP-0067 which was proposed at the same time as this CIP. It's more of placeholder for now, but gives a better understanding of the idea.


### Constraints and conditions

For a correct relationship between the `user token` and the `reference NFT` a few conditions **must** be met.
- The `user token` and `reference NFT` **must** be under the same policy ID.
- For a specific `user token` there **must** exist exactly **one** `reference NFT`
- The `user token` and associated `reference NFT` **must** follow the standard naming pattern. The asset name of both assets is prefixed with its respective `asset_name_label` followed by a pattern defined by the asset class (e.g. asset_name_label 222)

Some remarks about the above,
1. The `user token` and `reference NFT` do not need to be minted in the same transaction. The order of minting is also not important.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a clever approach that also doesn't depend on any logging standard.

One way you could get around some of the restrictions with your approach is to instead having some meta-NFT for the project that is used in all the transactions that create the reference NFTs and the user tokens. This would avoid the asset-name stuffing issue your CIP introduces and this meta-NFT could be kept after the initial mint is done to add other reference NFT CIPs for the collection in the future without having to keep the policy used for the user tokens open. This is of course significantly more complicated than your approach though

Copy link
Contributor

@perturbing perturbing Jul 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback. The asset_name_label is primarily used for classifying the different kinds of tokens, as described in our other CIP 0067. From only the name, wallets might not know how to display/handle the token. They can leverage this classification, with many tokens it can be quite the task for 3rd parties.

About your idea on keeping the minting policy open for future standards, we thought the same. Have a look at the following construction visualized by Alessandro,
Standard_1
,

Besides this, we thought it would be a good idea for pre-determined collections to use a Merkle root in the minting policy to commit to what can be minted, this allows for decentralized minting as well. If the collection is open, say for a game another route need to be used, of course.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more something similar to what NMKR is doing with their DID proposal (#294) except instead of an open minting policy to store multiple DIDs, the open minting policy is what is connected to other minting scripts

In other words, the minting policy script in the your image servers two purposes: minting the assets and connecting to the reference scripts. This could be split up into two different scripts similar to the diagram in the DID proposal

2. It may be the case that there can be multiple `user tokens` (multiple asset names or quantity greater than 1) referencing the same `reference NFT`.

The datum in the output with the `reference NFT` contains the metadata at the first field of the constructor 0. The version number is at the second field of this constructor:
```
big_int = int / big_uint / big_nint
big_uint = #6.2(bounded_bytes)
big_nint = #6.3(bounded_bytes)

metadata =
{ * metadata => metadata }
/ [ * metadata ]
/ big_int
/ bounded_bytes

version = int

datum = #6.121([metadata, version])
Copy link
Member

@KtorZ KtorZ Aug 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: while reviewing another CIP that proposes an extension to CIP-0025, it occurred to me that CIP-0025 does not currently provide a nice extension mechanism. While there's now a version number, it forces every version increment to support all the newly introduce 'features'. I'd hope CIP-0068 to resolve that short-coming.

I'd suggest a restricted set of core features and a flexible extension mechanisms which allows people to extend the standard to new use-cases while letting downstream consumers chose which extensions they intend to support. This could very much be CIP-driven, for example an extra field: extensions or plugins listing all the extensions used by metadata. A consumer service processing the metadata and not being aware of one of the required extension could therefore stop and error instead of showing potentially wrong metadata.
Alternatively, each extensions could be marked as "required" or not (in the same spirit as it is done for vocabularies on JSON-schemas); as some extensions may only provide additional details (like, say, extra optional information) but ignoring them wouldn't affect the core interpretation of the metadata.

Copy link
Contributor

@perturbing perturbing Aug 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KtorZ, thank you for reviewing this CIP.

I'd hope CIP-0068 to resolve that short-coming.

I agree, and I think it does resolve these. Note that this CIP keeps things flexible with CIP 67 (1). The goal was to create a flexible framework for all kind of tokens, not just NFT's. I think the main why reason this is achieved, is because data is spread out and localized.

@alessandrokonrad made the following diagram that shows how flexible this standard could be.
Standard_1

Here there is one main minting policy script which can mint the main token. These could be FT's, NFT, fractionalized NFT, or any other token standard that might come up. Each standard is marked with (xxx) (though the delimiter is arbitrary at this point). Separate to this minting policy script, there is a registry which can introduce new standards under the main minting policy, in the example a royalty standard is made. Other standards/extensions can easily be introduced and made available under the main policy ID via the registry.

One standard to rule them all!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could very much be CIP-driven, for example an extra field: extensions or plugins listing all the extensions used by metadata.

That's a good idea. This would make the version field redundant/not necessary right?
One thing to note is that the asset name labels allow you to also define other NFT types with different metadata structures. For instance the 222 NFT Metadata Standard is mostly about "Collectibles" or media NFTs. I think extensions here should be suitable, otherwise it may make sense to introduce a new asset name label.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KtorZ, thank you for reviewing this CIP.

I am only starting now actually (well, ~20 minutes ago). This remark was more of a memo but I admittedly hadn't read the CIP in full when I wrote it 2 hours ago.

This would make the version field redundant/not necessary right?

I think so. Provided that this proposal reaches a final state and that any modification / additions is done exclusively through extensions.

Separate to this minting policy script, there is a registry which can introduce new standards under the main minting policy

So, if I understand the idea correctly, the "registry" is a user-specified subset of an agreed upon set of standard referenced in CIP-0067. This would therefore acts as this kind of extension mechanism that I am suggesting, right? Or do I get this completely wrong and simply am reading what I would want to be reading :)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if I understand the idea correctly, the "registry" is a user-specified subset of an agreed upon set of standard referenced in #298. This would therefore acts as this kind of extension mechanism that I am suggesting, right? Or do I get this completely wrong and simply am reading what I would want to be reading :)?

Yes that is correct. The question is just do we need for every little change/addition in the metadata an entire new asset name label standard. I think a combination of your idea with extensions within the same standard and having asset name labels to define new structures and token types is the best.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KtorZ how would you continue with this extension idea? Do you think it's overkill when we already have CIP-0067 to register token types with different metadata formats? Wouldn't the extensions need their own standard/registry?

```

## 222 NFT Standard

Besides the necessary standard for the `reference NFT` we're introducing two specific token standards in this CIP. Note that the possibilities are endless here and more standards can be built on top of this CIP for FTs, other NFTs, semi fungible tokens, etc. The first is the `222` NFT standard with the registered `asset_name_label` prefix value

| asset_name_label | class | description |
| --------------------------- | ------------ | -------------------------------------------------------------------- |
| 222 | NFT | NFT hold by the user's wallet making use of CIP-0025 inner structure |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't that:

  • Be added to the CIP-0067's registry
  • Be a separate proposal which piggybacks on this current proposal's structure? This would make the NFT standard more self-describing and have two immediate side-benefits (at least, obvious ones I see):
    • It'll make it more consistent for software down the lines to deal with such datum metadata. Right now, it feels that this asset class 222 is promoted to a blessed, implicit asset class, possibly omitted from the side registry. Having it part of the registry makes it more systematic to process by consumers down the line.
    • Moreover, this may keep the standard even more open as it would allow to define other kind of NFT standard (or simply, iterate on the existing one). It may help resolve some of the disputes regarding the adoption of this this particular NFT standard.

?



### Class

The `user token` represents an NFT (non-fungible token).

### Pattern

The `user token` and `reference NFT` **must** have an identical name, preceded by the `asset_name_label` prefix.

Example:\
`user token`: `(222)Test123`\
`reference NFT`: `(100)Test123`


### Metadata

This is a low-level representation of the metadata, following closely the structure of CIP-0025. All UTF-8 encoded keys and values need to be converted into their respective byte's representation when creating the datum on-chain.

```
files_details =
{
? name : bounded_bytes, ; UTF-8
mediaType : bounded_bytes, ; UTF-8
src : bounded_bytes ; UTF-8
}

metadata =
{
name : bounded_bytes, ; UTF-8
image : bounded_bytes, ; UTF-8
? mediaType : bounded_bytes, ; UTF-8
? description : bounded_bytes, ; UTF-8
? files : [* files_details]
}

datum = #6.121([metadata, 1]) ; version 1
```
Example datum as JSON:
```json
{"constructor" : 0, "fields": [{"map": [{"k": "6E616D65", "v": "5370616365427564"}, {"k": "696D616765", "v": "697066733A2F2F74657374"}]}, {"int": 1}]}
```

### Retrieve metadata as 3rd party

A third party has the following NFT `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(222)TestToken` they want to lookup. The steps are

1. Construct `reference NFT` from `user token`: `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(100)TestToken`
2. Look up `reference NFT` and find the output it's locked in.
3. Get the datum from the output and lookup metadata by going into the first field of constructor 0.
4. Convert to JSON and encode all string entries to UTF-8 if possible, otherwise leave them in hex.

### Retrieve metadata from a Plutus validator

We want to bring the metadata of the NFT `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(222)TestToken` in the Plutus validator context. To do this we

1. Construct `reference NFT` from `user token`: `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(100)TestToken` (off-chain)
2. Look up `reference NFT` and find the output it's locked in. (off-chain)
3. Reference the output in the transaction. (off-chain)
4. Verify validity of datum of the referenced output by checking if policy ID of `reference NFT` and `user token` and their asset names without the `asset_name_label` prefix match. (on-chain)

## 333 FT Standard

The second introduced standard is the `333` FT standard with the registered `asset_name_label` prefix value

| asset_name_label | class | description |
| --------------------------- | ------------ | -------------------------------------------------------------------- |
| 333 | FT | FT hold by the user's wallet making use of Cardano foundation off-chain registry inner structure |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remark as for the NFT class. I believe this should be a separate proposal / addition to the registry instead of a blessed / implicit one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes of course it will be added to the registry of CIP-0067. The table here is just helpful as extra information and pointer to the registry.



### Class

The `user token` is an FT (fungible token).


### Pattern

The `user token` and `reference NFT` **must** have an identical name, preceded by the `asset_name_label` prefix.

Example:\
`user token`: `(333)Test123`\
`reference NFT`: `(100)Test123`


### Metadata

This is a low-level representation of the metadata, following closely the structure of the Cardano foundation off-chain metadata registry. All UTF-8 encoded keys and values need to be converted into their respective byte's representation when creating the datum on-chain.

```
; Explanation here: https://developers.cardano.org/docs/native-tokens/token-registry/cardano-token-registry/

metadata =
{
name : bounded_bytes, ; UTF-8
description : bounded_bytes, ; UTF-8
? ticker: bounded_bytes, ; UTF-8
? url: bounded_bytes, ; UTF-8
? logo: bounded_bytes, ; UTF-8
? decimals: big_int
}

datum = #6.121([metadata, 1]) ; version 1
```
Example datum as JSON:
```json
{"constructor" : 0, "fields": [{"map": [{"k": "6E616D65", "v": "5370616365427564"}, {"k": "6465736372697074696F6E", "v": "54686973206973206D79207465737420746F6B656E"}]}, {"int": 1}]}
```

### Retrieve metadata as 3rd party

A third party has the following FT `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(333)TestToken` they want to lookup. The steps are

1. Construct `reference NFT` from `user token`: `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(100)TestToken`
2. Look up `reference NFT` and find the output it's locked in.
3. Get the datum from the output and lookup metadata by going into the first field of constructor 0.
4. Convert to JSON and encode all string entries to UTF-8 if possible, otherwise leave them in hex.

### Retrieve metadata from a Plutus validator

We want to bring the metadata of the FT `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(333)TestToken` in the Plutus validator context. To do this we

1. Construct `reference NFT` from `user token`: `d5e6bf0500378d4f0da4e8dde6becec7621cd8cbf5cbb9b87013d4cc.(100)TestToken` (off-chain)
2. Look up `reference NFT` and find the output it's locked in. (off-chain)
3. Reference the output in the transaction. (off-chain)
4. Verify validity of datum of the referenced output by checking if policy ID of `reference NFT` and `user token` and their asset names without the `asset_name_label` prefix match. (on-chain)

## Rationale
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section seems a bit thin. In particular, I'd love to see considerations such as:

  • Costs: how much does this would cost to on a prototypical scenario).
  • Limitations: how many tokens can one mint in a single transaction with this approach? This of course depends on many variables, but some order of magnitude would be nice (considering both transaction size and maximum execution budgets of underlying scripts). I can imagine this to get quite complex as we start composing many scripts.
  • Maybe, alternatives considered and abandoned, as well as the reason why they've been rejected?


Without seperation of `reference NFT` and `user token` you lose all flexibility and moving the `user token` would be quite cumbersome as you would need to add the metadata everytime to the new output where the `user token` is sent to. Hence you separate metadata and `user token` and lock the metadata inside another UTxO, so you can freely move the `user token` around.

In order to reference the correct UTxO containing the metadata, it needs to be authenticated, otherwise metadata spoofing attacks become possible. One way to achieve that is by adding an NFT (`reference NFT`) to the UTxO. This NFT needs to under the same Policy ID as the `user token`, followed by an asset name pattern defined in the standard. This way you create a secure link between `reference NFT` and `user token` without the need for any extra data and you can make use of this off-chain and on-chain.

The security for the link is derived from the minting policy itself, so it's important to write the validator with the right constraints and rules since this CIP solely defines the interface to keep flexibility as high as possible.


## Backward Compatibility

To keep metadata compatibility with changes coming in the future, we introduce a `version` field in the datum.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is in principle meant for discussing backward-compatibility with existing, established standards. So in that case, that would be CIP-0025 & CIP-0010, to cover things such as what Blake mentioned regarding existing CIP-0010 labels.



## Path to Active

- Agree on a binary encoding for asset name labels in [CIP-0067](https://github.com/cardano-foundation/CIPs/pull/298).
- Get support for this CIP by wallets, explorers, tools, minting platforms and other 3rd parties.
- Minimal reference implementation making use of [Lucid](https://github.com/spacebudz/lucid) (off-chain), [PlutusTx](https://github.com/input-output-hk/plutus) (on-chain):
[Implementation](./ref_impl/)
- Open-source more practical implementations/projects which make use of this CIP.


## References

- CIP-0025: https://github.com/cardano-foundation/CIPs/blob/master/CIP-0025
- CIP-0031: https://github.com/cardano-foundation/CIPs/tree/master/CIP-0031

## Copyright

This CIP is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode).

18 changes: 18 additions & 0 deletions CIP-0068/ref_impl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Minimal implementation of CIP-0068

## Concept

The minting policy is a one-shot policy. Only single NFT (pair of `reference NFT` and `user token`) can be minted. When deciding to burn the NFT the whole pair must be burned, otherwise the validator will throw an error.
PlutusTx was used to create the [on-chain code](onchain.hs) and [Lucid](https://github.com/spacebudz/lucid) for the [off-chain part](offchain.ts). It can be run in [Deno](https://deno.land/) and with a few modifications also in [Node.js](https://nodejs.org/) and Browser.

## Api

- mintNFT(assetName : string, metadata: Metadata)
- burnNFT(assetName : string)
- viewNFT(assetName : string)

## Run

```
deno run -A offchain.ts
```
Loading