foundry_wallets/
utils.rs
1use crate::{error::PrivateKeyError, PendingSigner, WalletSigner};
2use alloy_primitives::{hex::FromHex, B256};
3use alloy_signer_ledger::HDPath as LedgerHDPath;
4use alloy_signer_local::PrivateKeySigner;
5use alloy_signer_trezor::HDPath as TrezorHDPath;
6use eyre::{Context, Result};
7use foundry_config::Config;
8use std::{
9 fs,
10 path::{Path, PathBuf},
11};
12
13fn ensure_pk_not_env(pk: &str) -> Result<()> {
14 if !pk.starts_with("0x") && std::env::var(pk).is_ok() {
15 return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string()).into());
16 }
17 Ok(())
18}
19
20pub fn create_private_key_signer(private_key_str: &str) -> Result<WalletSigner> {
22 let Ok(private_key) = B256::from_hex(private_key_str) else {
23 ensure_pk_not_env(private_key_str)?;
24 eyre::bail!("Failed to decode private key")
25 };
26 match PrivateKeySigner::from_bytes(&private_key) {
27 Ok(pk) => Ok(WalletSigner::Local(pk)),
28 Err(err) => {
29 ensure_pk_not_env(private_key_str)?;
30 eyre::bail!("Failed to create wallet from private key: {err}")
31 }
32 }
33}
34
35pub fn create_mnemonic_signer(
39 mnemonic: &str,
40 passphrase: Option<&str>,
41 hd_path: Option<&str>,
42 index: u32,
43) -> Result<WalletSigner> {
44 let mnemonic = if Path::new(mnemonic).is_file() {
45 fs::read_to_string(mnemonic)?.replace('\n', "")
46 } else {
47 mnemonic.to_owned()
48 };
49
50 Ok(WalletSigner::from_mnemonic(&mnemonic, passphrase, hd_path, index)?)
51}
52
53pub async fn create_ledger_signer(
55 hd_path: Option<&str>,
56 mnemonic_index: u32,
57) -> Result<WalletSigner> {
58 let derivation = if let Some(hd_path) = hd_path {
59 LedgerHDPath::Other(hd_path.to_owned())
60 } else {
61 LedgerHDPath::LedgerLive(mnemonic_index as usize)
62 };
63
64 WalletSigner::from_ledger_path(derivation).await.wrap_err_with(|| {
65 "\
66Could not connect to Ledger device.
67Make sure it's connected and unlocked, with no other desktop wallet apps open."
68 })
69}
70
71pub async fn create_trezor_signer(
73 hd_path: Option<&str>,
74 mnemonic_index: u32,
75) -> Result<WalletSigner> {
76 let derivation = if let Some(hd_path) = hd_path {
77 TrezorHDPath::Other(hd_path.to_owned())
78 } else {
79 TrezorHDPath::TrezorLive(mnemonic_index as usize)
80 };
81
82 WalletSigner::from_trezor_path(derivation).await.wrap_err_with(|| {
83 "\
84Could not connect to Trezor device.
85Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open."
86 })
87}
88
89pub fn maybe_get_keystore_path(
90 maybe_path: Option<&str>,
91 maybe_name: Option<&str>,
92) -> Result<Option<PathBuf>> {
93 let default_keystore_dir = Config::foundry_keystores_dir()
94 .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?;
95 Ok(maybe_path
96 .map(PathBuf::from)
97 .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name))))
98}
99
100pub fn create_keystore_signer(
108 path: &PathBuf,
109 maybe_password: Option<&str>,
110 maybe_password_file: Option<&str>,
111) -> Result<(Option<WalletSigner>, Option<PendingSigner>)> {
112 if !path.exists() {
113 eyre::bail!("Keystore file `{path:?}` does not exist")
114 }
115
116 if path.is_dir() {
117 eyre::bail!(
118 "Keystore path `{path:?}` is a directory. Please specify the keystore file directly."
119 )
120 }
121
122 let password = match (maybe_password, maybe_password_file) {
123 (Some(password), _) => Ok(Some(password.to_string())),
124 (_, Some(password_file)) => {
125 let password_file = Path::new(password_file);
126 if !password_file.is_file() {
127 Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist"))
128 } else {
129 Ok(Some(
130 fs::read_to_string(password_file)
131 .wrap_err_with(|| {
132 format!("Failed to read keystore password file at {password_file:?}")
133 })?
134 .trim_end()
135 .to_string(),
136 ))
137 }
138 }
139 (None, None) => Ok(None),
140 }?;
141
142 if let Some(password) = password {
143 let wallet = PrivateKeySigner::decrypt_keystore(path, password)
144 .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?;
145 Ok((Some(WalletSigner::Local(wallet)), None))
146 } else {
147 Ok((None, Some(PendingSigner::Keystore(path.clone()))))
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn parse_private_key_signer() {
157 let pk = B256::random();
158 let pk_str = pk.to_string();
159 assert!(create_private_key_signer(&pk_str).is_ok());
160 assert!(create_private_key_signer(&pk_str[2..]).is_ok());
162 }
163}