变更记录

  • 2021-05-01: 初始草案
  • 2021-07-02: 评审更新
  • 2022-06-15: 添加批量操作
  • 2022-11-11: 移除对 classID 和 tokenID 的严格校验

状态

已提议

摘要

本 ADR 定义了 x/nft 模块,它是 NFT 的通用实现,与 ERC721 大致“兼容”。使用 x/nft 模块的应用必须实现以下函数:
  • MsgNewClass - 接收用户创建类的请求,并调用 x/nft 模块的 NewClass。
  • MsgUpdateClass - 接收用户更新类的请求,并调用 x/nft 模块的 UpdateClass。
  • MsgMintNFT - 接收用户铸造 nft 的请求,并调用 x/nft 模块的 MintNFT。
  • BurnNFT - 接收用户销毁 nft 的请求,并调用 x/nft 模块的 BurnNFT。
  • UpdateNFT - 接收用户更新 nft 的请求,并调用 x/nft 模块的 UpdateNFT。

背景

NFT 并不只是加密艺术,这对于 Cosmos 生态系统积累价值非常有帮助。因此,Cosmos Hub 应当实现 NFT 功能,并启用统一机制来存储和发送 NFT 所有权的代表信息,详见链接。 如 #9065 中所讨论,可以考虑若干潜在方案:
  • irismod/nft 和 modules/incubator/nft
  • CW721
  • DID NFTs
  • interNFT
由于 NFT 的功能和使用场景与其逻辑紧密相关,因此几乎不可能仅通过定义并实现不同的交易类型,在一个 Cosmos SDK 模块中支持所有 NFT 的使用场景。 考虑到包括 IBC 和 Gravity Bridge 在内的跨链协议的通用使用方式与兼容性,更适合采用一个处理通用 NFT 逻辑的通用 NFT 模块设计。 这一设计思路可以实现可组合性,即应用特定功能应通过导入 NFT 模块,由 Cosmos Hub 上的其他模块或其他 Zone 上的模块来管理。 当前设计基于 IRISnet team 已完成的工作,以及 Cosmos repository 中较早的实现。

决策

我们创建一个 x/nft 模块,它包含以下功能:
  • 存储 NFT 并跟踪其所有权。
  • 暴露 Keeper 接口,供组合模块转移、铸造和销毁 NFT。
  • 暴露外部 Message 接口,供用户转移其 NFT 的所有权。
  • 查询 NFT 及其供应信息。
提议中的模块是 NFT 应用逻辑的基础模块。它的目标是为存储、基础转移功能和 IBC 提供一个通用层。该模块不应作为独立模块使用。 相反,应用应创建一个专用模块来处理应用特定逻辑(例如:NFT ID 构造、版税)、面向用户的铸造和销毁。此外,应用专用模块还应处理用于支持应用逻辑的辅助数据(例如索引、ORM、业务数据)。 所有通过 IBC 传输的数据都必须是下文描述的 NFT 或 Class 类型的一部分。应用特定的 NFT 数据应编码在 NFT.data 中,以保证跨链完整性。与 NFT 相关但对完整性不重要的其他对象,可以属于应用专用模块。

类型

我们提出两种主要类型:
  • Class — 描述 NFT 类。可以将其理解为智能合约地址。
  • NFT — 表示唯一、不可替代资产的对象。每个 NFT 都关联一个 Class。

Class

NFT Class 可类比于一个 ERC-721 智能合约(提供智能合约的描述),在其之下可以创建和管理一组 NFT。
message Class {
  string id          = 1;
  string name        = 2;
  string symbol      = 3;
  string description = 4;
  string uri         = 5;
  string uri_hash    = 6;
  google.protobuf.Any data = 7;
}
  • id 用作存储该类的主索引;必填
  • name 是 NFT 类的描述性名称;可选
  • symbol 是 NFT 类通常在交易所显示的符号;可选
  • description 是 NFT 类的详细描述;可选
  • uri 是链下存储的类元数据 URI。它应当是一个 JSON 文件,包含 NFT 类及 NFT 数据模式的元数据(OpenSea 示例);可选
  • uri_hash 是 uri 指向文档的哈希;可选
  • data 是该类的应用特定元数据;可选

NFT

我们将 NFT 的通用模型定义如下。
message NFT {
  string class_id           = 1;
  string id                 = 2;
  string uri                = 3;
  string uri_hash           = 4;
  google.protobuf.Any data  = 10;
}
  • class_id 是 NFT 所属 NFT 类的标识符;必填
  • id 是 NFT 的标识符,在其类的范围内唯一。它由 NFT 的创建者指定,未来可能扩展为使用 DID。class_id 与 id 组合后可唯一标识一个 NFT,并用作存储该 NFT 的主索引;必填
    {class_id}/{id} --> NFT (bytes)
    
  • uri 是链下存储的 NFT 元数据 URI。应指向一个包含该 NFT 元数据的 JSON 文件(参考:ERC721 标准和 OpenSea 扩展);必填
  • uri_hash 是 uri 指向文档的哈希;可选
  • data 是 NFT 的应用特定数据。组合模块可以用它来指定 NFT 的附加属性;可选
本 ADR 没有规定 data 可以采用哪些值;不过,最佳实践建议上层 NFT 模块应明确说明其内容。虽然该字段的值并不能提供管理 NFT 记录所需的额外上下文,这意味着从技术上讲该字段可以从规范中移除,但该字段的存在使基础的信息展示/UI 功能成为可能。

Keeper 接口

type Keeper interface {
    NewClass(ctx sdk.Context,class Class)

UpdateClass(ctx sdk.Context,class Class)

Mint(ctx sdk.Context,nft NFT,receiver sdk.AccAddress)   // updates totalSupply
  BatchMint(ctx sdk.Context, tokens []NFT,receiver sdk.AccAddress)

error

  Burn(ctx sdk.Context, classId string, nftId string)    // updates totalSupply
  BatchBurn(ctx sdk.Context, classID string, nftIDs []string)

error

  Update(ctx sdk.Context, nft NFT)

BatchUpdate(ctx sdk.Context, tokens []NFT)

error

  Transfer(ctx sdk.Context, classId string, nftId string, receiver sdk.AccAddress)

BatchTransfer(ctx sdk.Context, classID string, nftIDs []string, receiver sdk.AccAddress)

error

  GetClass(ctx sdk.Context, classId string)

Class
  GetClasses(ctx sdk.Context) []Class

  GetNFT(ctx sdk.Context, classId string, nftId string)

NFT
  GetNFTsOfClassByOwner(ctx sdk.Context, classId string, owner sdk.AccAddress) []NFT
  GetNFTsOfClass(ctx sdk.Context, classId string) []NFT

  GetOwner(ctx sdk.Context, classId string, nftId string)

sdk.AccAddress
  GetBalance(ctx sdk.Context, classId string, owner sdk.AccAddress)

uint64
  GetTotalSupply(ctx sdk.Context, classId string)

uint64
}
其他业务逻辑实现应定义在导入 x/nft 并使用其 Keeper 的组合模块中。

Msg 服务

service Msg {
  rpc Send(MsgSend)         returns (MsgSendResponse);
}

message MsgSend {
  string class_id = 1;
  string id       = 2;
  string sender   = 3;
  string reveiver = 4;
}
message MsgSendResponse {}
MsgSend 可用于将 NFT 的所有权转移到另一个地址。 服务端的实现概要如下:
type msgServer struct{
    k Keeper
}

func (m msgServer)

Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) {
  // 检查当前所有权
  assertEqual(msg.Sender, m.k.GetOwner(msg.ClassId, msg.Id))

  // 转移所有权
  m.k.Transfer(msg.ClassId, msg.Id, msg.Receiver)

return &types.MsgSendResponse{
}, nil
}
x/nft 模块的查询服务方法如下:
service Query {
  // Balance 查询 owner 拥有的指定类 NFT 数量,与 ERC721 中的 balanceOf 相同
  rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/balance/{owner}/{class_id}";
  }

  // Owner 根据类和 id 查询 NFT 的所有者,与 ERC721 中的 ownerOf 相同
  rpc Owner(QueryOwnerRequest) returns (QueryOwnerResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/owner/{class_id}/{id}";
  }

  // Supply 查询给定类的 NFT 数量,与 ERC721 的 totalSupply 相同。
  rpc Supply(QuerySupplyRequest) returns (QuerySupplyResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/supply/{class_id}";
  }

  // NFTs 查询给定类或所有者的全部 NFT,二者至少选择其一,类似 ERC721Enumerable 中的 tokenByIndex
  rpc NFTs(QueryNFTsRequest) returns (QueryNFTsResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/nfts";
  }

  // NFT 根据类和 id 查询某个 NFT。
  rpc NFT(QueryNFTRequest) returns (QueryNFTResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/nfts/{class_id}/{id}";
  }

  // Class 根据 id 查询 NFT 类
  rpc Class(QueryClassRequest) returns (QueryClassResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/classes/{class_id}";
  }

  // Classes 查询全部 NFT 类
  rpc Classes(QueryClassesRequest) returns (QueryClassesResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/classes";
  }
}

// QueryBalanceRequest 是 Query/Balance RPC 方法的请求类型
message QueryBalanceRequest {
  string class_id = 1;
  string owner    = 2;
}

// QueryBalanceResponse 是 Query/Balance RPC 方法的响应类型
message QueryBalanceResponse {
  uint64 amount = 1;
}

// QueryOwnerRequest 是 Query/Owner RPC 方法的请求类型
message QueryOwnerRequest {
  string class_id = 1;
  string id       = 2;
}

// QueryOwnerResponse 是 Query/Owner RPC 方法的响应类型
message QueryOwnerResponse {
  string owner = 1;
}

// QuerySupplyRequest 是 Query/Supply RPC 方法的请求类型
message QuerySupplyRequest {
  string class_id = 1;
}

// QuerySupplyResponse 是 Query/Supply RPC 方法的响应类型
message QuerySupplyResponse {
  uint64 amount = 1;
}

// QueryNFTstRequest 是 Query/NFTs RPC 方法的请求类型
message QueryNFTsRequest {
  string                                class_id   = 1;
  string                                owner      = 2;
  cosmos.base.query.v1beta1.PageRequest pagination = 3;
}

// QueryNFTsResponse 是 Query/NFTs RPC 方法的响应类型
message QueryNFTsResponse {
  repeated cosmos.nft.v1beta1.NFT        nfts       = 1;
  cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryNFTRequest 是 Query/NFT RPC 方法的请求类型
message QueryNFTRequest {
  string class_id = 1;
  string id       = 2;
}

// QueryNFTResponse 是 Query/NFT RPC 方法的响应类型
message QueryNFTResponse {
  cosmos.nft.v1beta1.NFT nft = 1;
}

// QueryClassRequest 是 Query/Class RPC 方法的请求类型
message QueryClassRequest {
  string class_id = 1;
}

// QueryClassResponse 是 Query/Class RPC 方法的响应类型
message QueryClassResponse {
  cosmos.nft.v1beta1.Class class = 1;
}

// QueryClassesRequest 是 Query/Classes RPC 方法的请求类型
message QueryClassesRequest {
  // pagination 定义了请求的可选分页。
  cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

// QueryClassesResponse 是 Query/Classes RPC 方法的响应类型
message QueryClassesResponse {
  repeated cosmos.nft.v1beta1.Class      classes    = 1;
  cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

互操作性

互操作性的核心是在模块之间和链之间复用资产。前者通过 ADR-33: Protobuf 客户端-服务端通信实现。写作本文时,ADR-33 尚未最终定稿。后者通过 IBC 实现。这里我们将重点关注 IBC 这一侧。 IBC 按模块实现。在这里,我们统一将 NFT 记录并管理在 x/nft 中。这要求创建一个新的 IBC 标准并实现它。 对于 IBC 互操作性,NFT 自定义模块必须使用 IBC 客户端能够理解的 NFT 对象类型。因此,为了实现 x/nft 互操作性,自定义 NFT 实现(例如:x/cryptokitty)应当使用规范的 x/nft 模块,并将所有 NFT 余额维护功能代理到 x/nft;否则就需要使用 IBC 客户端能够理解的 NFT 对象类型重新实现全部功能。换句话说:x/nft 将成为所有 Cosmos NFT 的标准 NFT 注册表(例如:x/cryptokitty 会在 x/nft 中注册一个 kitty NFT,并使用 x/nft 进行账务维护)。这一点已在将 x/bank 用作通用资产余额账本的背景下进行过讨论。如果不使用 x/nft,就需要为 IBC 再实现另一个模块。

影响

向后兼容性

没有向后不兼容问题。

向前兼容性

本规范在 NFT 标识符方面符合 ERC-721 智能合约规范。需要注意的是,ERC-721 基于(合约地址,uint256 tokenId)定义唯一性,而我们之所以能隐式遵循这一点,是因为当前的目标是由单个模块跟踪 NFT 标识符。注意:使用(可变的)data 字段来判定唯一性并不安全。

正面影响

  • Cosmos Hub 上可提供 NFT 标识符。
  • 能够为 Cosmos Hub 构建不同的 NFT 模块,例如 ERC-721。
  • 支持与 IBC 以及 Gravity Bridge 等其他跨链基础设施互操作的 NFT 模块

负面影响

  • x/nft 需要新的 IBC 应用
  • 需要 CW721 适配器

中性影响

  • 其他功能需要更多模块。例如,NFT 交易功能需要托管模块,定义 NFT 属性需要收藏品模块。

进一步讨论

对于 Hub 上的其他类型应用,未来可以开发更多面向特定应用的模块:
  • x/nft/custody:托管 NFT,以支持交易功能。
  • x/nft/marketplace:使用 sdk.Coins 买卖 NFT。
  • x/fractional:将某项资产(NFT 或其他资产)的所有权拆分给多个利益相关方的模块。x/group 在大多数情况下应当可以满足需求。
Cosmos 生态中的其他网络也可以为特定的 NFT 应用和使用场景设计并实现它们自己的 NFT 模块。

参考资料


Changelog

  • 2021-05-01: Initial Draft
  • 2021-07-02: Review updates
  • 2022-06-15: Add batch operation
  • 2022-11-11: Remove strict validation of classID and tokenID

Status

PROPOSED

Abstract

This ADR defines the x/nft module which is a generic implementation of NFTs, roughly “compatible” with ERC721. Applications using the x/nft module must implement the following functions:
  • MsgNewClass - Receive the user’s request to create a class, and call the NewClass of the x/nft module.
  • MsgUpdateClass - Receive the user’s request to update a class, and call the UpdateClass of the x/nft module.
  • MsgMintNFT - Receive the user’s request to mint a nft, and call the MintNFT of the x/nft module.
  • BurnNFT - Receive the user’s request to burn a nft, and call the BurnNFT of the x/nft module.
  • UpdateNFT - Receive the user’s request to update a nft, and call the UpdateNFT of the x/nft module.

Context

NFTs are more than just crypto art, which is very helpful for accruing value to the Cosmos ecosystem. As a result, Cosmos Hub should implement NFT functions and enable a unified mechanism for storing and sending the ownership representative of NFTs as discussed in Link. As discussed in #9065, several potential solutions can be considered:
  • irismod/nft and modules/incubator/nft
  • CW721
  • DID NFTs
  • interNFT
Since functions/use cases of NFTs are tightly connected with their logic, it is almost impossible to support all the NFTs’ use cases in one Cosmos SDK module by defining and implementing different transaction types. Considering generic usage and compatibility of interchain protocols including IBC and Gravity Bridge, it is preferred to have a generic NFT module design which handles the generic NFTs logic. This design idea can enable composability that application-specific functions should be managed by other modules on Cosmos Hub or on other Zones by importing the NFT module. The current design is based on the work done by IRISnet team and an older implementation in the Cosmos repository.

Decision

We create a x/nft module, which contains the following functionality:
  • Store NFTs and track their ownership.
  • Expose Keeper interface for composing modules to transfer, mint and burn NFTs.
  • Expose external Message interface for users to transfer ownership of their NFTs.
  • Query NFTs and their supply information.
The proposed module is a base module for NFT app logic. It’s goal it to provide a common layer for storage, basic transfer functionality and IBC. The module should not be used as a standalone. Instead an app should create a specialized module to handle app specific logic (eg: NFT ID construction, royalty), user level minting and burning. Moreover an app specialized module should handle auxiliary data to support the app logic (eg indexes, ORM, business data). All data carried over IBC must be part of the NFT or Class type described below. The app specific NFT data should be encoded in NFT.data for cross-chain integrity. Other objects related to NFT, which are not important for integrity can be part of the app specific module.

Types

We propose two main types:
  • Class — describes NFT class. We can think about it as a smart contract address.
  • NFT — object representing unique, non fungible asset. Each NFT is associated with a Class.

Class

NFT Class is comparable to an ERC-721 smart contract (provides description of a smart contract), under which a collection of NFTs can be created and managed.
message Class {
  string id          = 1;
  string name        = 2;
  string symbol      = 3;
  string description = 4;
  string uri         = 5;
  string uri_hash    = 6;
  google.protobuf.Any data = 7;
}
  • id is used as the primary index for storing the class; required
  • name is a descriptive name of the NFT class; optional
  • symbol is the symbol usually shown on exchanges for the NFT class; optional
  • description is a detailed description of the NFT class; optional
  • uri is a URI for the class metadata stored off chain. It should be a JSON file that contains metadata about the NFT class and NFT data schema (OpenSea example); optional
  • uri_hash is a hash of the document pointed by uri; optional
  • data is app specific metadata of the class; optional

NFT

We define a general model for NFT as follows.
message NFT {
  string class_id           = 1;
  string id                 = 2;
  string uri                = 3;
  string uri_hash           = 4;
  google.protobuf.Any data  = 10;
}
  • class_id is the identifier of the NFT class where the NFT belongs; required
  • id is an identifier of the NFT, unique within the scope of its class. It is specified by the creator of the NFT and may be expanded to use DID in the future. class_id combined with id uniquely identifies an NFT and is used as the primary index for storing the NFT; required
    {class_id}/{id} --> NFT (bytes)
    
  • uri is a URI for the NFT metadata stored off chain. Should point to a JSON file that contains metadata about this NFT (Ref: ERC721 standard and OpenSea extension); required
  • uri_hash is a hash of the document pointed by uri; optional
  • data is an app specific data of the NFT. CAN be used by composing modules to specify additional properties of the NFT; optional
This ADR doesn’t specify values that data can take; however, best practices recommend upper-level NFT modules clearly specify their contents. Although the value of this field doesn’t provide the additional context required to manage NFT records, which means that the field can technically be removed from the specification, the field’s existence allows basic informational/UI functionality.

Keeper Interface

type Keeper interface {
    NewClass(ctx sdk.Context,class Class)

UpdateClass(ctx sdk.Context,class Class)

Mint(ctx sdk.Context,nft NFT,receiver sdk.AccAddress)   // updates totalSupply
  BatchMint(ctx sdk.Context, tokens []NFT,receiver sdk.AccAddress)

error

  Burn(ctx sdk.Context, classId string, nftId string)    // updates totalSupply
  BatchBurn(ctx sdk.Context, classID string, nftIDs []string)

error

  Update(ctx sdk.Context, nft NFT)

BatchUpdate(ctx sdk.Context, tokens []NFT)

error

  Transfer(ctx sdk.Context, classId string, nftId string, receiver sdk.AccAddress)

BatchTransfer(ctx sdk.Context, classID string, nftIDs []string, receiver sdk.AccAddress)

error

  GetClass(ctx sdk.Context, classId string)

Class
  GetClasses(ctx sdk.Context) []Class

  GetNFT(ctx sdk.Context, classId string, nftId string)

NFT
  GetNFTsOfClassByOwner(ctx sdk.Context, classId string, owner sdk.AccAddress) []NFT
  GetNFTsOfClass(ctx sdk.Context, classId string) []NFT

  GetOwner(ctx sdk.Context, classId string, nftId string)

sdk.AccAddress
  GetBalance(ctx sdk.Context, classId string, owner sdk.AccAddress)

uint64
  GetTotalSupply(ctx sdk.Context, classId string)

uint64
}
Other business logic implementations should be defined in composing modules that import x/nft and use its Keeper.

Msg Service

service Msg {
  rpc Send(MsgSend)         returns (MsgSendResponse);
}

message MsgSend {
  string class_id = 1;
  string id       = 2;
  string sender   = 3;
  string reveiver = 4;
}
message MsgSendResponse {}
MsgSend can be used to transfer the ownership of an NFT to another address. The implementation outline of the server is as follows:
type msgServer struct{
    k Keeper
}

func (m msgServer)

Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) {
  // check current ownership
  assertEqual(msg.Sender, m.k.GetOwner(msg.ClassId, msg.Id))

  // transfer ownership
  m.k.Transfer(msg.ClassId, msg.Id, msg.Receiver)

return &types.MsgSendResponse{
}, nil
}
The query service methods for the x/nft module are:
service Query {
  // Balance queries the number of NFTs of a given class owned by the owner, same as balanceOf in ERC721
  rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/balance/{owner}/{class_id}";
  }

  // Owner queries the owner of the NFT based on its class and id, same as ownerOf in ERC721
  rpc Owner(QueryOwnerRequest) returns (QueryOwnerResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/owner/{class_id}/{id}";
  }

  // Supply queries the number of NFTs from the given class, same as totalSupply of ERC721.
  rpc Supply(QuerySupplyRequest) returns (QuerySupplyResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/supply/{class_id}";
  }

  // NFTs queries all NFTs of a given class or owner,choose at least one of the two, similar to tokenByIndex in ERC721Enumerable
  rpc NFTs(QueryNFTsRequest) returns (QueryNFTsResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/nfts";
  }

  // NFT queries an NFT based on its class and id.
  rpc NFT(QueryNFTRequest) returns (QueryNFTResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/nfts/{class_id}/{id}";
  }

  // Class queries an NFT class based on its id
  rpc Class(QueryClassRequest) returns (QueryClassResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/classes/{class_id}";
  }

  // Classes queries all NFT classes
  rpc Classes(QueryClassesRequest) returns (QueryClassesResponse) {
    option (google.api.http).get = "/cosmos/nft/v1beta1/classes";
  }
}

// QueryBalanceRequest is the request type for the Query/Balance RPC method
message QueryBalanceRequest {
  string class_id = 1;
  string owner    = 2;
}

// QueryBalanceResponse is the response type for the Query/Balance RPC method
message QueryBalanceResponse {
  uint64 amount = 1;
}

// QueryOwnerRequest is the request type for the Query/Owner RPC method
message QueryOwnerRequest {
  string class_id = 1;
  string id       = 2;
}

// QueryOwnerResponse is the response type for the Query/Owner RPC method
message QueryOwnerResponse {
  string owner = 1;
}

// QuerySupplyRequest is the request type for the Query/Supply RPC method
message QuerySupplyRequest {
  string class_id = 1;
}

// QuerySupplyResponse is the response type for the Query/Supply RPC method
message QuerySupplyResponse {
  uint64 amount = 1;
}

// QueryNFTstRequest is the request type for the Query/NFTs RPC method
message QueryNFTsRequest {
  string                                class_id   = 1;
  string                                owner      = 2;
  cosmos.base.query.v1beta1.PageRequest pagination = 3;
}

// QueryNFTsResponse is the response type for the Query/NFTs RPC methods
message QueryNFTsResponse {
  repeated cosmos.nft.v1beta1.NFT        nfts       = 1;
  cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryNFTRequest is the request type for the Query/NFT RPC method
message QueryNFTRequest {
  string class_id = 1;
  string id       = 2;
}

// QueryNFTResponse is the response type for the Query/NFT RPC method
message QueryNFTResponse {
  cosmos.nft.v1beta1.NFT nft = 1;
}

// QueryClassRequest is the request type for the Query/Class RPC method
message QueryClassRequest {
  string class_id = 1;
}

// QueryClassResponse is the response type for the Query/Class RPC method
message QueryClassResponse {
  cosmos.nft.v1beta1.Class class = 1;
}

// QueryClassesRequest is the request type for the Query/Classes RPC method
message QueryClassesRequest {
  // pagination defines an optional pagination for the request.
  cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

// QueryClassesResponse is the response type for the Query/Classes RPC method
message QueryClassesResponse {
  repeated cosmos.nft.v1beta1.Class      classes    = 1;
  cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

Interoperability

Interoperability is all about reusing assets between modules and chains. The former one is achieved by ADR-33: Protobuf client - server communication. At the time of writing ADR-33 is not finalized. The latter is achieved by IBC. Here we will focus on the IBC side. IBC is implemented per module. Here, we aligned that NFTs will be recorded and managed in the x/nft. This requires creation of a new IBC standard and implementation of it. For IBC interoperability, NFT custom modules MUST use the NFT object type understood by the IBC client. So, for x/nft interoperability, custom NFT implementations (example: x/cryptokitty) should use the canonical x/nft module and proxy all NFT balance keeping functionality to x/nft or else re-implement all functionality using the NFT object type understood by the IBC client. In other words: x/nft becomes the standard NFT registry for all Cosmos NFTs (example: x/cryptokitty will register a kitty NFT in x/nft and use x/nft for book keeping). This was discussed in the context of using x/bank as a general asset balance book. Not using x/nft will require implementing another module for IBC.

Consequences

Backward Compatibility

No backward incompatibilities.

Forward Compatibility

This specification conforms to the ERC-721 smart contract specification for NFT identifiers. Note that ERC-721 defines uniqueness based on (contract address, uint256 tokenId), and we conform to this implicitly because a single module is currently aimed to track NFT identifiers. Note: use of the (mutable) data field to determine uniqueness is not safe.s

Positive

  • NFT identifiers available on Cosmos Hub.
  • Ability to build different NFT modules for the Cosmos Hub, e.g., ERC-721.
  • NFT module which supports interoperability with IBC and other cross-chain infrastructures like Gravity Bridge

Negative

  • New IBC app is required for x/nft
  • CW721 adapter is required

Neutral

  • Other functions need more modules. For example, a custody module is needed for NFT trading function, a collectible module is needed for defining NFT properties.

Further Discussions

For other kinds of applications on the Hub, more app-specific modules can be developed in the future:
  • x/nft/custody: custody of NFTs to support trading functionality.
  • x/nft/marketplace: selling and buying NFTs using sdk.Coins.
  • x/fractional: a module to split an ownership of an asset (NFT or other assets) for multiple stakeholder. x/group should work for most of the cases.
Other networks in the Cosmos ecosystem could design and implement their own NFT modules for specific NFT applications and use cases.

References