Application Specific NFTs on Cosmos

by All in Bits (Tendermint Inc)

Billy Rennekamp
Interchain Ecosystem Blog

--

Note: The Cosmos ecosystem is constantly growing and expanding. This article’s content is now outdated. For the latest content, see https://cosmos.network/.

If you’d like to jump straight to the good stuff check out the spec here , the code here and a sample app using the module here.

NOTE: This is beta software and is not considered production-ready. The module is subject to updates as there are still minor performance improvement work needed before this module is ready for primetime. Open-source contributions are welcome.

Application Specific Blockchains

There are a lot of reasons why an application might need its own blockchain but most of them boil down to the question of sovereignty. Every application has its own features and requirements — speed, security, cost, privacy— while blockchains like Ethereum, EOS or Polkadot require all their apps to share resources even if they have conflicting needs. When a conflict becomes contentious, a fork can occur. This happened to Ethereum when a single application (The DAO) was compromised and the wider community couldn’t agree on a unified plan to respond. If The DAO had been its own chain, it would’ve had the sovereign right to decide its own outcome. Instead all those interested in Ethereum maintaining the “code-is-law” concept forked into Ethereum Classic — I seems that technically ETH forked from ETC… but that’s ancient history 📜

Application Specific Blockchains come with their own problems and overhead or course. For example a certain degree of composability is lost by not having direct access to the state of other applications. Instead, Application Specific Blockchains might use Inter-Blockchain Communication (IBC) to send messages and queries between chains that rely on light client proofs to verify the information (Cosmos is a blochchain that will act as a hub to route IBC traffic). This article isn’t meant to debate whether an Application Specific Blockchain is right for you. Rather, assuming an app warrants its own chain, what might Non-Fungible Tokens look like in that setting?

Non-Fungible Things

Non-Fungible Tokens (or Non-Fungible Things as Sunny Aggarwal has conceded to call them) are essentially inventory lists. The concept might have originated with CryptoPunks or with an early version of Clovers Network, but it was CryptoKitties which established the first standard called ERC-721. This allowed for an extreme variety of use cases because the standard was as un-opinionated as possible as to exactly how the standard was used. That’s why we’ve been able to see such a wide range of uses from games and art to invoices and domain names. That’s why at it’s core a Non-Fungible Token only cares about the unique identification number and the token’s place of origin (which makes them essentially inventory lists).

This concept was core to the creation of an NFT Module within the Cosmos-SDK. For those not familiar with the SDK, it’s like ruby-on-rails for building scalable and interconnected blockchains. While there are many ways to build Application Specific Blockchains, Tendermint Inc. hopes to make the Cosmos-SDK the best option out there. Part of that goal is making reusable modules that take care of common use-cases on blockchains. For example the notion of a token is taken care of by the Bank Module, Liquid Democracy is handled by the Gov Module and securing the network with Proof-Of-Stake is handled by the Staking Module. These modules can be modified and integrated into what ever use case is needed on a chain-by-chain basis. The goal of the NFT Module is to allow any chain to re-use code related to Non-Fungible Tokens so that apps and wallets can use the same process for generating messages and querying state no matter which NFT blockchain they’re dealing with.

What’s Included?

Types

The NFT Module is built around the BaseNFT type. This contains the essential information relative to an NFT.

type BaseNFT struct {
ID string
Owner sdk.AccAddress
TokenURI string
}

There is also an NFT interface which allows the struct to be extended to include other information that might be deemed desirable to be stored inside the NFT Module. At the moment this is restricted to the tokenURI to make it backwards compatible with ERC-721. However you could put as little or as much as you wanted. There is also discussion about a Metadata Module which would have the dedicated purpose storing and managing any necessary on-chain metadata.

type NFT interface {
GetID() string
GetOwner() sdk.AccAddress
SetOwner(address sdk.AccAddress)
GetTokenURI() string
EditMetadata(tokenURI string)
String() string
}

Since there is no contract address to designate the origin of the NFT a denomination name is used (referred to as denom). This allows for many different collections of NFTs to live on the same chain and takes care of the aspect of origin I mentioned earlier. You may notice denom is missing in the NFT interface and BaseNFT struct. That’s because it is stored in the Collection type which contains all the underlying NFTs.

type Collection struct {
Denom string
NFTs NFTs
}
type NFTs []NFT

Messages

The NFT Module comes with four message types that are common among NFTs. The exact use or inclusion of these messages will differ from application to application so they come with a process for tailor fitting. There are also a number of Queriers, which are basically read only messages, that can be expected to be useful across use-cases.

MsgTransferNFT

type MsgTransferNFT struct {
Sender sdk.AccAddress
Recipient sdk.AccAddress
Denom string
ID string
}

This is the most commonly expected Msg type to be supported across chains. While each application specific blockchain will have very different adoption of the MsgMintNFT, MsgBurnNFT and MsgEditNFTMetadata it should be expected that most chains support the ability to transfer ownership of the NFT asset. The exception to this would be non-transferable NFTs that might be attached to reputation or some asset which should not be transferable. 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-transferable. This Message will fail if the NFT does not exist. By default it will not fail if the transfer is executed by someone beside the owner. It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.

MsgEditNFTMetadata

type MsgEditNFTMetadata struct {
Sender sdk.AccAddress
ID string
Denom string
TokenURI string
}

This message type allows the TokenURI to be updated. By default anyone can execute this Message type. It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.

MsgMintNFT

type MsgMintNFT struct {
Sender sdk.AccAddress
Recipient sdk.AccAddress
ID string
Denom string
TokenURI string
}

This message type is used for minting new tokens. 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's IDCollection. By default anyone can execute this Message type. It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.

MsgBurnNFT

type MsgBurnNFT struct {
Sender sdk.AccAddress
ID string
Denom string
}

This message type is used for burning tokens which destroys and deletes them. By default anyone can execute this Message type. It is highly recommended that a custom handler is made to restrict use of this Message type to prevent unintended use.

Unintended Use

Each of these Message Types come with very little restriction on who can execute them. That’s why it is “highly recommended that a custom handler” dot dot dot. What that means is that each Application Specific Blockchain should write a custom handler that will handle the messages upon receipt and decide what to do with them. This is where custom logic can be applied like, “only the owner of an NFT can transfer it” or “only players with a score over 100 can mint new NFTs”. Once those requirements are met, the original handler can be used to carry out the rest of the operation. This saves time for engineers who don’t have to re-build the whole module to cater to their specific use. An example of this can be seen in the sample app here with the custom handler seen here that makes sure the UTC time is during twilight before allowing an NFT to be minted.

func CustomHandler(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()
}

Next Steps

Next steps are up to you! Take a look at the code, try out the Cosmos-SDK tutorial to build your first Application Specific Blockchain, then try building one that uses NFTs! You’ll be able to make your own decisions about how the resources of your blockchain are used to accommodate your own specific use case. The sky is the limit 🚀

The views and details expressed in this blog post are those of All In Bits Inc (dba Tendermint Inc), and do not necessarily represent the opinions or actions of the Interchain Foundation.

--

--