Skip to main content

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::wallet_multi::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    ///
27    /// Ensure either one of AWS_KMS_KEY_IDS (comma-separated) or AWS_KMS_KEY_ID environment
28    /// variables are set.
29    #[arg(long, hide = !cfg!(feature = "aws-kms"))]
30    aws: bool,
31
32    /// List accounts from Google Cloud KMS.
33    ///
34    /// Ensure the following environment variables are set: GCP_PROJECT_ID, GCP_LOCATION,
35    /// GCP_KEY_RING, GCP_KEY_NAME, GCP_KEY_VERSION.
36    ///
37    /// See: <https://cloud.google.com/kms/docs>
38    #[arg(long, hide = !cfg!(feature = "gcp-kms"))]
39    gcp: bool,
40
41    /// List accounts from Turnkey.
42    #[arg(long, hide = !cfg!(feature = "turnkey"))]
43    turnkey: bool,
44
45    /// List all configured accounts.
46    #[arg(long, group = "hw-wallets")]
47    all: bool,
48
49    /// Max number of addresses to display from hardware wallets.
50    #[arg(long, short, default_value = "3", requires = "hw-wallets")]
51    max_senders: Option<usize>,
52}
53
54impl ListArgs {
55    pub async fn run(self) -> Result<()> {
56        // list local accounts as files in keystore dir, no need to unlock / provide password
57        if self.dir.is_some()
58            || self.all
59            || (!self.ledger && !self.trezor && !self.aws && !self.gcp)
60        {
61            match self.list_local_senders() {
62                Ok(()) => {}
63                Err(e) => {
64                    if !self.all {
65                        sh_err!("{}", e)?;
66                    }
67                }
68            }
69        }
70
71        // Create options for multi wallet - ledger, trezor and AWS
72        let list_opts = MultiWalletOptsBuilder::default()
73            .ledger(self.ledger || self.all)
74            .mnemonic_indexes(Some(vec![0]))
75            .trezor(self.trezor || self.all)
76            .aws(self.aws || self.all)
77            .gcp(self.gcp || (self.all && gcp_env_vars_set()))
78            .turnkey(self.turnkey || self.all)
79            .interactives(0)
80            .interactive(false)
81            .browser(false)
82            .browser_port(Default::default())
83            .browser_disable_open(Default::default())
84            .browser_development(Default::default())
85            .build()
86            .expect("build multi wallet");
87
88        // macro to print senders for a list of signers
89        macro_rules! list_senders {
90            ($signers:expr, $label:literal) => {
91                match $signers.await {
92                    Ok(signers) => {
93                        for signer in signers.unwrap_or_default().iter() {
94                            signer
95                                .available_senders(self.max_senders.unwrap())
96                                .await?
97                                .iter()
98                                .for_each(|sender| {
99                                    let _ = sh_println!("{} ({})", sender, $label);
100                                })
101                        }
102                    }
103                    Err(e) => {
104                        if !self.all {
105                            sh_err!("{}", e)?;
106                        }
107                    }
108                }
109            };
110        }
111
112        list_senders!(list_opts.ledgers(), "Ledger");
113        list_senders!(list_opts.trezors(), "Trezor");
114        list_senders!(list_opts.aws_signers(), "AWS");
115        list_senders!(list_opts.gcp_signers(), "GCP");
116
117        Ok(())
118    }
119
120    fn list_local_senders(&self) -> Result<()> {
121        let keystore_path = self.dir.as_deref().unwrap_or_default();
122        let keystore_dir = if keystore_path.is_empty() {
123            // Create the keystore default directory if it doesn't exist
124            let default_dir = Config::foundry_keystores_dir().unwrap();
125            fs::create_dir_all(&default_dir)?;
126            default_dir
127        } else {
128            dunce::canonicalize(keystore_path)?
129        };
130
131        // List all files within the keystore directory.
132        for entry in std::fs::read_dir(keystore_dir)? {
133            let path = entry?.path();
134            if path.is_file()
135                && let Some(file_name) = path.file_name()
136                && let Some(name) = file_name.to_str()
137            {
138                // Extract address from keystore filename format: UTC--{timestamp}--{address}
139                if let Some(address) = name.split("--").last() {
140                    sh_println!("0x{} (Local)", address)?;
141                }
142            }
143        }
144
145        Ok(())
146    }
147}
148
149fn gcp_env_vars_set() -> bool {
150    let required_vars =
151        ["GCP_PROJECT_ID", "GCP_LOCATION", "GCP_KEY_RING", "GCP_KEY_NAME", "GCP_KEY_VERSION"];
152
153    required_vars.iter().all(|&var| env::var(var).is_ok())
154}