foundry_wallets/
wallet_signer.rs
1use crate::error::WalletSignerError;
2use alloy_consensus::SignableTransaction;
3use alloy_dyn_abi::TypedData;
4use alloy_network::TxSigner;
5use alloy_primitives::{hex, Address, ChainId, PrimitiveSignature, B256};
6use alloy_signer::Signer;
7use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner};
8use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner};
9use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner};
10use alloy_sol_types::{Eip712Domain, SolStruct};
11use async_trait::async_trait;
12use std::path::PathBuf;
13
14#[cfg(feature = "aws-kms")]
15use {alloy_signer_aws::AwsSigner, aws_config::BehaviorVersion, aws_sdk_kms::Client as AwsClient};
16
17#[cfg(feature = "gcp-kms")]
18use {
19 alloy_signer_gcp::{GcpKeyRingRef, GcpSigner, GcpSignerError, KeySpecifier},
20 gcloud_sdk::{
21 google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient,
22 GoogleApi,
23 },
24};
25
26pub type Result<T> = std::result::Result<T, WalletSignerError>;
27
28#[derive(Debug)]
30pub enum WalletSigner {
31 Local(PrivateKeySigner),
33 Ledger(LedgerSigner),
35 Trezor(TrezorSigner),
37 #[cfg(feature = "aws-kms")]
39 Aws(AwsSigner),
40 #[cfg(feature = "gcp-kms")]
42 Gcp(GcpSigner),
43}
44
45impl WalletSigner {
46 pub async fn from_ledger_path(path: LedgerHDPath) -> Result<Self> {
47 let ledger = LedgerSigner::new(path, None).await?;
48 Ok(Self::Ledger(ledger))
49 }
50
51 pub async fn from_trezor_path(path: TrezorHDPath) -> Result<Self> {
52 let trezor = TrezorSigner::new(path, None).await?;
53 Ok(Self::Trezor(trezor))
54 }
55
56 pub async fn from_aws(key_id: String) -> Result<Self> {
57 #[cfg(feature = "aws-kms")]
58 {
59 let config = aws_config::load_defaults(BehaviorVersion::latest()).await;
60 let client = AwsClient::new(&config);
61
62 Ok(Self::Aws(AwsSigner::new(client, key_id, None).await?))
63 }
64
65 #[cfg(not(feature = "aws-kms"))]
66 {
67 let _ = key_id;
68 Err(WalletSignerError::aws_unsupported())
69 }
70 }
71
72 pub async fn from_gcp(
73 project_id: String,
74 location: String,
75 keyring: String,
76 key_name: String,
77 key_version: u64,
78 ) -> Result<Self> {
79 #[cfg(feature = "gcp-kms")]
80 {
81 let keyring = GcpKeyRingRef::new(&project_id, &location, &keyring);
82 let client = match GoogleApi::from_function(
83 KeyManagementServiceClient::new,
84 "https://cloudkms.googleapis.com",
85 None,
86 )
87 .await
88 {
89 Ok(c) => c,
90 Err(e) => return Err(WalletSignerError::from(GcpSignerError::GoogleKmsError(e))),
91 };
92
93 let specifier = KeySpecifier::new(keyring, &key_name, key_version);
94
95 Ok(Self::Gcp(GcpSigner::new(client, specifier, None).await?))
96 }
97
98 #[cfg(not(feature = "gcp-kms"))]
99 {
100 let _ = project_id;
101 let _ = location;
102 let _ = keyring;
103 let _ = key_name;
104 let _ = key_version;
105 Err(WalletSignerError::gcp_unsupported())
106 }
107 }
108
109 pub fn from_private_key(private_key: &B256) -> Result<Self> {
110 Ok(Self::Local(PrivateKeySigner::from_bytes(private_key)?))
111 }
112
113 pub async fn available_senders(&self, max: usize) -> Result<Vec<Address>> {
120 let mut senders = Vec::new();
121 match self {
122 Self::Local(local) => {
123 senders.push(local.address());
124 }
125 Self::Ledger(ledger) => {
126 for i in 0..max {
127 if let Ok(address) =
128 ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await
129 {
130 senders.push(address);
131 }
132 }
133 for i in 0..max {
134 if let Ok(address) =
135 ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await
136 {
137 senders.push(address);
138 }
139 }
140 }
141 Self::Trezor(trezor) => {
142 for i in 0..max {
143 if let Ok(address) =
144 trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await
145 {
146 senders.push(address);
147 }
148 }
149 }
150 #[cfg(feature = "aws-kms")]
151 Self::Aws(aws) => {
152 senders.push(alloy_signer::Signer::address(aws));
153 }
154 #[cfg(feature = "gcp-kms")]
155 Self::Gcp(gcp) => {
156 senders.push(alloy_signer::Signer::address(gcp));
157 }
158 }
159 Ok(senders)
160 }
161
162 pub fn from_mnemonic(
163 mnemonic: &str,
164 passphrase: Option<&str>,
165 derivation_path: Option<&str>,
166 index: u32,
167 ) -> Result<Self> {
168 let mut builder = MnemonicBuilder::<English>::default().phrase(mnemonic);
169
170 if let Some(passphrase) = passphrase {
171 builder = builder.password(passphrase)
172 }
173
174 builder = if let Some(hd_path) = derivation_path {
175 builder.derivation_path(hd_path)?
176 } else {
177 builder.index(index)?
178 };
179
180 Ok(Self::Local(builder.build()?))
181 }
182}
183
184macro_rules! delegate {
185 ($s:ident, $inner:ident => $e:expr) => {
186 match $s {
187 Self::Local($inner) => $e,
188 Self::Ledger($inner) => $e,
189 Self::Trezor($inner) => $e,
190 #[cfg(feature = "aws-kms")]
191 Self::Aws($inner) => $e,
192 #[cfg(feature = "gcp-kms")]
193 Self::Gcp($inner) => $e,
194 }
195 };
196}
197
198#[async_trait]
199impl Signer for WalletSigner {
200 async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result<PrimitiveSignature> {
202 delegate!(self, inner => inner.sign_hash(hash)).await
203 }
204
205 async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result<PrimitiveSignature> {
206 delegate!(self, inner => inner.sign_message(message)).await
207 }
208
209 fn address(&self) -> Address {
210 delegate!(self, inner => alloy_signer::Signer::address(inner))
211 }
212
213 fn chain_id(&self) -> Option<ChainId> {
214 delegate!(self, inner => inner.chain_id())
215 }
216
217 fn set_chain_id(&mut self, chain_id: Option<ChainId>) {
218 delegate!(self, inner => inner.set_chain_id(chain_id))
219 }
220
221 async fn sign_typed_data<T: SolStruct + Send + Sync>(
222 &self,
223 payload: &T,
224 domain: &Eip712Domain,
225 ) -> alloy_signer::Result<PrimitiveSignature>
226 where
227 Self: Sized,
228 {
229 delegate!(self, inner => inner.sign_typed_data(payload, domain)).await
230 }
231
232 async fn sign_dynamic_typed_data(
233 &self,
234 payload: &TypedData,
235 ) -> alloy_signer::Result<PrimitiveSignature> {
236 delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await
237 }
238}
239
240#[async_trait]
241impl TxSigner<PrimitiveSignature> for WalletSigner {
242 fn address(&self) -> Address {
243 delegate!(self, inner => alloy_signer::Signer::address(inner))
244 }
245
246 async fn sign_transaction(
247 &self,
248 tx: &mut dyn SignableTransaction<PrimitiveSignature>,
249 ) -> alloy_signer::Result<PrimitiveSignature> {
250 delegate!(self, inner => inner.sign_transaction(tx)).await
251 }
252}
253
254#[derive(Debug, Clone)]
256pub enum PendingSigner {
257 Keystore(PathBuf),
258 Interactive,
259}
260
261impl PendingSigner {
262 pub fn unlock(self) -> Result<WalletSigner> {
263 match self {
264 Self::Keystore(path) => {
265 let password = rpassword::prompt_password("Enter keystore password:")?;
266 match PrivateKeySigner::decrypt_keystore(path, password) {
267 Ok(signer) => Ok(WalletSigner::Local(signer)),
268 Err(e) => match e {
269 alloy_signer_local::LocalSignerError::EthKeystoreError(
272 eth_keystore::KeystoreError::MacMismatch,
273 ) => Err(WalletSignerError::IncorrectKeystorePassword),
274 _ => Err(WalletSignerError::Local(e)),
275 },
276 }
277 }
278 Self::Interactive => {
279 let private_key = rpassword::prompt_password("Enter private key:")?;
280 Ok(WalletSigner::from_private_key(&hex::FromHex::from_hex(private_key)?)?)
281 }
282 }
283 }
284}