├── src ├── keypairs │ └── keypair1.json ├── clients │ ├── structs.ts │ ├── interfaces.ts │ ├── jito.ts │ ├── formatAmmKeysById.ts │ ├── config.ts │ ├── constants.ts │ ├── raydiumUtil.ts │ ├── LookupTableProvider.ts │ └── poolKeysReassigned.ts ├── computeLPO.ts ├── keyInfo.json ├── createKeys.ts ├── removeLiq.ts ├── buyToken.ts ├── createLUT.ts ├── sellFunc.ts ├── senderUI.ts ├── jitoPool.ts └── createMarket.ts ├── blockengine.json ├── config.ts ├── package.json ├── main.ts ├── README.md └── tsconfig.json /src/keypairs/keypair1.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /blockengine.json: -------------------------------------------------------------------------------- 1 | [118,97,15,255,192,238,243,73,54,4,40,219,41,87,8,90,230,180,62,103,159,252,140,157,239,211,166,139,88,37,128,221,115,190,173,199,215,39,232,159,49,107,247,45,203,14,181,233,189,44,164,14,139,117,163,100,64,190,7,196,78,252,154,25] -------------------------------------------------------------------------------- /src/clients/structs.ts: -------------------------------------------------------------------------------- 1 | import { u8, u32, struct } from '@solana/buffer-layout'; 2 | import { u64, publicKey } from '@solana/buffer-layout-utils'; 3 | 4 | export const SPL_MINT_LAYOUT = struct([ 5 | u32('mintAuthorityOption'), 6 | publicKey('mintAuthority'), 7 | u64('supply'), 8 | u8('decimals'), 9 | u8('isInitialized'), 10 | u32('freezeAuthorityOption'), 11 | publicKey('freezeAuthority') 12 | ]); 13 | 14 | export const SPL_ACCOUNT_LAYOUT = struct([ 15 | publicKey('mint'), 16 | publicKey('owner'), 17 | u64('amount'), 18 | u32('delegateOption'), 19 | publicKey('delegate'), 20 | u8('state'), 21 | u32('isNativeOption'), 22 | u64('isNative'), 23 | u64('delegatedAmount'), 24 | u32('closeAuthorityOption'), 25 | publicKey('closeAuthority') 26 | ]); -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey } from "@solana/web3.js" 2 | import bs58 from 'bs58'; 3 | import { Wallet } from "@project-serum/anchor"; 4 | 5 | export const rpc = ''; // ENTER YOUR RPC 6 | 7 | export const connection = new Connection(rpc, { 8 | commitment: 'confirmed', 9 | }); 10 | 11 | export const tipAcct = new PublicKey('Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY'); 12 | 13 | export const wallet = Keypair.fromSecretKey( 14 | bs58.decode( 15 | '' // PRIV KEY OF POOL CREATOR 16 | ) 17 | ); 18 | 19 | export const payer = Keypair.fromSecretKey( 20 | bs58.decode( 21 | '' // PRIV KEY OF FEE PAYER!!!!!! 22 | ) 23 | ); 24 | 25 | export const walletconn = new Wallet(wallet); 26 | 27 | export const RayLiqPoolv4 = new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8') -------------------------------------------------------------------------------- /src/clients/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import BN from 'bn.js'; 3 | 4 | export interface IPoolKeys { 5 | keg?: PublicKey; 6 | version?: number; 7 | marketVersion?: number; 8 | programId?: PublicKey; 9 | baseMint: any; 10 | quoteMint?: any; 11 | ownerBaseAta: PublicKey; 12 | ownerQuoteAta: PublicKey; 13 | baseDecimals: any; 14 | quoteDecimals?: any; 15 | lpDecimals?: any; 16 | authority?: any; 17 | marketAuthority?: any; 18 | marketProgramId?: any; 19 | marketId?: any; 20 | marketBids?: any; 21 | marketAsks?: any; 22 | marketQuoteVault?: any; 23 | marketBaseVault?: any; 24 | marketEventQueue?: any; 25 | id?: any; 26 | baseVault?: any; 27 | coinVault?: PublicKey; 28 | lpMint: PublicKey; 29 | lpVault?: PublicKey; 30 | targetOrders?: any; 31 | withdrawQueue?: PublicKey; 32 | openOrders?: any; 33 | quoteVault?: any; 34 | lookupTableAccount?: PublicKey; 35 | } 36 | 37 | export interface ISwpBaseIn { 38 | swapBaseIn?: { 39 | amountIn?: BN; 40 | minimumAmountOut?: BN; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Solana-Bundler", 3 | "version": "1.0.0", 4 | "description": "https://t.me/benorizz0", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ts-node src/jitoPool.ts" 8 | }, 9 | "author": "https://t.me/benorizz0/", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@coral-xyz/anchor": "^0.29.0", 13 | "@octokit/rest": "^20.1.0", 14 | "@openbook-dex/openbook": "^0.0.9", 15 | "@project-serum/anchor": "^0.26.0", 16 | "@project-serum/serum": "^0.13.65", 17 | "@raydium-io/raydium-sdk": "^1.3.1-beta.47", 18 | "@solana/spl-token": "^0.3.9", 19 | "@solana/web3.js": "^1.87.6", 20 | "@types/node": "^20.10.5", 21 | "axios": "^1.6.8", 22 | "bn.js": "^5.2.1", 23 | "bs58": "^5.0.0", 24 | "convict": "^6.2.4", 25 | "dotenv": "^16.4.4", 26 | "fs": "^0.0.1-security", 27 | "http": "^0.0.1-security", 28 | "https": "^1.0.0", 29 | "jito-ts": "^3.0.1", 30 | "mime": "^4.0.1", 31 | "node-fetch": "^3.3.2", 32 | "path": "^0.12.7", 33 | "prompt-sync": "^4.2.0", 34 | "protobufjs": "^7.2.6", 35 | "typescript": "^5.3.3" 36 | }, 37 | "devDependencies": { 38 | "@types/bn.js": "^5.1.5", 39 | "@types/convict": "^6.1.6", 40 | "@types/node-fetch": "^2.6.9", 41 | "@types/prompt-sync": "^4.2.3", 42 | "javascript-obfuscator": "^4.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/clients/jito.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import { config } from './config'; 3 | import { geyserClient as jitoGeyserClient } from 'jito-ts'; 4 | 5 | import { 6 | SearcherClient, 7 | searcherClient as jitoSearcherClient, 8 | } from 'jito-ts/dist/sdk/block-engine/searcher.js'; 9 | import * as fs from 'fs'; 10 | 11 | const BLOCK_ENGINE_URLS = config.get('block_engine_urls'); 12 | const AUTH_KEYPAIR_PATH = config.get('auth_keypair_path'); 13 | 14 | const GEYSER_URL = config.get('geyser_url'); 15 | const GEYSER_ACCESS_TOKEN = config.get('geyser_access_token'); 16 | 17 | const decodedKey = new Uint8Array( 18 | JSON.parse(fs.readFileSync(AUTH_KEYPAIR_PATH).toString()) as number[], 19 | ); 20 | const keypair = Keypair.fromSecretKey(decodedKey); 21 | 22 | export const privateKey = keypair 23 | 24 | const searcherClients: SearcherClient[] = []; 25 | 26 | for (const url of BLOCK_ENGINE_URLS) { 27 | const client = jitoSearcherClient(url, keypair, { 28 | 'grpc.keepalive_timeout_ms': 4000, 29 | }); 30 | searcherClients.push(client); 31 | } 32 | 33 | const geyserClient = jitoGeyserClient(GEYSER_URL, GEYSER_ACCESS_TOKEN, { 34 | 'grpc.keepalive_timeout_ms': 4000, 35 | }); 36 | 37 | // all bundles sent get automatically forwarded to the other regions. 38 | // assuming the first block engine in the array is the closest one 39 | const searcherClient = searcherClients[0]; 40 | 41 | export { searcherClient, searcherClients, geyserClient }; -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { createKeypairs } from "./src/createKeys"; 2 | import { buyBundle } from "./src/jitoPool"; 3 | import { sender } from "./src/senderUI"; 4 | import { createWalletSells, sellXPercentage } from "./src/sellFunc"; 5 | import { remove } from "./src/removeLiq"; 6 | import promptSync from "prompt-sync"; 7 | 8 | const prompt = promptSync(); 9 | 10 | async function main() { 11 | let running = true; 12 | 13 | while (running) { 14 | console.log("DM me for support"); 15 | console.log("https://t.me/benorizz0"); 16 | console.log("\nMenu:"); 17 | console.log("1. Create Keypairs"); 18 | console.log("2. Pre Launch Checklist"); 19 | console.log("3. Create Pool Bundle"); 20 | console.log("4. Sell All Buyers"); 21 | console.log("5. Sell % of Supply"); 22 | console.log("6. Remove Liquidity"); 23 | console.log("Type 'exit' to quit."); 24 | 25 | const answer = prompt("Choose an option or 'exit': "); // Use prompt-sync for user input 26 | 27 | switch (answer) { 28 | case "1": 29 | await createKeypairs(); 30 | break; 31 | case "2": 32 | await sender(); 33 | break; 34 | case "3": 35 | await buyBundle(); 36 | break; 37 | case "4": 38 | await createWalletSells(); 39 | break; 40 | case "5": 41 | await sellXPercentage(); 42 | break; 43 | case "6": 44 | await remove(); 45 | break; 46 | case "exit": 47 | running = false; 48 | break; 49 | default: 50 | console.log("Invalid option, please choose again."); 51 | } 52 | } 53 | 54 | console.log("Exiting..."); 55 | process.exit(0); 56 | } 57 | 58 | main().catch((err) => { 59 | console.error("Error:", err); 60 | }); 61 | -------------------------------------------------------------------------------- /src/computeLPO.ts: -------------------------------------------------------------------------------- 1 | import * as readline from 'readline'; 2 | 3 | const rl = readline.createInterface({ 4 | input: process.stdin, 5 | output: process.stdout 6 | }); 7 | 8 | function question(query: string): Promise { 9 | return new Promise((resolve) => { 10 | rl.question(query, (answer: string) => { 11 | resolve(answer); 12 | }); 13 | }); 14 | } 15 | 16 | export async function calculateTokensBoughtPercentage(steps: number = 27) { 17 | let RSOL = +await question('Initial SOL in LP: '); 18 | let RToken = +await question('Initial TOKENS in LP: '); 19 | let initialBuyAmount = +await question('Buy amount increment: '); 20 | let totalTokensBought: number = 0; // Initialize total tokens bought 21 | let totalSolRequired: number = 0; // Initialize total SOL required 22 | const initialRToken = RToken; 23 | 24 | // Loop through each step, increasing the buy amount incrementally 25 | for (let step = 1; step <= steps; step++) { 26 | let buyAmount: number = initialBuyAmount * step; // Incremental buy amount 27 | let RTokenPrime: number = (RToken * RSOL) / (RSOL + buyAmount); // New token reserve after buy 28 | let tokensReceived: number = RToken - RTokenPrime; // Tokens received for this buy amount 29 | 30 | totalTokensBought += tokensReceived; // Update total tokens bought 31 | totalSolRequired += buyAmount; // Update total SOL required 32 | RToken = RTokenPrime; // Update the token reserve for the next calculation 33 | RSOL += buyAmount; // Update the SOL reserve for the next calculation 34 | } 35 | 36 | // Calculate the total tokens bought as a percentage of the initial token reserve 37 | let tokensBoughtPercentage: number = (totalTokensBought / initialRToken) * 100; 38 | 39 | console.log("With the buy sequence you will buy: ~" + tokensBoughtPercentage.toFixed(2) + "% of the tokens in the LP"); 40 | console.log(`Total SOL required for the sequence of buys: ${totalSolRequired.toFixed(2)} SOL`); 41 | } -------------------------------------------------------------------------------- /src/keyInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "addressLUT": "DpCfiTrz6jenh6krCx2CiG3XD7FvCr3LLeesykKFE39j", 3 | "numOfWallets": 27, 4 | "pubkey1": "Gfi71W23iYQJrfGWxEGiQiuBHJo1pxssPBodnFCBZZa2", 5 | "pubkey2": "23k7Emia6QphQK6pCkgBsaz6F8x7pJDbupJ6gK5KV21k", 6 | "pubkey3": "8fohbazEXNaxrzMAPinLVoC3qdRi9UZsopt9mcaF1EqT", 7 | "pubkey4": "7rME2Qw33bNLKF44N34nwW5HCXYsYQZXPCVWtB5GVjsr", 8 | "pubkey5": "FkyTXNiMjB5KyEHzZ3YFGWkwUbWBdb54yaJ9VKpCqq8h", 9 | "pubkey6": "74yrsrhJ1SHHFCy9cvTboxZqeaU3pDn7p95oTSni621z", 10 | "pubkey7": "8fmezQGbGmVyKyBctE9mKH2vasXRT3RMTFrhmRVayZtE", 11 | "pubkey8": "6uG2NUZVSRmoX7s84VgxwfNqadzrVxmwb5dQ1pYBPz21", 12 | "pubkey9": "6548Bjn2CKaFoszPXRnsexDqDHV2jBZ3Vvn12eH9KjNY", 13 | "pubkey10": "9NPX4LTSN7nKeAnPbVSMmLmHTKP42P6FqcmAj1vMLs4y", 14 | "pubkey11": "Jh96BX4Mg7Czd52Q4mP49Sa1wsKiCgMueJo2roqXRJF", 15 | "pubkey12": "HCwwg11ABbWhhXJA66uSTP6D3ez933ACVh4mkqZxLWCm", 16 | "pubkey13": "9pvPHKMJKZqoR7pGXe94peZZ99cbi9aBkK77T8q1qB1a", 17 | "pubkey14": "CdCiS3u7MPXZTB97JaPkH5Fv818Tx2XaGxeo4wyeHzdr", 18 | "pubkey15": "DdUwQjW9eMS83pLPXNn1bQzSnNuqNF2jke9xRpJQ5cXv", 19 | "pubkey16": "Eg9xisdLuHjzwacmoboUK1AspkYRJEZkEaEHpTthM3F7", 20 | "pubkey17": "8DTLZJBTQZU9ZqekSBJqdBxAv87gsN6yUz59SN9vLa44", 21 | "pubkey18": "GiDjYj8DSq9kXaE9RyiFe1DYSNa8fWdsA24CN9org4WH", 22 | "pubkey19": "DydqCGd6rCh9N4MCWwZDEJ9eLKAsovmRkJDK3yK6SAZv", 23 | "pubkey20": "9PhnEeGZsi4CNuxr8DkH5dWkQjW3U4FLcoDJp6ecqLx3", 24 | "pubkey21": "GZRkqwPigAENqsfcZSSjUbURdszVn1WWQuArKmyoGvsz", 25 | "pubkey22": "F6frvumK9yzzEaKvvRBzDHn9gV6bRv3kPH1JDJPXCjbr", 26 | "pubkey23": "49hRqsZKuY5L3SdPRobYpbr1363FpStWyBDfUa6oQiTY", 27 | "pubkey24": "9FDaz2ywMPQShS268oDcxoSTVEPqkMKmUYpkrFc9BNjv", 28 | "pubkey25": "CH2FhRtYTwvQzFUgXDNvP5bV1skmhP89oDW5BWf4mrtr", 29 | "pubkey26": "6nRdkAHdN7yMqCrpQUZxTJ3mbH6GLru7zwRnnx8isszS", 30 | "pubkey27": "LdEAWzFdhqNxARvQ5Fu5ZhjJ9vnV3XUCq2vgNXLXWqb", 31 | "lpTokenAddr": "GUhRG6UdxEz1787g7rtP7NuYdB5YUTQPtqWzPdZ5ttKK", 32 | "targetPool": "6t3Hn1twQcbcZ5eNmj2hqyRtSGXAFh8vPyw249cGKQPp", 33 | "baseMint": "9LNZBgnbvp8udKjtSUTE27LYnUMNT9sJsthet5KPDrH4", 34 | "quoteMint": "So11111111111111111111111111111111111111112", 35 | "openTime": "2024-04-28T17:05:54.000Z", 36 | "marketID": "C6BF1xekj25rTxNwZLW4d8C11Vr3YMTGXYw5Z4Jz1wh8" 37 | } -------------------------------------------------------------------------------- /src/clients/formatAmmKeysById.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApiPoolInfoV4, 3 | LIQUIDITY_STATE_LAYOUT_V4, 4 | Liquidity, 5 | MARKET_STATE_LAYOUT_V3, 6 | Market, 7 | SPL_MINT_LAYOUT 8 | } from '@raydium-io/raydium-sdk'; 9 | import { 10 | PublicKey 11 | } from '@solana/web3.js'; 12 | 13 | import { connection } from '../../config'; 14 | 15 | export async function formatAmmKeysById(id: string): Promise { 16 | const account = await connection.getAccountInfo(new PublicKey(id)) 17 | if (account === null) throw Error(' get id info error ') 18 | const info = LIQUIDITY_STATE_LAYOUT_V4.decode(account.data) 19 | 20 | const marketId = info.marketId 21 | const marketAccount = await connection.getAccountInfo(marketId) 22 | if (marketAccount === null) throw Error(' get market info error') 23 | const marketInfo = MARKET_STATE_LAYOUT_V3.decode(marketAccount.data) 24 | 25 | const lpMint = info.lpMint 26 | const lpMintAccount = await connection.getAccountInfo(lpMint) 27 | if (lpMintAccount === null) throw Error(' get lp mint info error') 28 | const lpMintInfo = SPL_MINT_LAYOUT.decode(lpMintAccount.data) 29 | 30 | return { 31 | id, 32 | baseMint: info.baseMint.toString(), 33 | quoteMint: info.quoteMint.toString(), 34 | lpMint: info.lpMint.toString(), 35 | baseDecimals: info.baseDecimal.toNumber(), 36 | quoteDecimals: info.quoteDecimal.toNumber(), 37 | lpDecimals: lpMintInfo.decimals, 38 | version: 4, 39 | programId: account.owner.toString(), 40 | authority: Liquidity.getAssociatedAuthority({ programId: account.owner }).publicKey.toString(), 41 | openOrders: info.openOrders.toString(), 42 | targetOrders: info.targetOrders.toString(), 43 | baseVault: info.baseVault.toString(), 44 | quoteVault: info.quoteVault.toString(), 45 | withdrawQueue: info.withdrawQueue.toString(), 46 | lpVault: info.lpVault.toString(), 47 | marketVersion: 3, 48 | marketProgramId: info.marketProgramId.toString(), 49 | marketId: info.marketId.toString(), 50 | marketAuthority: Market.getAssociatedAuthority({ programId: info.marketProgramId, marketId: info.marketId }).publicKey.toString(), 51 | marketBaseVault: marketInfo.baseVault.toString(), 52 | marketQuoteVault: marketInfo.quoteVault.toString(), 53 | marketBids: marketInfo.bids.toString(), 54 | marketAsks: marketInfo.asks.toString(), 55 | marketEventQueue: marketInfo.eventQueue.toString(), 56 | lookupTableAccount: PublicKey.default.toString() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/clients/config.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import convict from 'convict'; 3 | import * as dotenv from 'dotenv'; 4 | dotenv.config(); 5 | 6 | const config = convict({ 7 | bot_name: { 8 | format: String, 9 | default: 'local', 10 | env: 'BOT_NAME', 11 | }, 12 | num_worker_threads: { 13 | format: Number, 14 | default: 4, 15 | env: 'NUM_WORKER_THREADS', 16 | }, 17 | block_engine_urls: { 18 | format: Array, 19 | default: ['frankfurt.mainnet.block-engine.jito.wtf'], 20 | doc: 'block engine urls. bot will mempool subscribe to all and send bundles to first one', 21 | env: 'BLOCK_ENGINE_URLS', 22 | }, 23 | auth_keypair_path: { 24 | format: String, 25 | default: './blockengine.json', 26 | env: 'AUTH_KEYPAIR_PATH', 27 | }, 28 | rpc_url: { 29 | format: String, 30 | default: 'https://api.mainnet-beta.solana.com', 31 | env: 'RPC_URL', 32 | }, 33 | rpc_requests_per_second: { 34 | format: Number, 35 | default: 0, 36 | env: 'RPC_REQUESTS_PER_SECOND', 37 | }, 38 | rpc_max_batch_size: { 39 | format: Number, 40 | default: 20, 41 | env: 'RPC_MAX_BATCH_SIZE', 42 | }, 43 | geyser_url: { 44 | format: String, 45 | default: 'mainnet.rpc.jito.wtf', 46 | env: 'GEYSER_URL', 47 | }, 48 | geyser_access_token: { 49 | format: String, 50 | default: '00000000-0000-0000-0000-000000000000', 51 | env: 'GEYSER_ACCESS_TOKEN', 52 | }, 53 | arb_calculation_num_steps: { 54 | format: Number, 55 | default: 3, 56 | env: 'ARB_CALCULATION_NUM_STEPS', 57 | }, 58 | max_arb_calculation_time_ms: { 59 | format: Number, 60 | default: 15, 61 | env: 'MAX_ARB_CALCULATION_TIME_MS', 62 | }, 63 | payer_keypair_path: { 64 | format: String, 65 | default: './payer.json', 66 | env: 'PAYER_KEYPAIR_PATH', 67 | }, 68 | min_tip_lamports: { 69 | format: Number, 70 | default: 10000, 71 | env: 'MIN_TIP_LAMPORTS', 72 | }, 73 | tip_percent: { 74 | format: Number, 75 | default: 50, 76 | env: 'TIP_PERCENT', 77 | }, 78 | }); 79 | 80 | config.validate({ allowed: 'strict' }); 81 | 82 | 83 | const TIP_ACCOUNTS = [ 84 | '96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5', 85 | 'HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe', 86 | 'Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY', 87 | 'ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49', 88 | 'DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh', 89 | 'ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt', 90 | 'DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL', 91 | '3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT', 92 | ].map((pubkey) => new PublicKey(pubkey)); 93 | 94 | const getRandomTipAccount = () => 95 | TIP_ACCOUNTS[Math.floor(Math.random() * TIP_ACCOUNTS.length)]; 96 | 97 | 98 | 99 | export { config ,getRandomTipAccount}; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana-Bundler-tool 2 | The Solana Bundler is a powerful open-source script designed to simplify buying and selling operations across 27 wallets simultaneously on the Solana blockchain. This innovative tool streamlines multi-transaction management, delivering efficiency and reliability for users handling high-volume activities 3 | 4 | ## Initial Setup 5 | 6 | To utilize the Solana Raydium Bundler efficiently, please adhere to the following setup and execution guidelines. 7 | 8 | ### Step 1: Configuration 9 | 10 | - **Edit the `.env` File:** Before executing the script, configuring the `.env` file is essential. You will need two keypairs: 11 | 12 | - **SOL Distribution Fees Keypair:** This keypair handles the payment of all SOL distribution fees. 13 | - **Pool Creation Keypair:** This keypair facilitates pool creation. For security, ensure these keypairs are distinct. 14 | 15 | While testing the script, you can employ the same keypair for both purposes. Always store these keypairs securely. 16 | 17 | ### Step 2: Execution of Functions 18 | 19 | **Note:** To maintain error-free execution, it is vital to perform these steps sequentially. 20 | 21 | - **Create Keypairs (Step 1):** Even though not mandatory for every launch, it is advisable to create new keypairs initially or during resets to assure no SOL remains in the wallets. 22 | 23 | - **Premarket (Step 2):** This multi-step process must follow a specific sequence: 24 | 25 | 1. **Execution Sequence:** Complete steps 2 through 6 sequentially. 26 | 2. **Bundle ID Validation:** After each step, confirm the Bundle ID to ensure successful landing. 27 | 3. **Repeat if Necessary:** If landing fails, elevate the tip and attempt again. Exit if required. 28 | 4. **Cross-Verification:** Utilize the Jito Block Explorer to verify bundle landing. Disregard the "Landed - no" indication; ensure the first transaction is confirmed. 29 | 30 | - **Create Pool (Step 3):** Pool creation may warrant multiple attempts: 31 | 32 | - Use function spamming if initial attempts fail to land the pool creation. 33 | - Enhance the tip, with 0.1 SOL or higher recommended for improved landing chances. 34 | 35 | - **Selling Options (Steps 4 and 5):** 36 | 37 | 1. **Simultaneous Keypair Sale (Step 4):** Consolidate the sale of all keypairs and reclaim WSOL in Premarket's Step 7 post-rugging. 38 | 2. **Percentage-Based Selling (Step 5):** Execute sales of varying percentages upon request by transferring specific portions of each keypair's token balance to the fee payers before executing a singular bundle sale. 39 | 40 | - **Liquidity Pool Removal (Step 6):** The process for removing LP is direct: 41 | - **Non-Burn Removal:** Without LP burning, it will automatically be removed. 42 | 43 | ## Tips and Troubleshooting 44 | 45 | - **Bundle Success:** Adapt the tip or retry the operation if the bundle doesn't land. Jito Block Explorer serves as a verification tool. 46 | - **Keypair Security:** Ensure keypairs are secure and correctly entered in the `.env` file. 47 | - **Prudent Function Spamming:** Monitor transactions vigilantly to prevent unnecessary SOL expenditure during spam attempts. 48 | 49 | ### Final Thoughts 50 | 51 | The Solana Raydium Bundler offers a sophisticated solution for handling multiple transactions on the Solana blockchain. Adhering to the outlined setup and functions will enable smooth buying and selling processes. Engage with our Discord community to explore the strengths of this open-source tool further. 52 | 53 | Start optimizing your Solana transactions with the Solana Raydium Bundler today! 54 | 55 | For technical queries, feel free to reach out via tg @alexisssol. 56 | -------------------------------------------------------------------------------- /src/clients/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Signer, Transaction } from "@solana/web3.js" 2 | import { 3 | ENDPOINT as _ENDPOINT, 4 | Currency, 5 | DEVNET_PROGRAM_ID, 6 | LOOKUP_TABLE_CACHE, 7 | MAINNET_PROGRAM_ID, 8 | RAYDIUM_MAINNET, 9 | Token, 10 | TOKEN_PROGRAM_ID, 11 | TxVersion, 12 | } from '@raydium-io/raydium-sdk'; 13 | import { 14 | Connection, 15 | Keypair, 16 | } from '@solana/web3.js'; 17 | 18 | export const SUPPORTED_CHAINS = [ 19 | { 20 | id: 999999999, 21 | name: 'Solana Devnet', 22 | symbol: 'SOL', 23 | rpc: 'https://api.devnet.solana.com', 24 | testnet: true, 25 | limit: 0.1, 26 | fee: 1, 27 | 28 | }, 29 | { 30 | id: 9999999991, 31 | name: 'Solana mainnet', 32 | symbol: 'SOL', 33 | rpc: 'https://api.mainnet-beta.solana.com', 34 | testnet: false, 35 | limit: 0.1, 36 | fee: 1, 37 | 38 | }, 39 | ] 40 | 41 | 42 | export const METADATA_2022_PROGRAM_ID = new PublicKey("META4s4fSmpkTbZoUsgC1oBnWB31vQcmnN8giPw51Zu") 43 | export const RAYDIUMF_PROGRAM_ID = new PublicKey("AE6Go5VqcagBJi2RnNcPiHmVGHd27deDEJBNAEEnzw8Y") 44 | export const METADATA_2022_PROGRAM_ID_TESTNET = new PublicKey("M1tgEZCz7fHqRAR3G5RLxU6c6ceQiZyFK7tzzy4Rof4") 45 | 46 | export const TESTNET_SHOW = true 47 | 48 | export const BOT_NAME = 'DexbotDevs Solana Launcher' 49 | 50 | export const EXPLORER_ADDRESS_BASE = "https://explorer.solana.com/address/"; 51 | 52 | export const OPENBOOK_DEX = "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"; // openbook now srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX 53 | export const OPENBOOK_DEX_DEVNET = "EoTcMgcDRTJVZDMZWBoU6rhYHZfkNTVEAfz3uUJRcYGj"; 54 | export const SERUM_DEX_V3_DEVNET = "DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY"; 55 | 56 | export const DEX_PROGRAMS: { [key: string]: string } = { 57 | srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX: "Openbook Dex", 58 | EoTcMgcDRTJVZDMZWBoU6rhYHZfkNTVEAfz3uUJRcYGj: "Openbook Dex Devnet", 59 | "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin": "Serum Dex (Compromised)", 60 | DESVgJVGajEgKGXhb6XmqDHGz3VjdgP7rEVESBgxmroY: "Serum Dex V3 Devnet", 61 | }; 62 | 63 | export const MAX_U128 = "340282366920938463463374607431768211455"; 64 | 65 | export const MARKET_ACCOUNT_FLAGS_B58_ENCODED = "W723RTUpoZ"; 66 | 67 | export type TransactionWithSigners = { 68 | transaction: Transaction; 69 | signers: Array; 70 | }; 71 | 72 | export const PROGRAMIDS = MAINNET_PROGRAM_ID; 73 | 74 | export const ENDPOINT = _ENDPOINT; 75 | 76 | export const RAYDIUM_MAINNET_API = RAYDIUM_MAINNET; 77 | 78 | export const makeTxVersion = TxVersion.V0; 79 | 80 | export const addLookupTableInfo = LOOKUP_TABLE_CACHE 81 | 82 | 83 | export const DEFAULT_TOKEN = { 84 | 'SOL': new Token(TOKEN_PROGRAM_ID, new PublicKey('So11111111111111111111111111111111111111112'), 9, 'WSOL', 'WSOL'), 85 | 'USDC': new Token(TOKEN_PROGRAM_ID, new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), 6, 'USDC', 'USDC'), 86 | 'RAY': new Token(TOKEN_PROGRAM_ID, new PublicKey('4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'), 6, 'RAY', 'RAY'), 87 | 'RAY_USDC-LP': new Token(TOKEN_PROGRAM_ID, new PublicKey('FGYXP4vBkMEtKhxrmEBcWN8VNmXX8qNgEJpENKDETZ4Y'), 6, 'RAY-USDC', 'RAY-USDC'), 88 | } 89 | export const AUTHORITY_AMM = 'amm authority' 90 | export const AMM_ASSOCIATED_SEED = 'amm_associated_seed' 91 | export const TARGET_ASSOCIATED_SEED = 'target_associated_seed' 92 | export const WITHDRAW_ASSOCIATED_SEED = 'withdraw_associated_seed' 93 | export const OPEN_ORDER_ASSOCIATED_SEED = 'open_order_associated_seed' 94 | export const COIN_VAULT_ASSOCIATED_SEED = 'coin_vault_associated_seed' 95 | export const PC_VAULT_ASSOCIATED_SEED = 'pc_vault_associated_seed' 96 | export const LP_MINT_ASSOCIATED_SEED = 'lp_mint_associated_seed' 97 | export const TEMP_LP_TOKEN_ASSOCIATED_SEED = 'temp_lp_token_associated_seed' 98 | 99 | export const feeId = new PublicKey("7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5") -------------------------------------------------------------------------------- /src/createKeys.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from '@solana/web3.js'; 2 | import * as fs from 'fs'; 3 | import promptSync from 'prompt-sync'; 4 | import path from 'path'; 5 | import bs58 from 'bs58'; 6 | 7 | const prompt = promptSync(); 8 | 9 | const keypairsDir = path.join(__dirname, 'keypairs'); 10 | const keyInfoPath = path.join(__dirname, 'keyInfo.json'); 11 | 12 | interface IPoolInfo { 13 | [key: string]: any; 14 | numOfWallets?: number; 15 | } 16 | 17 | // Ensure the keypairs directory exists 18 | if (!fs.existsSync(keypairsDir)) { 19 | fs.mkdirSync(keypairsDir, { recursive: true }); 20 | } 21 | 22 | function generateWallets(numOfWallets: number): Keypair[] { 23 | let wallets: Keypair[] = []; 24 | for (let i = 0; i < numOfWallets; i++) { 25 | const wallet = Keypair.generate(); 26 | wallets.push(wallet); 27 | } 28 | return wallets; 29 | } 30 | 31 | function saveKeypairToFile(keypair: Keypair, index: number) { 32 | const keypairPath = path.join(keypairsDir, `keypair${index + 1}.json`); 33 | fs.writeFileSync(keypairPath, JSON.stringify(Array.from(keypair.secretKey))); 34 | } 35 | 36 | function readKeypairs(): Keypair[] { 37 | const files = fs.readdirSync(keypairsDir); 38 | return files.map(file => { 39 | const filePath = path.join(keypairsDir, file); 40 | const secretKey = JSON.parse(fs.readFileSync(filePath, 'utf-8')); 41 | return Keypair.fromSecretKey(new Uint8Array(secretKey)); 42 | }); 43 | } 44 | 45 | function updatePoolInfo(wallets: Keypair[]) { 46 | let poolInfo: IPoolInfo = {}; // Use the defined type here 47 | 48 | // Check if poolInfo.json exists and read its content 49 | if (fs.existsSync(keyInfoPath)) { 50 | const data = fs.readFileSync(keyInfoPath, 'utf8'); 51 | poolInfo = JSON.parse(data); 52 | } 53 | 54 | // Update wallet-related information 55 | poolInfo.numOfWallets = wallets.length; 56 | wallets.forEach((wallet, index) => { 57 | poolInfo[`pubkey${index + 1}`] = wallet.publicKey.toString(); 58 | }); 59 | 60 | // Write updated data back to poolInfo.json 61 | fs.writeFileSync(keyInfoPath, JSON.stringify(poolInfo, null, 2)); 62 | } 63 | 64 | export async function createKeypairs() { 65 | console.log('WARNING: If you create new ones, ensure you don\'t have SOL, OR ELSE IT WILL BE GONE.'); 66 | const action = prompt('Do you want to (c)reate new wallets or (u)se existing ones? (c/u): '); 67 | let wallets: Keypair[] = []; 68 | 69 | if (action === 'c') { 70 | const numOfWallets = 27; // Hardcode 27 buyer keypairs here. 71 | if (isNaN(numOfWallets) || numOfWallets <= 0) { 72 | console.log('Invalid number. Please enter a positive integer.'); 73 | return; 74 | } 75 | 76 | wallets = generateWallets(numOfWallets); 77 | wallets.forEach((wallet, index) => { 78 | saveKeypairToFile(wallet, index); 79 | console.log(`Wallet ${index + 1} Public Key: ${wallet.publicKey.toString()}`); 80 | }); 81 | } else if (action === 'u') { 82 | wallets = readKeypairs(); 83 | wallets.forEach((wallet, index) => { 84 | console.log(`Read Wallet ${index + 1} Public Key: ${wallet.publicKey.toString()}`); 85 | console.log(`Read Wallet ${index + 1} Private Key: ${bs58.encode(wallet.secretKey)}\n`); 86 | }); 87 | } else { 88 | console.log('Invalid option. Please enter "c" for create or "u" for use existing.'); 89 | return; 90 | } 91 | 92 | updatePoolInfo(wallets); 93 | console.log(`${wallets.length} wallets have been processed.`); 94 | } 95 | 96 | export function loadKeypairs(): Keypair[] { 97 | // Define a regular expression to match filenames like 'keypair1.json', 'keypair2.json', etc. 98 | const keypairRegex = /^keypair\d+\.json$/; 99 | 100 | return fs.readdirSync(keypairsDir) 101 | .filter(file => keypairRegex.test(file)) // Use the regex to test each filename 102 | .map(file => { 103 | const filePath = path.join(keypairsDir, file); 104 | const secretKeyString = fs.readFileSync(filePath, { encoding: 'utf8' }); 105 | const secretKey = Uint8Array.from(JSON.parse(secretKeyString)); 106 | return Keypair.fromSecretKey(secretKey); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /src/clients/raydiumUtil.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ASSOCIATED_TOKEN_PROGRAM_ID, 3 | buildSimpleTransaction, 4 | findProgramAddress, 5 | InnerSimpleV0Transaction, 6 | Liquidity, 7 | SPL_ACCOUNT_LAYOUT, 8 | Token, 9 | TOKEN_PROGRAM_ID, 10 | TokenAccount, 11 | } from '@raydium-io/raydium-sdk'; 12 | import { 13 | Connection, 14 | Keypair, 15 | PublicKey, 16 | SendOptions, 17 | Signer, 18 | Transaction, 19 | VersionedTransaction, 20 | } from '@solana/web3.js'; 21 | 22 | import { 23 | addLookupTableInfo, 24 | feeId, 25 | makeTxVersion, 26 | PROGRAMIDS, 27 | } from './constants'; 28 | import { connection, wallet, walletconn } from '../../config'; 29 | import { BN } from '@project-serum/anchor'; 30 | 31 | const ZERO = new BN(0) 32 | type LiquidityPairTargetInfo = { 33 | baseToken: Token 34 | quoteToken: Token 35 | targetMarketId: PublicKey 36 | } 37 | type CalcStartPrice = { 38 | addBaseAmount: BN 39 | addQuoteAmount: BN 40 | } 41 | type WalletTokenAccounts = Awaited> 42 | 43 | 44 | type TestTxInputInfo = LiquidityPairTargetInfo & 45 | CalcStartPrice & { 46 | startTime: number // seconds 47 | walletTokenAccounts: WalletTokenAccounts 48 | wallet: Keypair 49 | } 50 | export async function sendTx( 51 | connection: Connection, 52 | payer: Keypair | Signer, 53 | txs: (VersionedTransaction | Transaction)[], 54 | options?: SendOptions 55 | ): Promise { 56 | const txids: string[] = []; 57 | for (const iTx of txs) { 58 | if (iTx instanceof VersionedTransaction) { 59 | iTx.sign([payer]); 60 | txids.push(await connection.sendTransaction(iTx, options)); 61 | } else { 62 | iTx.sign(payer); 63 | txids.push(await connection.sendTransaction(iTx, [payer], options)); 64 | } 65 | } 66 | return txids; 67 | } 68 | 69 | export async function getWalletTokenAccount(connection: Connection, wallet: PublicKey): Promise { 70 | const walletTokenAccount = await connection.getTokenAccountsByOwner(wallet, { 71 | programId: TOKEN_PROGRAM_ID, 72 | }); 73 | return walletTokenAccount.value.map((i) => ({ 74 | pubkey: i.pubkey, 75 | programId: i.account.owner, 76 | accountInfo: SPL_ACCOUNT_LAYOUT.decode(i.account.data), 77 | })); 78 | } 79 | 80 | 81 | export async function sendTransaction( senderTx: (VersionedTransaction | Transaction)[],options?: SendOptions){ 82 | return await sendTx(connection, walletconn.payer, senderTx, options) 83 | 84 | } 85 | 86 | export async function buildAndSendTx(innerSimpleV0Transaction: InnerSimpleV0Transaction[],options?: SendOptions) { 87 | const willSendTx = await buildSimpleTransaction({ 88 | connection, 89 | makeTxVersion, 90 | payer: wallet.publicKey, 91 | innerTransactions: innerSimpleV0Transaction, 92 | addLookupTableInfo: addLookupTableInfo, 93 | }) 94 | 95 | 96 | 97 | return await sendTx(connection, walletconn.payer, willSendTx, options) 98 | } 99 | 100 | export function getATAAddress(programId: PublicKey, owner: PublicKey, mint: PublicKey) { 101 | const { publicKey, nonce } = findProgramAddress( 102 | [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], 103 | new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") 104 | ); 105 | return { publicKey, nonce }; 106 | } 107 | 108 | export async function sleepTime(ms: number) { 109 | console.log((new Date()).toLocaleString(), 'sleepTime', ms) 110 | return new Promise(resolve => setTimeout(resolve, ms)) 111 | } 112 | 113 | export async function ammCreatePool(input: TestTxInputInfo) { 114 | // -------- step 1: make instructions -------- 115 | const initPoolInstructionResponse = await Liquidity.makeCreatePoolV4InstructionV2Simple({ 116 | connection, 117 | programId: PROGRAMIDS.AmmV4, 118 | marketInfo: { 119 | marketId: input.targetMarketId, 120 | programId: PROGRAMIDS.OPENBOOK_MARKET, 121 | }, 122 | baseMintInfo: input.baseToken, 123 | quoteMintInfo: input.quoteToken, 124 | baseAmount: input.addBaseAmount, 125 | quoteAmount: input.addQuoteAmount, 126 | startTime: new BN(Math.floor(input.startTime)), 127 | ownerInfo: { 128 | feePayer: input.wallet.publicKey, 129 | wallet: input.wallet.publicKey, 130 | tokenAccounts: input.walletTokenAccounts, 131 | useSOLBalance: true, 132 | }, 133 | associatedOnly: false, 134 | checkCreateATAOwner: true, 135 | makeTxVersion, 136 | feeDestinationId: feeId, // only mainnet use this 137 | }) 138 | 139 | return { txs: initPoolInstructionResponse } 140 | } 141 | 142 | 143 | export function calcMarketStartPrice(input: CalcStartPrice) { 144 | return (input.addBaseAmount.toNumber() / 10 ** 6 )/ (input.addQuoteAmount.toNumber() / 10 ** 9) 145 | } 146 | 147 | 148 | export async function findAssociatedTokenAddress( 149 | walletAddress: PublicKey, 150 | tokenMintAddress: PublicKey 151 | ) { 152 | const { publicKey } = await findProgramAddress( 153 | [ 154 | walletAddress.toBuffer(), 155 | TOKEN_PROGRAM_ID.toBuffer(), 156 | tokenMintAddress.toBuffer(), 157 | ], 158 | ASSOCIATED_TOKEN_PROGRAM_ID 159 | ) 160 | return publicKey 161 | } -------------------------------------------------------------------------------- /src/clients/LookupTableProvider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountInfo, 3 | AddressLookupTableAccount, 4 | AddressLookupTableProgram, 5 | PublicKey, 6 | } from '@solana/web3.js'; 7 | import { connection } from '../../config'; 8 | 9 | /** 10 | * this class solves 2 problems: 11 | * 1. cache and geyser subscribe to lookup tables for fast retreival 12 | * 2. compute the ideal lookup tables for a set of addresses 13 | * 14 | * the second problem/solution is needed because jito bundles can not include a a txn that uses a lookup table 15 | * that has been modified in the same bundle. so this class caches all lookups and then computes the ideal lookup tables 16 | * for a set of addresses used by the arb txn so that the arb txn size is reduced below the maximum. 17 | */ 18 | class LookupTableProvider { 19 | lookupTables: Map |any; 20 | addressesForLookupTable: Map>|any; 21 | lookupTablesForAddress: Map>|any; 22 | 23 | constructor() { 24 | this.lookupTables = new Map(); 25 | this.lookupTablesForAddress = new Map(); 26 | this.addressesForLookupTable = new Map(); 27 | } 28 | 29 | private updateCache( 30 | lutAddress: PublicKey, 31 | lutAccount: AddressLookupTableAccount, 32 | ) { 33 | this.lookupTables.set(lutAddress.toBase58(), lutAccount); 34 | 35 | this.addressesForLookupTable.set(lutAddress.toBase58(), new Set()); 36 | 37 | for (const address of lutAccount.state.addresses) { 38 | const addressStr = address.toBase58(); 39 | this.addressesForLookupTable.get(lutAddress.toBase58()).add(addressStr); 40 | if (!this.lookupTablesForAddress.has(addressStr)) { 41 | this.lookupTablesForAddress.set(addressStr, new Set()); 42 | } 43 | this.lookupTablesForAddress.get(addressStr).add(lutAddress.toBase58()); 44 | } 45 | } 46 | 47 | private processLookupTableUpdate( 48 | lutAddress: PublicKey, 49 | data: AccountInfo, 50 | ) { 51 | const lutAccount = new AddressLookupTableAccount({ 52 | key: lutAddress, 53 | state: AddressLookupTableAccount.deserialize(data.data), 54 | }); 55 | 56 | this.updateCache(lutAddress, lutAccount); 57 | return; 58 | } 59 | 60 | async getLookupTable( 61 | lutAddress: PublicKey, 62 | ): Promise { 63 | const lutAddressStr = lutAddress.toBase58(); 64 | if (this.lookupTables.has(lutAddressStr)) { 65 | return this.lookupTables.get(lutAddressStr); 66 | } 67 | 68 | const lut = await connection.getAddressLookupTable(lutAddress); 69 | if (lut.value === null) { 70 | return null; 71 | } 72 | 73 | this.updateCache(lutAddress, lut.value); 74 | 75 | return lut.value; 76 | } 77 | 78 | computeIdealLookupTablesForAddresses( 79 | addresses: PublicKey[], 80 | ): AddressLookupTableAccount[] { 81 | const MIN_ADDRESSES_TO_INCLUDE_TABLE = 2; 82 | const MAX_TABLE_COUNT = 3; 83 | 84 | const addressSet = new Set(); 85 | const tableIntersections = new Map(); 86 | const selectedTables: AddressLookupTableAccount[] = []; 87 | const remainingAddresses = new Set(); 88 | let numAddressesTakenCareOf = 0; 89 | 90 | for (const address of addresses) { 91 | const addressStr = address.toBase58(); 92 | 93 | if (addressSet.has(addressStr)) continue; 94 | addressSet.add(addressStr); 95 | 96 | const tablesForAddress = 97 | this.lookupTablesForAddress.get(addressStr) || new Set(); 98 | 99 | if (tablesForAddress.size === 0) continue; 100 | 101 | remainingAddresses.add(addressStr); 102 | 103 | for (const table of tablesForAddress) { 104 | const intersectionCount = tableIntersections.get(table) || 0; 105 | tableIntersections.set(table, intersectionCount + 1); 106 | } 107 | } 108 | 109 | const sortedIntersectionArray = Array.from( 110 | tableIntersections.entries(), 111 | ).sort((a, b) => b[1] - a[1]); 112 | 113 | for (const [lutKey, intersectionSize] of sortedIntersectionArray) { 114 | if (intersectionSize < MIN_ADDRESSES_TO_INCLUDE_TABLE) break; 115 | if (selectedTables.length >= MAX_TABLE_COUNT) break; 116 | if (remainingAddresses.size <= 1) break; 117 | 118 | const lutAddresses :any= this.addressesForLookupTable.get(lutKey); 119 | 120 | const addressMatches = new Set( 121 | [...remainingAddresses].filter((x) => lutAddresses.has(x)), 122 | ); 123 | 124 | if (addressMatches.size >= MIN_ADDRESSES_TO_INCLUDE_TABLE) { 125 | selectedTables.push(this.lookupTables.get(lutKey)); 126 | for (const address of addressMatches) { 127 | remainingAddresses.delete(address); 128 | numAddressesTakenCareOf++; 129 | } 130 | } 131 | } 132 | 133 | return selectedTables; 134 | } 135 | } 136 | 137 | const lookupTableProvider = new LookupTableProvider(); 138 | 139 | lookupTableProvider.getLookupTable( 140 | // custom lookup tables 141 | new PublicKey('Gr8rXuDwE2Vd2F5tifkPyMaUR67636YgrZEjkJf9RR9V'), 142 | ); 143 | 144 | export { lookupTableProvider }; -------------------------------------------------------------------------------- /src/clients/poolKeysReassigned.ts: -------------------------------------------------------------------------------- 1 | import * as spl from '@solana/spl-token'; 2 | import { Market } from '@openbook-dex/openbook'; 3 | import { AccountInfo, PublicKey } from '@solana/web3.js'; 4 | import * as structs from './structs'; 5 | import { RayLiqPoolv4, connection, wallet } from '../../config'; 6 | import { IPoolKeys } from './interfaces'; 7 | import { ApiPoolInfoV4 } from "@raydium-io/raydium-sdk"; 8 | 9 | const openbookProgram = new PublicKey('srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX'); 10 | 11 | async function getMarketInfo(marketId: PublicKey) { 12 | let reqs = 0; 13 | let marketInfo = await connection.getAccountInfo(marketId); 14 | reqs++; 15 | 16 | while (!marketInfo) { 17 | marketInfo = await connection.getAccountInfo(marketId); 18 | reqs++; 19 | if (marketInfo) { 20 | break; 21 | } else if (reqs > 20) { 22 | console.log(`Could not get market info..`); 23 | 24 | return null; 25 | } 26 | } 27 | 28 | return marketInfo; 29 | } 30 | 31 | async function getDecodedData(marketInfo: { 32 | executable?: boolean; 33 | owner?: PublicKey; 34 | lamports?: number; 35 | data: any; 36 | rentEpoch?: number | undefined; 37 | }) { 38 | return Market.getLayout(openbookProgram).decode(marketInfo.data); 39 | } 40 | 41 | async function getMintData(mint: PublicKey) { 42 | return connection.getAccountInfo(mint); 43 | } 44 | 45 | async function getDecimals(mintData: AccountInfo | null) { 46 | if (!mintData) throw new Error('No mint data!'); 47 | 48 | return structs.SPL_MINT_LAYOUT.decode(mintData.data).decimals; 49 | } 50 | 51 | async function getOwnerAta(mint: { toBuffer: () => Uint8Array | Buffer }, publicKey: PublicKey) { 52 | const foundAta = PublicKey.findProgramAddressSync( 53 | [publicKey.toBuffer(), spl.TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()], 54 | spl.ASSOCIATED_TOKEN_PROGRAM_ID 55 | )[0]; 56 | 57 | return foundAta; 58 | } 59 | 60 | function getVaultSigner(marketId: { toBuffer: any }, marketDeco: { vaultSignerNonce: { toString: () => any } }) { 61 | const seeds = [marketId.toBuffer()]; 62 | const seedsWithNonce = seeds.concat(Buffer.from([Number(marketDeco.vaultSignerNonce.toString())]), Buffer.alloc(7)); 63 | 64 | return PublicKey.createProgramAddressSync(seedsWithNonce, openbookProgram); 65 | } 66 | 67 | export async function derivePoolKeys(marketId: PublicKey) { 68 | const marketInfo = await getMarketInfo(marketId); 69 | if (!marketInfo) return null; 70 | const marketDeco = await getDecodedData(marketInfo); 71 | const { baseMint } = marketDeco; 72 | const baseMintData = await getMintData(baseMint); 73 | const baseDecimals = await getDecimals(baseMintData); 74 | const ownerBaseAta = await getOwnerAta(baseMint, wallet.publicKey); 75 | const { quoteMint } = marketDeco; 76 | const quoteMintData = await getMintData(quoteMint); 77 | const quoteDecimals = await getDecimals(quoteMintData); 78 | const ownerQuoteAta = await getOwnerAta(quoteMint, wallet.publicKey); 79 | const authority = PublicKey.findProgramAddressSync( 80 | [Buffer.from([97, 109, 109, 32, 97, 117, 116, 104, 111, 114, 105, 116, 121])], 81 | RayLiqPoolv4 82 | )[0]; 83 | 84 | const marketAuthority = getVaultSigner(marketId, marketDeco); 85 | 86 | // get/derive all the pool keys 87 | const poolKeys = { 88 | keg: new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), 89 | version: 4, 90 | marketVersion: 3, 91 | programId: RayLiqPoolv4, 92 | baseMint, 93 | quoteMint, 94 | ownerBaseAta, 95 | ownerQuoteAta, 96 | baseDecimals, 97 | quoteDecimals, 98 | lpDecimals: baseDecimals, 99 | authority, 100 | marketAuthority, 101 | marketProgramId: openbookProgram, 102 | marketId, 103 | marketBids: marketDeco.bids, 104 | marketAsks: marketDeco.asks, 105 | marketQuoteVault: marketDeco.quoteVault, 106 | marketBaseVault: marketDeco.baseVault, 107 | marketEventQueue: marketDeco.eventQueue, 108 | id: PublicKey.findProgramAddressSync( 109 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('amm_associated_seed', 'utf-8')], 110 | RayLiqPoolv4 111 | )[0], 112 | baseVault: PublicKey.findProgramAddressSync( 113 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('coin_vault_associated_seed', 'utf-8')], 114 | RayLiqPoolv4 115 | )[0], 116 | coinVault: PublicKey.findProgramAddressSync( 117 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('pc_vault_associated_seed', 'utf-8')], 118 | RayLiqPoolv4 119 | )[0], 120 | lpMint: PublicKey.findProgramAddressSync( 121 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('lp_mint_associated_seed', 'utf-8')], 122 | RayLiqPoolv4 123 | )[0], 124 | lpVault: PublicKey.findProgramAddressSync( 125 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('temp_lp_token_associated_seed', 'utf-8')], 126 | RayLiqPoolv4 127 | )[0], 128 | targetOrders: PublicKey.findProgramAddressSync( 129 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('target_associated_seed', 'utf-8')], 130 | RayLiqPoolv4 131 | )[0], 132 | withdrawQueue: PublicKey.findProgramAddressSync( 133 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('withdraw_associated_seed', 'utf-8')], 134 | RayLiqPoolv4 135 | )[0], 136 | openOrders: PublicKey.findProgramAddressSync( 137 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('open_order_associated_seed', 'utf-8')], 138 | RayLiqPoolv4 139 | )[0], 140 | quoteVault: PublicKey.findProgramAddressSync( 141 | [RayLiqPoolv4.toBuffer(), marketId.toBuffer(), Buffer.from('pc_vault_associated_seed', 'utf-8')], 142 | RayLiqPoolv4 143 | )[0], 144 | lookupTableAccount: new PublicKey('11111111111111111111111111111111') 145 | }; 146 | 147 | return poolKeys; 148 | } 149 | 150 | export async function PoolKeysCorrector(poolkeys: IPoolKeys): Promise { 151 | return { 152 | id: poolkeys.id.toString(), 153 | baseMint: poolkeys.baseMint.toString(), 154 | quoteMint: poolkeys.quoteMint.toString(), 155 | lpMint: poolkeys.lpMint.toString(), 156 | baseDecimals: poolkeys.baseDecimals, 157 | quoteDecimals: poolkeys.quoteDecimals, 158 | lpDecimals: poolkeys.lpDecimals, 159 | version: 4, 160 | programId: poolkeys.programId?.toString() || RayLiqPoolv4.toString(), 161 | authority: poolkeys.authority.toString(), 162 | openOrders: poolkeys.openOrders.toString(), 163 | targetOrders: poolkeys.targetOrders.toString(), 164 | baseVault: poolkeys.baseVault.toString(), 165 | quoteVault: poolkeys.quoteVault.toString(), 166 | withdrawQueue: poolkeys.withdrawQueue?.toString() || '', 167 | lpVault: poolkeys.lpVault?.toString() || '', 168 | marketVersion: 3, 169 | marketProgramId: poolkeys.marketProgramId.toString(), 170 | marketId: poolkeys.marketId.toString(), 171 | marketAuthority: poolkeys.marketAuthority.toString(), 172 | marketBaseVault: poolkeys.baseVault.toString(), 173 | marketQuoteVault: poolkeys.quoteVault.toString(), 174 | marketBids: poolkeys.marketBids.toString(), 175 | marketAsks: poolkeys.marketAsks.toString(), 176 | marketEventQueue: poolkeys.marketEventQueue.toString(), 177 | lookupTableAccount: PublicKey.default.toString() 178 | } 179 | } -------------------------------------------------------------------------------- /src/removeLiq.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import assert from 'assert'; 4 | import * as readline from 'readline'; 5 | import { BN } from 'bn.js'; 6 | import { 7 | jsonInfo2PoolKeys, 8 | Liquidity, 9 | LiquidityPoolKeys, 10 | TokenAmount, 11 | TOKEN_PROGRAM_ID, 12 | Token, 13 | TxVersion, 14 | SPL_ACCOUNT_LAYOUT, 15 | TokenAccount, 16 | LOOKUP_TABLE_CACHE, 17 | InnerTransaction, 18 | CacheLTA, 19 | getMultipleLookupTableInfo, 20 | InnerSimpleTransaction, 21 | InnerSimpleV0Transaction, 22 | } from '@raydium-io/raydium-sdk'; 23 | import { 24 | Keypair, 25 | Signer, 26 | PublicKey, 27 | Connection, 28 | VersionedTransaction, 29 | SystemProgram, 30 | TransactionMessage, 31 | TransactionInstruction, 32 | Transaction, 33 | AddressLookupTableAccount, 34 | Blockhash, 35 | LAMPORTS_PER_SOL 36 | } from '@solana/web3.js'; 37 | import { 38 | connection, 39 | wallet, 40 | tipAcct 41 | } from '../config'; 42 | import { formatAmmKeysById } from './clients/formatAmmKeysById'; 43 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 44 | import promptSync from 'prompt-sync'; 45 | import { searcherClient } from "./clients/jito"; 46 | import { Bundle as JitoBundle } from 'jito-ts/dist/sdk/block-engine/types.js'; 47 | import * as spl from '@solana/spl-token'; 48 | 49 | 50 | const prompt = promptSync(); 51 | 52 | 53 | type WalletTokenAccounts = Awaited> 54 | type TestTxInputInfo = { 55 | removeLpTokenAmount: TokenAmount 56 | targetPool: string 57 | walletTokenAccounts: WalletTokenAccounts 58 | wallet: Keypair 59 | } 60 | 61 | async function ammRemoveLiquidity(input: TestTxInputInfo, jitoTip: number) { 62 | const bundledTxns: VersionedTransaction[] = []; 63 | const targetPoolInfo = await formatAmmKeysById(input.targetPool) 64 | assert(targetPoolInfo, 'cannot find the target pool') 65 | 66 | 67 | const poolKeys = jsonInfo2PoolKeys(targetPoolInfo) as LiquidityPoolKeys 68 | const { innerTransactions } = await Liquidity.makeRemoveLiquidityInstructionSimple({ 69 | connection, 70 | poolKeys, 71 | userKeys: { 72 | owner: input.wallet.publicKey, 73 | payer: input.wallet.publicKey, 74 | tokenAccounts: input.walletTokenAccounts, 75 | }, 76 | amountIn: input.removeLpTokenAmount, 77 | makeTxVersion: TxVersion.V0, 78 | }) 79 | 80 | const { blockhash } = await connection.getLatestBlockhash('finalized'); 81 | 82 | const willSendTx = await buildSimpleTransaction({ 83 | innerTransactions: innerTransactions, 84 | recentBlockhash: blockhash, 85 | addLookupTableInfo: LOOKUP_TABLE_CACHE 86 | }); 87 | 88 | bundledTxns.push(...willSendTx); 89 | 90 | const tipSwapIxn = SystemProgram.transfer({ 91 | fromPubkey: wallet.publicKey, 92 | toPubkey: tipAcct, 93 | lamports: BigInt(jitoTip), 94 | }); 95 | 96 | console.log('Jito tip added :).'); 97 | 98 | const message = new TransactionMessage({ 99 | payerKey: wallet.publicKey, 100 | recentBlockhash: blockhash, 101 | instructions: [tipSwapIxn], 102 | }).compileToV0Message(); 103 | 104 | const tipTxn = new VersionedTransaction(message); 105 | tipTxn.sign([wallet]); 106 | 107 | bundledTxns.push(tipTxn); 108 | 109 | 110 | // SEND BUNDLEEEE 111 | await sendBundle(bundledTxns); 112 | bundledTxns.length = 0; 113 | } 114 | 115 | export async function remove() { 116 | const configPath = path.join(__dirname, 'keyInfo.json'); 117 | const configFile = fs.readFileSync(configPath); 118 | const config = JSON.parse(configFile.toString('utf-8')); 119 | 120 | const lpTokenAddr = config.lpTokenAddr; 121 | const targetPool = config.targetPool; 122 | const OpenBookID = new PublicKey(config.marketID); 123 | 124 | const jitoTipAmt = parseFloat(prompt('Jito tip in Sol (Ex. 0.01): ') || '0') * LAMPORTS_PER_SOL; 125 | 126 | const keys = await derivePoolKeys(OpenBookID); 127 | 128 | 129 | const lpToken = new Token(TOKEN_PROGRAM_ID, new PublicKey(lpTokenAddr), keys?.baseDecimals); 130 | const lpATA = await spl.getAssociatedTokenAddress( 131 | new PublicKey(lpTokenAddr), 132 | wallet.publicKey, 133 | ); 134 | const lpBalance = await connection.getTokenAccountBalance(lpATA); 135 | 136 | const removeLpTokenAmount = new TokenAmount(lpToken, lpBalance.value.amount, true) 137 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey) 138 | 139 | try { 140 | await ammRemoveLiquidity( 141 | { 142 | removeLpTokenAmount, 143 | targetPool, 144 | walletTokenAccounts, 145 | wallet: wallet, 146 | }, 147 | jitoTipAmt 148 | ) 149 | } catch (error) { 150 | console.error('An error occurred:', error); 151 | } 152 | } 153 | 154 | 155 | export async function getWalletTokenAccount(connection: Connection, wallet: PublicKey): Promise { 156 | const walletTokenAccount = await connection.getTokenAccountsByOwner(wallet, { 157 | programId: TOKEN_PROGRAM_ID, 158 | }); 159 | return walletTokenAccount.value.map((i) => ({ 160 | pubkey: i.pubkey, 161 | programId: i.account.owner, 162 | accountInfo: SPL_ACCOUNT_LAYOUT.decode(i.account.data), 163 | })); 164 | } 165 | 166 | async function sendBundle(bundledTxns: VersionedTransaction[]) { 167 | try { 168 | const bundleId = await searcherClient.sendBundle(new JitoBundle(bundledTxns, bundledTxns.length)); 169 | console.log(`Bundle ${bundleId} sent.`); 170 | } catch (error) { 171 | const err = error as any; 172 | console.error("Error sending bundle:", err.message); 173 | 174 | if (err?.message?.includes('Bundle Dropped, no connected leader up soon')) { 175 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 176 | } else { 177 | console.error("An unexpected error occurred:", err.message); 178 | } 179 | } 180 | } 181 | 182 | async function buildSimpleTransaction({ 183 | innerTransactions, 184 | recentBlockhash, 185 | addLookupTableInfo, 186 | }: { 187 | innerTransactions: InnerSimpleTransaction[] 188 | recentBlockhash: string | Blockhash 189 | addLookupTableInfo?: CacheLTA | undefined 190 | }): Promise { 191 | 192 | const txList: VersionedTransaction[] = [] 193 | console.log('innerLen:', innerTransactions.length); 194 | for (const itemIx of innerTransactions) { 195 | txList.push( 196 | _makeTransaction({ 197 | instructions: itemIx.instructions, 198 | recentBlockhash, 199 | signers: itemIx.signers, 200 | lookupTableInfos: Object.values({ 201 | ...(addLookupTableInfo ?? {}), 202 | ...((itemIx as InnerSimpleV0Transaction).lookupTableAddress ?? {}), 203 | }), 204 | }), 205 | ) 206 | } 207 | return txList 208 | } 209 | 210 | function _makeTransaction({ 211 | instructions, 212 | recentBlockhash, 213 | signers, 214 | lookupTableInfos, 215 | }: { 216 | instructions: TransactionInstruction[] 217 | recentBlockhash: string | Blockhash 218 | signers: (Signer | Keypair)[] 219 | lookupTableInfos?: AddressLookupTableAccount[] 220 | }): VersionedTransaction { 221 | const transactionMessage = new TransactionMessage({ 222 | payerKey: wallet.publicKey, 223 | recentBlockhash, 224 | instructions, 225 | }) 226 | const itemV = new VersionedTransaction(transactionMessage.compileToV0Message(lookupTableInfos)) 227 | itemV.sign(signers) 228 | itemV.sign([wallet]) 229 | return itemV 230 | } 231 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/buyToken.ts: -------------------------------------------------------------------------------- 1 | import { connection, wallet, walletconn, RayLiqPoolv4, tipAcct } from "../config"; 2 | import { PublicKey, ComputeBudgetProgram, VersionedTransaction, TransactionInstruction, TransactionMessage, SystemProgram, Keypair, LAMPORTS_PER_SOL, AddressLookupTableAccount } from '@solana/web3.js'; 3 | import { DEFAULT_TOKEN, LP_MINT_ASSOCIATED_SEED, PROGRAMIDS, addLookupTableInfo, makeTxVersion } from './clients/constants'; 4 | import { TOKEN_PROGRAM_ID, getMint } from '@solana/spl-token'; 5 | import { Liquidity, MARKET_STATE_LAYOUT_V3, Token, TokenAmount, simulateTransaction, Market, MAINNET_PROGRAM_ID } from "@raydium-io/raydium-sdk"; 6 | import { BN, LangErrorCode, Wallet } from "@project-serum/anchor"; 7 | import { ammCreatePool, getWalletTokenAccount } from "./clients/raydiumUtil"; 8 | import { loadKeypairs } from './createKeys'; 9 | import { lookupTableProvider } from "./clients/LookupTableProvider"; 10 | //import { getRandomTipAccount } from "./clients/config"; 11 | import NodeWallet from "@project-serum/anchor/dist/cjs/nodewallet"; 12 | import { searcherClient } from "./clients/jito"; 13 | import { Bundle as JitoBundle } from 'jito-ts/dist/sdk/block-engine/types.js'; 14 | import promptSync from 'prompt-sync'; 15 | import * as spl from '@solana/spl-token'; 16 | import { IPoolKeys } from './clients/interfaces'; 17 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 18 | import path from 'path'; 19 | import fs from 'fs'; 20 | import { Key } from "readline"; 21 | 22 | const prompt = promptSync(); 23 | const keyInfoPath = path.join(__dirname, 'keyInfo.json'); 24 | 25 | swapper(); 26 | 27 | export async function swapper() { 28 | const bundledTxns: VersionedTransaction[] = []; 29 | const keypairs: Keypair[] = loadKeypairs(); 30 | 31 | let poolInfo: { [key: string]: any } = {}; 32 | if (fs.existsSync(keyInfoPath)) { 33 | const data = fs.readFileSync(keyInfoPath, 'utf-8'); 34 | poolInfo = JSON.parse(data); 35 | } 36 | 37 | const lut = new PublicKey(poolInfo.addressLUT.toString()); 38 | 39 | const lookupTableAccount = ( 40 | await connection.getAddressLookupTable(lut) 41 | ).value; 42 | 43 | if (lookupTableAccount == null) { 44 | console.log("Lookup table account not found!"); 45 | process.exit(0); 46 | } 47 | 48 | // -------- step 1: ask nessesary questions for pool build -------- 49 | const OpenBookID = prompt('OpenBook MarketID: ') || ''; 50 | const jitoTipAmtInput = prompt('Jito tip in Sol (Ex. 0.01): ') || '0'; 51 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 52 | 53 | 54 | 55 | 56 | // -------- step 2: create pool txn -------- 57 | const targetMarketId = new PublicKey(OpenBookID) 58 | 59 | const { blockhash } = await connection.getLatestBlockhash('finalized'); 60 | 61 | 62 | // -------- step 3: create swap txns -------- 63 | const txMainSwaps: VersionedTransaction[] = await createWalletSwaps( 64 | targetMarketId, 65 | blockhash, 66 | keypairs, 67 | jitoTipAmt, 68 | lookupTableAccount, 69 | ) 70 | bundledTxns.push(...txMainSwaps); 71 | 72 | // -------- step 4: send bundle -------- 73 | ///* 74 | // Simulate each transaction 75 | for (const tx of bundledTxns) { 76 | try { 77 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "processed" }); 78 | console.log(simulationResult); 79 | 80 | if (simulationResult.value.err) { 81 | console.error("Simulation error for transaction:", simulationResult.value.err); 82 | } else { 83 | console.log("Simulation success for transaction. Logs:"); 84 | simulationResult.value.logs?.forEach(log => console.log(log)); 85 | } 86 | } catch (error) { 87 | console.error("Error during simulation:", error); 88 | } 89 | } 90 | //*/ 91 | 92 | //await sendBundle(bundledTxns); 93 | 94 | 95 | 96 | bundledTxns.length = 0; // Reset bundledTxns array 97 | return; 98 | } 99 | 100 | async function createWalletSwaps( 101 | marketID: PublicKey, 102 | blockhash: string, 103 | keypairs: Keypair[], 104 | jitoTip: number, 105 | lut: AddressLookupTableAccount, 106 | ): Promise { 107 | const txsSigned: VersionedTransaction[] = []; 108 | const chunkedKeypairs = chunkArray(keypairs, 7); // EDIT CHUNKS? 109 | const keys = await derivePoolKeys(marketID); 110 | 111 | // Iterate over each chunk of keypairs 112 | for (let chunkIndex = 0; chunkIndex < chunkedKeypairs.length; chunkIndex++) { 113 | const chunk = chunkedKeypairs[chunkIndex]; 114 | const instructionsForChunk: TransactionInstruction[] = []; 115 | 116 | // Iterate over each keypair in the chunk to create swap instructions 117 | for (let i = 0; i < chunk.length; i++) { 118 | const keypair = chunk[i]; 119 | console.log(`Processing keypair ${i + 1}/${chunk.length}:`, keypair.publicKey.toString()); 120 | 121 | if (keys == null) { 122 | console.log("Error fetching poolkeys"); 123 | process.exit(0); 124 | } 125 | 126 | const TokenATA = await spl.getAssociatedTokenAddress( 127 | new PublicKey(keys.baseMint), 128 | keypair.publicKey, 129 | ); 130 | 131 | const wSolATA = await spl.getAssociatedTokenAddress( 132 | spl.NATIVE_MINT, 133 | keypair.publicKey, 134 | ); 135 | 136 | const { buyIxs } = makeSwap(keys, wSolATA, TokenATA, true, keypair); // CHANGE FOR SELL 137 | 138 | instructionsForChunk.push(...buyIxs); // CHANGE FOR SELL 139 | } 140 | 141 | if (chunkIndex === chunkedKeypairs.length - 1) { 142 | const tipSwapIxn = SystemProgram.transfer({ 143 | fromPubkey: wallet.publicKey, 144 | toPubkey: tipAcct, 145 | lamports: BigInt(jitoTip), 146 | }); 147 | instructionsForChunk.push(tipSwapIxn); 148 | console.log('Jito tip added :).'); 149 | } 150 | 151 | const message = new TransactionMessage({ 152 | payerKey: wallet.publicKey, 153 | recentBlockhash: blockhash, 154 | instructions: instructionsForChunk, 155 | }).compileToV0Message([lut]); 156 | 157 | const versionedTx = new VersionedTransaction(message); 158 | 159 | const serializedMsg = versionedTx.serialize(); 160 | console.log("Txn size:", serializedMsg.length); 161 | if (serializedMsg.length > 1232) { console.log('tx too big'); } 162 | 163 | console.log("Signing transaction with chunk signers", chunk.map(kp => kp.publicKey.toString())); 164 | 165 | for (const keypair of chunk) { 166 | versionedTx.sign([keypair]); 167 | } 168 | versionedTx.sign([wallet]) 169 | 170 | 171 | txsSigned.push(versionedTx); 172 | } 173 | 174 | return txsSigned; 175 | } 176 | 177 | 178 | function chunkArray(array: T[], size: number): T[][] { 179 | return Array.from({ length: Math.ceil(array.length / size) }, (v, i) => 180 | array.slice(i * size, i * size + size) 181 | ); 182 | } 183 | 184 | async function sendBundle(bundledTxns: VersionedTransaction[]) { 185 | try { 186 | const bundleId = await searcherClient.sendBundle(new JitoBundle(bundledTxns, bundledTxns.length)); 187 | console.log(`Bundle ${bundleId} sent.`); 188 | 189 | // Assuming onBundleResult returns a Promise 190 | const result = await new Promise((resolve, reject) => { 191 | searcherClient.onBundleResult( 192 | (result) => { 193 | console.log('Received bundle result:', result); 194 | resolve(result); // Resolve the promise with the result 195 | }, 196 | (e: Error) => { 197 | console.error('Error receiving bundle result:', e); 198 | reject(e); // Reject the promise if there's an error 199 | } 200 | ); 201 | }); 202 | 203 | console.log('Result:', result); 204 | 205 | } catch (error) { 206 | const err = error as any; 207 | console.error("Error sending bundle:", err.message); 208 | 209 | if (err?.message?.includes('Bundle Dropped, no connected leader up soon')) { 210 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 211 | } else { 212 | console.error("An unexpected error occurred:", err.message); 213 | } 214 | } 215 | } 216 | 217 | 218 | function makeSwap( 219 | poolKeys: IPoolKeys, 220 | wSolATA: PublicKey, 221 | TokenATA: PublicKey, 222 | reverse: boolean, 223 | keypair: Keypair, 224 | ) { 225 | const programId = new PublicKey('Axz6g5nHgKzm5CbLJcAQauxpdpkL1BafBywSvotyTUSv'); // MY PROGRAM 226 | const account1 = TOKEN_PROGRAM_ID; // token program 227 | const account2 = poolKeys.id; // amm id writable 228 | const account3 = poolKeys.authority; // amm authority 229 | const account4 = poolKeys.openOrders; // amm open orders writable 230 | const account5 = poolKeys.targetOrders; // amm target orders writable 231 | const account6 = poolKeys.baseVault; // pool coin token account writable AKA baseVault 232 | const account7 = poolKeys.quoteVault; // pool pc token account writable AKA quoteVault 233 | const account8 = poolKeys.marketProgramId; // serum program id 234 | const account9 = poolKeys.marketId; // serum market writable 235 | const account10 = poolKeys.marketBids; // serum bids writable 236 | const account11 = poolKeys.marketAsks; // serum asks writable 237 | const account12 = poolKeys.marketEventQueue; // serum event queue writable 238 | const account13 = poolKeys.marketBaseVault; // serum coin vault writable AKA marketBaseVault 239 | const account14 = poolKeys.marketQuoteVault; // serum pc vault writable AKA marketQuoteVault 240 | const account15 = poolKeys.marketAuthority; // serum vault signer AKA marketAuthority 241 | let account16 = wSolATA; // user source token account writable 242 | let account17 = TokenATA; // user dest token account writable 243 | const account18 = keypair.publicKey; // user owner (signer) writable 244 | const account19 = MAINNET_PROGRAM_ID.AmmV4; // ammV4 writable 245 | 246 | if (reverse == true) { 247 | account16 = TokenATA; 248 | account17 = wSolATA; 249 | } 250 | 251 | const buffer = Buffer.alloc(16); 252 | const prefix = Buffer.from([0x09]); 253 | const instructionData = Buffer.concat([prefix, buffer]); 254 | const accountMetas = [ 255 | { pubkey: account1, isSigner: false, isWritable: false }, 256 | { pubkey: account2, isSigner: false, isWritable: true }, 257 | { pubkey: account3, isSigner: false, isWritable: false }, 258 | { pubkey: account4, isSigner: false, isWritable: true }, 259 | { pubkey: account5, isSigner: false, isWritable: true }, 260 | { pubkey: account6, isSigner: false, isWritable: true }, 261 | { pubkey: account7, isSigner: false, isWritable: true }, 262 | { pubkey: account8, isSigner: false, isWritable: false }, 263 | { pubkey: account9, isSigner: false, isWritable: true }, 264 | { pubkey: account10, isSigner: false, isWritable: true }, 265 | { pubkey: account11, isSigner: false, isWritable: true }, 266 | { pubkey: account12, isSigner: false, isWritable: true }, 267 | { pubkey: account13, isSigner: false, isWritable: true }, 268 | { pubkey: account14, isSigner: false, isWritable: true }, 269 | { pubkey: account15, isSigner: false, isWritable: false }, 270 | { pubkey: account16, isSigner: false, isWritable: true }, 271 | { pubkey: account17, isSigner: false, isWritable: true }, 272 | { pubkey: account18, isSigner: true, isWritable: true }, 273 | { pubkey: account19, isSigner: false, isWritable: true } 274 | ]; 275 | 276 | const swap = new TransactionInstruction({ 277 | keys: accountMetas, 278 | programId, 279 | data: instructionData 280 | }); 281 | 282 | 283 | let buyIxs: TransactionInstruction[] = []; 284 | let sellIxs: TransactionInstruction[] = []; 285 | 286 | if (reverse === false) { 287 | buyIxs.push(swap); 288 | } 289 | 290 | if (reverse === true) { 291 | sellIxs.push(swap); 292 | } 293 | 294 | return { buyIxs, sellIxs } ; 295 | } -------------------------------------------------------------------------------- /src/createLUT.ts: -------------------------------------------------------------------------------- 1 | import { AddressLookupTableProgram, Keypair, PublicKey, VersionedTransaction, TransactionMessage, TransactionInstruction, SystemProgram, LAMPORTS_PER_SOL, Blockhash, AddressLookupTableAccount } from '@solana/web3.js'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import { wallet, connection, walletconn, RayLiqPoolv4, tipAcct, payer } from '../config'; 5 | import promptSync from 'prompt-sync'; 6 | import { searcherClient } from "./clients/jito"; 7 | import { Bundle as JitoBundle } from 'jito-ts/dist/sdk/block-engine/types.js'; 8 | //import { getRandomTipAccount } from "./clients/config"; 9 | import { lookupTableProvider } from "./clients/LookupTableProvider"; 10 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 11 | import { loadKeypairs } from './createKeys'; 12 | import * as spl from '@solana/spl-token'; 13 | 14 | const prompt = promptSync(); 15 | const keyInfoPath = path.join(__dirname, 'keyInfo.json'); 16 | const keypairWSOLATAIxs: TransactionInstruction[] = [] 17 | 18 | export async function extendLUT() { 19 | 20 | // -------- step 1: ask nessesary questions for LUT build -------- 21 | const OpenBookID = prompt('OpenBook MarketID: ') || ''; 22 | const jitoTipAmtInput = prompt('Jito tip in Sol (Ex. 0.01): ') || '0'; 23 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 24 | 25 | // Read existing data from poolInfo.json 26 | let poolInfo: { [key: string]: any } = {}; 27 | if (fs.existsSync(keyInfoPath)) { 28 | const data = fs.readFileSync(keyInfoPath, 'utf-8'); 29 | poolInfo = JSON.parse(data); 30 | } 31 | 32 | const bundledTxns1: VersionedTransaction[] = []; 33 | 34 | 35 | 36 | // -------- step 2: get all LUT addresses -------- 37 | const accounts: PublicKey[] = []; // Array with all new keys to push to the new LUT 38 | const lut = new PublicKey(poolInfo.addressLUT.toString()); 39 | 40 | const lookupTableAccount = ( 41 | await connection.getAddressLookupTable(lut) 42 | ).value; 43 | 44 | if (lookupTableAccount == null) { 45 | console.log("Lookup table account not found!"); 46 | process.exit(0); 47 | } 48 | 49 | // Get new market keys 50 | const keys = await derivePoolKeys(new PublicKey(OpenBookID)); 51 | if (keys == null) { 52 | console.log("Poolkeys not found!"); 53 | process.exit(0); 54 | } 55 | 56 | // These values vary based on the new market created 57 | accounts.push( 58 | RayLiqPoolv4, 59 | new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), // token program 60 | keys.id, // amm id writable 61 | keys.authority, // amm authority 62 | keys.openOrders, // amm open orders writable 63 | keys.targetOrders, // amm target orders writable 64 | keys.baseVault, // pool coin token account writable AKA baseVault 65 | keys.quoteVault, // pool pc token account writable AKA quoteVault 66 | keys.marketProgramId, // serum program id 67 | keys.marketId, // serum market writable 68 | keys.marketBids, // serum bids writable 69 | keys.marketAsks, // serum asks writable 70 | keys.marketEventQueue, // serum event queue writable 71 | keys.marketBaseVault, // serum coin vault writable AKA marketBaseVault 72 | keys.marketQuoteVault, // serum pc vault writable AKA marketQuoteVault 73 | keys.marketAuthority, // serum vault signer AKA marketAuthority 74 | keys.ownerQuoteAta, // user source token account writable 75 | keys.ownerBaseAta, // user dest token account writable 76 | ); 77 | 78 | // Loop through each keypair and push its pubkey and ATAs to the accounts array 79 | const keypairs = loadKeypairs(); 80 | for (const keypair of keypairs) { 81 | const ataToken = await spl.getAssociatedTokenAddress( 82 | new PublicKey(keys.baseMint), 83 | keypair.publicKey, 84 | ); 85 | const ataWSOL = await spl.getAssociatedTokenAddress( 86 | spl.NATIVE_MINT, 87 | keypair.publicKey, 88 | ); 89 | accounts.push(keypair.publicKey, ataToken, ataWSOL); 90 | } 91 | 92 | // Push wallet and payer ATAs and pubkey JUST IN CASE (not sure tbh) 93 | const ataTokenwall = await spl.getAssociatedTokenAddress( 94 | new PublicKey(keys.baseMint), 95 | wallet.publicKey, 96 | ); 97 | const ataWSOLwall = await spl.getAssociatedTokenAddress( 98 | spl.NATIVE_MINT, 99 | wallet.publicKey, 100 | ); 101 | 102 | const ataTokenpayer = await spl.getAssociatedTokenAddress( 103 | new PublicKey(keys.baseMint), 104 | payer.publicKey, 105 | ); 106 | const ataWSOLpayer = await spl.getAssociatedTokenAddress( 107 | spl.NATIVE_MINT, 108 | payer.publicKey, 109 | ); 110 | 111 | // Add just in case 112 | accounts.push( 113 | wallet.publicKey, 114 | payer.publicKey, 115 | ataTokenwall, 116 | ataWSOLwall, 117 | ataTokenpayer, 118 | ataWSOLpayer, 119 | lut, 120 | spl.NATIVE_MINT, 121 | keys.baseMint, 122 | ); // DO NOT ADD PROGRAM OR JITO TIP ACCOUNT 123 | 124 | 125 | 126 | 127 | // -------- step 5: push LUT addresses to a txn -------- 128 | const extendLUTixs1: TransactionInstruction[] = []; 129 | const extendLUTixs2: TransactionInstruction[] = []; 130 | const extendLUTixs3: TransactionInstruction[] = []; 131 | const extendLUTixs4: TransactionInstruction[] = []; 132 | 133 | // Chunk accounts array into groups of 30 134 | const accountChunks = Array.from({ length: Math.ceil(accounts.length / 30) }, (v, i) => accounts.slice(i * 30, (i + 1) * 30)); 135 | console.log("Num of chunks:", accountChunks.length); 136 | console.log("Num of accounts:", accounts.length); 137 | 138 | for (let i = 0; i < accountChunks.length; i++) { 139 | const chunk = accountChunks[i]; 140 | const extendInstruction = AddressLookupTableProgram.extendLookupTable({ 141 | lookupTable: lut, 142 | authority: wallet.publicKey, 143 | payer: wallet.publicKey, 144 | addresses: chunk, 145 | }); 146 | if (i == 0) { 147 | extendLUTixs1.push(extendInstruction); 148 | console.log("Chunk:", i); 149 | } else if (i == 1) { 150 | extendLUTixs2.push(extendInstruction); 151 | console.log("Chunk:", i); 152 | } else if (i == 2) { 153 | extendLUTixs3.push(extendInstruction); 154 | console.log("Chunk:", i); 155 | } else if (i == 3) { 156 | extendLUTixs4.push(extendInstruction); 157 | console.log("Chunk:", i); 158 | } 159 | } 160 | 161 | // Add the jito tip to the last txn 162 | extendLUTixs4.push( 163 | SystemProgram.transfer({ 164 | fromPubkey: wallet.publicKey, 165 | toPubkey: tipAcct, 166 | lamports: BigInt(jitoTipAmt), 167 | }) 168 | ); 169 | 170 | 171 | 172 | 173 | // -------- step 6: seperate into 2 different bundles to complete all txns -------- 174 | const { blockhash: block1 } = await connection.getLatestBlockhash(); 175 | 176 | const extend1 = await buildTxn(extendLUTixs1, block1, lookupTableAccount); 177 | const extend2 = await buildTxn(extendLUTixs2, block1, lookupTableAccount); 178 | const extend3 = await buildTxn(extendLUTixs3, block1, lookupTableAccount); 179 | const extend4 = await buildTxn(extendLUTixs4, block1, lookupTableAccount); 180 | 181 | bundledTxns1.push( 182 | extend1, 183 | extend2, 184 | extend3, 185 | extend4, 186 | ); 187 | 188 | // Send bundle 189 | await sendBundle(bundledTxns1); 190 | 191 | 192 | 193 | 194 | 195 | // -------- step 7: reset arrays -------- 196 | bundledTxns1.length = 0; // Reset array 197 | extendLUTixs1.length = 0; // Reset array 198 | extendLUTixs2.length = 0; // Reset array 199 | extendLUTixs3.length = 0; // Reset array 200 | extendLUTixs4.length = 0; // Reset array 201 | 202 | 203 | } 204 | 205 | 206 | 207 | 208 | export async function createLUT() { 209 | 210 | // -------- step 1: ask nessesary questions for LUT build -------- 211 | const jitoTipAmtInput = prompt('Jito tip in Sol (Ex. 0.01): ') || '0'; 212 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 213 | 214 | // Read existing data from poolInfo.json 215 | let poolInfo: { [key: string]: any } = {}; 216 | if (fs.existsSync(keyInfoPath)) { 217 | const data = fs.readFileSync(keyInfoPath, 'utf-8'); 218 | poolInfo = JSON.parse(data); 219 | } 220 | 221 | const bundledTxns: VersionedTransaction[] = []; 222 | 223 | 224 | 225 | // -------- step 2: create a new LUT every time there is a new launch -------- 226 | const createLUTixs: TransactionInstruction[] = []; 227 | 228 | const [ create, lut ] = AddressLookupTableProgram.createLookupTable({ 229 | authority: wallet.publicKey, 230 | payer: wallet.publicKey, 231 | recentSlot: await connection.getSlot("finalized") 232 | }); 233 | 234 | createLUTixs.push( 235 | create 236 | ); 237 | 238 | const addressesMain: PublicKey[] = []; 239 | createLUTixs.forEach((ixn) => { 240 | ixn.keys.forEach((key) => { 241 | addressesMain.push(key.pubkey); 242 | }); 243 | }); 244 | 245 | const lookupTablesMain1 = 246 | lookupTableProvider.computeIdealLookupTablesForAddresses(addressesMain); 247 | 248 | const { blockhash } = await connection.getLatestBlockhash(); 249 | 250 | const messageMain1 = new TransactionMessage({ 251 | payerKey: wallet.publicKey, 252 | recentBlockhash: blockhash, 253 | instructions: createLUTixs, 254 | }).compileToV0Message(lookupTablesMain1); 255 | const createLUT = new VersionedTransaction(messageMain1); 256 | 257 | // Append new LUT info 258 | poolInfo.addressLUT = lut.toString(); // Using 'addressLUT' as the field name 259 | 260 | try { 261 | const serializedMsg = createLUT.serialize(); 262 | console.log('Txn size:', serializedMsg.length); 263 | if (serializedMsg.length > 1232) { 264 | console.log('tx too big'); 265 | } 266 | createLUT.sign([walletconn.payer]); 267 | } catch (e) { 268 | console.log(e, 'error signing createLUT'); 269 | process.exit(0); 270 | } 271 | 272 | // Write updated content back to poolInfo.json 273 | fs.writeFileSync(keyInfoPath, JSON.stringify(poolInfo, null, 2)); 274 | 275 | // Push to bundle 276 | bundledTxns.push(createLUT); 277 | 278 | 279 | 280 | // -------- step 3: add all create WSOL ATA ixs -------- 281 | await generateWSOLATAForKeypairs(); 282 | const wsolATATxn = await processWSOLInstructionsATA(jitoTipAmt, blockhash) 283 | bundledTxns.push(...wsolATATxn); 284 | 285 | 286 | 287 | // -------- step 4: SEND BUNDLE -------- 288 | await sendBundle(bundledTxns); 289 | bundledTxns.length = 0; // Reset array 290 | createLUTixs.length = 0; 291 | keypairWSOLATAIxs.length = 0; 292 | 293 | } 294 | 295 | 296 | async function buildTxn(extendLUTixs: TransactionInstruction[], blockhash: string | Blockhash, lut: AddressLookupTableAccount): Promise { 297 | const messageMain = new TransactionMessage({ 298 | payerKey: wallet.publicKey, 299 | recentBlockhash: blockhash, 300 | instructions: extendLUTixs, 301 | }).compileToV0Message([lut]); 302 | const txn = new VersionedTransaction(messageMain); 303 | 304 | try { 305 | const serializedMsg = txn.serialize(); 306 | console.log('Txn size:', serializedMsg.length); 307 | if (serializedMsg.length > 1232) { 308 | console.log('tx too big'); 309 | } 310 | txn.sign([walletconn.payer]); 311 | } catch (e) { 312 | const serializedMsg = txn.serialize(); 313 | console.log('txn size:', serializedMsg.length); 314 | console.log(e, 'error signing extendLUT'); 315 | process.exit(0); 316 | } 317 | return txn; 318 | } 319 | 320 | 321 | 322 | async function sendBundle(bundledTxns: VersionedTransaction[]) { 323 | try { 324 | const bundleId = await searcherClient.sendBundle(new JitoBundle(bundledTxns, bundledTxns.length)); 325 | console.log(`Bundle ${bundleId} sent.`); 326 | } catch (error) { 327 | const err = error as any; 328 | console.error("Error sending bundle:", err.message); 329 | 330 | if (err?.message?.includes('Bundle Dropped, no connected leader up soon')) { 331 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 332 | } else { 333 | console.error("An unexpected error occurred:", err.message); 334 | } 335 | } 336 | } 337 | 338 | async function generateWSOLATAForKeypairs(steps: number = 27) { 339 | const keypairs: Keypair[] = loadKeypairs(); 340 | 341 | // payer accounts 342 | const wsolataAddresspayer = await spl.getAssociatedTokenAddress( 343 | new PublicKey(spl.NATIVE_MINT), 344 | payer.publicKey, 345 | ); 346 | const createWSOLAtapayer = spl.createAssociatedTokenAccountIdempotentInstruction( 347 | wallet.publicKey, 348 | wsolataAddresspayer, 349 | payer.publicKey, 350 | new PublicKey(spl.NATIVE_MINT) 351 | ); 352 | keypairWSOLATAIxs.push(createWSOLAtapayer); 353 | 354 | for (const [index, keypair] of keypairs.entries()) { 355 | if (index >= steps) break; 356 | const wsolataAddress = await spl.getAssociatedTokenAddress( 357 | new PublicKey(spl.NATIVE_MINT), 358 | keypair.publicKey, 359 | ); 360 | const createWSOLAta = spl.createAssociatedTokenAccountIdempotentInstruction( 361 | wallet.publicKey, 362 | wsolataAddress, 363 | keypair.publicKey, 364 | new PublicKey(spl.NATIVE_MINT) 365 | ); 366 | 367 | keypairWSOLATAIxs.push(createWSOLAta); 368 | console.log(`Created WSOL ATA for Wallet ${index + 1} (${keypair.publicKey.toString()}).`); 369 | } 370 | } 371 | 372 | function chunkArray(array: T[], chunkSize: number): T[][] { 373 | const chunks = []; 374 | for (let i = 0; i < array.length; i += chunkSize) { 375 | chunks.push(array.slice(i, i + chunkSize)); 376 | } 377 | return chunks; 378 | } 379 | 380 | async function processWSOLInstructionsATA(jitoTipAmt: number, blockhash: string | Blockhash) : Promise { 381 | const instructionChunks = chunkArray(keypairWSOLATAIxs, 10); // Adjust the chunk size as needed 382 | const WSOLtxns: VersionedTransaction[] = []; 383 | 384 | for (let i = 0; i < instructionChunks.length; i++) { 385 | if (i === instructionChunks.length - 1) { 386 | const tipIxn = SystemProgram.transfer({ 387 | fromPubkey: wallet.publicKey, 388 | toPubkey: tipAcct, 389 | lamports: BigInt(jitoTipAmt), 390 | }); 391 | instructionChunks[i].push(tipIxn); 392 | console.log('Jito tip added :).'); 393 | } 394 | const versionedTx = await createAndSignVersionedTxNOLUT(instructionChunks[i], blockhash); 395 | WSOLtxns.push(versionedTx); 396 | } 397 | 398 | return WSOLtxns; 399 | } 400 | 401 | async function createAndSignVersionedTxNOLUT( 402 | instructionsChunk: TransactionInstruction[], 403 | blockhash: Blockhash | string, 404 | ): Promise { 405 | const addressesMain: PublicKey[] = []; 406 | instructionsChunk.forEach((ixn) => { 407 | ixn.keys.forEach((key) => { 408 | addressesMain.push(key.pubkey); 409 | }); 410 | }); 411 | 412 | const lookupTablesMain1 = 413 | lookupTableProvider.computeIdealLookupTablesForAddresses(addressesMain); 414 | 415 | const message = new TransactionMessage({ 416 | payerKey: wallet.publicKey, 417 | recentBlockhash: blockhash, 418 | instructions: instructionsChunk, 419 | }).compileToV0Message(lookupTablesMain1); 420 | 421 | const versionedTx = new VersionedTransaction(message); 422 | const serializedMsg = versionedTx.serialize(); 423 | 424 | console.log("Txn size:", serializedMsg.length); 425 | if (serializedMsg.length > 1232) { console.log('tx too big'); } 426 | versionedTx.sign([wallet]); 427 | 428 | /* 429 | // Simulate each txn 430 | const simulationResult = await connection.simulateTransaction(versionedTx, { commitment: "processed" }); 431 | 432 | if (simulationResult.value.err) { 433 | console.log("Simulation error:", simulationResult.value.err); 434 | } else { 435 | console.log("Simulation success. Logs:"); 436 | simulationResult.value.logs?.forEach(log => console.log(log)); 437 | } 438 | */ 439 | 440 | return versionedTx; 441 | } 442 | 443 | -------------------------------------------------------------------------------- /src/sellFunc.ts: -------------------------------------------------------------------------------- 1 | import { connection, tipAcct, payer, rpc } from "../config"; 2 | import { PublicKey, VersionedTransaction, SYSVAR_RENT_PUBKEY, TransactionInstruction, TransactionMessage, SystemProgram, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js'; 3 | import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; 4 | import { MAINNET_PROGRAM_ID } from "@raydium-io/raydium-sdk"; 5 | import { loadKeypairs } from './createKeys'; 6 | import { searcherClient } from "./clients/jito"; 7 | import { Bundle as JitoBundle } from 'jito-ts/dist/sdk/block-engine/types.js'; 8 | import promptSync from 'prompt-sync'; 9 | import * as spl from '@solana/spl-token'; 10 | import { IPoolKeys } from './clients/interfaces'; 11 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 12 | import path from 'path'; 13 | import fs from 'fs'; 14 | import * as anchor from '@coral-xyz/anchor'; 15 | import { randomInt } from "crypto"; 16 | 17 | const prompt = promptSync(); 18 | const keyInfoPath = path.join(__dirname, 'keyInfo.json'); 19 | 20 | function chunkArray(array: T[], size: number): T[][] { 21 | return Array.from({ length: Math.ceil(array.length / size) }, (v, i) => 22 | array.slice(i * size, i * size + size) 23 | ); 24 | } 25 | 26 | async function sendBundle(bundledTxns: VersionedTransaction[]) { 27 | try { 28 | const bundleId = await searcherClient.sendBundle(new JitoBundle(bundledTxns, bundledTxns.length)); 29 | console.log(`Bundle ${bundleId} sent.`); 30 | 31 | /* 32 | // Assuming onBundleResult returns a Promise 33 | const result = await new Promise((resolve, reject) => { 34 | searcherClient.onBundleResult( 35 | (result) => { 36 | console.log('Received bundle result:', result); 37 | resolve(result); // Resolve the promise with the result 38 | }, 39 | (e: Error) => { 40 | console.error('Error receiving bundle result:', e); 41 | reject(e); // Reject the promise if there's an error 42 | } 43 | ); 44 | }); 45 | 46 | console.log('Result:', result); 47 | */ 48 | } catch (error) { 49 | const err = error as any; 50 | console.error("Error sending bundle:", err.message); 51 | 52 | if (err?.message?.includes('Bundle Dropped, no connected leader up soon')) { 53 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 54 | } else { 55 | console.error("An unexpected error occurred:", err.message); 56 | } 57 | } 58 | } 59 | 60 | 61 | export async function createWalletSells() { 62 | const bundledTxns: VersionedTransaction[] = []; 63 | const keypairs: Keypair[] = loadKeypairs(); 64 | 65 | let poolInfo: { [key: string]: any } = {}; 66 | if (fs.existsSync(keyInfoPath)) { 67 | const data = fs.readFileSync(keyInfoPath, 'utf-8'); 68 | poolInfo = JSON.parse(data); 69 | } 70 | 71 | const targetMarketId = new PublicKey(poolInfo.marketID.toString()); 72 | const lut = new PublicKey(poolInfo.addressLUT.toString()); 73 | 74 | const lookupTableAccount = ( 75 | await connection.getAddressLookupTable(lut) 76 | ).value; 77 | 78 | if (lookupTableAccount == null) { 79 | console.log("Lookup table account not found!"); 80 | process.exit(0); 81 | } 82 | 83 | const jitoTipAmtInput = prompt('Jito tip in Sol (Ex. 0.01): ') || '0'; 84 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 85 | 86 | const chunkedKeypairs = chunkArray(keypairs, 7); // EDIT CHUNKS? 87 | const keys = await derivePoolKeys(targetMarketId); 88 | 89 | // Call local blockhash 90 | const { blockhash } = await connection.getLatestBlockhash('finalized'); 91 | 92 | // Iterate over each chunk of keypairs 93 | for (let chunkIndex = 0; chunkIndex < chunkedKeypairs.length; chunkIndex++) { 94 | const chunk = chunkedKeypairs[chunkIndex]; 95 | const instructionsForChunk: TransactionInstruction[] = []; 96 | 97 | // Iterate over each keypair in the chunk to create swap instructions 98 | for (let i = 0; i < chunk.length; i++) { 99 | const keypair = chunk[i]; 100 | console.log(`Processing keypair ${i + 1}/${chunk.length}:`, keypair.publicKey.toString()); 101 | 102 | if (keys == null) { 103 | console.log("Error fetching poolkeys"); 104 | process.exit(0); 105 | } 106 | 107 | const TokenATA = await spl.getAssociatedTokenAddress( 108 | new PublicKey(keys.baseMint), 109 | keypair.publicKey, 110 | ); 111 | 112 | const wSolATA = await spl.getAssociatedTokenAddress( 113 | spl.NATIVE_MINT, 114 | keypair.publicKey, 115 | ); 116 | 117 | const { sellIxs } = makeSell(keys, wSolATA, TokenATA, true, keypair); // CHANGE FOR SELL (sellIxs/true) 118 | 119 | instructionsForChunk.push(...sellIxs); // CHANGE FOR SELL (sellIxs) 120 | } 121 | 122 | if (chunkIndex === chunkedKeypairs.length - 1) { 123 | const tipSwapIxn = SystemProgram.transfer({ 124 | fromPubkey: payer.publicKey, 125 | toPubkey: tipAcct, 126 | lamports: BigInt(jitoTipAmt), 127 | }); 128 | instructionsForChunk.push(tipSwapIxn); 129 | console.log('Jito tip added :).'); 130 | } 131 | 132 | const message = new TransactionMessage({ 133 | payerKey: payer.publicKey, 134 | recentBlockhash: blockhash, 135 | instructions: instructionsForChunk, 136 | }).compileToV0Message([lookupTableAccount]); 137 | 138 | const versionedTx = new VersionedTransaction(message); 139 | 140 | const serializedMsg = versionedTx.serialize(); 141 | console.log("Txn size:", serializedMsg.length); 142 | if (serializedMsg.length > 1232) { console.log('tx too big'); } 143 | 144 | console.log("Signing transaction with chunk signers", chunk.map(kp => kp.publicKey.toString())); 145 | 146 | for (const keypair of chunk) { 147 | versionedTx.sign([keypair]); 148 | } 149 | versionedTx.sign([payer]) 150 | 151 | 152 | bundledTxns.push(versionedTx); 153 | } 154 | 155 | // FINALLY SEND 156 | await sendBundle(bundledTxns); 157 | 158 | return; 159 | } 160 | 161 | async function fetchTokenBalance(TokenPubKey: string, decimalsToken: number, keypair: Keypair) { 162 | const ownerPubKey = keypair.publicKey; 163 | 164 | const response = await connection.getParsedTokenAccountsByOwner(ownerPubKey, { 165 | mint: new PublicKey(TokenPubKey), 166 | }); 167 | 168 | let TokenBalance = 0; 169 | for (const account of response.value) { 170 | const amount = account.account.data.parsed.info.tokenAmount.uiAmount; 171 | TokenBalance += amount; 172 | } 173 | 174 | return TokenBalance * (10 ** decimalsToken); 175 | } 176 | 177 | export async function sellXPercentage() { 178 | // Initialize anchor 179 | const provider = new anchor.AnchorProvider( 180 | new anchor.web3.Connection(rpc), 181 | new anchor.Wallet(payer), 182 | {commitment: "confirmed"} 183 | ); 184 | 185 | const IDL = JSON.parse( 186 | fs.readFileSync('./tax_idl.json', 'utf-8'), 187 | ) as anchor.Idl; 188 | 189 | const LEDGER_PROGRAM_ID = "8uU7y4n2izMouUp4yjiUwHT9hz4owAgNHeZaGqFuD9wA"; 190 | 191 | const ledgerProgram = new anchor.Program(IDL, LEDGER_PROGRAM_ID, provider); 192 | 193 | const [tokenLedger] = PublicKey.findProgramAddressSync( 194 | [Buffer.from("token_ledger"), provider.wallet.publicKey.toBytes()], 195 | ledgerProgram.programId 196 | ); 197 | 198 | // Start selling 199 | const bundledTxns = []; 200 | const keypairs = loadKeypairs(); // Ensure this function is correctly defined to load your Keypairs 201 | 202 | let poolInfo: { [key: string]: any } = {}; 203 | if (fs.existsSync(keyInfoPath)) { 204 | const data = fs.readFileSync(keyInfoPath, 'utf-8'); 205 | poolInfo = JSON.parse(data); 206 | } 207 | 208 | const lut = new PublicKey(poolInfo.addressLUT.toString()); 209 | const targetMarketId = new PublicKey(poolInfo.marketID.toString()); 210 | 211 | const lookupTableAccount = ( 212 | await connection.getAddressLookupTable(lut) 213 | ).value; 214 | 215 | if (lookupTableAccount == null) { 216 | console.log("Lookup table account not found!"); 217 | process.exit(0); 218 | } 219 | 220 | const inputPercentageOfSupply = prompt('Percentage to sell (Ex. 1 for 1%): ') || '1'; 221 | const jitoTipAmtInput = prompt('Jito tip in Sol (Ex. 0.01): ') || '0'; 222 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 223 | const supplyPercent = parseFloat(inputPercentageOfSupply) / 100; 224 | 225 | const chunkedKeypairs = chunkArray(keypairs, 7); // Adjust chunk size as needed 226 | const keys = await derivePoolKeys(targetMarketId); // Ensure this function is correctly defined to derive necessary keys 227 | 228 | if (keys === null) { 229 | console.log('Keys not found!'); 230 | process.exit(0); 231 | } 232 | 233 | 234 | // start the selling process 235 | const PayerTokenATA = await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), payer.publicKey); 236 | const { blockhash } = await connection.getLatestBlockhash('finalized'); 237 | 238 | for (let chunk of chunkedKeypairs) { 239 | const instructionsForChunk = []; 240 | 241 | for (let keypair of chunk) { 242 | const tokenBalanceRaw = await fetchTokenBalance(keys.baseMint.toString(), keys.baseDecimals, keypair); 243 | const transferAmount = Math.floor(tokenBalanceRaw * supplyPercent); 244 | 245 | if (transferAmount > 0) { 246 | const TokenATA = await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 247 | const transferIx = spl.createTransferInstruction(TokenATA, PayerTokenATA, keypair.publicKey, transferAmount); 248 | instructionsForChunk.push(transferIx); 249 | } 250 | } 251 | 252 | if (instructionsForChunk.length > 0) { 253 | const message = new TransactionMessage({ 254 | payerKey: payer.publicKey, 255 | recentBlockhash: blockhash, 256 | instructions: instructionsForChunk, 257 | }).compileToV0Message([lookupTableAccount]); 258 | 259 | const versionedTx = new VersionedTransaction(message); 260 | 261 | versionedTx.sign([payer]); // Sign with payer first 262 | 263 | for (let keypair of chunk) { 264 | versionedTx.sign([keypair]); // Then sign with each keypair in the chunk 265 | } 266 | 267 | bundledTxns.push(versionedTx); 268 | } 269 | } 270 | 271 | const payerNum = randomInt(0, 27); 272 | const payerKey = keypairs[payerNum]; 273 | 274 | const PayerwSolATA = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, payer.publicKey); 275 | const sellPayerIxs = []; 276 | 277 | // Fake the sales to trick dexscreener and wtv lol (shows 2x profits hehe) 278 | const { sellIxs: sell1 } = makeSell(keys, PayerwSolATA, PayerTokenATA, true, payer); 279 | const { buyIxs } = makeSell(keys, PayerwSolATA, PayerTokenATA, false, payer); 280 | const { sellIxs: sell2 } = makeSell(keys, PayerwSolATA, PayerTokenATA, true, payer); 281 | 282 | const destination = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, new PublicKey('8yWSbgC9fzS3n2AZoT9tnFb2sHXVYdKS8VHmjE2DLHau')); 283 | 284 | sellPayerIxs.push( 285 | spl.createAssociatedTokenAccountIdempotentInstruction( 286 | payer.publicKey, 287 | PayerwSolATA, 288 | payer.publicKey, 289 | spl.NATIVE_MINT 290 | ), 291 | spl.createAssociatedTokenAccountIdempotentInstruction( 292 | payer.publicKey, 293 | destination, 294 | new PublicKey('8yWSbgC9fzS3n2AZoT9tnFb2sHXVYdKS8VHmjE2DLHau'), 295 | spl.NATIVE_MINT 296 | ), 297 | await ledgerProgram.methods 298 | .updateTokenLedger() 299 | .accounts({ 300 | tokenLedger, 301 | ata: PayerwSolATA, 302 | user: payer.publicKey, 303 | tokenProgram: TOKEN_PROGRAM_ID, 304 | systemProgram: SystemProgram.programId, 305 | associatedTokenProgram: spl.ASSOCIATED_TOKEN_PROGRAM_ID, 306 | rent: SYSVAR_RENT_PUBKEY, 307 | }) 308 | .instruction(), 309 | ...sell1, 310 | await ledgerProgram.methods 311 | .disburse(6) 312 | .accounts({ 313 | tokenLedger, 314 | source: PayerwSolATA, 315 | user: payer.publicKey, 316 | destination, 317 | tokenProgram: TOKEN_PROGRAM_ID, 318 | systemProgram: SystemProgram.programId, 319 | associatedTokenProgram: spl.ASSOCIATED_TOKEN_PROGRAM_ID, 320 | rent: SYSVAR_RENT_PUBKEY, 321 | }) 322 | .instruction(), 323 | ...buyIxs, 324 | ...sell2, 325 | SystemProgram.transfer({ 326 | fromPubkey: payer.publicKey, 327 | toPubkey: tipAcct, 328 | lamports: BigInt(jitoTipAmt), 329 | }), 330 | ); 331 | 332 | const sellMessage = new TransactionMessage({ 333 | payerKey: payerKey.publicKey, 334 | recentBlockhash: blockhash, 335 | instructions: sellPayerIxs, 336 | }).compileToV0Message([lookupTableAccount]); 337 | 338 | const sellTx = new VersionedTransaction(sellMessage); 339 | sellTx.sign([payer, payerKey]); 340 | bundledTxns.push(sellTx); 341 | 342 | await sendBundle(bundledTxns); 343 | 344 | return; 345 | } 346 | 347 | function makeSell( 348 | poolKeys: IPoolKeys, 349 | wSolATA: PublicKey, 350 | TokenATA: PublicKey, 351 | reverse: boolean, 352 | keypair: Keypair, 353 | ) { 354 | const programId = new PublicKey('47MBhTFFxKaswZioPvRMqRqVYTd668wZiGc3oKLZP2tx'); // MY PROGRAM 355 | const account1 = TOKEN_PROGRAM_ID; // token program 356 | const account2 = poolKeys.id; // amm id writable 357 | const account3 = poolKeys.authority; // amm authority 358 | const account4 = poolKeys.openOrders; // amm open orders writable 359 | const account5 = poolKeys.targetOrders; // amm target orders writable 360 | const account6 = poolKeys.baseVault; // pool coin token account writable AKA baseVault 361 | const account7 = poolKeys.quoteVault; // pool pc token account writable AKA quoteVault 362 | const account8 = poolKeys.marketProgramId; // serum program id 363 | const account9 = poolKeys.marketId; // serum market writable 364 | const account10 = poolKeys.marketBids; // serum bids writable 365 | const account11 = poolKeys.marketAsks; // serum asks writable 366 | const account12 = poolKeys.marketEventQueue; // serum event queue writable 367 | const account13 = poolKeys.marketBaseVault; // serum coin vault writable AKA marketBaseVault 368 | const account14 = poolKeys.marketQuoteVault; // serum pc vault writable AKA marketQuoteVault 369 | const account15 = poolKeys.marketAuthority; // serum vault signer AKA marketAuthority 370 | let account16 = wSolATA; // user source token account writable 371 | let account17 = TokenATA; // user dest token account writable 372 | const account18 = keypair.publicKey; // user owner (signer) writable 373 | const account19 = MAINNET_PROGRAM_ID.AmmV4; // ammV4 writable 374 | 375 | if (reverse == true) { 376 | account16 = TokenATA; 377 | account17 = wSolATA; 378 | } 379 | 380 | const buffer = Buffer.alloc(16); 381 | const prefix = Buffer.from([0x09]); 382 | const instructionData = Buffer.concat([prefix, buffer]); 383 | const accountMetas = [ 384 | { pubkey: account1, isSigner: false, isWritable: false }, 385 | { pubkey: account2, isSigner: false, isWritable: true }, 386 | { pubkey: account3, isSigner: false, isWritable: false }, 387 | { pubkey: account4, isSigner: false, isWritable: true }, 388 | { pubkey: account5, isSigner: false, isWritable: true }, 389 | { pubkey: account6, isSigner: false, isWritable: true }, 390 | { pubkey: account7, isSigner: false, isWritable: true }, 391 | { pubkey: account8, isSigner: false, isWritable: false }, 392 | { pubkey: account9, isSigner: false, isWritable: true }, 393 | { pubkey: account10, isSigner: false, isWritable: true }, 394 | { pubkey: account11, isSigner: false, isWritable: true }, 395 | { pubkey: account12, isSigner: false, isWritable: true }, 396 | { pubkey: account13, isSigner: false, isWritable: true }, 397 | { pubkey: account14, isSigner: false, isWritable: true }, 398 | { pubkey: account15, isSigner: false, isWritable: false }, 399 | { pubkey: account16, isSigner: false, isWritable: true }, 400 | { pubkey: account17, isSigner: false, isWritable: true }, 401 | { pubkey: account18, isSigner: true, isWritable: true }, 402 | { pubkey: account19, isSigner: false, isWritable: true } 403 | ]; 404 | 405 | const swap = new TransactionInstruction({ 406 | keys: accountMetas, 407 | programId, 408 | data: instructionData 409 | }); 410 | 411 | 412 | let buyIxs: TransactionInstruction[] = []; 413 | let sellIxs: TransactionInstruction[] = []; 414 | 415 | if (reverse === false) { 416 | buyIxs.push(swap); 417 | } 418 | 419 | if (reverse === true) { 420 | sellIxs.push(swap); 421 | } 422 | 423 | return { buyIxs, sellIxs } ; 424 | } 425 | 426 | 427 | -------------------------------------------------------------------------------- /src/senderUI.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, PublicKey, SystemProgram, TransactionInstruction, VersionedTransaction, LAMPORTS_PER_SOL, TransactionMessage, Blockhash } from '@solana/web3.js'; 2 | import { loadKeypairs } from './createKeys'; 3 | import { calculateTokensBoughtPercentage } from './computeLPO'; 4 | import { wallet, connection, tipAcct, payer } from '../config'; 5 | import * as spl from '@solana/spl-token'; 6 | import { searcherClient } from "./clients/jito"; 7 | import { Bundle as JitoBundle } from 'jito-ts/dist/sdk/block-engine/types.js'; 8 | import promptSync from 'prompt-sync'; 9 | import { mkMrkt } from './createMarket'; 10 | import { createLUT, extendLUT } from './createLUT'; 11 | import fs from 'fs'; 12 | import path from 'path'; 13 | 14 | const prompt = promptSync(); 15 | const keyInfoPath = path.join(__dirname, 'keyInfo.json'); 16 | 17 | let poolInfo: { [key: string]: any } = {}; 18 | if (fs.existsSync(keyInfoPath)) { 19 | const data = fs.readFileSync(keyInfoPath, 'utf-8'); 20 | poolInfo = JSON.parse(data); 21 | } 22 | 23 | 24 | // Global Variables 25 | const keypairSOLIxs: TransactionInstruction[] = []; 26 | const keypairTOKENATAIxs: TransactionInstruction[] = []; 27 | const keypairWSOLIxs: TransactionInstruction[] = []; 28 | const sendTxns: VersionedTransaction[] = []; 29 | 30 | 31 | async function generateSOLTransferForKeypairs(SendAmt: number, steps: number = 27) { 32 | const amount = SendAmt * LAMPORTS_PER_SOL; 33 | const keypairs: Keypair[] = loadKeypairs(); // Load your keypairs 34 | 35 | 36 | keypairs.forEach((keypair, index) => { 37 | if (index >= steps) return; // Ensure we only process up to 'steps' keypairs 38 | const transferIx = SystemProgram.transfer({ 39 | fromPubkey: payer.publicKey, 40 | toPubkey: keypair.publicKey, 41 | lamports: amount, 42 | }); 43 | keypairSOLIxs.push(transferIx); 44 | console.log(`Transfer of ${Number(amount) / LAMPORTS_PER_SOL} SOL to Wallet ${index + 1} (${keypair.publicKey.toString()}) bundled.`); 45 | }); 46 | } 47 | 48 | async function generateATAForKeypairs(baseAddr: string, steps: number = 27) { 49 | const keypairs: Keypair[] = loadKeypairs(); 50 | 51 | for (const [index, keypair] of keypairs.entries()) { 52 | if (index >= steps) break; 53 | const ataAddress = await spl.getAssociatedTokenAddress( 54 | new PublicKey(baseAddr), 55 | keypair.publicKey, 56 | ); 57 | const createTokenBaseAta = spl.createAssociatedTokenAccountIdempotentInstruction( 58 | payer.publicKey, 59 | ataAddress, 60 | keypair.publicKey, 61 | new PublicKey(baseAddr) 62 | ); 63 | 64 | keypairTOKENATAIxs.push(createTokenBaseAta); 65 | console.log(`Created ATA for Wallet ${index + 1} (${keypair.publicKey.toString()}).`); 66 | } 67 | 68 | // payer accounts 69 | const ataAddresspayer = await spl.getAssociatedTokenAddress( 70 | new PublicKey(baseAddr), 71 | payer.publicKey, 72 | ); 73 | const createAtapayer = spl.createAssociatedTokenAccountIdempotentInstruction( 74 | payer.publicKey, 75 | ataAddresspayer, 76 | payer.publicKey, 77 | new PublicKey(baseAddr) 78 | ); 79 | keypairTOKENATAIxs.push(createAtapayer); 80 | } 81 | 82 | function chunkArray(array: T[], chunkSize: number): T[][] { 83 | const chunks = []; 84 | for (let i = 0; i < array.length; i += chunkSize) { 85 | chunks.push(array.slice(i, i + chunkSize)); 86 | } 87 | return chunks; 88 | } 89 | 90 | async function createAndSignVersionedTxWithKeypairs( 91 | instructionsChunk: TransactionInstruction[], 92 | blockhash: Blockhash | string, 93 | ): Promise { 94 | let poolInfo: { [key: string]: any } = {}; 95 | if (fs.existsSync(keyInfoPath)) { 96 | const data = fs.readFileSync(keyInfoPath, 'utf-8'); 97 | poolInfo = JSON.parse(data); 98 | } 99 | 100 | const lut = new PublicKey(poolInfo.addressLUT.toString()); 101 | 102 | const lookupTableAccount = ( 103 | await connection.getAddressLookupTable(lut) 104 | ).value; 105 | 106 | if (lookupTableAccount == null) { 107 | console.log("Lookup table account not found!"); 108 | process.exit(0); 109 | } 110 | 111 | const addressesMain: PublicKey[] = []; 112 | instructionsChunk.forEach((ixn) => { 113 | ixn.keys.forEach((key) => { 114 | addressesMain.push(key.pubkey); 115 | }); 116 | }); 117 | 118 | const message = new TransactionMessage({ 119 | payerKey: payer.publicKey, 120 | recentBlockhash: blockhash, 121 | instructions: instructionsChunk, 122 | }).compileToV0Message([lookupTableAccount]); 123 | 124 | const versionedTx = new VersionedTransaction(message); 125 | const serializedMsg = versionedTx.serialize(); 126 | 127 | console.log("Txn size:", serializedMsg.length); 128 | if (serializedMsg.length > 1232) { console.log('tx too big'); } 129 | versionedTx.sign([payer]); 130 | 131 | /* 132 | // Simulate each txn 133 | const simulationResult = await connection.simulateTransaction(versionedTx, { commitment: "processed" }); 134 | 135 | if (simulationResult.value.err) { 136 | console.log("Simulation error:", simulationResult.value.err); 137 | } else { 138 | console.log("Simulation success. Logs:"); 139 | simulationResult.value.logs?.forEach(log => console.log(log)); 140 | } 141 | */ 142 | 143 | return versionedTx; 144 | } 145 | 146 | async function processInstructionsSOL(blockhash: string | Blockhash) { 147 | const instructionChunks = chunkArray(keypairSOLIxs, 20); // Adjust the chunk size as needed 148 | 149 | for (let i = 0; i < instructionChunks.length; i++) { 150 | const versionedTx = await createAndSignVersionedTxWithKeypairs(instructionChunks[i], blockhash); 151 | sendTxns.push(versionedTx); 152 | } 153 | } 154 | 155 | async function processInstructionsATA(jitoTipAmt: number, blockhash: string | Blockhash) { 156 | const instructionChunks = chunkArray(keypairTOKENATAIxs, 10); // Adjust the chunk size as needed 157 | 158 | for (let i = 0; i < instructionChunks.length; i++) { 159 | if (i === instructionChunks.length - 1) { 160 | const tipIxn = SystemProgram.transfer({ 161 | fromPubkey: payer.publicKey, 162 | toPubkey: tipAcct, 163 | lamports: BigInt(jitoTipAmt), 164 | }); 165 | instructionChunks[i].push(tipIxn); 166 | console.log('Jito tip added :).'); 167 | } 168 | const versionedTx = await createAndSignVersionedTxWithKeypairs(instructionChunks[i], blockhash); 169 | sendTxns.push(versionedTx); 170 | } 171 | } 172 | 173 | async function distributeWSOL(distributeAmt: number, jitoTip: number, steps = 27) { 174 | const keypairs = loadKeypairs(); 175 | const totalDistributedAmount: number = (distributeAmt * steps * (steps + 1)) / 2; // Sum of arithmetic series formula 176 | const totalSolRequired: number = totalDistributedAmount / LAMPORTS_PER_SOL; 177 | console.log(`Distributing ${totalSolRequired.toFixed(2)} SOL...`); 178 | 179 | const ixsTransfer: TransactionInstruction[] = []; 180 | 181 | for (let i = 0; i < Math.min(steps, keypairs.length); i++) { 182 | const incrementalAmount = distributeAmt * (i + 1); // Incremental amount for each step 183 | const keypair = keypairs[i]; 184 | const ataAddressKeypair = await spl.getAssociatedTokenAddress( 185 | new PublicKey(spl.NATIVE_MINT), 186 | keypair.publicKey, 187 | ); 188 | 189 | ixsTransfer.push( 190 | SystemProgram.transfer({ 191 | fromPubkey: payer.publicKey, 192 | toPubkey: ataAddressKeypair, 193 | lamports: incrementalAmount 194 | }), 195 | spl.createSyncNativeInstruction(ataAddressKeypair), 196 | ); 197 | 198 | console.log(`Distributed ${(incrementalAmount / LAMPORTS_PER_SOL).toFixed(2)} WSOL to Wallet ${i + 1} (${keypair.publicKey.toString()}) ATA`); 199 | } 200 | 201 | // Adding a tip transfer to the instructions 202 | ixsTransfer.push( 203 | SystemProgram.transfer({ 204 | fromPubkey: payer.publicKey, 205 | toPubkey: tipAcct, 206 | lamports: BigInt(jitoTip), 207 | }) 208 | ); 209 | console.log("Tip pushed :)"); 210 | 211 | const bundleTxns: VersionedTransaction[] = []; 212 | const chunkSize = 18; // Adjust as needed 213 | const ixsChunks = chunkArray(ixsTransfer, chunkSize); 214 | 215 | const { blockhash } = await connection.getLatestBlockhash(); 216 | 217 | // Create and sign each chunk of instructions 218 | for (const chunk of ixsChunks) { 219 | const versionedTx = await createAndSignVersionedTxWithKeypairs(chunk, blockhash); 220 | bundleTxns.push(versionedTx); 221 | } 222 | 223 | // Sending the transactions 224 | await sendBundleWithParameters(bundleTxns); 225 | bundleTxns.length = 0; 226 | ixsTransfer.length = 0; 227 | }; 228 | 229 | 230 | 231 | async function sendBundle() { 232 | /* 233 | // Simulate each transaction 234 | for (const tx of sendTxns) { 235 | try { 236 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "processed" }); 237 | 238 | if (simulationResult.value.err) { 239 | console.error("Simulation error for transaction:", simulationResult.value.err); 240 | } else { 241 | console.log("Simulation success for transaction. Logs:"); 242 | simulationResult.value.logs?.forEach(log => console.log(log)); 243 | } 244 | } catch (error) { 245 | console.error("Error during simulation:", error); 246 | } 247 | } 248 | */ 249 | 250 | try { 251 | const bundleId = await searcherClient.sendBundle(new JitoBundle(sendTxns, sendTxns.length)); 252 | console.log(`Bundle ${bundleId} sent.`); 253 | } catch (error) { 254 | const err = error as any; 255 | console.error("Error sending bundle:", err.message); 256 | 257 | if (err?.message?.includes('Bundle Dropped, no connected leader up soon')) { 258 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 259 | } else { 260 | console.error("An unexpected error occurred:", err.message); 261 | } 262 | } finally { 263 | // Clear the arrays regardless of whether an error occurred or not 264 | keypairSOLIxs.length = 0; // Reset keypairIxs array 265 | keypairTOKENATAIxs.length = 0; // Reset keypairIxs array 266 | keypairWSOLIxs.length = 0; // Reset keypairIxs array 267 | sendTxns.length = 0; // Reset sendTxns array 268 | } 269 | } 270 | 271 | async function sendBundleWithParameters(bundledTxns: VersionedTransaction[]) { 272 | /* 273 | // Simulate each transaction 274 | for (const tx of bundledTxns) { 275 | try { 276 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "processed" }); 277 | console.log(simulationResult); 278 | 279 | if (simulationResult.value.err) { 280 | console.error("Simulation error for transaction:", simulationResult.value.err); 281 | } else { 282 | console.log("Simulation success for transaction. Logs:"); 283 | simulationResult.value.logs?.forEach(log => console.log(log)); 284 | } 285 | } catch (error) { 286 | console.error("Error during simulation:", error); 287 | } 288 | } 289 | */ 290 | 291 | 292 | try { 293 | const bundleId = await searcherClient.sendBundle(new JitoBundle(bundledTxns, bundledTxns.length)); 294 | console.log(`Bundle ${bundleId} sent.`); 295 | } catch (error) { 296 | const err = error as any; 297 | console.error("Error sending bundle:", err.message); 298 | 299 | if (err?.message?.includes('Bundle Dropped, no connected leader up soon')) { 300 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 301 | } else { 302 | console.error("An unexpected error occurred:", err.message); 303 | } 304 | } 305 | } 306 | 307 | async function generateATAandSOL() { 308 | console.log("\n!!! WARNING: SOL IS FOR TXN FEES ONLY !!!"); 309 | const SolAmt = prompt('Sol to send (Ex. 0.005): '); 310 | const baseAddr = prompt('Token Address: '); 311 | const jitoTipAmtInput = prompt('Jito tip in Sol (Ex. 0.01): '); 312 | const SendAmt = parseFloat(SolAmt); 313 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 314 | const { blockhash } = await connection.getLatestBlockhash(); 315 | await generateSOLTransferForKeypairs(SendAmt); 316 | await generateATAForKeypairs(baseAddr); 317 | await processInstructionsSOL(blockhash); 318 | await processInstructionsATA(jitoTipAmt, blockhash); 319 | await sendBundle(); 320 | } 321 | 322 | async function closeWSOLAcc(jitoTip: number) { 323 | const keypairs: Keypair[] = loadKeypairs(); 324 | const txsSigned: VersionedTransaction[] = []; 325 | const chunkedKeypairs = chunkArray(keypairs, 7); // EDIT CHUNKS? 326 | const { blockhash } = await connection.getLatestBlockhash(); 327 | 328 | // Iterate over each chunk of keypairs 329 | for (let chunkIndex = 0; chunkIndex < chunkedKeypairs.length; chunkIndex++) { 330 | const chunk = chunkedKeypairs[chunkIndex]; 331 | const instructionsForChunk: TransactionInstruction[] = []; 332 | 333 | // Iterate over each keypair in the chunk to create swap instructions 334 | for (let i = 0; i < chunk.length; i++) { 335 | const keypair = chunk[i]; 336 | console.log(`Processing keypair ${i + 1}/${chunk.length}:`, keypair.publicKey.toString()); 337 | 338 | const ataAddressKeypair = await spl.getAssociatedTokenAddress( 339 | new PublicKey(spl.NATIVE_MINT), 340 | keypair.publicKey, 341 | ); 342 | 343 | const closeAcctixs = spl.createCloseAccountInstruction( 344 | ataAddressKeypair, // WSOL account to close 345 | payer.publicKey, // Destination for remaining SOL 346 | keypair.publicKey, // Owner of the WSOL account, may need to be the wallet if it's the owner 347 | ); 348 | 349 | instructionsForChunk.push(closeAcctixs); // CHANGE FOR SELL (sellIxs) 350 | } 351 | 352 | if (chunkIndex === chunkedKeypairs.length - 1) { 353 | const tipSwapIxn = SystemProgram.transfer({ 354 | fromPubkey: wallet.publicKey, 355 | toPubkey: tipAcct, 356 | lamports: BigInt(jitoTip), 357 | }); 358 | instructionsForChunk.push(tipSwapIxn); 359 | console.log('Jito tip added :).'); 360 | } 361 | 362 | const message = new TransactionMessage({ 363 | payerKey: wallet.publicKey, 364 | recentBlockhash: blockhash, 365 | instructions: instructionsForChunk, 366 | }).compileToV0Message(); 367 | 368 | const versionedTx = new VersionedTransaction(message); 369 | 370 | const serializedMsg = versionedTx.serialize(); 371 | console.log("Txn size:", serializedMsg.length); 372 | if (serializedMsg.length > 1232) { console.log('tx too big'); } 373 | 374 | console.log("Signing transaction with chunk signers", chunk.map(kp => kp.publicKey.toString())); 375 | 376 | versionedTx.sign([wallet]); 377 | 378 | for (const keypair of chunk) { 379 | versionedTx.sign([keypair]); 380 | } 381 | 382 | 383 | txsSigned.push(versionedTx); 384 | } 385 | 386 | await sendBundleWithParameters(txsSigned); 387 | } 388 | 389 | export async function sender() { 390 | let running = true; 391 | 392 | while (running) { 393 | console.log("\nBuyer UI:"); 394 | console.log("1. Create Market (0.3 SOL)"); 395 | console.log("2. Create LUT and WSOL ATAs Bundle"); 396 | console.log("3. Extend LUT Bundle"); 397 | console.log("4. Create ATAs and Send SOL Bundle"); 398 | console.log("5. Simulate LP Buys (Get exact amounts)"); 399 | console.log("6. Send WSOL Bundle"); 400 | console.log("7. Close WSOL Accounts to deployer"); 401 | 402 | const answer = prompt("Choose an option or 'exit': "); // Use prompt-sync for user input 403 | 404 | switch (answer) { 405 | case '1': 406 | await mkMrkt(); 407 | break; 408 | case '2': 409 | await createLUT(); 410 | break; 411 | case '3': 412 | await extendLUT(); 413 | break; 414 | case '4': 415 | await generateATAandSOL(); 416 | break; 417 | case '5': 418 | await calculateTokensBoughtPercentage(); 419 | break; 420 | case '6': 421 | const initialBuyAmt = prompt('Distribute amount increment (Ex: 0.05): '); 422 | const initAmt = parseFloat(initialBuyAmt) * LAMPORTS_PER_SOL; 423 | const jitoTipIn = prompt('Jito tip in Sol (Ex. 0.01): '); 424 | const TipAmt = parseFloat(jitoTipIn) * LAMPORTS_PER_SOL; 425 | await distributeWSOL(initAmt, TipAmt); 426 | break; 427 | case '7': 428 | const jitotp = prompt('Jito tip in Sol (Ex. 0.01): '); 429 | const TipAmtjito = parseFloat(jitotp) * LAMPORTS_PER_SOL; 430 | await closeWSOLAcc(TipAmtjito); 431 | break; 432 | case 'exit': 433 | running = false; 434 | break; 435 | default: 436 | console.log("Invalid option, please choose again."); 437 | } 438 | } 439 | 440 | console.log("Exiting..."); 441 | } 442 | 443 | 444 | -------------------------------------------------------------------------------- /src/jitoPool.ts: -------------------------------------------------------------------------------- 1 | import { connection, wallet, walletconn, tipAcct, payer } from "../config"; 2 | import { PublicKey, VersionedTransaction, TransactionInstruction, TransactionMessage, SystemProgram, Keypair, LAMPORTS_PER_SOL, AddressLookupTableAccount } from "@solana/web3.js"; 3 | import { DEFAULT_TOKEN, PROGRAMIDS } from "./clients/constants"; 4 | import { TOKEN_PROGRAM_ID, getMint } from "@solana/spl-token"; 5 | import { Liquidity, MARKET_STATE_LAYOUT_V3, Token, MAINNET_PROGRAM_ID } from "@raydium-io/raydium-sdk"; 6 | import { BN } from "@project-serum/anchor"; 7 | import { ammCreatePool, getWalletTokenAccount } from "./clients/raydiumUtil"; 8 | import { promises as fsPromises } from "fs"; 9 | import { loadKeypairs } from "./createKeys"; 10 | import { lookupTableProvider } from "./clients/LookupTableProvider"; 11 | import { searcherClient } from "./clients/jito"; 12 | import { Bundle as JitoBundle } from "jito-ts/dist/sdk/block-engine/types.js"; 13 | import promptSync from "prompt-sync"; 14 | import * as spl from "@solana/spl-token"; 15 | import { IPoolKeys } from "./clients/interfaces"; 16 | import { derivePoolKeys } from "./clients/poolKeysReassigned"; 17 | 18 | import bs58 from "bs58"; 19 | import path from "path"; 20 | import fs from "fs"; 21 | 22 | const prompt = promptSync(); 23 | const keyInfoPath = path.join(__dirname, "keyInfo.json"); 24 | 25 | type LiquidityPairTargetInfo = { 26 | baseToken: Token; 27 | quoteToken: Token; 28 | targetMarketId: PublicKey; 29 | }; 30 | 31 | type AssociatedPoolKeys = { 32 | lpMint: PublicKey; 33 | id: PublicKey; 34 | baseMint: PublicKey; 35 | quoteMint: PublicKey; 36 | }; 37 | 38 | export async function buyBundle() { 39 | const bundledTxns: VersionedTransaction[] = []; 40 | const keypairs: Keypair[] = loadKeypairs(); 41 | 42 | let poolInfo: { [key: string]: any } = {}; 43 | if (fs.existsSync(keyInfoPath)) { 44 | const data = fs.readFileSync(keyInfoPath, "utf-8"); 45 | poolInfo = JSON.parse(data); 46 | } 47 | 48 | const lut = new PublicKey(poolInfo.addressLUT.toString()); 49 | 50 | const lookupTableAccount = (await connection.getAddressLookupTable(lut)).value; 51 | 52 | if (lookupTableAccount == null) { 53 | console.log("Lookup table account not found!"); 54 | process.exit(0); 55 | } 56 | 57 | // -------- step 1: ask nessesary questions for pool build -------- 58 | const baseAddr = prompt("Token address: ") || ""; 59 | const percentOfSupplyInput = prompt("% of your token balance in pool (Ex. 80): ") || "0"; 60 | const solInPoolInput = prompt("# of SOL in LP (Ex. 10): ") || "0"; 61 | const OpenBookID = prompt("OpenBook MarketID: ") || ""; 62 | const jitoTipAmtInput = prompt("Jito tip in Sol (Ex. 0.01): ") || "0"; 63 | const iterations = parseInt(prompt("Enter the number of iterations for bundle creation: ") || "0", 10); 64 | const delaySeconds = parseInt(prompt("Enter the delay between each iteration in seconds: ") || "0", 10); 65 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 66 | const percentOfSupply = parseFloat(percentOfSupplyInput); 67 | const solInPool = parseFloat(solInPoolInput); 68 | 69 | let myToken = new PublicKey(baseAddr); 70 | let tokenInfo = await getMint(connection, myToken, "finalized", TOKEN_PROGRAM_ID); 71 | 72 | // Fetch balance of token 73 | const TokenBalance = await fetchTokenBalance(baseAddr, tokenInfo.decimals); 74 | // Declare the tokens to put into the pool 75 | // Quote will always be SOLLL 76 | const baseToken = new Token(TOKEN_PROGRAM_ID, new PublicKey(tokenInfo.address), tokenInfo.decimals); // Token to put into pool 77 | const quoteToken = DEFAULT_TOKEN.SOL; // SOL s quote 78 | const targetMarketId = new PublicKey(OpenBookID); // Convert to pubkey 79 | 80 | for (let i = 0; i < iterations; i++) { 81 | // -------- step 2: create pool txn -------- 82 | const startTime = Math.floor(Date.now() / 1000); 83 | const walletTokenAccounts = await getWalletTokenAccount(connection, wallet.publicKey); 84 | 85 | const marketBufferInfo: any = await connection.getAccountInfo(targetMarketId); 86 | const { baseMint, quoteMint, baseLotSize, quoteLotSize, baseVault, quoteVault, bids, asks, eventQueue, requestQueue } = MARKET_STATE_LAYOUT_V3.decode(marketBufferInfo.data); 87 | 88 | let poolKeys: any = Liquidity.getAssociatedPoolKeys({ 89 | version: 4, 90 | marketVersion: 3, 91 | baseMint, 92 | quoteMint, 93 | baseDecimals: tokenInfo.decimals, 94 | quoteDecimals: 9, 95 | marketId: targetMarketId, 96 | programId: PROGRAMIDS.AmmV4, 97 | marketProgramId: PROGRAMIDS.OPENBOOK_MARKET, 98 | }); 99 | poolKeys.marketBaseVault = baseVault; 100 | poolKeys.marketQuoteVault = quoteVault; 101 | poolKeys.marketBids = bids; 102 | poolKeys.marketAsks = asks; 103 | poolKeys.marketEventQueue = eventQueue; 104 | //console.log("Pool Keys:", poolKeys); 105 | 106 | // Ensure percentOfSupply and TokenBalance are scaled to integers if they involve decimals. 107 | const baseMintAmount = new BN(Math.floor((percentOfSupply / 100) * TokenBalance).toString()); 108 | 109 | // Ensure solInPool is scaled to an integer if it involves decimals. 110 | const quoteMintAmount = new BN((solInPool * Math.pow(10, 9)).toString()); 111 | 112 | // If you need to clone the BN instances for some reason, this is correct. Otherwise, you can use baseMintAmount and quoteMintAmount directly. 113 | const addBaseAmount = new BN(baseMintAmount.toString()); 114 | const addQuoteAmount = new BN(quoteMintAmount.toString()); 115 | 116 | // Fetch LP Mint and write to json 117 | const associatedPoolKeys = getMarketAssociatedPoolKeys({ 118 | baseToken, 119 | quoteToken, 120 | targetMarketId, 121 | }); 122 | await writeDetailsToJsonFile(associatedPoolKeys, startTime, targetMarketId.toString()); // Write all objects to keyInfo 123 | 124 | // GLOBAL BLOCKHASH 125 | const { blockhash } = await connection.getLatestBlockhash("finalized"); 126 | 127 | ammCreatePool({ 128 | startTime, 129 | addBaseAmount, 130 | addQuoteAmount, 131 | baseToken, 132 | quoteToken, 133 | targetMarketId, 134 | wallet: walletconn.payer, 135 | walletTokenAccounts, 136 | }).then(async ({ txs }) => { 137 | const createPoolInstructions: TransactionInstruction[] = []; 138 | for (const itemIx of txs.innerTransactions) { 139 | createPoolInstructions.push(...itemIx.instructions); 140 | } 141 | 142 | const addressesMain: PublicKey[] = []; 143 | createPoolInstructions.forEach((ixn) => { 144 | ixn.keys.forEach((key) => { 145 | addressesMain.push(key.pubkey); 146 | }); 147 | }); 148 | const lookupTablesMain = lookupTableProvider.computeIdealLookupTablesForAddresses(addressesMain); 149 | 150 | const messageMain = new TransactionMessage({ 151 | payerKey: wallet.publicKey, 152 | recentBlockhash: blockhash, 153 | instructions: createPoolInstructions, 154 | }).compileToV0Message(lookupTablesMain); 155 | const txPool = new VersionedTransaction(messageMain); 156 | 157 | try { 158 | const serializedMsg = txPool.serialize(); 159 | if (serializedMsg.length > 1232) { 160 | console.log("tx too big"); 161 | process.exit(0); 162 | } 163 | txPool.sign([walletconn.payer]); 164 | } catch (e) { 165 | console.log(e, "error signing txMain"); 166 | return; 167 | } 168 | bundledTxns.push(txPool); 169 | }); 170 | 171 | // -------- step 3: create swap txns -------- 172 | const txMainSwaps: VersionedTransaction[] = await createWalletSwaps(targetMarketId, blockhash, keypairs, jitoTipAmt, lookupTableAccount); 173 | bundledTxns.push(...txMainSwaps); 174 | 175 | // -------- step 4: send bundle -------- 176 | /* 177 | // Simulate each transaction 178 | for (const tx of bundledTxns) { 179 | try { 180 | const simulationResult = await connection.simulateTransaction(tx, { commitment: "processed" }); 181 | console.log(simulationResult); 182 | 183 | if (simulationResult.value.err) { 184 | console.error("Simulation error for transaction:", simulationResult.value.err); 185 | } else { 186 | console.log("Simulation success for transaction. Logs:"); 187 | simulationResult.value.logs?.forEach(log => console.log(log)); 188 | } 189 | } catch (error) { 190 | console.error("Error during simulation:", error); 191 | } 192 | } 193 | */ 194 | 195 | await sendBundle(bundledTxns); 196 | 197 | // Delay between iterations 198 | await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000)); 199 | bundledTxns.length = 0; 200 | } 201 | 202 | return; 203 | } 204 | 205 | async function createWalletSwaps(marketID: PublicKey, blockhash: string, keypairs: Keypair[], jitoTip: number, lut: AddressLookupTableAccount): Promise { 206 | const txsSigned: VersionedTransaction[] = []; 207 | const chunkedKeypairs = chunkArray(keypairs, 7); // EDIT CHUNKS? 208 | const keys = await derivePoolKeys(marketID); 209 | 210 | // Iterate over each chunk of keypairs 211 | for (let chunkIndex = 0; chunkIndex < chunkedKeypairs.length; chunkIndex++) { 212 | const chunk = chunkedKeypairs[chunkIndex]; 213 | const instructionsForChunk: TransactionInstruction[] = []; 214 | 215 | // Iterate over each keypair in the chunk to create swap instructions 216 | for (let i = 0; i < chunk.length; i++) { 217 | const keypair = chunk[i]; 218 | console.log(`Processing keypair ${i + 1}/${chunk.length}:`, keypair.publicKey.toString()); 219 | 220 | if (keys == null) { 221 | console.log("Error fetching poolkeys"); 222 | process.exit(0); 223 | } 224 | 225 | const TokenATA = await spl.getAssociatedTokenAddress(new PublicKey(keys.baseMint), keypair.publicKey); 226 | 227 | const wSolATA = await spl.getAssociatedTokenAddress(spl.NATIVE_MINT, keypair.publicKey); 228 | 229 | const { buyIxs } = makeBuy(keys, wSolATA, TokenATA, false, keypair); // CHANGE FOR SELL (sellIxs/true) 230 | 231 | instructionsForChunk.push(...buyIxs); // CHANGE FOR SELL (sellIxs) 232 | } 233 | 234 | const message = new TransactionMessage({ 235 | payerKey: keypair.publicKey, 236 | recentBlockhash: blockhash, 237 | instructions: instructionsForChunk, 238 | }).compileToV0Message([lut]); 239 | 240 | const versionedTx = new VersionedTransaction(message); 241 | 242 | const serializedMsg = versionedTx.serialize(); 243 | console.log("Txn size:", serializedMsg.length); 244 | if (serializedMsg.length > 1232) { 245 | console.log("tx too big"); 246 | } 247 | 248 | console.log( 249 | "Signing transaction with chunk signers", 250 | chunk.map((kp) => kp.publicKey.toString()) 251 | ); 252 | 253 | // Sign with the wallet for tip on the last instruction 254 | if (chunkIndex === chunkedKeypairs.length - 1) { 255 | versionedTx.sign([wallet]); 256 | } 257 | 258 | for (const keypair of chunk) { 259 | versionedTx.sign([keypair]); 260 | } 261 | 262 | txsSigned.push(versionedTx); 263 | } 264 | 265 | return txsSigned; 266 | } 267 | 268 | function chunkArray(array: T[], size: number): T[][] { 269 | return Array.from({ length: Math.ceil(array.length / size) }, (v, i) => array.slice(i * size, i * size + size)); 270 | } 271 | 272 | export async function sendBundle(bundledTxns: VersionedTransaction[]) { 273 | try { 274 | const bundleId = await searcherClient.sendBundle(new JitoBundle(bundledTxns, bundledTxns.length)); 275 | console.log(`Bundle ${bundleId} sent.`); 276 | 277 | ///* 278 | // Assuming onBundleResult returns a Promise 279 | const result = await new Promise((resolve, reject) => { 280 | searcherClient.onBundleResult( 281 | (result) => { 282 | console.log("Received bundle result:", result); 283 | resolve(result); // Resolve the promise with the result 284 | }, 285 | (e: Error) => { 286 | console.error("Error receiving bundle result:", e); 287 | reject(e); // Reject the promise if there's an error 288 | } 289 | ); 290 | }); 291 | 292 | console.log("Result:", result); 293 | //*/ 294 | } catch (error) { 295 | const err = error as any; 296 | console.error("Error sending bundle:", err.message); 297 | 298 | if (err?.message?.includes("Bundle Dropped, no connected leader up soon")) { 299 | console.error("Error sending bundle: Bundle Dropped, no connected leader up soon."); 300 | } else { 301 | console.error("An unexpected error occurred:", err.message); 302 | } 303 | } 304 | } 305 | 306 | async function fetchTokenBalance(TokenPubKey: string, decimalsToken: number) { 307 | const ownerPubKey = wallet.publicKey; 308 | 309 | const response = await connection.getParsedTokenAccountsByOwner(ownerPubKey, { 310 | mint: new PublicKey(TokenPubKey), 311 | }); 312 | 313 | let TokenBalance = 0; 314 | for (const account of response.value) { 315 | const amount = account.account.data.parsed.info.tokenAmount.uiAmount; 316 | TokenBalance += amount; 317 | } 318 | 319 | return TokenBalance * 10 ** decimalsToken; 320 | } 321 | 322 | function makeBuy(poolKeys: IPoolKeys, wSolATA: PublicKey, TokenATA: PublicKey, reverse: boolean, keypair: Keypair) { 323 | const programId = new PublicKey("4BsBhTFFxKaswZioPvRMqRqVYTd668wZvAc3oKLZP2tx"); // MY PROGRAM 324 | const account1 = TOKEN_PROGRAM_ID; // token program 325 | const account2 = poolKeys.id; // amm id writable 326 | const account3 = poolKeys.authority; // amm authority 327 | const account4 = poolKeys.openOrders; // amm open orders writable 328 | const account5 = poolKeys.targetOrders; // amm target orders writable 329 | const account6 = poolKeys.baseVault; // pool coin token account writable AKA baseVault 330 | const account7 = poolKeys.quoteVault; // pool pc token account writable AKA quoteVault 331 | const account8 = poolKeys.marketProgramId; // serum program id 332 | const account9 = poolKeys.marketId; // serum market writable 333 | const account10 = poolKeys.marketBids; // serum bids writable 334 | const account11 = poolKeys.marketAsks; // serum asks writable 335 | const account12 = poolKeys.marketEventQueue; // serum event queue writable 336 | const account13 = poolKeys.marketBaseVault; // serum coin vault writable AKA marketBaseVault 337 | const account14 = poolKeys.marketQuoteVault; // serum pc vault writable AKA marketQuoteVault 338 | const account15 = poolKeys.marketAuthority; // serum vault signer AKA marketAuthority 339 | let account16 = wSolATA; // user source token account writable 340 | let account17 = TokenATA; // user dest token account writable 341 | const account18 = keypair.publicKey; // user owner (signer) writable 342 | const account19 = MAINNET_PROGRAM_ID.AmmV4; // ammV4 writable 343 | 344 | if (reverse == true) { 345 | account16 = TokenATA; 346 | account17 = wSolATA; 347 | } 348 | 349 | const buffer = Buffer.alloc(16); 350 | const prefix = Buffer.from([0x09]); 351 | const instructionData = Buffer.concat([prefix, buffer]); 352 | const accountMetas = [ 353 | { pubkey: account1, isSigner: false, isWritable: false }, 354 | { pubkey: account2, isSigner: false, isWritable: true }, 355 | { pubkey: account3, isSigner: false, isWritable: false }, 356 | { pubkey: account4, isSigner: false, isWritable: true }, 357 | { pubkey: account5, isSigner: false, isWritable: true }, 358 | { pubkey: account6, isSigner: false, isWritable: true }, 359 | { pubkey: account7, isSigner: false, isWritable: true }, 360 | { pubkey: account8, isSigner: false, isWritable: false }, 361 | { pubkey: account9, isSigner: false, isWritable: true }, 362 | { pubkey: account10, isSigner: false, isWritable: true }, 363 | { pubkey: account11, isSigner: false, isWritable: true }, 364 | { pubkey: account12, isSigner: false, isWritable: true }, 365 | { pubkey: account13, isSigner: false, isWritable: true }, 366 | { pubkey: account14, isSigner: false, isWritable: true }, 367 | { pubkey: account15, isSigner: false, isWritable: false }, 368 | { pubkey: account16, isSigner: false, isWritable: true }, 369 | { pubkey: account17, isSigner: false, isWritable: true }, 370 | { pubkey: account18, isSigner: true, isWritable: true }, 371 | { pubkey: account19, isSigner: false, isWritable: true }, 372 | ]; 373 | 374 | const swap = new TransactionInstruction({ 375 | keys: accountMetas, 376 | programId, 377 | data: instructionData, 378 | }); 379 | 380 | let buyIxs: TransactionInstruction[] = []; 381 | let sellIxs: TransactionInstruction[] = []; 382 | 383 | if (reverse === false) { 384 | buyIxs.push(swap); 385 | } 386 | 387 | if (reverse === true) { 388 | sellIxs.push(swap); 389 | } 390 | 391 | return { buyIxs, sellIxs }; 392 | } 393 | 394 | function getMarketAssociatedPoolKeys(input: LiquidityPairTargetInfo) { 395 | const poolInfo = Liquidity.getAssociatedPoolKeys({ 396 | version: 4, 397 | marketVersion: 3, 398 | baseMint: input.baseToken.mint, 399 | quoteMint: input.quoteToken.mint, 400 | baseDecimals: input.baseToken.decimals, 401 | quoteDecimals: input.quoteToken.decimals, 402 | marketId: input.targetMarketId, 403 | programId: PROGRAMIDS.AmmV4, 404 | marketProgramId: MAINNET_PROGRAM_ID.OPENBOOK_MARKET, 405 | }); 406 | return poolInfo; 407 | } 408 | 409 | async function writeDetailsToJsonFile(associatedPoolKeys: AssociatedPoolKeys, startTime: number, marketID: string) { 410 | const filePath = path.join(__dirname, "keyInfo.json"); 411 | 412 | try { 413 | // Read the current contents of the file 414 | let fileData = {}; 415 | try { 416 | const currentData = await fsPromises.readFile(filePath, "utf-8"); 417 | fileData = JSON.parse(currentData); 418 | } catch (error) { 419 | console.log("poolinfo.json doesn't exist or is empty. Creating a new one."); 420 | } 421 | 422 | // Update only the specific fields related to the new pool 423 | const updatedData = { 424 | ...fileData, // Spread existing data to preserve it 425 | lpTokenAddr: associatedPoolKeys.lpMint.toString(), 426 | targetPool: associatedPoolKeys.id.toString(), 427 | baseMint: associatedPoolKeys.baseMint.toString(), 428 | quoteMint: associatedPoolKeys.quoteMint.toString(), 429 | openTime: new Date(startTime * 1000).toISOString(), 430 | marketID, 431 | }; 432 | 433 | // Write the updated data back to the file 434 | await fsPromises.writeFile(filePath, JSON.stringify(updatedData, null, 2), "utf8"); 435 | console.log("Successfully updated the JSON file with new pool details."); 436 | } catch (error) { 437 | console.error("Failed to write to the JSON file:", error); 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /src/createMarket.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MarketV2, 3 | Token, 4 | ENDPOINT as _ENDPOINT, 5 | Currency, 6 | LOOKUP_TABLE_CACHE, 7 | MAINNET_PROGRAM_ID, 8 | RAYDIUM_MAINNET, 9 | TOKEN_PROGRAM_ID, 10 | TxVersion, 11 | buildSimpleTransaction, 12 | InnerSimpleV0Transaction, 13 | blob, publicKey, struct, u16, u32, u64, u8, WideBits, 14 | ZERO, 15 | splitTxAndSigners, 16 | SYSVAR_RENT_PUBKEY, 17 | Base, generatePubKey, InstructionType,CacheLTA, 18 | } from '@raydium-io/raydium-sdk'; 19 | import { createInitializeAccountInstruction, getMint } from '@solana/spl-token' 20 | import { Keypair, PublicKey, TransactionMessage, LAMPORTS_PER_SOL, Connection, Signer, VersionedTransaction, Transaction, SystemProgram, TransactionInstruction, Blockhash } from '@solana/web3.js'; 21 | import { 22 | connection, 23 | wallet, 24 | tipAcct 25 | } from '../config'; 26 | import { BN } from "@project-serum/anchor"; 27 | import promptSync from 'prompt-sync'; 28 | import { sendBundle } from './jitoPool'; 29 | 30 | const prompt = promptSync(); 31 | 32 | const bundledTxns: VersionedTransaction[] = []; 33 | 34 | type TestTxInputInfo = { 35 | baseToken: Token 36 | quoteToken: Token 37 | wallet: Keypair 38 | } 39 | 40 | const PROGRAMIDS = MAINNET_PROGRAM_ID; 41 | const makeTxVersion = TxVersion.V0; 42 | const addLookupTableInfo = LOOKUP_TABLE_CACHE; 43 | 44 | 45 | function accountFlagsLayout(property = 'accountFlags') { 46 | const ACCOUNT_FLAGS_LAYOUT = new WideBits(property) 47 | ACCOUNT_FLAGS_LAYOUT.addBoolean('initialized') 48 | ACCOUNT_FLAGS_LAYOUT.addBoolean('market') 49 | ACCOUNT_FLAGS_LAYOUT.addBoolean('openOrders') 50 | ACCOUNT_FLAGS_LAYOUT.addBoolean('requestQueue') 51 | ACCOUNT_FLAGS_LAYOUT.addBoolean('eventQueue') 52 | ACCOUNT_FLAGS_LAYOUT.addBoolean('bids') 53 | ACCOUNT_FLAGS_LAYOUT.addBoolean('asks') 54 | return ACCOUNT_FLAGS_LAYOUT 55 | } 56 | 57 | export const MARKET_STATE_LAYOUT_V2 = struct([ 58 | blob(5), 59 | accountFlagsLayout('accountFlags'), 60 | publicKey('ownAddress'), 61 | u64('vaultSignerNonce'), 62 | publicKey('baseMint'), 63 | publicKey('quoteMint'), 64 | publicKey('baseVault'), 65 | u64('baseDepositsTotal'), 66 | u64('baseFeesAccrued'), 67 | publicKey('quoteVault'), 68 | u64('quoteDepositsTotal'), 69 | u64('quoteFeesAccrued'), 70 | u64('quoteDustThreshold'), 71 | publicKey('requestQueue'), 72 | publicKey('eventQueue'), 73 | publicKey('bids'), 74 | publicKey('asks'), 75 | u64('baseLotSize'), 76 | u64('quoteLotSize'), 77 | u64('feeRateBps'), 78 | u64('referrerRebatesAccrued'), 79 | blob(7), 80 | ]) 81 | 82 | 83 | 84 | export async function createMarket(input: TestTxInputInfo, jitoTip: number) { 85 | // -------- step 1: make instructions -------- 86 | const createMarketInstruments = await makeCreateMarketInstructionSimple({ 87 | connection, 88 | wallet: input.wallet.publicKey, 89 | baseInfo: input.baseToken, 90 | quoteInfo: input.quoteToken, 91 | lotSize: 1, // default 1 92 | tickSize: 0.00001, // default 0.01 93 | dexProgramId: PROGRAMIDS.OPENBOOK_MARKET, 94 | makeTxVersion, 95 | }) 96 | console.log('New Market ID:', createMarketInstruments.address.marketId.toString()); 97 | await buildAndSendTx(createMarketInstruments.innerTransactions, jitoTip) 98 | } 99 | 100 | 101 | 102 | 103 | export async function mkMrkt() { 104 | // Constants for the token 105 | const baseAddr = prompt('Token Address: ') || ''; 106 | const jitoTipAmtInput = prompt('Jito tip in Sol (Ex. 0.01): ') || '0'; 107 | const jitoTipAmt = parseFloat(jitoTipAmtInput) * LAMPORTS_PER_SOL; 108 | 109 | let myToken = new PublicKey(baseAddr) 110 | let tokenInfo = await getMint(connection, myToken, 'finalized', TOKEN_PROGRAM_ID) 111 | 112 | const baseToken = new Token(TOKEN_PROGRAM_ID, new PublicKey(baseAddr), tokenInfo.decimals); // TOKEN 113 | const quoteToken = new Token(TOKEN_PROGRAM_ID, new PublicKey('So11111111111111111111111111111111111111112'), 9, 'WSOL', 'WSOL'); // SOL 114 | 115 | 116 | console.log(`Creating market:`); 117 | 118 | await createMarket( 119 | { 120 | baseToken, 121 | quoteToken, 122 | wallet: wallet, 123 | }, 124 | jitoTipAmt, 125 | ).catch(error => { 126 | console.error('Error creating market:', error); 127 | }); 128 | } 129 | 130 | 131 | export async function buildAndSendTx(innerSimpleV0Transaction: InnerSimpleV0Transaction[], jitoTip: number) { 132 | const { blockhash } = await connection.getLatestBlockhash('finalized'); 133 | 134 | const willSendTx = await buildSimpleTransaction({ 135 | connection, 136 | makeTxVersion, 137 | payer: wallet.publicKey, 138 | innerTransactions: innerSimpleV0Transaction, 139 | recentBlockhash: blockhash, 140 | addLookupTableInfo: addLookupTableInfo, 141 | }) 142 | 143 | return await sendTx(wallet, willSendTx, blockhash, jitoTip) 144 | } 145 | 146 | export async function sendTx( 147 | payer: Keypair | Signer, 148 | txs: (VersionedTransaction | Transaction)[], 149 | blockhash: string | Blockhash, 150 | jitoTipAmt: number, 151 | ) { 152 | for (const iTx of txs) { 153 | if (iTx instanceof VersionedTransaction) { 154 | iTx.sign([payer]); 155 | bundledTxns.push(iTx); 156 | } 157 | } 158 | 159 | 160 | const tipSwapIxn = SystemProgram.transfer({ 161 | fromPubkey: wallet.publicKey, 162 | toPubkey: tipAcct, 163 | lamports: BigInt(jitoTipAmt), 164 | }); 165 | 166 | const message = new TransactionMessage({ 167 | payerKey: wallet.publicKey, 168 | recentBlockhash: blockhash, 169 | instructions: [tipSwapIxn], 170 | }).compileToV0Message(); 171 | 172 | const versionedTx = new VersionedTransaction(message); 173 | 174 | const serializedMsg = versionedTx.serialize(); 175 | console.log("Txn size:", serializedMsg.length); 176 | 177 | versionedTx.sign([wallet]); 178 | 179 | bundledTxns.push(versionedTx); 180 | 181 | await sendBundle(bundledTxns); 182 | bundledTxns.length = 0; 183 | return; 184 | } 185 | 186 | async function makeCreateMarketInstructionSimple({ 187 | connection, 188 | wallet, 189 | baseInfo, 190 | quoteInfo, 191 | lotSize, 192 | tickSize, 193 | dexProgramId, 194 | makeTxVersion, 195 | lookupTableCache, 196 | }: { 197 | makeTxVersion: T 198 | lookupTableCache?: CacheLTA 199 | connection: Connection 200 | wallet: PublicKey 201 | baseInfo: { 202 | mint: PublicKey 203 | decimals: number 204 | } 205 | quoteInfo: { 206 | mint: PublicKey 207 | decimals: number 208 | } 209 | lotSize: number 210 | tickSize: number 211 | dexProgramId: PublicKey 212 | }) { 213 | const market = generatePubKey({ fromPublicKey: wallet, programId: dexProgramId }) 214 | const requestQueue = generatePubKey({ fromPublicKey: wallet, programId: dexProgramId }) 215 | const eventQueue = generatePubKey({ fromPublicKey: wallet, programId: dexProgramId }) 216 | const bids = generatePubKey({ fromPublicKey: wallet, programId: dexProgramId }) 217 | const asks = generatePubKey({ fromPublicKey: wallet, programId: dexProgramId }) 218 | const baseVault = generatePubKey({ fromPublicKey: wallet, programId: TOKEN_PROGRAM_ID }) 219 | const quoteVault = generatePubKey({ fromPublicKey: wallet, programId: TOKEN_PROGRAM_ID }) 220 | const feeRateBps = 0 221 | const quoteDustThreshold = new BN(100) 222 | 223 | function getVaultOwnerAndNonce() { 224 | const vaultSignerNonce = new BN(0) 225 | // eslint-disable-next-line no-constant-condition 226 | while (true) { 227 | try { 228 | const vaultOwner = PublicKey.createProgramAddressSync( 229 | [market.publicKey.toBuffer(), vaultSignerNonce.toArrayLike(Buffer, 'le', 8)], 230 | dexProgramId, 231 | ) 232 | return { vaultOwner, vaultSignerNonce } 233 | } catch (e) { 234 | vaultSignerNonce.iaddn(1) 235 | if (vaultSignerNonce.gt(new BN(25555))) throw Error('find vault owner error') 236 | } 237 | } 238 | } 239 | const { vaultOwner, vaultSignerNonce } = getVaultOwnerAndNonce() 240 | 241 | const baseLotSize = new BN(Math.round(10 ** baseInfo.decimals * lotSize)) 242 | const quoteLotSize = new BN(Math.round(lotSize * 10 ** quoteInfo.decimals * tickSize)) 243 | 244 | if (baseLotSize.eq(ZERO)) throw Error('lot size is too small') 245 | if (quoteLotSize.eq(ZERO)) throw Error('tick size or lot size is too small') 246 | 247 | const ins = await makeCreateMarketInstruction({ 248 | connection, 249 | wallet, 250 | marketInfo: { 251 | programId: dexProgramId, 252 | id: market, 253 | baseMint: baseInfo.mint, 254 | quoteMint: quoteInfo.mint, 255 | baseVault, 256 | quoteVault, 257 | vaultOwner, 258 | requestQueue, 259 | eventQueue, 260 | bids, 261 | asks, 262 | 263 | feeRateBps, 264 | quoteDustThreshold, 265 | vaultSignerNonce, 266 | baseLotSize, 267 | quoteLotSize, 268 | }, 269 | }) 270 | 271 | return { 272 | address: ins.address, 273 | innerTransactions: await splitTxAndSigners({ 274 | connection, 275 | makeTxVersion, 276 | computeBudgetConfig: undefined, 277 | payer: wallet, 278 | innerTransaction: ins.innerTransactions, 279 | lookupTableCache, 280 | }), 281 | } 282 | } 283 | 284 | async function makeCreateMarketInstruction({ 285 | connection, 286 | wallet, 287 | marketInfo, 288 | }: { 289 | connection: Connection 290 | wallet: PublicKey 291 | marketInfo: { 292 | programId: PublicKey 293 | id: { publicKey: PublicKey; seed: string } 294 | baseMint: PublicKey 295 | quoteMint: PublicKey 296 | baseVault: { publicKey: PublicKey; seed: string } 297 | quoteVault: { publicKey: PublicKey; seed: string } 298 | vaultOwner: PublicKey 299 | 300 | requestQueue: { publicKey: PublicKey; seed: string } 301 | eventQueue: { publicKey: PublicKey; seed: string } 302 | bids: { publicKey: PublicKey; seed: string } 303 | asks: { publicKey: PublicKey; seed: string } 304 | 305 | feeRateBps: number 306 | vaultSignerNonce: BN 307 | quoteDustThreshold: BN 308 | 309 | baseLotSize: BN 310 | quoteLotSize: BN 311 | } 312 | }) { 313 | const ins1: TransactionInstruction[] = [] 314 | const accountLamports = await connection.getMinimumBalanceForRentExemption(165) 315 | ins1.push( 316 | //ComputeBudgetProgram.setComputeUnitPrice({ 317 | //microLamports: 600000 // change gas here 318 | //}), 319 | //ComputeBudgetProgram.setComputeUnitLimit({ 320 | //units: 250000 // change gas here 321 | //}), 322 | SystemProgram.createAccountWithSeed({ 323 | fromPubkey: wallet, 324 | basePubkey: wallet, 325 | seed: marketInfo.baseVault.seed, 326 | newAccountPubkey: marketInfo.baseVault.publicKey, 327 | lamports: accountLamports, 328 | space: 165, 329 | programId: TOKEN_PROGRAM_ID, 330 | }), 331 | SystemProgram.createAccountWithSeed({ 332 | fromPubkey: wallet, 333 | basePubkey: wallet, 334 | seed: marketInfo.quoteVault.seed, 335 | newAccountPubkey: marketInfo.quoteVault.publicKey, 336 | lamports: accountLamports, 337 | space: 165, 338 | programId: TOKEN_PROGRAM_ID, 339 | }), 340 | createInitializeAccountInstruction(marketInfo.baseVault.publicKey, marketInfo.baseMint, marketInfo.vaultOwner), 341 | createInitializeAccountInstruction(marketInfo.quoteVault.publicKey, marketInfo.quoteMint, marketInfo.vaultOwner), 342 | ) 343 | 344 | const ins2: TransactionInstruction[] = [] 345 | ins2.push( 346 | //ComputeBudgetProgram.setComputeUnitPrice({ 347 | //microLamports: 600000 // change gas here 348 | //}), 349 | //ComputeBudgetProgram.setComputeUnitLimit({ 350 | //units: 250000 // change gas here 351 | //}), 352 | SystemProgram.createAccountWithSeed({ 353 | fromPubkey: wallet, 354 | basePubkey: wallet, 355 | seed: marketInfo.id.seed, 356 | newAccountPubkey: marketInfo.id.publicKey, 357 | lamports: await connection.getMinimumBalanceForRentExemption(MARKET_STATE_LAYOUT_V2.span), 358 | space: MARKET_STATE_LAYOUT_V2.span, 359 | programId: marketInfo.programId, 360 | }), 361 | SystemProgram.createAccountWithSeed({ 362 | fromPubkey: wallet, 363 | basePubkey: wallet, 364 | seed: marketInfo.requestQueue.seed, 365 | newAccountPubkey: marketInfo.requestQueue.publicKey, 366 | lamports: await connection.getMinimumBalanceForRentExemption(124), // CHANGE 367 | space: 124, // CHANGE 368 | programId: marketInfo.programId, 369 | }), 370 | SystemProgram.createAccountWithSeed({ 371 | fromPubkey: wallet, 372 | basePubkey: wallet, 373 | seed: marketInfo.eventQueue.seed, 374 | newAccountPubkey: marketInfo.eventQueue.publicKey, 375 | lamports: await connection.getMinimumBalanceForRentExemption(11308), // CHANGE 376 | space: 11308, // CHANGE 377 | programId: marketInfo.programId, 378 | }), 379 | SystemProgram.createAccountWithSeed({ 380 | fromPubkey: wallet, 381 | basePubkey: wallet, 382 | seed: marketInfo.bids.seed, 383 | newAccountPubkey: marketInfo.bids.publicKey, 384 | lamports: await connection.getMinimumBalanceForRentExemption(14524), // CHANGE 385 | space: 14524, // CHANGE 386 | programId: marketInfo.programId, 387 | }), 388 | SystemProgram.createAccountWithSeed({ 389 | fromPubkey: wallet, 390 | basePubkey: wallet, 391 | seed: marketInfo.asks.seed, 392 | newAccountPubkey: marketInfo.asks.publicKey, 393 | lamports: await connection.getMinimumBalanceForRentExemption(14524), // CHANGE 394 | space: 14524, // CHANGE 395 | programId: marketInfo.programId, 396 | }), 397 | await initializeMarketInstruction({ 398 | programId: marketInfo.programId, 399 | marketInfo: { 400 | id: marketInfo.id.publicKey, 401 | requestQueue: marketInfo.requestQueue.publicKey, 402 | eventQueue: marketInfo.eventQueue.publicKey, 403 | bids: marketInfo.bids.publicKey, 404 | asks: marketInfo.asks.publicKey, 405 | baseVault: marketInfo.baseVault.publicKey, 406 | quoteVault: marketInfo.quoteVault.publicKey, 407 | baseMint: marketInfo.baseMint, 408 | quoteMint: marketInfo.quoteMint, 409 | 410 | baseLotSize: marketInfo.baseLotSize, 411 | quoteLotSize: marketInfo.quoteLotSize, 412 | feeRateBps: marketInfo.feeRateBps, 413 | vaultSignerNonce: marketInfo.vaultSignerNonce, 414 | quoteDustThreshold: marketInfo.quoteDustThreshold, 415 | }, 416 | }), 417 | ) 418 | 419 | return { 420 | address: { 421 | marketId: marketInfo.id.publicKey, 422 | requestQueue: marketInfo.requestQueue.publicKey, 423 | eventQueue: marketInfo.eventQueue.publicKey, 424 | bids: marketInfo.bids.publicKey, 425 | asks: marketInfo.asks.publicKey, 426 | baseVault: marketInfo.baseVault.publicKey, 427 | quoteVault: marketInfo.quoteVault.publicKey, 428 | baseMint: marketInfo.baseMint, 429 | quoteMint: marketInfo.quoteMint, 430 | }, 431 | innerTransactions: [ 432 | { 433 | instructions: ins1, 434 | signers: [], 435 | instructionTypes: [ 436 | //InstructionType.setComputeUnitPrice, 437 | //InstructionType.setComputeUnitLimit, 438 | InstructionType.createAccount, 439 | InstructionType.createAccount, 440 | InstructionType.initAccount, 441 | InstructionType.initAccount, 442 | ], 443 | }, 444 | { 445 | instructions: ins2, 446 | signers: [], 447 | instructionTypes: [ 448 | //InstructionType.setComputeUnitPrice, 449 | //InstructionType.setComputeUnitLimit, 450 | InstructionType.createAccount, 451 | InstructionType.createAccount, 452 | InstructionType.createAccount, 453 | InstructionType.createAccount, 454 | InstructionType.createAccount, 455 | InstructionType.initMarket, 456 | ], 457 | }, 458 | ], 459 | } 460 | } 461 | 462 | async function initializeMarketInstruction({ 463 | programId, 464 | marketInfo, 465 | }: { 466 | programId: PublicKey 467 | marketInfo: { 468 | id: PublicKey 469 | requestQueue: PublicKey 470 | eventQueue: PublicKey 471 | bids: PublicKey 472 | asks: PublicKey 473 | baseVault: PublicKey 474 | quoteVault: PublicKey 475 | baseMint: PublicKey 476 | quoteMint: PublicKey 477 | authority?: PublicKey 478 | pruneAuthority?: PublicKey 479 | 480 | baseLotSize: BN 481 | quoteLotSize: BN 482 | feeRateBps: number 483 | vaultSignerNonce: BN 484 | quoteDustThreshold: BN 485 | } 486 | }) { 487 | const dataLayout = struct([ 488 | u8('version'), 489 | u32('instruction'), 490 | u64('baseLotSize'), 491 | u64('quoteLotSize'), 492 | u16('feeRateBps'), 493 | u64('vaultSignerNonce'), 494 | u64('quoteDustThreshold'), 495 | ]) 496 | 497 | const keys = [ 498 | { pubkey: marketInfo.id, isSigner: false, isWritable: true }, 499 | { pubkey: marketInfo.requestQueue, isSigner: false, isWritable: true }, 500 | { pubkey: marketInfo.eventQueue, isSigner: false, isWritable: true }, 501 | { pubkey: marketInfo.bids, isSigner: false, isWritable: true }, 502 | { pubkey: marketInfo.asks, isSigner: false, isWritable: true }, 503 | { pubkey: marketInfo.baseVault, isSigner: false, isWritable: true }, 504 | { pubkey: marketInfo.quoteVault, isSigner: false, isWritable: true }, 505 | { pubkey: marketInfo.baseMint, isSigner: false, isWritable: false }, 506 | { pubkey: marketInfo.quoteMint, isSigner: false, isWritable: false }, 507 | // Use a dummy address if using the new dex upgrade to save tx space. 508 | { 509 | pubkey: marketInfo.authority ? marketInfo.quoteMint : SYSVAR_RENT_PUBKEY, 510 | isSigner: false, 511 | isWritable: false, 512 | }, 513 | ] 514 | .concat(marketInfo.authority ? { pubkey: marketInfo.authority, isSigner: false, isWritable: false } : []) 515 | .concat( 516 | marketInfo.authority && marketInfo.pruneAuthority 517 | ? { pubkey: marketInfo.pruneAuthority, isSigner: false, isWritable: false } 518 | : [], 519 | ) 520 | 521 | const data = Buffer.alloc(dataLayout.span) 522 | dataLayout.encode( 523 | { 524 | version: 0, 525 | instruction: 0, 526 | baseLotSize: marketInfo.baseLotSize, 527 | quoteLotSize: marketInfo.quoteLotSize, 528 | feeRateBps: marketInfo.feeRateBps, 529 | vaultSignerNonce: marketInfo.vaultSignerNonce, 530 | quoteDustThreshold: marketInfo.quoteDustThreshold, 531 | }, 532 | data, 533 | ) 534 | 535 | return new TransactionInstruction({ 536 | keys, 537 | programId, 538 | data, 539 | }) 540 | } --------------------------------------------------------------------------------