Skip to main content

Non-Fungible Assets

Overview

The Non-Fungible Token (NFT) pallet extends the Asset pallet, allowing users to create NFT collections, issue unique tokens, and redeem (i.e. burn) existing non-fungible tokens. Documentation for the NFT crate can be found here.

NFT Collection

All non-fungible tokens are linked to a unique NFT collection, which is tied to an Asset and user-defined metadata.

Creating an NFT Collection

To successfully create an NFT collection, the dispatchable create_nft_collection must be called, and the following conditions must be met:

  • If the asset already exists, it must be of type NonFungible, and the caller must have the appropriate permission for the asset. If the asset does not exist, create_nft_collection will also create one non-fungible asset using the values passed as nft_type.
  • This must be the first collection associated with the given AssetId (i.e. only one collection per Asset ID is allowed).
  • The number of metadata keys associated with the collection must be less than or equal to MaxNumberOfCollectionKeys.
  • All metadata keys must be registered before the collection is created. When using local metadata keys, you must call create_asset and register_asset_metadata_local_type before create_nft_collection.

Once the dispatchable succeeds, a unique ID is tied to the NFT collection, in addition to the Asset ID, which identifies the underlying asset details.

Issuing an NFT

After creating the collection, tokens can be issued with the issue_nft extrinsic. For an NFT to be issued successfully, the following conditions must hold:

  • An NFT collection associated with AssetId must exist.
  • The caller must have the appropriate permission for the Asset.
  • The portfolio_kind of the caller must be valid.
  • All metadata keys specified in the NFT collection must have a value set when issuing the token. Issuing a token with metadata keys not defined in the collection will fail.

Once the dispatchable succeeds, a unique non-fungible token is linked to the specified portfolio.

Redeeming an NFT

A non-fungible token can be redeemed by calling the redeem_nft dispatchable. To successfully redeem an NFT, the following conditions must hold:

  • An NFT collection associated with AssetId must exist.
  • The caller must have the appropriate permission for the Asset.
  • The nft_id of the token must exist in the caller's portfolio.

Once the dispatchable succeeds, the non-fungible token will no longer be linked to the caller's portfolio.

NFTs and the Settlement Pallet

Polymesh's Settlement engine fully supports NFTs, and transfers of non-fungible tokens follow the same process as fungible assets. This means that all compliance rules defined for the underlying asset must be respected for a successful transfer of an NFT.

Metadata Schema

NFTs can be associated with metadata at both the collection and individual NFT level. Each NFT collection can define local metadata keys for their collection.

There are also four globally defined metadata keys, intended to be used in a standardized way to allow third-party dApps to process metadata associated with NFTs.

Unlike other chains and standards (e.g., EIP-721), Polymesh encourages metadata to be stored directly on-chain for greater transparency, reduced reliance on external systems such as IPFS, and allowing direct reference by smart contract business logic.

In some cases, it may be preferable to store some metadata off-chain, for compatibility or other reasons.

The two standard approaches below for specifying a Token URI and an Image URI are optional but encouraged if storing this data off-chain.

Token URI

The Token URI of an NFT is intended to point to an off-chain URI containing a JSON blob defining metadata for the NFT.

The Token URI can be specified in various ways:

  • via the global metadata key tokenUri defined for an individual NFT
  • via the global metadata key baseTokenUri defined for the whole NFT collection

In both cases, the metadata value can include {tokenId}, which will be substituted by a client with the corresponding tokenId of the individual NFT.

The full process to determine the Token URI of an individual NFT is:

  • If the individual NFT has a tokenUri value set, return this.
  • Otherwise, if the NFT collection has a baseTokenUri value set, then:
    • If the baseTokenUri does not already include {tokenId}, append /{tokenId} (e.g., the resolved URI is {baseTokenUri}/{tokenId}).
    • Substitute any {tokenId} references with the underlying tokenId of the individual NFT, then return this.

The JSON blob referenced by an individual NFT's Token URI should conform to the JSON schema below:

{
"title": "Token Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset this token represents"
},
"description": {
"type": "string",
"description": "Describes the asset this token represents"
},
"properties": {
"type": "object",
"description": "Arbitrary properties. Values may be strings, numbers, objects, or arrays."
}
}
}

Image URI

You can also specify an image associated with an individual NFT by providing an Image URI, intended to point to an off-chain URI containing image data for the NFT.

The Image URI can be specified in various ways:

  • via the global metadata key imageUri defined for an individual NFT
  • via the global metadata key baseImageUri defined for the entire NFT collection

In both cases, the metadata value can include {tokenId}, which will be substituted by a client with the tokenId of the individual NFT.

The process to determine the Image URI of an individual NFT is:

  • If the individual NFT has an imageUri value set, return this.
  • Otherwise, if the NFT collection has a baseImageUri value set, then:
    • If the baseImageUri does not already include {tokenId}, append /{tokenId} (e.g., the resolved URI is {baseImageUri}/{tokenId}).
    • Substitute any {tokenId} references with the underlying tokenId of the individual NFT, then return this.

This logic matches the process described for the Token URI.

Notes
  • When using baseImageUri, it's highly recommended to include {tokenId} to ensure a file extension is included in the URI. Without a file extension, there may be ambiguity in how the image is handled, which could lead to display or processing issues.
  • The above specification allows an image to be specified via the Image URI or through an image tag in the JSON data linked by the Token URI. The recommended approach is to first look for a dedicated Image URI and, if not found, fall back to the image tag in the data stored at the Token URI.