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