├── .gitignore ├── settings.json ├── config.example.env ├── package.json ├── src ├── types │ └── index.ts ├── constants │ └── constants.ts ├── layout │ ├── sell.ts │ └── buy.ts ├── jito │ └── bundle.ts ├── menu │ └── index.ts └── utils │ └── config.ts ├── README.md └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | /dist 4 | 5 | /settings.json -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "mint": "fRfKGCriduzDwSudCwpL7ySCEiboNuryhZDVJtr1a1C", 3 | "poolId": "7bDv4GUJwdbUsw1mBCM3iTrcMByjzyuTF4j72qQbY5Sz", 4 | "amount": "0.00001", 5 | "slippage": "50" 6 | } 7 | -------------------------------------------------------------------------------- /config.example.env: -------------------------------------------------------------------------------- 1 | # Solana Trading Bot Configuration 2 | # Copy this file to .env and configure your settings 3 | 4 | # Wallet Configuration 5 | PRIVATE_KEY=your_private_key_here 6 | RPC_ENDPOINT=https://api.mainnet-beta.solana.com 7 | RPC_WEBSOCKET_ENDPOINT=wss://api.mainnet-beta.solana.com 8 | 9 | # Trading Configuration 10 | BUY_AMOUNT=0.01 11 | SLIPPAGE=50 12 | SELL_TIMER=30000 13 | STOP_LOSS=10 14 | 15 | # Market Cap Monitoring 16 | LOWER_MC_INTERVAL=5 17 | HIGHER_MC_INTERVAL=10 18 | LOWER_TP_INTERVAL=5 19 | HIGHER_TP_INTERVAL=10 20 | 21 | # DEX Platform Configuration 22 | DEX_PLATFORM=meteora 23 | TRADING_STRATEGY=market_cap_monitor 24 | 25 | # Jito Configuration (Optional) 26 | JITO_KEY=your_jito_key 27 | JITO_TIP=1000000 28 | BLOCKENGINE_URL=ny.mainnet.block-engine.jito.wtf 29 | USE_JITO=false 30 | 31 | # Price Feed Configuration 32 | PRICE_FEED_URL=https://api.jup.ag/price/v2 33 | 34 | # Logging Configuration 35 | LOG_LEVEL=info 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solana-trading-bot", 3 | "version": "2.0.0", 4 | "main": "index.js", 5 | "types": "module", 6 | "author": "trading-bot", 7 | "scripts": { 8 | "dev": "ts-node index.ts", 9 | "build": "tsc", 10 | "start": "ts-node index.ts" 11 | }, 12 | "keywords": [ 13 | "solana", 14 | "trading", 15 | "bot", 16 | "defi", 17 | "dex", 18 | "automated-trading", 19 | "meteora", 20 | "dlmm", 21 | "sdk", 22 | "node", 23 | "module" 24 | ], 25 | "license": "ISC", 26 | "description": "A generic Solana trading bot supporting multiple DEX platforms with automated buy/sell functionality", 27 | "dependencies": { 28 | "@meteora-ag/dlmm": "^1.4.11", 29 | "@solana/spl-token": "^0.4.13", 30 | "@solana/web3.js": "^1.98.0", 31 | "bs58": "^5.0.0", 32 | "dotenv": "^16.4.1", 33 | "jito-ts": "^4.2.0", 34 | "pino": "^8.18.0", 35 | "pino-pretty": "^10.3.1", 36 | "pino-std-serializers": "^6.2.2" 37 | }, 38 | "devDependencies": { 39 | "@types/bn.js": "^5.1.6", 40 | "prettier": "^3.2.4", 41 | "ts-node": "^10.9.2", 42 | "typescript": "^5.8.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | // Trading Direction Types 2 | export type Direction = "quoteToBase" | "baseToQuote" 3 | export type TradeType = "buy" | "sell" 4 | 5 | // DEX Platform Types 6 | export type DEXPlatform = "meteora" | "raydium" | "orca" | "jupiter" 7 | 8 | // Trading Strategy Types 9 | export type TradingStrategy = "market_cap_monitor" | "price_monitor" | "manual" 10 | 11 | // Configuration Types 12 | export interface TradingConfig { 13 | mint: string 14 | poolId: string 15 | amount: number 16 | slippage: number 17 | isPump?: boolean 18 | platform?: DEXPlatform 19 | } 20 | 21 | export interface MarketCapConfig { 22 | lowerInterval: number 23 | higherInterval: number 24 | lowerTPInterval: number 25 | higherTPInterval: number 26 | } 27 | 28 | export interface TradingParams { 29 | buyAmount: number 30 | sellTimer: number 31 | stopLoss: number 32 | slippage: number 33 | } 34 | 35 | // Price Feed Types 36 | export interface PriceData { 37 | price: number 38 | timestamp: number 39 | source: string 40 | } 41 | 42 | export interface TokenInfo { 43 | mint: string 44 | symbol?: string 45 | name?: string 46 | decimals: number 47 | supply?: number 48 | } 49 | 50 | // Trading Result Types 51 | export interface TradeResult { 52 | success: boolean 53 | signature?: string 54 | error?: string 55 | amountIn: number 56 | amountOut: number 57 | timestamp: number 58 | } 59 | 60 | // Monitoring Types 61 | export interface MarketCapData { 62 | current: number 63 | lower: number 64 | higher: number 65 | change: number 66 | timestamp: number 67 | } 68 | 69 | export interface PnLData { 70 | current: number 71 | percentage: number 72 | takeProfit: number 73 | stopLoss: number 74 | timestamp: number 75 | } -------------------------------------------------------------------------------- /src/constants/constants.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js" 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | // Wallet Configuration 7 | export const PRIVATE_KEY = process.env.PRIVATE_KEY 8 | export const RPC_ENDPOINT = process.env.RPC_ENDPOINT || "https://api.mainnet-beta.solana.com" 9 | export const RPC_WEBSOCKET_ENDPOINT = process.env.RPC_WEBSOCKET_ENDPOINT 10 | export const solanaConnection = new Connection(RPC_ENDPOINT, "confirmed") 11 | 12 | // Trading Configuration 13 | export const SELL_TIMER = Number(process.env.SELL_TIMER) || 30000 14 | export const STOP_LOSS = Number(process.env.STOP_LOSS) || 10 15 | export const BUY_AMOUNT = Number(process.env.BUY_AMOUNT) || 0.01 16 | export const SLIPPAGE = Number(process.env.SLIPPAGE) || 50 17 | 18 | // Market Cap Monitoring 19 | export const LOWER_MC_INTERVAL = Number(process.env.LOWER_MC_INTERVAL) || 5 20 | export const HIGHER_MC_INTERVAL = Number(process.env.HIGHER_MC_INTERVAL) || 10 21 | export const LOWER_TP_INTERVAL = Number(process.env.LOWER_TP_INTERVAL) || 5 22 | export const HIGHER_TP_INTERVAL = Number(process.env.HIGHER_TP_INTERVAL) || 10 23 | 24 | // Jito Configuration (Optional) 25 | export const BLOCKENGINE_URL = process.env.BLOCKENGINE_URL || "ny.mainnet.block-engine.jito.wtf" 26 | export const JITO_KEY = process.env.JITO_KEY 27 | export const JITO_TIP = Number(process.env.JITO_TIP) || 1000000 28 | 29 | // DEX Platform Configuration 30 | export const DEX_PLATFORM = process.env.DEX_PLATFORM || "meteora" 31 | export const USE_JITO = process.env.USE_JITO === "true" || false 32 | 33 | // Logging Configuration 34 | export const LOG_LEVEL = process.env.LOG_LEVEL || "info" 35 | 36 | // Generic DEX Constants 37 | export const GLOBAL_CONFIG_SEED = 'global_config' 38 | export const LP_MINT_SEED = 'pool_lp_mint' 39 | export const POOL_SEED = 'pool' 40 | 41 | // Protocol Fee Recipients (can be configured per platform) 42 | export const PROTOCOL_FEE_RECIPIENTS = { 43 | meteora: new PublicKey("12e2F4DKkD3Lff6WPYsU7Xd76SHPEyN9T8XSsTJNF8oT"), 44 | mainnet: new PublicKey("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX") 45 | } 46 | 47 | // Price Feed Configuration 48 | export const PRICE_FEED_URL = process.env.PRICE_FEED_URL || "https://api.jup.ag/price/v2" -------------------------------------------------------------------------------- /src/layout/sell.ts: -------------------------------------------------------------------------------- 1 | import { LAMPORTS_PER_SOL, PublicKey, Transaction } from "@solana/web3.js" 2 | import { solanaConnection, DEX_PLATFORM } from "../constants" 3 | import { mainKp, swap } from "./buy" 4 | import { logger, readSettings } from "../utils" 5 | import { fetchDLMMPoolId, mainMenuWaiting } from "../.." 6 | import { getAssociatedTokenAddress, NATIVE_MINT } from "@solana/spl-token" 7 | import { BN } from "bn.js" 8 | import DLMM from "@meteora-ag/dlmm" 9 | import { TradeResult } from "../types" 10 | 11 | export const sell_token = async (): Promise => { 12 | const solBalance = (await solanaConnection.getBalance(mainKp.publicKey)) / LAMPORTS_PER_SOL 13 | 14 | logger.info(`Starting token sell operation`); 15 | logger.info(`Wallet address: ${mainKp.publicKey.toBase58()}`); 16 | logger.info(`SOL balance: ${solBalance.toFixed(6)} SOL`); 17 | 18 | try { 19 | const settings = readSettings(); 20 | const slippage = Number(settings.slippage!); 21 | const TOKEN_CA = new PublicKey(settings.mint!); 22 | 23 | // Get pool ID based on DEX platform 24 | let poolId: string; 25 | if (DEX_PLATFORM === "meteora") { 26 | poolId = await fetchDLMMPoolId(settings.mint!); 27 | if (!poolId) { 28 | throw new Error("Could not find pool ID for the token"); 29 | } 30 | } else { 31 | // For other platforms, use the configured pool ID 32 | poolId = settings.poolId!; 33 | } 34 | 35 | logger.info(`Using pool ID: ${poolId}`); 36 | const POOL_ID = new PublicKey(poolId); 37 | 38 | // Create DEX pool instance 39 | const dlmmPool = await DLMM.create(solanaConnection, POOL_ID); 40 | const ata = await getAssociatedTokenAddress(TOKEN_CA, mainKp.publicKey); 41 | 42 | logger.info(`Token account address: ${ata.toBase58()}`); 43 | 44 | // Get token balance 45 | const tokenInfo = await solanaConnection.getTokenAccountBalance(ata); 46 | const tokenAmount = Number(tokenInfo.value.amount!); 47 | 48 | if (tokenAmount === 0) { 49 | logger.warn("No tokens to sell"); 50 | return { 51 | success: false, 52 | error: "No tokens to sell", 53 | amountIn: 0, 54 | amountOut: 0, 55 | timestamp: Date.now() 56 | }; 57 | } 58 | 59 | logger.info(`Selling ${tokenAmount} tokens`); 60 | 61 | // Execute sell swap 62 | const result = await swap(dlmmPool, POOL_ID, TOKEN_CA, NATIVE_MINT, new BN(tokenAmount), slippage, mainKp); 63 | 64 | if (result.success) { 65 | logger.info(`Sell transaction successful: ${result.signature}`); 66 | } else { 67 | logger.error(`Sell transaction failed: ${result.error}`); 68 | } 69 | 70 | return result; 71 | 72 | } catch (error: any) { 73 | logger.error("Sell operation failed:", error); 74 | return { 75 | success: false, 76 | error: error.message, 77 | amountIn: 0, 78 | amountOut: 0, 79 | timestamp: Date.now() 80 | }; 81 | } finally { 82 | mainMenuWaiting(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/jito/bundle.ts: -------------------------------------------------------------------------------- 1 | import bs58 from 'bs58'; 2 | 3 | import { logger } from '../utils/logger'; 4 | 5 | // import { newTokenTimestampPerf } from '../streaming/raydium'; 6 | 7 | import { 8 | PRIVATE_KEY, 9 | BLOCKENGINE_URL, 10 | JITO_TIP, 11 | RPC_ENDPOINT, 12 | RPC_WEBSOCKET_ENDPOINT 13 | } from '../constants'; 14 | 15 | import { 16 | PublicKey, 17 | Keypair, 18 | VersionedTransaction, 19 | MessageV0, 20 | Connection 21 | } from '@solana/web3.js'; 22 | 23 | import { Bundle } from 'jito-ts/dist/sdk/block-engine/types'; 24 | import { searcherClient } from 'jito-ts/dist/sdk/block-engine/searcher'; 25 | 26 | const SIGNER_WALLET = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY!)); 27 | 28 | const blockEngineUrl = BLOCKENGINE_URL || ''; 29 | // console.log('BLOCK_ENGINE_URL:', blockEngineUrl); 30 | const c = searcherClient(blockEngineUrl, undefined); 31 | 32 | const solanaConnection = new Connection(RPC_ENDPOINT, { 33 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, commitment: "processed" 34 | }) 35 | 36 | // Get Tip Accounts 37 | let tipAccounts: string[] = []; 38 | (async () => { 39 | try { 40 | const result = await c.getTipAccounts(); // Result 41 | if (result.ok) { 42 | tipAccounts = result.value; // Access the successful value 43 | // console.log('Result:', tipAccounts); 44 | } else { 45 | console.error('Error:', result.error); // Access the error 46 | } 47 | } catch (error) { 48 | console.error('Unexpected Error:', error); // Handle unexpected errors 49 | } 50 | })(); 51 | 52 | export async function sendBundle(poolId: string, latestBlockhash: string, message: MessageV0, mint: PublicKey) { 53 | 54 | try { 55 | 56 | const transaction = new VersionedTransaction(message); 57 | 58 | transaction.sign([SIGNER_WALLET]); 59 | 60 | console.log("simulation => ", await solanaConnection.simulateTransaction(transaction, { sigVerify: true })) 61 | 62 | const _tipAccount = tipAccounts[Math.floor(Math.random() * 6)]; 63 | const tipAccount = new PublicKey(_tipAccount); 64 | const tipAmount: number = Number(JITO_TIP); 65 | 66 | 67 | const b = new Bundle([transaction], 2); 68 | b.addTipTx( 69 | SIGNER_WALLET, 70 | tipAmount, // Adjust Jito tip amount here 71 | tipAccount, 72 | latestBlockhash 73 | ); 74 | 75 | 76 | const bundleResult = await c.sendBundle(b); 77 | 78 | const bundleTimestampPerf = performance.now() 79 | const bundleTimestampDate = new Date(); 80 | const bundleTimeFormatted = `${bundleTimestampDate.getHours().toString().padStart(2, '0')}:${bundleTimestampDate.getMinutes().toString().padStart(2, '0')}:${bundleTimestampDate.getSeconds().toString().padStart(2, '0')}.${bundleTimestampDate.getMilliseconds().toString().padStart(3, '0')}`; 81 | // const elapsedStreamToBundlePerf = bundleTimestampPerf - newTokenTimestampPerf; 82 | 83 | // logger.warn(`Time Elapsed (Streamed > Bundle send): ${elapsedStreamToBundlePerf}ms`); 84 | logger.info(`Time bundle sent: ${bundleTimeFormatted} | bundleResult = ${bundleResult}`); 85 | logger.info(`https://dexscreener.com/solana/${poolId}?maker=${SIGNER_WALLET.publicKey}`); 86 | logger.info(`https://dexscreener.com/solana/${poolId}`); 87 | 88 | } catch (error) { 89 | logger.error(error); 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/menu/index.ts: -------------------------------------------------------------------------------- 1 | import readline from "readline" 2 | 3 | export const rl = readline.createInterface({ 4 | input: process.stdin, 5 | output: process.stdout 6 | }) 7 | 8 | export const screen_clear = () => { 9 | console.clear(); 10 | } 11 | 12 | export const main_menu_display = () => { 13 | console.log('\n╔══════════════════════════════════════════════════════════════╗'); 14 | console.log('║ Solana Trading Bot ║'); 15 | console.log('╚══════════════════════════════════════════════════════════════╝\n'); 16 | console.log('\t[1] - Show Current Settings'); 17 | console.log('\t[2] - Configure Settings'); 18 | console.log('\t[3] - Start Automated Trading'); 19 | console.log('\t[4] - Manual Sell Token'); 20 | console.log('\t[5] - Show Wallet Balance'); 21 | console.log('\t[6] - Exit'); 22 | console.log('\n' + '═'.repeat(70)); 23 | } 24 | 25 | export const settings_display = () => { 26 | console.log('\n╔══════════════════════════════════════════════════════════════╗'); 27 | console.log('║ Configuration ║'); 28 | console.log('╚══════════════════════════════════════════════════════════════╝\n'); 29 | console.log('\t[1] - Token Contract Address'); 30 | console.log('\t[2] - Pool ID'); 31 | console.log('\t[3] - Trading Amount (SOL)'); 32 | console.log('\t[4] - Slippage (%)'); 33 | console.log('\t[5] - DEX Platform'); 34 | console.log('\t[6] - Market Cap Settings'); 35 | console.log('\t[7] - Trading Strategy'); 36 | console.log('\t[8] - Back to Main Menu'); 37 | console.log('\t[9] - Exit'); 38 | console.log('\n' + '═'.repeat(70)); 39 | } 40 | 41 | export const market_cap_settings_display = () => { 42 | console.log('\n╔══════════════════════════════════════════════════════════════╗'); 43 | console.log('║ Market Cap Settings ║'); 44 | console.log('╚══════════════════════════════════════════════════════════════╝\n'); 45 | console.log('\t[1] - Lower MC Interval (%)'); 46 | console.log('\t[2] - Higher MC Interval (%)'); 47 | console.log('\t[3] - Lower Take Profit Interval (%)'); 48 | console.log('\t[4] - Higher Take Profit Interval (%)'); 49 | console.log('\t[5] - Stop Loss (%)'); 50 | console.log('\t[6] - Sell Timer (ms)'); 51 | console.log('\t[7] - Back to Settings'); 52 | console.log('\t[8] - Exit'); 53 | console.log('\n' + '═'.repeat(70)); 54 | } 55 | 56 | export const dex_platform_display = () => { 57 | console.log('\n╔══════════════════════════════════════════════════════════════╗'); 58 | console.log('║ DEX Platform Selection ║'); 59 | console.log('╚══════════════════════════════════════════════════════════════╝\n'); 60 | console.log('\t[1] - Meteora DLMM'); 61 | console.log('\t[2] - Raydium'); 62 | console.log('\t[3] - Orca'); 63 | console.log('\t[4] - Jupiter Aggregator'); 64 | console.log('\t[5] - Back to Settings'); 65 | console.log('\t[6] - Exit'); 66 | console.log('\n' + '═'.repeat(70)); 67 | } 68 | 69 | export const trading_strategy_display = () => { 70 | console.log('\n╔══════════════════════════════════════════════════════════════╗'); 71 | console.log('║ Trading Strategy ║'); 72 | console.log('╚══════════════════════════════════════════════════════════════╝\n'); 73 | console.log('\t[1] - Market Cap Monitoring'); 74 | console.log('\t[2] - Price Monitoring'); 75 | console.log('\t[3] - Manual Trading'); 76 | console.log('\t[4] - Back to Settings'); 77 | console.log('\t[5] - Exit'); 78 | console.log('\n' + '═'.repeat(70)); 79 | } 80 | 81 | export const display_welcome = () => { 82 | console.log('\n╔══════════════════════════════════════════════════════════════╗'); 83 | console.log('║ ║'); 84 | console.log('║ Welcome to Solana Trading Bot ║'); 85 | console.log('║ ║'); 86 | console.log('║ Advanced automated trading for Solana DEXs ║'); 87 | console.log('║ ║'); 88 | console.log('╚══════════════════════════════════════════════════════════════╝\n'); 89 | } 90 | 91 | export const display_error = (message: string) => { 92 | console.log('\n❌ Error:', message); 93 | console.log('═'.repeat(70)); 94 | } 95 | 96 | export const display_success = (message: string) => { 97 | console.log('\n✅ Success:', message); 98 | console.log('═'.repeat(70)); 99 | } 100 | 101 | export const display_info = (message: string) => { 102 | console.log('\nℹ️ Info:', message); 103 | console.log('═'.repeat(70)); 104 | } -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { readFileSync, writeFileSync, existsSync } from "fs"; 3 | import { TradingConfig, DEXPlatform, TradingStrategy } from "../types"; 4 | 5 | const CONFIG_FILE = "settings.json"; 6 | const ENV_FILE = ".env"; 7 | 8 | export interface BotConfig { 9 | trading: TradingConfig; 10 | platform: DEXPlatform; 11 | strategy: TradingStrategy; 12 | marketCap: { 13 | lowerInterval: number; 14 | higherInterval: number; 15 | lowerTPInterval: number; 16 | higherTPInterval: number; 17 | }; 18 | tradingParams: { 19 | buyAmount: number; 20 | sellTimer: number; 21 | stopLoss: number; 22 | slippage: number; 23 | }; 24 | } 25 | 26 | export const defaultConfig: BotConfig = { 27 | trading: { 28 | mint: "", 29 | poolId: "", 30 | amount: 0.01, 31 | slippage: 50, 32 | isPump: false, 33 | platform: "meteora" 34 | }, 35 | platform: "meteora", 36 | strategy: "market_cap_monitor", 37 | marketCap: { 38 | lowerInterval: 5, 39 | higherInterval: 10, 40 | lowerTPInterval: 5, 41 | higherTPInterval: 10 42 | }, 43 | tradingParams: { 44 | buyAmount: 0.01, 45 | sellTimer: 30000, 46 | stopLoss: 10, 47 | slippage: 50 48 | } 49 | }; 50 | 51 | export const loadConfig = (): BotConfig => { 52 | try { 53 | if (existsSync(CONFIG_FILE)) { 54 | const configData = readFileSync(CONFIG_FILE, "utf8"); 55 | const parsed = JSON.parse(configData); 56 | return { ...defaultConfig, ...parsed }; 57 | } 58 | } catch (error) { 59 | console.warn("Failed to load config file, using defaults:", error); 60 | } 61 | return defaultConfig; 62 | }; 63 | 64 | export const saveConfig = (config: BotConfig): void => { 65 | try { 66 | writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); 67 | } catch (error) { 68 | console.error("Failed to save config:", error); 69 | } 70 | }; 71 | 72 | export const loadEnvConfig = (): Partial => { 73 | const envConfig: Partial = {}; 74 | 75 | try { 76 | if (existsSync(ENV_FILE)) { 77 | const envData = readFileSync(ENV_FILE, "utf8"); 78 | const lines = envData.split("\n"); 79 | 80 | for (const line of lines) { 81 | const [key, value] = line.split("="); 82 | if (key && value) { 83 | switch (key.trim()) { 84 | case "BUY_AMOUNT": 85 | envConfig.tradingParams = { ...envConfig.tradingParams, buyAmount: Number(value) }; 86 | break; 87 | case "SLIPPAGE": 88 | envConfig.tradingParams = { ...envConfig.tradingParams, slippage: Number(value) }; 89 | break; 90 | case "SELL_TIMER": 91 | envConfig.tradingParams = { ...envConfig.tradingParams, sellTimer: Number(value) }; 92 | break; 93 | case "STOP_LOSS": 94 | envConfig.tradingParams = { ...envConfig.tradingParams, stopLoss: Number(value) }; 95 | break; 96 | case "LOWER_MC_INTERVAL": 97 | envConfig.marketCap = { ...envConfig.marketCap, lowerInterval: Number(value) }; 98 | break; 99 | case "HIGHER_MC_INTERVAL": 100 | envConfig.marketCap = { ...envConfig.marketCap, higherInterval: Number(value) }; 101 | break; 102 | case "LOWER_TP_INTERVAL": 103 | envConfig.marketCap = { ...envConfig.marketCap, lowerTPInterval: Number(value) }; 104 | break; 105 | case "HIGHER_TP_INTERVAL": 106 | envConfig.marketCap = { ...envConfig.marketCap, higherTPInterval: Number(value) }; 107 | break; 108 | case "DEX_PLATFORM": 109 | envConfig.platform = value.trim() as DEXPlatform; 110 | break; 111 | case "TRADING_STRATEGY": 112 | envConfig.strategy = value.trim() as TradingStrategy; 113 | break; 114 | } 115 | } 116 | } 117 | } 118 | } catch (error) { 119 | console.warn("Failed to load environment config:", error); 120 | } 121 | 122 | return envConfig; 123 | }; 124 | 125 | export const mergeConfigs = (base: BotConfig, env: Partial): BotConfig => { 126 | return { 127 | ...base, 128 | ...env, 129 | trading: { ...base.trading, ...env.trading }, 130 | marketCap: { ...base.marketCap, ...env.marketCap }, 131 | tradingParams: { ...base.tradingParams, ...env.tradingParams } 132 | }; 133 | }; 134 | 135 | export const validateConfig = (config: BotConfig): string[] => { 136 | const errors: string[] = []; 137 | 138 | if (!config.trading.mint) { 139 | errors.push("Token mint address is required"); 140 | } 141 | 142 | if (!config.trading.poolId && config.platform !== "meteora") { 143 | errors.push("Pool ID is required for non-Meteora platforms"); 144 | } 145 | 146 | if (config.trading.amount <= 0) { 147 | errors.push("Trading amount must be greater than 0"); 148 | } 149 | 150 | if (config.trading.slippage < 0 || config.trading.slippage > 100) { 151 | errors.push("Slippage must be between 0 and 100"); 152 | } 153 | 154 | if (config.tradingParams.buyAmount <= 0) { 155 | errors.push("Buy amount must be greater than 0"); 156 | } 157 | 158 | if (config.tradingParams.sellTimer <= 0) { 159 | errors.push("Sell timer must be greater than 0"); 160 | } 161 | 162 | if (config.tradingParams.stopLoss < 0 || config.tradingParams.stopLoss > 100) { 163 | errors.push("Stop loss must be between 0 and 100"); 164 | } 165 | 166 | return errors; 167 | }; 168 | 169 | export const getConfig = (): BotConfig => { 170 | const baseConfig = loadConfig(); 171 | const envConfig = loadEnvConfig(); 172 | const mergedConfig = mergeConfigs(baseConfig, envConfig); 173 | 174 | const errors = validateConfig(mergedConfig); 175 | if (errors.length > 0) { 176 | console.warn("Configuration validation warnings:"); 177 | errors.forEach(error => console.warn(`- ${error}`)); 178 | } 179 | 180 | return mergedConfig; 181 | }; 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PumpFun Comment Bot 2 | 3 | An advanced automated comment bot for PumpFun platform that helps increase engagement and visibility for your token projects. This bot provides intelligent commenting strategies with customizable templates and timing controls. 4 | 5 | ## 📱 Social Media 6 | 7 | ### Stay Connected 8 | | Platform | Link | Purpose | 9 | |----------|------|---------| 10 | | Telegram | [t.me/FroganBee.sol](https://t.me/froganbee_sol) | Announcements & Support | 11 | | X | [x.com/FroganBee.sol](https://x.com/froganbee_sol) | News & Updates | 12 | 13 | ## 📹 Sample Videos 14 | 15 | Check out these demonstration videos to see the bot in action: 16 | 17 | - [PumpFun Comment Bot Demo 1](https://drive.google.com/file/d/12cG11VyLE6QJ4brn6ijQSG1RTurBAqwY/view?usp=drive_link) 18 | - [PumpFun Comment Bot Demo 2](https://drive.google.com/file/d/1XdBldd3p7P6iQfXJ6y13gSLwdjxYmmND/view?usp=drive_link) 19 | 20 | ## Features 21 | 22 | - **Automated Commenting**: Post comments automatically on PumpFun token pages 23 | - **Smart Timing**: Configurable comment intervals and timing strategies 24 | - **Template System**: Customizable comment templates with dynamic content 25 | - **Multi-Account Support**: Manage multiple accounts for increased engagement 26 | - **Anti-Detection**: Built-in features to avoid detection and maintain account safety 27 | - **Analytics**: Track comment performance and engagement metrics 28 | - **Customizable Settings**: Fine-tune bot behavior and appearance 29 | - **Easy Configuration**: Simple setup with user-friendly interface 30 | 31 | ## Supported Platforms 32 | 33 | - PumpFun (Primary platform) 34 | - Compatible with Solana-based token projects 35 | - Extensible for other social platforms 36 | 37 | ## Environment Variables 38 | 39 | Create a `.env` file in the root directory with the following variables: 40 | 41 | ```env 42 | # PumpFun Configuration 43 | PUMPFUN_API_URL=https://frontend-api.pump.fun 44 | PUMPFUN_WEBSOCKET_URL=wss://frontend-api.pump.fun 45 | 46 | # Bot Configuration 47 | COMMENT_INTERVAL=30000 48 | MAX_COMMENTS_PER_HOUR=20 49 | ACCOUNT_DELAY_MIN=5000 50 | ACCOUNT_DELAY_MAX=15000 51 | 52 | # Comment Templates 53 | COMMENT_TEMPLATES=template1,template2,template3 54 | RANDOMIZE_TEMPLATES=true 55 | 56 | # Account Management 57 | ACCOUNTS_FILE=accounts.json 58 | ACCOUNT_ROTATION=true 59 | 60 | # Safety Settings 61 | ANTI_DETECTION=true 62 | HUMAN_LIKE_DELAYS=true 63 | RANDOM_TYPING_SPEED=true 64 | 65 | # Logging 66 | LOG_LEVEL=info 67 | ``` 68 | 69 | ## Installation 70 | 71 | 1. Clone the repository 72 | ```bash 73 | git clone 74 | cd pumpfun-comment-bot 75 | ``` 76 | 77 | 2. Install dependencies 78 | ```bash 79 | npm install 80 | ``` 81 | 82 | 3. Configure environment variables 83 | ```bash 84 | cp config.example.env .env 85 | # Edit .env with your configuration 86 | ``` 87 | 88 | 4. Set up accounts 89 | ```bash 90 | # Create accounts.json file with your PumpFun accounts 91 | # See accounts.example.json for format 92 | ``` 93 | 94 | 5. Build the project 95 | ```bash 96 | npm run build 97 | ``` 98 | 99 | ## Usage 100 | 101 | ### Start the bot 102 | ```bash 103 | npm start 104 | ``` 105 | 106 | ### Development mode 107 | ```bash 108 | npm run dev 109 | ``` 110 | 111 | ### Interactive Menu 112 | 113 | The bot provides an interactive CLI with the following options: 114 | 115 | 1. **Show Current Settings** - Display current configuration 116 | 2. **Settings** - Configure bot parameters 117 | - Comment templates 118 | - Timing intervals 119 | - Account management 120 | - Safety settings 121 | 3. **Start Commenting** - Begin automated commenting 122 | 4. **Manage Accounts** - Add/remove accounts 123 | 5. **View Analytics** - Check comment performance 124 | 6. **Exit** - Close the application 125 | 126 | ## Comment Strategy 127 | 128 | ### Smart Commenting 129 | - Automatically posts comments on PumpFun token pages 130 | - Uses configurable templates with dynamic content 131 | - Implements human-like timing and behavior patterns 132 | 133 | ### Account Management 134 | - Multi-account support for increased engagement 135 | - Account rotation to avoid detection 136 | - Random delays between account switches 137 | 138 | ### Safety Features 139 | - Anti-detection mechanisms 140 | - Human-like typing speeds 141 | - Randomized comment intervals 142 | - Account cooldown periods 143 | 144 | ## Configuration 145 | 146 | ### Settings File 147 | The bot uses `settings.json` to store bot parameters: 148 | 149 | ```json 150 | { 151 | "commentTemplates": [ 152 | "Great project! 🚀", 153 | "This looks promising! 💎", 154 | "Amazing work team! 🔥" 155 | ], 156 | "commentInterval": 30000, 157 | "maxCommentsPerHour": 20, 158 | "accountRotation": true 159 | } 160 | ``` 161 | 162 | ### Bot Parameters 163 | - **Comment Templates**: Predefined comment messages 164 | - **Timing Intervals**: Delay between comments 165 | - **Account Management**: Multi-account configuration 166 | - **Safety Settings**: Anti-detection parameters 167 | 168 | ## Architecture 169 | 170 | ``` 171 | src/ 172 | ├── constants/ # Configuration constants 173 | ├── layout/ # Comment posting logic 174 | ├── menu/ # CLI interface 175 | ├── types/ # TypeScript type definitions 176 | ├── utils/ # Utility functions 177 | ├── templates/ # Comment templates 178 | └── accounts/ # Account management 179 | ``` 180 | 181 | ## Dependencies 182 | 183 | - **axios**: HTTP requests to PumpFun API 184 | - **ws**: WebSocket connections 185 | - **pino**: Structured logging 186 | - **chalk**: Terminal styling 187 | - **inquirer**: Interactive prompts 188 | 189 | ## Contributing 190 | 191 | 1. Fork the repository 192 | 2. Create a feature branch 193 | 3. Make your changes 194 | 4. Add tests if applicable 195 | 5. Submit a pull request 196 | 197 | ## License 198 | 199 | ISC License - see LICENSE file for details 200 | 201 | ## Disclaimer 202 | 203 | This software is for educational and research purposes only. Trading cryptocurrencies involves substantial risk of loss and is not suitable for all investors. The past performance of any trading system or methodology is not necessarily indicative of future results. 204 | 205 | ## Support 206 | 207 | For issues and questions: 208 | - Create an issue in the repository 209 | - Check the documentation 210 | - Review the configuration examples 211 | 212 | ## Version History 213 | 214 | - **v2.0.0**: PumpFun Comment Bot with advanced features 215 | - **v1.x**: Basic commenting functionality 216 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; 2 | import { 3 | main_menu_display, 4 | rl, 5 | screen_clear, 6 | settings_display, 7 | market_cap_settings_display, 8 | dex_platform_display, 9 | trading_strategy_display, 10 | display_welcome, 11 | display_error, 12 | display_success, 13 | display_info 14 | } from "./src/menu"; 15 | import { readSettings, saveSettingsToFile, sleep } from "./src/utils/utils"; 16 | import { swap, buy_monitor_autosell, mainKp } from "./src/layout/buy"; 17 | import { sell_token } from "./src/layout/sell"; 18 | import { solanaConnection, DEX_PLATFORM, PRICE_FEED_URL } from "./src/constants"; 19 | import DLMM from '@meteora-ag/dlmm' 20 | import { BN } from "bn.js"; 21 | import { NATIVE_MINT } from "@solana/spl-token"; 22 | import { DEXPlatform, TradingStrategy } from "./src/types"; 23 | 24 | export async function fetchDLMMPoolId(tokenAddress: string) { 25 | const url = `https://dlmm-api.meteora.ag/pair/all_by_groups?sort_key=tvl&order_by=desc&search_term=${tokenAddress}`; 26 | // console.log(url); 27 | const response = await (await fetch(url)).json(); 28 | 29 | const listOfGroups = response.groups; 30 | for (const group of listOfGroups) { 31 | for (const pair of group.pairs) { 32 | if (pair.mint_x === tokenAddress || pair.mint_y === tokenAddress) { 33 | console.log("Found matching pool address:", pair.address); 34 | return pair.address; 35 | } 36 | } 37 | } 38 | 39 | return ""; 40 | } 41 | 42 | const testBuy = async () => { 43 | const data = readSettings(); 44 | const BUY_AMOUNT = Number(data.amount); // Convert to lamports 45 | const TOKEN_CA = new PublicKey(data.mint!); 46 | // const POOL_ID = new PublicKey(data.poolId!); 47 | 48 | const poolId = await fetchDLMMPoolId(data.mint!); 49 | console.log("🚀 ~ testBuy ~ POOL_ID:", poolId) 50 | const POOL_ID = new PublicKey(poolId); 51 | 52 | const dlmmPool = await DLMM.create(solanaConnection, POOL_ID); 53 | console.log("🚀 ~ testBuy ~ dlmmPool:", dlmmPool) 54 | 55 | const result = await swap(dlmmPool, POOL_ID, NATIVE_MINT, TOKEN_CA, new BN(BUY_AMOUNT * 10 ** 9), 1000, mainKp); 56 | console.log("🚀 ~ testBuy ~ result:", result) 57 | } 58 | 59 | export const init = () => { 60 | screen_clear(); 61 | display_welcome(); 62 | main_menu_display(); 63 | 64 | rl.question("\t[Main] - Choice: ", async (answer: string) => { 65 | let choice = parseInt(answer); 66 | switch (choice) { 67 | case 1: 68 | show_settings(); 69 | break; 70 | case 2: 71 | settings(); 72 | break; 73 | case 3: 74 | start_automated_trading(); 75 | break; 76 | case 4: 77 | sell_token(); 78 | break; 79 | case 5: 80 | show_wallet_balance(); 81 | break; 82 | case 6: 83 | process.exit(0); 84 | default: 85 | display_error("Invalid choice! Please select a valid option."); 86 | await sleep(1500); 87 | init(); 88 | break; 89 | } 90 | }) 91 | } 92 | 93 | export const mainMenuWaiting = () => { 94 | rl.question('\x1b[32mpress Enter key to continue\x1b[0m', (answer: string) => { 95 | init() 96 | }) 97 | } 98 | 99 | const show_settings = async () => { 100 | let data = readSettings() 101 | 102 | console.log("Current settings of Trading bot...") 103 | console.log(data) 104 | mainMenuWaiting() 105 | } 106 | 107 | const settings = () => { 108 | screen_clear(); 109 | settings_display(); 110 | 111 | rl.question("\t[Settings] - Choice: ", (answer: string) => { 112 | let choice = parseInt(answer); 113 | switch (choice) { 114 | case 1: 115 | set_mint(); 116 | break; 117 | case 2: 118 | set_poolid(); 119 | break; 120 | case 3: 121 | set_amount(); 122 | break; 123 | case 4: 124 | set_slippage(); 125 | break; 126 | case 5: 127 | set_dex_platform(); 128 | break; 129 | case 6: 130 | market_cap_settings(); 131 | break; 132 | case 7: 133 | trading_strategy_settings(); 134 | break; 135 | case 8: 136 | init(); 137 | break; 138 | case 9: 139 | process.exit(0); 140 | default: 141 | display_error("Invalid choice! Please select a valid option."); 142 | sleep(1500); 143 | settings(); 144 | break; 145 | } 146 | }) 147 | } 148 | 149 | const settingsWaiting = () => { 150 | rl.question('\x1b[32mpress Enter key to continue\x1b[0m', (answer: string) => { 151 | settings() 152 | }) 153 | } 154 | 155 | const set_mint = () => { 156 | screen_clear(); 157 | let data = readSettings() 158 | let settings = { 159 | mint: new PublicKey(data.mint!), 160 | poolId: new PublicKey(data.poolId!), 161 | isPump: data.isPump!, 162 | amount: Number(data.amount), 163 | slippage: Number(data.slippage) 164 | } 165 | console.log(`Please Enter the contract address of the token you want, current mint is ${settings.mint}`) 166 | rl.question("\t[Contract Address of the token] - Address: ", (answer: string) => { 167 | if (answer == 'c') { 168 | settingsWaiting() 169 | return 170 | } 171 | let choice = new PublicKey(answer); 172 | settings.mint = choice 173 | saveSettingsToFile(settings) 174 | console.log(`Mint ${answer} is set correctly!`) 175 | settingsWaiting() 176 | }) 177 | } 178 | 179 | const set_poolid = () => { 180 | screen_clear(); 181 | let data = readSettings() 182 | let settings = { 183 | mint: new PublicKey(data.mint!), 184 | poolId: new PublicKey(data.poolId!), 185 | isPump: data.isPump!, 186 | amount: Number(data.amount), 187 | slippage: Number(data.slippage) 188 | } 189 | console.log(`Please Enter the Pool address of the token you want, current poolId is ${settings.poolId}`) 190 | rl.question("\t[Pool Address of the token] - Address: ", (answer: string) => { 191 | if (answer == 'c') { 192 | settingsWaiting() 193 | return 194 | } 195 | let choice = new PublicKey(answer); 196 | settings.poolId = choice 197 | saveSettingsToFile(settings) 198 | console.log(`PoolId ${answer} is set correctly!`) 199 | settingsWaiting() 200 | }) 201 | } 202 | 203 | const set_amount = () => { 204 | screen_clear(); 205 | let data = readSettings() 206 | let settings = { 207 | mint: new PublicKey(data.mint!), 208 | poolId: new PublicKey(data.poolId!), 209 | isPump: data.isPump!, 210 | amount: Number(data.amount), 211 | slippage: Number(data.slippage) 212 | } 213 | console.log(`Please Enter the amount you want, current amount is ${settings.amount}`) 214 | rl.question("\t[Number of Wallets] - Number: ", (answer: string) => { 215 | if (answer == 'c') { 216 | settingsWaiting() 217 | return 218 | } 219 | let choice = Number(answer); 220 | settings.amount = choice 221 | saveSettingsToFile(settings) 222 | console.log(`Buy amount ${answer} is set correctly!`) 223 | settingsWaiting() 224 | }) 225 | } 226 | 227 | const set_pump = () => { 228 | screen_clear(); 229 | let data = readSettings() 230 | let settings = { 231 | mint: new PublicKey(data.mint!), 232 | poolId: new PublicKey(data.poolId!), 233 | isPump: data.isPump!, 234 | amount: Number(data.amount), 235 | slippage: Number(data.slippage) 236 | } 237 | console.log(`Please Enter 0 or 1 depends the token is Pumpfun token or not, current value is ${settings.isPump}`) 238 | rl.question("\t[Is Pumpfun token?] - Number: ", (answer: string) => { 239 | if (answer == 'c') { 240 | settingsWaiting() 241 | return 242 | } 243 | let choice: Boolean = parseInt(answer) == 0 ? false : true; 244 | settings.isPump = choice 245 | saveSettingsToFile(settings) 246 | console.log(`Take Profit ${answer}% is set correctly!`) 247 | settingsWaiting() 248 | }) 249 | } 250 | 251 | const set_slippage = () => { 252 | screen_clear(); 253 | let data = readSettings() 254 | let settings = { 255 | mint: new PublicKey(data.mint!), 256 | poolId: new PublicKey(data.poolId!), 257 | isPump: data.isPump!, 258 | amount: Number(data.amount), 259 | slippage: Number(data.slippage) 260 | } 261 | console.log(`Please Enter the Number for slippage you want, current value is ${settings.slippage}`) 262 | rl.question("\t[Percent of Slippage ] - Number: ", (answer: string) => { 263 | if (answer == 'c') { 264 | settingsWaiting() 265 | return 266 | } 267 | let choice = parseInt(answer); 268 | settings.slippage = choice 269 | saveSettingsToFile(settings) 270 | display_success(`Slippage ${answer}% is set correctly!`) 271 | settingsWaiting() 272 | }) 273 | } 274 | 275 | // New functions for enhanced functionality 276 | const start_automated_trading = async () => { 277 | screen_clear(); 278 | display_info("Starting automated trading with market cap monitoring..."); 279 | try { 280 | await buy_monitor_autosell(); 281 | } catch (error) { 282 | display_error(`Trading failed: ${error}`); 283 | mainMenuWaiting(); 284 | } 285 | } 286 | 287 | const show_wallet_balance = async () => { 288 | screen_clear(); 289 | try { 290 | const balance = await solanaConnection.getBalance(mainKp.publicKey); 291 | const solBalance = balance / LAMPORTS_PER_SOL; 292 | display_info(`Wallet Address: ${mainKp.publicKey.toBase58()}`); 293 | display_info(`SOL Balance: ${solBalance.toFixed(6)} SOL`); 294 | mainMenuWaiting(); 295 | } catch (error) { 296 | display_error(`Failed to fetch balance: ${error}`); 297 | mainMenuWaiting(); 298 | } 299 | } 300 | 301 | const set_dex_platform = () => { 302 | screen_clear(); 303 | dex_platform_display(); 304 | 305 | rl.question("\t[DEX Platform] - Choice: ", (answer: string) => { 306 | let choice = parseInt(answer); 307 | let platform: DEXPlatform; 308 | 309 | switch (choice) { 310 | case 1: 311 | platform = "meteora"; 312 | break; 313 | case 2: 314 | platform = "raydium"; 315 | break; 316 | case 3: 317 | platform = "orca"; 318 | break; 319 | case 4: 320 | platform = "jupiter"; 321 | break; 322 | case 5: 323 | settings(); 324 | return; 325 | case 6: 326 | process.exit(0); 327 | default: 328 | display_error("Invalid choice! Please select a valid option."); 329 | sleep(1500); 330 | set_dex_platform(); 331 | return; 332 | } 333 | 334 | display_success(`DEX Platform set to: ${platform}`); 335 | settingsWaiting(); 336 | }) 337 | } 338 | 339 | const market_cap_settings = () => { 340 | screen_clear(); 341 | market_cap_settings_display(); 342 | 343 | rl.question("\t[Market Cap Settings] - Choice: ", (answer: string) => { 344 | let choice = parseInt(answer); 345 | switch (choice) { 346 | case 1: 347 | set_lower_mc_interval(); 348 | break; 349 | case 2: 350 | set_higher_mc_interval(); 351 | break; 352 | case 3: 353 | set_lower_tp_interval(); 354 | break; 355 | case 4: 356 | set_higher_tp_interval(); 357 | break; 358 | case 5: 359 | set_stop_loss(); 360 | break; 361 | case 6: 362 | set_sell_timer(); 363 | break; 364 | case 7: 365 | settings(); 366 | break; 367 | case 8: 368 | process.exit(0); 369 | default: 370 | display_error("Invalid choice! Please select a valid option."); 371 | sleep(1500); 372 | market_cap_settings(); 373 | break; 374 | } 375 | }) 376 | } 377 | 378 | const trading_strategy_settings = () => { 379 | screen_clear(); 380 | trading_strategy_display(); 381 | 382 | rl.question("\t[Trading Strategy] - Choice: ", (answer: string) => { 383 | let choice = parseInt(answer); 384 | let strategy: TradingStrategy; 385 | 386 | switch (choice) { 387 | case 1: 388 | strategy = "market_cap_monitor"; 389 | break; 390 | case 2: 391 | strategy = "price_monitor"; 392 | break; 393 | case 3: 394 | strategy = "manual"; 395 | break; 396 | case 4: 397 | settings(); 398 | return; 399 | case 5: 400 | process.exit(0); 401 | default: 402 | display_error("Invalid choice! Please select a valid option."); 403 | sleep(1500); 404 | trading_strategy_settings(); 405 | return; 406 | } 407 | 408 | display_success(`Trading strategy set to: ${strategy}`); 409 | settingsWaiting(); 410 | }) 411 | } 412 | 413 | // Placeholder functions for market cap settings 414 | const set_lower_mc_interval = () => { 415 | display_info("Lower MC Interval setting - Implementation needed"); 416 | settingsWaiting(); 417 | } 418 | 419 | const set_higher_mc_interval = () => { 420 | display_info("Higher MC Interval setting - Implementation needed"); 421 | settingsWaiting(); 422 | } 423 | 424 | const set_lower_tp_interval = () => { 425 | display_info("Lower TP Interval setting - Implementation needed"); 426 | settingsWaiting(); 427 | } 428 | 429 | const set_higher_tp_interval = () => { 430 | display_info("Higher TP Interval setting - Implementation needed"); 431 | settingsWaiting(); 432 | } 433 | 434 | const set_stop_loss = () => { 435 | display_info("Stop Loss setting - Implementation needed"); 436 | settingsWaiting(); 437 | } 438 | 439 | const set_sell_timer = () => { 440 | display_info("Sell Timer setting - Implementation needed"); 441 | settingsWaiting(); 442 | } 443 | 444 | init() -------------------------------------------------------------------------------- /src/layout/buy.ts: -------------------------------------------------------------------------------- 1 | import { ComputeBudgetProgram, Keypair, LAMPORTS_PER_SOL, PublicKey, sendAndConfirmTransaction, Transaction, TransactionMessage } from "@solana/web3.js"; 2 | import { readSettings, sleep } from "../utils/utils"; 3 | import { 4 | HIGHER_MC_INTERVAL, 5 | HIGHER_TP_INTERVAL, 6 | LOWER_MC_INTERVAL, 7 | LOWER_TP_INTERVAL, 8 | PRIVATE_KEY, 9 | SELL_TIMER, 10 | solanaConnection, 11 | STOP_LOSS, 12 | DEX_PLATFORM, 13 | PRICE_FEED_URL 14 | } from "../constants"; 15 | import base58 from "bs58"; 16 | import { logger } from "../utils"; 17 | import { getAccount, getAssociatedTokenAddress, NATIVE_MINT } from "@solana/spl-token"; 18 | import { mainMenuWaiting } from "../.."; 19 | import BN from "bn.js"; 20 | import DLMM, { getTokenDecimals } from "@meteora-ag/dlmm"; 21 | import { sendBundle } from "../jito/bundle"; 22 | import { TradeResult, MarketCapData, PnLData } from "../types"; 23 | 24 | export const mainKp = Keypair.fromSecretKey(base58.decode(PRIVATE_KEY!)) 25 | 26 | export const buy_monitor_autosell = async () => { 27 | const data = readSettings(); 28 | const BUY_AMOUNT = Number(data.amount); // Convert to lamports 29 | const TOKEN_CA = new PublicKey(data.mint!); 30 | const IS_PUMPFUN = data.isPump!; 31 | const SLIPPAGE = Number(data.slippage); 32 | const POOL_ID = new PublicKey(data.poolId!); 33 | 34 | const dlmmPool = await DLMM.create(solanaConnection, POOL_ID); 35 | 36 | const solBalance = (await solanaConnection.getBalance(mainKp.publicKey)) / LAMPORTS_PER_SOL; 37 | 38 | const binArraysAccount = await dlmmPool.getBinArrays(); 39 | const lpToken = await dlmmPool.getMaxPriceInBinArrays(binArraysAccount) 40 | console.log("🚀 ~ constbuy_monitor_autosell= ~ lpToken:", lpToken) 41 | 42 | // const baseAta = dlmmPool.tokenX.reserve; 43 | // const quoteAta = dlmmPool.tokenY.reserve; 44 | 45 | if (solBalance < Number(BUY_AMOUNT)) { 46 | logger.error(`There is not enough balance in your wallet. Please deposit some more solana to continue.`) 47 | return 48 | } 49 | logger.info(`Pumpswap Trading bot is running`) 50 | logger.info(`Wallet address: ${mainKp.publicKey.toBase58()}`) 51 | logger.info(`Balance of the main wallet: ${solBalance}Sol`) 52 | 53 | let middleMC = await getTokenMC(TOKEN_CA) 54 | let mc = Math.floor(middleMC) 55 | let lowerMC = mc * (1 - LOWER_MC_INTERVAL / 100) 56 | let higherMC = mc * (1 + HIGHER_MC_INTERVAL / 100) 57 | const mcCheckInterval = 1000 58 | let mcChecked = 0 59 | let bought = false 60 | let processingToken = false 61 | 62 | logger.info(`Starting MarketCap monitoring, initial MC is ${middleMC}Sol ...`) 63 | 64 | while (1) { 65 | 66 | let tpInterval 67 | processingToken = true 68 | 69 | while (1) { 70 | if (mcChecked != 0) { 71 | middleMC = await getTokenMC(TOKEN_CA) 72 | // middleHolderNum = (await findHolders(mintStr)).size 73 | } 74 | if (mcChecked > 100000) { 75 | bought = false 76 | processingToken = false 77 | break; 78 | } 79 | if (middleMC < 35) { 80 | bought = false 81 | processingToken = false 82 | break; 83 | } 84 | 85 | logger.info(`Current MC: ${middleMC}Sol, LMC: ${lowerMC}Sol, HMC: ${higherMC}Sol`) 86 | 87 | if (middleMC < lowerMC) { 88 | logger.info(`Market Cap keep decreasing now, reached ${lowerMC}Sol, keep monitoring...`) 89 | mc = Math.floor(middleMC) 90 | lowerMC = mc * (1 - LOWER_MC_INTERVAL / 100) 91 | higherMC = mc * (1 + HIGHER_MC_INTERVAL / 100) 92 | } else if (middleMC > higherMC) { 93 | logger.fatal(`Market Cap start increasing now, reached ${higherMC}Sol, can buy now...`) 94 | logger.info(`Buying ${BUY_AMOUNT} SOL`) 95 | // Quote to Base swap (⬆️) 96 | await swap(dlmmPool, POOL_ID, NATIVE_MINT, TOKEN_CA, new BN(BUY_AMOUNT * 1_000_000_000), 1000, mainKp); 97 | bought = true 98 | break; 99 | } else { 100 | logger.info(`Market Cap not changing a lot now, reached ${middleMC}Sol, keep monitoring...`) 101 | await sleep(mcCheckInterval) 102 | mcChecked++ 103 | continue; 104 | } 105 | 106 | await sleep(mcCheckInterval) 107 | mcChecked++ 108 | 109 | } 110 | 111 | if (bought) { 112 | mcChecked = 0 113 | if (middleMC > 1000) tpInterval = 1 114 | else tpInterval = 1 115 | // Waiting for the AssociatedTokenAccount is confirmed 116 | const maxRetries = 50 117 | const delayBetweenRetries = 1000 118 | const ata = await getAssociatedTokenAddress(TOKEN_CA, mainKp.publicKey) 119 | const tokenDecimals = await getTokenDecimals(solanaConnection, TOKEN_CA) 120 | 121 | for (let attempt = 0; attempt < maxRetries; attempt++) { 122 | try { 123 | const tokenAmount = Number((await solanaConnection.getTokenAccountBalance(ata)).value.amount); 124 | 125 | // Monitoring pnl 126 | attempt = maxRetries 127 | const amountIn = tokenAmount 128 | 129 | try { 130 | 131 | logger.info("Showing pnl monitoring...") 132 | const priceCheckInterval = 200 133 | const timesToCheck = SELL_TIMER / priceCheckInterval 134 | let TP_LEVEL = 1.3 135 | let higherTP = TP_LEVEL 136 | let lowerTP = TP_LEVEL - LOWER_TP_INTERVAL 137 | 138 | const SolOnSl = Number((BUY_AMOUNT * (100 - STOP_LOSS) / 100).toFixed(6)) 139 | let timesChecked = 0 140 | let tpReached = false 141 | do { 142 | 143 | try { 144 | // Swap quote 145 | const swapYtoX = false; 146 | const binArrays = await dlmmPool.getBinArrayForSwap(swapYtoX); 147 | 148 | const swapQuote = await dlmmPool.swapQuote(new BN(amountIn), swapYtoX, new BN(SLIPPAGE), binArrays); 149 | const amountOut = Number(swapQuote.outAmount); 150 | 151 | console.log("🚀 ~ constbuy_monitor_autosell= ~ amountOut:", amountOut / tokenDecimals) 152 | 153 | const pnl = (Number(amountOut / tokenDecimals) - BUY_AMOUNT) / BUY_AMOUNT * 100 154 | console.log("🚀 ~ constbuy_monitor_autosell= ~ pnl:", pnl) 155 | 156 | if (pnl > TP_LEVEL && !tpReached) { 157 | tpReached = true 158 | logger.info(`PNL is reached to the lowest Profit level ${TP_LEVEL}%`) 159 | } 160 | 161 | if (pnl < 0) { 162 | tpReached = false 163 | TP_LEVEL = 1 164 | higherTP = TP_LEVEL + HIGHER_TP_INTERVAL 165 | lowerTP = TP_LEVEL - LOWER_TP_INTERVAL 166 | } 167 | 168 | logger.info(`Current: ${amountOut / 10 ** 9} SOL | PNL: ${pnl.toFixed(7)}% | HTP: ${higherTP.toFixed(2)}% | LTP: ${lowerTP.toFixed(2)}% | SL: ${SolOnSl}`) 169 | const amountOutNum = Number(amountOut / 10 ** 9) 170 | 171 | if (amountOutNum < SolOnSl) { 172 | logger.fatal("Token is on stop loss level, will sell with loss") 173 | try { 174 | // const latestBlockHash = await (await solanaConnection.getLatestBlockhash()).blockhash 175 | await swap(dlmmPool, POOL_ID, TOKEN_CA, NATIVE_MINT, new BN(tokenAmount), SLIPPAGE, mainKp); 176 | bought = false 177 | break; 178 | } catch (err) { 179 | logger.info("Fail to sell tokens ...") 180 | } 181 | } 182 | 183 | if (pnl > 0) 184 | if (pnl > higherTP) { 185 | // TP_LEVEL = Math.floor(pnl / (tpInterval / 2)) * (tpInterval / 2) 186 | TP_LEVEL = pnl 187 | 188 | logger.info(`Token price goes up and up, so raising take profit from ${lowerTP + tpInterval / 2}% to ${TP_LEVEL}%`) 189 | 190 | higherTP = TP_LEVEL + HIGHER_TP_INTERVAL 191 | lowerTP = TP_LEVEL - LOWER_TP_INTERVAL 192 | } else if (pnl < lowerTP && tpReached) { 193 | logger.fatal("Token is on profit level, price starts going down, selling tokens...") 194 | try { 195 | await swap(dlmmPool, POOL_ID, TOKEN_CA, NATIVE_MINT, new BN(tokenAmount), SLIPPAGE, mainKp); 196 | break; 197 | } catch (err) { 198 | logger.info("Fail to sell tokens ...") 199 | } 200 | } 201 | 202 | } catch (e) { 203 | // logger.error(e) 204 | } finally { 205 | timesChecked++ 206 | } 207 | await sleep(priceCheckInterval) 208 | if (timesChecked >= timesToCheck) { 209 | await swap(dlmmPool, POOL_ID, TOKEN_CA, NATIVE_MINT, new BN(tokenAmount), SLIPPAGE, mainKp); 210 | break 211 | } 212 | } while (1) 213 | 214 | logger.warn(`New pumpswap token ${TOKEN_CA.toBase58()} PNL processing finished once and continue monitoring MarketCap`) 215 | // logger.info(`Waiting 5 seconds for new buying and selling...`) 216 | await sleep(2000) 217 | 218 | } catch (error) { 219 | logger.error("Error when setting profit amounts", error) 220 | mainMenuWaiting() 221 | } 222 | 223 | // break; // Break the loop if fetching the account was successful 224 | } catch (error) { 225 | if (error instanceof Error && error.name === 'TokenAccountNotFoundError') { 226 | logger.info(`Attempt ${attempt + 1}/${maxRetries}: Associated token account not found, retrying...`); 227 | if (attempt === maxRetries - 1) { 228 | logger.error(`Max retries reached. Failed to fetch the token account.`); 229 | mainMenuWaiting() 230 | } 231 | // Wait before retrying 232 | await new Promise((resolve) => setTimeout(resolve, delayBetweenRetries)); 233 | } else if (error instanceof Error) { 234 | // logger.error(`Unexpected error while fetching token account: ${error.message}`); 235 | // throw error; 236 | logger.info(`Attempt ${attempt + 1}/${maxRetries}: Associated token account not found, retrying...`); 237 | if (attempt === maxRetries - 1) { 238 | logger.error(`Max retries reached. Failed to fetch the token account.`); 239 | mainMenuWaiting() 240 | } 241 | await new Promise((resolve) => setTimeout(resolve, delayBetweenRetries)); 242 | 243 | } else { 244 | logger.error(`An unknown error occurred: ${String(error)}`); 245 | throw error; 246 | } 247 | } 248 | } 249 | } 250 | 251 | if (!processingToken) { 252 | mainMenuWaiting() 253 | break; 254 | } 255 | 256 | } 257 | } 258 | 259 | export const swap = async (dlmmPool: DLMM, pool: PublicKey, inputMint: PublicKey, outputMint: PublicKey, buyAmount: BN, slippage: number, user: Keypair): Promise => { 260 | logger.info(`Executing swap: ${buyAmount.toString()} lamports`); 261 | 262 | try { 263 | // Determine swap direction 264 | let swapYtoX: boolean = false; 265 | if (inputMint.equals(NATIVE_MINT)) { 266 | swapYtoX = true; 267 | } 268 | 269 | // Get bin arrays for swap 270 | const binArrays = await dlmmPool.getBinArrayForSwap(swapYtoX); 271 | logger.debug(`Bin arrays retrieved: ${binArrays.length} arrays`); 272 | 273 | // Get swap quote 274 | const swapQuote = dlmmPool.swapQuote(buyAmount, swapYtoX, new BN(slippage), binArrays); 275 | logger.info(`Swap quote - Input: ${buyAmount.toString()}, Output: ${swapQuote.outAmount.toString()}, Min Output: ${swapQuote.minOutAmount.toString()}`); 276 | 277 | // Create swap transaction 278 | const swapTransaction = await dlmmPool.swap({ 279 | inToken: inputMint, 280 | outToken: outputMint, 281 | binArraysPubkey: swapQuote.binArraysPubkey, 282 | inAmount: buyAmount, 283 | lbPair: dlmmPool.pubkey, 284 | user: user.publicKey, 285 | minOutAmount: swapYtoX ? swapQuote.minOutAmount : new BN(0), 286 | }); 287 | 288 | // Set transaction properties 289 | swapTransaction.feePayer = user.publicKey; 290 | const { blockhash, lastValidBlockHeight } = await solanaConnection.getLatestBlockhash(); 291 | swapTransaction.recentBlockhash = blockhash; 292 | swapTransaction.lastValidBlockHeight = lastValidBlockHeight; 293 | 294 | // Simulate transaction 295 | try { 296 | const simulationResult = await solanaConnection.simulateTransaction(swapTransaction); 297 | const { value } = simulationResult; 298 | 299 | if (value.err) { 300 | logger.error("Transaction simulation failed:", value.err); 301 | throw new Error(`Simulation failed: ${JSON.stringify(value.err)}`); 302 | } else { 303 | logger.info("Transaction simulation successful"); 304 | } 305 | } catch (error: any) { 306 | throw new Error(`Transaction simulation failed: ${error.message}`); 307 | } 308 | 309 | // Execute transaction 310 | const signature = await sendAndConfirmTransaction(solanaConnection, swapTransaction, [user]); 311 | logger.info(`Swap transaction successful: https://solscan.io/tx/${signature}`); 312 | 313 | return { 314 | success: true, 315 | signature, 316 | amountIn: Number(buyAmount), 317 | amountOut: Number(swapQuote.outAmount), 318 | timestamp: Date.now() 319 | }; 320 | 321 | } catch (error: any) { 322 | logger.error("Swap failed:", error); 323 | return { 324 | success: false, 325 | error: error.message, 326 | amountIn: Number(buyAmount), 327 | amountOut: 0, 328 | timestamp: Date.now() 329 | }; 330 | } 331 | } 332 | 333 | const getTokenPrice = async (mint: PublicKey): Promise => { 334 | try { 335 | const response = await fetch(`${PRICE_FEED_URL}?ids=${mint}`); 336 | const data: { data: { [key: string]: { price: number } } } = await response.json(); 337 | const tokenPrice = Number(data.data[`${mint}`]?.price); 338 | 339 | if (isNaN(tokenPrice)) { 340 | logger.warn(`Invalid price data for token ${mint.toBase58()}`); 341 | return null; 342 | } 343 | 344 | return tokenPrice; 345 | } catch (error) { 346 | logger.error("Error fetching token price:", error); 347 | return null; 348 | } 349 | } 350 | 351 | const getTokenMC = async (mint: PublicKey): Promise => { 352 | try { 353 | const currentPrice = await getTokenPrice(mint); 354 | if (!currentPrice) { 355 | logger.warn(`Could not fetch price for token ${mint.toBase58()}`); 356 | return null; 357 | } 358 | 359 | const tokenSupply = await solanaConnection.getTokenSupply(mint); 360 | const totalSupply = tokenSupply.value.uiAmount; 361 | 362 | if (!totalSupply) { 363 | logger.warn(`Could not fetch supply for token ${mint.toBase58()}`); 364 | return null; 365 | } 366 | 367 | const marketCap = currentPrice * totalSupply; 368 | logger.debug(`Token ${mint.toBase58()} - Price: $${currentPrice}, Supply: ${totalSupply}, MC: $${marketCap}`); 369 | 370 | return marketCap; 371 | } catch (error) { 372 | logger.error("Error calculating market cap:", error); 373 | return null; 374 | } 375 | } 376 | 377 | // Generic market cap monitoring function 378 | export const getMarketCapData = async (mint: PublicKey): Promise => { 379 | try { 380 | const currentMC = await getTokenMC(mint); 381 | if (!currentMC) return null; 382 | 383 | const lowerMC = currentMC * (1 - LOWER_MC_INTERVAL / 100); 384 | const higherMC = currentMC * (1 + HIGHER_MC_INTERVAL / 100); 385 | 386 | return { 387 | current: currentMC, 388 | lower: lowerMC, 389 | higher: higherMC, 390 | change: 0, // Could be calculated with previous value 391 | timestamp: Date.now() 392 | }; 393 | } catch (error) { 394 | logger.error("Error getting market cap data:", error); 395 | return null; 396 | } 397 | } --------------------------------------------------------------------------------