Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NFT Sample Implementation #4118

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions x/nft/client/cli/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cli

import (
"github.com/spf13/viper"

"github.com/cosmos/cosmos-sdk/x/nfts"
)

const (
flagName = "name"
flagDescription = "description"
flagImage = "image"
flagTokenURI = "tokenURI"
)

func parseEditMetadataFlags() (nfts.MsgEditNFTMetadata, error) {
msg := nfts.MsgEditNFTMetadata{}

name := viper.GetString(flagName)
if name != "" {
msg.EditName = true
msg.Name = name
}

description := viper.GetString(flagDescription)
if description != "" {
msg.EditDescription = true
msg.Description = description
}

image := viper.GetString(flagImage)
if image != "" {
msg.EditImage = true
msg.Image = image
}

tokenURI := viper.GetString(flagTokenURI)
if tokenURI != "" {
msg.EditTokenURI = true
msg.TokenURI = tokenURI
}

return msg, nil
}
89 changes: 89 additions & 0 deletions x/nft/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cli

import (
"fmt"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/nfts"
"github.com/spf13/cobra"
)

// QueryBalanceOf = "balanceOf"
// QueryOwnerOf = "ownerOf"
// QueryMetadata = "metadata"

// GetCmdQueryBalanceOf queries balance of an account per some denom
func GetCmdQueryBalanceOf(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "balanceOf [denom] [accountAddress]",
Short: "balanceOf denom accountAddress",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
denom := args[0]
account := args[1]

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/balanceOf/%s/%s", queryRoute, denom, account), nil)
if err != nil {
fmt.Printf("could not query %s balance of account %s \n", denom, account)
fmt.Print(err.Error())
return nil
}

var out nfts.QueryResBalance
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}

// GetCmdQueryOwnerOf queries owner of an nft per some denom
func GetCmdQueryOwnerOf(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "ownerOf [denom] [tokenID]",
Short: "ownerOf denom tokenID",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
denom := args[0]
tokenID := args[1]

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/ownerOf/%s/%s", queryRoute, denom, tokenID), nil)
if err != nil {
fmt.Printf("could not query owner of %s #%s\n", denom, tokenID)
fmt.Print(err.Error())
return nil
}

var out nfts.QueryResOwnerOf
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}

// GetCmdQueryMetadata queries owner of an nft per some denom
func GetCmdQueryMetadata(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "metadata [denom] [tokenID]",
Short: "metadata denom tokenID",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
denom := args[0]
tokenID := args[1]

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/metadata/%s/%s", queryRoute, denom, tokenID), nil)
if err != nil {
fmt.Printf("could not query metadata of %s #%s\n", denom, tokenID)
fmt.Print(err.Error())
return nil
}

var out nfts.NFT
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
184 changes: 184 additions & 0 deletions x/nft/client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package cli

import (
"strconv"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
"github.com/cosmos/cosmos-sdk/x/nfts"
"github.com/spf13/cobra"
)

// GetCmdTransferNFT is the CLI command for sending a TransferNFT transaction
func GetCmdTransferNFT(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "transfer-nft [recipient] [denom] [tokenID]",
Short: "transfer a token of some denom with some tokenID to some recipient",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)

txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))

// TODO: Does this need to be true? What does it mean to have an account that doesn't exist?
// If it just means having a balance in some token then no, an account doens't need to "exist".
if err := cliCtx.EnsureAccountExists(); err != nil {
return err
}

tokenID, err := strconv.ParseUint(args[2], 10, 64)
if err != nil {
return err
}

msg := nfts.NewMsgTransferNFT(cliCtx.GetFromAddress(), sdk.AccAddress(args[0]), nfts.Denom(args[1]), nfts.TokenID(tokenID))
err = msg.ValidateBasic()
if err != nil {
return err
}

cliCtx.PrintResponse = true

return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
},
}
}

// GetCmdEditNFTMetadata is the CLI command for a EditNFTMetadata transaction
func GetCmdEditNFTMetadata(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "edit-nft-metadata [denom] [tokenID]",
Short: "transfer a token of some denom with some tokenID to some recipient",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {

msg, err := parseEditMetadataFlags()
if err != nil {
return err
}

cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)
txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))

tokenID, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
return err
}

msg = nfts.NewMsgEditNFTMetadata(cliCtx.GetFromAddress(),
nfts.Denom(args[0]),
nfts.TokenID(tokenID),
msg.EditName,
msg.EditDescription,
msg.EditImage,
msg.EditTokenURI,
msg.Name,
msg.Description,
msg.Image,
msg.TokenURI,
)
err = msg.ValidateBasic()
if err != nil {
return err
}

cliCtx.PrintResponse = true

return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
},
}

cmd.Flags().String(flagName, "", "Name/title of nft")
cmd.Flags().String(flagDescription, "", "Description of nft")
cmd.Flags().String(flagImage, "", "Image uri of nft")
cmd.Flags().String(flagTokenURI, "", "URI for supplemental off-chain metadata (should return a JSON object)")

return cmd
}

// GetCmdMintNFT is the CLI command for a MintNFT transaction
func GetCmdMintNFT(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "mint-nft [recipient] [denom] [tokenID]",
Short: "mints a token of some denom with some tokenID to some recipient",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {

metadata, err := parseEditMetadataFlags()
if err != nil {
return err
}

cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)
txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))

tokenID, err := strconv.ParseUint(args[2], 10, 64)
if err != nil {
return err
}

msg := nfts.NewMsgMintNFT(cliCtx.GetFromAddress(),
sdk.AccAddress(args[0]),
nfts.TokenID(tokenID),
nfts.Denom(args[1]),
metadata.Name,
metadata.Description,
metadata.Image,
metadata.TokenURI,
)
err = msg.ValidateBasic()
if err != nil {
return err
}

cliCtx.PrintResponse = true

return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
},
}

cmd.Flags().String(flagName, "", "Name/title of nft")
cmd.Flags().String(flagDescription, "", "Description of nft")
cmd.Flags().String(flagImage, "", "Image uri of nft")
cmd.Flags().String(flagTokenURI, "", "URI for supplemental off-chain metadata (should return a JSON object)")

return cmd
}

// GetCmdBurnNFT is the CLI command for sending a BurnNFT transaction
func GetCmdBurnNFT(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "burn-nft [denom] [tokenID]",
Short: "burn a token of some denom with some tokenID",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)

txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))

// TODO: Does this need to be true? What does it mean to have an account that doesn't exist?
// If it just means having a balance in some token then no, an account doens't need to "exist".
if err := cliCtx.EnsureAccountExists(); err != nil {
return err
}

tokenID, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
return err
}

msg := nfts.NewMsgBurnNFT(cliCtx.GetFromAddress(), nfts.TokenID(tokenID), nfts.Denom(args[1]))
err = msg.ValidateBasic()
if err != nil {
return err
}

cliCtx.PrintResponse = true

return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
},
}
}
54 changes: 54 additions & 0 deletions x/nft/client/module_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package client

import (
"github.com/cosmos/cosmos-sdk/client"
nftcmd "github.com/cosmos/cosmos-sdk/x/nfts/client/cli"
"github.com/spf13/cobra"

amino "github.com/tendermint/go-amino"
)

// ModuleClient exports all client functionality from this module
type ModuleClient struct {
storeKey string
cdc *amino.Codec
}

// NewModuleClient creates a new module client
func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
return ModuleClient{storeKey, cdc}
}

// GetQueryCmd returns the cli query commands for this module
func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// Group nameservice queries under a subcommand
nftQueryCmd := &cobra.Command{
Use: "nft",
Short: "Querying commands for the NFT module",
}

nftQueryCmd.AddCommand(client.GetCommands(
nftcmd.GetCmdQueryBalanceOf(mc.storeKey, mc.cdc),
nftcmd.GetCmdQueryOwnerOf(mc.storeKey, mc.cdc),
nftcmd.GetCmdQueryMetadata(mc.storeKey, mc.cdc),
)...)

return nftQueryCmd
}

// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
nftTxCmd := &cobra.Command{
Use: "nft",
Short: "Non-FungibleToken transactions subcommands",
}

nftTxCmd.AddCommand(client.PostCommands(
nftcmd.GetCmdTransferNFT(mc.cdc),
nftcmd.GetCmdEditNFTMetadata(mc.cdc),
nftcmd.GetCmdMintNFT(mc.cdc),
nftcmd.GetCmdBurnNFT(mc.cdc),
)...)

return nftTxCmd
}
Loading