soroban_cli/commands/keys/
add.rs1use std::io::Write;
2
3use clap::command;
4use sep5::SeedPhrase;
5
6use crate::{
7 commands::global,
8 config::{
9 address::KeyName,
10 key, locator,
11 secret::{self, Secret},
12 },
13 print::Print,
14 signer::secure_store,
15};
16
17#[derive(thiserror::Error, Debug)]
18pub enum Error {
19 #[error(transparent)]
20 Secret(#[from] secret::Error),
21 #[error(transparent)]
22 Key(#[from] key::Error),
23 #[error(transparent)]
24 Config(#[from] locator::Error),
25
26 #[error(transparent)]
27 SecureStore(#[from] secure_store::Error),
28
29 #[error(transparent)]
30 SeedPhrase(#[from] sep5::error::Error),
31
32 #[error("secret input error")]
33 PasswordRead,
34}
35
36#[derive(Debug, clap::Parser, Clone)]
37#[group(skip)]
38pub struct Cmd {
39 pub name: KeyName,
41
42 #[command(flatten)]
43 pub secrets: secret::Args,
44
45 #[command(flatten)]
46 pub config_locator: locator::Args,
47
48 #[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")]
50 pub public_key: Option<String>,
51}
52
53impl Cmd {
54 pub fn run(&self, global_args: &global::Args) -> Result<(), Error> {
55 let print = Print::new(global_args.quiet);
56 let key = if let Some(key) = self.public_key.as_ref() {
57 key.parse()?
58 } else {
59 self.read_secret(&print)?.into()
60 };
61
62 let path = self.config_locator.write_key(&self.name, &key)?;
63
64 print.checkln(format!("Key saved with alias {} in {path:?}", self.name));
65
66 Ok(())
67 }
68
69 fn read_secret(&self, print: &Print) -> Result<Secret, Error> {
70 if let Ok(secret_key) = std::env::var("SOROBAN_SECRET_KEY") {
71 Ok(Secret::SecretKey { secret_key })
72 } else if self.secrets.secure_store {
73 let prompt = "Type a 12/24 word seed phrase:";
74 let secret_key = read_password(print, prompt)?;
75 if secret_key.split_whitespace().count() < 24 {
76 print.warnln("The provided seed phrase lacks sufficient entropy and should be avoided. Using a 24-word seed phrase is a safer option.".to_string());
77 print.warnln(
78 "To generate a new key, use the `stellar keys generate` command.".to_string(),
79 );
80 }
81
82 let seed_phrase: SeedPhrase = secret_key.parse()?;
83
84 Ok(secure_store::save_secret(print, &self.name, seed_phrase)?)
85 } else {
86 let prompt = "Type a secret key or 12/24 word seed phrase:";
87 let secret_key = read_password(print, prompt)?;
88 let secret = secret_key.parse()?;
89 if let Secret::SeedPhrase { seed_phrase } = &secret {
90 if seed_phrase.split_whitespace().count() < 24 {
91 print.warnln("The provided seed phrase lacks sufficient entropy and should be avoided. Using a 24-word seed phrase is a safer option.".to_string());
92 print.warnln(
93 "To generate a new key, use the `stellar keys generate` command."
94 .to_string(),
95 );
96 }
97 }
98 Ok(secret)
99 }
100 }
101}
102
103fn read_password(print: &Print, prompt: &str) -> Result<String, Error> {
104 print.arrowln(prompt);
105 std::io::stdout().flush().map_err(|_| Error::PasswordRead)?;
106 rpassword::read_password().map_err(|_| Error::PasswordRead)
107}