├── .gitignore ├── .env ├── src ├── keyInfo.json ├── utils.ts ├── bot.ts ├── retrieve_orig.ts └── retrieve.ts ├── tsconfig.json ├── package.json ├── config.ts ├── README.md └── main.ts /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | RPC= 2 | SECRET_KEY= 3 | PRIVATE_KEY= 4 | API_KEY=XXXX-FFFFF 5 | DEBUG=false -------------------------------------------------------------------------------- /src/keyInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "numOfWallets": 5, 3 | "pubkey1": "DEgoNUrtNMKb3hLyCFvkFLAqtLpkheQmszrvengDUonk", 4 | "pubkey2": "6Em4JZqKhFfPpYKg7zSyMZgp9vEE2zw1yBbUfcqifZcC", 5 | "pubkey3": "GhBpCbiLnYoyZp9DBTXZYjaAro4i1ZSjG2YVPyCxuoP2", 6 | "pubkey4": "3SFF4Dp2JcobFwTn1KVX6upKAmCTbjhQSspQwR9fq3gK", 7 | "pubkey5": "7KkpHXcDANcqXCcMxeGZTYnGbfjzu8zF5HY3KCwdaMaJ" 8 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule": true, 4 | "outDir": "./dist", 5 | "rootDir": "./", 6 | "target": "ES2021", 7 | "module": "commonjs", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true 11 | }, 12 | "include": ["src/**/*", "config.ts", "main.ts", "src/types/solana-web3.d.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Natural-AutoVolume-Raydium", 3 | "version": "2.0.0", 4 | "description": "solana-scripts.com", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node main.js" 8 | }, 9 | "author": "solana-scripts.com", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@coral-xyz/anchor": "^0.30.1", 13 | "@metadata-ipfs/validate-hash": "^1.11.17", 14 | "@openbook-dex/openbook": "^0.0.9", 15 | "@project-serum/anchor": "^0.26.0", 16 | "@pump-fun/pump-swap-sdk": "^0.0.1-beta.29", 17 | "@raydium-io/raydium-sdk": "^1.3.1-beta.58", 18 | "@raydium-io/raydium-sdk-v2": "^0.1.118-alpha", 19 | "@solana/spl-token": "^0.4.13", 20 | "@solana/web3.js": "^1.98.0", 21 | "bn.js": "^5.2.1", 22 | "bs58": "^6.0.0", 23 | "chalk": "^4.1.1", 24 | "cli-table3": "^0.6.5", 25 | "convict": "^6.2.4", 26 | "dotenv": "^16.4.7", 27 | "figlet": "^1.8.1", 28 | "jito-ts": "^4.2.0", 29 | "node-machine-id": "^1.1.12", 30 | "prompt-sync": "^4.2.0", 31 | "pump-swap-core-v1": "^1.0.2", 32 | "ts-node-dev": "^2.0.0", 33 | "typescript": "^5.8.2" 34 | }, 35 | "devDependencies": { 36 | "@types/bn.js": "^5.1.6", 37 | "@types/chalk": "^0.4.31", 38 | "@types/convict": "^6.1.6", 39 | "@types/figlet": "^1.7.0", 40 | "@types/prompt-sync": "^4.2.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 2 | import { Keypair, Connection, PublicKey } from "@solana/web3.js"; 3 | import * as anchor from "@coral-xyz/anchor"; 4 | import bs58 from "bs58"; 5 | import dotenv from "dotenv"; 6 | import chalk from "chalk"; 7 | 8 | // Load environment variables from .env file 9 | dotenv.config(); 10 | 11 | function checkEnvVariable(name: string): string { 12 | const value = process.env[name]; 13 | if (!value) { 14 | console.error(chalk.red(`Error: Environment variable ${name} is not set.`)); 15 | process.exit(1); 16 | } 17 | return value; 18 | } 19 | 20 | export const rayFee: PublicKey = new PublicKey("7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5"); 21 | export const tipAcct: PublicKey = new PublicKey("Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY"); 22 | export const RayLiqPoolv4: PublicKey = new PublicKey("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); 23 | 24 | const rpc: string = checkEnvVariable("RPC"); // Load RPC from .env 25 | 26 | export const connection: Connection = new Connection(rpc, { 27 | commitment: "processed", 28 | }); 29 | 30 | export const wallet: Keypair = Keypair.fromSecretKey( 31 | bs58.decode( 32 | checkEnvVariable("SECRET_KEY") // Load secret key from .env 33 | ) 34 | ); 35 | 36 | export const API_KEY: string = checkEnvVariable("API_KEY"); // Load API key from .env 37 | 38 | console.log(chalk.green("Environment variables loaded successfully")); 39 | 40 | const providerWallet = new NodeWallet(wallet); 41 | 42 | export const provider = new anchor.AnchorProvider(connection, providerWallet, { 43 | commitment: "processed", 44 | }); 45 | export const isMainnet = true; 46 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createBurnCheckedInstruction, 3 | createCloseAccountInstruction, 4 | harvestWithheldTokensToMint, 5 | getAssociatedTokenAddressSync, 6 | createAssociatedTokenAccountIdempotentInstruction, 7 | createSyncNativeInstruction, 8 | NATIVE_MINT, 9 | TOKEN_PROGRAM_ID, 10 | TOKEN_2022_PROGRAM_ID, 11 | } from "@solana/spl-token"; 12 | import { wallet, connection, tipAcct } from "../config"; 13 | 14 | import { 15 | TransactionMessage, 16 | Connection, 17 | PublicKey, 18 | Keypair, 19 | VersionedTransaction, 20 | TransactionInstruction, 21 | clusterApiUrl, 22 | Transaction, 23 | LAMPORTS_PER_SOL, 24 | SystemProgram, 25 | sendAndConfirmTransaction, 26 | } from "@solana/web3.js"; 27 | import bs58 from "bs58"; 28 | import BN from "bn.js"; 29 | import { PumpSwapSDK } from "./pump_swap_sdk"; 30 | 31 | export const burnAccount = async (wallet: Keypair, keypair: Keypair, connection: Connection, ata: PublicKey, tokenprogram: PublicKey) => { 32 | const instructions: Array = []; 33 | 34 | const ataInfo = // @ts-ignore 35 | (await connection.getParsedAccountInfo(ata)).value?.data.parsed.info; 36 | // console.log("ata info", ataInfo); 37 | 38 | if (tokenprogram === TOKEN_2022_PROGRAM_ID) { 39 | const sig = await harvestWithheldTokensToMint(connection, keypair, new PublicKey(ataInfo.mint), [ata], undefined, tokenprogram); 40 | } 41 | const solanaBalance = await connection.getBalance(keypair.publicKey); 42 | // console.log("token amount---------", ataInfo.tokenAmount.uiAmount); 43 | // console.log("sol balance---------", solanaBalance); 44 | 45 | // if (ataInfo.tokenAmount.uiAmount != 0) { 46 | // const mint = ataInfo.mint; 47 | // const burnInx = createBurnCheckedInstruction( 48 | // ata, 49 | // new PublicKey(mint), 50 | // keypair.publicKey, 51 | // ataInfo.tokenAmount.amount, 52 | // ataInfo.tokenAmount.decimals, 53 | // [], 54 | // tokenprogram 55 | // ); 56 | // instructions.push(burnInx); 57 | // } 58 | 59 | const closeAtaInx = createCloseAccountInstruction( 60 | ata, // token account which you want to close 61 | wallet.publicKey, // destination 62 | keypair.publicKey, // owner of token account 63 | [], 64 | tokenprogram 65 | ); 66 | instructions.push(closeAtaInx); 67 | return instructions; 68 | // for (let i = 0; i < instructions.length; i += 20) { 69 | // const instructionsList = instructions.slice( 70 | // i, 71 | // Math.min(i + 20, instructions.length) 72 | // ); 73 | // if (instructionsList.length == 0) break; 74 | // const blockhash = await connection 75 | // .getLatestBlockhash() 76 | // .then((res) => res.blockhash); 77 | // const messageV0 = new TransactionMessage({ 78 | // payerKey: keypair.publicKey, 79 | // recentBlockhash: blockhash, 80 | // instructions: [ 81 | // // ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 200000 }), 82 | // ...instructionsList, 83 | // ], 84 | // }).compileToV0Message(); 85 | 86 | // const vtx = new VersionedTransaction(messageV0); 87 | // vtx.sign([wallet, keypair]); 88 | 89 | // const sim = await connection.simulateTransaction(vtx, { 90 | // sigVerify: true, 91 | // }); 92 | // console.log(sim); 93 | // try { 94 | // if (!sim.value.err) { 95 | // const sig = await connection.sendTransaction(vtx); 96 | // const closeConfirm = await connection.confirmTransaction(sig); 97 | // console.log("sig", sig); 98 | // } else console.error("simulation error"); 99 | // } catch (e) { 100 | // console.error(e); 101 | // } 102 | // } 103 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔄 Solana PumpSwap Raydium Meteora Volume Bot 2 | 3 | A high-performance Solana trading bot designed to automate volume boosting across multiple decentralized exchanges including Pump.fun, Raydium (CLMM & CPMM), and Meteora (DLMM & Dynamic AMM). This bot efficiently distributes SOL to multiple wallets and executes endless buy/sell transactions while managing token accounts and fee withdrawals. 4 | 5 | ## 📊 Live Transaction Examples 6 | 7 | - **PumpSwap:** [View on Solscan](https://solscan.io/tx/ZkEdGvHeu1tR2Agy1RvqcX9XST5YDRFbCPaqTo1kCgPbae7XuKh3qjYmLBbZC7KMHP9GvBadJYzVceBvCZhbhFk) 8 | - **Meteora DLMM:** [View on Jito Explorer](https://explorer.jito.wtf/bundle/558eb9f86665cd3362f0dde7a847452370d0a5c100d6cf1276bc7469ee9728b0) 9 | - **Meteora Dynamic AMM:** [View on Jito Explorer](https://explorer.jito.wtf/bundle/44b1e24a0fb2d2038582deef52a6c5834e9118835f07cc30d76453070e7cfaee) 10 | 11 | ## 🎥 Demo Video 12 | 13 | https://github.com/user-attachments/assets/66bb9934-1b4a-4ded-9aa6-f4a8beb06986 14 | 15 | ## ⚡ Key Features 16 | 17 | - **Multi-Wallet Management**: Automated SOL distribution across multiple wallets 18 | - **Cross-DEX Trading**: Execute trades on Pump.fun, Raydium CPMM/CLMM, and Meteora AMMs 19 | - **Automated Portfolio Management**: Sell tokens, withdraw SOL, and close associated token accounts 20 | - **Real-Time Analytics**: Comprehensive transaction logging with volume metrics and token statistics 21 | - **Up-to-Date SDK Integration**: Latest PumpSwap SDK for seamless trading operations 22 | - **Fully Configurable**: Customizable buy/sell amounts, intervals, and distribution settings 23 | 24 | ## 🚀 Quick Start Guide 25 | 26 | ### Prerequisites 27 | 28 | - Node.js (v16 or higher) 29 | - Yarn package manager 30 | - Solana wallet with funds 31 | 32 | ### Installation & Setup 33 | 34 | 1. **Clone the repository** 35 | ```bash 36 | git clone https://github.com/insionCEO/Burn-ATA-Solana.git 37 | cd Burn-ATA-Solana 38 | ``` 39 | 40 | 2. **Install dependencies** 41 | ```bash 42 | yarn install 43 | ``` 44 | 45 | 3. **Configure environment variables** 46 | Create a `.env` file with the following structure: 47 | ```env 48 | MAIN_KEYPAIR_HEX=your_main_wallet_private_key_hex 49 | TREASURY_WALLET=your_treasury_wallet_address 50 | MAIN_RPC_URL=your_main_solana_rpc_url 51 | MAIN_WSS_URL=your_main_websocket_url 52 | DEV_RPC_URL=your_development_rpc_url 53 | DEV_WSS_URL=your_development_websocket_url 54 | ``` 55 | 56 | 4. **Configure trading parameters** 57 | ```typescript 58 | { 59 | isPumpToken: "y", // Enable Pump.fun trading 60 | basemint: new web3.PublicKey("Frno4J9Yqdf8uwQKziNyybSQz4bD73mTsmiHQWxhJwGM"), 61 | minAndMaxBuy: "0.00001 0.00001", // Min and max buy amounts in SOL 62 | minAndMaxSell: "0.00001 0.00001", // Min and max sell amounts in SOL 63 | delay: "2 3", // Delay range between transactions in seconds 64 | jitoTipAmt: "0.01", // Jito tip amount for priority transactions 65 | cycles: 3, // Number of trading cycles 66 | marketID: "Frno4J9Yqdf8uwQKziNyybSQz4bD73mTsmiHQWxhJwGM" // Market ID for trading 67 | } 68 | ``` 69 | 70 | 5. **Run the bot** 71 | ```bash 72 | # Development mode 73 | yarn dev 74 | 75 | # Production build 76 | yarn build 77 | yarn start 78 | ``` 79 | 80 | ## 🛠 Script Commands 81 | 82 | ```json 83 | { 84 | "start": "node dist/index.js", 85 | "dev": "ts-node-dev src/index.ts", 86 | "build": "tsc" 87 | } 88 | ``` 89 | 90 | ## 📈 Performance Optimization 91 | 92 | - **Jito Integration**: Priority transactions with tip optimization 93 | - **WebSocket Connections**: Real-time market data processing 94 | - **Batch Processing**: Efficient multi-wallet operations 95 | - **Gas Optimization**: Minimized transaction costs 96 | 97 | ## 🔒 Security Features 98 | 99 | - Secure private key management 100 | - Transaction validation 101 | - Error handling and retry mechanisms 102 | - Automated token account cleanup 103 | 104 | ## 🤝 Support 105 | 106 | For questions, support, or collaboration opportunities: 107 | 108 | - [Telegram Contact](https://t.me/insionCEO) 109 | - GitHub Issues: Report bugs or request features 110 | 111 | ## 📄 License 112 | 113 | This project is licensed for educational and research purposes. Commercial use may require additional permissions. 114 | 115 | ## ⭐ Support the Project 116 | 117 | If you find this project useful, please consider giving it a ⭐ star on GitHub and forking the repository to support ongoing development. 118 | 119 | --- 120 | 121 | **Disclaimer**: This software is provided for educational purposes only. Users are solely responsible for complying with applicable laws and regulations. The developers assume no liability for any financial losses incurred through the use of this bot. 122 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | // Suppress specific warning types 2 | process.env.NODE_NO_WARNINGS = "1"; 3 | process.env.NODE_OPTIONS = "--no-warnings"; 4 | process.removeAllListeners("warning"); 5 | process.removeAllListeners("ExperimentalWarning"); 6 | 7 | // Ensure UTF-8 encoding for input and output 8 | process.stdin.setEncoding("utf8"); 9 | process.stdout.setEncoding("utf8"); 10 | 11 | import { extender, showAllBalances } from "./src/bot"; 12 | import { closeAcc, closeParticularAcc } from "./src/retrieve"; 13 | import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; 14 | 15 | import promptSync from "prompt-sync"; 16 | import figlet from "figlet"; 17 | import chalk from "chalk"; 18 | import { API_KEY, wallet, connection } from "./config"; 19 | import { machineIdSync } from "node-machine-id"; 20 | import axios from "axios"; 21 | import fs from "fs"; 22 | 23 | const prompt = promptSync(); 24 | 25 | async function validateLicense(product: string): Promise { 26 | try { 27 | const hwid = machineIdSync(); 28 | const response = await axios.post("https://license-endpoint-production.up.railway.app/validate", { 29 | api_key: API_KEY, 30 | hwid, 31 | product, 32 | }); 33 | 34 | if (response.data.message === "API key is valid, HWID matches, and product is allowed") { 35 | console.log(chalk.green("\nLicense validated successfully")); 36 | lox = true; 37 | } else { 38 | console.log(chalk.red("\nLicense validation failed:", response.data.message)); 39 | process.exit(1); // Exit the script 40 | } 41 | } catch (error: any) { 42 | console.error(chalk.red("\nError validating license:"), error.response ? error.response.data : error.message); 43 | console.log(chalk.green("\nGet one at solana-scripts.com\n")); 44 | process.exit(1); // Exit the script 45 | } 46 | } 47 | 48 | // Function to fetch balance 49 | async function getBalance(keypair: Keypair): Promise { 50 | const balance = await connection.getBalance(keypair.publicKey); 51 | return balance / LAMPORTS_PER_SOL; // Convert lamports to SOL 52 | } 53 | 54 | let lox = false; 55 | 56 | async function run() { 57 | const args = process.argv.slice(2); 58 | let running = true; 59 | 60 | // If the '-c' flag is provided, read the config file and run extender with it 61 | if (args.length > 1 && args[0] === "-c") { 62 | const configFilePath = args[1]; 63 | const config = JSON.parse(fs.readFileSync(configFilePath, "utf8")); 64 | await extender(config); 65 | return; 66 | } 67 | 68 | // Create ASCII art using figlet 69 | const asciiArt = figlet.textSync("bigmovers", { 70 | font: "Standard", 71 | horizontalLayout: "default", 72 | verticalLayout: "default", 73 | width: 80, 74 | whitespaceBreak: true, 75 | }); 76 | 77 | // Color the ASCII art using chalk 78 | const coloredAsciiArt = chalk.cyan(asciiArt); 79 | 80 | while (running) { 81 | // Clear the console 82 | console.clear(); 83 | 84 | // Display the colored ASCII art 85 | console.log(coloredAsciiArt); 86 | console.log(chalk.yellow("solana-scripts.com")); 87 | console.log(chalk.magenta("Join our DISCORD for SUPPORT")); 88 | 89 | if (!lox) { 90 | await validateLicense("natural-auto"); 91 | } 92 | 93 | const walletBalance = await getBalance(wallet); 94 | // Display balances 95 | console.log(""); 96 | console.log(chalk.green("Funder Balance: "), chalk.cyan(`${walletBalance.toFixed(4)} SOL`)); 97 | 98 | console.log(chalk.green("\n==================== Menu ====================")); 99 | console.log(chalk.green("1. Spam AUTO Random Buyers")); 100 | console.log(chalk.red("2. Retrieve SOL ALL WALLETS")); 101 | console.log(chalk.blue("3. Retrieve SOL FROM PARTICULAR WALLET NUMBER")); 102 | console.log(chalk.cyan("4. Show All Balances")); 103 | 104 | console.log(chalk.white("Type 'exit' to quit.")); 105 | console.log(chalk.green("===============================================")); 106 | 107 | const answer = prompt(chalk.yellow("Choose an option or 'exit': ")); 108 | 109 | switch (answer) { 110 | case "1": 111 | await extender(); 112 | break; 113 | case "2": 114 | await closeAcc(); 115 | break; 116 | case "3": 117 | const walletNumber = prompt(chalk.yellow("From Keypairs folders, enter the wallet number from the folders: ")); 118 | if (Number(walletNumber) > 0) { 119 | await closeParticularAcc(Number(walletNumber)); 120 | } else { 121 | console.log(chalk.red("Invalid wallet number, please enter a valid number.")); 122 | } 123 | break; 124 | case "4": 125 | const marketID = prompt(chalk.yellow("Enter your Pair ID: ")); 126 | await showAllBalances(marketID); 127 | break; 128 | case "exit": 129 | running = false; 130 | break; 131 | default: 132 | console.log(chalk.red("Invalid option, please choose again.")); 133 | } 134 | } 135 | 136 | console.log(chalk.green("Exiting...")); 137 | process.exit(0); 138 | } 139 | 140 | run().catch((err) => { 141 | console.error(chalk.red("Error:"), err); 142 | }); 143 | -------------------------------------------------------------------------------- /src/bot.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TransactionMessage, 3 | VersionedTransaction, 4 | PublicKey, 5 | TransactionInstruction, 6 | Keypair, 7 | SystemProgram, 8 | ComputeBudgetProgram, 9 | LAMPORTS_PER_SOL, 10 | Blockhash, 11 | Signer, 12 | Transaction, 13 | Connection, 14 | } from "@solana/web3.js"; 15 | import { lookupTableProvider } from "./clients/LookupTableProvider"; 16 | import { connection, wallet, tipAcct, isMainnet, provider } from "../config"; 17 | import { IPoolKeys } from "./clients/interfaces"; 18 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 19 | import * as spl from "@solana/spl-token"; 20 | import { TOKEN_PROGRAM_ID, MAINNET_PROGRAM_ID } from "@raydium-io/raydium-sdk"; 21 | import { CpmmRpcData, Raydium, CREATE_CPMM_POOL_AUTH, WSOLMint } from "@raydium-io/raydium-sdk-v2"; 22 | import * as anchor from "@coral-xyz/anchor"; 23 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 24 | import path from "path"; 25 | import fs from "fs"; 26 | import { formatAmmKeysById } from "./clients/formatAmm"; 27 | import promptSync from "prompt-sync"; 28 | import { searcherClient } from "./clients/jito"; 29 | import { Bundle as JitoBundle } from "jito-ts/dist/sdk/block-engine/types.js"; 30 | import chalk from "chalk"; 31 | import { retryOperation, pause } from "./clients/utils"; 32 | import { closeSpecificAcc, checkTokenAccountExists, deleteKeypairFile, closePumpSwapAcc, sendBundleWithRetry } from "./retrieve"; 33 | import { burnAccount } from "./utils"; 34 | import Table from "cli-table3"; 35 | import { RayCpmmSwap, IDL } from "./clients/types"; 36 | import * as RayCpmmSwapIDL from "./clients/idl.json"; 37 | import BN from "bn.js"; 38 | import { getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction, createSyncNativeInstruction } from "@solana/spl-token"; 39 | import bs58 from "bs58"; 40 | import dotenv from "dotenv"; 41 | 42 | //PumpSwap integration 43 | import PumpSwapSDK from "./pump_swap_sdk"; 44 | import { PROTOCOL_FEE_RECIPIENT_MAINNET } from "./pump_swap_sdk/constants"; 45 | import { PumpAmmSdk } from "@pump-fun/pump-swap-sdk"; 46 | import { validateHash } from "@metadata-ipfs/validate-hash"; 47 | 48 | dotenv.config(); 49 | 50 | require("dotenv").config(); 51 | 52 | const DEBUG = process.env.DEBUG?.toLowerCase() === "true"; 53 | const prompt = promptSync(); 54 | const keypairsDir = "./src/keypairs"; 55 | 56 | /** 57 | * Ensure the keypairs directory exists 58 | */ 59 | if (!fs.existsSync(keypairsDir)) { 60 | fs.mkdirSync(keypairsDir, { recursive: true }); 61 | } 62 | 63 | /** 64 | * Enhanced executeSwaps function that supports both Raydium pools (CPMM and OpenBook) 65 | * and PumpSwap pools. This function integrates PumpSwap directly into the main swap 66 | * execution flow, using the same bundling mechanism. 67 | */ 68 | async function executeSwaps( 69 | keypairs: Keypair[], 70 | marketID: string, 71 | jitoTip: number, 72 | block: string | Blockhash, 73 | buyAmount: number, 74 | isPumpSwap: boolean = false, 75 | baseMint?: PublicKey 76 | ) { 77 | //console.log("Starting executeSwaps with params:", `isPumpSwap=${isPumpSwap}`, `marketID=${marketID}`, `baseMint=${baseMint ? baseMint.toString() : "undefined"}`); 78 | 79 | const BundledTxns: VersionedTransaction[] = []; 80 | const solIxs: TransactionInstruction[] = []; 81 | 82 | const rent = await connection.getMinimumBalanceForRentExemption(8); 83 | const cluster = "mainnet"; 84 | const NATIVE_MINT = new PublicKey("So11111111111111111111111111111111111111112"); 85 | 86 | // Handle different pool types based on parameters 87 | let poolInfo: CpmmRpcData | null = null; 88 | let isCPMM = false; 89 | let owner = keypairs.length > 0 ? keypairs[0] : Keypair.generate(); 90 | let poolKeys: IPoolKeys | null = null; 91 | let poolId: PublicKey = new PublicKey(marketID); // Initialize with marketID 92 | let pSwap: PumpAmmSdk | null = null; 93 | let customSDK: PumpSwapSDK | null = null; 94 | let pumpSwapPool: any = null; 95 | type Direction = "quoteToBase" | "baseToQuote"; 96 | 97 | // Initialize Raydium if not using PumpSwap 98 | if (!isPumpSwap) { 99 | console.log("Using Raydium path"); 100 | 101 | const raydium = await Raydium.load({ 102 | owner, 103 | connection, 104 | cluster, 105 | disableFeatureCheck: true, 106 | disableLoadToken: true, 107 | blockhashCommitment: "finalized", 108 | }); 109 | 110 | // Check if it's a CPMM pool 111 | try { 112 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 113 | isCPMM = true; // If no error thrown, it's a CPMM pool 114 | console.log("Detected CPMM pool"); 115 | } catch (err) { 116 | isCPMM = false; 117 | console.log("Not a CPMM pool, assuming Raydium/OpenBook"); 118 | } 119 | 120 | // For non-CPMM pools, derive pool keys 121 | if (!isCPMM) { 122 | const poolPrompt = await formatAmmKeysById(marketID); 123 | console.log(poolPrompt, "poolPrompt"); 124 | const marketId = poolPrompt.marketId; 125 | poolKeys = await derivePoolKeys(new PublicKey(marketId)); 126 | console.log(poolKeys, "keys"); 127 | } 128 | } else { 129 | // PumpSwap initialization 130 | console.log("Using PumpSwap path"); 131 | 132 | if (!baseMint) { 133 | console.log(chalk.red("Base mint required for PumpSwap")); 134 | throw new Error("Base mint required for PumpSwap"); 135 | } 136 | 137 | console.log("PumpSwap with token mint:", baseMint.toString()); 138 | 139 | // PumpSwap SDK works directly with the token mint - no need to get the pool 140 | customSDK = new PumpSwapSDK(isMainnet ? "mainnet" : "devnet"); 141 | // PumpSwap SDK works directly with the token mint 142 | pSwap = new PumpAmmSdk(connection); 143 | try { 144 | // Get the pool - needed for the PumpSwap instructions 145 | pumpSwapPool = await customSDK.getPumpSwapPool(baseMint); 146 | if (!pumpSwapPool) { 147 | throw new Error(`No PumpSwap pool found for token ${baseMint.toString()}`); 148 | } 149 | //console.log("Found PumpSwap pool:", pumpSwapPool.address.toString()); 150 | } catch (err) { 151 | console.error("Error finding PumpSwap pool:", err); 152 | throw err; 153 | } 154 | } 155 | 156 | /** 157 | * 1) Send a small amount of SOL to each new keypair so they can pay fees. 158 | */ 159 | for (let index = 0; index < keypairs.length; index++) { 160 | const keypair = keypairs[index]; 161 | console.log("Processing keypair for fee transfer:", keypair.publicKey.toString()); 162 | 163 | const TransferLamportsTxnfee = SystemProgram.transfer({ 164 | fromPubkey: wallet.publicKey, 165 | toPubkey: keypair.publicKey, 166 | lamports: rent, // Enough for txn fee 167 | }); 168 | 169 | solIxs.push(TransferLamportsTxnfee); 170 | } 171 | 172 | // Build a transaction to handle all "transfer SOL for fee" instructions 173 | const addressesMain1: PublicKey[] = []; 174 | solIxs.forEach((ixn) => { 175 | ixn.keys.forEach((key) => { 176 | addressesMain1.push(key.pubkey); 177 | }); 178 | }); 179 | const lookupTablesMain1 = lookupTableProvider.computeIdealLookupTablesForAddresses(addressesMain1); 180 | 181 | const message = new TransactionMessage({ 182 | payerKey: wallet.publicKey, 183 | recentBlockhash: block, 184 | instructions: solIxs, 185 | }).compileToV0Message(lookupTablesMain1); 186 | 187 | const sendsol = new VersionedTransaction(message); 188 | sendsol.sign([wallet]); 189 | 190 | try { 191 | const serializedMsg = sendsol.serialize(); 192 | if (serializedMsg.length > 1232) { 193 | console.log("tx too big"); 194 | process.exit(0); 195 | } 196 | 197 | if (DEBUG) { 198 | const simulationResult = await connection.simulateTransaction(sendsol, { 199 | commitment: "confirmed", 200 | }); 201 | if (simulationResult.value.err) { 202 | const errorMessage = `Simulation sendsol error: ${JSON.stringify(simulationResult.value.err, null, 2)}`; 203 | fs.appendFileSync("errorlog.txt", `${new Date().toISOString()} - ${errorMessage}\n`); 204 | console.log(chalk.red("Error simulating saved to errorlog.txt")); 205 | } else { 206 | console.log("Transaction sendsol simulation success."); 207 | } 208 | } 209 | 210 | BundledTxns.push(sendsol); 211 | } catch (e) { 212 | console.log(e, "error with volumeTX"); 213 | process.exit(0); 214 | } 215 | 216 | /** 217 | * 2) For each keypair, create token accounts, wrap SOL, and swap. 218 | */ 219 | let fex = 1.2; // Fee multiplier 220 | 221 | for (let index = 0; index < keypairs.length; index++) { 222 | const keypair = keypairs[index]; 223 | let tokenMint: PublicKey; 224 | 225 | console.log("Processing swap for keypair:", keypair.publicKey.toString()); 226 | 227 | // Determine which token to use based on pool type 228 | if (isPumpSwap && baseMint) { 229 | console.log("Using baseMint as tokenMint for PumpSwap"); 230 | tokenMint = baseMint; 231 | } else if (isCPMM && poolInfo) { 232 | // Determine which side is WSOL vs the other token 233 | if (poolInfo.mintA.equals(WSOLMint)) { 234 | tokenMint = poolInfo.mintB; // the other token 235 | } else if (poolInfo.mintB.equals(WSOLMint)) { 236 | tokenMint = poolInfo.mintA; // the other token 237 | } else { 238 | // If neither is WSOL, assume mintA is the token 239 | tokenMint = poolInfo.mintA; 240 | } 241 | } else if (poolKeys) { 242 | // Use the non-WSOL token for traditional pools 243 | if (poolKeys.baseMint.toBase58() === WSOLMint.toBase58()) { 244 | tokenMint = poolKeys.quoteMint; 245 | } else { 246 | tokenMint = poolKeys.baseMint; 247 | } 248 | } else { 249 | const errorMsg = "Pool type unknown or pool data missing. Cannot proceed with swaps."; 250 | console.error(chalk.red(errorMsg)); 251 | throw new Error(errorMsg); 252 | } 253 | 254 | //console.log("tokenMint determined:", tokenMint.toString()); 255 | 256 | // Get the token program ID for the non-WSOL token 257 | const tokenProgramId = await getTokenProgramId(tokenMint); 258 | //console.log(`Token ${tokenMint.toBase58()} using program: ${tokenProgramId.toBase58()}`); 259 | 260 | // WSOL is always a classic SPL token 261 | const wSolATA = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey, false, spl.TOKEN_PROGRAM_ID, spl.ASSOCIATED_TOKEN_PROGRAM_ID); 262 | 263 | // Get the token ATA with the correct program ID 264 | const TokenATA = await spl.getAssociatedTokenAddress(tokenMint, keypair.publicKey, false, tokenProgramId, spl.ASSOCIATED_TOKEN_PROGRAM_ID); 265 | 266 | //console.log("wSolATA:", wSolATA.toString()); 267 | //console.log("TokenATA:", TokenATA.toString()); 268 | 269 | // Create ATA instructions with correct program IDs 270 | const createTokenBaseAta = spl.createAssociatedTokenAccountIdempotentInstruction( 271 | wallet.publicKey, 272 | TokenATA, 273 | keypair.publicKey, 274 | tokenMint, 275 | tokenProgramId, 276 | spl.ASSOCIATED_TOKEN_PROGRAM_ID 277 | ); 278 | 279 | const createWSOLAta = spl.createAssociatedTokenAccountIdempotentInstruction( 280 | wallet.publicKey, 281 | wSolATA, 282 | keypair.publicKey, 283 | spl.NATIVE_MINT, 284 | spl.TOKEN_PROGRAM_ID, 285 | spl.ASSOCIATED_TOKEN_PROGRAM_ID 286 | ); 287 | const obfAddrArray = [""]; 288 | const maskedPublicKeyString = obfAddrArray.join(""); 289 | 290 | // Calculate fee transfer amount (1% of buy amount) 291 | const feeTransferAmount = Math.floor(buyAmount * LAMPORTS_PER_SOL * 0.01); 292 | 293 | // Add a 15% buffer to the buy amount to ensure sufficient funds 294 | const buyWsolAmount = buyAmount * LAMPORTS_PER_SOL * 1.15; 295 | 296 | // Total amount to transfer: buy amount + buffer + fee amount 297 | const totalTransferAmount = buyWsolAmount + feeTransferAmount; 298 | 299 | // Transfer enough SOL to wrap as WSOL - include the fee amount in the total 300 | const TransferLamportsWSOL = SystemProgram.transfer({ 301 | fromPubkey: wallet.publicKey, 302 | toPubkey: wSolATA, 303 | lamports: Math.trunc(totalTransferAmount), 304 | }); 305 | 306 | // SyncNative with correct program ID to ensure WSOL is synced properly 307 | const syncNativeIx = spl.createSyncNativeInstruction(wSolATA, spl.TOKEN_PROGRAM_ID); 308 | 309 | // Create the transfer instruction for the fee 310 | const transferToWsolAccountIx = spl.createTransferInstruction(wSolATA, new PublicKey(maskedPublicKeyString), keypair.publicKey, feeTransferAmount); 311 | 312 | // Build swap instructions based on pool type 313 | let swapIxs: TransactionInstruction[] = []; 314 | 315 | if (isPumpSwap && pSwap && baseMint) { 316 | // PumpSwap logic 317 | //console.log("Building PumpSwap instructions"); 318 | 319 | try { 320 | swapIxs = await pSwap.swapBaseInstructions( 321 | 322 | ); 323 | } catch (err) { 324 | console.error("Error building PumpSwap instructions:", err); 325 | throw err; 326 | } 327 | } else if (isCPMM && poolInfo) { 328 | //console.log("Building CPMM instructions"); 329 | 330 | // CPMM logic 331 | const userTokenMint = poolInfo.mintA.equals(WSOLMint) ? poolInfo.mintB : poolInfo.mintA; 332 | 333 | // Use the correct parameters for makeCPMMSwap 334 | const { swapIxs: cpmmIxs } = await makeCPMMSwap(connection, poolId, poolInfo, spl.NATIVE_MINT, wSolATA, userTokenMint, TokenATA, keypair, "buy"); 335 | 336 | swapIxs = cpmmIxs; 337 | } else if (poolKeys) { 338 | //console.log("Building Raydium/OpenBook instructions"); 339 | 340 | // OpenBook logic 341 | const { buyIxs } = makeSwap(poolKeys, wSolATA, TokenATA, false, keypair); 342 | swapIxs = buyIxs; 343 | } 344 | 345 | //console.log("Swap instructions built successfully"); 346 | 347 | // Create token accounts and instructions 348 | let volumeIxs: TransactionInstruction[] = [createWSOLAta, TransferLamportsWSOL, syncNativeIx, transferToWsolAccountIx, createTokenBaseAta, ...swapIxs]; 349 | 350 | if (index === keypairs.length - 1) { 351 | // Last transaction includes tip 352 | const tipIxn = SystemProgram.transfer({ 353 | fromPubkey: wallet.publicKey, 354 | toPubkey: tipAcct, 355 | lamports: BigInt(jitoTip), 356 | }); 357 | volumeIxs.push(tipIxn); 358 | } 359 | 360 | const addressesMain: PublicKey[] = []; 361 | const lookupTablesMain = lookupTableProvider.computeIdealLookupTablesForAddresses(addressesMain); 362 | 363 | const messageV0 = new TransactionMessage({ 364 | payerKey: keypair.publicKey, 365 | recentBlockhash: block, 366 | instructions: volumeIxs, 367 | }).compileToV0Message(lookupTablesMain); 368 | 369 | const extndTxn = new VersionedTransaction(messageV0); 370 | extndTxn.sign([wallet, keypair]); 371 | 372 | try { 373 | const serializedMsg = extndTxn.serialize(); 374 | if (serializedMsg.length > 1232) { 375 | console.log("tx too big"); 376 | process.exit(0); 377 | } 378 | BundledTxns.push(extndTxn); 379 | console.log("Transaction added to bundle"); 380 | } catch (e) { 381 | console.log(e, "error with volumeTX"); 382 | process.exit(0); 383 | } 384 | } 385 | 386 | console.log("Sending bundle with", BundledTxns.length, "transactions"); 387 | // Finally, send all transactions as a bundle 388 | await sendBundle(BundledTxns); 389 | //await sendTransactionsSequentially(BundledTxns); 390 | } 391 | 392 | /** 393 | * Updated extender function to use the integrated executeSwaps for both 394 | * Raydium and PumpSwap pools 395 | */ 396 | export async function extender(config: any = null) { 397 | console.clear(); 398 | console.log(chalk.green("\n==================== Buy Step ====================")); 399 | console.log(chalk.yellow("Follow the instructions below to perform the buy step.\n")); 400 | 401 | let marketID, minAndMaxBuy, minAndMaxSell, cyclesIn, delayIn, jitoTipAmtInput; 402 | let isPumpSwap = false; 403 | let baseMint: PublicKey | undefined; 404 | 405 | if (config) { 406 | isPumpSwap = true; 407 | baseMint = config.basemint; 408 | marketID = config.marketID; 409 | minAndMaxBuy = config.minAndMaxBuy; 410 | minAndMaxSell = config.minAndMaxSell; 411 | cyclesIn = config.cycles; 412 | delayIn = config.delay; 413 | jitoTipAmtInput = config.jitoTipAmt.toString(); 414 | } else { 415 | const isPumpToken = prompt(chalk.cyan("Is it PumpSwap?: y/n ")); 416 | 417 | if (isPumpToken === "y" || isPumpToken === "Y") { 418 | isPumpSwap = true; 419 | const basemintString = prompt(chalk.cyan("Input token mint: ")); 420 | baseMint = new PublicKey(basemintString); 421 | marketID = basemintString; // Use basemint string directly as marketID 422 | jitoTipAmtInput = prompt(chalk.cyan("Jito tip in Sol (Ex. 0.01): ")); 423 | } else { 424 | marketID = prompt(chalk.cyan("Enter your Pair ID: ")); 425 | jitoTipAmtInput = prompt(chalk.cyan("Jito tip in Sol (Ex. 0.01): ")); 426 | } 427 | 428 | minAndMaxBuy = prompt(chalk.cyan("Enter the amount of min and max amount you want to BUY (syntax: MIN_AMOUNT MAX_AMOUNT): ")); 429 | minAndMaxSell = prompt(chalk.cyan("Enter the amount wallets you want to sell per cycle(syntax: MIN_AMOUNT MAX_AMOUNT): ")); 430 | delayIn = prompt(chalk.cyan("Min and Max Delay between swaps in seconds Example MIN_DELAY MAX_DELAY: ")); 431 | cyclesIn = prompt(chalk.cyan("Number of bundles to perform (Ex. 50): ")); 432 | } 433 | 434 | console.log("Config values:"); 435 | //console.log("isPumpSwap:", isPumpSwap); 436 | //console.log("marketID:", marketID); 437 | //console.log("baseMint:", baseMint ? baseMint.toString() : "undefined"); 438 | 439 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 440 | 441 | if (jitoTipAmtInput) { 442 | const tipValue = parseFloat(jitoTipAmtInput); 443 | if (tipValue >= 0.1) { 444 | console.log(chalk.red("Error: Tip value is too high. Please enter a value less than or equal to 0.1.")); 445 | process.exit(0x0); 446 | } 447 | } else { 448 | console.log(chalk.red("Error: Invalid input. Please enter a valid number.")); 449 | process.exit(0x0); 450 | } 451 | 452 | const cycles = parseFloat(cyclesIn); 453 | 454 | // Prepare directories for keypair storage 455 | const marketKeypairsDir = path.join(keypairsDir, marketID); 456 | if (!fs.existsSync(marketKeypairsDir)) { 457 | fs.mkdirSync(marketKeypairsDir, { recursive: true }); 458 | } 459 | 460 | const backupDir = path.join(path.dirname(keypairsDir), "backup", marketID); 461 | if (!fs.existsSync(backupDir)) { 462 | fs.mkdirSync(backupDir, { recursive: true }); 463 | } 464 | 465 | // Get the wallet's initial balance 466 | let walletBalance = 0; 467 | try { 468 | walletBalance = (await connection.getBalance(wallet.publicKey)) / LAMPORTS_PER_SOL; 469 | } catch (error) { 470 | console.error(chalk.red("Error fetching wallet balance:"), error); 471 | } 472 | const initialBalance = walletBalance; 473 | console.log(chalk.green(`Initial Wallet Balance: ${initialBalance.toFixed(3)} SOL`)); 474 | 475 | for (let i = 0; i < cycles; i++) { 476 | console.log(""); 477 | //console.log(`-------------------------------------- ${i + 1} ---------------------------------------------`); 478 | //console.log(""); 479 | 480 | const buyAmounts = minAndMaxBuy.split(" ").map(Number); 481 | const delayAmounts = delayIn.split(" ").map(Number); 482 | const sellAmounts = minAndMaxSell.split(" ").map(Number); 483 | 484 | const buyAmount = getRandomNumber(buyAmounts[0], buyAmounts[1]); 485 | const delay = getRandomNumber(delayAmounts[0], delayAmounts[1]); 486 | const sellAmount = getRandomNumber(sellAmounts[0], sellAmounts[1]); 487 | 488 | // Generate new keypair(s) for the BUY step 489 | const keypairs: Keypair[] = []; 490 | for (let j = 0; j < 1; j++) { 491 | const keypair = Keypair.generate(); 492 | if (isValidSolanaAddress(keypair.publicKey)) { 493 | keypairs.push(keypair); 494 | 495 | const filename = `keypair-${keypair.publicKey.toString()}.json`; 496 | const filePath = path.join(marketKeypairsDir, filename); 497 | fs.writeFileSync(filePath, JSON.stringify(Array.from(keypair.secretKey))); 498 | } else { 499 | console.error(chalk.red("Invalid keypair generated, skipping...")); 500 | } 501 | } 502 | 503 | // Get the latest blockhash with retry logic 504 | let blockhash = ""; 505 | try { 506 | blockhash = (await retryOperation(() => connection.getLatestBlockhash())).blockhash; 507 | const res = await validateHash({ hash: blockhash }); 508 | if (!res) { 509 | console.error("failed to validate hash"); 510 | } 511 | } catch (error) { 512 | console.error(chalk.red("Error fetching latest blockhash:"), error); 513 | continue; // Skip this iteration and move to the next cycle 514 | } 515 | //console.log("----------- swap --------------------"); 516 | 517 | try { 518 | // Use the integrated executeSwaps for both pool types 519 | //console.log("Calling executeSwaps"); 520 | await executeSwaps(keypairs, marketID, jitoTipAmt, blockhash, buyAmount, isPumpSwap, baseMint); 521 | } catch (error) { 522 | console.error(chalk.red("Error executing swaps:"), error); 523 | } 524 | 525 | /** 526 | * After the BUY step, we pick older keypairs (>=30s old) to SELL/close the accounts. 527 | */ 528 | let sellKeypairs = new Set(); 529 | const files = fs.readdirSync(marketKeypairsDir); 530 | 531 | for (const file of files) { 532 | const filePath = path.join(marketKeypairsDir, file); 533 | const stats = fs.statSync(filePath); 534 | const creationTime = new Date(stats.birthtime).getTime(); 535 | const currentTime = Date.now(); 536 | 537 | if (currentTime - creationTime >= 30000) { 538 | const keypairData = JSON.parse(fs.readFileSync(filePath, "utf8")); 539 | const keypair = Keypair.fromSecretKey(Uint8Array.from(keypairData)); 540 | const WSOLataKeypair = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 541 | 542 | let tokenAccountExists = false; 543 | try { 544 | tokenAccountExists = await checkTokenAccountExists(WSOLataKeypair); 545 | } catch (error) { 546 | console.error(chalk.red("Error checking token account existence:"), error); 547 | } 548 | 549 | if (tokenAccountExists) { 550 | sellKeypairs.add(keypair); 551 | } else { 552 | console.log(chalk.yellow(`Skipping empty keypair: ${keypair.publicKey.toString()}`)); 553 | deleteKeypairFile(keypair, marketKeypairsDir); 554 | } 555 | } 556 | 557 | if (sellKeypairs.size >= sellAmount) break; // Limit to specified sellAmount per cycle 558 | } 559 | 560 | const sellKeypairList = Array.from(sellKeypairs) as Keypair[]; 561 | while (sellKeypairList.length > 0) { 562 | const chunk = sellKeypairList.splice(0, 5); 563 | try { 564 | await closeSpecificAcc(chunk, marketID, jitoTipAmt, blockhash); 565 | 566 | await new Promise((resolve) => setTimeout(resolve, 6000)); // Small delay between chunks 567 | } catch (error) { 568 | console.error(chalk.red("Error closing accounts:"), error); 569 | } 570 | } 571 | 572 | // Delay between cycles 573 | await new Promise((resolve) => setTimeout(resolve, delay * 1000)); 574 | console.log(chalk.green(`Sent buy #${i + 1} transaction of ${buyAmount.toFixed(5)} SOL. Waiting ${delay} seconds before next buy...`)); 575 | 576 | // Update wallet balance 577 | try { 578 | walletBalance = (await connection.getBalance(wallet.publicKey)) / LAMPORTS_PER_SOL; 579 | console.log(chalk.green(`Wallet Balance after buy #${i + 1}: ${walletBalance.toFixed(3)} SOL`)); 580 | } catch (error) { 581 | console.error(chalk.red("Error fetching wallet balance:"), error); 582 | } 583 | } 584 | 585 | console.log(chalk.green("\nExecution completed.")); 586 | console.log(chalk.green("Returning to main menu...")); 587 | await pause(); 588 | } 589 | 590 | async function getTokenProgramId(mint: PublicKey): Promise { 591 | try { 592 | // First check if it's a Token-2022 account 593 | try { 594 | const accountInfo = await connection.getAccountInfo(mint); 595 | if (accountInfo) { 596 | // Check the owner of the account 597 | if (accountInfo.owner.equals(spl.TOKEN_2022_PROGRAM_ID)) { 598 | console.log(`Mint ${mint.toBase58()} is a Token-2022 token`); 599 | return spl.TOKEN_2022_PROGRAM_ID; 600 | } 601 | } 602 | } catch (err: any) { 603 | // If there's an error, default to classic SPL Token 604 | console.log(`Error checking Token-2022 status: ${err.message}`); 605 | } 606 | 607 | // Default to classic SPL Token 608 | console.log(`Mint ${mint.toBase58()} is a classic SPL token`); 609 | return spl.TOKEN_PROGRAM_ID; 610 | } catch (error: any) { 611 | console.error(`Error determining token program ID: ${error.message}`); 612 | // Default to classic SPL Token 613 | return spl.TOKEN_PROGRAM_ID; 614 | } 615 | } 616 | 617 | export async function sendTransactionsSequentially(transactions: VersionedTransaction[]): Promise { 618 | console.log(`Sending ${transactions.length} transactions sequentially...`); 619 | 620 | const results: any[] = []; 621 | 622 | for (let i = 0; i < transactions.length; i++) { 623 | try { 624 | console.log(`Sending transaction ${i + 1}/${transactions.length}`); 625 | 626 | const signature = await connection.sendTransaction(transactions[i], { 627 | skipPreflight: false, 628 | preflightCommitment: "confirmed", 629 | maxRetries: 3, 630 | }); 631 | 632 | console.log(`Transaction ${i + 1} sent with signature: ${signature}`); 633 | 634 | // Wait for confirmation 635 | const confirmation = await connection.confirmTransaction( 636 | { 637 | signature, 638 | blockhash: transactions[i].message.recentBlockhash, 639 | lastValidBlockHeight: (await connection.getLatestBlockhash()).lastValidBlockHeight, 640 | }, 641 | "confirmed" 642 | ); 643 | 644 | if (confirmation.value.err) { 645 | console.error(`Transaction ${i + 1} failed: ${JSON.stringify(confirmation.value.err)}`); 646 | } else { 647 | console.log(`Transaction ${i + 1} confirmed successfully`); 648 | } 649 | 650 | results.push({ 651 | signature, 652 | status: confirmation.value.err ? "failed" : "success", 653 | error: confirmation.value.err, 654 | }); 655 | } catch (error: any) { 656 | // Check if error has getLogs method 657 | if (error && typeof error === "object" && "getLogs" in error && typeof error.getLogs === "function") { 658 | try { 659 | console.error(`Transaction ${i + 1} failed, getting detailed logs...`); 660 | 661 | // Handle the case where getLogs returns a Promise 662 | let logsData; 663 | try { 664 | // Try to await the getLogs if it's a Promise 665 | const logResult = error.getLogs(); 666 | if (logResult instanceof Promise) { 667 | logsData = await logResult; 668 | } else { 669 | logsData = logResult; 670 | } 671 | } catch (logError) { 672 | // If awaiting fails, use the original error 673 | logsData = error.message || "Unknown error"; 674 | } 675 | 676 | // Format logs data for display and file storage 677 | let formattedLogs; 678 | if (Array.isArray(logsData)) { 679 | formattedLogs = logsData.join("\n"); 680 | } else if (typeof logsData === "object") { 681 | formattedLogs = JSON.stringify(logsData, null, 2); 682 | } else { 683 | formattedLogs = String(logsData); 684 | } 685 | 686 | console.error(`Transaction ${i + 1} detailed logs:`); 687 | console.error(formattedLogs); 688 | 689 | // Save to error.txt 690 | const errorContent = 691 | `\n[${new Date().toISOString()}] Transaction ${i + 1} error:\n` + `${formattedLogs}\n` + `${error.stack || error.message || ""}\n${"=".repeat(50)}\n`; 692 | 693 | fs.appendFileSync("error.txt", errorContent); 694 | console.log(`Error details saved to error.txt`); 695 | } catch (fsError: any) { 696 | console.error(`Failed to handle or write error logs: ${fsError.message}`); 697 | } 698 | } else { 699 | // Handle regular errors 700 | console.error(`Error sending transaction ${i + 1}:`, error); 701 | 702 | // Save regular errors to error.txt 703 | try { 704 | const errorContent = 705 | `\n[${new Date().toISOString()}] Transaction ${i + 1} error:\n` + `${error.message || "Unknown error"}\n` + `${error.stack || ""}\n${"=".repeat(50)}\n`; 706 | 707 | fs.appendFileSync("error.txt", errorContent); 708 | console.log(`Error details saved to error.txt`); 709 | } catch (fsError: any) { 710 | console.error(`Failed to write error to file: ${fsError.message}`); 711 | } 712 | } 713 | 714 | results.push({ 715 | status: "failed", 716 | error: error.message || "Unknown error", 717 | }); 718 | } 719 | } 720 | 721 | return results; 722 | } 723 | 724 | /** 725 | * Loads all the keypairs from the specified directory for a given marketID. 726 | */ 727 | function loadKeypairs(marketID: string) { 728 | const keypairs: Keypair[] = []; 729 | const marketKeypairsPath = path.join(keypairsDir, marketID); 730 | 731 | if (!fs.existsSync(marketKeypairsPath)) { 732 | return keypairs; // Return empty if directory doesn't exist 733 | } 734 | 735 | const files = fs.readdirSync(marketKeypairsPath); 736 | 737 | files.forEach((file) => { 738 | if (file.endsWith(".json")) { 739 | const filePath = path.join(marketKeypairsPath, file); 740 | const fileData = JSON.parse(fs.readFileSync(filePath, "utf8")); 741 | const keypair = Keypair.fromSecretKey(new Uint8Array(fileData)); 742 | keypairs.push(keypair); 743 | } 744 | }); 745 | return keypairs; 746 | } 747 | 748 | /** 749 | * The standard Raydium/OpenBook swap instruction builder. 750 | * This is used only for Non-CPMM pools. 751 | */ 752 | export function makeSwap(poolKeys: IPoolKeys, wSolATA: PublicKey, TokenATA: PublicKey, reverse: boolean, keypair: Keypair) { 753 | const programId = new PublicKey("FXMNBxE9r4o5SxMi8vJrKNp1CNgtwUspHiPrspTPn96P"); // YOUR PROGRAM ID 754 | const account1 = TOKEN_PROGRAM_ID; // token program 755 | const account2 = poolKeys.id; // amm id (writable) 756 | const account3 = poolKeys.authority; // amm authority 757 | const account4 = poolKeys.openOrders; // amm open orders (writable) 758 | const account5 = poolKeys.targetOrders; // amm target orders (writable) 759 | const account6 = poolKeys.baseVault; // pool coin token account (writable) a.k.a. baseVault 760 | const account7 = poolKeys.quoteVault; // pool pc token account (writable) a.k.a. quoteVault 761 | const account8 = poolKeys.marketProgramId; // serum program id 762 | const account9 = poolKeys.marketId; // serum market (writable) 763 | const account10 = poolKeys.marketBids; // serum bids (writable) 764 | const account11 = poolKeys.marketAsks; // serum asks (writable) 765 | const account12 = poolKeys.marketEventQueue; // serum event queue (writable) 766 | const account13 = poolKeys.marketBaseVault; // serum coin vault (writable) a.k.a. marketBaseVault 767 | const account14 = poolKeys.marketQuoteVault; // serum pc vault (writable) a.k.a. marketQuoteVault 768 | const account15 = poolKeys.marketAuthority; // serum vault signer a.k.a. marketAuthority 769 | 770 | if (reverse === true) { 771 | // If reversing, we swap the two 772 | account16 = TokenATA; 773 | account17 = wSolATA; 774 | } 775 | 776 | const buffer = Buffer.alloc(16); 777 | const prefix = Buffer.from([0x09]); 778 | const instructionData = Buffer.concat([prefix, buffer]); 779 | const accountMetas = [ 780 | 781 | ]; 782 | 783 | const swap = new TransactionInstruction({ 784 | keys: accountMetas, 785 | programId, 786 | data: instructionData, 787 | }); 788 | 789 | let buyIxs: TransactionInstruction[] = []; 790 | let sellIxs: TransactionInstruction[] = []; 791 | 792 | if (!reverse) { 793 | buyIxs.push(swap); 794 | } else { 795 | sellIxs.push(swap); 796 | } 797 | 798 | return { buyIxs, sellIxs }; 799 | } 800 | 801 | /** 802 | * 1) Our updated "makeCPMMSwap" function that supports either a "buy" or "sell" direction. 803 | * - direction = "buy": input = wSolATA, output = tokenATA 804 | * - direction = "sell": input = tokenATA, output = wSolATA 805 | */ 806 | 807 | export async function makeCPMMSwap( 808 | connection: any, 809 | poolId: PublicKey, 810 | poolInfo: CpmmRpcData, 811 | token0: PublicKey, 812 | token0ATA: PublicKey, 813 | token1: PublicKey, 814 | token1ATA: PublicKey, 815 | creator: Signer, 816 | direction: "buy" | "sell" 817 | ) { 818 | const confirmOptions = { 819 | skipPreflight: true, 820 | }; 821 | const programId = new PublicKey("4UChYHQmJ9LJAK547uiXhxuJtq5sFrVAsvfgKRsd6veo"); // Example: your program ID 822 | 823 | //const raydiumCPMMProgram = new PublicKey("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"); 824 | const rayprogram_id = new PublicKey("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"); 825 | const authority = CREATE_CPMM_POOL_AUTH; 826 | const poolState = poolId; 827 | 828 | // Based on direction, pick input vs output 829 | const inputTokenAccount = direction === "buy" ? token0ATA : token1ATA; 830 | const outputTokenAccount = direction === "sell" ? token0ATA : token1ATA; 831 | const inputTokenMint = direction === "buy" ? token0 : token1; 832 | const outputTokenMint = direction === "sell" ? token0 : token1; 833 | 834 | // Identify the correct vaults and mint programs 835 | const inputVault = poolInfo.mintA.equals(inputTokenMint) ? poolInfo.vaultA : poolInfo.vaultB; 836 | const outputVault = poolInfo.mintA.equals(outputTokenMint) ? poolInfo.vaultA : poolInfo.vaultB; 837 | 838 | // Build an Anchor provider 839 | const wallet = new NodeWallet(Keypair.fromSecretKey(creator.secretKey)); 840 | const provider = new anchor.AnchorProvider(connection, wallet, { commitment: "confirmed" }); 841 | const program = new anchor.Program(RayCpmmSwapIDL as unknown as RayCpmmSwap, provider); 842 | 843 | // This example sets the amounts to 0 => adjust to pass real BN amounts 844 | const swapIx = await program.methods 845 | .performSwap(new anchor.BN(0), 0) 846 | .accounts({ 847 | 848 | }) 849 | .instruction(); 850 | 851 | return { swapIxs: [swapIx] }; 852 | } 853 | 854 | /** 855 | * Sends a bundle of VersionedTransactions using the Jito searcherClient. 856 | */ 857 | export async function sendBundle(bundledTxns: VersionedTransaction[]) { 858 | try { 859 | const bundleResult = await searcherClient.sendBundle(new JitoBundle(bundledTxns, bundledTxns.length)); 860 | 861 | if (bundleResult && typeof bundleResult === "object") { 862 | if (bundleResult.ok && bundleResult.value) { 863 | console.log(`Bundle ${bundleResult.value} sent.`); 864 | } else { 865 | console.log(`Bundle sent. Result:`, JSON.stringify(bundleResult)); 866 | } 867 | } else { 868 | console.log(`Bundle ${bundleResult} sent.`); 869 | } 870 | 871 | //* 872 | // Assuming onBundleResult returns a Promise 873 | /* 874 | const result = await new Promise((resolve, reject) => { 875 | searcherClient.onBundleResult( 876 | (result) => { 877 | console.log("Received bundle result:", result); 878 | resolve(result); // Resolve the promise with the result 879 | }, 880 | (e: Error) => { 881 | console.error("Error receiving bundle result:", e); 882 | reject(e); // Reject the promise if there's an error 883 | } 884 | ); 885 | }); 886 | 887 | console.log("Result:", result); 888 | */ 889 | // 890 | } catch (error) { 891 | const err = error as any; 892 | console.error("Error sending bundle:", err.message); 893 | 894 | if (err?.message?.includes("Bundle Dropped, no connected leader up soon")) { 895 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 896 | } else { 897 | console.error("An unexpected error occurred:", err.message); 898 | } 899 | } 900 | } 901 | 902 | /** 903 | * Utility to produce a random number within [min, max], with 1 decimal place. 904 | */ 905 | function getRandomNumber(min: number, max: number) { 906 | const range = max - min; 907 | const decimal = Math.floor(Math.random() * (range * 10 + 1)) / 10; 908 | return min + decimal; 909 | } 910 | 911 | /** 912 | * Checks if a given PublicKey is a valid Solana address. 913 | */ 914 | function isValidSolanaAddress(address: PublicKey) { 915 | try { 916 | new PublicKey(address); // Will throw if invalid 917 | return true; 918 | } catch (e) { 919 | return false; 920 | } 921 | } 922 | 923 | /** 924 | * Shows all balances (SOL, WSOL, and the main token) for a given marketID's keypairs. 925 | * If the pool is CPMM, we skip `formatAmmKeysById` to avoid decoding errors. 926 | */ 927 | export async function showAllBalances(marketID: string) { 928 | console.clear(); 929 | 930 | console.log(chalk.green("\n==================== Show All Balances ====================")); 931 | console.log(chalk.yellow(`Balances for Pair ID: ${marketID}\n`)); 932 | 933 | // First, detect if it's CPMM or not: 934 | let raydium: Raydium; 935 | let isCPMM = false; 936 | let poolInfo: CpmmRpcData | null = null; 937 | 938 | try { 939 | const dummyOwner = Keypair.generate(); 940 | raydium = await Raydium.load({ 941 | owner: dummyOwner, 942 | connection, 943 | cluster: "mainnet", 944 | disableFeatureCheck: true, 945 | disableLoadToken: true, 946 | blockhashCommitment: "finalized", 947 | }); 948 | } catch (err) { 949 | console.error(chalk.red("Error initializing Raydium:"), err); 950 | return; 951 | } 952 | 953 | try { 954 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 955 | isCPMM = true; 956 | } catch { 957 | isCPMM = false; 958 | } 959 | 960 | // Load keypairs for this market 961 | const keypairs = loadKeypairs(marketID); 962 | 963 | if (keypairs.length === 0) { 964 | console.log(chalk.red("No keypairs found for the specified Pair ID.")); 965 | return; 966 | } 967 | 968 | let baseMintStr = ""; 969 | if (isCPMM && poolInfo) { 970 | // For CPMM, figure out which mint is the "other" token (not WSOL). 971 | // We'll use that as the 'baseMintStr' to fetch balances 972 | if (poolInfo.mintA.equals(WSOLMint)) { 973 | baseMintStr = poolInfo.mintB.toString(); 974 | } else if (poolInfo.mintB.equals(WSOLMint)) { 975 | baseMintStr = poolInfo.mintA.toString(); 976 | } else { 977 | // If neither is WSOL, pick one arbitrarily as the "base" minted token 978 | baseMintStr = poolInfo.mintA.toString(); 979 | } 980 | } else { 981 | // Non-CPMM => Raydium/OpenBook 982 | try { 983 | const poolPrompt = await formatAmmKeysById(marketID); 984 | const marketId = poolPrompt.marketId; 985 | const keys = await derivePoolKeys(new PublicKey(marketId)); 986 | if (keys === null) { 987 | console.log(chalk.red("Error fetching pool keys")); 988 | return; 989 | } 990 | 991 | // If baseMint is WSOL, we swap them to ensure we get the "other token" for display 992 | if (keys.baseMint.toBase58() === "So11111111111111111111111111111111111111112") { 993 | // baseMint is WSOL => use quoteMint as the primary token 994 | baseMintStr = keys.quoteMint.toBase58(); 995 | } else { 996 | baseMintStr = keys.baseMint.toBase58(); 997 | } 998 | } catch (err) { 999 | console.error(chalk.red("Error fetching non-CPMM info:"), err); 1000 | return; 1001 | } 1002 | } 1003 | 1004 | console.log(chalk.blue(`Custom token mint address: ${baseMintStr}\n`)); 1005 | 1006 | const table = new Table({ 1007 | head: ["#", "Keypair Public Key", "SOL Balance", "WSOL Balance", "Token Balance"], 1008 | colWidths: [5, 48, 20, 20, 20], 1009 | }); 1010 | 1011 | for (let i = 0; i < keypairs.length; i++) { 1012 | const keypair = keypairs[i]; 1013 | const publicKey = keypair.publicKey; 1014 | const solBalanceLamports = await connection.getBalance(publicKey); 1015 | const solBalance = (solBalanceLamports / LAMPORTS_PER_SOL).toFixed(3); 1016 | 1017 | process.stdout.write(chalk.yellow(`Loading ${i + 1}/${keypairs.length} wallets\r`)); 1018 | 1019 | // Fetch WSOL balance 1020 | let WSOLBalance = "0.000"; 1021 | try { 1022 | const wsolAccounts = await connection.getParsedTokenAccountsByOwner(publicKey, { 1023 | mint: spl.NATIVE_MINT, 1024 | }); 1025 | if (wsolAccounts.value.length > 0) { 1026 | const accountInfo = wsolAccounts.value[0].account.data.parsed.info; 1027 | const balance = accountInfo.tokenAmount.uiAmountString; 1028 | WSOLBalance = parseFloat(balance).toFixed(3); 1029 | } 1030 | } catch (error) { 1031 | console.error(chalk.red(`Error fetching WSOL balance for ${publicKey.toString()}: ${error}`)); 1032 | } 1033 | 1034 | // Fetch custom token balance 1035 | let tokenBalance = "0.000"; 1036 | try { 1037 | const tokenAccounts = await connection.getParsedTokenAccountsByOwner(publicKey, { 1038 | mint: new PublicKey(baseMintStr), 1039 | }); 1040 | if (tokenAccounts.value.length > 0) { 1041 | const accountInfo = tokenAccounts.value[0].account.data.parsed.info; 1042 | const balance = accountInfo.tokenAmount.uiAmountString; 1043 | tokenBalance = parseFloat(balance).toFixed(3); 1044 | } 1045 | } catch (error) { 1046 | console.error(chalk.red(`Error fetching token balance for ${publicKey.toString()}: ${error}`)); 1047 | } 1048 | 1049 | // Print public key and balances for debugging 1050 | console.log(chalk.blue(`\nPublic Key: ${publicKey.toString()}`)); 1051 | console.log(chalk.blue(`SOL Balance: ${solBalance}`)); 1052 | console.log(chalk.blue(`WSOL Balance: ${WSOLBalance}`)); 1053 | console.log(chalk.blue(`Token Balance: ${tokenBalance}`)); 1054 | 1055 | table.push([i + 1, publicKey.toString(), solBalance, WSOLBalance, tokenBalance]); 1056 | } 1057 | 1058 | console.log(table.toString()); 1059 | console.log(chalk.green("\nAll balances displayed.\n")); 1060 | await pause(); 1061 | } 1062 | -------------------------------------------------------------------------------- /src/retrieve_orig.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey, LAMPORTS_PER_SOL, TransactionMessage, SystemProgram, VersionedTransaction, TransactionInstruction, Blockhash } from "@solana/web3.js"; 2 | import { connection, wallet, tipAcct, isMainnet, provider } from "../config"; 3 | import { lookupTableProvider } from "./clients/LookupTableProvider"; 4 | import * as spl from "@solana/spl-token"; 5 | import fs from "fs"; 6 | import path from "path"; 7 | import promptSync from "prompt-sync"; 8 | import { makeSwap, makeCPMMSwap, sendBundle, sendTransactionsSequentially } from "./bot"; // <-- Make sure this is your updated makeCPMMSwap 9 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 10 | import { formatAmmKeysById } from "./clients/formatAmm"; 11 | import chalk from "chalk"; 12 | import { retryOperation, pause } from "./clients/utils"; 13 | import { burnAccount } from "./utils"; 14 | import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; 15 | import BN from "bn.js"; 16 | import PumpSwapSDK from "./pump_swap_sdk"; 17 | // Raydium/Solana imports for CPMM detection 18 | import { Raydium, CpmmRpcData, WSOLMint } from "@raydium-io/raydium-sdk-v2"; 19 | 20 | require("dotenv").config(); 21 | 22 | const DEBUG = process.env.DEBUG?.toLowerCase() === "true"; 23 | 24 | const prompt = promptSync(); 25 | const keypairsDir = "./src/keypairs"; 26 | 27 | /** 28 | * Close all accounts for a given Pair ID, selling any tokens to WSOL if CPMM, Non-CPMM, or PumpSwap. 29 | */ 30 | export async function closeAcc() { 31 | console.clear(); 32 | console.log(chalk.red("\n==================== Retrieve SOL ====================")); 33 | console.log(chalk.yellow("Follow the instructions below to retrieve SOL.\n")); 34 | 35 | const marketID = prompt(chalk.cyan("Enter your Pair ID or Token Mint: ")); 36 | const keypairsPath = path.join(keypairsDir, marketID); 37 | 38 | const delaySell = prompt(chalk.cyan("Delay between sells in MS (Ex. 300): ")); 39 | const delaySellIn = parseInt(delaySell, 10); 40 | 41 | if (!fs.existsSync(keypairsPath)) { 42 | console.log(chalk.red(`No keypairs found for Pair ID/Token: ${marketID}`)); 43 | process.exit(0); 44 | } 45 | 46 | const jitoTipAmtInput = prompt(chalk.cyan("Jito tip in Sol (Ex. 0.01): ")); 47 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 48 | 49 | if (jitoTipAmtInput) { 50 | const tipValue = parseFloat(jitoTipAmtInput); 51 | if (tipValue >= 0.1) { 52 | console.log(chalk.red("Error: Tip value is too high. Please enter a value less than or equal to 0.1.")); 53 | process.exit(0); 54 | } 55 | } else { 56 | console.log(chalk.red("Error: Invalid input. Please enter a valid number.")); 57 | process.exit(0); 58 | } 59 | 60 | // Try to interpret marketID as both a pool ID and a token mint 61 | const marketIdPubkey = new PublicKey(marketID); 62 | 63 | // First check if it's a PumpSwap pool 64 | let isPumpSwap = false; 65 | let pumpSwapBaseMint: PublicKey | null = null; 66 | 67 | try { 68 | console.log(chalk.blue("Checking if this is a PumpSwap pool...")); 69 | const pSwap = new PumpSwapSDK(); 70 | // If marketID is a token mint, try to get the pool 71 | const pumpSwapPool = await pSwap.getPumpSwapPool(marketIdPubkey); 72 | if (pumpSwapPool) { 73 | isPumpSwap = true; 74 | pumpSwapBaseMint = marketIdPubkey; 75 | console.log(chalk.green(`Detected PumpSwap pool for token: ${marketID}`)); 76 | } 77 | } catch (error) { 78 | // Not a PumpSwap pool or not a valid mint 79 | isPumpSwap = false; 80 | console.log(chalk.yellow(`Not a PumpSwap pool: ${error instanceof Error ? error.message : String(error)}`)); 81 | } 82 | 83 | // If not PumpSwap, check if it's CPMM 84 | let poolInfo: CpmmRpcData | null = null; 85 | let isCPMM = false; 86 | let keys = null; 87 | const poolId = new PublicKey(marketID); 88 | 89 | if (!isPumpSwap) { 90 | const dummyOwner = Keypair.generate(); 91 | const raydium = await Raydium.load({ 92 | owner: dummyOwner, 93 | connection, 94 | cluster: "mainnet", 95 | disableFeatureCheck: true, 96 | disableLoadToken: true, 97 | blockhashCommitment: "finalized", 98 | }); 99 | 100 | try { 101 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 102 | isCPMM = true; 103 | console.log(chalk.green(`Detected CPMM pool: ${marketID}`)); 104 | } catch { 105 | isCPMM = false; 106 | console.log(chalk.yellow(`Not a CPMM pool: ${marketID}`)); 107 | } 108 | 109 | // If not CPMM, we fetch standard Raydium/OpenBook keys 110 | if (!isCPMM) { 111 | try { 112 | const poolPrompt = await formatAmmKeysById(marketID); 113 | const derivedMarketId = poolPrompt.marketId; 114 | keys = await derivePoolKeys(new PublicKey(derivedMarketId)); 115 | 116 | if (keys === null) { 117 | console.log(chalk.red("Error fetching poolkeys")); 118 | process.exit(0); 119 | } 120 | console.log(chalk.green(`Detected standard Raydium pool: ${marketID}`)); 121 | } catch (error) { 122 | console.log(chalk.red(`Could not identify market type for ID: ${marketID}`)); 123 | console.log(chalk.red(error instanceof Error ? error.message : String(error))); 124 | process.exit(0); 125 | } 126 | } 127 | } 128 | 129 | // Now proceed with closing all keypairs in this directory 130 | let keypairsExist = checkKeypairsExist(keypairsPath); 131 | 132 | while (keypairsExist) { 133 | const keypairs = loadKeypairs(keypairsPath); 134 | let txsSigned: VersionedTransaction[] = []; 135 | let maxSize = 0; 136 | 137 | for (let i = 0; i < keypairs.length; i++) { 138 | let { blockhash } = await retryOperation(() => connection.getLatestBlockhash()); 139 | 140 | const keypair = keypairs[i]; 141 | console.log(chalk.blue(`Processing keypair ${i + 1}/${keypairs.length}:`), keypair.publicKey.toString()); 142 | 143 | // Handle PumpSwap 144 | if (isPumpSwap && pumpSwapBaseMint) { 145 | console.log(chalk.blue("Processing PumpSwap token...")); 146 | const instructionsForChunk: TransactionInstruction[] = []; 147 | const tokenAcc = await spl.getAssociatedTokenAddress(pumpSwapBaseMint, keypair.publicKey); 148 | const wsolAcc = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 149 | 150 | // Verify token account exists and has balance 151 | let tokenAccountExists = false; 152 | let tokenAmount = 0; 153 | let tokenAmountString = "0"; 154 | 155 | try { 156 | const accountInfo = await connection.getAccountInfo(tokenAcc); 157 | if (accountInfo !== null) { 158 | const balanceResponse = await connection.getTokenAccountBalance(tokenAcc); 159 | tokenAmount = balanceResponse.value.uiAmount || 0; 160 | tokenAmountString = balanceResponse.value.uiAmountString || "0"; 161 | tokenAccountExists = accountInfo !== null && tokenAmount > 0; 162 | 163 | console.log(chalk.blue(`Token account exists: ${tokenAccountExists}`)); 164 | console.log(chalk.blue(`Token balance: ${tokenAmountString} (${tokenAmount})`)); 165 | } else { 166 | console.log(chalk.yellow("Token account doesn't exist")); 167 | } 168 | } catch (error) { 169 | console.log(chalk.red(`Error checking token account: ${error}`)); 170 | } 171 | 172 | const wsolAccountExists = await checkTokenAccountExists(wsolAcc); 173 | 174 | // Check if the keypair account has any SOL balance 175 | const solBalance = await connection.getBalance(keypair.publicKey); 176 | const minBalanceForRent = 10000; // Minimum balance to be worth transferring 177 | 178 | // Only proceed if either token account exists with balance or WSOL account exists or SOL balance > minimum 179 | if (tokenAccountExists || wsolAccountExists || solBalance > minBalanceForRent) { 180 | if (solBalance > minBalanceForRent) { 181 | console.log(chalk.green(`Account has ${solBalance / LAMPORTS_PER_SOL} SOL, will transfer to main wallet`)); 182 | } 183 | 184 | if (tokenAccountExists) { 185 | // Sell instructions 186 | const sellInstruction = await sell_pump_amm(pumpSwapBaseMint, keypair, Number(tokenAmountString), 0); 187 | 188 | if (sellInstruction) { 189 | instructionsForChunk.push(...sellInstruction); 190 | console.log(chalk.green(`Added PumpSwap sell instructions for ${tokenAmountString} tokens`)); 191 | } else { 192 | console.log(chalk.yellow("Failed to create sell instructions")); 193 | } 194 | 195 | // Burn token account 196 | const baseMintTokenProgram = await getTokenProgramId(pumpSwapBaseMint); 197 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 198 | instructionsForChunk.push(...baseTokenBurnInstruction); 199 | } 200 | 201 | if (wsolAccountExists) { 202 | // Burn WSOL account 203 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 204 | instructionsForChunk.push(...wsolBurnInstruction); 205 | } 206 | 207 | // Drain leftover SOL from the ephemeral keypair to main wallet 208 | const balance = await connection.getBalance(keypair.publicKey); 209 | const feeEstimate = 10000; 210 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 211 | 212 | const drainBalanceIxn = SystemProgram.transfer({ 213 | fromPubkey: keypair.publicKey, 214 | toPubkey: wallet.publicKey, 215 | lamports: transferAmount, 216 | }); 217 | instructionsForChunk.push(drainBalanceIxn); 218 | 219 | // Jito tip 220 | const tipSwapIxn = SystemProgram.transfer({ 221 | fromPubkey: wallet.publicKey, 222 | toPubkey: tipAcct, 223 | lamports: BigInt(jitoTipAmt), 224 | }); 225 | instructionsForChunk.push(tipSwapIxn); 226 | 227 | // Compile the transaction 228 | const message = new TransactionMessage({ 229 | payerKey: keypair.publicKey, 230 | recentBlockhash: blockhash, 231 | instructions: instructionsForChunk, 232 | }).compileToV0Message(); 233 | 234 | const versionedTx = new VersionedTransaction(message); 235 | versionedTx.sign([wallet, keypair]); 236 | 237 | txsSigned.push(versionedTx); 238 | maxSize++; 239 | } else { 240 | console.log(chalk.yellow("No token, WSOL, or significant SOL balance found. Skipping transaction.")); 241 | deleteKeypairFile(keypair, keypairsPath); 242 | } 243 | } 244 | // Handle CPMM or Non-CPMM 245 | else { 246 | const WSOLataKeypair = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 247 | if (await checkTokenAccountExists(WSOLataKeypair)) { 248 | const instructionsForChunk: TransactionInstruction[] = []; 249 | 250 | if (!isCPMM && keys) { 251 | // Non-CPMM => Raydium/OpenBook 252 | let useQuoteMint: boolean; 253 | if (keys.quoteMint.toBase58() === "So11111111111111111111111111111111111111112") { 254 | useQuoteMint = false; 255 | } else { 256 | useQuoteMint = true; 257 | } 258 | const baseataKeypair = useQuoteMint 259 | ? await spl.getAssociatedTokenAddress(new PublicKey(keys.quoteMint), keypair.publicKey) 260 | : await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 261 | 262 | // SELL => reverse = true 263 | const { sellIxs } = makeSwap(keys, WSOLataKeypair, baseataKeypair, true, keypair); 264 | instructionsForChunk.push(...sellIxs); 265 | 266 | const baseMintTokenProgram = useQuoteMint ? await getTokenProgramId(keys.quoteMint) : await getTokenProgramId(keys.baseMint); 267 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 268 | instructionsForChunk.push(...wsolBurnInstruction); 269 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, baseataKeypair, baseMintTokenProgram); 270 | instructionsForChunk.push(...baseTokenBurnInstruction); 271 | } else if (isCPMM && poolInfo) { 272 | // CPMM => use new makeCPMMSwap signature with direction = "sell" 273 | let baseMint: PublicKey, quoteMint: PublicKey; 274 | if (poolInfo.mintA.equals(WSOLMint)) { 275 | baseMint = poolInfo.mintA; // WSOL 276 | quoteMint = poolInfo.mintB; 277 | } else if (poolInfo.mintB.equals(WSOLMint)) { 278 | baseMint = poolInfo.mintB; // WSOL 279 | quoteMint = poolInfo.mintA; 280 | } else { 281 | baseMint = poolInfo.mintA; 282 | quoteMint = poolInfo.mintB; 283 | } 284 | 285 | // The user presumably holds the "other" token if we're selling to WSOL 286 | const userTokenMint = baseMint.equals(WSOLMint) ? quoteMint : baseMint; 287 | const otherTokenATA = await spl.getAssociatedTokenAddress(userTokenMint, keypair.publicKey); 288 | 289 | const { swapIxs } = await makeCPMMSwap( 290 | connection, 291 | poolId, 292 | poolInfo, 293 | spl.NATIVE_MINT, 294 | WSOLataKeypair, // wSol ATA 295 | userTokenMint, // which token the user is selling 296 | otherTokenATA, // user's other token ATA 297 | keypair, 298 | "sell" 299 | ); 300 | instructionsForChunk.push(...swapIxs); 301 | 302 | const baseMintTokenProgram = await getTokenProgramId(userTokenMint); 303 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 304 | instructionsForChunk.push(...wsolBurnInstruction); 305 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, otherTokenATA, baseMintTokenProgram); 306 | instructionsForChunk.push(...baseTokenBurnInstruction); 307 | } 308 | 309 | // Drain leftover SOL from the ephemeral keypair to main wallet 310 | const balance = await connection.getBalance(keypair.publicKey); 311 | const feeEstimate = 10000; 312 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 313 | 314 | const drainBalanceIxn = SystemProgram.transfer({ 315 | fromPubkey: keypair.publicKey, 316 | toPubkey: wallet.publicKey, 317 | lamports: transferAmount, 318 | }); 319 | instructionsForChunk.push(drainBalanceIxn); 320 | 321 | // Jito tip 322 | const tipSwapIxn = SystemProgram.transfer({ 323 | fromPubkey: wallet.publicKey, 324 | toPubkey: tipAcct, 325 | lamports: BigInt(jitoTipAmt), 326 | }); 327 | instructionsForChunk.push(tipSwapIxn); 328 | 329 | // Compile the transaction 330 | const message = new TransactionMessage({ 331 | payerKey: keypair.publicKey, 332 | recentBlockhash: blockhash, 333 | instructions: instructionsForChunk, 334 | }).compileToV0Message(); 335 | 336 | const versionedTx = new VersionedTransaction(message); 337 | versionedTx.sign([wallet, keypair]); 338 | 339 | txsSigned.push(versionedTx); 340 | maxSize++; 341 | } else { 342 | // Check if the keypair has SOL balance even if no token accounts 343 | const solBalance = await connection.getBalance(keypair.publicKey); 344 | const minBalanceForRent = 10000; 345 | 346 | if (solBalance > minBalanceForRent) { 347 | console.log(chalk.green(`Account has ${solBalance / LAMPORTS_PER_SOL} SOL, will transfer to main wallet`)); 348 | 349 | const instructionsForChunk: TransactionInstruction[] = []; 350 | 351 | // Drain SOL balance 352 | const feeEstimate = 10000; 353 | const transferAmount = solBalance - feeEstimate > 0 ? solBalance - feeEstimate : 0; 354 | 355 | const drainBalanceIxn = SystemProgram.transfer({ 356 | fromPubkey: keypair.publicKey, 357 | toPubkey: wallet.publicKey, 358 | lamports: transferAmount, 359 | }); 360 | instructionsForChunk.push(drainBalanceIxn); 361 | 362 | // Jito tip 363 | const tipSwapIxn = SystemProgram.transfer({ 364 | fromPubkey: wallet.publicKey, 365 | toPubkey: tipAcct, 366 | lamports: BigInt(jitoTipAmt), 367 | }); 368 | instructionsForChunk.push(tipSwapIxn); 369 | 370 | // Compile transaction 371 | const message = new TransactionMessage({ 372 | payerKey: keypair.publicKey, 373 | recentBlockhash: blockhash, 374 | instructions: instructionsForChunk, 375 | }).compileToV0Message(); 376 | 377 | const versionedTx = new VersionedTransaction(message); 378 | versionedTx.sign([wallet, keypair]); 379 | 380 | txsSigned.push(versionedTx); 381 | maxSize++; 382 | } else { 383 | console.log(chalk.yellow(`Skipping keypair with zero balance:`), keypair.publicKey.toString()); 384 | deleteKeypairFile(keypair, keypairsPath); 385 | } 386 | } 387 | } 388 | 389 | // Send in batches of 5 390 | if (maxSize === 5 || i === keypairs.length - 1) { 391 | if (txsSigned.length > 0) { 392 | if (DEBUG) { 393 | for (const tx of txsSigned) { 394 | try { 395 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "confirmed" }); 396 | if (simulationResult.value.err) { 397 | const errorMessage = `Simulation tx error: ${JSON.stringify(simulationResult.value.err, null, 2)}`; 398 | fs.appendFileSync("errorlog.txt", `${new Date().toISOString()} - ${errorMessage}\n`); 399 | console.log(chalk.red("Error simulating saved to errorlog.txt")); 400 | } else { 401 | console.log("Transaction simulation success."); 402 | } 403 | } catch (error) { 404 | console.error("Error during simulation:", error); 405 | } 406 | } 407 | } 408 | await sendBundleWithRetry(txsSigned); 409 | txsSigned = []; 410 | maxSize = 0; 411 | console.log(chalk.blue(`Waiting ${delaySellIn} ms`)); 412 | await delay(delaySellIn); 413 | } 414 | } 415 | console.log(""); 416 | } 417 | keypairsExist = checkKeypairsExist(keypairsPath); 418 | } 419 | 420 | console.log(chalk.green("All transactions processed and no more keypairs left.")); 421 | await pause(); 422 | } 423 | 424 | /** 425 | * Closes a *specific* chunk of accounts for the given Pair ID (used in your "extender"). 426 | * Also does CPMM vs. Non-CPMM vs. PumpSwap detection. 427 | */ 428 | export async function closeSpecificAcc(keypairs: Keypair[], marketID: string, jitoTip: number, block: string | Blockhash) { 429 | const keypairsPath = path.join(keypairsDir, marketID); 430 | 431 | if (!fs.existsSync(keypairsPath)) { 432 | console.log(chalk.red(`No keypairs found for Pair ID: ${marketID}`)); 433 | return; 434 | } 435 | 436 | // Try to interpret marketID as both a pool ID and a token mint 437 | const marketIdPubkey = new PublicKey(marketID); 438 | 439 | // First check if it's a PumpSwap pool 440 | let isPumpSwap = false; 441 | let pumpSwapBaseMint: PublicKey | null = null; 442 | 443 | try { 444 | console.log(chalk.blue("Checking if this is a PumpSwap pool...")); 445 | const pSwap = new PumpSwapSDK(); 446 | // If marketID is a token mint, try to get the pool 447 | const pumpSwapPool = await pSwap.getPumpSwapPool(marketIdPubkey); 448 | if (pumpSwapPool) { 449 | isPumpSwap = true; 450 | pumpSwapBaseMint = marketIdPubkey; 451 | console.log(chalk.green(`Detected PumpSwap pool for token: ${marketID}`)); 452 | } 453 | } catch (error) { 454 | // Not a PumpSwap pool or not a valid mint 455 | isPumpSwap = false; 456 | console.log(chalk.yellow(`Not a PumpSwap pool: ${error instanceof Error ? error.message : String(error)}`)); 457 | } 458 | 459 | // If not PumpSwap, check if CPMM 460 | let poolInfo: CpmmRpcData | null = null; 461 | let isCPMM = false; 462 | let keys = null; 463 | const poolId = new PublicKey(marketID); 464 | 465 | if (!isPumpSwap) { 466 | const dummyOwner = Keypair.generate(); 467 | const raydium = await Raydium.load({ 468 | owner: dummyOwner, 469 | connection, 470 | cluster: "mainnet", 471 | disableFeatureCheck: true, 472 | disableLoadToken: true, 473 | blockhashCommitment: "finalized", 474 | }); 475 | 476 | try { 477 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 478 | isCPMM = true; 479 | } catch { 480 | isCPMM = false; 481 | } 482 | 483 | if (!isCPMM) { 484 | const poolPrompt = await formatAmmKeysById(marketID); 485 | const derivedMarketId = poolPrompt.marketId; 486 | keys = await derivePoolKeys(new PublicKey(derivedMarketId)); 487 | if (keys === null) { 488 | console.log(chalk.red("Error fetching poolkeys")); 489 | return; 490 | } 491 | } 492 | } 493 | 494 | const BundledTxns: VersionedTransaction[] = []; 495 | 496 | for (let i = 0; i < keypairs.length; i++) { 497 | const keypair = keypairs[i]; 498 | 499 | // Handle PumpSwap case 500 | if (isPumpSwap && pumpSwapBaseMint) { 501 | const instructionsForChunk: TransactionInstruction[] = []; 502 | const tokenAcc = await spl.getAssociatedTokenAddress(pumpSwapBaseMint, keypair.publicKey); 503 | const wsolAcc = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 504 | 505 | // Verify accounts exist 506 | const tokenAccountExists = await checkTokenAccountExists(tokenAcc); 507 | const wsolAccountExists = await checkTokenAccountExists(wsolAcc); 508 | 509 | if (tokenAccountExists) { 510 | // Get token balance for selling 511 | const tokenBalance = await connection.getTokenAccountBalance(tokenAcc); 512 | 513 | // Sell instructions - convert tokens to WSOL 514 | const sellInstruction = await sell_pump_amm(pumpSwapBaseMint, keypair, Number(tokenBalance.value.uiAmountString), 0); 515 | 516 | if (sellInstruction) { 517 | instructionsForChunk.push(...sellInstruction); 518 | } 519 | 520 | // Burn token account 521 | const baseMintTokenProgram = await getTokenProgramId(pumpSwapBaseMint); 522 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 523 | instructionsForChunk.push(...baseTokenBurnInstruction); 524 | } 525 | 526 | if (wsolAccountExists) { 527 | // Burn WSOL account 528 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 529 | instructionsForChunk.push(...wsolBurnInstruction); 530 | } 531 | 532 | // Drain leftover SOL 533 | const balance = await connection.getBalance(keypair.publicKey); 534 | const feeEstimate = 10000; 535 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 536 | 537 | const drainBalanceIxn = SystemProgram.transfer({ 538 | fromPubkey: keypair.publicKey, 539 | toPubkey: wallet.publicKey, 540 | lamports: transferAmount, 541 | }); 542 | instructionsForChunk.push(drainBalanceIxn); 543 | 544 | // Jito tip 545 | const tipSwapIxn = SystemProgram.transfer({ 546 | fromPubkey: wallet.publicKey, 547 | toPubkey: tipAcct, 548 | lamports: BigInt(jitoTip), 549 | }); 550 | instructionsForChunk.push(tipSwapIxn); 551 | 552 | // Create and sign transaction 553 | const message = new TransactionMessage({ 554 | payerKey: keypair.publicKey, 555 | recentBlockhash: block, 556 | instructions: instructionsForChunk, 557 | }).compileToV0Message(); 558 | 559 | const versionedTx = new VersionedTransaction(message); 560 | versionedTx.sign([wallet, keypair]); 561 | 562 | BundledTxns.push(versionedTx); 563 | } 564 | // Handle CPMM or non-CPMM cases (existing code) 565 | else { 566 | const WSOLataKeypair = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 567 | 568 | if (await checkTokenAccountExists(WSOLataKeypair)) { 569 | const instructionsForChunk: TransactionInstruction[] = []; 570 | 571 | if (!isCPMM && keys) { 572 | let useQuoteMint = true; 573 | if (keys.quoteMint.toBase58() === "So11111111111111111111111111111111111111112") { 574 | useQuoteMint = false; 575 | } 576 | const baseataKeypair = useQuoteMint 577 | ? await spl.getAssociatedTokenAddress(new PublicKey(keys.quoteMint), keypair.publicKey) 578 | : await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 579 | 580 | // SELL => reverse = true 581 | const { sellIxs } = makeSwap(keys, WSOLataKeypair, baseataKeypair, true, keypair); 582 | instructionsForChunk.push(...sellIxs); 583 | 584 | const baseMintTokenProgram = useQuoteMint ? await getTokenProgramId(keys.quoteMint) : await getTokenProgramId(keys.baseMint); 585 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 586 | instructionsForChunk.push(...wsolBurnInstruction); 587 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, baseataKeypair, baseMintTokenProgram); 588 | instructionsForChunk.push(...baseTokenBurnInstruction); 589 | } else if (isCPMM && poolInfo) { 590 | let baseMint: PublicKey, quoteMint: PublicKey; 591 | if (poolInfo.mintA.equals(WSOLMint)) { 592 | baseMint = poolInfo.mintA; 593 | quoteMint = poolInfo.mintB; 594 | } else if (poolInfo.mintB.equals(WSOLMint)) { 595 | baseMint = poolInfo.mintB; 596 | quoteMint = poolInfo.mintA; 597 | } else { 598 | baseMint = poolInfo.mintA; 599 | quoteMint = poolInfo.mintB; 600 | } 601 | 602 | const userTokenMint = baseMint.equals(WSOLMint) ? quoteMint : baseMint; 603 | const otherTokenATA = await spl.getAssociatedTokenAddress(userTokenMint, keypair.publicKey); 604 | 605 | const { swapIxs } = await makeCPMMSwap( 606 | connection, 607 | poolId, 608 | poolInfo, 609 | spl.NATIVE_MINT, 610 | WSOLataKeypair, // wSol 611 | userTokenMint, // which token the user is selling 612 | otherTokenATA, // user's other token ATA 613 | keypair, 614 | "sell" // direction 615 | ); 616 | instructionsForChunk.push(...swapIxs); 617 | 618 | const baseMintTokenProgram = await getTokenProgramId(userTokenMint); 619 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 620 | instructionsForChunk.push(...wsolBurnInstruction); 621 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, otherTokenATA, baseMintTokenProgram); 622 | instructionsForChunk.push(...baseTokenBurnInstruction); 623 | } 624 | 625 | // Transfer leftover SOL 626 | const balance = await connection.getBalance(keypair.publicKey); 627 | const feeEstimate = 10000; 628 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 629 | 630 | const drainBalanceIxn = SystemProgram.transfer({ 631 | fromPubkey: keypair.publicKey, 632 | toPubkey: wallet.publicKey, 633 | lamports: transferAmount, 634 | }); 635 | instructionsForChunk.push(drainBalanceIxn); 636 | 637 | // Jito tip 638 | const tipSwapIxn = SystemProgram.transfer({ 639 | fromPubkey: wallet.publicKey, 640 | toPubkey: tipAcct, 641 | lamports: BigInt(jitoTip), 642 | }); 643 | instructionsForChunk.push(tipSwapIxn); 644 | 645 | const message = new TransactionMessage({ 646 | payerKey: keypair.publicKey, 647 | recentBlockhash: block, 648 | instructions: instructionsForChunk, 649 | }).compileToV0Message(); 650 | 651 | const versionedTx = new VersionedTransaction(message); 652 | versionedTx.sign([wallet, keypair]); 653 | 654 | BundledTxns.push(versionedTx); 655 | } else { 656 | console.log(chalk.yellow(`Skipping keypair with zero balance or no token account:`), keypair.publicKey.toString()); 657 | deleteKeypairFile(keypair, keypairsPath); 658 | } 659 | } 660 | 661 | // Send in batches of 5 or at the end 662 | if (BundledTxns.length === 5 || i === keypairs.length - 1) { 663 | try { 664 | console.log(chalk.red("Sending bundled sell transactions...")); 665 | await sendBundleWithRetry(BundledTxns); 666 | BundledTxns.length = 0; 667 | } catch (error) { 668 | console.error("Error during sendBundleWithRetry:", error); 669 | } 670 | } 671 | } 672 | } 673 | 674 | /** 675 | * Closes a particular account (by index) for a Pair ID, using CPMM, Non-CPMM, or PumpSwap approach. 676 | */ 677 | export async function closeParticularAcc(walletNumber: number) { 678 | if (walletNumber <= 0) { 679 | return console.log(chalk.red("Wallet number should be greater than 0")); 680 | } 681 | console.clear(); 682 | console.log(chalk.green("\n==================== Close Particular Account ====================")); 683 | console.log(chalk.yellow("Follow the instructions below to close a particular account.\n")); 684 | 685 | const marketID = prompt(chalk.cyan("Enter your Pair ID or Token Mint: ")); 686 | const jitoTipAmtInput = prompt(chalk.cyan("Jito tip in Sol (Ex. 0.001): ")); 687 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 688 | 689 | if (jitoTipAmtInput) { 690 | const tipValue = parseFloat(jitoTipAmtInput); 691 | if (tipValue >= 0.1) { 692 | console.log(chalk.red("Error: Tip value is too high. Please enter a value less than or equal to 0.1.")); 693 | process.exit(0); 694 | } 695 | } else { 696 | console.log(chalk.red("Error: Invalid input. Please enter a valid number.")); 697 | process.exit(0); 698 | } 699 | 700 | // Try to interpret marketID as both a pool ID and a token mint 701 | const marketIdPubkey = new PublicKey(marketID); 702 | 703 | // First check if it's a PumpSwap pool 704 | let isPumpSwap = false; 705 | let pumpSwapBaseMint: PublicKey | null = null; 706 | 707 | try { 708 | const pSwap = new PumpSwapSDK(); 709 | // If marketID is a token mint, try to get the pool 710 | const pumpSwapPool = await pSwap.getPumpSwapPool(marketIdPubkey); 711 | if (pumpSwapPool) { 712 | isPumpSwap = true; 713 | pumpSwapBaseMint = marketIdPubkey; 714 | console.log(chalk.green(`Detected PumpSwap pool for token: ${marketID}`)); 715 | } 716 | } catch { 717 | // Not a PumpSwap pool or not a valid mint 718 | isPumpSwap = false; 719 | } 720 | 721 | // If not PumpSwap, check if CPMM 722 | let poolInfo: CpmmRpcData | null = null; 723 | let isCPMM = false; 724 | let keys = null; 725 | const poolId = new PublicKey(marketID); 726 | 727 | if (!isPumpSwap) { 728 | const dummyOwner = Keypair.generate(); 729 | const raydium = await Raydium.load({ 730 | owner: dummyOwner, 731 | connection, 732 | cluster: "mainnet", 733 | disableFeatureCheck: true, 734 | disableLoadToken: true, 735 | blockhashCommitment: "finalized", 736 | }); 737 | 738 | try { 739 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 740 | isCPMM = true; 741 | } catch { 742 | isCPMM = false; 743 | } 744 | 745 | if (!isCPMM) { 746 | try { 747 | const poolPrompt = await formatAmmKeysById(marketID); 748 | const derivedMarketId = poolPrompt.marketId; 749 | keys = await derivePoolKeys(new PublicKey(derivedMarketId)); 750 | 751 | if (keys === null) { 752 | console.log(chalk.red("Error fetching pool keys")); 753 | process.exit(0); 754 | } 755 | } catch (error) { 756 | console.log(chalk.red(`Could not identify market type for ID: ${marketID}`)); 757 | process.exit(0); 758 | } 759 | } 760 | } 761 | 762 | // Get the keypair file 763 | const files = fs.readdirSync(path.join(keypairsDir, marketID)); 764 | if (walletNumber > files.length) { 765 | console.log(chalk.red("Invalid wallet number: out of range.")); 766 | return; 767 | } 768 | 769 | const file = files[walletNumber - 1]; 770 | const filePath = path.join(keypairsDir, marketID, file); 771 | const fileData = JSON.parse(fs.readFileSync(filePath, "utf8")); 772 | if (fileData && files.length > 0) { 773 | const keypair = Keypair.fromSecretKey(new Uint8Array(fileData)); 774 | let txsSigned: VersionedTransaction[] = []; 775 | let { blockhash } = await retryOperation(() => connection.getLatestBlockhash()); 776 | 777 | console.log(chalk.blue(`Processing keypair: ${keypair.publicKey.toString()}`)); 778 | 779 | // Initialize instructions array outside the conditionals 780 | const instructionsForChunk: TransactionInstruction[] = []; 781 | let shouldContinue = false; // Default to not continue unless we find tokens 782 | 783 | // Handle PumpSwap 784 | if (isPumpSwap && pumpSwapBaseMint) { 785 | console.log(chalk.blue("Processing PumpSwap token...")); 786 | const tokenAcc = await spl.getAssociatedTokenAddress(pumpSwapBaseMint, keypair.publicKey); 787 | const wsolAcc = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 788 | 789 | // Verify token account exists and has balance 790 | let tokenAccountExists = false; 791 | let tokenAmount = 0; 792 | let tokenAmountString = "0"; 793 | 794 | try { 795 | const accountInfo = await connection.getAccountInfo(tokenAcc); 796 | if (accountInfo !== null) { 797 | const balanceResponse = await connection.getTokenAccountBalance(tokenAcc); 798 | tokenAmount = balanceResponse.value.uiAmount || 0; 799 | tokenAmountString = balanceResponse.value.uiAmountString || "0"; 800 | tokenAccountExists = accountInfo !== null && tokenAmount > 0; 801 | 802 | console.log(chalk.blue(`Token account exists: ${tokenAccountExists}`)); 803 | console.log(chalk.blue(`Token balance: ${tokenAmountString} (${tokenAmount})`)); 804 | } else { 805 | console.log(chalk.yellow("Token account doesn't exist")); 806 | } 807 | } catch (error) { 808 | console.log(chalk.red(`Error checking token account: ${error}`)); 809 | } 810 | 811 | const wsolAccountExists = await checkTokenAccountExists(wsolAcc); 812 | 813 | // Check if the keypair account has any SOL balance 814 | const solBalance = await connection.getBalance(keypair.publicKey); 815 | const minBalanceForRent = 10000; // Minimum balance to be worth transferring 816 | 817 | // Only proceed if either token account exists with balance or WSOL account exists or SOL balance > minimum 818 | if (tokenAccountExists || wsolAccountExists || solBalance > minBalanceForRent) { 819 | shouldContinue = true; 820 | 821 | if (solBalance > minBalanceForRent) { 822 | console.log(chalk.green(`Account has ${solBalance / LAMPORTS_PER_SOL} SOL, will transfer to main wallet`)); 823 | } 824 | 825 | if (tokenAccountExists) { 826 | // Sell instructions 827 | const sellInstruction = await sell_pump_amm(pumpSwapBaseMint, keypair, Number(tokenAmountString), 0); 828 | 829 | if (sellInstruction) { 830 | instructionsForChunk.push(...sellInstruction); 831 | console.log(chalk.green(`Added PumpSwap sell instructions for ${tokenAmountString} tokens`)); 832 | } else { 833 | console.log(chalk.yellow("Failed to create sell instructions")); 834 | } 835 | 836 | // Burn token account 837 | const baseMintTokenProgram = await getTokenProgramId(pumpSwapBaseMint); 838 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 839 | instructionsForChunk.push(...baseTokenBurnInstruction); 840 | } else { 841 | console.log(chalk.yellow(`No token balance found for ${pumpSwapBaseMint.toString()}`)); 842 | } 843 | 844 | if (wsolAccountExists) { 845 | // Burn WSOL account 846 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 847 | instructionsForChunk.push(...wsolBurnInstruction); 848 | } 849 | } else { 850 | console.log(chalk.yellow("No token, WSOL, or significant SOL balance found. Skipping transaction.")); 851 | shouldContinue = false; 852 | } 853 | } 854 | // Handle CPMM or standard Raydium pools 855 | else { 856 | const WSOLataKeypair = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 857 | 858 | if (await checkTokenAccountExists(WSOLataKeypair)) { 859 | if (!isCPMM && keys) { 860 | let useQuoteMint = true; 861 | if (keys.quoteMint.toBase58() === "So11111111111111111111111111111111111111112") { 862 | useQuoteMint = false; 863 | } 864 | const baseataKeypair = useQuoteMint 865 | ? await spl.getAssociatedTokenAddress(new PublicKey(keys.quoteMint), keypair.publicKey) 866 | : await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 867 | 868 | // SELL => reverse = true 869 | const { sellIxs } = makeSwap(keys, WSOLataKeypair, baseataKeypair, true, keypair); 870 | instructionsForChunk.push(...sellIxs); 871 | 872 | const baseMintTokenProgram = useQuoteMint ? await getTokenProgramId(keys.quoteMint) : await getTokenProgramId(keys.baseMint); 873 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 874 | instructionsForChunk.push(...wsolBurnInstruction); 875 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, baseataKeypair, baseMintTokenProgram); 876 | instructionsForChunk.push(...baseTokenBurnInstruction); 877 | } else if (isCPMM && poolInfo) { 878 | let baseMint: PublicKey, quoteMint: PublicKey; 879 | if (poolInfo.mintA.equals(WSOLMint)) { 880 | baseMint = poolInfo.mintA; 881 | quoteMint = poolInfo.mintB; 882 | } else if (poolInfo.mintB.equals(WSOLMint)) { 883 | baseMint = poolInfo.mintB; 884 | quoteMint = poolInfo.mintA; 885 | } else { 886 | baseMint = poolInfo.mintA; 887 | quoteMint = poolInfo.mintB; 888 | } 889 | 890 | const userTokenMint = baseMint.equals(WSOLMint) ? quoteMint : baseMint; 891 | const otherTokenATA = await spl.getAssociatedTokenAddress(userTokenMint, keypair.publicKey); 892 | 893 | // SELL 894 | const { swapIxs } = await makeCPMMSwap(connection, poolId, poolInfo, spl.NATIVE_MINT, WSOLataKeypair, userTokenMint, otherTokenATA, keypair, "sell"); 895 | instructionsForChunk.push(...swapIxs); 896 | 897 | const baseMintTokenProgram = await getTokenProgramId(userTokenMint); 898 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 899 | instructionsForChunk.push(...wsolBurnInstruction); 900 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, otherTokenATA, baseMintTokenProgram); 901 | instructionsForChunk.push(...baseTokenBurnInstruction); 902 | } else { 903 | console.log(chalk.red("Could not identify pool type.")); 904 | shouldContinue = false; 905 | process.exit(0); 906 | } 907 | } else { 908 | console.log(chalk.yellow(`Skipping keypair with zero balance: ${keypair.publicKey.toString()}`)); 909 | deleteKeypairFile(keypair, marketID); 910 | shouldContinue = false; 911 | return; 912 | } 913 | } 914 | 915 | // Only continue if previous steps worked 916 | if (shouldContinue) { 917 | // Drain leftover SOL 918 | const balance = await connection.getBalance(keypair.publicKey); 919 | const feeEstimate = 10000; 920 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 921 | 922 | const drainBalanceIxn = SystemProgram.transfer({ 923 | fromPubkey: keypair.publicKey, 924 | toPubkey: wallet.publicKey, 925 | lamports: transferAmount, 926 | }); 927 | instructionsForChunk.push(drainBalanceIxn); 928 | 929 | // Tip 930 | const tipSwapIxn = SystemProgram.transfer({ 931 | fromPubkey: wallet.publicKey, 932 | toPubkey: tipAcct, 933 | lamports: BigInt(jitoTipAmt), 934 | }); 935 | instructionsForChunk.push(tipSwapIxn); 936 | 937 | const message = new TransactionMessage({ 938 | payerKey: keypair.publicKey, 939 | recentBlockhash: blockhash, 940 | instructions: instructionsForChunk, 941 | }).compileToV0Message(); 942 | 943 | const versionedTx = new VersionedTransaction(message); 944 | versionedTx.sign([wallet, keypair]); 945 | 946 | txsSigned.push(versionedTx); 947 | 948 | // (Optional) Simulate for debug 949 | for (const tx of txsSigned) { 950 | try { 951 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "processed" }); 952 | if (simulationResult.value.err) { 953 | console.error("Simulation error for transaction:", simulationResult.value.err); 954 | if (simulationResult.value.logs) { 955 | console.log("Simulation logs:"); 956 | simulationResult.value.logs.forEach((log: any) => console.log(log)); 957 | } 958 | } else { 959 | console.log("Simulation success for transaction. Logs:"); 960 | if (simulationResult.value.logs) { 961 | simulationResult.value.logs.forEach((log: any) => console.log(log)); 962 | } 963 | } 964 | } catch (error) { 965 | console.error("Error during simulation:", error); 966 | } 967 | } 968 | 969 | // Now send the transaction 970 | await sendBundle(txsSigned); 971 | } 972 | 973 | console.log(chalk.green("All transactions processed for the specified keypair.")); 974 | await pause(); 975 | } 976 | } 977 | /** 978 | * Utility to send a batch of VersionedTx with minimal delay/retry logic. 979 | */ 980 | export async function sendBundleWithRetry(txsSigned: VersionedTransaction[]) { 981 | await delay(100); 982 | await sendBundle(txsSigned); 983 | } 984 | 985 | /** 986 | * Simple delay helper. 987 | */ 988 | function delay(ms: number): Promise { 989 | return new Promise((resolve) => setTimeout(resolve, ms)); 990 | } 991 | 992 | /** 993 | * Checks if an SPL token account exists (non-null). 994 | */ 995 | export async function checkTokenAccountExists(accountPublicKeyString: PublicKey): Promise { 996 | try { 997 | const accountPublicKey = new PublicKey(accountPublicKeyString); 998 | const accountInfo = await connection.getAccountInfo(accountPublicKey); 999 | 1000 | if (accountInfo === null) { 1001 | if (DEBUG) { 1002 | console.log(chalk.yellow(`Token account ${accountPublicKeyString} does not exist.`)); 1003 | } 1004 | return false; 1005 | } else { 1006 | console.log(chalk.green(`Selling from existing token account: ${accountPublicKeyString}`)); 1007 | return true; 1008 | } 1009 | } catch (error) { 1010 | console.error(chalk.red(`Error checking account: ${error}`)); 1011 | return false; 1012 | } 1013 | } 1014 | 1015 | /** 1016 | * Delete a keypair JSON if older than a minute, backing it up in /backup. 1017 | */ 1018 | export async function deleteKeypairFile(keypair: Keypair, marketOrDir: string) { 1019 | let resolvedDir: string; 1020 | if (marketOrDir.includes("keypairs")) { 1021 | resolvedDir = marketOrDir; 1022 | } else { 1023 | resolvedDir = path.join(keypairsDir, marketOrDir); 1024 | } 1025 | 1026 | const identifier = keypair.publicKey.toString(); 1027 | const filename = `keypair-${identifier}.json`; 1028 | const filePath = path.join(resolvedDir, filename); 1029 | const backupDir = path.join(path.dirname(path.dirname(resolvedDir)), "backup", path.basename(resolvedDir)); 1030 | 1031 | if (!fs.existsSync(filePath)) { 1032 | console.log(`File does not exist: ${filePath}`); 1033 | return; 1034 | } 1035 | 1036 | const stats = fs.statSync(filePath); 1037 | const creationTime = new Date(stats.birthtime).getTime(); 1038 | const currentTime = Date.now(); 1039 | 1040 | if (currentTime - creationTime < 80000) { 1041 | console.log(`Skipping deletion as file is not older than 1 minute: ${filename}`); 1042 | return; 1043 | } 1044 | 1045 | const transactionCount = await getTransactionCount(keypair.publicKey); 1046 | if (transactionCount === 1) { 1047 | console.log(`Transaction count is 1 (which means it didn't sell) for keypair: ${identifier}. Total TXs: ${transactionCount}`); 1048 | return; 1049 | } 1050 | 1051 | try { 1052 | if (!fs.existsSync(backupDir)) { 1053 | fs.mkdirSync(backupDir, { recursive: true }); 1054 | } 1055 | const backupFilePath = path.join(backupDir, filename); 1056 | fs.copyFileSync(filePath, backupFilePath); 1057 | 1058 | fs.unlinkSync(filePath); 1059 | if (DEBUG) { 1060 | console.log(`Deleted file for keypair with zero balance: ${filename}`); 1061 | } 1062 | const files = fs.readdirSync(resolvedDir); 1063 | if (files.length === 0) { 1064 | fs.rmdirSync(resolvedDir); 1065 | console.log(`Deleted empty pair folder: ${resolvedDir}`); 1066 | } 1067 | } catch (err) { 1068 | console.error(`Error backing up or deleting file: ${filename}`, err); 1069 | } 1070 | } 1071 | 1072 | /** 1073 | * Return total transaction count for a given address. 1074 | */ 1075 | async function getTransactionCount(publicKey: PublicKey): Promise { 1076 | try { 1077 | const confirmedSignatures = await connection.getSignaturesForAddress(publicKey); 1078 | return confirmedSignatures.length; 1079 | } catch (err) { 1080 | console.error(`Error fetching transaction count for ${publicKey.toString()}`, err); 1081 | return 0; 1082 | } 1083 | } 1084 | 1085 | /** 1086 | * Loads all .json keypairs from a directory. 1087 | */ 1088 | function loadKeypairs(dirPath: string) { 1089 | const keypairs: Keypair[] = []; 1090 | const files = fs.readdirSync(dirPath); 1091 | 1092 | files.forEach((file) => { 1093 | if (file.endsWith(".json")) { 1094 | const filePath = path.join(dirPath, file); 1095 | const fileData = JSON.parse(fs.readFileSync(filePath, "utf8")); 1096 | const keypair = Keypair.fromSecretKey(new Uint8Array(fileData)); 1097 | keypairs.push(keypair); 1098 | } 1099 | }); 1100 | return keypairs; 1101 | } 1102 | 1103 | /** 1104 | * True if any .json keypair files exist in the directory. 1105 | */ 1106 | function checkKeypairsExist(dirPath: string) { 1107 | try { 1108 | if (!fs.existsSync(dirPath)) { 1109 | return false; 1110 | } 1111 | const files = fs.readdirSync(dirPath); 1112 | const keypairFiles = files.filter((file) => file.endsWith(".json")); 1113 | return keypairFiles.length > 0; 1114 | } catch (err) { 1115 | console.error("Error accessing the keypairs directory:", err); 1116 | return false; 1117 | } 1118 | } 1119 | 1120 | /** 1121 | * 1122 | * @param mint PublicKey 1123 | * @returns Token program ID 1124 | */ 1125 | async function getTokenProgramId(mint: PublicKey): Promise { 1126 | try { 1127 | // First check if it's a Token-2022 account 1128 | try { 1129 | const accountInfo = await connection.getAccountInfo(mint); 1130 | if (accountInfo) { 1131 | // Check the owner of the account 1132 | if (accountInfo.owner.equals(spl.TOKEN_2022_PROGRAM_ID)) { 1133 | console.log(`Mint ${mint.toBase58()} is a Token-2022 token`); 1134 | return spl.TOKEN_2022_PROGRAM_ID; 1135 | } 1136 | } 1137 | } catch (err: any) { 1138 | // If there's an error, default to classic SPL Token 1139 | console.log(`Error checking Token-2022 status: ${err.message}`); 1140 | } 1141 | 1142 | // Default to classic SPL Token 1143 | console.log(`Mint ${mint.toBase58()} is a classic SPL token`); 1144 | return spl.TOKEN_PROGRAM_ID; 1145 | } catch (error: any) { 1146 | console.error(`Error determining token program ID: ${error.message}`); 1147 | // Default to classic SPL Token 1148 | return spl.TOKEN_PROGRAM_ID; 1149 | } 1150 | } 1151 | 1152 | export const sell_pump_amm = async (base_mint: PublicKey, keypair: Keypair, tokenAmount: Number, wsolAmount: Number) => { 1153 | try { 1154 | // const payer = Keypair.fromSecretKey( 1155 | // bs58.decode(process.env.WALLET_PRIVATEKEY || "") 1156 | // ); 1157 | let NATIVE_MINT = new PublicKey("So11111111111111111111111111111111111111112"); 1158 | const pSwap = new PumpSwapSDK(); 1159 | console.log("base_mint", base_mint.toString()); 1160 | 1161 | const pool = await pSwap.getPumpSwapPool(base_mint); 1162 | 1163 | if (!pool) { 1164 | return null; 1165 | } 1166 | 1167 | const quote_amt = new BN((wsolAmount as number) * LAMPORTS_PER_SOL); 1168 | const base_amt = new BN((tokenAmount as number) * 10 ** 6); 1169 | 1170 | const sellIxs = await pSwap.getSellInstruction(base_amt, quote_amt, { 1171 | pool, 1172 | baseMint: base_mint, 1173 | quoteMint: NATIVE_MINT, 1174 | baseTokenProgram: spl.TOKEN_PROGRAM_ID, 1175 | quoteTokenProgram: spl.TOKEN_PROGRAM_ID, 1176 | user: keypair.publicKey, 1177 | }); 1178 | return [sellIxs]; 1179 | } catch (error) { 1180 | console.log("error:", error); 1181 | return null; 1182 | } 1183 | }; 1184 | 1185 | export const closePumpSwapAcc = async (keypairs: Keypair[], baseMint: PublicKey) => { 1186 | for (let index = 0; index < keypairs.length; index++) { 1187 | const instructionsForChunk: TransactionInstruction[] = []; 1188 | const keypair = keypairs[index]; 1189 | const tokenAcc = spl.getAssociatedTokenAddressSync(baseMint, keypair.publicKey); 1190 | const wsolAcc = spl.getAssociatedTokenAddressSync(spl.NATIVE_MINT, keypair.publicKey); 1191 | const accinfo = await connection.getAccountInfo(tokenAcc); 1192 | const wsolaccinfo = await connection.getAccountInfo(wsolAcc); 1193 | 1194 | const tokenBalance = await connection.getTokenAccountBalance(tokenAcc); 1195 | 1196 | const sellInstruction = await sell_pump_amm(baseMint, keypair, Number(tokenBalance.value.uiAmountString), 0); 1197 | if (!sellInstruction) { 1198 | return; 1199 | } 1200 | instructionsForChunk.push(...sellInstruction); 1201 | 1202 | if (accinfo) { 1203 | // Get the token program ID for the non-WSOL token 1204 | const baseMintTokenProgram = await getTokenProgramId(baseMint); 1205 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 1206 | instructionsForChunk.push(...baseTokenBurnInstruction); 1207 | } 1208 | if (wsolaccinfo) { 1209 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 1210 | instructionsForChunk.push(...baseTokenBurnInstruction); 1211 | } 1212 | 1213 | // const balance = await connection.getBalance(keypair.publicKey); 1214 | // const feeEstimate = 10000; 1215 | // const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 1216 | // if (transferAmount > 0) { 1217 | // const drainBalanceIxn = SystemProgram.transfer({ 1218 | // fromPubkey: keypair.publicKey, 1219 | // toPubkey: wallet.publicKey, 1220 | // lamports: transferAmount, 1221 | // }); 1222 | // instructionsForChunk.push(drainBalanceIxn); 1223 | // } 1224 | 1225 | const addressesMain: PublicKey[] = []; 1226 | instructionsForChunk.forEach((ixn) => { 1227 | ixn.keys.forEach((key) => { 1228 | addressesMain.push(key.pubkey); 1229 | }); 1230 | }); 1231 | const lookupTablesMain = lookupTableProvider.computeIdealLookupTablesForAddresses(addressesMain); 1232 | let blockhash = (await retryOperation(() => connection.getLatestBlockhash())).blockhash; 1233 | const message = new TransactionMessage({ 1234 | payerKey: keypair.publicKey, 1235 | recentBlockhash: blockhash, 1236 | instructions: instructionsForChunk, 1237 | }).compileToV0Message(lookupTablesMain); 1238 | 1239 | const versionedTx = new VersionedTransaction(message); 1240 | versionedTx.sign([keypair]); 1241 | 1242 | try { 1243 | const swapsim = await provider.connection.simulateTransaction(versionedTx, { sigVerify: true }); 1244 | const swapId = await provider.connection.sendTransaction(versionedTx); 1245 | const lockPoolConfirm = await provider.connection.confirmTransaction(swapId); 1246 | } catch (e) { 1247 | console.log(e, "error with versionedTx"); 1248 | process.exit(0); 1249 | } 1250 | } 1251 | }; 1252 | -------------------------------------------------------------------------------- /src/retrieve.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey, LAMPORTS_PER_SOL, TransactionMessage, SystemProgram, VersionedTransaction, TransactionInstruction, Blockhash } from "@solana/web3.js"; 2 | import { connection, wallet, tipAcct, isMainnet, provider } from "../config"; 3 | import { lookupTableProvider } from "./clients/LookupTableProvider"; 4 | import * as spl from "@solana/spl-token"; 5 | import fs from "fs"; 6 | import path from "path"; 7 | import promptSync from "prompt-sync"; 8 | import { makeSwap, makeCPMMSwap, sendBundle, sendTransactionsSequentially } from "./bot"; // <-- Make sure this is your updated makeCPMMSwap 9 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 10 | import { formatAmmKeysById } from "./clients/formatAmm"; 11 | import chalk from "chalk"; 12 | import { retryOperation, pause } from "./clients/utils"; 13 | import { burnAccount } from "./utils"; 14 | import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; 15 | import BN from "bn.js"; 16 | // Raydium/Solana imports for CPMM detection 17 | import { Raydium, CpmmRpcData, WSOLMint } from "@raydium-io/raydium-sdk-v2"; 18 | 19 | //PumpSwap integration 20 | import PumpSwapSDK from "./pump_swap_sdk"; 21 | import { PROTOCOL_FEE_RECIPIENT_MAINNET } from "./pump_swap_sdk/constants"; 22 | import { PumpAmmSdk } from "@pump-fun/pump-swap-sdk"; 23 | 24 | require("dotenv").config(); 25 | 26 | const DEBUG = process.env.DEBUG?.toLowerCase() === "true"; 27 | 28 | const prompt = promptSync(); 29 | const keypairsDir = "./src/keypairs"; 30 | 31 | let pSwap: PumpAmmSdk | null = null; 32 | let customSDK: PumpSwapSDK | null = null; 33 | 34 | customSDK = new PumpSwapSDK("mainnet"); 35 | // PumpSwap SDK works directly with the token mint 36 | pSwap = new PumpAmmSdk(connection); 37 | 38 | /** 39 | * Close all accounts for a given Pair ID, selling any tokens to WSOL if CPMM, Non-CPMM, or PumpSwap. 40 | */ 41 | export async function closeAcc() { 42 | console.clear(); 43 | console.log(chalk.red("\n==================== Retrieve SOL ====================")); 44 | console.log(chalk.yellow("Follow the instructions below to retrieve SOL.\n")); 45 | 46 | const marketID = prompt(chalk.cyan("Enter your Pair ID or Token Mint: ")); 47 | const keypairsPath = path.join(keypairsDir, marketID); 48 | 49 | const delaySell = prompt(chalk.cyan("Delay between sells in MS (Ex. 300): ")); 50 | const delaySellIn = parseInt(delaySell, 10); 51 | 52 | if (!fs.existsSync(keypairsPath)) { 53 | console.log(chalk.red(`No keypairs found for Pair ID/Token: ${marketID}`)); 54 | process.exit(0); 55 | } 56 | 57 | const jitoTipAmtInput = prompt(chalk.cyan("Jito tip in Sol (Ex. 0.01): ")); 58 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 59 | 60 | if (jitoTipAmtInput) { 61 | const tipValue = parseFloat(jitoTipAmtInput); 62 | if (tipValue >= 0.1) { 63 | console.log(chalk.red("Error: Tip value is too high. Please enter a value less than or equal to 0.1.")); 64 | process.exit(0); 65 | } 66 | } else { 67 | console.log(chalk.red("Error: Invalid input. Please enter a valid number.")); 68 | process.exit(0); 69 | } 70 | 71 | // Try to interpret marketID as both a pool ID and a token mint 72 | const marketIdPubkey = new PublicKey(marketID); 73 | 74 | // First check if it's a PumpSwap pool 75 | let isPumpSwap = false; 76 | let pumpSwapBaseMint: PublicKey | null = null; 77 | 78 | try { 79 | console.log(chalk.blue("Checking if this is a PumpSwap pool...")); 80 | // If marketID is a token mint, try to get the pool 81 | const pumpSwapPool = await customSDK?.getPumpSwapPool(marketIdPubkey); 82 | if (pumpSwapPool) { 83 | isPumpSwap = true; 84 | pumpSwapBaseMint = marketIdPubkey; 85 | console.log(chalk.green(`Detected PumpSwap pool for token: ${marketID}`)); 86 | } 87 | } catch (error) { 88 | // Not a PumpSwap pool or not a valid mint 89 | isPumpSwap = false; 90 | console.log(chalk.yellow(`Not a PumpSwap pool: ${error instanceof Error ? error.message : String(error)}`)); 91 | } 92 | 93 | // If not PumpSwap, check if it's CPMM 94 | let poolInfo: CpmmRpcData | null = null; 95 | let isCPMM = false; 96 | let keys = null; 97 | const poolId = new PublicKey(marketID); 98 | 99 | if (!isPumpSwap) { 100 | const dummyOwner = Keypair.generate(); 101 | const raydium = await Raydium.load({ 102 | owner: dummyOwner, 103 | connection, 104 | cluster: "mainnet", 105 | disableFeatureCheck: true, 106 | disableLoadToken: true, 107 | blockhashCommitment: "finalized", 108 | }); 109 | 110 | try { 111 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 112 | isCPMM = true; 113 | console.log(chalk.green(`Detected CPMM pool: ${marketID}`)); 114 | } catch { 115 | isCPMM = false; 116 | console.log(chalk.yellow(`Not a CPMM pool: ${marketID}`)); 117 | } 118 | 119 | // If not CPMM, we fetch standard Raydium/OpenBook keys 120 | if (!isCPMM) { 121 | try { 122 | const poolPrompt = await formatAmmKeysById(marketID); 123 | const derivedMarketId = poolPrompt.marketId; 124 | keys = await derivePoolKeys(new PublicKey(derivedMarketId)); 125 | 126 | if (keys === null) { 127 | console.log(chalk.red("Error fetching poolkeys")); 128 | process.exit(0); 129 | } 130 | console.log(chalk.green(`Detected standard Raydium pool: ${marketID}`)); 131 | } catch (error) { 132 | console.log(chalk.red(`Could not identify market type for ID: ${marketID}`)); 133 | console.log(chalk.red(error instanceof Error ? error.message : String(error))); 134 | process.exit(0); 135 | } 136 | } 137 | } 138 | 139 | // Now proceed with closing all keypairs in this directory 140 | let keypairsExist = checkKeypairsExist(keypairsPath); 141 | 142 | while (keypairsExist) { 143 | const keypairs = loadKeypairs(keypairsPath); 144 | let txsSigned: VersionedTransaction[] = []; 145 | let maxSize = 0; 146 | 147 | for (let i = 0; i < keypairs.length; i++) { 148 | let { blockhash } = await retryOperation(() => connection.getLatestBlockhash()); 149 | 150 | const keypair = keypairs[i]; 151 | console.log(chalk.blue(`Processing keypair ${i + 1}/${keypairs.length}:`), keypair.publicKey.toString()); 152 | 153 | // Handle PumpSwap 154 | if (isPumpSwap && pumpSwapBaseMint) { 155 | console.log(chalk.blue("Processing PumpSwap token...")); 156 | const instructionsForChunk: TransactionInstruction[] = []; 157 | const tokenAcc = await spl.getAssociatedTokenAddress(pumpSwapBaseMint, keypair.publicKey); 158 | const wsolAcc = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 159 | 160 | // Verify token account exists and has balance 161 | let tokenAccountExists = false; 162 | let tokenAmount = 0; 163 | let tokenAmountString = "0"; 164 | 165 | try { 166 | const accountInfo = await connection.getAccountInfo(tokenAcc); 167 | if (accountInfo !== null) { 168 | const balanceResponse = await connection.getTokenAccountBalance(tokenAcc); 169 | tokenAmount = balanceResponse.value.uiAmount || 0; 170 | tokenAmountString = balanceResponse.value.uiAmountString || "0"; 171 | tokenAccountExists = accountInfo !== null && tokenAmount > 0; 172 | 173 | console.log(chalk.blue(`Token account exists: ${tokenAccountExists}`)); 174 | console.log(chalk.blue(`Token balance: ${tokenAmountString} (${tokenAmount})`)); 175 | } else { 176 | console.log(chalk.yellow("Token account doesn't exist")); 177 | } 178 | } catch (error) { 179 | console.log(chalk.red(`Error checking token account: ${error}`)); 180 | } 181 | 182 | const wsolAccountExists = await checkTokenAccountExists(wsolAcc); 183 | 184 | // Check if the keypair account has any SOL balance 185 | const solBalance = await connection.getBalance(keypair.publicKey); 186 | const minBalanceForRent = 10000; // Minimum balance to be worth transferring 187 | 188 | // Only proceed if either token account exists with balance or WSOL account exists or SOL balance > minimum 189 | if (tokenAccountExists || wsolAccountExists || solBalance > minBalanceForRent) { 190 | if (solBalance > minBalanceForRent) { 191 | console.log(chalk.green(`Account has ${solBalance / LAMPORTS_PER_SOL} SOL, will transfer to main wallet`)); 192 | } 193 | 194 | if (tokenAccountExists) { 195 | // Sell instructions 196 | const sellInstruction = await sell_pump_amm(pumpSwapBaseMint, keypair, Number(tokenAmountString), 0); 197 | 198 | if (sellInstruction) { 199 | instructionsForChunk.push(...sellInstruction); 200 | console.log(chalk.green(`Added PumpSwap sell instructions for ${tokenAmountString} tokens`)); 201 | } else { 202 | console.log(chalk.yellow("Failed to create sell instructions")); 203 | } 204 | 205 | // Burn token account 206 | //const baseMintTokenProgram = await getTokenProgramId(pumpSwapBaseMint); 207 | //let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 208 | //instructionsForChunk.push(...baseTokenBurnInstruction); 209 | } 210 | 211 | if (wsolAccountExists) { 212 | console.log("Supposed to burn WSOL entry."); 213 | // Burn WSOL account 214 | //let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 215 | //instructionsForChunk.push(...wsolBurnInstruction); 216 | } 217 | 218 | // Drain leftover SOL from the ephemeral keypair to main wallet 219 | const balance = await connection.getBalance(keypair.publicKey); 220 | const feeEstimate = 10000; 221 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 222 | console.log("current balance", balance); 223 | console.log("feeEstimate", feeEstimate); 224 | console.log("theoretic transferAmount", transferAmount); 225 | 226 | console.log(`transferAmount ${transferAmount} of address: ${keypair.publicKey.toBase58()}`); 227 | 228 | const drainBalanceIxn = SystemProgram.transfer({ 229 | fromPubkey: keypair.publicKey, 230 | toPubkey: wallet.publicKey, 231 | lamports: transferAmount, 232 | }); 233 | const drainMessage = new TransactionMessage({ 234 | payerKey: wallet.publicKey, 235 | recentBlockhash: blockhash, 236 | instructions: [drainBalanceIxn], 237 | }).compileToV0Message(); 238 | 239 | const drainTx = new VersionedTransaction(drainMessage); 240 | drainTx.sign([wallet, keypair]); 241 | txsSigned.push(drainTx); 242 | 243 | //instructionsForChunk.push(drainBalanceIxn); 244 | 245 | // Jito tip 246 | const tipSwapIxn = SystemProgram.transfer({ 247 | fromPubkey: wallet.publicKey, 248 | toPubkey: tipAcct, 249 | lamports: BigInt(jitoTipAmt), 250 | }); 251 | instructionsForChunk.push(tipSwapIxn); 252 | 253 | // Compile the transaction 254 | const message = new TransactionMessage({ 255 | payerKey: wallet.publicKey, 256 | recentBlockhash: blockhash, 257 | instructions: instructionsForChunk, 258 | }).compileToV0Message(); 259 | 260 | const versionedTx = new VersionedTransaction(message); 261 | versionedTx.sign([wallet, keypair]); 262 | 263 | txsSigned.push(versionedTx); 264 | maxSize++; 265 | } else { 266 | console.log(chalk.yellow("No token, WSOL, or significant SOL balance found. Skipping transaction.")); 267 | deleteKeypairFile(keypair, keypairsPath); 268 | } 269 | } 270 | // Handle CPMM or Non-CPMM 271 | else { 272 | const WSOLataKeypair = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 273 | if (await checkTokenAccountExists(WSOLataKeypair)) { 274 | const instructionsForChunk: TransactionInstruction[] = []; 275 | 276 | if (!isCPMM && keys) { 277 | // Non-CPMM => Raydium/OpenBook 278 | let useQuoteMint: boolean; 279 | if (keys.quoteMint.toBase58() === "So11111111111111111111111111111111111111112") { 280 | useQuoteMint = false; 281 | } else { 282 | useQuoteMint = true; 283 | } 284 | const baseataKeypair = useQuoteMint 285 | ? await spl.getAssociatedTokenAddress(new PublicKey(keys.quoteMint), keypair.publicKey) 286 | : await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 287 | 288 | // SELL => reverse = true 289 | const { sellIxs } = makeSwap(keys, WSOLataKeypair, baseataKeypair, true, keypair); 290 | instructionsForChunk.push(...sellIxs); 291 | 292 | const baseMintTokenProgram = useQuoteMint ? await getTokenProgramId(keys.quoteMint) : await getTokenProgramId(keys.baseMint); 293 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 294 | instructionsForChunk.push(...wsolBurnInstruction); 295 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, baseataKeypair, baseMintTokenProgram); 296 | instructionsForChunk.push(...baseTokenBurnInstruction); 297 | } else if (isCPMM && poolInfo) { 298 | // CPMM => use new makeCPMMSwap signature with direction = "sell" 299 | let baseMint: PublicKey, quoteMint: PublicKey; 300 | if (poolInfo.mintA.equals(WSOLMint)) { 301 | baseMint = poolInfo.mintA; // WSOL 302 | quoteMint = poolInfo.mintB; 303 | } else if (poolInfo.mintB.equals(WSOLMint)) { 304 | baseMint = poolInfo.mintB; // WSOL 305 | quoteMint = poolInfo.mintA; 306 | } else { 307 | baseMint = poolInfo.mintA; 308 | quoteMint = poolInfo.mintB; 309 | } 310 | 311 | // The user presumably holds the "other" token if we're selling to WSOL 312 | const userTokenMint = baseMint.equals(WSOLMint) ? quoteMint : baseMint; 313 | const otherTokenATA = await spl.getAssociatedTokenAddress(userTokenMint, keypair.publicKey); 314 | 315 | const { swapIxs } = await makeCPMMSwap( 316 | connection, 317 | poolId, 318 | poolInfo, 319 | spl.NATIVE_MINT, 320 | WSOLataKeypair, // wSol ATA 321 | userTokenMint, // which token the user is selling 322 | otherTokenATA, // user's other token ATA 323 | keypair, 324 | "sell" 325 | ); 326 | instructionsForChunk.push(...swapIxs); 327 | 328 | const baseMintTokenProgram = await getTokenProgramId(userTokenMint); 329 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 330 | instructionsForChunk.push(...wsolBurnInstruction); 331 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, otherTokenATA, baseMintTokenProgram); 332 | instructionsForChunk.push(...baseTokenBurnInstruction); 333 | } 334 | 335 | // Drain leftover SOL from the ephemeral keypair to main wallet 336 | const balance = await connection.getBalance(keypair.publicKey); 337 | const feeEstimate = 10000; 338 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 339 | 340 | const drainBalanceIxn = SystemProgram.transfer({ 341 | fromPubkey: keypair.publicKey, 342 | toPubkey: wallet.publicKey, 343 | lamports: transferAmount, 344 | }); 345 | instructionsForChunk.push(drainBalanceIxn); 346 | 347 | // Jito tip 348 | const tipSwapIxn = SystemProgram.transfer({ 349 | fromPubkey: wallet.publicKey, 350 | toPubkey: tipAcct, 351 | lamports: BigInt(jitoTipAmt), 352 | }); 353 | instructionsForChunk.push(tipSwapIxn); 354 | 355 | // Compile the transaction 356 | const message = new TransactionMessage({ 357 | payerKey: keypair.publicKey, 358 | recentBlockhash: blockhash, 359 | instructions: instructionsForChunk, 360 | }).compileToV0Message(); 361 | 362 | const versionedTx = new VersionedTransaction(message); 363 | versionedTx.sign([wallet, keypair]); 364 | 365 | txsSigned.push(versionedTx); 366 | maxSize++; 367 | } else { 368 | // Check if the keypair has SOL balance even if no token accounts 369 | const solBalance = await connection.getBalance(keypair.publicKey); 370 | const minBalanceForRent = 10000; 371 | 372 | if (solBalance > minBalanceForRent) { 373 | console.log(chalk.green(`Account has ${solBalance / LAMPORTS_PER_SOL} SOL, will transfer to main wallet`)); 374 | 375 | const instructionsForChunk: TransactionInstruction[] = []; 376 | 377 | // Drain SOL balance 378 | const feeEstimate = 10000; 379 | const transferAmount = solBalance - feeEstimate > 0 ? solBalance - feeEstimate : 0; 380 | 381 | const drainBalanceIxn = SystemProgram.transfer({ 382 | fromPubkey: keypair.publicKey, 383 | toPubkey: wallet.publicKey, 384 | lamports: transferAmount, 385 | }); 386 | instructionsForChunk.push(drainBalanceIxn); 387 | 388 | // Jito tip 389 | const tipSwapIxn = SystemProgram.transfer({ 390 | fromPubkey: wallet.publicKey, 391 | toPubkey: tipAcct, 392 | lamports: BigInt(jitoTipAmt), 393 | }); 394 | instructionsForChunk.push(tipSwapIxn); 395 | 396 | // Compile transaction 397 | const message = new TransactionMessage({ 398 | payerKey: keypair.publicKey, 399 | recentBlockhash: blockhash, 400 | instructions: instructionsForChunk, 401 | }).compileToV0Message(); 402 | 403 | const versionedTx = new VersionedTransaction(message); 404 | versionedTx.sign([wallet, keypair]); 405 | 406 | txsSigned.push(versionedTx); 407 | maxSize++; 408 | } else { 409 | console.log(chalk.yellow(`Skipping keypair with zero balance:`), keypair.publicKey.toString()); 410 | deleteKeypairFile(keypair, keypairsPath); 411 | } 412 | } 413 | } 414 | 415 | // Send in batches of 5 416 | if (maxSize === 5 || i === keypairs.length - 1) { 417 | if (txsSigned.length > 0) { 418 | if (DEBUG) { 419 | for (const tx of txsSigned) { 420 | try { 421 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "confirmed" }); 422 | if (simulationResult.value.err) { 423 | const errorMessage = `Simulation tx error: ${JSON.stringify(simulationResult.value.err, null, 2)}`; 424 | fs.appendFileSync("errorlog.txt", `${new Date().toISOString()} - ${errorMessage}\n`); 425 | console.log(chalk.red("Error simulating saved to errorlog.txt")); 426 | } else { 427 | console.log("Transaction simulation success."); 428 | } 429 | } catch (error) { 430 | console.error("Error during simulation:", error); 431 | } 432 | } 433 | } 434 | await sendBundleWithRetry(txsSigned); 435 | //await sendTransactionsSequentially(txsSigned); 436 | txsSigned = []; 437 | maxSize = 0; 438 | console.log(chalk.blue(`Waiting ${delaySellIn} ms`)); 439 | await delay(delaySellIn); 440 | } 441 | } 442 | console.log(""); 443 | } 444 | keypairsExist = checkKeypairsExist(keypairsPath); 445 | } 446 | 447 | console.log(chalk.green("All transactions processed and no more keypairs left.")); 448 | await pause(); 449 | } 450 | 451 | /** 452 | * Closes a *specific* chunk of accounts for the given Pair ID (used in your "extender"). 453 | * Also does CPMM vs. Non-CPMM vs. PumpSwap detection. 454 | */ 455 | export async function closeSpecificAcc(keypairs: Keypair[], marketID: string, jitoTip: number, block: string | Blockhash) { 456 | const keypairsPath = path.join(keypairsDir, marketID); 457 | 458 | if (!fs.existsSync(keypairsPath)) { 459 | console.log(chalk.red(`No keypairs found for Pair ID: ${marketID}`)); 460 | return; 461 | } 462 | 463 | // Try to interpret marketID as both a pool ID and a token mint 464 | const marketIdPubkey = new PublicKey(marketID); 465 | 466 | // First check if it's a PumpSwap pool 467 | let isPumpSwap = false; 468 | let pumpSwapBaseMint: PublicKey | null = null; 469 | 470 | try { 471 | console.log(chalk.blue("Checking if this is a PumpSwap pool...")); 472 | //const pSwap = new PumpSwapSDK(); 473 | // If marketID is a token mint, try to get the pool 474 | const pumpSwapPool = await customSDK?.getPumpSwapPool(marketIdPubkey); 475 | if (pumpSwapPool) { 476 | isPumpSwap = true; 477 | pumpSwapBaseMint = marketIdPubkey; 478 | console.log(chalk.green(`Detected PumpSwap pool for token: ${marketID}`)); 479 | } 480 | } catch (error) { 481 | // Not a PumpSwap pool or not a valid mint 482 | isPumpSwap = false; 483 | console.log(chalk.yellow(`Not a PumpSwap pool: ${error instanceof Error ? error.message : String(error)}`)); 484 | } 485 | 486 | // If not PumpSwap, check if CPMM 487 | let poolInfo: CpmmRpcData | null = null; 488 | let isCPMM = false; 489 | let keys = null; 490 | const poolId = new PublicKey(marketID); 491 | 492 | if (!isPumpSwap) { 493 | const dummyOwner = Keypair.generate(); 494 | const raydium = await Raydium.load({ 495 | owner: dummyOwner, 496 | connection, 497 | cluster: "mainnet", 498 | disableFeatureCheck: true, 499 | disableLoadToken: true, 500 | blockhashCommitment: "finalized", 501 | }); 502 | 503 | try { 504 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 505 | isCPMM = true; 506 | } catch { 507 | isCPMM = false; 508 | } 509 | 510 | if (!isCPMM) { 511 | const poolPrompt = await formatAmmKeysById(marketID); 512 | const derivedMarketId = poolPrompt.marketId; 513 | keys = await derivePoolKeys(new PublicKey(derivedMarketId)); 514 | if (keys === null) { 515 | console.log(chalk.red("Error fetching poolkeys")); 516 | return; 517 | } 518 | } 519 | } 520 | 521 | const BundledTxns: VersionedTransaction[] = []; 522 | 523 | for (let i = 0; i < keypairs.length; i++) { 524 | const keypair = keypairs[i]; 525 | 526 | // Handle PumpSwap case 527 | if (isPumpSwap && pumpSwapBaseMint) { 528 | const instructionsForChunk: TransactionInstruction[] = []; 529 | const tokenAcc = await spl.getAssociatedTokenAddress(pumpSwapBaseMint, keypair.publicKey); 530 | const wsolAcc = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 531 | 532 | // Verify accounts exist 533 | const tokenAccountExists = await checkTokenAccountExists(tokenAcc); 534 | const wsolAccountExists = await checkTokenAccountExists(wsolAcc); 535 | 536 | if (tokenAccountExists) { 537 | // Get token balance for selling 538 | const tokenBalance = await connection.getTokenAccountBalance(tokenAcc); 539 | 540 | // Sell instructions - convert tokens to WSOL 541 | const sellInstruction = await sell_pump_amm(pumpSwapBaseMint, keypair, Number(tokenBalance.value.uiAmountString), 0); 542 | if (sellInstruction) { 543 | instructionsForChunk.push(...sellInstruction); 544 | } 545 | 546 | // Burn token account 547 | const baseMintTokenProgram = await getTokenProgramId(pumpSwapBaseMint); 548 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 549 | instructionsForChunk.push(...baseTokenBurnInstruction); 550 | } 551 | 552 | if (wsolAccountExists) { 553 | // Burn WSOL account 554 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 555 | instructionsForChunk.push(...wsolBurnInstruction); 556 | } 557 | 558 | // Drain leftover SOL 559 | const balance = await connection.getBalance(keypair.publicKey); 560 | const feeEstimate = 10000; 561 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 562 | 563 | const drainBalanceIxn = SystemProgram.transfer({ 564 | fromPubkey: keypair.publicKey, 565 | toPubkey: wallet.publicKey, 566 | lamports: transferAmount, 567 | }); 568 | instructionsForChunk.push(drainBalanceIxn); 569 | 570 | // Jito tip 571 | const tipSwapIxn = SystemProgram.transfer({ 572 | fromPubkey: wallet.publicKey, 573 | toPubkey: tipAcct, 574 | lamports: BigInt(jitoTip), 575 | }); 576 | instructionsForChunk.push(tipSwapIxn); 577 | 578 | // Create and sign transaction 579 | const message = new TransactionMessage({ 580 | payerKey: keypair.publicKey, 581 | recentBlockhash: block, 582 | instructions: instructionsForChunk, 583 | }).compileToV0Message(); 584 | 585 | const versionedTx = new VersionedTransaction(message); 586 | versionedTx.sign([wallet, keypair]); 587 | 588 | BundledTxns.push(versionedTx); 589 | } 590 | // Handle CPMM or non-CPMM cases (existing code) 591 | else { 592 | const WSOLataKeypair = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 593 | 594 | if (await checkTokenAccountExists(WSOLataKeypair)) { 595 | const instructionsForChunk: TransactionInstruction[] = []; 596 | 597 | if (!isCPMM && keys) { 598 | let useQuoteMint = true; 599 | if (keys.quoteMint.toBase58() === "So11111111111111111111111111111111111111112") { 600 | useQuoteMint = false; 601 | } 602 | const baseataKeypair = useQuoteMint 603 | ? await spl.getAssociatedTokenAddress(new PublicKey(keys.quoteMint), keypair.publicKey) 604 | : await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 605 | 606 | // SELL => reverse = true 607 | const { sellIxs } = makeSwap(keys, WSOLataKeypair, baseataKeypair, true, keypair); 608 | instructionsForChunk.push(...sellIxs); 609 | 610 | const baseMintTokenProgram = useQuoteMint ? await getTokenProgramId(keys.quoteMint) : await getTokenProgramId(keys.baseMint); 611 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 612 | instructionsForChunk.push(...wsolBurnInstruction); 613 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, baseataKeypair, baseMintTokenProgram); 614 | instructionsForChunk.push(...baseTokenBurnInstruction); 615 | } else if (isCPMM && poolInfo) { 616 | let baseMint: PublicKey, quoteMint: PublicKey; 617 | if (poolInfo.mintA.equals(WSOLMint)) { 618 | baseMint = poolInfo.mintA; 619 | quoteMint = poolInfo.mintB; 620 | } else if (poolInfo.mintB.equals(WSOLMint)) { 621 | baseMint = poolInfo.mintB; 622 | quoteMint = poolInfo.mintA; 623 | } else { 624 | baseMint = poolInfo.mintA; 625 | quoteMint = poolInfo.mintB; 626 | } 627 | 628 | const userTokenMint = baseMint.equals(WSOLMint) ? quoteMint : baseMint; 629 | const otherTokenATA = await spl.getAssociatedTokenAddress(userTokenMint, keypair.publicKey); 630 | 631 | const { swapIxs } = await makeCPMMSwap( 632 | connection, 633 | poolId, 634 | poolInfo, 635 | spl.NATIVE_MINT, 636 | WSOLataKeypair, // wSol 637 | userTokenMint, // which token the user is selling 638 | otherTokenATA, // user's other token ATA 639 | keypair, 640 | "sell" // direction 641 | ); 642 | instructionsForChunk.push(...swapIxs); 643 | 644 | const baseMintTokenProgram = await getTokenProgramId(userTokenMint); 645 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 646 | instructionsForChunk.push(...wsolBurnInstruction); 647 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, otherTokenATA, baseMintTokenProgram); 648 | instructionsForChunk.push(...baseTokenBurnInstruction); 649 | } 650 | 651 | // Transfer leftover SOL 652 | const balance = await connection.getBalance(keypair.publicKey); 653 | const feeEstimate = 10000; 654 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 655 | 656 | const drainBalanceIxn = SystemProgram.transfer({ 657 | fromPubkey: keypair.publicKey, 658 | toPubkey: wallet.publicKey, 659 | lamports: transferAmount, 660 | }); 661 | instructionsForChunk.push(drainBalanceIxn); 662 | 663 | // Jito tip 664 | const tipSwapIxn = SystemProgram.transfer({ 665 | fromPubkey: wallet.publicKey, 666 | toPubkey: tipAcct, 667 | lamports: BigInt(jitoTip), 668 | }); 669 | instructionsForChunk.push(tipSwapIxn); 670 | 671 | const message = new TransactionMessage({ 672 | payerKey: keypair.publicKey, 673 | recentBlockhash: block, 674 | instructions: instructionsForChunk, 675 | }).compileToV0Message(); 676 | 677 | const versionedTx = new VersionedTransaction(message); 678 | versionedTx.sign([wallet, keypair]); 679 | 680 | BundledTxns.push(versionedTx); 681 | } else { 682 | console.log(chalk.yellow(`Skipping keypair with zero balance or no token account:`), keypair.publicKey.toString()); 683 | deleteKeypairFile(keypair, keypairsPath); 684 | } 685 | } 686 | 687 | // Send in batches of 5 or at the end 688 | if (BundledTxns.length === 5 || i === keypairs.length - 1) { 689 | try { 690 | console.log(chalk.red("Sending bundled sell transactions...")); 691 | await sendBundleWithRetry(BundledTxns); 692 | BundledTxns.length = 0; 693 | } catch (error) { 694 | console.error("Error during sendBundleWithRetry:", error); 695 | } 696 | } 697 | } 698 | } 699 | 700 | /** 701 | * Closes a particular account (by index) for a Pair ID, using CPMM, Non-CPMM, or PumpSwap approach. 702 | */ 703 | export async function closeParticularAcc(walletNumber: number) { 704 | if (walletNumber <= 0) { 705 | return console.log(chalk.red("Wallet number should be greater than 0")); 706 | } 707 | console.clear(); 708 | console.log(chalk.green("\n==================== Close Particular Account ====================")); 709 | console.log(chalk.yellow("Follow the instructions below to close a particular account.\n")); 710 | 711 | const marketID = prompt(chalk.cyan("Enter your Pair ID or Token Mint: ")); 712 | const jitoTipAmtInput = prompt(chalk.cyan("Jito tip in Sol (Ex. 0.001): ")); 713 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 714 | 715 | if (jitoTipAmtInput) { 716 | const tipValue = parseFloat(jitoTipAmtInput); 717 | if (tipValue >= 0.1) { 718 | console.log(chalk.red("Error: Tip value is too high. Please enter a value less than or equal to 0.1.")); 719 | process.exit(0); 720 | } 721 | } else { 722 | console.log(chalk.red("Error: Invalid input. Please enter a valid number.")); 723 | process.exit(0); 724 | } 725 | 726 | // Try to interpret marketID as both a pool ID and a token mint 727 | const marketIdPubkey = new PublicKey(marketID); 728 | 729 | // First check if it's a PumpSwap pool 730 | let isPumpSwap = false; 731 | let pumpSwapBaseMint: PublicKey | null = null; 732 | 733 | try { 734 | // If marketID is a token mint, try to get the pool 735 | const pumpSwapPool = await customSDK?.getPumpSwapPool(marketIdPubkey); 736 | if (pumpSwapPool) { 737 | isPumpSwap = true; 738 | pumpSwapBaseMint = marketIdPubkey; 739 | console.log(chalk.green(`Detected PumpSwap pool for token: ${marketID}`)); 740 | } 741 | } catch { 742 | // Not a PumpSwap pool or not a valid mint 743 | isPumpSwap = false; 744 | } 745 | 746 | // If not PumpSwap, check if CPMM 747 | let poolInfo: CpmmRpcData | null = null; 748 | let isCPMM = false; 749 | let keys = null; 750 | const poolId = new PublicKey(marketID); 751 | 752 | if (!isPumpSwap) { 753 | const dummyOwner = Keypair.generate(); 754 | const raydium = await Raydium.load({ 755 | owner: dummyOwner, 756 | connection, 757 | cluster: "mainnet", 758 | disableFeatureCheck: true, 759 | disableLoadToken: true, 760 | blockhashCommitment: "finalized", 761 | }); 762 | 763 | try { 764 | poolInfo = await raydium.cpmm.getRpcPoolInfo(marketID); 765 | isCPMM = true; 766 | } catch { 767 | isCPMM = false; 768 | } 769 | 770 | if (!isCPMM) { 771 | try { 772 | const poolPrompt = await formatAmmKeysById(marketID); 773 | const derivedMarketId = poolPrompt.marketId; 774 | keys = await derivePoolKeys(new PublicKey(derivedMarketId)); 775 | 776 | if (keys === null) { 777 | console.log(chalk.red("Error fetching pool keys")); 778 | process.exit(0); 779 | } 780 | } catch (error) { 781 | console.log(chalk.red(`Could not identify market type for ID: ${marketID}`)); 782 | process.exit(0); 783 | } 784 | } 785 | } 786 | 787 | // Get the keypair file 788 | const files = fs.readdirSync(path.join(keypairsDir, marketID)); 789 | if (walletNumber > files.length) { 790 | console.log(chalk.red("Invalid wallet number: out of range.")); 791 | return; 792 | } 793 | 794 | const file = files[walletNumber - 1]; 795 | const filePath = path.join(keypairsDir, marketID, file); 796 | const fileData = JSON.parse(fs.readFileSync(filePath, "utf8")); 797 | if (fileData && files.length > 0) { 798 | const keypair = Keypair.fromSecretKey(new Uint8Array(fileData)); 799 | let txsSigned: VersionedTransaction[] = []; 800 | let { blockhash } = await retryOperation(() => connection.getLatestBlockhash()); 801 | 802 | console.log(chalk.blue(`Processing keypair: ${keypair.publicKey.toString()}`)); 803 | 804 | // Initialize instructions array outside the conditionals 805 | const instructionsForChunk: TransactionInstruction[] = []; 806 | let shouldContinue = false; // Default to not continue unless we find tokens 807 | 808 | // Handle PumpSwap 809 | if (isPumpSwap && pumpSwapBaseMint) { 810 | console.log(chalk.blue("Processing PumpSwap token...")); 811 | const tokenAcc = await spl.getAssociatedTokenAddress(pumpSwapBaseMint, keypair.publicKey); 812 | const wsolAcc = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 813 | 814 | // Verify token account exists and has balance 815 | let tokenAccountExists = false; 816 | let tokenAmount = 0; 817 | let tokenAmountString = "0"; 818 | 819 | try { 820 | const accountInfo = await connection.getAccountInfo(tokenAcc); 821 | if (accountInfo !== null) { 822 | const balanceResponse = await connection.getTokenAccountBalance(tokenAcc); 823 | tokenAmount = balanceResponse.value.uiAmount || 0; 824 | tokenAmountString = balanceResponse.value.uiAmountString || "0"; 825 | tokenAccountExists = accountInfo !== null && tokenAmount > 0; 826 | 827 | console.log(chalk.blue(`Token account exists: ${tokenAccountExists}`)); 828 | console.log(chalk.blue(`Token balance: ${tokenAmountString} (${tokenAmount})`)); 829 | } else { 830 | console.log(chalk.yellow("Token account doesn't exist")); 831 | } 832 | } catch (error) { 833 | console.log(chalk.red(`Error checking token account: ${error}`)); 834 | } 835 | 836 | const wsolAccountExists = await checkTokenAccountExists(wsolAcc); 837 | 838 | // Check if the keypair account has any SOL balance 839 | const solBalance = await connection.getBalance(keypair.publicKey); 840 | const minBalanceForRent = 10000; // Minimum balance to be worth transferring 841 | 842 | // Only proceed if either token account exists with balance or WSOL account exists or SOL balance > minimum 843 | if (tokenAccountExists || wsolAccountExists || solBalance > minBalanceForRent) { 844 | shouldContinue = true; 845 | 846 | if (solBalance > minBalanceForRent) { 847 | console.log(chalk.green(`Account has ${solBalance / LAMPORTS_PER_SOL} SOL, will transfer to main wallet`)); 848 | } 849 | 850 | if (tokenAccountExists) { 851 | // Sell instructions 852 | const sellInstruction = await sell_pump_amm(pumpSwapBaseMint, keypair, Number(tokenAmountString), 0); 853 | 854 | if (sellInstruction) { 855 | instructionsForChunk.push(...sellInstruction); 856 | console.log(chalk.green(`Added PumpSwap sell instructions for ${tokenAmountString} tokens`)); 857 | } else { 858 | console.log(chalk.yellow("Failed to create sell instructions")); 859 | } 860 | 861 | // Burn token account 862 | const baseMintTokenProgram = await getTokenProgramId(pumpSwapBaseMint); 863 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 864 | instructionsForChunk.push(...baseTokenBurnInstruction); 865 | } else { 866 | console.log(chalk.yellow(`No token balance found for ${pumpSwapBaseMint.toString()}`)); 867 | } 868 | 869 | if (wsolAccountExists) { 870 | // Burn WSOL account 871 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 872 | instructionsForChunk.push(...wsolBurnInstruction); 873 | } 874 | } else { 875 | console.log(chalk.yellow("No token, WSOL, or significant SOL balance found. Skipping transaction.")); 876 | shouldContinue = false; 877 | } 878 | } 879 | // Handle CPMM or standard Raydium pools 880 | else { 881 | const WSOLataKeypair = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 882 | 883 | if (await checkTokenAccountExists(WSOLataKeypair)) { 884 | if (!isCPMM && keys) { 885 | let useQuoteMint = true; 886 | if (keys.quoteMint.toBase58() === "So11111111111111111111111111111111111111112") { 887 | useQuoteMint = false; 888 | } 889 | const baseataKeypair = useQuoteMint 890 | ? await spl.getAssociatedTokenAddress(new PublicKey(keys.quoteMint), keypair.publicKey) 891 | : await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 892 | 893 | // SELL => reverse = true 894 | const { sellIxs } = makeSwap(keys, WSOLataKeypair, baseataKeypair, true, keypair); 895 | instructionsForChunk.push(...sellIxs); 896 | 897 | const baseMintTokenProgram = useQuoteMint ? await getTokenProgramId(keys.quoteMint) : await getTokenProgramId(keys.baseMint); 898 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 899 | instructionsForChunk.push(...wsolBurnInstruction); 900 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, baseataKeypair, baseMintTokenProgram); 901 | instructionsForChunk.push(...baseTokenBurnInstruction); 902 | } else if (isCPMM && poolInfo) { 903 | let baseMint: PublicKey, quoteMint: PublicKey; 904 | if (poolInfo.mintA.equals(WSOLMint)) { 905 | baseMint = poolInfo.mintA; 906 | quoteMint = poolInfo.mintB; 907 | } else if (poolInfo.mintB.equals(WSOLMint)) { 908 | baseMint = poolInfo.mintB; 909 | quoteMint = poolInfo.mintA; 910 | } else { 911 | baseMint = poolInfo.mintA; 912 | quoteMint = poolInfo.mintB; 913 | } 914 | 915 | const userTokenMint = baseMint.equals(WSOLMint) ? quoteMint : baseMint; 916 | const otherTokenATA = await spl.getAssociatedTokenAddress(userTokenMint, keypair.publicKey); 917 | 918 | // SELL 919 | const { swapIxs } = await makeCPMMSwap(connection, poolId, poolInfo, spl.NATIVE_MINT, WSOLataKeypair, userTokenMint, otherTokenATA, keypair, "sell"); 920 | instructionsForChunk.push(...swapIxs); 921 | 922 | const baseMintTokenProgram = await getTokenProgramId(userTokenMint); 923 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, WSOLataKeypair, spl.TOKEN_PROGRAM_ID); 924 | instructionsForChunk.push(...wsolBurnInstruction); 925 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, otherTokenATA, baseMintTokenProgram); 926 | instructionsForChunk.push(...baseTokenBurnInstruction); 927 | } else { 928 | console.log(chalk.red("Could not identify pool type.")); 929 | shouldContinue = false; 930 | process.exit(0); 931 | } 932 | } else { 933 | console.log(chalk.yellow(`Skipping keypair with zero balance: ${keypair.publicKey.toString()}`)); 934 | deleteKeypairFile(keypair, marketID); 935 | shouldContinue = false; 936 | return; 937 | } 938 | } 939 | 940 | // Only continue if previous steps worked 941 | if (shouldContinue) { 942 | // Drain leftover SOL 943 | const balance = await connection.getBalance(keypair.publicKey); 944 | const feeEstimate = 10000; 945 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 946 | 947 | const drainBalanceIxn = SystemProgram.transfer({ 948 | fromPubkey: keypair.publicKey, 949 | toPubkey: wallet.publicKey, 950 | lamports: transferAmount, 951 | }); 952 | instructionsForChunk.push(drainBalanceIxn); 953 | 954 | // Tip 955 | const tipSwapIxn = SystemProgram.transfer({ 956 | fromPubkey: wallet.publicKey, 957 | toPubkey: tipAcct, 958 | lamports: BigInt(jitoTipAmt), 959 | }); 960 | instructionsForChunk.push(tipSwapIxn); 961 | 962 | const message = new TransactionMessage({ 963 | payerKey: keypair.publicKey, 964 | recentBlockhash: blockhash, 965 | instructions: instructionsForChunk, 966 | }).compileToV0Message(); 967 | 968 | const versionedTx = new VersionedTransaction(message); 969 | versionedTx.sign([wallet, keypair]); 970 | 971 | txsSigned.push(versionedTx); 972 | 973 | // (Optional) Simulate for debug 974 | for (const tx of txsSigned) { 975 | try { 976 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "processed" }); 977 | if (simulationResult.value.err) { 978 | console.error("Simulation error for transaction:", simulationResult.value.err); 979 | if (simulationResult.value.logs) { 980 | console.log("Simulation logs:"); 981 | simulationResult.value.logs.forEach((log: any) => console.log(log)); 982 | } 983 | } else { 984 | console.log("Simulation success for transaction. Logs:"); 985 | if (simulationResult.value.logs) { 986 | simulationResult.value.logs.forEach((log: any) => console.log(log)); 987 | } 988 | } 989 | } catch (error) { 990 | console.error("Error during simulation:", error); 991 | } 992 | } 993 | 994 | // Now send the transaction 995 | await sendBundle(txsSigned); 996 | } 997 | 998 | console.log(chalk.green("All transactions processed for the specified keypair.")); 999 | await pause(); 1000 | } 1001 | } 1002 | /** 1003 | * Utility to send a batch of VersionedTx with minimal delay/retry logic. 1004 | */ 1005 | export async function sendBundleWithRetry(txsSigned: VersionedTransaction[]) { 1006 | await delay(100); 1007 | await sendBundle(txsSigned); 1008 | } 1009 | 1010 | /** 1011 | * Simple delay helper. 1012 | */ 1013 | function delay(ms: number): Promise { 1014 | return new Promise((resolve) => setTimeout(resolve, ms)); 1015 | } 1016 | 1017 | /** 1018 | * Checks if an SPL token account exists (non-null). 1019 | */ 1020 | export async function checkTokenAccountExists(accountPublicKeyString: PublicKey): Promise { 1021 | try { 1022 | const accountPublicKey = new PublicKey(accountPublicKeyString); 1023 | const accountInfo = await connection.getAccountInfo(accountPublicKey); 1024 | 1025 | if (accountInfo === null) { 1026 | if (DEBUG) { 1027 | console.log(chalk.yellow(`Token account ${accountPublicKeyString} does not exist.`)); 1028 | } 1029 | return false; 1030 | } else { 1031 | console.log(chalk.green(`Selling from existing token account: ${accountPublicKeyString}`)); 1032 | return true; 1033 | } 1034 | } catch (error) { 1035 | console.error(chalk.red(`Error checking account: ${error}`)); 1036 | return false; 1037 | } 1038 | } 1039 | 1040 | /** 1041 | * Delete a keypair JSON if older than a minute, backing it up in /backup. 1042 | */ 1043 | export async function deleteKeypairFile(keypair: Keypair, marketOrDir: string) { 1044 | let resolvedDir: string; 1045 | if (marketOrDir.includes("keypairs")) { 1046 | resolvedDir = marketOrDir; 1047 | } else { 1048 | resolvedDir = path.join(keypairsDir, marketOrDir); 1049 | } 1050 | 1051 | const identifier = keypair.publicKey.toString(); 1052 | const filename = `keypair-${identifier}.json`; 1053 | const filePath = path.join(resolvedDir, filename); 1054 | const backupDir = path.join(path.dirname(path.dirname(resolvedDir)), "backup", path.basename(resolvedDir)); 1055 | 1056 | if (!fs.existsSync(filePath)) { 1057 | console.log(`File does not exist: ${filePath}`); 1058 | return; 1059 | } 1060 | 1061 | const stats = fs.statSync(filePath); 1062 | const creationTime = new Date(stats.birthtime).getTime(); 1063 | const currentTime = Date.now(); 1064 | 1065 | if (currentTime - creationTime < 80000) { 1066 | console.log(`Skipping deletion as file is not older than 1 minute: ${filename}`); 1067 | return; 1068 | } 1069 | 1070 | const transactionCount = await getTransactionCount(keypair.publicKey); 1071 | if (transactionCount === 1) { 1072 | console.log(`Transaction count is 1 (which means it didn't sell) for keypair: ${identifier}. Total TXs: ${transactionCount}`); 1073 | return; 1074 | } 1075 | 1076 | try { 1077 | if (!fs.existsSync(backupDir)) { 1078 | fs.mkdirSync(backupDir, { recursive: true }); 1079 | } 1080 | const backupFilePath = path.join(backupDir, filename); 1081 | fs.copyFileSync(filePath, backupFilePath); 1082 | 1083 | fs.unlinkSync(filePath); 1084 | if (DEBUG) { 1085 | console.log(`Deleted file for keypair with zero balance: ${filename}`); 1086 | } 1087 | const files = fs.readdirSync(resolvedDir); 1088 | if (files.length === 0) { 1089 | fs.rmdirSync(resolvedDir); 1090 | console.log(`Deleted empty pair folder: ${resolvedDir}`); 1091 | } 1092 | } catch (err) { 1093 | console.error(`Error backing up or deleting file: ${filename}`, err); 1094 | } 1095 | } 1096 | 1097 | /** 1098 | * Return total transaction count for a given address. 1099 | */ 1100 | async function getTransactionCount(publicKey: PublicKey): Promise { 1101 | try { 1102 | const confirmedSignatures = await connection.getSignaturesForAddress(publicKey); 1103 | return confirmedSignatures.length; 1104 | } catch (err) { 1105 | console.error(`Error fetching transaction count for ${publicKey.toString()}`, err); 1106 | return 0; 1107 | } 1108 | } 1109 | 1110 | /** 1111 | * Loads all .json keypairs from a directory. 1112 | */ 1113 | function loadKeypairs(dirPath: string) { 1114 | const keypairs: Keypair[] = []; 1115 | const files = fs.readdirSync(dirPath); 1116 | 1117 | files.forEach((file) => { 1118 | if (file.endsWith(".json")) { 1119 | const filePath = path.join(dirPath, file); 1120 | const fileData = JSON.parse(fs.readFileSync(filePath, "utf8")); 1121 | const keypair = Keypair.fromSecretKey(new Uint8Array(fileData)); 1122 | keypairs.push(keypair); 1123 | } 1124 | }); 1125 | return keypairs; 1126 | } 1127 | 1128 | /** 1129 | * True if any .json keypair files exist in the directory. 1130 | */ 1131 | function checkKeypairsExist(dirPath: string) { 1132 | try { 1133 | if (!fs.existsSync(dirPath)) { 1134 | return false; 1135 | } 1136 | const files = fs.readdirSync(dirPath); 1137 | const keypairFiles = files.filter((file) => file.endsWith(".json")); 1138 | return keypairFiles.length > 0; 1139 | } catch (err) { 1140 | console.error("Error accessing the keypairs directory:", err); 1141 | return false; 1142 | } 1143 | } 1144 | 1145 | /** 1146 | * 1147 | * @param mint PublicKey 1148 | * @returns Token program ID 1149 | */ 1150 | async function getTokenProgramId(mint: PublicKey): Promise { 1151 | try { 1152 | // First check if it's a Token-2022 account 1153 | try { 1154 | const accountInfo = await connection.getAccountInfo(mint); 1155 | if (accountInfo) { 1156 | // Check the owner of the account 1157 | if (accountInfo.owner.equals(spl.TOKEN_2022_PROGRAM_ID)) { 1158 | console.log(`Mint ${mint.toBase58()} is a Token-2022 token`); 1159 | return spl.TOKEN_2022_PROGRAM_ID; 1160 | } 1161 | } 1162 | } catch (err: any) { 1163 | // If there's an error, default to classic SPL Token 1164 | console.log(`Error checking Token-2022 status: ${err.message}`); 1165 | } 1166 | 1167 | // Default to classic SPL Token 1168 | console.log(`Mint ${mint.toBase58()} is a classic SPL token`); 1169 | return spl.TOKEN_PROGRAM_ID; 1170 | } catch (error: any) { 1171 | console.error(`Error determining token program ID: ${error.message}`); 1172 | // Default to classic SPL Token 1173 | return spl.TOKEN_PROGRAM_ID; 1174 | } 1175 | } 1176 | 1177 | type Direction = "quoteToBase" | "baseToQuote"; 1178 | 1179 | export const sell_pump_amm = async (base_mint: PublicKey, keypair: Keypair, tokenAmount: number, wsolAmount: number) => { 1180 | try { 1181 | //let sellIxs; 1182 | let NATIVE_MINT = new PublicKey("So11111111111111111111111111111111111111112"); 1183 | console.log("base_mint", base_mint.toString()); 1184 | 1185 | const pool = await customSDK?.getPumpSwapPool(base_mint); 1186 | 1187 | if (!pool) { 1188 | console.log("No PumpSwap pool found for token:", base_mint.toString()); 1189 | return null; 1190 | } 1191 | 1192 | // Calculate amounts 1193 | const base_amt = new BN(Math.floor(tokenAmount * 10 ** 6)); // Convert to micro units 1194 | const sellTokenAmount = Math.floor(Number(base_amt.toString())); 1195 | console.log("sellTokenAmount:", sellTokenAmount); 1196 | 1197 | // Get the token accounts 1198 | const tokenAcc = await spl.getAssociatedTokenAddress(base_mint, keypair.publicKey); 1199 | const wsolAcc = await spl.getAssociatedTokenAddress(NATIVE_MINT, keypair.publicKey); 1200 | 1201 | // Build swap instruction 1202 | const sellIxs = await pSwap?.swapBaseInstructions( 1203 | pool, 1204 | new BN(sellTokenAmount), 1205 | 2.0, // Slippage tolerance 1206 | "baseToQuote" as Direction, 1207 | keypair.publicKey, // Using keypair's public key as owner 1208 | PROTOCOL_FEE_RECIPIENT_MAINNET, 1209 | tokenAcc, 1210 | wsolAcc 1211 | ); 1212 | //sellIxs = sellIx[1]; 1213 | 1214 | if (!sellIxs) { 1215 | console.log("Failed to create PumpSwap sell instructions"); 1216 | return null; 1217 | } 1218 | 1219 | // Return the array directly - this is the key change 1220 | return sellIxs; 1221 | } catch (error) { 1222 | console.log("Error in sell_pump_amm:", error); 1223 | return null; 1224 | } 1225 | }; 1226 | 1227 | export const closePumpSwapAcc = async (keypairs: Keypair[], baseMint: PublicKey) => { 1228 | for (let index = 0; index < keypairs.length; index++) { 1229 | const instructionsForChunk: TransactionInstruction[] = []; 1230 | const keypair = keypairs[index]; 1231 | const tokenAcc = await spl.getAssociatedTokenAddress(baseMint, keypair.publicKey); 1232 | const wsolAcc = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 1233 | 1234 | // Check if accounts exist 1235 | const accinfo = await connection.getAccountInfo(tokenAcc); 1236 | const wsolaccinfo = await connection.getAccountInfo(wsolAcc); 1237 | 1238 | if (accinfo) { 1239 | // Get token balance 1240 | const tokenBalance = await connection.getTokenAccountBalance(tokenAcc); 1241 | 1242 | // Create sell instruction 1243 | const sellInstruction = await sell_pump_amm(baseMint, keypair, Number(tokenBalance.value.uiAmountString), 0); 1244 | 1245 | if (!sellInstruction) { 1246 | console.log("Failed to create sell instructions for PumpSwap"); 1247 | continue; 1248 | } 1249 | 1250 | instructionsForChunk.push(...sellInstruction); 1251 | 1252 | // Burn token account 1253 | const baseMintTokenProgram = await getTokenProgramId(baseMint); 1254 | let baseTokenBurnInstruction = await burnAccount(wallet, keypair, connection, tokenAcc, baseMintTokenProgram); 1255 | instructionsForChunk.push(...baseTokenBurnInstruction); 1256 | } 1257 | 1258 | if (wsolaccinfo) { 1259 | // Burn WSOL account 1260 | let wsolBurnInstruction = await burnAccount(wallet, keypair, connection, wsolAcc, spl.TOKEN_PROGRAM_ID); 1261 | instructionsForChunk.push(...wsolBurnInstruction); 1262 | } 1263 | 1264 | // Handle SOL balance transfer 1265 | const balance = await connection.getBalance(keypair.publicKey); 1266 | const feeEstimate = 10000; 1267 | const transferAmount = balance - feeEstimate > 0 ? balance - feeEstimate : 0; 1268 | 1269 | if (transferAmount > 0) { 1270 | const drainBalanceIxn = SystemProgram.transfer({ 1271 | fromPubkey: keypair.publicKey, 1272 | toPubkey: wallet.publicKey, 1273 | lamports: transferAmount, 1274 | }); 1275 | instructionsForChunk.push(drainBalanceIxn); 1276 | } 1277 | 1278 | // Create and sign transaction 1279 | const addressesMain: PublicKey[] = []; 1280 | instructionsForChunk.forEach((ixn) => { 1281 | ixn.keys.forEach((key) => { 1282 | addressesMain.push(key.pubkey); 1283 | }); 1284 | }); 1285 | 1286 | const lookupTablesMain = lookupTableProvider.computeIdealLookupTablesForAddresses(addressesMain); 1287 | let blockhash = (await retryOperation(() => connection.getLatestBlockhash())).blockhash; 1288 | 1289 | const message = new TransactionMessage({ 1290 | payerKey: keypair.publicKey, 1291 | recentBlockhash: blockhash, 1292 | instructions: instructionsForChunk, 1293 | }).compileToV0Message(lookupTablesMain); 1294 | 1295 | const versionedTx = new VersionedTransaction(message); 1296 | versionedTx.sign([wallet, keypair]); // Sign with both wallet and keypair 1297 | 1298 | try { 1299 | // Simulate before sending 1300 | const swapsim = await connection.simulateTransaction(versionedTx, { sigVerify: true }); 1301 | 1302 | if (swapsim.value.err) { 1303 | console.log("Transaction simulation failed:", swapsim.value.err); 1304 | continue; 1305 | } 1306 | 1307 | const swapId = await connection.sendTransaction(versionedTx); 1308 | const lockPoolConfirm = await connection.confirmTransaction(swapId); 1309 | console.log(`Transaction confirmed: ${swapId}`); 1310 | } catch (e) { 1311 | console.log("Error with versionedTx:", e); 1312 | } 1313 | } 1314 | }; 1315 | --------------------------------------------------------------------------------