Skip to content

Commit 37f14f4

Browse files
authored
Update index.js
1 parent 259313c commit 37f14f4

File tree

1 file changed

+204
-10
lines changed

1 file changed

+204
-10
lines changed

index.js

Lines changed: 204 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import crypto from "crypto";
1010

1111
dotenv.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
1420
const __filename = fileURLToPath(import.meta.url);
1521
const __dirname = path.dirname(__filename);
@@ -158,12 +164,21 @@ function decryptWithAnyKey(encrypted) {
158164
// === CONFIG ===
159165
// List of public RPCs for Celo.
160166
const 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
167182
const GAS_LIMIT = 21000;
168183
const keysFile = "key.txt";
169184
let 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
404419
setInterval(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
408438
function 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 => /^tx_log_\d{4}-\d{2}-\d{2}\.csv$/.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

Comments
 (0)