Skip to content

Commit 233fb7b

Browse files
authored
Update index.js
proxy added
1 parent 50dc33d commit 233fb7b

File tree

1 file changed

+189
-63
lines changed

1 file changed

+189
-63
lines changed

index.js

Lines changed: 189 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
const { ethers } = require("ethers");
2-
const dotenv = require("dotenv");
3-
const fs = require("fs");
4-
const path = require("path");
5-
const chalk = require("chalk");
1+
import { ethers } from "ethers";
2+
import dotenv from "dotenv";
3+
import * as fs from "fs/promises";
4+
import path from "path";
5+
import chalk from "chalk";
6+
import { HttpsProxyAgent } from "https-proxy-agent";
7+
import { SocksProxyAgent } from "socks-proxy-agent";
8+
import { fileURLToPath } from "url";
69

710
dotenv.config();
811

12+
// Define __dirname equivalent for ES Modules
13+
const __filename = fileURLToPath(import.meta.url);
14+
const __dirname = path.dirname(__filename);
15+
916
// === CONFIG ===
1017
// List of public RPCs for Celo.
1118
const RPCS = [
@@ -18,49 +25,94 @@ const GAS_LIMIT = 21000;
1825
const keysFile = "key.txt";
1926
let lastKey = null;
2027

21-
// --- Load keys from file ---
22-
const PRIVATE_KEYS = fs.readFileSync(keysFile, "utf-8")
23-
.split("\n")
24-
.map(line => line.trim())
25-
.filter(line => line.length > 0);
28+
// --- Key loader (inline, no extra files) ---
29+
let PRIVATE_KEYS = [];
30+
let ALL_WALLETS = [];
2631

27-
// Pre-calculate all wallet addresses to easily check if all are inactive
28-
const ALL_WALLETS = PRIVATE_KEYS.map(key => {
29-
const wallet = new ethers.Wallet(key);
30-
return wallet.address;
31-
});
32+
// normalize to 0x-prefixed hex string
33+
function normalizeKey(k) {
34+
if (!k) return null;
35+
k = String(k).trim();
36+
if (!k) return null;
37+
return k.startsWith("0x") ? k : "0x" + k;
38+
}
39+
40+
// Validate by constructing an ethers.Wallet
41+
function isValidPrivateKey(k) {
42+
try {
43+
new ethers.Wallet(k);
44+
return true;
45+
} catch {
46+
return false;
47+
}
48+
}
49+
50+
// Load keys: primary from env PRIVATE_KEYS (comma separated), fallback to keysFile (one-per-line)
51+
async function loadKeysInline({ allowFileFallback = true } = {}) {
52+
// 1) env var (preferred)
53+
const env = process.env.PRIVATE_KEYS;
54+
if (env && env.trim().length > 0) {
55+
PRIVATE_KEYS = env.split(",").map(normalizeKey).filter(Boolean);
56+
} else if (allowFileFallback) {
57+
// 2) fallback to keysFile (async fs from fs/promises is used in this file)
58+
try {
59+
const raw = await fs.readFile(keysFile, "utf-8");
60+
PRIVATE_KEYS = raw.split(/\r?\n/).map(normalizeKey).filter(Boolean);
61+
} catch (err) {
62+
// leave PRIVATE_KEYS empty if file not found / unreadable
63+
PRIVATE_KEYS = [];
64+
}
65+
}
66+
67+
// validate & compute addresses
68+
const valid = [];
69+
const addrs = [];
70+
for (const k of PRIVATE_KEYS) {
71+
if (!k) continue;
72+
if (!isValidPrivateKey(k)) {
73+
console.warn("Ignored invalid private key:", k);
74+
continue;
75+
}
76+
valid.push(k);
77+
addrs.push(new ethers.Wallet(k).address);
78+
}
79+
PRIVATE_KEYS = valid;
80+
ALL_WALLETS = addrs;
81+
}
3282

3383
// --- Wallet Persona Management ---
3484
const personaFile = "personas.json";
3585
let walletProfiles = {};
3686
let lastPersonaSave = 0;
3787
const PERSONA_SAVE_DEBOUNCE_MS = 5_000; // coalesce multiple writes into one
3888

39-
function loadPersonas() {
40-
if (fs.existsSync(personaFile)) {
41-
try {
42-
walletProfiles = JSON.parse(fs.readFileSync(personaFile, "utf-8"));
43-
console.log(chalk.cyan("🎭 Loaded existing personas"));
44-
} catch (e) {
89+
async function loadPersonas() {
90+
try {
91+
const data = await fs.readFile(personaFile, "utf-8");
92+
walletProfiles = JSON.parse(data);
93+
console.log(chalk.cyan("🎭 Loaded existing personas"));
94+
} catch (e) {
95+
if (e.code === 'ENOENT') {
96+
console.log(chalk.yellow("🎭 Personas file not found, starting fresh."));
97+
} else {
4598
console.error(chalk.bgRed.white.bold("❌ Error parsing personas.json, starting fresh."));
46-
walletProfiles = {};
4799
}
100+
walletProfiles = {};
48101
}
49102
}
50103

51104
// Debounced save to avoid frequent blocking disk writes
52-
function savePersonas() {
105+
async function savePersonas() {
53106
try {
54107
const now = Date.now();
55108
if (now - lastPersonaSave < PERSONA_SAVE_DEBOUNCE_MS) {
56-
// schedule a final write shortly
57109
setTimeout(() => {
58-
try { fs.writeFileSync(personaFile, JSON.stringify(walletProfiles, null, 2)); lastPersonaSave = Date.now(); }
110+
try { fs.writeFile(personaFile, JSON.stringify(walletProfiles, null, 2)); lastPersonaSave = Date.now(); }
59111
catch (e) { console.error("failed saving personas:", e.message); }
60112
}, PERSONA_SAVE_DEBOUNCE_MS);
61113
return;
62114
}
63-
fs.writeFileSync(personaFile, JSON.stringify(walletProfiles, null, 2));
115+
await fs.writeFile(personaFile, JSON.stringify(walletProfiles, null, 2));
64116
lastPersonaSave = now;
65117
} catch (e) {
66118
console.error("failed saving personas:", e.message);
@@ -74,13 +126,12 @@ function ensurePersona(wallet) {
74126
pingBias: Math.random() * 0.25,
75127
minAmount: 0.00005 + Math.random() * 0.0001,
76128
maxAmount: 0.015 + Math.random() * 0.005,
77-
// NEW TRAITS
78-
activeHours: [6 + Math.floor(Math.random() * 6), 22], // e.g. 06:00-22:00 UTC
79-
cooldownAfterFail: 60 + Math.floor(Math.random() * 180), // 1-4 min
80-
avgWait: 60 + Math.floor(Math.random() * 41), // base wait time 1-1.6 min
129+
activeHours: [2 + Math.floor(Math.random() * 4), 22 + Math.floor(Math.random() * 3)], // 2-5 to 22-24 UTC
130+
cooldownAfterFail: 60 + Math.floor(Math.random() * 120), // 1-3 min
131+
avgWait: 30 + Math.floor(Math.random() * 40), // base wait time 30-70 sec
81132
retryBias: Math.random() * 0.5, // 0-50% chance to retry
82133
// dynamic per-wallet nonce retirement
83-
maxNonce: 520 + Math.floor(Math.random() * 100),
134+
maxNonce: 520 + Math.floor(Math.random() * 80),
84135
// failure tracking
85136
failCount: 0,
86137
lastFailAt: null
@@ -94,21 +145,24 @@ function ensurePersona(wallet) {
94145
const inactiveFile = "inactive.json";
95146
let inactiveWallets = new Set();
96147

97-
function loadInactive() {
98-
if (fs.existsSync(inactiveFile)) {
99-
try {
100-
inactiveWallets = new Set(JSON.parse(fs.readFileSync(inactiveFile, "utf-8")));
101-
console.log(chalk.gray(`📂 Loaded ${inactiveWallets.size} inactive wallets`));
102-
} catch (e) {
148+
async function loadInactive() {
149+
try {
150+
const data = await fs.readFile(inactiveFile, "utf-8");
151+
inactiveWallets = new Set(JSON.parse(data));
152+
console.log(chalk.gray(`📂 Loaded ${inactiveWallets.size} inactive wallets`));
153+
} catch (e) {
154+
if (e.code === 'ENOENT') {
155+
console.log(chalk.yellow("📂 Inactive file not found, starting empty."));
156+
} else {
103157
console.error("❌ Failed parsing inactive.json, starting empty");
104-
inactiveWallets = new Set();
105158
}
159+
inactiveWallets = new Set();
106160
}
107161
}
108162

109-
function saveInactive() {
163+
async function saveInactive() {
110164
try {
111-
fs.writeFileSync(inactiveFile, JSON.stringify([...inactiveWallets], null, 2));
165+
await fs.writeFile(inactiveFile, JSON.stringify([...inactiveWallets], null, 2));
112166
} catch (e) {
113167
console.error("❌ Failed saving inactive.json:", e.message);
114168
}
@@ -121,11 +175,13 @@ function getLogFile() {
121175
return path.join(__dirname, `tx_log_${today}.csv`);
122176
}
123177

124-
// This function initializes the log file with a header if it doesn't exist.
125-
function initLogFile() {
178+
async function initLogFile() {
126179
const logFile = getLogFile();
127-
if (!fs.existsSync(logFile)) {
128-
fs.writeFileSync(
180+
try {
181+
await fs.access(logFile);
182+
} catch (e) {
183+
// File doesn't exist, create it with a header
184+
await fs.writeFile(
129185
logFile,
130186
"timestamp,wallet,tx_hash,nonce,gas_used,gas_price_gwei,fee_celo,status,action\n"
131187
);
@@ -139,10 +195,10 @@ const FLUSH_INTERVAL = 300 * 1000;
139195
function bufferTxLog(entry) {
140196
txBuffer.push(entry);
141197
}
142-
function flushTxLog() {
198+
async function flushTxLog() {
143199
if (txBuffer.length === 0) return;
144-
const logFile = initLogFile();
145-
fs.appendFileSync(logFile, txBuffer.join("\n") + "\n");
200+
const logFile = await initLogFile();
201+
await fs.appendFile(logFile, txBuffer.join("\n") + "\n");
146202
console.log(chalk.gray(`📝 Flushed ${txBuffer.length} tx logs to disk`));
147203
txBuffer = [];
148204
}
@@ -158,13 +214,46 @@ function pickRandomKey() {
158214
return lastKey;
159215
}
160216

161-
// --- Provider without proxy ---
162-
function getProvider(rpcUrl) {
217+
// --- Proxy Variables ---
218+
let proxies = [];
219+
220+
// --- Proxy Functions ---
221+
async function loadProxies() {
222+
try {
223+
const fileContent = await fs.readFile("proxy.txt", "utf8");
224+
proxies = fileContent.split("\n").map(proxy => proxy.trim()).filter(proxy => proxy);
225+
if (proxies.length === 0) {
226+
console.log(chalk.cyan(`[${new Date().toISOString()}] ⟐ No proxy found in proxy.txt. Running without proxy.`));
227+
} else {
228+
console.log(chalk.green(`[${new Date().toISOString()}] ✔ Loaded ${proxies.length} proxies from proxy.txt`));
229+
}
230+
} catch (error) {
231+
if (error.code === 'ENOENT') {
232+
console.log(chalk.cyan(`[${new Date().toISOString()}] ⟐ No proxy.txt found, running without proxy.`));
233+
} else {
234+
console.log(chalk.red(`[${new Date().toISOString()}] ✖ Failed to load proxy: ${error.message}`));
235+
}
236+
proxies = [];
237+
}
238+
}
239+
240+
function createAgent(proxyUrl) {
241+
if (!proxyUrl) return null;
242+
if (proxyUrl.startsWith("socks")) {
243+
return new SocksProxyAgent(proxyUrl);
244+
} else {
245+
return new HttpsProxyAgent(proxyUrl);
246+
}
247+
}
248+
249+
// --- Provider with proxy support ---
250+
function getProvider(rpcUrl, agent) {
163251
const network = {
164252
chainId: 42220,
165253
name: "celo"
166254
};
167-
return new ethers.JsonRpcProvider(rpcUrl, network);
255+
const providerOptions = agent ? { fetchOptions: { agent } } : {};
256+
return new ethers.JsonRpcProvider(rpcUrl, network, providerOptions);
168257
}
169258

170259
/**
@@ -175,7 +264,10 @@ async function tryProviders() {
175264
console.log(chalk.hex("#00FFFF").bold("🔍 Searching for a working RPC endpoint..."));
176265
for (const url of RPCS) {
177266
try {
178-
const provider = getProvider(url);
267+
const proxyUrl = proxies.length > 0 ? proxies[Math.floor(Math.random() * proxies.length)] : null;
268+
const agent = createAgent(proxyUrl);
269+
270+
const provider = getProvider(url, agent);
179271
const network = await Promise.race([
180272
provider.getNetwork(),
181273
new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 5000))
@@ -357,7 +449,7 @@ async function safeSendTx(wallet, provider, profile, url) {
357449
}
358450

359451
// === NEW: Refresh inactive wallets every 30 minutes ===
360-
setInterval(() => {
452+
setInterval(async () => {
361453
console.log(chalk.cyan("🔄 Refreshing inactive wallets..."));
362454
for (const addr of [...inactiveWallets]) {
363455
const profile = walletProfiles[addr];
@@ -366,17 +458,32 @@ setInterval(() => {
366458
console.log(chalk.green(`🌅 Wallet ${addr} is now inside active hours`));
367459
}
368460
}
369-
saveInactive();
461+
await saveInactive();
370462
}, 30 * 60 * 1000);
371463

372-
async function loop() {
373-
loadPersonas();
374-
loadInactive();
464+
async function main() {
465+
// Load keys (env primary, file fallback)
466+
await loadKeysInline({ allowFileFallback: true });
467+
468+
if (PRIVATE_KEYS.length === 0) {
469+
console.error(chalk.bgRed.white.bold(`❌ No valid private keys found (env or ${keysFile}). Make sure PRIVATE_KEYS env var or ${keysFile} exists and contains keys.`));
470+
process.exit(1);
471+
}
472+
473+
474+
await loadPersonas();
475+
await loadInactive();
476+
await loadProxies();
477+
478+
// === NEW LOGIC: Initial log file creation before the loop starts ===
479+
await initLogFile();
480+
375481
while (true) {
376482
// === NEW LOGIC: Check if all wallets are inactive and sleep if so ===
377483
if (inactiveWallets.size >= ALL_WALLETS.length) {
378484
console.log(chalk.yellow("😴 All wallets are currently inactive. Sleeping for 5 minutes..."));
379-
await randomDelay(300, 300); // 5 minutes
485+
await randomDelay(240, 360); // 5 minutes
486+
continue;
380487
}
381488

382489
// === NEW LOGIC: Retry loop for RPC connection ===
@@ -400,7 +507,7 @@ async function loop() {
400507

401508
// === NEW: Main loop modification ===
402509
if (!checkActive(wallet, profile)) {
403-
await randomDelay(10, 15); // just a short pause
510+
await randomDelay(10, 15);
404511
continue;
405512
}
406513

@@ -415,10 +522,29 @@ async function loop() {
415522
}
416523
}
417524

418-
loop();
525+
main();
419526

420527
// ensure flush/persona save on termination/unhandled
421-
process.on("SIGINT", () => { console.log("SIGINT"); flushTxLog(); savePersonas(); process.exit(); });
422-
process.on("SIGTERM", () => { console.log("SIGTERM"); flushTxLog(); savePersonas(); process.exit(); });
423-
process.on("exit", () => { flushTxLog(); savePersonas(); });
424-
process.on("unhandledRejection", (r) => { console.error("unhandledRejection:", r); flushTxLog(); savePersonas(); });
528+
process.on("SIGINT", async () => {
529+
console.log("SIGINT received, shutting down gracefully...");
530+
await flushTxLog();
531+
await savePersonas();
532+
process.exit();
533+
});
534+
process.on("SIGTERM", async () => {
535+
console.log("SIGTERM received, shutting down gracefully...");
536+
await flushTxLog();
537+
await savePersonas();
538+
process.exit();
539+
});
540+
process.on("exit", async () => {
541+
console.log("Exiting, flushing final logs...");
542+
await flushTxLog();
543+
await savePersonas();
544+
});
545+
process.on("unhandledRejection", async (r) => {
546+
console.error("unhandledRejection:", r);
547+
await flushTxLog();
548+
await savePersonas();
549+
process.exit(1);
550+
});

0 commit comments

Comments
 (0)