├── proxies.txt ├── package.json ├── README.md └── index.js /proxies.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openfi-auto-bot", 3 | "version": "1.0.0", 4 | "description": "Automation bot for interacting with OpenFi testnet protocol", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint .", 10 | "format": "prettier --write ." 11 | }, 12 | "keywords": [ 13 | "openfi", 14 | "defi", 15 | "automation", 16 | "ethereum", 17 | "web3", 18 | "pharos" 19 | ], 20 | "author": "vikitoshi", 21 | "license": "MIT", 22 | "dependencies": { 23 | "dotenv": "^16.4.1", 24 | "ethers": "^6.11.1", 25 | "https-proxy-agent": "^7.0.4", 26 | "http-proxy-agent": "^7.0.2", 27 | "socks-proxy-agent": "^8.0.2" 28 | }, 29 | "devDependencies": { 30 | "eslint": "^8.56.0", 31 | "prettier": "^3.2.5" 32 | }, 33 | "engines": { 34 | "node": ">=18.0.0" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/vikitoshi/OpenFi-Auto-Bot.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/vikitoshi/OpenFi-Auto-Bot/issues" 42 | }, 43 | "homepage": "https://github.com/vikitoshi/OpenFi-Auto-Bot#readme" 44 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenFi Auto Bot 2 | 3 | A sophisticated automation bot for interacting with the OpenFi testnet protocol, designed to perform various DeFi operations including supplying assets, borrowing tokens, and claiming faucet tokens. 4 | 5 | ## Features 6 | 7 | - � **Multi-Wallet Support**: Process multiple wallets simultaneously 8 | - 🛡 **Proxy Integration**: Supports HTTP/HTTPS/SOCKS proxies for anonymity 9 | - 💰 **PHRS Operations**: Deposit and manage PHRS tokens 10 | - 🚰 **Faucet Minting**: Mint testnet tokens from the faucet 11 | - 🔄 **Token Management**: Supply, borrow, and withdraw various ERC20 tokens 12 | - ⚙️ **Configurable**: Customize transaction counts and amounts 13 | 14 | ## Prerequisites 15 | 16 | - Node.js (v18 or higher) 17 | - npm or yarn 18 | - Private keys for wallets you want to use 19 | - (Optional) Proxy list for anonymity 20 | 21 | ## Installation 22 | 23 | 1. Clone the repository: 24 | ```bash 25 | git clone https://github.com/vikitoshi/OpenFi-Auto-Bot.git 26 | cd OpenFi-Auto-Bot 27 | ``` 28 | 29 | 2. Install dependencies: 30 | ```bash 31 | npm install 32 | ``` 33 | 34 | 3. Create a `.env` file in the project root and add your private keys: 35 | ```env 36 | PRIVATE_KEY_1=your_private_key_here 37 | PRIVATE_KEY_2=your_second_private_key_here 38 | # Add as many keys as needed 39 | ``` 40 | 41 | 4. (Optional) Add proxies to `proxies.txt` (one per line): 42 | ```text 43 | http://user:pass@proxyip:port 44 | socks5://user:pass@proxyip:port 45 | ``` 46 | 47 | ## Usage 48 | 49 | Run the bot: 50 | ```bash 51 | node index.js 52 | ``` 53 | 54 | The bot will present a menu with the following options: 55 | 56 | 1. **Supply PHRS** - Deposit PHRS tokens to the lending pool 57 | 2. **Mint Faucet Tokens** - Claim testnet tokens from the faucet 58 | 3. **Supply ERC20 Tokens** - Deposit supported ERC20 tokens 59 | 4. **Borrow Tokens** - Borrow assets from the lending pool 60 | 5. **Withdraw Tokens** - Withdraw supplied tokens 61 | 6. **Exit** - Quit the bot 62 | 63 | Follow the on-screen prompts to configure each operation. 64 | 65 | ## Supported Tokens 66 | 67 | The bot supports the following testnet tokens: 68 | 69 | 1. NVIDIA 70 | 2. USDT 71 | 3. USDC 72 | 4. GOLD 73 | 5. TSLA 74 | 6. BTC 75 | 76 | ## Configuration 77 | 78 | You can modify the following constants in the code: 79 | 80 | - `NETWORK_CONFIG`: RPC URL, chain ID, and explorer 81 | - `CONTRACTS`: Contract addresses for the protocol 82 | - Transaction delays and gas limits 83 | 84 | ## Security Notes 85 | 86 | ⚠️ **Important Security Considerations**: 87 | - Never share your private keys 88 | - This is for testnet use only 89 | - The bot comes with no warranties 90 | - Use at your own risk 91 | 92 | ## Contributing 93 | 94 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 95 | 96 | ## License 97 | 98 | [MIT](https://choosealicense.com/licenses/mit/) 99 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const readline = require('readline'); 5 | const { HttpsProxyAgent } = require('https-proxy-agent'); 6 | const { HttpProxyAgent } = require('http-proxy-agent'); 7 | const { SocksProxyAgent } = require('socks-proxy-agent'); 8 | require('dotenv').config(); 9 | 10 | const colors = { 11 | reset: "\x1b[0m", 12 | cyan: "\x1b[36m", 13 | green: "\x1b[32m", 14 | yellow: "\x1b[33m", 15 | red: "\x1b[31m", 16 | white: "\x1b[37m", 17 | bold: "\x1b[1m" 18 | }; 19 | 20 | const logger = { 21 | info: (msg) => console.log(`${colors.green}[✓] ${msg}${colors.reset}`), 22 | warn: (msg) => console.log(`${colors.yellow}[⚠] ${msg}${colors.reset}`), 23 | error: (msg) => console.log(`${colors.red}[✗] ${msg}${colors.reset}`), 24 | success: (msg) => console.log(`${colors.green}[✅] ${msg}${colors.reset}`), 25 | loading: (msg) => console.log(`${colors.cyan}[⟳] ${msg}${colors.reset}`), 26 | step: (msg) => console.log(`${colors.white}[➤] ${msg}${colors.reset}`), 27 | userInfo: (msg) => console.log(`${colors.white}[✓] ${msg}${colors.reset}`), 28 | banner: () => { 29 | console.log(`${colors.cyan}${colors.bold}`); 30 | console.log(`---------------------------------------------`); 31 | console.log(` OpenFi Auto Bot - Airdrop Insiders `); 32 | console.log(`---------------------------------------------${colors.reset}`); 33 | console.log(); 34 | } 35 | }; 36 | 37 | const NETWORK_CONFIG = { 38 | rpc: 'https://testnet.dplabs-internal.com', 39 | chainId: 688688, 40 | symbol: 'PHRS', 41 | explorer: 'https://pharos-testnet.socialscan.io/' 42 | }; 43 | 44 | const CONTRACTS = { 45 | LENDING_POOL: '0xa8e550710bf113db6a1b38472118b8d6d5176d12', 46 | FAUCET: '0x2e9d89d372837f71cb529e5ba85bfbc1785c69cd', 47 | SUPPLY_CONTRACT: '0xad3b4e20412a097f87cd8e8d84fbbe17ac7c89e9', 48 | TOKENS: { 49 | NVIDIA: '0x3299cc551b2a39926bf14144e65630e533df6944', 50 | USDT: '0x0b00fb1f513e02399667fba50772b21f34c1b5d9', 51 | USDC: '0x48249feeb47a8453023f702f15cf00206eebdf08', 52 | GOLD: '0x77f532df5f46ddff1c97cdae3115271a523fa0f4', 53 | TSLA: '0xcda3df4aab8a571688fe493eb1bdc1ad210c09e4', 54 | BTC: '0xa4a967fc7cf0e9815bf5c2700a055813628b65be' 55 | } 56 | }; 57 | 58 | const ERC20_ABI = [ 59 | "function approve(address spender, uint256 amount) external returns (bool)", 60 | "function balanceOf(address account) external view returns (uint256)", 61 | "function decimals() external view returns (uint8)" 62 | ]; 63 | 64 | const FAUCET_ABI = [ 65 | "function mint(address _asset, address _account, uint256 _amount) external" 66 | ]; 67 | 68 | const LENDING_POOL_ABI = [ 69 | "function depositETH(address lendingPool, address onBehalfOf, uint16 referralCode) external payable", 70 | "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external", 71 | "function withdraw(address asset, uint256 amount, address to) external" 72 | ]; 73 | 74 | class PharosBot { 75 | constructor() { 76 | this.providers = []; 77 | this.wallets = []; 78 | this.proxies = []; 79 | this.rl = readline.createInterface({ 80 | input: process.stdin, 81 | output: process.stdout 82 | }); 83 | } 84 | 85 | async initialize() { 86 | logger.banner(); 87 | await this.loadProxies(); 88 | await this.loadWallets(); 89 | logger.success(`Initialized with ${this.wallets.length} wallets and ${this.proxies.length} proxies`); 90 | } 91 | 92 | async loadProxies() { 93 | try { 94 | if (fs.existsSync('proxies.txt')) { 95 | const proxyData = fs.readFileSync('proxies.txt', 'utf8'); 96 | this.proxies = proxyData.split('\n').filter(line => line.trim()); 97 | logger.info(`Loaded ${this.proxies.length} proxies`); 98 | } else { 99 | logger.warn('proxies.txt not found, running without proxies'); 100 | } 101 | } catch (error) { 102 | logger.error(`Error loading proxies: ${error.message}`); 103 | } 104 | } 105 | 106 | createProxyAgent(proxyUrl) { 107 | try { 108 | if (proxyUrl.startsWith('http://')) { 109 | return new HttpProxyAgent(proxyUrl); 110 | } else if (proxyUrl.startsWith('https://')) { 111 | return new HttpsProxyAgent(proxyUrl); 112 | } else if (proxyUrl.startsWith('socks://') || proxyUrl.startsWith('socks5://')) { 113 | return new SocksProxyAgent(proxyUrl); 114 | } 115 | return null; 116 | } catch (error) { 117 | logger.error(`Error creating proxy agent: ${error.message}`); 118 | return null; 119 | } 120 | } 121 | 122 | async loadWallets() { 123 | const privateKeys = []; 124 | let i = 1; 125 | 126 | while (process.env[`PRIVATE_KEY_${i}`]) { 127 | privateKeys.push(process.env[`PRIVATE_KEY_${i}`]); 128 | i++; 129 | } 130 | 131 | if (privateKeys.length === 0) { 132 | logger.error('No private keys found in .env file'); 133 | process.exit(1); 134 | } 135 | 136 | for (let i = 0; i < privateKeys.length; i++) { 137 | const proxyUrl = this.proxies[i % this.proxies.length]; 138 | let provider; 139 | 140 | if (proxyUrl) { 141 | const agent = this.createProxyAgent(proxyUrl); 142 | if (agent) { 143 | provider = new ethers.JsonRpcProvider(NETWORK_CONFIG.rpc, { 144 | chainId: NETWORK_CONFIG.chainId, 145 | name: 'pharos-testnet' 146 | }, { 147 | staticNetwork: true, 148 | fetchOptions: { 149 | agent: agent 150 | } 151 | }); 152 | } else { 153 | provider = new ethers.JsonRpcProvider(NETWORK_CONFIG.rpc); 154 | } 155 | } else { 156 | provider = new ethers.JsonRpcProvider(NETWORK_CONFIG.rpc); 157 | } 158 | 159 | const wallet = new ethers.Wallet(privateKeys[i], provider); 160 | this.wallets.push(wallet); 161 | this.providers.push(provider); 162 | 163 | logger.info(`Wallet ${i + 1}: ${wallet.address} ${proxyUrl ? `(Proxy: ${proxyUrl.substring(0, 20)}...)` : '(No Proxy)'}`); 164 | } 165 | } 166 | 167 | async getUserInput(question) { 168 | return new Promise((resolve) => { 169 | this.rl.question(question, (answer) => { 170 | resolve(answer.trim()); 171 | }); 172 | }); 173 | } 174 | 175 | async showMenu() { 176 | console.log(`\n${colors.cyan}${colors.bold}--- OPENFI TESTNET BOT MENU ---${colors.reset}`); 177 | console.log(`${colors.white}1. Supply PHRS${colors.reset}`); 178 | console.log(`${colors.white}2. Mint Faucet Tokens${colors.reset}`); 179 | console.log(`${colors.white}3. Supply ERC20 Tokens${colors.reset}`); 180 | console.log(`${colors.white}4. Borrow Tokens${colors.reset}`); 181 | console.log(`${colors.white}5. Withdraw Tokens${colors.reset}`); 182 | console.log(`${colors.white}6. Exit${colors.reset}`); 183 | console.log(`${colors.cyan}------------------------------${colors.reset}\n`); 184 | 185 | const choice = await this.getUserInput('Select an option (1-6): '); 186 | return choice; 187 | } 188 | 189 | async supplyPHRS() { 190 | logger.step('Starting PHRS Supply Process'); 191 | 192 | const amount = await this.getUserInput('Enter amount of PHRS to supply: '); 193 | const transactions = await this.getUserInput('Enter number of transactions per wallet: '); 194 | 195 | const amountWei = ethers.parseEther(amount); 196 | const txCount = parseInt(transactions); 197 | 198 | for (let i = 0; i < this.wallets.length; i++) { 199 | const wallet = this.wallets[i]; 200 | logger.loading(`Processing wallet ${i + 1}: ${wallet.address}`); 201 | 202 | try { 203 | const balance = await wallet.provider.getBalance(wallet.address); 204 | logger.info(`Wallet balance: ${ethers.formatEther(balance)} PHRS`); 205 | 206 | if (balance < amountWei * BigInt(txCount)) { 207 | logger.warn(`Insufficient balance for wallet ${i + 1}`); 208 | continue; 209 | } 210 | 211 | const lendingContract = new ethers.Contract( 212 | CONTRACTS.LENDING_POOL, 213 | LENDING_POOL_ABI, 214 | wallet 215 | ); 216 | 217 | for (let j = 0; j < txCount; j++) { 218 | try { 219 | logger.loading(`Transaction ${j + 1}/${txCount} for wallet ${i + 1}`); 220 | 221 | const tx = await lendingContract.depositETH( 222 | '0x0000000000000000000000000000000000000000', 223 | wallet.address, 224 | 0, 225 | { value: amountWei } 226 | ); 227 | 228 | logger.success(`TX Hash: ${tx.hash}`); 229 | await tx.wait(); 230 | logger.success(`Transaction ${j + 1} confirmed`); 231 | 232 | if (j < txCount - 1) { 233 | await this.delay(2000); 234 | } 235 | } catch (error) { 236 | logger.error(`Transaction ${j + 1} failed: ${error.message}`); 237 | } 238 | } 239 | } catch (error) { 240 | logger.error(`Error processing wallet ${i + 1}: ${error.message}`); 241 | } 242 | 243 | if (i < this.wallets.length - 1) { 244 | await this.delay(3000); 245 | } 246 | } 247 | } 248 | 249 | async mintFaucetTokens() { 250 | logger.step('Starting Faucet Token Minting'); 251 | 252 | console.log('\nAvailable tokens for minting:'); 253 | const tokenNames = Object.keys(CONTRACTS.TOKENS); 254 | tokenNames.forEach((token, index) => { 255 | console.log(`${index + 1}. ${token}`); 256 | }); 257 | 258 | const tokenChoice = await this.getUserInput('Select token to mint (1-6): '); 259 | const tokenIndex = parseInt(tokenChoice) - 1; 260 | 261 | if (tokenIndex < 0 || tokenIndex >= tokenNames.length) { 262 | logger.error('Invalid token selection'); 263 | return; 264 | } 265 | 266 | const selectedToken = tokenNames[tokenIndex]; 267 | const tokenAddress = CONTRACTS.TOKENS[selectedToken]; 268 | 269 | const amount = await this.getUserInput(`Enter amount of ${selectedToken} to mint: `); 270 | const transactions = await this.getUserInput('Enter number of transactions per wallet: '); 271 | 272 | const decimals = selectedToken === 'USDT' || selectedToken === 'USDC' || selectedToken === 'BTC' ? 6 : 18; 273 | const amountWei = ethers.parseUnits(amount, decimals); 274 | const txCount = parseInt(transactions); 275 | 276 | for (let i = 0; i < this.wallets.length; i++) { 277 | const wallet = this.wallets[i]; 278 | logger.loading(`Processing wallet ${i + 1}: ${wallet.address}`); 279 | 280 | try { 281 | const faucetContract = new ethers.Contract( 282 | CONTRACTS.FAUCET, 283 | FAUCET_ABI, 284 | wallet 285 | ); 286 | 287 | for (let j = 0; j < txCount; j++) { 288 | try { 289 | logger.loading(`Minting ${selectedToken} - Transaction ${j + 1}/${txCount} for wallet ${i + 1}`); 290 | 291 | const tx = await faucetContract.mint( 292 | tokenAddress, 293 | wallet.address, 294 | amountWei 295 | ); 296 | 297 | logger.success(`TX Hash: ${tx.hash}`); 298 | await tx.wait(); 299 | logger.success(`Mint transaction ${j + 1} confirmed`); 300 | 301 | if (j < txCount - 1) { 302 | await this.delay(2000); 303 | } 304 | } catch (error) { 305 | logger.error(`Mint transaction ${j + 1} failed: ${error.message}`); 306 | } 307 | } 308 | } catch (error) { 309 | logger.error(`Error processing wallet ${i + 1}: ${error.message}`); 310 | } 311 | 312 | if (i < this.wallets.length - 1) { 313 | await this.delay(3000); 314 | } 315 | } 316 | } 317 | 318 | async supplyERC20Tokens() { 319 | logger.step('Starting ERC20 Token Supply'); 320 | 321 | console.log('\nAvailable tokens for supply:'); 322 | const tokenNames = Object.keys(CONTRACTS.TOKENS); 323 | tokenNames.forEach((token, index) => { 324 | console.log(`${index + 1}. ${token}`); 325 | }); 326 | 327 | const tokenChoice = await this.getUserInput('Select token to supply (1-6): '); 328 | const tokenIndex = parseInt(tokenChoice) - 1; 329 | 330 | if (tokenIndex < 0 || tokenIndex >= tokenNames.length) { 331 | logger.error('Invalid token selection'); 332 | return; 333 | } 334 | 335 | const selectedToken = tokenNames[tokenIndex]; 336 | const tokenAddress = CONTRACTS.TOKENS[selectedToken]; 337 | 338 | const amount = await this.getUserInput(`Enter amount of ${selectedToken} to supply: `); 339 | const transactions = await this.getUserInput('Enter number of transactions per wallet: '); 340 | 341 | const decimals = selectedToken === 'USDT' || selectedToken === 'USDC' || selectedToken === 'BTC' ? 6 : 18; 342 | const amountWei = ethers.parseUnits(amount, decimals); 343 | const txCount = parseInt(transactions); 344 | 345 | for (let i = 0; i < this.wallets.length; i++) { 346 | const wallet = this.wallets[i]; 347 | logger.loading(`Processing wallet ${i + 1}: ${wallet.address}`); 348 | 349 | try { 350 | const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, wallet); 351 | 352 | for (let j = 0; j < txCount; j++) { 353 | try { 354 | logger.loading(`Approving ${selectedToken} - Transaction ${j + 1}/${txCount} for wallet ${i + 1}`); 355 | 356 | const approveTx = await tokenContract.approve( 357 | CONTRACTS.SUPPLY_CONTRACT, 358 | ethers.MaxUint256 359 | ); 360 | 361 | logger.info(`Approve TX Hash: ${approveTx.hash}`); 362 | await approveTx.wait(); 363 | 364 | logger.loading(`Supplying ${selectedToken} - Transaction ${j + 1}/${txCount} for wallet ${i + 1}`); 365 | 366 | const iface = new ethers.Interface([ 367 | "function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)" 368 | ]); 369 | 370 | const supplyData = iface.encodeFunctionData("supply", [ 371 | tokenAddress, 372 | amountWei, 373 | wallet.address, 374 | 0 375 | ]); 376 | 377 | const abiCoder = ethers.AbiCoder.defaultAbiCoder(); 378 | const encodedParams = abiCoder.encode( 379 | ['address', 'uint256', 'address', 'uint16'], 380 | [tokenAddress, amountWei, wallet.address, 0] 381 | ); 382 | 383 | const finalData = '0x617ba037' + encodedParams.slice(2); 384 | 385 | const supplyTx = await wallet.sendTransaction({ 386 | to: CONTRACTS.SUPPLY_CONTRACT, 387 | data: finalData, 388 | gasLimit: 500000 389 | }); 390 | 391 | logger.success(`Supply TX Hash: ${supplyTx.hash}`); 392 | await supplyTx.wait(); 393 | logger.success(`Supply transaction ${j + 1} confirmed`); 394 | 395 | if (j < txCount - 1) { 396 | await this.delay(2000); 397 | } 398 | } catch (error) { 399 | logger.error(`Supply transaction ${j + 1} failed: ${error.message}`); 400 | } 401 | } 402 | } catch (error) { 403 | logger.error(`Error processing wallet ${i + 1}: ${error.message}`); 404 | } 405 | 406 | if (i < this.wallets.length - 1) { 407 | await this.delay(3000); 408 | } 409 | } 410 | } 411 | 412 | async borrowTokens() { 413 | logger.step('Starting Token Borrow Process'); 414 | 415 | console.log('\nAvailable tokens for borrowing:'); 416 | const tokenNames = Object.keys(CONTRACTS.TOKENS); 417 | tokenNames.forEach((token, index) => { 418 | console.log(`${index + 1}. ${token}`); 419 | }); 420 | 421 | const tokenChoice = await this.getUserInput('Select token to borrow (1-6): '); 422 | const tokenIndex = parseInt(tokenChoice) - 1; 423 | 424 | if (tokenIndex < 0 || tokenIndex >= tokenNames.length) { 425 | logger.error('Invalid token selection'); 426 | return; 427 | } 428 | 429 | const selectedToken = tokenNames[tokenIndex]; 430 | const tokenAddress = CONTRACTS.TOKENS[selectedToken]; 431 | 432 | const amount = await this.getUserInput(`Enter amount of ${selectedToken} to borrow: `); 433 | const transactions = await this.getUserInput('Enter number of transactions per wallet: '); 434 | 435 | const decimals = selectedToken === 'USDT' || selectedToken === 'USDC' || selectedToken === 'BTC' ? 6 : 18; 436 | const amountWei = ethers.parseUnits(amount, decimals); 437 | const txCount = parseInt(transactions); 438 | 439 | for (let i = 0; i < this.wallets.length; i++) { 440 | const wallet = this.wallets[i]; 441 | logger.loading(`Processing wallet ${i + 1}: ${wallet.address}`); 442 | 443 | try { 444 | const lendingContract = new ethers.Contract( 445 | CONTRACTS.SUPPLY_CONTRACT, 446 | LENDING_POOL_ABI, 447 | wallet 448 | ); 449 | 450 | for (let j = 0; j < txCount; j++) { 451 | try { 452 | logger.loading(`Borrowing ${selectedToken} - Transaction ${j + 1}/${txCount} for wallet ${i + 1}`); 453 | 454 | const iface = new ethers.Interface([ 455 | "function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)" 456 | ]); 457 | 458 | const borrowData = iface.encodeFunctionData("borrow", [ 459 | tokenAddress, 460 | amountWei, 461 | 2, 462 | 0, 463 | wallet.address 464 | ]); 465 | 466 | const tx = await wallet.sendTransaction({ 467 | to: CONTRACTS.SUPPLY_CONTRACT, 468 | data: borrowData, 469 | gasLimit: 465383 470 | }); 471 | 472 | logger.success(`Borrow TX Hash: ${tx.hash}`); 473 | await tx.wait(); 474 | logger.success(`Borrow transaction ${j + 1} confirmed`); 475 | 476 | if (j < txCount - 1) { 477 | await this.delay(2000); 478 | } 479 | } catch (error) { 480 | logger.error(`Borrow transaction ${j + 1} failed: ${error.message}`); 481 | } 482 | } 483 | } catch (error) { 484 | logger.error(`Error processing wallet ${i + 1}: ${error.message}`); 485 | } 486 | 487 | if (i < this.wallets.length - 1) { 488 | await this.delay(3000); 489 | } 490 | } 491 | } 492 | 493 | async withdrawTokens() { 494 | logger.step('Starting Token Withdraw Process'); 495 | 496 | console.log('\nAvailable tokens for withdrawal:'); 497 | const tokenNames = Object.keys(CONTRACTS.TOKENS); 498 | tokenNames.forEach((token, index) => { 499 | console.log(`${index + 1}. ${token}`); 500 | }); 501 | 502 | const tokenChoice = await this.getUserInput('Select token to withdraw (1-6): '); 503 | const tokenIndex = parseInt(tokenChoice) - 1; 504 | 505 | if (tokenIndex < 0 || tokenIndex >= tokenNames.length) { 506 | logger.error('Invalid token selection'); 507 | return; 508 | } 509 | 510 | const selectedToken = tokenNames[tokenIndex]; 511 | const tokenAddress = CONTRACTS.TOKENS[selectedToken]; 512 | 513 | const amount = await this.getUserInput(`Enter amount of ${selectedToken} to withdraw: `); 514 | const transactions = await this.getUserInput('Enter number of transactions per wallet: '); 515 | 516 | const decimals = selectedToken === 'USDT' || selectedToken === 'USDC' || selectedToken === 'BTC' ? 6 : 18; 517 | const amountWei = ethers.parseUnits(amount, decimals); 518 | const txCount = parseInt(transactions); 519 | 520 | for (let i = 0; i < this.wallets.length; i++) { 521 | const wallet = this.wallets[i]; 522 | logger.loading(`Processing wallet ${i + 1}: ${wallet.address}`); 523 | 524 | try { 525 | const lendingContract = new ethers.Contract( 526 | CONTRACTS.SUPPLY_CONTRACT, 527 | LENDING_POOL_ABI, 528 | wallet 529 | ); 530 | 531 | for (let j = 0; j < txCount; j++) { 532 | try { 533 | logger.loading(`Withdrawing ${selectedToken} - Transaction ${j + 1}/${txCount} for wallet ${i + 1}`); 534 | 535 | const iface = new ethers.Interface([ 536 | "function withdraw(address asset, uint256 amount, address to)" 537 | ]); 538 | 539 | const withdrawData = iface.encodeFunctionData("withdraw", [ 540 | tokenAddress, 541 | amountWei, 542 | wallet.address 543 | ]); 544 | 545 | const tx = await wallet.sendTransaction({ 546 | to: CONTRACTS.SUPPLY_CONTRACT, 547 | data: withdrawData, 548 | gasLimit: 512475 549 | }); 550 | 551 | logger.success(`Withdraw TX Hash: ${tx.hash}`); 552 | await tx.wait(); 553 | logger.success(`Withdraw transaction ${j + 1} confirmed`); 554 | 555 | if (j < txCount - 1) { 556 | await this.delay(2000); 557 | } 558 | } catch (error) { 559 | logger.error(`Withdraw transaction ${j + 1} failed: ${error.message}`); 560 | } 561 | } 562 | } catch (error) { 563 | logger.error(`Error processing wallet ${i + 1}: ${error.message}`); 564 | } 565 | 566 | if (i < this.wallets.length - 1) { 567 | await this.delay(3000); 568 | } 569 | } 570 | } 571 | 572 | delay(ms) { 573 | return new Promise(resolve => setTimeout(resolve, ms)); 574 | } 575 | 576 | async run() { 577 | try { 578 | await this.initialize(); 579 | 580 | while (true) { 581 | const choice = await this.showMenu(); 582 | 583 | switch (choice) { 584 | case '1': 585 | await this.supplyPHRS(); 586 | break; 587 | case '2': 588 | await this.mintFaucetTokens(); 589 | break; 590 | case '3': 591 | await this.supplyERC20Tokens(); 592 | break; 593 | case '4': 594 | await this.borrowTokens(); 595 | break; 596 | case '5': 597 | await this.withdrawTokens(); 598 | break; 599 | case '6': 600 | logger.success('Exiting bot...'); 601 | this.rl.close(); 602 | process.exit(0); 603 | break; 604 | default: 605 | logger.error('Invalid choice. Please select 1-6.'); 606 | } 607 | 608 | await this.getUserInput('\nPress Enter to continue...'); 609 | } 610 | } catch (error) { 611 | logger.error(`Fatal error: ${error.message}`); 612 | this.rl.close(); 613 | process.exit(1); 614 | } 615 | } 616 | } 617 | 618 | const bot = new PharosBot(); 619 | bot.run().catch(console.error); --------------------------------------------------------------------------------