In golang the convention is to place files that compile to a binary in the ./cmd
folder of a project. For your application there are 2 binaries that you want to create:
nsd
: This binary is similar tobitcoind
or other cryptocurrency daemons in that it maintains p2p connections, propagates transactions, handles local storage and provides an RPC interface to interact with the network. In this case, Tendermint is used for networking and transaction ordering.nscli
: This binary provides commands that allow users to interact with your application.
To get started create two files in your project directory that will instantiate these binaries:
./cmd/nsd/main.go
./cmd/nscli/main.go
Start by adding the following code to cmd/nsd/main.go
:
NOTE: Your application needs to import the code you just wrote. Here the import path is set to this repository (
github.com/cosmos/sdk-application-tutorial
). If you are following along in your own repo you will need to change the import path to reflect that (github.com/{ .Username }/{ .Project.Repo }
).
package main
import (
"encoding/json"
"io"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/x/genaccounts"
genaccscli "github.com/cosmos/cosmos-sdk/x/genaccounts/client/cli"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
sdk "github.com/cosmos/cosmos-sdk/types"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
app "github.com/cosmos/sdk-application-tutorial"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
tmtypes "github.com/tendermint/tendermint/types"
)
func main() {
cobra.EnableCommandSorting = false
cdc := app.MakeCodec()
config := sdk.GetConfig()
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
config.Seal()
ctx := server.NewDefaultContext()
rootCmd := &cobra.Command{
Use: "nsd",
Short: "nameservice App Daemon (server)",
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
// CLI commands to initialize the chain
rootCmd.AddCommand(
genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome),
genutilcli.CollectGenTxsCmd(ctx, cdc, genaccounts.AppModuleBasic{}, app.DefaultNodeHome),
genutilcli.GenTxCmd(ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{}, genaccounts.AppModuleBasic{}, app.DefaultNodeHome, app.DefaultCLIHome),
genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics),
// AddGenesisAccountCmd allows users to add accounts to the genesis file
genaccscli.AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome),
)
server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators)
// prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "NS", app.DefaultNodeHome)
err := executor.Execute()
if err != nil {
panic(err)
}
}
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewNameServiceApp(logger, db)
}
func exportAppStateAndTMValidators(
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string,
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
if height != -1 {
nsApp := app.NewNameServiceApp(logger, db)
err := nsApp.LoadHeight(height)
if err != nil {
return nil, nil, err
}
return nsApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
}
nsApp := app.NewNameServiceApp(logger, db)
return nsApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
}
Notes on the above code:
- Most of the code above combines the CLI commands from Tendermint, Cosmos-SDK and your Nameservice module.
Finish up by building the nscli
command:
NOTE: Your application needs to import the code you just wrote. Here the import path is set to this repository (
github.com/cosmos/sdk-application-tutorial
). If you are following along in your own repo you will need to change the import path to reflect that (github.com/{ .Username }/{ .Project.Repo }
).
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"
sdk "github.com/cosmos/cosmos-sdk/types"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
app "github.com/cosmos/sdk-application-tutorial"
"github.com/spf13/cobra"
"github.com/spf13/viper"
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/libs/cli"
)
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()
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(app.DefaultCLIHome),
queryCmd(cdc),
txCmd(cdc),
client.LineBreak,
lcd.ServeCommand(cdc, registerRoutes),
client.LineBreak,
keys.Commands(),
client.LineBreak,
)
executor := cli.PrepareMainCmd(rootCmd, "NS", app.DefaultCLIHome)
err := executor.Execute()
if err != nil {
panic(err)
}
}
func registerRoutes(rs *lcd.RestServer) {
client.RegisterRoutes(rs.CliCtx, rs.Mux)
app.ModuleBasics.RegisterRESTRoutes(rs.CliCtx, rs.Mux)
}
func queryCmd(cdc *amino.Codec) *cobra.Command {
queryCmd := &cobra.Command{
Use: "query",
Aliases: []string{"q"},
Short: "Querying subcommands",
}
queryCmd.AddCommand(
authcmd.GetAccountCmd(cdc),
client.LineBreak,
rpc.ValidatorCommand(cdc),
rpc.BlockCommand(),
authcmd.QueryTxsByEventsCmd(cdc),
authcmd.QueryTxCmd(cdc),
client.LineBreak,
)
// add modules' query commands
app.ModuleBasics.AddQueryCommands(queryCmd, cdc)
return queryCmd
}
func txCmd(cdc *amino.Codec) *cobra.Command {
txCmd := &cobra.Command{
Use: "tx",
Short: "Transactions subcommands",
}
txCmd.AddCommand(
bankcmd.SendTxCmd(cdc),
client.LineBreak,
authcmd.GetSignCommand(cdc),
authcmd.GetMultiSignCommand(cdc),
client.LineBreak,
authcmd.GetBroadcastCommand(cdc),
authcmd.GetEncodeCommand(cdc),
client.LineBreak,
)
// add modules' tx commands
app.ModuleBasics.AddTxCommands(txCmd, cdc)
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))
}
Note:
- The code combines the CLI commands from Tendermint, Cosmos-SDK and your Nameservice module.
- The
cobra
CLI documentation will help with understanding the above code. - You can see the
ModuleClient
defined earlier in action here. - Note how the routes are included in the
registerRoutes
function.