1use crate::Error;
10use alloy::primitives::{Address as EthAddress, Signature, B256};
11use alloy::signers::{
12 local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner},
13 Signer,
14};
15use std::str::FromStr;
16
17#[derive(Clone)]
19pub struct Wallet {
20 inner: PrivateKeySigner,
22 address: EthAddress,
24 chain_id: Option<u64>,
26}
27
28impl Wallet {
29 pub fn new_random() -> Self {
39 let inner = PrivateKeySigner::random();
40 let address = inner.address();
41
42 tracing::info!("Created new random wallet: {}", address);
43
44 Self {
45 inner,
46 address,
47 chain_id: None,
48 }
49 }
50
51 pub fn from_private_key(private_key: &str) -> Result<Self, Error> {
65 let key = private_key.trim_start_matches("0x");
66
67 let inner = PrivateKeySigner::from_str(key)
68 .map_err(|e| Error::Other(format!("Invalid private key: {}", e)))?;
69
70 let address = inner.address();
71
72 tracing::info!("Loaded wallet from private key: {}", address);
73
74 Ok(Self {
75 inner,
76 address,
77 chain_id: None,
78 })
79 }
80
81 pub fn from_mnemonic(mnemonic: &str, index: u32) -> Result<Self, Error> {
97 let signer = MnemonicBuilder::<English>::default()
98 .phrase(mnemonic)
99 .index(index)
100 .map_err(|e| Error::Other(format!("Invalid index: {}", e)))?
101 .build()
102 .map_err(|e| Error::Other(format!("Failed to build wallet from mnemonic: {}", e)))?;
103
104 let address = signer.address();
105
106 tracing::info!(
107 "Loaded wallet from mnemonic at index {}: {}",
108 index,
109 address
110 );
111
112 Ok(Self {
113 inner: signer,
114 address,
115 chain_id: None,
116 })
117 }
118
119 pub fn with_chain_id(mut self, chain_id: u64) -> Self {
123 self.chain_id = Some(chain_id);
124 tracing::debug!("Set wallet chain ID to {}", chain_id);
125 self
126 }
127
128 pub fn address(&self) -> String {
130 format!("{:?}", self.address)
131 }
132
133 pub fn eth_address(&self) -> EthAddress {
135 self.address
136 }
137
138 pub async fn sign_transaction_hash(&self, hash: &B256) -> Result<Signature, Error> {
146 let signature = self
147 .inner
148 .sign_hash(hash)
149 .await
150 .map_err(|e| Error::Transaction(format!("Failed to sign transaction: {}", e)))?;
151
152 tracing::debug!("Signed transaction");
153
154 Ok(signature)
155 }
156
157 pub async fn sign_message<S: AsRef<[u8]> + Send + Sync>(
165 &self,
166 message: S,
167 ) -> Result<Signature, Error> {
168 let signature = self
169 .inner
170 .sign_message(message.as_ref())
171 .await
172 .map_err(|e| Error::Transaction(format!("Failed to sign message: {}", e)))?;
173
174 tracing::debug!("Signed message");
175
176 Ok(signature)
177 }
178
179 pub async fn sign_typed_data_hash(&self, hash: &B256) -> Result<Signature, Error> {
187 let signature = self
189 .inner
190 .sign_hash(hash)
191 .await
192 .map_err(|e| Error::Transaction(format!("Failed to sign typed data: {}", e)))?;
193
194 tracing::debug!("Signed typed data");
195
196 Ok(signature)
197 }
198
199 pub fn chain_id(&self) -> Option<u64> {
201 self.chain_id
202 }
203
204 pub fn export_private_key(&self) -> String {
209 tracing::warn!("Private key exported - ensure secure handling!");
210 format!("0x{}", hex::encode(self.inner.to_bytes()))
211 }
212}
213
214impl std::fmt::Debug for Wallet {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 f.debug_struct("Wallet")
217 .field("address", &self.address())
218 .field("chain_id", &self.chain_id())
219 .finish()
220 }
221}
222
223pub struct WalletManager {
225 wallets: Vec<Wallet>,
226 active_index: usize,
227}
228
229impl WalletManager {
230 pub fn new() -> Self {
232 Self {
233 wallets: Vec::new(),
234 active_index: 0,
235 }
236 }
237
238 pub fn add_wallet(&mut self, wallet: Wallet) -> usize {
240 self.wallets.push(wallet);
241 self.wallets.len() - 1
242 }
243
244 pub fn active_wallet(&self) -> Option<&Wallet> {
246 self.wallets.get(self.active_index)
247 }
248
249 pub fn wallet(&self, index: usize) -> Option<&Wallet> {
251 self.wallets.get(index)
252 }
253
254 pub fn set_active(&mut self, index: usize) -> Result<(), Error> {
256 if index >= self.wallets.len() {
257 return Err(Error::Other("Invalid wallet index".to_string()));
258 }
259 self.active_index = index;
260 Ok(())
261 }
262
263 pub fn wallet_count(&self) -> usize {
265 self.wallets.len()
266 }
267
268 pub fn list_addresses(&self) -> Vec<String> {
270 self.wallets.iter().map(|w| w.address()).collect()
271 }
272
273 pub fn create_wallet(&mut self) -> usize {
275 let wallet = Wallet::new_random();
276 self.add_wallet(wallet)
277 }
278
279 pub fn import_wallet(&mut self, private_key: &str) -> Result<usize, Error> {
281 let wallet = Wallet::from_private_key(private_key)?;
282 Ok(self.add_wallet(wallet))
283 }
284
285 pub fn import_from_mnemonic(&mut self, mnemonic: &str, index: u32) -> Result<usize, Error> {
287 let wallet = Wallet::from_mnemonic(mnemonic, index)?;
288 Ok(self.add_wallet(wallet))
289 }
290}
291
292impl Default for WalletManager {
293 fn default() -> Self {
294 Self::new()
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_new_random_wallet() {
304 let wallet = Wallet::new_random();
305 assert!(wallet.address().starts_with("0x"));
306 assert_eq!(wallet.address().len(), 42);
307 }
308
309 #[test]
310 fn test_from_private_key() {
311 let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
313 let wallet = Wallet::from_private_key(private_key).unwrap();
314
315 let expected_address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266";
317 assert_eq!(wallet.address().to_lowercase(), expected_address);
318 }
319
320 #[test]
321 fn test_from_mnemonic() {
322 let mnemonic = "test test test test test test test test test test test junk";
324 let wallet = Wallet::from_mnemonic(mnemonic, 0).unwrap();
325
326 assert!(wallet.address().starts_with("0x"));
328 assert_eq!(wallet.address().len(), 42);
329 }
330
331 #[test]
332 fn test_wallet_with_chain_id() {
333 let wallet = Wallet::new_random().with_chain_id(1);
334 assert_eq!(wallet.chain_id(), Some(1));
335 }
336
337 #[tokio::test]
338 async fn test_sign_message() {
339 let wallet = Wallet::new_random();
340 let message = "Hello, Ethereum!";
341
342 let signature = wallet.sign_message(message).await.unwrap();
343
344 let sig_bytes = signature.as_bytes();
346 assert_eq!(sig_bytes.len(), 65);
347 }
348
349 #[test]
350 fn test_wallet_manager() {
351 let mut manager = WalletManager::new();
352
353 let idx1 = manager.create_wallet();
355 let idx2 = manager.create_wallet();
356
357 assert_eq!(manager.wallet_count(), 2);
358 assert_eq!(idx1, 0);
359 assert_eq!(idx2, 1);
360
361 assert!(manager.active_wallet().is_some());
363
364 manager.set_active(1).unwrap();
366 assert!(manager.active_wallet().is_some());
367
368 let addresses = manager.list_addresses();
370 assert_eq!(addresses.len(), 2);
371 }
372
373 #[test]
374 fn test_wallet_manager_import() {
375 let mut manager = WalletManager::new();
376
377 let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
378 let idx = manager.import_wallet(private_key).unwrap();
379
380 assert_eq!(idx, 0);
381 assert_eq!(manager.wallet_count(), 1);
382
383 let wallet = manager.wallet(0).unwrap();
384 assert_eq!(
385 wallet.address().to_lowercase(),
386 "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
387 );
388 }
389}