# Cosmos NFT Module * [Abstract](#Abstract) * [Summary](#Summary) * [Types](#Types) * [BaseNFT](#BaseNFT) * [\*A Note on Metadata](#A-Note-on-Metadata) * [NFT Interface](#NFT-Interface) * [NFTs](#NFTs) * [Collection](#Collection) * [Collections](#Collections) * [Owner](#Owner) * [IDCollections](#IDCollections) * [IDCollection](#IDCollection) * [Msg Types](#Msg-Types) * [MsgTransferNFT](#MsgTransferNFT) * [MsgEditNFTMetadata](#MsgEditNFTMetadata) * [MsgMintNFT](#MsgMintNFT) * [MsgBurnNFT](#MsgBurnNFT) * [Handlers](#Handlers) * [Custom App-Specific Logic](#Custom-App-Specific-Logic) * [HandlerMsgTransferNFT](#HandleMsgTransferNFT]) * [HandleMsgEditNFTMetadata](#HandleMsgEditNFTMetadata) * [HandleMsgMintNFT](#HandleMsgMintNFT) * [HandleMsgBurnNFT](#HandleMsgBurnNFT) * [Queriers](#Queriers) * [QuerySupply](#QuerySupply) * [QueryOwner](#QueryOwner) * [QueryOwnerByDenom](#QueryOwnerByDenom) * [QueryCollection](#QueryCollection) * [QueryDenoms](#QueryDenoms) * [QueryNFT](#QueryNFT) * [Further Reading](#Further-Reading) ## Abstract This is a description of the implementation of the Cosmos-SDK NFT Module and some thoughts behind the design. ## Summary The Cosmos-SDK is a software development kit for building application specific blockchains. It includes common functionality between blockchains and provides standards that can be expected across these blockchains including common patterns. An example of this is the `MsgSend` message type which is used for transfering fungible tokens of designated denominations between two parties using the bank module. Another example is the `account` querier which is part of the Auth Module and gives information about an account regardless of what chain it is part of. The NFT Module described here is meant to be used as a module across chains for managing non-fungible token that represent individual assets with unique features. This standard was first developed on Ethereum within the ERC-721 and the subsequent EIP of the same name. This standard utilized the features of the Ethereum blockchain as well as the restrictions. The subsequent ERC-1155 standard addressed some of the restrictions of Ethereum regarding storage costs and semi-fungible assets. NFTs on application specific blockchains share some but not all features as their Ethereum brethren. Since application specific blockchains are more flexible in how their resources are utilized it makes sense that should have the option of exploiting those resources. This includes the aility to use strings as IDs and to optionally store metadata on chain. The user-flow of composability with smart contracts should also be rethought on application specific blockchains with regard to Inter-Blockchain Communication as it is a different design experience from communicatoin between smart contracts. ## Types ### BaseNFT & Metadata The BaseNFT type is a struct that is used by the NFT interface to allow flexibility within the standard. It includes the following data: ```go // BaseNFT non fungible token definition type BaseNFT struct { ID string `json:"id,omitempty"` // id of the token; not exported to clients Owner sdk.AccAddress `json:"owner"` // account address that owns the NFT Name string `json:"name"` // name of the token Description string `json:"description"` // unique description of the NFT Image string `json:"image"` // image path TokenURI string `json:"token_uri"` // optional extra properties available for querying } ``` #### \*A Note on Metadata & IBC The BaseNFT includes what was considered part of the off-chain metadata for the original ERC-721. These were the fields expected in the JSON object that was expected to be returned by resolvingn the URI found in the on-chain field `tokenURI`. You can see that `tokenURI` is also included here. This is to represent that it is possible to store this data on chain while allowing more data to be stored off chain. While this was the format chosen for the first version of the Cosmos NFT, it is under discussion to move all metadata to a separate module that can handle arbitrary amounts of data on chain and can be used to describe assets beyond Non-Fungible Tokens. A stand-alone metadata Module would allow for independent standards to evolve regarding arbitrary asset types with expanding precision. The standards supported by http://schema.org and the process of adding nested information is being considered as a starting point for that standard. With regard to Inter-Blockchain Communication the responsibility of the integrity of the metadata should be left to the origin chain. If a secondary chain was resopnsible for storing the source of truth of the metadata for an asset tracking that source of truth would become difficult if not impossible. Since origin chains are where the design and use of the NFT is determined, it should be up to that origin chain to decide who can update metadata and under what circumstances. Secondary chains can use IBC queriers to check needed metadata or keep redundant copies of the metadata locally. In that case it should be up to te secondary chain to keep the metadata in sync, similar to how layer 2 solutions keep metadata in sync with a source of truth using events. ### NFT Interface The NFT Interface inherits the BaseNFT struct and includes getter functions for the asset data. It also lincludes a Stringer function in order to print the struct. The interface may change if metadata is moved to it's own module as it might no longer be necessary for the flexibility of an interface. ```go // NFT non fungible token interface type NFT interface { GetID() string GetOwner() sdk.AccAddress SetOwner(address sdk.AccAddress) BaseNFT GetName() string GetDescription() string GetImage() string GetTokenURI() string EditMetadata(name, description, image, tokenURI string) BaseNFT String() string } var _ NFT = (*BaseNFT)(nil) ``` ### NFTs NFTs is an array of type NFT ```go // NFTs define a list of NFT type NFTs []NFT ``` ### Collection A Collection is used to organized sets of NFTs. It contains the denomination of the NFT instead of storing it within each NFT. This saves storage space by removing redundancy. ```go // Collection of non fungible tokens type Collection struct { Denom string `json:"denom,omitempty"` // name of the collection; not exported to clients NFTs NFTs `json:"nfts"` // NFTs that belong to a collection } ``` ### Collections Collections is a top level storage of all collections present on an applicationn specific blockchain. ```go // Collections define an array of Collection type Collections []Collection ``` ### Owner An Owner is a struct that includes information about all NFTs owned by a single account. It would be possible to retrieve this information by looping through all Collections but that process could become computationaly prohibitive so a more efficient retrieval system is to store redundant information limited to the token ID by owner. ```go // Owner of non fungible tokens type Owner struct { Address sdk.AccAddress `json:"address"` IDCollections IDCollections `json:"IDCollections"` } ``` ### IDCollections IDCollections is an array of type IDCollection. ```go // IDCollections is an array of ID Collections whose sole purpose is for find type IDCollections []IDCollection ``` ### IDCollection An IDCollection is similar to a `Collection` except instead of containing `NFTs` it only contains an array of NFT IDs. This saves storage by avoiding redundancy. ```go // IDCollection of non fungible tokens type IDCollection struct { Denom string `json:"denom"` IDs []string `json:"IDs"` } ``` ## Msg Types ### MsgTransferNFT This is the most commonly expected MsgType to be supported across chains. While each application specific blockchain will have vey different adoption of the `MsgMintNFT`, `MsgBurnNFT` and `MsgEditNFTMetadata` it should be expected that each chain supports the ability to transfer ownership of the NFT asset. Even if that transfer is heavily restricted it should be mostly supported. The exception to this would be non-transferrable NFTs that might be attached to reputation or some asset which should not be transferrable. It still makes sense for this to be represented as an NFT because there are common queriers which will remain relevant to the NFT type even if non-transferrable. | **Field** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | Sender | `sdk.AccAddress` | The account address of the user sending the NFT. It is required that the sender is also the owner of the NFT. | | Recipient | `sdk.AccAddress` | The account address who will receive the NFT as a result of the transfer transaction. | | Denom | `string` | The denomination of the NFT, necessary as multiple denominations are able to be represented on each chain. | | ID | `string` | The unique ID of the NFT being transferred | ```go // MsgTransferNFT defines a TransferNFT message type MsgTransferNFT struct { Sender sdk.AccAddress Recipient sdk.AccAddress Denom string ID string } ``` ### MsgEditNFTMetadata In the V1 of the NFT Module you've seen that some metadata is stored on chain along with the `TokenURI` that points to further metadata. This message type allows that specific metadata to be edited by the owner. | **Field** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | Owner | `sdk.AccAddress` | The owner of the NFT, which should also be the creator of the message | | ID | `string` | The unique ID of the NFT being edited | | Denom | `string` | The denomination of the NFT, necessary as multiple denominations are able to be represented on each chain. | | Name | `string` | The name of the Token| | Description | `string` | The description of the Token | | Image | `string` | The Image of the Token | | TokenURI| `string`| The URI pointing to a JSON object that contains subsequet metadata information off-chain | ```go // MsgEditNFTMetadata edits an NFT's metadata type MsgEditNFTMetadata struct { Owner sdk.AccAddress ID string Denom string Name string Description string Image string TokenURI string } ``` ### MsgMintNFT This message type is used for minting new tokens. Without a restriction from a custom handler anyone can mint a new `NFT`. If a new `NFT` is minted under a new `Denom`, a new `Collection` will also be created, otherwise the `NFT` is added to the existing `Collection`. If a new `NFT` is minted by a new account, a new `Owner` is created, otherwise the `NFT` `ID` is added to the existing `Owner`. | **Field** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | Sender | `sdk.AccAddress` | The sender of the Message | | Recipient | `sdk.AccAddress` | The recipiet of the new NFT | | ID | `string` | The unique ID of the NFT being minted | | Denom | `string` | The denomination of the NFT. | | Name | `string` | The name of the Token| | Description | `string` | The description of the Token | | Image | `string` | The Image of the Token | | TokenURI| `string`| The URI pointing to a JSON object that contains subsequet metadata information off-chain | ```go // MsgMintNFT defines a MintNFT message type MsgMintNFT struct { Sender sdk.AccAddress Recipient sdk.AccAddress ID string Denom string Name string Description string Image string TokenURI string } ``` ### MsgBurnNFT This message type is used for burning tokens which destroys and deletes them. Without a restriction from a custom handler only the owner can burn an `NFT`. | **Field** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | Sender | `sdk.AccAddress` | The account address of the user burning the token. | | ID | `string` | The ID of the Token. | | Denom | `string` | The Denom of the Token. | ```go // MsgBurnNFT defines a BurnNFT message type MsgBurnNFT struct { Sender sdk.AccAddress ID string Denom string } ``` ## Handlers ### Custom App-Specific Logic Each Message type comes with a default handler that can be used by default but will most likely be too limited for each use case. We recommend that custom handlers are created to add in custom logic and restrictions over when the Message types can be executed. Below is a recomended method for initializing the module within the module manager so that a custom handler can be added. This can be seen in the example NFT app https://github.com/okwme/cosmos-nft. ```go // custom-handler.go // OverrideNFTModule overrides the NFT module for custom handlers type OverrideNFTModule struct { nft.AppModule k nft.Keeper } // NewHandler module handler for the OerrideNFTModule func (am OverrideNFTModule) NewHandler() sdk.Handler { return CustomNFTHandler(am.k) } // NewOverrideNFTModule generates a new NFT Module func NewOverrideNFTModule(appModule nft.AppModule, keeper nft.Keeper) OverrideNFTModule { return OverrideNFTModule{ AppModule: appModule, k: keeper, } } ``` You can see here that `OverrideNFTModule` is the same as `nft.AppModule` except for the `NewHandler()` method. This method now returns a new Handler called `CustomNFTHandler`. This custom handler can be seen below: ```go // CustomNFTHandler routes the messages to the handlers func CustomNFTHandler(k keeper.Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { switch msg := msg.(type) { case types.MsgTransferNFT: return nft.HandleMsgTransferNFT(ctx, msg, k) case types.MsgEditNFTMetadata: return nft.HandleMsgEditNFTMetadata(ctx, msg, k) case types.MsgMintNFT: return HandleMsgMintNFTCustom(ctx, msg, k) case types.MsgBurnNFT: return nft.HandleMsgBurnNFT(ctx, msg, k) default: errMsg := fmt.Sprintf("unrecognized nft message type: %T", msg) return sdk.ErrUnknownRequest(errMsg).Result() } } } // HandleMsgMintNFTCustom handles MsgMintNFT func HandleMsgMintNFTCustom(ctx sdk.Context, msg types.MsgMintNFT, k keeper.Keeper, ) sdk.Result { isTwilight := checkTwilight(ctx) if isTwilight { return nft.HandleMsgMintNFT(ctx, msg, k) } errMsg := fmt.Sprintf("Can't mint astral bodies outside of twilight!") return sdk.ErrUnknownRequest(errMsg).Result() } ``` The default handlers are imported here with the NFT module and used for `MsgTransferNFT`, `MsgEditNFTMetadata` and `MsgBurnNFT`. The `MsgMintNFT` however is handled with a custom function called `HandleMsgMintNFTCustom`. This custom function also utilizes the imported NFT module handler `HandleMsgBurnNFT`, but only after certain conditions are checked. In this case it checks a function called `checkTwilight` which returns a boolean. Only if `isTwilight` is true will the Message succeed. This pattern of inheriting and utlizing the module handlers wrapped in custom logic should allow each application specific blockchain to use the NFT while customizing it to their specific requirements. ### HandleMsgTransferNFT The default handler for the `MsgTransferNFT` type allows anyone to transfer tokens from one user to another. If there should be restrictions in place about who can transfer and when, then it should be handled by a custom wrapper that if successful utilizes the default `HandleMsgTransferNFT`. ### HandleMsgEditNFTMetadata The default handler for the `MsgEditNFTMetadata` type allows anyone to edit Metadata on a token that already exxx. If there should be restrictions in place about who can transfer and when, then it should be handled by a custom wrapper that if successful utilizes the default `HandleMsgEditNFTMetadata`. ### HandleMsgMintNFT The default handler for the `MsgMintNFT` type allows anyone to mint new tokens. If there should be restrictions in place about who can transfer and when, then it should be handled by a custom wrapper that if successful utilizes the default `HandleMsgMintNFT`. ### HandleMsgBurnNFT The default handler for the `MsgBurnNFT` type allows anyone to burn a token that already exists. If there should be a restriction in place about who can transfer and when, then it should be handled by a custom wrapper that if successful utilizes the default `HandleMsgBurnNFT`. ## Queriers ### QuerySupply | **Param** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | Denom | `string` | The denomination of the NFT being queried | **Return** | **Type** | **Description** | | Supply | `uint64` | The amount of tokens that exist of that denomination | ### QueryOwner | **Param** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | owner | `sdk.AccAddress` | The account address of the owner in question | | **Return** | **Type** | **Description** | | owner | `Owner` | The Owner type that includes `IDCollections` that contain `Denom` and `IDCollection` of all `NFT` ids. | ### QueryOwnerByDenom | **Param** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | owner | `sdk.AccAddress` | The account address of the owner in question | | denom | `string` | The denomination to be used as a filter on the owner. Only NFTs with this denom will be included with the returned owner. | | **Return** | **Type** | **Description** | | owner | `Owner` | The Owner type that includes `IDCollections` of only the requested `Denom` and `IDCollection` of all `NFT` ids. | ### QueryCollection | **Param** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | Denom | `string` | The denomination of the Collectionn of NFTs to be retrieved | | **Return** | **Type** | **Description** | | collection | `Collection` | The Collection type that includes all `NFTs` and their relevant metadata. | ### QueryDenoms | **Param** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | - | - | - | | **Return** | **Type** | **Description** | | denoms | `[]string` | An array of all denominations of NFTs on this application specific blockchain | ### QueryNFT | **Param** | **Type** | **Description** | | :--------- | :-------- | :---------------------------- | | Denom | `string` | The denomination of the NFT in question | | ID | `string` | The ID of the NFT in question | | **Return** | **Type** | **Description** | | nft | `NFT` | Returnes the `NFT` type interface of the relevant denomination and ID | ## Further Reading * [cosmos/cosmos-sdk Open PR #4209](https://github.com/cosmos/cosmos-sdk/pull/4209) * [cosmos/cosmos-sdk Open Issue #4046](https://github.com/cosmos/cosmos-sdk/issues/4046) * [Binance Improvement Proposal 7 (NFT)](https://github.com/binance-chain/BEPs/pull/7) * [Ethereum Improvement Proposal 721 (NFT)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md)