Repository: cosmos/relayer
Pull request #744 (2022-05-14 00:23:19)

Map config by chain_name and use chain_name across relayer instead of chain_ID
**This is a breaking change!** - users will need to rebuild their config files.

This refactor parses out the config file by Chain-name.
It also implements `chain_name` instead of `chain_id` across all relevant commands. 

**Benefits:**
- Allows users to run queriers and commands based off the Chain Name rather than the Chain-ID
- Makes the config file easier to read
- Will simplify fetching paths from the chain-registry (TODO)

It also improved the efficiency of adding chains and verifying RPC endpoints from chain-registry.
-Should help with rate limit https://github.com/cosmos/relayer/issues/562

Thank you for the help @agouin. 



mark-rushakoff: The README likely needs updating too, by the way.

Patch:
diff --git a/README.md b/README.md
index 7bea8df..1740273 100644
--- a/README.md
+++ b/README.md
@@ -84,8 +84,8 @@ Additional information on how IBC works can be found [here](https://ibc.cosmos.n
 
    To add the chain config files manually, example config files have been included [here](https://github.com/cosmos/relayer/tree/main/docs/example-configs/)
    ```shell
-   $ rly chains add --url https://raw.githubusercontent.com/cosmos/relayer/main/docs/example-configs/cosmoshub-4.json
-   $ rly chains add --url https://raw.githubusercontent.com/cosmos/relayer/main/docs/example-configs/osmosis-1.json
+   $ rly chains add --url https://raw.githubusercontent.com/cosmos/relayer/main/docs/example-configs/cosmoshub-4.json cosmoshub
+   $ rly chains add --url https://raw.githubusercontent.com/cosmos/relayer/main/docs/example-configs/osmosis-1.json osmosis
    ```
    
 4. **Import OR create new keys for the relayer to use when signing and relaying transactions.**
@@ -95,15 +95,15 @@ Additional information on how IBC works can be found [here](https://ibc.cosmos.n
    If you need to generate a new private key you can use the `add` subcommand.
 
     ```shell
-    $ rly keys add cosmoshub-4 [key-name]  
-    $ rly keys add osmosis-1 [key-name]  
+    $ rly keys add cosmoshub [key-name]  
+    $ rly keys add osmosis [key-name]  
     ```
   
    If you already have a private key and want to restore it from your mnemonic you can use the `restore` subcommand.
 
    ```shell
-   $ rly keys restore cosmoshub-4 [key-name] "mnemonic words here"
-   $ rly keys restore osmosis-1 [key-name] "mnemonic words here"
+   $ rly keys restore cosmoshub [key-name] "mnemonic words here"
+   $ rly keys restore osmosis [key-name] "mnemonic words here"
    ```
 
 5. **Edit the relayer's `key` values in the config file to match the `key-name`'s chosen above.**
@@ -127,8 +127,8 @@ Additional information on how IBC works can be found [here](https://ibc.cosmos.n
    You can query the balance of each configured key by running:  
 
    ```shell
-   $ rly q balance cosmoshub-4
-   $ rly q balance osmosis-1
+   $ rly q balance cosmoshub
+   $ rly q balance osmosis
    ```
 
 7. **Configure path meta-data in config file.**
diff --git a/cmd/appstate.go b/cmd/appstate.go
index 1091894..377782a 100644
--- a/cmd/appstate.go
+++ b/cmd/appstate.go
@@ -145,7 +145,7 @@ func (a *appState) OverwriteConfig(cfg *Config) error {
 	}
 
 	// marshal the new config
-	out, err := yaml.Marshal(ConfigToWrapper(cfg))
+	out, err := yaml.Marshal(cfg.Wrapped())
 	if err != nil {
 		return err
 	}
diff --git a/cmd/chains.go b/cmd/chains.go
index 98d9f3c..140195e 100644
--- a/cmd/chains.go
+++ b/cmd/chains.go
@@ -3,10 +3,12 @@ package cmd
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net/http"
 	"net/url"
 	"os"
+	"path/filepath"
 	"strings"
 
 	"github.com/cosmos/relayer/v2/relayer"
@@ -44,7 +46,7 @@ func chainsCmd(a *appState) *cobra.Command {
 
 func chainsAddrCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "address chain_id",
+		Use:     "address chain_name",
 		Aliases: []string{"addr"},
 		Short:   "Returns a chain's configured key's address",
 		Args:    withUsage(cobra.ExactArgs(1)),
@@ -52,9 +54,9 @@ func chainsAddrCmd(a *appState) *cobra.Command {
 $ %s chains address ibc-0
 $ %s ch addr ibc-0`, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			address, err := chain.ChainProvider.ShowAddress(chain.ChainProvider.Key())
@@ -71,7 +73,7 @@ $ %s ch addr ibc-0`, appName, appName)),
 
 func chainsShowCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "show chain_id",
+		Use:     "show chain_name",
 		Aliases: []string{"s"},
 		Short:   "Returns a chain's configuration data",
 		Args:    withUsage(cobra.ExactArgs(1)),
@@ -81,9 +83,9 @@ $ %s chains show ibc-0 --yaml
 $ %s ch s ibc-0 --json
 $ %s ch s ibc-0 --yaml`, appName, appName, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			c, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			c, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 			jsn, err := cmd.Flags().GetBool(flagJSON)
 			if err != nil {
@@ -120,7 +122,7 @@ $ %s ch s ibc-0 --yaml`, appName, appName, appName, appName)),
 
 func chainsDeleteCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "delete chain_id",
+		Use:     "delete chain_name",
 		Aliases: []string{"d"},
 		Short:   "Removes chain from config based off chain-id",
 		Args:    withUsage(cobra.ExactArgs(1)),
@@ -128,6 +130,10 @@ func chainsDeleteCmd(a *appState) *cobra.Command {
 $ %s chains delete ibc-0
 $ %s ch d ibc-0`, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
+			_, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
+			}
 			a.Config.DeleteChain(args[0])
 			return a.OverwriteConfig(a.Config)
 		},
@@ -205,7 +211,7 @@ $ %s ch l`, appName, appName)),
 				return err
 			}
 
-			configs := ConfigToWrapper(a.Config).ProviderConfigs
+			configs := a.Config.Wrapped().ProviderConfigs
 			if len(configs) == 0 {
 				fmt.Fprintln(cmd.ErrOrStderr(), "warning: no chains found (do you need to run 'rly chains add'?)")
 			}
@@ -228,7 +234,8 @@ $ %s ch l`, appName, appName)),
 				fmt.Fprintln(cmd.OutOrStdout(), string(out))
 				return nil
 			default:
-				for i, c := range a.Config.Chains {
+				i := 0
+				for _, c := range a.Config.Chains {
 					var (
 						key = xIcon
 						p   = xIcon
@@ -249,6 +256,7 @@ $ %s ch l`, appName, appName)),
 							p = check
 						}
 					}
+					i++
 					fmt.Fprintf(cmd.OutOrStdout(), "%2d: %-20s -> type(%s) key(%s) bal(%s) path(%s)\n", i, c.ChainID(), c.ChainProvider.Type(), key, bal, p)
 				}
 				return nil
@@ -267,23 +275,39 @@ func chainsAddCmd(a *appState) *cobra.Command {
 		Args: withUsage(cobra.MinimumNArgs(0)),
 		Example: fmt.Sprintf(` $ %s chains add cosmoshub
  $ %s chains add cosmoshub osmosis
- $ %s chains add --file chains/ibc0.json
- $ %s chains add --url https://relayer.com/ibc0.json`, appName, appName, appName, appName),
+ $ %s chains add --file chains/ibc0.json ibc0
+ $ %s chains add --url https://relayer.com/ibc0.json ibc0`, appName, appName, appName, appName),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			file, url, err := getAddInputs(cmd)
 			if err != nil {
 				return err
 			}
 
+			if ok := a.Config; ok == nil {
+				return fmt.Errorf("config not initialized, consider running `rly config init`")
+			}
+
 			// default behavior fetch from chain registry
 			// still allow for adding config from url or file
 			switch {
 			case file != "":
-				if err := addChainFromFile(a, file); err != nil {
+				var chainName string
+				switch len(args) {
+				case 0:
+					chainName = strings.Split(filepath.Base(file), ".")[0]
+				case 1:
+					chainName = args[0]
+				default:
+					return errors.New("one chain name is required")
+				}
+				if err := addChainFromFile(a, chainName, file); err != nil {
 					return err
 				}
 			case url != "":
-				if err := addChainFromURL(a, url); err != nil {
+				if len(args) != 1 {
+					return errors.New("one chain name is required")
+				}
+				if err := addChainFromURL(a, args[0], url); err != nil {
 					return err
 				}
 			default:
@@ -326,7 +350,7 @@ $ %s ch ad testnet/chains/`, appName, appName)),
 
 // addChainFromFile reads a JSON-formatted chain from the named file
 // and adds it to a's chains.
-func addChainFromFile(a *appState, file string) error {
+func addChainFromFile(a *appState, chainName string, file string) error {
 	// If the user passes in a file, attempt to read the chain config from that file
 	var pcw ProviderConfigWrapper
 	if _, err := os.Stat(file); err != nil {
@@ -344,7 +368,7 @@ func addChainFromFile(a *appState, file string) error {
 
 	prov, err := pcw.Value.NewProvider(
 		a.Log.With(zap.String("provider_type", pcw.Type)),
-		a.HomePath, a.Debug,
+		a.HomePath, a.Debug, chainName,
 	)
 	if err != nil {
 		return fmt.Errorf("failed to build ChainProvider for %s: %w", file, err)
@@ -360,7 +384,7 @@ func addChainFromFile(a *appState, file string) error {
 
 // addChainFromURL fetches a JSON-encoded chain from the given URL
 // and adds it to a's chains.
-func addChainFromURL(a *appState, rawurl string) error {
+func addChainFromURL(a *appState, chainName string, rawurl string) error {
 	u, err := url.Parse(rawurl)
 	if err != nil || u.Scheme == "" || u.Host == "" {
 		return fmt.Errorf("invalid URL %s", rawurl)
@@ -384,7 +408,7 @@ func addChainFromURL(a *appState, rawurl string) error {
 	// build the ChainProvider before initializing the chain
 	prov, err := pcw.Value.NewProvider(
 		a.Log.With(zap.String("provider_type", pcw.Type)),
-		a.HomePath, a.Debug,
+		a.HomePath, a.Debug, chainName,
 	)
 	if err != nil {
 		return fmt.Errorf("failed to build ChainProvider for %s: %w", rawurl, err)
@@ -403,8 +427,16 @@ func addChainsFromRegistry(ctx context.Context, a *appState, chains []string) er
 	if err != nil {
 		return err
 	}
-
+	foundError := false
 	for _, chain := range chains {
+		if _, ok := a.Config.Chains[chain]; ok {
+			a.Log.Warn(
+				"Chain already exists",
+				zap.String("chain", chain),
+				zap.String("source_link", chainRegistry.SourceLink()),
+			)
+			continue
+		}
 		found := false
 		for _, possibleChain := range allChains {
 			if chain == possibleChain {
@@ -427,6 +459,7 @@ func addChainsFromRegistry(ctx context.Context, a *appState, chains []string) er
 					zap.String("chain", chain),
 					zap.Error(err),
 				)
+				foundError = true
 				continue
 			}
 
@@ -437,12 +470,14 @@ func addChainsFromRegistry(ctx context.Context, a *appState, chains []string) er
 					zap.String("chain", chain),
 					zap.Error(err),
 				)
+				foundError = true
 				continue
 			}
 
 			// build the ChainProvider
 			pcfg := &cosmos.CosmosProviderConfig{
 				Key:            chainConfig.Key,
+				ChainName:      chainInfo.ChainName,
 				ChainID:        chainConfig.ChainID,
 				RPCAddr:        chainConfig.RPCAddr,
 				AccountPrefix:  chainConfig.AccountPrefix,
@@ -457,7 +492,7 @@ func addChainsFromRegistry(ctx context.Context, a *appState, chains []string) er
 
 			prov, err := pcfg.NewProvider(
 				a.Log.With(zap.String("provider_type", "cosmos")),
-				a.HomePath, a.Debug,
+				a.HomePath, a.Debug, chainInfo.ChainName,
 			)
 			if err != nil {
 				a.Log.Warn(
@@ -465,6 +500,7 @@ func addChainsFromRegistry(ctx context.Context, a *appState, chains []string) er
 					zap.String("chain_id", chainConfig.ChainID),
 					zap.Error(err),
 				)
+				foundError = true
 				continue
 			}
 
@@ -476,13 +512,16 @@ func addChainsFromRegistry(ctx context.Context, a *appState, chains []string) er
 					zap.String("chain", chain),
 					zap.Error(err),
 				)
-				return err
+				foundError = true
+				continue
 			}
 
 			// found the correct chain so move on to next chain in chains
 			break
 		}
 	}
-
+	if foundError {
+		return errors.New("some chain(s) failed to be added to config")
+	}
 	return nil
 }
diff --git a/cmd/config.go b/cmd/config.go
index 542c67e..c4fcf1a 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -92,14 +92,14 @@ $ %s cfg list`, appName, defaultHome, appName)),
 			case yml && jsn:
 				return fmt.Errorf("can't pass both --json and --yaml, must pick one")
 			case jsn:
-				out, err := json.Marshal(ConfigToWrapper(a.Config))
+				out, err := json.Marshal(a.Config.Wrapped())
 				if err != nil {
 					return err
 				}
 				fmt.Fprintln(cmd.OutOrStdout(), string(out))
 				return nil
 			default:
-				out, err := yaml.Marshal(ConfigToWrapper(a.Config))
+				out, err := yaml.Marshal(a.Config.Wrapped())
 				if err != nil {
 					return err
 				}
@@ -112,7 +112,7 @@ $ %s cfg list`, appName, defaultHome, appName)),
 	return yamlFlag(a.Viper, jsonFlag(a.Viper, cmd))
 }
 
-// Command for inititalizing an empty config at the --home location
+// Command for initializing an empty config at the --home location
 func configInitCmd() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:     "init",
@@ -241,10 +241,10 @@ func addChainsFromDirectory(stderr io.Writer, a *appState, dir string) error {
 			fmt.Fprintf(stderr, "failed to unmarshal file %s. Err: %v skipping...\n", pth, err)
 			continue
 		}
-
+		chainName := strings.Split(f.Name(), ".")[0]
 		prov, err := pcw.Value.NewProvider(
 			a.Log.With(zap.String("provider_type", pcw.Type)),
-			a.HomePath, a.Debug,
+			a.HomePath, a.Debug, chainName,
 		)
 		if err != nil {
 			fmt.Fprintf(stderr, "failed to build ChainProvider for %s. Err: %v \n", pth, err)
@@ -304,19 +304,17 @@ func addPathsFromDirectory(ctx context.Context, stderr io.Writer, a *appState, d
 	return nil
 }
 
-// ConfigToWrapper converts the Config struct into a ConfigOutputWrapper struct
-func ConfigToWrapper(config *Config) *ConfigOutputWrapper {
-	cfgw := &ConfigOutputWrapper{Global: config.Global, Paths: config.Paths}
-	var providers []*ProviderConfigWrapper
+// Wrapped converts the Config struct into a ConfigOutputWrapper struct
+func (config *Config) Wrapped() *ConfigOutputWrapper {
+	providers := make(ProviderConfigs)
 	for _, chain := range config.Chains {
 		pcfgw := &ProviderConfigWrapper{
 			Type:  chain.ChainProvider.Type(),
 			Value: chain.ChainProvider.ProviderConfig(),
 		}
-		providers = append(providers, pcfgw)
+		providers[chain.ChainProvider.ChainName()] = pcfgw
 	}
-	cfgw.ProviderConfigs = providers
-	return cfgw
+	return &ConfigOutputWrapper{Global: config.Global, ProviderConfigs: providers, Paths: config.Paths}
 }
 
 // Config represents the config file for the relayer
@@ -335,12 +333,12 @@ type ConfigOutputWrapper struct {
 
 // ConfigInputWrapper is an intermediary type for parsing the config.yaml file
 type ConfigInputWrapper struct {
-	Global          GlobalConfig                 `yaml:"global"`
-	ProviderConfigs []*ProviderConfigYAMLWrapper `yaml:"chains"`
-	Paths           relayer.Paths                `yaml:"paths"`
+	Global          GlobalConfig                          `yaml:"global"`
+	ProviderConfigs map[string]*ProviderConfigYAMLWrapper `yaml:"chains"`
+	Paths           relayer.Paths                         `yaml:"paths"`
 }
 
-type ProviderConfigs []*ProviderConfigWrapper
+type ProviderConfigs map[string]*ProviderConfigWrapper
 
 // ProviderConfigWrapper is an intermediary type for parsing arbitrary ProviderConfigs from json files and writing to json/yaml files
 type ProviderConfigWrapper struct {
@@ -484,7 +482,7 @@ func (c *Config) AddChain(chain *relayer.Chain) (err error) {
 	if chn != nil || err == nil {
 		return fmt.Errorf("chain with ID %s already exists in config", chainId)
 	}
-	c.Chains = append(c.Chains, chain)
+	c.Chains[chain.ChainProvider.ChainName()] = chain
 	return nil
 }
 
@@ -539,13 +537,7 @@ func (c *Config) AddPath(name string, path *relayer.Path) (err error) {
 
 // DeleteChain modifies c in-place to remove any chains that have the given name.
 func (c *Config) DeleteChain(chain string) {
-	var set relayer.Chains
-	for _, ch := range c.Chains {
-		if ch.ChainID() != chain {
-			set = append(set, ch)
-		}
-	}
-	c.Chains = set
+	delete(c.Chains, chain)
 }
 
 // validateConfig is used to validate the GlobalConfig values
@@ -592,18 +584,18 @@ func initConfig(cmd *cobra.Command, a *appState) error {
 			}
 
 			// build the config struct
-			var chains relayer.Chains
-			for _, pcfg := range cfgWrapper.ProviderConfigs {
+			chains := make(relayer.Chains)
+			for chainName, pcfg := range cfgWrapper.ProviderConfigs {
 				prov, err := pcfg.Value.(provider.ProviderConfig).NewProvider(
 					a.Log.With(zap.String("provider_type", pcfg.Type)),
-					a.HomePath, a.Debug,
+					a.HomePath, a.Debug, chainName,
 				)
 				if err != nil {
 					return fmt.Errorf("failed to build ChainProviders: %w", err)
 				}
 
 				chain := relayer.NewChain(a.Log, prov, a.Debug)
-				chains = append(chains, chain)
+				chains[chainName] = chain
 			}
 
 			a.Config = &Config{
diff --git a/cmd/errors.go b/cmd/errors.go
index 721a79b..31f5ee0 100644
--- a/cmd/errors.go
+++ b/cmd/errors.go
@@ -13,6 +13,10 @@ func errKeyDoesntExist(name string) error {
 	return fmt.Errorf("a key with name %s doesn't exist", name)
 }
 
+func errChainNotFound(chainName string) error {
+	return fmt.Errorf("chain with name \"%s\" not found in config. consider running `rly chains add %s`", chainName, chainName)
+}
+
 var (
 	errMultipleAddFlags = errors.New("expected either --file/-f OR --url/u, found multiple")
 )
diff --git a/cmd/keys.go b/cmd/keys.go
index ad2c8b3..ddd17d8 100644
--- a/cmd/keys.go
+++ b/cmd/keys.go
@@ -55,18 +55,18 @@ func keysCmd(a *appState) *cobra.Command {
 // keysAddCmd respresents the `keys add` command
 func keysAddCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "add chain_id name",
+		Use:     "add chain_name key_name",
 		Aliases: []string{"a"},
 		Short:   "Adds a key to the keychain associated with a particular chain",
 		Args:    withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
 $ %s keys add ibc-0
 $ %s keys add ibc-1 key2
-$ %s k a ibc-2 testkey`, appName, appName, appName)),
+$ %s k a cosmoshub testkey`, appName, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			keyName := args[1]
@@ -101,18 +101,19 @@ $ %s k a ibc-2 testkey`, appName, appName, appName)),
 // keysRestoreCmd respresents the `keys add` command
 func keysRestoreCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "restore chain_id name mnemonic",
+		Use:     "restore chain_name key_name mnemonic",
 		Aliases: []string{"r"},
 		Short:   "Restores a mnemonic to the keychain associated with a particular chain",
 		Args:    withUsage(cobra.ExactArgs(3)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
 $ %s keys restore ibc-0 testkey "[mnemonic-words]"
-$ %s k r ibc-1 faucet-key "[mnemonic-words]"`, appName, appName)),
+$ %s k r cosmoshub faucet-key "[mnemonic-words]"`, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			keyName := args[1]
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			if chain.ChainProvider.KeyExists(keyName) {
@@ -141,18 +142,18 @@ $ %s k r ibc-1 faucet-key "[mnemonic-words]"`, appName, appName)),
 // keysDeleteCmd respresents the `keys delete` command
 func keysDeleteCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "delete chain_id name",
+		Use:     "delete chain_name key_name",
 		Aliases: []string{"d"},
 		Short:   "Deletes a key from the keychain associated with a particular chain",
 		Args:    withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
 $ %s keys delete ibc-0 -y
 $ %s keys delete ibc-1 key2 -y
-$ %s k d ibc-2 testkey`, appName, appName, appName)),
+$ %s k d cosmoshub default`, appName, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			keyName := args[1]
@@ -167,7 +168,7 @@ $ %s k d ibc-2 testkey`, appName, appName, appName)),
 				}
 			}
 
-			err = chain.ChainProvider.DeleteKey(keyName)
+			err := chain.ChainProvider.DeleteKey(keyName)
 			if err != nil {
 				return err
 			}
@@ -202,7 +203,7 @@ func askForConfirmation(a *appState, stdin io.Reader, stderr io.Writer) bool {
 // keysListCmd respresents the `keys list` command
 func keysListCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "list chain_id",
+		Use:     "list chain_name",
 		Aliases: []string{"l"},
 		Short:   "Lists keys from the keychain associated with a particular chain",
 		Args:    withUsage(cobra.ExactArgs(1)),
@@ -210,11 +211,11 @@ func keysListCmd(a *appState) *cobra.Command {
 $ %s keys list ibc-0
 $ %s k l ibc-1`, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chainID := args[0]
+			chainName := args[0]
 
-			chain, err := a.Config.Chains.Get(chainID)
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[chainName]
+			if !ok {
+				return errChainNotFound(chainName)
 			}
 
 			info, err := chain.ChainProvider.ListAddresses()
@@ -223,7 +224,7 @@ $ %s k l ibc-1`, appName, appName)),
 			}
 
 			if len(info) == 0 {
-				fmt.Fprintf(cmd.ErrOrStderr(), "warning: no keys found for chain %s (do you need to run 'rly keys add %s'?)\n", chainID, chainID)
+				fmt.Fprintf(cmd.ErrOrStderr(), "warning: no keys found for chain %s (do you need to run 'rly keys add %s'?)\n", chainName, chainName)
 			}
 
 			for key, val := range info {
@@ -240,7 +241,7 @@ $ %s k l ibc-1`, appName, appName)),
 // keysShowCmd respresents the `keys show` command
 func keysShowCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "show chain_id [name]",
+		Use:     "show chain_name [key_name]",
 		Aliases: []string{"s"},
 		Short:   "Shows a key from the keychain associated with a particular chain",
 		Args:    withUsage(cobra.RangeArgs(1, 2)),
@@ -249,9 +250,9 @@ $ %s keys show ibc-0
 $ %s keys show ibc-1 key2
 $ %s k s ibc-2 testkey`, appName, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			var keyName string
@@ -281,18 +282,18 @@ $ %s k s ibc-2 testkey`, appName, appName, appName)),
 // keysExportCmd respresents the `keys export` command
 func keysExportCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "export chain_id name",
+		Use:     "export chain_name key_name",
 		Aliases: []string{"e"},
 		Short:   "Exports a privkey from the keychain associated with a particular chain",
 		Args:    withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
 $ %s keys export ibc-0 testkey
-$ %s k e ibc-2 testkey`, appName, appName)),
+$ %s k e cosmoshub testkey`, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			keyName := args[1]
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			if !chain.ChainProvider.KeyExists(keyName) {
diff --git a/cmd/query.go b/cmd/query.go
index 20747ea..b5c63ef 100644
--- a/cmd/query.go
+++ b/cmd/query.go
@@ -53,7 +53,7 @@ func queryCmd(a *appState) *cobra.Command {
 
 func queryIBCDenoms(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "ibc-denoms chain_id",
+		Use:   "ibc-denoms chain_name",
 		Short: "query denomination traces for a given network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(1)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -62,9 +62,9 @@ $ %s q ibc-denoms ibc-0`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			h, err := chain.ChainProvider.QueryLatestHeight(cmd.Context())
@@ -93,14 +93,14 @@ func queryBaseDenomFromIBCDenom(a *appState) *cobra.Command {
 		Short: "query that retrieves the base denom from the IBC denomination trace",
 		Args:  withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
-$ %s query denom-trace osmosis-1 9BBA9A1C257E971E38C1422780CE6F0B0686F0A3085E2D61118D904BFE0F5F5E
-$ %s q denom-trace osmosis-1 9BBA9A1C257E971E38C1422780CE6F0B0686F0A3085E2D61118D904BFE0F5F5E`,
+$ %s query denom-trace osmosis 9BBA9A1C257E971E38C1422780CE6F0B0686F0A3085E2D61118D904BFE0F5F5E
+$ %s q denom-trace osmosis 9BBA9A1C257E971E38C1422780CE6F0B0686F0A3085E2D61118D904BFE0F5F5E`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			c, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			c, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 			res, err := c.ChainProvider.QueryDenomTrace(cmd.Context(), args[1])
 			if err != nil {
@@ -117,7 +117,7 @@ $ %s q denom-trace osmosis-1 9BBA9A1C257E971E38C1422780CE6F0B0686F0A3085E2D61118
 
 func queryTx(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "tx chain_id tx_hash",
+		Use:   "tx chain_name tx_hash",
 		Short: "query for a transaction on a given network by transaction hash and chain ID",
 		Args:  withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -126,9 +126,9 @@ $ %s q tx ibc-0 A5DF8D272F1C451CFF92BA6C41942C4D29B5CF180279439ED6AB038282F956BE
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			txs, err := chain.ChainProvider.QueryTx(cmd.Context(), args[1])
@@ -151,7 +151,7 @@ $ %s q tx ibc-0 A5DF8D272F1C451CFF92BA6C41942C4D29B5CF180279439ED6AB038282F956BE
 
 func queryTxs(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "txs chain_id events",
+		Use:   "txs chain_name events",
 		Short: "query for transactions on a given network by chain ID and a set of transaction events",
 		Long: strings.TrimSpace(`Search for a paginated list of transactions that match the given set of
 events. Each event takes the form of '{eventType}.{eventAttribute}={value}' with multiple events
@@ -167,9 +167,9 @@ $ %s q txs ibc-0 "message.action=transfer"`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			offset, err := cmd.Flags().GetUint64(flags.FlagOffset)
@@ -255,7 +255,7 @@ $ %s q txs ibc-0 "message.action=transfer"`,
 
 func queryBalanceCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "balance chain_id [key_name]",
+		Use:     "balance chain_name [key_name]",
 		Aliases: []string{"bal"},
 		Short:   "query the relayer's account balance on a given network by chain-ID",
 		Args:    withUsage(cobra.RangeArgs(1, 2)),
@@ -265,9 +265,9 @@ $ %s query balance ibc-0 testkey`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			showDenoms, err := cmd.Flags().GetBool(flagIBCDenoms)
@@ -304,7 +304,7 @@ $ %s query balance ibc-0 testkey`,
 
 func queryHeaderCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "header chain_id [height]",
+		Use:   "header chain_name [height]",
 		Short: "query the header of a network by chain ID at a given height or the latest height",
 		Args:  withUsage(cobra.RangeArgs(1, 2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -313,12 +313,13 @@ $ %s query header ibc-0 1400`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			var header ibcexported.Header
+			var err error
 			switch len(args) {
 			case 1:
 				header, err = chain.ChainProvider.GetLightSignedHeaderAtHeight(cmd.Context(), 0)
@@ -351,7 +352,7 @@ $ %s query header ibc-0 1400`,
 // the chain as defined in https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#query
 func queryNodeStateCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "node-state chain_id",
+		Use:   "node-state chain_name",
 		Short: "query the consensus state of a network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(1)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -360,9 +361,9 @@ $ %s q node-state ibc-1`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			height, err := chain.ChainProvider.QueryLatestHeight(cmd.Context())
@@ -391,18 +392,18 @@ $ %s q node-state ibc-1`,
 
 func queryClientCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "client chain_id client_id",
+		Use:   "client chain_name client_id",
 		Short: "query the state of a light client on a network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
-$ %s query client ibc-0 ibczeroclient
+$ %s query client osmosis 07-tendermint-259
 $ %s query client ibc-0 ibczeroclient --height 1205`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			height, err := cmd.Flags().GetInt64(flags.FlagHeight)
@@ -442,19 +443,19 @@ $ %s query client ibc-0 ibczeroclient --height 1205`,
 
 func queryClientsCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "clients chain_id",
+		Use:     "clients chain_name",
 		Aliases: []string{"clnts"},
 		Short:   "query for all light client states on a network by chain ID",
 		Args:    withUsage(cobra.ExactArgs(1)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
-$ %s query clients ibc-0
+$ %s query clients osmosis
 $ %s query clients ibc-2 --offset 2 --limit 30`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			// TODO fix pagination
@@ -534,9 +535,9 @@ $ %s q conns ibc-1`,
 			appName, appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			// TODO fix pagination
@@ -570,7 +571,7 @@ $ %s q conns ibc-1`,
 
 func queryConnectionsUsingClient(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "client-connections chain_id client_id",
+		Use:   "client-connections chain_name client_id",
 		Short: "query for all connections for a given client on a network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -579,9 +580,11 @@ $ %s query client-connections ibc-0 ibczeroclient --height 1205`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			//TODO - Add pagination
+
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			if err := chain.AddPath(args[1], dcon); err != nil {
@@ -621,7 +624,7 @@ $ %s query client-connections ibc-0 ibczeroclient --height 1205`,
 
 func queryConnection(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "connection chain_id connection_id",
+		Use:     "connection chain_name connection_id",
 		Aliases: []string{"conn"},
 		Short:   "query the connection state for a given connection id on a network by chain ID",
 		Args:    withUsage(cobra.ExactArgs(2)),
@@ -631,9 +634,9 @@ $ %s q conn ibc-1 ibconeconn`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			if err := chain.AddPath(dcli, args[1]); err != nil {
@@ -666,7 +669,7 @@ $ %s q conn ibc-1 ibconeconn`,
 
 func queryConnectionChannels(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "connection-channels chain_id connection_id",
+		Use:   "connection-channels chain_name connection_id",
 		Short: "query all channels associated with a given connection on a network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(2)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -675,12 +678,12 @@ $ %s query connection-channels ibc-2 ibcconnection2 --offset 2 --limit 30`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
-			if err = chain.AddPath(dcli, args[1]); err != nil {
+			if err := chain.AddPath(dcli, args[1]); err != nil {
 				return err
 			}
 
@@ -715,7 +718,7 @@ $ %s query connection-channels ibc-2 ibcconnection2 --offset 2 --limit 30`,
 
 func queryChannel(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "channel chain_id channel_id port_id",
+		Use:   "channel chain_name channel_id port_id",
 		Short: "query a channel by channel and port ID on a network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(3)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -724,14 +727,14 @@ $ %s query channel ibc-2 ibctwochannel transfer --height 1205`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			channelID := args[1]
 			portID := args[2]
-			if err = chain.AddPath(dcli, dcon); err != nil {
+			if err := chain.AddPath(dcli, dcon); err != nil {
 				return err
 			}
 
@@ -768,7 +771,7 @@ $ %s query channel ibc-2 ibctwochannel transfer --height 1205`,
 
 func queryChannels(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "channels chain_id",
+		Use:   "channels chain_name",
 		Short: "query for all channels on a network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(1)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -777,9 +780,9 @@ $ %s query channels ibc-2 --offset 2 --limit 30`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			// TODO fix pagination
@@ -813,7 +816,7 @@ $ %s query channels ibc-2 --offset 2 --limit 30`,
 
 func queryPacketCommitment(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "packet-commit chain_id channel_id port_id seq",
+		Use:   "packet-commit chain_name channel_id port_id seq",
 		Short: "query for the packet commitment given a sequence and channel ID on a network by chain ID",
 		Args:  withUsage(cobra.ExactArgs(4)),
 		Example: strings.TrimSpace(fmt.Sprintf(`
@@ -822,9 +825,9 @@ $ %s q packet-commit ibc-1 ibconechannel transfer 31`,
 			appName, appName,
 		)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			chain, err := a.Config.Chains.Get(args[0])
-			if err != nil {
-				return err
+			chain, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
 			}
 
 			channelID := args[1]
diff --git a/cmd/tx.go b/cmd/tx.go
index d86d746..e672a61 100644
--- a/cmd/tx.go
+++ b/cmd/tx.go
@@ -158,7 +158,7 @@ func createClientsCmd(a *appState) *cobra.Command {
 
 func createClientCmd(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "client src_chain_id dst_chain_id path_name",
+		Use:   "client src_chain_name dst_chain_name path_name",
 		Short: "create a client between two configured chains with a configured path",
 		Long: "Creates a working ibc client for chain configured on each end of the" +
 			" path by querying headers from each chain and then sending the corresponding create-client messages",
@@ -180,11 +180,13 @@ func createClientCmd(a *appState) *cobra.Command {
 				return err
 			}
 
-			src := args[0]
-			dst := args[1]
-			c, err := a.Config.Chains.Gets(src, dst)
-			if err != nil {
-				return err
+			src, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
+			}
+			dst, ok := a.Config.Chains[args[1]]
+			if !ok {
+				return errChainNotFound(args[1])
 			}
 
 			pathName := args[2]
@@ -193,21 +195,21 @@ func createClientCmd(a *appState) *cobra.Command {
 				return err
 			}
 
-			c[src].PathEnd = path.End(c[src].ChainID())
-			c[dst].PathEnd = path.End(c[dst].ChainID())
+			src.PathEnd = path.End(src.ChainID())
+			dst.PathEnd = path.End(dst.ChainID())
 
 			// ensure that keys exist
-			if exists := c[src].ChainProvider.KeyExists(c[src].ChainProvider.Key()); !exists {
-				return fmt.Errorf("key %s not found on src chain %s", c[src].ChainProvider.Key(), c[src].ChainID())
+			if exists := src.ChainProvider.KeyExists(src.ChainProvider.Key()); !exists {
+				return fmt.Errorf("key %s not found on src chain %s", src.ChainProvider.Key(), src.ChainID())
 			}
-			if exists := c[dst].ChainProvider.KeyExists(c[dst].ChainProvider.Key()); !exists {
-				return fmt.Errorf("key %s not found on dst chain %s", c[dst].ChainProvider.Key(), c[dst].ChainID())
+			if exists := dst.ChainProvider.KeyExists(dst.ChainProvider.Key()); !exists {
+				return fmt.Errorf("key %s not found on dst chain %s", dst.ChainProvider.Key(), dst.ChainID())
 			}
 
 			// Query the latest heights on src and dst and retry if the query fails
 			var srch, dsth int64
 			if err = retry.Do(func() error {
-				srch, dsth, err = relayer.QueryLatestHeights(cmd.Context(), c[src], c[dst])
+				srch, dsth, err = relayer.QueryLatestHeights(cmd.Context(), src, dst)
 				if srch == 0 || dsth == 0 || err != nil {
 					return fmt.Errorf("failed to query latest heights: %w", err)
 				}
@@ -219,7 +221,7 @@ func createClientCmd(a *appState) *cobra.Command {
 			// Query the light signed headers for src & dst at the heights srch & dsth, retry if the query fails
 			var srcUpdateHeader, dstUpdateHeader exported.Header
 			if err = retry.Do(func() error {
-				srcUpdateHeader, dstUpdateHeader, err = relayer.GetLightSignedHeadersAtHeights(cmd.Context(), c[src], c[dst], srch, dsth)
+				srcUpdateHeader, dstUpdateHeader, err = relayer.GetLightSignedHeadersAtHeights(cmd.Context(), src, dst, srch, dsth)
 				if err != nil {
 					return err
 				}
@@ -227,20 +229,20 @@ func createClientCmd(a *appState) *cobra.Command {
 			}, retry.Context(cmd.Context()), relayer.RtyAtt, relayer.RtyDel, relayer.RtyErr, retry.OnRetry(func(n uint, err error) {
 				a.Log.Info(
 					"Failed to get light signed header",
-					zap.String("src_chain_id", c[src].ChainID()),
+					zap.String("src_chain_id", src.ChainID()),
 					zap.Int64("src_height", srch),
-					zap.String("dst_chain_id", c[dst].ChainID()),
+					zap.String("dst_chain_id", dst.ChainID()),
 					zap.Int64("dst_height", dsth),
 					zap.Uint("attempt", n+1),
 					zap.Uint("max_attempts", relayer.RtyAttNum),
 					zap.Error(err),
 				)
-				srch, dsth, _ = relayer.QueryLatestHeights(cmd.Context(), c[src], c[dst])
+				srch, dsth, _ = relayer.QueryLatestHeights(cmd.Context(), src, dst)
 			})); err != nil {
 				return err
 			}
 
-			modified, err := relayer.CreateClient(cmd.Context(), c[src], c[dst], srcUpdateHeader, dstUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
+			modified, err := relayer.CreateClient(cmd.Context(), src, dst, srcUpdateHeader, dstUpdateHeader, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour, override)
 			if err != nil {
 				return err
 			}
@@ -854,7 +856,7 @@ $ %s tx relay-acks demo-path channel-0 -l 3 -s 6`,
 
 func xfersend(a *appState) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "transfer src_chain_id dst_chain_id amount dst_addr src_channel_id",
+		Use:   "transfer src_chain_name dst_chain_name amount dst_addr src_channel_id",
 		Short: "initiate a transfer from one network to another",
 		Long: `Initiate a token transfer via IBC between two networks. The created packet
 must be relayed to the destination chain.`,
@@ -866,10 +868,13 @@ $ %s tx transfer ibc-0 ibc-1 100000stake raw:non-bech32-address channel-0 --path
 $ %s tx raw send ibc-0 ibc-1 100000stake cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9nf39lk channel-0 --path demo -c 5
 `, appName, appName, appName, appName)),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			src, dst := args[0], args[1]
-			c, err := a.Config.Chains.Gets(src, dst)
-			if err != nil {
-				return err
+			src, ok := a.Config.Chains[args[0]]
+			if !ok {
+				return errChainNotFound(args[0])
+			}
+			dst, ok := a.Config.Chains[args[1]]
+			if !ok {
+				return errChainNotFound(args[1])
 			}
 
 			pathString, err := cmd.Flags().GetString(flagPath)
@@ -878,7 +883,7 @@ $ %s tx raw send ibc-0 ibc-1 100000stake cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9
 			}
 
 			var path *relayer.Path
-			if path, err = setPathsFromArgs(a, c[src], c[dst], pathString); err != nil {
+			if path, err = setPathsFromArgs(a, src, dst, pathString); err != nil {
 				return err
 			}
 
@@ -887,14 +892,14 @@ $ %s tx raw send ibc-0 ibc-1 100000stake cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9
 				return err
 			}
 
-			srch, err := c[src].ChainProvider.QueryLatestHeight(cmd.Context())
+			srch, err := src.ChainProvider.QueryLatestHeight(cmd.Context())
 			if err != nil {
 				return err
 			}
 
 			// Query all channels for the configured connection on the src chain
 			srcChannelID := args[4]
-			channels, err := c[src].ChainProvider.QueryConnectionChannels(cmd.Context(), srch, path.Src.ConnectionID)
+			channels, err := src.ChainProvider.QueryConnectionChannels(cmd.Context(), srch, path.Src.ConnectionID)
 			if err != nil {
 				return err
 			}
@@ -910,10 +915,10 @@ $ %s tx raw send ibc-0 ibc-1 100000stake cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9
 
 			if srcChannel == nil {
 				return fmt.Errorf("could not find channel{%s} for chain{%s}@connection{%s}",
-					srcChannelID, c[src], path.Src.ConnectionID)
+					srcChannelID, src, path.Src.ConnectionID)
 			}
 
-			dts, err := c[src].ChainProvider.QueryDenomTraces(cmd.Context(), 0, 100, srch)
+			dts, err := src.ChainProvider.QueryDenomTraces(cmd.Context(), 0, 100, srch)
 			if err != nil {
 				return err
 			}
@@ -943,7 +948,7 @@ $ %s tx raw send ibc-0 ibc-1 100000stake cosmos1skjwj5whet0lpe65qaq4rpq03hjxlwd9
 				dstAddr = rawDstAddr
 			}
 
-			return c[src].SendTransferMsg(cmd.Context(), a.Log, c[dst], amount, dstAddr, toHeightOffset, toTimeOffset, srcChannel)
+			return src.SendTransferMsg(cmd.Context(), a.Log, dst, amount, dstAddr, toHeightOffset, toTimeOffset, srcChannel)
 		},
 	}
 
diff --git a/relayer/chain.go b/relayer/chain.go
index 9081ad4..3aa00c7 100644
--- a/relayer/chain.go
+++ b/relayer/chain.go
@@ -36,6 +36,9 @@ type Chain struct {
 	debug bool
 }
 
+// Chains is a collection of Chain (mapped by chain_name)
+type Chains map[string]*Chain
+
 // ValidatePaths takes two chains and validates their paths
 func ValidatePaths(src, dst *Chain) error {
 	if err := src.PathEnd.ValidateFull(); err != nil {
@@ -111,9 +114,6 @@ func (c *Chain) String() string {
 	return string(out)
 }
 
-// Chains is a collection of Chain
-type Chains []*Chain
-
 // Get returns the configuration for a given chain
 func (c Chains) Get(chainID string) (*Chain, error) {
 	for _, chain := range c {
diff --git a/relayer/provider/cosmos/provider.go b/relayer/provider/cosmos/provider.go
index e79a931..443a513 100644
--- a/relayer/provider/cosmos/provider.go
+++ b/relayer/provider/cosmos/provider.go
@@ -118,6 +118,7 @@ func (cm CosmosMessage) MarshalLogObject(enc zapcore.ObjectEncoder) error {
 
 type CosmosProviderConfig struct {
 	Key            string  `json:"key" yaml:"key"`
+	ChainName      string  `json:"-" yaml:"-"`
 	ChainID        string  `json:"chain-id" yaml:"chain-id"`
 	RPCAddr        string  `json:"rpc-addr" yaml:"rpc-addr"`
 	AccountPrefix  string  `json:"account-prefix" yaml:"account-prefix"`
@@ -138,7 +139,7 @@ func (pc CosmosProviderConfig) Validate() error {
 }
 
 // NewProvider validates the CosmosProviderConfig, instantiates a ChainClient and then instantiates a CosmosProvider
-func (pc CosmosProviderConfig) NewProvider(log *zap.Logger, homepath string, debug bool) (provider.ChainProvider, error) {
+func (pc CosmosProviderConfig) NewProvider(log *zap.Logger, homepath string, debug bool, chainName string) (provider.ChainProvider, error) {
 	if err := pc.Validate(); err != nil {
 		return nil, err
 	}
@@ -152,6 +153,7 @@ func (pc CosmosProviderConfig) NewProvider(log *zap.Logger, homepath string, deb
 	if err != nil {
 		return nil, err
 	}
+	pc.ChainName = chainName
 	return &CosmosProvider{
 		log: log,
 
@@ -194,6 +196,10 @@ func (cc *CosmosProvider) ChainId() string {
 	return cc.PCfg.ChainID
 }
 
+func (cc *CosmosProvider) ChainName() string {
+	return cc.PCfg.ChainName
+}
+
 func (cc *CosmosProvider) Type() string {
 	return "cosmos"
 }
diff --git a/relayer/provider/provider.go b/relayer/provider/provider.go
index 8b62ec5..d82fd25 100644
--- a/relayer/provider/provider.go
+++ b/relayer/provider/provider.go
@@ -16,7 +16,7 @@ import (
 )
 
 type ProviderConfig interface {
-	NewProvider(log *zap.Logger, homepath string, debug bool) (ChainProvider, error)
+	NewProvider(log *zap.Logger, homepath string, debug bool, chainName string) (ChainProvider, error)
 	Validate() error
 }
 
@@ -114,6 +114,7 @@ type ChainProvider interface {
 	GetLightSignedHeaderAtHeight(ctx context.Context, h int64) (ibcexported.Header, error)
 	GetIBCUpdateHeader(ctx context.Context, srch int64, dst ChainProvider, dstClientId string) (ibcexported.Header, error)
 
+	ChainName() string
 	ChainId() string
 	Type() string
 	ProviderConfig() ProviderConfig


Test patch:
diff --git a/_test/relayer_chain_test.go b/_test/relayer_chain_test.go
index 5e5f3c0..e5c6c1d 100644
--- a/_test/relayer_chain_test.go
+++ b/_test/relayer_chain_test.go
@@ -57,7 +57,7 @@ func chainTest(t *testing.T, tcs []testChain) {
 			var err error
 			srcExpected, err = src.ChainProvider.QueryBalance(egCtx, src.ChainProvider.Key())
 			if srcExpected.IsZero() {
-				return fmt.Errorf("expected non-zero balance. Err: %w", err)
+				return fmt.Errorf("(src chain) expected non-zero balance. Err: %w", err)
 			}
 			return err
 		})
@@ -67,7 +67,7 @@ func chainTest(t *testing.T, tcs []testChain) {
 			var err error
 			dstExpected, err = dst.ChainProvider.QueryBalance(egCtx, dst.ChainProvider.Key())
 			if dstExpected.IsZero() {
-				return fmt.Errorf("expected non-zero balance. Err: %w", err)
+				return fmt.Errorf("(dst chain) expected non-zero balance. Err: %w", err)
 			}
 			return err
 		})
@@ -148,8 +148,8 @@ func TestGaiaReuseIdentifiers(t *testing.T) {
 	// TODO: fix and re-enable this test
 	t.Skip()
 	chains := spinUpTestChains(t, []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
 	}...)
 
 	var (
@@ -229,8 +229,8 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 	// need to figure out what this feature is supposed to do
 	t.Skip()
 	chains := spinUpTestChains(t, []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
 	}...)
 
 	var (
@@ -330,8 +330,8 @@ func TestGaiaMisbehaviourMonitoring(t *testing.T) {
 
 func TestRelayAllChannelsOnConnection(t *testing.T) {
 	chains := spinUpTestChains(t, []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
 	}...)
 
 	var (
@@ -535,10 +535,11 @@ func createTMClientHeader(t *testing.T, chainID string, blockHeight int64, trust
 	}
 }
 
+// a.Config.Chains[args[0]]
 func TestUnorderedChannelBlockHeightTimeout(t *testing.T) {
 	chains := spinUpTestChains(t, []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
 	}...)
 
 	var (
@@ -635,8 +636,8 @@ func TestUnorderedChannelBlockHeightTimeout(t *testing.T) {
 
 func TestUnorderedChannelTimestampTimeout(t *testing.T) {
 	chains := spinUpTestChains(t, []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
 	}...)
 
 	var (
diff --git a/_test/relayer_compat_test.go b/_test/relayer_compat_test.go
index d17b8ed..2fe18c0 100644
--- a/_test/relayer_compat_test.go
+++ b/_test/relayer_compat_test.go
@@ -4,16 +4,16 @@ import "testing"
 
 var (
 	gaiaChains = []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, gaiaTestConfig, gaiaProviderCfg},
 	}
 	akashChains = []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, akashTestConfig, akashProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, akashTestConfig, akashProviderCfg},
 	}
 	osmoChains = []testChain{
-		{"ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
-		{"ibc-1", 1, osmosisTestConfig, osmosisProviderCfg},
+		{"testChain0", "ibc-0", 0, gaiaTestConfig, gaiaProviderCfg},
+		{"testChain1", "ibc-1", 1, osmosisTestConfig, osmosisProviderCfg},
 	}
 )
 
diff --git a/_test/test_chains.go b/_test/test_chains.go
index 6e3835d..b42951f 100644
--- a/_test/test_chains.go
+++ b/_test/test_chains.go
@@ -28,6 +28,7 @@ const (
 var (
 	gaiaProviderCfg = cosmos.CosmosProviderConfig{
 		Key:            "",
+		ChainName:      "",
 		ChainID:        "",
 		RPCAddr:        "",
 		AccountPrefix:  "cosmos",
@@ -49,6 +50,7 @@ var (
 
 	akashProviderCfg = cosmos.CosmosProviderConfig{
 		Key:            "",
+		ChainName:      "",
 		ChainID:        "",
 		RPCAddr:        "",
 		AccountPrefix:  "akash",
@@ -70,6 +72,7 @@ var (
 
 	osmosisProviderCfg = cosmos.CosmosProviderConfig{
 		Key:            "",
+		ChainName:      "",
 		ChainID:        "",
 		RPCAddr:        "",
 		AccountPrefix:  "osmo",
@@ -96,10 +99,11 @@ type (
 	// testChain represents the different configuration options for spinning up a test
 	// cosmos-sdk based blockchain
 	testChain struct {
-		chainID string
-		seed    int
-		t       testChainConfig
-		pcfg    provider.ProviderConfig
+		chainName string
+		chainID   string
+		seed      int
+		t         testChainConfig
+		pcfg      provider.ProviderConfig
 	}
 
 	// testChainConfig represents the chain specific docker and codec configurations
@@ -133,7 +137,7 @@ func newTestChain(t *testing.T, tc testChain) *relayer.Chain {
 		panic(fmt.Errorf("no case for type %T when trying to edit ProviderConfig", tc.pcfg))
 	}
 
-	prov, err := tc.pcfg.NewProvider(zaptest.NewLogger(t), "/tmp", true)
+	prov, err := tc.pcfg.NewProvider(zaptest.NewLogger(t), "/tmp", true, tc.chainName)
 	require.NoError(t, err)
 
 	var debug bool
diff --git a/_test/test_setup.go b/_test/test_setup.go
index ac2381a..d3f1853 100644
--- a/_test/test_setup.go
+++ b/_test/test_setup.go
@@ -29,7 +29,7 @@ import (
 func spinUpTestChains(t *testing.T, testChains ...testChain) relayer.Chains {
 	var (
 		resources []*dockertest.Resource
-		chains    = make([]*relayer.Chain, len(testChains))
+		chains    = make(relayer.Chains, len(testChains))
 
 		wg    sync.WaitGroup
 		rchan = make(chan *dockertest.Resource, len(testChains))
@@ -49,10 +49,10 @@ func spinUpTestChains(t *testing.T, testChains ...testChain) relayer.Chains {
 
 	var eg errgroup.Group
 	// make each container and initialize the chains
-	for i, tc := range testChains {
+	for _, tc := range testChains {
 		tc := tc
 		c := newTestChain(t, tc)
-		chains[i] = c
+		chains[tc.chainName] = c
 		wg.Add(1)
 		genPrivValKeyJSON(tc.seed)
 		eg.Go(func() error {
@@ -193,7 +193,7 @@ func spinUpTestContainer(t *testing.T, rchan chan<- *dockertest.Resource, pool *
 // cleanUpTest is called as a goroutine to wait until the tests have completed and
 // cleans up the docker containers and relayer config
 func cleanUpTest(t *testing.T, testsDone <-chan struct{}, contDone chan<- struct{},
-	resources []*dockertest.Resource, pool *dockertest.Pool, dir string, chains []*relayer.Chain) {
+	resources []*dockertest.Resource, pool *dockertest.Pool, dir string, chains relayer.Chains) {
 	// block here until tests are complete
 	<-testsDone
 
@@ -216,7 +216,7 @@ func cleanUpTest(t *testing.T, testsDone <-chan struct{}, contDone chan<- struct
 }
 
 // for the love of logs https://www.youtube.com/watch?v=DtsKcHmceqY
-func getLoggingChain(chns []*relayer.Chain, rsr *dockertest.Resource) *relayer.Chain {
+func getLoggingChain(chns relayer.Chains, rsr *dockertest.Resource) *relayer.Chain {
 	for _, c := range chns {
 		if strings.Contains(rsr.Container.Name, c.ChainID()) {
 			return c
diff --git a/cmd/chains_test.go b/cmd/chains_test.go
index ef7730f..b49df31 100644
--- a/cmd/chains_test.go
+++ b/cmd/chains_test.go
@@ -34,7 +34,7 @@ func TestChainsAdd_File(t *testing.T) {
 
 	_ = sys.MustRun(t, "config", "init")
 
-	sys.MustAddChain(t, cmd.ProviderConfigWrapper{
+	sys.MustAddChain(t, "testChain", cmd.ProviderConfigWrapper{
 		Type: "cosmos",
 		Value: cosmos.CosmosProviderConfig{
 			ChainID:        "testcosmos",
@@ -79,7 +79,8 @@ func TestChainsAdd_URL(t *testing.T) {
 	defer srv.Close()
 
 	// Add the chain at the URL and path of the test server.
-	res := sys.MustRun(t, "chains", "add", "--url", srv.URL+"/chain.json")
+	chainName := "testChain"
+	res := sys.MustRun(t, "chains", "add", "--url", srv.URL+"/chain.json", chainName)
 	require.Empty(t, res.Stdout.String())
 	require.Empty(t, res.Stderr.String())
 
@@ -96,7 +97,7 @@ func TestChainsAdd_Delete(t *testing.T) {
 
 	_ = sys.MustRun(t, "config", "init")
 
-	sys.MustAddChain(t, cmd.ProviderConfigWrapper{
+	sys.MustAddChain(t, "testChain", cmd.ProviderConfigWrapper{
 		Type: "cosmos",
 		Value: cosmos.CosmosProviderConfig{
 			ChainID:        "testcosmos",
@@ -111,7 +112,7 @@ func TestChainsAdd_Delete(t *testing.T) {
 	require.Empty(t, res.Stderr.String())
 
 	// Delete the added chain. Silent output.
-	res = sys.MustRun(t, "chains", "delete", "testcosmos")
+	res = sys.MustRun(t, "chains", "delete", "testChain")
 	require.Empty(t, res.Stdout.String())
 	require.Empty(t, res.Stderr.String())
 
diff --git a/cmd/keys_test.go b/cmd/keys_test.go
index 43d49b4..e241656 100644
--- a/cmd/keys_test.go
+++ b/cmd/keys_test.go
@@ -18,7 +18,7 @@ func TestKeysList_Empty(t *testing.T) {
 
 	_ = sys.MustRun(t, "config", "init")
 
-	sys.MustAddChain(t, cmd.ProviderConfigWrapper{
+	sys.MustAddChain(t, "testChain", cmd.ProviderConfigWrapper{
 		Type: "cosmos",
 		Value: cosmos.CosmosProviderConfig{
 			ChainID:        "testcosmos",
@@ -27,9 +27,9 @@ func TestKeysList_Empty(t *testing.T) {
 		},
 	})
 
-	res := sys.MustRun(t, "keys", "list", "testcosmos")
+	res := sys.MustRun(t, "keys", "list", "testChain")
 	require.Empty(t, res.Stdout.String())
-	require.Contains(t, res.Stderr.String(), "no keys found for chain testcosmos")
+	require.Contains(t, res.Stderr.String(), "no keys found for chain testChain")
 }
 
 func TestKeysRestore_Delete(t *testing.T) {
@@ -39,7 +39,7 @@ func TestKeysRestore_Delete(t *testing.T) {
 
 	_ = sys.MustRun(t, "config", "init")
 
-	sys.MustAddChain(t, cmd.ProviderConfigWrapper{
+	sys.MustAddChain(t, "testChain", cmd.ProviderConfigWrapper{
 		Type: "cosmos",
 		Value: cosmos.CosmosProviderConfig{
 			AccountPrefix:  "cosmos",
@@ -50,24 +50,25 @@ func TestKeysRestore_Delete(t *testing.T) {
 	})
 
 	// Restore a key with mnemonic to the chain.
-	res := sys.MustRun(t, "keys", "restore", "testcosmos", "default", relayertest.ZeroMnemonic)
+
+	res := sys.MustRun(t, "keys", "restore", "testChain", "default", relayertest.ZeroMnemonic)
 	require.Equal(t, res.Stdout.String(), relayertest.ZeroCosmosAddr+"\n")
 	require.Empty(t, res.Stderr.String())
 
 	// Restored key must show up in list.
-	res = sys.MustRun(t, "keys", "list", "testcosmos")
+	res = sys.MustRun(t, "keys", "list", "testChain")
 	require.Equal(t, res.Stdout.String(), "key(default) -> "+relayertest.ZeroCosmosAddr+"\n")
 	require.Empty(t, res.Stderr.String())
 
 	// Deleting the key must succeed.
-	res = sys.MustRun(t, "keys", "delete", "testcosmos", "default", "-y")
+	res = sys.MustRun(t, "keys", "delete", "testChain", "default", "-y")
 	require.Empty(t, res.Stdout.String())
 	require.Equal(t, res.Stderr.String(), "key default deleted\n")
 
 	// Listing the keys again gives the no keys warning.
-	res = sys.MustRun(t, "keys", "list", "testcosmos")
+	res = sys.MustRun(t, "keys", "list", "testChain")
 	require.Empty(t, res.Stdout.String())
-	require.Contains(t, res.Stderr.String(), "no keys found for chain testcosmos")
+	require.Contains(t, res.Stderr.String(), "no keys found for chain testChain")
 }
 
 func TestKeysExport(t *testing.T) {
@@ -77,7 +78,7 @@ func TestKeysExport(t *testing.T) {
 
 	_ = sys.MustRun(t, "config", "init")
 
-	sys.MustAddChain(t, cmd.ProviderConfigWrapper{
+	sys.MustAddChain(t, "testChain", cmd.ProviderConfigWrapper{
 		Type: "cosmos",
 		Value: cosmos.CosmosProviderConfig{
 			AccountPrefix:  "cosmos",
@@ -88,12 +89,12 @@ func TestKeysExport(t *testing.T) {
 	})
 
 	// Restore a key with mnemonic to the chain.
-	res := sys.MustRun(t, "keys", "restore", "testcosmos", "default", relayertest.ZeroMnemonic)
+	res := sys.MustRun(t, "keys", "restore", "testChain", "default", relayertest.ZeroMnemonic)
 	require.Equal(t, res.Stdout.String(), relayertest.ZeroCosmosAddr+"\n")
 	require.Empty(t, res.Stderr.String())
 
 	// Export the key.
-	res = sys.MustRun(t, "keys", "export", "testcosmos", "default")
+	res = sys.MustRun(t, "keys", "export", "testChain", "default")
 	armorOut := res.Stdout.String()
 	require.Contains(t, armorOut, "BEGIN TENDERMINT PRIVATE KEY")
 	require.Empty(t, res.Stderr.String())
diff --git a/internal/relayertest/system.go b/internal/relayertest/system.go
index ee17440..8b3c63d 100644
--- a/internal/relayertest/system.go
+++ b/internal/relayertest/system.go
@@ -105,7 +105,7 @@ func (s *System) MustRunWithInput(t *testing.T, in io.Reader, args ...string) Ru
 }
 
 // MustAddChain serializes pcw to disk and calls "chains add --file".
-func (s *System) MustAddChain(t *testing.T, pcw cmd.ProviderConfigWrapper) {
+func (s *System) MustAddChain(t *testing.T, chainName string, pcw cmd.ProviderConfigWrapper) {
 	t.Helper()
 
 	// Temporary file for the new chain.
@@ -119,7 +119,7 @@ func (s *System) MustAddChain(t *testing.T, pcw cmd.ProviderConfigWrapper) {
 	f.Close() // Now that the content has been written, close the file so it is readable by the 'chains add' call.
 
 	// Add the chain. Output is expected to be silent.
-	res := s.MustRun(t, "chains", "add", "--file", f.Name())
+	res := s.MustRun(t, "chains", "add", "--file", f.Name(), chainName)
 	require.Empty(t, res.Stdout.String())
 	require.Empty(t, res.Stderr.String())
 }