@@ -5,7 +5,7 @@ use aligned_sdk::verification_layer::{
55use ethers:: prelude:: * ;
66use ethers:: utils:: parse_ether;
77use k256:: ecdsa:: SigningKey ;
8- use log:: { debug, error, info} ;
8+ use log:: { debug, error, info, warn } ;
99use rand:: seq:: SliceRandom ;
1010use rand:: thread_rng;
1111use std:: fs:: { self , File } ;
@@ -149,70 +149,273 @@ pub async fn generate_and_fund_wallets(args: GenerateAndFundWalletsArgs) {
149149 . expect ( "Invalid private key" )
150150 . with_chain_id ( chain_id. as_u64 ( ) ) ;
151151
152- for i in 0 ..args. number_of_wallets {
153- // this is necessary because of the move
154- let eth_rpc_provider = eth_rpc_provider. clone ( ) ;
155- let funding_wallet = funding_wallet. clone ( ) ;
156- let amount_to_deposit = args. amount_to_deposit . clone ( ) ;
157- let amount_to_deposit_aligned = args. amount_to_deposit_to_aligned . clone ( ) ;
152+ // Generate all wallets first
153+ let mut wallets = Vec :: new ( ) ;
154+ let mut wallet_private_keys = Vec :: new ( ) ;
158155
159- // Generate new wallet
156+ info ! ( "Generating {} wallets..." , args. number_of_wallets) ;
157+ for i in 0 ..args. number_of_wallets {
160158 let wallet = Wallet :: new ( & mut thread_rng ( ) ) . with_chain_id ( chain_id. as_u64 ( ) ) ;
161159 info ! ( "Generated wallet {} with address {:?}" , i, wallet. address( ) ) ;
162160
163- // Fund the wallet
164- let signer = SignerMiddleware :: new ( eth_rpc_provider. clone ( ) , funding_wallet. clone ( ) ) ;
165- let amount_to_deposit =
166- parse_ether ( & amount_to_deposit) . expect ( "Ether format should be: XX.XX" ) ;
167- info ! ( "Depositing {}wei to wallet {}" , amount_to_deposit, i) ;
168- let tx = TransactionRequest :: new ( )
169- . from ( funding_wallet. address ( ) )
170- . to ( wallet. address ( ) )
171- . value ( amount_to_deposit) ;
172-
173- let pending_transaction = match signer. send_transaction ( tx, None ) . await {
174- Ok ( tx) => tx,
161+ let signer_bytes = wallet. signer ( ) . to_bytes ( ) ;
162+ let secret_key_hex = ethers:: utils:: hex:: encode ( signer_bytes) ;
163+ wallet_private_keys. push ( secret_key_hex) ;
164+ wallets. push ( wallet) ;
165+ }
166+
167+ // Get base nonce for funding wallet to avoid nonce conflicts
168+ let mut current_nonce = match eth_rpc_provider
169+ . get_transaction_count (
170+ funding_wallet. address ( ) ,
171+ Some ( ethers:: types:: BlockNumber :: Pending . into ( ) ) ,
172+ )
173+ . await
174+ {
175+ Ok ( nonce) => nonce,
176+ Err ( err) => {
177+ error ! ( "Could not get base nonce for funding wallet: {}" , err) ;
178+ return ;
179+ }
180+ } ;
181+
182+ let batch_size = 25 ;
183+ let amount_to_deposit =
184+ parse_ether ( & args. amount_to_deposit ) . expect ( "Ether format should be: XX.XX" ) ;
185+ let amount_to_deposit_to_aligned =
186+ parse_ether ( & args. amount_to_deposit_to_aligned ) . expect ( "Ether format should be: XX.XX" ) ;
187+
188+ let mut total_successful = 0 ;
189+ let total_batches = args. number_of_wallets . div_ceil ( batch_size) ;
190+
191+ // Process wallets in batches
192+ for ( batch_idx, wallet_chunk) in wallets. chunks ( batch_size) . enumerate ( ) {
193+ info ! (
194+ "Processing batch {} of {} ({} wallets)..." ,
195+ batch_idx + 1 ,
196+ total_batches,
197+ wallet_chunk. len( )
198+ ) ;
199+
200+ // Refresh nonce for each batch to avoid stale nonce issues
201+ current_nonce = match eth_rpc_provider
202+ . get_transaction_count (
203+ funding_wallet. address ( ) ,
204+ Some ( ethers:: types:: BlockNumber :: Pending . into ( ) ) ,
205+ )
206+ . await
207+ {
208+ Ok ( nonce) => {
209+ info ! ( "Batch {}: Using fresh nonce {}" , batch_idx + 1 , nonce) ;
210+ nonce
211+ }
175212 Err ( err) => {
176- error ! ( "Could not fund wallet {}" , err) ;
177- return ;
213+ error ! ( "Could not get fresh nonce for batch {}: {}" , batch_idx + 1 , err) ;
214+ current_nonce // Use previous nonce as fallback
178215 }
179216 } ;
180- if let Err ( err) = pending_transaction. await {
181- error ! ( "Could not fund wallet {}" , err) ;
217+
218+ // ETH funding phase for this batch
219+ info ! (
220+ "Batch {}: Starting ETH funding transactions..." ,
221+ batch_idx + 1
222+ ) ;
223+ let mut eth_funding_handles = Vec :: new ( ) ;
224+
225+ for ( chunk_idx, wallet) in wallet_chunk. iter ( ) . enumerate ( ) {
226+ let global_idx = batch_idx * batch_size + chunk_idx;
227+ let eth_rpc_provider = eth_rpc_provider. clone ( ) ;
228+ let funding_wallet = funding_wallet. clone ( ) ;
229+ let wallet_address = wallet. address ( ) ;
230+ let nonce = current_nonce + U256 :: from ( chunk_idx) ;
231+
232+ let handle = tokio:: spawn ( async move {
233+
234+ info ! (
235+ "Submitting ETH funding transaction for wallet {} with nonce {}" ,
236+ global_idx, nonce
237+ ) ;
238+ let signer = SignerMiddleware :: new ( eth_rpc_provider, funding_wallet. clone ( ) ) ;
239+
240+ // Get current gas price and bump it by 20% to avoid replacement issues
241+ let base_gas_price = match signer. provider ( ) . get_gas_price ( ) . await {
242+ Ok ( price) => price,
243+ Err ( _) => U256 :: from ( 20_000_000_000u64 ) , // 20 gwei fallback
244+ } ;
245+ let bumped_gas_price = base_gas_price * 120 / 100 ; // 20% bump
246+
247+ let tx = TransactionRequest :: new ( )
248+ . from ( funding_wallet. address ( ) )
249+ . to ( wallet_address)
250+ . value ( amount_to_deposit)
251+ . nonce ( nonce)
252+ . gas_price ( bumped_gas_price) ;
253+
254+ let result = {
255+ match signer. send_transaction ( tx, None ) . await {
256+ Ok ( pending_tx) => {
257+ info ! (
258+ "ETH funding transaction submitted for wallet {}" ,
259+ global_idx
260+ ) ;
261+ pending_tx. await
262+ }
263+ Err ( err) => {
264+ error ! (
265+ "Could not submit ETH funding transaction for wallet {}: {}" ,
266+ global_idx, err
267+ ) ;
268+ return None ;
269+ }
270+ }
271+ } ;
272+
273+ match result {
274+ Ok ( receipt) => {
275+ if let Some ( receipt) = receipt {
276+ info ! (
277+ "ETH funding confirmed for wallet {} (tx: {:?})" ,
278+ global_idx, receipt. transaction_hash
279+ ) ;
280+ } else {
281+ info ! (
282+ "ETH funding confirmed for wallet {} (no receipt)" ,
283+ global_idx
284+ ) ;
285+ }
286+ Some ( global_idx)
287+ }
288+ Err ( err) => {
289+ error ! ( "ETH funding failed for wallet {}: {}" , global_idx, err) ;
290+ None
291+ }
292+ }
293+ } ) ;
294+ eth_funding_handles. push ( handle) ;
295+ }
296+
297+ // Wait for ETH funding to complete
298+ let mut funded_indices = Vec :: new ( ) ;
299+ for handle in eth_funding_handles {
300+ if let Ok ( Some ( idx) ) = handle. await {
301+ funded_indices. push ( idx) ;
302+ }
182303 }
183- info ! ( "Wallet {} funded" , i) ;
184304
185- // Deposit to aligned
186- let amount_to_deposit_to_aligned =
187- parse_ether ( & amount_to_deposit_aligned) . expect ( "Ether format should be: XX.XX" ) ;
188305 info ! (
189- "Depositing {}wei to aligned {}" ,
190- amount_to_deposit_to_aligned, i
306+ "Batch {}: ETH funding completed for {} out of {} wallets" ,
307+ batch_idx + 1 ,
308+ funded_indices. len( ) ,
309+ wallet_chunk. len( )
191310 ) ;
192- let signer = SignerMiddleware :: new ( eth_rpc_provider. clone ( ) , wallet. clone ( ) ) ;
193- if let Err ( err) = deposit_to_aligned (
194- amount_to_deposit_to_aligned,
195- signer,
196- args. network . clone ( ) . into ( ) ,
197- )
198- . await
199- {
200- error ! ( "Could not deposit to aligned, err: {:?}" , err) ;
201- return ;
311+
312+ if funded_indices. is_empty ( ) {
313+ warn ! (
314+ "Batch {}: No wallets were funded, skipping Aligned deposits" ,
315+ batch_idx + 1
316+ ) ;
317+ current_nonce += U256 :: from ( wallet_chunk. len ( ) ) ;
318+ continue ;
202319 }
203- info ! ( "Successfully deposited to aligned for wallet {}" , i) ;
204320
205- // Store private key
206- info ! ( "Storing private key" ) ;
207- let signer_bytes = wallet. signer ( ) . to_bytes ( ) ;
208- let secret_key_hex = ethers:: utils:: hex:: encode ( signer_bytes) ;
321+ // Aligned deposit phase for funded wallets in this batch
322+ info ! (
323+ "Batch {}: Starting Aligned deposit transactions..." ,
324+ batch_idx + 1
325+ ) ;
326+ let mut aligned_deposit_handles = Vec :: new ( ) ;
327+
328+ for & idx in & funded_indices {
329+ let wallet = wallets[ idx] . clone ( ) ;
330+ let eth_rpc_provider = eth_rpc_provider. clone ( ) ;
331+ let network = args. network . clone ( ) ;
332+
333+ let handle = tokio:: spawn ( async move {
209334
210- if let Err ( err) = writeln ! ( file, "{}" , secret_key_hex) {
211- error ! ( "Could not store private key: {}" , err) ;
212- } else {
213- info ! ( "Private key {} stored" , i) ;
335+ info ! ( "Submitting Aligned deposit for wallet {}" , idx) ;
336+ let signer = SignerMiddleware :: new ( eth_rpc_provider, wallet) ;
337+
338+ match deposit_to_aligned ( amount_to_deposit_to_aligned, signer, network. into ( ) ) . await
339+ {
340+ Ok ( _) => {
341+ info ! ( "Successfully deposited to aligned for wallet {}" , idx) ;
342+ Ok ( idx)
343+ }
344+ Err ( err) => {
345+ error ! ( "Could not deposit to aligned for wallet {}: {:?}" , idx, err) ;
346+ Err ( idx)
347+ }
348+ }
349+ } ) ;
350+ aligned_deposit_handles. push ( handle) ;
351+ }
352+
353+ // Wait for Aligned deposits to complete and write private keys immediately
354+ let mut batch_successful = 0 ;
355+ for handle in aligned_deposit_handles {
356+ if let Ok ( Ok ( idx) ) = handle. await {
357+ let wallet_address = wallets[ idx] . address ( ) ;
358+ let private_key = & wallet_private_keys[ idx] ;
359+
360+ // Write to original file (private key only) for compatibility
361+ if let Err ( err) = writeln ! ( file, "{}" , private_key) {
362+ error ! ( "Could not store private key for wallet {}: {}" , idx, err) ;
363+ continue ;
364+ }
365+
366+ // Write to new file (private_key;address format)
367+ let detailed_filepath = format ! ( "{}.detailed" , args. private_keys_filepath) ;
368+ let detailed_file = std:: fs:: OpenOptions :: new ( )
369+ . create ( true )
370+ . append ( true )
371+ . open ( & detailed_filepath) ;
372+
373+ match detailed_file {
374+ Ok ( mut f) => {
375+ if let Err ( err) = writeln ! ( f, "{};{:?}" , private_key, wallet_address) {
376+ error ! ( "Could not store detailed info for wallet {}: {}" , idx, err) ;
377+ } else {
378+ info ! ( "Wallet {} stored: private key and address saved" , idx) ;
379+ batch_successful += 1 ;
380+ }
381+ }
382+ Err ( err) => {
383+ error ! ( "Could not open detailed file {}: {}" , detailed_filepath, err) ;
384+ // Still count as successful since main file was written
385+ info ! ( "Private key for wallet {} stored (detailed file failed)" , idx) ;
386+ batch_successful += 1 ;
387+ }
388+ }
389+ }
214390 }
391+
392+ total_successful += batch_successful;
393+ current_nonce += U256 :: from ( wallet_chunk. len ( ) ) ;
394+
395+ info ! (
396+ "Batch {} completed: {} wallets successfully funded and deposited (Total: {} / {})" ,
397+ batch_idx + 1 ,
398+ batch_successful,
399+ total_successful,
400+ args. number_of_wallets
401+ ) ;
402+
403+ // Optional: Small delay between batches (commented out for speed)
404+ // if batch_idx + 1 < total_batches {
405+ // tokio::time::sleep(Duration::from_millis(50)).await;
406+ // }
215407 }
408+
409+ info ! (
410+ "All batches completed! Successfully created and funded {} wallets out of {} requested" ,
411+ total_successful, args. number_of_wallets
412+ ) ;
413+ info ! (
414+ "Private keys for {} successful wallets stored in:" ,
415+ total_successful
416+ ) ;
417+ info ! ( " - {} (private keys only, for compatibility)" , args. private_keys_filepath) ;
418+ info ! ( " - {}.detailed (private_key;address format)" , args. private_keys_filepath) ;
216419}
217420
218421/// infinitely hangs connections
0 commit comments