1use crate::{error::WalletSignerError, wallet_browser::signer::BrowserSigner};
2use alloy_consensus::SignableTransaction;
3use alloy_dyn_abi::TypedData;
4use alloy_network::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 std::{collections::HashSet, path::PathBuf, time::Duration};
13use tracing::warn;
14
15#[cfg(feature = "aws-kms")]
16use alloy_signer_aws::{AwsSigner, aws_config::BehaviorVersion, aws_sdk_kms::Client as AwsClient};
17
18#[cfg(feature = "gcp-kms")]
19use alloy_signer_gcp::{
20 GcpKeyRingRef, GcpSigner, GcpSignerError, KeySpecifier,
21 gcloud_sdk::{
22 GoogleApi,
23 google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient,
24 },
25};
26
27#[cfg(feature = "turnkey")]
28use alloy_signer_turnkey::TurnkeySigner;
29
30pub type Result<T> = std::result::Result<T, WalletSignerError>;
31
32#[derive(Debug)]
34pub enum WalletSigner {
35 Local(PrivateKeySigner),
37 Ledger(LedgerSigner),
39 Trezor(TrezorSigner),
41 Browser(BrowserSigner),
43 #[cfg(feature = "aws-kms")]
45 Aws(AwsSigner),
46 #[cfg(feature = "gcp-kms")]
48 Gcp(GcpSigner),
49 #[cfg(feature = "turnkey")]
51 Turnkey(TurnkeySigner),
52}
53
54impl WalletSigner {
55 pub async fn from_ledger_path(path: LedgerHDPath) -> Result<Self> {
56 let ledger = LedgerSigner::new(path, None).await?;
57 Ok(Self::Ledger(ledger))
58 }
59
60 pub async fn from_trezor_path(path: TrezorHDPath) -> Result<Self> {
61 let trezor = TrezorSigner::new(path, None).await?;
62 Ok(Self::Trezor(trezor))
63 }
64
65 pub async fn from_browser(
66 port: u16,
67 open_browser: bool,
68 browser_development: bool,
69 ) -> Result<Self> {
70 let browser_signer =
71 BrowserSigner::new(port, open_browser, Duration::from_secs(300), browser_development)
72 .await
73 .map_err(|e| WalletSignerError::Browser(e.into()))?;
74 Ok(Self::Browser(browser_signer))
75 }
76
77 pub async fn from_aws(key_id: String) -> Result<Self> {
78 #[cfg(feature = "aws-kms")]
79 {
80 let config =
81 alloy_signer_aws::aws_config::load_defaults(BehaviorVersion::latest()).await;
82 let client = AwsClient::new(&config);
83
84 Ok(Self::Aws(
85 AwsSigner::new(client, key_id, None)
86 .await
87 .map_err(|e| WalletSignerError::Aws(Box::new(e)))?,
88 ))
89 }
90
91 #[cfg(not(feature = "aws-kms"))]
92 {
93 let _ = key_id;
94 Err(WalletSignerError::aws_unsupported())
95 }
96 }
97
98 pub async fn from_gcp(
99 project_id: String,
100 location: String,
101 keyring: String,
102 key_name: String,
103 key_version: u64,
104 ) -> Result<Self> {
105 #[cfg(feature = "gcp-kms")]
106 {
107 let keyring = GcpKeyRingRef::new(&project_id, &location, &keyring);
108 let client = match GoogleApi::from_function(
109 KeyManagementServiceClient::new,
110 "https://cloudkms.googleapis.com",
111 None,
112 )
113 .await
114 {
115 Ok(c) => c,
116 Err(e) => {
117 return Err(WalletSignerError::Gcp(Box::new(GcpSignerError::GoogleKmsError(
118 e,
119 ))));
120 }
121 };
122
123 let specifier = KeySpecifier::new(keyring, &key_name, key_version);
124
125 Ok(Self::Gcp(
126 GcpSigner::new(client, specifier, None)
127 .await
128 .map_err(|e| WalletSignerError::Gcp(Box::new(e)))?,
129 ))
130 }
131
132 #[cfg(not(feature = "gcp-kms"))]
133 {
134 let _ = project_id;
135 let _ = location;
136 let _ = keyring;
137 let _ = key_name;
138 let _ = key_version;
139 Err(WalletSignerError::gcp_unsupported())
140 }
141 }
142
143 pub fn from_turnkey(
144 api_private_key: String,
145 organization_id: String,
146 address: Address,
147 ) -> Result<Self> {
148 #[cfg(feature = "turnkey")]
149 {
150 Ok(Self::Turnkey(TurnkeySigner::from_api_key(
151 &api_private_key,
152 organization_id,
153 address,
154 None,
155 )?))
156 }
157
158 #[cfg(not(feature = "turnkey"))]
159 {
160 let _ = api_private_key;
161 let _ = organization_id;
162 let _ = address;
163 Err(WalletSignerError::UnsupportedSigner("Turnkey"))
164 }
165 }
166
167 pub fn from_private_key(private_key: &B256) -> Result<Self> {
168 Ok(Self::Local(PrivateKeySigner::from_bytes(private_key)?))
169 }
170
171 pub async fn available_senders(&self, max: usize) -> Result<Vec<Address>> {
180 let mut senders = HashSet::new();
181
182 match self {
183 Self::Local(local) => {
184 senders.insert(local.address());
185 }
186 Self::Ledger(ledger) => {
187 for i in 0..max {
189 match ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await {
190 Ok(address) => {
191 senders.insert(address);
192 }
193 Err(e) => {
194 warn!("Failed to get Ledger address at index {i} (LedgerLive): {e}");
195 }
196 }
197 }
198 for i in 0..max {
200 match ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await {
201 Ok(address) => {
202 senders.insert(address);
203 }
204 Err(e) => {
205 warn!("Failed to get Ledger address at index {i} (Legacy): {e}");
206 }
207 }
208 }
209 }
210 Self::Trezor(trezor) => {
211 for i in 0..max {
212 match trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await {
213 Ok(address) => {
214 senders.insert(address);
215 }
216 Err(e) => {
217 warn!("Failed to get Trezor address at index {i} (TrezorLive): {e}",);
218 }
219 }
220 }
221 }
222 Self::Browser(browser) => {
223 senders.insert(alloy_signer::Signer::address(browser));
224 }
225 #[cfg(feature = "aws-kms")]
226 Self::Aws(aws) => {
227 senders.insert(alloy_signer::Signer::address(aws));
228 }
229 #[cfg(feature = "gcp-kms")]
230 Self::Gcp(gcp) => {
231 senders.insert(alloy_signer::Signer::address(gcp));
232 }
233 #[cfg(feature = "turnkey")]
234 Self::Turnkey(turnkey) => {
235 senders.insert(alloy_signer::Signer::address(turnkey));
236 }
237 }
238 Ok(senders.into_iter().collect())
239 }
240
241 pub fn from_mnemonic(
242 mnemonic: &str,
243 passphrase: Option<&str>,
244 derivation_path: Option<&str>,
245 index: u32,
246 ) -> Result<Self> {
247 let mut builder = MnemonicBuilder::<English>::default().phrase(mnemonic);
248
249 if let Some(passphrase) = passphrase {
250 builder = builder.password(passphrase)
251 }
252
253 builder = if let Some(hd_path) = derivation_path {
254 builder.derivation_path(hd_path)?
255 } else {
256 builder.index(index)?
257 };
258
259 Ok(Self::Local(builder.build()?))
260 }
261}
262
263macro_rules! delegate {
264 ($s:ident, $inner:ident => $e:expr) => {
265 match $s {
266 Self::Local($inner) => $e,
267 Self::Ledger($inner) => $e,
268 Self::Trezor($inner) => $e,
269 Self::Browser($inner) => $e,
270 #[cfg(feature = "aws-kms")]
271 Self::Aws($inner) => $e,
272 #[cfg(feature = "gcp-kms")]
273 Self::Gcp($inner) => $e,
274 #[cfg(feature = "turnkey")]
275 Self::Turnkey($inner) => $e,
276 }
277 };
278}
279
280#[async_trait]
281impl Signer for WalletSigner {
282 async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result<Signature> {
284 delegate!(self, inner => inner.sign_hash(hash)).await
285 }
286
287 async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result<Signature> {
288 delegate!(self, inner => inner.sign_message(message)).await
289 }
290
291 fn address(&self) -> Address {
292 delegate!(self, inner => alloy_signer::Signer::address(inner))
293 }
294
295 fn chain_id(&self) -> Option<ChainId> {
296 delegate!(self, inner => inner.chain_id())
297 }
298
299 fn set_chain_id(&mut self, chain_id: Option<ChainId>) {
300 delegate!(self, inner => inner.set_chain_id(chain_id))
301 }
302
303 async fn sign_typed_data<T: SolStruct + Send + Sync>(
304 &self,
305 payload: &T,
306 domain: &Eip712Domain,
307 ) -> alloy_signer::Result<Signature>
308 where
309 Self: Sized,
310 {
311 delegate!(self, inner => inner.sign_typed_data(payload, domain)).await
312 }
313
314 async fn sign_dynamic_typed_data(
315 &self,
316 payload: &TypedData,
317 ) -> alloy_signer::Result<Signature> {
318 delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await
319 }
320}
321
322#[async_trait]
323impl TxSigner<Signature> for WalletSigner {
324 fn address(&self) -> Address {
325 delegate!(self, inner => alloy_signer::Signer::address(inner))
326 }
327
328 async fn sign_transaction(
329 &self,
330 tx: &mut dyn SignableTransaction<Signature>,
331 ) -> alloy_signer::Result<Signature> {
332 delegate!(self, inner => inner.sign_transaction(tx)).await
333 }
334}
335
336#[derive(Debug, Clone)]
338pub enum PendingSigner {
339 Keystore(PathBuf),
340 Interactive,
341}
342
343impl PendingSigner {
344 pub fn unlock(self) -> Result<WalletSigner> {
345 match self {
346 Self::Keystore(path) => {
347 let password = rpassword::prompt_password("Enter keystore password:")?;
348 match PrivateKeySigner::decrypt_keystore(path, password) {
349 Ok(signer) => Ok(WalletSigner::Local(signer)),
350 Err(e) => match e {
351 alloy_signer_local::LocalSignerError::EthKeystoreError(
354 eth_keystore::KeystoreError::MacMismatch,
355 ) => Err(WalletSignerError::IncorrectKeystorePassword),
356 _ => Err(WalletSignerError::Local(e)),
357 },
358 }
359 }
360 Self::Interactive => {
361 let private_key = rpassword::prompt_password("Enter private key:")?;
362 Ok(WalletSigner::from_private_key(&hex::FromHex::from_hex(private_key)?)?)
363 }
364 }
365 }
366}