1use crate::Error;
10use ethers::prelude::*;
11use ethers::signers::{coins_bip39::English, LocalWallet, Signer};
12use ethers::types::{
13 transaction::eip2718::TypedTransaction, transaction::eip712::Eip712, Address as EthAddress,
14 Signature,
15};
16use std::str::FromStr;
17
18#[derive(Clone)]
20pub struct Wallet {
21 inner: LocalWallet,
23 address: EthAddress,
25}
26
27impl Wallet {
28 pub fn new_random() -> Self {
38 let inner = LocalWallet::new(&mut rand::thread_rng());
39 let address = inner.address();
40
41 tracing::info!("Created new random wallet: {}", address);
42
43 Self { inner, address }
44 }
45
46 pub fn from_private_key(private_key: &str) -> Result<Self, Error> {
60 let key = private_key.trim_start_matches("0x");
61
62 let inner = LocalWallet::from_str(key)
63 .map_err(|e| Error::Other(format!("Invalid private key: {}", e)))?;
64
65 let address = inner.address();
66
67 tracing::info!("Loaded wallet from private key: {}", address);
68
69 Ok(Self { inner, address })
70 }
71
72 pub fn from_mnemonic(mnemonic: &str, index: u32) -> Result<Self, Error> {
88 let wallet = MnemonicBuilder::<English>::default()
89 .phrase(mnemonic)
90 .index(index)
91 .map_err(|e| Error::Other(format!("Invalid index: {}", e)))?
92 .build()
93 .map_err(|e| Error::Other(format!("Failed to build wallet from mnemonic: {}", e)))?;
94
95 let address = wallet.address();
96
97 tracing::info!(
98 "Loaded wallet from mnemonic at index {}: {}",
99 index,
100 address
101 );
102
103 Ok(Self {
104 inner: wallet,
105 address,
106 })
107 }
108
109 pub fn with_chain_id(mut self, chain_id: u64) -> Self {
113 self.inner = self.inner.with_chain_id(chain_id);
114 tracing::debug!("Set wallet chain ID to {}", chain_id);
115 self
116 }
117
118 pub fn address(&self) -> String {
120 format!("{:?}", self.address)
121 }
122
123 pub fn eth_address(&self) -> EthAddress {
125 self.address
126 }
127
128 pub async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<Signature, Error> {
136 let signature = self
137 .inner
138 .sign_transaction(tx)
139 .await
140 .map_err(|e| Error::Transaction(format!("Failed to sign transaction: {}", e)))?;
141
142 tracing::debug!("Signed transaction");
143
144 Ok(signature)
145 }
146
147 pub async fn sign_message<S: AsRef<[u8]> + Send + Sync>(
155 &self,
156 message: S,
157 ) -> Result<Signature, Error> {
158 let signature = self
159 .inner
160 .sign_message(message)
161 .await
162 .map_err(|e| Error::Transaction(format!("Failed to sign message: {}", e)))?;
163
164 tracing::debug!("Signed message");
165
166 Ok(signature)
167 }
168
169 pub async fn sign_typed_data<T: Eip712 + Send + Sync>(
177 &self,
178 data: &T,
179 ) -> Result<Signature, Error> {
180 let signature = self
181 .inner
182 .sign_typed_data(data)
183 .await
184 .map_err(|e| Error::Transaction(format!("Failed to sign typed data: {}", e)))?;
185
186 tracing::debug!("Signed typed data");
187
188 Ok(signature)
189 }
190
191 pub fn chain_id(&self) -> Option<u64> {
193 Some(self.inner.chain_id())
194 }
195
196 pub fn export_private_key(&self) -> String {
201 tracing::warn!("Private key exported - ensure secure handling!");
202 format!("0x{}", hex::encode(self.inner.signer().to_bytes()))
203 }
204}
205
206impl std::fmt::Debug for Wallet {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 f.debug_struct("Wallet")
209 .field("address", &self.address())
210 .field("chain_id", &self.chain_id())
211 .finish()
212 }
213}
214
215pub struct WalletManager {
217 wallets: Vec<Wallet>,
218 active_index: usize,
219}
220
221impl WalletManager {
222 pub fn new() -> Self {
224 Self {
225 wallets: Vec::new(),
226 active_index: 0,
227 }
228 }
229
230 pub fn add_wallet(&mut self, wallet: Wallet) -> usize {
232 self.wallets.push(wallet);
233 self.wallets.len() - 1
234 }
235
236 pub fn active_wallet(&self) -> Option<&Wallet> {
238 self.wallets.get(self.active_index)
239 }
240
241 pub fn wallet(&self, index: usize) -> Option<&Wallet> {
243 self.wallets.get(index)
244 }
245
246 pub fn set_active(&mut self, index: usize) -> Result<(), Error> {
248 if index >= self.wallets.len() {
249 return Err(Error::Other("Invalid wallet index".to_string()));
250 }
251 self.active_index = index;
252 Ok(())
253 }
254
255 pub fn wallet_count(&self) -> usize {
257 self.wallets.len()
258 }
259
260 pub fn list_addresses(&self) -> Vec<String> {
262 self.wallets.iter().map(|w| w.address()).collect()
263 }
264
265 pub fn create_wallet(&mut self) -> usize {
267 let wallet = Wallet::new_random();
268 self.add_wallet(wallet)
269 }
270
271 pub fn import_wallet(&mut self, private_key: &str) -> Result<usize, Error> {
273 let wallet = Wallet::from_private_key(private_key)?;
274 Ok(self.add_wallet(wallet))
275 }
276
277 pub fn import_from_mnemonic(&mut self, mnemonic: &str, index: u32) -> Result<usize, Error> {
279 let wallet = Wallet::from_mnemonic(mnemonic, index)?;
280 Ok(self.add_wallet(wallet))
281 }
282}
283
284impl Default for WalletManager {
285 fn default() -> Self {
286 Self::new()
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_new_random_wallet() {
296 let wallet = Wallet::new_random();
297 assert!(wallet.address().starts_with("0x"));
298 assert_eq!(wallet.address().len(), 42);
299 }
300
301 #[test]
302 fn test_from_private_key() {
303 let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
305 let wallet = Wallet::from_private_key(private_key).unwrap();
306
307 let expected_address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266";
309 assert_eq!(wallet.address().to_lowercase(), expected_address);
310 }
311
312 #[test]
313 fn test_from_mnemonic() {
314 let mnemonic = "test test test test test test test test test test test junk";
316 let wallet = Wallet::from_mnemonic(mnemonic, 0).unwrap();
317
318 assert!(wallet.address().starts_with("0x"));
320 assert_eq!(wallet.address().len(), 42);
321 }
322
323 #[test]
324 fn test_wallet_with_chain_id() {
325 let wallet = Wallet::new_random().with_chain_id(1);
326 assert_eq!(wallet.chain_id(), Some(1));
327 }
328
329 #[tokio::test]
330 async fn test_sign_message() {
331 let wallet = Wallet::new_random();
332 let message = "Hello, Ethereum!";
333
334 let signature = wallet.sign_message(message).await.unwrap();
335
336 let sig_bytes = signature.to_vec();
338 assert_eq!(sig_bytes.len(), 65);
339 }
340
341 #[test]
342 fn test_wallet_manager() {
343 let mut manager = WalletManager::new();
344
345 let idx1 = manager.create_wallet();
347 let idx2 = manager.create_wallet();
348
349 assert_eq!(manager.wallet_count(), 2);
350 assert_eq!(idx1, 0);
351 assert_eq!(idx2, 1);
352
353 assert!(manager.active_wallet().is_some());
355
356 manager.set_active(1).unwrap();
358 assert!(manager.active_wallet().is_some());
359
360 let addresses = manager.list_addresses();
362 assert_eq!(addresses.len(), 2);
363 }
364
365 #[test]
366 fn test_wallet_manager_import() {
367 let mut manager = WalletManager::new();
368
369 let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
370 let idx = manager.import_wallet(private_key).unwrap();
371
372 assert_eq!(idx, 0);
373 assert_eq!(manager.wallet_count(), 1);
374
375 let wallet = manager.wallet(0).unwrap();
376 assert_eq!(
377 wallet.address().to_lowercase(),
378 "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
379 );
380 }
381}