1use clap::Parser;
2use eyre::Result;
3use foundry_cli::json::print_json_success;
4use foundry_common::{fs, sh_err, sh_println, shell};
5use foundry_config::Config;
6use foundry_wallets::wallet_multi::MultiWalletOptsBuilder;
7use serde::Serialize;
8use std::env;
9
10#[derive(Clone, Debug, Parser)]
12pub struct ListArgs {
13 #[arg(long, default_missing_value = "", num_args(0..=1))]
16 dir: Option<String>,
17
18 #[arg(long, short, group = "hw-wallets")]
20 ledger: bool,
21
22 #[arg(long, short, group = "hw-wallets")]
24 trezor: bool,
25
26 #[arg(long, hide = !cfg!(feature = "aws-kms"))]
31 aws: bool,
32
33 #[arg(long, hide = !cfg!(feature = "gcp-kms"))]
40 gcp: bool,
41
42 #[arg(long, hide = !cfg!(feature = "turnkey"))]
44 turnkey: bool,
45
46 #[arg(long, group = "hw-wallets")]
48 all: bool,
49
50 #[arg(long, short, default_value = "3", requires = "hw-wallets")]
52 max_senders: Option<usize>,
53}
54
55impl ListArgs {
56 pub async fn run(self) -> Result<()> {
57 let format_json = shell::is_json();
58 let mut accounts: Vec<WalletAccount> = Vec::new();
59
60 if self.dir.is_some()
62 || self.all
63 || (!self.ledger && !self.trezor && !self.aws && !self.gcp)
64 {
65 match self.list_local_senders(format_json) {
66 Ok(local) => accounts.extend(local),
67 Err(e) if !self.all => {
68 sh_err!("{}", e)?;
69 }
70 _ => {}
71 }
72 }
73
74 let list_opts = MultiWalletOptsBuilder::default()
76 .ledger(self.ledger || self.all)
77 .mnemonic_indexes(Some(vec![0]))
78 .trezor(self.trezor || self.all)
79 .aws(self.aws || self.all)
80 .gcp(self.gcp || (self.all && gcp_env_vars_set()))
81 .turnkey(self.turnkey || self.all)
82 .interactives(0)
83 .interactive(false)
84 .browser(Default::default())
85 .build()
86 .expect("build multi wallet");
87
88 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 for sender in
95 signer.available_senders(self.max_senders.unwrap()).await?
96 {
97 if format_json {
98 accounts.push(WalletAccount {
99 address: sender.to_string(),
100 source: $label,
101 });
102 } else {
103 let _ = sh_println!("{} ({})", sender, $label);
104 }
105 }
106 }
107 }
108 Err(e) if !self.all => {
109 sh_err!("{}", e)?;
110 }
111 _ => {}
112 }
113 };
114 }
115
116 list_senders!(list_opts.ledgers(), "Ledger");
117 list_senders!(list_opts.trezors(), "Trezor");
118 list_senders!(list_opts.aws_signers(), "AWS");
119 list_senders!(list_opts.gcp_signers(), "GCP");
120
121 if format_json {
122 print_json_success(accounts)?;
123 }
124
125 Ok(())
126 }
127
128 fn list_local_senders(&self, format_json: bool) -> Result<Vec<WalletAccount>> {
129 let keystore_path = self.dir.as_deref().unwrap_or_default();
130 let keystore_dir = if keystore_path.is_empty() {
131 let default_dir = Config::foundry_keystores_dir().unwrap();
133 fs::create_dir_all(&default_dir)?;
134 default_dir
135 } else {
136 dunce::canonicalize(keystore_path)?
137 };
138
139 let mut accounts = Vec::new();
140
141 for entry in std::fs::read_dir(keystore_dir)? {
143 let path = entry?.path();
144 if path.is_file()
145 && let Some(file_name) = path.file_name()
146 && let Some(name) = file_name.to_str()
147 {
148 if let Some(address) = name.split("--").last() {
150 if format_json {
151 accounts.push(WalletAccount {
152 address: format!("0x{address}"),
153 source: "Local",
154 });
155 } else {
156 sh_println!("0x{} (Local)", address)?;
157 }
158 }
159 }
160 }
161
162 Ok(accounts)
163 }
164}
165
166#[derive(Serialize)]
167struct WalletAccount {
168 address: String,
169 source: &'static str,
170}
171
172fn gcp_env_vars_set() -> bool {
173 let required_vars =
174 ["GCP_PROJECT_ID", "GCP_LOCATION", "GCP_KEY_RING", "GCP_KEY_NAME", "GCP_KEY_VERSION"];
175
176 required_vars.iter().all(|&var| env::var(var).is_ok())
177}