From 4ee10b6bd7eeca8fc59d3802bddf5038acd47967 Mon Sep 17 00:00:00 2001 From: Zhiqiang Zhang <745124335@qq.com> Date: Wed, 10 Nov 2021 21:00:10 +0800 Subject: [PATCH] feat: add command line for nft module (#10505) ## Description Add nft module command line and unit test,refer #9826 --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --- docs/core/proto-docs.md | 2 +- proto/cosmos/nft/v1beta1/query.proto | 2 +- x/nft/client/cli/query.go | 255 +++++++++++++ x/nft/client/cli/tx.go | 60 ++++ x/nft/client/testutil/cli_test.go | 15 + x/nft/client/testutil/grpc.go | 518 +++++++++++++++++++++++++++ x/nft/client/testutil/query.go | 475 ++++++++++++++++++++++++ x/nft/client/testutil/test_helper.go | 84 +++++ x/nft/client/testutil/tx.go | 166 +++++++++ x/nft/module/module.go | 10 +- x/nft/query.pb.go | 86 ++--- x/nft/query.pb.gw.go | 46 +-- x/nft/validation.go | 6 +- 13 files changed, 1651 insertions(+), 74 deletions(-) create mode 100644 x/nft/client/cli/query.go create mode 100644 x/nft/client/cli/tx.go create mode 100644 x/nft/client/testutil/cli_test.go create mode 100644 x/nft/client/testutil/grpc.go create mode 100644 x/nft/client/testutil/query.go create mode 100644 x/nft/client/testutil/test_helper.go create mode 100644 x/nft/client/testutil/tx.go diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 04cffc6b1..47cfa54f6 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -7614,7 +7614,7 @@ Query defines the gRPC querier service. | Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | | ----------- | ------------ | ------------- | ------------| ------- | -------- | -| `Balance` | [QueryBalanceRequest](#cosmos.nft.v1beta1.QueryBalanceRequest) | [QueryBalanceResponse](#cosmos.nft.v1beta1.QueryBalanceResponse) | Balance queries the number of NFTs of a given class owned by the owner, same as balanceOf in ERC721 | GET|/cosmos/nft/v1beta1/balance/{class_id}/{owner}| +| `Balance` | [QueryBalanceRequest](#cosmos.nft.v1beta1.QueryBalanceRequest) | [QueryBalanceResponse](#cosmos.nft.v1beta1.QueryBalanceResponse) | Balance queries the number of NFTs of a given class owned by the owner, same as balanceOf in ERC721 | GET|/cosmos/nft/v1beta1/balance/{owner}/{class_id}| | `Owner` | [QueryOwnerRequest](#cosmos.nft.v1beta1.QueryOwnerRequest) | [QueryOwnerResponse](#cosmos.nft.v1beta1.QueryOwnerResponse) | Owner queries the owner of the NFT based on its class and id, same as ownerOf in ERC721 | GET|/cosmos/nft/v1beta1/owner/{class_id}/{id}| | `Supply` | [QuerySupplyRequest](#cosmos.nft.v1beta1.QuerySupplyRequest) | [QuerySupplyResponse](#cosmos.nft.v1beta1.QuerySupplyResponse) | Supply queries the number of NFTs from the given class, same as totalSupply of ERC721. | GET|/cosmos/nft/v1beta1/supply/{class_id}| | `NFTsOfClass` | [QueryNFTsOfClassRequest](#cosmos.nft.v1beta1.QueryNFTsOfClassRequest) | [QueryNFTsOfClassResponse](#cosmos.nft.v1beta1.QueryNFTsOfClassResponse) | NFTsOfClass queries all NFTs of a given class or optional owner, similar to tokenByIndex in ERC721Enumerable | GET|/cosmos/nft/v1beta1/nfts/{class_id}| diff --git a/proto/cosmos/nft/v1beta1/query.proto b/proto/cosmos/nft/v1beta1/query.proto index fb9b82ad5..4beb72e9a 100644 --- a/proto/cosmos/nft/v1beta1/query.proto +++ b/proto/cosmos/nft/v1beta1/query.proto @@ -11,7 +11,7 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/nft"; 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/{class_id}/{owner}"; + 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 diff --git a/x/nft/client/cli/query.go b/x/nft/client/cli/query.go new file mode 100644 index 000000000..cb8a379d6 --- /dev/null +++ b/x/nft/client/cli/query.go @@ -0,0 +1,255 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/nft" +) + +// Flag names and values +const ( + FlagOwner = "owner" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd() *cobra.Command { + nftQueryCmd := &cobra.Command{ + Use: nft.ModuleName, + Short: "Querying commands for the nft module", + Long: "", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + nftQueryCmd.AddCommand( + GetCmdQueryClass(), + GetCmdQueryClasses(), + GetCmdQueryNFT(), + GetCmdQueryNFTs(), + GetCmdQueryOwner(), + GetCmdQueryBalance(), + GetCmdQuerySupply(), + ) + return nftQueryCmd +} + +// GetCmdQueryClass implements the query class command. +func GetCmdQueryClass() *cobra.Command { + cmd := &cobra.Command{ + Use: "class [class-id]", + Args: cobra.ExactArgs(1), + Short: "query an NFT class based on its id", + Example: fmt.Sprintf(`$ %s query %s class `, version.AppName, nft.ModuleName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := nft.NewQueryClient(clientCtx) + res, err := queryClient.Class(cmd.Context(), &nft.QueryClassRequest{ + ClassId: args[0], + }) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryClasses implements the query classes command. +func GetCmdQueryClasses() *cobra.Command { + cmd := &cobra.Command{ + Use: "classes", + Short: "query all NFT classes", + Example: fmt.Sprintf(`$ %s query %s classes`, version.AppName, nft.ModuleName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := nft.NewQueryClient(clientCtx) + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + res, err := queryClient.Classes(cmd.Context(), &nft.QueryClassesRequest{ + Pagination: pageReq, + }) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "classes") + return cmd +} + +// GetCmdQueryNFT implements the query nft command. +func GetCmdQueryNFT() *cobra.Command { + cmd := &cobra.Command{ + Use: "nft [class-id] [nft-id]", + Args: cobra.ExactArgs(2), + Short: "query an NFT based on its class and id.", + Example: fmt.Sprintf(`$ %s query %s nft`, version.AppName, nft.ModuleName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := nft.NewQueryClient(clientCtx) + res, err := queryClient.NFT(cmd.Context(), &nft.QueryNFTRequest{ + ClassId: args[0], + Id: args[1], + }) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryNFTs implements the query nft command. +func GetCmdQueryNFTs() *cobra.Command { + cmd := &cobra.Command{ + Use: "nfts [class-id]", + Args: cobra.ExactArgs(1), + Short: "query all NFTs of a given class or owner address.", + Long: strings.TrimSpace( + fmt.Sprintf(`Query all NFTs of a given class or owner address. If owner +is set, all nfts that belong to the owner are filtered out. +Examples: +$ %s query %s nfts --owner= +`, + version.AppName, nft.ModuleName), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := nft.NewQueryClient(clientCtx) + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + request := &nft.QueryNFTsOfClassRequest{ + ClassId: args[0], + Pagination: pageReq, + } + + owner, err := cmd.Flags().GetString(FlagOwner) + if err != nil { + return err + } + + if len(owner) > 0 { + request.Owner = owner + } + res, err := queryClient.NFTsOfClass(cmd.Context(), request) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "nfts") + cmd.Flags().String(FlagOwner, "", "The owner of the nft") + return cmd +} + +// GetCmdQueryOwner implements the query owner command. +func GetCmdQueryOwner() *cobra.Command { + cmd := &cobra.Command{ + Use: "owner [class-id] [nft-id]", + Args: cobra.ExactArgs(2), + Short: "query the owner of the NFT based on its class and id.", + Example: fmt.Sprintf(`$ %s query %s owner `, version.AppName, nft.ModuleName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := nft.NewQueryClient(clientCtx) + res, err := queryClient.Owner(cmd.Context(), &nft.QueryOwnerRequest{ + ClassId: args[0], + Id: args[1], + }) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryBalance implements the query balance command. +func GetCmdQueryBalance() *cobra.Command { + cmd := &cobra.Command{ + Use: "balance [owner] [class-id]", + Args: cobra.ExactArgs(2), + Short: "query the number of NFTs of a given class owned by the owner.", + Example: fmt.Sprintf(`$ %s query %s balance `, version.AppName, nft.ModuleName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := nft.NewQueryClient(clientCtx) + res, err := queryClient.Balance(cmd.Context(), &nft.QueryBalanceRequest{ + ClassId: args[1], + Owner: args[0], + }) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQuerySupply implements the query supply command. +func GetCmdQuerySupply() *cobra.Command { + cmd := &cobra.Command{ + Use: "supply [class-id]", + Args: cobra.ExactArgs(1), + Short: "query the number of nft based on the class.", + Example: fmt.Sprintf(`$ %s query %s supply `, version.AppName, nft.ModuleName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := nft.NewQueryClient(clientCtx) + res, err := queryClient.Supply(cmd.Context(), &nft.QuerySupplyRequest{ + ClassId: args[0], + }) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/nft/client/cli/tx.go b/x/nft/client/cli/tx.go new file mode 100644 index 000000000..45320a97a --- /dev/null +++ b/x/nft/client/cli/tx.go @@ -0,0 +1,60 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/nft" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + nftTxCmd := &cobra.Command{ + Use: nft.ModuleName, + Short: "nft transactions subcommands", + Long: "Provides the most common nft logic for upper-level applications, compatible with Ethereum's erc721 contract", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + nftTxCmd.AddCommand( + NewCmdSend(), + ) + + return nftTxCmd +} + +func NewCmdSend() *cobra.Command { + cmd := &cobra.Command{ + Use: "send [class-id] [nft-id] [receiver] --from [sender]", + Args: cobra.ExactArgs(3), + Short: "transfer ownership of nft", + Long: strings.TrimSpace(fmt.Sprintf(` + $ %s tx %s send --from --chain-id `, version.AppName, nft.ModuleName), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + msg := nft.MsgSend{ + ClassId: args[0], + Id: args[1], + Sender: clientCtx.GetFromAddress().String(), + Receiver: args[2], + } + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/x/nft/client/testutil/cli_test.go b/x/nft/client/testutil/cli_test.go new file mode 100644 index 000000000..7f92bbeef --- /dev/null +++ b/x/nft/client/testutil/cli_test.go @@ -0,0 +1,15 @@ +package testutil + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/testutil/network" +) + +func TestIntegrationTestSuite(t *testing.T) { + cfg := network.DefaultConfig() + cfg.NumValidators = 1 + suite.Run(t, NewIntegrationTestSuite(cfg)) +} diff --git a/x/nft/client/testutil/grpc.go b/x/nft/client/testutil/grpc.go new file mode 100644 index 000000000..e89196cbf --- /dev/null +++ b/x/nft/client/testutil/grpc.go @@ -0,0 +1,518 @@ +package testutil + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/testutil/rest" + "github.com/cosmos/cosmos-sdk/x/nft" +) + +func (s *IntegrationTestSuite) TestQueryBalanceGRPC() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassId string + Owner string + } + expectErr bool + errMsg string + expectValue uint64 + }{ + { + name: "fail not exist class id", + args: struct { + ClassId string + Owner string + }{ + ClassId: "invalid_class_id", + Owner: s.owner.String(), + }, + expectErr: true, + errMsg: "invalid class id", + expectValue: 0, + }, + { + name: "fail not exist owner", + args: struct { + ClassId string + Owner string + }{ + ClassId: ExpNFT.ClassId, + Owner: s.owner.String(), + }, + expectErr: false, + expectValue: 0, + }, + { + name: "success", + args: struct { + ClassId string + Owner string + }{ + ClassId: ExpNFT.ClassId, + Owner: val.Address.String(), + }, + expectErr: false, + expectValue: 1, + }, + } + balanceURL := val.APIAddress + "/cosmos/nft/v1beta1/balance/%s/%s" + for _, tc := range testCases { + uri := fmt.Sprintf(balanceURL, tc.args.Owner, tc.args.ClassId) + s.Run(tc.name, func() { + resp, _ := rest.GetRequest(uri) + if tc.expectErr { + s.Require().Contains(string(resp), tc.errMsg) + } else { + var g nft.QueryBalanceResponse + err := val.ClientCtx.Codec.UnmarshalJSON(resp, &g) + s.Require().NoError(err) + s.Require().Equal(tc.expectValue, g.Amount) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryOwnerGRPC() { + val := s.network.Validators[0] + + testCases := []struct { + name string + args struct { + ClassId string + Id string + } + expectErr bool + errMsg string + expectResult string + }{ + { + name: "class id is invalid", + args: struct { + ClassId string + Id string + }{ + ClassId: "invalid_class_id", + Id: ExpNFT.Id, + }, + expectErr: true, + errMsg: "invalid class id", + expectResult: "", + }, + { + name: "class id does not exist", + args: struct { + ClassId string + Id string + }{ + ClassId: "class-id", + Id: ExpNFT.Id, + }, + expectErr: false, + expectResult: "", + }, + { + name: "nft id is invalid", + args: struct { + ClassId string + Id string + }{ + ClassId: ExpNFT.ClassId, + Id: "invalid_nft_id", + }, + expectErr: true, + expectResult: "", + }, + { + name: "nft id does not exist", + args: struct { + ClassId string + Id string + }{ + ClassId: ExpNFT.ClassId, + Id: "nft-id", + }, + expectErr: false, + expectResult: "", + }, + { + name: "nft exist", + args: struct { + ClassId string + Id string + }{ + ClassId: ExpNFT.ClassId, + Id: ExpNFT.Id, + }, + expectErr: false, + expectResult: val.Address.String(), + }, + } + ownerURL := val.APIAddress + "/cosmos/nft/v1beta1/owner/%s/%s" + for _, tc := range testCases { + uri := fmt.Sprintf(ownerURL, tc.args.ClassId, tc.args.Id) + s.Run(tc.name, func() { + resp, err := rest.GetRequest(uri) + if tc.expectErr { + s.Require().Contains(string(resp), tc.errMsg) + } else { + s.Require().NoError(err) + var result nft.QueryOwnerResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp, &result) + s.Require().NoError(err) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQuerySupplyGRPC() { + val := s.network.Validators[0] + + testCases := []struct { + name string + args struct { + ClassId string + } + expectErr bool + errMsg string + expectResult uint64 + }{ + { + name: "class id is invalid", + args: struct { + ClassId string + }{ + ClassId: "invalid_class_id", + }, + expectErr: true, + errMsg: "invalid class id", + expectResult: 0, + }, + { + name: "class id does not exist", + args: struct { + ClassId string + }{ + ClassId: "class-id", + }, + expectErr: false, + expectResult: 0, + }, + { + name: "class id exist", + args: struct { + ClassId string + }{ + ClassId: ExpNFT.ClassId, + }, + expectErr: false, + expectResult: 1, + }, + } + supplyURL := val.APIAddress + "/cosmos/nft/v1beta1/supply/%s" + for _, tc := range testCases { + uri := fmt.Sprintf(supplyURL, tc.args.ClassId) + s.Run(tc.name, func() { + resp, err := rest.GetRequest(uri) + if tc.expectErr { + s.Require().Contains(string(resp), tc.errMsg) + } else { + s.Require().NoError(err) + var result nft.QuerySupplyResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp, &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Amount) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryNFTsByOwnerGRPC() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassId string + Owner string + } + expectErr bool + errorMsg string + expectResult []*nft.NFT + }{ + { + name: "class id is invalid", + args: struct { + ClassId string + Owner string + }{ + ClassId: "invalid_class_id", + Owner: s.owner.String(), + }, + expectErr: true, + errorMsg: "invalid class id", + expectResult: []*nft.NFT{}, + }, + { + name: "class id does not exist", + args: struct { + ClassId string + Owner string + }{ + ClassId: "class-id", + Owner: s.owner.String(), + }, + expectErr: false, + expectResult: []*nft.NFT{}, + }, + { + name: "owner does not exist", + args: struct { + ClassId string + Owner string + }{ + ClassId: ExpNFT.ClassId, + Owner: s.owner.String(), + }, + expectErr: false, + expectResult: []*nft.NFT{}, + }, + { + name: "nft exist", + args: struct { + ClassId string + Owner string + }{ + ClassId: ExpNFT.ClassId, + Owner: val.Address.String(), + }, + expectErr: false, + expectResult: []*nft.NFT{&ExpNFT}, + }, + } + nftsOfClassURL := val.APIAddress + "/cosmos/nft/v1beta1/nfts/%s?owner=%s" + for _, tc := range testCases { + uri := fmt.Sprintf(nftsOfClassURL, tc.args.ClassId, tc.args.Owner) + s.Run(tc.name, func() { + resp, err := rest.GetRequest(uri) + if tc.expectErr { + s.Require().Contains(string(resp), tc.errorMsg) + } else { + s.Require().NoError(err) + var result nft.QueryNFTsOfClassResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp, &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Nfts) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryNFTsOfClassGRPC() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassId string + } + expectErr bool + errorMsg string + expectResult []*nft.NFT + }{ + { + name: "class id is invalid", + args: struct { + ClassId string + }{ + ClassId: "invalid_class_id", + }, + expectErr: true, + expectResult: []*nft.NFT{}, + }, + { + name: "class id does not exist", + args: struct { + ClassId string + }{ + ClassId: "class-id", + }, + expectErr: false, + expectResult: []*nft.NFT{}, + }, + { + name: "class id exist", + args: struct { + ClassId string + }{ + ClassId: ExpNFT.ClassId, + }, + expectErr: false, + expectResult: []*nft.NFT{&ExpNFT}, + }, + } + nftsOfClassURL := val.APIAddress + "/cosmos/nft/v1beta1/nfts/%s" + for _, tc := range testCases { + uri := fmt.Sprintf(nftsOfClassURL, tc.args.ClassId) + s.Run(tc.name, func() { + resp, err := rest.GetRequest(uri) + if tc.expectErr { + s.Require().Contains(string(resp), tc.errorMsg) + } else { + s.Require().NoError(err) + var result nft.QueryNFTsOfClassResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp, &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Nfts) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryNFTGRPC() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassId string + Id string + } + expectErr bool + errorMsg string + }{ + { + name: "class id is invalid", + args: struct { + ClassId string + Id string + }{ + ClassId: "invalid_class_id", + Id: ExpNFT.Id, + }, + expectErr: true, + errorMsg: "invalid class id", + }, + { + name: "class id does not exist", + args: struct { + ClassId string + Id string + }{ + ClassId: "class", + Id: ExpNFT.Id, + }, + expectErr: true, + errorMsg: "not found nft", + }, + { + name: "nft id is invalid", + args: struct { + ClassId string + Id string + }{ + ClassId: ExpNFT.ClassId, + Id: "invalid_nft_id", + }, + expectErr: true, + errorMsg: "invalid nft id", + }, + { + name: "nft id does not exist", + args: struct { + ClassId string + Id string + }{ + ClassId: ExpNFT.ClassId, + Id: "nft-id", + }, + expectErr: true, + errorMsg: "not found nft", + }, + { + name: "exist nft", + args: struct { + ClassId string + Id string + }{ + ClassId: ExpNFT.ClassId, + Id: ExpNFT.Id, + }, + expectErr: false, + }, + } + nftURL := val.APIAddress + "/cosmos/nft/v1beta1/nfts/%s/%s" + for _, tc := range testCases { + uri := fmt.Sprintf(nftURL, tc.args.ClassId, tc.args.Id) + s.Run(tc.name, func() { + resp, err := rest.GetRequest(uri) + if tc.expectErr { + s.Require().Contains(string(resp), tc.errorMsg) + } else { + s.Require().NoError(err) + var result nft.QueryNFTResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp, &result) + s.Require().NoError(err) + s.Require().EqualValues(ExpNFT, *result.Nft) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryClassGRPC() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassId string + } + expectErr bool + errorMsg string + }{ + { + name: "class id does not exist", + args: struct { + ClassId string + }{ + ClassId: "class-id", + }, + expectErr: true, + errorMsg: "not found class", + }, + { + name: "class id exist", + args: struct { + ClassId string + }{ + ClassId: ExpNFT.ClassId, + }, + expectErr: false, + }, + } + classURL := val.APIAddress + "/cosmos/nft/v1beta1/classes/%s" + for _, tc := range testCases { + uri := fmt.Sprintf(classURL, tc.args.ClassId) + s.Run(tc.name, func() { + resp, err := rest.GetRequest(uri) + if tc.expectErr { + s.Require().Contains(string(resp), tc.errorMsg) + } else { + s.Require().NoError(err) + var result nft.QueryClassResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp, &result) + s.Require().NoError(err) + s.Require().EqualValues(ExpClass, *result.Class) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryClassesGRPC() { + val := s.network.Validators[0] + classURL := val.APIAddress + "/cosmos/nft/v1beta1/classes" + resp, err := rest.GetRequest(classURL) + s.Require().NoError(err) + var result nft.QueryClassesResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp, &result) + s.Require().NoError(err) + s.Require().Len(result.Classes, 1) + s.Require().EqualValues(ExpClass, *result.Classes[0]) +} diff --git a/x/nft/client/testutil/query.go b/x/nft/client/testutil/query.go new file mode 100644 index 000000000..83588d524 --- /dev/null +++ b/x/nft/client/testutil/query.go @@ -0,0 +1,475 @@ +package testutil + +import ( + "github.com/cosmos/cosmos-sdk/x/nft" +) + +func (s *IntegrationTestSuite) TestQueryClass() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassID string + } + expectErr bool + }{ + { + name: "class id does not exist", + args: struct { + ClassID string + }{ + ClassID: "class", + }, + expectErr: true, + }, + { + name: "class id exist", + args: struct { + ClassID string + }{ + ClassID: testClassID, + }, + expectErr: false, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQueryClass(val, tc.args.ClassID) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + var result nft.QueryClassResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().EqualValues(ExpClass, *result.Class) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryClasses() { + val := s.network.Validators[0] + testCases := []struct { + name string + expectErr bool + }{ + { + name: "no params", + expectErr: false, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQueryClasses(val) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + var result nft.QueryClassesResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().Len(result.Classes, 1) + s.Require().EqualValues(ExpClass, *result.Classes[0]) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryNFT() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassID string + ID string + } + expectErr bool + }{ + { + name: "class id does not exist", + args: struct { + ClassID string + ID string + }{ + ClassID: "class", + ID: testID, + }, + expectErr: true, + }, + { + name: "nft id does not exist", + args: struct { + ClassID string + ID string + }{ + ClassID: testClassID, + ID: "id", + }, + expectErr: true, + }, + { + name: "exist nft", + args: struct { + ClassID string + ID string + }{ + ClassID: testClassID, + ID: testID, + }, + expectErr: false, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQueryNFT(val, tc.args.ClassID, tc.args.ID) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + var result nft.QueryNFTResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().EqualValues(ExpNFT, *result.Nft) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryNFTs() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassID string + } + expectErr bool + expectResult []*nft.NFT + }{ + { + name: "class id does not exist", + args: struct { + ClassID string + }{ + ClassID: "class", + }, + expectErr: false, + expectResult: []*nft.NFT{}, + }, + { + name: "class id exist", + args: struct { + ClassID string + }{ + ClassID: testClassID, + }, + expectErr: false, + expectResult: []*nft.NFT{&ExpNFT}, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQueryNFTs(val, tc.args.ClassID) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + var result nft.QueryNFTsOfClassResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Nfts) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryNFTsByOwner() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassID string + Owner string + } + expectErr bool + expectResult []*nft.NFT + }{ + { + name: "class id does not exist", + args: struct { + ClassID string + Owner string + }{ + ClassID: "class", + Owner: val.Address.String(), + }, + expectErr: false, + expectResult: []*nft.NFT{}, + }, + { + name: "owner does not exist", + args: struct { + ClassID string + Owner string + }{ + ClassID: testClassID, + Owner: s.owner.String(), + }, + expectErr: false, + expectResult: []*nft.NFT{}, + }, + { + name: "nft exist", + args: struct { + ClassID string + Owner string + }{ + ClassID: testClassID, + Owner: val.Address.String(), + }, + expectErr: false, + expectResult: []*nft.NFT{&ExpNFT}, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQueryNFTsByOwner(val, tc.args.ClassID, tc.args.Owner) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + var result nft.QueryNFTsOfClassResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Nfts) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryOwner() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassID string + ID string + } + expectErr bool + errorMsg string + expectResult string + }{ + { + name: "class id is invalid", + args: struct { + ClassID string + ID string + }{ + ClassID: "invalid_class_id", + ID: testID, + }, + expectErr: true, + errorMsg: "invalid class id", + expectResult: "", + }, + { + name: "class id does not exist", + args: struct { + ClassID string + ID string + }{ + ClassID: "class", + ID: testID, + }, + expectErr: false, + expectResult: "", + }, + { + name: "nft id is invalid", + args: struct { + ClassID string + ID string + }{ + ClassID: testClassID, + ID: "invalid_nft_id", + }, + expectErr: true, + expectResult: "", + }, + { + name: "nft id does not exist", + args: struct { + ClassID string + ID string + }{ + ClassID: testClassID, + ID: "nft-id", + }, + expectErr: false, + expectResult: "", + }, + { + name: "nft exist", + args: struct { + ClassID string + ID string + }{ + ClassID: testClassID, + ID: testID, + }, + expectErr: false, + expectResult: val.Address.String(), + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQueryOwner(val, tc.args.ClassID, tc.args.ID) + if tc.expectErr { + s.Require().Contains(string(resp.Bytes()), tc.errorMsg) + } else { + s.Require().NoError(err) + var result nft.QueryOwnerResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Owner) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQueryBalance() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassID string + Owner string + } + expectErr bool + errorMsg string + expectResult uint64 + }{ + { + name: "class id is invalid", + args: struct { + ClassID string + Owner string + }{ + ClassID: "invalid_class_id", + Owner: val.Address.String(), + }, + expectErr: true, + errorMsg: "invalid class id", + expectResult: 0, + }, + { + name: "class id does not exist", + args: struct { + ClassID string + Owner string + }{ + ClassID: "class", + Owner: val.Address.String(), + }, + expectErr: false, + expectResult: 0, + }, + { + name: "owner does not exist", + args: struct { + ClassID string + Owner string + }{ + ClassID: testClassID, + Owner: s.owner.String(), + }, + expectErr: false, + expectResult: 0, + }, + { + name: "nft exist", + args: struct { + ClassID string + Owner string + }{ + ClassID: testClassID, + Owner: val.Address.String(), + }, + expectErr: false, + expectResult: 1, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQueryBalance(val, tc.args.ClassID, tc.args.Owner) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + var result nft.QueryBalanceResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Amount) + } + }) + } +} + +func (s *IntegrationTestSuite) TestQuerySupply() { + val := s.network.Validators[0] + testCases := []struct { + name string + args struct { + ClassID string + } + expectErr bool + errorMsg string + expectResult uint64 + }{ + { + name: "class id is invalid", + args: struct { + ClassID string + }{ + ClassID: "invalid_class_id", + }, + expectErr: true, + errorMsg: "invalid class id", + expectResult: 0, + }, + { + name: "class id does not exist", + args: struct { + ClassID string + }{ + ClassID: "class", + }, + expectErr: false, + expectResult: 0, + }, + { + name: "class id exist", + args: struct { + ClassID string + }{ + ClassID: testClassID, + }, + expectErr: false, + expectResult: 1, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + resp, err := ExecQuerySupply(val, tc.args.ClassID) + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + var result nft.QuerySupplyResponse + err = val.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &result) + s.Require().NoError(err) + s.Require().EqualValues(tc.expectResult, result.Amount) + } + }) + } +} diff --git a/x/nft/client/testutil/test_helper.go b/x/nft/client/testutil/test_helper.go new file mode 100644 index 000000000..a3f43045c --- /dev/null +++ b/x/nft/client/testutil/test_helper.go @@ -0,0 +1,84 @@ +package testutil + +import ( + "fmt" + + tmcli "github.com/tendermint/tendermint/libs/cli" + + "github.com/cosmos/cosmos-sdk/testutil" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/cosmos/cosmos-sdk/x/nft/client/cli" +) + +func ExecSend(val *network.Validator, args []string) (testutil.BufferWriter, error) { + cmd := cli.NewCmdSend() + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQueryClass(val *network.Validator, classID string) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQueryClass() + var args []string + args = append(args, classID) + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQueryClasses(val *network.Validator) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQueryClasses() + var args []string + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQueryNFT(val *network.Validator, classID, nftID string) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQueryNFT() + var args []string + args = append(args, classID) + args = append(args, nftID) + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQueryNFTs(val *network.Validator, classID string) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQueryNFTs() + var args []string + args = append(args, classID) + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQueryNFTsByOwner(val *network.Validator, classID, owner string) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQueryNFTs() + var args []string + args = append(args, classID) + args = append(args, fmt.Sprintf("--%s=%s", cli.FlagOwner, owner)) + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQueryOwner(val *network.Validator, classID, nftID string) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQueryOwner() + var args []string + args = append(args, classID) + args = append(args, nftID) + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQueryBalance(val *network.Validator, classID, owner string) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQueryBalance() + var args []string + args = append(args, owner) + args = append(args, classID) + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} + +func ExecQuerySupply(val *network.Validator, classID string) (testutil.BufferWriter, error) { + cmd := cli.GetCmdQuerySupply() + var args []string + args = append(args, classID) + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + return clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) +} diff --git a/x/nft/client/testutil/tx.go b/x/nft/client/testutil/tx.go new file mode 100644 index 000000000..e95bbf6b1 --- /dev/null +++ b/x/nft/client/testutil/tx.go @@ -0,0 +1,166 @@ +package testutil + +import ( + "fmt" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil" + + "github.com/cosmos/cosmos-sdk/x/nft" +) + +const ( + OwnerName = "owner" + Owner = "cosmos1kznrznww4pd6gx0zwrpthjk68fdmqypjpkj5hp" + OwnerArmor = `-----BEGIN TENDERMINT PRIVATE KEY----- +salt: C3586B75587D2824187D2CDA22B6AFB6 +type: secp256k1 +kdf: bcrypt + +1+15OrCKgjnwym1zO3cjo/SGe3PPqAYChQ5wMHjdUbTZM7mWsH3/ueL6swgjzI3b +DDzEQAPXBQflzNW6wbne9IfT651zCSm+j1MWaGk= +=wEHs +-----END TENDERMINT PRIVATE KEY-----` + + testClassID = "kitty" + testClassName = "Crypto Kitty" + testClassSymbol = "kitty" + testClassDescription = "Crypto Kitty" + testClassURI = "class uri" + testID = "kitty1" + testURI = "kitty uri" +) + +var ( + ExpClass = nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + } + + ExpNFT = nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + } +) + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + owner sdk.AccAddress +} + +func NewIntegrationTestSuite(cfg network.Config) *IntegrationTestSuite { + return &IntegrationTestSuite{cfg: cfg} +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + genesisState := s.cfg.GenesisState + nftGenesis := nft.GenesisState{ + Classes: []*nft.Class{&ExpClass}, + Entries: []*nft.Entry{{ + Owner: Owner, + Nfts: []*nft.NFT{&ExpNFT}, + }}, + } + + nftDataBz, err := s.cfg.Codec.MarshalJSON(&nftGenesis) + s.Require().NoError(err) + genesisState[nft.ModuleName] = nftDataBz + s.cfg.GenesisState = genesisState + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + s.initAccount() + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *IntegrationTestSuite) TestCLITxSend() { + val := s.network.Validators[0] + args := []string{ + fmt.Sprintf("--%s=%s", flags.FlagFrom, OwnerName), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + testCases := []struct { + name string + args []string + expectedCode uint32 + expectErr bool + }{ + { + "valid transaction", + []string{ + testClassID, + testID, + val.Address.String(), + }, + 0, + false, + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + clientCtx := val.ClientCtx + args = append(args, tc.args...) + out, err := ExecSend( + val, + args, + ) + if tc.expectErr { + s.Require().Error(err) + } else { + var txResp sdk.TxResponse + s.Require().NoError(err) + s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), &txResp), out.String()) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) initAccount() { + val := s.network.Validators[0] + ctx := val.ClientCtx + err := ctx.Keyring.ImportPrivKey(OwnerName, OwnerArmor, "1234567890") + s.Require().NoError(err) + + keyinfo, err := ctx.Keyring.Key(OwnerName) + s.Require().NoError(err) + + args := []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + s.owner, err = keyinfo.GetAddress() + s.Require().NoError(err) + + amount := sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(200))) + _, err = banktestutil.MsgSendExec(ctx, val.Address, s.owner, amount, args...) + s.Require().NoError(err) +} diff --git a/x/nft/module/module.go b/x/nft/module/module.go index c68c5d28b..f1cd27b53 100644 --- a/x/nft/module/module.go +++ b/x/nft/module/module.go @@ -17,6 +17,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/nft" + "github.com/cosmos/cosmos-sdk/x/nft/client/cli" "github.com/cosmos/cosmos-sdk/x/nft/keeper" ) @@ -39,6 +40,7 @@ func (AppModuleBasic) Name() string { // module-specific gRPC queries. func (am AppModule) RegisterServices(cfg module.Configurator) { nft.RegisterMsgServer(cfg.MsgServer(), am.keeper) + nft.RegisterQueryServer(cfg.QueryServer(), am.keeper) } // RegisterLegacyAminoCodec registers the nft module's types for the given codec. @@ -71,17 +73,19 @@ func (AppModuleBasic) RegisterRESTRoutes(clientCtx sdkclient.Context, r *mux.Rou // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the nft module. func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) { - nft.RegisterQueryHandlerClient(context.Background(), mux, nft.NewQueryClient(clientCtx)) + if err := nft.RegisterQueryHandlerClient(context.Background(), mux, nft.NewQueryClient(clientCtx)); err != nil { + panic(err) + } } // GetQueryCmd returns the cli query commands for the nft module func (AppModuleBasic) GetQueryCmd() *cobra.Command { - return nil + return cli.GetQueryCmd() } // GetTxCmd returns the transaction commands for the nft module func (AppModuleBasic) GetTxCmd() *cobra.Command { - return nil + return cli.GetTxCmd() } // AppModule implements the sdk.AppModule interface diff --git a/x/nft/query.pb.go b/x/nft/query.pb.go index 662288849..b25a1c9e4 100644 --- a/x/nft/query.pb.go +++ b/x/nft/query.pb.go @@ -739,50 +739,50 @@ var fileDescriptor_0d24e0db697b0f9d = []byte{ // 734 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0x4f, 0x4f, 0x13, 0x4f, 0x18, 0xc7, 0x99, 0x96, 0xd2, 0xdf, 0xef, 0x21, 0xf1, 0xcf, 0x48, 0xa4, 0x54, 0xdd, 0x90, 0x45, - 0xda, 0x42, 0x65, 0x96, 0x3f, 0x89, 0x27, 0xf4, 0x80, 0xb1, 0xc6, 0x0b, 0x68, 0xe5, 0x64, 0x62, - 0xcc, 0xb6, 0xdd, 0xd6, 0x8d, 0x65, 0x67, 0x61, 0xb6, 0x2a, 0x21, 0x1c, 0xe4, 0x60, 0xf4, 0x46, + 0xda, 0x42, 0x65, 0x86, 0x3f, 0x89, 0x27, 0xf4, 0x80, 0xb1, 0xc6, 0x0b, 0x68, 0xe5, 0x64, 0x62, + 0xcc, 0xb6, 0xdd, 0xd6, 0x8d, 0x65, 0x77, 0x61, 0xb6, 0x2a, 0x21, 0x1c, 0xe4, 0x60, 0xf4, 0x46, 0x14, 0xdf, 0x93, 0x47, 0x12, 0x2f, 0xde, 0x34, 0xe0, 0x0b, 0x31, 0x3b, 0xf3, 0x6c, 0xd9, 0x95, - 0xed, 0x6e, 0x43, 0x3c, 0x91, 0xd9, 0xf9, 0x3e, 0xcf, 0xf7, 0x33, 0xf3, 0x3c, 0xf3, 0x50, 0xd0, - 0x9a, 0x5c, 0x6c, 0x71, 0x61, 0x38, 0x6d, 0xcf, 0x78, 0xb3, 0xd4, 0xb0, 0x3c, 0x73, 0xc9, 0xd8, - 0xee, 0x59, 0x3b, 0xbb, 0xcc, 0xdd, 0xe1, 0x1e, 0xa7, 0x54, 0xed, 0x33, 0xa7, 0xed, 0x31, 0xdc, - 0x2f, 0xce, 0x63, 0x4c, 0xc3, 0x14, 0x96, 0x12, 0xf7, 0x43, 0x5d, 0xb3, 0x63, 0x3b, 0xa6, 0x67, - 0x73, 0x47, 0xc5, 0x17, 0x6f, 0x76, 0x38, 0xef, 0x74, 0x2d, 0xc3, 0x74, 0x6d, 0xc3, 0x74, 0x1c, - 0xee, 0xc9, 0x4d, 0x11, 0xec, 0xc6, 0xb8, 0xfb, 0x4e, 0x72, 0x57, 0xaf, 0xc1, 0xb5, 0xa7, 0x7e, - 0xf6, 0x35, 0xb3, 0x6b, 0x3a, 0x4d, 0xab, 0x6e, 0x6d, 0xf7, 0x2c, 0xe1, 0xd1, 0x29, 0xf8, 0xaf, - 0xd9, 0x35, 0x85, 0x78, 0x69, 0xb7, 0x0a, 0x64, 0x9a, 0x54, 0xfe, 0xaf, 0xe7, 0xe5, 0xfa, 0x71, - 0x8b, 0x4e, 0x40, 0x8e, 0xbf, 0x75, 0xac, 0x9d, 0x42, 0x46, 0x7e, 0x57, 0x0b, 0x9d, 0xc1, 0x44, - 0x34, 0x8f, 0x70, 0xb9, 0x23, 0x2c, 0x7a, 0x1d, 0xc6, 0xcc, 0x2d, 0xde, 0x73, 0x3c, 0x99, 0x66, - 0xb4, 0x8e, 0x2b, 0xfd, 0x3e, 0x5c, 0x95, 0xfa, 0x0d, 0x3f, 0x7a, 0x08, 0xd7, 0x4b, 0x90, 0xb1, - 0x5b, 0x68, 0x99, 0xb1, 0x5b, 0xfa, 0x3c, 0xd0, 0x70, 0x3c, 0xba, 0xf5, 0xd9, 0x48, 0x98, 0xcd, - 0x40, 0xed, 0xb3, 0x9e, 0xeb, 0x76, 0x77, 0xd3, 0xcd, 0xf4, 0x05, 0xbc, 0x94, 0x20, 0x20, 0xe5, - 0x2c, 0x9f, 0x09, 0x4c, 0x4a, 0xfd, 0x7a, 0x6d, 0x53, 0x6c, 0xb4, 0x1f, 0xf8, 0x59, 0x2e, 0x7a, - 0x91, 0xb4, 0x06, 0x70, 0x56, 0xe0, 0x42, 0x76, 0x9a, 0x54, 0xc6, 0x97, 0x4b, 0x0c, 0x3b, 0xc4, - 0xef, 0x06, 0xa6, 0x5a, 0x07, 0x4b, 0xc9, 0x9e, 0x98, 0x9d, 0xa0, 0x6a, 0xf5, 0x50, 0xa4, 0x7e, - 0x48, 0xa0, 0x70, 0x1e, 0x0a, 0x4f, 0x52, 0x85, 0x51, 0xa7, 0xed, 0x89, 0x02, 0x99, 0xce, 0x56, - 0xc6, 0x97, 0x27, 0xd9, 0xf9, 0x06, 0x64, 0xeb, 0xb5, 0xcd, 0xba, 0x14, 0xd1, 0x47, 0x11, 0xa2, - 0x8c, 0x24, 0x2a, 0xa7, 0x12, 0x29, 0xa7, 0x08, 0xd2, 0x2a, 0x5c, 0x0e, 0x88, 0x2e, 0x50, 0xf1, - 0x7b, 0x70, 0xe5, 0x2c, 0x1a, 0xcf, 0x31, 0x07, 0x59, 0xa7, 0xad, 0xca, 0x91, 0x70, 0x0c, 0x5f, - 0xa3, 0x33, 0x6c, 0xb8, 0x21, 0xab, 0xa3, 0x3f, 0xc4, 0xa6, 0x89, 0x5e, 0x9c, 0x01, 0x39, 0x29, - 0x40, 0xcb, 0xa9, 0x38, 0x4b, 0x15, 0xa1, 0x74, 0xfa, 0x0b, 0x6c, 0x25, 0xf9, 0xd1, 0xea, 0x1b, - 0x47, 0xab, 0x4c, 0x2e, 0x5c, 0xe5, 0x23, 0x82, 0xef, 0xae, 0x9f, 0x1f, 0x41, 0x57, 0x40, 0x9d, - 0xc4, 0x0a, 0x8a, 0x9c, 0x80, 0x1a, 0x28, 0xff, 0x59, 0xa5, 0x97, 0x7f, 0xe6, 0x21, 0x27, 0xb1, - 0xe8, 0x11, 0x81, 0x3c, 0xce, 0x04, 0x5a, 0x8e, 0x43, 0x88, 0x99, 0x3e, 0xc5, 0x4a, 0xba, 0x50, - 0x99, 0xea, 0x77, 0x0f, 0xbe, 0xff, 0xfe, 0x92, 0x59, 0xa4, 0xcc, 0x88, 0x99, 0x72, 0x0d, 0x25, - 0x36, 0xf6, 0x82, 0x1a, 0xef, 0x1b, 0x7b, 0xf2, 0x91, 0xed, 0xd3, 0x4f, 0x04, 0x72, 0x72, 0x74, - 0xd0, 0xd9, 0x81, 0x5e, 0xe1, 0xd1, 0x54, 0x2c, 0xa5, 0xc9, 0x10, 0x68, 0x49, 0x02, 0x55, 0xe9, - 0x5c, 0x1c, 0x90, 0x34, 0x8f, 0xe0, 0xd8, 0xad, 0x7d, 0xfa, 0x91, 0xc0, 0x98, 0x9a, 0x34, 0x74, - 0xb0, 0x4b, 0x64, 0x76, 0x15, 0xcb, 0xa9, 0x3a, 0xc4, 0x59, 0x90, 0x38, 0x65, 0x3a, 0x1b, 0x87, - 0x23, 0xa4, 0x36, 0xc4, 0x43, 0xbf, 0x12, 0x18, 0x0f, 0xcd, 0x0b, 0x5a, 0x1d, 0xe8, 0x73, 0x7e, - 0xd4, 0x15, 0xef, 0x0c, 0x27, 0x46, 0xb2, 0xaa, 0x24, 0x9b, 0xa5, 0x33, 0x46, 0xfc, 0xff, 0x27, - 0x11, 0xe6, 0x3a, 0x20, 0x90, 0x5d, 0xaf, 0x6d, 0xd2, 0x99, 0x24, 0x8b, 0x80, 0xe3, 0x76, 0xb2, - 0x08, 0xfd, 0x17, 0xa5, 0xff, 0x3c, 0xad, 0x0c, 0xe1, 0xaf, 0xea, 0xf4, 0x81, 0x40, 0x4e, 0x5d, - 0xcb, 0xe0, 0x9e, 0x89, 0x5c, 0x48, 0x29, 0x4d, 0x86, 0x28, 0x4c, 0xa2, 0x54, 0x68, 0x29, 0x0e, - 0x05, 0xdf, 0x66, 0xf8, 0x36, 0xde, 0x13, 0xc8, 0xe3, 0x7b, 0x4f, 0x78, 0x53, 0xd1, 0x89, 0x93, - 0xf0, 0xa6, 0xfe, 0x1a, 0x1d, 0xfa, 0x8c, 0xc4, 0xb9, 0x45, 0x6f, 0x24, 0xe0, 0xac, 0xad, 0x7e, - 0x3b, 0xd1, 0xc8, 0xf1, 0x89, 0x46, 0x7e, 0x9d, 0x68, 0xe4, 0xf0, 0x54, 0x1b, 0x39, 0x3e, 0xd5, - 0x46, 0x7e, 0x9c, 0x6a, 0x23, 0xcf, 0xf5, 0x8e, 0xed, 0xbd, 0xea, 0x35, 0x58, 0x93, 0x6f, 0x05, - 0x09, 0xd4, 0x9f, 0x05, 0xd1, 0x7a, 0x6d, 0xbc, 0xf3, 0xb3, 0x35, 0xc6, 0xe4, 0x8f, 0x8f, 0x95, - 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xcd, 0x08, 0xc1, 0xd7, 0x1a, 0x09, 0x00, 0x00, + 0xed, 0x6e, 0x43, 0x3c, 0x91, 0xd9, 0xf9, 0x3e, 0xf3, 0xfd, 0xcc, 0xf3, 0x3c, 0xf3, 0x50, 0xd0, + 0x9a, 0x8e, 0xd8, 0x72, 0x04, 0xb7, 0xdb, 0x1e, 0x7f, 0xb3, 0xd4, 0x30, 0x3d, 0x63, 0x89, 0x6f, + 0xf7, 0xcc, 0x9d, 0x5d, 0xe6, 0xee, 0x38, 0x9e, 0x43, 0xa9, 0xda, 0x67, 0x76, 0xdb, 0x63, 0xb8, + 0x5f, 0x9c, 0xc7, 0x98, 0x86, 0x21, 0x4c, 0x25, 0xee, 0x87, 0xba, 0x46, 0xc7, 0xb2, 0x0d, 0xcf, + 0x72, 0x6c, 0x15, 0x5f, 0xbc, 0xd9, 0x71, 0x9c, 0x4e, 0xd7, 0xe4, 0x86, 0x6b, 0x71, 0xc3, 0xb6, + 0x1d, 0x4f, 0x6e, 0x8a, 0x60, 0x37, 0xc6, 0xdd, 0x77, 0x92, 0xbb, 0x7a, 0x0d, 0xae, 0x3d, 0xf5, + 0x4f, 0x5f, 0x33, 0xba, 0x86, 0xdd, 0x34, 0xeb, 0xe6, 0x76, 0xcf, 0x14, 0x1e, 0x9d, 0x82, 0xff, + 0x9a, 0x5d, 0x43, 0x88, 0x97, 0x56, 0xab, 0x40, 0xa6, 0x49, 0xe5, 0xff, 0x7a, 0x5e, 0xae, 0x1f, + 0xb7, 0xe8, 0x04, 0xe4, 0x9c, 0xb7, 0xb6, 0xb9, 0x53, 0xc8, 0xc8, 0xef, 0x6a, 0xa1, 0x33, 0x98, + 0x88, 0x9e, 0x23, 0x5c, 0xc7, 0x16, 0x26, 0xbd, 0x0e, 0x63, 0xc6, 0x96, 0xd3, 0xb3, 0x3d, 0x79, + 0xcc, 0x68, 0x1d, 0x57, 0xfa, 0x7d, 0xb8, 0x2a, 0xf5, 0x1b, 0x7e, 0xf4, 0x10, 0xae, 0x97, 0x20, + 0x63, 0xb5, 0xd0, 0x32, 0x63, 0xb5, 0xf4, 0x79, 0xa0, 0xe1, 0x78, 0x74, 0xeb, 0xb3, 0x91, 0x30, + 0x1b, 0x47, 0xed, 0xb3, 0x9e, 0xeb, 0x76, 0x77, 0xd3, 0xcd, 0xf4, 0x05, 0x4c, 0x4a, 0x10, 0x90, + 0x72, 0x97, 0xcf, 0x04, 0x26, 0xa5, 0x7e, 0xbd, 0xb6, 0x29, 0x36, 0xda, 0x0f, 0xfc, 0x53, 0x2e, + 0x9a, 0x48, 0x5a, 0x03, 0x38, 0x2b, 0x70, 0x21, 0x3b, 0x4d, 0x2a, 0xe3, 0xcb, 0x25, 0x86, 0x1d, + 0xe2, 0x77, 0x03, 0x53, 0xad, 0x83, 0xa5, 0x64, 0x4f, 0x8c, 0x4e, 0x50, 0xb5, 0x7a, 0x28, 0x52, + 0x3f, 0x24, 0x50, 0x38, 0x0f, 0x85, 0x37, 0xa9, 0xc2, 0xa8, 0xdd, 0xf6, 0x44, 0x81, 0x4c, 0x67, + 0x2b, 0xe3, 0xcb, 0x93, 0xec, 0x7c, 0x03, 0xb2, 0xf5, 0xda, 0x66, 0x5d, 0x8a, 0xe8, 0xa3, 0x08, + 0x51, 0x46, 0x12, 0x95, 0x53, 0x89, 0x94, 0x53, 0x04, 0x69, 0x15, 0x2e, 0x07, 0x44, 0x17, 0xa8, + 0xf8, 0x3d, 0xb8, 0x72, 0x16, 0x8d, 0xf7, 0x98, 0x83, 0xac, 0xdd, 0x56, 0xe5, 0x48, 0xb8, 0x86, + 0xaf, 0xd1, 0x19, 0x36, 0xdc, 0x90, 0xd5, 0xd1, 0x1f, 0x62, 0xd3, 0x44, 0x13, 0xc7, 0x21, 0x27, + 0x05, 0x68, 0x39, 0x15, 0x67, 0xa9, 0x22, 0x94, 0x4e, 0x7f, 0x81, 0xad, 0x24, 0x3f, 0x9a, 0x7d, + 0xe3, 0x68, 0x95, 0xc9, 0x85, 0xab, 0x7c, 0x44, 0xf0, 0xdd, 0xf5, 0xcf, 0x47, 0xd0, 0x15, 0x50, + 0x37, 0x31, 0x83, 0x22, 0x27, 0xa0, 0x06, 0xca, 0x7f, 0x56, 0xe9, 0xe5, 0x9f, 0x79, 0xc8, 0x49, + 0x2c, 0x7a, 0x44, 0x20, 0x8f, 0x33, 0x81, 0x96, 0xe3, 0x10, 0x62, 0xa6, 0x4f, 0xb1, 0x92, 0x2e, + 0x54, 0xa6, 0xfa, 0xdd, 0x83, 0xef, 0xbf, 0xbf, 0x64, 0x16, 0x29, 0xe3, 0x31, 0x53, 0xae, 0xa1, + 0xc4, 0x7c, 0x4f, 0xbe, 0xac, 0x7d, 0xbe, 0x17, 0xd4, 0x7a, 0x9f, 0x7e, 0x22, 0x90, 0x93, 0xa3, + 0x83, 0xce, 0x0e, 0xf4, 0x0a, 0x8f, 0xa6, 0x62, 0x29, 0x4d, 0x86, 0x40, 0x4b, 0x12, 0xa8, 0x4a, + 0xe7, 0xe2, 0x80, 0x24, 0x47, 0x08, 0x83, 0xef, 0xf9, 0x2c, 0x1f, 0x09, 0x8c, 0xa9, 0x49, 0x43, + 0x07, 0xbb, 0x44, 0x66, 0x57, 0xb1, 0x9c, 0xaa, 0x43, 0x9c, 0x05, 0x89, 0x53, 0xa6, 0xb3, 0x71, + 0x38, 0x42, 0x6a, 0xc3, 0x69, 0xf9, 0x4a, 0x60, 0x3c, 0x34, 0x2f, 0x68, 0x75, 0xa0, 0xcf, 0xf9, + 0x51, 0x57, 0xbc, 0x33, 0x9c, 0x18, 0xc9, 0xaa, 0x92, 0x6c, 0x96, 0xce, 0xf0, 0xf8, 0xff, 0x4f, + 0x22, 0xcc, 0x75, 0x40, 0x20, 0xbb, 0x5e, 0xdb, 0xa4, 0x33, 0x49, 0x16, 0x01, 0xc7, 0xed, 0x64, + 0x11, 0xfa, 0x2f, 0x4a, 0xff, 0x79, 0x5a, 0x19, 0xc2, 0x5f, 0xd5, 0xe9, 0x03, 0x81, 0x9c, 0x4a, + 0xcb, 0xe0, 0x9e, 0x89, 0x24, 0xa4, 0x94, 0x26, 0x43, 0x14, 0x26, 0x51, 0x2a, 0xb4, 0x14, 0x87, + 0x82, 0x6f, 0x33, 0x9c, 0x8d, 0xf7, 0x04, 0xf2, 0xf8, 0xde, 0x13, 0xde, 0x54, 0x74, 0xe2, 0x24, + 0xbc, 0xa9, 0xbf, 0x46, 0x87, 0x3e, 0x23, 0x71, 0x6e, 0xd1, 0x1b, 0x09, 0x38, 0x6b, 0xab, 0xdf, + 0x4e, 0x34, 0x72, 0x7c, 0xa2, 0x91, 0x5f, 0x27, 0x1a, 0x39, 0x3c, 0xd5, 0x46, 0x8e, 0x4f, 0xb5, + 0x91, 0x1f, 0xa7, 0xda, 0xc8, 0x73, 0xbd, 0x63, 0x79, 0xaf, 0x7a, 0x0d, 0xd6, 0x74, 0xb6, 0x82, + 0x03, 0xd4, 0x9f, 0x05, 0xd1, 0x7a, 0xcd, 0xdf, 0xf9, 0xa7, 0x35, 0xc6, 0xe4, 0x8f, 0x8f, 0x95, + 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xed, 0xff, 0x2d, 0x1a, 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/nft/query.pb.gw.go b/x/nft/query.pb.gw.go index 4121fae00..67a634117 100644 --- a/x/nft/query.pb.gw.go +++ b/x/nft/query.pb.gw.go @@ -42,17 +42,6 @@ func request_Query_Balance_0(ctx context.Context, marshaler runtime.Marshaler, c _ = err ) - val, ok = pathParams["class_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "class_id") - } - - protoReq.ClassId, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "class_id", err) - } - val, ok = pathParams["owner"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "owner") @@ -64,6 +53,17 @@ func request_Query_Balance_0(ctx context.Context, marshaler runtime.Marshaler, c return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "owner", err) } + val, ok = pathParams["class_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "class_id") + } + + protoReq.ClassId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "class_id", err) + } + msg, err := client.Balance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err @@ -80,17 +80,6 @@ func local_request_Query_Balance_0(ctx context.Context, marshaler runtime.Marsha _ = err ) - val, ok = pathParams["class_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "class_id") - } - - protoReq.ClassId, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "class_id", err) - } - val, ok = pathParams["owner"] if !ok { return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "owner") @@ -102,6 +91,17 @@ func local_request_Query_Balance_0(ctx context.Context, marshaler runtime.Marsha return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "owner", err) } + val, ok = pathParams["class_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "class_id") + } + + protoReq.ClassId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "class_id", err) + } + msg, err := server.Balance(ctx, &protoReq) return msg, metadata, err @@ -806,7 +806,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } var ( - pattern_Query_Balance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"cosmos", "nft", "v1beta1", "balance", "class_id", "owner"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_Balance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"cosmos", "nft", "v1beta1", "balance", "owner", "class_id"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_Owner_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"cosmos", "nft", "v1beta1", "owner", "class_id", "id"}, "", runtime.AssumeColonVerbOpt(false))) diff --git a/x/nft/validation.go b/x/nft/validation.go index 9ff72431a..189b6621e 100644 --- a/x/nft/validation.go +++ b/x/nft/validation.go @@ -9,12 +9,12 @@ import ( var ( // reClassIDString can be 3 ~ 100 characters long and support letters, followed by either - // a letter, a number or a slash ('/') or a colon (':'). - reClassIDString = `[a-zA-Z][a-zA-Z0-9/-:]{2,100}` + // a letter, a number or a slash ('/') or a colon (':') or ('-'). + reClassIDString = `[a-zA-Z][a-zA-Z0-9/:-]{2,100}` reClassID = regexp.MustCompile(fmt.Sprintf(`^%s$`, reClassIDString)) // reNFTIDString can be 3 ~ 100 characters long and support letters, followed by either - // a letter, a number or a slash ('/') or a colon (':'). + // a letter, a number or a slash ('/') or a colon (':') or ('-'). reNFTID = reClassID )