@@ -10,6 +10,12 @@ import crypto from "crypto";
1010
1111dotenv . config ( ) ;
1212
13+ // Validate required environment variables
14+ if ( ! process . env . PRIVATE_KEYS ) {
15+ console . error ( chalk . bgRed . white . bold ( "❌ PRIVATE_KEYS environment variable is not set!" ) ) ;
16+ process . exit ( 1 ) ;
17+ }
18+
1319// Define __dirname equivalent for ES Modules
1420const __filename = fileURLToPath ( import . meta. url ) ;
1521const __dirname = path . dirname ( __filename ) ;
@@ -158,12 +164,21 @@ function decryptWithAnyKey(encrypted) {
158164// === CONFIG ===
159165// List of public RPCs for Celo.
160166const RPCS = [
161- "https://celo-mainnet.infura.io/v3/f0c6b3797dd54dc2aa91cd4a463bcc57" ,
162167 "https://rpc.ankr.com/celo" ,
163168 "https://celo.drpc.org" ,
164169 "https://forno.celo.org" ,
165170 "https://1rpc.io/celo"
166171] ;
172+
173+ // Gas tracking RPCs (for gas price monitoring)
174+ const GAS_RPC_URLS = [
175+ "https://forno.celo.org"
176+ ] ;
177+
178+ // Gas tolerance threshold (in Gwei) - transactions will only be sent if gas is below this value
179+ // Set this in your .env file as GAS_TOLERANCE_THRESHOLD=30 for 30 Gwei threshold
180+ const GAS_TOLERANCE_THRESHOLD = process . env . GAS_TOLERANCE_THRESHOLD ?
181+ parseFloat ( process . env . GAS_TOLERANCE_THRESHOLD ) : 51 ; // Default: 50 Gwei
167182const GAS_LIMIT = 21000 ;
168183const keysFile = "key.txt" ;
169184let lastKey = null ;
@@ -278,8 +293,8 @@ async function savePersonas() {
278293 try {
279294 const now = Date . now ( ) ;
280295 if ( now - lastPersonaSave < PERSONA_SAVE_DEBOUNCE_MS ) {
281- setTimeout ( ( ) => {
282- try { fs . writeFile ( personaFile , JSON . stringify ( walletProfiles , null , 2 ) ) ; lastPersonaSave = Date . now ( ) ; }
296+ setTimeout ( async ( ) => {
297+ try { await fs . writeFile ( personaFile , JSON . stringify ( walletProfiles , null , 2 ) ) ; lastPersonaSave = Date . now ( ) ; }
283298 catch ( e ) { console . error ( "failed saving personas:" , e . message ) ; }
284299 } , PERSONA_SAVE_DEBOUNCE_MS ) ;
285300 return ;
@@ -403,6 +418,21 @@ async function flushTxLog() {
403418// Periodic flusher
404419setInterval ( flushTxLog , FLUSH_INTERVAL ) ;
405420
421+ // Periodic gas price monitoring (every 5 minutes)
422+ setInterval ( async ( ) => {
423+ try {
424+ const gasData = await getAverageGasPrice ( false ) ; // Less verbose periodic monitoring
425+ if ( gasData ) {
426+ const { avgGasPriceGwei } = gasData ;
427+ if ( ! isGasPriceAcceptable ( avgGasPriceGwei ) ) {
428+ console . log ( chalk . yellow ( `⛽ High gas prices detected: ${ avgGasPriceGwei . toFixed ( 2 ) } Gwei (threshold: ${ GAS_TOLERANCE_THRESHOLD } Gwei)` ) ) ;
429+ }
430+ }
431+ } catch ( err ) {
432+ // Silently ignore gas monitoring errors
433+ }
434+ } , 5 * 60 * 1000 ) ; // 5 minutes
435+
406436// --- Pick random key (with small chance of reusing last key) ---
407437// Now decrypts the key before use
408438function pickRandomKey ( ) {
@@ -490,6 +520,133 @@ function getProvider(rpcUrl, agent, userAgent) {
490520 return new ethers . JsonRpcProvider ( req , network ) ;
491521}
492522
523+ /**
524+ * Fetches current gas price from a specific RPC endpoint
525+ * @param {string } rpcUrl - The RPC endpoint URL
526+ * @returns {Promise<bigint|null> } The gas price in wei, or null if failed
527+ */
528+ async function getGasPriceFromRpc ( rpcUrl ) {
529+ try {
530+ const provider = getProvider ( rpcUrl , null , null ) ;
531+ const feeData = await provider . getFeeData ( ) ;
532+
533+ // Prioritize EIP-1559 maxFeePerGas, fallback to legacy gasPrice
534+ return feeData . maxFeePerGas || feeData . gasPrice || null ;
535+ } catch ( err ) {
536+ // Silently ignore gas price fetching errors to reduce verbosity
537+ return null ;
538+ }
539+ }
540+
541+ /**
542+ * Gets average gas price from multiple RPCs
543+ * @returns {Promise<{avgGasPriceGwei: number, gasPricesGwei: number[]}|null> } Average gas price and individual prices
544+ */
545+ async function getAverageGasPrice ( verbose = true ) {
546+ if ( verbose ) {
547+ console . log ( chalk . cyan ( "🔍 Checking gas prices from multiple RPCs..." ) ) ;
548+ }
549+
550+ // Fetch gas prices from all RPCs concurrently
551+ const gasPricePromises = GAS_RPC_URLS . map ( url => getGasPriceFromRpc ( url ) ) ;
552+ const gasPrices = await Promise . all ( gasPricePromises ) ;
553+
554+ // Filter out null values
555+ const validGasPrices = gasPrices . filter ( price => price !== null ) ;
556+
557+ if ( validGasPrices . length === 0 ) {
558+ console . error ( chalk . red ( "❌ Failed to get gas prices from any RPC" ) ) ;
559+ return null ;
560+ }
561+
562+ // Convert to Gwei for easier comparison
563+ const gasPricesGwei = validGasPrices . map ( price =>
564+ parseFloat ( ethers . formatUnits ( price , "gwei" ) )
565+ ) ;
566+
567+ // Calculate average
568+ const sum = gasPricesGwei . reduce ( ( acc , price ) => acc + price , 0 ) ;
569+ const avgGasPriceGwei = sum / gasPricesGwei . length ;
570+
571+ if ( verbose ) {
572+ console . log ( chalk . cyan ( `📊 Gas prices: ${ gasPricesGwei . map ( p => p . toFixed ( 2 ) ) . join ( ", " ) } Gwei (avg: ${ avgGasPriceGwei . toFixed ( 2 ) } Gwei)` ) ) ;
573+ }
574+
575+ return {
576+ avgGasPriceGwei,
577+ gasPricesGwei
578+ } ;
579+ }
580+
581+ /**
582+ * Checks if current gas price is below tolerance threshold
583+ * @param {number } avgGasPriceGwei - Average gas price in Gwei
584+ * @returns {boolean } True if gas price is acceptable
585+ */
586+ function isGasPriceAcceptable ( avgGasPriceGwei ) {
587+ const isAcceptable = avgGasPriceGwei <= GAS_TOLERANCE_THRESHOLD ;
588+ if ( ! isAcceptable ) {
589+ console . log ( chalk . yellow ( `⛽ Gas price ${ avgGasPriceGwei . toFixed ( 2 ) } Gwei exceeds threshold of ${ GAS_TOLERANCE_THRESHOLD } Gwei` ) ) ;
590+ }
591+ return isAcceptable ;
592+ }
593+
594+ /**
595+ * Simulates micro-behaviors of real user devices during transaction processing
596+ * @returns {Promise<void> }
597+ */
598+ async function simulateMicroBehaviors ( ) {
599+ // 80-180ms signing delay
600+ const signingDelay = 80 + Math . floor ( Math . random ( ) * 101 ) ;
601+ await new Promise ( resolve => setTimeout ( resolve , signingDelay ) ) ;
602+
603+ // 20-90ms network preparation delay
604+ const networkDelay = 20 + Math . floor ( Math . random ( ) * 71 ) ;
605+ await new Promise ( resolve => setTimeout ( resolve , networkDelay ) ) ;
606+
607+ // 40-200ms hesitation before broadcasting
608+ const broadcastDelay = 40 + Math . floor ( Math . random ( ) * 161 ) ;
609+ await new Promise ( resolve => setTimeout ( resolve , broadcastDelay ) ) ;
610+ }
611+
612+ /**
613+ * Implements random hesitation when gas prices spike (simulating human behavior)
614+ * @param {number } avgGasPriceGwei - Average gas price in Gwei
615+ * @param {number[] } gasPricesGwei - Individual gas prices from different RPCs
616+ * @returns {Promise<boolean> } True if should proceed with transaction
617+ */
618+ async function shouldProceedWithGasSpike ( avgGasPriceGwei , gasPricesGwei ) {
619+ // Calculate standard deviation to detect price volatility
620+ const variance = gasPricesGwei . reduce ( ( acc , price ) => acc + Math . pow ( price - avgGasPriceGwei , 2 ) , 0 ) / gasPricesGwei . length ;
621+ const stdDev = Math . sqrt ( variance ) ;
622+
623+ // If there's high volatility or gas price is above 80% of threshold, consider hesitating
624+ const volatilityThreshold = GAS_TOLERANCE_THRESHOLD * 0.8 ;
625+ const isVolatile = stdDev > 5 || avgGasPriceGwei > volatilityThreshold ;
626+
627+ if ( isVolatile ) {
628+ console . log ( chalk . yellow ( "⚠️ Gas price volatility detected, simulating human hesitation..." ) ) ;
629+
630+ // Random hesitation time between 3-10 seconds
631+ const hesitationTime = 3 + Math . floor ( Math . random ( ) * 7 ) ;
632+ console . log ( chalk . yellow ( `⏳ Hesitating for ${ hesitationTime } seconds...` ) ) ;
633+
634+ await new Promise ( resolve => setTimeout ( resolve , hesitationTime * 1000 ) ) ;
635+
636+ // After hesitation, check gas prices again (less verbose)
637+ const updatedGasData = await getAverageGasPrice ( false ) ;
638+ if ( updatedGasData && isGasPriceAcceptable ( updatedGasData . avgGasPriceGwei ) ) {
639+ console . log ( chalk . green ( "✅ Gas prices improved after hesitation" ) ) ;
640+ return true ;
641+ } else {
642+ console . log ( chalk . red ( "❌ Gas prices still high after hesitation" ) ) ;
643+ return false ;
644+ }
645+ }
646+
647+ return true ;
648+ }
649+
493650/**
494651 * Attempts to connect to an RPC endpoint.
495652 * @returns {Promise<{provider: ethers.JsonRpcProvider, url: string}|null> } The working provider and its URL, or null if all fail.
@@ -507,10 +664,10 @@ async function tryProviders(profile) {
507664 provider . getNetwork ( ) ,
508665 new Promise ( ( _ , reject ) => setTimeout ( ( ) => reject ( new Error ( "Timeout" ) ) , 5000 ) )
509666 ] ) ;
510- console . log ( chalk . hex ( "#00FF7F" ) . bold ( `✅ Connected: ${ url } , Chain ID: ${ network . chainId } ` ) ) ;
667+ // Only log successful connections to reduce verbosity
511668 return { provider, url } ;
512669 } catch ( e ) {
513- console . warn ( chalk . hex ( "#FF5555" ) . bold ( `❌ Failed to connect to ${ url } : ${ e . message } ` ) ) ;
670+ // Silently ignore failed connections to reduce verbosity
514671 }
515672 }
516673 return null ;
@@ -594,6 +751,39 @@ async function sendTx(wallet, provider, profile, url) {
594751 return ;
595752 }
596753
754+ // === NEW: Dynamic Gas Pricing Check ===
755+ // Check gas prices before transaction (less verbose)
756+ const gasData = await getAverageGasPrice ( true ) ; // Verbose for initial check
757+
758+ if ( ! gasData ) {
759+ console . error ( chalk . red ( "❌ Failed to get gas price data, skipping transaction" ) ) ;
760+ return ;
761+ }
762+
763+ const { avgGasPriceGwei, gasPricesGwei } = gasData ;
764+
765+ // Check if gas price is within tolerance
766+ if ( ! isGasPriceAcceptable ( avgGasPriceGwei ) ) {
767+ console . log ( chalk . yellow ( `⛽ Gas price ${ avgGasPriceGwei . toFixed ( 2 ) } Gwei exceeds threshold, skipping transaction` ) ) ;
768+ return ;
769+ }
770+
771+ // Check for gas price spikes and implement human-like hesitation
772+ const shouldProceed = await shouldProceedWithGasSpike ( avgGasPriceGwei , gasPricesGwei ) ;
773+ if ( ! shouldProceed ) {
774+ console . log ( chalk . yellow ( "⏳ Skipping transaction due to gas price conditions" ) ) ;
775+ return ;
776+ }
777+
778+ // Get updated gas data after any hesitation (less verbose)
779+ const finalGasData = await getAverageGasPrice ( false ) ;
780+ if ( ! finalGasData || ! isGasPriceAcceptable ( finalGasData . avgGasPriceGwei ) ) {
781+ console . log ( chalk . yellow ( `⛽ Gas price ${ finalGasData ?. avgGasPriceGwei ?. toFixed ( 2 ) || 'N/A' } Gwei above threshold after hesitation, skipping transaction` ) ) ;
782+ return ;
783+ }
784+
785+ console . log ( chalk . green ( `✅ Gas price ${ finalGasData . avgGasPriceGwei . toFixed ( 2 ) } Gwei is acceptable, proceeding with transaction` ) ) ;
786+
597787 // === Decide action: normal send vs ping ===
598788 let action = "normal" ;
599789 let value ;
@@ -623,6 +813,10 @@ async function sendTx(wallet, provider, profile, url) {
623813 }
624814 }
625815
816+ // Simulate micro-behaviors before sending transaction
817+ console . log ( chalk . gray ( "📱 Simulating device micro-behaviors before transaction..." ) ) ;
818+ await simulateMicroBehaviors ( ) ;
819+
626820 const tx = await wallet . sendTransaction ( {
627821 to : toAddress ,
628822 value : value ,
@@ -657,10 +851,10 @@ async function sendTx(wallet, provider, profile, url) {
657851 feeCELO = ethers . formatEther ( gasPriceUsed * ( receipt ?. gasUsed ?? 0n ) ) ;
658852
659853 console . log ( chalk . bgGreen . white . bold ( "🟢 Confirmed!" ) ) ;
660- console . log ( ` Nonce: ${ txNonce } ` ) ;
661- console . log ( chalk . hex ( "#ADFF2F" ) . bold ( ` Gas Used: ${ gasUsed } ` ) ) ;
662- console . log ( chalk . hex ( "#FFB6C1" ) . bold ( ` Gas Price: ${ gasPriceGwei } gwei` ) ) ;
663- console . log ( chalk . hex ( "#FFD700" ) . bold ( ` Fee Paid: ${ feeCELO } CELO` ) ) ;
854+ console . log ( `Nonce: ${ txNonce } ` ) ;
855+ console . log ( chalk . hex ( "#ADFF2F" ) . bold ( `Gas Used: ${ gasUsed } ` ) ) ;
856+ console . log ( chalk . hex ( "#FFB6C1" ) . bold ( `Gas Price: ${ gasPriceGwei } gwei` ) ) ;
857+ console . log ( chalk . hex ( "#FFD700" ) . bold ( `Fee Paid: ${ feeCELO } CELO` ) ) ;
664858 } else {
665859 console . warn ( chalk . bgYellow . white . bold ( "🟡 No confirmation in 30s, moving on..." ) ) ;
666860 status = "timeout" ;
@@ -770,7 +964,7 @@ async function main() {
770964 const logFiles = files . filter ( file => / ^ t x _ l o g _ \d { 4 } - \d { 2 } - \d { 2 } \. c s v $ / . test ( file ) ) ;
771965 for ( const file of logFiles ) {
772966 await fs . unlink ( path . join ( __dirname , file ) ) ;
773- console . log ( chalk . gray ( ` - Deleted ${ file } ` ) ) ;
967+ console . log ( chalk . gray ( `- Deleted ${ file } ` ) ) ;
774968 }
775969 console . log ( chalk . green ( '✅ Old logs cleared successfully.' ) ) ;
776970 } catch ( err ) {
0 commit comments