From a1e83b7370efe5e05e615fad28eb9031161417ff Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Sat, 4 Oct 2025 22:45:10 +0200 Subject: [PATCH] feat(wallet): enhance keystore listing with address suffix - Updated keystore file naming to include wallet address for better identification. - Introduced `find_keystore_by_name` utility to support both old and new filename formats. --- crates/cast/src/cmd/wallet/mod.rs | 75 +++++++++++++++++++++++++------ crates/wallets/src/utils.rs | 47 +++++++++++++++++-- 2 files changed, 105 insertions(+), 17 deletions(-) diff --git a/crates/cast/src/cmd/wallet/mod.rs b/crates/cast/src/cmd/wallet/mod.rs index 7c9a99d08b0ce..6e0434fee72d2 100644 --- a/crates/cast/src/cmd/wallet/mod.rs +++ b/crates/cast/src/cmd/wallet/mod.rs @@ -13,7 +13,7 @@ use eyre::{Context, Result}; use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; use foundry_common::{fs, sh_println, shell}; use foundry_config::Config; -use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner, utils::find_keystore_by_name}; use rand_08::thread_rng; use serde_json::json; use std::path::Path; @@ -337,25 +337,37 @@ impl WalletSubcommands { password.clone(), account_name_ref.as_deref(), )?; + + // Rename the keystore file to include the address let identifier = account_name_ref.as_deref().unwrap_or(&uuid); + let old_path = path.join(identifier); + let path_with_address = path.join(format!( + "{}_{}", + identifier, + wallet.address().to_checksum(None) + )); + + if old_path.exists() && old_path != path_with_address { + std::fs::rename(&old_path, &path_with_address)?; + } if let Some(json) = json_values.as_mut() { json.push(if shell::verbosity() > 0 { json!({ "address": wallet.address().to_checksum(None), "public_key": format!("0x{}", hex::encode(wallet.public_key())), - "path": format!("{}", path.join(identifier).display()), + "path": format!("{}", path_with_address.display()), }) } else { json!({ "address": wallet.address().to_checksum(None), - "path": format!("{}", path.join(identifier).display()), + "path": format!("{}", path_with_address.display()), }) }); } else { sh_println!( "Created new encrypted keystore file: {}", - path.join(identifier).display() + path_with_address.display() )?; sh_println!("Address: {}", wallet.address().to_checksum(None))?; if shell::verbosity() > 0 { @@ -621,10 +633,9 @@ impl WalletSubcommands { fs::create_dir_all(&dir)?; - // check if account exists already - let keystore_path = Path::new(&dir).join(&account_name); - if keystore_path.exists() { - eyre::bail!("Keystore file already exists at {}", keystore_path.display()); + // Check if account exists already (using shared utility) + if find_keystore_by_name(&dir, &account_name).is_some() { + eyre::bail!("Keystore file already exists for account '{}'", account_name); } // get wallet @@ -654,13 +665,23 @@ flag to set your key via: let mut rng = thread_rng(); let (wallet, _) = PrivateKeySigner::encrypt_keystore( - dir, + dir.clone(), &mut rng, private_key, password, Some(&account_name), )?; let address = wallet.address(); + + // Rename the file to include the address in the new format + let old_path = dir.join(&account_name); + let path_with_address = + dir.join(format!("{}_{}", account_name, address.to_checksum(None))); + + if old_path.exists() && old_path != path_with_address { + std::fs::rename(&old_path, &path_with_address)?; + } + let success_message = format!( "`{}` keystore was saved successfully. Address: {:?}", &account_name, address, @@ -679,7 +700,13 @@ flag to set your key via: })? }; - let keystore_path = Path::new(&dir).join(&name); + // Use shared utility for keystore lookup + let keystore_path = if let Some(found_path) = find_keystore_by_name(&dir, &name) { + found_path + } else { + dir.join(&name) // Fallback to direct path for error message + }; + if !keystore_path.exists() { eyre::bail!("Keystore file does not exist at {}", keystore_path.display()); } @@ -752,7 +779,12 @@ flag to set your key via: })? }; - let keypath = dir.join(&account_name); + // Use shared utility for keystore lookup + let keypath = if let Some(found_path) = find_keystore_by_name(&dir, &account_name) { + found_path + } else { + dir.join(&account_name) // Fallback to direct path for error message + }; if !keypath.exists() { eyre::bail!("Keystore file does not exist at {}", keypath.display()); @@ -789,7 +821,12 @@ flag to set your key via: })? }; - let keypath = dir.join(&account_name); + // Use shared utility for keystore lookup + let keypath = if let Some(found_path) = find_keystore_by_name(&dir, &account_name) { + found_path + } else { + dir.join(&account_name) // Fallback to direct path for error message + }; if !keypath.exists() { eyre::bail!("Keystore file does not exist at {}", keypath.display()); @@ -821,13 +858,25 @@ flag to set your key via: let private_key = wallet.credential().to_bytes(); let mut rng = thread_rng(); let (wallet, _) = PrivateKeySigner::encrypt_keystore( - dir, + dir.clone(), &mut rng, private_key, new_password, Some(&account_name), )?; + // Remove the old keystore file + std::fs::remove_file(&keypath)?; + + // Rename the new file to include the address + let old_path = dir.join(&account_name); + let path_with_address = + dir.join(format!("{}_{}", account_name, wallet.address().to_checksum(None))); + + if old_path.exists() && old_path != path_with_address { + std::fs::rename(&old_path, &path_with_address)?; + } + let success_message = format!( "Password for keystore `{}` was changed successfully. Address: {:?}", &account_name, diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs index f5282496ed0ca..ebafc329441a2 100644 --- a/crates/wallets/src/utils.rs +++ b/crates/wallets/src/utils.rs @@ -1,5 +1,5 @@ use crate::{PendingSigner, WalletSigner, error::PrivateKeyError}; -use alloy_primitives::{B256, hex::FromHex}; +use alloy_primitives::{Address, B256, hex::FromHex}; use alloy_signer_ledger::HDPath as LedgerHDPath; use alloy_signer_local::PrivateKeySigner; use alloy_signer_trezor::HDPath as TrezorHDPath; @@ -93,9 +93,48 @@ pub fn maybe_get_keystore_path( ) -> Result> { let default_keystore_dir = Config::foundry_keystores_dir() .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - Ok(maybe_path - .map(PathBuf::from) - .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name)))) + + if let Some(path) = maybe_path { + return Ok(Some(PathBuf::from(path))); + } + + if let Some(name) = maybe_name { + if let Some(found_path) = find_keystore_by_name(&default_keystore_dir, name) { + return Ok(Some(found_path)); + } + + // Return the direct path even if it doesn't exist (for better error messages) + return Ok(Some(default_keystore_dir.join(name))); + } + + Ok(None) +} + +/// Finds a keystore file by account name, supporting both old and new filename formats +/// Returns the path if found, or None if not found +pub fn find_keystore_by_name( + keystore_dir: &std::path::Path, + account_name: &str, +) -> Option { + // Try exact match first (old format or exact filename) + let direct_path = keystore_dir.join(account_name); + if direct_path.exists() { + return Some(direct_path); + } + + // Try finding with address suffix (new format: account_name_0x
) + if let Ok(entries) = std::fs::read_dir(keystore_dir) { + let search_prefix = format!("{account_name}_"); + for entry in entries.flatten() { + if let Some(file_name) = entry.file_name().to_str() + && file_name.starts_with(&search_prefix) + && Address::parse_checksummed(&file_name[search_prefix.len()..], None).is_ok() + { + return Some(entry.path()); + } + } + } + None } /// Creates keystore signer from given parameters.