Skip to main content

Multi Token Metadata

caution

This is part of the proposed spec NEP-245 and is subject to change.

Version 1.0.0

Summary​

An interface for a multi token's metadata. The goal is to keep the metadata future-proof as well as lightweight. This will be important to dApps needing additional information about multi token properties, and broadly compatible with other token standards such that the NEAR Rainbow Bridge can move tokens between chains.

Motivation​

The primary value of tokens comes from their metadata. While the core standard provides the minimum interface that can be considered a multi token, most artists, developers, and dApps will want to associate more data with each token, and will want a predictable way to interact with any MT's metadata.

NEAR's unique storage staking approach makes it feasible to store more data on-chain than other blockchains. This standard leverages this strength for common metadata attributes, and provides a standard way to link to additional offchain data to support rapid community experimentation.

This standard also provides a spec version. This makes it easy for consumers of Multi Tokens, such as marketplaces, to know if they support all the features of a given token.

Prior art:

Interface​

Metadata applies at both the class level (MTBaseTokenMetadata) and the specific instance level (MTTokenMetadata). The relevant metadata for each:


type MTContractMetadata = {
spec: string, // required, essentially a version like "mt-1.0.0"
name: string, // required Zoink's Digitial Sword Collection
}

type MTBaseTokenMetadata = {
name: string, // required, ex. "Silver Swords" or "Metaverse 3"
id: string, // required a unique identifier for the metadata
symbol: string|null, // required, ex. "MOCHI"
icon: string|null, // Data URL
decimals: string|null // number of decimals for the token useful for FT related tokens
base_uri: string|null, // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs
reference: string|null, // URL to a JSON file with more info
copies: number|null, // number of copies of this set of metadata in existence when token was minted.
reference_hash: string|null, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
}

type MTTokenMetadata = {
title: string|null, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055"
description: string|null, // free-form description
media: string|null, // URL to associated media, preferably to decentralized, content-addressed storage
media_hash: string|null, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included.
issued_at: string|null, // When token was issued or minted, Unix epoch in milliseconds
expires_at: string|null, // When token expires, Unix epoch in milliseconds
starts_at: string|null, // When token starts being valid, Unix epoch in milliseconds
updated_at: string|null, // When token was last updated, Unix epoch in milliseconds
extra: string|null, // Anything extra the MT wants to store on-chain. Can be stringified JSON.
reference: string|null, // URL to an off-chain JSON file with more info.
reference_hash: string|null // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included.
}

type MTTokenMetadataAll = {
base: MTBaseTokenMetadata
token: MTTokenMetadata
}

A new set of functions MUST be supported on the MT contract:

// Returns the top-level contract level metadtata
function mt_metadata_contract(): MTContractMetadata {}
function mt_metadata_token_all(token_ids: string[]): MTTokenMetadataAll[]
function mt_metadata_token_by_token_id(token_ids: string[]): MTTokenMetadata[]
function mt_metadata_base_by_token_id(token_ids: string[]): MTBaseTokenMetadata[]
function mt_metadata_base_by_metadata_id(base_metadata_ids: string[]): MTBaseTokenMetadata[]

A new attribute MUST be added to each Token struct:

 type Token = {
token_id: string,
+ token_metadata?: MTTokenMetadata,
+ base_metadata_id: string,
}

An implementing contract MUST include the following fields on-chain​

For MTContractMetadata:

  • spec: a string that MUST be formatted mt-1.0.0 to indicate that a Multi Token contract adheres to the current versions of this Metadata spec. This will allow consumers of the Multi Token to know if they support the features of a given contract.
  • name: the human-readable name of the contract.

An implementing contract must include the following fields on-chain​

For MTBaseTokenMetadata:

  • name: the human-readable name of the Token.
  • base_uri: Centralized gateway known to have reliable access to decentralized storage assets referenced by reference or media URLs. Can be used by other frontends for initial retrieval of assets, even if these frontends then replicate the data to their own decentralized nodes, which they are encouraged to do.

An implementing contract MAY include the following fields on-chain​

For MTBaseTokenMetadata:

  • symbol: the abbreviated symbol of the contract, like MOCHI or MV3
  • icon: a small image associated with this contract. Encouraged to be a data URL, to help consumers display it quickly while protecting user data. Recommendation: use optimized SVG, which can result in high-resolution images with only 100s of bytes of storage cost. (Note that these storage costs are incurred to the contract deployer, but that querying these icons is a very cheap & cacheable read operation for all consumers of the contract and the RPC nodes that serve the data.) Recommendation: create icons that will work well with both light-mode and dark-mode websites by either using middle-tone color schemes, or by embedding media queries in the SVG.
  • reference: a link to a valid JSON file containing various keys offering supplementary details on the token. Example: "/ipfs/QmdmQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7RgQm", etc. If the information given in this document conflicts with the on-chain attributes, the values in reference shall be considered the source of truth.
  • reference_hash: the base64-encoded sha256 hash of the JSON file contained in the reference field. This is to guard against off-chain tampering.
  • copies: The number of tokens with this set of metadata or media known to exist at time of minting. Supply is a more accurate current reflection.

For MTTokenMetadata:

  • title: The title of this specific token.
  • description: A longer description of the token.
  • media: URL to associated media. Preferably to decentralized, content-addressed storage.
  • media_hash: the base64-encoded sha256 hash of content referenced by the media field. This is to guard against off-chain tampering.
  • copies: The number of tokens with this set of metadata or media known to exist at time of minting.
  • issued_at: Unix epoch in milliseconds when token was issued or minted (an unsigned 32-bit integer would suffice until the year 2106)
  • expires_at: Unix epoch in milliseconds when token expires
  • starts_at: Unix epoch in milliseconds when token starts being valid
  • updated_at: Unix epoch in milliseconds when token was last updated
  • extra: anything extra the MT wants to store on-chain. Can be stringified JSON.
  • reference: URL to an off-chain JSON file with more info.
  • reference_hash: Base64-encoded sha256 hash of JSON from reference field. Required if reference is included.

For MTTokenMetadataAll :

  • base: The base metadata that corresponds to MTBaseTokenMetadata for the token.
  • token: The token specific metadata that corresponds to MTTokenMetadata.

No incurred cost for core MT behavior​

Contracts should be implemented in a way to avoid extra gas fees for serialization & deserialization of metadata for calls to mt_* methods other than mt_metadata* or mt_tokens. See near-contract-standards implementation using LazyOption as a reference example.

Drawbacks​

  • When this MT contract is created and initialized, the storage use per-token will be higher than an MT Core version. Frontends can account for this by adding extra deposit when minting. This could be done by padding with a reasonable amount, or by the frontend using the RPC call detailed here that gets genesis configuration and actually determine precisely how much deposit is needed.
  • Convention of icon being a data URL rather than a link to an HTTP endpoint that could contain privacy-violating code cannot be done on deploy or update of contract metadata, and must be done on the consumer/app side when displaying token data.
  • If on-chain icon uses a data URL or is not set but the document given by reference contains a privacy-violating icon URL, consumers & apps of this data should not naΓ―vely display the reference version, but should prefer the safe version. This is technically a violation of the "reference setting wins" policy described above.

Future possibilities​

  • Detailed conventions that may be enforced for versions.
  • A fleshed out schema for what the reference object should contain.