Nicolas Cantu
Apr 11 · 23 min read

I followed: https://github.com/cosmos/sdk-application-tutorial/tree/master/tutorial#sdk-application-tutorial

The environment I used

I am using VirtualBox with Windows:

Configuration:

  • OS type : Linux — Debian 64 Bit
  • Disk type : VDI 700 Go (dynamic)
  • Memory : 8 Go
  • Video memory: 16Mo
  • CD Rom: debian-testing-amd64-netinst.iso

Result:

Note:

If you need to extend the VDI size (Windows):

> “D:\Program Files\Oracle\VirtualBox\VBoxManage.exe” modifyhd “D:\Users\Chain Accelerator\VirtualBox VMs\wanchain\wanchain.vdi” — resize 700000"

My Debian testing (buster)

Install Debian:

  • without root password (sudo activated)
  • Default Debian interface
  • No other requirement

Debian first run:

chainacc@cosmos:~$ cd ~chainacc@cosmos:~$ sudo apt-get updatechainacc@cosmos:~$ sudo apt-get upgradechainacc@cosmos:~$ sudo apt-get install -y vim emacs ntpdate tree wget git curlchainacc@cosmos:~$ sudo ntpdate-debianchainacc@cosmos:~$ cd ~

Installing Cosmos prerequisites

chainacc@cosmos:~$ wget https://dl.google.com/go/go1.12.2.linux-amd64.tar.gzchainacc@cosmos:~$ sudo tar -xvf go1.12.2.linux-amd64.tar.gzchainacc@cosmos:~$ mkdir go

then, in ~./bashrc add this to the end:

export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH

and then you can:

source ~./bashrc to apply your changes.

My Cosmos project install

Before I start, please note this: iNukeLaPeste is my Github username. Everytime you see it, you have to change it with YOUR Github username.

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ mkdir -p $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git init
  • chainacc@cosmos:~$ cd ~

What we will do ?

The goal of the application you are building is to let users buy names and to set a value these names resolve to. The owner of a given name will be the current highest bidder. In this section, you will learn how these simple requirements translate to application design.

Start coding

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs app.go

Import requirements:

package app

import (
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/x/auth"

bam "github.com/cosmos/cosmos-sdk/baseapp"
dbm "github.com/tendermint/tendermint/libs/db"
)

Application config:

const (
appName = "nameservice"
)

type nameServiceApp struct {
*bam.BaseApp
}

Adding a constructor:

func NewNameServiceApp(logger log.Logger, db dbm.DB) *nameServiceApp {

// First define the top level codec that will be shared by the different modules. Note: Codec will be explained later
cdc := MakeCodec()

// BaseApp handles interactions with Tendermint through the ABCI protocol
bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc))

var app = &nameServiceApp{
BaseApp: bApp,
cdc: cdc,
}

return app
}

Adding a type Whois :

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ mkdir -p ./x/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/types.go
package nameservice

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// Whois is a struct that contains all the metadata of a name
type Whois struct {
Value string `json:"value"`
Owner sdk.AccAddress `json:"owner"`
Price sdk.Coins `json:"price"`
}
// Initial Starting Price for a name that was never previously owned
var MinNamePrice = sdk.Coins{sdk.NewInt64Coin("nametoken", 1)}

// Returns a new Whois with the minprice as the price
func NewWhois() Whois {
return Whois{
Price: MinNamePrice,
}
}

Adding a main part of the SDK :

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/keeper.go
package nameservice

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/bank"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// Keeper maintains the link to data storage and exposes getter/setter methods for the various parts of the state machine
type Keeper struct {
coinKeeper bank.Keeper

storeKey sdk.StoreKey // Unexposed key to access store from sdk.Context

cdc *codec.Codec // The wire codec for binary encoding/decoding.
}
// Sets the entire Whois metadata struct for a name
func (k Keeper) SetWhois(ctx sdk.Context, name string, whois Whois) {
if whois.Owner.Empty() {
return
}
store := ctx.KVStore(k.storeKey)
store.Set([]byte(name), k.cdc.MustMarshalBinaryBare(whois))
}
// Gets the entire Whois metadata struct for a name
func (k Keeper) GetWhois(ctx sdk.Context, name string) Whois {
store := ctx.KVStore(k.storeKey)
if !store.Has([]byte(name)) {
return NewWhois()
}
bz := store.Get([]byte(name))
var whois Whois
k.cdc.MustUnmarshalBinaryBare(bz, &whois)
return whois
}
// ResolveName - returns the string that the name resolves to
func (k Keeper) ResolveName(ctx sdk.Context, name string) string {
return k.GetWhois(ctx, name).Value
}

// SetName - sets the value string that a name resolves to
func (k Keeper) SetName(ctx sdk.Context, name string, value string) {
whois := k.GetWhois(ctx, name)
whois.Value = value
k.SetWhois(ctx, name, whois)
}

// HasOwner - returns whether or not the name already has an owner
func (k Keeper) HasOwner(ctx sdk.Context, name string) bool {
return !k.GetWhois(ctx, name).Owner.Empty()
}

// GetOwner - get the current owner of a name
func (k Keeper) GetOwner(ctx sdk.Context, name string) sdk.AccAddress {
return k.GetWhois(ctx, name).Owner
}

// SetOwner - sets the current owner of a name
func (k Keeper) SetOwner(ctx sdk.Context, name string, owner sdk.AccAddress) {
whois := k.GetWhois(ctx, name)
whois.Owner = owner
k.SetWhois(ctx, name, whois)
}

// GetPrice - gets the current price of a name. If price doesn't exist yet, set to 1nametoken.
func (k Keeper) GetPrice(ctx sdk.Context, name string) sdk.Coins {
return k.GetWhois(ctx, name).Price
}

// SetPrice - sets the current price of a name
func (k Keeper) SetPrice(ctx sdk.Context, name string, price sdk.Coins) {
whois := k.GetWhois(ctx, name)
whois.Price = price
k.SetWhois(ctx, name, whois)
}
// Get an iterator over all names in which the keys are the names and the values are the whois
func (k Keeper) GetNamesIterator(ctx sdk.Context) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return sdk.KVStorePrefixIterator(store, []byte{})
}
// NewKeeper creates new instances of the nameservice Keeper
func NewKeeper(coinKeeper bank.Keeper, storeKey sdk.StoreKey, cdc *codec.Codec) Keeper {
return Keeper{
coinKeeper: coinKeeper,
storeKey: storeKey,
cdc: cdc,
}
}

Adding Msg management :

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/msgs.go
package nameservice

import (
"encoding/json"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// MsgSetName defines a SetName message
type MsgSetName struct {
Name string
Value string
Owner sdk.AccAddress
}

// NewMsgSetName is a constructor function for MsgSetName
func NewMsgSetName(name string, value string, owner sdk.AccAddress) MsgSetName {
return MsgSetName{
Name: name,
Value: value,
Owner: owner,
}
}
// Route should return the name of the module
func (msg MsgSetName) Route() string { return "nameservice" }

// Type should return the action
func (msg MsgSetName) Type() string { return "set_name"}
// ValidateBasic runs stateless checks on the message
func (msg MsgSetName) ValidateBasic() sdk.Error {
if msg.Owner.Empty() {
return sdk.ErrInvalidAddress(msg.Owner.String())
}
if len(msg.Name) == 0 || len(msg.Value) == 0 {
return sdk.ErrUnknownRequest("Name and/or Value cannot be empty")
}
return nil
}
// GetSignBytes encodes the message for signing
func (msg MsgSetName) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(b)
}
// GetSigners defines whose signature is required
func (msg MsgSetName) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Owner}
}

Adding Handler :

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/handler.go
package nameservice

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// NewHandler returns a handler for "nameservice" type messages.
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgSetName:
return handleMsgSetName(ctx, keeper, msg)
default:
errMsg := fmt.Sprintf("Unrecognized nameservice Msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// Handle a message to set name
func handleMsgSetName(ctx sdk.Context, keeper Keeper, msg MsgSetName) sdk.Result {
if !msg.Owner.Equals(keeper.GetOwner(ctx, msg.Name)) { // Checks if the the msg sender is the same as the current owner
return sdk.ErrUnauthorized("Incorrect Owner").Result() // If not, throw an error
}
keeper.SetName(ctx, msg.Name, msg.Value) // If so, set the name to the value specified in the msg.
return sdk.Result{} // return
}

Specific Msgs :

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/msgs.go
// MsgBuyName defines the BuyName message
type MsgBuyName struct {
Name string
Bid sdk.Coins
Buyer sdk.AccAddress
}

// NewMsgBuyName is the constructor function for MsgBuyName
func NewMsgBuyName(name string, bid sdk.Coins, buyer sdk.AccAddress) MsgBuyName {
return MsgBuyName{
Name: name,
Bid: bid,
Buyer: buyer,
}
}

// Route should return the name of the module
func (msg MsgBuyName) Route() string { return "nameservice" }

// Type should return the action
func (msg MsgBuyName) Type() string { return "buy_name" }

// ValidateBasic runs stateless checks on the message
func (msg MsgBuyName) ValidateBasic() sdk.Error {
if msg.Buyer.Empty() {
return sdk.ErrInvalidAddress(msg.Buyer.String())
}
if len(msg.Name) == 0 {
return sdk.ErrUnknownRequest("Name cannot be empty")
}
if !msg.Bid.IsAllPositive() {
return sdk.ErrInsufficientCoins("Bids must be positive")
}
return nil
}

// GetSignBytes encodes the message for signing
func (msg MsgBuyName) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return sdk.MustSortJSON(b)
}

// GetSigners defines whose signature is required
func (msg MsgBuyName) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Buyer}
}

Back to Handler:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/handler.go
// NewHandler returns a handler for "nameservice" type messages.
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgSetName:
return handleMsgSetName(ctx, keeper, msg)
case MsgBuyName:
return handleMsgBuyName(ctx, keeper, msg)
default:
errMsg := fmt.Sprintf("Unrecognized nameservice Msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// Handle a message to buy name
func handleMsgBuyName(ctx sdk.Context, keeper Keeper, msg MsgBuyName) sdk.Result {
if keeper.GetPrice(ctx, msg.Name).IsAllGT(msg.Bid) { // Checks if the the bid price is greater than the price paid by the current owner
return sdk.ErrInsufficientCoins("Bid not high enough").Result() // If not, throw an error
}
if keeper.HasOwner(ctx, msg.Name) {
_, err := keeper.coinKeeper.SendCoins(ctx, msg.Buyer, keeper.GetOwner(ctx, msg.Name), msg.Bid)
if err != nil {
return sdk.ErrInsufficientCoins("Buyer does not have enough coins").Result()
}
} else {
_, _, err := keeper.coinKeeper.SubtractCoins(ctx, msg.Buyer, msg.Bid) // If so, deduct the Bid amount from the sender
if err != nil {
return sdk.ErrInsufficientCoins("Buyer does not have enough coins").Result()
}
}
keeper.SetOwner(ctx, msg.Name, msg.Buyer)
keeper.SetPrice(ctx, msg.Name, msg.Bid)
return sdk.Result{}
}

Adding queriers:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/querier.go
package nameservice

import (
"fmt"
"strings"

"github.com/cosmos/cosmos-sdk/codec"

sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
)

// query endpoints supported by the nameservice Querier
const (
QueryResolve = "resolve"
QueryWhois = "whois"
QueryNames = "names"
)

// NewQuerier is the module level router for state queries
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
switch path[0] {
case QueryResolve:
return queryResolve(ctx, path[1:], req, keeper)
case QueryWhois:
return queryWhois(ctx, path[1:], req, keeper)
case QueryNames:
return queryNames(ctx, req, keeper)
default:
return nil, sdk.ErrUnknownRequest("unknown nameservice query endpoint")
}
}
}
// nolint: unparam
func queryResolve(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
name := path[0]

value := keeper.ResolveName(ctx, name)

if value == "" {
return []byte{}, sdk.ErrUnknownRequest("could not resolve name")
}

bz, err2 := codec.MarshalJSONIndent(keeper.cdc, QueryResResolve{value})
if err2 != nil {
panic("could not marshal result to JSON")
}

return bz, nil
}

// Query Result Payload for a resolve query
type QueryResResolve struct {
Value string `json:"value"`
}

// implement fmt.Stringer
func (r QueryResResolve) String() string {
return r.Value
}

// nolint: unparam
func queryWhois(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
name := path[0]

whois := keeper.GetWhois(ctx, name)

bz, err2 := codec.MarshalJSONIndent(keeper.cdc, whois)
if err2 != nil {
panic("could not marshal result to JSON")
}

return bz, nil
}

// implement fmt.Stringer
func (w Whois) String() string {
return strings.TrimSpace(fmt.Sprintf(`Owner: %s
Value: %s
Price: %s`, w.Owner, w.Value, w.Price))
}

func queryNames(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var namesList QueryResNames

iterator := keeper.GetNamesIterator(ctx)

for ; iterator.Valid(); iterator.Next() {
name := string(iterator.Key())
namesList = append(namesList, name)
}

bz, err2 := codec.MarshalJSONIndent(keeper.cdc, namesList)
if err2 != nil {
panic("could not marshal result to JSON")
}

return bz, nil
}

// Query Result Payload for a names query
type QueryResNames []string

// implement fmt.Stringer
func (n QueryResNames) String() string {
return strings.Join(n[:], "\n")
}

Adding encoding/decoding features:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/codec.go
package nameservice

import (
"github.com/cosmos/cosmos-sdk/codec"
)

// RegisterCodec registers concrete types on wire codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgSetName{}, "nameservice/SetName", nil)
cdc.RegisterConcrete(MsgBuyName{}, "nameservice/BuyName", nil)
}

Before CLI access, we need to commit and push the repo for the requirement of the next modules:

Creating a nameservice repository on https://github.com/iNukeLaPeste/

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git config — global user.email “your@email.com"
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git config — global user.name “iNukeLaPeste
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “init
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git remote
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git remote add origin https://github.com/iNukeLaPeste/nameservice.git
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git remote -v
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git push origin master

Access CLI for queries:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ mkdir -p ./x/nameservice/client/cli
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/client/cli/query.go (do not forget to change to your own repository in the import)
package cli

import (
"fmt"

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

// GetCmdResolveName queries information about a name
func GetCmdResolveName(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "resolve [name]",
Short: "resolve name",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
name := args[0]

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/resolve/%s", queryRoute, name), nil)
if err != nil {
fmt.Printf("could not resolve name - %s \n", string(name))
return nil
}

var out nameservice.QueryResResolve
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}

// GetCmdWhois queries information about a domain
func GetCmdWhois(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "whois [name]",
Short: "Query whois info of name",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
name := args[0]

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/whois/%s", queryRoute, name), nil)
if err != nil {
fmt.Printf("could not resolve whois - %s \n", string(name))
return nil
}

var out nameservice.Whois
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}

// GetCmdNames queries a list of all names
func GetCmdNames(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "names",
Short: "names",
// Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/names", queryRoute), nil)
if err != nil {
fmt.Printf("could not get query names\n")
return nil
}

var out nameservice.QueryResNames
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}

Access CLI for transactions:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/client/cli/tx.go (do not forget to change to your own repository in the import)
package cli

import (
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/iNukeLaPeste/nameservice/x/nameservice"

sdk "github.com/cosmos/cosmos-sdk/types"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
)

// GetCmdBuyName is the CLI command for sending a BuyName transaction
func GetCmdBuyName(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "buy-name [name] [amount]",
Short: "bid for existing name or claim new name",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)

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

if err := cliCtx.EnsureAccountExists(); err != nil {
return err
}

coins, err := sdk.ParseCoins(args[1])
if err != nil {
return err
}

msg := nameservice.NewMsgBuyName(args[0], coins, cliCtx.GetFromAddress())
err = msg.ValidateBasic()
if err != nil {
return err
}

cliCtx.PrintResponse = true

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

// GetCmdSetName is the CLI command for sending a SetName transaction
func GetCmdSetName(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "set-name [name] [value]",
Short: "set the value associated with a name that you own",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)

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

if err := cliCtx.EnsureAccountExists(); err != nil {
return err
}

msg := nameservice.NewMsgSetName(args[0], args[1], cliCtx.GetFromAddress())
err := msg.ValidateBasic()
if err != nil {
return err
}

cliCtx.PrintResponse = true

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

New module publishing:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “test” && git push origin master

Access CLI for Client:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/client/module_client.go (do not forget to change to your own repository in the import)
package client

import (
"github.com/cosmos/cosmos-sdk/client"
nameservicecmd "github.com/iNukeLaPeste/nameservice/x/nameservice/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
}

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
namesvcQueryCmd := &cobra.Command{
Use: "nameservice",
Short: "Querying commands for the nameservice module",
}

namesvcQueryCmd.AddCommand(client.GetCommands(
nameservicecmd.GetCmdResolveName(mc.storeKey, mc.cdc),
nameservicecmd.GetCmdWhois(mc.storeKey, mc.cdc),
)...)

return namesvcQueryCmd
}

// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
namesvcTxCmd := &cobra.Command{
Use: "nameservice",
Short: "Nameservice transactions subcommands",
}

namesvcTxCmd.AddCommand(client.PostCommands(
nameservicecmd.GetCmdBuyName(mc.cdc),
nameservicecmd.GetCmdSetName(mc.cdc),
)...)

return namesvcTxCmd
}

New module publishing:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “test” && git push origin master

Adding an API Rest:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ mkdir -p ./x/nameservice/rest
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/rest/rest.go (do not forget to change to your own repository in the import)
package rest

import (
"fmt"
"net/http"

"github.com/cosmos/cosmos-sdk/client/context"
clientrest "github.com/cosmos/cosmos-sdk/client/rest"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/iNukeLaPeste/nameservice/x/nameservice"

"github.com/gorilla/mux"
)

const (
restName = "name"
)
// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, storeName string) {
r.HandleFunc(fmt.Sprintf("/%s/names", storeName), buyNameHandler(cdc, cliCtx)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/%s/names", storeName), setNameHandler(cdc, cliCtx)).Methods("PUT")
r.HandleFunc(fmt.Sprintf("/%s/names/{%s}", storeName, restName), resolveNameHandler(cdc, cliCtx, storeName)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/names/{%s}/whois", storeName, restName), whoIsHandler(cdc, cliCtx, storeName)).Methods("GET")
}
func resolveNameHandler(cdc *codec.Codec, cliCtx context.CLIContext, storeName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
paramType := vars[restName]

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/resolve/%s", storeName, paramType), nil)
if err != nil {
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return
}

rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
}

func whoIsHandler(cdc *codec.Codec, cliCtx context.CLIContext, storeName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
paramType := vars[restName]

res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/whois/%s", storeName, paramType), nil)
if err != nil {
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return
}

rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
}


func namesHandler(cdc *codec.Codec, cliCtx context.CLIContext, storeName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/names", storeName), nil)
if err != nil {
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return
}
rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
}
}
type buyNameReq struct {
BaseReq rest.BaseReq `json:"base_req"`
Name string `json:"name"`
Amount string `json:"amount"`
Buyer string `json:"buyer"`
}

func buyNameHandler(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req buyNameReq

if !rest.ReadRESTReq(w, r, cdc, &req) {
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
return
}

baseReq := req.BaseReq.Sanitize()
if !baseReq.ValidateBasic(w) {
return
}

addr, err := sdk.AccAddressFromBech32(req.Buyer)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

coins, err := sdk.ParseCoins(req.Amount)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

// create the message
msg := nameservice.NewMsgBuyName(req.Name, coins, addr)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, baseReq, []sdk.Msg{msg})
}
}

type setNameReq struct {
BaseReq rest.BaseReq `json:"base_req"`
Name string `json:"name"`
Value string `json:"value"`
Owner string `json:"owner"`
}

func setNameHandler(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req setNameReq
if !rest.ReadRESTReq(w, r, cdc, &req) {
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
return
}

baseReq := req.BaseReq.Sanitize()
if !baseReq.ValidateBasic(w) {
return
}

addr, err := sdk.AccAddressFromBech32(req.Owner)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

// create the message
msg := nameservice.NewMsgSetName(req.Name, req.Value, addr)
err = msg.ValidateBasic()
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}

clientrest.WriteGenerateStdTxResponse(w, cdc, cliCtx, baseReq, []sdk.Msg{msg})
}
}

New module publishing:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “test” && git push origin master

Application update:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/app.go (do not forget to change to your own repository in the import)
package app

import (
"encoding/json"

"github.com/tendermint/tendermint/libs/log"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/iNukeLaPeste/nameservice/x/nameservice"

bam "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db"
tmtypes "github.com/tendermint/tendermint/types"
)
const (
appName = "nameservice"
)

// NewNameServiceApp is a constructor function for nameServiceApp
func NewNameServiceApp(logger log.Logger, db dbm.DB) *nameServiceApp {

// First define the top level codec that will be shared by the different modules
cdc := MakeCodec()

// BaseApp handles interactions with Tendermint through the ABCI protocol
bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc))

// Here you initialize your application with the store keys it requires
var app = &nameServiceApp{
BaseApp: bApp,
cdc: cdc,

keyMain: sdk.NewKVStoreKey("main"),
keyAccount: sdk.NewKVStoreKey("acc"),
keyNS: sdk.NewKVStoreKey("ns"),
keyFeeCollection: sdk.NewKVStoreKey("fee_collection"),
keyParams: sdk.NewKVStoreKey("params"),
tkeyParams: sdk.NewTransientStoreKey("transient_params"),
}

// The ParamsKeeper handles parameter storage for the application
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams)

// The AccountKeeper handles address -> account lookups
app.accountKeeper = auth.NewAccountKeeper(
app.cdc,
app.keyAccount,
app.paramsKeeper.Subspace(auth.DefaultParamspace),
auth.ProtoBaseAccount,
)

// The BankKeeper allows you perform sdk.Coins interactions
app.bankKeeper = bank.NewBaseKeeper(
app.accountKeeper,
app.paramsKeeper.Subspace(bank.DefaultParamspace),
bank.DefaultCodespace,
)

// The FeeCollectionKeeper collects transaction fees and renders them to the fee distribution module
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(cdc, app.keyFeeCollection)

// The NameserviceKeeper is the Keeper from the module for this tutorial
// It handles interactions with the namestore
app.nsKeeper = nameservice.NewKeeper(
app.bankKeeper,
app.keyNS,
app.cdc,
)

// The AnteHandler handles signature verification and transaction pre-processing
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))

// The app.Router is the main transaction router where each module registers its routes
// Register the bank and nameservice routes here
app.Router().
AddRoute("bank", bank.NewHandler(app.bankKeeper)).
AddRoute("nameservice", nameservice.NewHandler(app.nsKeeper))

// The app.QueryRouter is the main query router where each module registers its routes
app.QueryRouter().
AddRoute("nameservice", nameservice.NewQuerier(app.nsKeeper)).
AddRoute("acc", auth.NewQuerier(app.accountKeeper))

// The initChainer handles translating the genesis.json file into initial state for the network
app.SetInitChainer(app.initChainer)

app.MountStores(
app.keyMain,
app.keyAccount,
app.keyNS,
app.keyFeeCollection,
app.keyParams,
app.tkeyParams,
)

err := app.LoadLatestVersion(app.keyMain)
if err != nil {
cmn.Exit(err.Error())
}

return app
}
// GenesisState represents chain state at the start of the chain. Any initial state (account balances) are stored here.
type GenesisState struct {
AuthData auth.GenesisState `json:"auth"`
BankData bank.GenesisState `json:"bank"`
Accounts []*auth.BaseAccount `json:"accounts"`
}

func (app *nameServiceApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes

genesisState := new(GenesisState)
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
if err != nil {
panic(err)
}

for _, acc := range genesisState.Accounts {
acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx)
app.accountKeeper.SetAccount(ctx, acc)
}

auth.InitGenesis(ctx, app.accountKeeper, app.feeCollectionKeeper, genesisState.AuthData)
bank.InitGenesis(ctx, app.bankKeeper, genesisState.BankData)

return abci.ResponseInitChain{}
}

// ExportAppStateAndValidators does the things
func (app *nameServiceApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
ctx := app.NewContext(true, abci.Header{})
accounts := []*auth.BaseAccount{}

appendAccountsFn := func(acc auth.Account) bool {
account := &auth.BaseAccount{
Address: acc.GetAddress(),
Coins: acc.GetCoins(),
}

accounts = append(accounts, account)
return false
}

app.accountKeeper.IterateAccounts(ctx, appendAccountsFn)

genState := GenesisState{
Accounts: accounts,
AuthData: auth.DefaultGenesisState(),
BankData: bank.DefaultGenesisState(),
}

appState, err = codec.MarshalJSONIndent(app.cdc, genState)
if err != nil {
return nil, nil, err
}

return appState, validators, err
}
// MakeCodec generates the necessary codecs for Amino
func MakeCodec() *codec.Codec {
var cdc = codec.New()
auth.RegisterCodec(cdc)
bank.RegisterCodec(cdc)
nameservice.RegisterCodec(cdc)
staking.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
}

New module publishing:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “test” && git push origin master

Create Entrypoints:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ mkdir -p ./ cmd/nsd
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/cmd/nsd/main.go (do not forget to change to your own repository in the import)
package main

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"

gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
sdk "github.com/cosmos/cosmos-sdk/types"
app "github.com/iNukeLaPeste/nameservice"
abci "github.com/tendermint/tendermint/abci/types"
cfg "github.com/tendermint/tendermint/config"
dbm "github.com/tendermint/tendermint/libs/db"
tmtypes "github.com/tendermint/tendermint/types"
)

// DefaultNodeHome sets the folder where the application data and configuration will be stored
var DefaultNodeHome = os.ExpandEnv("$HOME/.nsd")

const (
flagOverwrite = "overwrite"
)

func main() {
cobra.EnableCommandSorting = false

cdc := app.MakeCodec()
ctx := server.NewDefaultContext()

rootCmd := &cobra.Command{
Use: "nsd",
Short: "nameservice App Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}

rootCmd.AddCommand(InitCmd(ctx, cdc))
rootCmd.AddCommand(AddGenesisAccountCmd(ctx, cdc))
server.AddCommands(ctx, cdc, rootCmd, newApp, appExporter())

// prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "NS", DefaultNodeHome)
err := executor.Execute()
if err != nil {
// handle with #870
panic(err)
}
}

func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewNameServiceApp(logger, db)
}

func appExporter() server.AppExporter {
return func(logger log.Logger, db dbm.DB, _ io.Writer, _ int64, _ bool, _ []string) (
json.RawMessage, []tmtypes.GenesisValidator, error) {
dapp := app.NewNameServiceApp(logger, db)
return dapp.ExportAppStateAndValidators()
}
}

// InitCmd initializes all files for tendermint and application
func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize genesis config, priv-validator file, and p2p-node file",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(cli.HomeFlag))

chainID := viper.GetString(client.FlagChainID)
if chainID == "" {
chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6))
}

_, pk, err := gaiaInit.InitializeNodeValidatorFiles(config)
if err != nil {
return err
}

var appState json.RawMessage
genFile := config.GenesisFile()

if !viper.GetBool(flagOverwrite) && common.FileExists(genFile) {
return fmt.Errorf("genesis.json file already exists: %v", genFile)
}

genesis := app.GenesisState{
AuthData: auth.DefaultGenesisState(),
BankData: bank.DefaultGenesisState(),
}

appState, err = codec.MarshalJSONIndent(cdc, genesis)
if err != nil {
return err
}

_, _, validator, err := SimpleAppGenTx(cdc, pk)
if err != nil {
return err
}

if err = gaiaInit.ExportGenesisFile(genFile, chainID, []tmtypes.GenesisValidator{validator}, appState); err != nil {
return err
}

cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)

fmt.Printf("Initialized nsd configuration and bootstrapping files in %s...\n", viper.GetString(cli.HomeFlag))
return nil
},
}

cmd.Flags().String(cli.HomeFlag, DefaultNodeHome, "node's home directory")
cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file")

return cmd
}

// AddGenesisAccountCmd allows users to add accounts to the genesis file
func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "add-genesis-account [address] [coins[,coins]]",
Short: "Adds an account to the genesis file",
Args: cobra.ExactArgs(2),
Long: strings.TrimSpace(`
Adds accounts to the genesis file so that you can start a chain with coins in the CLI:

$ nsd add-genesis-account cosmos1tse7r2fadvlrrgau3pa0ss7cqh55wrv6y9alwh 1000STAKE,1000nametoken
`),
RunE: func(_ *cobra.Command, args []string) error {
addr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
coins, err := sdk.ParseCoins(args[1])
if err != nil {
return err
}
coins.Sort()

var genDoc tmtypes.GenesisDoc
config := ctx.Config
genFile := config.GenesisFile()
if !common.FileExists(genFile) {
return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile)
}
genContents, err := ioutil.ReadFile(genFile)
if err != nil {
}

if err = cdc.UnmarshalJSON(genContents, &genDoc); err != nil {
return err
}

var appState app.GenesisState
if err = cdc.UnmarshalJSON(genDoc.AppState, &appState); err != nil {
return err
}

for _, stateAcc := range appState.Accounts {
if stateAcc.Address.Equals(addr) {
return fmt.Errorf("the application state already contains account %v", addr)
}
}

acc := auth.NewBaseAccountWithAddress(addr)
acc.Coins = coins
appState.Accounts = append(appState.Accounts, &acc)
appStateJSON, err := cdc.MarshalJSON(appState)
if err != nil {
return err
}

return gaiaInit.ExportGenesisFile(genFile, genDoc.ChainID, genDoc.Validators, appStateJSON)
},
}
return cmd
}

// SimpleAppGenTx returns a simple GenTx command that makes the node a valdiator from the start
func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey) (
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {

addr, secret, err := server.GenerateCoinKey()
if err != nil {
return
}

bz, err := cdc.MarshalJSON(struct {
Addr sdk.AccAddress `json:"addr"`
}{addr})
if err != nil {
return
}

appGenTx = json.RawMessage(bz)

bz, err = cdc.MarshalJSON(map[string]string{"secret": secret})
if err != nil {
return
}

cliPrint = json.RawMessage(bz)

validator = tmtypes.GenesisValidator{
PubKey: pk,
Power: 10,
}

return
}

New module publishing:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “test” && git push origin master

Create Entrypoints:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ mkdir -p ./ cmd/nsdcli
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ emacs ./x/nameservice/cmd/nscli/main.go (do not forget to change to your own repository in the import)
package main

import (
"os"
"path"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/lcd"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/spf13/cobra"
"github.com/spf13/viper"
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/libs/cli"

sdk "github.com/cosmos/cosmos-sdk/types"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest"
app "github.com/iNukeLaPeste/nameservice"
nsclient "github.com/iNukeLaPeste/nameservice/x/nameservice/client"
nsrest "github.com/iNukeLaPeste/nameservice/x/nameservice/client/rest"

)

const (
storeAcc = "acc"
storeNS = "nameservice"
)

var defaultCLIHome = os.ExpandEnv("$HOME/.nscli")

func main() {
cobra.EnableCommandSorting = false

cdc := app.MakeCodec()

// Read in the configuration file for the sdk
config := sdk.GetConfig()
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
config.Seal()

mc := []sdk.ModuleClients{
nsclient.NewModuleClient(storeNS, cdc),
}

rootCmd := &cobra.Command{
Use: "nscli",
Short: "nameservice Client",
}

// Add --chain-id to persistent flags and mark it required
rootCmd.PersistentFlags().String(client.FlagChainID, "", "Chain ID of tendermint node")
rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error {
return initConfig(rootCmd)
}

// Construct Root Command
rootCmd.AddCommand(
rpc.StatusCommand(),
client.ConfigCmd(defaultCLIHome),
queryCmd(cdc, mc),
txCmd(cdc, mc),
client.LineBreak,
lcd.ServeCommand(cdc, registerRoutes),
client.LineBreak,
keys.Commands(),
client.LineBreak,

)

executor := cli.PrepareMainCmd(rootCmd, "NS", defaultCLIHome)
err := executor.Execute()
if err != nil {
panic(err)
}
}

func registerRoutes(rs *lcd.RestServer) {
rs.CliCtx = rs.CliCtx.WithAccountDecoder(rs.Cdc)
rpc.RegisterRoutes(rs.CliCtx, rs.Mux)
tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc)
auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, storeAcc)
bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase)
nsrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, storeNS)
}

func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
queryCmd := &cobra.Command{
Use: "query",
Aliases: []string{"q"},
Short: "Querying subcommands",
}

queryCmd.AddCommand(
rpc.ValidatorCommand(cdc),
rpc.BlockCommand(),
tx.SearchTxCmd(cdc),
tx.QueryTxCmd(cdc),
client.LineBreak,
authcmd.GetAccountCmd(storeAcc, cdc),
)

for _, m := range mc {
queryCmd.AddCommand(m.GetQueryCmd())
}

return queryCmd
}

func txCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
txCmd := &cobra.Command{
Use: "tx",
Short: "Transactions subcommands",
}

txCmd.AddCommand(
bankcmd.SendTxCmd(cdc),
client.LineBreak,
authcmd.GetSignCommand(cdc),
tx.GetBroadcastCommand(cdc),
client.LineBreak,
)

for _, m := range mc {
txCmd.AddCommand(m.GetTxCmd())
}

return txCmd
}

func initConfig(cmd *cobra.Command) error {
home, err := cmd.PersistentFlags().GetString(cli.HomeFlag)
if err != nil {
return err
}

cfgFile := path.Join(home, "config", "config.toml")
if _, err := os.Stat(cfgFile); err == nil {
viper.SetConfigFile(cfgFile)

if err := viper.ReadInConfig(); err != nil {
return err
}
}
if err := viper.BindPFlag(client.FlagChainID, cmd.PersistentFlags().Lookup(client.FlagChainID)); err != nil {
return err
}
if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil {
return err
}
return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag))
}

New module publishing:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “test” && git push origin master

Build

Create Entrypoints:

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ emacs Makefile
include Makefile.ledgerall: install

install: go.sum
go install -tags "$(build_tags)" ./cmd/nsd
go install -tags "$(build_tags)" ./cmd/nscli

go.sum: go.mod
@echo "--> Ensure dependencies have not been modified"
@go mod verify
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ emacs Makefile.ledger
LEDGER_ENABLED ?= true

build_tags =
ifeq ($(LEDGER_ENABLED),true)
ifeq ($(OS),Windows_NT)
GCCEXE = $(shell where gcc.exe 2> NUL)
ifeq ($(GCCEXE),)
$(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
else
UNAME_S = $(shell uname -s)
ifeq ($(UNAME_S),OpenBSD)
$(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988))
else
GCC = $(shell command -v gcc 2> /dev/null)
ifeq ($(GCC),)
$(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
endif
endif
endif
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ emacs go.mod
module nameservice

go 1.12

require (
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
github.com/bgentry/speakeasy v0.1.0
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a
github.com/cosmos/cosmos-sdk v0.33.0
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8
github.com/cosmos/ledger-cosmos-go v0.9.8
github.com/cosmos/sdk-application-tutorial v0.0.0-20190401171757-ef9c78014e84
github.com/davecgh/go-spew v1.1.1
github.com/ethereum/go-ethereum v1.8.23
github.com/fsnotify/fsnotify v1.4.7
github.com/go-kit/kit v0.8.0
github.com/go-logfmt/logfmt v0.4.0
github.com/go-stack/stack v1.8.0
github.com/gogo/protobuf v1.1.1
github.com/golang/protobuf v1.2.0
github.com/golang/snappy v0.0.1
github.com/gorilla/mux v1.7.0
github.com/gorilla/websocket v1.4.0
github.com/hashicorp/hcl v1.0.0
github.com/inconshreveable/mousetrap v1.0.0
github.com/jmhodges/levigo v1.0.0
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515
github.com/magiconair/properties v1.8.0
github.com/mattn/go-isatty v0.0.7
github.com/matttproud/golang_protobuf_extensions v1.0.1
github.com/mitchellh/mapstructure v1.1.2
github.com/pelletier/go-toml v1.2.0
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v0.9.2
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
github.com/prometheus/common v0.2.0
github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb
github.com/rakyll/statik v0.1.4
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165
github.com/rs/cors v1.6.0
github.com/spf13/afero v1.2.2
github.com/spf13/cast v1.3.0
github.com/spf13/cobra v0.0.3
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.0.3
github.com/stretchr/testify v1.2.2
github.com/syndtr/goleveldb v1.0.0
github.com/tendermint/btcd v0.1.1
github.com/tendermint/go-amino v0.14.1
github.com/tendermint/iavl v0.12.1
github.com/tendermint/tendermint v0.31.0-dev0
github.com/zondax/hid v0.9.0
github.com/zondax/ledger-go v0.8.0
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65
golang.org/x/text v0.3.0
google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d
google.golang.org/grpc v1.19.1
gopkg.in/yaml.v2 v2.2.2
)

replace golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5

Building the app:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ make install
Error: Makefile.ledger:19: *** gcc not installed for ledger support, please install or set LEDGER_ENABLED=false. Stop.

To resolve this:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$sudo apt-get install gcc
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ make install

Fixed! Next steps:

  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git add *
  • chainacc@cosmos:~/go/src/github.com/iNukeLaPeste/nameservice$ git commit -m “test” && git push origin master
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ make install
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nsd help
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli help

Running the live network

Key Manangement

  • chainacc@cosmos:~$ cd ~
  • chainacc@cosmos:~$ cd $GOPATH/src/github.com/iNukeLaPeste/nameservice
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nsd init — chain-id testchain

Initialized nsd configuration and bootstrapping files in /home/chainacc/.nsd…

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli keys add jack

chainacc@cosmos:~/go/src/github.com/chainaccelerator/nameservice$ nscli keys add jack

Enter a passphrase to encrypt your key to disk:
Repeat the passphrase:

NAME: TYPE: ADDRESS: PUBKEY:
jack local cosmos1mlsngkp8770rcjgc69cjsgdlmdxta4aruhmjmj cosmospub1addwnpepq00ce65qsh8a7pyhjntxgdz5h2gd9qg6cc96wvjr0g6dl5uex7myqx9mkas

**Important** write this mnemonic phrase in a safe place.
It is the only way to recover your account if you ever forget your password.

earn glare direct catch zero olympic pepper merit fossil absurd during crime soul draft jaguar weapon alien light meadow canyon park vendor above grass

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli keys add alice

Enter a passphrase to encrypt your key to disk:
Repeat the passphrase:

NAME: TYPE: ADDRESS: PUBKEY:
alice local cosmos15ds22wz9vawwpnlfn6da5rxexsym6jq0egan73 cosmospub1addwnpepqgure9vmd0d4944hc32glmls6hc4r9xp95cscxl70dlcrwkx5yr26aeh5ur

**Important** write this mnemonic phrase in a safe place.
It is the only way to recover your account if you ever forget your password.

rice summer water giraffe zoo beauty clarify agent another multiply decorate gadget lock beauty gym island wink wreck ancient core armed enroll during boring

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nsd add-genesis-account $(nscli keys show jack -a) 1000nametoken,1000jackcoin
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nsd add-genesis-account $(nscli keys show alice -a) 1000nametoken,1000alicecoin

Configuration

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli config chain-id testchain

/home/chainacc/.nscli/config/config.toml does not exist
configuration saved to /home/chainacc/.nscli/config/config.toml

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli config output json

configuration saved to /home/chainacc/.nscli/config/config.toml

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli config indent true

configuration saved to /home/chainacc/.nscli/config/config.toml

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli config trust-node true

configuration saved to /home/chainacc/.nscli/config/config.toml

Start

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nsd start

chainacc@cosmos:~/go/src/github.com/chainaccelerator/nameservice$ nsd start
I[2019–03–27|19:04:44.646] Starting ABCI with Tendermint module=main
E[2019–03–27|19:04:44.683] Couldn’t connect to any seeds module=p2p
I[2019–03–27|19:04:49.715] Executed block module=state height=1 validTxs=0 invalidTxs=0
I[2019–03–27|19:04:49.717] Committed state module=state height=1 txs=0 appHash=E1CDD36E114FCBF7AA34DB73C0DEE161B541FF692B07A5DE4BA9B26864CFD8D1
I[2019–03–27|19:04:54.744] Executed block module=state height=2 validTxs=0 invalidTxs=0
I[2019–03–27|19:04:54.745] Committed state module=state height=2 txs=0 appHash=0973662DBBBC46570A669FBEDD4C825E1429E569464036CEF3F5A90837756CFB
I[2019–03–27|19:04:59.753] Executed block module=state heig

Using Nameservice

Checking accounts to ensure they have funds:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli query account $(nscli keys show jack -a)
{
“type”: “auth/Account”,
“value”: {
“address”: “cosmos1mlsngkp8770rcjgc69cjsgdlmdxta4aruhmjmj”,
“coins”: [
{
“denom”: “jackcoin”,
“amount”: “1000”
},
{
“denom”: “nametoken”,
“amount”: “1000”
}
],
“public_key”: null,
“account_number”: “0”,
“sequence”: “0”
}
}
  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli query account $(nscli keys show alice -a)
{
"type": "auth/Account",
"value": {
"address": "cosmos15ds22wz9vawwpnlfn6da5rxexsym6jq0egan73",
"coins": [
{
"denom": "alicecoin",
"amount": "1000"
},
{
"denom": "nametoken",
"amount": "1000"
}
],
"public_key": null,
"account_number": "1",
"sequence": "0"
}
}

Buying our first name with coins from genesis file:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli tx nameservice buy-name jack.id 5nametoken --from jack
{“chain_id”:”namechain”,”account_number”:”0",”sequence”:”0",”fee”:{“amount”:null,”gas”:”200000"},”msgs”:[{“type”:”nameservice/BuyName”,”value”:{“Name”:”jack.id”,”Bid”:[{“denom”:”nametoken”,”amount”:”5"}],”Buyer”:”cosmos1mlsngkp8770rcjgc69cjsgdlmdxta4aruhmjmj”}}],”memo”:””}
confirm transaction before signing and broadcasting [Y/n]: Y
Password to sign with ‘jack’:
{
“height”: “105”,
“txhash”: “60DF2C1DAF929BD07474A86FE50CA3A921AEBB1B336D94081E8ED7EF2FA850CE”,
“logs”: [
{
“msg_index”: “0”,
“success”: true,
“log”: “”
}
],
“gas_wanted”: “200000”,
“gas_used”: “28213”,
“tags”: [
{
“key”: “action”,
“value”: “buy_name”
}
]
}

Setting the value for the name we just bought:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli tx nameservice set-name jack.id 8.8.8.8 — from jack
{"chain_id":"namechain","account_number":"0","sequence":"1","fee":{"amount":null,"gas":"200000"},"msgs":[{"type":"nameservice/SetName","value":{"Name":"jack.id","Value":"8.8.8.8","Owner":"cosmos1mlsngkp8770rcjgc69cjsgdlmdxta4aruhmjmj"}}],"memo":""}confirm transaction before signing and broadcasting [Y/n]: Y
Password to sign with 'jack':
{
"height": "516",
"txhash": "12F744C65AD94A0387557D5D624BD89A8DA3AE73649051F2D3E4ED4F9B0EE80D",
"logs": [
{
"msg_index": "0",
"success": true,
"log": ""
}
],
"gas_wanted": "200000",
"gas_used": "16730",
"tags": [
{
"key": "action",
"value": "set_name"
}
]
}

Trying out a resolve against the name we registered:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli query nameservice resolve jack.id
{
"value": "8.8.8.8"
}

Trying out a whois query against the name we registered:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli query nameservice whois jack.id
{
"value": "8.8.8.8",
"owner": "cosmos1mlsngkp8770rcjgc69cjsgdlmdxta4aruhmjmj",
"price": [
{
"denom": "nametoken",
"amount": "5"
}
]
}

Alice buys our name from Jack:

  • chainacc@cosmos: $GOPATH/src/github.com/iNukeLaPeste/nameservice$ nscli tx nameservice buy-name jack.id 10nametoken --from alice
{"chain_id":"namechain","account_number":"1","sequence":"0","fee":{"amount":null,"gas":"200000"},"msgs":[{"type":"nameservice/BuyName","value":{"Name":"jack.id","Bid":[{"denom":"nametoken","amount":"10"}],"Buyer":"cosmos15ds22wz9vawwpnlfn6da5rxexsym6jq0egan73"}}],"memo":""}confirm transaction before signing and broadcasting [Y/n]: Y
Password to sign with 'alice':
{
"height": "802",
"txhash": "16B55B40EBCDB251C38EC060F4DDA606F0DF419AA84EEF40B1383D661F3B308B",
"logs": [
{
"msg_index": "0",
"success": true,
"log": ""
}
],
"gas_wanted": "200000",
"gas_used": "42365",
"tags": [
{
"key": "action",
"value": "buy_name"
}
]
}

And we’re all done :)


In-house partners
Event partner corporates
Partner experts

Let us know which offer you are interested in and apply here => this form.

Don’t forget to follow us on social medias : twitter, telegram, slack

Chain Accelerator

Global network for blockchain startup acceleration

Nicolas Cantu

Written by

Chain Accelerator

Global network for blockchain startup acceleration

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade