github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-043-nft-module.md (about)

     1  # ADR 43: NFT Module
     2  
     3  ## Changelog
     4  
     5  * 2021-05-01: Initial Draft
     6  * 2021-07-02: Review updates
     7  * 2022-06-15: Add batch operation
     8  * 2022-11-11: Remove strict validation of classID and tokenID
     9  
    10  ## Status
    11  
    12  PROPOSED
    13  
    14  ## Abstract
    15  
    16  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**:
    17  
    18  * `MsgNewClass` - Receive the user's request to create a class, and call the `NewClass` of the `x/nft` module.
    19  * `MsgUpdateClass` - Receive the user's request to update a class, and call the `UpdateClass` of the `x/nft` module.
    20  * `MsgMintNFT` - Receive the user's request to mint a nft, and call the `MintNFT` of the `x/nft` module.
    21  * `BurnNFT` - Receive the user's request to burn a nft, and call the `BurnNFT` of the `x/nft` module.
    22  * `UpdateNFT` - Receive the user's request to update a nft, and call the `UpdateNFT` of the `x/nft` module.
    23  
    24  ## Context
    25  
    26  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 https://github.com/cosmos/cosmos-sdk/discussions/9065.
    27  
    28  As discussed in [#9065](https://github.com/cosmos/cosmos-sdk/discussions/9065), several potential solutions can be considered:
    29  
    30  * irismod/nft and modules/incubator/nft
    31  * CW721
    32  * DID NFTs
    33  * interNFT
    34  
    35  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.
    36  
    37  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.
    38  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.
    39  
    40  The current design is based on the work done by [IRISnet team](https://github.com/irisnet/irismod/tree/master/modules/nft) and an older implementation in the [Cosmos repository](https://github.com/cosmos/modules/tree/master/incubator/nft).
    41  
    42  ## Decision
    43  
    44  We create a `x/nft` module, which contains the following functionality:
    45  
    46  * Store NFTs and track their ownership.
    47  * Expose `Keeper` interface for composing modules to transfer, mint and burn NFTs.
    48  * Expose external `Message` interface for users to transfer ownership of their NFTs.
    49  * Query NFTs and their supply information.
    50  
    51  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.
    52  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).
    53  
    54  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.
    55  
    56  ### Types
    57  
    58  We propose two main types:
    59  
    60  * `Class` -- describes NFT class. We can think about it as a smart contract address.
    61  * `NFT` -- object representing unique, non fungible asset. Each NFT is associated with a Class.
    62  
    63  #### Class
    64  
    65  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.
    66  
    67  ```protobuf
    68  message Class {
    69    string id          = 1;
    70    string name        = 2;
    71    string symbol      = 3;
    72    string description = 4;
    73    string uri         = 5;
    74    string uri_hash    = 6;
    75    google.protobuf.Any data = 7;
    76  }
    77  ```
    78  
    79  * `id` is used as the primary index for storing the class; _required_
    80  * `name` is a descriptive name of the NFT class; _optional_
    81  * `symbol` is the symbol usually shown on exchanges for the NFT class; _optional_
    82  * `description` is a detailed description of the NFT class; _optional_
    83  * `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](https://docs.opensea.io/docs/contract-level-metadata)); _optional_
    84  * `uri_hash` is a hash of the document pointed by uri; _optional_
    85  * `data` is app specific metadata of the class; _optional_
    86  
    87  #### NFT
    88  
    89  We define a general model for `NFT` as follows.
    90  
    91  ```protobuf
    92  message NFT {
    93    string class_id           = 1;
    94    string id                 = 2;
    95    string uri                = 3;
    96    string uri_hash           = 4;
    97    google.protobuf.Any data  = 10;
    98  }
    99  ```
   100  
   101  * `class_id` is the identifier of the NFT class where the NFT belongs; _required_
   102  * `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_
   103  
   104    ```text
   105    {class_id}/{id} --> NFT (bytes)
   106    ```
   107  
   108  * `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](https://docs.opensea.io/docs/metadata-standards)); _required_
   109  * `uri_hash` is a hash of the document pointed by uri; _optional_
   110  * `data` is an app specific data of the NFT. CAN be used by composing modules to specify additional properties of the NFT; _optional_
   111  
   112  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.
   113  
   114  ### `Keeper` Interface
   115  
   116  ```go
   117  type Keeper interface {
   118    NewClass(ctx sdk.Context,class Class)
   119    UpdateClass(ctx sdk.Context,class Class)
   120  
   121    Mint(ctx sdk.Context,nft NFT,receiver sdk.AccAddress)   // updates totalSupply
   122    BatchMint(ctx sdk.Context, tokens []NFT,receiver sdk.AccAddress) error
   123  
   124    Burn(ctx sdk.Context, classId string, nftId string)    // updates totalSupply
   125    BatchBurn(ctx sdk.Context, classID string, nftIDs []string) error
   126  
   127    Update(ctx sdk.Context, nft NFT)
   128    BatchUpdate(ctx sdk.Context, tokens []NFT) error
   129  
   130    Transfer(ctx sdk.Context, classId string, nftId string, receiver sdk.AccAddress)
   131    BatchTransfer(ctx sdk.Context, classID string, nftIDs []string, receiver sdk.AccAddress) error
   132  
   133    GetClass(ctx sdk.Context, classId string) Class
   134    GetClasses(ctx sdk.Context) []Class
   135  
   136    GetNFT(ctx sdk.Context, classId string, nftId string) NFT
   137    GetNFTsOfClassByOwner(ctx sdk.Context, classId string, owner sdk.AccAddress) []NFT
   138    GetNFTsOfClass(ctx sdk.Context, classId string) []NFT
   139  
   140    GetOwner(ctx sdk.Context, classId string, nftId string) sdk.AccAddress
   141    GetBalance(ctx sdk.Context, classId string, owner sdk.AccAddress) uint64
   142    GetTotalSupply(ctx sdk.Context, classId string) uint64
   143  }
   144  ```
   145  
   146  Other business logic implementations should be defined in composing modules that import `x/nft` and use its `Keeper`.
   147  
   148  ### `Msg` Service
   149  
   150  ```protobuf
   151  service Msg {
   152    rpc Send(MsgSend)         returns (MsgSendResponse);
   153  }
   154  
   155  message MsgSend {
   156    string class_id = 1;
   157    string id       = 2;
   158    string sender   = 3;
   159    string reveiver = 4;
   160  }
   161  message MsgSendResponse {}
   162  ```
   163  
   164  `MsgSend` can be used to transfer the ownership of an NFT to another address.
   165  
   166  The implementation outline of the server is as follows:
   167  
   168  ```go
   169  type msgServer struct{
   170    k Keeper
   171  }
   172  
   173  func (m msgServer) Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) {
   174    // check current ownership
   175    assertEqual(msg.Sender, m.k.GetOwner(msg.ClassId, msg.Id))
   176  
   177    // transfer ownership
   178    m.k.Transfer(msg.ClassId, msg.Id, msg.Receiver)
   179  
   180    return &types.MsgSendResponse{}, nil
   181  }
   182  ```
   183  
   184  The query service methods for the `x/nft` module are:
   185  
   186  ```protobuf
   187  service Query {
   188    // Balance queries the number of NFTs of a given class owned by the owner, same as balanceOf in ERC721
   189    rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) {
   190      option (google.api.http).get = "/cosmos/nft/v1beta1/balance/{owner}/{class_id}";
   191    }
   192  
   193    // Owner queries the owner of the NFT based on its class and id, same as ownerOf in ERC721
   194    rpc Owner(QueryOwnerRequest) returns (QueryOwnerResponse) {
   195      option (google.api.http).get = "/cosmos/nft/v1beta1/owner/{class_id}/{id}";
   196    }
   197  
   198    // Supply queries the number of NFTs from the given class, same as totalSupply of ERC721.
   199    rpc Supply(QuerySupplyRequest) returns (QuerySupplyResponse) {
   200      option (google.api.http).get = "/cosmos/nft/v1beta1/supply/{class_id}";
   201    }
   202  
   203    // NFTs queries all NFTs of a given class or owner,choose at least one of the two, similar to tokenByIndex in ERC721Enumerable
   204    rpc NFTs(QueryNFTsRequest) returns (QueryNFTsResponse) {
   205      option (google.api.http).get = "/cosmos/nft/v1beta1/nfts";
   206    }
   207  
   208    // NFT queries an NFT based on its class and id.
   209    rpc NFT(QueryNFTRequest) returns (QueryNFTResponse) {
   210      option (google.api.http).get = "/cosmos/nft/v1beta1/nfts/{class_id}/{id}";
   211    }
   212  
   213    // Class queries an NFT class based on its id
   214    rpc Class(QueryClassRequest) returns (QueryClassResponse) {
   215      option (google.api.http).get = "/cosmos/nft/v1beta1/classes/{class_id}";
   216    }
   217  
   218    // Classes queries all NFT classes
   219    rpc Classes(QueryClassesRequest) returns (QueryClassesResponse) {
   220      option (google.api.http).get = "/cosmos/nft/v1beta1/classes";
   221    }
   222  }
   223  
   224  // QueryBalanceRequest is the request type for the Query/Balance RPC method
   225  message QueryBalanceRequest {
   226    string class_id = 1;
   227    string owner    = 2;
   228  }
   229  
   230  // QueryBalanceResponse is the response type for the Query/Balance RPC method
   231  message QueryBalanceResponse {
   232    uint64 amount = 1;
   233  }
   234  
   235  // QueryOwnerRequest is the request type for the Query/Owner RPC method
   236  message QueryOwnerRequest {
   237    string class_id = 1;
   238    string id       = 2;
   239  }
   240  
   241  // QueryOwnerResponse is the response type for the Query/Owner RPC method
   242  message QueryOwnerResponse {
   243    string owner = 1;
   244  }
   245  
   246  // QuerySupplyRequest is the request type for the Query/Supply RPC method
   247  message QuerySupplyRequest {
   248    string class_id = 1;
   249  }
   250  
   251  // QuerySupplyResponse is the response type for the Query/Supply RPC method
   252  message QuerySupplyResponse {
   253    uint64 amount = 1;
   254  }
   255  
   256  // QueryNFTstRequest is the request type for the Query/NFTs RPC method
   257  message QueryNFTsRequest {
   258    string                                class_id   = 1;
   259    string                                owner      = 2;
   260    cosmos.base.query.v1beta1.PageRequest pagination = 3;
   261  }
   262  
   263  // QueryNFTsResponse is the response type for the Query/NFTs RPC methods
   264  message QueryNFTsResponse {
   265    repeated cosmos.nft.v1beta1.NFT        nfts       = 1;
   266    cosmos.base.query.v1beta1.PageResponse pagination = 2;
   267  }
   268  
   269  // QueryNFTRequest is the request type for the Query/NFT RPC method
   270  message QueryNFTRequest {
   271    string class_id = 1;
   272    string id       = 2;
   273  }
   274  
   275  // QueryNFTResponse is the response type for the Query/NFT RPC method
   276  message QueryNFTResponse {
   277    cosmos.nft.v1beta1.NFT nft = 1;
   278  }
   279  
   280  // QueryClassRequest is the request type for the Query/Class RPC method
   281  message QueryClassRequest {
   282    string class_id = 1;
   283  }
   284  
   285  // QueryClassResponse is the response type for the Query/Class RPC method
   286  message QueryClassResponse {
   287    cosmos.nft.v1beta1.Class class = 1;
   288  }
   289  
   290  // QueryClassesRequest is the request type for the Query/Classes RPC method
   291  message QueryClassesRequest {
   292    // pagination defines an optional pagination for the request.
   293    cosmos.base.query.v1beta1.PageRequest pagination = 1;
   294  }
   295  
   296  // QueryClassesResponse is the response type for the Query/Classes RPC method
   297  message QueryClassesResponse {
   298    repeated cosmos.nft.v1beta1.Class      classes    = 1;
   299    cosmos.base.query.v1beta1.PageResponse pagination = 2;
   300  }
   301  ```
   302  
   303  ### Interoperability
   304  
   305  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.
   306  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.
   307  
   308  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](https://github.com/cosmos/cosmos-sdk/discussions/9065#discussioncomment-873206) in the context of using x/bank as a general asset balance book. Not using x/nft will require implementing another module for IBC.
   309  
   310  ## Consequences
   311  
   312  ### Backward Compatibility
   313  
   314  No backward incompatibilities.
   315  
   316  ### Forward Compatibility
   317  
   318  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
   319  
   320  ### Positive
   321  
   322  * NFT identifiers available on Cosmos Hub.
   323  * Ability to build different NFT modules for the Cosmos Hub, e.g., ERC-721.
   324  * NFT module which supports interoperability with IBC and other cross-chain infrastructures like Gravity Bridge
   325  
   326  ### Negative
   327  
   328  * New IBC app is required for x/nft
   329  * CW721 adapter is required
   330  
   331  ### Neutral
   332  
   333  * 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.
   334  
   335  ## Further Discussions
   336  
   337  For other kinds of applications on the Hub, more app-specific modules can be developed in the future:
   338  
   339  * `x/nft/custody`: custody of NFTs to support trading functionality.
   340  * `x/nft/marketplace`: selling and buying NFTs using sdk.Coins.
   341  * `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.
   342  
   343  Other networks in the Cosmos ecosystem could design and implement their own NFT modules for specific NFT applications and use cases.
   344  
   345  ## References
   346  
   347  * Initial discussion: https://github.com/cosmos/cosmos-sdk/discussions/9065
   348  * x/nft: initialize module: https://github.com/cosmos/cosmos-sdk/pull/9174
   349  * [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md)