# Command-Line Interface
# Pre-requisite Readings
# Command-Line Interface
One of the main entrypoints of an application is the command-line interface. This entrypoint is created via a
main.go file which compiles to a binary, conventionally placed in the application's
./cmd/cli folder. The CLI for an application is typically be referred to as the name of the application suffixed with
appcli. Here is where the interfaces docs lie in the directory from the nameservice tutorial.
# Example Command
There is no set way to create a CLI, but SDK modules typically use the Cobra Library. Building a CLI with Cobra entails defining commands, arguments, and flags. Commands understand the actions users wish to take, such as
tx for creating a transaction and
query for querying the application. Each command can also have nested subcommands, necessary for naming the specific transaction type. Users also supply Arguments, such as account numbers to send coins to, and Flags to modify various aspects of the commands, such as gas prices or which node to broadcast to.
Here is an example of a command a user might enter to interact with the nameservice CLI
nscli in order to buy a name:
The first four strings specify the command:
- The root command for the entire application
- The subcommand
tx, which contains all commands that let users create transactions.
- The subcommand
nameserviceto indicate which module to route the command to (
nameservicemodule in this case).
- The type of transaction
The next two strings are arguments: the
name the user wishes to buy and the
amount they want to pay for it. Finally, the last few strings of the command are flags to indicate how much the user is willing to pay in fees (calculated using the amount of gas used to execute the transaction and the gas prices provided by the user).
The CLI interacts with a node (running
nsd) to handle this command. The interface itself is defined in a
# Building the CLI
main.go file needs to have a
main() function that does the following to run the command-line interface:
- Instantiate the
codecby calling the application's
codecis used to code and encode data structures for the application - stores can only persist
bytes so the developer must define a serialization format for their data structures or use the default, Amino.
- Configurations are set by reading in configuration files (e.g. the sdk config file).
- Create the root command to which all the application commands will be added as subcommands and add any required flags to it, such as
- Add subcommands for all the possible user interactions, including transaction commands and query commands.
- Create an Executor and execute the root command.
See an example of
main() function from the
The rest of the document will detail what needs to be implemented for each step and include smaller portions of code from the nameservice CLI
# Adding Commands to the CLI
Every application CLI first constructs a root command, then adds functionality by aggregating subcommands (often with further nested subcommands) using
rootCmd.AddCommand(). The bulk of an application's unique capabilities lies in its transaction and query commands, called
# Root Command
The root command (called
rootCmd) is what the user first types into the command line to indicate which application they wish to interact with. The string used to invoke the command (the "Use" field) is typically the name of the application suffixed with
appcli. The root command typically includes the following commands to support basic functionality in the application.
- Status command from the SDK rpc client tools, which prints information about the status of the connected
Node. The Status of a node includes
- Config command from the SDK client tools, which allows the user to edit a
config.tomlfile that sets values for flags such as
--nodethey wish to connect to. The
configcommand can be invoked by typing
appcli configwith optional arguments
<key> [value]and a
--getflag to query configurations or
--homeflag to create a new configuration.
- Keys commands from the SDK client tools, which includes a collection of subcommands for using the key functions in the SDK crypto tools, including adding a new key and saving it to disk, listing all public keys stored in the key manager, and deleting a key. For example, users can type
appcli keys add <name>to add a new key and save an encrypted copy to disk, using the flag
--recoverto recover a private key from a seed phrase or the flag
--multisigto group multiple keys together to create a multisig key. For full details on the
addkey command, see the code here. For more details about usage of
--keyring-backendfor storage of key credentials look at the keyring docs.
- Transaction commands.
- Query commands.
Next is an example
main() function from the
nameservice application. It instantiates the root command, adds a persistent flag and
PreRun function to be run before every execution, and adds all of the necessary subcommands.
keys subcommands are common across most applications and do not interact with application state. The bulk of an application's functionality - what users can actually do with it - is enabled by its transaction commands.
# Transaction Commands
txCmd function adds all the transaction available to end-users for the application. This typically includes:
- Sign command from the
authmodule that signs messages in a transaction. To enable multisig, add the
MultiSigncommand. Since every transaction requires some sort of signature in order to be valid, thithe signing command is necessary for every application.
- Broadcast command from the SDK client tools, to broadcast transactions.
- Send command from the
bankmodule, which is a transaction that allows accounts to send coins to one another, including gas and fees for transactions.
- All module transaction commands the application is dependent on, retrieved by using the basic module manager's
Here is an example of a
txCmd aggregating these subcommands from the
# Query Commands
Queries are objects that allow users to retrieve information about the application's state. To enable the creation of transactions using the CLI interface, a function
txCmd is generally added to the
queryCmd function adds all the queries available to end-users for the application. This typically includes:
- QueryTx and/or other transaction query commands] from the
authmodule which allow the user to search for a transaction by inputting its hash, a list of tags, or a block height. These queries allow users to see if transactions have been included in a block.
- Account command from the
authmodule, which displays the state (e.g. account balance) of an account given an address.
- Validator command from the SDK rpc client tools, which displays the validator set of a given height.
- Block command from the SDK rpc client tools, which displays the block data for a given height.
- All module query commands the application is dependent on, retrieved by using the basic module manager's
Here is an example of a
queryCmd aggregating subcommands from the
Flags are used to modify commands; developers can include them in a
flags.go file with their CLI. Users can explicitly include them in commands or pre-configure them by entering a command in the format
appcli config <flag> <value> into their command line. Commonly pre-configured flags include the
--node to connect to and
--chain-id of the blockchain the user wishes to interact with.
A persistent flag (as opposed to a local flag) added to a command transcends all of its children: subcommands will inherit the configured values for these flags. Additionally, all flags have default values when they are added to commands; some toggle an option off but others are empty values that the user needs to override to create valid commands. A flag can be explicitly marked as required so that an error is automatically thrown if the user does not provide a value, but it is also acceptable to handle unexpected missing flags differently.
Flags are added to commands directly (generally in the module's CLI file where module commands are defined) and no flag except for the
rootCmd persistent flags has to be added at application level. It is common to add a persistent flag for
--chain-id, the unique identifier of the blockchain the application pertains to, to the root command. Adding this flag can be done in the
main() function. Adding this flag makes sense as the chain ID should not be changing across commands in this application CLI. Here is an example from the
The last function to define in
initConfig, which does exactly what it sounds like - initialize configurations. To call this function, set it as a
PersistentPreRunE function for the root command, so that it always executes before the main execution of the root command and any of its subcommands.
initConfig() does the following:
- Read in the
config.tomlfile. This same file is edited through
- Use the Viper to read in configurations from the file and set them.
- Set any persistent flags defined by the user:
Here is an example of an
initConfig() function from the nameservice tutorial CLI:
And an example of how to add
initConfig as a
PersistentPreRunE to the root command: