├── 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 | |  |  |
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 |
23 | https://bscscan.com/address/0x2e828b4910333f6d211055b5717a8de0e8eb2058
24 |
25 |
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 |
--------------------------------------------------------------------------------