├── src ├── 4meme-utils │ ├── config.ts │ └── abi.ts ├── Stealth-utils │ ├── OneTimeForwarder.json │ └── StealthFund.json ├── gather.ts ├── server.ts ├── trader.ts └── volume-bot.ts ├── tsconfig.json ├── package.json ├── .gitignore ├── .env.example ├── README.md ├── public └── index.html └── yarn.lock /src/4meme-utils/config.ts: -------------------------------------------------------------------------------- 1 | // Environment variables matching Rust implementation 2 | const TOKEN_MANAGER2 = process.env.TOKEN_MANAGER2 || '0x5c952063c7fc8610FFDB798152D69F0B9550762b'; 3 | const HELPER3_ADDRESS = process.env.HELPER3_ADDRESS || '0xF251F83e40a78868FcfA3FA4599Dad6494E46034'; 4 | const RPC_URL = process.env.RPC_URL || 'https://bsc-dataseed.binance.org'; 5 | 6 | export { 7 | TOKEN_MANAGER2 as TOKEN_MANAGER_ADDRESS, 8 | HELPER3_ADDRESS, 9 | RPC_URL 10 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": ["ES2020"], 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | -------------------------------------------------------------------------------- /src/Stealth-utils/OneTimeForwarder.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OneTimeForwarder", 4 | "sourceName": "contracts/StealthFund.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address payable", 10 | "name": "recipient", 11 | "type": "address" 12 | } 13 | ], 14 | "stateMutability": "payable", 15 | "type": "constructor" 16 | } 17 | ], 18 | "bytecode": "0x6080601f60d438819003918201601f19168301916001600160401b0383118484101760bd5780849260209460405283398101031260b857516001600160a01b03811680820360b857156082573415605257ff5b60405162461bcd60e51b81526020600482015260086024820152676e6f2076616c756560c01b6044820152606490fd5b60405162461bcd60e51b815260206004820152600e60248201526d1e995c9bc81c9958da5c1a595b9d60921b6044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe", 19 | "deployedBytecode": "0x600080fdfea164736f6c634300081c000a", 20 | "linkReferences": {}, 21 | "deployedLinkReferences": {} 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4meme-trading-bot", 3 | "version": "1.0.0", 4 | "description": "Four.meme token trading bot with buy/sell functionality", 5 | "main": "dist/trader.js", 6 | "scripts": { 7 | "dev": "ts-node src/trader.ts", 8 | "start": "ts-node src/volume-bot.ts", 9 | "gather": "ts-node src/gather.ts", 10 | "server": "ts-node src/server.ts", 11 | "ui": "ts-node src/server.ts" 12 | }, 13 | "keywords": [ 14 | "ethereum", 15 | "bsc", 16 | "trading", 17 | "bot", 18 | "four.meme" 19 | ], 20 | "author": "", 21 | "license": "MIT", 22 | "dependencies": { 23 | "cors": "^2.8.5", 24 | "dotenv": "^16.3.1", 25 | "ethers": "^6.8.1", 26 | "express": "^4.18.2", 27 | "fs-extra": "^11.2.0", 28 | "helmet": "^7.1.0" 29 | }, 30 | "devDependencies": { 31 | "@types/cors": "^2.8.16", 32 | "@types/express": "^4.17.21", 33 | "@types/fs-extra": "^11.0.4", 34 | "@types/node": "^20.8.10", 35 | "@validate-ethereum-address/core": "^1.0.6", 36 | "ts-node": "^10.9.1", 37 | "typescript": "^5.2.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build output 8 | dist/ 9 | build/ 10 | 11 | # Environment variables 12 | .env 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | # Logs 19 | *.log 20 | logs/ 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage/ 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Dependency directories 35 | node_modules/ 36 | jspm_packages/ 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | 44 | # Output of 'npm pack' 45 | *.tgz 46 | 47 | # Yarn Integrity file 48 | .yarn-integrity 49 | 50 | # dotenv environment variables file 51 | .env 52 | 53 | # IDE 54 | .vscode/ 55 | .idea/ 56 | *.swp 57 | *.swo 58 | 59 | # OS 60 | .DS_Store 61 | Thumbs.db 62 | 63 | # Trading logs 64 | bought_tokens.log 65 | sold_tokens.log 66 | created_tokens.log 67 | 68 | wallets.json 69 | wallets copy.json 70 | 1.json 71 | 2.json 72 | 3.json 73 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Required 2 | PRIVATE_KEY=0xYOUR_PRIVATE_KEY 3 | RPC_URL=https://bsc-dataseed.binance.org 4 | TOKEN_ADDRESS=0x0000000000000000000000000000000000000000 5 | 6 | # Web UI 7 | UI_PORT=3000 8 | 9 | # Contract Addresses (defaults exist in code if omitted) 10 | PANCAKE_ROUTER_ADDRESS=0x10ED43C718714eb63d5aA57B78B54704E256024E 11 | WBNB_ADDRESS=0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c 12 | STEALTH_FUND_ADDRESS=0x3774b227aee720423a62710c7Ce2D70EA16eE0D0 13 | HELPER3_ADDRESS=0xF251F83e40a78868FcfA3FA4599Dad6494E46034 14 | TOKEN_MANAGER2=0x5c952063c7fc8610FFDB798152D69F0B9550762b 15 | 16 | # Volume Bot Budget / Wallets 17 | TOTAL_BNB=18 18 | TOTAL_NUM_WALLETS=10 19 | MIN_DEPOSIT_BNB=0.01 20 | MAX_DEPOSIT_BNB=0.02 21 | 22 | # Per-cycle Buy Count 23 | MIN_BUY_NUM_PER_CYCLE=3 24 | MAX_BUY_NUM_PER_CYCLE=5 25 | 26 | # Buy amount as percent of (BNB - GAS_BUFFER_BNB) 27 | MIN_BUY_PERCENT_BNB=100 28 | MAX_BUY_PERCENT_BNB=100 29 | 30 | # Per-cycle Sell: percent of bought wallets to sell 31 | MIN_PERCENT_SELL=0 32 | MAX_PERCENT_SELL=0 33 | 34 | # Per-wallet Sell: percent of token balance to sell 35 | MIN_PERCENT_SELL_AMOUNT_AFTER_BUY=50 36 | MAX_PERCENT_SELL_AMOUNT_AFTER_BUY=80 37 | 38 | # Cycle control 39 | CYCLE_LIMIT=3 40 | MIN_PER_CYCLE_TIME=5000 41 | MAX_PER_CYCLE_TIME=10000 42 | 43 | # Timing 44 | MIN_DELAY_BUY=2000 45 | MAX_DELAY_BUY=8000 46 | MIN_DELAY_SELL=4000 47 | MAX_DELAY_SELL=12000 48 | 49 | # Gas buffer (BNB left in wallet to cover tx fees) 50 | GAS_BUFFER_BNB=0.001 51 | 52 | # Features 53 | STEALTH_MODE=false 54 | USE_PANCAKE_AFTER_MIGRATION=true -------------------------------------------------------------------------------- /src/4meme-utils/abi.ts: -------------------------------------------------------------------------------- 1 | const TOKEN_MANAGER_ABI = [ 2 | 'function buyToken(address token, uint256 amount, uint256 maxFunds) payable', 3 | 'function buyTokenAMAP(address token, uint256 funds, uint256 minAmount) payable', 4 | 'function sellToken(address token, uint256 amount)', 5 | 'function sellTokenAMAP(address token, uint256 amount, uint256 minBNB)' 6 | ]; 7 | 8 | const ERC20_ABI = [ 9 | 'function approve(address spender, uint256 amount) returns (bool)', 10 | 'function allowance(address owner, address spender) view returns (uint256)', 11 | 'function balanceOf(address owner) view returns (uint256)', 12 | 'function decimals() view returns (uint8)', 13 | 'function symbol() view returns (string)', 14 | 'function name() view returns (string)' 15 | ]; 16 | 17 | const HELPER3_ABI = [ 18 | { 19 | "inputs": [ 20 | { "internalType": "address", "name": "token", "type": "address" } 21 | ], 22 | "name": "getTokenInfo", 23 | "outputs": [ 24 | { "internalType": "uint256", "name": "version", "type": "uint256" }, 25 | { "internalType": "address", "name": "tokenManager", "type": "address" }, 26 | { "internalType": "address", "name": "quote", "type": "address" }, 27 | { "internalType": "uint256", "name": "lastPrice", "type": "uint256" }, 28 | { "internalType": "uint256", "name": "tradingFeeRate", "type": "uint256" }, 29 | { "internalType": "uint256", "name": "minTradingFee", "type": "uint256" }, 30 | { "internalType": "uint256", "name": "launchTime", "type": "uint256" }, 31 | { "internalType": "uint256", "name": "offers", "type": "uint256" }, 32 | { "internalType": "uint256", "name": "maxOffers", "type": "uint256" }, 33 | { "internalType": "uint256", "name": "funds", "type": "uint256" }, 34 | { "internalType": "uint256", "name": "maxFunds", "type": "uint256" }, 35 | { "internalType": "bool", "name": "liquidityAdded", "type": "bool" } 36 | ], 37 | "stateMutability": "view", 38 | "type": "function" 39 | }, 40 | { 41 | "inputs": [ 42 | { "internalType": "address", "name": "token", "type": "address" }, 43 | { "internalType": "uint256", "name": "amount", "type": "uint256" }, 44 | { "internalType": "uint256", "name": "funds", "type": "uint256" } 45 | ], 46 | "name": "tryBuy", 47 | "outputs": [ 48 | { "internalType": "address", "name": "tokenManager", "type": "address" }, 49 | { "internalType": "address", "name": "quote", "type": "address" }, 50 | { "internalType": "uint256", "name": "estimatedAmount", "type": "uint256" }, 51 | { "internalType": "uint256", "name": "estimatedCost", "type": "uint256" }, 52 | { "internalType": "uint256", "name": "estimatedFee", "type": "uint256" }, 53 | { "internalType": "uint256", "name": "amountMsgValue", "type": "uint256" }, 54 | { "internalType": "uint256", "name": "amountApproval", "type": "uint256" }, 55 | { "internalType": "uint256", "name": "amountFunds", "type": "uint256" } 56 | ], 57 | "stateMutability": "view", 58 | "type": "function" 59 | }, 60 | { 61 | "inputs": [ 62 | { "internalType": "address", "name": "token", "type": "address" }, 63 | { "internalType": "uint256", "name": "amount", "type": "uint256" } 64 | ], 65 | "name": "trySell", 66 | "outputs": [ 67 | { "internalType": "address", "name": "tokenManager", "type": "address" }, 68 | { "internalType": "address", "name": "quote", "type": "address" }, 69 | { "internalType": "uint256", "name": "funds", "type": "uint256" }, 70 | { "internalType": "uint256", "name": "fee", "type": "uint256" } 71 | ], 72 | "stateMutability": "view", 73 | "type": "function" 74 | } 75 | ] as const; 76 | 77 | const PANCAKE_ROUTER_ABI = [ 78 | 'function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts)', 79 | 'function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)' 80 | ] 81 | 82 | export { 83 | TOKEN_MANAGER_ABI, 84 | ERC20_ABI, 85 | HELPER3_ABI, 86 | PANCAKE_ROUTER_ABI 87 | }; -------------------------------------------------------------------------------- /src/gather.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import * as fs from 'fs'; 3 | import * as dotenv from 'dotenv'; 4 | import FourMemeTrader from './trader'; 5 | import { isAddress } from '@validate-ethereum-address/core'; 6 | dotenv.config(); 7 | 8 | const WALLET_LOG_FILE = 'wallets.json'; 9 | const TOKEN_ADDRESS = process.env.TOKEN_ADDRESS!; 10 | const MAIN_WALLET_PK = process.env.PRIVATE_KEY!; 11 | const RPC_URL = process.env.RPC_URL!; 12 | 13 | interface WalletLog { 14 | index: number; 15 | address: string; 16 | privateKey: string; 17 | depositBNB: number; 18 | status?: string; 19 | lastBNB?: number; 20 | buyTxHash?: string; 21 | sellTxHash?: string; 22 | boughtVia?: string; 23 | soldVia?: string; 24 | estimatedTokens?: string; 25 | timestamp: string; 26 | } 27 | 28 | async function main() { 29 | const provider = new ethers.JsonRpcProvider(RPC_URL); 30 | const mainWallet = new ethers.Wallet(MAIN_WALLET_PK, provider); 31 | if (isAddress(TOKEN_ADDRESS, false) === false) { 32 | console.log('Invalid token address'); 33 | return; 34 | } 35 | console.log('Token address is valid'); 36 | 37 | let wallets: WalletLog[] = JSON.parse(fs.readFileSync(WALLET_LOG_FILE, 'utf8')); 38 | console.log(`Found ${wallets.length} wallets.`); 39 | 40 | function updateWalletInList(wallet: WalletLog) { 41 | const idx = wallets.findIndex(w => w.address === wallet.address); 42 | if (idx >= 0) { 43 | wallets[idx] = { ...wallets[idx], ...wallet }; 44 | } 45 | fs.writeFileSync(WALLET_LOG_FILE, JSON.stringify(wallets, null, 2), 'utf8'); 46 | } 47 | 48 | for (const entry of wallets) { 49 | try { 50 | const wallet = new ethers.Wallet(entry.privateKey, provider); 51 | const trader = new FourMemeTrader(); 52 | trader.setWallet(wallet); 53 | 54 | console.log(`\n--- Processing wallet: ${wallet.address} ---`); 55 | 56 | // 1️⃣ Sell token 57 | const tokenBalance = await trader.getTokenBalance(TOKEN_ADDRESS); 58 | console.log(`Token balance: ${tokenBalance}`); 59 | 60 | let sellTx: string | undefined; 61 | if (tokenBalance > 0n) { 62 | const migration = await trader.getMigrationStatus(TOKEN_ADDRESS); 63 | if (migration) { 64 | const { txHash } = await trader.sellPancakeTokenBigInt(TOKEN_ADDRESS, tokenBalance); 65 | sellTx = txHash; 66 | } else { 67 | const sellAmount = tokenBalance; 68 | const sellRealAmount = sellAmount / BigInt(10 ** 9) * BigInt(10 ** 9); 69 | sellTx = await trader.sellAmountBigInt(TOKEN_ADDRESS, sellRealAmount); 70 | } 71 | console.log(`Sold tokens: tx ${sellTx}`); 72 | 73 | // Update wallet with new sell transaction 74 | const newSellTxHash = entry.sellTxHash ? `${entry.sellTxHash},${sellTx}` : sellTx; 75 | updateWalletInList({ 76 | ...entry, 77 | sellTxHash: newSellTxHash, 78 | status: 'SOLD', 79 | timestamp: new Date().toISOString(), 80 | }); 81 | } else { 82 | console.log('No tokens to sell.'); 83 | } 84 | 85 | // 2️⃣ Send BNB back to main wallet 86 | const bnbBalance = await provider.getBalance(wallet.address); 87 | const gasLimit = 21000n; 88 | const feeData = await provider.getFeeData(); 89 | console.log("feeData:", feeData); 90 | 91 | const gasFee = feeData.gasPrice! * gasLimit; 92 | const sendAmount = bnbBalance > gasFee ? bnbBalance - gasFee : 0n; 93 | 94 | if (sendAmount > 0n) { 95 | const tx = await wallet.sendTransaction({ 96 | to: mainWallet.address, 97 | value: sendAmount, 98 | gasLimit: Number(gasLimit), 99 | }); 100 | await tx.wait(); 101 | console.log(`Sent ${ethers.formatEther(sendAmount)} BNB back to main wallet -> tx ${tx.hash}`); 102 | 103 | // Update lastBNB to 0 or remaining balance 104 | const remainingBalance = await provider.getBalance(wallet.address); 105 | updateWalletInList({ 106 | ...entry, 107 | lastBNB: Number(ethers.formatEther(remainingBalance)), 108 | timestamp: new Date().toISOString(), 109 | }); 110 | } else { 111 | console.log('BNB balance too low to send back.'); 112 | // Update lastBNB even if not sent 113 | updateWalletInList({ 114 | ...entry, 115 | lastBNB: Number(ethers.formatEther(bnbBalance)), 116 | timestamp: new Date().toISOString(), 117 | }); 118 | } 119 | 120 | } catch (e) { 121 | console.error(`Error processing wallet ${entry.address}:`, e); 122 | updateWalletInList({ 123 | ...entry, 124 | status: 'GATHER_FAILED', 125 | timestamp: new Date().toISOString(), 126 | }); 127 | } 128 | } 129 | 130 | console.log('✅ All wallets processed.'); 131 | } 132 | 133 | main().catch(e => { console.error(e); process.exit(1); }); 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Four.meme Volume Bot (Bypassing Bubble Map detection) 2 | 3 | A trading bot platform for four.meme and Pancakeswap tokens with automatic migration detection, dual exchange support, and volume trading capabilities with stealth mode. 4 | 5 | image 6 | image (1) 7 | 8 | ## Features 9 | 10 | - **Migration Detection**: Automatically detects if token has migrated to PancakeSwap 11 | - **Four.meme Trading**: Buy/sell tokens before migration (using Four.meme contracts) 12 | - **PancakeSwap Trading**: Buy/sell tokens after migration (using PancakeRouter) 13 | - **Smart Routing**: Automatically chooses the correct exchange based on migration status 14 | - **Volume Bot**: Automated multi-wallet volume trading with stealth funding 15 | - **Stealth Mode**: Use StealthFund contract for anonymous wallet funding 16 | - **Simple Setup**: Just install and run with environment variables 17 | 18 | ## Setup 19 | 20 | 1. **Install Dependencies** 21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | 2. **Configure Environment** 27 | Create a `.env` file with your configuration (see `.env.example`): 28 | 29 | ```bash 30 | # Required 31 | PRIVATE_KEY=0x... 32 | RPC_URL=https://bsc-dataseed.binance.org 33 | TOKEN_ADDRESS=0x... # Token to trade 34 | 35 | # Web UI 36 | UI_PORT=3000 37 | 38 | # Contract Addresses (defaults provided in code) 39 | PANCAKE_ROUTER_ADDRESS=0x10ED43C718714eb63d5aA57B78B54704E256024E 40 | WBNB_ADDRESS=0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c 41 | STEALTH_FUND_ADDRESS=0x3774b227aee720423a62710c7Ce2D70EA16eE0D0 42 | 43 | # Volume Bot Budget / Wallets 44 | TOTAL_BNB=18 45 | TOTAL_NUM_WALLETS=50 46 | MIN_DEPOSIT_BNB=0.01 47 | MAX_DEPOSIT_BNB=0.02 48 | 49 | # Per-cycle Buy Count 50 | MIN_BUY_NUM_PER_CYCLE=3 51 | MAX_BUY_NUM_PER_CYCLE=5 52 | 53 | # Buy amount as percent of (BNB - GAS_BUFFER_BNB) 54 | MIN_BUY_PERCENT_BNB=100 55 | MAX_BUY_PERCENT_BNB=100 56 | 57 | # Per-cycle Sell: percent of bought wallets to sell 58 | MIN_PERCENT_SELL=0 59 | MAX_PERCENT_SELL=0 60 | 61 | # Per-wallet Sell: percent of token balance to sell 62 | MIN_PERCENT_SELL_AMOUNT_AFTER_BUY=50 63 | MAX_PERCENT_SELL_AMOUNT_AFTER_BUY=80 64 | 65 | # Cycle control 66 | CYCLE_LIMIT=3 67 | MIN_PER_CYCLE_TIME=5000 68 | MAX_PER_CYCLE_TIME=10000 69 | 70 | # Timing 71 | MIN_DELAY_BUY=2000 72 | MAX_DELAY_BUY=8000 73 | MIN_DELAY_SELL=4000 74 | MAX_DELAY_SELL=12000 75 | 76 | # Gas buffer (BNB left for gas) 77 | GAS_BUFFER_BNB=0.001 78 | 79 | # Features 80 | STEALTH_MODE=false 81 | USE_PANCAKE_AFTER_MIGRATION=true 82 | ``` 83 | 84 | 3. **Run the Bot** 85 | 86 | **Option 1: Web UI (Recommended)** 87 | 88 | ```bash 89 | npm run ui 90 | ``` 91 | 92 | Then open http://localhost:3000 in your browser 93 | 94 | **Option 2: Command Line** 95 | 96 | ```bash 97 | npm run start # Start volume bot 98 | npm run gather # Gather funds from wallets 99 | ``` 100 | 101 | ## How It Works 102 | 103 | ### Volume Bot Mode 104 | 105 | The VolumeBot creates multiple random wallets and executes coordinated trading: 106 | 107 | 1. **Wallet Creation**: Generates random wallets for each trading cycle 108 | 2. **Funding**: Uses either direct transfers or StealthFund contract (stealth mode) 109 | 3. **Sequential Funding**: Funds wallets one by one to avoid nonce conflicts 110 | 4. **Parallel Trading**: Executes buy orders in parallel after funding 111 | 5. **Sell Strategy**: Randomly selects wallets to sell based on configuration 112 | 6. **Cycle Management**: Repeats process for specified number of cycles 113 | 114 | ### Migration Detection 115 | 116 | The bot automatically detects the migration status of any four.meme token: 117 | 118 | - **Before Migration**: Uses Four.meme contracts for trading 119 | - **After Migration**: Uses PancakeSwap Router for trading 120 | 121 | The bot checks the `liquidityAdded` status from the Helper3 contract: 122 | 123 | - `false` = Token still on Four.meme (use Four.meme contracts) 124 | - `true` = Token migrated to PancakeSwap (use PancakeRouter) 125 | 126 | ### Trading Flow 127 | 128 | 1. **Check Migration Status**: Determines if token has migrated 129 | 2. **Route to Correct Exchange**: 130 | - Four.meme → Uses `buyTokenAMAP` and `sellToken` 131 | - PancakeSwap → Uses `swapExactETHForTokens` and `swapExactTokensForETH` 132 | 3. **Execute Trade**: Handles approvals, timing, and transaction execution 133 | 134 | ### Stealth Mode 135 | 136 | When `STEALTH_MODE=true`, the bot uses the StealthFund contract to fund wallets: 137 | 138 | - Creates anonymous funding transactions 139 | - Hides the connection between main wallet and trading wallets 140 | - Uses `stealthMultipleFund` function for efficient batch funding 141 | 142 | ## Code Structure 143 | 144 | ### Main Components 145 | 146 | - **`FourMemeTrader`**: Main trading class with migration detection 147 | - **`VolumeBot`**: Automated multi-wallet volume trading bot 148 | - **`getMigrationStatus()`**: Checks if token has migrated 149 | - **`buyToken()`**: Four.meme buy (before migration) 150 | - **`sellAmount()`**: Four.meme sell (before migration) 151 | - **`buyPancakeToken()`**: PancakeSwap buy (after migration) 152 | - **`sellPancakeToken()`**: PancakeSwap sell (after migration) 153 | - **`fundStealthChild()`**: Stealth funding via StealthFund contract 154 | - **`fundChild()`**: Direct wallet funding 155 | 156 | ### Example Usage 157 | 158 | #### Basic Trading 159 | 160 | ```typescript 161 | const trader = new FourMemeTrader(); 162 | const tokenAddress = "0x..."; 163 | 164 | // Check migration status 165 | const migrated = await trader.getMigrationStatus(tokenAddress); 166 | 167 | if (migrated) { 168 | // Token migrated to PancakeSwap 169 | await trader.buyPancakeToken(tokenAddress, 0.01); // Buy with 0.01 BNB 170 | await trader.sellPancakeToken(tokenAddress, 1000); // Sell 1000 tokens 171 | } else { 172 | // Token still on Four.meme 173 | const result = await trader.buyToken(tokenAddress, 0.01); 174 | console.log(`Estimated tokens: ${result.estimatedTokens}`); 175 | await trader.sellAmount(tokenAddress, parseFloat(result.estimatedTokens)); 176 | } 177 | ``` 178 | 179 | #### Volume Bot Usage 180 | 181 | ```typescript 182 | import VolumeBot from "./src/volume-bot"; 183 | 184 | const params = { 185 | totalBNB: 1.0, // Total BNB to use 186 | minDepositBNB: 0.01, // Min BNB per wallet 187 | maxDepositBNB: 0.05, // Max BNB per wallet 188 | minBuyWalletsNum: 3, // Min wallets per cycle 189 | maxBuyWalletsNum: 5, // Max wallets per cycle 190 | minSellWalletsNum: 1, // Min wallets to sell 191 | maxSellWalletsNum: 3, // Max wallets to sell 192 | minPercentSellAmountAfterBuy: 50, // Min % to sell 193 | maxPercentSellAmountAfterBuy: 80, // Max % to sell 194 | cyclesLimit: 3, // Number of cycles 195 | tokenAddress: "0x...", // Token to trade 196 | gasBufferBNB: 0.001, // Gas buffer 197 | // ... other parameters 198 | }; 199 | 200 | const bot = new VolumeBot(params); 201 | await bot.run(); 202 | ``` 203 | 204 | ## Environment Variables 205 | 206 | ### Required Variables 207 | 208 | | Variable | Description | Default | 209 | | --------------- | ------------------------------- | -------- | 210 | | `PRIVATE_KEY` | Your wallet private key | Required | 211 | | `RPC_URL` | BSC RPC endpoint | Required | 212 | | `TOKEN_ADDRESS` | Token contract address to trade | Required | 213 | 214 | ### Contract Addresses 215 | 216 | | Variable | Description | Default | 217 | | ------------------------ | ------------------------------------------ | -------------------------------------------- | 218 | | `PANCAKE_ROUTER_ADDRESS` | PancakeSwap Router contract | `0x10ED43C718714eb63d5aA57B78B54704E256024E` | 219 | | `WBNB_ADDRESS` | Wrapped BNB contract | `0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c` | 220 | | `STEALTH_FUND_ADDRESS` | StealthFund contract for anonymous funding | `0x3774b227aee720423a62710c7Ce2D70EA16eE0D0` | 221 | 222 | ### Volume Bot Configuration 223 | 224 | | Variable | Description | Default | 225 | | ----------------------------------- | ----------------------------------------- | ------- | 226 | | `TOTAL_BNB` | Total BNB to use for volume trading | `18` | 227 | | `TOTAL_NUM_WALLETS` | Total wallets to create and deposit into | `50` | 228 | | `MIN_DEPOSIT_BNB` | Minimum BNB per wallet | `0.01` | 229 | | `MAX_DEPOSIT_BNB` | Maximum BNB per wallet | `0.02` | 230 | | `MIN_BUY_NUM_PER_CYCLE` | Min wallets to buy per cycle | `3` | 231 | | `MAX_BUY_NUM_PER_CYCLE` | Max wallets to buy per cycle | `5` | 232 | | `MIN_BUY_PERCENT_BNB` | Min % of available BNB to spend | `100` | 233 | | `MAX_BUY_PERCENT_BNB` | Max % of available BNB to spend | `100` | 234 | | `MIN_PERCENT_SELL` | Min % of bought wallets to sell per cycle | `0` | 235 | | `MAX_PERCENT_SELL` | Max % of bought wallets to sell per cycle | `0` | 236 | | `MIN_PERCENT_SELL_AMOUNT_AFTER_BUY` | Min % of tokens to sell | `50` | 237 | | `MAX_PERCENT_SELL_AMOUNT_AFTER_BUY` | Max % of tokens to sell | `80` | 238 | | `CYCLE_LIMIT` | Number of trading cycles | `3` | 239 | | `MIN_PER_CYCLE_TIME` | Min ms between cycles | `5000` | 240 | | `MAX_PER_CYCLE_TIME` | Max ms between cycles | `10000` | 241 | | `MIN_DELAY_BUY` | Min delay before buy (ms) | `2000` | 242 | | `MAX_DELAY_BUY` | Max delay before buy (ms) | `8000` | 243 | | `MIN_DELAY_SELL` | Min delay before sell (ms) | `4000` | 244 | | `MAX_DELAY_SELL` | Max delay before sell (ms) | `12000` | 245 | | `GAS_BUFFER_BNB` | BNB buffer for gas fees | `0.001` | 246 | 247 | ### Optional Features 248 | 249 | | Variable | Description | Default | 250 | | ----------------------------- | ------------------------------------------ | ------- | 251 | | `STEALTH_MODE` | Use StealthFund for wallet funding | `true` | 252 | | `USE_PANCAKE_AFTER_MIGRATION` | Auto-switch to PancakeSwap after migration | `true` | 253 | 254 | ## Security Notes 255 | 256 | - Keep your private keys secure 257 | - Use environment variables for sensitive data 258 | - Test with small amounts first 259 | - Consider using hardware wallets for large amounts 260 | 261 | ## Error Handling 262 | 263 | Common errors and solutions: 264 | 265 | - **"Disabled"**: Token has migrated, use PancakeSwap methods 266 | - **"Insufficient BNB balance"**: Add more BNB to your wallet 267 | - **"Insufficient token balance"**: You don't have enough tokens to sell 268 | - **"Missing environment variable"**: Check your `.env` file 269 | - **"Selling amount is 0 or greater than estimated tokens"**: Adjust sell percentage or check token balance 270 | - **"Funding failed"**: Check StealthFund contract or increase gas price 271 | - **"SKIPPED_LOW_DEPOSIT"**: Increase deposit amount or reduce gas buffer 272 | 273 | ## Wallet Logs 274 | 275 | The VolumeBot creates a `wallets.json` file to track all trading activity: 276 | 277 | ```json 278 | [ 279 | { 280 | "index": 0, 281 | "address": "0x...", 282 | "privateKey": "0x...", 283 | "depositBNB": 0.01, 284 | "buyTxHash": "0x...", 285 | "sellTxHash": "0x...", 286 | "boughtVia": "fourmeme", 287 | "soldVia": "pancake", 288 | "estimatedTokens": "1000000", 289 | "timestamp": "2024-01-01T00:00:00.000Z" 290 | } 291 | ] 292 | ``` 293 | 294 | ## Development 295 | 296 | ```bash 297 | # Install dependencies 298 | npm install 299 | 300 | # Run the volume bot 301 | npm run start 302 | 303 | # Run gather from wallet.json 304 | npm run gather 305 | 306 | # Type checking 307 | npx tsc --noEmit 308 | ``` 309 | 310 | --- 311 | 312 | ## 📩 Contact 313 | For inquiries, custom integrations, or tailored solutions, reach out via: 314 | 315 | 💬 **Telegram**: [@bettyjk_0915](https://t.me/bettyjk_0915) 316 | 317 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import * as dotenv from 'dotenv'; 4 | import * as fs from 'fs'; 5 | import * as path from 'path'; 6 | import { exec } from 'child_process'; 7 | import { promisify } from 'util'; 8 | import { isAddress } from '@validate-ethereum-address/core'; 9 | 10 | const execAsync = promisify(exec); 11 | const app = express(); 12 | 13 | // Change to project root directory 14 | process.chdir(path.join(__dirname, '..')); 15 | 16 | // Allow inline scripts by setting permissive CSP 17 | app.use((req, res, next) => { 18 | res.removeHeader('Content-Security-Policy'); 19 | res.setHeader('Content-Security-Policy', "script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"); 20 | next(); 21 | }); 22 | 23 | app.use(cors()); 24 | app.use(express.json()); 25 | app.use(express.static('public')); 26 | 27 | const PORT = process.env.UI_PORT || 3000; 28 | 29 | // Get current .env values 30 | app.get('/api/env', async (req, res) => { 31 | try { 32 | // Read .env file directly 33 | const envPath = '.env'; 34 | const envData: { [key: string]: string } = {}; 35 | 36 | console.log('Reading env from:', path.resolve(envPath)); 37 | console.log('File exists:', fs.existsSync(envPath)); 38 | 39 | if (fs.existsSync(envPath)) { 40 | const envContent = fs.readFileSync(envPath, 'utf8'); 41 | envContent.split('\n').forEach(line => { 42 | const trimmed = line.trim(); 43 | if (trimmed && !trimmed.startsWith('#')) { 44 | const [key, ...valueParts] = trimmed.split('='); 45 | if (key && valueParts.length > 0) { 46 | envData[key.trim()] = valueParts.join('=').trim(); 47 | } 48 | } 49 | }); 50 | console.log('Loaded env data:', Object.keys(envData)); 51 | } 52 | console.log('envData:', envData); 53 | if (!envData.RPC_URL) { 54 | console.log("RPC_URL is not set"); 55 | return res.status(400).json({ error: 'RPC_URL is not set' }); 56 | } 57 | if (!envData.PRIVATE_KEY) { 58 | console.log("PRIVATE_KEY is not set"); 59 | return res.status(400).json({ error: 'PRIVATE_KEY is not set' }); 60 | } 61 | // Token address is optional for distribution phase 62 | if (envData.TOKEN_ADDRESS && isAddress(envData.TOKEN_ADDRESS, false) === false) { 63 | console.log("TOKEN_ADDRESS is invalid"); 64 | return res.status(400).json({ error: 'Invalid token address' }); 65 | } 66 | res.json({ 67 | TOTAL_BNB: envData.TOTAL_BNB, 68 | TOTAL_NUM_WALLETS: envData.TOTAL_NUM_WALLETS, 69 | MIN_DEPOSIT_BNB: envData.MIN_DEPOSIT_BNB, 70 | MAX_DEPOSIT_BNB: envData.MAX_DEPOSIT_BNB, 71 | MIN_BUY_NUM_PER_CYCLE: envData.MIN_BUY_NUM_PER_CYCLE, 72 | MAX_BUY_NUM_PER_CYCLE: envData.MAX_BUY_NUM_PER_CYCLE, 73 | MIN_BUY_PERCENT_BNB: envData.MIN_BUY_PERCENT_BNB, 74 | MAX_BUY_PERCENT_BNB: envData.MAX_BUY_PERCENT_BNB, 75 | MIN_PERCENT_SELL: envData.MIN_PERCENT_SELL, 76 | MAX_PERCENT_SELL: envData.MAX_PERCENT_SELL, 77 | MIN_PERCENT_SELL_AMOUNT_AFTER_BUY: envData.MIN_PERCENT_SELL_AMOUNT_AFTER_BUY, 78 | MAX_PERCENT_SELL_AMOUNT_AFTER_BUY: envData.MAX_PERCENT_SELL_AMOUNT_AFTER_BUY, 79 | MIN_PER_CYCLE_TIME: envData.MIN_PER_CYCLE_TIME, 80 | MAX_PER_CYCLE_TIME: envData.MAX_PER_CYCLE_TIME, 81 | MIN_DELAY_BUY: envData.MIN_DELAY_BUY, 82 | MAX_DELAY_BUY: envData.MAX_DELAY_BUY, 83 | MIN_DELAY_SELL: envData.MIN_DELAY_SELL, 84 | MAX_DELAY_SELL: envData.MAX_DELAY_SELL, 85 | GAS_BUFFER_BNB: envData.GAS_BUFFER_BNB, 86 | CYCLE_LIMIT: envData.CYCLE_LIMIT, 87 | TOKEN_ADDRESS: envData.TOKEN_ADDRESS, 88 | RPC_URL: envData.RPC_URL, 89 | PRIVATE_KEY: envData.PRIVATE_KEY, 90 | WBNB_ADDRESS: envData.WBNB_ADDRESS, 91 | PANCAKE_ROUTER_ADDRESS: envData.PANCAKE_ROUTER_ADDRESS, 92 | USE_PANCAKE_AFTER_MIGRATION: envData.USE_PANCAKE_AFTER_MIGRATION, 93 | STEALTH_MODE: envData.STEALTH_MODE, 94 | }); 95 | } catch (error) { 96 | res.status(500).json({ error: 'Failed to load env' }); 97 | } 98 | }); 99 | 100 | // Save .env values 101 | app.post('/api/env', async (req, res) => { 102 | try { 103 | const envPath = '.env'; 104 | const envContent = Object.entries(req.body) 105 | .filter(([key]) => req.body[key] !== undefined && req.body[key] !== null) 106 | .map(([key, value]) => `${key}=${value}`) 107 | .join('\n'); 108 | 109 | console.log('envContent:', envContent); 110 | if (!envContent.includes('RPC_URL=')) { 111 | console.log("RPC_URL is not set"); 112 | return res.status(400).json({ error: 'RPC_URL is not set' }); 113 | } 114 | if (!envContent.includes('PRIVATE_KEY=')) { 115 | console.log("PRIVATE_KEY is not set"); 116 | return res.status(400).json({ error: 'PRIVATE_KEY is not set' }); 117 | } 118 | 119 | // Token address is optional for distribution phase 120 | const tokenAddress = envContent.includes('TOKEN_ADDRESS=') ? envContent.split('TOKEN_ADDRESS=')[1].trim() : ''; 121 | if (tokenAddress) { 122 | const tokenAddressTrimmed = tokenAddress.trim().slice(0, 42); 123 | if (isAddress(tokenAddressTrimmed, false) === false) { 124 | console.log("TOKEN_ADDRESS is invalid"); 125 | return res.status(400).json({ error: 'TOKEN_ADDRESS is invalid' }); 126 | } 127 | } 128 | fs.writeFileSync(envPath, envContent, 'utf8'); 129 | res.json({ success: true, message: 'Env updated successfully' }); 130 | } catch (error) { 131 | res.status(500).json({ error: 'Failed to save env' }); 132 | } 133 | }); 134 | 135 | // Start bot (Phase 2 - Trading) 136 | app.post('/api/start', async (req, res) => { 137 | try { 138 | console.log('Starting bot for trading...'); 139 | // Check if already running by looking for actual node/ts-node processes with volume-bot 140 | exec('ps aux | grep "ts-node.*volume-bot" | grep -v grep | grep -v "ps aux"', (err, stdout) => { 141 | const isRunning = stdout && stdout.trim().length > 0; 142 | 143 | if (!isRunning) { 144 | // No process found, so we can start 145 | console.log('No existing bot process found, starting...'); 146 | addBotLog('Bot starting for trading...'); 147 | // Start bot in background - use --trading flag to explicitly run trading mode 148 | // This ensures UI buttons control behavior via flags, ignoring DISTRIBUTE_ONLY from .env 149 | const child = exec('ts-node src/volume-bot.ts --trading', { cwd: process.cwd() }); 150 | child.stdout?.on('data', (data) => { 151 | const logData = data.toString(); 152 | console.log('Bot:', logData); 153 | addBotLog(logData); 154 | }); 155 | child.stderr?.on('data', (data) => { 156 | const logData = data.toString(); 157 | console.error('Bot error:', logData); 158 | addBotLog(`ERROR: ${logData}`); 159 | }); 160 | child.on('exit', (code) => { 161 | addBotLog(`Bot exited with code ${code}`); 162 | }); 163 | res.json({ success: true, message: 'Bot started for trading' }); 164 | } else { 165 | // Process found, already running 166 | console.log('Bot is already running:', stdout.trim()); 167 | res.status(400).json({ error: 'Bot is already running' }); 168 | } 169 | }); 170 | } catch (error) { 171 | console.error('Start error:', error); 172 | res.status(500).json({ error: 'Failed to start bot' }); 173 | } 174 | }); 175 | 176 | // Distribute BNB only (Phase 1) 177 | app.post('/api/distribute', async (req, res) => { 178 | try { 179 | console.log('Starting BNB distribution...'); 180 | // Check if already running 181 | exec('ps aux | grep "ts-node.*volume-bot" | grep -v grep | grep -v "ps aux"', (err, stdout) => { 182 | const isRunning = stdout && stdout.trim().length > 0; 183 | 184 | if (!isRunning) { 185 | console.log('Starting distribution...'); 186 | addBotLog('Starting BNB distribution...'); 187 | // Start bot in distribution-only mode using command line argument 188 | const child = exec('ts-node src/volume-bot.ts --distribute', { cwd: process.cwd() }); 189 | child.stdout?.on('data', (data) => { 190 | const logData = data.toString(); 191 | console.log('Distribution:', logData); 192 | addBotLog(logData); 193 | }); 194 | child.stderr?.on('data', (data) => { 195 | const logData = data.toString(); 196 | console.error('Distribution error:', logData); 197 | addBotLog(`ERROR: ${logData}`); 198 | }); 199 | child.on('exit', (code) => { 200 | addBotLog(`Distribution completed with code ${code}`); 201 | }); 202 | res.json({ success: true, message: 'Distribution started' }); 203 | } else { 204 | console.log('Bot is already running:', stdout.trim()); 205 | res.status(400).json({ error: 'Bot is already running' }); 206 | } 207 | }); 208 | } catch (error) { 209 | console.error('Distribution error:', error); 210 | res.status(500).json({ error: 'Failed to start distribution' }); 211 | } 212 | }); 213 | 214 | // Stop bot (graceful shutdown) 215 | app.post('/api/stop', async (req, res) => { 216 | try { 217 | // Check if running 218 | const { stdout } = await execAsync('ps aux | grep "ts-node.*volume-bot" | grep -v grep | grep -v "ps aux" || true'); 219 | const isRunning = stdout.trim().length > 0; 220 | 221 | if (!isRunning) { 222 | addBotLog('Stop requested: bot already stopped'); 223 | return res.json({ success: true, message: 'Bot already stopped' }); 224 | } 225 | 226 | try { 227 | // Send SIGTERM for graceful shutdown (allows bot to save state) 228 | await execAsync('pkill -SIGTERM -f "ts-node.*volume-bot"'); 229 | addBotLog('Bot stop signal sent (graceful shutdown)'); 230 | return res.json({ success: true, message: 'Bot stop signal sent. State will be saved.' }); 231 | } catch (e) { 232 | // Even if pkill errors, try pkill without signal 233 | try { 234 | await execAsync('pkill -f "ts-node.*volume-bot"'); 235 | addBotLog('Bot stopped'); 236 | return res.json({ success: true, message: 'Bot stopped' }); 237 | } catch (e2) { 238 | addBotLog('Bot stop attempted (no process matched)'); 239 | return res.json({ success: true, message: 'Bot stopped' }); 240 | } 241 | } 242 | } catch (error) { 243 | res.status(500).json({ error: 'Failed to stop bot' }); 244 | } 245 | }); 246 | 247 | // Gather 248 | app.post('/api/gather', async (req, res) => { 249 | try { 250 | // Check if already running 251 | exec('ps aux | grep "ts-node.*gather" | grep -v grep | grep -v "ps aux"', (err, stdout) => { 252 | const isRunning = stdout && stdout.trim().length > 0; 253 | 254 | if (!isRunning) { 255 | console.log('Starting gather...'); 256 | addBotLog('Starting gather process...'); 257 | const child = exec('npm run gather', { cwd: process.cwd() }); 258 | child.stdout?.on('data', (data) => { 259 | const logData = data.toString(); 260 | console.log('Gather:', logData); 261 | addBotLog(logData); 262 | }); 263 | child.stderr?.on('data', (data) => { 264 | const logData = data.toString(); 265 | console.error('Gather error:', logData); 266 | addBotLog(`ERROR: ${logData}`); 267 | }); 268 | child.on('close', (code) => { 269 | const message = code === 0 ? 'Gather completed successfully' : `Gather completed with code ${code}`; 270 | console.log(message); 271 | addBotLog(message); 272 | }); 273 | res.json({ success: true, message: 'Gather started' }); 274 | } else { 275 | console.log('Gather is already running:', stdout.trim()); 276 | res.status(400).json({ error: 'Gather is already running' }); 277 | } 278 | }); 279 | } catch (error) { 280 | console.error('Gather error:', error); 281 | addBotLog(`Failed to start gather: ${error}`); 282 | res.status(500).json({ error: 'Failed to start gather' }); 283 | } 284 | }); 285 | 286 | // Get bot status 287 | app.get('/api/status', async (req, res) => { 288 | try { 289 | const { stdout } = await execAsync('ps aux | grep "ts-node.*volume-bot" | grep -v grep | grep -v "ps aux"'); 290 | res.json({ running: stdout.trim().length > 0 }); 291 | } catch (error) { 292 | res.json({ running: false }); 293 | } 294 | }); 295 | 296 | // Get bot state (for resuming cycles) 297 | app.get('/api/state', async (req, res) => { 298 | try { 299 | const stateFile = 'bot-state.json'; 300 | if (!fs.existsSync(stateFile)) { 301 | return res.json({ state: null }); 302 | } 303 | const stateContent = fs.readFileSync(stateFile, 'utf8'); 304 | const state = JSON.parse(stateContent); 305 | res.json({ state }); 306 | } catch (error) { 307 | console.error('Failed to load bot state:', error); 308 | res.json({ state: null }); 309 | } 310 | }); 311 | 312 | // In-memory log storage 313 | const botLogs: string[] = []; 314 | const maxLogs = 500; 315 | 316 | function addBotLog(message: string) { 317 | botLogs.push(`[${new Date().toISOString()}] ${message}`); 318 | if (botLogs.length > maxLogs) { 319 | botLogs.shift(); 320 | } 321 | } 322 | 323 | // Get bot logs 324 | app.get('/api/logs', (req, res) => { 325 | res.json({ logs: botLogs }); 326 | }); 327 | 328 | // Clear logs 329 | app.post('/api/logs/clear', (req, res) => { 330 | botLogs.length = 0; 331 | res.json({ success: true }); 332 | }); 333 | 334 | app.listen(PORT, () => { 335 | console.log(`Web UI running on http://localhost:${PORT}`); 336 | }); 337 | -------------------------------------------------------------------------------- /src/trader.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import * as dotenv from 'dotenv'; 3 | import { ERC20_ABI, HELPER3_ABI, PANCAKE_ROUTER_ABI, TOKEN_MANAGER_ABI } from './4meme-utils/abi'; 4 | 5 | dotenv.config(); 6 | 7 | export default class FourMemeTrader { 8 | private provider: ethers.JsonRpcProvider; 9 | private wallet: ethers.Wallet; 10 | private tokenManagerAddress: string; 11 | private helper3Address: string; 12 | private pancakeRouterAddress: string; 13 | private wbnbAddress: string; 14 | 15 | constructor() { 16 | // Load environment variables matching Rust implementation 17 | const privateKey = process.env.PRIVATE_KEY; 18 | const rpcUrl = process.env.RPC_URL; 19 | const managerAddress = process.env.TOKEN_MANAGER2 || '0x5c952063c7fc8610FFDB798152D69F0B9550762b'; 20 | const helper3Address = process.env.HELPER3_ADDRESS || '0xF251F83e40a78868FcfA3FA4599Dad6494E46034'; 21 | const pancakeRouterAddress = process.env.PANCAKE_ROUTER_ADDRESS || '0x10ED43C718714eb63d5aA57B78B54704E256024E'; 22 | const wbnbAddress = process.env.WBNB_ADDRESS || '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; 23 | if (!pancakeRouterAddress) { 24 | throw new Error('Missing PANCAKE_ROUTER_ADDRESS in .env'); 25 | } 26 | if (!wbnbAddress) { 27 | throw new Error('Missing WBNB_ADDRESS in .env'); 28 | } 29 | if (!privateKey) { 30 | throw new Error('Missing PRIVATE_KEY in .env'); 31 | } 32 | if (!rpcUrl) { 33 | throw new Error('Missing RPC_URL in .env'); 34 | } 35 | if (!managerAddress) { 36 | throw new Error('Missing TOKEN_MANAGER2 in .env'); 37 | } 38 | if (!helper3Address) { 39 | throw new Error('Missing HELPER3_ADDRESS in .env'); 40 | } 41 | 42 | // Initialize provider + wallet matching Rust 43 | this.provider = new ethers.JsonRpcProvider(rpcUrl, { 44 | name: 'bsc', 45 | chainId: 56 46 | }); 47 | 48 | this.wallet = new ethers.Wallet(privateKey, this.provider); 49 | this.tokenManagerAddress = managerAddress; 50 | this.helper3Address = helper3Address; 51 | this.pancakeRouterAddress = pancakeRouterAddress; 52 | this.wbnbAddress = wbnbAddress; 53 | console.log(`🔗 Connected wallet: ${this.wallet.address}`); 54 | } 55 | 56 | setWallet(wallet: ethers.Wallet) { 57 | this.wallet = wallet; 58 | console.log(`🔗 Set wallet: ${this.wallet.address}`); 59 | } 60 | 61 | getWallet(): ethers.Wallet { 62 | return this.wallet; 63 | } 64 | 65 | // Fetch current gas price for BNB chain 66 | private async getGasPrice(): Promise { 67 | const gasPrice = BigInt(await this.provider.send("eth_gasPrice", [])); 68 | return gasPrice; 69 | } 70 | 71 | async getMigrationStatus(tokenAddress: string): Promise { 72 | try { 73 | const helperContract = new ethers.Contract(this.helper3Address, HELPER3_ABI, this.provider); 74 | const tokenInfo = await helperContract.getTokenInfo(tokenAddress); 75 | const liquidityAdded = tokenInfo[11] as boolean; 76 | return liquidityAdded; 77 | } catch (error) { 78 | return false; 79 | } 80 | } 81 | 82 | async approveToken(tokenAddress: string): Promise { 83 | try { 84 | const erc20Contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 85 | 86 | // Check current allowance 87 | console.log('🔍 Checking current allowance...'); 88 | const currentAllowance = await erc20Contract.allowance(this.wallet.address, this.tokenManagerAddress); 89 | console.log(`📊 Current allowance: ${ethers.formatEther(currentAllowance)} tokens`); 90 | 91 | // Only approve if allowance is insufficient (less than 1 token) 92 | const minAllowance = ethers.parseEther('1'); 93 | if (currentAllowance >= minAllowance) { 94 | console.log('✅ Sufficient allowance already exists, skipping approval'); 95 | return true; 96 | } 97 | 98 | console.log('🔓 Approving TokenManager as spender...'); 99 | const tx = await erc20Contract.approve(this.tokenManagerAddress, ethers.MaxUint256, { gasPrice: await this.getGasPrice() }); 100 | console.log(`✅ Approval tx sent: ${tx.hash}`); 101 | const receipt = await tx.wait(); 102 | console.log(`✅ Approval confirmed! Gas used: ${receipt?.gasUsed.toString()}`); 103 | 104 | // Verify the approval 105 | const newAllowance = await erc20Contract.allowance(this.wallet.address, this.tokenManagerAddress); 106 | console.log(`📊 New allowance: ${ethers.formatEther(newAllowance)} tokens`); 107 | 108 | return true; 109 | } catch (error) { 110 | console.error('❌ Failed to approve token:', error); 111 | return false; 112 | } 113 | } 114 | 115 | // Four.Meme buy token before migration 116 | async buyToken(tokenAddress: string, bnbAmount: number): Promise<{ estimatedTokens: string, realTokenBalance: bigint, txHash: string, gasUsed: string, duration: number }> { 117 | try { 118 | console.log(`🟣 Running buyTokenAMAP (spend fixed BNB)... with Wallet: ${this.wallet.address} and BNB amount: ${bnbAmount}`); 119 | 120 | const fundsToSpend = ethers.parseEther(bnbAmount.toFixed(18)); 121 | console.log(`Funds to spend: ${fundsToSpend}`); 122 | 123 | const helperContract = new ethers.Contract( 124 | this.helper3Address, 125 | HELPER3_ABI, 126 | this.provider 127 | ); 128 | 129 | // Estimate tokens you can buy - matching Rust exactly 130 | console.log('📊 Estimating tokens you can buy...'); 131 | const [, , estimatedTokens] = await helperContract.tryBuy(tokenAddress, 0, fundsToSpend); 132 | const estimatedTokensFormatted = ethers.formatEther(estimatedTokens); 133 | 134 | console.log(`💰 You'll likely receive ~${estimatedTokensFormatted} tokens for ${bnbAmount} BNB`); 135 | 136 | // Get start timestamp (in seconds) - matching Rust exactly 137 | const startTime = Math.floor(Date.now() / 1000); 138 | console.log(`🕒 Start time: ${startTime}`); 139 | 140 | const tokenManagerContract = new ethers.Contract( 141 | this.tokenManagerAddress, 142 | TOKEN_MANAGER_ABI, 143 | this.wallet 144 | ); 145 | 146 | const tx = await tokenManagerContract.buyTokenAMAP( 147 | tokenAddress, 148 | fundsToSpend, 149 | 0, // minAmount 150 | { value: fundsToSpend, gasPrice: await this.getGasPrice() } 151 | ); 152 | 153 | // Get end timestamp (in seconds) - matching Rust exactly 154 | const endTime = Math.floor(Date.now() / 1000); 155 | const duration = endTime - startTime; 156 | 157 | console.log(`✅ Transaction confirmed in ${duration} seconds`); 158 | console.log(`✅ buyTokenAMAP tx sent: ${tx.hash}`); 159 | 160 | const receipt = await tx.wait(); 161 | console.log(`✅ Transaction confirmed! Gas used: ${receipt?.gasUsed.toString()}`); 162 | 163 | // ✅ Fetch real token balance after buy 164 | const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 165 | const realTokenBalance = await tokenContract.balanceOf(this.wallet.address); 166 | 167 | return { 168 | estimatedTokens: estimatedTokensFormatted, 169 | realTokenBalance: realTokenBalance, 170 | txHash: tx.hash, 171 | gasUsed: receipt?.gasUsed.toString(), 172 | duration: duration, 173 | }; 174 | } catch (error) { 175 | return { 176 | estimatedTokens: '0', 177 | realTokenBalance: BigInt(0), 178 | txHash: '', 179 | gasUsed: '0', 180 | duration: 0, 181 | }; 182 | } 183 | } 184 | 185 | async buyTokenBigInt(tokenAddress: string, bnbAmount: BigInt): Promise<{ estimatedTokens: string, realTokenBalance: bigint, txHash: string, gasUsed: string, duration: number }> { 186 | try { 187 | console.log(`🟣 Running buyTokenAMAP (spend fixed BNB)... with Wallet: ${this.wallet.address} and BNB amount: ${ethers.formatEther(bnbAmount as any)}`); 188 | 189 | const fundsToSpend = bnbAmount;//ethers.parseEther(bnbAmount.toFixed(18)); 190 | console.log(`Funds to spend: ${fundsToSpend}`); 191 | 192 | const helperContract = new ethers.Contract( 193 | this.helper3Address, 194 | HELPER3_ABI, 195 | this.provider 196 | ); 197 | 198 | // Estimate tokens you can buy - matching Rust exactly 199 | console.log('📊 Estimating tokens you can buy...'); 200 | const [, , estimatedTokens] = await helperContract.tryBuy(tokenAddress, 0, fundsToSpend); 201 | const estimatedTokensFormatted = ethers.formatEther(estimatedTokens); 202 | 203 | console.log(`💰 You'll likely receive ~${estimatedTokensFormatted} tokens for ${bnbAmount} BNB`); 204 | 205 | // Get start timestamp (in seconds) - matching Rust exactly 206 | const startTime = Math.floor(Date.now() / 1000); 207 | console.log(`🕒 Start time: ${startTime}`); 208 | 209 | const tokenManagerContract = new ethers.Contract( 210 | this.tokenManagerAddress, 211 | TOKEN_MANAGER_ABI, 212 | this.wallet 213 | ); 214 | 215 | const tx = await tokenManagerContract.buyTokenAMAP( 216 | tokenAddress, 217 | fundsToSpend, 218 | 0, // minAmount 219 | { value: fundsToSpend, gasPrice: await this.getGasPrice() } 220 | ); 221 | 222 | // Get end timestamp (in seconds) - matching Rust exactly 223 | const endTime = Math.floor(Date.now() / 1000); 224 | const duration = endTime - startTime; 225 | 226 | console.log(`✅ Transaction confirmed in ${duration} seconds`); 227 | console.log(`✅ buyTokenAMAP tx sent: ${tx.hash}`); 228 | 229 | const receipt = await tx.wait(); 230 | console.log(`✅ Transaction confirmed! Gas used: ${receipt?.gasUsed.toString()}`); 231 | 232 | // ✅ Fetch real token balance after buy 233 | const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 234 | const realTokenBalance = await tokenContract.balanceOf(this.wallet.address); 235 | 236 | return { 237 | estimatedTokens: estimatedTokensFormatted, 238 | realTokenBalance: realTokenBalance, 239 | txHash: tx.hash, 240 | gasUsed: receipt?.gasUsed.toString(), 241 | duration: duration, 242 | }; 243 | } catch (error) { 244 | return { 245 | estimatedTokens: '0', 246 | realTokenBalance: BigInt(0), 247 | txHash: '', 248 | gasUsed: '0', 249 | duration: 0, 250 | }; 251 | } 252 | } 253 | 254 | 255 | async getTokenBalance(tokenAddress: string): Promise { 256 | const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 257 | const balance = await tokenContract.balanceOf(this.wallet.address); 258 | return balance; 259 | } 260 | 261 | async getTokenDecimals(tokenAddress: string): Promise { 262 | const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 263 | const decimals = await tokenContract.decimals(); 264 | return decimals; 265 | } 266 | 267 | async approveTokenManager(tokenAddress: string): Promise { 268 | const erc20Contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 269 | const allowance = await erc20Contract.allowance(this.wallet.address, this.tokenManagerAddress); 270 | console.log(`📊 Allowance: ${ethers.formatEther(allowance)} tokens`); 271 | if (allowance < ethers.MaxUint256) { 272 | const approveTx = await erc20Contract.approve(this.tokenManagerAddress, ethers.MaxUint256, { gasPrice: await this.getGasPrice() }); 273 | console.log(`✅ Approval tx sent: ${approveTx.hash}`); 274 | await approveTx.wait(); 275 | } 276 | return true; 277 | } 278 | 279 | // Four.Meme sell token before migration 280 | async sellAmount(tokenAddress: string, tokenAmount: number): Promise { 281 | try { 282 | console.log(`🔵 Running sellToken (sell exact amount)...`); 283 | 284 | const amountToSell = ethers.parseEther(tokenAmount.toString()); 285 | console.log(`Amount to sell: ${amountToSell}`); 286 | console.log(`Token amount: ${tokenAmount}`); 287 | 288 | const erc20Contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 289 | 290 | const allowance = await erc20Contract.allowance(this.wallet.address, this.tokenManagerAddress); 291 | console.log(`📊 Allowance: ${ethers.formatEther(allowance)} tokens`); 292 | 293 | if (allowance < amountToSell) { 294 | // Approve first - matching Rust exactly 295 | console.log('🔓 Approving TokenManager2 as spender...'); 296 | const approveTx = await erc20Contract.approve(this.tokenManagerAddress, ethers.MaxUint256, { gasPrice: await this.getGasPrice() }); 297 | console.log(`✅ Approval tx sent: ${approveTx.hash}`); 298 | await approveTx.wait(); 299 | 300 | // Wait 5 seconds like Rust 301 | console.log('⏳ Waiting 5 seconds before sell transaction...'); 302 | await new Promise(resolve => setTimeout(resolve, 5000)); 303 | } 304 | 305 | const tokenManagerContract = new ethers.Contract( 306 | this.tokenManagerAddress, 307 | TOKEN_MANAGER_ABI, 308 | this.wallet 309 | ); 310 | 311 | const tx = await tokenManagerContract.sellToken(tokenAddress, amountToSell, { gasPrice: await this.getGasPrice() }); 312 | console.log(`✅ sellToken tx sent: ${tx.hash}`); 313 | 314 | const receipt = await tx.wait(); 315 | console.log(`✅ Transaction confirmed! Gas used: ${receipt?.gasUsed.toString()}`); 316 | 317 | return tx.hash; 318 | } catch (error) { 319 | console.error('❌ Failed to sell amount:', error); 320 | throw error; 321 | } 322 | } 323 | 324 | // Four.Meme sell token before migration 325 | async sellAmountBigInt(tokenAddress: string, tokenAmount: bigint): Promise { 326 | try { 327 | console.log(`🔵 Running sellToken (sell exact amount)...`); 328 | 329 | const amountToSell = tokenAmount; 330 | 331 | const erc20Contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 332 | 333 | const allowance = await erc20Contract.allowance(this.wallet.address, this.tokenManagerAddress); 334 | console.log(`📊 Allowance: ${ethers.formatEther(allowance)} tokens`); 335 | 336 | if (allowance < amountToSell) { 337 | // Approve first - matching Rust exactly 338 | console.log('🔓 Approving TokenManager2 as spender...'); 339 | const approveTx = await erc20Contract.approve(this.tokenManagerAddress, ethers.MaxUint256, { gasPrice: await this.getGasPrice() }); 340 | console.log(`✅ Approval tx sent: ${approveTx.hash}`); 341 | await approveTx.wait(); 342 | 343 | // Wait 5 seconds like Rust 344 | console.log('⏳ Waiting 5 seconds before sell transaction...'); 345 | await new Promise(resolve => setTimeout(resolve, 5000)); 346 | } 347 | 348 | const tokenManagerContract = new ethers.Contract( 349 | this.tokenManagerAddress, 350 | TOKEN_MANAGER_ABI, 351 | this.wallet 352 | ); 353 | 354 | const tx = await tokenManagerContract.sellToken(tokenAddress, amountToSell, { gasPrice: await this.getGasPrice() }); 355 | console.log(`✅ sellToken tx sent: ${tx.hash}`); 356 | 357 | const receipt = await tx.wait(); 358 | console.log(`✅ Transaction confirmed! Gas used: ${receipt?.gasUsed.toString()}`); 359 | 360 | return tx.hash; 361 | } catch (error) { 362 | console.error('❌ Failed to sell amount:', error); 363 | throw error; 364 | } 365 | } 366 | 367 | // Buy token via PancakeRouter after migration 368 | async buyPancakeToken(tokenAddress: string, bnbAmount: number): Promise<{ realTokenBalance: bigint, txHash: string, gasUsed: string }> { 369 | try { 370 | console.log(`🟣 Running buyPancakeToken (spend fixed BNB)...`); 371 | const fundsToSpend = ethers.parseEther(bnbAmount.toString()); 372 | console.log(`💰 Funds to spend: ${fundsToSpend} BNB`); 373 | const pancakeRouterContract = new ethers.Contract(this.pancakeRouterAddress, PANCAKE_ROUTER_ABI, this.wallet); 374 | 375 | const tx = await pancakeRouterContract.swapExactETHForTokens( 376 | 0, // amountOutMin = 0 for maximum speed 377 | [this.wbnbAddress, tokenAddress], 378 | this.wallet.address, 379 | Math.floor(Date.now() / 1000) + 3600, 380 | { value: fundsToSpend, gasPrice: await this.getGasPrice() } 381 | ); 382 | const receipt = await tx.wait(); 383 | 384 | // ✅ Fetch real token balance after sell 385 | const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 386 | const realTokenBalance = await tokenContract.balanceOf(this.wallet.address); 387 | 388 | return { 389 | realTokenBalance: realTokenBalance, 390 | txHash: tx.hash, 391 | gasUsed: receipt?.gasUsed.toString() 392 | }; 393 | } 394 | catch (error) { 395 | return { 396 | realTokenBalance: BigInt(0), 397 | txHash: '', 398 | gasUsed: '0' 399 | }; 400 | } 401 | } 402 | 403 | async buyPancakeTokenBigInt(tokenAddress: string, bnbAmount: BigInt): Promise<{ realTokenBalance: bigint, txHash: string, gasUsed: string }> { 404 | try { 405 | console.log(`🟣 Running buyPancakeToken (spend fixed BNB)...`); 406 | const fundsToSpend = bnbAmount;//ethers.parseEther(bnbAmount.toString()); 407 | console.log(`💰 Funds to spend: ${fundsToSpend} BNB`); 408 | const pancakeRouterContract = new ethers.Contract(this.pancakeRouterAddress, PANCAKE_ROUTER_ABI, this.wallet); 409 | 410 | const tx = await pancakeRouterContract.swapExactETHForTokens( 411 | 0, // amountOutMin = 0 for maximum speed 412 | [this.wbnbAddress, tokenAddress], 413 | this.wallet.address, 414 | Math.floor(Date.now() / 1000) + 3600, 415 | { value: fundsToSpend, gasPrice: await this.getGasPrice() } 416 | ); 417 | const receipt = await tx.wait(); 418 | 419 | // ✅ Fetch real token balance after sell 420 | const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 421 | const realTokenBalance = await tokenContract.balanceOf(this.wallet.address); 422 | 423 | return { 424 | realTokenBalance: realTokenBalance, 425 | txHash: tx.hash, 426 | gasUsed: receipt?.gasUsed.toString() 427 | }; 428 | } 429 | catch (error) { 430 | return { 431 | realTokenBalance: BigInt(0), 432 | txHash: '', 433 | gasUsed: '0' 434 | }; 435 | } 436 | } 437 | 438 | async approvePancakeRouter(tokenAddress: string): Promise { 439 | const erc20Contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 440 | const allowance = await erc20Contract.allowance(this.wallet.address, this.pancakeRouterAddress); 441 | console.log(`📊 Allowance: ${ethers.formatEther(allowance)} tokens`); 442 | if (allowance < ethers.MaxUint256) { 443 | const approveTx = await erc20Contract.approve(this.pancakeRouterAddress, ethers.MaxUint256, { gasPrice: await this.getGasPrice() }); 444 | console.log(`✅ Approval tx sent: ${approveTx.hash}`); 445 | } 446 | return true; 447 | } 448 | 449 | // Sell token via PancakeRouter after migration 450 | async sellPancakeToken(tokenAddress: string, tokenAmount: number): Promise<{ txHash: string, gasUsed: string }> { 451 | try { 452 | console.log(`🔵 Running sellPancakeToken (sell exact amount)...`); 453 | const amountToSell = ethers.parseEther(tokenAmount.toString()); 454 | console.log(`💰 Amount to sell: ${amountToSell} tokens`); 455 | const pancakeRouterContract = new ethers.Contract(this.pancakeRouterAddress, PANCAKE_ROUTER_ABI, this.wallet); 456 | 457 | const erc20Contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 458 | 459 | const allowance = await erc20Contract.allowance(this.wallet.address, this.pancakeRouterAddress); 460 | console.log(`📊 Allowance: ${ethers.formatEther(allowance)} tokens`); 461 | 462 | if (allowance < amountToSell) { 463 | // Approve first - matching Rust exactly 464 | console.log('🔓 Approving PancakeRouter as spender...'); 465 | const approveTx = await erc20Contract.approve(this.pancakeRouterAddress, ethers.MaxUint256, { gasPrice: await this.getGasPrice() }); 466 | console.log(`✅ Approval tx sent: ${approveTx.hash}`); 467 | await approveTx.wait(); 468 | 469 | // Wait 5 seconds like Rust 470 | console.log('⏳ Waiting 5 seconds before sell transaction...'); 471 | await new Promise(resolve => setTimeout(resolve, 5000)); 472 | } 473 | 474 | const tx = await pancakeRouterContract.swapExactTokensForETH( 475 | amountToSell, 476 | 0, 477 | [tokenAddress, this.wbnbAddress], 478 | this.wallet.address, 479 | Math.floor(Date.now() / 1000) + 3600, 480 | { gasPrice: await this.getGasPrice() } 481 | ); 482 | const receipt = await tx.wait(); 483 | console.log(`✅ Transaction confirmed! Gas used: ${receipt?.gasUsed.toString()}`); 484 | console.log(`✅ sellPancakeToken tx sent: ${tx.hash}`); 485 | return { 486 | txHash: tx.hash, 487 | gasUsed: receipt?.gasUsed.toString() 488 | }; 489 | } 490 | catch (error) { 491 | console.error('❌ Failed to sell pancake token:', error); 492 | return { 493 | txHash: '', 494 | gasUsed: '0' 495 | }; 496 | } 497 | } 498 | 499 | async sellPancakeTokenBigInt(tokenAddress: string, tokenAmount: bigint): Promise<{ txHash: string, gasUsed: string }> { 500 | try { 501 | console.log(`🔵 Running sellPancakeToken (sell exact amount)...`); 502 | const amountToSell = tokenAmount; 503 | console.log(`💰 Amount to sell: ${amountToSell} tokens`); 504 | const pancakeRouterContract = new ethers.Contract(this.pancakeRouterAddress, PANCAKE_ROUTER_ABI, this.wallet); 505 | 506 | const erc20Contract = new ethers.Contract(tokenAddress, ERC20_ABI, this.wallet); 507 | 508 | const allowance = await erc20Contract.allowance(this.wallet.address, this.pancakeRouterAddress); 509 | console.log(`📊 Allowance: ${ethers.formatEther(allowance)} tokens`); 510 | 511 | if (allowance < amountToSell) { 512 | // Approve first - matching Rust exactly 513 | console.log('🔓 Approving PancakeRouter as spender...'); 514 | const approveTx = await erc20Contract.approve(this.pancakeRouterAddress, ethers.MaxUint256, { gasPrice: await this.getGasPrice() }); 515 | console.log(`✅ Approval tx sent: ${approveTx.hash}`); 516 | await approveTx.wait(); 517 | 518 | // Wait 5 seconds like Rust 519 | console.log('⏳ Waiting 5 seconds before sell transaction...'); 520 | await new Promise(resolve => setTimeout(resolve, 5000)); 521 | } 522 | 523 | const tx = await pancakeRouterContract.swapExactTokensForETH( 524 | amountToSell, 525 | 0, 526 | [tokenAddress, this.wbnbAddress], 527 | this.wallet.address, 528 | Math.floor(Date.now() / 1000) + 3600, 529 | { gasPrice: await this.getGasPrice() } 530 | ); 531 | const receipt = await tx.wait(); 532 | console.log(`✅ Transaction confirmed! Gas used: ${receipt?.gasUsed.toString()}`); 533 | console.log(`✅ sellPancakeToken tx sent: ${tx.hash}`); 534 | return { 535 | txHash: tx.hash, 536 | gasUsed: receipt?.gasUsed.toString() 537 | }; 538 | } 539 | catch (error) { 540 | console.error('❌ Failed to sell pancake token:', error); 541 | return { 542 | txHash: '', 543 | gasUsed: '0' 544 | }; 545 | } 546 | } 547 | } 548 | 549 | // Main execution - matching Rust implementation exactly 550 | // async function main() { 551 | // const trader = new FourMemeTrader(); 552 | // const tokenAddress = '0xd4d5f202dc0c4395ab27bccd9ff0f55c3d1d4444'; 553 | // const migrationStatus = await trader.getMigrationStatus(tokenAddress); 554 | // if (migrationStatus) { 555 | // console.log('✅ Migration Status: True'); 556 | // const buyAmount = 0.00001; 557 | // await trader.buyPancakeToken(tokenAddress, buyAmount); 558 | // const sellAmount = 10; 559 | // await trader.sellPancakeToken(tokenAddress, sellAmount); 560 | // } else { 561 | // console.log('❌ Migration Status: False'); 562 | // const buyAmount = 0.00001; 563 | // const { estimatedTokens } = await trader.buyToken(tokenAddress, buyAmount); 564 | // console.log(`💰 Estimated Tokens: ${estimatedTokens}`); 565 | // const sellAmount = estimatedTokens; 566 | // await trader.sellAmount(tokenAddress, Number(sellAmount)); 567 | // } 568 | // } 569 | 570 | // main().catch(console.error); -------------------------------------------------------------------------------- /src/Stealth-utils/StealthFund.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "StealthFund", 4 | "sourceName": "contracts/StealthFund.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "stateMutability": "nonpayable", 9 | "type": "constructor" 10 | }, 11 | { 12 | "inputs": [ 13 | { 14 | "internalType": "address", 15 | "name": "target", 16 | "type": "address" 17 | } 18 | ], 19 | "name": "AddressEmptyCode", 20 | "type": "error" 21 | }, 22 | { 23 | "inputs": [ 24 | { 25 | "internalType": "address", 26 | "name": "implementation", 27 | "type": "address" 28 | } 29 | ], 30 | "name": "ERC1967InvalidImplementation", 31 | "type": "error" 32 | }, 33 | { 34 | "inputs": [], 35 | "name": "ERC1967NonPayable", 36 | "type": "error" 37 | }, 38 | { 39 | "inputs": [], 40 | "name": "FailedCall", 41 | "type": "error" 42 | }, 43 | { 44 | "inputs": [], 45 | "name": "InvalidInitialization", 46 | "type": "error" 47 | }, 48 | { 49 | "inputs": [], 50 | "name": "NotInitializing", 51 | "type": "error" 52 | }, 53 | { 54 | "inputs": [ 55 | { 56 | "internalType": "address", 57 | "name": "owner", 58 | "type": "address" 59 | } 60 | ], 61 | "name": "OwnableInvalidOwner", 62 | "type": "error" 63 | }, 64 | { 65 | "inputs": [ 66 | { 67 | "internalType": "address", 68 | "name": "account", 69 | "type": "address" 70 | } 71 | ], 72 | "name": "OwnableUnauthorizedAccount", 73 | "type": "error" 74 | }, 75 | { 76 | "inputs": [], 77 | "name": "ReentrancyGuardReentrantCall", 78 | "type": "error" 79 | }, 80 | { 81 | "inputs": [], 82 | "name": "UUPSUnauthorizedCallContext", 83 | "type": "error" 84 | }, 85 | { 86 | "inputs": [ 87 | { 88 | "internalType": "bytes32", 89 | "name": "slot", 90 | "type": "bytes32" 91 | } 92 | ], 93 | "name": "UUPSUnsupportedProxiableUUID", 94 | "type": "error" 95 | }, 96 | { 97 | "anonymous": false, 98 | "inputs": [ 99 | { 100 | "indexed": true, 101 | "internalType": "address", 102 | "name": "forwarder", 103 | "type": "address" 104 | }, 105 | { 106 | "indexed": true, 107 | "internalType": "address", 108 | "name": "recipient", 109 | "type": "address" 110 | }, 111 | { 112 | "indexed": false, 113 | "internalType": "uint256", 114 | "name": "amount", 115 | "type": "uint256" 116 | } 117 | ], 118 | "name": "ForwarderDeployed", 119 | "type": "event" 120 | }, 121 | { 122 | "anonymous": false, 123 | "inputs": [ 124 | { 125 | "indexed": false, 126 | "internalType": "uint64", 127 | "name": "version", 128 | "type": "uint64" 129 | } 130 | ], 131 | "name": "Initialized", 132 | "type": "event" 133 | }, 134 | { 135 | "anonymous": false, 136 | "inputs": [ 137 | { 138 | "indexed": true, 139 | "internalType": "address", 140 | "name": "previousOwner", 141 | "type": "address" 142 | }, 143 | { 144 | "indexed": true, 145 | "internalType": "address", 146 | "name": "newOwner", 147 | "type": "address" 148 | } 149 | ], 150 | "name": "OwnershipTransferred", 151 | "type": "event" 152 | }, 153 | { 154 | "anonymous": false, 155 | "inputs": [ 156 | { 157 | "indexed": false, 158 | "internalType": "string", 159 | "name": "key", 160 | "type": "string" 161 | }, 162 | { 163 | "indexed": false, 164 | "internalType": "address", 165 | "name": "value", 166 | "type": "address" 167 | } 168 | ], 169 | "name": "ParamsUpdatedPlatform", 170 | "type": "event" 171 | }, 172 | { 173 | "anonymous": false, 174 | "inputs": [ 175 | { 176 | "indexed": false, 177 | "internalType": "string", 178 | "name": "key", 179 | "type": "string" 180 | }, 181 | { 182 | "indexed": false, 183 | "internalType": "address", 184 | "name": "value", 185 | "type": "address" 186 | } 187 | ], 188 | "name": "ParamsUpdatedTreasury", 189 | "type": "event" 190 | }, 191 | { 192 | "anonymous": false, 193 | "inputs": [ 194 | { 195 | "indexed": true, 196 | "internalType": "address", 197 | "name": "from", 198 | "type": "address" 199 | }, 200 | { 201 | "indexed": true, 202 | "internalType": "address", 203 | "name": "to", 204 | "type": "address" 205 | }, 206 | { 207 | "indexed": false, 208 | "internalType": "uint256", 209 | "name": "amount", 210 | "type": "uint256" 211 | } 212 | ], 213 | "name": "StealthFundSent", 214 | "type": "event" 215 | }, 216 | { 217 | "anonymous": false, 218 | "inputs": [ 219 | { 220 | "indexed": true, 221 | "internalType": "address", 222 | "name": "implementation", 223 | "type": "address" 224 | } 225 | ], 226 | "name": "Upgraded", 227 | "type": "event" 228 | }, 229 | { 230 | "inputs": [], 231 | "name": "UPGRADE_INTERFACE_VERSION", 232 | "outputs": [ 233 | { 234 | "internalType": "string", 235 | "name": "", 236 | "type": "string" 237 | } 238 | ], 239 | "stateMutability": "view", 240 | "type": "function" 241 | }, 242 | { 243 | "inputs": [], 244 | "name": "WETH", 245 | "outputs": [ 246 | { 247 | "internalType": "address", 248 | "name": "", 249 | "type": "address" 250 | } 251 | ], 252 | "stateMutability": "view", 253 | "type": "function" 254 | }, 255 | { 256 | "inputs": [], 257 | "name": "initialize", 258 | "outputs": [], 259 | "stateMutability": "nonpayable", 260 | "type": "function" 261 | }, 262 | { 263 | "inputs": [], 264 | "name": "owner", 265 | "outputs": [ 266 | { 267 | "internalType": "address", 268 | "name": "", 269 | "type": "address" 270 | } 271 | ], 272 | "stateMutability": "view", 273 | "type": "function" 274 | }, 275 | { 276 | "inputs": [], 277 | "name": "platformWallet", 278 | "outputs": [ 279 | { 280 | "internalType": "address", 281 | "name": "", 282 | "type": "address" 283 | } 284 | ], 285 | "stateMutability": "view", 286 | "type": "function" 287 | }, 288 | { 289 | "inputs": [], 290 | "name": "proxiableUUID", 291 | "outputs": [ 292 | { 293 | "internalType": "bytes32", 294 | "name": "", 295 | "type": "bytes32" 296 | } 297 | ], 298 | "stateMutability": "view", 299 | "type": "function" 300 | }, 301 | { 302 | "inputs": [], 303 | "name": "renounceOwnership", 304 | "outputs": [], 305 | "stateMutability": "nonpayable", 306 | "type": "function" 307 | }, 308 | { 309 | "inputs": [ 310 | { 311 | "internalType": "address", 312 | "name": "_platformWallet", 313 | "type": "address" 314 | } 315 | ], 316 | "name": "setAdmin", 317 | "outputs": [], 318 | "stateMutability": "nonpayable", 319 | "type": "function" 320 | }, 321 | { 322 | "inputs": [ 323 | { 324 | "internalType": "uint256[]", 325 | "name": "_amounts", 326 | "type": "uint256[]" 327 | }, 328 | { 329 | "internalType": "address[]", 330 | "name": "_wallets", 331 | "type": "address[]" 332 | } 333 | ], 334 | "name": "stealthMultipleFund", 335 | "outputs": [], 336 | "stateMutability": "payable", 337 | "type": "function" 338 | }, 339 | { 340 | "inputs": [ 341 | { 342 | "internalType": "address", 343 | "name": "newOwner", 344 | "type": "address" 345 | } 346 | ], 347 | "name": "transferOwnership", 348 | "outputs": [], 349 | "stateMutability": "nonpayable", 350 | "type": "function" 351 | }, 352 | { 353 | "inputs": [], 354 | "name": "treasuryWallet", 355 | "outputs": [ 356 | { 357 | "internalType": "address", 358 | "name": "", 359 | "type": "address" 360 | } 361 | ], 362 | "stateMutability": "view", 363 | "type": "function" 364 | }, 365 | { 366 | "inputs": [ 367 | { 368 | "internalType": "address", 369 | "name": "value", 370 | "type": "address" 371 | } 372 | ], 373 | "name": "updateTreasuryWallet", 374 | "outputs": [], 375 | "stateMutability": "nonpayable", 376 | "type": "function" 377 | }, 378 | { 379 | "inputs": [ 380 | { 381 | "internalType": "address", 382 | "name": "newImplementation", 383 | "type": "address" 384 | }, 385 | { 386 | "internalType": "bytes", 387 | "name": "data", 388 | "type": "bytes" 389 | } 390 | ], 391 | "name": "upgradeToAndCall", 392 | "outputs": [], 393 | "stateMutability": "payable", 394 | "type": "function" 395 | }, 396 | { 397 | "stateMutability": "payable", 398 | "type": "receive" 399 | } 400 | ], 401 | "bytecode": "0x60a080604052346100ea57306080527ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c166100d9576002600160401b03196001600160401b03821601610073575b604051610eca90816100f082396080518181816105aa015261067e0152f35b6001600160401b0319166001600160401b039081177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005581527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a13880610054565b63f92ee8a960e01b60005260046000fd5b600080fdfe6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c80631f7e75c81461086b5780634626402b146108425780634f1ef2861461060157806352d1902d14610597578063704b6c02146104c4578063715018a61461045a578063809d458d1461037f5780638129fc1c146101e15780638da5cb5b146101ab578063ad3cb1cc1461012f578063ad5c464814610106578063f2fde38b146100dd5763fa2af9da0361000e57346100d85760003660031901126100d8576001546040516001600160a01b039091168152602090f35b600080fd5b346100d85760203660031901126100d8576100196100f9610bd2565b610101610ca4565b610c2e565b346100d85760003660031901126100d8576000546040516001600160a01b039091168152602090f35b346100d85760003660031901126100d857604080519061014f8183610b98565b60058252640352e302e360dc1b6020830152805180926020825280519081602084015260005b8281106101945750506000828201840152601f01601f19168101030190f35b602082820181015187830187015286945001610175565b346100d85760003660031901126100d857600080516020610e3e833981519152546040516001600160a01b039091168152602090f35b346100d85760003660031901126100d857600080516020610e9e8339815191525460ff8160401c16159067ffffffffffffffff811680159081610377575b600114908161036d575b159081610364575b506103535767ffffffffffffffff198116600117600080516020610e9e8339815191525581610326575b50610264610cda565b61026c610cda565b61027533610c2e565b61027d610cda565b610285610cda565b61028d610cda565b6001600080516020610e7e83398151915255336001600160601b0360a01b6002541617600255336001600160601b0360a01b60015416176001556102cd57005b68ff000000000000000019600080516020610e9e8339815191525416600080516020610e9e833981519152557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a1005b68ffffffffffffffffff19166801000000000000000117600080516020610e9e833981519152558161025b565b63f92ee8a960e01b60005260046000fd5b90501583610231565b303b159150610229565b83915061021f565b346100d85760203660031901126100d857610398610bd2565b6103a0610ca4565b6001600160a01b03168015610415576080817f1e56b377b522438e71796d1885290a090be46e7ebba978e21e1b68da06d29598926001600160601b0360a01b60025416176002556040519060408252600e60408301526d1d1c99585cdd5c9e55d85b1b195d60921b60608301526020820152a1005b60405162461bcd60e51b815260206004820152601860248201527f496e76616c6964207472656173757279206164647265737300000000000000006044820152606490fd5b346100d85760003660031901126100d857610473610ca4565b600080516020610e3e83398151915280546001600160a01b031981169091556000906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b346100d85760203660031901126100d8576104dd610bd2565b6104e5610ca4565b6001600160a01b0316801561055a576080817f247fc6b6060a248bd48c4b239fbf251adc2b73a9695b3b181598f02aa931e877926001600160601b0360a01b60015416176001556040519060408252600e60408301526d1c1b185d199bdc9b55d85b1b195d60921b60608301526020820152a1005b60405162461bcd60e51b8152602060048201526015602482015274496e76616c69642061646d696e206164647265737360581b6044820152606490fd5b346100d85760003660031901126100d8577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036105f0576020604051600080516020610e5e8339815191528152f35b63703e46dd60e11b60005260046000fd5b60403660031901126100d857610615610bd2565b6024359067ffffffffffffffff82116100d857366023830112156100d85781600401359061064282610be8565b916106506040519384610b98565b808352602083019336602483830101116100d857816000926024602093018737840101526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630811490811561081f575b506105f0576106b6610ca4565b6040516352d1902d60e01b81526001600160a01b0382169390602081600481885afa600091816107eb575b506106fb5784634c9c8ce360e01b60005260045260246000fd5b80600080516020610e5e8339815191528692036107d75750823b156107c357600080516020610e5e83398151915280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28251156107a85760008091610019945190845af43d156107a0573d9161078383610be8565b926107916040519485610b98565b83523d6000602085013e610d08565b606091610d08565b505050346107b257005b63b398979f60e01b60005260046000fd5b634c9c8ce360e01b60005260045260246000fd5b632a87526960e21b60005260045260246000fd5b9091506020813d602011610817575b8161080760209383610b98565b810103126100d8575190866106e1565b3d91506107fa565b600080516020610e5e833981519152546001600160a01b031614159050846106a9565b346100d85760003660031901126100d8576002546040516001600160a01b039091168152602090f35b60403660031901126100d85760043567ffffffffffffffff81116100d857366023820112156100d8578060040135906108a382610bba565b916108b16040519384610b98565b8083526024602084019160051b830101913683116100d857602401905b828210610b88578360243567ffffffffffffffff81116100d857366023820112156100d857806004013561090181610bba565b9161090f6040519384610b98565b8183526024602084019260051b820101903682116100d857602401915b818310610b68575050506002600080516020610e7e8339815191525414610b57576002600080516020610e7e833981519152558151815103610b2057815115610af157600091825b81518410156109b2576109878483610c04565b51810180911161099c57600190930192610974565b634e487b7160e01b600052601160045260246000fd5b3410610aac57610d6a9060d460005b8251811015610a98576109d48184610c04565b516001600160a01b036109e78388610c04565b5116604051908482019082821067ffffffffffffffff831117610a82576020918391878a84398152030191f0908115610a76576001916001600160a01b03610a2f8389610c04565b5116907fad92a691bda70ce78bd3e14e780181bdf98abb197ed2b962ed1137f574840cb36020610a5f8589610c04565b5192604051938452868060a01b031692a3016109c1565b6040513d6000823e3d90fd5b634e487b7160e01b600052604160045260246000fd5b6001600080516020610e7e83398151915255005b60405162461bcd60e51b815260206004820152601960248201527f6d73672e76616c7565203c20746f74616c20616d6f756e7473000000000000006044820152606490fd5b60405162461bcd60e51b81526020600482015260076024820152664e6f206461746160c81b6044820152606490fd5b60405162461bcd60e51b815260206004820152600f60248201526e098cadccee8d040dad2e6dac2e8c6d608b1b6044820152606490fd5b633ee5aeb560e01b60005260046000fd5b82356001600160a01b03811681036100d85781526020928301920161092c565b81358152602091820191016108ce565b90601f8019910116810190811067ffffffffffffffff821117610a8257604052565b67ffffffffffffffff8111610a825760051b60200190565b600435906001600160a01b03821682036100d857565b67ffffffffffffffff8111610a8257601f01601f191660200190565b8051821015610c185760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b03168015610c8e57600080516020610e3e83398151915280546001600160a01b0319811683179091556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3565b631e4fbdf760e01b600052600060045260246000fd5b600080516020610e3e833981519152546001600160a01b03163303610cc557565b63118cdaa760e01b6000523360045260246000fd5b60ff600080516020610e9e8339815191525460401c1615610cf757565b631afcd79f60e31b60005260046000fd5b90610d2e5750805115610d1d57602081519101fd5b63d6bda27560e01b60005260046000fd5b81511580610d60575b610d3f575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b15610d3756fe6080601f60d438819003918201601f19168301916001600160401b0383118484101760bd5780849260209460405283398101031260b857516001600160a01b03811680820360b857156082573415605257ff5b60405162461bcd60e51b81526020600482015260086024820152676e6f2076616c756560c01b6044820152606490fd5b60405162461bcd60e51b815260206004820152600e60248201526d1e995c9bc81c9958da5c1a595b9d60921b6044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a164736f6c634300081c000a", 402 | "deployedBytecode": "0x6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c80631f7e75c81461086b5780634626402b146108425780634f1ef2861461060157806352d1902d14610597578063704b6c02146104c4578063715018a61461045a578063809d458d1461037f5780638129fc1c146101e15780638da5cb5b146101ab578063ad3cb1cc1461012f578063ad5c464814610106578063f2fde38b146100dd5763fa2af9da0361000e57346100d85760003660031901126100d8576001546040516001600160a01b039091168152602090f35b600080fd5b346100d85760203660031901126100d8576100196100f9610bd2565b610101610ca4565b610c2e565b346100d85760003660031901126100d8576000546040516001600160a01b039091168152602090f35b346100d85760003660031901126100d857604080519061014f8183610b98565b60058252640352e302e360dc1b6020830152805180926020825280519081602084015260005b8281106101945750506000828201840152601f01601f19168101030190f35b602082820181015187830187015286945001610175565b346100d85760003660031901126100d857600080516020610e3e833981519152546040516001600160a01b039091168152602090f35b346100d85760003660031901126100d857600080516020610e9e8339815191525460ff8160401c16159067ffffffffffffffff811680159081610377575b600114908161036d575b159081610364575b506103535767ffffffffffffffff198116600117600080516020610e9e8339815191525581610326575b50610264610cda565b61026c610cda565b61027533610c2e565b61027d610cda565b610285610cda565b61028d610cda565b6001600080516020610e7e83398151915255336001600160601b0360a01b6002541617600255336001600160601b0360a01b60015416176001556102cd57005b68ff000000000000000019600080516020610e9e8339815191525416600080516020610e9e833981519152557fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2602060405160018152a1005b68ffffffffffffffffff19166801000000000000000117600080516020610e9e833981519152558161025b565b63f92ee8a960e01b60005260046000fd5b90501583610231565b303b159150610229565b83915061021f565b346100d85760203660031901126100d857610398610bd2565b6103a0610ca4565b6001600160a01b03168015610415576080817f1e56b377b522438e71796d1885290a090be46e7ebba978e21e1b68da06d29598926001600160601b0360a01b60025416176002556040519060408252600e60408301526d1d1c99585cdd5c9e55d85b1b195d60921b60608301526020820152a1005b60405162461bcd60e51b815260206004820152601860248201527f496e76616c6964207472656173757279206164647265737300000000000000006044820152606490fd5b346100d85760003660031901126100d857610473610ca4565b600080516020610e3e83398151915280546001600160a01b031981169091556000906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b346100d85760203660031901126100d8576104dd610bd2565b6104e5610ca4565b6001600160a01b0316801561055a576080817f247fc6b6060a248bd48c4b239fbf251adc2b73a9695b3b181598f02aa931e877926001600160601b0360a01b60015416176001556040519060408252600e60408301526d1c1b185d199bdc9b55d85b1b195d60921b60608301526020820152a1005b60405162461bcd60e51b8152602060048201526015602482015274496e76616c69642061646d696e206164647265737360581b6044820152606490fd5b346100d85760003660031901126100d8577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031630036105f0576020604051600080516020610e5e8339815191528152f35b63703e46dd60e11b60005260046000fd5b60403660031901126100d857610615610bd2565b6024359067ffffffffffffffff82116100d857366023830112156100d85781600401359061064282610be8565b916106506040519384610b98565b808352602083019336602483830101116100d857816000926024602093018737840101526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630811490811561081f575b506105f0576106b6610ca4565b6040516352d1902d60e01b81526001600160a01b0382169390602081600481885afa600091816107eb575b506106fb5784634c9c8ce360e01b60005260045260246000fd5b80600080516020610e5e8339815191528692036107d75750823b156107c357600080516020610e5e83398151915280546001600160a01b031916821790557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600080a28251156107a85760008091610019945190845af43d156107a0573d9161078383610be8565b926107916040519485610b98565b83523d6000602085013e610d08565b606091610d08565b505050346107b257005b63b398979f60e01b60005260046000fd5b634c9c8ce360e01b60005260045260246000fd5b632a87526960e21b60005260045260246000fd5b9091506020813d602011610817575b8161080760209383610b98565b810103126100d8575190866106e1565b3d91506107fa565b600080516020610e5e833981519152546001600160a01b031614159050846106a9565b346100d85760003660031901126100d8576002546040516001600160a01b039091168152602090f35b60403660031901126100d85760043567ffffffffffffffff81116100d857366023820112156100d8578060040135906108a382610bba565b916108b16040519384610b98565b8083526024602084019160051b830101913683116100d857602401905b828210610b88578360243567ffffffffffffffff81116100d857366023820112156100d857806004013561090181610bba565b9161090f6040519384610b98565b8183526024602084019260051b820101903682116100d857602401915b818310610b68575050506002600080516020610e7e8339815191525414610b57576002600080516020610e7e833981519152558151815103610b2057815115610af157600091825b81518410156109b2576109878483610c04565b51810180911161099c57600190930192610974565b634e487b7160e01b600052601160045260246000fd5b3410610aac57610d6a9060d460005b8251811015610a98576109d48184610c04565b516001600160a01b036109e78388610c04565b5116604051908482019082821067ffffffffffffffff831117610a82576020918391878a84398152030191f0908115610a76576001916001600160a01b03610a2f8389610c04565b5116907fad92a691bda70ce78bd3e14e780181bdf98abb197ed2b962ed1137f574840cb36020610a5f8589610c04565b5192604051938452868060a01b031692a3016109c1565b6040513d6000823e3d90fd5b634e487b7160e01b600052604160045260246000fd5b6001600080516020610e7e83398151915255005b60405162461bcd60e51b815260206004820152601960248201527f6d73672e76616c7565203c20746f74616c20616d6f756e7473000000000000006044820152606490fd5b60405162461bcd60e51b81526020600482015260076024820152664e6f206461746160c81b6044820152606490fd5b60405162461bcd60e51b815260206004820152600f60248201526e098cadccee8d040dad2e6dac2e8c6d608b1b6044820152606490fd5b633ee5aeb560e01b60005260046000fd5b82356001600160a01b03811681036100d85781526020928301920161092c565b81358152602091820191016108ce565b90601f8019910116810190811067ffffffffffffffff821117610a8257604052565b67ffffffffffffffff8111610a825760051b60200190565b600435906001600160a01b03821682036100d857565b67ffffffffffffffff8111610a8257601f01601f191660200190565b8051821015610c185760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b03168015610c8e57600080516020610e3e83398151915280546001600160a01b0319811683179091556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3565b631e4fbdf760e01b600052600060045260246000fd5b600080516020610e3e833981519152546001600160a01b03163303610cc557565b63118cdaa760e01b6000523360045260246000fd5b60ff600080516020610e9e8339815191525460401c1615610cf757565b631afcd79f60e31b60005260046000fd5b90610d2e5750805115610d1d57602081519101fd5b63d6bda27560e01b60005260046000fd5b81511580610d60575b610d3f575090565b639996b31560e01b60009081526001600160a01b0391909116600452602490fd5b50803b15610d3756fe6080601f60d438819003918201601f19168301916001600160401b0383118484101760bd5780849260209460405283398101031260b857516001600160a01b03811680820360b857156082573415605257ff5b60405162461bcd60e51b81526020600482015260086024820152676e6f2076616c756560c01b6044820152606490fd5b60405162461bcd60e51b815260206004820152600e60248201526d1e995c9bc81c9958da5c1a595b9d60921b6044820152606490fd5b600080fd5b634e487b7160e01b600052604160045260246000fdfe9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00a164736f6c634300081c000a", 403 | "linkReferences": {}, 404 | "deployedLinkReferences": {} 405 | } 406 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 4Meme Trading Bot Control Panel 8 | 258 | 259 | 260 | 261 |
262 |
263 |

🤖 4Meme Trading Bot

264 |

Control Panel

265 |
266 | 267 |
268 |
269 |
270 | Checking status... 271 |
272 | 276 |
277 | 278 |
279 |

Configuration

280 |
281 |

⚙️ Basic Settings

282 |
283 |
284 | 285 | 287 |
288 |
289 | 291 | 292 |
293 |
294 | 295 | 296 |
297 |
298 | 299 | 300 |
301 |
302 | 303 | 304 |
305 |
306 | 307 |
308 | 309 |

💰 Deposit Settings

310 |
311 |
312 | 313 | 314 |
315 |
316 | 317 | 318 |
319 |
320 | 321 |
322 | 323 |

📈 Buy Settings

324 |
325 |
326 | 327 | 328 |
329 |
330 | 331 | 332 |
333 |
334 | 335 | 336 |
337 |
338 | 339 | 340 |
341 |
342 | 343 |
344 | 345 |

📉 Sell Settings

346 |
347 |
348 | 349 | 350 |
351 |
352 | 353 | 354 |
355 |
356 | 357 | 358 |
359 |
360 | 361 | 362 |
363 |
364 | 365 |
366 | 367 |

⏱️ Timing Settings

368 |
369 |
370 | 371 | 372 |
373 |
374 | 375 | 376 |
377 |
378 | 379 | 380 |
381 |
382 | 383 | 384 |
385 |
386 | 387 | 388 |
389 |
390 | 391 | 392 |
393 |
394 | 395 |
396 | 397 |

🔧 Other Settings

398 |
399 |
400 | 401 | 402 |
403 |
404 | 405 | 406 |
407 |
408 | 409 | 410 |
411 |
412 | 413 | 414 |
415 |
416 |
417 | 418 |
419 | 420 |
421 | 422 |
423 |
424 | 425 |
426 |

Bot Control

427 |
429 |

430 | Two-Phase Workflow:
431 | 1️⃣ Distribute BNB - Create wallets and fund them 432 | (no token address needed)
433 | 2️⃣ Start Trading - Enter token address and begin 434 | trading cycles
435 | 💡 Bot will auto-resume from saved state if stopped 436 | mid-cycle 437 |

438 |
439 |
440 | 442 | 443 | 444 | 445 |
446 |
447 | 448 |
449 |

📋 Bot Logs

450 |
451 | 453 |
454 |
456 |
No logs yet...
457 |
458 |
459 |
460 | 461 | 688 | 689 | 690 | -------------------------------------------------------------------------------- /src/volume-bot.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers'; 2 | import * as dotenv from 'dotenv'; 3 | import * as fs from 'fs'; 4 | import FourMemeTrader from './trader'; 5 | import StealthFundABI from './Stealth-utils/StealthFund.json'; 6 | import { isAddress } from '@validate-ethereum-address/core'; 7 | 8 | dotenv.config(); 9 | 10 | /* ---------- Types ---------- */ 11 | 12 | type VolumeBotParameters = { 13 | totalBNB: number; 14 | totalNumWallets: number; 15 | minDepositBNB: number; 16 | maxDepositBNB: number; 17 | minBuyNumPerCycle: number; 18 | maxBuyNumPerCycle: number; 19 | minPerCycleTime: number; 20 | maxPerCycleTime: number; 21 | minSellPercentWallets: number; // percent of bought wallets to sell per cycle 22 | maxSellPercentWallets: number; // percent of bought wallets to sell per cycle 23 | minPercentSellAmountAfterBuy: number; 24 | maxPercentSellAmountAfterBuy: number; 25 | minBuyPercentBNB: number; // percent of available BNB (excl gas) to spend 26 | maxBuyPercentBNB: number; // percent of available BNB (excl gas) to spend 27 | cyclesLimit?: number; 28 | mnemonicPathPrefix?: string; 29 | networkRpc?: string; 30 | pancakeRouter?: string; 31 | wbnb?: string; 32 | tokenAddress?: string; // Optional for distribution phase 33 | usePancakeAfterMigration?: boolean; 34 | minDelayBuy?: number; 35 | maxDelayBuy?: number; 36 | minDelaySell?: number; 37 | maxDelaySell?: number; 38 | gasBufferBNB: number; 39 | }; 40 | 41 | interface WalletLog { 42 | index: number; 43 | address: string; 44 | privateKey: string; 45 | depositBNB: number; 46 | status?: 'CREATED' | 'DEPOSITED' | 'LOW_GAS' | 'BOUGHT' | 'SOLD' | 'NO_TOKENS' | 'FAILED'; 47 | lastBNB?: number; 48 | buyTxHash?: string; 49 | sellTxHash?: string; 50 | boughtVia?: string; 51 | soldVia?: string; 52 | estimatedTokens?: string; 53 | timestamp: string; 54 | } 55 | 56 | interface BotState { 57 | currentCycle: number; 58 | totalCycles: number; 59 | startedAt: string; 60 | lastUpdated: string; 61 | tokenAddress?: string; 62 | } 63 | 64 | /* ---------- Helpers ---------- */ 65 | 66 | const randInt = (min: number, max: number) => 67 | Math.floor(Math.random() * (max - min + 1)) + min; 68 | 69 | const randFloat = (min: number, max: number) => 70 | Math.random() * (max - min) + min; 71 | 72 | const sleep = (ms: number) => new Promise(res => setTimeout(res, ms)); 73 | 74 | /* ---------- Main Class ---------- */ 75 | 76 | export default class VolumeBot { 77 | provider: ethers.JsonRpcProvider; 78 | mainWallet: ethers.Wallet; 79 | params: VolumeBotParameters; 80 | wbnbAddress: string; 81 | pancakeRouterAddress: string; 82 | walletLogFile = 'wallets.json'; 83 | walletLogs: WalletLog[] = []; 84 | stealthFundAddress: string; 85 | stealthMode: boolean; 86 | stateFile = 'bot-state.json'; 87 | shouldStop: boolean = false; 88 | 89 | constructor(params: VolumeBotParameters) { 90 | const rpc = params.networkRpc ?? process.env.RPC_URL!; 91 | if (!rpc) throw new Error('RPC_URL required'); 92 | 93 | const privateKey = process.env.PRIVATE_KEY; 94 | if (!privateKey) throw new Error('PRIVATE_KEY required in .env'); 95 | 96 | 97 | this.provider = new ethers.JsonRpcProvider(rpc, { name: 'bsc', chainId: 56 }); 98 | this.mainWallet = new ethers.Wallet(privateKey, this.provider); 99 | this.params = params; 100 | this.wbnbAddress = process.env.WBNB_ADDRESS || '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; 101 | this.pancakeRouterAddress = process.env.PANCAKE_ROUTER_ADDRESS || '0x10ED43C718714eb63d5aA57B78B54704E256024E'; 102 | this.stealthFundAddress = process.env.STEALTH_FUND_ADDRESS || '0x3774b227aee720423a62710c7Ce2D70EA16eE0D0'; 103 | this.stealthMode = process.env.STEALTH_MODE === 'true'; 104 | 105 | console.log(`Main wallet: ${this.mainWallet.address}`); 106 | console.log(`Stealth mode: ${this.stealthMode}`); 107 | 108 | // ensure log file exists 109 | if (!fs.existsSync(this.walletLogFile)) fs.writeFileSync(this.walletLogFile, '[]', 'utf8'); 110 | else { 111 | try { 112 | this.walletLogs = JSON.parse(fs.readFileSync(this.walletLogFile, 'utf8')); 113 | } catch { 114 | this.walletLogs = []; 115 | } 116 | } 117 | } 118 | 119 | saveWalletInfo(log: WalletLog) { 120 | const idx = this.walletLogs.findIndex(l => l.address === log.address); 121 | if (idx >= 0) this.walletLogs[idx] = { ...this.walletLogs[idx], ...log }; 122 | else this.walletLogs.push(log); 123 | fs.writeFileSync(this.walletLogFile, JSON.stringify(this.walletLogs, null, 2), 'utf8'); 124 | } 125 | 126 | saveBotState(state: BotState) { 127 | fs.writeFileSync(this.stateFile, JSON.stringify(state, null, 2), 'utf8'); 128 | } 129 | 130 | loadBotState(): BotState | null { 131 | if (!fs.existsSync(this.stateFile)) return null; 132 | try { 133 | const raw = fs.readFileSync(this.stateFile, 'utf8'); 134 | return JSON.parse(raw); 135 | } catch { 136 | return null; 137 | } 138 | } 139 | 140 | clearBotState() { 141 | if (fs.existsSync(this.stateFile)) { 142 | fs.unlinkSync(this.stateFile); 143 | } 144 | } 145 | 146 | reloadWalletLogsFromDisk() { 147 | try { 148 | const raw = fs.readFileSync(this.walletLogFile, 'utf8'); 149 | this.walletLogs = JSON.parse(raw); 150 | } catch { 151 | // ignore 152 | } 153 | } 154 | 155 | createChildWallet(index: number) { 156 | const w = ethers.Wallet.createRandom().connect(this.provider); 157 | console.log(`Child wallet ${index}: ${w.address}`); 158 | return w; 159 | } 160 | 161 | async getGasPrice() { 162 | const gasPriceHex = await this.provider.send('eth_gasPrice', []); 163 | return BigInt(gasPriceHex) * 12n / 10n; 164 | } 165 | 166 | async fundStealthChild(childAddress: string, valueWei: bigint) { 167 | try { 168 | console.log(`Funding stealth child ${childAddress} with ${ethers.formatEther(valueWei)} BNB`); 169 | 170 | const stealthFund = new ethers.Contract(this.stealthFundAddress, StealthFundABI.abi, this.mainWallet); 171 | 172 | // :two: Get gas price 173 | const gasPrice = await this.getGasPrice(); 174 | 175 | // :three: Send tx (optional) 176 | const tx = await stealthFund.stealthMultipleFund([valueWei], [childAddress], { 177 | value: valueWei, 178 | gasPrice: await this.getGasPrice() 179 | }); 180 | 181 | console.log(`Funding ${childAddress} -> tx ${tx.hash}`); 182 | const receipt = await tx.wait(); 183 | console.log(`:white_check_mark: Funded ${childAddress}, gasUsed ${receipt.gasUsed.toString()}`); 184 | 185 | return { tx, receipt }; 186 | } catch (e) { 187 | console.error(`:x: Failed to fund stealth child ${childAddress}:`, (e as any).reason ?? (e as any).message ?? (e as any)); 188 | return { error: e }; 189 | } 190 | } 191 | 192 | 193 | 194 | async fundChild(childAddress: string, valueWei: bigint) { 195 | try { 196 | const gasPrice = await this.getGasPrice(); 197 | const tx = await this.mainWallet.sendTransaction({ 198 | to: childAddress, 199 | value: valueWei, 200 | gasLimit: 21000n, 201 | gasPrice, 202 | }); 203 | 204 | console.log(`Funding ${childAddress} -> tx ${tx.hash}`); 205 | const receipt = await tx.wait(); 206 | console.log(`✅ Funded ${childAddress}, gasUsed ${receipt?.gasUsed.toString()}`); 207 | return { tx, receipt }; 208 | } catch (e) { 209 | console.error(`❌ Failed to fund ${childAddress}:`, e); 210 | return { error: e }; 211 | } 212 | } 213 | 214 | makeTraderForWallet(wallet: ethers.Wallet) { 215 | const base = new FourMemeTrader(); 216 | base.setWallet(wallet.connect(this.provider)); 217 | return base; 218 | } 219 | 220 | /** 221 | * Check if wallets are already fully funded by checking actual blockchain balances 222 | * This is more accurate than checking depositBNB field (which persists after gather) 223 | */ 224 | async areWalletsFunded(): Promise { 225 | if (this.walletLogs.length === 0) return false; 226 | if (this.walletLogs.length < this.params.totalNumWallets) return false; 227 | 228 | // Check actual blockchain balances, not just depositBNB field 229 | // (depositBNB persists even after gather empties wallets) 230 | const minBalance = this.params.minDepositBNB; 231 | let fundedCount = 0; 232 | 233 | for (const wl of this.walletLogs) { 234 | try { 235 | const balance = await this.provider.getBalance(wl.address); 236 | const balanceBNB = Number(ethers.formatEther(balance)); 237 | if (balanceBNB >= minBalance) { 238 | fundedCount++; 239 | } 240 | } catch (e) { 241 | console.warn(`Failed to check balance for ${wl.address}:`, e); 242 | // On error, assume not funded 243 | } 244 | } 245 | 246 | // Consider wallets funded if at least 80% have sufficient balance 247 | // This allows for some wallets that might have been used for gas 248 | const threshold = Math.floor(this.params.totalNumWallets * 0.8); 249 | return fundedCount >= threshold; 250 | } 251 | 252 | /** 253 | * Ensure a pool of wallets are created and deposited per TOTAL_BNB budget. 254 | * Will create up to totalNumWallets and distribute deposits randomly within min/max bounds. 255 | * Token approvals are only done if tokenAddress is provided. 256 | */ 257 | async ensureDeposits(remainingBNB: { value: number }, skipApprovals = false) { 258 | console.log('Ensuring deposits...', remainingBNB.value); 259 | const p = this.params; 260 | // Create missing wallets 261 | for (let i = this.walletLogs.length; i < p.totalNumWallets; i++) { 262 | const w = this.createChildWallet(i); 263 | this.saveWalletInfo({ 264 | index: i, 265 | address: w.address, 266 | privateKey: w.privateKey, 267 | depositBNB: 0, 268 | status: 'CREATED', 269 | timestamp: new Date().toISOString(), 270 | }); 271 | } 272 | 273 | // Deposit into wallets that need funding (check actual blockchain balance, not just depositBNB field) 274 | for (const wl of this.walletLogs) { 275 | if (remainingBNB.value <= p.minDepositBNB) break; 276 | 277 | // Check actual blockchain balance instead of relying on depositBNB field 278 | // (depositBNB may persist after gather even though balance is 0) 279 | let currentBalance = 0; 280 | try { 281 | const balance = await this.provider.getBalance(wl.address); 282 | currentBalance = Number(ethers.formatEther(balance)); 283 | 284 | // Update wallet log with current balance 285 | if (currentBalance !== (wl.lastBNB ?? 0)) { 286 | this.saveWalletInfo({ 287 | ...wl, 288 | lastBNB: currentBalance, 289 | timestamp: new Date().toISOString(), 290 | }); 291 | } 292 | } catch (e) { 293 | console.warn(`Failed to check balance for ${wl.address}:`, e); 294 | // Continue to next wallet on error 295 | continue; 296 | } 297 | 298 | // Skip if wallet already has sufficient balance 299 | if (currentBalance >= p.minDepositBNB) { 300 | console.log(`Wallet ${wl.address} already has ${currentBalance} BNB, skipping...`); 301 | continue; 302 | } 303 | 304 | // Calculate deposit amount 305 | const maxForThis = Math.min(p.maxDepositBNB, remainingBNB.value); 306 | if (maxForThis < p.minDepositBNB) continue; 307 | const deposit = Math.min(maxForThis, Math.max(p.minDepositBNB, randFloat(p.minDepositBNB, maxForThis))); 308 | 309 | try { 310 | console.log(`Depositing ${deposit} BNB to ${wl.address} (current balance: ${currentBalance})`); 311 | const valueWei = ethers.parseEther(deposit.toFixed(18)); 312 | this.stealthMode 313 | ? await this.fundStealthChild(wl.address, valueWei) 314 | : await this.fundChild(wl.address, valueWei); 315 | 316 | await sleep(500); 317 | const bnbBalance = await this.provider.getBalance(wl.address); 318 | const deposited = Number(ethers.formatEther(bnbBalance)); 319 | this.saveWalletInfo({ 320 | ...wl, 321 | depositBNB: deposited, 322 | lastBNB: deposited, 323 | status: 'DEPOSITED', 324 | timestamp: new Date().toISOString(), 325 | }); 326 | remainingBNB.value -= deposit; 327 | 328 | // Only do token approvals if tokenAddress is provided and not skipping 329 | if (!skipApprovals && p.tokenAddress) { 330 | try { 331 | const child = new ethers.Wallet(wl.privateKey, this.provider); 332 | const trader = this.makeTraderForWallet(child); 333 | const migration = await trader.getMigrationStatus(p.tokenAddress).catch(() => false); 334 | if (migration) { 335 | await trader.approvePancakeRouter(p.tokenAddress); 336 | } else { 337 | await trader.approveTokenManager(p.tokenAddress); 338 | } 339 | } catch (e) { 340 | console.warn(`Failed to approve token for ${wl.address}, continuing...`); 341 | } 342 | } 343 | } catch (e) { 344 | this.saveWalletInfo({ ...wl, status: 'FAILED', timestamp: new Date().toISOString() }); 345 | } 346 | } 347 | } 348 | 349 | /** 350 | * Phase 1: Create wallets and distribute BNB without requiring token address 351 | */ 352 | async distributeBNB() { 353 | const p = this.params; 354 | let remaining = { value: p.totalBNB }; 355 | 356 | console.log('=== Phase 1: Distributing BNB to wallets ==='); 357 | console.log(`Total BNB: ${p.totalBNB}, Target wallets: ${p.totalNumWallets}`); 358 | 359 | // Check if wallets are already funded (check actual blockchain balances) 360 | const alreadyFunded = await this.areWalletsFunded(); 361 | if (alreadyFunded) { 362 | console.log('✅ Wallets are already funded. Skipping distribution.'); 363 | return; 364 | } 365 | 366 | await this.ensureDeposits(remaining, true); // Skip token approvals during distribution 367 | 368 | console.log(`✅ Distribution complete. Remaining BNB: ${remaining.value}`); 369 | console.log(`Funded ${this.walletLogs.filter(w => (w.depositBNB ?? 0) > 0).length} wallets`); 370 | } 371 | 372 | async runCycle(cycleIndex: number) { 373 | if (!this.params.tokenAddress) { 374 | throw new Error('tokenAddress is required for trading cycles'); 375 | } 376 | 377 | const p = this.params; 378 | const tokenAddress = p.tokenAddress!; // Non-null: we checked above 379 | const buyWalletsNum = randInt(p.minBuyNumPerCycle, p.maxBuyNumPerCycle); 380 | console.log(`\n--- Cycle ${cycleIndex} start. Target buys: ${buyWalletsNum} ---`); 381 | 382 | // Check for stop signal 383 | if (this.shouldStop) { 384 | console.log('Stop signal received, saving state...'); 385 | this.saveBotState({ 386 | currentCycle: cycleIndex, 387 | totalCycles: p.cyclesLimit ?? 0, 388 | startedAt: this.loadBotState()?.startedAt || new Date().toISOString(), 389 | lastUpdated: new Date().toISOString(), 390 | tokenAddress: tokenAddress, 391 | }); 392 | throw new Error('Bot stopped by user'); 393 | } 394 | 395 | // Candidate wallets are those with a deposit 396 | const deposited = this.walletLogs.filter(w => (w.depositBNB ?? 0) > 0); 397 | 398 | // Pre-calc sell bounds for this cycle (percent of intended buys) 399 | const minPct = Math.max(0, Math.min(100, p.minSellPercentWallets)); 400 | const maxPct = Math.max(minPct, Math.min(100, p.maxSellPercentWallets)); 401 | const targetSellPercent = randFloat(minPct, maxPct); 402 | const minSellCount = Math.floor((buyWalletsNum * minPct) / 100); 403 | const maxSellCount = Math.ceil((buyWalletsNum * maxPct) / 100); 404 | let sellsDone = 0; 405 | let buysProcessed = 0; 406 | const boughtThisCycle: Array = []; 407 | const soldSet = new Set(); 408 | 409 | // Helper to pick random wallet not yet chosen 410 | const chosen = new Set(); 411 | const pickRandomWallet = () => { 412 | const pool = deposited.filter(w => !chosen.has(w.address)); 413 | if (pool.length === 0) return undefined; 414 | const w = pool[Math.floor(Math.random() * pool.length)]; 415 | chosen.add(w.address); 416 | return w; 417 | }; 418 | 419 | for (let i = 0; i < buyWalletsNum; i++) { 420 | const wl = pickRandomWallet(); 421 | if (!wl) break; 422 | 423 | const gasBufferBNB = p.gasBufferBNB; 424 | const child = new ethers.Wallet(wl.privateKey, this.provider); 425 | 426 | const bnbBalance = await this.provider.getBalance(wl.address); 427 | const bnb = Number(ethers.formatEther(bnbBalance)); 428 | if (bnb <= gasBufferBNB) { 429 | this.saveWalletInfo({ ...wl, lastBNB: bnb, status: 'LOW_GAS', timestamp: new Date().toISOString() }); 430 | i--; // try another wallet for this slot 431 | continue; 432 | } 433 | 434 | const available = Math.max(0, bnb - gasBufferBNB); 435 | const buyPercent = randFloat(p.minBuyPercentBNB, p.maxBuyPercentBNB); // percent [min,max] 436 | const spendBNB = Math.min(available, available * (buyPercent / 100)); 437 | if (spendBNB <= 0) { 438 | this.saveWalletInfo({ ...wl, lastBNB: bnb, status: 'LOW_GAS', timestamp: new Date().toISOString() }); 439 | i--; 440 | continue; 441 | } 442 | 443 | try { 444 | const delay = randInt(p.minDelayBuy ?? 1000, p.maxDelayBuy ?? 5000); 445 | console.log(`Delay before buy: ${delay} ms`); 446 | await sleep(delay); 447 | 448 | const trader = this.makeTraderForWallet(child); 449 | const migration = await trader.getMigrationStatus(tokenAddress).catch(() => false); 450 | 451 | const buyAmount = ethers.parseEther(spendBNB.toFixed(18)); 452 | const result = migration && (p.usePancakeAfterMigration ?? true) 453 | ? await trader.buyPancakeTokenBigInt(tokenAddress, buyAmount) 454 | : await trader.buyTokenBigInt(tokenAddress, buyAmount); 455 | 456 | const bnbBalanceAfterBuy = await this.provider.getBalance(wl.address); 457 | const bnbAfterBuy = Number(ethers.formatEther(bnbBalanceAfterBuy)); 458 | 459 | const log: WalletLog = { 460 | ...wl, 461 | lastBNB: bnbAfterBuy, 462 | buyTxHash: result.txHash, 463 | boughtVia: migration ? 'pancake' : 'fourmeme', 464 | estimatedTokens: result.realTokenBalance.toString(), 465 | status: 'BOUGHT', 466 | timestamp: new Date().toISOString(), 467 | }; 468 | this.saveWalletInfo(log); 469 | console.log(`[BUY ${log.boughtVia}] ${wl.address} -> tx ${log.buyTxHash}`); 470 | boughtThisCycle.push(log); 471 | 472 | // After each buy, decide sell or not immediately for a wallet chosen from the global buy list 473 | buysProcessed++; 474 | const walletsLeft = buyWalletsNum - buysProcessed + 1; // including this one for decision context 475 | const mustSellRemaining = Math.max(0, minSellCount - sellsDone); 476 | const capacityRemaining = Math.max(0, maxSellCount - sellsDone); 477 | 478 | let shouldSell = false; 479 | if (capacityRemaining <= 0) { 480 | shouldSell = false; 481 | } else if (walletsLeft <= mustSellRemaining) { 482 | // Force sell to meet minimum target 483 | shouldSell = true; 484 | } else { 485 | // Probabilistic within remaining capacity 486 | const probability = capacityRemaining / walletsLeft; 487 | shouldSell = Math.random() < probability; 488 | } 489 | 490 | if (shouldSell) { 491 | const delaySell = randInt(p.minDelaySell ?? 2000, p.maxDelaySell ?? 10000); 492 | console.log(`Decided to sell now. Delay before sell: ${delaySell} ms`); 493 | await sleep(delaySell); 494 | 495 | // Refresh from disk and choose a wallet from the overall buy list across all cycles 496 | this.reloadWalletLogsFromDisk(); 497 | const candidates = this.walletLogs 498 | .filter(b => !!b.buyTxHash) // can include previously partially sold wallets 499 | .filter(b => !soldSet.has(b.address)); 500 | if (candidates.length === 0) { 501 | console.log('No candidates to sell from bought list.'); 502 | } else { 503 | let soldThisAttempt = false; 504 | // Try a few random candidates until one has tokens or we exhaust options 505 | const attemptsOrder = [...candidates].sort(() => Math.random() - 0.5); 506 | for (const candidate of attemptsOrder) { 507 | const sellWallet = new ethers.Wallet(candidate.privateKey, this.provider); 508 | const sellTrader = this.makeTraderForWallet(sellWallet); 509 | const migrationSell = await sellTrader.getMigrationStatus(tokenAddress).catch(() => false); 510 | const tokenBal = await sellTrader.getTokenBalance(tokenAddress); 511 | if (tokenBal <= 0n) { 512 | this.saveWalletInfo({ ...candidate, status: 'NO_TOKENS', timestamp: new Date().toISOString() }); 513 | continue; // pick another 514 | } 515 | const sellPercentAmt = randFloat(p.minPercentSellAmountAfterBuy ?? 100, p.maxPercentSellAmountAfterBuy ?? 100); 516 | const sellAmount = (tokenBal * BigInt(Math.floor(sellPercentAmt * 100))) / BigInt(100 * 100); 517 | const sellRealAmount = sellAmount / BigInt(10 ** 9) * BigInt(10 ** 9); 518 | if (sellRealAmount <= 0n || sellRealAmount > tokenBal) { 519 | continue; 520 | } 521 | try { 522 | const res = migrationSell && (p.usePancakeAfterMigration ?? true) 523 | ? await sellTrader.sellPancakeTokenBigInt(tokenAddress, sellRealAmount) 524 | : { txHash: await sellTrader.sellAmountBigInt(tokenAddress, sellRealAmount) }; 525 | // After sell, fetch real remaining token balance and persist 526 | const remainingTokens = await sellTrader.getTokenBalance(tokenAddress); 527 | this.saveWalletInfo({ 528 | index: candidate.index, 529 | address: candidate.address, 530 | privateKey: candidate.privateKey, 531 | depositBNB: candidate.depositBNB, 532 | buyTxHash: candidate.buyTxHash, 533 | sellTxHash: res.txHash, 534 | soldVia: migrationSell ? 'pancake' : 'fourmeme', 535 | estimatedTokens: remainingTokens.toString(), 536 | status: 'SOLD', 537 | timestamp: new Date().toISOString(), 538 | }); 539 | console.log(`[SELL ${migrationSell ? 'pancake' : 'fourmeme'}] ${candidate.address} -> tx ${res.txHash}`); 540 | soldSet.add(candidate.address); 541 | sellsDone++; 542 | soldThisAttempt = true; 543 | break; 544 | } catch (e) { 545 | console.error(`Sell failed for ${candidate.address}:`, e); 546 | } 547 | } 548 | if (!soldThisAttempt) console.log('Could not sell any candidate this attempt.'); 549 | } 550 | } 551 | } catch (e) { 552 | console.error(`Buy failed for ${wl.address}:`, e); 553 | this.saveWalletInfo({ ...wl, status: 'FAILED', timestamp: new Date().toISOString() }); 554 | } 555 | } 556 | console.log(`Cycle ${cycleIndex} complete. Sells executed: ${sellsDone}.`); 557 | return { sellsDone }; 558 | } 559 | 560 | async run() { 561 | const p = this.params; 562 | 563 | if (!p.tokenAddress) { 564 | throw new Error('tokenAddress is required for trading. Use distributeBNB() first to distribute funds.'); 565 | } 566 | 567 | // Load saved state or start fresh 568 | let savedState = this.loadBotState(); 569 | let startCycle = 1; 570 | let cycles = 0; 571 | 572 | if (savedState) { 573 | // Check if token address changed 574 | if (savedState.tokenAddress && savedState.tokenAddress !== p.tokenAddress) { 575 | console.log('⚠️ Token address changed. Clearing previous state and starting fresh.'); 576 | this.clearBotState(); 577 | savedState = null; 578 | } else if (savedState.currentCycle) { 579 | startCycle = savedState.currentCycle; 580 | cycles = startCycle - 1; 581 | console.log(`🔄 Resuming from cycle ${startCycle}`); 582 | } 583 | } 584 | 585 | // Ensure wallets are funded (reuse existing if already funded) 586 | let remaining = { value: p.totalBNB }; 587 | const alreadyFunded = await this.areWalletsFunded(); 588 | if (!alreadyFunded) { 589 | console.log('Wallets not fully funded, ensuring deposits...'); 590 | await this.ensureDeposits(remaining); 591 | } else { 592 | console.log('✅ Using existing funded wallets'); 593 | } 594 | 595 | // Approve token for all funded wallets if not already done 596 | if (p.tokenAddress) { 597 | console.log('Approving token for all wallets...'); 598 | for (const wl of this.walletLogs.filter(w => (w.depositBNB ?? 0) > 0)) { 599 | try { 600 | const child = new ethers.Wallet(wl.privateKey, this.provider); 601 | const trader = this.makeTraderForWallet(child); 602 | const migration = await trader.getMigrationStatus(p.tokenAddress).catch(() => false); 603 | if (migration) { 604 | await trader.approvePancakeRouter(p.tokenAddress); 605 | } else { 606 | await trader.approveTokenManager(p.tokenAddress); 607 | } 608 | } catch (e) { 609 | console.warn(`Failed to approve token for ${wl.address}, continuing...`); 610 | } 611 | } 612 | } 613 | 614 | // Initialize state if starting fresh 615 | if (!savedState) { 616 | this.saveBotState({ 617 | currentCycle: startCycle, 618 | totalCycles: p.cyclesLimit ?? 0, 619 | startedAt: new Date().toISOString(), 620 | lastUpdated: new Date().toISOString(), 621 | tokenAddress: p.tokenAddress, 622 | }); 623 | } 624 | 625 | const totalCycles = p.cyclesLimit ?? Infinity; 626 | this.shouldStop = false; 627 | 628 | try { 629 | while (cycles < totalCycles && startCycle <= totalCycles) { 630 | if (this.shouldStop) { 631 | console.log('Stop signal received, saving state...'); 632 | this.saveBotState({ 633 | currentCycle: startCycle, 634 | totalCycles: p.cyclesLimit ?? 0, 635 | startedAt: savedState?.startedAt || new Date().toISOString(), 636 | lastUpdated: new Date().toISOString(), 637 | tokenAddress: p.tokenAddress, 638 | }); 639 | break; 640 | } 641 | 642 | await this.runCycle(startCycle); 643 | 644 | // Update state after successful cycle 645 | cycles++; 646 | startCycle++; 647 | this.saveBotState({ 648 | currentCycle: startCycle, 649 | totalCycles: p.cyclesLimit ?? 0, 650 | startedAt: savedState?.startedAt || new Date().toISOString(), 651 | lastUpdated: new Date().toISOString(), 652 | tokenAddress: p.tokenAddress, 653 | }); 654 | 655 | if (startCycle > totalCycles) break; 656 | 657 | const waitSec = randInt(p.minPerCycleTime, p.maxPerCycleTime); 658 | console.log(`Waiting ${waitSec / 1000}s before next cycle...`); 659 | await sleep(waitSec); 660 | } 661 | 662 | // Clear state when all cycles complete 663 | if (startCycle > totalCycles) { 664 | console.log('✅ All cycles completed. Clearing state file.'); 665 | this.clearBotState(); 666 | } 667 | 668 | console.log('✅ VolumeBot finished. Remaining BNB:', remaining.value); 669 | } catch (e: any) { 670 | if (e.message === 'Bot stopped by user') { 671 | console.log('Bot paused. State saved. Resume to continue from cycle', startCycle); 672 | } else { 673 | // Save state on error 674 | this.saveBotState({ 675 | currentCycle: startCycle, 676 | totalCycles: p.cyclesLimit ?? 0, 677 | startedAt: savedState?.startedAt || new Date().toISOString(), 678 | lastUpdated: new Date().toISOString(), 679 | tokenAddress: p.tokenAddress, 680 | }); 681 | throw e; 682 | } 683 | } 684 | } 685 | 686 | stop() { 687 | this.shouldStop = true; 688 | console.log('Stop signal set. Bot will pause after current cycle.'); 689 | } 690 | } 691 | 692 | /* ---------- Example usage ---------- */ 693 | 694 | if (require.main === module) { 695 | (async () => { 696 | console.log("Starting VolumeBot..."); 697 | 698 | // Check for distribution-only mode: 699 | // 1. --distribute flag = distribution mode (UI or CLI) 700 | // 2. --trading flag = trading mode (UI explicitly, ignores .env) 701 | // 3. No flags = check DISTRIBUTE_ONLY env var (CLI only) 702 | // This ensures UI buttons control behavior via flags, while CLI can use .env 703 | const hasDistributeFlag = process.argv.includes('--distribute'); 704 | const hasTradingFlag = process.argv.includes('--trading'); 705 | 706 | let distributionOnly = false; 707 | if (hasDistributeFlag) { 708 | distributionOnly = true; // UI "Distribute BNB" or CLI with --distribute 709 | } else if (hasTradingFlag) { 710 | distributionOnly = false; // UI "START TRADING" - explicitly trading, ignore .env 711 | } else { 712 | distributionOnly = process.env.DISTRIBUTE_ONLY === 'true'; // CLI without flags - use .env 713 | } 714 | 715 | if (!process.env.RPC_URL) { 716 | console.error("RPC_URL is not set"); 717 | process.exit(1); 718 | } 719 | if (!process.env.PRIVATE_KEY) { 720 | console.error("PRIVATE_KEY is not set"); 721 | process.exit(1); 722 | } 723 | 724 | // Token address is optional for distribution phase 725 | const tokenAddress = process.env.TOKEN_ADDRESS; 726 | if (!distributionOnly && (!tokenAddress || isAddress(tokenAddress, false) === false)) { 727 | console.error("TOKEN_ADDRESS is not set or invalid (required for trading)"); 728 | process.exit(1); 729 | } 730 | 731 | const params: VolumeBotParameters = { 732 | networkRpc: process.env.RPC_URL!, 733 | tokenAddress: tokenAddress, 734 | totalBNB: process.env.TOTAL_BNB ? Number(process.env.TOTAL_BNB) : 18, 735 | totalNumWallets: process.env.TOTAL_NUM_WALLETS ? Number(process.env.TOTAL_NUM_WALLETS) : 50, 736 | minDepositBNB: process.env.MIN_DEPOSIT_BNB ? Number(process.env.MIN_DEPOSIT_BNB) : 0.01, 737 | maxDepositBNB: process.env.MAX_DEPOSIT_BNB ? Number(process.env.MAX_DEPOSIT_BNB) : 0.02, 738 | minPerCycleTime: process.env.MIN_PER_CYCLE_TIME ? Number(process.env.MIN_PER_CYCLE_TIME) : 5000, 739 | maxPerCycleTime: process.env.MAX_PER_CYCLE_TIME ? Number(process.env.MAX_PER_CYCLE_TIME) : 10000, 740 | minBuyNumPerCycle: process.env.MIN_BUY_NUM_PER_CYCLE ? Number(process.env.MIN_BUY_NUM_PER_CYCLE) : 3, 741 | maxBuyNumPerCycle: process.env.MAX_BUY_NUM_PER_CYCLE ? Number(process.env.MAX_BUY_NUM_PER_CYCLE) : 5, 742 | minBuyPercentBNB: process.env.MIN_BUY_PERCENT_BNB ? Number(process.env.MIN_BUY_PERCENT_BNB) : 100, 743 | maxBuyPercentBNB: process.env.MAX_BUY_PERCENT_BNB ? Number(process.env.MAX_BUY_PERCENT_BNB) : 100, 744 | minSellPercentWallets: process.env.MIN_PERCENT_SELL ? Number(process.env.MIN_PERCENT_SELL) : 0, 745 | maxSellPercentWallets: process.env.MAX_PERCENT_SELL ? Number(process.env.MAX_PERCENT_SELL) : 0, 746 | minPercentSellAmountAfterBuy: process.env.MIN_PERCENT_SELL_AMOUNT_AFTER_BUY ? Number(process.env.MIN_PERCENT_SELL_AMOUNT_AFTER_BUY) : 50, 747 | maxPercentSellAmountAfterBuy: process.env.MAX_PERCENT_SELL_AMOUNT_AFTER_BUY ? Number(process.env.MAX_PERCENT_SELL_AMOUNT_AFTER_BUY) : 80, 748 | cyclesLimit: process.env.CYCLE_LIMIT ? Number(process.env.CYCLE_LIMIT) : 3, 749 | minDelayBuy: process.env.MIN_DELAY_BUY ? Number(process.env.MIN_DELAY_BUY) : 2000, 750 | maxDelayBuy: process.env.MAX_DELAY_BUY ? Number(process.env.MAX_DELAY_BUY) : 8000, 751 | minDelaySell: process.env.MIN_DELAY_SELL ? Number(process.env.MIN_DELAY_SELL) : 4000, 752 | maxDelaySell: process.env.MAX_DELAY_SELL ? Number(process.env.MAX_DELAY_SELL) : 12000, 753 | gasBufferBNB: process.env.GAS_BUFFER_BNB ? Number(process.env.GAS_BUFFER_BNB) : 0.001, 754 | usePancakeAfterMigration: process.env.USE_PANCAKE_AFTER_MIGRATION === 'true', 755 | }; 756 | 757 | console.log("Params:", params); 758 | 759 | const bot = new VolumeBot(params); 760 | 761 | // Handle graceful shutdown (ensure single execution) 762 | let shutdownInitiated = false; 763 | const handleShutdown = () => { 764 | if (shutdownInitiated) return; 765 | shutdownInitiated = true; 766 | console.log('\n⚠️ Shutdown signal received. Stopping bot gracefully...'); 767 | bot.stop(); 768 | // Give bot time to save state 769 | setTimeout(() => process.exit(0), 2000); 770 | }; 771 | 772 | process.on('SIGINT', handleShutdown); 773 | process.on('SIGTERM', handleShutdown); 774 | 775 | if (distributionOnly) { 776 | console.log("Running in distribution-only mode..."); 777 | await bot.distributeBNB(); 778 | console.log("✅ Distribution complete. You can now set TOKEN_ADDRESS and run trading."); 779 | } else { 780 | await bot.run(); 781 | } 782 | })().catch(e => { 783 | console.error(e); 784 | process.exit(1); 785 | }); 786 | } 787 | -------------------------------------------------------------------------------- /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.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" 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.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" 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.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" 20 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== 21 | 22 | "@jridgewell/sourcemap-codec@^1.4.10": 23 | version "1.5.5" 24 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" 25 | integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== 26 | 27 | "@jridgewell/trace-mapping@0.3.9": 28 | version "0.3.9" 29 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" 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.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" 38 | integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== 39 | dependencies: 40 | "@noble/hashes" "1.3.2" 41 | 42 | "@noble/hashes@1.3.2": 43 | version "1.3.2" 44 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" 45 | integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== 46 | 47 | "@noble/hashes@^1.4.0": 48 | version "1.8.0" 49 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" 50 | integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== 51 | 52 | "@tsconfig/node10@^1.0.7": 53 | version "1.0.12" 54 | resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" 55 | integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== 56 | 57 | "@tsconfig/node12@^1.0.7": 58 | version "1.0.11" 59 | resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" 60 | integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== 61 | 62 | "@tsconfig/node14@^1.0.0": 63 | version "1.0.3" 64 | resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" 65 | integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== 66 | 67 | "@tsconfig/node16@^1.0.2": 68 | version "1.0.4" 69 | resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" 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.yarnpkg.com/@types/bn.js/-/bn.js-5.2.0.tgz#4349b9710e98f9ab3cdc50f1c5e4dcbd8ef29c80" 75 | integrity sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q== 76 | dependencies: 77 | "@types/node" "*" 78 | 79 | "@types/body-parser@*": 80 | version "1.19.6" 81 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" 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.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" 90 | integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== 91 | dependencies: 92 | "@types/node" "*" 93 | 94 | "@types/cors@^2.8.16": 95 | version "2.8.19" 96 | resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" 97 | integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== 98 | dependencies: 99 | "@types/node" "*" 100 | 101 | "@types/express-serve-static-core@^4.17.33": 102 | version "4.19.7" 103 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" 104 | integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== 105 | dependencies: 106 | "@types/node" "*" 107 | "@types/qs" "*" 108 | "@types/range-parser" "*" 109 | "@types/send" "*" 110 | 111 | "@types/express@^4.17.21": 112 | version "4.17.25" 113 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b" 114 | integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== 115 | dependencies: 116 | "@types/body-parser" "*" 117 | "@types/express-serve-static-core" "^4.17.33" 118 | "@types/qs" "*" 119 | "@types/serve-static" "^1" 120 | 121 | "@types/fs-extra@^11.0.4": 122 | version "11.0.4" 123 | resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" 124 | integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== 125 | dependencies: 126 | "@types/jsonfile" "*" 127 | "@types/node" "*" 128 | 129 | "@types/http-errors@*": 130 | version "2.0.5" 131 | resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" 132 | integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== 133 | 134 | "@types/jsonfile@*": 135 | version "6.1.4" 136 | resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702" 137 | integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ== 138 | dependencies: 139 | "@types/node" "*" 140 | 141 | "@types/mime@^1": 142 | version "1.3.5" 143 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" 144 | integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== 145 | 146 | "@types/node@*": 147 | version "24.10.1" 148 | resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.1.tgz#91e92182c93db8bd6224fca031e2370cef9a8f01" 149 | integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ== 150 | dependencies: 151 | undici-types "~7.16.0" 152 | 153 | "@types/node@22.7.5": 154 | version "22.7.5" 155 | resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" 156 | integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== 157 | dependencies: 158 | undici-types "~6.19.2" 159 | 160 | "@types/node@^20.8.10": 161 | version "20.19.25" 162 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.25.tgz#467da94a2fd966b57cc39c357247d68047611190" 163 | integrity sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ== 164 | dependencies: 165 | undici-types "~6.21.0" 166 | 167 | "@types/qs@*": 168 | version "6.14.0" 169 | resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" 170 | integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== 171 | 172 | "@types/range-parser@*": 173 | version "1.2.7" 174 | resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" 175 | integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== 176 | 177 | "@types/send@*": 178 | version "1.2.1" 179 | resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" 180 | integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== 181 | dependencies: 182 | "@types/node" "*" 183 | 184 | "@types/send@<1": 185 | version "0.17.6" 186 | resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.6.tgz#aeb5385be62ff58a52cd5459daa509ae91651d25" 187 | integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== 188 | dependencies: 189 | "@types/mime" "^1" 190 | "@types/node" "*" 191 | 192 | "@types/serve-static@^1": 193 | version "1.15.10" 194 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" 195 | integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== 196 | dependencies: 197 | "@types/http-errors" "*" 198 | "@types/node" "*" 199 | "@types/send" "<1" 200 | 201 | "@validate-ethereum-address/core@^1.0.6": 202 | version "1.0.6" 203 | resolved "https://registry.yarnpkg.com/@validate-ethereum-address/core/-/core-1.0.6.tgz#8211171c19e956dfba31e29a6b752e37f4287447" 204 | integrity sha512-Tg//Gy9CEMeS53ASIwQfRWH5G7XtdFASYF8rEi4bNz6uaRFxZPBZgnNzzT1lTbZHe3CNcGG8BVHPgx/fbXQHmg== 205 | dependencies: 206 | "@noble/hashes" "^1.4.0" 207 | "@validate-sdk/v2" "^1.22.11" 208 | axios "^1.13.2" 209 | 210 | "@validate-sdk/v2@^1.22.11": 211 | version "1.22.14" 212 | resolved "https://registry.yarnpkg.com/@validate-sdk/v2/-/v2-1.22.14.tgz#bddf288c8e2257543621fa3066bdaa686b15a5b3" 213 | integrity sha512-lO0kKrkgOV8wMYBIpsIzP6e2xCQShZOVFlza0BeBWJXBesiqd/gUFrn8REI4jGl1wWlCLAjyC/xLtszZk+7ueg== 214 | dependencies: 215 | "@types/bn.js" "^5.2.0" 216 | bn.js "^5.2.2" 217 | 218 | accepts@~1.3.8: 219 | version "1.3.8" 220 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" 221 | integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== 222 | dependencies: 223 | mime-types "~2.1.34" 224 | negotiator "0.6.3" 225 | 226 | acorn-walk@^8.1.1: 227 | version "8.3.4" 228 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" 229 | integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== 230 | dependencies: 231 | acorn "^8.11.0" 232 | 233 | acorn@^8.11.0, acorn@^8.4.1: 234 | version "8.15.0" 235 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" 236 | integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== 237 | 238 | aes-js@4.0.0-beta.5: 239 | version "4.0.0-beta.5" 240 | resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" 241 | integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== 242 | 243 | arg@^4.1.0: 244 | version "4.1.3" 245 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 246 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 247 | 248 | array-flatten@1.1.1: 249 | version "1.1.1" 250 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 251 | integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== 252 | 253 | asynckit@^0.4.0: 254 | version "0.4.0" 255 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 256 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 257 | 258 | axios@^1.13.2: 259 | version "1.13.2" 260 | resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687" 261 | integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA== 262 | dependencies: 263 | follow-redirects "^1.15.6" 264 | form-data "^4.0.4" 265 | proxy-from-env "^1.1.0" 266 | 267 | bn.js@^5.2.2: 268 | version "5.2.2" 269 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" 270 | integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw== 271 | 272 | body-parser@1.20.3: 273 | version "1.20.3" 274 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" 275 | integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== 276 | dependencies: 277 | bytes "3.1.2" 278 | content-type "~1.0.5" 279 | debug "2.6.9" 280 | depd "2.0.0" 281 | destroy "1.2.0" 282 | http-errors "2.0.0" 283 | iconv-lite "0.4.24" 284 | on-finished "2.4.1" 285 | qs "6.13.0" 286 | raw-body "2.5.2" 287 | type-is "~1.6.18" 288 | unpipe "1.0.0" 289 | 290 | bytes@3.1.2: 291 | version "3.1.2" 292 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 293 | integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 294 | 295 | call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: 296 | version "1.0.2" 297 | resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" 298 | integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== 299 | dependencies: 300 | es-errors "^1.3.0" 301 | function-bind "^1.1.2" 302 | 303 | call-bound@^1.0.2: 304 | version "1.0.4" 305 | resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" 306 | integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== 307 | dependencies: 308 | call-bind-apply-helpers "^1.0.2" 309 | get-intrinsic "^1.3.0" 310 | 311 | combined-stream@^1.0.8: 312 | version "1.0.8" 313 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 314 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 315 | dependencies: 316 | delayed-stream "~1.0.0" 317 | 318 | content-disposition@0.5.4: 319 | version "0.5.4" 320 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 321 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 322 | dependencies: 323 | safe-buffer "5.2.1" 324 | 325 | content-type@~1.0.4, content-type@~1.0.5: 326 | version "1.0.5" 327 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" 328 | integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== 329 | 330 | cookie-signature@1.0.6: 331 | version "1.0.6" 332 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 333 | integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== 334 | 335 | cookie@0.7.1: 336 | version "0.7.1" 337 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" 338 | integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== 339 | 340 | cors@^2.8.5: 341 | version "2.8.5" 342 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 343 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 344 | dependencies: 345 | object-assign "^4" 346 | vary "^1" 347 | 348 | create-require@^1.1.0: 349 | version "1.1.1" 350 | resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" 351 | integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== 352 | 353 | debug@2.6.9: 354 | version "2.6.9" 355 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 356 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 357 | dependencies: 358 | ms "2.0.0" 359 | 360 | delayed-stream@~1.0.0: 361 | version "1.0.0" 362 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 363 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 364 | 365 | depd@2.0.0: 366 | version "2.0.0" 367 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 368 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 369 | 370 | destroy@1.2.0: 371 | version "1.2.0" 372 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" 373 | integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== 374 | 375 | diff@^4.0.1: 376 | version "4.0.2" 377 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 378 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 379 | 380 | dotenv@^16.3.1: 381 | version "16.6.1" 382 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" 383 | integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== 384 | 385 | dunder-proto@^1.0.1: 386 | version "1.0.1" 387 | resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" 388 | integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== 389 | dependencies: 390 | call-bind-apply-helpers "^1.0.1" 391 | es-errors "^1.3.0" 392 | gopd "^1.2.0" 393 | 394 | ee-first@1.1.1: 395 | version "1.1.1" 396 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 397 | integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== 398 | 399 | encodeurl@~1.0.2: 400 | version "1.0.2" 401 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 402 | integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== 403 | 404 | encodeurl@~2.0.0: 405 | version "2.0.0" 406 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" 407 | integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== 408 | 409 | es-define-property@^1.0.1: 410 | version "1.0.1" 411 | resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" 412 | integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== 413 | 414 | es-errors@^1.3.0: 415 | version "1.3.0" 416 | resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" 417 | integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== 418 | 419 | es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: 420 | version "1.1.1" 421 | resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" 422 | integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== 423 | dependencies: 424 | es-errors "^1.3.0" 425 | 426 | es-set-tostringtag@^2.1.0: 427 | version "2.1.0" 428 | resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" 429 | integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== 430 | dependencies: 431 | es-errors "^1.3.0" 432 | get-intrinsic "^1.2.6" 433 | has-tostringtag "^1.0.2" 434 | hasown "^2.0.2" 435 | 436 | escape-html@~1.0.3: 437 | version "1.0.3" 438 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 439 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== 440 | 441 | etag@~1.8.1: 442 | version "1.8.1" 443 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 444 | integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== 445 | 446 | ethers@^6.8.1: 447 | version "6.15.0" 448 | resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.15.0.tgz#2980f2a3baf0509749b7e21f8692fa8a8349c0e3" 449 | integrity sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ== 450 | dependencies: 451 | "@adraffy/ens-normalize" "1.10.1" 452 | "@noble/curves" "1.2.0" 453 | "@noble/hashes" "1.3.2" 454 | "@types/node" "22.7.5" 455 | aes-js "4.0.0-beta.5" 456 | tslib "2.7.0" 457 | ws "8.17.1" 458 | 459 | express@^4.18.2: 460 | version "4.21.2" 461 | resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" 462 | integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== 463 | dependencies: 464 | accepts "~1.3.8" 465 | array-flatten "1.1.1" 466 | body-parser "1.20.3" 467 | content-disposition "0.5.4" 468 | content-type "~1.0.4" 469 | cookie "0.7.1" 470 | cookie-signature "1.0.6" 471 | debug "2.6.9" 472 | depd "2.0.0" 473 | encodeurl "~2.0.0" 474 | escape-html "~1.0.3" 475 | etag "~1.8.1" 476 | finalhandler "1.3.1" 477 | fresh "0.5.2" 478 | http-errors "2.0.0" 479 | merge-descriptors "1.0.3" 480 | methods "~1.1.2" 481 | on-finished "2.4.1" 482 | parseurl "~1.3.3" 483 | path-to-regexp "0.1.12" 484 | proxy-addr "~2.0.7" 485 | qs "6.13.0" 486 | range-parser "~1.2.1" 487 | safe-buffer "5.2.1" 488 | send "0.19.0" 489 | serve-static "1.16.2" 490 | setprototypeof "1.2.0" 491 | statuses "2.0.1" 492 | type-is "~1.6.18" 493 | utils-merge "1.0.1" 494 | vary "~1.1.2" 495 | 496 | finalhandler@1.3.1: 497 | version "1.3.1" 498 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" 499 | integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== 500 | dependencies: 501 | debug "2.6.9" 502 | encodeurl "~2.0.0" 503 | escape-html "~1.0.3" 504 | on-finished "2.4.1" 505 | parseurl "~1.3.3" 506 | statuses "2.0.1" 507 | unpipe "~1.0.0" 508 | 509 | follow-redirects@^1.15.6: 510 | version "1.15.11" 511 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" 512 | integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== 513 | 514 | form-data@^4.0.4: 515 | version "4.0.5" 516 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" 517 | integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== 518 | dependencies: 519 | asynckit "^0.4.0" 520 | combined-stream "^1.0.8" 521 | es-set-tostringtag "^2.1.0" 522 | hasown "^2.0.2" 523 | mime-types "^2.1.12" 524 | 525 | forwarded@0.2.0: 526 | version "0.2.0" 527 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 528 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 529 | 530 | fresh@0.5.2: 531 | version "0.5.2" 532 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 533 | integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== 534 | 535 | fs-extra@^11.2.0: 536 | version "11.3.2" 537 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.2.tgz#c838aeddc6f4a8c74dd15f85e11fe5511bfe02a4" 538 | integrity sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A== 539 | dependencies: 540 | graceful-fs "^4.2.0" 541 | jsonfile "^6.0.1" 542 | universalify "^2.0.0" 543 | 544 | function-bind@^1.1.2: 545 | version "1.1.2" 546 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" 547 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 548 | 549 | get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: 550 | version "1.3.0" 551 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" 552 | integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== 553 | dependencies: 554 | call-bind-apply-helpers "^1.0.2" 555 | es-define-property "^1.0.1" 556 | es-errors "^1.3.0" 557 | es-object-atoms "^1.1.1" 558 | function-bind "^1.1.2" 559 | get-proto "^1.0.1" 560 | gopd "^1.2.0" 561 | has-symbols "^1.1.0" 562 | hasown "^2.0.2" 563 | math-intrinsics "^1.1.0" 564 | 565 | get-proto@^1.0.1: 566 | version "1.0.1" 567 | resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" 568 | integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== 569 | dependencies: 570 | dunder-proto "^1.0.1" 571 | es-object-atoms "^1.0.0" 572 | 573 | gopd@^1.2.0: 574 | version "1.2.0" 575 | resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" 576 | integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== 577 | 578 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 579 | version "4.2.11" 580 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" 581 | integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== 582 | 583 | has-symbols@^1.0.3, has-symbols@^1.1.0: 584 | version "1.1.0" 585 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" 586 | integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== 587 | 588 | has-tostringtag@^1.0.2: 589 | version "1.0.2" 590 | resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" 591 | integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== 592 | dependencies: 593 | has-symbols "^1.0.3" 594 | 595 | hasown@^2.0.2: 596 | version "2.0.2" 597 | resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" 598 | integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== 599 | dependencies: 600 | function-bind "^1.1.2" 601 | 602 | helmet@^7.1.0: 603 | version "7.2.0" 604 | resolved "https://registry.yarnpkg.com/helmet/-/helmet-7.2.0.tgz#8b2dcc425b4a46c88f6953481b40453cbe66b167" 605 | integrity sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw== 606 | 607 | http-errors@2.0.0: 608 | version "2.0.0" 609 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" 610 | integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== 611 | dependencies: 612 | depd "2.0.0" 613 | inherits "2.0.4" 614 | setprototypeof "1.2.0" 615 | statuses "2.0.1" 616 | toidentifier "1.0.1" 617 | 618 | iconv-lite@0.4.24: 619 | version "0.4.24" 620 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 621 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 622 | dependencies: 623 | safer-buffer ">= 2.1.2 < 3" 624 | 625 | inherits@2.0.4: 626 | version "2.0.4" 627 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 628 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 629 | 630 | ipaddr.js@1.9.1: 631 | version "1.9.1" 632 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 633 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 634 | 635 | jsonfile@^6.0.1: 636 | version "6.2.0" 637 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" 638 | integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== 639 | dependencies: 640 | universalify "^2.0.0" 641 | optionalDependencies: 642 | graceful-fs "^4.1.6" 643 | 644 | make-error@^1.1.1: 645 | version "1.3.6" 646 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 647 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 648 | 649 | math-intrinsics@^1.1.0: 650 | version "1.1.0" 651 | resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" 652 | integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== 653 | 654 | media-typer@0.3.0: 655 | version "0.3.0" 656 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 657 | integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== 658 | 659 | merge-descriptors@1.0.3: 660 | version "1.0.3" 661 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" 662 | integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== 663 | 664 | methods@~1.1.2: 665 | version "1.1.2" 666 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 667 | integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== 668 | 669 | mime-db@1.52.0: 670 | version "1.52.0" 671 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 672 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 673 | 674 | mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: 675 | version "2.1.35" 676 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 677 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 678 | dependencies: 679 | mime-db "1.52.0" 680 | 681 | mime@1.6.0: 682 | version "1.6.0" 683 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 684 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 685 | 686 | ms@2.0.0: 687 | version "2.0.0" 688 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 689 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== 690 | 691 | ms@2.1.3: 692 | version "2.1.3" 693 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 694 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 695 | 696 | negotiator@0.6.3: 697 | version "0.6.3" 698 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 699 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 700 | 701 | object-assign@^4: 702 | version "4.1.1" 703 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 704 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 705 | 706 | object-inspect@^1.13.3: 707 | version "1.13.4" 708 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" 709 | integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== 710 | 711 | on-finished@2.4.1: 712 | version "2.4.1" 713 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" 714 | integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== 715 | dependencies: 716 | ee-first "1.1.1" 717 | 718 | parseurl@~1.3.3: 719 | version "1.3.3" 720 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 721 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 722 | 723 | path-to-regexp@0.1.12: 724 | version "0.1.12" 725 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" 726 | integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== 727 | 728 | proxy-addr@~2.0.7: 729 | version "2.0.7" 730 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 731 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 732 | dependencies: 733 | forwarded "0.2.0" 734 | ipaddr.js "1.9.1" 735 | 736 | proxy-from-env@^1.1.0: 737 | version "1.1.0" 738 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 739 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 740 | 741 | qs@6.13.0: 742 | version "6.13.0" 743 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" 744 | integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== 745 | dependencies: 746 | side-channel "^1.0.6" 747 | 748 | range-parser@~1.2.1: 749 | version "1.2.1" 750 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 751 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 752 | 753 | raw-body@2.5.2: 754 | version "2.5.2" 755 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" 756 | integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== 757 | dependencies: 758 | bytes "3.1.2" 759 | http-errors "2.0.0" 760 | iconv-lite "0.4.24" 761 | unpipe "1.0.0" 762 | 763 | safe-buffer@5.2.1: 764 | version "5.2.1" 765 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 766 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 767 | 768 | "safer-buffer@>= 2.1.2 < 3": 769 | version "2.1.2" 770 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 771 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 772 | 773 | send@0.19.0: 774 | version "0.19.0" 775 | resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" 776 | integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== 777 | dependencies: 778 | debug "2.6.9" 779 | depd "2.0.0" 780 | destroy "1.2.0" 781 | encodeurl "~1.0.2" 782 | escape-html "~1.0.3" 783 | etag "~1.8.1" 784 | fresh "0.5.2" 785 | http-errors "2.0.0" 786 | mime "1.6.0" 787 | ms "2.1.3" 788 | on-finished "2.4.1" 789 | range-parser "~1.2.1" 790 | statuses "2.0.1" 791 | 792 | serve-static@1.16.2: 793 | version "1.16.2" 794 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" 795 | integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== 796 | dependencies: 797 | encodeurl "~2.0.0" 798 | escape-html "~1.0.3" 799 | parseurl "~1.3.3" 800 | send "0.19.0" 801 | 802 | setprototypeof@1.2.0: 803 | version "1.2.0" 804 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 805 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 806 | 807 | side-channel-list@^1.0.0: 808 | version "1.0.0" 809 | resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" 810 | integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== 811 | dependencies: 812 | es-errors "^1.3.0" 813 | object-inspect "^1.13.3" 814 | 815 | side-channel-map@^1.0.1: 816 | version "1.0.1" 817 | resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" 818 | integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== 819 | dependencies: 820 | call-bound "^1.0.2" 821 | es-errors "^1.3.0" 822 | get-intrinsic "^1.2.5" 823 | object-inspect "^1.13.3" 824 | 825 | side-channel-weakmap@^1.0.2: 826 | version "1.0.2" 827 | resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" 828 | integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== 829 | dependencies: 830 | call-bound "^1.0.2" 831 | es-errors "^1.3.0" 832 | get-intrinsic "^1.2.5" 833 | object-inspect "^1.13.3" 834 | side-channel-map "^1.0.1" 835 | 836 | side-channel@^1.0.6: 837 | version "1.1.0" 838 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" 839 | integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== 840 | dependencies: 841 | es-errors "^1.3.0" 842 | object-inspect "^1.13.3" 843 | side-channel-list "^1.0.0" 844 | side-channel-map "^1.0.1" 845 | side-channel-weakmap "^1.0.2" 846 | 847 | statuses@2.0.1: 848 | version "2.0.1" 849 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" 850 | integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== 851 | 852 | toidentifier@1.0.1: 853 | version "1.0.1" 854 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 855 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 856 | 857 | ts-node@^10.9.1: 858 | version "10.9.2" 859 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" 860 | integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== 861 | dependencies: 862 | "@cspotcode/source-map-support" "^0.8.0" 863 | "@tsconfig/node10" "^1.0.7" 864 | "@tsconfig/node12" "^1.0.7" 865 | "@tsconfig/node14" "^1.0.0" 866 | "@tsconfig/node16" "^1.0.2" 867 | acorn "^8.4.1" 868 | acorn-walk "^8.1.1" 869 | arg "^4.1.0" 870 | create-require "^1.1.0" 871 | diff "^4.0.1" 872 | make-error "^1.1.1" 873 | v8-compile-cache-lib "^3.0.1" 874 | yn "3.1.1" 875 | 876 | tslib@2.7.0: 877 | version "2.7.0" 878 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" 879 | integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== 880 | 881 | type-is@~1.6.18: 882 | version "1.6.18" 883 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 884 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 885 | dependencies: 886 | media-typer "0.3.0" 887 | mime-types "~2.1.24" 888 | 889 | typescript@^5.2.2: 890 | version "5.9.3" 891 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" 892 | integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== 893 | 894 | undici-types@~6.19.2: 895 | version "6.19.8" 896 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" 897 | integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== 898 | 899 | undici-types@~6.21.0: 900 | version "6.21.0" 901 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" 902 | integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== 903 | 904 | undici-types@~7.16.0: 905 | version "7.16.0" 906 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" 907 | integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== 908 | 909 | universalify@^2.0.0: 910 | version "2.0.1" 911 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" 912 | integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== 913 | 914 | unpipe@1.0.0, unpipe@~1.0.0: 915 | version "1.0.0" 916 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 917 | integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== 918 | 919 | utils-merge@1.0.1: 920 | version "1.0.1" 921 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 922 | integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== 923 | 924 | v8-compile-cache-lib@^3.0.1: 925 | version "3.0.1" 926 | resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" 927 | integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== 928 | 929 | vary@^1, vary@~1.1.2: 930 | version "1.1.2" 931 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 932 | integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== 933 | 934 | ws@8.17.1: 935 | version "8.17.1" 936 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" 937 | integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== 938 | 939 | yn@3.1.1: 940 | version "3.1.1" 941 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 942 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 943 | --------------------------------------------------------------------------------