├── module ├── executor │ ├── index.ts │ └── legacy.ts ├── index.ts ├── utils │ ├── index.ts │ ├── interface.ts │ ├── getTokenHolders.ts │ ├── generateRandomSentence.ts │ ├── jupiterSwap.ts │ ├── generateTokenMetadata.ts │ ├── workerPool.ts │ ├── jito.ts │ ├── processUtils.ts │ ├── distribution.ts │ └── utils.ts └── pumpfun_sdk │ ├── types.ts │ ├── globalAccount.ts │ ├── bondingCurveAccount.ts │ └── util.ts ├── .env.example ├── index.ts ├── tsconfig.json ├── README.md ├── package.json ├── processes ├── process_volume │ ├── gather.ts │ └── volume.ts ├── process_maker │ ├── gather.ts │ └── maker.ts └── index.ts └── .gitignore /module/executor/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jito" 2 | export * from "./legacy" 3 | -------------------------------------------------------------------------------- /module/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./executor"; 2 | export * from "./utils"; 3 | export * from "./configs"; 4 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MAIN_WALLET_PRIVATE_KEY= 2 | TOKEN_LAUNCHER_PRIVATE_KEY= 3 | RPC_ENDPOINT= 4 | RPC_WEBSOCKET_ENDPOINT= 5 | JITO_KEY= 6 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { TOKNE_INFO } from "./module"; 2 | import { pumpProcess } from "./processes"; 3 | 4 | (async () => { 5 | const resp = await pumpProcess(TOKNE_INFO); 6 | })(); 7 | -------------------------------------------------------------------------------- /module/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | export * from './jupiterSwap' 3 | export * from "./workerPool" 4 | export * from './distribution' 5 | export * from './interface' 6 | export * from './processUtils' 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 4 | "module": "commonjs", /* Specify what module code is generated. */ 5 | "resolveJsonModule": true, /* Enable importing .json files. */ 6 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 7 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 8 | "strict": true, /* Enable all strict type-checking options. */ 9 | "skipLibCheck": true /* Skip type checking all .d.ts files. */, 10 | "outDir": "dist" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pumpfun voume and market maker bot 2 | 3 | This is script that optimize market making process by automatic buy and sell, boost token volume. This is for giving basic understanding about volume bot and market maker. Feel free to reach out of me when you need full script and custom requirement[Telegram: https://t.me/DevCutup, Whatsapp: https://wa.me/13137423660]. 4 | 5 | ## Functionality 6 | 7 | 1. Volume boost 8 | 9 | 2. Market making 10 | 11 | 3. Automated buy/sell 12 | 13 | 4. Manual sell option 14 | 15 | 5. Gather and close LUT account 16 | 17 | ## How to run it 18 | 19 | ```bash 20 | # Clone the repository 21 | git clone https://github.com/cutupdev/Pumpfun-Volume-Market-Maker-Bot.git 22 | 23 | # Navigate to project directory 24 | cd Pumpfun-Volume-Market-Maker-Bot 25 | 26 | # Install dependencies 27 | npm install 28 | 29 | # Configure environment variables 30 | # Create a .env file with your settings (see .env.example Setup section) 31 | 32 | # Start the application 33 | npm run start 34 | ``` 35 | 36 | ### Contacts 37 | - Telegram: https://t.me/DevCutup 38 | - Whatsapp: https://wa.me/13137423660 39 | - Twitter: https://x.com/devcutup 40 | -------------------------------------------------------------------------------- /module/utils/interface.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey } from "@solana/web3.js"; 2 | 3 | export interface BundleData { 4 | kp: Keypair, 5 | lamports: number 6 | } 7 | 8 | export interface KeysFileData { 9 | mintStr: string, 10 | mainKp: string, 11 | mint: string, 12 | lut: string, 13 | bundlers: string[], 14 | tokenWallets: string[], 15 | makers: string[], 16 | volumes: string[] 17 | } 18 | 19 | export interface Keys { 20 | mintPk: PublicKey, 21 | mainKp: Keypair, 22 | mint: Keypair, 23 | lut: PublicKey | null, 24 | bundlers: Keypair[], 25 | tokenWallets: Keypair[], 26 | makers: Keypair[], 27 | volumes: Keypair[] 28 | } 29 | 30 | export interface LutData { 31 | mainKp: string, 32 | lut: string, 33 | needToClose: boolean 34 | } 35 | 36 | export interface Pack { 37 | bundledWallet: Keypair, 38 | distributionWallet: Keypair[] 39 | } 40 | 41 | export interface ITokenInfo { 42 | name: string; 43 | symbol: string; 44 | description: string; 45 | showName: string; 46 | createOn: number; 47 | twitter: string; 48 | telegram: string; 49 | website: string; 50 | file: string; 51 | } 52 | 53 | export interface IWebhookRequestBody { 54 | data: { 55 | name: string; 56 | symbol: string; 57 | description: string; 58 | tweet_url: string; 59 | logo?: string; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pumpfun-volume-and-market-maker", 3 | "author": "cutupdev", 4 | "scripts": { 5 | "start": "ts-node index.ts", 6 | "close": "ts-node ./processes/process_bundle/closeLut.ts", 7 | "status": "ts-node status.ts", 8 | "build": "tsc", 9 | "test": "npm run build && node ./dist/index.js", 10 | "manual-sell": "ts-node ./processes/manualSell.ts", 11 | "manual-gather": "ts-node ./processes/manualGather.ts", 12 | "feat-js": "npm run build && node ./dist/test.js", 13 | "feat": "ts-node ./test.ts" 14 | }, 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@coral-xyz/anchor": "^0.30.1", 18 | "@solana/spl-token": "^0.4.0", 19 | "@solana/web3.js": "^1.89.1", 20 | "@types/bn.js": "^5.1.5", 21 | "@types/body-parser": "^1.19.5", 22 | "@types/bs58": "^4.0.4", 23 | "@types/cors": "^2.8.17", 24 | "@types/express": "^4.17.21", 25 | "@types/mongoose": "^5.11.97", 26 | "@types/node": "^20.14.1", 27 | "@types/node-cron": "^3.0.11", 28 | "dotenv": "^16.4.5", 29 | "ts-node": "^10.9.2" 30 | }, 31 | "dependencies": { 32 | "@fleekxyz/sdk": "^1.4.2", 33 | "@raydium-io/raydium-sdk": "^1.3.1-beta.58", 34 | "axios": "^1.7.9", 35 | "axios-rate-limit": "^1.4.0", 36 | "body-parser": "^1.20.2", 37 | "bs58": "^6.0.0", 38 | "bull": "^4.16.5", 39 | "cors": "^2.8.5", 40 | "dayjs": "^1.11.13", 41 | "express": "^4.21.2", 42 | "ioredis": "^5.4.1", 43 | "jito-ts": "^4.2.0", 44 | "js-sha256": "^0.11.0", 45 | "mongodb": "^6.12.0", 46 | "mongoose": "^8.8.4", 47 | "node-cron": "^3.0.3", 48 | "tslib": "^2.8.1", 49 | "typescript": "^5.3.3", 50 | "undici": "^6.19.2", 51 | "workerpool": "^9.2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /processes/process_volume/gather.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, SystemProgram, Transaction, Connection, ComputeBudgetProgram, PublicKey, Commitment, sendAndConfirmTransaction, TransactionInstruction } from "@solana/web3.js" 2 | import base58 from "bs58" 3 | 4 | import { COMMITMENT, DEBUG_MODE, Keys, MAIN_WALLET_PRIVATE_KEY, readJson, RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT, sleep } from "../../module" 5 | 6 | const commitment: Commitment = COMMITMENT === "processed" ? "processed" : COMMITMENT === "confirmed" ? "confirmed" : "finalized" 7 | const connection = new Connection(RPC_ENDPOINT, { 8 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, commitment 9 | }) 10 | const mainKp = Keypair.fromSecretKey(base58.decode(MAIN_WALLET_PRIVATE_KEY)) 11 | 12 | 13 | 14 | export const volumeGather = async (keysData: Keys) => { 15 | const { volumes: volumeKps, mint: mintKp, mainKp } = keysData 16 | const mint = mintKp.publicKey 17 | 18 | if (DEBUG_MODE) 19 | console.log("Mint address imported : ", mint.toBase58()) 20 | 21 | const kpsWithSol: Keypair[] = [] 22 | for (let i = 0; i < volumeKps.length; i++) { 23 | const balance = await connection.getBalance(volumeKps[i].publicKey, { commitment: "processed" }) 24 | if (balance > 0) 25 | kpsWithSol.push(volumeKps[i]) 26 | } 27 | console.log("Volume keypairs number with funds : ", kpsWithSol.length) 28 | 29 | const kpBundles: Keypair[][] = [] 30 | let completed = 0 31 | const batchNum = 7 32 | const num = Math.ceil(kpsWithSol.length / batchNum) 33 | for (let i = 0; i < num; i++) { 34 | const bundle: Keypair[] = [] 35 | for (let j = 0; j < batchNum; j++) { 36 | let index = i * batchNum + j 37 | if (kpsWithSol[index]) { 38 | bundle.push(kpsWithSol[index]) 39 | } 40 | } 41 | kpBundles.push(bundle) 42 | } 43 | 44 | const processes = kpBundles.map(async (kps, index) => await volumeGatherProcess(kps, index)) 45 | await Promise.all(processes) 46 | } 47 | 48 | 49 | 50 | // volumeGather() 51 | -------------------------------------------------------------------------------- /module/pumpfun_sdk/types.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, VersionedTransactionResponse } from "@solana/web3.js"; 2 | 3 | export type CreateTokenMetadata = { 4 | name: string; 5 | symbol: string; 6 | description: string; 7 | file: Blob; 8 | twitter?: string; 9 | telegram?: string; 10 | website?: string; 11 | }; 12 | 13 | export type TokenMetadata = { 14 | name: string; 15 | symbol: string; 16 | description: string; 17 | image: string; 18 | showName: boolean; 19 | createdOn: string; 20 | twitter: string; 21 | }; 22 | 23 | export type CreateEvent = { 24 | name: string; 25 | symbol: string; 26 | uri: string; 27 | mint: PublicKey; 28 | bondingCurve: PublicKey; 29 | user: PublicKey; 30 | }; 31 | 32 | export type TradeEvent = { 33 | mint: PublicKey; 34 | solAmount: bigint; 35 | tokenAmount: bigint; 36 | isBuy: boolean; 37 | user: PublicKey; 38 | timestamp: number; 39 | virtualSolReserves: bigint; 40 | virtualTokenReserves: bigint; 41 | realSolReserves: bigint; 42 | realTokenReserves: bigint; 43 | }; 44 | 45 | export type CompleteEvent = { 46 | user: PublicKey; 47 | mint: PublicKey; 48 | bondingCurve: PublicKey; 49 | timestamp: number; 50 | }; 51 | 52 | export type SetParamsEvent = { 53 | feeRecipient: PublicKey; 54 | initialVirtualTokenReserves: bigint; 55 | initialVirtualSolReserves: bigint; 56 | initialRealTokenReserves: bigint; 57 | tokenTotalSupply: bigint; 58 | feeBasisPoints: bigint; 59 | }; 60 | 61 | export interface PumpFunEventHandlers { 62 | createEvent: CreateEvent; 63 | tradeEvent: TradeEvent; 64 | completeEvent: CompleteEvent; 65 | setParamsEvent: SetParamsEvent; 66 | } 67 | 68 | export type PumpFunEventType = keyof PumpFunEventHandlers; 69 | 70 | export type PriorityFee = { 71 | unitLimit: number; 72 | unitPrice: number; 73 | }; 74 | 75 | export type TransactionResult = { 76 | signature?: string; 77 | error?: unknown; 78 | results?: VersionedTransactionResponse; 79 | success: boolean; 80 | }; 81 | -------------------------------------------------------------------------------- /module/utils/getTokenHolders.ts: -------------------------------------------------------------------------------- 1 | 2 | import { DEBUG_MODE, RPC_ENDPOINT } from "../configs"; 3 | import { Keys } from "./interface"; 4 | interface Data { 5 | owner: string, 6 | amount: string 7 | } 8 | export const findHolders = async (mintStr: string, keysData: Keys, pool: string) => { 9 | let page = 1; 10 | let allOwners: Data[] = []; 11 | 12 | while (true) { 13 | const response = await fetch(RPC_ENDPOINT, { 14 | method: "POST", 15 | headers: { 16 | "Content-Type": "application/json", 17 | }, 18 | body: JSON.stringify({ 19 | jsonrpc: "2.0", 20 | method: "getTokenAccounts", 21 | id: "helius-test", 22 | params: { 23 | page: page, 24 | limit: 1000, 25 | displayOptions: {}, 26 | mint: mintStr 27 | }, 28 | }), 29 | }); 30 | 31 | // Check if any error in the response 32 | if (!response.ok) { 33 | console.log("Error fetching token holder numbers") 34 | if (DEBUG_MODE) 35 | console.log(`Error: ${response.status}, ${response.statusText}`); 36 | break; 37 | } 38 | 39 | const data = await response.json(); 40 | if (!data.result || data.result.token_accounts.length === 0) { 41 | break; 42 | } 43 | data.result.token_accounts.forEach((account: any) => 44 | allOwners.push({owner: account.owner, amount: account.amount}) 45 | ); 46 | page++; 47 | } 48 | 49 | console.log("token holders number", allOwners.length) 50 | 51 | const makers = keysData.makers.map(maker => maker.publicKey.toBase58()) 52 | const makersSet = new Set(makers) 53 | const bundlers = keysData.bundlers.map(bundler => bundler.publicKey.toBase58()) 54 | const bundlerSet = new Set(bundlers) 55 | const distributedWallets = keysData.tokenWallets.map(distWallet => distWallet.publicKey.toBase58()) 56 | const distSet = new Set(distributedWallets) 57 | const holderNum = allOwners.length 58 | console.log("pool:", pool) 59 | const nonBotBuyers = allOwners.filter(({owner}) => !makersSet.has(owner) && !bundlerSet.has(owner) && !distSet.has(owner) && (owner !== pool)) 60 | 61 | let totalAmount = BigInt(0) 62 | nonBotBuyers.map(({amount}) => { 63 | totalAmount += BigInt(amount) 64 | }) 65 | 66 | console.log("Non Bot Buyer number : ", nonBotBuyers.length) 67 | return {nonBotBuyerNum: nonBotBuyers.length, holderNum, nonBotHoldAmount: totalAmount} 68 | }; 69 | -------------------------------------------------------------------------------- /processes/process_volume/volume.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, SystemProgram, Transaction, Connection, ComputeBudgetProgram, TransactionInstruction, TransactionMessage, AddressLookupTableProgram, PublicKey, SYSVAR_RENT_PUBKEY, Commitment, sendAndConfirmTransaction, LAMPORTS_PER_SOL } from "@solana/web3.js" 2 | import { createAssociatedTokenAccountIdempotentInstruction, createAssociatedTokenAccountInstruction, getAssociatedTokenAddressSync, NATIVE_MINT, TOKEN_PROGRAM_ID } from "@solana/spl-token"; 3 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 4 | import { AnchorProvider } from "@coral-xyz/anchor"; 5 | 6 | import { PumpFunSDK } from "../../module/pumpfun_sdk/pumpfun"; 7 | import { 8 | // constants 9 | COMMITMENT, 10 | RETRY_MODE, 11 | GLOBAL_MINT, 12 | // executor 13 | distributeSol, 14 | // utils 15 | sleep, 16 | DEBUG_MODE, 17 | RPC_ENDPOINT, 18 | RPC_WEBSOCKET_ENDPOINT, 19 | VOLUME_DURATION, 20 | VOLUME_WALLET_NUM, 21 | VOLUME_BUY_AMOUNT_MAX, 22 | VOLUME_BUY_AMOUNT_MIN, 23 | Keys, 24 | } from "../../module" 25 | import { BN } from "bn.js"; 26 | 27 | 28 | const commitment: Commitment = COMMITMENT === "processed" ? "processed" : COMMITMENT === "confirmed" ? "confirmed" : "finalized" 29 | const connection = new Connection(RPC_ENDPOINT, { 30 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, commitment 31 | }) 32 | const sdk = new PumpFunSDK(new AnchorProvider(connection, new NodeWallet(new Keypair()), { commitment })); 33 | let solBalEnough: boolean = true 34 | let volumeNum = 0 35 | 36 | 37 | export const volume = async (keysData: Keys) => { 38 | 39 | const {volumes, mint: mintKp, mainKp} = keysData 40 | const global_mint = new PublicKey(GLOBAL_MINT) 41 | const microLamports = 620_500 42 | const distributionNum = 20 43 | const units = 120_000 44 | const fee = Math.floor(microLamports * units / 10 ** 6) 45 | const distSolAmount = ????????? 46 | console.log("Distribution amount", distSolAmount) 47 | 48 | const round = Math.ceil(VOLUME_WALLET_NUM / distributionNum) 49 | const volumeQueue = new Array(round).fill(true) 50 | 51 | const distributionInterval = Math.floor(VOLUME_DURATION / round * 1000) // millisecond 52 | 53 | const mint = mintKp.publicKey 54 | 55 | let { feeRecipient } = await sdk.getGlobalAccount(commitment); 56 | const associatedBondingCurve = getAssociatedTokenAddressSync( 57 | mint, 58 | sdk.getBondingCurvePDA(mint), 59 | true 60 | ); 61 | 62 | } 63 | 64 | 65 | // workerPool.worker({ 66 | // executeScript: volume 67 | // }); 68 | 69 | 70 | // volume() 71 | -------------------------------------------------------------------------------- /processes/process_maker/gather.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, SystemProgram, Transaction, Connection, ComputeBudgetProgram, Commitment, sendAndConfirmTransaction, TransactionInstruction } from "@solana/web3.js" 2 | import { createAssociatedTokenAccountIdempotentInstruction, createCloseAccountInstruction, createTransferCheckedInstruction, getAssociatedTokenAddressSync } from "@solana/spl-token"; 3 | import { COMMITMENT, DEBUG_MODE, Keys, RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT, sleep } from "../../module" 4 | 5 | const commitment: Commitment = COMMITMENT === "processed" ? "processed" : COMMITMENT === "confirmed" ? "confirmed" : "finalized" 6 | const connection = new Connection(RPC_ENDPOINT, { 7 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, commitment 8 | }) 9 | 10 | export const makerGather = async (keysData: Keys) => { 11 | const { mint: mintKp, makers: makerKps, mainKp } = keysData 12 | const mint = mintKp.publicKey 13 | 14 | if (DEBUG_MODE) 15 | console.log("Mint address imported : ", mint.toBase58()) 16 | 17 | const totalNumber = makerKps.length 18 | console.log("Total maker number", totalNumber) 19 | 20 | const kpBundles: Keypair[][] = [] 21 | let completed = 0 22 | const batchNum = 4 23 | const num = Math.ceil(makerKps.length / batchNum) 24 | for (let i = 0; i < num; i++) { 25 | const bundle: Keypair[] = [] 26 | for (let j = 0; j < batchNum; j++) { 27 | let index = i * batchNum + j 28 | if (makerKps[index]) { 29 | bundle.push(makerKps[index]) 30 | } 31 | } 32 | kpBundles.push(bundle) 33 | } 34 | 35 | const mainKpAta = getAssociatedTokenAddressSync(mint, mainKp.publicKey) 36 | 37 | async function makerGatherProcess(kps: Keypair[], index: number) { 38 | let isEmptyTx = true 39 | try { 40 | await sleep(index * 100) 41 | 42 | const signers: Keypair[] = [] 43 | 44 | if (!isEmptyTx) { 45 | transaction.feePayer = mainKp.publicKey 46 | transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash 47 | 48 | if (DEBUG_MODE) 49 | console.log(await connection.simulateTransaction(transaction)) 50 | 51 | const sig = await sendAndConfirmTransaction(connection, transaction, [mainKp, ...signers], { skipPreflight: true }) 52 | completed++ 53 | console.log(`SOL gathered and closed the token account : https://solscan.io/tx/${sig}`) 54 | } 55 | } catch (error) { 56 | console.log("Error happened while gathering in one of the bundles") 57 | if (DEBUG_MODE) 58 | console.log(error) 59 | return 60 | } 61 | } 62 | 63 | const processes = kpBundles.map(async (kps, index) => await makerGatherProcess(kps, index)) 64 | await Promise.all(processes) 65 | } 66 | -------------------------------------------------------------------------------- /module/pumpfun_sdk/globalAccount.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { struct, bool, u64, publicKey, Layout } from "@coral-xyz/borsh"; 3 | 4 | export class GlobalAccount { 5 | public discriminator: bigint; 6 | public initialized: boolean = false; 7 | public authority: PublicKey; 8 | public feeRecipient: PublicKey; 9 | public initialVirtualTokenReserves: bigint; 10 | public initialVirtualSolReserves: bigint; 11 | public initialRealTokenReserves: bigint; 12 | public tokenTotalSupply: bigint; 13 | public feeBasisPoints: bigint; 14 | 15 | constructor( 16 | discriminator: bigint, 17 | initialized: boolean, 18 | authority: PublicKey, 19 | feeRecipient: PublicKey, 20 | initialVirtualTokenReserves: bigint, 21 | initialVirtualSolReserves: bigint, 22 | initialRealTokenReserves: bigint, 23 | tokenTotalSupply: bigint, 24 | feeBasisPoints: bigint 25 | ) { 26 | this.discriminator = discriminator; 27 | this.initialized = initialized; 28 | this.authority = authority; 29 | this.feeRecipient = feeRecipient; 30 | this.initialVirtualTokenReserves = initialVirtualTokenReserves; 31 | this.initialVirtualSolReserves = initialVirtualSolReserves; 32 | this.initialRealTokenReserves = initialRealTokenReserves; 33 | this.tokenTotalSupply = tokenTotalSupply; 34 | this.feeBasisPoints = feeBasisPoints; 35 | } 36 | 37 | getInitialBuyPrice(amount: bigint): bigint { 38 | if (amount <= BigInt(0)) { 39 | return BigInt(0); 40 | } 41 | 42 | let n = this.initialVirtualSolReserves * this.initialVirtualTokenReserves; 43 | let i = this.initialVirtualSolReserves + amount; 44 | let r = n / i + BigInt(1); 45 | let s = this.initialVirtualTokenReserves - r; 46 | return s < this.initialRealTokenReserves 47 | ? s 48 | : this.initialRealTokenReserves; 49 | } 50 | 51 | public static fromBuffer(buffer: Buffer): GlobalAccount { 52 | const structure: Layout = struct([ 53 | u64("discriminator"), 54 | bool("initialized"), 55 | publicKey("authority"), 56 | publicKey("feeRecipient"), 57 | u64("initialVirtualTokenReserves"), 58 | u64("initialVirtualSolReserves"), 59 | u64("initialRealTokenReserves"), 60 | u64("tokenTotalSupply"), 61 | u64("feeBasisPoints"), 62 | ]); 63 | 64 | let value = structure.decode(buffer); 65 | return new GlobalAccount( 66 | BigInt(value.discriminator), 67 | value.initialized, 68 | value.authority, 69 | value.feeRecipient, 70 | BigInt(value.initialVirtualTokenReserves), 71 | BigInt(value.initialVirtualSolReserves), 72 | BigInt(value.initialRealTokenReserves), 73 | BigInt(value.tokenTotalSupply), 74 | BigInt(value.feeBasisPoints) 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /module/utils/generateRandomSentence.ts: -------------------------------------------------------------------------------- 1 | interface WordLists { 2 | subjects: string[]; 3 | verbs: string[]; 4 | adjectives: string[]; 5 | objects: string[]; 6 | prepositions: string[]; 7 | conjunctions: string[]; 8 | adverbs: string[]; 9 | } 10 | 11 | const words: WordLists = { 12 | subjects: ['the market', 'crypto', 'bitcoin', 'blockchain', 'web3', 'defi', 'NFTs', 'traders', 'holders', 'investors'], 13 | verbs: ['moves', 'grows', 'builds', 'creates', 'develops', 'transforms', 'revolutionizes', 'accelerates', 'drives', 'enables'], 14 | adjectives: ['innovative', 'decentralized', 'secure', 'scalable', 'revolutionary', 'dynamic', 'powerful', 'sustainable', 'efficient', 'advanced'], 15 | objects: ['solutions', 'opportunities', 'technologies', 'systems', 'platforms', 'networks', 'ecosystems', 'protocols', 'applications', 'frameworks'], 16 | prepositions: ['in', 'through', 'with', 'across', 'beyond', 'within', 'around', 'towards', 'during', 'throughout'], 17 | conjunctions: ['and', 'while', 'as', 'because', 'although', 'however', 'moreover', 'furthermore', 'therefore', 'meanwhile'], 18 | adverbs: ['quickly', 'efficiently', 'seamlessly', 'continuously', 'rapidly', 'effectively', 'consistently', 'reliably', 'securely', 'progressively'] 19 | }; 20 | 21 | function getRandomElement(array: T[]): T { 22 | return array[Math.floor(Math.random() * array.length)]; 23 | } 24 | 25 | export function generateRandomSentence(minWords: number = 10, maxWords: number = 25): string { 26 | const targetLength = Math.floor(Math.random() * (maxWords - minWords + 1)) + minWords; 27 | let sentence: string[] = []; 28 | 29 | while (sentence.length < targetLength) { 30 | const patterns = [ 31 | // Pattern: Subject + Verb + Object 32 | () => [getRandomElement(words.subjects), getRandomElement(words.verbs), getRandomElement(words.adjectives), getRandomElement(words.objects)], 33 | // Pattern: Subject + Verb + Preposition + Object 34 | () => [getRandomElement(words.subjects), getRandomElement(words.verbs), getRandomElement(words.prepositions), getRandomElement(words.objects)], 35 | // Pattern: Adverb + Subject + Verb + Object 36 | () => [getRandomElement(words.adverbs), getRandomElement(words.subjects), getRandomElement(words.verbs), getRandomElement(words.objects)], 37 | // Pattern: Conjunction + Subject + Verb 38 | () => [getRandomElement(words.conjunctions), getRandomElement(words.subjects), getRandomElement(words.verbs)] 39 | ]; 40 | 41 | const newPhrase = getRandomElement(patterns)(); 42 | sentence = [...sentence, ...newPhrase]; 43 | } 44 | 45 | sentence = sentence.slice(0, targetLength); 46 | sentence[0] = sentence[0].charAt(0).toUpperCase() + sentence[0].slice(1); 47 | 48 | return sentence.join(' ') + '.'; 49 | } 50 | -------------------------------------------------------------------------------- /module/utils/jupiterSwap.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | PublicKey, 4 | Keypair, 5 | Connection, 6 | VersionedTransaction 7 | } from '@solana/web3.js'; 8 | const SLIPPAGE = 50 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") 83 | return null 84 | } 85 | }; -------------------------------------------------------------------------------- /.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 | data 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # PNPM 127 | pnpm-lock.yaml 128 | 129 | # yarn v2 130 | .yarn/cache 131 | .yarn/unplugged 132 | .yarn/build-state.yml 133 | .yarn/install-state.gz 134 | .pnp.* 135 | 136 | # JetBrains 137 | .idea 138 | 139 | # Visual Studio Code 140 | *.code-workspace 141 | 142 | data.json 143 | keys/* 144 | downloads/* 145 | image/* 146 | -------------------------------------------------------------------------------- /module/utils/generateTokenMetadata.ts: -------------------------------------------------------------------------------- 1 | import { generateRandomSentence } from './generateRandomSentence'; 2 | 3 | export interface TokenMetadata { 4 | name: string; 5 | symbol: string; 6 | description: string; 7 | showName: string; 8 | createOn: string; 9 | twitter: string; 10 | telegram: string; 11 | website: string; 12 | file: string; 13 | } 14 | 15 | const adjectives: string[] = [ 16 | 'EPIC', 'SAFE', 'MEGA', 'HYPER', 'BASED', 'BULLISH', 'VIRAL', 'GOLDEN', 'RICH', 'WEALTHY', 17 | 'PREMIUM', 'ELITE', 'ALPHA', 'BETA', 'GAMMA', 'SUPER', 'ULTRA', 'MAXIMUM', 'SUPREME', 'ULTIMATE', 18 | 'EXTREME', 'POWERFUL', 'MIGHTY', 'STRONG', 'PRIME', 'HYPE', 'WILD', 'CRAZY', 'INSANE', 'SAVAGE', 19 | 'LEGENDARY', 'EPIC', 'AMAZING', 'AWESOME', 'FANTASTIC', 'DANK', 'BASED', 'CHAD', 'SIGMA', 'ALPHA', 20 | 'GIGACHAD', 'PEPE', 'DOGE', 'SHIBA', 'WOJAK', 'WINNING', 'PUMPING', 'MOONING', 'SOARING', 'RISING', 21 | 'FLYING', 'LAUNCHING', 'ROCKETING', 'DIAMOND', 'GOLDEN']; 22 | const nouns: string[] = ['HODL', 'GAINS', 'LAMBO', 'MOON', 'GEMS', 'PROFIT', 'WEALTH', 'FUTURE', 'DREAMS', 'MEME', 23 | 'DIAMOND', 'ROCKET', 'PEPE', 'DOGE', 'ASSETS', 'WALLET', 'TOKEN', 'COIN', 'PLATFORM', 'EXCHANGE', 24 | 'LEDGER', 'PROTOCOL', 'NETWORK', 'STAKE', 'FARM', 'LIQUIDITY', 'REWARD', 'INTEREST', 'DIVIDEND', 25 | 'AIRDROP', 'NFT', 'BLOCKCHAIN', 'SMARTCONTRACT', 'GOVERNANCE', 'LIQUID']; 26 | const actions: string[] = ['BUY', 'HOLD', 'STAKE', 'FARM', 'MINT', 'SWAP', 'TRADE', 'EARN', 'MINE', 'STACK', 27 | 'SELL', 'LEND', 'BORROW', 'LIQUIDATE', 'BURN', 'VOTE', 'REDEEM', 'CREATE', 'DIVIDING', 'CHAINING', 28 | 'POOLING', 'TRADING', 'SETTLE']; 29 | 30 | export function generateTokenMetadata(): TokenMetadata { 31 | 32 | const getRandomElement = (arr: T[]): T => { 33 | return arr[Math.floor(Math.random() * arr.length)]; 34 | }; 35 | 36 | const randomAdjective: string = getRandomElement(adjectives); 37 | const randomNoun: string = getRandomElement(nouns); 38 | const randomAction: string = getRandomElement(actions); 39 | 40 | const futureDate: Date = new Date(); 41 | futureDate.setFullYear(futureDate.getFullYear() + Math.floor(Math.random() * 2)); 42 | const tokenName = `${randomAdjective} ${randomAction} ${randomNoun}`; 43 | const symbol = `${randomAdjective}`.slice(0,2) + `${randomAction}`.slice(0,2) + `${randomNoun}`; 44 | const description: string = generateRandomSentence(15, 25); 45 | const randomString: string = Math.random().toString(36).substring(7); 46 | 47 | return { 48 | name: tokenName, 49 | symbol: symbol, 50 | description: description, 51 | showName: description, 52 | createOn: futureDate.toISOString(), 53 | twitter: `https://x.com/${randomString}`, 54 | telegram: `https://t.me/${randomString}`, 55 | website: `https://${randomString}.io`, 56 | file: `./image/heart.jpg`, 57 | }; 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /module/utils/workerPool.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Worker } from 'worker_threads'; 3 | 4 | interface Task { 5 | worker: string; // Path to the worker file 6 | data: any; 7 | } 8 | 9 | export class WorkerPool { 10 | private poolSize: number; 11 | private tasks: Task[] = []; 12 | private workers: Worker[] = []; 13 | private activeWorkers: number = 0; 14 | 15 | constructor(poolSize: number) { 16 | this.poolSize = poolSize; 17 | } 18 | 19 | // Add tasks to the queue 20 | addTask(task: Task): void { 21 | this.tasks.push(task); 22 | this.runTask(); 23 | } 24 | 25 | // Run the tasks using available slots in the pool 26 | private runTask(): void { 27 | if (this.tasks.length > 0 && this.activeWorkers < this.poolSize) { 28 | const task = this.tasks.shift(); 29 | if (task) { 30 | this.activeWorkers++; 31 | const worker = new Worker(task.worker, { workerData: task.data }); 32 | this.workers.push(worker); 33 | 34 | // Handle worker events 35 | worker.on('message', (message) => { 36 | console.log('Worker completed:', message); 37 | }); 38 | 39 | worker.on('error', (error) => { 40 | console.error('Worker error:', error); 41 | }); 42 | 43 | worker.on('exit', (exitCode) => { 44 | console.log('Worker exited with code:', exitCode); 45 | this.activeWorkers--; 46 | this.runTask(); 47 | }); 48 | } 49 | } 50 | } 51 | 52 | 53 | // // Add tasks to the queue and return a promise for task completion 54 | // addAsyncTask(task: Task): Promise { 55 | // return new Promise((resolve, reject) => { 56 | // this.tasks.push(task); 57 | // this.runAsyncTask(resolve, reject); 58 | // }); 59 | // } 60 | 61 | // // Run the tasks using the available slots in the pool 62 | // private runAsyncTask(resolve: (value: any) => void, reject: (reason: any) => void): void { 63 | // if (this.tasks.length > 0 && this.activeWorkers < this.poolSize) { 64 | // const task = this.tasks.shift(); 65 | // if (task) { 66 | // this.activeWorkers++; 67 | // const worker = new Worker(task.worker, { workerData: task.data }); 68 | // this.workers.push(worker); 69 | 70 | // // Handle worker events 71 | // worker.on('message', (message) => { 72 | // console.log('Async worker completed:', message); 73 | // resolve(message); // Resolve the promise when the worker sends a message 74 | // }); 75 | 76 | // worker.on('error', (error) => { 77 | // console.error('Async worker error:', error); 78 | // reject(error); // Reject the promise if the worker errors out 79 | // }); 80 | 81 | // worker.on('exit', (exitCode) => { 82 | // console.log('Async worker exited with code:', exitCode); 83 | // this.activeWorkers--; 84 | // if (exitCode !== 0) { 85 | // reject(new Error(`Async worker exited with code: ${exitCode}`)); // Reject if worker exits with non-zero code 86 | // } else { 87 | // this.runAsyncTask(resolve, reject); // Continue processing 88 | // } 89 | // }); 90 | // } 91 | // } 92 | // } 93 | 94 | // Terminate all workers 95 | terminateAll(): void { 96 | this.workers.forEach((worker) => worker.terminate()); 97 | this.workers = []; 98 | this.activeWorkers = 0; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /module/executor/legacy.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, SignatureStatus, TransactionConfirmationStatus, TransactionInstruction, TransactionMessage, TransactionSignature, VersionedTransaction } from "@solana/web3.js"; 2 | import { DEBUG_MODE, RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT } from "../configs"; 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 | 37 | export const createAndSendV0Tx = async (txInstructions: TransactionInstruction[], kp: Keypair, connection: Connection) => { 38 | try { 39 | let latestBlockhash = await connection.getLatestBlockhash(); 40 | 41 | const messageV0 = new TransactionMessage({ 42 | payerKey: kp.publicKey, 43 | recentBlockhash: latestBlockhash.blockhash, 44 | instructions: txInstructions 45 | }).compileToV0Message(); 46 | const transaction = new VersionedTransaction(messageV0); 47 | 48 | transaction.sign([kp]); 49 | if (DEBUG_MODE) 50 | console.log(await connection.simulateTransaction(transaction, {sigVerify: true})) 51 | const txid = await connection.sendTransaction(transaction, { skipPreflight: true }); 52 | 53 | const confirmation = await confirmTransaction(connection, txid); 54 | 55 | if (DEBUG_MODE) 56 | console.log('LUT transaction successfully confirmed!', '\n', `https://explorer.solana.com/tx/${txid}`); 57 | return confirmation.err == null 58 | 59 | } catch (error) { 60 | if (DEBUG_MODE) 61 | console.log("Error in transaction") 62 | return false 63 | } 64 | } 65 | 66 | async function confirmTransaction( 67 | connection: Connection, 68 | signature: TransactionSignature, 69 | desiredConfirmationStatus: TransactionConfirmationStatus = 'confirmed', 70 | timeout: number = 30000, 71 | pollInterval: number = 1000, 72 | searchTransactionHistory: boolean = false 73 | ): Promise { 74 | const start = Date.now(); 75 | 76 | while (Date.now() - start < timeout) { 77 | const { value: statuses } = await connection.getSignatureStatuses([signature], { searchTransactionHistory }); 78 | 79 | if (!statuses || statuses.length === 0) { 80 | throw new Error('Failed to get signature status'); 81 | } 82 | 83 | const status = statuses[0]; 84 | 85 | if (status === null) { 86 | // If status is null, the transaction is not yet known 87 | await new Promise(resolve => setTimeout(resolve, pollInterval)); 88 | continue; 89 | } 90 | 91 | if (status.err) { 92 | throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`); 93 | } 94 | 95 | if (status.confirmationStatus && status.confirmationStatus === desiredConfirmationStatus) { 96 | return status; 97 | } 98 | 99 | if (status.confirmationStatus === 'finalized') { 100 | return status; 101 | } 102 | 103 | await new Promise(resolve => setTimeout(resolve, pollInterval)); 104 | } 105 | 106 | throw new Error(`Transaction confirmation timeout after ${timeout}ms`); 107 | } 108 | -------------------------------------------------------------------------------- /processes/process_maker/maker.ts: -------------------------------------------------------------------------------- 1 | import { VersionedTransaction, Keypair, SystemProgram, Transaction, Connection, ComputeBudgetProgram, TransactionInstruction, TransactionMessage, AddressLookupTableProgram, PublicKey, SYSVAR_RENT_PUBKEY, Commitment, sendAndConfirmTransaction, LAMPORTS_PER_SOL } from "@solana/web3.js" 2 | import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, createAssociatedTokenAccountInstruction, getAssociatedTokenAddressSync, NATIVE_MINT, TOKEN_PROGRAM_ID } from "@solana/spl-token"; 3 | import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; 4 | import workerPool from 'workerpool' 5 | import { AnchorProvider } from "@coral-xyz/anchor"; 6 | import { constants, openAsBlob } from "fs"; 7 | import base58 from "bs58" 8 | 9 | import { PumpFunSDK } from "../../module/pumpfun_sdk/pumpfun"; 10 | import { 11 | // constants 12 | COMMITMENT, 13 | MAKER_RUN_DURATION, 14 | MAKER_WALLET_NUM, 15 | distributeSol, 16 | sleep, 17 | DEBUG_MODE, 18 | RPC_ENDPOINT, 19 | RPC_WEBSOCKET_ENDPOINT, 20 | Keys, 21 | MAKER_TOKEN_BUY_MAX, 22 | MAKER_TOKEN_BUY_MIN, 23 | } from "../../module" 24 | import { BN } from "bn.js"; 25 | 26 | const commitment: Commitment = COMMITMENT === "processed" ? "processed" : COMMITMENT === "confirmed" ? "confirmed" : "finalized" 27 | const connection = new Connection(RPC_ENDPOINT, { 28 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, commitment 29 | }) 30 | const sdk = new PumpFunSDK(new AnchorProvider(connection, new NodeWallet(new Keypair()), { commitment })); 31 | let solBalEnough: boolean = true 32 | let makerNum = 0 33 | 34 | 35 | export const maker = async (keysData: Keys) => { 36 | 37 | const { makers, mainKp, mint: mintKp } = keysData 38 | 39 | const microLamports = 62_500 40 | const distributionNum = 20 41 | const units = 80_000 42 | const fee = Math.floor(microLamports * units / 10 ** 6) 43 | const distSolAmount = ?????? 44 | console.log("Distribution amount", distSolAmount) 45 | // fee is 0.0001SOL 46 | const round = Math.ceil(MAKER_WALLET_NUM / distributionNum) 47 | const makerQueue = new Array(round).fill(true) 48 | const distributionInterval = Math.floor(MAKER_RUN_DURATION / round * 1000) // millisecond 49 | 50 | const mint = mintKp.publicKey 51 | 52 | let { feeRecipient } = await sdk.getGlobalAccount(commitment); 53 | const associatedBondingCurve = getAssociatedTokenAddressSync( 54 | mint, 55 | sdk.getBondingCurvePDA(mint), 56 | true 57 | ); 58 | 59 | makerQueue.map(async (result, index) => { 60 | const keypairs = [] 61 | for (let i = 0; i < distributionNum; i++) { 62 | if (makers[index * distributionNum + i]) 63 | keypairs.push(makers[index * distributionNum + i]) 64 | } 65 | 66 | if (!solBalEnough) return 67 | 68 | await sleep(distributionInterval * index) 69 | const mainBal = await connection.getBalance(mainKp.publicKey) 70 | if (mainBal < 0.003 * distributionNum) { 71 | console.log("Insufficient SOL balance in main wallet ", (mainBal / 10 ** 9).toFixed(3), "SOL") 72 | console.log("Exiting...") 73 | solBalEnough = false 74 | return 75 | } 76 | 77 | const kps = await distributeSol(connection, mainKp, distSolAmount, keypairs, "maker") 78 | if (!kps) { 79 | console.log("Distribution failed") 80 | return 81 | } 82 | 83 | kps.map(async (kp, kpIndex) => { 84 | try { 85 | const txInterval = Math.floor(distributionInterval / distributionNum) 86 | if (index) { 87 | await sleep(txInterval * kpIndex) 88 | } 89 | const ata = getAssociatedTokenAddressSync(mint, kp.publicKey) 90 | const tokenBuyAmount = Math.floor((Math.random() * (MAKER_TOKEN_BUY_MAX - MAKER_TOKEN_BUY_MIN) + MAKER_TOKEN_BUY_MIN) * 10 ** 6) 91 | 92 | if (DEBUG_MODE) 93 | console.log((await connection.getBalance(kp.publicKey) / 10 ** 9).toFixed(4), "sol left in wallet") 94 | makerNum++ 95 | } catch (error) { 96 | console.log("Error in maker transaction") 97 | if (DEBUG_MODE) 98 | console.log(error) 99 | } 100 | }) 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /processes/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Commitment, 3 | Connection, 4 | Keypair, 5 | LAMPORTS_PER_SOL, 6 | } from "@solana/web3.js"; 7 | import base58 from "bs58"; 8 | 9 | import { maker } from "./process_maker/maker"; 10 | import { volume } from "./process_volume/volume"; 11 | import { makerGather } from "./process_maker/gather"; 12 | import { volumeGather } from "./process_volume/gather"; 13 | import { 14 | BUYER_SOL_AMOUNT, 15 | COMMITMENT, 16 | DEBUG_MODE, 17 | MAKER_WALLET_NUM, 18 | MINT_TO_MANUAL_GATHER, 19 | MAIN_WALLET_PRIVATE_KEY, 20 | RETRY_MODE, 21 | RPC_ENDPOINT, 22 | RPC_WEBSOCKET_ENDPOINT, 23 | VOLUME_WALLET_NUM, 24 | makeAndSaveKeys, 25 | importKeysFromFile, 26 | Keys, 27 | checkBundleResult, 28 | transferSol, 29 | transferToMain, 30 | validate, 31 | sleep, 32 | TOKEN_DISTRIBUTION_WALLET_NUM, 33 | ITokenInfo, 34 | initLogsFile, 35 | extendLogsFile, 36 | } from "../module"; 37 | 38 | export const pumpProcess = async (tokenMetaData: ITokenInfo) => { 39 | try { 40 | // set token data accordingly in the current script 41 | console.log("token data", tokenMetaData); 42 | 43 | // Make keypairs for token process 44 | let keysData: Keys; 45 | if (RETRY_MODE) { 46 | const data = importKeysFromFile(mint); 47 | if (!data) return; 48 | keysData = data; 49 | } else { 50 | keysData = makeAndSaveKeys( 51 | MAKER_WALLET_NUM, 52 | VOLUME_WALLET_NUM, 53 | TOKEN_DISTRIBUTION_WALLET_NUM 54 | ); 55 | } 56 | 57 | const mintAddress = keysData.mintPk.toBase58(); 58 | initLogsFile(mintAddress, `logs initiated for${mintAddress}`); 59 | 60 | // Print the main wallet balance 61 | console.log( 62 | "SOL balance in main wallet is ", 63 | (mainBal / 10 ** 9).toFixed(3) 64 | ); 65 | initLogsFile( 66 | mintAddress, 67 | `SOL balance in main wallet is , 68 | ${(mainBal / 10 ** 9).toFixed(3)}` 69 | ); 70 | console.log("Mint address for token : ", keysData.mintPk.toBase58()); 71 | initLogsFile( 72 | mintAddress, 73 | `"Mint address for token : ", ${keysData.mintPk.toBase58()}` 74 | ); 75 | 76 | // Transfer SOL to token wallet, (BUYER_SOL to token wallet) 77 | const wallet = keysData.mainKp; 78 | const sig = await transferSol( 79 | connection, 80 | mainKp, 81 | wallet.publicKey, 82 | Math.floor(BUYER_SOL_AMOUNT * LAMPORTS_PER_SOL) 83 | ); 84 | if (!sig) return; 85 | console.log( 86 | `Transfer for token process success: https://solscan.io/tx/${sig}` 87 | ); 88 | extendLogsFile( 89 | mintAddress, 90 | `Transfer for token process success: https://solscan.io/tx/${sig}` 91 | ); 92 | 93 | // Bundling part 94 | for (let i = 0; i < 3; i++) { 95 | await sleep(5000); 96 | maker(keysData); 97 | volume(keysData); 98 | break 99 | if (i == 2) { 100 | extendLogsFile( 101 | mintAddress, 102 | "============== Bundle failed, gathering and exiting... ==============" 103 | ); 104 | console.log( 105 | "============== Bundle failed, gathering and exiting... ==============" 106 | ); 107 | } else { 108 | console.log( 109 | "============== Bundle failed, retrying... ==============" 110 | ); 111 | } 112 | } 113 | 114 | // Gather all funds back to token wallet 115 | await Promise.all([ 116 | makerGather(keysData), 117 | volumeGather(keysData), 118 | ]); 119 | 120 | // double check funds in keypairs 121 | 122 | const keys = [ 123 | ...keysData.bundlers, 124 | ...keysData.makers, 125 | ...keysData.volumes, 126 | ]; 127 | await gather(keysData.mainKp, keys); 128 | await sleep(5000); 129 | 130 | // Transfer token wallet's fund back to main wallet 131 | const gatherSig = await transferToMain(connection, wallet, mainKp, keysData.mint.publicKey); 132 | console.log( 133 | `Token process ended and gathered the fund back to main wallet https://solscan.io/tx/${gatherSig}` 134 | ); 135 | extendLogsFile( 136 | mintAddress, 137 | `Token process ended and gathered the fund back to main wallet https://solscan.io/tx/${gatherSig}` 138 | ); 139 | 140 | return "Pump process completed successfully!"; 141 | } catch (error) { 142 | console.log("Error in token process"); 143 | 144 | if (DEBUG_MODE) console.log(error); 145 | } 146 | }; 147 | 148 | // workerpool.worker({ 149 | // executeScript: pumpProcess, 150 | // }); 151 | -------------------------------------------------------------------------------- /module/utils/jito.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | Connection, 4 | Keypair, 5 | VersionedTransaction, 6 | } from '@solana/web3.js'; 7 | import { SearcherClient, searcherClient, SearcherClientError } from 'jito-ts/dist/sdk/block-engine/searcher'; 8 | import { Bundle } from 'jito-ts/dist/sdk/block-engine/types'; 9 | import { isError, Result } from 'jito-ts/dist/sdk/block-engine/utils'; 10 | import bs58 from 'bs58'; 11 | import { BLOCK_ENGINE_URL, JITO_KEY } from '../configs'; 12 | import base58 from 'bs58'; 13 | import { sleep } from './utils'; 14 | 15 | export const sendBundles = async ( 16 | c: SearcherClient, 17 | transactions: VersionedTransaction[], 18 | bundleTransactionLimit: number, 19 | keypair: Keypair, 20 | connection: Connection 21 | ): Promise> => { 22 | try { 23 | let isLeaderSlot = false; 24 | while (!isLeaderSlot) { 25 | const next_leader = await c.getNextScheduledLeader(); 26 | if (!next_leader.ok) { 27 | return next_leader; 28 | } 29 | const num_slots = next_leader.value.nextLeaderSlot - next_leader.value.currentSlot; 30 | isLeaderSlot = num_slots <= 2; 31 | // console.log(`next jito leader slot in ${num_slots} slots`); 32 | await new Promise(r => setTimeout(r, 1000)); 33 | } 34 | 35 | // const blockHash = await connection.getLatestBlockhash(); 36 | const b = new Bundle([], bundleTransactionLimit); 37 | // console.log(blockHash.blockhash); 38 | 39 | const bundles = [b]; 40 | 41 | let maybeBundle = b.addTransactions(...transactions); 42 | if (isError(maybeBundle)) { 43 | return { 44 | ok: false, 45 | error: new SearcherClientError( 46 | 3, // INVALID_ARGUMENT 47 | 'Failed to add transactions to bundle', 48 | maybeBundle.message 49 | ) 50 | }; 51 | } 52 | 53 | if (isError(maybeBundle)) { 54 | return { 55 | ok: false, 56 | error: new SearcherClientError( 57 | 3, // INVALID_ARGUMENT 58 | 'Failed to add tip transaction to bundle', 59 | maybeBundle.message 60 | ) 61 | }; 62 | } 63 | 64 | type BundleResponse = Result; 65 | const results: BundleResponse[] = await Promise.all( 66 | bundles.map(async b => { 67 | try { 68 | const resp = await c.sendBundle(b); 69 | if (!resp.ok) { 70 | return resp; 71 | } 72 | // console.log('Bundle ID : ', resp.value); 73 | return resp; 74 | } catch (e) { 75 | console.error('error sending bundle:', e); 76 | return { 77 | ok: false, 78 | error: e as SearcherClientError 79 | }; 80 | } 81 | }) 82 | ); 83 | 84 | // Check if any bundle sends failed 85 | const error = results.find(r => !r.ok); 86 | if (error && !error.ok) { 87 | return { ok: false, error: error.error }; 88 | } 89 | 90 | // At this point we know all results are successful 91 | const successResults = results.filter((r): r is { ok: true; value: string } => r.ok); 92 | return { ok: true, value: successResults.map(r => r.value) }; 93 | 94 | } catch (e) { 95 | return { 96 | ok: false, 97 | error: e as SearcherClientError 98 | }; 99 | } 100 | }; 101 | 102 | export const onBundleResult = (c: SearcherClient) => { 103 | return c.onBundleResult( 104 | result => { 105 | console.log('Received bundle result', result.bundleId); 106 | }, 107 | e => { 108 | console.error('Bundle result error:', e); 109 | throw e; 110 | } 111 | ); 112 | }; 113 | 114 | export const executeBundle = async (connection: Connection, transactions: VersionedTransaction[], mode: "buy" | "sell") => { 115 | const keypair = Keypair.fromSecretKey(bs58.decode(JITO_KEY)); 116 | const bundleTransactionLimit = 5 117 | const c = searcherClient(BLOCK_ENGINE_URL, keypair); 118 | await sleep(2000) 119 | const result = await sendBundles(c, transactions, bundleTransactionLimit, keypair, connection); 120 | if (!result.ok) { 121 | console.error('Failed to send bundles:', result.error); 122 | return; 123 | } 124 | 125 | console.log('Successfully sent bundles, checking result'); 126 | onBundleResult(c); 127 | 128 | let latestBlockhash = await connection.getLatestBlockhash(); 129 | 130 | const bundleSig = base58.encode(transactions[0].signatures[0]); 131 | const confirmation = await connection.confirmTransaction( 132 | { 133 | signature: bundleSig, 134 | lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, 135 | blockhash: latestBlockhash.blockhash, 136 | }, 137 | 'confirmed' 138 | ); 139 | 140 | if (mode == "sell") 141 | console.log( 142 | "Wallet sold token in bundle, gathering funds back to main wallets...." 143 | ); 144 | else 145 | console.log( 146 | "Wallets bought the token plz check keypairs in the data.json file in key folder" 147 | ); 148 | 149 | if (confirmation.value.err) { 150 | console.log("Confirmtaion error"); 151 | return; 152 | } else { 153 | return bundleSig; 154 | } 155 | }; 156 | -------------------------------------------------------------------------------- /module/utils/processUtils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComputeBudgetProgram, 3 | Connection, 4 | Keypair, 5 | LAMPORTS_PER_SOL, 6 | PublicKey, 7 | sendAndConfirmTransaction, 8 | SystemProgram, 9 | Transaction, 10 | } from "@solana/web3.js"; 11 | import { 12 | BUNDLE_SLIPPAGE, 13 | BUYER_SOL_AMOUNT, 14 | DEBUG_MODE, 15 | SWAP_AMOUNT_MAX, 16 | SWAP_AMOUNT_MIN, 17 | SWAP_AMOUNT_TOTAL, 18 | TOKEN_DISTRIBUTION_WALLET_NUM, 19 | } from "../configs"; 20 | import { getAssociatedTokenAddressSync } from "@solana/spl-token"; 21 | import { Keys } from "./interface"; 22 | import { extendLogsFile } from "./utils"; 23 | 24 | export const transferSol = async ( 25 | connection: Connection, 26 | from: Keypair, 27 | to: PublicKey, 28 | amount: number 29 | ) => { 30 | try { 31 | const tx = new Transaction().add( 32 | ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }), 33 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 10_000 }), 34 | SystemProgram.transfer({ 35 | fromPubkey: from.publicKey, 36 | toPubkey: to, 37 | lamports: amount, 38 | }) 39 | ); 40 | tx.feePayer = from.publicKey; 41 | tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; 42 | const sig = await sendAndConfirmTransaction(connection, tx, [from], { commitment: "confirmed" }); 43 | return sig; 44 | } catch (error) { 45 | console.log("Error in SOL transfer"); 46 | if (DEBUG_MODE) console.log(error); 47 | return; 48 | } 49 | }; 50 | 51 | export const transferToMain = async ( 52 | connection: Connection, 53 | tokenWallet: Keypair, 54 | mainWallet: Keypair, 55 | mint: PublicKey 56 | ) => { 57 | try { 58 | const resultBal = await connection.getBalance(tokenWallet.publicKey); 59 | const solUsedInTokenProcess = (BUYER_SOL_AMOUNT - resultBal / 10 ** 9).toFixed(3) 60 | console.log("Total SOL used in token process is ", solUsedInTokenProcess, "SOL") 61 | extendLogsFile( 62 | mint.toBase58(), 63 | `Total SOL used in token process is ${solUsedInTokenProcess}SOL` 64 | ); 65 | const gatherTx = new Transaction().add( 66 | ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }), 67 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 10_000 }), 68 | SystemProgram.transfer({ 69 | fromPubkey: tokenWallet.publicKey, 70 | toPubkey: mainWallet.publicKey, 71 | lamports: resultBal, 72 | }) 73 | ); 74 | gatherTx.feePayer = mainWallet.publicKey; 75 | gatherTx.recentBlockhash = ( 76 | await connection.getLatestBlockhash() 77 | ).blockhash; 78 | const gatherSig = await sendAndConfirmTransaction(connection, gatherTx, [ 79 | tokenWallet, 80 | mainWallet, 81 | ]); 82 | return gatherSig; 83 | } catch (error) { 84 | console.log("Error in SOL transfer"); 85 | if (DEBUG_MODE) console.log(error); 86 | return; 87 | } 88 | }; 89 | 90 | export const checkBundleResult = async ( 91 | connection: Connection, 92 | keysData: Keys 93 | ) => { 94 | try { 95 | const mint = keysData.mintPk; 96 | const bundleWalletKp = keysData.bundlers[0]; 97 | const ata = getAssociatedTokenAddressSync(mint, bundleWalletKp.publicKey); 98 | const info = await connection.getAccountInfo(ata); 99 | if (info) { 100 | const balance = (await connection.getTokenAccountBalance(ata)).value 101 | .uiAmount; 102 | console.log("One of the bundled wallet has token balance of ", balance); 103 | return true; 104 | } else { 105 | return false; 106 | } 107 | } catch (error) { 108 | //@ts-ignore 109 | console.log("Error while checking the bundle result", error?.message); 110 | return false; 111 | } 112 | }; 113 | 114 | export const validate = (mainBal: number) => { 115 | // check bundling amount sum 116 | if (BUYER_SOL_AMOUNT * 10 ** 9 > mainBal) { 117 | console.log("Insufficient main balance") 118 | return false 119 | } 120 | 121 | if (SWAP_AMOUNT_MAX * 20 < SWAP_AMOUNT_TOTAL) { 122 | console.log(SWAP_AMOUNT_MAX * 20); 123 | console.log( 124 | "Invalid env setting for bundling: max bundle amount * wallet num must be greater than or equal to total." 125 | ); 126 | return false; 127 | } 128 | 129 | if (SWAP_AMOUNT_MIN * 20 > SWAP_AMOUNT_TOTAL) { 130 | console.log(SWAP_AMOUNT_MIN); 131 | console.log(SWAP_AMOUNT_TOTAL); 132 | console.log(SWAP_AMOUNT_MIN * 20); 133 | console.log( 134 | "Invalid env setting for bundling: min bundle amount * wallet num must be less than or equal to total." 135 | ); 136 | return false; 137 | } 138 | 139 | if ( 140 | mainBal < BUYER_SOL_AMOUNT * 10 ** 9 && 141 | BUYER_SOL_AMOUNT * 10 ** 9 < 142 | SWAP_AMOUNT_TOTAL * LAMPORTS_PER_SOL * (1 + BUNDLE_SLIPPAGE / 100) + 143 | 10 ** 8 + 144 | 2039280 * (Math.ceil(TOKEN_DISTRIBUTION_WALLET_NUM / 20) + 1) 145 | ) { 146 | console.log("Main balance is not enough to run the token"); 147 | return false; 148 | } 149 | 150 | if (SWAP_AMOUNT_TOTAL + 0.2 > BUYER_SOL_AMOUNT) { 151 | console.log( 152 | "Env setting for swap_total_amount and wallet_balance_buyer_sol_amount is not valid" 153 | ); 154 | return false; 155 | } 156 | return true; 157 | }; 158 | -------------------------------------------------------------------------------- /module/pumpfun_sdk/bondingCurveAccount.ts: -------------------------------------------------------------------------------- 1 | import { struct, bool, u64, Layout } from "@coral-xyz/borsh"; 2 | import { BN } from "bn.js"; 3 | 4 | export class BondingCurveAccount { 5 | public discriminator: bigint; 6 | public virtualTokenReserves: bigint; 7 | public virtualSolReserves: bigint; 8 | public realTokenReserves: bigint; 9 | public realSolReserves: bigint; 10 | public tokenTotalSupply: bigint; 11 | public complete: boolean; 12 | 13 | constructor( 14 | discriminator: bigint, 15 | virtualTokenReserves: bigint, 16 | virtualSolReserves: bigint, 17 | realTokenReserves: bigint, 18 | realSolReserves: bigint, 19 | tokenTotalSupply: bigint, 20 | complete: boolean 21 | ) { 22 | this.discriminator = discriminator; 23 | this.virtualTokenReserves = virtualTokenReserves; 24 | this.virtualSolReserves = virtualSolReserves; 25 | this.realTokenReserves = realTokenReserves; 26 | this.realSolReserves = realSolReserves; 27 | this.tokenTotalSupply = tokenTotalSupply; 28 | this.complete = complete; 29 | } 30 | 31 | getBuyPrice(amount: bigint): bigint { 32 | if (this.complete) { 33 | throw new Error("Curve is complete"); 34 | } 35 | 36 | if (amount <= BigInt(0)) { 37 | return BigInt(0); 38 | } 39 | 40 | // Calculate the product of virtual reserves 41 | let n = this.virtualSolReserves * this.virtualTokenReserves; 42 | 43 | // Calculate the new virtual sol reserves after the purchase 44 | let i = this.virtualSolReserves + amount; 45 | 46 | // Calculate the new virtual token reserves after the purchase 47 | let r = n / i + BigInt(1); 48 | 49 | // Calculate the amount of tokens to be purchased 50 | let s = this.virtualTokenReserves - r; 51 | 52 | // Return the minimum of the calculated tokens and real token reserves 53 | return s < this.realTokenReserves ? s : this.realTokenReserves; 54 | } 55 | 56 | getPriceWhenBundleBought(tokenAmount: string, solLamports: string): number { 57 | const initialStatus = { 58 | discriminator: BigInt("6966180631402821399"), 59 | virtualTokenReserves: BigInt("1073000000000000"), 60 | virtualSolReserves: BigInt("30000000000"), 61 | realTokenReserves: BigInt("793100000000000"), 62 | realSolReserves: BigInt("0"), 63 | tokenTotalSupply: BigInt("1000000000000000"), 64 | complete: false 65 | } 66 | 67 | const price = new BN(initialStatus.virtualTokenReserves.toString()).sub(new BN(tokenAmount)) 68 | .div(new BN(initialStatus.virtualSolReserves.toString()).add(new BN(solLamports))).toNumber() 69 | return 1 / price 70 | } 71 | 72 | getSellPrice(amount: bigint, feeBasisPoints: bigint): bigint { 73 | if (this.complete) { 74 | throw new Error("Curve is complete"); 75 | } 76 | 77 | if (amount <= BigInt(0)) { 78 | return BigInt(0); 79 | } 80 | 81 | // Calculate the proportional amount of virtual sol reserves to be received 82 | let n = 83 | (amount * this.virtualSolReserves) / (this.virtualTokenReserves + amount); 84 | 85 | // Calculate the fee amount in the same units 86 | let a = (n * feeBasisPoints) / BigInt(10000); 87 | 88 | // Return the net amount after deducting the fee 89 | return n - a; 90 | } 91 | 92 | getCurrentPrice(): number { 93 | if (this.virtualTokenReserves === BigInt(0)) { 94 | return 0; 95 | } 96 | const price = new BN(this.virtualTokenReserves.toString()).div(new BN(this.virtualSolReserves.toString())).toNumber() 97 | return 1 / price 98 | } 99 | 100 | getMarketCapSOL(): bigint { 101 | if (this.virtualTokenReserves === BigInt(0)) { 102 | return BigInt(0); 103 | } 104 | 105 | return ( 106 | (this.tokenTotalSupply * this.virtualSolReserves) / 107 | this.virtualTokenReserves 108 | ); 109 | } 110 | 111 | getFinalMarketCapSOL(feeBasisPoints: bigint): bigint { 112 | let totalSellValue = this.getBuyOutPrice( 113 | this.realTokenReserves, 114 | feeBasisPoints 115 | ); 116 | let totalVirtualValue = this.virtualSolReserves + totalSellValue; 117 | let totalVirtualTokens = this.virtualTokenReserves - this.realTokenReserves; 118 | 119 | if (totalVirtualTokens === BigInt(0)) { 120 | return BigInt(0); 121 | } 122 | 123 | return (this.tokenTotalSupply * totalVirtualValue) / totalVirtualTokens; 124 | } 125 | 126 | getBuyOutPrice(amount: bigint, feeBasisPoints: bigint): bigint { 127 | let solTokens = 128 | amount < this.realSolReserves ? this.realSolReserves : amount; 129 | let totalSellValue = 130 | (solTokens * this.virtualSolReserves) / 131 | (this.virtualTokenReserves - solTokens) + 132 | BigInt(1); 133 | let fee = (totalSellValue * feeBasisPoints) / BigInt(10000); 134 | return totalSellValue + fee; 135 | } 136 | 137 | public static fromBuffer(buffer: Buffer): BondingCurveAccount { 138 | const structure: Layout = struct([ 139 | u64("discriminator"), 140 | u64("virtualTokenReserves"), 141 | u64("virtualSolReserves"), 142 | u64("realTokenReserves"), 143 | u64("realSolReserves"), 144 | u64("tokenTotalSupply"), 145 | bool("complete"), 146 | ]); 147 | 148 | let value = structure.decode(buffer); 149 | return new BondingCurveAccount( 150 | BigInt(value.discriminator), 151 | BigInt(value.virtualTokenReserves), 152 | BigInt(value.virtualSolReserves), 153 | BigInt(value.realTokenReserves), 154 | BigInt(value.realSolReserves), 155 | BigInt(value.tokenTotalSupply), 156 | value.complete 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /module/utils/distribution.ts: -------------------------------------------------------------------------------- 1 | import { ComputeBudgetProgram, Connection, Keypair, sendAndConfirmTransaction, SystemProgram, Transaction, TransactionInstruction } from "@solana/web3.js" 2 | import { DEBUG_MODE } from "../configs" 3 | import { Keys, Pack } from "./interface" 4 | import { sleep } from "./utils" 5 | import { createAssociatedTokenAccountInstruction, createCloseAccountInstruction, createTransferCheckedInstruction, getAssociatedTokenAddressSync } from "@solana/spl-token" 6 | 7 | // Distribute SOL to maker or volume wallets 8 | export const distributeSol = async ( 9 | connection: Connection, 10 | mainKp: Keypair, 11 | solAmount: number, 12 | keypairs: Keypair[], 13 | mode: "maker" | "volume" 14 | ) => { 15 | const data: string[] = [] 16 | try { 17 | const sendSolTx: TransactionInstruction[] = [] 18 | sendSolTx.push( 19 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100_000 }), 20 | ComputeBudgetProgram.setComputeUnitLimit({ units: 12_000 }) 21 | ) 22 | const mainSolBal = await connection.getBalance(mainKp.publicKey) 23 | if (mainSolBal <= 5 * 10 ** 7) { 24 | console.log("Main wallet balance is not enough") 25 | return [] 26 | } 27 | 28 | for (let i = 0; i < keypairs.length; i++) { 29 | const wallet = keypairs[i] 30 | sendSolTx.push( 31 | SystemProgram.transfer({ 32 | fromPubkey: mainKp.publicKey, 33 | toPubkey: wallet.publicKey, 34 | lamports: solAmount 35 | }) 36 | ) 37 | } 38 | 39 | try { 40 | const siTx = new Transaction().add(...sendSolTx) 41 | const latestBlockhash = await connection.getLatestBlockhash() 42 | siTx.feePayer = mainKp.publicKey 43 | siTx.recentBlockhash = latestBlockhash.blockhash 44 | 45 | if (DEBUG_MODE) 46 | console.log(await connection.simulateTransaction(siTx)) 47 | 48 | const txSig = await sendAndConfirmTransaction(connection, siTx, [mainKp], { skipPreflight: true }) 49 | if (txSig) { 50 | const sig = txSig ? `https://solscan.io/tx/${txSig}` : '' 51 | // console.log("SOL distributed ", sig) 52 | } 53 | } catch (error) { 54 | console.log("Distribution error") 55 | if (DEBUG_MODE) 56 | console.log(error) 57 | return null 58 | } 59 | 60 | // console.log("Success in distribution") 61 | return keypairs 62 | } catch (error) { 63 | console.log(`Failed to transfer SOL`) 64 | if (DEBUG_MODE) 65 | console.log(error) 66 | return null 67 | } 68 | } 69 | 70 | export const distributeToken = async (connection: Connection, keysData: Keys) => { 71 | try { 72 | const { tokenWallets, bundlers, mintPk } = keysData 73 | const packs: Pack[] = [] 74 | const walleNumInPack = Math.ceil(tokenWallets.length / bundlers.length) 75 | 76 | for (let i = 0; i < bundlers.length; i++) { 77 | packs.push({ bundledWallet: bundlers[i], distributionWallet: [] }) 78 | for (let j = 0; j < walleNumInPack; j++) { 79 | const index = i * walleNumInPack + j 80 | if (tokenWallets[index]) 81 | packs[i].distributionWallet.push(tokenWallets[index]) 82 | } 83 | } 84 | 85 | const distTokenProcess = async (pack: Pack, index: number) => { 86 | try { 87 | await sleep(index * 1000) 88 | const { bundledWallet, distributionWallet } = pack 89 | const transaction = new Transaction().add( 90 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 150_000 }), 91 | ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }) 92 | ) 93 | const srcAta = getAssociatedTokenAddressSync(mintPk, bundledWallet.publicKey) 94 | 95 | if (!(await connection.getAccountInfo(srcAta))) 96 | return 97 | const tokenBalInfo = await connection.getTokenAccountBalance(srcAta) 98 | 99 | const transferAmount = BigInt(tokenBalInfo.value.amount) / BigInt(distributionWallet.length) 100 | for (let i = 0; i < distributionWallet.length; i++) { 101 | const destWalletKp = distributionWallet[i] 102 | const destAta = getAssociatedTokenAddressSync(mintPk, destWalletKp.publicKey) 103 | 104 | transaction.add( 105 | createAssociatedTokenAccountInstruction(bundledWallet.publicKey, destAta, destWalletKp.publicKey, mintPk), 106 | createTransferCheckedInstruction(srcAta, mintPk, destAta, bundledWallet.publicKey, transferAmount, tokenBalInfo.value.decimals) 107 | ) 108 | } 109 | 110 | transaction.feePayer = bundledWallet.publicKey 111 | transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash 112 | if (DEBUG_MODE) 113 | console.log(await connection.simulateTransaction(transaction)) 114 | const sig = await sendAndConfirmTransaction(connection, transaction, [bundledWallet], { skipPreflight: true }) 115 | 116 | console.log(`One of the sub bundle wallet token distributed : https://solscan.io/tx/${sig}`) 117 | } catch (error) { 118 | console.log("Error in on of the sub bundle wallet token distribution") 119 | if (DEBUG_MODE) 120 | console.log(error) 121 | } 122 | } 123 | const tokenDistributionProcesses = packs.map(async (pack, i) => await distTokenProcess(pack, i)) 124 | 125 | await Promise.all(tokenDistributionProcesses) 126 | return true 127 | } catch (error) { 128 | console.log("Error in bundle wallet token distribution") 129 | if (DEBUG_MODE) 130 | console.log(error) 131 | return false 132 | } 133 | } 134 | 135 | 136 | export const gatherToken = async (connection: Connection, keysData: Keys) => { 137 | try { 138 | const { tokenWallets, bundlers, mintPk } = keysData 139 | const packs: Pack[] = [] 140 | const walleNumInPack = Math.ceil(tokenWallets.length / bundlers.length) 141 | for (let i = 0; i < bundlers.length; i++) { 142 | packs.push({ bundledWallet: bundlers[i], distributionWallet: [] }) 143 | for (let j = 0; j < walleNumInPack; j++) { 144 | const index = i * walleNumInPack + j 145 | if (tokenWallets[index]) 146 | packs[i].distributionWallet.push(tokenWallets[index]) 147 | } 148 | } 149 | 150 | const gatherTokenProcess = async (pack: Pack, index: number) => { 151 | try { 152 | await sleep(index * 1000) 153 | const { bundledWallet, distributionWallet: gatheringWallet } = pack 154 | const transaction = new Transaction().add( 155 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 150_000 }), 156 | ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }) 157 | ) 158 | const srcAta = getAssociatedTokenAddressSync(mintPk, bundledWallet.publicKey) 159 | 160 | const signers: Keypair[] = [] 161 | for (let i = 0; i < gatheringWallet.length; i++) { 162 | const destWalletKp = gatheringWallet[i] 163 | const destAta = getAssociatedTokenAddressSync(mintPk, destWalletKp.publicKey) 164 | const tokenAtaInfo = await connection.getAccountInfo(destAta) 165 | if (!tokenAtaInfo) 166 | continue 167 | const tokenBalInfo = await connection.getTokenAccountBalance(destAta) 168 | transaction.add( 169 | createTransferCheckedInstruction(destAta, mintPk, srcAta, destWalletKp.publicKey, BigInt(tokenBalInfo.value.amount), tokenBalInfo.value.decimals), 170 | createCloseAccountInstruction(destAta, bundledWallet.publicKey, destWalletKp.publicKey) 171 | ) 172 | signers.push(destWalletKp) 173 | } 174 | 175 | if (signers.length == 0) 176 | return true 177 | 178 | transaction.feePayer = bundledWallet.publicKey 179 | transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash 180 | if (DEBUG_MODE) 181 | console.log(await connection.simulateTransaction(transaction)) 182 | const sig = await sendAndConfirmTransaction(connection, transaction, [bundledWallet, ...signers], { skipPreflight: true }) 183 | 184 | console.log(`One of the sub bundle wallet token gathered : https://solscan.io/tx/${sig}`) 185 | } catch (error) { 186 | console.log("Error in on of the sub bundle wallet token gathering process") 187 | if (DEBUG_MODE) 188 | console.log(error) 189 | return false 190 | } 191 | } 192 | const tokenGatheringProcesses = packs.map(async (pack, i) => await gatherTokenProcess(pack, i)) 193 | 194 | await Promise.all(tokenGatheringProcesses) 195 | return true 196 | } catch (error) { 197 | console.log("Error in bundle wallet token gathering") 198 | if (DEBUG_MODE) 199 | console.log(error) 200 | return false 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /module/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey } from "@solana/web3.js"; 2 | import axios from "axios"; 3 | import base58 from "bs58"; 4 | import fs from "fs"; 5 | import dotenv from "dotenv"; 6 | import path from "path"; 7 | import { DEBUG_MODE } from "../configs"; 8 | import { Keys, KeysFileData, LutData } from "./interface"; 9 | 10 | dotenv.config(); 11 | 12 | export const retrieveEnvVariable = (variableName: string) => { 13 | const variable = process.env[variableName] || ""; 14 | if (!variable) { 15 | console.log(`${variableName} is not set`); 16 | process.exit(1); 17 | } 18 | return variable; 19 | }; 20 | 21 | export const randVal = ( 22 | min: number, 23 | max: number, 24 | count: number, 25 | total: number, 26 | isEven: boolean 27 | ): number[] => { 28 | const arr: number[] = Array(count).fill(total / count); 29 | if (isEven) return arr; 30 | 31 | if (max * count < total) 32 | throw new Error( 33 | "Invalid input: max * count must be greater than or equal to total." 34 | ); 35 | if (min * count > total) 36 | throw new Error( 37 | "Invalid input: min * count must be less than or equal to total." 38 | ); 39 | const average = total / count; 40 | // Randomize pairs of elements 41 | for (let i = 0; i < count; i += 2) { 42 | // Generate a random adjustment within the range 43 | const adjustment = parseFloat( 44 | (Math.random() * Math.min(max - average, average - min)).toFixed(4) 45 | ); 46 | // Add adjustment to one element and subtract from the other 47 | arr[i] += adjustment; 48 | arr[i + 1] -= adjustment; 49 | } 50 | // if (count % 2) arr.pop() 51 | return arr; 52 | }; 53 | 54 | export const saveDataToFile = ( 55 | newData: LutData[], 56 | fileName: string = "data.json", 57 | newFile: boolean = false 58 | ) => { 59 | const folderPath = "keys"; 60 | const filePath = path.join(folderPath, fileName); 61 | 62 | try { 63 | // Create the folder if it doesn't exist 64 | if (!fs.existsSync(folderPath)) { 65 | fs.mkdirSync(folderPath, { recursive: true }); 66 | } 67 | 68 | let existingData: LutData[] = []; 69 | 70 | // Check if the file exists 71 | if (fs.existsSync(filePath)) { 72 | // If the file exists, read its content 73 | const fileContent = fs.readFileSync(filePath, "utf-8"); 74 | existingData = JSON.parse(fileContent); 75 | } 76 | 77 | if (newFile) 78 | // replace the existing data with incoming new data 79 | existingData = newData 80 | else 81 | // Add the new data to the existing array 82 | existingData.push(...newData); 83 | 84 | // Write the updated data back to the file 85 | fs.writeFileSync(filePath, JSON.stringify(existingData, null, 2)); 86 | if (DEBUG_MODE) 87 | console.log("File is saved successfully."); 88 | } catch (error) { 89 | 90 | try { 91 | if (fs.existsSync(filePath)) { 92 | fs.unlinkSync(filePath); 93 | console.log(`File ${filePath} deleted and will be recreated.`); 94 | } 95 | fs.writeFileSync(filePath, JSON.stringify(newData, null, 2)); 96 | if (DEBUG_MODE) 97 | console.log("File is saved successfully."); 98 | } catch (error) { 99 | if (DEBUG_MODE) 100 | console.log("Error saving data to JSON file:", error); 101 | } 102 | 103 | } 104 | }; 105 | 106 | // Function to read JSON file from the "keys" folder 107 | export function readJson(fileName: string = "data.json"): LutData[] { 108 | const folderPath = "keys"; 109 | const filePath = path.join(folderPath, fileName); 110 | 111 | if (!fs.existsSync(filePath)) { 112 | // If the file does not exist, create an empty array file in the "keys" folder 113 | fs.writeFileSync(filePath, "[]", "utf-8"); 114 | } 115 | 116 | const data = fs.readFileSync(filePath, "utf-8"); 117 | return JSON.parse(data) as LutData[]; 118 | } 119 | 120 | export function deleteConsoleLines(numLines: number) { 121 | for (let i = 0; i < numLines; i++) { 122 | process.stdout.moveCursor(0, -1); // Move cursor up one line 123 | process.stdout.clearLine(-1); // Clear the line 124 | } 125 | } 126 | 127 | export const sleep = async (ms: number) => { 128 | await new Promise((resolve) => setTimeout(resolve, ms)); 129 | }; 130 | 131 | export const makeAndSaveKeys = ( 132 | makerNum: number, 133 | volumeNum: number, 134 | tokenWalletsNum: number 135 | ) => { 136 | const mintKp = Keypair.generate(); 137 | const keys: Keys = { 138 | mintPk: mintKp.publicKey, 139 | mainKp: Keypair.generate(), 140 | mint: mintKp, 141 | lut: null, 142 | bundlers: [], 143 | tokenWallets: [], 144 | makers: [], 145 | volumes: [], 146 | }; 147 | keys.bundlers = new Array(20).fill(true).map(() => Keypair.generate()); 148 | keys.makers = new Array(makerNum).fill(true).map(() => Keypair.generate()); 149 | keys.volumes = new Array(volumeNum).fill(true).map(() => Keypair.generate()); 150 | keys.tokenWallets = new Array(tokenWalletsNum) 151 | .fill(true) 152 | .map(() => Keypair.generate()); 153 | 154 | const keyFile: KeysFileData = { 155 | mainKp: base58.encode(keys.mainKp.secretKey), 156 | mint: base58.encode(keys.mint.secretKey), 157 | mintStr: keys.mintPk.toBase58(), 158 | lut: "", 159 | bundlers: keys.bundlers.map((kp) => base58.encode(kp.secretKey)), 160 | tokenWallets: keys.tokenWallets.map((kp) => base58.encode(kp.secretKey)), 161 | makers: keys.makers.map((kp) => base58.encode(kp.secretKey)), 162 | volumes: keys.volumes.map((kp) => base58.encode(kp.secretKey)), 163 | }; 164 | saveKeysToFile(keyFile, keys.mint.publicKey.toBase58()); 165 | return keys; 166 | }; 167 | 168 | export const saveLut = (mint: PublicKey, lut: PublicKey, mainKp: Keypair) => { 169 | const keyData = readKeyFile(`${mint.toBase58()}.json`); 170 | if (!keyData) { 171 | console.log("Key data doesn't exist"); 172 | return; 173 | } 174 | 175 | keyData.lut = lut.toBase58(); 176 | saveKeysToFile(keyData, mint.toBase58()); 177 | const lutData: LutData = { 178 | mainKp: keyData.mainKp, 179 | lut: lut.toBase58(), 180 | needToClose: false 181 | }; 182 | saveDataToFile([lutData], "lut.json"); 183 | }; 184 | 185 | export const removeLutFromJson = (lutStr: string) => { 186 | const folderPath = "keys"; 187 | const filePath = path.join(folderPath, 'lut.json'); 188 | 189 | if (!fs.existsSync(filePath)) return 190 | 191 | const data = fs.readFileSync(filePath, "utf-8"); 192 | const luts = JSON.parse(data) as LutData[]; 193 | const remained = luts.filter(lut => lut.lut !== lutStr) 194 | saveDataToFile(remained, 'lut.json', true) 195 | } 196 | 197 | export const saveKeysToFile = (newData: KeysFileData, name: string) => { 198 | const folderPath = "keys"; 199 | const fileName = `${name}.json`; 200 | const filePath = path.join(folderPath, fileName); 201 | 202 | try { 203 | // Create the folder if it doesn't exist 204 | if (!fs.existsSync(folderPath)) { 205 | fs.mkdirSync(folderPath, { recursive: true }); 206 | } 207 | 208 | // Write the updated data back to the file 209 | fs.writeFileSync(filePath, JSON.stringify(newData, null, 2)); 210 | 211 | if (DEBUG_MODE) console.log("File is saved successfully."); 212 | } catch (error) { 213 | try { 214 | if (fs.existsSync(filePath)) { 215 | fs.unlinkSync(filePath); 216 | console.log(`File ${filePath} deleted and will be recreated.`); 217 | } 218 | fs.writeFileSync(filePath, JSON.stringify(newData, null, 2)); 219 | 220 | if (DEBUG_MODE) console.log("File is saved successfully."); 221 | } catch (error) { 222 | console.log("Error saving data to JSON file:", error); 223 | } 224 | } 225 | }; 226 | 227 | export function readKeyFile(fileName: string): KeysFileData | null { 228 | const folderPath = "keys"; 229 | const filePath = path.join(folderPath, fileName); 230 | 231 | if (!fs.existsSync(filePath)) { 232 | return null; 233 | } 234 | 235 | const data = fs.readFileSync(filePath, "utf-8"); 236 | return JSON.parse(data) as KeysFileData; 237 | } 238 | 239 | export const importKeysFromFile = (mint: string) => { 240 | try { 241 | const data = readKeyFile(`${mint}.json`); 242 | if (!data) { 243 | console.log("Key file doesn't exist with ", mint); 244 | return; 245 | } 246 | const keys: Keys = { 247 | mainKp: Keypair.fromSecretKey(base58.decode(data.mainKp)), 248 | mint: Keypair.fromSecretKey(base58.decode(data.mint)), 249 | mintPk: new PublicKey(data.mintStr), 250 | lut: data.lut ? new PublicKey(data.lut) : null, 251 | bundlers: [], 252 | tokenWallets: [], 253 | makers: [], 254 | volumes: [], 255 | }; 256 | keys.bundlers = data.bundlers.map((str) => 257 | Keypair.fromSecretKey(base58.decode(str)) 258 | ); 259 | keys.makers = data.makers.map((str) => 260 | Keypair.fromSecretKey(base58.decode(str)) 261 | ); 262 | keys.volumes = data.volumes.map((str) => 263 | Keypair.fromSecretKey(base58.decode(str)) 264 | ); 265 | keys.tokenWallets = data.tokenWallets.map((str) => 266 | Keypair.fromSecretKey(base58.decode(str)) 267 | ); 268 | return keys; 269 | } catch (error) { 270 | console.log("Error happened while importing the key file"); 271 | if (DEBUG_MODE) console.log(error); 272 | return; 273 | } 274 | }; 275 | 276 | export async function downloadImage( 277 | url: string, 278 | directoryPath: string = "./downloads" 279 | ): Promise { 280 | try { 281 | // Fetch the image 282 | const response = await axios.get(url, { responseType: "stream" }); 283 | 284 | // Extract or determine the file name 285 | let fileName = path.basename(url); // Default to the name extracted from the URL 286 | const contentDisposition = response.headers["content-disposition"]; 287 | if (contentDisposition) { 288 | const match = contentDisposition.match(/filename="(.+)"/); 289 | if (match && match[1]) fileName = match[1]; 290 | } 291 | 292 | // Ensure the file has an extension, default to ".jpg" if none 293 | const extension = path.extname(fileName) || ".jpg"; 294 | fileName = path.basename(fileName, extension) + extension; 295 | 296 | // Ensure the download directory exists 297 | if (!fs.existsSync(directoryPath)) { 298 | fs.mkdirSync(directoryPath, { recursive: true }); 299 | } 300 | 301 | // Generate the full relative file path for the image 302 | const relativeFilePath = path.posix.join(directoryPath, fileName); 303 | 304 | try { 305 | // Write the image to the file system 306 | const writer = fs.createWriteStream(path.join(directoryPath, fileName)); 307 | response.data.pipe(writer); 308 | // Return a promise to ensure the file is written completely 309 | return new Promise((resolve, reject) => { 310 | writer.on("finish", () => resolve(relativeFilePath)); // Return relative path with UNIX-style separators 311 | writer.on("error", (error) => { 312 | console.error("File write error:", error); 313 | reject(false); // Return false on failure 314 | }); 315 | }); 316 | } catch (error) { 317 | console.error("Error writing the file:", error); 318 | return false; 319 | } 320 | } catch (error) { 321 | console.error("Error downloading the image:", error); 322 | return false; 323 | } 324 | } 325 | 326 | export const initLogsFile = (mint: string, data: string) => { 327 | // Use mint as the file name and construct the file path 328 | const dataFolderPath = path.join(process.cwd(), "logs"); 329 | if (!fs.existsSync(dataFolderPath)) { 330 | console.log(`Creating data folder at: ${dataFolderPath}`); 331 | fs.mkdirSync(dataFolderPath, { recursive: true }); 332 | } 333 | 334 | // Create a folder with today's date inside the logs folder 335 | const todayDate = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD 336 | const datedFolderPath = path.join(dataFolderPath, todayDate); 337 | 338 | if (!fs.existsSync(datedFolderPath)) { 339 | console.log(`Creating dated folder at: ${datedFolderPath}`); 340 | fs.mkdirSync(datedFolderPath, { recursive: true }); 341 | } 342 | 343 | const filePath = path.join(datedFolderPath, `${mint.toString()}.txt`); 344 | 345 | fs.writeFileSync(filePath, data + "\n", "utf8"); 346 | }; 347 | 348 | export const extendLogsFile = (mint: string, data: string) => { 349 | const dataFolderPath = path.join(process.cwd(), "logs"); 350 | if (!fs.existsSync(dataFolderPath)) { 351 | console.log(`Creating data folder at: ${dataFolderPath}`); 352 | fs.mkdirSync(dataFolderPath, { recursive: true }); 353 | } 354 | 355 | // Create a folder with today's date inside the logs folder 356 | const todayDate = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD 357 | const datedFolderPath = path.join(dataFolderPath, todayDate); 358 | 359 | if (!fs.existsSync(datedFolderPath)) { 360 | console.log(`Creating dated folder at: ${datedFolderPath}`); 361 | fs.mkdirSync(datedFolderPath, { recursive: true }); 362 | } 363 | 364 | const filePath = path.join(datedFolderPath, `${mint.toString()}.txt`); 365 | // Append the data to the CSV file 366 | fs.appendFileSync(filePath, data + "\n", "utf8"); 367 | }; 368 | -------------------------------------------------------------------------------- /module/pumpfun_sdk/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Commitment, 3 | ComputeBudgetProgram, 4 | Connection, 5 | Finality, 6 | Keypair, 7 | PublicKey, 8 | SendTransactionError, 9 | Transaction, 10 | TransactionMessage, 11 | VersionedTransaction, 12 | VersionedTransactionResponse, 13 | LAMPORTS_PER_SOL, 14 | TransactionInstruction, 15 | sendAndConfirmTransaction, 16 | } from "@solana/web3.js"; 17 | import { PriorityFee, TransactionResult } from "./types"; 18 | import fs from "fs" 19 | import bs58 from "bs58"; 20 | import { createAssociatedTokenAccountInstruction, createTransferCheckedInstruction, getAssociatedTokenAddress, getAssociatedTokenAddressSync } from "@solana/spl-token"; 21 | import { sha256 } from "js-sha256"; 22 | import { DEBUG_MODE, RPC_ENDPOINT, RPC_WEBSOCKET_ENDPOINT } from "../configs"; 23 | 24 | 25 | const commitment = "confirmed" 26 | 27 | const connection = new Connection(RPC_ENDPOINT, { 28 | wsEndpoint: RPC_WEBSOCKET_ENDPOINT, commitment 29 | }) 30 | 31 | export const DEFAULT_COMMITMENT: Commitment = "finalized"; 32 | export const DEFAULT_FINALITY: Finality = "finalized"; 33 | 34 | export const sleep = async (ms: number) => { 35 | await new Promise((resolve) => setTimeout(resolve, ms)) 36 | } 37 | 38 | export const calculateWithSlippageBuy = ( 39 | amount: bigint, 40 | basisPoints: bigint 41 | ) => { 42 | return amount + (amount * basisPoints) / BigInt(1000); 43 | }; 44 | 45 | export const calculateWithSlippageSell = ( 46 | amount: bigint, 47 | basisPoints: bigint 48 | ) => { 49 | return amount - (amount * basisPoints) / BigInt(1000); 50 | }; 51 | 52 | export async function sendTx( 53 | connection: Connection, 54 | tx: Transaction, 55 | payer: PublicKey, 56 | signers: Keypair[], 57 | priorityFees?: PriorityFee, 58 | commitment: Commitment = DEFAULT_COMMITMENT, 59 | finality: Finality = DEFAULT_FINALITY 60 | ): Promise { 61 | 62 | let newTx = new Transaction(); 63 | 64 | if (priorityFees) { 65 | const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ 66 | units: priorityFees.unitLimit, 67 | }); 68 | 69 | const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({ 70 | microLamports: priorityFees.unitPrice, 71 | }); 72 | newTx.add(modifyComputeUnits); 73 | newTx.add(addPriorityFee); 74 | } 75 | newTx.add(tx); 76 | let versionedTx = await buildVersionedTx(connection, payer, newTx, commitment); 77 | versionedTx.sign(signers); 78 | try { 79 | console.log((await connection.simulateTransaction(versionedTx, undefined))) 80 | 81 | const sig = await connection.sendTransaction(versionedTx, { 82 | skipPreflight: false, 83 | }); 84 | console.log("Transaction signature: ", `https://solscan.io/tx/${sig}`); 85 | 86 | let txResult = await getTxDetails(connection, sig, commitment, finality); 87 | if (!txResult) { 88 | return { 89 | success: false, 90 | error: "Transaction failed", 91 | }; 92 | } 93 | return { 94 | success: true, 95 | signature: sig, 96 | results: txResult, 97 | }; 98 | } catch (e) { 99 | if (e instanceof SendTransactionError) { 100 | let ste = e as SendTransactionError; 101 | } else { 102 | console.error(e); 103 | } 104 | return { 105 | error: e, 106 | success: false, 107 | }; 108 | } 109 | } 110 | 111 | export async function buildTx( 112 | connection: Connection, 113 | tx: Transaction, 114 | payer: PublicKey, 115 | signers: Keypair[], 116 | priorityFees?: PriorityFee, 117 | commitment: Commitment = DEFAULT_COMMITMENT, 118 | finality: Finality = DEFAULT_FINALITY 119 | ): Promise { 120 | let newTx = new Transaction(); 121 | 122 | if (priorityFees) { 123 | const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ 124 | units: priorityFees.unitLimit, 125 | }); 126 | 127 | const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({ 128 | microLamports: priorityFees.unitPrice, 129 | }); 130 | newTx.add(modifyComputeUnits); 131 | newTx.add(addPriorityFee); 132 | } 133 | newTx.add(tx); 134 | let versionedTx = await buildVersionedTx(connection, payer, newTx, commitment); 135 | versionedTx.sign(signers); 136 | return versionedTx; 137 | } 138 | 139 | export const buildVersionedTx = async ( 140 | connection: Connection, 141 | payer: PublicKey, 142 | tx: Transaction, 143 | commitment: Commitment = DEFAULT_COMMITMENT 144 | ): Promise => { 145 | const blockHash = (await connection.getLatestBlockhash(commitment)) 146 | .blockhash; 147 | 148 | let messageV0 = new TransactionMessage({ 149 | payerKey: payer, 150 | recentBlockhash: blockHash, 151 | instructions: tx.instructions, 152 | }).compileToV0Message(); 153 | 154 | return new VersionedTransaction(messageV0); 155 | }; 156 | 157 | export const getTxDetails = async ( 158 | connection: Connection, 159 | sig: string, 160 | commitment: Commitment = DEFAULT_COMMITMENT, 161 | finality: Finality = DEFAULT_FINALITY 162 | ): Promise => { 163 | const latestBlockHash = await connection.getLatestBlockhash(); 164 | await connection.confirmTransaction( 165 | { 166 | blockhash: latestBlockHash.blockhash, 167 | lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, 168 | signature: sig, 169 | }, 170 | commitment 171 | ); 172 | 173 | return connection.getTransaction(sig, { 174 | maxSupportedTransactionVersion: 0, 175 | commitment: finality, 176 | }); 177 | }; 178 | 179 | export const getRandomInt = (min: number, max: number): number => { 180 | min = Math.ceil(min); 181 | max = Math.floor(max); 182 | return Math.floor(Math.random() * (max - min + 1)) + min; // The maximum is inclusive, the minimum is inclusive 183 | } 184 | 185 | export const readBuyerWallet = (fileName: string) => { 186 | const filePath = `.keys/${fileName}.txt` 187 | try { 188 | // Check if the file exists 189 | if (fs.existsSync(filePath)) { 190 | // Read the file content 191 | const publicKey = fs.readFileSync(filePath, 'utf-8'); 192 | return publicKey.trim(); // Remove any surrounding whitespace or newlines 193 | } else { 194 | console.log(`File ${filePath} does not exist.`); 195 | return null; // Return null if the file does not exist 196 | } 197 | } catch (error) { 198 | console.log('Error reading public key from file:', error); 199 | return null; // Return null in case of error 200 | } 201 | }; 202 | 203 | export const retrieveEnvVariable = (variableName: string) => { 204 | const variable = process.env[variableName] || '' 205 | if (!variable) { 206 | console.log(`${variableName} is not set`) 207 | process.exit(1) 208 | } 209 | return variable 210 | } 211 | 212 | export function getOrCreateKeypair(dir: string, keyName: string): Keypair { 213 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); 214 | const authorityKey = dir + "/" + keyName + ".json"; 215 | if (fs.existsSync(authorityKey)) { 216 | const data: { 217 | secretKey: string; 218 | publicKey: string; 219 | } = JSON.parse(fs.readFileSync(authorityKey, "utf-8")); 220 | return Keypair.fromSecretKey(bs58.decode(data.secretKey)); 221 | } else { 222 | const keypair = Keypair.generate(); 223 | keypair.secretKey; 224 | fs.writeFileSync( 225 | authorityKey, 226 | JSON.stringify({ 227 | secretKey: bs58.encode(keypair.secretKey), 228 | publicKey: keypair.publicKey.toBase58(), 229 | }) 230 | ); 231 | return keypair; 232 | } 233 | } 234 | 235 | export const printSOLBalance = async ( 236 | connection: Connection, 237 | pubKey: PublicKey, 238 | info: string = "" 239 | ) => { 240 | const balance = await connection.getBalance(pubKey); 241 | console.log( 242 | `${info ? info + " " : ""}${pubKey.toBase58()}:`, 243 | balance / LAMPORTS_PER_SOL, 244 | `SOL` 245 | ); 246 | }; 247 | 248 | export const getSPLBalance = async ( 249 | connection: Connection, 250 | mintAddress: PublicKey, 251 | pubKey: PublicKey, 252 | allowOffCurve: boolean = false 253 | ) => { 254 | try { 255 | let ata = getAssociatedTokenAddressSync(mintAddress, pubKey, allowOffCurve); 256 | const balance = await connection.getTokenAccountBalance(ata, "processed"); 257 | return balance.value.uiAmount; 258 | } catch (e) { } 259 | return null; 260 | }; 261 | 262 | export const printSPLBalance = async ( 263 | connection: Connection, 264 | mintAddress: PublicKey, 265 | user: PublicKey, 266 | info: string = "" 267 | ) => { 268 | const balance = await getSPLBalance(connection, mintAddress, user); 269 | if (balance === null) { 270 | console.log( 271 | `${info ? info + " " : ""}${user.toBase58()}:`, 272 | "No Account Found" 273 | ); 274 | } else { 275 | console.log(`${info ? info + " " : ""}${user.toBase58()}:`, balance); 276 | } 277 | }; 278 | 279 | export const baseToValue = (base: number, decimals: number): number => { 280 | return base * Math.pow(10, decimals); 281 | }; 282 | 283 | export const valueToBase = (value: number, decimals: number): number => { 284 | return value / Math.pow(10, decimals); 285 | }; 286 | 287 | //i.e. account:BondingCurve 288 | export function getDiscriminator(name: string) { 289 | return sha256.digest(name).slice(0, 8); 290 | } 291 | 292 | // Define the type for the JSON file content 293 | export interface Data { 294 | privateKey: string; 295 | pubkey: string; 296 | } 297 | 298 | interface Blockhash { 299 | blockhash: string; 300 | lastValidBlockHeight: number; 301 | } 302 | 303 | export const execute = async (transaction: VersionedTransaction, latestBlockhash: Blockhash, isBuy: boolean | 1 = true) => { 304 | 305 | const signature = await connection.sendRawTransaction(transaction.serialize(), { skipPreflight: true }) 306 | const confirmation = await connection.confirmTransaction( 307 | { 308 | signature, 309 | lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, 310 | blockhash: latestBlockhash.blockhash, 311 | } 312 | ); 313 | 314 | if (confirmation.value.err) { 315 | console.log("Confirmation error") 316 | return "" 317 | } else { 318 | if (isBuy === 1) { 319 | return signature 320 | } else if (isBuy) 321 | console.log(`Success in buy transaction: https://solscan.io/tx/${signature}`) 322 | else 323 | console.log(`Success in Sell transaction: https://solscan.io/tx/${signature}`) 324 | } 325 | return signature 326 | } 327 | 328 | export const saveHolderWalletsToFile = (newData: Data[], filePath: string = ".keys/holderWallets.json") => { 329 | try { 330 | let existingData: Data[] = []; 331 | 332 | // Check if the file exists 333 | if (fs.existsSync(filePath)) { 334 | // If the file exists, read its content 335 | const fileContent = fs.readFileSync(filePath, 'utf-8'); 336 | existingData = JSON.parse(fileContent); 337 | } 338 | 339 | // Add the new data to the existing array 340 | existingData.push(...newData); 341 | 342 | // Write the updated data back to the file 343 | fs.writeFileSync(filePath, JSON.stringify(existingData, null, 2)); 344 | 345 | } catch (error) { 346 | try { 347 | if (fs.existsSync(filePath)) { 348 | fs.unlinkSync(filePath); 349 | console.log(`File ${filePath} deleted and create new file.`); 350 | } 351 | fs.writeFileSync(filePath, JSON.stringify(newData, null, 2)); 352 | if (DEBUG_MODE) 353 | console.log("File is saved successfully.") 354 | } catch (error) { 355 | console.log('Error saving data to JSON file:', error); 356 | } 357 | } 358 | }; 359 | 360 | export async function newSendToken( 361 | walletKeypairs: Keypair[], tokensToSendArr: number[], walletKeypair: Keypair, mintAddress: PublicKey, tokenDecimal: number 362 | ) { 363 | try { 364 | const srcAta = await getAssociatedTokenAddress(mintAddress, walletKeypair.publicKey) 365 | if (tokensToSendArr.length !== walletKeypairs.length) { 366 | console.log("Number of wallets and token amounts array is not matching") 367 | throw new Error("Number of wallets and token amounts array is not matching") 368 | } 369 | 370 | console.log("Token amount of the srcAta: ", (await connection.getTokenAccountBalance(srcAta)).value.amount) 371 | 372 | const insts: TransactionInstruction[] = [] 373 | console.log("Wallet length to distribute: ", walletKeypairs.length) 374 | for (let i = 0; i < walletKeypairs.length; i++) { 375 | const destKp = walletKeypairs[i] 376 | const amount = tokensToSendArr[i] 377 | console.log("token amount ", amount) 378 | 379 | const baseAta = await getAssociatedTokenAddress(mintAddress, destKp.publicKey) 380 | if (!await connection.getAccountInfo(baseAta)) { 381 | insts.push( 382 | createAssociatedTokenAccountInstruction( 383 | walletKeypair.publicKey, 384 | baseAta, 385 | destKp.publicKey, 386 | mintAddress 387 | ) 388 | ) 389 | } 390 | 391 | insts.push( 392 | createTransferCheckedInstruction( 393 | srcAta, 394 | mintAddress, 395 | baseAta, 396 | walletKeypair.publicKey, 397 | Math.floor(amount * 10 ** tokenDecimal), 398 | tokenDecimal 399 | ) 400 | ) 401 | } 402 | 403 | console.log("total number of instructions : ", insts.length) 404 | const txs = await makeTxs(insts, walletKeypair) 405 | if (!txs) { 406 | console.log("Transaction not retrieved from makeTxs function") 407 | throw new Error("Transaction not retrieved from makeTxs function") 408 | } 409 | try { 410 | await Promise.all(txs.map(async (transaction, i) => { 411 | await sleep(i * 200) 412 | // Assuming you have a function to send a transaction 413 | return handleTxs(transaction, walletKeypair) 414 | })); 415 | 416 | } catch (error) { 417 | console.log("Error in transaction confirmation part : ", error) 418 | } 419 | } catch (error) { 420 | console.log("New Send Token function error : ", error) 421 | } 422 | } 423 | 424 | const makeTxs = async (insts: TransactionInstruction[], mainKp: Keypair) => { 425 | try { 426 | 427 | const batchNum = 12 428 | const txNum = Math.ceil(insts.length / batchNum) 429 | const txs: Transaction[] = [] 430 | for (let i = 0; i < txNum; i++) { 431 | const upperIndex = batchNum * (i + 1) 432 | const downIndex = batchNum * i 433 | const tx = new Transaction().add( 434 | ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), 435 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100_000 }) 436 | ) 437 | 438 | for (let j = downIndex; j < upperIndex; j++) 439 | if (insts[j]) 440 | tx.add(insts[j]) 441 | 442 | tx.recentBlockhash = (await connection.getLatestBlockhash("confirmed")).blockhash 443 | tx.feePayer = mainKp.publicKey 444 | console.log(await connection.simulateTransaction(tx)) 445 | 446 | txs.push(tx) 447 | } 448 | if (txs.length == 0) { 449 | console.log("Empty instructions as input") 450 | throw new Error("Empty instructions as input") 451 | } 452 | return txs 453 | } catch (error) { 454 | console.log("MakeTxs ~ error") 455 | } 456 | 457 | } 458 | 459 | const handleTxs = async (transaction: Transaction, mainKp: Keypair) => { 460 | const sig = await sendAndConfirmTransaction(connection, transaction, [mainKp], { skipPreflight: true }) 461 | console.log(`https://solscan.io/tx/${sig}`); 462 | } 463 | --------------------------------------------------------------------------------