cast/cmd/wallet/
list.rs

1use clap::Parser;
2use eyre::Result;
3use std::env;
4
5use foundry_common::{fs, sh_err, sh_println};
6use foundry_config::Config;
7use foundry_wallets::multi_wallet::MultiWalletOptsBuilder;
8
9/// CLI arguments for `cast wallet list`.
10#[derive(Clone, Debug, Parser)]
11pub struct ListArgs {
12    /// List all the accounts in the keystore directory.
13    /// Default keystore directory is used if no path provided.
14    #[arg(long, default_missing_value = "", num_args(0..=1))]
15    dir: Option<String>,
16
17    /// List accounts from a Ledger hardware wallet.
18    #[arg(long, short, group = "hw-wallets")]
19    ledger: bool,
20
21    /// List accounts from a Trezor hardware wallet.
22    #[arg(long, short, group = "hw-wallets")]
23    trezor: bool,
24
25    /// List accounts from AWS KMS.
26    #[arg(long, hide = !cfg!(feature = "aws-kms"))]
27    aws: bool,
28
29    /// List accounts from Google Cloud KMS.
30    #[arg(long, hide = !cfg!(feature = "gcp-kms"))]
31    gcp: bool,
32
33    /// List all configured accounts.
34    #[arg(long, group = "hw-wallets")]
35    all: bool,
36
37    /// Max number of addresses to display from hardware wallets.
38    #[arg(long, short, default_value = "3", requires = "hw-wallets")]
39    max_senders: Option<usize>,
40}
41
42impl ListArgs {
43    pub async fn run(self) -> Result<()> {
44        // list local accounts as files in keystore dir, no need to unlock / provide password
45        if self.dir.is_some() ||
46            self.all ||
47            (!self.ledger && !self.trezor && !self.aws && !self.gcp)
48        {
49            let _ = self.list_local_senders();
50        }
51
52        // Create options for multi wallet - ledger, trezor and AWS
53        let list_opts = MultiWalletOptsBuilder::default()
54            .ledger(self.ledger || self.all)
55            .mnemonic_indexes(Some(vec![0]))
56            .trezor(self.trezor || self.all)
57            .aws(self.aws || self.all)
58            .gcp(self.gcp || (self.all && gcp_env_vars_set()))
59            .interactives(0)
60            .build()
61            .expect("build multi wallet");
62
63        // macro to print senders for a list of signers
64        macro_rules! list_senders {
65            ($signers:expr, $label:literal) => {
66                match $signers.await {
67                    Ok(signers) => {
68                        for signer in signers.unwrap_or_default().iter() {
69                            signer
70                                .available_senders(self.max_senders.unwrap())
71                                .await?
72                                .iter()
73                                .for_each(|sender| {
74                                    let _ = sh_println!("{} ({})", sender, $label);
75                                })
76                        }
77                    }
78                    Err(e) => {
79                        if !self.all {
80                            sh_err!("{}", e)?;
81                        }
82                    }
83                }
84            };
85        }
86
87        list_senders!(list_opts.ledgers(), "Ledger");
88        list_senders!(list_opts.trezors(), "Trezor");
89        list_senders!(list_opts.aws_signers(), "AWS");
90
91        Ok(())
92    }
93
94    fn list_local_senders(&self) -> Result<()> {
95        let keystore_path = self.dir.clone().unwrap_or_default();
96        let keystore_dir = if keystore_path.is_empty() {
97            // Create the keystore default directory if it doesn't exist
98            let default_dir = Config::foundry_keystores_dir().unwrap();
99            fs::create_dir_all(&default_dir)?;
100            default_dir
101        } else {
102            dunce::canonicalize(keystore_path)?
103        };
104
105        // List all files within the keystore directory.
106        for entry in std::fs::read_dir(keystore_dir)? {
107            let path = entry?.path();
108            if path.is_file() {
109                if let Some(file_name) = path.file_name() {
110                    if let Some(name) = file_name.to_str() {
111                        sh_println!("{name} (Local)")?;
112                    }
113                }
114            }
115        }
116
117        Ok(())
118    }
119}
120
121fn gcp_env_vars_set() -> bool {
122    let required_vars =
123        ["GCP_PROJECT_ID", "GCP_LOCATION", "GCP_KEY_RING", "GCP_KEY_NAME", "GCP_KEY_VERSION"];
124
125    required_vars.iter().all(|&var| env::var(var).is_ok())
126}