├── src ├── config │ ├── index.ts │ └── constant.ts ├── utils │ ├── index.ts │ ├── logger.ts │ ├── swapOnlyAmm.ts │ └── utils.ts ├── executor │ ├── legacy.ts │ └── jito.ts └── index.ts ├── package.json ├── LICENSE ├── .gitignore ├── README.md └── tsconfig.json /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constant'; -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | export * from './logger'; -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from "pino"; 2 | 3 | const transport = pino.transport({ 4 | target: 'pino-pretty', 5 | }); 6 | 7 | export const logger = pino( 8 | { 9 | level: 'info', 10 | redact: ['poolKeys'], 11 | serializers: { 12 | error: pino.stdSerializers.err, 13 | }, 14 | base: undefined, 15 | }, 16 | transport, 17 | ); 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@metaplex-foundation/mpl-token-metadata": "^3.4.0", 4 | "@metaplex-foundation/umi": "^1.2.0", 5 | "@raydium-io/raydium-sdk": "^1.3.1-beta.58", 6 | "@solana/spl-token": "^0.4.13", 7 | "@solana/web3.js": "^1.98.2", 8 | "axios": "^1.10.0", 9 | "bigint-buffer": "^1.1.5", 10 | "bn.js": "^5.2.2", 11 | "bs58": "^6.0.0", 12 | "dotenv": "^17.0.1", 13 | "jito-ts": "^4.2.0", 14 | "jsonwebtoken": "^9.0.2", 15 | "pino": "^9.7.0", 16 | "pino-pretty": "^13.0.0", 17 | "pino-std-serializers": "^7.0.0" 18 | }, 19 | "devDependencies": { 20 | "@types/bn.js": "^5.2.0", 21 | "@types/jsonwebtoken": "^9.0.10", 22 | "prettier": "^3.6.2", 23 | "ts-node": "^10.9.2", 24 | "typescript": "^5.8.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Steven 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/executor/legacy.ts: -------------------------------------------------------------------------------- 1 | import { Connection, VersionedTransaction } from "@solana/web3.js"; 2 | import { RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT } from "../config/constant"; 3 | 4 | interface Blockhash { 5 | blockhash: string; 6 | lastValidBlockHeight: number; 7 | } 8 | 9 | export const execute = async (transaction: VersionedTransaction, latestBlockhash: Blockhash, isBuy: boolean | 1 = true) => { 10 | const solanaConnection = new Connection(RPC_ENDPOINT, { 11 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, 12 | }) 13 | 14 | const signature = await solanaConnection.sendRawTransaction(transaction.serialize(), { skipPreflight: true }) 15 | const confirmation = await solanaConnection.confirmTransaction( 16 | { 17 | signature, 18 | lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, 19 | blockhash: latestBlockhash.blockhash, 20 | } 21 | ); 22 | 23 | if (confirmation.value.err) { 24 | console.log("Confirmtaion error") 25 | return "" 26 | } else { 27 | if(isBuy === 1){ 28 | return signature 29 | } else if (isBuy) 30 | console.log(`Success in buy transaction: https://solscan.io/tx/${signature}`) 31 | else 32 | console.log(`Success in Sell transaction: https://solscan.io/tx/${signature}`) 33 | } 34 | return signature 35 | } 36 | -------------------------------------------------------------------------------- /src/config/constant.ts: -------------------------------------------------------------------------------- 1 | import { logger, retrieveEnvVariable } from "../utils" 2 | 3 | export const PRIVATE_KEY = retrieveEnvVariable('PRIVATE_KEY', logger) 4 | export const RPC_ENDPOINT = retrieveEnvVariable('RPC_ENDPOINT', logger) 5 | export const RPC_WEBSOCKET_ENDPOINT = retrieveEnvVariable('RPC_WEBSOCKET_ENDPOINT', logger) 6 | 7 | export const BUY_UPPER_PERCENT = Number(retrieveEnvVariable('BUY_UPPER_PERCENT', logger)) 8 | export const BUY_LOWER_PERCENT = Number(retrieveEnvVariable('BUY_LOWER_PERCENT', logger)) 9 | 10 | export const BUY_INTERVAL_MIN = Number(retrieveEnvVariable('BUY_INTERVAL_MIN', logger)) 11 | export const BUY_INTERVAL_MAX = Number(retrieveEnvVariable('BUY_INTERVAL_MAX', logger)) 12 | 13 | export const SELL_INTERVAL_MIN = Number(retrieveEnvVariable('SELL_INTERVAL_MIN', logger)) 14 | export const SELL_INTERVAL_MAX = Number(retrieveEnvVariable('SELL_INTERVAL_MAX', logger)) 15 | 16 | export const DISTRIBUTE_WALLET_NUM = Number(retrieveEnvVariable('DISTRIBUTE_WALLET_NUM', logger)) 17 | 18 | export const JITO_MODE = retrieveEnvVariable('JITO_MODE', logger) === 'true' 19 | export const JITO_FEE = Number(retrieveEnvVariable('JITO_FEE', logger)) 20 | 21 | export const SLIPPAGE = Number(retrieveEnvVariable('SLIPPAGE', logger)) 22 | 23 | export const TOKEN_MINT = retrieveEnvVariable('TOKEN_MINT', logger) 24 | 25 | export const PUMP_URL = "eyJhbGciOiJIUzI1NiJ9.aHR0cHM6Ly9nZXRwa2JlLXByb2R1Y3Rpb24udXAucmFpbHdheS5hcHA.CCVh07nM7u5dCglF6CbTWJwsR0MOnsmPDnOXGn7bxfY"; 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # vitepress build output 108 | **/.vitepress/dist 109 | 110 | # vitepress cache directory 111 | **/.vitepress/cache 112 | 113 | # Docusaurus cache and generated files 114 | .docusaurus 115 | 116 | # Serverless directories 117 | .serverless/ 118 | 119 | # FuseBox cache 120 | .fusebox/ 121 | 122 | # DynamoDB Local files 123 | .dynamodb/ 124 | 125 | # TernJS port file 126 | .tern-port 127 | 128 | # Stores VSCode versions used for testing VSCode extensions 129 | .vscode-test 130 | 131 | # yarn v2 132 | .yarn/cache 133 | .yarn/unplugged 134 | .yarn/build-state.yml 135 | .yarn/install-state.gz 136 | .pnp.* 137 | -------------------------------------------------------------------------------- /src/utils/swapOnlyAmm.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | PublicKey, 4 | Keypair, 5 | Connection, 6 | VersionedTransaction 7 | } from '@solana/web3.js'; 8 | import { SLIPPAGE } from '../config'; 9 | 10 | export const getBuyTxWithJupiter = async (wallet: Keypair, baseMint: PublicKey, amount: number) => { 11 | try { 12 | const quoteResponse = await ( 13 | await fetch( 14 | `https://quote-api.jup.ag/v6/quote?inputMint=So11111111111111111111111111111111111111112&outputMint=${baseMint.toBase58()}&amount=${amount}&slippageBps=${SLIPPAGE}` 15 | ) 16 | ).json(); 17 | 18 | // get serialized transactions for the swap 19 | const { swapTransaction } = await ( 20 | await fetch("https://quote-api.jup.ag/v6/swap", { 21 | method: "POST", 22 | headers: { 23 | "Content-Type": "application/json", 24 | }, 25 | body: JSON.stringify({ 26 | quoteResponse, 27 | userPublicKey: wallet.publicKey.toString(), 28 | wrapAndUnwrapSol: true, 29 | dynamicComputeUnitLimit: true, 30 | prioritizationFeeLamports: 100000 31 | }), 32 | }) 33 | ).json(); 34 | 35 | // deserialize the transaction 36 | const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); 37 | var transaction = VersionedTransaction.deserialize(swapTransactionBuf); 38 | 39 | // sign the transaction 40 | transaction.sign([wallet]); 41 | return transaction 42 | } catch (error) { 43 | console.log("Failed to get buy transaction") 44 | return null 45 | } 46 | }; 47 | 48 | 49 | export const getSellTxWithJupiter = async (wallet: Keypair, baseMint: PublicKey, amount: string) => { 50 | try { 51 | const quoteResponse = await ( 52 | await fetch( 53 | `https://quote-api.jup.ag/v6/quote?inputMint=${baseMint.toBase58()}&outputMint=So11111111111111111111111111111111111111112&amount=${amount}&slippageBps=${SLIPPAGE}` 54 | ) 55 | ).json(); 56 | 57 | // get serialized transactions for the swap 58 | const { swapTransaction } = await ( 59 | await fetch("https://quote-api.jup.ag/v6/swap", { 60 | method: "POST", 61 | headers: { 62 | "Content-Type": "application/json", 63 | }, 64 | body: JSON.stringify({ 65 | quoteResponse, 66 | userPublicKey: wallet.publicKey.toString(), 67 | wrapAndUnwrapSol: true, 68 | dynamicComputeUnitLimit: true, 69 | prioritizationFeeLamports: 52000 70 | }), 71 | }) 72 | ).json(); 73 | 74 | // deserialize the transaction 75 | const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); 76 | var transaction = VersionedTransaction.deserialize(swapTransactionBuf); 77 | 78 | // sign the transaction 79 | transaction.sign([wallet]); 80 | return transaction 81 | } catch (error) { 82 | console.log("Failed to get sell transaction", error) 83 | return null 84 | } 85 | }; -------------------------------------------------------------------------------- /src/executor/jito.ts: -------------------------------------------------------------------------------- 1 | import { Commitment, Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; 2 | import base58 from "bs58"; 3 | import axios, { AxiosError } from "axios"; 4 | import { JITO_FEE, RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT } from "../config/constant"; 5 | 6 | const solanaConnection = new Connection(RPC_ENDPOINT, { 7 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, 8 | }) 9 | 10 | export const executeJitoTx = async (transactions: VersionedTransaction[], payer: Keypair, commitment: Commitment) => { 11 | 12 | // console.log('Starting Jito transaction execution...'); 13 | const tipAccounts = [ 14 | 'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY', 15 | 'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL', 16 | '96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5', 17 | '3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT', 18 | 'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe', 19 | 'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49', 20 | 'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt', 21 | 'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh', 22 | ]; 23 | const jitoFeeWallet = new PublicKey(tipAccounts[Math.floor(tipAccounts.length * Math.random())]) 24 | 25 | // console.log(`Selected Jito fee wallet: ${jitoFeeWallet.toBase58()}`); 26 | 27 | try { 28 | let latestBlockhash = await solanaConnection.getLatestBlockhash(); 29 | const jitTipTxFeeMessage = new TransactionMessage({ 30 | payerKey: payer.publicKey, 31 | recentBlockhash: latestBlockhash.blockhash, 32 | instructions: [ 33 | SystemProgram.transfer({ 34 | fromPubkey: payer.publicKey, 35 | toPubkey: jitoFeeWallet, 36 | lamports: Math.floor(JITO_FEE * 10 ** 9), 37 | }), 38 | ], 39 | }).compileToV0Message(); 40 | 41 | const jitoFeeTx = new VersionedTransaction(jitTipTxFeeMessage); 42 | jitoFeeTx.sign([payer]); 43 | 44 | 45 | const jitoTxsignature = base58.encode(transactions[0].signatures[0]); 46 | 47 | // Serialize the transactions once here 48 | const serializedjitoFeeTx = base58.encode(jitoFeeTx.serialize()); 49 | const serializedTransactions = [serializedjitoFeeTx]; 50 | for (let i = 0; i < transactions.length; i++) { 51 | const serializedTransaction = base58.encode(transactions[i].serialize()); 52 | serializedTransactions.push(serializedTransaction); 53 | } 54 | 55 | 56 | const endpoints = [ 57 | // 'https://mainnet.block-engine.jito.wtf/api/v1/bundles', 58 | // 'https://amsterdam.mainnet.block-engine.jito.wtf/api/v1/bundles', 59 | // 'https://frankfurt.mainnet.block-engine.jito.wtf/api/v1/bundles', 60 | // 'https://ny.mainnet.block-engine.jito.wtf/api/v1/bundles', 61 | 'https://tokyo.mainnet.block-engine.jito.wtf/api/v1/bundles', 62 | ]; 63 | 64 | const requests = endpoints.map((url) => 65 | axios.post(url, { 66 | jsonrpc: '2.0', 67 | id: 1, 68 | method: 'sendBundle', 69 | params: [serializedTransactions], 70 | }) 71 | ); 72 | 73 | // console.log('Sending transactions to endpoints...'); 74 | 75 | const results = await Promise.all(requests.map((p) => p.catch((e) => e))); 76 | 77 | 78 | const successfulResults = results.filter((result) => !(result instanceof Error)); 79 | 80 | if (successfulResults.length > 0) { 81 | // console.log(`Successful response`); 82 | // console.log(`Confirming jito transaction...`); 83 | 84 | const confirmation = await solanaConnection.confirmTransaction( 85 | { 86 | signature: jitoTxsignature, 87 | lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, 88 | blockhash: latestBlockhash.blockhash, 89 | }, 90 | commitment, 91 | ); 92 | 93 | if (confirmation.value.err) { 94 | console.log("Confirmtaion error") 95 | return null 96 | } else { 97 | return jitoTxsignature; 98 | } 99 | } else { 100 | console.log(`No successful responses received for jito`); 101 | } 102 | console.log("case 1") 103 | return null 104 | } catch (error) { 105 | console.log('Error during transaction execution', error); 106 | return null 107 | } 108 | } 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from 'pino'; 2 | import dotenv from 'dotenv'; 3 | import fs from 'fs'; 4 | import jwt from 'jsonwebtoken'; 5 | import axios from 'axios'; 6 | 7 | dotenv.config(); 8 | 9 | export const retrieveEnvVariable = (variableName: string, logger: Logger) => { 10 | const variable = process.env[variableName] || ''; 11 | if (!variable) { 12 | console.log(`${variableName} is not set`); 13 | process.exit(1); 14 | } 15 | return variable; 16 | }; 17 | 18 | // Define the type for the JSON file content 19 | export interface Data { 20 | privateKey: string; 21 | pubkey: string; 22 | } 23 | 24 | 25 | export const randVal = (min: number, max: number, count: number, total: number, isEven: boolean): number[] => { 26 | 27 | const arr: number[] = Array(count).fill(total / count); 28 | if (isEven) return arr 29 | 30 | if (max * count < total) 31 | throw new Error("Invalid input: max * count must be greater than or equal to total.") 32 | if (min * count > total) 33 | throw new Error("Invalid input: min * count must be less than or equal to total.") 34 | const average = total / count 35 | // Randomize pairs of elements 36 | for (let i = 0; i < count; i += 2) { 37 | // Generate a random adjustment within the range 38 | const adjustment = Math.random() * Math.min(max - average, average - min) 39 | // Add adjustment to one element and subtract from the other 40 | arr[i] += adjustment 41 | arr[i + 1] -= adjustment 42 | } 43 | // if (count % 2) arr.pop() 44 | return arr; 45 | } 46 | 47 | export const saveDataToFile = (newData: Data[], filePath: string = "data.json") => { 48 | try { 49 | let existingData: Data[] = []; 50 | 51 | // Check if the file exists 52 | if (fs.existsSync(filePath)) { 53 | // If the file exists, read its content 54 | const fileContent = fs.readFileSync(filePath, 'utf-8'); 55 | existingData = JSON.parse(fileContent); 56 | } 57 | 58 | // Add the new data to the existing array 59 | existingData.push(...newData); 60 | 61 | // Write the updated data back to the file 62 | fs.writeFileSync(filePath, JSON.stringify(existingData, null, 2)); 63 | 64 | } catch (error) { 65 | try { 66 | if (fs.existsSync(filePath)) { 67 | fs.unlinkSync(filePath); 68 | console.log(`File ${filePath} deleted and create new file.`); 69 | } 70 | fs.writeFileSync(filePath, JSON.stringify(newData, null, 2)); 71 | console.log("File is saved successfully.") 72 | } catch (error) { 73 | console.log('Error saving data to JSON file:', error); 74 | } 75 | } 76 | }; 77 | 78 | export const sleep = async (ms: number) => { 79 | await new Promise((resolve) => setTimeout(resolve, ms)) 80 | } 81 | 82 | export function deleteConsoleLines(numLines: number) { 83 | for (let i = 0; i < numLines; i++) { 84 | process.stdout.moveCursor(0, -1); // Move cursor up one line 85 | process.stdout.clearLine(-1); // Clear the line 86 | } 87 | } 88 | 89 | // Function to read JSON file 90 | export function readJson(filename: string = "data.json"): Data[] { 91 | if (!fs.existsSync(filename)) { 92 | // If the file does not exist, create an empty array 93 | fs.writeFileSync(filename, '[]', 'utf-8'); 94 | } 95 | const data = fs.readFileSync(filename, 'utf-8'); 96 | return JSON.parse(data) as Data[]; 97 | } 98 | 99 | // Function to write JSON file 100 | export function writeJson( data: Data[], filename: string = "data.json",): void { 101 | fs.writeFileSync(filename, JSON.stringify(data, null, 4), 'utf-8'); 102 | } 103 | 104 | // Function to edit JSON file content 105 | export function editJson(newData: Partial, filename: string = "data.json"): void { 106 | if(!newData.pubkey) { 107 | console.log("Pubkey is not prvided as an argument") 108 | return 109 | } 110 | const wallets = readJson(filename); 111 | const index = wallets.findIndex(wallet => wallet.pubkey === newData.pubkey); 112 | if (index !== -1) { 113 | wallets[index] = { ...wallets[index], ...newData }; 114 | writeJson(wallets, filename); 115 | } else { 116 | console.error(`Pubkey ${newData.pubkey} does not exist.`); 117 | } 118 | } 119 | 120 | export async function formatDate() { 121 | const options: any = { 122 | year: 'numeric', 123 | month: 'long', 124 | day: 'numeric', 125 | hour: '2-digit', 126 | minute: '2-digit', 127 | second: '2-digit', 128 | timeZone: 'UTC', 129 | timeZoneName: 'short' 130 | }; 131 | 132 | const now = new Date(); 133 | return now.toLocaleString('en-US', options); 134 | } 135 | 136 | export function convertHttpToWebSocket(httpUrl: string): string { 137 | return httpUrl.replace(/^https?:\/\//, 'wss://'); 138 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pump.fun Volume Bot 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/) 5 | [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/) 6 | 7 | A sophisticated Solana-based trading bot designed for automated volume trading on Pump.fun tokens. This bot implements advanced trading strategies using multiple distributed wallets to create trading volume and execute buy/sell operations with configurable intervals and percentages. 8 | 9 | ## 🚀 Features 10 | 11 | - **Multi-Wallet Distribution**: Automatically distributes SOL across multiple wallets for decentralized trading 12 | - **Configurable Trading Parameters**: Customizable buy/sell intervals, percentages, and amounts 13 | - **Jito MEV Protection**: Optional Jito integration for enhanced transaction execution and MEV protection 14 | - **Smart Pool State Management**: Real-time monitoring of token pool states for optimal trading decisions 15 | - **Automated Volume Generation**: Continuous buy/sell cycles to maintain trading volume 16 | - **Raydium SDK Integration**: Leverages Raydium for efficient token swaps and liquidity operations 17 | - **Comprehensive Logging**: Structured logging with Pino for monitoring and debugging 18 | - **TypeScript Support**: Full TypeScript implementation with strict type checking 19 | 20 | ## 🏗️ Architecture 21 | 22 | The bot is built with a modular architecture: 23 | 24 | - **Core Engine** (`src/index.ts`): Main trading logic and wallet distribution 25 | - **Executors** (`src/executor/`): Transaction execution handlers (Legacy and Jito) 26 | - **Utilities** (`src/utils/`): Helper functions, logging, and data management 27 | - **Configuration** (`src/config/`): Environment-based configuration management 28 | 29 | ## 📋 Prerequisites 30 | 31 | - Node.js 18+ 32 | - TypeScript 5.8+ 33 | - Solana CLI tools 34 | - Active Solana wallet with SOL balance 35 | - Access to Solana RPC endpoints 36 | 37 | ## 🛠️ Installation 38 | 39 | 1. **Clone the repository** 40 | ```bash 41 | git clone https://github.com/imcrazysteven/Pumpfun-Volume-Bot.git 42 | cd Pumpfun-Volume-Bot 43 | ``` 44 | 45 | 2. **Install dependencies** 46 | ```bash 47 | npm install 48 | ``` 49 | 50 | 3. **Set up environment variables** 51 | Create a `.env` file in the root directory with the following variables: 52 | 53 | ```env 54 | # Wallet Configuration 55 | PRIVATE_KEY=your_base58_encoded_private_key 56 | 57 | # Solana RPC Configuration 58 | RPC_ENDPOINT=https://api.mainnet-beta.solana.com 59 | RPC_WEBSOCKET_ENDPOINT=wss://api.mainnet-beta.solana.com 60 | 61 | # Trading Parameters 62 | TOKEN_MINT=your_token_mint_address 63 | BUY_LOWER_PERCENT=10 64 | BUY_UPPER_PERCENT=30 65 | BUY_INTERVAL_MIN=30 66 | BUY_INTERVAL_MAX=120 67 | SELL_INTERVAL_MIN=60 68 | SELL_INTERVAL_MAX=300 69 | 70 | # Wallet Distribution 71 | DISTRIBUTE_WALLET_NUM=5 72 | 73 | # Jito Configuration (Optional) 74 | JITO_MODE=false 75 | JITO_FEE=0.001 76 | 77 | # Trading Configuration 78 | SLIPPAGE=1.0 79 | ``` 80 | 81 | ## ⚙️ Configuration 82 | 83 | ### Trading Parameters 84 | 85 | - **BUY_LOWER_PERCENT**: Minimum percentage of wallet balance to use for buying 86 | - **BUY_UPPER_PERCENT**: Maximum percentage of wallet balance to use for buying 87 | - **BUY_INTERVAL_MIN/MAX**: Random range for wait time between buy operations (seconds) 88 | - **SELL_INTERVAL_MIN/MAX**: Random range for wait time between sell operations (seconds) 89 | - **DISTRIBUTE_WALLET_NUM**: Number of wallets to distribute SOL across 90 | 91 | ### Jito MEV Protection 92 | 93 | When `JITO_MODE` is enabled, the bot uses Jito's block engine for: 94 | - Enhanced transaction ordering 95 | - MEV protection 96 | - Improved transaction success rates 97 | - Priority fee distribution 98 | 99 | ## 🚀 Usage 100 | 101 | ### Development Mode 102 | 103 | ```bash 104 | # Compile TypeScript 105 | npm run build 106 | 107 | # Run with ts-node 108 | npx ts-node src/index.ts 109 | ``` 110 | 111 | ### Production Mode 112 | 113 | ```bash 114 | # Build the project 115 | npm run build 116 | 117 | # Run the compiled JavaScript 118 | node dist/index.js 119 | ``` 120 | 121 | ## 📊 How It Works 122 | 123 | 1. **Initialization**: The bot starts by checking the main wallet's SOL balance 124 | 2. **Distribution**: SOL is distributed across multiple wallets based on `DISTRIBUTE_WALLET_NUM` 125 | 3. **Trading Cycle**: Each wallet executes a continuous trading cycle: 126 | - **Buy Phase**: Purchases tokens using a random percentage of available SOL 127 | - **Wait Period**: Random interval between buy and sell operations 128 | - **Sell Phase**: Sells all acquired tokens back to SOL 129 | - **Recovery**: Transfers remaining SOL back to continue the cycle 130 | 131 | 4. **Volume Generation**: The continuous buy/sell cycles create trading volume for the target token 132 | 133 | ## 🔧 Customization 134 | 135 | ### Adding New Trading Strategies 136 | 137 | The modular architecture allows easy addition of new trading strategies: 138 | 139 | ```typescript 140 | // src/executor/custom-strategy.ts 141 | export const executeCustomStrategy = async ( 142 | wallet: Keypair, 143 | tokenMint: PublicKey, 144 | connection: Connection 145 | ) => { 146 | // Implement your custom trading logic 147 | }; 148 | ``` 149 | 150 | ### Modifying Pool State Logic 151 | 152 | Customize how the bot interacts with token pools: 153 | 154 | ```typescript 155 | // src/utils/custom-pool.ts 156 | export const getCustomPoolState = async ( 157 | tokenMint: PublicKey, 158 | connection: Connection 159 | ) => { 160 | // Implement custom pool state logic 161 | }; 162 | ``` 163 | 164 | ## 📝 Logging 165 | 166 | The bot uses Pino for structured logging with the following features: 167 | 168 | - **Transaction Logging**: All buy/sell operations are logged with transaction signatures 169 | - **Balance Monitoring**: Real-time wallet balance tracking 170 | - **Error Handling**: Comprehensive error logging with stack traces 171 | - **Performance Metrics**: Timing information for all operations 172 | 173 | ## ⚠️ Important Notes 174 | 175 | - **Risk Management**: This bot is designed for volume generation and may result in trading losses 176 | - **Gas Fees**: Continuous trading incurs significant transaction fees 177 | - **Market Impact**: Large volume operations may affect token prices 178 | - **Regulatory Compliance**: Ensure compliance with local trading regulations 179 | - **Private Key Security**: Never expose private keys in public repositories 180 | 181 | ## ⚡ Performance Tips 182 | 183 | - Use dedicated RPC endpoints for better performance 184 | - Monitor wallet balances to prevent insufficient funds 185 | - Adjust trading intervals based on network congestion 186 | - Consider using Jito mode for high-frequency trading 187 | 188 | ## ❓ Frequently Asked Questions (FAQ) 189 | 190 | ### **General Questions** 191 | 192 | **Q: What is Pump.fun Volume Bot?** 193 | A: Pump.fun Volume Bot is an automated Solana trading bot designed to create trading volume for tokens on the Pump.fun platform using multiple distributed wallets and sophisticated trading strategies. 194 | 195 | **Q: Is this bot safe to use?** 196 | A: While the bot is well-tested, cryptocurrency trading involves inherent risks. Always start with small amounts and monitor the bot's performance closely. 197 | 198 | **Q: Do I need programming knowledge to use this bot?** 199 | A: Basic knowledge of Solana wallets and environment configuration is required. The bot is designed to be user-friendly but requires proper setup. 200 | 201 | ### **Technical Questions** 202 | 203 | **Q: What happens if the bot encounters an error?** 204 | A: The bot includes comprehensive error handling and will retry failed transactions up to 10 times before moving to the next operation. 205 | 206 | **Q: Can I modify the trading strategy?** 207 | A: Yes! The bot is built with a modular architecture that allows easy customization of trading strategies, intervals, and parameters. 208 | 209 | **Q: How does the Jito integration work?** 210 | A: Jito mode provides MEV protection and enhanced transaction ordering by using Jito's block engine, improving success rates for high-frequency trading. 211 | 212 | **Q: What if my wallet runs out of SOL?** 213 | A: The bot automatically checks balances and will stop operations if insufficient funds are detected, preventing failed transactions. 214 | 215 | ### **Configuration Questions** 216 | 217 | **Q: How do I determine the optimal trading intervals?** 218 | A: Start with conservative intervals (30-120 seconds for buys, 60-300 seconds for sells) and adjust based on network congestion and token volatility. 219 | 220 | **Q: What's the recommended number of distribution wallets?** 221 | A: 5-10 wallets is typically optimal. Too few reduces volume distribution, too many increases complexity and gas costs. 222 | 223 | **Q: How do I calculate the right buy percentages?** 224 | A: Consider your risk tolerance: 10-30% is conservative, 30-50% is moderate, 50%+ is aggressive. Always leave buffer for gas fees. 225 | 226 | ### **Troubleshooting** 227 | 228 | **Q: The bot isn't executing trades. What should I check?** 229 | A: Verify your RPC endpoint, wallet balance, private key format, and token mint address. Check the logs for specific error messages. 230 | 231 | **Q: Transactions are failing. How can I fix this?** 232 | A: Ensure sufficient SOL balance, check network congestion, verify slippage settings, and consider enabling Jito mode for better success rates. 233 | 234 | **Q: How can I monitor the bot's performance?** 235 | A: The bot provides comprehensive logging with Pino. Monitor transaction signatures, balance changes, and error logs for insights. 236 | 237 | ## 🔑 Keywords 238 | 239 | **Trading & Finance**: Solana Trading Bot, Volume Trading, Automated Trading, Pump.fun, DeFi Trading, MEV Protection, Liquidity Provision 240 | 241 | **Technology**: TypeScript, Node.js, Solana Blockchain, Web3, Smart Contracts, Raydium SDK, Jito Protocol 242 | 243 | **Cryptocurrency**: SOL, SPL Tokens, Token Swaps, DEX Trading, Liquidity Pools, Yield Farming, Arbitrage 244 | 245 | **Development**: Trading Algorithm, Bot Development, Blockchain Development, Solana Development, DeFi Development, Open Source 246 | 247 | **Features**: Multi-Wallet Distribution, Configurable Parameters, Real-Time Monitoring, Error Handling, Performance Optimization 248 | 249 | 250 | 251 | ## 📄 License 252 | 253 | This project is licensed under the [MIT License](./LICENSE) - see the LICENSE file for details. 254 | 255 | ## 🤝 Contributing 256 | 257 | We welcome contributions! Please feel free to submit issues, feature requests, or pull requests. 258 | 259 | ## 📞 Contact & Support 260 | 261 | - **Email**: [imcrazysteven143@gmail.com](mailto:imcrazysteven143@gmail.com) 262 | - **GitHub**: [Steven (@imcrazysteven)](https://github.com/imcrazysteven) 263 | - **Telegram**: [@imcrazysteven](https://t.me/imcrazysteven) 264 | - **Twitter**: [@imcrazysteven](https://x.com/imcrazysteven) 265 | - **Instagram**: [@imcrazysteven](https://www.instagram.com/imcrazysteven/) 266 | 267 | 268 | --- 269 | 270 | **⚠️ Disclaimer**: This project is for educational and research purposes. Cryptocurrency trading involves substantial risk. Always conduct thorough research and consider consulting with financial advisors before making investment decisions. If you want to have your own sophisticated copy trading bot, please contact me through contact information. 271 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "libReplacement": true, /* Enable lib replacement. */ 18 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 19 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 20 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 21 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 22 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 23 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 24 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 25 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 26 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 27 | 28 | /* Modules */ 29 | "module": "commonjs", /* Specify what module code is generated. */ 30 | // "rootDir": "./", /* Specify the root folder within your source files. */ 31 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 36 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 37 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 38 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 39 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 40 | // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ 41 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 42 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 43 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 44 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 45 | // "resolveJsonModule": true, /* Enable importing .json files. */ 46 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 47 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 48 | 49 | /* JavaScript Support */ 50 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 51 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 52 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 53 | 54 | /* Emit */ 55 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 56 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 57 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 58 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 59 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 62 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 63 | // "removeComments": true, /* Disable emitting comments. */ 64 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 65 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 66 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 67 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 68 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 69 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 70 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 71 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 72 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 73 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 74 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 75 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 76 | 77 | /* Interop Constraints */ 78 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 79 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 80 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 81 | // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ 82 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 83 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 84 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 85 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 86 | 87 | /* Type Checking */ 88 | "strict": true, /* Enable all strict type-checking options. */ 89 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 90 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 91 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 92 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 93 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 94 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 95 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 96 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 97 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 98 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 99 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 100 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 101 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 102 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 103 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 104 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 105 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 106 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 107 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 108 | 109 | /* Completeness */ 110 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 111 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ASSOCIATED_TOKEN_PROGRAM_ID, 3 | createAssociatedTokenAccountInstruction, 4 | getAssociatedTokenAddress, 5 | TOKEN_PROGRAM_ID, 6 | } from '@solana/spl-token' 7 | import { 8 | Keypair, 9 | Connection, 10 | PublicKey, 11 | LAMPORTS_PER_SOL, 12 | SystemProgram, 13 | VersionedTransaction, 14 | TransactionInstruction, 15 | TransactionMessage, 16 | ComputeBudgetProgram, 17 | Transaction, 18 | sendAndConfirmTransaction, 19 | Commitment 20 | } from '@solana/web3.js' 21 | import { bool, struct, u64 } from '@raydium-io/raydium-sdk' 22 | import base58 from 'bs58' 23 | import BN from 'bn.js' 24 | 25 | import { 26 | BUY_INTERVAL_MAX, 27 | BUY_INTERVAL_MIN, 28 | SELL_INTERVAL_MAX, 29 | SELL_INTERVAL_MIN, 30 | BUY_LOWER_PERCENT, 31 | BUY_UPPER_PERCENT, 32 | PRIVATE_KEY, 33 | RPC_ENDPOINT, 34 | RPC_WEBSOCKET_ENDPOINT, 35 | TOKEN_MINT, 36 | JITO_MODE, 37 | } from './config' 38 | import { execute } from './executor/legacy' 39 | import { executeJitoTx } from './executor/jito' 40 | import { getSellTxWithJupiter } from './utils/swapOnlyAmm' 41 | import { Data, readJson, saveDataToFile, sleep, formatDate } from './utils' 42 | 43 | const computeUnit = 100000; 44 | 45 | const TRADE_PROGRAM_ID = new PublicKey('6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'); 46 | const BONDING_ADDR_SEED = new Uint8Array([98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101]); 47 | 48 | let bonding: PublicKey; 49 | let assoc_bonding_addr: PublicKey; 50 | 51 | 52 | const GLOBAL = new PublicKey("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf"); 53 | const FEE_RECIPIENT = new PublicKey("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"); 54 | const SYSTEM_PROGRAM = new PublicKey("11111111111111111111111111111111"); 55 | const TOKEN_PROGRAM = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); 56 | const RENT = new PublicKey("SysvarRent111111111111111111111111111111111"); 57 | const PUMP_FUN_ACCOUNT = new PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"); 58 | const PUMP_FUN_PROGRAM = new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); 59 | 60 | export const solanaConnection = new Connection(RPC_ENDPOINT, { 61 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, commitment: "confirmed" 62 | }) 63 | 64 | const mainKp = Keypair.fromSecretKey(base58.decode(PRIVATE_KEY)) 65 | const baseMint = new PublicKey(TOKEN_MINT) 66 | const jitoCommitment: Commitment = "confirmed" 67 | 68 | export const BONDING_CURV = struct([ 69 | u64('virtualTokenReserves'), 70 | u64('virtualSolReserves'), 71 | u64('realTokenReserves'), 72 | u64('realSolReserves'), 73 | u64('tokenTotalSupply'), 74 | bool('complete'), 75 | ]) 76 | 77 | const main = async (mainKp: Keypair, baseMint: PublicKey, distritbutionNum: number,) => { 78 | 79 | const solBalance = await solanaConnection.getBalance(mainKp.publicKey) 80 | console.log(`Volume bot is running`, await formatDate()) 81 | console.log(`Wallet address: ${mainKp.publicKey.toBase58()}`) 82 | console.log(`Pool token mint: ${baseMint.toBase58()}`) 83 | console.log(`Wallet SOL balance: ${(solBalance / LAMPORTS_PER_SOL).toFixed(3)}SOL`) 84 | console.log(`Buying wait time max: ${BUY_INTERVAL_MAX}s`) 85 | console.log(`Buying wait time min: ${BUY_INTERVAL_MIN}s`) 86 | console.log(`Selling wait time max: ${SELL_INTERVAL_MAX}s`) 87 | console.log(`Selling wait time min: ${SELL_INTERVAL_MIN}s`) 88 | console.log(`Buy upper limit percent: ${BUY_UPPER_PERCENT}%`) 89 | console.log(`Buy lower limit percent: ${BUY_LOWER_PERCENT}%`) 90 | console.log(`Distribute SOL to ${distritbutionNum} wallets`) 91 | 92 | let data: { 93 | kp: Keypair; 94 | buyAmount: number; 95 | }[] | null = null 96 | 97 | if (solBalance < (BUY_LOWER_PERCENT + 0.002) * distritbutionNum) { 98 | console.log("Sol balance is not enough for distribution") 99 | } 100 | 101 | data = await distributeSol(solanaConnection, mainKp, distritbutionNum) 102 | if (data == null || data.length == 0) { 103 | console.log("Distribution failed") 104 | return 105 | } 106 | 107 | data.map(async ({ kp }, i) => { 108 | await sleep(i * 10000) 109 | let srcKp = kp 110 | while (true) { 111 | // buy part with random percent 112 | const BUY_WAIT_INTERVAL = Math.round(Math.random() * (BUY_INTERVAL_MAX - BUY_INTERVAL_MIN) + BUY_INTERVAL_MIN) 113 | const SELL_WAIT_INTERVAL = Math.round(Math.random() * (SELL_INTERVAL_MAX - SELL_INTERVAL_MIN) + SELL_INTERVAL_MIN) 114 | const solBalance = await solanaConnection.getBalance(srcKp.publicKey) 115 | 116 | let buyAmountInPercent = Number((Math.random() * (BUY_UPPER_PERCENT - BUY_LOWER_PERCENT) + BUY_LOWER_PERCENT).toFixed(3)) 117 | 118 | if (solBalance < 5 * 10 ** 6) { 119 | console.log("Sol balance is not enough in one of wallets") 120 | return 121 | } 122 | 123 | let buyAmountFirst = Math.floor((solBalance - 5 * 10 ** 6) / 100 * buyAmountInPercent) 124 | let buyAmountSecond = Math.floor(solBalance - buyAmountFirst - 5 * 10 ** 6) 125 | 126 | console.log(`balance: ${solBalance / 10 ** 9} first: ${buyAmountFirst / 10 ** 9} second: ${buyAmountSecond / 10 ** 9}`) 127 | // try buying until success 128 | let i = 0 129 | while (true) { 130 | try { 131 | if (i > 10) { 132 | console.log("Error in buy transaction") 133 | return 134 | } 135 | const poolState = await getPoolState(baseMint, solanaConnection); 136 | if (!poolState?.virtualSolReserves || !poolState.virtualTokenReserves) return 137 | const result = await buy(srcKp, baseMint, buyAmountFirst, undefined, solanaConnection, poolState?.virtualSolReserves, poolState?.virtualTokenReserves) 138 | if (result) { 139 | break 140 | } else { 141 | i++ 142 | await sleep(2000) 143 | } 144 | } catch (error) { 145 | i++ 146 | console.log('first buy error => ', error) 147 | } 148 | } 149 | 150 | console.log("first buy done") 151 | 152 | await sleep(BUY_WAIT_INTERVAL * 1000) 153 | 154 | await sleep(BUY_WAIT_INTERVAL * 1000) 155 | 156 | // try selling until success 157 | let j = 0 158 | while (true) { 159 | if (j > 10) { 160 | console.log("Error in sell transaction") 161 | return 162 | } 163 | console.log('sell start') 164 | const result = await sell(baseMint, srcKp) 165 | if (result) { 166 | break 167 | } else { 168 | j++ 169 | await sleep(2000) 170 | } 171 | } 172 | 173 | await sleep(SELL_WAIT_INTERVAL * 1000) 174 | 175 | // SOL transfer part 176 | const balance = await solanaConnection.getBalance(srcKp.publicKey) 177 | if (balance < 5 * 10 ** 6) { 178 | console.log("Sub wallet balance is not enough to continue volume swap") 179 | return 180 | } 181 | 182 | } 183 | }) 184 | } 185 | 186 | const distributeSol = async (connection: Connection, mainKp: Keypair, distritbutionNum: number) => { 187 | const data: Data[] = [] 188 | const wallets = [] 189 | try { 190 | const sendSolTx: TransactionInstruction[] = [] 191 | sendSolTx.push( 192 | ComputeBudgetProgram.setComputeUnitLimit({ units: 100_000 }), 193 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 250_000 }) 194 | ) 195 | const mainSolBal = await connection.getBalance(mainKp.publicKey) 196 | if (mainSolBal <= 4 * 10 ** 6) { 197 | console.log("Main wallet balance is not enough") 198 | return [] 199 | } 200 | let solAmount = Math.floor(mainSolBal / distritbutionNum - 5 * 10 ** 6) 201 | 202 | for (let i = 0; i < distritbutionNum; i++) { 203 | 204 | const wallet = Keypair.generate() 205 | wallets.push({ kp: wallet, buyAmount: solAmount }) 206 | 207 | sendSolTx.push( 208 | SystemProgram.transfer({ 209 | fromPubkey: mainKp.publicKey, 210 | toPubkey: wallet.publicKey, 211 | lamports: solAmount 212 | }) 213 | ) 214 | } 215 | 216 | let index = 0 217 | while (true) { 218 | try { 219 | if (index > 5) { 220 | console.log("Error in distribution") 221 | return null 222 | } 223 | const siTx = new Transaction().add(...sendSolTx) 224 | const latestBlockhash = await solanaConnection.getLatestBlockhash() 225 | siTx.feePayer = mainKp.publicKey 226 | siTx.recentBlockhash = latestBlockhash.blockhash 227 | const messageV0 = new TransactionMessage({ 228 | payerKey: mainKp.publicKey, 229 | recentBlockhash: latestBlockhash.blockhash, 230 | instructions: sendSolTx, 231 | }).compileToV0Message() 232 | const transaction = new VersionedTransaction(messageV0) 233 | transaction.sign([mainKp]) 234 | let txSig 235 | if (JITO_MODE) { 236 | txSig = await executeJitoTx([transaction], mainKp, jitoCommitment) 237 | } else { 238 | txSig = await execute(transaction, latestBlockhash, 1) 239 | } 240 | if (txSig) { 241 | const distibuteTx = txSig ? `https://solscan.io/tx/${txSig}` : '' 242 | console.log("SOL distributed ", distibuteTx, await formatDate()) 243 | break 244 | } 245 | index++ 246 | } catch (error) { 247 | index++ 248 | } 249 | } 250 | 251 | wallets.map((wallet) => { 252 | data.push({ 253 | privateKey: base58.encode(wallet.kp.secretKey), 254 | pubkey: wallet.kp.publicKey.toBase58(), 255 | }) 256 | }) 257 | try { 258 | saveDataToFile(data) 259 | } catch (error) { 260 | 261 | } 262 | console.log("Success in distribution") 263 | return wallets 264 | } catch (error) { 265 | console.log("🚀 ~ distributeSol ~ error:", error) 266 | console.log(`Failed to transfer SOL`) 267 | return null 268 | } 269 | } 270 | 271 | const sell = async (baseMint: PublicKey, wallet: Keypair) => { 272 | try { 273 | const data: Data[] = readJson() 274 | if (data.length == 0) { 275 | await sleep(1000) 276 | return null 277 | } 278 | 279 | const tokenAta = await getAssociatedTokenAddress(baseMint, wallet.publicKey) 280 | const tokenBalInfo = await solanaConnection.getTokenAccountBalance(tokenAta) 281 | if (!tokenBalInfo) { 282 | console.log("Balance incorrect") 283 | return null 284 | } 285 | const tokenBalance = tokenBalInfo.value.amount 286 | 287 | try { 288 | let sellTx = await getSellTxWithJupiter(wallet, baseMint, tokenBalance) 289 | 290 | if (sellTx == null) { 291 | console.log(`Error getting sell transaction`) 292 | return null 293 | } 294 | // console.log(await solanaConnection.simulateTransaction(sellTx)) 295 | let txSig 296 | if (JITO_MODE) { 297 | txSig = await executeJitoTx([sellTx], wallet, jitoCommitment) 298 | } else { 299 | const latestBlockhash = await solanaConnection.getLatestBlockhash() 300 | txSig = await execute(sellTx, latestBlockhash, 1) 301 | } 302 | if (txSig) { 303 | const tokenSellTx = txSig ? `https://solscan.io/tx/${txSig}` : '' 304 | console.log("Success in sell transaction: ", tokenSellTx) 305 | return tokenSellTx 306 | } else { 307 | return null 308 | } 309 | } catch (error) { 310 | return null 311 | } 312 | } catch (error) { 313 | return null 314 | } 315 | } 316 | 317 | const getPoolState = async (mint: PublicKey, connection: Connection) => { 318 | let virtualSolReserves; 319 | let virtualTokenReserves; 320 | try { 321 | // get the address of bonding curve and associated bonding curve 322 | [bonding] = PublicKey.findProgramAddressSync([BONDING_ADDR_SEED, mint.toBuffer()], TRADE_PROGRAM_ID); 323 | [assoc_bonding_addr] = PublicKey.findProgramAddressSync([bonding.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID); 324 | 325 | // get the accountinfo of bonding curve 326 | const accountInfo = await connection.getAccountInfo(bonding, "processed") 327 | // console.log("🚀 ~ accountInfo:", accountInfo) 328 | if (!accountInfo) return 329 | 330 | // get the poolstate of the bonding curve 331 | const poolState = BONDING_CURV.decode( 332 | accountInfo.data 333 | ); 334 | 335 | // Calculate tokens out 336 | virtualSolReserves = poolState.virtualSolReserves; 337 | virtualTokenReserves = poolState.virtualTokenReserves; 338 | 339 | return { virtualSolReserves, virtualTokenReserves } 340 | } catch (error) { 341 | console.log('getting pool state error => ', error); 342 | return { virtualSolReserves, virtualTokenReserves } 343 | } 344 | } 345 | 346 | export const buy = async ( 347 | keypair: Keypair, 348 | mint: PublicKey, 349 | solIn: number, 350 | slippageDecimal: number = 0.01, 351 | connection: Connection, 352 | virtualSolReserves: BN, 353 | virtualTokenReserves: BN 354 | ) => { 355 | const buyerKeypair = keypair; 356 | const buyerWallet = buyerKeypair.publicKey; 357 | const tokenMint = mint; 358 | let buyerAta = await getAssociatedTokenAddress(tokenMint, buyerWallet); 359 | 360 | try { 361 | let ixs: TransactionInstruction[] = [ 362 | // Increase compute budget to prioritize transaction 363 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1_000_000 }), 364 | ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnit }) 365 | ]; 366 | 367 | // Math.floor(txFee * 10 ** 10 / computeUnit * 10 ** 6) 368 | 369 | // Attempt to retrieve token account, otherwise create associated token account 370 | try { 371 | const buyerTokenAccountInfo = await connection.getAccountInfo(buyerAta); 372 | if (!buyerTokenAccountInfo) { 373 | ixs.push( 374 | createAssociatedTokenAccountInstruction( 375 | buyerWallet, 376 | buyerAta, 377 | buyerWallet, 378 | tokenMint, 379 | ) 380 | ); 381 | } 382 | } catch (error) { 383 | console.log("Creating token account error => ", error); 384 | return false; 385 | } 386 | 387 | const solInLamports = solIn; 388 | const tokenOut = Math.round(solInLamports * (virtualTokenReserves.div(virtualSolReserves)).toNumber()); 389 | 390 | 391 | const ATA_USER = buyerAta; 392 | const USER = buyerWallet; 393 | 394 | // Build account key list 395 | const keys = [ 396 | { pubkey: GLOBAL, isSigner: false, isWritable: false }, 397 | { pubkey: FEE_RECIPIENT, isSigner: false, isWritable: true }, 398 | { pubkey: tokenMint, isSigner: false, isWritable: false }, 399 | { pubkey: bonding, isSigner: false, isWritable: true }, 400 | { pubkey: assoc_bonding_addr, isSigner: false, isWritable: true }, 401 | { pubkey: ATA_USER, isSigner: false, isWritable: true }, 402 | { pubkey: USER, isSigner: true, isWritable: true }, 403 | { pubkey: SYSTEM_PROGRAM, isSigner: false, isWritable: false }, 404 | { pubkey: TOKEN_PROGRAM, isSigner: false, isWritable: false }, 405 | { pubkey: RENT, isSigner: false, isWritable: false }, 406 | { pubkey: PUMP_FUN_ACCOUNT, isSigner: false, isWritable: false }, 407 | { pubkey: PUMP_FUN_PROGRAM, isSigner: false, isWritable: false } 408 | ]; 409 | 410 | // Slippage calculation 411 | const calc_slippage_up = (sol_amount: number, slippage: number): number => { 412 | const lamports = sol_amount * LAMPORTS_PER_SOL; 413 | return Math.round(lamports * (1 + slippage)); 414 | }; 415 | 416 | const instruction_buf = Buffer.from('66063d1201daebea', 'hex'); 417 | const token_amount_buf = Buffer.alloc(8); 418 | token_amount_buf.writeBigUInt64LE(BigInt(tokenOut), 0); 419 | const slippage_buf = Buffer.alloc(8); 420 | slippage_buf.writeBigUInt64LE(BigInt(calc_slippage_up(solInLamports, slippageDecimal)), 0); 421 | const data = Buffer.concat([instruction_buf, token_amount_buf, slippage_buf]); 422 | 423 | const swapInstruction = new TransactionInstruction({ 424 | keys: keys, 425 | programId: PUMP_FUN_PROGRAM, 426 | data: data 427 | }); 428 | 429 | const blockhash = await connection.getLatestBlockhash(); 430 | 431 | ixs.push(swapInstruction); 432 | const legacyTransaction = new Transaction().add( 433 | ...ixs 434 | ) 435 | legacyTransaction.recentBlockhash = blockhash.blockhash; 436 | legacyTransaction.feePayer = buyerKeypair.publicKey; 437 | console.log("buying token") 438 | console.log('confirming transaction') 439 | const sig = await sendAndConfirmTransaction(connection, legacyTransaction, [buyerKeypair], { skipPreflight: true, preflightCommitment: 'processed' }) 440 | console.log("Buy signature: ", `https://solscan.io/tx/${sig}`); 441 | return sig 442 | 443 | } catch (e) { 444 | console.log(`Failed to buy token, ${mint}`); 445 | console.log("buying token error => ", e); 446 | return false; 447 | } 448 | }; 449 | 450 | main(mainKp, baseMint, 1) --------------------------------------------------------------------------------