├── .gitignore ├── tsconfig.json ├── .env.example ├── package.json ├── README.md ├── testIndex.ts ├── buyAfteTakingConditon.ts ├── new.ts ├── index.ts └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /node_modules -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // "module": "esnext", 4 | // "moduleResolution": "bundler", 5 | "module": "commonjs", 6 | "target": "es2020", 7 | "lib": ["es6"], 8 | "allowJs": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "esModuleInterop": true, 12 | "resolveJsonModule": false, 13 | "baseUrl": "." 14 | } 15 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | BLOXROUTE_AUTH = "" 2 | TARGET_CREATOR = "" 3 | SEND_BNB = "" 4 | EMERGENCY_SELL_TIMEOUT = "" 5 | TAKE_PROFIT_PERCENT = "" 6 | 7 | SNIPER_CONTRACT_ADDRESS = "" 8 | # WS_PROVIDER_URL = "wss://billowing-solitary-sponge.bsc.quiknode.pro/d1daf19446f76e47396933aaf9e80f7053b9a8e3" 9 | RPC_PROVIDER_URL = "" 10 | WS_PROVIDER_URL = "" 11 | SEND_BNB = 0.001 12 | 13 | TOKEN_NAME_CONTAINS = 14 | 15 | MIN_LIQUIDITY_BNB = 1 16 | 17 | PRIVATE_KEY = "" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dextra_bsc", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "ts-node-dev index.ts" 7 | }, 8 | "dependencies": { 9 | "dotenv": "^17.2.3", 10 | "ethers": "^6.15.0", 11 | "ws": "^8.18.0" 12 | }, 13 | "devDependencies": { 14 | "@types/express": "^5.0.0", 15 | "@types/node": "^22.13.4", 16 | "@types/ws": "^8.5.13", 17 | "@validate-ethereum-address/core": "^1.0.6", 18 | "ts-node": "^10.9.2", 19 | "ts-node-dev": "^2.0.0", 20 | "typescript": "^5.7.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BNB Sniper Bot - Pancakeswap Edition 2 | 3 | Advanced BNB Chain sniper bot for detecting and buying tokens instantly on Pancakeswap platform. 4 | 5 | ## Features 6 | 7 | - **Real-time Token Detection**: Monitors PancakeSwap factory for new token pair creations 8 | - **Auto-Buy**: Instantly purchases tokens upon detection with competitive gas pricing 9 | - **Auto-Sell**: Monitors price and automatically sells when take-profit target is reached 10 | - **Multi-RPC Broadcasting**: Submits transactions to multiple RPC endpoints for faster propagation 11 | - **Atomic Nonce Management**: Prevents race conditions with proper nonce locking 12 | - **Emergency Sell**: Automatic sell after timeout to prevent stuck positions 13 | - **Trade Metrics**: Tracks profit percentage, duration, and entry/exit prices 14 | - **Error Recovery**: Automatic unlock mechanism prevents bot from getting stuck 15 | 16 | ## Configuration 17 | 18 | ### Environment Variables 19 | 20 | Required: 21 | - `PRIVATE_KEY` - Your wallet private key 22 | - `SNIPER_CONTRACT_ADDRESS` - Your sniper contract address 23 | - `WS_PROVIDER_URL` - WebSocket provider URL for event monitoring 24 | - `RPC_PROVIDER_URL` - RPC provider URL for transactions 25 | - `SEND_BNB` - Amount of BNB to send per trade (e.g., "0.0005"). Default: "0.000001" 26 | 27 | Optional: 28 | - `TAKE_PROFIT_PERCENT` - Take profit percentage (default: 5). Bot will sell when price reaches this percentage above entry 29 | - `EMERGENCY_SELL_TIMEOUT` - Timeout in milliseconds before emergency sell triggers (default: 600000 = 10 minutes) 30 | - `TOKEN_NAME_CONTAINS` - Filter out tokens by name (see Token Filtering section) - Available in index.ts 31 | - `MIN_LIQUIDITY_BNB` - Minimum liquidity filter (see Liquidity Filtering section) - Available in index.ts 32 | 33 | ### Take Profit Configuration 34 | 35 | The bot automatically monitors the price after buying and sells when the target is reached: 36 | 37 | ```env 38 | # Sell when price reaches +5% above entry (default) 39 | TAKE_PROFIT_PERCENT=5 40 | 41 | # Sell when price reaches +10% above entry 42 | TAKE_PROFIT_PERCENT=10 43 | 44 | # Sell when price reaches +20% above entry 45 | TAKE_PROFIT_PERCENT=20 46 | ``` 47 | 48 | ### Emergency Sell Timeout 49 | 50 | If the take-profit target is not reached within the timeout period, the bot will automatically sell to prevent stuck positions: 51 | 52 | ```env 53 | # Emergency sell after 5 minutes (300000 ms) 54 | EMERGENCY_SELL_TIMEOUT=300000 55 | 56 | # Emergency sell after 10 minutes (600000 ms) - default 57 | EMERGENCY_SELL_TIMEOUT=600000 58 | 59 | # Emergency sell after 15 minutes (900000 ms) 60 | EMERGENCY_SELL_TIMEOUT=900000 61 | ``` 62 | 63 | ### Token Filtering (Optional - Available in index.ts) 64 | 65 | You can filter out tokens by name before buying. Add this to your `.env` file: 66 | 67 | - `TOKEN_NAME_CONTAINS` - Tokens with names containing these strings will be **SKIPPED** (case-insensitive) 68 | - Multiple keywords can be separated by commas 69 | - The bot will **SKIP** tokens if the name contains **ANY** of the specified keywords 70 | - The bot will **BUY** all other tokens that don't match the filter 71 | 72 | **Examples:** 73 | ```env 74 | # Skip tokens where name contains "Moon" (buy everything else) 75 | TOKEN_NAME_CONTAINS=Moon 76 | 77 | # Skip tokens where name contains "Moon" OR "DOGE" OR "Pepe" (buy everything else) 78 | TOKEN_NAME_CONTAINS=Moon,DOGE,Pepe 79 | 80 | # Skip tokens where name contains "Safe" OR "Moon" OR "Rocket" (buy everything else) 81 | TOKEN_NAME_CONTAINS=Safe,Moon,Rocket 82 | ``` 83 | 84 | **Note:** 85 | - If `TOKEN_NAME_CONTAINS` is not set or empty, the bot will buy all detected tokens 86 | - Filtering is case-insensitive 87 | - Keywords are separated by commas (spaces around commas are automatically trimmed) 88 | - This is a **blacklist** filter - tokens matching the keywords are excluded 89 | 90 | ### Liquidity Filtering (Optional - Available in index.ts) 91 | 92 | You can filter tokens by minimum liquidity before buying. Add this to your `.env` file: 93 | 94 | - `MIN_LIQUIDITY_BNB` - Minimum total liquidity required in BNB (e.g., "1.0" for 1 BNB minimum) 95 | - Pools with less liquidity than this will be skipped 96 | - Total liquidity = 2 × WBNB reserve (since AMM pools maintain equal value on both sides) 97 | 98 | **Examples:** 99 | ```env 100 | # Only buy tokens with at least 1 BNB total liquidity 101 | MIN_LIQUIDITY_BNB=1.0 102 | 103 | # Only buy tokens with at least 5 BNB total liquidity 104 | MIN_LIQUIDITY_BNB=5.0 105 | 106 | # Only buy tokens with at least 0.5 BNB total liquidity 107 | MIN_LIQUIDITY_BNB=0.5 108 | ``` 109 | 110 | **Note:** 111 | - If `MIN_LIQUIDITY_BNB` is not set or 0, liquidity filtering is disabled 112 | - The bot calculates total liquidity as 2 × WBNB reserve 113 | - This helps avoid low-liquidity tokens that may be rug pulls or have high slippage 114 | 115 | ## How It Works 116 | 117 | 1. **Event Monitoring**: Bot listens to `PairCreated` events from PancakeSwap Factory contract via WebSocket 118 | 2. **Token Detection**: When a new WBNB pair is created, the bot immediately identifies the new token 119 | 3. **Buy Execution**: Bot executes a buy transaction with competitive gas pricing and multi-RPC broadcasting 120 | 4. **Price Monitoring**: After buy confirmation, bot polls pair reserves every second to track price 121 | 5. **Take Profit**: When price reaches the target percentage, bot automatically executes sell 122 | 6. **Emergency Exit**: If target not reached within timeout, bot sells to prevent stuck positions 123 | 7. **State Management**: Bot locks during active trades and unlocks after completion or failure 124 | 125 | ## Trade Metrics 126 | 127 | The bot tracks and logs: 128 | - Entry price 129 | - Exit price 130 | - Profit percentage 131 | - Trade duration 132 | - Buy and sell transaction hashes 133 | 134 | ## Error Handling 135 | 136 | - Automatic nonce synchronization to prevent transaction failures 137 | - State unlock mechanism prevents bot from getting stuck 138 | - Retry logic for failed sell attempts (up to 3 attempts) 139 | - Graceful error recovery with proper state cleanup 140 | 141 | ## Development Status 142 | 143 | - Monitoring (Completed) 144 | - Sniping (Completed) 145 | - Sniping as first buyer (Completed) 146 | - Selling logic (Completed) 147 | - Token filtering (Available in index.ts) 148 | - Liquidity filtering (Available in index.ts) 149 | - Trade metrics (Completed) 150 | - Emergency sell timeout (Completed) 151 | 152 | ## Contact 153 | 154 | For inquiries, custom integrations, or tailored solutions, reach out via: 155 | 156 | Telegram: [@trum3it](https://t.me/trum3it) 157 | -------------------------------------------------------------------------------- /testIndex.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import * as dotenv from 'dotenv'; 3 | dotenv.config(); 4 | 5 | // BSC Mainnet addresses 6 | const ADDRESSES = { 7 | WBNB: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 8 | FACTORY: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73' as const, // PancakeSwap V2 Factory 9 | ROUTER: '0x10ED43C718714eb63d5aA57B78B54704E256024E' as const, // PancakeSwap V2 Router 10 | QUOTER_V2: '0xB048Bbc1B6aAD7B1bB7987a14F6d34bE1FBE9F6E' as const // QuoterV2 11 | } as const; 12 | 13 | // Sniper Contract address (REQUIRED - deploy first and set in .env) 14 | const SNIPER_CONTRACT_ADDRESS = process.env.SNIPER_CONTRACT_ADDRESS; 15 | 16 | if (!SNIPER_CONTRACT_ADDRESS) { 17 | throw new Error('SNIPER_CONTRACT_ADDRESS not set in .env file! Deploy the contract first.'); 18 | } 19 | 20 | // Primary QuickNode WebSocket URL (for critical operations - event monitoring, swaps) 21 | const PROVIDER_URL = process.env.WS_PROVIDER_URL!; 22 | const RPC_PROVIDER_URL = process.env.RPC_PROVIDER_URL!; 23 | 24 | // Multiple BSC RPC endpoints for multi-broadcast (faster propagation) 25 | const BSC_RPC_ENDPOINTS = [ 26 | RPC_PROVIDER_URL, 27 | // 'https://bsc-dataseed1.binance.org', 28 | // 'https://bsc-dataseed2.binance.org', 29 | // 'https://bsc-dataseed3.binance.org', 30 | // 'https://bsc-dataseed4.binance.org', 31 | // 'https://bsc-dataseed1.defibit.io', 32 | // 'https://bsc-dataseed2.defibit.io', 33 | // 'https://bsc-dataseed1.ninicoin.io', 34 | // 'https://bsc-dataseed2.ninicoin.io', 35 | ]; 36 | 37 | // Private key from env (REQUIRED for swaps) 38 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 39 | 40 | if (!PRIVATE_KEY) { 41 | throw new Error('PRIVATE_KEY not set in .env file!'); 42 | } 43 | 44 | const provider = new ethers.WebSocketProvider(PROVIDER_URL); 45 | const wallet = new ethers.Wallet(PRIVATE_KEY!, provider); 46 | 47 | // Minimal ABI for Factory (unchanged) 48 | const FACTORY_ABI = [ 49 | 'event PairCreated(address indexed token0, address indexed token1, address pair, uint)', 50 | 'function allPairsLength() view returns (uint)', 51 | ] as const; 52 | 53 | const PANCAKE_ROUTER_ABI = [ 54 | 'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns(uint[] memory amounts)', 55 | 'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)' 56 | ] as const 57 | 58 | const PAIR_ABI = [ 59 | 'function getReserves() external view returns (uint112,uint112,uint32)', 60 | 'function token0() external view returns (address)', 61 | 'function token1() external view returns (address)', 62 | ] as const; 63 | 64 | // ERC20 ABI (for approve & balance) 65 | const ERC20_ABI = [ 66 | 'function approve(address spender, uint256 amount) returns (bool)', 67 | 'function allowance(address owner, address spender) view returns (uint256)', 68 | 'function balanceOf(address) view returns (uint256)', 69 | 'function decimals() view returns (uint8)', 70 | ] as const; 71 | 72 | // Quoter ABI (for price estimation) 73 | const QUOTER_ABI = [ 74 | 'function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut)', 75 | ] as const; 76 | 77 | // ABI for SniperContract buy function 78 | const SNIPER_ABI = [ 79 | 'function buy(address token, uint256 amountIn, uint256 deadlineOffset) external', 80 | ] as const; 81 | 82 | // Initialize multiple RPC providers for broadcasting 83 | const rpcProviders: ethers.JsonRpcProvider[] = BSC_RPC_ENDPOINTS.map( 84 | url => new ethers.JsonRpcProvider(url) 85 | ); 86 | 87 | // OPTIMIZED: Multi-RPC broadcast - Submit signed tx to multiple RPCs simultaneously 88 | async function submitRawTx(rawTx: string, timeoutMs = 5000): Promise { 89 | const start = Date.now(); 90 | // Add 0x prefix if missing 91 | const txWithPrefix = rawTx.startsWith('0x') ? rawTx : '0x' + rawTx; 92 | console.log(' Broadcasting to', rpcProviders.length, 'RPC endpoints...'); 93 | // Submit to all RPCs simultaneously 94 | const submissions = rpcProviders.map(async (provider, index) => { 95 | try { 96 | const response = await Promise.race([ 97 | provider.broadcastTransaction(txWithPrefix), 98 | new Promise((_, reject) => 99 | setTimeout(() => reject(new Error('Timeout')), timeoutMs) 100 | ) 101 | ]); 102 | if (!response || !response.hash) { 103 | throw new Error('No transaction hash returned'); 104 | } 105 | 106 | console.log(`RPC ${index + 1} accepted (${Date.now() - start}ms)`); 107 | return { success: true, hash: response.hash, provider: index }; 108 | } catch (err: any) { 109 | console.log(`RPC ${index + 1} failed: ${err.message.substring(0, 50)}`); 110 | return { success: false, error: err.message, provider: index }; 111 | } 112 | }); 113 | 114 | // Wait for first successful response 115 | const results = await Promise.allSettled(submissions); 116 | // Find first successful submission 117 | for (const result of results) { 118 | if (result.status === 'fulfilled' && result.value.success && result.value.hash) { 119 | const txHash = result.value.hash; 120 | console.log('Transaction broadcast successful: ${ txHash }'); 121 | 122 | // Let other submissions complete in background (don't await) 123 | Promise.allSettled(submissions).then(() => { 124 | const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length; 125 | console.log(` Final: ${successCount}/${rpcProviders.length} RPCs accepted tx`); 126 | }); 127 | 128 | return txHash; 129 | } 130 | } 131 | // All failed 132 | throw new Error('All RPC endpoints failed to broadcast transaction'); 133 | } 134 | 135 | // === APPROVE TOKEN === 136 | async function approveToken(token: string, amount: bigint): Promise { 137 | const tokenContract = new ethers.Contract(token, ERC20_ABI, wallet); 138 | const allowance = await tokenContract.allowance(wallet.address, ADDRESSES.ROUTER); 139 | 140 | if (allowance >= amount) { 141 | console.log('Already approved: ${ token }'); 142 | return; 143 | } 144 | console.log('Approving ${ token }...'); 145 | const tx = await tokenContract.approve(ADDRESSES.ROUTER, ethers.MaxUint256); 146 | await tx.wait(1); 147 | console.log('Approved: ${ tx.hash }'); 148 | } 149 | 150 | // === GET BALANCE === 151 | async function getTokenBalance(token: string): Promise { 152 | const contract = new ethers.Contract(token, ERC20_ABI, provider); 153 | return await contract.balanceOf(wallet.address); 154 | } 155 | 156 | async function main() { 157 | console.log(' Starting Multi-RPC sniper...'); 158 | console.log('Configured ${ rpcProviders.length } RPC endpoints for broadcasting'); 159 | // Connect to BSC via WebSocket for events (read-only) 160 | console.log('Connected to BSC WebSocket provider (event monitoring)...'); 161 | 162 | // Initialize Factory for events (read-only) 163 | const factory = new ethers.Contract(ADDRESSES.FACTORY, FACTORY_ABI, provider); 164 | 165 | // Initialize SniperContract with signer for swaps 166 | const sniperContract = new ethers.Contract(SNIPER_CONTRACT_ADDRESS!, SNIPER_ABI, wallet); 167 | const routerContract = new ethers.Contract(ADDRESSES.ROUTER, PANCAKE_ROUTER_ABI, wallet); 168 | console.log('Wallet address: ${ wallet.address }'); 169 | console.log('Sniper Contract: ${ SNIPER_CONTRACT_ADDRESS }'); 170 | 171 | // NEW: Manual nonce management for speed 172 | let currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 173 | console.log('Starting nonce: ${ currentNonce }'); 174 | 175 | // OPTIMIZED: Lightning-fast swap function (BNB → New Token via SniperContract) - Multi-RPC Broadcast 176 | async function executeSwap(newToken: string, amountInBNB: string, deadlineMinutes = 3, competitiveGas = false) { 177 | const start = Date.now(); try { 178 | console.log(`SWAP: ${amountInBNB} BNB → ${newToken} (via PancakeSwap Router)`); 179 | 180 | const amountIn = ethers.parseEther(amountInBNB); 181 | 182 | // CORRECT: deadline = current Unix time + X minutes 183 | const deadline = BigInt(Math.floor(Date.now() / 1000)) + BigInt(deadlineMinutes * 60); 184 | 185 | // OPTIMIZATION: Dynamic gas pricing 186 | const gasLimit = 300000n; 187 | let gasPrice: bigint; 188 | 189 | if (competitiveGas) { 190 | const feeData = await provider.getFeeData(); 191 | const networkGas = feeData.gasPrice || ethers.parseUnits('3', 'gwei'); 192 | gasPrice = (networkGas * 150n) / 100n; 193 | console.log(` Using COMPETITIVE gas: ${ethers.formatUnits(gasPrice, 'gwei')} Gwei`); 194 | } else { 195 | gasPrice = ethers.parseUnits('3', 'gwei'); 196 | } 197 | 198 | // ---- READ ONCE ---- 199 | const nonce = currentNonce; 200 | 201 | // CORRECT: Use proper deadline (timestamp), not offset 202 | const txRequest = await routerContract.swapExactETHForTokens.populateTransaction( 203 | 0, // amountOutMin = 0 (slippage tolerance: accept any output) 204 | [ADDRESSES.WBNB, newToken], 205 | wallet.address, 206 | deadline, // ← NOW A VALID FUTURE TIMESTAMP 207 | { 208 | value: amountIn, 209 | gasLimit, 210 | gasPrice, 211 | nonce, 212 | chainId: 56n, 213 | } 214 | ); 215 | 216 | const signedTx = await wallet.signTransaction(txRequest); 217 | const rawTx = signedTx.slice(2); 218 | 219 | const signTime = Date.now() - start; 220 | const txHash = await submitRawTx(rawTx, 5000); 221 | 222 | // ---- INCREMENT ONLY HERE ---- 223 | currentNonce++; 224 | 225 | const submitTime = Date.now() - start; 226 | console.log(`Buy submitted (sign: ${signTime}ms, submit: ${submitTime}ms): ${txHash}`); 227 | 228 | // ---- NEW: schedule sell 1 s after buy ---- 229 | scheduleSellAfterBuy(newToken, amountIn, txHash).catch(console.error); 230 | 231 | // Background confirmation 232 | provider.waitForTransaction(txHash, 1, 45000) 233 | .then(async (receipt) => { 234 | if (receipt?.status === 1) { 235 | console.log(`Confirmed in block ${receipt.blockNumber}`); 236 | } 237 | }) 238 | .catch(err => { 239 | console.error(`Confirmation timeout for ${txHash}: ${err.message}`); 240 | }); 241 | 242 | return { txHash, success: true, submitTime }; 243 | 244 | } catch (error: any) { 245 | console.error(`Buy failed (${Date.now() - start}ms): ${error.message}`); 246 | 247 | if (error.message.includes('nonce') || error.message.includes('already known')) { 248 | console.log('Resyncing nonce...'); 249 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 250 | } 251 | 252 | return { success: false, error: error.message, time: Date.now() - start }; 253 | } 254 | } 255 | 256 | // ---- sell after Buy ---- 257 | async function scheduleSellAfterBuy( 258 | token: string, 259 | amountInBNB: bigint, 260 | buyTxHash: string 261 | ) { 262 | // 1. Wait for the buy to be mined 263 | const receipt = await provider.waitForTransaction(buyTxHash, 1, 60_000); 264 | if (!receipt || receipt.status !== 1) { 265 | console.log('Buy ${ buyTxHash } failed or reverted - dropped – no sell'); 266 | return; 267 | } 268 | console.log('Buy confirmed in block ${ receipt.blockNumber }'); 269 | // 2. Get the exact token amount we received 270 | const tokenBalance = await getTokenBalance(token); 271 | if (tokenBalance === 0n) { 272 | console.log(`Zero token balance after buy – nothing to sell`); 273 | return; 274 | } 275 | 276 | // 3. Wait 1 seconds 277 | await new Promise(r => setTimeout(r, 1000)); 278 | // 4. Approve router (if needed) 279 | await approveToken(token, tokenBalance); 280 | 281 | // 5. Build sell tx (swapExactTokensForETH) 282 | const deadline = BigInt(Math.floor(Date.now() / 1000) + 180); 283 | const gasPrice = ethers.parseUnits('3', 'gwei'); // you can make it dynamic 284 | 285 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 286 | // ---- READ ONCE (after buy nonce already incremented) ---- 287 | const nonce = currentNonce; 288 | 289 | const txReq = await routerContract.swapExactTokensForETH.populateTransaction( 290 | tokenBalance, // amountIn = what we own 291 | 0n, // amountOutMin = 0 → accept any BNB (you said “don’t care”) 292 | [token, ADDRESSES.WBNB], 293 | wallet.address, 294 | deadline, 295 | { 296 | gasLimit: 250_000n, 297 | gasPrice, 298 | nonce, 299 | chainId: 56n, 300 | } 301 | ); 302 | 303 | const signed = await wallet.signTransaction(txReq); 304 | const raw = signed.slice(2); 305 | 306 | try { 307 | const sellHash = await submitRawTx(raw, 5_000); 308 | currentNonce++; // ← ONLY ONE INCREMENT 309 | console.log(`SELL submitted ${sellHash} (≈${ethers.formatEther(tokenBalance)} tokens)`); 310 | 311 | } catch (e: any) { 312 | console.error(`SELL FAILED: ${e.message}`); 313 | // rollback nonce on broadcast error 314 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 315 | } 316 | } // OPTIMIZED: Subscribe to PairCreated events with fast filtering 317 | factory.on('PairCreated', async (token0: string, token1: string, pair: string, event: any) => { 318 | const eventStart = Date.now();// OPTIMIZATION: Fast lowercase comparison with pre-computed constant 319 | const token0Lower = token0.toLowerCase(); 320 | const token1Lower = token1.toLowerCase(); 321 | const wbnbLower = ADDRESSES.WBNB.toLowerCase(); 322 | 323 | // Quick filter: Must be WBNB pair 324 | if (token0Lower !== wbnbLower && token1Lower !== wbnbLower) { 325 | return; // Skip non-WBNB pairs instantly 326 | } 327 | 328 | const newToken = token0Lower === wbnbLower ? token1 : token0; 329 | 330 | // Target found! Log and execute 331 | console.log(`\n TARGET DETECTED (${Date.now() - eventStart}ms from event)`); 332 | console.log(` Pair: ${pair}`); 333 | console.log(` Token: ${newToken}`); 334 | console.log(` Block: ${event.log?.blockNumber || 'unknown'}`); 335 | console.log(` Time: ${new Date().toISOString()}`); 336 | console.log('─'.repeat(100)); 337 | 338 | // CRITICAL: Execute swap immediately(normal gas - pair already created) 339 | executeSwap(newToken, '0.00000001', 3, false).catch(err => { 340 | console.error('Swap execution error:', err.message); 341 | }); 342 | }); 343 | 344 | 345 | setInterval(async () => { 346 | try { 347 | const pending = await provider.getTransactionCount(wallet.address, 'pending'); 348 | if (pending > currentNonce) { 349 | console.log('Nonce drift → ${ currentNonce } → ${ pending }'); 350 | currentNonce = pending; 351 | } 352 | } catch { } 353 | }, 8_000); 354 | 355 | // Graceful shutdown 356 | process.on('SIGINT', () => { 357 | console.log('\n Shutting down...'); 358 | provider.destroy(); 359 | process.exit(0); 360 | }); 361 | } 362 | 363 | main().catch(console.error); 364 | 365 | -------------------------------------------------------------------------------- /buyAfteTakingConditon.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import * as dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | interface TradeMetrics { 7 | buyTime: number; 8 | buyTxHash: string; 9 | sellTxHash?: string; 10 | entryPrice: bigint; 11 | exitPrice?: bigint; 12 | profitPercent?: number; 13 | duration?: number; 14 | } 15 | 16 | let isSnipingActive = false; 17 | let currentTargetToken: string | null = null; 18 | let nonceLock = false; 19 | const tradeMetrics = new Map(); 20 | 21 | const ADDRESSES = { 22 | WBNB: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 23 | FACTORY: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73', 24 | ROUTER: '0x10ED43C718714eb63d5aA57B78B54704E256024E', 25 | QUOTER_V2: '0xB048Bbc1B6aAD7B1bB7987a14F6d34bE1FBE9F6E' 26 | }; 27 | 28 | const SNIPER_CONTRACT_ADDRESS = process.env.SNIPER_CONTRACT_ADDRESS; 29 | if (!SNIPER_CONTRACT_ADDRESS) { 30 | throw new Error('SNIPER_CONTRACT_ADDRESS not set in .env file'); 31 | } 32 | 33 | const PROVIDER_URL = process.env.WS_PROVIDER_URL!; 34 | const RPC_PROVIDER_URL = process.env.RPC_PROVIDER_URL!; 35 | const SEND_BNB = process.env.SEND_BNB || '0.000001'; 36 | const EMERGENCY_SELL_TIMEOUT = parseInt(process.env.EMERGENCY_SELL_TIMEOUT || '600000'); 37 | const TAKE_PROFIT_PERCENT = parseInt(process.env.TAKE_PROFIT_PERCENT || '5'); 38 | 39 | const BSC_RPC_ENDPOINTS = [RPC_PROVIDER_URL]; 40 | 41 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 42 | if (!PRIVATE_KEY) { 43 | throw new Error('PRIVATE_KEY not set in .env file'); 44 | } 45 | 46 | const provider = new ethers.WebSocketProvider(PROVIDER_URL); 47 | const wallet = new ethers.Wallet(PRIVATE_KEY, provider); 48 | 49 | const FACTORY_ABI = [ 50 | 'event PairCreated(address indexed token0, address indexed token1, address pair, uint)', 51 | 'function allPairsLength() view returns (uint)', 52 | ] as const; 53 | 54 | const PANCAKE_ROUTER_ABI = [ 55 | 'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns(uint[] memory amounts)', 56 | 'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)' 57 | ] as const; 58 | 59 | const PAIR_ABI = [ 60 | 'function getReserves() external view returns (uint112,uint112,uint32)', 61 | 'function token0() external view returns (address)', 62 | 'function token1() external view returns (address)', 63 | ] as const; 64 | 65 | const ERC20_ABI = [ 66 | 'function approve(address spender, uint256 amount) returns (bool)', 67 | 'function allowance(address owner, address spender) view returns (uint256)', 68 | 'function balanceOf(address) view returns (uint256)', 69 | 'function decimals() view returns (uint8)', 70 | ] as const; 71 | 72 | const SNIPER_ABI = [ 73 | 'function buy(address token, uint256 amountIn, uint256 deadlineOffset) external', 74 | ] as const; 75 | 76 | const rpcProviders: ethers.JsonRpcProvider[] = BSC_RPC_ENDPOINTS.map( 77 | url => new ethers.JsonRpcProvider(url) 78 | ); 79 | 80 | async function submitRawTx(rawTx: string, timeoutMs = 5000): Promise { 81 | const txWithPrefix = rawTx.startsWith('0x') ? rawTx : '0x' + rawTx; 82 | 83 | const submissions = rpcProviders.map(async (provider, index) => { 84 | try { 85 | const response = await Promise.race([ 86 | provider.broadcastTransaction(txWithPrefix), 87 | new Promise((_, reject) => 88 | setTimeout(() => reject(new Error('Timeout')), timeoutMs) 89 | ) 90 | ]); 91 | 92 | if (!response || !response.hash) { 93 | throw new Error('No transaction hash returned'); 94 | } 95 | 96 | return { success: true, hash: response.hash, provider: index }; 97 | } catch (err: any) { 98 | return { success: false, error: err.message, provider: index }; 99 | } 100 | }); 101 | 102 | const results = await Promise.allSettled(submissions); 103 | 104 | for (const result of results) { 105 | if (result.status === 'fulfilled' && result.value.success && result.value.hash) { 106 | const txHash = result.value.hash; 107 | const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length; 108 | console.log(`Transaction broadcast: ${txHash} (${successCount}/${rpcProviders.length} RPCs)`); 109 | return txHash; 110 | } 111 | } 112 | 113 | throw new Error('All RPC endpoints failed to broadcast transaction'); 114 | } 115 | 116 | async function approveToken(token: string, amount: bigint): Promise { 117 | const tokenContract = new ethers.Contract(token, ERC20_ABI, wallet); 118 | const allowance = await tokenContract.allowance(wallet.address, ADDRESSES.ROUTER); 119 | 120 | if (allowance >= amount) { 121 | return; 122 | } 123 | 124 | const tx = await tokenContract.approve(ADDRESSES.ROUTER, ethers.MaxUint256); 125 | await tx.wait(1); 126 | } 127 | 128 | async function getTokenBalance(token: string): Promise { 129 | const contract = new ethers.Contract(token, ERC20_ABI, provider); 130 | return await contract.balanceOf(wallet.address); 131 | } 132 | 133 | async function getCurrentPriceFromReserves(pairAddress: string, tokenIsToken1: boolean): Promise { 134 | try { 135 | const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, provider); 136 | const [reserve0, reserve1] = await pairContract.getReserves(); 137 | 138 | const reserveWBNB = tokenIsToken1 ? reserve0 : reserve1; 139 | const reserveToken = tokenIsToken1 ? reserve1 : reserve0; 140 | 141 | if (reserveToken === 0n) return 0n; 142 | 143 | return (BigInt(reserveWBNB) * 1_000_000n * 10n ** 18n) / BigInt(reserveToken); 144 | } catch (e) { 145 | return 0n; 146 | } 147 | } 148 | 149 | function unlockSniper(token: string) { 150 | if (currentTargetToken === token || !isSnipingActive) { 151 | isSnipingActive = false; 152 | currentTargetToken = null; 153 | console.log(`Sniper unlocked for token: ${token}`); 154 | } 155 | } 156 | 157 | async function getGasPrice(competitive: boolean): Promise { 158 | if (competitive) { 159 | const feeData = await provider.getFeeData(); 160 | const networkGas = feeData.gasPrice || ethers.parseUnits('3', 'gwei'); 161 | return (networkGas * 150n) / 100n; 162 | } 163 | return ethers.parseUnits('3', 'gwei'); 164 | } 165 | 166 | async function main() { 167 | console.log('Starting sniper bot...'); 168 | console.log(`Wallet: ${wallet.address}`); 169 | console.log(`RPC endpoints: ${rpcProviders.length}`); 170 | 171 | const factory = new ethers.Contract(ADDRESSES.FACTORY, FACTORY_ABI, provider); 172 | const routerContract = new ethers.Contract(ADDRESSES.ROUTER, PANCAKE_ROUTER_ABI, wallet); 173 | 174 | async function executeSell( 175 | token: string, 176 | tokenBalance: bigint, 177 | pairAddress: string, 178 | tokenIsToken1: boolean, 179 | reason: string, 180 | nonce: number 181 | ): Promise<{ success: boolean; txHash?: string }> { 182 | try { 183 | await approveToken(token, tokenBalance); 184 | 185 | const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); 186 | const gasPrice = await getGasPrice(true); 187 | 188 | const txReq = await routerContract.swapExactTokensForETH.populateTransaction( 189 | tokenBalance, 190 | 0n, 191 | [token, ADDRESSES.WBNB], 192 | wallet.address, 193 | deadline, 194 | { 195 | gasLimit: 350000n, 196 | gasPrice, 197 | nonce, 198 | chainId: 56n, 199 | } 200 | ); 201 | 202 | const signed = await wallet.signTransaction(txReq); 203 | const sellTxHash = await submitRawTx(signed.slice(2)); 204 | 205 | const exitPrice = await getCurrentPriceFromReserves(pairAddress, tokenIsToken1); 206 | const metrics = tradeMetrics.get(token); 207 | if (metrics && exitPrice > 0n) { 208 | metrics.sellTxHash = sellTxHash; 209 | metrics.exitPrice = exitPrice; 210 | if (metrics.entryPrice > 0n) { 211 | metrics.profitPercent = Number((exitPrice * 10000n / metrics.entryPrice - 10000n)) / 100; 212 | } 213 | metrics.duration = Date.now() - metrics.buyTime; 214 | } 215 | 216 | console.log(`Sell executed (${reason}): ${sellTxHash}`); 217 | if (metrics && metrics.profitPercent !== undefined) { 218 | console.log(`Profit: ${metrics.profitPercent.toFixed(2)}% | Duration: ${((metrics.duration || 0) / 1000).toFixed(1)}s`); 219 | } 220 | 221 | return { success: true, txHash: sellTxHash }; 222 | } catch (err: any) { 223 | console.error(`Sell failed: ${err.message}`); 224 | return { success: false }; 225 | } 226 | } 227 | 228 | let currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 229 | console.log(`Starting nonce: ${currentNonce}`); 230 | 231 | async function executeSwap(newToken: string, amountInBNB: string, deadlineMinutes = 3, competitiveGas = false) { 232 | const start = Date.now(); 233 | 234 | try { 235 | const amountIn = ethers.parseEther(amountInBNB); 236 | const deadline = BigInt(Math.floor(Date.now() / 1000)) + BigInt(deadlineMinutes * 60); 237 | const gasLimit = 300000n; 238 | const gasPrice = await getGasPrice(competitiveGas); 239 | 240 | while (nonceLock) { 241 | await new Promise(r => setTimeout(r, 10)); 242 | } 243 | 244 | nonceLock = true; 245 | const nonce = currentNonce; 246 | currentNonce++; 247 | nonceLock = false; 248 | 249 | const txRequest = await routerContract.swapExactETHForTokens.populateTransaction( 250 | 0, 251 | [ADDRESSES.WBNB, newToken], 252 | wallet.address, 253 | deadline, 254 | { 255 | value: amountIn, 256 | gasLimit, 257 | gasPrice, 258 | nonce, 259 | chainId: 56n, 260 | } 261 | ); 262 | 263 | const signedTx = await wallet.signTransaction(txRequest); 264 | const rawTx = signedTx.slice(2); 265 | const txHash = await submitRawTx(rawTx, 5000); 266 | 267 | const submitTime = Date.now() - start; 268 | console.log(`Buy submitted (${submitTime}ms): ${txHash}`); 269 | 270 | tradeMetrics.set(newToken, { 271 | buyTime: Date.now(), 272 | buyTxHash: txHash, 273 | entryPrice: 0n 274 | }); 275 | 276 | provider.waitForTransaction(txHash, 1, 45000) 277 | .then(async (receipt) => { 278 | if (receipt?.status === 1) { 279 | console.log(`Buy confirmed: block ${receipt.blockNumber}`); 280 | } 281 | }) 282 | .catch(err => { 283 | console.error(`Buy confirmation timeout: ${err.message}`); 284 | }); 285 | 286 | return { txHash, success: true, submitTime }; 287 | 288 | } catch (error: any) { 289 | nonceLock = false; 290 | console.error(`Buy failed (${Date.now() - start}ms): ${error.message}`); 291 | 292 | if (error.message.includes('nonce') || error.message.includes('already known')) { 293 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 294 | } 295 | 296 | return { success: false, error: error.message, time: Date.now() - start }; 297 | } 298 | } 299 | 300 | async function scheduleSellAfterBuy( 301 | token: string, 302 | buyTxHash: string, 303 | pairAddress: string, 304 | tokenIsToken1: boolean 305 | ) { 306 | let receipt; 307 | try { 308 | receipt = await provider.waitForTransaction(buyTxHash, 1, 90000); 309 | } catch (err: any) { 310 | console.log(`Buy transaction failed: ${buyTxHash}`); 311 | unlockSniper(token); 312 | return; 313 | } 314 | 315 | if (!receipt || receipt.status !== 1) { 316 | console.log(`Buy reverted: ${buyTxHash}`); 317 | unlockSniper(token); 318 | return; 319 | } 320 | 321 | console.log(`Buy confirmed: block ${receipt.blockNumber}`); 322 | 323 | const tokenBalance = await getTokenBalance(token); 324 | if (tokenBalance <= 0n) { 325 | console.log(`No tokens received`); 326 | unlockSniper(token); 327 | return; 328 | } 329 | 330 | const initialPrice = await getCurrentPriceFromReserves(pairAddress, tokenIsToken1); 331 | if (initialPrice === 0n) { 332 | console.log(`Could not fetch initial price`); 333 | unlockSniper(token); 334 | return; 335 | } 336 | 337 | const metrics = tradeMetrics.get(token); 338 | if (metrics) { 339 | metrics.entryPrice = initialPrice; 340 | } 341 | 342 | const targetPrice = (initialPrice * BigInt(100 + TAKE_PROFIT_PERCENT)) / 100n; 343 | console.log(`Monitoring: entry ${ethers.formatUnits(initialPrice, 18)} | target +${TAKE_PROFIT_PERCENT}%`); 344 | 345 | let highestPriceSeen = initialPrice; 346 | let sold = false; 347 | let sellAttempts = 0; 348 | const maxSellAttempts = 3; 349 | 350 | async function attemptSell(reason: string) { 351 | if (sold) return; 352 | sold = true; 353 | clearInterval(interval); 354 | 355 | while (nonceLock) { 356 | await new Promise(r => setTimeout(r, 10)); 357 | } 358 | 359 | nonceLock = true; 360 | const nonce = currentNonce; 361 | currentNonce++; 362 | nonceLock = false; 363 | 364 | const result = await executeSell(token, tokenBalance, pairAddress, tokenIsToken1, reason, nonce); 365 | 366 | if (result.success) { 367 | unlockSniper(token); 368 | } else { 369 | sellAttempts++; 370 | if (sellAttempts < maxSellAttempts) { 371 | sold = false; 372 | console.log(`Retrying sell (attempt ${sellAttempts + 1}/${maxSellAttempts})...`); 373 | setTimeout(() => attemptSell(reason), 2000); 374 | } else { 375 | console.log(`Max sell attempts reached, unlocking`); 376 | unlockSniper(token); 377 | } 378 | } 379 | } 380 | 381 | const interval = setInterval(async () => { 382 | if (sold) return; 383 | 384 | const currentPrice = await getCurrentPriceFromReserves(pairAddress, tokenIsToken1); 385 | if (currentPrice === 0n) return; 386 | 387 | if (currentPrice > highestPriceSeen) { 388 | highestPriceSeen = currentPrice; 389 | const gain = Number((currentPrice * 10000n / initialPrice - 10000n)) / 100; 390 | console.log(`New high: +${gain.toFixed(2)}%`); 391 | } 392 | 393 | if (currentPrice >= targetPrice) { 394 | await attemptSell('Take profit target reached'); 395 | } 396 | }, 1000); 397 | 398 | setTimeout(async () => { 399 | if (!sold) { 400 | await attemptSell('Emergency timeout'); 401 | } 402 | }, EMERGENCY_SELL_TIMEOUT); 403 | } 404 | 405 | factory.on('PairCreated', async (token0: string, token1: string, pair: string) => { 406 | const token0Lower = token0.toLowerCase(); 407 | const token1Lower = token1.toLowerCase(); 408 | const wbnbLower = ADDRESSES.WBNB.toLowerCase(); 409 | 410 | if (token0Lower !== wbnbLower && token1Lower !== wbnbLower) { 411 | return; 412 | } 413 | 414 | const newToken = token0Lower === wbnbLower ? token1 : token0; 415 | const tokenIsToken1 = token0Lower === wbnbLower; 416 | 417 | if (isSnipingActive) { 418 | console.log(`Skipping ${newToken} - already sniping ${currentTargetToken}`); 419 | return; 420 | } 421 | 422 | isSnipingActive = true; 423 | currentTargetToken = newToken; 424 | 425 | console.log(`Target detected: ${newToken}`); 426 | console.log(`Pair: ${pair}`); 427 | 428 | const result = await executeSwap(newToken, SEND_BNB, 3, true); 429 | 430 | if (result.success && result.txHash) { 431 | scheduleSellAfterBuy(newToken, result.txHash, pair, tokenIsToken1).catch(err => { 432 | console.error(`Sell monitor error: ${err.message}`); 433 | unlockSniper(newToken); 434 | }); 435 | } else { 436 | console.log(`Buy failed, unlocking`); 437 | unlockSniper(newToken); 438 | } 439 | }); 440 | 441 | setInterval(async () => { 442 | try { 443 | const pending = await provider.getTransactionCount(wallet.address, 'pending'); 444 | if (pending > currentNonce) { 445 | console.log(`Nonce sync: ${currentNonce} -> ${pending}`); 446 | currentNonce = pending; 447 | } 448 | } catch (err) { 449 | // silent 450 | } 451 | }, 8000); 452 | 453 | process.on('SIGINT', () => { 454 | console.log('Shutting down...'); 455 | provider.destroy(); 456 | process.exit(0); 457 | }); 458 | } 459 | 460 | main().catch(console.error); 461 | -------------------------------------------------------------------------------- /new.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import * as dotenv from 'dotenv'; 3 | 4 | // Load env vars (e.g., PRIVATE_KEY, SNIPER_CONTRACT_ADDRESS) 5 | dotenv.config(); 6 | 7 | // === PROFIT TRACKING === 8 | // interface Trade { 9 | // token: string; 10 | // bnbSpent: bigint; // tokens bought 11 | // buyTxHash: string; 12 | // buyTime: number; 13 | // tokenBalance: bigint; // Updated after confirm 14 | // } 15 | 16 | // const activeTrades = new Map(); // token → trade 17 | 18 | // === GLOBAL STATE LOCK === 19 | let isSnipingActive = false; // ← This prevents multiple concurrent snipes 20 | let currentTargetToken: string | null = null; 21 | 22 | // BSC Mainnet addresses 23 | const ADDRESSES = { 24 | WBNB: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 25 | FACTORY: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73' as const, // PancakeSwap V2 Factory 26 | ROUTER: '0x10ED43C718714eb63d5aA57B78B54704E256024E' as const, // PancakeSwap V2 Router 27 | QUOTER_V2: '0xB048Bbc1B6aAD7B1bB7987a14F6d34bE1FBE9F6E' as const // QuoterV2 28 | } as const; 29 | 30 | // Sniper Contract address (REQUIRED - deploy first and set in .env) 31 | const SNIPER_CONTRACT_ADDRESS = process.env.SNIPER_CONTRACT_ADDRESS; 32 | if (!SNIPER_CONTRACT_ADDRESS) { 33 | throw new Error('SNIPER_CONTRACT_ADDRESS not set in .env file! Deploy the contract first.'); 34 | } 35 | 36 | // Primary QuickNode WebSocket URL (for critical operations - event monitoring, swaps) 37 | const PROVIDER_URL = process.env.WS_PROVIDER_URL!; 38 | const RPC_PROVIDER_URL = process.env.RPC_PROVIDER_URL!; 39 | 40 | // Multiple BSC RPC endpoints for multi-broadcast (faster propagation) 41 | const BSC_RPC_ENDPOINTS = [ 42 | RPC_PROVIDER_URL, 43 | // 'https://bsc-dataseed1.binance.org', 44 | // 'https://bsc-dataseed2.binance.org', 45 | // 'https://bsc-dataseed3.binance.org', 46 | // 'https://bsc-dataseed4.binance.org', 47 | // 'https://bsc-dataseed1.defibit.io', 48 | // 'https://bsc-dataseed2.defibit.io', 49 | // 'https://bsc-dataseed1.ninicoin.io', 50 | // 'https://bsc-dataseed2.ninicoin.io', 51 | ]; 52 | 53 | // Private key from env (REQUIRED for swaps) 54 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 55 | if (!PRIVATE_KEY) { 56 | throw new Error('PRIVATE_KEY not set in .env file!'); 57 | } 58 | 59 | const provider = new ethers.WebSocketProvider(PROVIDER_URL); 60 | 61 | const wallet = new ethers.Wallet(PRIVATE_KEY!, provider); 62 | 63 | // Minimal ABI for Factory (unchanged) 64 | const FACTORY_ABI = [ 65 | 'event PairCreated(address indexed token0, address indexed token1, address pair, uint)', 66 | 'function allPairsLength() view returns (uint)', 67 | ] as const; 68 | 69 | const PANCAKE_ROUTER_ABI = [ 70 | 'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns(uint[] memory amounts)', 71 | 'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)' 72 | ] as const 73 | 74 | const PAIR_ABI = [ 75 | 'function getReserves() external view returns (uint112,uint112,uint32)', 76 | 'function token0() external view returns (address)', 77 | 'function token1() external view returns (address)', 78 | ] as const; 79 | 80 | // ERC20 ABI (for approve & balance) 81 | const ERC20_ABI = [ 82 | 'function approve(address spender, uint256 amount) returns (bool)', 83 | 'function allowance(address owner, address spender) view returns (uint256)', 84 | 'function balanceOf(address) view returns (uint256)', 85 | 'function decimals() view returns (uint8)', 86 | ] as const; 87 | 88 | // Quoter ABI (for price estimation) 89 | const QUOTER_ABI = [ 90 | 'function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut)', 91 | ] as const; 92 | 93 | // ABI for SniperContract buy function 94 | const SNIPER_ABI = [ 95 | 'function buy(address token, uint256 amountIn, uint256 deadlineOffset) external', 96 | ] as const; 97 | 98 | // Initialize multiple RPC providers for broadcasting 99 | const rpcProviders: ethers.JsonRpcProvider[] = BSC_RPC_ENDPOINTS.map( 100 | url => new ethers.JsonRpcProvider(url) 101 | ); 102 | 103 | 104 | // OPTIMIZED: Multi-RPC broadcast - Submit signed tx to multiple RPCs simultaneously 105 | async function submitRawTx(rawTx: string, timeoutMs = 5000): Promise { 106 | const start = Date.now(); 107 | 108 | // Add 0x prefix if missing 109 | const txWithPrefix = rawTx.startsWith('0x') ? rawTx : '0x' + rawTx; 110 | 111 | console.log('📤 Broadcasting to', rpcProviders.length, 'RPC endpoints...'); 112 | 113 | // Submit to all RPCs simultaneously 114 | const submissions = rpcProviders.map(async (provider, index) => { 115 | try { 116 | const response = await Promise.race([ 117 | provider.broadcastTransaction(txWithPrefix), 118 | new Promise((_, reject) => 119 | setTimeout(() => reject(new Error('Timeout')), timeoutMs) 120 | ) 121 | ]); 122 | 123 | if (!response || !response.hash) { 124 | throw new Error('No transaction hash returned'); 125 | } 126 | 127 | console.log(` ✅ RPC ${index + 1} accepted (${Date.now() - start}ms)`); 128 | return { success: true, hash: response.hash, provider: index }; 129 | } catch (err: any) { 130 | console.log(` ⚠️ RPC ${index + 1} failed: ${err.message.substring(0, 50)}`); 131 | return { success: false, error: err.message, provider: index }; 132 | } 133 | }); 134 | 135 | // Wait for first successful response 136 | const results = await Promise.allSettled(submissions); 137 | 138 | // Find first successful submission 139 | for (const result of results) { 140 | if (result.status === 'fulfilled' && result.value.success && result.value.hash) { 141 | const txHash = result.value.hash; 142 | console.log(`✅ Transaction broadcast successful: ${txHash}`); 143 | 144 | // Let other submissions complete in background (don't await) 145 | Promise.allSettled(submissions).then(() => { 146 | const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length; 147 | console.log(`📊 Final: ${successCount}/${rpcProviders.length} RPCs accepted tx`); 148 | }); 149 | 150 | return txHash; 151 | } 152 | } 153 | 154 | // All failed 155 | throw new Error('All RPC endpoints failed to broadcast transaction'); 156 | } 157 | 158 | // === APPROVE TOKEN === 159 | async function approveToken(token: string, amount: bigint): Promise { 160 | const tokenContract = new ethers.Contract(token, ERC20_ABI, wallet); 161 | const allowance = await tokenContract.allowance(wallet.address, ADDRESSES.ROUTER); 162 | 163 | if (allowance >= amount) { 164 | console.log(`Already approved: ${token}`); 165 | return; 166 | } 167 | 168 | console.log(`Approving ${token}...`); 169 | const tx = await tokenContract.approve(ADDRESSES.ROUTER, ethers.MaxUint256); 170 | await tx.wait(1); 171 | console.log(`Approved: ${tx.hash}`); 172 | } 173 | 174 | // === GET BALANCE === 175 | async function getTokenBalance(token: string): Promise { 176 | const contract = new ethers.Contract(token, ERC20_ABI, provider); 177 | return await contract.balanceOf(wallet.address); 178 | } 179 | 180 | // === GET CURRENT PRICE FROM RESERVES (WBNB / Token) === 181 | async function getCurrentPriceFromReserves(pairAddress: string, tokenIsToken1: boolean): Promise { 182 | try { 183 | const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, provider); 184 | const [reserve0, reserve1] = await pairContract.getReserves(); 185 | 186 | // Depending on token order: (WBNB, Token) or (Token, WBNB) 187 | const reserveWBNB = tokenIsToken1 ? reserve0 : reserve1; 188 | const reserveToken = tokenIsToken1 ? reserve1 : reserve0; 189 | 190 | if (reserveToken === 0n) return 0n; 191 | 192 | // Price in BNB per 1e18 tokens (fixed point) 193 | // price = reserveWBNB / reserveToken → but scaled to 1e18 194 | console.log("token price ==>> ", ethers.formatUnits((BigInt(reserveWBNB) * 1_000_000n * 10n ** 18n) / BigInt(reserveToken), 18)); 195 | 196 | return (BigInt(reserveWBNB) * 1_000_000n * 10n ** 18n) / BigInt(reserveToken); // 6 decimals precision boost 197 | 198 | } catch (e) { 199 | return 0n; 200 | } 201 | } 202 | 203 | 204 | async function main() { 205 | console.log('🚀 Starting Multi-RPC sniper...'); 206 | console.log(`📡 Configured ${rpcProviders.length} RPC endpoints for broadcasting`); 207 | 208 | // Connect to BSC via WebSocket for events (read-only) 209 | console.log('Connected to BSC WebSocket provider (event monitoring)...'); 210 | 211 | // Initialize Factory for events (read-only) 212 | const factory = new ethers.Contract(ADDRESSES.FACTORY, FACTORY_ABI, provider); 213 | 214 | // Initialize SniperContract with signer for swaps 215 | const sniperContract = new ethers.Contract(SNIPER_CONTRACT_ADDRESS!, SNIPER_ABI, wallet); 216 | const routerContract = new ethers.Contract(ADDRESSES.ROUTER, PANCAKE_ROUTER_ABI, wallet); 217 | 218 | console.log(`Wallet address: ${wallet.address}`); 219 | console.log(`Sniper Contract: ${SNIPER_CONTRACT_ADDRESS}`); 220 | 221 | // NEW: Manual nonce management for speed 222 | let currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 223 | console.log(`Starting nonce: ${currentNonce}`); 224 | 225 | // OPTIMIZED: Lightning-fast swap function (BNB → New Token via SniperContract) - Multi-RPC Broadcast 226 | async function executeSwap(newToken: string, amountInBNB: string, deadlineMinutes = 3, competitiveGas = false) { 227 | const start = Date.now(); 228 | 229 | try { 230 | console.log(`SWAP: ${amountInBNB} BNB → ${newToken} (via PancakeSwap Router)`); 231 | 232 | const amountIn = ethers.parseEther(amountInBNB); 233 | 234 | // CORRECT: deadline = current Unix time + X minutes 235 | const deadline = BigInt(Math.floor(Date.now() / 1000)) + BigInt(deadlineMinutes * 60); 236 | 237 | // OPTIMIZATION: Dynamic gas pricing 238 | const gasLimit = 300000n; 239 | let gasPrice: bigint; 240 | 241 | if (competitiveGas) { 242 | const feeData = await provider.getFeeData(); 243 | const networkGas = feeData.gasPrice || ethers.parseUnits('3', 'gwei'); 244 | gasPrice = (networkGas * 150n) / 100n; 245 | console.log(` Using COMPETITIVE gas: ${ethers.formatUnits(gasPrice, 'gwei')} Gwei`); 246 | } else { 247 | gasPrice = ethers.parseUnits('3', 'gwei'); 248 | } 249 | 250 | // ---- READ ONCE ---- 251 | const nonce = currentNonce; 252 | 253 | // CORRECT: Use proper deadline (timestamp), not offset 254 | const txRequest = await routerContract.swapExactETHForTokens.populateTransaction( 255 | 0, // amountOutMin = 0 (slippage tolerance: accept any output) 256 | [ADDRESSES.WBNB, newToken], 257 | wallet.address, 258 | deadline, // ← NOW A VALID FUTURE TIMESTAMP 259 | { 260 | value: amountIn, 261 | gasLimit, 262 | gasPrice, 263 | nonce, 264 | chainId: 56n, 265 | } 266 | ); 267 | 268 | const signedTx = await wallet.signTransaction(txRequest); 269 | const rawTx = signedTx.slice(2); 270 | 271 | const signTime = Date.now() - start; 272 | const txHash = await submitRawTx(rawTx, 5000); 273 | 274 | // ---- INCREMENT ONLY HERE ---- 275 | currentNonce++; 276 | 277 | const submitTime = Date.now() - start; 278 | console.log(`Buy submitted (sign: ${signTime}ms, submit: ${submitTime}ms): ${txHash}`); 279 | 280 | // ---- NEW: schedule sell 1 s after buy ---- 281 | // scheduleSellAfterBuy(newToken, amountIn, txHash).catch(console.error); 282 | 283 | // Background confirmation 284 | provider.waitForTransaction(txHash, 1, 45000) 285 | .then(async (receipt) => { 286 | if (receipt?.status === 1) { 287 | console.log(`Confirmed in block ${receipt.blockNumber}`); 288 | } 289 | }) 290 | .catch(err => { 291 | console.error(`Confirmation timeout for ${txHash}: ${err.message}`); 292 | }); 293 | 294 | return { txHash, success: true, submitTime }; 295 | 296 | } catch (error: any) { 297 | console.error(`Buy failed (${Date.now() - start}ms): ${error.message}`); 298 | 299 | if (error.message.includes('nonce') || error.message.includes('already known')) { 300 | console.log('Resyncing nonce...'); 301 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 302 | } 303 | 304 | return { success: false, error: error.message, time: Date.now() - start }; 305 | } 306 | } 307 | 308 | // ---- sell after Buy ---- 309 | // async function scheduleSellAfterBuy( 310 | // token: string, 311 | // amountInBNB: bigint, 312 | // buyTxHash: string, 313 | // pairAddress: string, 314 | // tokenIsToken1: boolean // true if token is token1 (i.e. token0 = WBNB) 315 | // ) { 316 | // // 1. Wait for the buy to be mined 317 | // const receipt = await provider.waitForTransaction(buyTxHash, 1, 60_000); 318 | // if (!receipt || receipt.status !== 1) { 319 | // console.log(`Buy ${buyTxHash} failed or reverted-dropped – no sell`); 320 | // return; 321 | // } 322 | // console.log(`Buy confirmed in block ${receipt.blockNumber}`); 323 | 324 | // // 2. Get the exact token amount we received 325 | // const tokenBalance = await getTokenBalance(token); 326 | // if (tokenBalance === 0n) { 327 | // console.log(`Zero token balance after buy – nothing to sell`); 328 | // return; 329 | // } 330 | 331 | // const initialPrice = await getCurrentPriceFromReserves(pairAddress, tokenIsToken1); 332 | // if (initialPrice === 0n) { 333 | // console.log(`Could not fetch initial price – skipping profit tracking`); 334 | // return; 335 | // } 336 | // console.log(`Monitoring profit for ${token}`); 337 | // console.log(`Entry price: ${ethers.formatUnits(initialPrice, 18)} BNB per 1 Token (scaled)`); 338 | // console.log(`Bought: ${ethers.formatUnits(tokenBalance, 18)} tokens with ${ethers.formatEther(amountInBNB)} BNB`); 339 | 340 | // const targetPrice = (initialPrice * 120n) / 100n; // +20% 341 | // console.log(`Take-profit target: +20% → ${ethers.formatUnits(targetPrice, 18)} BNB/Token`); 342 | 343 | // let highestPriceSeen = initialPrice; 344 | // let sold = false; 345 | 346 | // // 3. Wait 1 seconds 347 | // await new Promise(r => setTimeout(r, 1000)); 348 | 349 | // // 4. Approve router (if needed) 350 | // await approveToken(token, tokenBalance); 351 | 352 | // // 5. Build sell tx (swapExactTokensForETH) 353 | // const deadline = BigInt(Math.floor(Date.now() / 1000) + 180); 354 | // const gasPrice = ethers.parseUnits('3', 'gwei'); // you can make it dynamic 355 | 356 | // currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 357 | // // ---- READ ONCE (after buy nonce already incremented) ---- 358 | // const nonce = currentNonce; 359 | 360 | // const txReq = await routerContract.swapExactTokensForETH.populateTransaction( 361 | // tokenBalance, // amountIn = what we own 362 | // 0n, // amountOutMin = 0 → accept any BNB (you said “don’t care”) 363 | // [token, ADDRESSES.WBNB], 364 | // wallet.address, 365 | // deadline, 366 | // { 367 | // gasLimit: 250_000n, 368 | // gasPrice, 369 | // nonce, 370 | // chainId: 56n, 371 | // } 372 | // ); 373 | 374 | // const signed = await wallet.signTransaction(txReq); 375 | // const raw = signed.slice(2); 376 | 377 | // try { 378 | // const sellHash = await submitRawTx(raw, 5_000); 379 | // currentNonce++; // ← ONLY ONE INCREMENT 380 | // console.log(`SELL submitted ${sellHash} (≈${ethers.formatEther(tokenBalance)} tokens)`); 381 | 382 | // // optional: clean up trade map 383 | // // activeTrades.delete(token); 384 | // } catch (e: any) { 385 | // console.error(`SELL FAILED: ${e.message}`); 386 | // // rollback nonce on broadcast error 387 | // currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 388 | // } 389 | // } 390 | 391 | async function scheduleSellAfterBuy( 392 | token: string, 393 | buyTxHash: string, 394 | pairAddress: string, 395 | tokenIsToken1: boolean // true if token is token1 (i.e. token0 = WBNB) 396 | ) { 397 | // 1. Wait for buy confirmation 398 | const receipt = await provider.waitForTransaction(buyTxHash, 1, 90_000); 399 | if (!receipt || receipt.status !== 1) { 400 | console.log(`Buy failed/reverted: ${buyTxHash}`); 401 | return; 402 | } 403 | console.log(`Buy confirmed in block ${receipt.blockNumber}`); 404 | 405 | // 2. Get exact tokens received 406 | const tokenBalance = await getTokenBalance(token); 407 | if (tokenBalance <= 0n) { 408 | console.log(`No tokens received – aborting sell monitor`); 409 | return; 410 | } 411 | 412 | // 3. Get initial price (our entry price) 413 | const initialPrice = await getCurrentPriceFromReserves(pairAddress, tokenIsToken1); 414 | if (initialPrice === 0n) { 415 | console.log(`Could not fetch initial price – skipping profit tracking`); 416 | return; 417 | } 418 | 419 | console.log(`Monitoring profit for ${token}`); 420 | console.log(`Entry price: ${ethers.formatUnits(initialPrice, 18)} BNB per 1 Token (scaled)`); 421 | // console.log(`Bought: ${ethers.formatUnits(tokenBalance, 18)} tokens with ${ethers.formatEther(amountInBNB)} BNB`); 422 | 423 | const targetPrice = (initialPrice * 110n) / 100n; // +20% 424 | console.log(`Take-profit target: +20% → ${ethers.formatUnits(targetPrice, 18)} BNB/Token`); 425 | 426 | let highestPriceSeen = initialPrice; 427 | let sold = false; 428 | 429 | // === POLL RESERVES EVERY 1 SECOND (super fast) === 430 | const interval = setInterval(async () => { 431 | if (sold) return; 432 | 433 | const currentPrice = await getCurrentPriceFromReserves(pairAddress, tokenIsToken1); 434 | if (currentPrice === 0n) return; 435 | 436 | // Update peak 437 | if (currentPrice > highestPriceSeen) { 438 | highestPriceSeen = currentPrice; 439 | console.log(`New high: ${ethers.formatUnits(currentPrice, 18)} BNB/Token (+${((Number(currentPrice) * 100 / Number(initialPrice)) - 100).toFixed(1)}%)`); 440 | } 441 | 442 | // === TAKE PROFIT CONDITION === 443 | if (currentPrice >= targetPrice) { 444 | clearInterval(interval); 445 | sold = true; 446 | console.log(`TARGET HIT! Selling at +20% or more...`); 447 | 448 | await approveToken(token, tokenBalance); 449 | 450 | const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); 451 | 452 | try { 453 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 454 | const nonce = currentNonce++; 455 | 456 | const gasPrice = ethers.parseUnits('5', 'gwei'); // bump gas on sell 457 | 458 | const txReq = await routerContract.swapExactTokensForETH.populateTransaction( 459 | tokenBalance, 460 | 0n, // accept any output 461 | [token, ADDRESSES.WBNB], 462 | wallet.address, 463 | deadline, 464 | { 465 | gasLimit: 350000n, 466 | gasPrice, 467 | nonce, 468 | } 469 | ); 470 | 471 | const signed = await wallet.signTransaction(txReq); 472 | const txHash = await submitRawTx(signed.slice(2)); 473 | 474 | console.log(`SELL EXECUTED at +20%+: ${txHash}`); 475 | console.log(`Profit locked at ~${((Number(currentPrice) * 100 / Number(initialPrice)) - 100).toFixed(1)}%`); 476 | } catch (err: any) { 477 | console.error(`SELL FAILED: ${err.message}`); 478 | sold = false; // retry next tick? 479 | } 480 | } 481 | }, 1000); // every 1 second 482 | 483 | // Optional: emergency sell after 10 minutes if no TP(take profit) 484 | setTimeout(async () => { 485 | if (!sold) { 486 | clearInterval(interval); 487 | console.log(`Time limit reached – dumping position`); 488 | // same sell logic... 489 | } 490 | }, 10 * 60 * 1000); 491 | } 492 | 493 | // OPTIMIZED: Subscribe to PairCreated events with fast filtering 494 | factory.on('PairCreated', async (token0: string, token1: string, pair: string, event: any) => { 495 | const eventStart = Date.now(); 496 | 497 | // OPTIMIZATION: Fast lowercase comparison with pre-computed constant 498 | const token0Lower = token0.toLowerCase(); 499 | const token1Lower = token1.toLowerCase(); 500 | const wbnbLower = ADDRESSES.WBNB.toLowerCase(); 501 | 502 | // Quick filter: Must be WBNB pair 503 | if (token0Lower !== wbnbLower && token1Lower !== wbnbLower) { 504 | return; // Skip non-WBNB pairs instantly 505 | } 506 | 507 | const newToken = token0Lower === wbnbLower ? token1 : token0; 508 | const tokenIsToken1 = token0Lower === wbnbLower; // if true → token1 is new token 509 | 510 | // Target found! Log and execute 511 | console.log(`\n🚨 TARGET DETECTED (${Date.now() - eventStart}ms from event)`); 512 | console.log(` Pair: ${pair}`); 513 | console.log(` Token: ${newToken}`); 514 | console.log(` Block: ${event.log?.blockNumber || 'unknown'}`); 515 | console.log(` Time: ${new Date().toISOString()}`); 516 | console.log('─'.repeat(100)); 517 | 518 | const result = await executeSwap(newToken, '0.000001', 3, true); // increase size if you want 519 | 520 | if (result.success && result.txHash) { 521 | // Pass pair + order info to sell monitor 522 | scheduleSellAfterBuy(newToken, result.txHash, pair, tokenIsToken1) 523 | .catch(console.error); 524 | } 525 | 526 | // CRITICAL: Execute swap immediately(normal gas - pair already created) 527 | // executeSwap(newToken, '0.00000001', 3, false).catch(err => { 528 | // console.error('Swap execution error:', err.message); 529 | // }); 530 | }); 531 | 532 | 533 | setInterval(async () => { 534 | try { 535 | const pending = await provider.getTransactionCount(wallet.address, 'pending'); 536 | if (pending > currentNonce) { 537 | console.log(`Nonce drift → ${currentNonce} → ${pending}`); 538 | currentNonce = pending; 539 | } 540 | } catch { } 541 | }, 8_000); 542 | 543 | // Graceful shutdown 544 | process.on('SIGINT', () => { 545 | console.log('\n Shutting down...'); 546 | provider.destroy(); 547 | process.exit(0); 548 | }); 549 | } 550 | 551 | main().catch(console.error); 552 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import * as dotenv from 'dotenv'; 3 | import { isAddress } from '@validate-ethereum-address/core'; 4 | 5 | 6 | // Load env vars (e.g., PRIVATE_KEY, SNIPER_CONTRACT_ADDRESS) 7 | dotenv.config(); 8 | 9 | // === GLOBAL STATE LOCK === 10 | let isSnipingActive = false; // ← This prevents multiple concurrent snipes 11 | let currentTargetToken: string | null = null; 12 | let isListeningToEvents = false; // Track if event listener is active 13 | 14 | // === STATUS LOGGING === 15 | function logBotStatus() { 16 | const status = { 17 | 'Sniping Active': isSnipingActive ? '✅ YES' : '❌ NO', 18 | 'Current Target': currentTargetToken || 'None', 19 | 'Event Listener': isListeningToEvents ? '✅ ACTIVE' : '❌ INACTIVE', 20 | }; 21 | 22 | console.log('\n📊 Bot Status:'); 23 | console.log('─'.repeat(50)); 24 | Object.entries(status).forEach(([key, value]) => { 25 | console.log(` ${key}: ${value}`); 26 | }); 27 | console.log('─'.repeat(50) + '\n'); 28 | } 29 | 30 | const sendBNB = String(process.env.SEND_BNB); 31 | 32 | // Token filter configuration (from .env) 33 | // Set TOKEN_NAME_CONTAINS to filter OUT tokens by name containing string(s) (case-insensitive) 34 | // Multiple keywords can be separated by commas - tokens containing ANY of them will be SKIPPED 35 | // Leave empty to disable filtering (buy all tokens) 36 | // Example: "Moon,DOGE,Pepe" will SKIP tokens containing "Moon" OR "DOGE" OR "Pepe" 37 | const TOKEN_NAME_CONTAINS = process.env.TOKEN_NAME_CONTAINS?.trim() || ''; 38 | const TOKEN_NAME_KEYWORDS = TOKEN_NAME_CONTAINS 39 | ? TOKEN_NAME_CONTAINS.split(',').map(k => k.trim()).filter(k => k.length > 0) 40 | : []; 41 | 42 | // Minimum liquidity filter (from .env) 43 | // Set MIN_LIQUIDITY_BNB to minimum WBNB liquidity required (in BNB, e.g., "1.0" for 1 BNB) 44 | // Pools with less WBNB liquidity than this will be skipped 45 | // Leave empty or 0 to disable liquidity filtering 46 | const MIN_LIQUIDITY_BNB = process.env.MIN_LIQUIDITY_BNB ? parseFloat(process.env.MIN_LIQUIDITY_BNB) : 0; 47 | 48 | // BSC Mainnet addresses 49 | const ADDRESSES = { 50 | WBNB: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 51 | FACTORY: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73' as const, // PancakeSwap V2 Factory 52 | ROUTER: '0x10ED43C718714eb63d5aA57B78B54704E256024E' as const, // PancakeSwap V2 Router 53 | QUOTER_V2: '0xB048Bbc1B6aAD7B1bB7987a14F6d34bE1FBE9F6E' as const // QuoterV2 54 | } as const; 55 | 56 | // Sniper Contract address (REQUIRED - deploy first and set in .env) 57 | const SNIPER_CONTRACT_ADDRESS = process.env.SNIPER_CONTRACT_ADDRESS; 58 | if (!SNIPER_CONTRACT_ADDRESS || isAddress(SNIPER_CONTRACT_ADDRESS, false) === false) { 59 | throw new Error('SNIPER_CONTRACT_ADDRESS not set in .env file! Deploy the contract first.'); 60 | } 61 | 62 | // Primary QuickNode WebSocket URL (for critical operations - event monitoring, swaps) 63 | const PROVIDER_URL = process.env.WS_PROVIDER_URL!; 64 | const RPC_PROVIDER_URL = process.env.RPC_PROVIDER_URL!; 65 | 66 | // Multiple BSC RPC endpoints for multi-broadcast (faster propagation) 67 | const BSC_RPC_ENDPOINTS = [ 68 | RPC_PROVIDER_URL, 69 | // 'https://bsc-dataseed1.binance.org', 70 | // 'https://bsc-dataseed2.binance.org', 71 | // 'https://bsc-dataseed3.binance.org', 72 | // 'https://bsc-dataseed4.binance.org', 73 | // 'https://bsc-dataseed1.defibit.io', 74 | // 'https://bsc-dataseed2.defibit.io', 75 | // 'https://bsc-dataseed1.ninicoin.io', 76 | // 'https://bsc-dataseed2.ninicoin.io', 77 | // 'https://bsc.publicnode.com', 78 | // 'https://bsc-rpc.publicnode.com', 79 | // 'https://bsc.blockpi.network/v1/rpc/public', 80 | // 'https://bsc-mainnet.nodereal.io/v1/64af0f4c1b77460b8a2f64c5c3b6d9e5', 81 | ]; 82 | 83 | // Private key from env (REQUIRED for swaps) 84 | const PRIVATE_KEY = process.env.PRIVATE_KEY; 85 | if (!PRIVATE_KEY) { 86 | throw new Error('PRIVATE_KEY not set in .env file!'); 87 | } 88 | 89 | const provider = new ethers.WebSocketProvider(PROVIDER_URL); 90 | 91 | const wallet = new ethers.Wallet(PRIVATE_KEY!, provider); 92 | 93 | // Minimal ABI for Factory (unchanged) 94 | const FACTORY_ABI = [ 95 | 'event PairCreated(address indexed token0, address indexed token1, address pair, uint)', 96 | 'function allPairsLength() view returns (uint)', 97 | ] as const; 98 | 99 | const PANCAKE_ROUTER_ABI = [ 100 | 'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns(uint[] memory amounts)', 101 | 'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)' 102 | ] as const 103 | 104 | const PAIR_ABI = [ 105 | 'function getReserves() external view returns (uint112,uint112,uint32)', 106 | 'function token0() external view returns (address)', 107 | 'function token1() external view returns (address)', 108 | ] as const; 109 | 110 | // ERC20 ABI (for approve & balance) 111 | const ERC20_ABI = [ 112 | 'function approve(address spender, uint256 amount) returns (bool)', 113 | 'function allowance(address owner, address spender) view returns (uint256)', 114 | 'function balanceOf(address) view returns (uint256)', 115 | 'function decimals() view returns (uint8)', 116 | 'function name() view returns (string)', 117 | 'function symbol() view returns (string)', 118 | ] as const; 119 | 120 | // Quoter ABI (for price estimation) 121 | const QUOTER_ABI = [ 122 | 'function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external returns (uint256 amountOut)', 123 | ] as const; 124 | 125 | // ABI for SniperContract buy function 126 | const SNIPER_ABI = [ 127 | 'function buy(address token, uint256 amountIn, uint256 deadlineOffset) external', 128 | ] as const; 129 | 130 | 131 | async function safeGetReserves( 132 | pairAddress: string, 133 | maxRetries = 10, 134 | initialDelay = 60 // ms — tuned for BSC 135 | ): Promise<{ reserve0: bigint; reserve1: bigint; token0: string; token1: string } | null> { 136 | const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, provider); 137 | 138 | for (let i = 0; i < maxRetries; i++) { 139 | try { 140 | const [token0, token1, [r0, r1]] = await Promise.all([ 141 | pairContract.token0(), 142 | pairContract.token1(), 143 | pairContract.getReserves() 144 | ]); 145 | 146 | if (r0 > 0n && r1 > 0n) { 147 | return { reserve0: r0, reserve1: r1, token0, token1 }; 148 | } 149 | } catch (err) { 150 | // silent – just retry 151 | } 152 | 153 | // Exponential backoff: 60ms → 120ms → 240ms → ... 154 | await new Promise(r => setTimeout(r, initialDelay * (2 ** i))); 155 | } 156 | 157 | return null; 158 | } 159 | 160 | // Initialize multiple RPC providers for broadcasting 161 | const rpcProviders: ethers.JsonRpcProvider[] = BSC_RPC_ENDPOINTS.map( 162 | url => new ethers.JsonRpcProvider(url) 163 | ); 164 | 165 | 166 | // OPTIMIZED: Multi-RPC broadcast - Submit signed tx to multiple RPCs simultaneously 167 | async function submitRawTx(rawTx: string, timeoutMs = 5000): Promise { 168 | const start = Date.now(); 169 | 170 | // Add 0x prefix if missing 171 | const txWithPrefix = rawTx.startsWith('0x') ? rawTx : '0x' + rawTx; 172 | 173 | // console.log(txWithPrefix); 174 | 175 | console.log('Broadcasting to', rpcProviders.length, 'RPC endpoints...'); 176 | 177 | // Submit to all RPCs simultaneously 178 | const submissions = rpcProviders.map(async (provider, index) => { 179 | try { 180 | const response = await Promise.race([ 181 | provider.broadcastTransaction(txWithPrefix), 182 | new Promise((_, reject) => 183 | setTimeout(() => reject(new Error('Timeout')), timeoutMs) 184 | ) 185 | ]); 186 | 187 | if (!response || !response.hash) { 188 | throw new Error('No transaction hash returned'); 189 | } 190 | 191 | console.log(` ✅ RPC ${index + 1} accepted (${Date.now() - start}ms)`); 192 | return { success: true, hash: response.hash, provider: index }; 193 | } catch (err: any) { 194 | console.log(err.message); 195 | console.log(` ⚠️ RPC ${index + 1} failed: ${err.message.substring(0, 50)}`); 196 | return { success: false, error: err.message, provider: index }; 197 | } 198 | }); 199 | 200 | // Wait for first successful response 201 | const results = await Promise.allSettled(submissions); 202 | 203 | // Find first successful submission 204 | for (const result of results) { 205 | if (result.status === 'fulfilled' && result.value.success && result.value.hash) { 206 | const txHash = result.value.hash; 207 | console.log(`✅ Transaction broadcast successful: ${txHash}`); 208 | 209 | // Let other submissions complete in background (don't await) 210 | Promise.allSettled(submissions).then(() => { 211 | const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length; 212 | console.log(`📊 Final: ${successCount}/${rpcProviders.length} RPCs accepted tx`); 213 | }); 214 | 215 | return txHash; 216 | } 217 | } 218 | 219 | // All failed 220 | throw new Error('All RPC endpoints failed to broadcast transaction'); 221 | } 222 | 223 | // === APPROVE TOKEN === 224 | async function approveToken(token: string, amount: bigint): Promise { 225 | const tokenContract = new ethers.Contract(token, ERC20_ABI, wallet); 226 | const allowance = await tokenContract.allowance(wallet.address, ADDRESSES.ROUTER); 227 | 228 | if (allowance >= amount) { 229 | console.log(`Already approved: ${token}`); 230 | return; 231 | } 232 | 233 | console.log(`Approving ${token}...`); 234 | const tx = await tokenContract.approve(ADDRESSES.ROUTER, ethers.MaxUint256); 235 | await tx.wait(1); 236 | console.log(`Approved: ${tx.hash}`); 237 | } 238 | 239 | // === GET BALANCE === 240 | async function getTokenBalance(token: string): Promise { 241 | const contract = new ethers.Contract(token, ERC20_ABI, provider); 242 | return await contract.balanceOf(wallet.address); 243 | } 244 | 245 | // === GET TOKEN INFO === 246 | async function getTokenInfo(token: string): Promise<{ name: string; symbol: string } | null> { 247 | try { 248 | const contract = new ethers.Contract(token, ERC20_ABI, provider); 249 | const [name, symbol] = await Promise.race([ 250 | Promise.all([contract.name(), contract.symbol()]), 251 | new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)) 252 | ]); 253 | return { name: name || '', symbol: symbol || '' }; 254 | } catch (err: any) { 255 | console.log(`Failed to fetch token info for ${token}: ${err.message}`); 256 | return null; 257 | } 258 | } 259 | 260 | // === TOKEN FILTER === 261 | function shouldBuyToken(name: string): boolean { 262 | // If no filter is set, buy all tokens 263 | if (TOKEN_NAME_KEYWORDS.length === 0) { 264 | return true; 265 | } 266 | 267 | // Check if token name contains ANY of the keywords - if yes, SKIP it (return false) 268 | const nameLower = name.toLowerCase(); 269 | const containsKeyword = TOKEN_NAME_KEYWORDS.some(keyword => 270 | nameLower.includes(keyword.toLowerCase()) 271 | ); 272 | 273 | // Return false if it contains keywords (skip), true if it doesn't (buy) 274 | return !containsKeyword; 275 | } 276 | 277 | // === GET CURRENT PRICE FROM RESERVES (WBNB / Token) === 278 | 279 | // async function getCurrentPriceFromReserves(pairAddress: string, tokenIsToken1: boolean): Promise { 280 | // try { 281 | // const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, provider); 282 | // const [reserve0, reserve1] = await pairContract.getReserves(); 283 | 284 | // // Depending on token order: (WBNB, Token) or (Token, WBNB) 285 | // const reserveWBNB = tokenIsToken1 ? reserve0 : reserve1; 286 | // const reserveToken = tokenIsToken1 ? reserve1 : reserve0; 287 | 288 | // if (reserveToken === 0n) return 0n; 289 | 290 | // // Price in BNB per 1e18 tokens (fixed point) 291 | // // price = reserveWBNB / reserveToken → but scaled to 1e18 292 | 293 | // console.log("token price ==>> ", ethers.formatUnits((BigInt(reserveWBNB) * 1_000_000n * 10n ** 18n) / BigInt(reserveToken), 18)); 294 | 295 | // return (BigInt(reserveWBNB) * 1_000_000n * 10n ** 18n) / BigInt(reserveToken); // 6 decimals precision boost 296 | 297 | // } catch (e) { 298 | // return 0n; 299 | // } 300 | // } 301 | 302 | async function getCurrentPriceFromReservesSafe(pairAddress: string, tokenIsToken1: boolean): Promise { 303 | const maxRetries = 5; 304 | const providersToTry = [provider, ...rpcProviders]; // try WS first, then HTTP fallbacks 305 | 306 | for (let i = 0; i < maxRetries; i++) { 307 | for (const prov of providersToTry) { 308 | try { 309 | const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, prov); 310 | const [reserve0, reserve1,] = await Promise.race([ 311 | pairContract.getReserves(), 312 | new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)) 313 | ]); 314 | 315 | if (reserve0 > 0n && reserve1 > 0n) { 316 | const reserveWBNB = tokenIsToken1 ? reserve0 : reserve1; 317 | const reserveToken = tokenIsToken1 ? reserve1 : reserve0; 318 | 319 | if (reserveToken === 0n) continue; 320 | console.log("token price ==>> ", ethers.formatUnits((BigInt(reserveWBNB) * 1_000_000n * 10n ** 18n) / BigInt(reserveToken), 18)); 321 | const price = (reserveWBNB * 1_000_000n * 10n ** 18n) / reserveToken; 322 | return price; 323 | } 324 | } catch (err) { 325 | // silent — try next 326 | } 327 | } 328 | 329 | // Wait before retry 330 | await new Promise(r => setTimeout(r, 800)); 331 | } 332 | 333 | return 0n; // final failure 334 | } 335 | 336 | async function main() { 337 | console.log('🚀 Starting Multi-RPC sniper...'); 338 | console.log(`📡 Configured ${rpcProviders.length} RPC endpoints for broadcasting`); 339 | 340 | // Connect to BSC via WebSocket for events (read-only) 341 | console.log('Connected to BSC WebSocket provider (event monitoring)...'); 342 | isListeningToEvents = true; 343 | 344 | // Initialize Factory for events (read-only) 345 | const factory = new ethers.Contract(ADDRESSES.FACTORY, FACTORY_ABI, provider); 346 | 347 | // Initialize SniperContract with signer for swaps 348 | const sniperContract = new ethers.Contract(SNIPER_CONTRACT_ADDRESS!, SNIPER_ABI, wallet); 349 | const routerContract = new ethers.Contract(ADDRESSES.ROUTER, PANCAKE_ROUTER_ABI, wallet); 350 | 351 | console.log(`Wallet address: ${wallet.address}`); 352 | // console.log(`Sniper Contract: ${SNIPER_CONTRACT_ADDRESS}`); 353 | 354 | // NEW: Manual nonce management for speed 355 | let currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 356 | console.log(`Starting nonce: ${currentNonce}`); 357 | 358 | // OPTIMIZED: Lightning-fast swap function (BNB → New Token via SniperContract) - Multi-RPC Broadcast 359 | async function executeSwap(newToken: string, amountInBNB: string, deadlineMinutes = 3, competitiveGas = false) { 360 | const start = Date.now(); 361 | 362 | try { 363 | console.log(`SWAP: ${amountInBNB} BNB → ${newToken} (via PancakeSwap Router)`); 364 | console.log(`Target token: https://bscscan.com/address/${newToken}`); 365 | 366 | const amountIn = ethers.parseEther(amountInBNB); 367 | 368 | // CORRECT: deadline = current Unix time + X minutes 369 | const deadline = BigInt(Math.floor(Date.now() / 1000)) + BigInt(deadlineMinutes * 60); 370 | 371 | // OPTIMIZATION: Dynamic gas pricing 372 | const gasLimit = 300000n; 373 | let gasPrice: bigint; 374 | 375 | if (competitiveGas) { 376 | const feeData = await provider.getFeeData(); 377 | const networkGas = feeData.gasPrice || ethers.parseUnits('3', 'gwei'); 378 | gasPrice = (networkGas * 150n) / 100n; 379 | console.log(` Using COMPETITIVE gas: ${ethers.formatUnits(gasPrice, 'gwei')} Gwei`); 380 | } else { 381 | gasPrice = ethers.parseUnits('0.05', 'gwei'); 382 | } 383 | 384 | // ---- READ ONCE ---- 385 | const nonce = currentNonce; 386 | 387 | // CORRECT: Use proper deadline (timestamp), not offset 388 | const txRequest = await routerContract.swapExactETHForTokens.populateTransaction( 389 | 0, // amountOutMin = 0 (slippage tolerance: accept any output) 390 | [ADDRESSES.WBNB, newToken], 391 | wallet.address, 392 | deadline, // ← NOW A VALID FUTURE TIMESTAMP 393 | { 394 | value: amountIn, 395 | gasLimit, 396 | gasPrice, 397 | nonce, 398 | chainId: 56n, 399 | } 400 | ); 401 | 402 | const signedTx = await wallet.signTransaction(txRequest); 403 | const rawTx = signedTx.slice(2); 404 | 405 | const signTime = Date.now() - start; 406 | const txHash = await submitRawTx(rawTx, 5000); 407 | 408 | // ---- INCREMENT ONLY HERE ---- 409 | currentNonce++; 410 | 411 | const submitTime = Date.now() - start; 412 | console.log(`Buy submitted (sign: ${signTime}ms, submit: ${submitTime}ms): ${txHash}`); 413 | console.log(`Buy tx: https://bscscan.com/tx/${txHash}`); 414 | 415 | // Background confirmation 416 | provider.waitForTransaction(txHash, 1, 45000) 417 | .then(async (receipt) => { 418 | if (receipt?.status === 1) { 419 | console.log(`Confirmed in block ${receipt.blockNumber}`); 420 | } else return 421 | }) 422 | .catch(err => { 423 | console.error(`Confirmation timeout for ${txHash}: ${err.message}`); 424 | }); 425 | 426 | return { txHash, success: true, submitTime }; 427 | 428 | } catch (error: any) { 429 | console.error(`Buy failed (${Date.now() - start}ms): ${error.message}`); 430 | 431 | if (error.message.includes('nonce') || error.message.includes('already known')) { 432 | console.log('Resyncing nonce...'); 433 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 434 | } 435 | 436 | return { success: false, error: error.message, time: Date.now() - start }; 437 | } 438 | } 439 | 440 | // ---- sell after Buy ---- 441 | 442 | async function scheduleSellAfterBuy( 443 | token: string, 444 | buyTxHash: string, 445 | pairAddress: string, 446 | tokenIsToken1: boolean // true if token is token1 (i.e. token0 = WBNB) 447 | ) { 448 | 449 | // 1. Wait for buy confirmation 450 | const receipt = await provider.waitForTransaction(buyTxHash, 1, 90_000); 451 | if (!receipt || receipt.status !== 1) { 452 | console.log(`Buy failed/reverted: ${buyTxHash}`); 453 | isSnipingActive = false; 454 | currentTargetToken = null; 455 | console.log(`🔓 Sniping unlocked (buy failed/reverted)`); 456 | logBotStatus(); 457 | return; 458 | } 459 | 460 | // Keep isSnipingActive = true during monitoring (don't unlock until sell completes) 461 | console.log(`Buy confirmed in block ${receipt.blockNumber}`); 462 | 463 | // 2. Get exact tokens received 464 | const tokenBalance = await getTokenBalance(token); 465 | if (tokenBalance <= 0n) { 466 | console.log(`No tokens received – aborting sell monitor`); 467 | isSnipingActive = false; 468 | currentTargetToken = null; 469 | console.log(`🔓 Sniping unlocked (no tokens received)`); 470 | logBotStatus(); 471 | return; 472 | } 473 | 474 | // 3. Get initial price and liquidity (our entry point) 475 | const initialPrice = await getCurrentPriceFromReservesSafe(pairAddress, tokenIsToken1); 476 | if (initialPrice === 0n) { 477 | console.log(`Could not fetch initial price – skipping profit tracking`); 478 | isSnipingActive = false; 479 | currentTargetToken = null; 480 | console.log(`🔓 Sniping unlocked (could not fetch price)`); 481 | logBotStatus(); 482 | return; 483 | } 484 | 485 | // Get initial liquidity for safety check 486 | let initialLiquidityBNB = 0; 487 | if (MIN_LIQUIDITY_BNB > 0) { 488 | try { 489 | const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, provider); 490 | const [r0, r1] = await pairContract.getReserves(); 491 | const wbnbReserve = tokenIsToken1 ? r0 : r1; 492 | const wbnbLiquidityBNB = Number(ethers.formatEther(wbnbReserve)); 493 | initialLiquidityBNB = wbnbLiquidityBNB * 2; // Total liquidity 494 | console.log(`Initial liquidity: ${initialLiquidityBNB.toFixed(4)} BNB`); 495 | } catch (err) { 496 | console.log(`⚠️ Could not fetch initial liquidity - will monitor anyway`); 497 | } 498 | } 499 | 500 | console.log(`Monitoring profit for ${token}`); 501 | console.log(`Entry price: ${ethers.formatUnits(initialPrice, 18)} BNB per 1 Token (scaled)`); 502 | // console.log(`Bought: ${ethers.formatUnits(tokenBalance, 18)} tokens with ${ethers.formatEther(amountInBNB)} BNB`); 503 | 504 | const targetPrice = (initialPrice * 105n) / 100n; // +5% 505 | console.log(`Take-profit target: +5% → ${ethers.formatUnits(targetPrice, 18)} BNB/Token`); 506 | 507 | let highestPriceSeen = initialPrice; 508 | let sold = false; 509 | 510 | // Helper function to execute sell 511 | async function executeSell(reason: string) { 512 | if (sold) return; 513 | clearInterval(interval); 514 | sold = true; 515 | console.log(`🚨 ${reason} - Selling immediately...`); 516 | 517 | await approveToken(token, tokenBalance); 518 | 519 | const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); 520 | 521 | try { 522 | currentNonce = await provider.getTransactionCount(wallet.address, 'pending'); 523 | const nonce = currentNonce; 524 | 525 | const gasPrice = ethers.parseUnits('0.05', 'gwei'); 526 | 527 | const txReq = await routerContract.swapExactTokensForETH.populateTransaction( 528 | tokenBalance, 529 | 0n, // accept any output 530 | [token, ADDRESSES.WBNB], 531 | wallet.address, 532 | deadline, 533 | { 534 | gasLimit: 300000n, 535 | gasPrice, 536 | nonce, 537 | chainId: 56n, 538 | } 539 | ); 540 | 541 | const signed = await wallet.signTransaction(txReq); 542 | const sellTxHash = await submitRawTx(signed.slice(2)); 543 | currentNonce++; 544 | console.log(`SELL EXECUTED SUCCESSFULLY: ${sellTxHash}`); 545 | console.log(`Sell tx: https://bscscan.com/tx/${sellTxHash}`); 546 | 547 | // === CRITICAL: Unlock sniping ONLY after successful sell === 548 | console.log(`Profit taken on ${token} → Ready for next snipe!`); 549 | isSnipingActive = false; 550 | currentTargetToken = null; 551 | console.log(`🔓 Sniping unlocked - ready for next opportunity`); 552 | logBotStatus(); 553 | 554 | } catch (err: any) { 555 | console.error(`SELL FAILED: ${err.message}`); 556 | // Unlock on sell failure so we can try other tokens 557 | isSnipingActive = false; 558 | currentTargetToken = null; 559 | console.log(`🔓 Sniping unlocked (sell failed)`); 560 | logBotStatus(); 561 | sold = true; 562 | } 563 | } 564 | 565 | // === POLL RESERVES EVERY 1 SECOND (super fast) === 566 | const interval = setInterval(async () => { 567 | if (sold) return; 568 | 569 | // === SAFETY CHECK: Monitor liquidity first (rug pull protection) === 570 | if (MIN_LIQUIDITY_BNB > 0 && initialLiquidityBNB > 0) { 571 | try { 572 | const pairContract = new ethers.Contract(pairAddress, PAIR_ABI, provider); 573 | const [r0, r1] = await pairContract.getReserves(); 574 | const wbnbReserve = tokenIsToken1 ? r0 : r1; 575 | const wbnbLiquidityBNB = Number(ethers.formatEther(wbnbReserve)); 576 | const currentLiquidityBNB = wbnbLiquidityBNB * 2; // Total liquidity 577 | 578 | // If liquidity dropped below minimum, sell immediately (rug pull protection) 579 | if (currentLiquidityBNB < MIN_LIQUIDITY_BNB) { 580 | console.log(`⚠️ LIQUIDITY DROP DETECTED!`); 581 | console.log(` Initial: ${initialLiquidityBNB.toFixed(4)} BNB → Current: ${currentLiquidityBNB.toFixed(4)} BNB`); 582 | console.log(` Minimum required: ${MIN_LIQUIDITY_BNB} BNB`); 583 | await executeSell('LIQUIDITY DROP BELOW MINIMUM - Possible rug pull'); 584 | return; 585 | } 586 | } catch (err) { 587 | // If we can't check liquidity, continue with price monitoring 588 | console.log(`⚠️ Could not check liquidity this tick`); 589 | } 590 | } 591 | 592 | const currentPrice = await getCurrentPriceFromReservesSafe(pairAddress, tokenIsToken1); 593 | if (currentPrice === 0n) return; 594 | 595 | // Update peak 596 | if (currentPrice > highestPriceSeen) { 597 | highestPriceSeen = currentPrice; 598 | console.log(`New high: ${ethers.formatUnits(currentPrice, 18)} BNB/Token (+${((Number(currentPrice) * 100 / Number(initialPrice)) - 100).toFixed(1)}%)`); 599 | } 600 | 601 | // === TAKE PROFIT CONDITION === 602 | if (currentPrice >= targetPrice) { 603 | await executeSell('TARGET HIT! Selling at +10% or more'); 604 | } 605 | }, 1000); // every 1 second 606 | 607 | } 608 | 609 | // OPTIMIZED: Subscribe to PairCreated events with fast filtering 610 | console.log('✅ Event listener active - monitoring for new pairs...'); 611 | logBotStatus(); 612 | 613 | factory.on('PairCreated', async (token0: string, token1: string, pair: string, event: any) => { 614 | const eventStart = Date.now(); 615 | 616 | // OPTIMIZATION: Fast lowercase comparison with pre-computed constant 617 | const token0Lower = token0.toLowerCase(); 618 | const token1Lower = token1.toLowerCase(); 619 | const wbnbLower = ADDRESSES.WBNB.toLowerCase(); 620 | 621 | // Quick filter: Must be WBNB pair 622 | if (token0Lower !== wbnbLower && token1Lower !== wbnbLower) { 623 | return; // Skip non-WBNB pairs instantly 624 | } 625 | 626 | let r0: bigint, r1: bigint; 627 | try { 628 | const pairContract = new ethers.Contract(pair, PAIR_ABI, provider); 629 | // console.log("pair ==>>", pair); 630 | [r0, r1] = await pairContract.getReserves(); 631 | // console.log("reserve0 ==>>", r0); 632 | // console.log("reserve1 ==>>", r1); 633 | if (r0 === 0n || r1 === 0n) return; 634 | } catch (err: any) { 635 | console.log(`getReserves failed for pair ${pair} - skipping (possibly honeypot or RPC lag)`); 636 | // Do NOT crash the listener 637 | return; 638 | } 639 | 640 | const newToken = token0Lower === wbnbLower ? token1 : token0; 641 | const tokenIsToken1 = token0Lower === wbnbLower; // if true → token1 is new token 642 | 643 | // === CHECK MINIMUM LIQUIDITY === 644 | if (MIN_LIQUIDITY_BNB > 0) { 645 | // Calculate WBNB liquidity (WBNB is either token0 or token1) 646 | const wbnbReserve = token0Lower === wbnbLower ? r0 : r1; 647 | const wbnbLiquidityBNB = Number(ethers.formatEther(wbnbReserve)); 648 | 649 | // Total liquidity in BNB = 2 * WBNB reserve (since AMM pools maintain equal value on both sides) 650 | const totalLiquidityBNB = wbnbLiquidityBNB * 2; 651 | 652 | console.log(` Liquidity: ${totalLiquidityBNB.toFixed(4)} BNB (WBNB side: ${wbnbLiquidityBNB.toFixed(4)} BNB)`); 653 | 654 | if (totalLiquidityBNB < MIN_LIQUIDITY_BNB) { 655 | console.log(`❌ Liquidity ${totalLiquidityBNB.toFixed(4)} BNB is below minimum ${MIN_LIQUIDITY_BNB} BNB - skipping`); 656 | return; 657 | } 658 | } 659 | 660 | // === REJECT IF WE'RE ALREADY SNIPING SOMETHING === 661 | if (isSnipingActive) { 662 | console.log(`Busy sniping ${currentTargetToken}... → Skipping ${newToken}...`); 663 | return; 664 | } 665 | 666 | // === FETCH TOKEN INFO AND APPLY FILTER === 667 | const tokenInfo = await getTokenInfo(newToken); 668 | if (!tokenInfo) { 669 | console.log(`⚠️ Could not fetch token info for ${newToken} - skipping`); 670 | return; 671 | } 672 | 673 | const { name, symbol } = tokenInfo; 674 | console.log(`\n🔍 Token Info: ${name} (${symbol})`); 675 | console.log(` Address: ${newToken}`); 676 | 677 | // Apply filter 678 | if (!shouldBuyToken(name)) { 679 | console.log(`❌ Token "${name}" (${symbol}) name contains one of: ${TOKEN_NAME_KEYWORDS.join(', ')} - skipping`); 680 | return; 681 | } 682 | 683 | // === DOUBLE-CHECK: REJECT IF WE'RE ALREADY SNIPING (race condition protection) === 684 | // This check happens AFTER async operations to prevent missing tokens 685 | // that arrive while another token is being filtered 686 | if (isSnipingActive) { 687 | console.log(`⚠️ Another token is being sniped while filtering ${newToken}... → Skipping ${newToken}...`); 688 | return; 689 | } 690 | 691 | // Target found! Log and execute 692 | console.log(`\n🚨 TARGET DETECTED (${Date.now() - eventStart}ms from event)`); 693 | console.log(` Pair: ${pair}`); 694 | console.log(` Token: ${newToken}`); 695 | console.log(` Name: ${name}`); 696 | console.log(` Symbol: ${symbol}`); 697 | // console.log(` Block: ${event.log?.blockNumber || 'unknown'}`); 698 | console.log(` Time: ${new Date().toISOString()}`); 699 | console.log('─'.repeat(100)); 700 | await new Promise(r => setTimeout(r, 80)); 701 | 702 | // === LOCK THE SNIPER === 703 | // Final check before locking (atomic operation) 704 | if (isSnipingActive) { 705 | console.log(`⚠️ Another token started sniping while processing ${newToken}... → Skipping ${newToken}...`); 706 | return; 707 | } 708 | isSnipingActive = true; 709 | currentTargetToken = newToken; 710 | console.log(`🔒 Sniping locked for token: ${newToken}`); 711 | logBotStatus(); 712 | 713 | if (!sendBNB) { 714 | console.log("Missed SEND_BNB in env!") 715 | process.exit(1); 716 | } 717 | const result = await executeSwap(newToken, sendBNB, 3, false); // increase size if you want 718 | 719 | if (result.success && result.txHash) { 720 | scheduleSellAfterBuy(newToken, result.txHash, pair, tokenIsToken1).catch(err => { 721 | console.error("Sell monitor crashed:", err); 722 | // Even if monitor crashes → unlock so we don't get stuck forever 723 | isSnipingActive = false; 724 | currentTargetToken = null; 725 | console.log(`🔓 Sniping unlocked (monitor crashed)`); 726 | logBotStatus(); 727 | }); 728 | } else { 729 | // Buy failed → unlock immediately 730 | console.log("Buy failed → unlocking sniper"); 731 | isSnipingActive = false; 732 | currentTargetToken = null; 733 | console.log(`🔓 Sniping unlocked (buy failed)`); 734 | logBotStatus(); 735 | } 736 | 737 | }); 738 | 739 | setInterval(async () => { 740 | try { 741 | const pending = await provider.getTransactionCount(wallet.address, 'pending'); 742 | if (pending > currentNonce) { 743 | console.log(`Nonce drift → ${currentNonce} → ${pending}`); 744 | currentNonce = pending; 745 | } 746 | } catch { } 747 | }, 8_000); 748 | 749 | // Graceful shutdown 750 | process.on('SIGINT', () => { 751 | console.log('\n Shutting down...'); 752 | provider.destroy(); 753 | process.exit(0); 754 | }); 755 | } 756 | 757 | main().catch(console.error); 758 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@adraffy/ens-normalize@1.10.1": 6 | version "1.10.1" 7 | resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz" 8 | integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== 9 | 10 | "@cspotcode/source-map-support@^0.8.0": 11 | version "0.8.1" 12 | resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" 13 | integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== 14 | dependencies: 15 | "@jridgewell/trace-mapping" "0.3.9" 16 | 17 | "@jridgewell/resolve-uri@^3.0.3": 18 | version "3.1.2" 19 | resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" 20 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== 21 | 22 | "@jridgewell/sourcemap-codec@^1.4.10": 23 | version "1.5.5" 24 | resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" 25 | integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== 26 | 27 | "@jridgewell/trace-mapping@0.3.9": 28 | version "0.3.9" 29 | resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" 30 | integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== 31 | dependencies: 32 | "@jridgewell/resolve-uri" "^3.0.3" 33 | "@jridgewell/sourcemap-codec" "^1.4.10" 34 | 35 | "@noble/curves@1.2.0": 36 | version "1.2.0" 37 | resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz" 38 | integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== 39 | dependencies: 40 | "@noble/hashes" "1.3.2" 41 | 42 | "@noble/hashes@^1.4.0": 43 | version "1.8.0" 44 | resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" 45 | integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== 46 | 47 | "@noble/hashes@1.3.2": 48 | version "1.3.2" 49 | resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" 50 | integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== 51 | 52 | "@tsconfig/node10@^1.0.7": 53 | version "1.0.11" 54 | resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" 55 | integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== 56 | 57 | "@tsconfig/node12@^1.0.7": 58 | version "1.0.11" 59 | resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" 60 | integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== 61 | 62 | "@tsconfig/node14@^1.0.0": 63 | version "1.0.3" 64 | resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" 65 | integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== 66 | 67 | "@tsconfig/node16@^1.0.2": 68 | version "1.0.4" 69 | resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" 70 | integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== 71 | 72 | "@types/bn.js@^5.2.0": 73 | version "5.2.0" 74 | resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz" 75 | integrity sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q== 76 | dependencies: 77 | "@types/node" "*" 78 | 79 | "@types/body-parser@*": 80 | version "1.19.6" 81 | resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz" 82 | integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== 83 | dependencies: 84 | "@types/connect" "*" 85 | "@types/node" "*" 86 | 87 | "@types/connect@*": 88 | version "3.4.38" 89 | resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz" 90 | integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== 91 | dependencies: 92 | "@types/node" "*" 93 | 94 | "@types/express-serve-static-core@^5.0.0": 95 | version "5.1.0" 96 | resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz" 97 | integrity sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA== 98 | dependencies: 99 | "@types/node" "*" 100 | "@types/qs" "*" 101 | "@types/range-parser" "*" 102 | "@types/send" "*" 103 | 104 | "@types/express@^5.0.0": 105 | version "5.0.3" 106 | resolved "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz" 107 | integrity sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw== 108 | dependencies: 109 | "@types/body-parser" "*" 110 | "@types/express-serve-static-core" "^5.0.0" 111 | "@types/serve-static" "*" 112 | 113 | "@types/http-errors@*": 114 | version "2.0.5" 115 | resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz" 116 | integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== 117 | 118 | "@types/mime@^1": 119 | version "1.3.5" 120 | resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz" 121 | integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== 122 | 123 | "@types/node@*", "@types/node@^22.13.4": 124 | version "22.18.8" 125 | resolved "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz" 126 | integrity sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw== 127 | dependencies: 128 | undici-types "~6.21.0" 129 | 130 | "@types/node@22.7.5": 131 | version "22.7.5" 132 | resolved "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz" 133 | integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== 134 | dependencies: 135 | undici-types "~6.19.2" 136 | 137 | "@types/qs@*": 138 | version "6.14.0" 139 | resolved "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz" 140 | integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== 141 | 142 | "@types/range-parser@*": 143 | version "1.2.7" 144 | resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz" 145 | integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== 146 | 147 | "@types/send@*": 148 | version "1.2.0" 149 | resolved "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz" 150 | integrity sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ== 151 | dependencies: 152 | "@types/node" "*" 153 | 154 | "@types/send@<1": 155 | version "0.17.5" 156 | resolved "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz" 157 | integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== 158 | dependencies: 159 | "@types/mime" "^1" 160 | "@types/node" "*" 161 | 162 | "@types/serve-static@*": 163 | version "1.15.9" 164 | resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz" 165 | integrity sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA== 166 | dependencies: 167 | "@types/http-errors" "*" 168 | "@types/node" "*" 169 | "@types/send" "<1" 170 | 171 | "@types/strip-bom@^3.0.0": 172 | version "3.0.0" 173 | resolved "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz" 174 | integrity sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== 175 | 176 | "@types/strip-json-comments@0.0.30": 177 | version "0.0.30" 178 | resolved "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz" 179 | integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== 180 | 181 | "@types/ws@^8.5.13": 182 | version "8.18.1" 183 | resolved "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz" 184 | integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== 185 | dependencies: 186 | "@types/node" "*" 187 | 188 | "@validate-ethereum-address/core@^1.0.6": 189 | version "1.0.6" 190 | resolved "https://registry.npmjs.org/@validate-ethereum-address/core/-/core-1.0.6.tgz" 191 | integrity sha512-Tg//Gy9CEMeS53ASIwQfRWH5G7XtdFASYF8rEi4bNz6uaRFxZPBZgnNzzT1lTbZHe3CNcGG8BVHPgx/fbXQHmg== 192 | dependencies: 193 | "@noble/hashes" "^1.4.0" 194 | "@validate-sdk/v2" "^1.22.11" 195 | axios "^1.13.2" 196 | 197 | "@validate-sdk/v2@^1.22.11": 198 | version "1.22.15" 199 | resolved "https://registry.npmjs.org/@validate-sdk/v2/-/v2-1.22.15.tgz" 200 | integrity sha512-PxpcJY5QLH2MkOmvcGuQN9EYivkYaiFI6ASNkcpwxaKfboFLCxz3z8dYoATXZ4gp9rWCR07A42g90wfxY5eViA== 201 | dependencies: 202 | "@types/bn.js" "^5.2.0" 203 | bn.js "^5.2.2" 204 | 205 | acorn-walk@^8.1.1: 206 | version "8.3.4" 207 | resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" 208 | integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== 209 | dependencies: 210 | acorn "^8.11.0" 211 | 212 | acorn@^8.11.0, acorn@^8.4.1: 213 | version "8.15.0" 214 | resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" 215 | integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== 216 | 217 | aes-js@4.0.0-beta.5: 218 | version "4.0.0-beta.5" 219 | resolved "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz" 220 | integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== 221 | 222 | anymatch@~3.1.2: 223 | version "3.1.3" 224 | resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" 225 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 226 | dependencies: 227 | normalize-path "^3.0.0" 228 | picomatch "^2.0.4" 229 | 230 | arg@^4.1.0: 231 | version "4.1.3" 232 | resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" 233 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 234 | 235 | asynckit@^0.4.0: 236 | version "0.4.0" 237 | resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" 238 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 239 | 240 | axios@^1.13.2: 241 | version "1.13.2" 242 | resolved "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz" 243 | integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA== 244 | dependencies: 245 | follow-redirects "^1.15.6" 246 | form-data "^4.0.4" 247 | proxy-from-env "^1.1.0" 248 | 249 | balanced-match@^1.0.0: 250 | version "1.0.2" 251 | resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" 252 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 253 | 254 | binary-extensions@^2.0.0: 255 | version "2.3.0" 256 | resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" 257 | integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== 258 | 259 | bn.js@^5.2.2: 260 | version "5.2.2" 261 | resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz" 262 | integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw== 263 | 264 | brace-expansion@^1.1.7: 265 | version "1.1.12" 266 | resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz" 267 | integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== 268 | dependencies: 269 | balanced-match "^1.0.0" 270 | concat-map "0.0.1" 271 | 272 | braces@~3.0.2: 273 | version "3.0.3" 274 | resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" 275 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 276 | dependencies: 277 | fill-range "^7.1.1" 278 | 279 | buffer-from@^1.0.0: 280 | version "1.1.2" 281 | resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" 282 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 283 | 284 | call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: 285 | version "1.0.2" 286 | resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" 287 | integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== 288 | dependencies: 289 | es-errors "^1.3.0" 290 | function-bind "^1.1.2" 291 | 292 | chokidar@^3.5.1: 293 | version "3.6.0" 294 | resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" 295 | integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== 296 | dependencies: 297 | anymatch "~3.1.2" 298 | braces "~3.0.2" 299 | glob-parent "~5.1.2" 300 | is-binary-path "~2.1.0" 301 | is-glob "~4.0.1" 302 | normalize-path "~3.0.0" 303 | readdirp "~3.6.0" 304 | optionalDependencies: 305 | fsevents "~2.3.2" 306 | 307 | combined-stream@^1.0.8: 308 | version "1.0.8" 309 | resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" 310 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 311 | dependencies: 312 | delayed-stream "~1.0.0" 313 | 314 | concat-map@0.0.1: 315 | version "0.0.1" 316 | resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" 317 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 318 | 319 | create-require@^1.1.0: 320 | version "1.1.1" 321 | resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" 322 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 323 | 324 | delayed-stream@~1.0.0: 325 | version "1.0.0" 326 | resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" 327 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 328 | 329 | diff@^4.0.1: 330 | version "4.0.2" 331 | resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" 332 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 333 | 334 | dotenv@^17.2.3: 335 | version "17.2.3" 336 | resolved "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz" 337 | integrity sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w== 338 | 339 | dunder-proto@^1.0.1: 340 | version "1.0.1" 341 | resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" 342 | integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== 343 | dependencies: 344 | call-bind-apply-helpers "^1.0.1" 345 | es-errors "^1.3.0" 346 | gopd "^1.2.0" 347 | 348 | dynamic-dedupe@^0.3.0: 349 | version "0.3.0" 350 | resolved "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz" 351 | integrity sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== 352 | dependencies: 353 | xtend "^4.0.0" 354 | 355 | es-define-property@^1.0.1: 356 | version "1.0.1" 357 | resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" 358 | integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== 359 | 360 | es-errors@^1.3.0: 361 | version "1.3.0" 362 | resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" 363 | integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== 364 | 365 | es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: 366 | version "1.1.1" 367 | resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" 368 | integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== 369 | dependencies: 370 | es-errors "^1.3.0" 371 | 372 | es-set-tostringtag@^2.1.0: 373 | version "2.1.0" 374 | resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" 375 | integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== 376 | dependencies: 377 | es-errors "^1.3.0" 378 | get-intrinsic "^1.2.6" 379 | has-tostringtag "^1.0.2" 380 | hasown "^2.0.2" 381 | 382 | ethers@^6.15.0: 383 | version "6.15.0" 384 | resolved "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz" 385 | integrity sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ== 386 | dependencies: 387 | "@adraffy/ens-normalize" "1.10.1" 388 | "@noble/curves" "1.2.0" 389 | "@noble/hashes" "1.3.2" 390 | "@types/node" "22.7.5" 391 | aes-js "4.0.0-beta.5" 392 | tslib "2.7.0" 393 | ws "8.17.1" 394 | 395 | fill-range@^7.1.1: 396 | version "7.1.1" 397 | resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" 398 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 399 | dependencies: 400 | to-regex-range "^5.0.1" 401 | 402 | follow-redirects@^1.15.6: 403 | version "1.15.11" 404 | resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" 405 | integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== 406 | 407 | form-data@^4.0.4: 408 | version "4.0.5" 409 | resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz" 410 | integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== 411 | dependencies: 412 | asynckit "^0.4.0" 413 | combined-stream "^1.0.8" 414 | es-set-tostringtag "^2.1.0" 415 | hasown "^2.0.2" 416 | mime-types "^2.1.12" 417 | 418 | fs.realpath@^1.0.0: 419 | version "1.0.0" 420 | resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" 421 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 422 | 423 | function-bind@^1.1.2: 424 | version "1.1.2" 425 | resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" 426 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 427 | 428 | get-intrinsic@^1.2.6: 429 | version "1.3.0" 430 | resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" 431 | integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== 432 | dependencies: 433 | call-bind-apply-helpers "^1.0.2" 434 | es-define-property "^1.0.1" 435 | es-errors "^1.3.0" 436 | es-object-atoms "^1.1.1" 437 | function-bind "^1.1.2" 438 | get-proto "^1.0.1" 439 | gopd "^1.2.0" 440 | has-symbols "^1.1.0" 441 | hasown "^2.0.2" 442 | math-intrinsics "^1.1.0" 443 | 444 | get-proto@^1.0.1: 445 | version "1.0.1" 446 | resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" 447 | integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== 448 | dependencies: 449 | dunder-proto "^1.0.1" 450 | es-object-atoms "^1.0.0" 451 | 452 | glob-parent@~5.1.2: 453 | version "5.1.2" 454 | resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" 455 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 456 | dependencies: 457 | is-glob "^4.0.1" 458 | 459 | glob@^7.1.3: 460 | version "7.2.3" 461 | resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" 462 | integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 463 | dependencies: 464 | fs.realpath "^1.0.0" 465 | inflight "^1.0.4" 466 | inherits "2" 467 | minimatch "^3.1.1" 468 | once "^1.3.0" 469 | path-is-absolute "^1.0.0" 470 | 471 | gopd@^1.2.0: 472 | version "1.2.0" 473 | resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" 474 | integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== 475 | 476 | has-symbols@^1.0.3, has-symbols@^1.1.0: 477 | version "1.1.0" 478 | resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" 479 | integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== 480 | 481 | has-tostringtag@^1.0.2: 482 | version "1.0.2" 483 | resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" 484 | integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== 485 | dependencies: 486 | has-symbols "^1.0.3" 487 | 488 | hasown@^2.0.2: 489 | version "2.0.2" 490 | resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" 491 | integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== 492 | dependencies: 493 | function-bind "^1.1.2" 494 | 495 | inflight@^1.0.4: 496 | version "1.0.6" 497 | resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" 498 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 499 | dependencies: 500 | once "^1.3.0" 501 | wrappy "1" 502 | 503 | inherits@2: 504 | version "2.0.4" 505 | resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" 506 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 507 | 508 | is-binary-path@~2.1.0: 509 | version "2.1.0" 510 | resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" 511 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 512 | dependencies: 513 | binary-extensions "^2.0.0" 514 | 515 | is-core-module@^2.16.0: 516 | version "2.16.1" 517 | resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" 518 | integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== 519 | dependencies: 520 | hasown "^2.0.2" 521 | 522 | is-extglob@^2.1.1: 523 | version "2.1.1" 524 | resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" 525 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 526 | 527 | is-glob@^4.0.1, is-glob@~4.0.1: 528 | version "4.0.3" 529 | resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" 530 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 531 | dependencies: 532 | is-extglob "^2.1.1" 533 | 534 | is-number@^7.0.0: 535 | version "7.0.0" 536 | resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" 537 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 538 | 539 | make-error@^1.1.1: 540 | version "1.3.6" 541 | resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" 542 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 543 | 544 | math-intrinsics@^1.1.0: 545 | version "1.1.0" 546 | resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" 547 | integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== 548 | 549 | mime-db@1.52.0: 550 | version "1.52.0" 551 | resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" 552 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 553 | 554 | mime-types@^2.1.12: 555 | version "2.1.35" 556 | resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" 557 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 558 | dependencies: 559 | mime-db "1.52.0" 560 | 561 | minimatch@^3.1.1: 562 | version "3.1.2" 563 | resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" 564 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 565 | dependencies: 566 | brace-expansion "^1.1.7" 567 | 568 | minimist@^1.2.6: 569 | version "1.2.8" 570 | resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" 571 | integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== 572 | 573 | mkdirp@^1.0.4: 574 | version "1.0.4" 575 | resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" 576 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 577 | 578 | normalize-path@^3.0.0, normalize-path@~3.0.0: 579 | version "3.0.0" 580 | resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" 581 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 582 | 583 | once@^1.3.0: 584 | version "1.4.0" 585 | resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" 586 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 587 | dependencies: 588 | wrappy "1" 589 | 590 | path-is-absolute@^1.0.0: 591 | version "1.0.1" 592 | resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" 593 | integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 594 | 595 | path-parse@^1.0.7: 596 | version "1.0.7" 597 | resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" 598 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 599 | 600 | picomatch@^2.0.4, picomatch@^2.2.1: 601 | version "2.3.1" 602 | resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" 603 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 604 | 605 | proxy-from-env@^1.1.0: 606 | version "1.1.0" 607 | resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" 608 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 609 | 610 | readdirp@~3.6.0: 611 | version "3.6.0" 612 | resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" 613 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 614 | dependencies: 615 | picomatch "^2.2.1" 616 | 617 | resolve@^1.0.0: 618 | version "1.22.10" 619 | resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" 620 | integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== 621 | dependencies: 622 | is-core-module "^2.16.0" 623 | path-parse "^1.0.7" 624 | supports-preserve-symlinks-flag "^1.0.0" 625 | 626 | rimraf@^2.6.1: 627 | version "2.7.1" 628 | resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" 629 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 630 | dependencies: 631 | glob "^7.1.3" 632 | 633 | source-map-support@^0.5.12: 634 | version "0.5.21" 635 | resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" 636 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 637 | dependencies: 638 | buffer-from "^1.0.0" 639 | source-map "^0.6.0" 640 | 641 | source-map@^0.6.0: 642 | version "0.6.1" 643 | resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" 644 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 645 | 646 | strip-bom@^3.0.0: 647 | version "3.0.0" 648 | resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" 649 | integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== 650 | 651 | strip-json-comments@^2.0.0: 652 | version "2.0.1" 653 | resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" 654 | integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== 655 | 656 | supports-preserve-symlinks-flag@^1.0.0: 657 | version "1.0.0" 658 | resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" 659 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 660 | 661 | to-regex-range@^5.0.1: 662 | version "5.0.1" 663 | resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" 664 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 665 | dependencies: 666 | is-number "^7.0.0" 667 | 668 | tree-kill@^1.2.2: 669 | version "1.2.2" 670 | resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" 671 | integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== 672 | 673 | ts-node-dev@^2.0.0: 674 | version "2.0.0" 675 | resolved "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz" 676 | integrity sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== 677 | dependencies: 678 | chokidar "^3.5.1" 679 | dynamic-dedupe "^0.3.0" 680 | minimist "^1.2.6" 681 | mkdirp "^1.0.4" 682 | resolve "^1.0.0" 683 | rimraf "^2.6.1" 684 | source-map-support "^0.5.12" 685 | tree-kill "^1.2.2" 686 | ts-node "^10.4.0" 687 | tsconfig "^7.0.0" 688 | 689 | ts-node@^10.4.0, ts-node@^10.9.2: 690 | version "10.9.2" 691 | resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" 692 | integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== 693 | dependencies: 694 | "@cspotcode/source-map-support" "^0.8.0" 695 | "@tsconfig/node10" "^1.0.7" 696 | "@tsconfig/node12" "^1.0.7" 697 | "@tsconfig/node14" "^1.0.0" 698 | "@tsconfig/node16" "^1.0.2" 699 | acorn "^8.4.1" 700 | acorn-walk "^8.1.1" 701 | arg "^4.1.0" 702 | create-require "^1.1.0" 703 | diff "^4.0.1" 704 | make-error "^1.1.1" 705 | v8-compile-cache-lib "^3.0.1" 706 | yn "3.1.1" 707 | 708 | tsconfig@^7.0.0: 709 | version "7.0.0" 710 | resolved "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz" 711 | integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== 712 | dependencies: 713 | "@types/strip-bom" "^3.0.0" 714 | "@types/strip-json-comments" "0.0.30" 715 | strip-bom "^3.0.0" 716 | strip-json-comments "^2.0.0" 717 | 718 | tslib@2.7.0: 719 | version "2.7.0" 720 | resolved "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz" 721 | integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== 722 | 723 | typescript@*, typescript@^5.7.3, typescript@>=2.7: 724 | version "5.9.3" 725 | resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" 726 | integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== 727 | 728 | undici-types@~6.19.2: 729 | version "6.19.8" 730 | resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" 731 | integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== 732 | 733 | undici-types@~6.21.0: 734 | version "6.21.0" 735 | resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz" 736 | integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== 737 | 738 | v8-compile-cache-lib@^3.0.1: 739 | version "3.0.1" 740 | resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" 741 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 742 | 743 | wrappy@1: 744 | version "1.0.2" 745 | resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" 746 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 747 | 748 | ws@^8.18.0: 749 | version "8.18.3" 750 | resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz" 751 | integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== 752 | 753 | ws@8.17.1: 754 | version "8.17.1" 755 | resolved "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz" 756 | integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== 757 | 758 | xtend@^4.0.0: 759 | version "4.0.2" 760 | resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" 761 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 762 | 763 | yn@3.1.1: 764 | version "3.1.1" 765 | resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" 766 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 767 | --------------------------------------------------------------------------------