├── tsconfig.json ├── env.example ├── package.json ├── .gitignore ├── src ├── types │ └── index.ts ├── utils │ ├── logger.ts │ └── wallet.ts ├── config │ └── index.ts ├── __tests__ │ └── wallet.test.ts ├── index.ts └── services │ ├── bundlerBot.ts │ └── transactionService.ts ├── CHINESE.md ├── scripts └── setup.js └── README.md /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": ["ES2020"], 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules", "dist", "**/*.test.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | MAIN_WALLET_PRIVATE_KEY= 2 | MAIN_WALLET_ADDRESS= 3 | 4 | BSC_RPC_URL= 5 | BSC_TESTNET_RPC_URL= 6 | 7 | USE_TESTNET= 8 | GAS_PRICE_GWEI= 9 | GAS_LIMIT= 10 | MAX_RETRIES= 11 | RETRY_DELAY_MS= 12 | 13 | TOKEN_NAME=Lendon 14 | TOKEN_SYMBOL=lendon 15 | TOKEN_DESCRIPTION=I am Lendon Bracewell and this is my bot 16 | TOKEN_WEBSITE=https://tsagent.xyz 17 | TOKEN_TWITTER=https://x.com/lendon1114 18 | TOKEN_TELEGRAM=https://t.me/topsecretagent_007 19 | TOKEN_TAG=meme 20 | RAISED_TOKEN=bnb 21 | 22 | BUNDLER_WALLET_COUNT 23 | MIN_BUY_AMOUNT_BNB= 24 | MAX_BUY_AMOUNT_BNB= 25 | RANDOM_BUY_AMOUNTS= 26 | 27 | LOG_LEVEL= 28 | LOG_TO_FILE= 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bnb-bundler-bot-4meme", 3 | "version": "1.0.0", 4 | "description": "BNB Bundler Bot for four.meme token launchpad platform", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "start": "node dist/index.js", 9 | "dev": "ts-node src/index.ts", 10 | "test": "jest", 11 | "setup": "node scripts/setup.js" 12 | }, 13 | "keywords": ["bnb", "bundler", "bot", "meme", "token", "launchpad"], 14 | "author": "Lendon Bracewell", 15 | "license": "MIT", 16 | "dependencies": { 17 | "ethers": "^6.8.1", 18 | "dotenv": "^16.3.1", 19 | "axios": "^1.6.2", 20 | "chalk": "^4.1.2", 21 | "cli-progress": "^3.12.0", 22 | "winston": "^3.11.0" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^20.10.0", 26 | "@types/jest": "^29.5.8", 27 | "typescript": "^5.3.2", 28 | "ts-node": "^10.9.1", 29 | "jest": "^29.7.0", 30 | "@types/jest": "^29.5.8" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | # Build output 15 | dist/ 16 | build/ 17 | 18 | # Logs 19 | logs/ 20 | *.log 21 | 22 | # Runtime data 23 | pids/ 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage/ 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Dependency directories 35 | jspm_packages/ 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Yarn Integrity file 47 | .yarn-integrity 48 | 49 | # dotenv environment variables file 50 | .env 51 | 52 | # IDE files 53 | .vscode/ 54 | .idea/ 55 | *.swp 56 | *.swo 57 | *~ 58 | 59 | # OS generated files 60 | .DS_Store 61 | .DS_Store? 62 | ._* 63 | .Spotlight-V100 64 | .Trashes 65 | ehthumbs.db 66 | Thumbs.db 67 | 68 | # Wallet files (security) 69 | wallets.json 70 | private-keys.txt 71 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface WalletConfig { 2 | privateKey: string; 3 | address: string; 4 | buyAmount: string; 5 | isActive: boolean; 6 | } 7 | 8 | export interface TokenLaunchConfig { 9 | name: string; 10 | symbol: string; 11 | description: string; 12 | website: string; 13 | twitter: string; 14 | telegram: string; 15 | tag: string; 16 | raisedToken: string; 17 | } 18 | 19 | export interface BotConfig { 20 | mainWalletPrivateKey: string; 21 | mainWalletAddress: string; 22 | rpcUrl: string; 23 | useTestnet: boolean; 24 | gasPriceGwei: number; 25 | gasLimit: number; 26 | maxRetries: number; 27 | retryDelayMs: number; 28 | bundlerWalletCount: number; 29 | minBuyAmountBnb: number; 30 | maxBuyAmountBnb: number; 31 | randomBuyAmounts: boolean; 32 | logLevel: string; 33 | logToFile: boolean; 34 | } 35 | 36 | export interface TransactionResult { 37 | success: boolean; 38 | hash?: string; 39 | error?: string; 40 | walletAddress: string; 41 | amount: string; 42 | gasUsed?: string; 43 | } 44 | 45 | export interface BundleResult { 46 | totalTransactions: number; 47 | successfulTransactions: number; 48 | failedTransactions: number; 49 | totalGasUsed: string; 50 | totalBnbSpent: string; 51 | results: TransactionResult[]; 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | import { botConfig } from '../config'; 3 | 4 | const logFormat = winston.format.combine( 5 | winston.format.timestamp(), 6 | winston.format.errors({ stack: true }), 7 | winston.format.json() 8 | ); 9 | 10 | const consoleFormat = winston.format.combine( 11 | winston.format.colorize(), 12 | winston.format.timestamp({ format: 'HH:mm:ss' }), 13 | winston.format.printf(({ timestamp, level, message, ...meta }) => { 14 | let log = `${timestamp} [${level}]: ${message}`; 15 | if (Object.keys(meta).length > 0) { 16 | log += ` ${JSON.stringify(meta)}`; 17 | } 18 | return log; 19 | }) 20 | ); 21 | 22 | const transports: winston.transport[] = [ 23 | new winston.transports.Console({ 24 | format: consoleFormat, 25 | level: botConfig.logLevel 26 | }) 27 | ]; 28 | 29 | if (botConfig.logToFile) { 30 | transports.push( 31 | new winston.transports.File({ 32 | filename: 'logs/error.log', 33 | level: 'error', 34 | format: logFormat 35 | }), 36 | new winston.transports.File({ 37 | filename: 'logs/combined.log', 38 | format: logFormat 39 | }) 40 | ); 41 | } 42 | 43 | export const logger = winston.createLogger({ 44 | level: botConfig.logLevel, 45 | format: logFormat, 46 | transports 47 | }); 48 | 49 | export default logger; 50 | -------------------------------------------------------------------------------- /CHINESE.md: -------------------------------------------------------------------------------- 1 | # 🐋 BNB Bundler Bot for four.meme 2 | 3 | 一个为 **four.meme** 生态系统打造的 **BNB 代币打包机器人**,可自动生成钱包、打包交易,并实现高效的 BNB 管理和代币发布。 4 | 5 | --- 6 | 7 | ## ⚙️ 功能特点 8 | 9 | - 🚀 **批量钱包打包**:自动创建并管理 **50 个钱包**,用于交易打包 10 | - 💰 **主钱包管理**:使用 **主钱包** 进行资金分配与回收 BNB 11 | - 🎯 **灵活买入金额**:支持 **随机** 或 **固定范围** 的买入金额 12 | - ⛽ **Gas 优化**:可自定义 **Gas 价格** 与 **限额** 13 | - 📊 **进度追踪**:实时显示 **进度条** 与详细日志 14 | - 🔄 **自动回收系统**:自动将剩余 BNB 汇总回主钱包 15 | - 🛡️ **错误处理机制**:内置重试逻辑与 **完善的错误处理** 16 | 17 | --- 18 | 19 | ## 💎 代币配置 20 | 21 | 该机器人已为 **teemooo** 代币发布预配置。 22 | 23 | **代币地址:** 24 | ```bash 25 | 0x117ed7ab9ec2d241238320cb4b42ab89913d4444 26 | ``` 27 | 28 | **交易链接:** 29 | 🔗 [gmgn.ai 代币页面](https://gmgn.ai/bsc/token/0x117ed7ab9ec2d241238320cb4b42ab89913d4444) 30 | 🔗 [BscScan 合约页面](https://bscscan.com/address/0x117ed7ab9ec2d241238320cb4b42ab89913d4444) 31 | 32 | --- 33 | 34 | ### 🧠 钱包信息 35 | 36 | **主钱包:** 37 | ``` 38 | 0x2E828b4910333f6D211055B5717A8De0e8eB2058 39 | ``` 40 | 41 | **生成钱包:** 42 | ``` 43 | 0x270Cc208D1711768DA9C8ccE76b8b192dD093Fa5 44 | 0x085A516a00eb9637F26d12B68fECE26D44AeB3dE 45 | ``` 46 | 47 | --- 48 | 49 | ## 🖼️ 截图预览 50 | 51 | | 预览 | | 52 | |----------|----------| 53 | | ![image1](https://github.com/user-attachments/assets/34b4496a-1ce1-4200-b483-9b85eb0245ca) | ![image2](https://github.com/user-attachments/assets/39228a4f-8d0d-4099-b6e5-090d8ffa621f) | 54 | 55 | --- 56 | 57 | ## 📞 联系方式 58 | 59 | **Lendon Bracewell** 60 | 🌐 [tsagent.xyz](https://tsagent.xyz) 61 | 🐦 [@lendon1114](https://x.com/lendon1114) 62 | 💬 [@topsecretagent_007](https://t.me/topsecretagent_007) 63 | 💻 [@topsecretagent007](https://github.com/topsecretagent007) 64 | -------------------------------------------------------------------------------- /scripts/setup.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | console.log('🚀 Setting up BNB Bundler Bot for four.meme...\n'); 5 | 6 | // Create logs directory 7 | const logsDir = path.join(__dirname, '..', 'logs'); 8 | if (!fs.existsSync(logsDir)) { 9 | fs.mkdirSync(logsDir, { recursive: true }); 10 | console.log('✅ Created logs directory'); 11 | } 12 | 13 | // Create .env file if it doesn't exist 14 | const envPath = path.join(__dirname, '..', '.env'); 15 | const envExamplePath = path.join(__dirname, '..', 'env.example'); 16 | 17 | if (!fs.existsSync(envPath)) { 18 | if (fs.existsSync(envExamplePath)) { 19 | fs.copyFileSync(envExamplePath, envPath); 20 | console.log('✅ Created .env file from template'); 21 | console.log('⚠️ Please update .env with your actual configuration'); 22 | } else { 23 | console.log('❌ env.example file not found'); 24 | } 25 | } else { 26 | console.log('✅ .env file already exists'); 27 | } 28 | 29 | // Create package.json scripts 30 | console.log('\n📦 Available commands:'); 31 | console.log(' npm run dev - Run in development mode'); 32 | console.log(' npm run build - Build the project'); 33 | console.log(' npm start - Run the built project'); 34 | console.log(' npm test - Run tests'); 35 | 36 | console.log('\n🔧 Next steps:'); 37 | console.log('1. Update .env with your wallet configuration'); 38 | console.log('2. Install dependencies: npm install'); 39 | console.log('3. Test on BSC testnet first: npm run dev'); 40 | console.log('4. Update contract address in src/config/index.ts'); 41 | 42 | console.log('\n⚠️ Important:'); 43 | console.log('- Never share your private keys'); 44 | console.log('- Test on testnet before mainnet'); 45 | console.log('- Ensure sufficient BNB balance'); 46 | console.log('- Update contract address before use'); 47 | 48 | console.log('\n🎉 Setup complete!'); 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BNB Bundler Bot for four.meme 2 | 3 | four.meme token bundler bot 4 | 5 | ## Features 6 | 7 | - 🚀 **X Wallet Bundling**: Automatically generates and manages 1 ~ 200 wallets for transaction bundling 8 | - 💰 **Main Wallet Management**: Uses main wallet for funding and gathering BNB 9 | - 🎯 **Configurable Buy Amounts**: Random or fixed buy amounts within specified ranges 10 | - ⛽ **Gas Optimization**: Configurable gas prices and limits 11 | - 📊 **Progress Tracking**: Real-time progress bars and detailed logging 12 | - 🔄 **Automatic Recovery**: Gathers remaining BNB back to main wallet 13 | - 🛡️ **Error Handling**: Comprehensive error handling and retry mechanisms 14 | 15 | ## Token Configuration 16 | The bot is pre-configured for the **teemooo** token launch: 17 | 18 | https://github.com/user-attachments/assets/28e84a93-f4bf-4372-b84a-ed5b95faaf06 19 | 20 | TX: https://gmgn.ai/bsc/token/0x117ed7ab9ec2d241238320cb4b42ab89913d4444 21 | 22 | image 23 | https://bscscan.com/address/0x2e828b4910333f6d211055b5717a8de0e8eb2058 24 | 25 | image 26 | 1: 27 | https://bscscan.com/address/0x270cc208d1711768da9c8cce76b8b192dd093fa5 28 | 29 | 2: 30 | https://bscscan.com/address/0x085a516a00eb9637f26d12b68fece26d44aeb3de 31 | 32 | Create wallet: 33 | ```0x2E828b4910333f6D211055B5717A8De0e8eB2058``` 34 | 35 | Generator wallets: 36 | ```0x270Cc208D1711768DA9C8ccE76b8b192dD093Fa5``` 37 | ```0x085A516a00eb9637F26d12B68fECE26D44AeB3dE``` 38 | 39 | Token address: 40 | ```0x117ed7ab9ec2d241238320cb4b42ab89913d4444``` 41 | 42 | ## Contact 43 | 44 | **Lendon Bracewell** 45 | - Website: [https://tsagent.xyz](https://tsagent.xyz) 46 | - Twitter: [@lendon1114](https://x.com/lendon1114) 47 | - Telegram: [@topsecretagent_007](https://t.me/topsecretagent_007) 48 | - GitHub: [@topsecretagent007](https://github.com/topsecretagent007) 49 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { BotConfig, TokenLaunchConfig } from '../types'; 3 | 4 | dotenv.config(); 5 | 6 | export const tokenLaunchConfig: TokenLaunchConfig = { 7 | name: process.env.TOKEN_NAME || 'Lendon', 8 | symbol: process.env.TOKEN_SYMBOL || 'lendon', 9 | description: process.env.TOKEN_DESCRIPTION || 'I am Lendon Bracewell and this is my bot', 10 | website: process.env.TOKEN_WEBSITE || 'https://tsagent.xyz', 11 | twitter: process.env.TOKEN_TWITTER || 'https://x.com/lendon1114', 12 | telegram: process.env.TOKEN_TELEGRAM || 'https://t.me/topsecretagent_007', 13 | tag: process.env.TOKEN_TAG || 'meme', 14 | raisedToken: process.env.RAISED_TOKEN || 'bnb' 15 | }; 16 | 17 | export const botConfig: BotConfig = { 18 | mainWalletPrivateKey: process.env.MAIN_WALLET_PRIVATE_KEY || '', 19 | mainWalletAddress: process.env.MAIN_WALLET_ADDRESS || '', 20 | rpcUrl: process.env.USE_TESTNET === 'true' 21 | ? process.env.BSC_TESTNET_RPC_URL || 'https://data-seed-prebsc-1-s1.binance.org:8545/' 22 | : process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org/', 23 | useTestnet: process.env.USE_TESTNET === 'true', 24 | gasPriceGwei: parseInt(process.env.GAS_PRICE_GWEI || '5'), 25 | gasLimit: parseInt(process.env.GAS_LIMIT || '300000'), 26 | maxRetries: parseInt(process.env.MAX_RETRIES || '3'), 27 | retryDelayMs: parseInt(process.env.RETRY_DELAY_MS || '2000'), 28 | bundlerWalletCount: parseInt(process.env.BUNDLER_WALLET_COUNT || '50'), 29 | minBuyAmountBnb: parseFloat(process.env.MIN_BUY_AMOUNT_BNB || '0.001'), 30 | maxBuyAmountBnb: parseFloat(process.env.MAX_BUY_AMOUNT_BNB || '0.01'), 31 | randomBuyAmounts: process.env.RANDOM_BUY_AMOUNTS === 'true', 32 | logLevel: process.env.LOG_LEVEL || 'info', 33 | logToFile: process.env.LOG_TO_FILE === 'true' 34 | }; 35 | 36 | export const FOUR_MEME_CONTRACT_ADDRESS = botConfig.useTestnet 37 | ? '0x0000000000000000000000000000000000000000' // Replace with actual testnet contract 38 | : '0x0000000000000000000000000000000000000000'; // Replace with actual mainnet contract 39 | 40 | export const WBNB_ADDRESS = botConfig.useTestnet 41 | ? '0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd' // WBNB testnet 42 | : '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; // WBNB mainnet 43 | -------------------------------------------------------------------------------- /src/__tests__/wallet.test.ts: -------------------------------------------------------------------------------- 1 | import { WalletManager } from '../utils/wallet'; 2 | import { botConfig } from '../config'; 3 | 4 | describe('WalletManager', () => { 5 | let walletManager: WalletManager; 6 | 7 | beforeEach(() => { 8 | walletManager = new WalletManager(); 9 | }); 10 | 11 | test('should generate correct number of wallets', () => { 12 | const wallets = walletManager.generateWallets(); 13 | expect(wallets).toHaveLength(botConfig.bundlerWalletCount); 14 | }); 15 | 16 | test('should generate wallets with valid addresses', () => { 17 | const wallets = walletManager.generateWallets(); 18 | 19 | wallets.forEach(wallet => { 20 | expect(wallet.address).toMatch(/^0x[a-fA-F0-9]{40}$/); 21 | expect(wallet.privateKey).toMatch(/^0x[a-fA-F0-9]{64}$/); 22 | expect(wallet.isActive).toBe(true); 23 | expect(parseFloat(wallet.buyAmount)).toBeGreaterThan(0); 24 | }); 25 | }); 26 | 27 | test('should calculate total BNB required correctly', () => { 28 | const wallets = walletManager.generateWallets(); 29 | const totalRequired = walletManager.getTotalBnbRequired(); 30 | 31 | const expectedTotal = wallets 32 | .reduce((sum, wallet) => sum + parseFloat(wallet.buyAmount), 0) 33 | .toFixed(6); 34 | 35 | expect(totalRequired).toBe(expectedTotal); 36 | }); 37 | 38 | test('should deactivate wallet correctly', () => { 39 | const wallets = walletManager.generateWallets(); 40 | const firstWallet = wallets[0]; 41 | 42 | expect(firstWallet.isActive).toBe(true); 43 | 44 | walletManager.deactivateWallet(firstWallet.address); 45 | const updatedWallet = walletManager.getWallet(firstWallet.address); 46 | 47 | expect(updatedWallet?.isActive).toBe(false); 48 | }); 49 | 50 | test('should update wallet buy amount correctly', () => { 51 | const wallets = walletManager.generateWallets(); 52 | const firstWallet = wallets[0]; 53 | const newAmount = '0.005'; 54 | 55 | const success = walletManager.updateWalletBuyAmount(firstWallet.address, newAmount); 56 | expect(success).toBe(true); 57 | 58 | const updatedWallet = walletManager.getWallet(firstWallet.address); 59 | expect(updatedWallet?.buyAmount).toBe(newAmount); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { BundlerBot } from './services/bundlerBot'; 2 | import { botConfig, tokenLaunchConfig } from './config'; 3 | import logger from './utils/logger'; 4 | import chalk from 'chalk'; 5 | 6 | async function main() { 7 | try { 8 | // Display banner 9 | console.log(chalk.blue.bold(` 10 | ╔══════════════════════════════════════════════════════════════╗ 11 | ║ BNB BUNDLER BOT 4MEME ║ 12 | ║ ║ 13 | ║ Token: ${tokenLaunchConfig.name.padEnd(20)} (${tokenLaunchConfig.symbol.padEnd(10)}) ║ 14 | ║ Network: ${botConfig.useTestnet ? 'BSC Testnet'.padEnd(20) : 'BSC Mainnet'.padEnd(20)} ║ 15 | ║ Wallets: ${botConfig.bundlerWalletCount.toString().padEnd(20)} ║ 16 | ║ Amount Range: ${botConfig.minBuyAmountBnb}-${botConfig.maxBuyAmountBnb} BNB${' '.repeat(15)} ║ 17 | ╚══════════════════════════════════════════════════════════════╝ 18 | `)); 19 | 20 | // Validate configuration 21 | if (!botConfig.mainWalletPrivateKey || !botConfig.mainWalletAddress) { 22 | logger.error(chalk.red('❌ Main wallet configuration is missing!')); 23 | logger.error(chalk.yellow('Please set MAIN_WALLET_PRIVATE_KEY and MAIN_WALLET_ADDRESS in your .env file')); 24 | process.exit(1); 25 | } 26 | 27 | // Create and initialize bot 28 | const bot = new BundlerBot(); 29 | await bot.initialize(); 30 | 31 | // Display wallet information 32 | bot.displayWalletInfo(); 33 | 34 | // Confirm before proceeding 35 | console.log(chalk.yellow.bold('\n⚠️ WARNING: This will execute real transactions!')); 36 | console.log(chalk.yellow('Make sure you have sufficient BNB in your main wallet.')); 37 | console.log(chalk.yellow('Press Ctrl+C to cancel, or wait 10 seconds to continue...\n')); 38 | 39 | // Wait 10 seconds before proceeding 40 | await new Promise(resolve => setTimeout(resolve, 10000)); 41 | 42 | // Execute bundling 43 | logger.info(chalk.green.bold('🚀 Starting bundling process...')); 44 | const result = await bot.executeBundle(); 45 | 46 | // Display results 47 | console.log(chalk.green.bold('\n✅ BUNDLING COMPLETED!')); 48 | console.log(chalk.cyan(`📊 Results:`)); 49 | console.log(chalk.white(` Total Transactions: ${result.totalTransactions}`)); 50 | console.log(chalk.green(` Successful: ${result.successfulTransactions}`)); 51 | console.log(chalk.red(` Failed: ${result.failedTransactions}`)); 52 | console.log(chalk.yellow(` Total BNB Spent: ${result.totalBnbSpent} BNB`)); 53 | console.log(chalk.yellow(` Total Gas Used: ${result.totalGasUsed} BNB`)); 54 | 55 | // Display failed transactions if any 56 | const failedTransactions = result.results.filter(r => !r.success); 57 | if (failedTransactions.length > 0) { 58 | console.log(chalk.red.bold('\n❌ Failed Transactions:')); 59 | failedTransactions.forEach((tx, index) => { 60 | console.log(chalk.red(` ${index + 1}. ${tx.walletAddress}: ${tx.error}`)); 61 | }); 62 | } 63 | 64 | logger.info(chalk.green.bold('🎉 Bot execution completed successfully!')); 65 | 66 | } catch (error) { 67 | logger.error(chalk.red.bold('❌ Bot execution failed:'), error); 68 | process.exit(1); 69 | } 70 | } 71 | 72 | // Handle graceful shutdown 73 | process.on('SIGINT', () => { 74 | console.log(chalk.yellow('\n⚠️ Received SIGINT. Shutting down gracefully...')); 75 | process.exit(0); 76 | }); 77 | 78 | process.on('SIGTERM', () => { 79 | console.log(chalk.yellow('\n⚠️ Received SIGTERM. Shutting down gracefully...')); 80 | process.exit(0); 81 | }); 82 | 83 | // Start the bot 84 | main().catch(error => { 85 | logger.error('Unhandled error:', error); 86 | process.exit(1); 87 | }); 88 | -------------------------------------------------------------------------------- /src/utils/wallet.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { WalletConfig } from '../types'; 3 | import { botConfig } from '../config'; 4 | import logger from './logger'; 5 | 6 | export class WalletManager { 7 | private wallets: WalletConfig[] = []; 8 | private provider: ethers.JsonRpcProvider; 9 | 10 | constructor() { 11 | this.provider = new ethers.JsonRpcProvider(botConfig.rpcUrl); 12 | } 13 | 14 | /** 15 | * Generate 50 wallets for bundling 16 | */ 17 | generateWallets(): WalletConfig[] { 18 | logger.info(`Generating ${botConfig.bundlerWalletCount} bundler wallets...`); 19 | 20 | this.wallets = []; 21 | 22 | for (let i = 0; i < botConfig.bundlerWalletCount; i++) { 23 | const wallet = ethers.Wallet.createRandom(); 24 | const buyAmount = this.generateBuyAmount(); 25 | 26 | this.wallets.push({ 27 | privateKey: wallet.privateKey, 28 | address: wallet.address, 29 | buyAmount: buyAmount, 30 | isActive: true 31 | }); 32 | } 33 | 34 | logger.info(`Generated ${this.wallets.length} wallets`); 35 | return this.wallets; 36 | } 37 | 38 | /** 39 | * Generate random buy amount within configured range 40 | */ 41 | private generateBuyAmount(): string { 42 | if (!botConfig.randomBuyAmounts) { 43 | return botConfig.minBuyAmountBnb.toString(); 44 | } 45 | 46 | const min = botConfig.minBuyAmountBnb; 47 | const max = botConfig.maxBuyAmountBnb; 48 | const randomAmount = Math.random() * (max - min) + min; 49 | 50 | return randomAmount.toFixed(6); // 6 decimal places for BNB 51 | } 52 | 53 | /** 54 | * Get all active wallets 55 | */ 56 | getActiveWallets(): WalletConfig[] { 57 | return this.wallets.filter(wallet => wallet.isActive); 58 | } 59 | 60 | /** 61 | * Get wallet by address 62 | */ 63 | getWallet(address: string): WalletConfig | undefined { 64 | return this.wallets.find(wallet => wallet.address === address); 65 | } 66 | 67 | /** 68 | * Update wallet buy amount 69 | */ 70 | updateWalletBuyAmount(address: string, amount: string): boolean { 71 | const wallet = this.getWallet(address); 72 | if (wallet) { 73 | wallet.buyAmount = amount; 74 | return true; 75 | } 76 | return false; 77 | } 78 | 79 | /** 80 | * Deactivate wallet 81 | */ 82 | deactivateWallet(address: string): boolean { 83 | const wallet = this.getWallet(address); 84 | if (wallet) { 85 | wallet.isActive = false; 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | /** 92 | * Get total BNB required for all wallets 93 | */ 94 | getTotalBnbRequired(): string { 95 | const total = this.wallets 96 | .filter(wallet => wallet.isActive) 97 | .reduce((sum, wallet) => sum + parseFloat(wallet.buyAmount), 0); 98 | 99 | return total.toFixed(6); 100 | } 101 | 102 | /** 103 | * Get wallet balance 104 | */ 105 | async getWalletBalance(address: string): Promise { 106 | try { 107 | const balance = await this.provider.getBalance(address); 108 | return ethers.formatEther(balance); 109 | } catch (error) { 110 | logger.error(`Failed to get balance for wallet ${address}:`, error); 111 | return '0'; 112 | } 113 | } 114 | 115 | /** 116 | * Get main wallet balance 117 | */ 118 | async getMainWalletBalance(): Promise { 119 | try { 120 | const balance = await this.provider.getBalance(botConfig.mainWalletAddress); 121 | return ethers.formatEther(balance); 122 | } catch (error) { 123 | logger.error('Failed to get main wallet balance:', error); 124 | return '0'; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/services/bundlerBot.ts: -------------------------------------------------------------------------------- 1 | import { WalletManager } from '../utils/wallet'; 2 | import { TransactionService } from './transactionService'; 3 | import { BundleResult, TransactionResult } from '../types'; 4 | import { botConfig, tokenLaunchConfig } from '../config'; 5 | import logger from '../utils/logger'; 6 | import chalk from 'chalk'; 7 | import cliProgress from 'cli-progress'; 8 | 9 | export class BundlerBot { 10 | private walletManager: WalletManager; 11 | private transactionService: TransactionService; 12 | private progressBar: cliProgress.SingleBar; 13 | 14 | constructor() { 15 | this.walletManager = new WalletManager(); 16 | this.transactionService = new TransactionService(); 17 | this.progressBar = new cliProgress.SingleBar({ 18 | format: 'Progress |{bar}| {percentage}% | {value}/{total} | ETA: {eta}s', 19 | barCompleteChar: '\u2588', 20 | barIncompleteChar: '\u2591', 21 | hideCursor: true 22 | }); 23 | } 24 | 25 | /** 26 | * Initialize the bot 27 | */ 28 | async initialize(): Promise { 29 | logger.info(chalk.blue.bold('🚀 Initializing BNB Bundler Bot for four.meme')); 30 | logger.info(chalk.cyan(`Token: ${tokenLaunchConfig.name} (${tokenLaunchConfig.symbol})`)); 31 | logger.info(chalk.cyan(`Network: ${botConfig.useTestnet ? 'BSC Testnet' : 'BSC Mainnet'}`)); 32 | logger.info(chalk.cyan(`Bundler Wallets: ${botConfig.bundlerWalletCount}`)); 33 | 34 | // Generate wallets 35 | this.walletManager.generateWallets(); 36 | 37 | // Check main wallet balance 38 | const mainBalance = await this.transactionService.getMainWalletBalance(); 39 | logger.info(chalk.yellow(`Main wallet balance: ${mainBalance} BNB`)); 40 | 41 | // Calculate total BNB required 42 | const totalRequired = this.walletManager.getTotalBnbRequired(); 43 | logger.info(chalk.yellow(`Total BNB required: ${totalRequired} BNB`)); 44 | 45 | if (parseFloat(mainBalance) < parseFloat(totalRequired)) { 46 | logger.error(chalk.red(`❌ Insufficient main wallet balance! Required: ${totalRequired} BNB, Available: ${mainBalance} BNB`)); 47 | throw new Error('Insufficient main wallet balance'); 48 | } 49 | } 50 | 51 | /** 52 | * Execute the bundling process 53 | */ 54 | async executeBundle(): Promise { 55 | logger.info(chalk.green.bold('🎯 Starting bundling process...')); 56 | 57 | const wallets = this.walletManager.getActiveWallets(); 58 | 59 | // Step 1: Fund bundler wallets 60 | logger.info(chalk.blue('📤 Step 1: Funding bundler wallets...')); 61 | this.progressBar.start(wallets.length, 0); 62 | 63 | const fundingResults = await this.fundBundlerWallets(wallets); 64 | this.progressBar.stop(); 65 | 66 | const successfulFunding = fundingResults.filter(r => r.success).length; 67 | logger.info(chalk.green(`✅ Successfully funded ${successfulFunding}/${wallets.length} wallets`)); 68 | 69 | if (successfulFunding === 0) { 70 | throw new Error('Failed to fund any wallets'); 71 | } 72 | 73 | // Wait a bit for transactions to be confirmed 74 | logger.info(chalk.yellow('⏳ Waiting for funding transactions to be confirmed...')); 75 | await this.delay(10000); 76 | 77 | // Step 2: Execute buy transactions 78 | logger.info(chalk.blue('🛒 Step 2: Executing buy transactions...')); 79 | this.progressBar.start(wallets.length, 0); 80 | 81 | const buyResults = await this.executeBuyTransactions(wallets); 82 | this.progressBar.stop(); 83 | 84 | logger.info(chalk.green(`✅ Buy transactions completed: ${buyResults.successfulTransactions}/${buyResults.totalTransactions} successful`)); 85 | logger.info(chalk.cyan(`💰 Total BNB spent: ${buyResults.totalBnbSpent} BNB`)); 86 | logger.info(chalk.cyan(`⛽ Total gas used: ${buyResults.totalGasUsed} BNB`)); 87 | 88 | // Step 3: Gather remaining BNB 89 | logger.info(chalk.blue('📥 Step 3: Gathering remaining BNB...')); 90 | this.progressBar.start(wallets.length, 0); 91 | 92 | const gatheringResults = await this.gatherBnbFromWallets(wallets); 93 | this.progressBar.stop(); 94 | 95 | const successfulGathering = gatheringResults.filter(r => r.success).length; 96 | logger.info(chalk.green(`✅ Successfully gathered BNB from ${successfulGathering}/${wallets.length} wallets`)); 97 | 98 | // Final summary 99 | const finalBalance = await this.transactionService.getMainWalletBalance(); 100 | logger.info(chalk.yellow(`🏦 Final main wallet balance: ${finalBalance} BNB`)); 101 | 102 | return buyResults; 103 | } 104 | 105 | /** 106 | * Fund bundler wallets 107 | */ 108 | private async fundBundlerWallets(wallets: any[]): Promise { 109 | const results: TransactionResult[] = []; 110 | 111 | for (let i = 0; i < wallets.length; i++) { 112 | const wallet = wallets[i]; 113 | try { 114 | const result = await this.transactionService.fundBundlerWallets([wallet]); 115 | results.push(...result); 116 | this.progressBar.update(i + 1); 117 | 118 | // Add delay between funding transactions 119 | await this.delay(2000); 120 | } catch (error) { 121 | logger.error(`Failed to fund wallet ${wallet.address}:`, error); 122 | results.push({ 123 | success: false, 124 | error: error instanceof Error ? error.message : 'Unknown error', 125 | walletAddress: wallet.address, 126 | amount: wallet.buyAmount 127 | }); 128 | this.progressBar.update(i + 1); 129 | } 130 | } 131 | 132 | return results; 133 | } 134 | 135 | /** 136 | * Execute buy transactions 137 | */ 138 | private async executeBuyTransactions(wallets: any[]): Promise { 139 | return await this.transactionService.executeBuyTransactions(wallets); 140 | } 141 | 142 | /** 143 | * Gather BNB from wallets 144 | */ 145 | private async gatherBnbFromWallets(wallets: any[]): Promise { 146 | return await this.transactionService.gatherBnbFromWallets(wallets); 147 | } 148 | 149 | /** 150 | * Display wallet information 151 | */ 152 | displayWalletInfo(): void { 153 | const wallets = this.walletManager.getActiveWallets(); 154 | 155 | logger.info(chalk.blue.bold('\n📋 Bundler Wallet Information:')); 156 | logger.info(chalk.cyan(`Total Wallets: ${wallets.length}`)); 157 | logger.info(chalk.cyan(`Total BNB Required: ${this.walletManager.getTotalBnbRequired()} BNB`)); 158 | 159 | if (wallets.length <= 10) { 160 | logger.info(chalk.yellow('\nWallet Details:')); 161 | wallets.forEach((wallet, index) => { 162 | logger.info(chalk.gray(`${index + 1}. ${wallet.address} - ${wallet.buyAmount} BNB`)); 163 | }); 164 | } else { 165 | logger.info(chalk.yellow('\nFirst 5 wallets:')); 166 | wallets.slice(0, 5).forEach((wallet, index) => { 167 | logger.info(chalk.gray(`${index + 1}. ${wallet.address} - ${wallet.buyAmount} BNB`)); 168 | }); 169 | logger.info(chalk.gray(`... and ${wallets.length - 5} more wallets`)); 170 | } 171 | } 172 | 173 | /** 174 | * Utility function to add delay 175 | */ 176 | private delay(ms: number): Promise { 177 | return new Promise(resolve => setTimeout(resolve, ms)); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/services/transactionService.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import { WalletConfig, TransactionResult, BundleResult } from '../types'; 3 | import { botConfig, FOUR_MEME_CONTRACT_ADDRESS, WBNB_ADDRESS } from '../config'; 4 | import logger from '../utils/logger'; 5 | 6 | export class TransactionService { 7 | private provider: ethers.JsonRpcProvider; 8 | private mainWallet: ethers.Wallet; 9 | 10 | constructor() { 11 | this.provider = new ethers.JsonRpcProvider(botConfig.rpcUrl); 12 | this.mainWallet = new ethers.Wallet(botConfig.mainWalletPrivateKey, this.provider); 13 | } 14 | 15 | /** 16 | * Fund bundler wallets from main wallet 17 | */ 18 | async fundBundlerWallets(wallets: WalletConfig[]): Promise { 19 | logger.info('Starting to fund bundler wallets...'); 20 | const results: TransactionResult[] = []; 21 | 22 | for (const wallet of wallets) { 23 | try { 24 | const amount = ethers.parseEther(wallet.buyAmount); 25 | const gasPrice = ethers.parseUnits(botConfig.gasPriceGwei.toString(), 'gwei'); 26 | 27 | const tx = await this.mainWallet.sendTransaction({ 28 | to: wallet.address, 29 | value: amount, 30 | gasPrice: gasPrice, 31 | gasLimit: 21000 32 | }); 33 | 34 | await tx.wait(); 35 | 36 | results.push({ 37 | success: true, 38 | hash: tx.hash, 39 | walletAddress: wallet.address, 40 | amount: wallet.buyAmount, 41 | gasUsed: '21000' 42 | }); 43 | 44 | logger.info(`Funded wallet ${wallet.address} with ${wallet.buyAmount} BNB`); 45 | 46 | // Add delay between transactions to avoid nonce issues 47 | await this.delay(1000); 48 | 49 | } catch (error) { 50 | logger.error(`Failed to fund wallet ${wallet.address}:`, error); 51 | results.push({ 52 | success: false, 53 | error: error instanceof Error ? error.message : 'Unknown error', 54 | walletAddress: wallet.address, 55 | amount: wallet.buyAmount 56 | }); 57 | } 58 | } 59 | 60 | return results; 61 | } 62 | 63 | /** 64 | * Execute buy transactions for all bundler wallets 65 | */ 66 | async executeBuyTransactions(wallets: WalletConfig[]): Promise { 67 | logger.info('Starting buy transaction bundle...'); 68 | 69 | const results: TransactionResult[] = []; 70 | let successfulTransactions = 0; 71 | let failedTransactions = 0; 72 | let totalGasUsed = BigInt(0); 73 | let totalBnbSpent = BigInt(0); 74 | 75 | for (const wallet of wallets) { 76 | try { 77 | const walletInstance = new ethers.Wallet(wallet.privateKey, this.provider); 78 | const result = await this.executeBuyTransaction(walletInstance, wallet); 79 | 80 | results.push(result); 81 | 82 | if (result.success) { 83 | successfulTransactions++; 84 | if (result.gasUsed) { 85 | totalGasUsed += BigInt(result.gasUsed); 86 | } 87 | totalBnbSpent += ethers.parseEther(wallet.buyAmount); 88 | } else { 89 | failedTransactions++; 90 | } 91 | 92 | // Add delay between transactions 93 | await this.delay(500); 94 | 95 | } catch (error) { 96 | logger.error(`Failed to execute buy transaction for wallet ${wallet.address}:`, error); 97 | results.push({ 98 | success: false, 99 | error: error instanceof Error ? error.message : 'Unknown error', 100 | walletAddress: wallet.address, 101 | amount: wallet.buyAmount 102 | }); 103 | failedTransactions++; 104 | } 105 | } 106 | 107 | return { 108 | totalTransactions: wallets.length, 109 | successfulTransactions, 110 | failedTransactions, 111 | totalGasUsed: ethers.formatEther(totalGasUsed), 112 | totalBnbSpent: ethers.formatEther(totalBnbSpent), 113 | results 114 | }; 115 | } 116 | 117 | /** 118 | * Execute single buy transaction 119 | */ 120 | private async executeBuyTransaction( 121 | wallet: ethers.Wallet, 122 | walletConfig: WalletConfig 123 | ): Promise { 124 | try { 125 | // This is a placeholder for the actual four.meme contract interaction 126 | // You'll need to implement the actual contract ABI and method calls 127 | const contractABI = [ 128 | "function buy() external payable", 129 | "function buyTokens(uint256 amount) external payable" 130 | ]; 131 | 132 | const contract = new ethers.Contract(FOUR_MEME_CONTRACT_ADDRESS, contractABI, wallet); 133 | const gasPrice = ethers.parseUnits(botConfig.gasPriceGwei.toString(), 'gwei'); 134 | const value = ethers.parseEther(walletConfig.buyAmount); 135 | 136 | // Estimate gas for the transaction 137 | const gasEstimate = await contract.buy.estimateGas({ value }); 138 | 139 | const tx = await contract.buy({ 140 | value: value, 141 | gasPrice: gasPrice, 142 | gasLimit: gasEstimate 143 | }); 144 | 145 | const receipt = await tx.wait(); 146 | 147 | return { 148 | success: true, 149 | hash: tx.hash, 150 | walletAddress: wallet.address, 151 | amount: walletConfig.buyAmount, 152 | gasUsed: receipt?.gasUsed.toString() || '0' 153 | }; 154 | 155 | } catch (error) { 156 | logger.error(`Buy transaction failed for wallet ${wallet.address}:`, error); 157 | return { 158 | success: false, 159 | error: error instanceof Error ? error.message : 'Unknown error', 160 | walletAddress: wallet.address, 161 | amount: walletConfig.buyAmount 162 | }; 163 | } 164 | } 165 | 166 | /** 167 | * Gather remaining BNB from bundler wallets back to main wallet 168 | */ 169 | async gatherBnbFromWallets(wallets: WalletConfig[]): Promise { 170 | logger.info('Starting to gather BNB from bundler wallets...'); 171 | const results: TransactionResult[] = []; 172 | 173 | for (const wallet of wallets) { 174 | try { 175 | const walletInstance = new ethers.Wallet(wallet.privateKey, this.provider); 176 | const balance = await this.provider.getBalance(wallet.address); 177 | 178 | if (balance === 0n) { 179 | logger.info(`Wallet ${wallet.address} has no balance to gather`); 180 | continue; 181 | } 182 | 183 | const gasPrice = ethers.parseUnits(botConfig.gasPriceGwei.toString(), 'gwei'); 184 | const gasCost = gasPrice * BigInt(21000); 185 | const amountToSend = balance - gasCost; 186 | 187 | if (amountToSend <= 0n) { 188 | logger.info(`Wallet ${wallet.address} has insufficient balance for gas`); 189 | continue; 190 | } 191 | 192 | const tx = await walletInstance.sendTransaction({ 193 | to: botConfig.mainWalletAddress, 194 | value: amountToSend, 195 | gasPrice: gasPrice, 196 | gasLimit: 21000 197 | }); 198 | 199 | await tx.wait(); 200 | 201 | results.push({ 202 | success: true, 203 | hash: tx.hash, 204 | walletAddress: wallet.address, 205 | amount: ethers.formatEther(amountToSend), 206 | gasUsed: '21000' 207 | }); 208 | 209 | logger.info(`Gathered ${ethers.formatEther(amountToSend)} BNB from wallet ${wallet.address}`); 210 | 211 | // Add delay between transactions 212 | await this.delay(1000); 213 | 214 | } catch (error) { 215 | logger.error(`Failed to gather BNB from wallet ${wallet.address}:`, error); 216 | results.push({ 217 | success: false, 218 | error: error instanceof Error ? error.message : 'Unknown error', 219 | walletAddress: wallet.address, 220 | amount: '0' 221 | }); 222 | } 223 | } 224 | 225 | return results; 226 | } 227 | 228 | /** 229 | * Get main wallet balance 230 | */ 231 | async getMainWalletBalance(): Promise { 232 | try { 233 | const balance = await this.provider.getBalance(botConfig.mainWalletAddress); 234 | return ethers.formatEther(balance); 235 | } catch (error) { 236 | logger.error('Failed to get main wallet balance:', error); 237 | return '0'; 238 | } 239 | } 240 | 241 | /** 242 | * Utility function to add delay 243 | */ 244 | private delay(ms: number): Promise { 245 | return new Promise(resolve => setTimeout(resolve, ms)); 246 | } 247 | } 248 | --------------------------------------------------------------------------------