hermes_cli/commands/keys/
add.rs1use std::fs;
2use std::path::{Path, PathBuf};
3use std::str::FromStr;
4
5use hdpath::StandardHDPath;
6use hermes_cli_components::traits::build::CanLoadBuilder;
7use hermes_cli_framework::command::CommandRunner;
8use hermes_cli_framework::output::Output;
9use ibc_relayer::chain::cosmos::config::CosmosSdkConfig;
10use ibc_relayer::keyring::{
11 AnySigningKeyPair, KeyRing, Secp256k1KeyPair, SigningKeyPair, SigningKeyPairSized, Store,
12};
13use ibc_relayer_types::core::ics24_host::identifier::ChainId;
14use oneline_eyre::eyre;
15use oneline_eyre::eyre::{eyre, WrapErr};
16use tracing::warn;
17
18use crate::contexts::app::HermesApp;
19
20#[derive(Debug, clap::Parser)]
38#[clap(override_usage = "Add a key from a Comet keyring file:
39 hermes keys add [OPTIONS] --chain <CHAIN_ID> --key-file <KEY_FILE>
40
41 Add a key from a file containing its mnemonic:
42 hermes keys add [OPTIONS] --chain <CHAIN_ID> --mnemonic-file <MNEMONIC_FILE>
43
44 On *nix platforms, both flags also accept `/dev/stdin` as a value, which will read the key or the mnemonic from stdin.")]
45pub struct KeysAddCmd {
46 #[clap(
47 long = "chain",
48 required = true,
49 help_heading = "FLAGS",
50 help = "Identifier of the chain"
51 )]
52 chain_id: ChainId,
53
54 #[clap(
55 long = "key-file",
56 required = true,
57 value_name = "KEY_FILE",
58 help_heading = "FLAGS",
59 help = "Path to the key file, or /dev/stdin to read the content from stdin",
60 group = "add-restore"
61 )]
62 key_file: Option<PathBuf>,
63
64 #[clap(
65 long = "mnemonic-file",
66 required = true,
67 value_name = "MNEMONIC_FILE",
68 help_heading = "FLAGS",
69 help = "Path to file containing the mnemonic to restore the key from, or /dev/stdin to read the mnemonic from stdin",
70 group = "add-restore"
71 )]
72 mnemonic_file: Option<PathBuf>,
73
74 #[clap(
75 long = "key-name",
76 value_name = "KEY_NAME",
77 help = "Name of the key (defaults to the `key_name` defined in the config)"
78 )]
79 key_name: Option<String>,
80
81 #[clap(
82 long = "hd-path",
83 value_name = "HD_PATH",
84 help = "Derivation path for this key",
85 default_value = "m/44'/118'/0'/0/0"
86 )]
87 hd_path: String,
88
89 #[clap(
90 long = "overwrite",
91 help = "Overwrite the key if there is already one with the same key name"
92 )]
93 overwrite: bool,
94}
95
96impl KeysAddCmd {
97 fn options(&self, chain_config: &CosmosSdkConfig) -> eyre::Result<KeysAddOptions> {
98 let name = self
99 .key_name
100 .clone()
101 .unwrap_or_else(|| chain_config.key_name.to_string());
102
103 let hd_path = StandardHDPath::from_str(&self.hd_path)
104 .map_err(|_| eyre!("invalid derivation path: {}", self.hd_path))?;
105
106 Ok(KeysAddOptions {
107 config: chain_config.clone(),
108 name,
109 hd_path,
110 })
111 }
112}
113
114#[derive(Clone, Debug)]
115pub struct KeysAddOptions {
116 pub name: String,
117 pub config: CosmosSdkConfig,
118 pub hd_path: StandardHDPath,
119}
120
121pub fn add_key(
122 config: &CosmosSdkConfig,
123 key_name: &str,
124 file: &Path,
125 hd_path: &StandardHDPath,
126 overwrite: bool,
127) -> eyre::Result<AnySigningKeyPair> {
128 let mut keyring = KeyRing::new_secp256k1(
129 Store::Test,
130 &config.account_prefix,
131 &config.id,
132 &config.key_store_folder,
133 )?;
134
135 check_key_exists(&keyring, key_name, overwrite);
136
137 let key_contents = fs::read_to_string(file).wrap_err("error reading the key file")?;
138 let key_pair = Secp256k1KeyPair::from_seed_file(&key_contents, hd_path)?;
139
140 keyring.add_key(key_name, key_pair.clone())?;
141
142 Ok(key_pair.into())
143}
144
145pub fn restore_key(
146 mnemonic: &Path,
147 key_name: &str,
148 hdpath: &StandardHDPath,
149 config: &CosmosSdkConfig,
150 overwrite: bool,
151) -> eyre::Result<AnySigningKeyPair> {
152 let mnemonic_content =
153 fs::read_to_string(mnemonic).wrap_err("error reading the mnemonic file")?;
154
155 let mut keyring = KeyRing::new_secp256k1(
156 Store::Test,
157 &config.account_prefix,
158 &config.id,
159 &config.key_store_folder,
160 )?;
161
162 check_key_exists(&keyring, key_name, overwrite);
163
164 let key_pair = Secp256k1KeyPair::from_mnemonic(
165 &mnemonic_content,
166 hdpath,
167 &config.address_type,
168 keyring.account_prefix(),
169 )?;
170
171 keyring.add_key(key_name, key_pair.clone())?;
172
173 Ok(key_pair.into())
174}
175
176fn check_key_exists<S: SigningKeyPairSized>(keyring: &KeyRing<S>, key_name: &str, overwrite: bool) {
180 if keyring.get_key(key_name).is_ok() {
181 if overwrite {
182 warn!("key {} will be overwritten", key_name);
183 } else {
184 Output::error(format!("key with name '{key_name}' already exists")).exit();
185 }
186 }
187}
188
189impl CommandRunner<HermesApp> for KeysAddCmd {
190 async fn run(&self, app: &HermesApp) -> hermes_cli_framework::Result<Output> {
191 let builder = app.load_builder().await?;
192
193 let chain_config = builder
194 .config_map
195 .get(&self.chain_id)
196 .ok_or_else(|| eyre!("no chain configuration found for chain `{}`", self.chain_id))?;
197
198 let opts = match self.options(chain_config) {
199 Err(err) => Output::error(err).exit(),
200 Ok(result) => result,
201 };
202
203 match (self.key_file.clone(), self.mnemonic_file.clone()) {
205 (Some(key_file), _) => {
206 let key = add_key(
207 &opts.config,
208 &opts.name,
209 &key_file,
210 &opts.hd_path,
211 self.overwrite,
212 );
213 match key {
214 Ok(key) => Output::success_msg(format!(
215 "added key '{}' ({}) on chain `{}`",
216 opts.name,
217 key.account(),
218 opts.config.id,
219 ))
220 .exit(),
221 Err(e) => Output::error(format!(
222 "an error occurred adding the key on chain `{}` from file {:?}: {}",
223 self.chain_id, key_file, e
224 ))
225 .exit(),
226 }
227 }
228 (_, Some(mnemonic_file)) => {
229 let key = restore_key(
230 &mnemonic_file,
231 &opts.name,
232 &opts.hd_path,
233 &opts.config,
234 self.overwrite,
235 );
236
237 match key {
238 Ok(key) => Output::success_msg(format!(
239 "restored key '{}' ({}) on chain `{}`",
240 opts.name,
241 key.account(),
242 opts.config.id
243 ))
244 .exit(),
245 Err(e) => Output::error(format!(
246 "failed to restore the key on chain `{}` from file {:?}: {}",
247 self.chain_id, mnemonic_file, e
248 ))
249 .exit(),
250 }
251 }
252 _ => Output::error(
256 "exactly one of --mnemonic-file and --key-file must be given".to_string(),
257 )
258 .exit(),
259 }
260 }
261}