Toniq-Labs/extendable-token

Allow extended metadata for NFTs for easy wallet/client integration

Opened this issue · 9 comments

Hi, thank you for your high quality standard, I would like to propose and extension to get extended NFT metadata.

Right now, if we have a browser wallet plugin, we are unable to uniformly retrieve the content-type of the NFT that is written based on your standard. That means our wallet plugin would need to know about the actual contract to know the NFT content-type or query some aggregation service. Also based on your specification, someone currently cannot uniformly implement an NFT that might return a jpeg for some TokenId while returning a mpeg for some other TokenId.

The second problem for us is that NFTs based on your standard doesn't provide a uniform location specification where the NFT binary/blob is located. People might store their NFT in the token contract directly, or in an other canister, IPFS, or on web. For clients like a wallet it would be good to be able to uniformly retrieve the NFT binary.

The suggested extension:

type ExtendedMetadata = {
  #fungible : {
    name : Text;
    symbol : Text;
    decimals : Nat8;
    metadata : ?Blob;
  };
  #nonfungible : {
    content-type : Text; // some kind of mime type or any other standard
    location: IPFS|Canister|TokenContract|....
    metadata : ?Blob;
  };
};

for example if the location is IPFS, then the metadata should be an IPFS hash, if it is Canister then maybe a canister Principal.... We can discuss this in details, if people are happy with this in Principal.

type ExtendedMetadata = actor {
  extended_metadata: shared query (token : TokenIdentifier) -> async Result<ExtendedMetadata, CommonError>;

};

Thanks - yeah we are trying to figure out the best way to handle this. We have a few ideas:

  1. The idea you pose is good, but we want go even further and ensure we can handle any type of NFTs that people want to load using EXT
  2. One idea we are looking at is serializing data as JSON and converting to a blob for storage. This means that any client can read it and decide how to handle the data. We could then standardize the JSON format as well. The problem is that currently there is no way to read JSON inside Motoko, making it hard to use inside canisters (e.g. genetics for Cronics requires reading the data in the canister)
  3. We found a library which allows us to convert any motoko value into a blob - this seems to be a good option, but makes it harder for wallets to interpret (but not really). It does require some funky stuff to compile tho (you have to compile using a moc flag to unlock the serde library).
  4. Finally, we are looking to just setup an array with key=>value pairs. This can be read by both canisters and clients, and is a fairly simple (and easy to follow) method. eg:
type MetadataKey = Text;
type MetadataValue = {
  #text : Text;
  #blob : Blob;
  #nat : Nat;
  #nat8: Nat8; //can add more
};
type MetadataElement = {
  #keypair : (MetadataKey , MetadataValue);
  #byte : Nat8;
};
type ExtendedMetadata = {
  #fungible : [MetadataElement ];
  #nonfungible : [MetadataElement];
};

For something like Cronics, we would do something like (to represent 32 bytes):

metadata = #nonfungible([#byte(102), #byte(23)...]);

For something like a fungible token:

metadata = #fungible([#keypair("name", #text("Name of token"))...]);

For something like your example

metadata = #nonfungible([#keypair("content-type", #text("image/png"))...]);

What do you think? I totally agree though, metadata needs to be redone

I like the idea! I am thinking about how we should accomodate Json, after someone writes a parser, how about the one below (field names like arrayMetadataElement probably need change)?

type MetadataKey = Text;
type MetadataValue = {
  #text : Text;
  #blob : Blob;
  #nat : Nat;
  #nat8: Nat8; //can add more
};
type MetadataElement = {
  #keypair : (MetadataKey , MetadataValue);
  #byte : Nat8;
};

type MetadataContainer = {
  #arrayMetadataElement : [MetadataElement];
  #json : Text;
};

type ExtendedMetadata = {
  #fungible : MetaContainer;
  #nonfungible : MetadataContainer;
};

I am happy with your original suggestion too. Do you guys have some timeline when it will be in the standard?

I think for us the main thing is, where we should expect the location/content-type fields in the metadata, I suppose that will also be standardized.

Yeah I think that addition makes sense too. We may alter the names of the types a bit more (bundle it in it's own module)

We are uploading the marketplace changes this weekend, and can include this update in the spec. I definitely agree that something more than what we have is required so I'll make changes to the interface over the next week and update the github.

great, thank you!

We are looking at going with the following:

  type Value = {
    #text : Text;
    #blob : Blob;
    #nat : Nat;
    #nat8: Nat8;
  };
  type MetadataValue = (Text , Value);
  type MetadataContainer = {
      #data : [MetadataValue];
      #blob : Blob;
      #json : Text;
  };
  public type Metadata = {
    #fungible : {
      name : Text;
      symbol : Text;
      decimals : Nat8;
      metadata: ?MetadataContainer;
    };
    #nonfungible : ?MetadataContainer;
  };

Rationale - we want to support existing platform that have already adopted part of the standard for their apps. We think this still provides a way to extend metadata for either NFTs of fungible tokens. Let me know your thoughts @ferencdg ?

looks good to me! Can we also put the field names and values for the most important fields like
content-type : Text; // some kind of mime type or any other standard
location: IPFS|Canister|TokenContract|....

Where exactly do you want this added?

maybe pairs under non-fungible->data and we could have
contentType key -> #text(let's say Mime type)
locationType -> #nat8 (and we would also specify which integer means IPFS/TokenContract/URI....
location -> #text (URI(for URI type)/IPFS hash(for IPFS type)/empty string(for TokenContract meaning that the image is stored in the token contract)
name -> #text (name of the NFT like ICPunk)
nftBlob -> #blob (the nft binary, if the locationType is TokenContract)

if we standardize those fields, then all wallets can treat them uniformly