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