Skip to content

Commit 10cba9f

Browse files
rplusqEvalir
andauthored
improvement: cast wallet import / --account option (foundry-rs#5551)
* Reorganize Wallet struct to isolate raw options * feature: cast wallet import * feature: cast wallet list * MultiWallet/Wallet now leverage imported keystores * chore: fmt * chore: clippy * chore: foundry_keystores_dir function * chore: split wallet push for readability * chore: remove unused account from MultiWallet * fixup! chore: foundry_keystores_dir function * chore: wallet list creates dir if non-existent * chore: remove dirs from cli Cargo.toml * chore: docs for RawWallet * chore: error into * chore: success message for importing keystore * refactor: green println from ansi -> yansi::Paint --------- Co-authored-by: Enrique Ortiz <[email protected]>
1 parent e09147e commit 10cba9f

File tree

6 files changed

+224
-56
lines changed

6 files changed

+224
-56
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ui = { path = "../ui" }
2525
# eth
2626
ethers = { workspace = true, features = ["rustls"] }
2727
solang-parser.workspace = true
28+
eth-keystore = "0.5"
2829

2930
# cli
3031
clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] }

cli/src/cmd/cast/wallet/mod.rs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
33
pub mod vanity;
44

5+
use std::path::Path;
6+
57
use crate::{
68
cmd::{cast::wallet::vanity::VanityArgs, Cmd},
7-
opts::Wallet,
9+
opts::{RawWallet, Wallet},
810
};
911
use cast::SimpleCast;
1012
use clap::Parser;
@@ -14,6 +16,9 @@ use ethers::{
1416
types::{transaction::eip712::TypedData, Address, Signature},
1517
};
1618
use eyre::Context;
19+
use foundry_common::fs;
20+
use foundry_config::Config;
21+
use yansi::Paint;
1722

1823
/// CLI arguments for `cast wallet`.
1924
#[derive(Debug, Parser)]
@@ -98,6 +103,19 @@ pub enum WalletSubcommands {
98103
#[clap(long, short)]
99104
address: Address,
100105
},
106+
/// Import a private key into an encrypted keystore.
107+
#[clap(visible_alias = "i")]
108+
Import {
109+
#[clap(help = "The name for the account in the keystore.", value_name = "ACCOUNT_NAME")]
110+
account_name: String,
111+
#[clap(long, short)]
112+
keystore_dir: Option<String>,
113+
#[clap(flatten)]
114+
raw_wallet_options: RawWallet,
115+
},
116+
/// List all the accounts in the keystore default directory
117+
#[clap(visible_alias = "ls")]
118+
List,
101119
}
102120

103121
impl WalletSubcommands {
@@ -137,7 +155,10 @@ impl WalletSubcommands {
137155
}
138156
WalletSubcommands::Address { wallet, private_key_override } => {
139157
let wallet = private_key_override
140-
.map(|pk| Wallet { private_key: Some(pk), ..Default::default() })
158+
.map(|pk| Wallet {
159+
raw: RawWallet { private_key: Some(pk), ..Default::default() },
160+
..Default::default()
161+
})
141162
.unwrap_or(wallet)
142163
.signer(0)
143164
.await?;
@@ -170,6 +191,90 @@ impl WalletSubcommands {
170191
}
171192
}
172193
}
194+
WalletSubcommands::Import { account_name, keystore_dir, raw_wallet_options } => {
195+
// Set up keystore directory
196+
let dir = if let Some(path) = keystore_dir {
197+
Path::new(&path).to_path_buf()
198+
} else {
199+
Config::foundry_keystores_dir().ok_or_else(|| {
200+
eyre::eyre!("Could not find the default keystore directory.")
201+
})?
202+
};
203+
204+
fs::create_dir_all(&dir)?;
205+
206+
// check if account exists already
207+
let keystore_path = Path::new(&dir).join(&account_name);
208+
if keystore_path.exists() {
209+
eyre::bail!("Keystore file already exists at {}", keystore_path.display());
210+
}
211+
212+
// get wallet
213+
let wallet: Wallet = raw_wallet_options.into();
214+
let wallet = wallet.try_resolve_local_wallet()?.ok_or_else(|| {
215+
eyre::eyre!(
216+
"\
217+
Did you set a private key or mnemonic?
218+
Run `cast wallet import --help` and use the corresponding CLI
219+
flag to set your key via:
220+
--private-key, --mnemonic-path or --interactive."
221+
)
222+
})?;
223+
224+
let private_key = wallet.signer().to_bytes();
225+
let password = rpassword::prompt_password("Enter password: ")?;
226+
227+
let mut rng = thread_rng();
228+
eth_keystore::encrypt_key(
229+
&dir,
230+
&mut rng,
231+
private_key,
232+
&password,
233+
Some(&account_name),
234+
)?;
235+
let address = wallet.address();
236+
let success_message = format!(
237+
"`{}` keystore was saved successfully. Address: {}",
238+
&account_name, address,
239+
);
240+
println!("{}", Paint::green(success_message));
241+
}
242+
WalletSubcommands::List => {
243+
let default_keystore_dir = Config::foundry_keystores_dir()
244+
.ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?;
245+
// Create the keystore directory if it doesn't exist
246+
fs::create_dir_all(&default_keystore_dir)?;
247+
// List all files in keystore directory
248+
let keystore_files: Result<Vec<_>, eyre::Report> =
249+
std::fs::read_dir(&default_keystore_dir)
250+
.wrap_err("Failed to read the directory")?
251+
.filter_map(|entry| match entry {
252+
Ok(entry) => {
253+
let path = entry.path();
254+
if path.is_file() && path.extension().is_none() {
255+
Some(Ok(path))
256+
} else {
257+
None
258+
}
259+
}
260+
Err(e) => Some(Err(e.into())),
261+
})
262+
.collect::<Result<Vec<_>, eyre::Report>>();
263+
// Print the names of the keystore files
264+
match keystore_files {
265+
Ok(files) => {
266+
// Print the names of the keystore files
267+
for file in files {
268+
if let Some(file_name) = file.file_name() {
269+
if let Some(name) = file_name.to_str() {
270+
println!("{}", name);
271+
}
272+
}
273+
}
274+
}
275+
Err(e) => return Err(e),
276+
}
277+
}
173278
};
174279

175280
Ok(())

0 commit comments

Comments
 (0)