1use crate::{error::WalletSignerError, wallet_browser::signer::BrowserSigner};
2use alloy_consensus::{Sealed, SignableTransaction};
3use alloy_dyn_abi::TypedData;
4use alloy_network::{NetworkWallet, TransactionBuilder, TxSigner};
5use alloy_primitives::{Address, B256, ChainId, Signature, hex};
6use alloy_signer::Signer;
7use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner};
8use alloy_signer_local::{MnemonicBuilder, PrivateKeySigner, coins_bip39::English};
9use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner};
10use alloy_sol_types::{Eip712Domain, SolStruct};
11use async_trait::async_trait;
12use foundry_primitives::{
13 FoundryNetwork, FoundryTransactionRequest, FoundryTxEnvelope, FoundryTypedTx,
14};
15use std::{collections::HashSet, path::PathBuf, time::Duration};
16use tempo_primitives::TempoSignature;
17use tracing::warn;
18
19#[cfg(feature = "aws-kms")]
20use alloy_signer_aws::{AwsSigner, aws_config::BehaviorVersion, aws_sdk_kms::Client as AwsClient};
21
22#[cfg(feature = "gcp-kms")]
23use alloy_signer_gcp::{
24 GcpKeyRingRef, GcpSigner, GcpSignerError, KeySpecifier,
25 gcloud_sdk::{
26 GoogleApi,
27 google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient,
28 },
29};
30
31#[cfg(feature = "turnkey")]
32use alloy_signer_turnkey::TurnkeySigner;
33
34pub type Result<T> = std::result::Result<T, WalletSignerError>;
35
36#[derive(Debug)]
38pub enum WalletSigner {
39 Local(PrivateKeySigner),
41 Ledger(LedgerSigner),
43 Trezor(TrezorSigner),
45 Browser(BrowserSigner),
47 #[cfg(feature = "aws-kms")]
49 Aws(AwsSigner),
50 #[cfg(feature = "gcp-kms")]
52 Gcp(GcpSigner),
53 #[cfg(feature = "turnkey")]
55 Turnkey(TurnkeySigner),
56}
57
58impl WalletSigner {
59 pub async fn from_ledger_path(path: LedgerHDPath) -> Result<Self> {
60 let ledger = LedgerSigner::new(path, None).await?;
61 Ok(Self::Ledger(ledger))
62 }
63
64 pub async fn from_trezor_path(path: TrezorHDPath) -> Result<Self> {
65 let trezor = TrezorSigner::new(path, None).await?;
66 Ok(Self::Trezor(trezor))
67 }
68
69 pub async fn from_browser(
70 port: u16,
71 open_browser: bool,
72 browser_development: bool,
73 ) -> Result<Self> {
74 let browser_signer =
75 BrowserSigner::new(port, open_browser, Duration::from_secs(300), browser_development)
76 .await
77 .map_err(|e| WalletSignerError::Browser(e.into()))?;
78 Ok(Self::Browser(browser_signer))
79 }
80
81 pub async fn from_aws(key_id: String) -> Result<Self> {
82 #[cfg(feature = "aws-kms")]
83 {
84 let config =
85 alloy_signer_aws::aws_config::load_defaults(BehaviorVersion::latest()).await;
86 let client = AwsClient::new(&config);
87
88 Ok(Self::Aws(
89 AwsSigner::new(client, key_id, None)
90 .await
91 .map_err(|e| WalletSignerError::Aws(Box::new(e)))?,
92 ))
93 }
94
95 #[cfg(not(feature = "aws-kms"))]
96 {
97 let _ = key_id;
98 Err(WalletSignerError::aws_unsupported())
99 }
100 }
101
102 pub async fn from_gcp(
103 project_id: String,
104 location: String,
105 keyring: String,
106 key_name: String,
107 key_version: u64,
108 ) -> Result<Self> {
109 #[cfg(feature = "gcp-kms")]
110 {
111 let keyring = GcpKeyRingRef::new(&project_id, &location, &keyring);
112 let client = match GoogleApi::from_function(
113 KeyManagementServiceClient::new,
114 "https://cloudkms.googleapis.com",
115 None,
116 )
117 .await
118 {
119 Ok(c) => c,
120 Err(e) => {
121 return Err(WalletSignerError::Gcp(Box::new(GcpSignerError::GoogleKmsError(
122 e,
123 ))));
124 }
125 };
126
127 let specifier = KeySpecifier::new(keyring, &key_name, key_version);
128
129 Ok(Self::Gcp(
130 GcpSigner::new(client, specifier, None)
131 .await
132 .map_err(|e| WalletSignerError::Gcp(Box::new(e)))?,
133 ))
134 }
135
136 #[cfg(not(feature = "gcp-kms"))]
137 {
138 let _ = project_id;
139 let _ = location;
140 let _ = keyring;
141 let _ = key_name;
142 let _ = key_version;
143 Err(WalletSignerError::gcp_unsupported())
144 }
145 }
146
147 pub fn from_turnkey(
148 api_private_key: String,
149 organization_id: String,
150 address: Address,
151 ) -> Result<Self> {
152 #[cfg(feature = "turnkey")]
153 {
154 Ok(Self::Turnkey(TurnkeySigner::from_api_key(
155 &api_private_key,
156 organization_id,
157 address,
158 None,
159 )?))
160 }
161
162 #[cfg(not(feature = "turnkey"))]
163 {
164 let _ = api_private_key;
165 let _ = organization_id;
166 let _ = address;
167 Err(WalletSignerError::UnsupportedSigner("Turnkey"))
168 }
169 }
170
171 pub fn from_private_key(private_key: &B256) -> Result<Self> {
172 Ok(Self::Local(PrivateKeySigner::from_bytes(private_key)?))
173 }
174
175 pub async fn available_senders(&self, max: usize) -> Result<Vec<Address>> {
184 let mut senders = HashSet::new();
185
186 match self {
187 Self::Local(local) => {
188 senders.insert(local.address());
189 }
190 Self::Ledger(ledger) => {
191 for i in 0..max {
193 match ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await {
194 Ok(address) => {
195 senders.insert(address);
196 }
197 Err(e) => {
198 warn!("Failed to get Ledger address at index {i} (LedgerLive): {e}");
199 }
200 }
201 }
202 for i in 0..max {
204 match ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await {
205 Ok(address) => {
206 senders.insert(address);
207 }
208 Err(e) => {
209 warn!("Failed to get Ledger address at index {i} (Legacy): {e}");
210 }
211 }
212 }
213 }
214 Self::Trezor(trezor) => {
215 for i in 0..max {
216 match trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await {
217 Ok(address) => {
218 senders.insert(address);
219 }
220 Err(e) => {
221 warn!("Failed to get Trezor address at index {i} (TrezorLive): {e}",);
222 }
223 }
224 }
225 }
226 Self::Browser(browser) => {
227 senders.insert(alloy_signer::Signer::address(browser));
228 }
229 #[cfg(feature = "aws-kms")]
230 Self::Aws(aws) => {
231 senders.insert(alloy_signer::Signer::address(aws));
232 }
233 #[cfg(feature = "gcp-kms")]
234 Self::Gcp(gcp) => {
235 senders.insert(alloy_signer::Signer::address(gcp));
236 }
237 #[cfg(feature = "turnkey")]
238 Self::Turnkey(turnkey) => {
239 senders.insert(alloy_signer::Signer::address(turnkey));
240 }
241 }
242 Ok(senders.into_iter().collect())
243 }
244
245 pub fn from_mnemonic(
246 mnemonic: &str,
247 passphrase: Option<&str>,
248 derivation_path: Option<&str>,
249 index: u32,
250 ) -> Result<Self> {
251 let mut builder = MnemonicBuilder::<English>::default().phrase(mnemonic);
252
253 if let Some(passphrase) = passphrase {
254 builder = builder.password(passphrase)
255 }
256
257 builder = if let Some(hd_path) = derivation_path {
258 builder.derivation_path(hd_path)?
259 } else {
260 builder.index(index)?
261 };
262
263 Ok(Self::Local(builder.build()?))
264 }
265}
266
267macro_rules! delegate {
268 ($s:ident, $inner:ident => $e:expr) => {
269 match $s {
270 Self::Local($inner) => $e,
271 Self::Ledger($inner) => $e,
272 Self::Trezor($inner) => $e,
273 Self::Browser($inner) => $e,
274 #[cfg(feature = "aws-kms")]
275 Self::Aws($inner) => $e,
276 #[cfg(feature = "gcp-kms")]
277 Self::Gcp($inner) => $e,
278 #[cfg(feature = "turnkey")]
279 Self::Turnkey($inner) => $e,
280 }
281 };
282}
283
284#[async_trait]
285impl Signer for WalletSigner {
286 async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result<Signature> {
288 delegate!(self, inner => inner.sign_hash(hash)).await
289 }
290
291 async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result<Signature> {
292 delegate!(self, inner => inner.sign_message(message)).await
293 }
294
295 fn address(&self) -> Address {
296 delegate!(self, inner => alloy_signer::Signer::address(inner))
297 }
298
299 fn chain_id(&self) -> Option<ChainId> {
300 delegate!(self, inner => inner.chain_id())
301 }
302
303 fn set_chain_id(&mut self, chain_id: Option<ChainId>) {
304 delegate!(self, inner => inner.set_chain_id(chain_id))
305 }
306
307 async fn sign_typed_data<T: SolStruct + Send + Sync>(
308 &self,
309 payload: &T,
310 domain: &Eip712Domain,
311 ) -> alloy_signer::Result<Signature>
312 where
313 Self: Sized,
314 {
315 delegate!(self, inner => inner.sign_typed_data(payload, domain)).await
316 }
317
318 async fn sign_dynamic_typed_data(
319 &self,
320 payload: &TypedData,
321 ) -> alloy_signer::Result<Signature> {
322 delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await
323 }
324}
325
326#[async_trait]
327impl TxSigner<Signature> for WalletSigner {
328 fn address(&self) -> Address {
329 Signer::address(self)
330 }
331
332 async fn sign_transaction(
333 &self,
334 tx: &mut dyn SignableTransaction<Signature>,
335 ) -> alloy_signer::Result<Signature> {
336 delegate!(self, inner => TxSigner::sign_transaction(inner, tx)).await
337 }
338}
339
340impl NetworkWallet<FoundryNetwork> for WalletSigner {
341 fn default_signer_address(&self) -> Address {
342 alloy_signer::Signer::address(self)
343 }
344
345 fn has_signer_for(&self, address: &Address) -> bool {
346 self.default_signer_address() == *address
347 }
348
349 fn signer_addresses(&self) -> impl Iterator<Item = Address> {
350 std::iter::once(self.default_signer_address())
351 }
352
353 async fn sign_transaction_from(
354 &self,
355 sender: Address,
356 tx: FoundryTypedTx,
357 ) -> alloy_signer::Result<FoundryTxEnvelope> {
358 if sender != self.default_signer_address() {
359 return Err(alloy_signer::Error::other("Signer address mismatch"));
360 }
361
362 match tx {
363 FoundryTypedTx::Legacy(mut inner) => {
364 let sig = TxSigner::sign_transaction(self, &mut inner).await?;
365 Ok(FoundryTxEnvelope::Legacy(inner.into_signed(sig)))
366 }
367 FoundryTypedTx::Eip2930(mut inner) => {
368 let sig = TxSigner::sign_transaction(self, &mut inner).await?;
369 Ok(FoundryTxEnvelope::Eip2930(inner.into_signed(sig)))
370 }
371 FoundryTypedTx::Eip1559(mut inner) => {
372 let sig = TxSigner::sign_transaction(self, &mut inner).await?;
373 Ok(FoundryTxEnvelope::Eip1559(inner.into_signed(sig)))
374 }
375 FoundryTypedTx::Eip4844(mut inner) => {
376 let sig = TxSigner::sign_transaction(self, &mut inner).await?;
377 Ok(FoundryTxEnvelope::Eip4844(inner.into_signed(sig)))
378 }
379 FoundryTypedTx::Eip7702(mut inner) => {
380 let sig = TxSigner::sign_transaction(self, &mut inner).await?;
381 Ok(FoundryTxEnvelope::Eip7702(inner.into_signed(sig)))
382 }
383 FoundryTypedTx::Deposit(inner) => {
384 Ok(FoundryTxEnvelope::Deposit(Sealed::new(inner)))
386 }
387 FoundryTypedTx::Tempo(mut inner) => {
388 let sig = TxSigner::sign_transaction(self, &mut inner).await?;
389 let tempo_sig: TempoSignature = sig.into();
390 Ok(FoundryTxEnvelope::Tempo(inner.into_signed(tempo_sig)))
391 }
392 }
393 }
394
395 #[doc(hidden)]
396 async fn sign_request(
397 &self,
398 request: FoundryTransactionRequest,
399 ) -> alloy_signer::Result<FoundryTxEnvelope> {
400 let sender = request.from().unwrap_or_else(|| self.default_signer_address());
401 let tx = request.build_typed_tx().map_err(|_| {
402 alloy_signer::Error::other("Failed to build typed transaction from request")
403 })?;
404 self.sign_transaction_from(sender, tx).await
405 }
406}
407
408#[derive(Debug, Clone)]
410pub enum PendingSigner {
411 Keystore(PathBuf),
412 Interactive,
413}
414
415impl PendingSigner {
416 pub fn unlock(self) -> Result<WalletSigner> {
417 match self {
418 Self::Keystore(path) => {
419 let password = rpassword::prompt_password("Enter keystore password:")?;
420 match PrivateKeySigner::decrypt_keystore(path, password) {
421 Ok(signer) => Ok(WalletSigner::Local(signer)),
422 Err(e) => match e {
423 alloy_signer_local::LocalSignerError::EthKeystoreError(
426 eth_keystore::KeystoreError::MacMismatch,
427 ) => Err(WalletSignerError::IncorrectKeystorePassword),
428 _ => Err(WalletSignerError::Local(e)),
429 },
430 }
431 }
432 Self::Interactive => {
433 let private_key = rpassword::prompt_password("Enter private key:")?;
434 Ok(WalletSigner::from_private_key(&hex::FromHex::from_hex(private_key)?)?)
435 }
436 }
437 }
438}