Skip to main content

solana_clap_v3_utils/keygen/
mnemonic.rs

1use {
2    crate::{keypair::prompt_passphrase, ArgConstant},
3    bip39::Language,
4    clap::{builder::PossibleValuesParser, Arg, ArgMatches},
5    std::error,
6};
7
8pub const NO_PASSPHRASE: &str = "";
9
10pub const WORD_COUNT_ARG: ArgConstant<'static> = ArgConstant {
11    long: "word-count",
12    name: "word_count",
13    help: "Specify the number of words that will be present in the generated seed phrase",
14};
15
16pub const LANGUAGE_ARG: ArgConstant<'static> = ArgConstant {
17    long: "language",
18    name: "language",
19    help: "Specify the mnemonic language that will be present in the generated seed phrase",
20};
21
22pub const NO_PASSPHRASE_ARG: ArgConstant<'static> = ArgConstant {
23    long: "no-bip39-passphrase",
24    name: "no_passphrase",
25    help: "Do not prompt for a BIP39 passphrase",
26};
27
28// The constant `POSSIBLE_WORD_COUNTS` and function `try_get_word_count` must always be updated in
29// sync
30const POSSIBLE_WORD_COUNTS: &[&str] = &["12", "15", "18", "21", "24"];
31pub fn word_count_arg<'a>() -> Arg<'a> {
32    Arg::new(WORD_COUNT_ARG.name)
33        .long(WORD_COUNT_ARG.long)
34        .value_parser(PossibleValuesParser::new(POSSIBLE_WORD_COUNTS))
35        .default_value("12")
36        .value_name("NUMBER")
37        .takes_value(true)
38        .help(WORD_COUNT_ARG.help)
39}
40
41pub fn try_get_word_count(matches: &ArgMatches) -> Result<Option<usize>, Box<dyn error::Error>> {
42    Ok(matches
43        .try_get_one::<String>(WORD_COUNT_ARG.name)?
44        .map(|count| match count.as_str() {
45            "12" => 12,
46            "15" => 15,
47            "18" => 18,
48            "21" => 21,
49            "24" => 24,
50            _ => unreachable!(),
51        }))
52}
53
54// The constant `POSSIBLE_LANGUAGES` and function `try_get_language` must always be updated in sync
55const POSSIBLE_LANGUAGES: &[&str] = &[
56    "english",
57    "chinese-simplified",
58    "chinese-traditional",
59    "japanese",
60    "spanish",
61    "korean",
62    "french",
63    "italian",
64];
65pub fn language_arg<'a>() -> Arg<'a> {
66    Arg::new(LANGUAGE_ARG.name)
67        .long(LANGUAGE_ARG.long)
68        .value_parser(PossibleValuesParser::new(POSSIBLE_LANGUAGES))
69        .default_value("english")
70        .value_name("LANGUAGE")
71        .takes_value(true)
72        .help(LANGUAGE_ARG.help)
73}
74
75pub fn no_passphrase_arg<'a>() -> Arg<'a> {
76    Arg::new(NO_PASSPHRASE_ARG.name)
77        .long(NO_PASSPHRASE_ARG.long)
78        .alias("no-passphrase")
79        .help(NO_PASSPHRASE_ARG.help)
80}
81
82#[deprecated(since = "2.0.0", note = "Please use `try_get_language` instead")]
83pub fn acquire_language(matches: &ArgMatches) -> Language {
84    #[allow(deprecated)]
85    let language_name = LANGUAGE_ARG.name;
86    match matches.get_one::<String>(language_name).unwrap().as_str() {
87        "english" => Language::English,
88        "chinese-simplified" => Language::ChineseSimplified,
89        "chinese-traditional" => Language::ChineseTraditional,
90        "japanese" => Language::Japanese,
91        "spanish" => Language::Spanish,
92        "korean" => Language::Korean,
93        "french" => Language::French,
94        "italian" => Language::Italian,
95        _ => unreachable!(),
96    }
97}
98
99pub fn try_get_language(matches: &ArgMatches) -> Result<Option<Language>, Box<dyn error::Error>> {
100    Ok(matches
101        .try_get_one::<String>(LANGUAGE_ARG.name)?
102        .map(|language| match language.as_str() {
103            "english" => Language::English,
104            "chinese-simplified" => Language::ChineseSimplified,
105            "chinese-traditional" => Language::ChineseTraditional,
106            "japanese" => Language::Japanese,
107            "spanish" => Language::Spanish,
108            "korean" => Language::Korean,
109            "french" => Language::French,
110            "italian" => Language::Italian,
111            _ => unreachable!(),
112        }))
113}
114
115pub fn no_passphrase_and_message() -> (String, String) {
116    (NO_PASSPHRASE.to_string(), "".to_string())
117}
118
119pub fn acquire_passphrase_and_message(
120    matches: &ArgMatches,
121) -> Result<(String, String), Box<dyn error::Error>> {
122    #[rustfmt::skip]
123    const PROMPT: &str =
124        "\nFor added security, enter a BIP39 passphrase\n\
125         \nNOTE! This passphrase improves security of the recovery seed phrase NOT the\n\
126         keypair file itself, which is stored as insecure plain text\n\
127         \nBIP39 Passphrase (empty for none): ";
128
129    if matches.try_contains_id(NO_PASSPHRASE_ARG.name)? {
130        Ok(no_passphrase_and_message())
131    } else {
132        match prompt_passphrase(PROMPT) {
133            Ok(passphrase) => {
134                println!();
135                Ok((passphrase, " and your BIP39 passphrase".to_string()))
136            }
137            Err(e) => Err(e),
138        }
139    }
140}