├── raydium-copy-trading ├── utils │ ├── tick_array_state.ts │ ├── tick_array_bitmap_ext.ts │ ├── common_utils.ts │ ├── api.ts │ ├── clmm_utils.ts │ └── pool_utils.ts ├── config.ts ├── raydium │ ├── constants.ts │ ├── amm_v4.ts │ ├── clmm.ts │ └── cpmm.ts └── layouts │ ├── cpmm.ts │ ├── amm_v4.ts │ └── clmm.ts ├── DevMigrationCount ├── example_response.json └── readme.md ├── Pulse └── readme.md ├── Trending └── readme.md ├── DevTokens ├── readme.md └── example_response.json ├── PairStats └── readme.md ├── HolderData ├── readme.md └── example_response.json ├── TopTraders └── readme.md ├── TokenInfo ├── readme.md └── example_response.json ├── LastTransaction ├── readme.md └── example_response.json ├── PairInfo ├── readme.md └── example_response.json ├── TransactionsFeed └── readme.md ├── env.example ├── pumpfun-copy-trading ├── config.ts ├── constants.ts ├── utils.ts ├── coin_data.ts └── pump_fun.ts ├── WebSocket └── readme.md ├── README.md └── index.ts /raydium-copy-trading/utils/tick_array_state.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DevMigrationCount/example_response.json: -------------------------------------------------------------------------------- 1 | {"migrationCount":1} -------------------------------------------------------------------------------- /Pulse/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api5.axiom.trade/pulse 3 | 4 | # Response 5 | -------------------------------------------------------------------------------- /Trending/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://axiom.trade/api/axiom-trending?timePeriod=5m 3 | 4 | # Params -------------------------------------------------------------------------------- /DevTokens/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api2.axiom.trade/dev-tokens?devAddress=6ycXq6DRXHu86WVzhxuZ3Bnp5nNsktdvFHGsw6twhbgB 3 | 4 | # Params -------------------------------------------------------------------------------- /PairStats/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api4.axiom.trade/pair-stats?pairAddress=DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB 3 | 4 | # Params -------------------------------------------------------------------------------- /HolderData/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api5.axiom.trade/holder-data?pairAddress=DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB 3 | 4 | # Params -------------------------------------------------------------------------------- /TopTraders/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api.axiom.trade/top-traders?pairAddress=DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB 3 | 4 | # Params -------------------------------------------------------------------------------- /TokenInfo/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api2.axiom.trade/token-info?pairAddress=DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB 3 | 4 | # Response 5 | -------------------------------------------------------------------------------- /DevMigrationCount/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api.axiom.trade/dev-migration-count?devAddress=6ycXq6DRXHu86WVzhxuZ3Bnp5nNsktdvFHGsw6twhbgB 3 | 4 | # Params -------------------------------------------------------------------------------- /LastTransaction/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api4.axiom.trade/last-transaction?pairAddress=DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB 3 | 4 | # Params -------------------------------------------------------------------------------- /PairInfo/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api2.axiom.trade/pair-info?pairAddress=DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB&userWalletAddress= 3 | 4 | # Params -------------------------------------------------------------------------------- /TransactionsFeed/readme.md: -------------------------------------------------------------------------------- 1 | # Request 2 | https://api2.axiom.trade/transactions-feed?pairAddress=DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB&orderBy=DESC&makerAddress=&minTotalUsd=&maxTotalUsd= 3 | 4 | # Params -------------------------------------------------------------------------------- /TokenInfo/example_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundlersHoldPercent": 223.021942560162, 3 | "devHoldsPercent": 0, 4 | "dexPaid": true, 5 | "insidersHoldPercent": 26.3986851155173, 6 | "numBluechipHolders": 0, 7 | "numBotUsers": 310, 8 | "numHolders": 39593, 9 | "snipersHoldPercent": 0, 10 | "top10HoldersPercent": 25.6887707964303 11 | } -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | RPC_URL_PUMP_FUN=https://api.mainnet-beta.solana.com 2 | RPC_URL_RAYDIUM=https://api.mainnet-beta.solana.com 3 | WALLET_PUBLIC_KEY=your_public_key_here 4 | WALLET_PRIVATE_KEY=your_private_key_here 5 | TRACK_WALLETS=4ugZWiyAYH7auVajf3axVykxTLsc13cx9oHCzETUjc21 6 | SOL_IN=0.01 7 | MAX_SOL_SPEND=0.1 8 | MAX_SLIPPAGE=35 9 | ALLOW_REBUY=false 10 | MAX_BUY_ATTEMPTS=1 11 | DEBUG=false -------------------------------------------------------------------------------- /LastTransaction/example_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "createdAt": "2025-02-03T21:40:27.285Z", 3 | "innerIndex": 0, 4 | "liquiditySol": 100.938706075, 5 | "liquidityToken": 221786502.069181, 6 | "makerAddress": "7hARSicdYvWR9saun1F3JDQjnnHY3k6wvNH2XXp2EA6A", 7 | "outerIndex": 5, 8 | "pairAddress": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 9 | "priceSol": 4.55746148439908E-07, 10 | "priceUsd": 9.80127666834867E-05, 11 | "signature": "5G445AQHotg2sNnoRNvruaq4QhtPELg8BseFh1hYXk8fZxLCq9mW8Dd2rwFKNdJDKKE2sUcjzYN1KDgANmjFagfZ", 12 | "tokenAmount": 243598.949503, 13 | "totalSol": 0.111019283, 14 | "totalUsd": 23.87580700198, 15 | "type": "buy" 16 | } -------------------------------------------------------------------------------- /pumpfun-copy-trading/config.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { Connection, Keypair } from '@solana/web3.js'; 3 | 4 | // Load environment variables 5 | dotenv.config(); 6 | 7 | // Constants 8 | const UNIT_BUDGET = 100_000; 9 | const UNIT_PRICE = 1_000_000; 10 | 11 | // Environment variables 12 | const PRIV_KEY = process.env.WALLET_PRIVATE_KEY; 13 | const RPC = process.env.RPC_URL_RAYDIUM; 14 | 15 | if (!PRIV_KEY || !RPC) { 16 | throw new Error('Missing required environment variables'); 17 | } 18 | 19 | // Initialize Solana connection 20 | const connection = new Connection(RPC, 'confirmed'); 21 | 22 | // Create keypair from private key 23 | const payer_keypair = Keypair.fromSecretKey( 24 | Buffer.from(PRIV_KEY, 'base58') 25 | ); 26 | -------------------------------------------------------------------------------- /DevTokens/example_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "createdAt": "2025-02-03T13:04:37.282Z", 4 | "extra": { 5 | "migratedFrom": "Pump V1", 6 | "pumpDeployerAddress": "6ycXq6DRXHu86WVzhxuZ3Bnp5nNsktdvFHGsw6twhbgB" 7 | }, 8 | "hourlyVolumeSol": 286.658053488, 9 | "liquiditySol": 100.938706075, 10 | "liquidityToken": 221786502.069181, 11 | "migrated": true, 12 | "pairAddress": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 13 | "protocol": "Raydium V4", 14 | "supply": 999936905.273771, 15 | "tokenAddress": "9rPUZjQF33KvnGKgUreDnmqJeUaCm8Yx4adaGCj3pump", 16 | "tokenImage": "https://axiomtrading.sfo3.cdn.digitaloceanspaces.com/DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB.webp", 17 | "tokenName": "McDonald's McVALUE", 18 | "tokenTicker": "McVALUE" 19 | } 20 | ] -------------------------------------------------------------------------------- /pumpfun-copy-trading/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | 3 | // Define public keys as constants 4 | export const GLOBAL = new PublicKey('4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf'); 5 | 6 | export const FEE_RECIPIENT = new PublicKey('62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV'); 7 | 8 | export const SYSTEM_PROGRAM = new PublicKey('11111111111111111111111111111111'); 9 | 10 | export const TOKEN_PROGRAM = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); 11 | 12 | export const ASSOC_TOKEN_ACC_PROG = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); 13 | 14 | export const RENT = new PublicKey('SysvarRent111111111111111111111111111111111'); 15 | 16 | export const EVENT_AUTHORITY = new PublicKey('Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1'); 17 | 18 | export const PUMP_FUN_PROGRAM = new PublicKey('6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P'); 19 | -------------------------------------------------------------------------------- /raydium-copy-trading/config.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair } from '@solana/web3.js'; 2 | import dotenv from 'dotenv'; 3 | import bs58 from 'bs58'; 4 | 5 | // Load environment variables 6 | dotenv.config(); 7 | 8 | // Constants 9 | export const UNIT_BUDGET = 150_000; 10 | export const UNIT_PRICE = 1_000_000; 11 | 12 | // Environment variables 13 | const PRIV_KEY = process.env.WALLET_PRIVATE_KEY; 14 | const RPC = process.env.RPC_URL_RAYDIUM; 15 | 16 | // Validate required environment variables 17 | if (!PRIV_KEY || !RPC) { 18 | throw new Error('Missing required environment variables: WALLET_PRIVATE_KEY or RPC_URL_RAYDIUM'); 19 | } 20 | 21 | // Initialize connection 22 | export const connection = new Connection(RPC, 'confirmed'); 23 | 24 | // Create keypair from private key 25 | export const payerKeypair = Keypair.fromSecretKey( 26 | bs58.decode(PRIV_KEY) 27 | ); 28 | 29 | // Export connection instance for use in other modules 30 | export { Connection }; 31 | -------------------------------------------------------------------------------- /WebSocket/readme.md: -------------------------------------------------------------------------------- 1 | # Websocket Information 2 | Further testing and documentation necessary 3 | 4 | ## Base URL 5 | ``` 6 | wss://api.hyperliquid.xyz/ws 7 | ``` 8 |

9 | 10 | # Subscription Requests 11 |

12 | 13 | ## userFills 14 | ``` 15 | {"method":"subscribe","subscription":{"type":"userFills","user":"id_here","aggregateByTime":true}} 16 | ``` 17 | 18 | ## userNonFundingLedgerUpdates 19 | ``` 20 | {"method":"subscribe","subscription":{"type":"userNonFundingLedgerUpdates","user":"id_here"}} 21 | ``` 22 |

23 | 24 | # Responses 25 |

26 | ## subscriptionResponse 27 | ``` 28 | {"channel":"subscriptionResponse","data":{"method":"subscribe","subscription":{"type":"userFills","user":"id_here","aggregateByTime":true}}} 29 | ``` 30 | 31 | ## userFills 32 | ``` 33 | {"channel":"userFills","data":{"isSnapshot":true,"user":"id_here","fills":[]}} 34 | ``` 35 | 36 | ## userNonFundingLedgerUpdates 37 | ``` 38 | {"channel":"userNonFundingLedgerUpdates","data":{"isSnapshot":true,"user":"id_here","nonFundingLedgerUpdates":[]}} 39 | ``` -------------------------------------------------------------------------------- /raydium-copy-trading/raydium/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | 3 | // Raydium Program IDs 4 | export const RAYDIUM_AMM_V4 = new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'); 5 | export const RAYDIUM_CPMM = new PublicKey('CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C'); 6 | export const RAYDIUM_CLMM = new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK'); 7 | 8 | // Default quote token (SOL) 9 | export const DEFAULT_QUOTE_MINT = 'So11111111111111111111111111111111111111112'; 10 | 11 | // Token Program IDs 12 | export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); 13 | export const TOKEN_2022_PROGRAM_ID = new PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'); 14 | export const MEMO_PROGRAM_V2 = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'); 15 | 16 | // Account layout constant 17 | export const ACCOUNT_LAYOUT_LEN = 165; 18 | 19 | // WSOL constants 20 | export const WSOL = new PublicKey('So11111111111111111111111111111111111111112'); 21 | export const SOL_DECIMAL = 1e9; 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Axiom trading bots 2 | 3 | Axiom trading bots is api information about axiom.trade and using them to copy trade on raydium and pumpfun 4 | 5 | ## Contract 6 | 7 | If you wanna built more speed and profitable trading tools, contact here: [Telegram](https://t.me/shiny0103) | [Twitter](https://x.com/0xTan1319) 8 | 9 | ## Disclaimer 10 | 11 | - This project is not affiliated with Axiom.trade, and they do not endorse or support it in any way. 12 | 13 | - This is not an official product of Axiom.trade. 14 | 15 | - The bot has not been extensively tested, and I do not take responsibility for any losses incurred while using it. 16 | 17 | - I do not recommend putting money on it. Use it at your own risk. 18 | 19 | - The Raydium BUY transaction is disabled by default because it has not been tested yet. See below for enabling it. 20 | 21 | ## Environment Variables Explanation 22 | 23 | - RPC_URL_PUMP_FUN: The RPC URL used for transactions on Pump.fun. 24 | 25 | - RPC_URL_RAYDIUM: The RPC URL used for transactions on Raydium. 26 | 27 | - WALLET_PUBLIC_KEY: Your Solana wallet public key. 28 | 29 | - WALLET_PRIVATE_KEY: Your private key in Base58 format (can be extracted from Phantom Wallet - Guide). 30 | 31 | - TRACK_WALLETS: Wallets to track for copy trading. Multiple wallets can be separated using | (e.g., wallet1|wallet2|wallet3). 32 | 33 | - SOL_IN: The fixed amount of SOL to use for each buy order. 34 | 35 | - MAX_SOL_SPEND: The maximum amount of SOL that can be spent while the bot is running. If set to 0, there is no limit. 36 | 37 | - MAX_SLIPPAGE: The maximum slippage allowed for transactions before they fail. 38 | 39 | - ALLOW_REBUY: Defines whether the bot is allowed to rebuy the same token (true or false). 40 | 41 | - MAX_BUY_ATTEMPTS: The maximum number of times the bot will retry a failed buy transaction before giving up. 42 | DEBUG: If set to true, it enables detailed logs, including packet logs from WebSockets, for debugging purposes. 43 | -------------------------------------------------------------------------------- /raydium-copy-trading/utils/tick_array_bitmap_ext.ts: -------------------------------------------------------------------------------- 1 | const EXTENSION_TICKARRAY_BITMAP_SIZE = 14; 2 | 3 | type Uint8Array32 = Uint8Array & { length: 32 }; 4 | 5 | // Helper type for 8-element array of BigInt 6 | type Int64Array8 = [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]; 7 | 8 | export interface TickArrayBitmapExtension { 9 | // Padding(8) - typically ignored in high-level TypeScript representation 10 | poolId: Uint8Array32; // 32 bytes 11 | positiveTickArrayBitmap: Int64Array8[]; // length: EXTENSION_TICKARRAY_BITMAP_SIZE 12 | negativeTickArrayBitmap: Int64Array8[]; // length: EXTENSION_TICKARRAY_BITMAP_SIZE 13 | } 14 | 15 | // Binary encoding/decoding helpers (conceptual examples) 16 | 17 | // Parse 8 little-endian unsigned 64-bit integers 18 | function parseInt64Array8(view: DataView, offset: number): { value: Int64Array8, newOffset: number } { 19 | const arr: bigint[] = []; 20 | for (let i = 0; i < 8; i++) { 21 | arr.push(view.getBigUint64(offset, true)); 22 | offset += 8; 23 | } 24 | return { value: arr as Int64Array8, newOffset: offset }; 25 | } 26 | 27 | // Parse TickArrayBitmapExtension from binary data 28 | export function parseTickArrayBitmapExtension(buffer: ArrayBuffer): TickArrayBitmapExtension { 29 | const view = new DataView(buffer); 30 | 31 | let offset = 0; 32 | offset += 8; // Skip Padding(8) 33 | 34 | const poolIdArr = new Uint8Array(buffer, offset, 32) as Uint8Array32; 35 | offset += 32; 36 | 37 | const positiveTickArrayBitmap: Int64Array8[] = []; 38 | for (let i = 0; i < EXTENSION_TICKARRAY_BITMAP_SIZE; i++) { 39 | const parsed = parseInt64Array8(view, offset); 40 | positiveTickArrayBitmap.push(parsed.value); 41 | offset = parsed.newOffset; 42 | } 43 | 44 | const negativeTickArrayBitmap: Int64Array8[] = []; 45 | for (let i = 0; i < EXTENSION_TICKARRAY_BITMAP_SIZE; i++) { 46 | const parsed = parseInt64Array8(view, offset); 47 | negativeTickArrayBitmap.push(parsed.value); 48 | offset = parsed.newOffset; 49 | } 50 | 51 | return { 52 | poolId: poolIdArr, 53 | positiveTickArrayBitmap, 54 | negativeTickArrayBitmap, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /pumpfun-copy-trading/utils.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, Commitment, ParsedTransactionWithMeta } from '@solana/web3.js'; 2 | import { getCoinData } from './coin_data'; 3 | 4 | const connection = new Connection(process.env.RPC_URL_RAYDIUM || '', 'processed'); 5 | 6 | function sleep(ms: number): Promise { 7 | return new Promise(resolve => setTimeout(resolve, ms)); 8 | } 9 | 10 | export async function getTokenBalance(pubKey: PublicKey, mintStr: string): Promise { 11 | try { 12 | const mint = new PublicKey(mintStr); 13 | const response = await connection.getParsedTokenAccountsByOwner(pubKey, { mint }, 'processed'); 14 | if (response.value.length > 0) { 15 | const tokenAmount = response.value[0].account.data.parsed.info.tokenAmount.uiAmount; 16 | return Number(tokenAmount); 17 | } 18 | return null; 19 | } catch (error) { 20 | console.error(`[${new Date().toISOString()}] Error fetching token balance: ${error}`); 21 | return null; 22 | } 23 | } 24 | 25 | export async function confirmTxn(txnSig: string, maxRetries: number = 20, retryInterval: number = 3000): Promise { 26 | let retries = 1; 27 | while (retries < maxRetries) { 28 | try { 29 | const txnRes: ParsedTransactionWithMeta | null = await connection.getTransaction(txnSig, { 30 | commitment: 'confirmed' as Commitment, 31 | }); 32 | if (txnRes) { 33 | // Check if transaction executed without error 34 | if (txnRes.meta && txnRes.meta.err === null) { 35 | console.log(`[${new Date().toISOString()}] Transaction confirmed on try count: ${retries}`); 36 | return true; 37 | } 38 | if (txnRes.meta && txnRes.meta.err) { 39 | console.log(`[${new Date().toISOString()}] Transaction failed with error: ${JSON.stringify(txnRes.meta.err)}`); 40 | return false; 41 | } 42 | } else { 43 | console.log(`[${new Date().toISOString()}] Transaction not found. Retrying... Count: ${retries}`); 44 | } 45 | } catch (error) { 46 | // If an error occurs, just log and retry 47 | console.log(`[${new Date().toISOString()}] Awaiting confirmation... try count: ${retries}`); 48 | } 49 | retries++; 50 | await sleep(retryInterval); 51 | } 52 | console.log(`[${new Date().toISOString()}] Max retries reached. Transaction confirmation failed.`); 53 | return null; 54 | } 55 | 56 | export async function getTokenPrice(mintStr: string): Promise { 57 | try { 58 | const coinData = await getCoinData(mintStr); 59 | if (!coinData) { 60 | console.log(`[${new Date().toISOString()}] Failed to retrieve coin data!`); 61 | return null; 62 | } 63 | const virtualSolReserves = coinData.virtualSolReserves / 1e9; 64 | const virtualTokenReserves = coinData.virtualTokenReserves / 1e6; 65 | const tokenPrice = virtualSolReserves / virtualTokenReserves; 66 | console.log(`[${new Date().toISOString()}] Token Price: ${tokenPrice.toFixed(20)} SOL!`); 67 | return tokenPrice; 68 | } catch (error) { 69 | console.error(`[${new Date().toISOString()}] Error calculating token price: ${error}`); 70 | return null; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /PairInfo/example_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "createdAt": "2025-02-03T13:04:37.282Z", 3 | "deployerAddress": "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg", 4 | "discord": null, 5 | "extra": { 6 | "migratedFrom": "Pump V1", 7 | "pumpDeployerAddress": "6ycXq6DRXHu86WVzhxuZ3Bnp5nNsktdvFHGsw6twhbgB" 8 | }, 9 | "freezeAuthority": null, 10 | "initialLiquiditySol": 79.005359057, 11 | "initialLiquidityToken": 206900000, 12 | "isWatchlisted": false, 13 | "lpBurned": 99.9752661638717, 14 | "mintAuthority": null, 15 | "openTrading": "1970-01-01T00:00:00.000Z", 16 | "pairAddress": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 17 | "pairSolAccount": "BaSv8SZe1KjJJJ5epJJj4dviMpuCSNsJkx1gUjSnrKfJ", 18 | "pairTokenAccount": "A7EHZmDTNhrAyZNeZrfQU5xa7ZsibhEyVEECHkWrf6LP", 19 | "protocol": "Raydium V4", 20 | "protocolDetails": { 21 | "authority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1", 22 | "baseDecimals": 9, 23 | "baseMint": "So11111111111111111111111111111111111111112", 24 | "baseVault": "BaSv8SZe1KjJJJ5epJJj4dviMpuCSNsJkx1gUjSnrKfJ", 25 | "id": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 26 | "lookupTableAccount": "11111111111111111111111111111111", 27 | "lpDecimals": 0, 28 | "lpMint": "bRs9j3KURDFa2jkzFi38bhXvpvW9MZw44Nj2ZK5rGfN", 29 | "lpVault": "11111111111111111111111111111111", 30 | "marketAsks": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 31 | "marketAuthority": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 32 | "marketBaseVault": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 33 | "marketBids": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 34 | "marketEventQueue": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 35 | "marketId": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 36 | "marketProgramId": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 37 | "marketQuoteVault": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 38 | "marketVersion": 3, 39 | "openOrders": "DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB", 40 | "programId": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", 41 | "quoteDecimals": 6, 42 | "quoteMint": "9rPUZjQF33KvnGKgUreDnmqJeUaCm8Yx4adaGCj3pump", 43 | "quoteVault": "A7EHZmDTNhrAyZNeZrfQU5xa7ZsibhEyVEECHkWrf6LP", 44 | "targetOrders": "9DCxsMizn3H1hprZ7xWe6LDzeUeZBksYFpBWBtSf1PQX", 45 | "version": 4, 46 | "withdrawQueue": "11111111111111111111111111111111" 47 | }, 48 | "signature": "qMRAjtgN7rBNErwaoYcaMpKaZjAh2T6BQHWaNVncireNYuiF154YxFxfL2TfZh6JkiJPCRzdn7GLNEFsAUNLVjk", 49 | "slot": 318209187, 50 | "supply": 999936905.273771, 51 | "telegram": "https://t.me/McVALUE_SOL", 52 | "tokenAddress": "9rPUZjQF33KvnGKgUreDnmqJeUaCm8Yx4adaGCj3pump", 53 | "tokenDecimals": 6, 54 | "tokenImage": "https://axiomtrading.sfo3.cdn.digitaloceanspaces.com/DcSpjxxeZHJLPLpki5W1x7r9Nnn1E2C1Nyd9rjR1TczB.webp", 55 | "tokenName": "McDonald's McVALUE", 56 | "tokenTicker": "McVALUE", 57 | "tokenUri": "https://gateway.irys.xyz/hkO-AHdWOLD1Jz5M0pyJQxcpQZzVW9qWQkqbgDluUVo", 58 | "top10Holders": 15.7770636716853, 59 | "twitter": "https://x.com/McDonalds", 60 | "twitterHandleHistory": [ 61 | { 62 | "createdAt": "2024-10-10T07:17:24.591Z", 63 | "handle": "mcdonalds" 64 | } 65 | ], 66 | "updatedAt": "2025-02-03T21:38:29.755Z", 67 | "website": "https://mcdonalds.com" 68 | } -------------------------------------------------------------------------------- /raydium-copy-trading/utils/common_utils.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Commitment, GetTokenAccountsFilter } from '@solana/web3.js'; 2 | import { client, payerKeypair } from './config'; 3 | 4 | // Note: When calling "getTokenAccountsByOwner", ensure you use the "jsonParsed" encoding in your client configuration 5 | // if you need parsed account data. 12 6 | 7 | // Retrieves the token balance (uiAmount) for the first token account found for the given mint. 8 | export async function getTokenBalance(mintStr: string): Promise { 9 | const mint = new PublicKey(mintStr); 10 | const filters: GetTokenAccountsFilter = { mint }; 11 | const response = await client.getTokenAccountsByOwner(payerKeypair.publicKey, filters, { commitment: 'processed', encoding: 'jsonParsed' }); 12 | 13 | if (response.value && response.value.length > 0) { 14 | // Access the parsed account info 15 | const accountData = response.value[0].account.data; 16 | // Ensure that the "parsed" field exists and contains tokenAmount.uiAmount 17 | if ('parsed' in accountData) { 18 | const parsedInfo = (accountData as any).parsed?.info; 19 | const tokenAmount = parsedInfo?.tokenAmount?.uiAmount; 20 | if (tokenAmount !== null && tokenAmount !== undefined) { 21 | return parseFloat(tokenAmount); 22 | } 23 | } 24 | } 25 | return null; 26 | } 27 | 28 | // Confirms a transaction by repeatedly checking its status until confirmed, failed, or max retries reached. 29 | export async function confirmTxn( 30 | txnSig: string, 31 | maxRetries: number = 20, 32 | retryInterval: number = 3000 // in milliseconds 33 | ): Promise { 34 | let retries = 1; 35 | while (retries < maxRetries) { 36 | try { 37 | const txnRes = await client.getTransaction(txnSig, { 38 | encoding: 'json', 39 | commitment: 'confirmed' as Commitment, 40 | maxSupportedTransactionVersion: 0 41 | }); 42 | 43 | if (txnRes.value && txnRes.value.meta) { 44 | // If there is no error, the transaction is confirmed. 45 | if (txnRes.value.meta.err === null) { 46 | console.log(`[${new Date().toISOString()}] Transaction [RA] confirmed... try count: ${retries}!`); 47 | return true; 48 | } 49 | console.log(`[${new Date().toISOString()}] Transaction [RA] not confirmed. Retrying...`); 50 | // If there is an error, log it and return false. 51 | console.log(`[${new Date().toISOString()}] Transaction [RA] failed! Error: ${JSON.stringify(txnRes.value.meta.err)}`); 52 | return false; 53 | } 54 | } catch (error) { 55 | // If an error occurs (e.g. network issues), wait and retry. 56 | } 57 | retries++; 58 | // Wait for the specified retry interval before the next attempt. 59 | await new Promise((resolve) => setTimeout(resolve, retryInterval)); 60 | } 61 | // If max retries reached without confirmation, return null. 62 | return null; 63 | } 64 | -------------------------------------------------------------------------------- /raydium-copy-trading/layouts/cpmm.ts: -------------------------------------------------------------------------------- 1 | import * as BufferLayout from '@solana/buffer-layout'; 2 | import BN from 'bn.js'; 3 | 4 | /* UInt128 helper functions and layout */ 5 | function decodeUInt128(buffer: Buffer): BN { 6 | const low = new BN(buffer.slice(0, 8), 'le'); 7 | const high = new BN(buffer.slice(8, 16), 'le'); 8 | return high.shln(64).add(low); 9 | } 10 | 11 | function encodeUInt128(num: BN): Buffer { 12 | const low = num.maskn(64).toArrayLike(Buffer, 'le', 8); 13 | const high = num.shrn(64).toArrayLike(Buffer, 'le', 8); 14 | return Buffer.concat([low, high]); 15 | } 16 | 17 | const UInt128Layout = (property?: string) => { 18 | const layout = BufferLayout.struct([ 19 | BufferLayout.nu64('low'), 20 | BufferLayout.nu64('high') 21 | ], property); 22 | 23 | return { 24 | ...layout, 25 | decode: (buffer: Buffer, offset: number): BN => { 26 | const { low, high } = layout.decode(buffer, offset); 27 | return new BN(high).shln(64).add(new BN(low)); 28 | }, 29 | encode: (num: BN, buffer: Buffer, offset: number): number => { 30 | const high = num.shrn(64).toNumber(); 31 | const low = num.maskn(64).toNumber(); 32 | return layout.encode({ low, high }, buffer, offset); 33 | } 34 | }; 35 | }; 36 | 37 | /* CPMM_POOL_STATE_LAYOUT */ 38 | export const CPMM_POOL_STATE_LAYOUT = BufferLayout.struct([ 39 | BufferLayout.blob(8, 'padding'), 40 | BufferLayout.blob(32, 'amm_config'), 41 | BufferLayout.blob(32, 'pool_creator'), 42 | BufferLayout.blob(32, 'token_0_vault'), 43 | BufferLayout.blob(32, 'token_1_vault'), 44 | BufferLayout.blob(32, 'lp_mint'), 45 | BufferLayout.blob(32, 'token_0_mint'), 46 | BufferLayout.blob(32, 'token_1_mint'), 47 | BufferLayout.blob(32, 'token_0_program'), 48 | BufferLayout.blob(32, 'token_1_program'), 49 | BufferLayout.blob(32, 'observation_key'), 50 | BufferLayout.u8('auth_bump'), 51 | BufferLayout.u8('status'), 52 | BufferLayout.u8('lp_mint_decimals'), 53 | BufferLayout.u8('mint_0_decimals'), 54 | BufferLayout.u8('mint_1_decimals'), 55 | BufferLayout.nu64('lp_supply'), 56 | BufferLayout.nu64('protocol_fees_token_0'), 57 | BufferLayout.nu64('protocol_fees_token_1'), 58 | BufferLayout.nu64('fund_fees_token_0'), 59 | BufferLayout.nu64('fund_fees_token_1'), 60 | BufferLayout.nu64('open_time'), 61 | BufferLayout.seq(BufferLayout.nu64(), 32, 'padding') 62 | ]); 63 | 64 | /* AMM_CONFIG_LAYOUT */ 65 | export const AMM_CONFIG_LAYOUT = BufferLayout.struct([ 66 | BufferLayout.blob(8, 'padding'), 67 | BufferLayout.u8('bump'), 68 | BufferLayout.u8('disable_create_pool'), // Flag becomes u8 69 | BufferLayout.u16('index'), 70 | BufferLayout.nu64('trade_fee_rate'), 71 | BufferLayout.nu64('protocol_fee_rate'), 72 | BufferLayout.nu64('fund_fee_rate'), 73 | BufferLayout.nu64('create_pool_fee'), 74 | BufferLayout.blob(32, 'protocol_owner'), 75 | BufferLayout.blob(32, 'fund_owner'), 76 | BufferLayout.seq(BufferLayout.nu64(), 16, 'padding') 77 | ]); 78 | 79 | /* OBSERVATION structure */ 80 | export const OBSERVATION_LAYOUT = BufferLayout.struct([ 81 | BufferLayout.nu64('block_timestamp'), 82 | UInt128Layout('cumulative_token_0_price_x32'), 83 | UInt128Layout('cumulative_token_1_price_x32') 84 | ]); 85 | 86 | /* OBSERVATION_STATE structure */ 87 | export const OBSERVATION_STATE_LAYOUT = BufferLayout.struct([ 88 | BufferLayout.blob(8, 'padding'), 89 | BufferLayout.u8('initialized'), // Flag becomes u8 90 | BufferLayout.u16('observationIndex'), 91 | BufferLayout.blob(32, 'poolId'), 92 | // For GreedyRange, we'll need to implement custom parsing logic 93 | // Here's a fixed-size array as placeholder: 94 | BufferLayout.seq(OBSERVATION_LAYOUT, 1000, 'observations'), 95 | BufferLayout.seq(BufferLayout.nu64(), 32, 'padding') // Fixed size padding 96 | ]); 97 | 98 | // Types for the decoded structures 99 | export interface CpmmPoolState { 100 | amm_config: Buffer; 101 | pool_creator: Buffer; 102 | token_0_vault: Buffer; 103 | token_1_vault: Buffer; 104 | lp_mint: Buffer; 105 | token_0_mint: Buffer; 106 | token_1_mint: Buffer; 107 | token_0_program: Buffer; 108 | token_1_program: Buffer; 109 | observation_key: Buffer; 110 | auth_bump: number; 111 | status: number; 112 | lp_mint_decimals: number; 113 | mint_0_decimals: number; 114 | mint_1_decimals: number; 115 | lp_supply: BN; 116 | protocol_fees_token_0: BN; 117 | protocol_fees_token_1: BN; 118 | fund_fees_token_0: BN; 119 | fund_fees_token_1: BN; 120 | open_time: BN; 121 | } 122 | 123 | export interface AmmConfig { 124 | bump: number; 125 | disable_create_pool: number; 126 | index: nu 127 | -------------------------------------------------------------------------------- /pumpfun-copy-trading/coin_data.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | 3 | // Define interfaces for the parsed data structure 4 | interface VirtualReservesData { 5 | virtualTokenReserves: bigint; 6 | virtualSolReserves: bigint; 7 | realTokenReserves: bigint; 8 | realSolReserves: bigint; 9 | tokenTotalSupply: bigint; 10 | complete: boolean; 11 | } 12 | 13 | // Main CoinData class 14 | export class CoinData { 15 | constructor( 16 | public mint: PublicKey, 17 | public bondingCurve: PublicKey, 18 | public associatedBondingCurve: PublicKey, 19 | public virtualTokenReserves: number, 20 | public virtualSolReserves: number, 21 | public tokenTotalSupply: number, 22 | public complete: boolean 23 | ) {} 24 | } 25 | 26 | // Helper function to get virtual reserves 27 | export async function getVirtualReserves(bondingCurve: PublicKey): Promise { 28 | try { 29 | const response = await fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=ngn&order=market_cap_desc&per_page=250&page=1&sparkline=false'); 30 | const data = await response.json();5 31 | 32 | // Parse the account data according to the structure 33 | const parsedData: VirtualReservesData = { 34 | virtualTokenReserves: BigInt(data.virtualTokenReserves), 35 | virtualSolReserves: BigInt(data.virtualSolReserves), 36 | realTokenReserves: BigInt(data.realTokenReserves), 37 | realSolReserves: BigInt(data.realSolReserves), 38 | tokenTotalSupply: BigInt(data.tokenTotalSupply), 39 | complete: Boolean(data.complete) 40 | }; 41 | 42 | return parsedData; 43 | } catch (error) { 44 | console.error('Error fetching virtual reserves:', error); 45 | return null; 46 | } 47 | } 48 | 49 | // Helper function to derive bonding curve accounts 50 | export function deriveBondingCurveAccounts(mintStr: string): [PublicKey | null, PublicKey | null] { 51 | try { 52 | const mint = new PublicKey(mintStr); 53 | 54 | // Find program address 55 | const [bondingCurve] = PublicKey.findProgramAddressSync( 56 | [Buffer.from('bonding-curve'), mint.toBuffer()], 57 | new PublicKey(PUMP_FUN_PROGRAM) 58 | ); 59 | 60 | // Get associated token address 61 | const associatedBondingCurve = getAssociatedTokenAddress(bondingCurve, mint); 62 | 63 | return [bondingCurve, associatedBondingCurve]; 64 | } catch (error) { 65 | console.error('Error deriving bonding curve accounts:', error); 66 | return [null, null]; 67 | } 68 | } 69 | 70 | // Main function to get coin data 71 | export async function getCoinData(mintStr: string): Promise { 72 | const [bondingCurve, associatedBondingCurve] = deriveBondingCurveAccounts(mintStr); 73 | 74 | if (!bondingCurve || !associatedBondingCurve) { 75 | return null; 76 | } 77 | 78 | const virtualReserves = await getVirtualReserves(bondingCurve); 79 | if (!virtualReserves) { 80 | return null; 81 | } 82 | 83 | try { 84 | return new CoinData( 85 | new PublicKey(mintStr), 86 | bondingCurve, 87 | associatedBondingCurve, 88 | Number(virtualReserves.virtualTokenReserves), 89 | Number(virtualReserves.virtualSolReserves), 90 | Number(virtualReserves.tokenTotalSupply), 91 | virtualReserves.complete 92 | ); 93 | } catch (error) { 94 | console.error('Error creating CoinData:', error); 95 | return null; 96 | } 97 | } 98 | 99 | // Helper function to calculate SOL for tokens 100 | export function solForTokens( 101 | solSpent: number, 102 | solReserves: number, 103 | tokenReserves: number 104 | ): number { 105 | const newSolReserves = solReserves + solSpent; 106 | const newTokenReserves = (solReserves * tokenReserves) / newSolReserves; 107 | const tokenReceived = tokenReserves - newTokenReserves; 108 | 109 | return Math.round(tokenReceived); 110 | } 111 | 112 | // Helper function to calculate tokens for SOL 113 | export function tokensForSol( 114 | tokensToSell: number, 115 | solReserves: number, 116 | tokenReserves: number 117 | ): number { 118 | const newTokenReserves = tokenReserves + tokensToSell; 119 | const newSolReserves = (solReserves * tokenReserves) / newTokenReserves; 120 | const solReceived = solReserves - newSolReserves; 121 | 122 | return solReceived; 123 | } 124 | -------------------------------------------------------------------------------- /raydium-copy-trading/utils/api.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for the API responses and parameters 2 | interface PoolInfo { 3 | // Define pool info interface based on actual API response 4 | [key: string]: any; 5 | } 6 | 7 | interface PoolInfoResponse { 8 | error?: string; 9 | [key: string]: any; 10 | } 11 | 12 | interface PoolInfoByMintParams { 13 | mint1: string; 14 | poolType?: string; 15 | poolSortField?: string; 16 | sortType?: string; 17 | pageSize?: number; 18 | page?: number; 19 | } 20 | 21 | /** 22 | * Fetches pool information by pool ID 23 | */ 24 | async function getPoolInfoById(poolId: string): Promise { 25 | const baseUrl = 'https://api-v3.raydium.io/pools/info/ids'; 26 | // Fetch is used in the browser to interact with APIs over HTTP(S) 27 | // Fetch works well for this and is promise based5 28 | 29 | try { 30 | // Fetch is a promise-based HTTP client that's built into modern browsers 31 | // No need to install additional dependencies8 32 | 33 | const response = await fetch(`${baseUrl}?ids=${poolId}`); 34 | 35 | if (!response.ok) { 36 | throw new Error(`HTTP error! status: ${response.status}`); 37 | } 38 | 39 | return await response.json(); 40 | } catch (error) { 41 | return { 42 | error: `Failed to fetch pool info: ${error instanceof Error ? error.message : String(error)}` 43 | }; 44 | } 45 | } 46 | 47 | /** 48 | * Fetches pool information by mint address with optional parameters 49 | */ 50 | async function getPoolInfoByMint({ 51 | mint1, 52 | poolType = 'all', 53 | poolSortField = 'default', 54 | sortType = 'desc', 55 | pageSize = 100, 56 | page = 1 57 | }: PoolInfoByMintParams): Promise { 58 | const baseUrl = 'https://api-v3.raydium.io/pools/info/mint'; 59 | 60 | // When working with an API, you often want type safety 61 | // Create a generic way to handle fetch that can be extended for different use cases5 62 | 63 | // Construct URL with query parameters 64 | const params = new URLSearchParams({ 65 | mint1, 66 | poolType, 67 | poolSortField, 68 | sortType: sortType.toString(), 69 | pageSize: pageSize.toString(), 70 | page: page.toString() 71 | }); 72 | 73 | try { 74 | const response = await fetch(`${baseUrl}?${params}`); 75 | 76 | if (!response.ok) { 77 | throw new Error(`HTTP error! status: ${response.status}`); 78 | } 79 | 80 | return await response.json(); 81 | } catch (error) { 82 | // Handle both HTTP response errors (401/404 etc.) 83 | // and fetch errors (failed to fetch) 84 | // These aren't server specific5 85 | return { 86 | error: `Failed to fetch pair address: ${error instanceof Error ? error.message : String(error)}` 87 | }; 88 | } 89 | } 90 | 91 | // Example usage: 92 | /* 93 | // Get pool info by ID 94 | const poolInfo = await getPoolInfoById("your-pool-id"); 95 | 96 | // Get pool info by mint address 97 | const mintInfo = await getPoolInfoByMint({ 98 | mint1: "your-mint-address", 99 | poolType: "all", 100 | pageSize: 50, 101 | page: 1 102 | }); 103 | */ 104 | 105 | export { getPoolInfoById, getPoolInfoByMint }; 106 | -------------------------------------------------------------------------------- /raydium-copy-trading/layouts/amm_v4.ts: -------------------------------------------------------------------------------- 1 | import * as BufferLayout from '@solana/buffer-layout'; 2 | import BN from 'bn.js'; 3 | 4 | /** 5 | * Helper: define a layout representing an unsigned 64-bit integer. 6 | * The value is stored as an 8-byte blob that you can convert to BN. 7 | */ 8 | export const uint64 = (property?: string) => 9 | BufferLayout.blob(8, property); 10 | 11 | /** 12 | * LIQUIDITY_STATE_LAYOUT_V4 13 | * This layout follows the order and types of the original Construct schema. 14 | */ 15 | export const LIQUIDITY_STATE_LAYOUT_V4 = BufferLayout.struct([ 16 | uint64('status'), 17 | uint64('nonce'), 18 | uint64('orderNum'), 19 | uint64('depth'), 20 | uint64('coinDecimals'), 21 | uint64('pcDecimals'), 22 | uint64('state'), 23 | uint64('resetFlag'), 24 | uint64('minSize'), 25 | uint64('volMaxCutRatio'), 26 | uint64('amountWaveRatio'), 27 | uint64('coinLotSize'), 28 | uint64('pcLotSize'), 29 | uint64('minPriceMultiplier'), 30 | uint64('maxPriceMultiplier'), 31 | uint64('systemDecimalsValue'), 32 | uint64('minSeparateNumerator'), 33 | uint64('minSeparateDenominator'), 34 | uint64('tradeFeeNumerator'), 35 | uint64('tradeFeeDenominator'), 36 | uint64('pnlNumerator'), 37 | uint64('pnlDenominator'), 38 | uint64('swapFeeNumerator'), 39 | uint64('swapFeeDenominator'), 40 | uint64('needTakePnlCoin'), 41 | uint64('needTakePnlPc'), 42 | uint64('totalPnlPc'), 43 | uint64('totalPnlCoin'), 44 | uint64('poolOpenTime'), 45 | uint64('punishPcAmount'), 46 | uint64('punishCoinAmount'), 47 | uint64('orderbookToInitTime'), 48 | // For BytesInteger(16, signed=false, swapped=true) we use a 16-byte blob. 49 | BufferLayout.blob(16, 'swapCoinInAmount'), 50 | BufferLayout.blob(16, 'swapPcOutAmount'), 51 | uint64('swapCoin2PcFee'), 52 | BufferLayout.blob(16, 'swapPcInAmount'), 53 | BufferLayout.blob(16, 'swapCoinOutAmount'), 54 | uint64('swapPc2CoinFee'), 55 | BufferLayout.blob(32, 'poolCoinTokenAccount'), 56 | BufferLayout.blob(32, 'poolPcTokenAccount'), 57 | BufferLayout.blob(32, 'coinMintAddress'), 58 | BufferLayout.blob(32, 'pcMintAddress'), 59 | BufferLayout.blob(32, 'lpMintAddress'), 60 | BufferLayout.blob(32, 'ammOpenOrders'), 61 | BufferLayout.blob(32, 'serumMarket'), 62 | BufferLayout.blob(32, 'serumProgramId'), 63 | BufferLayout.blob(32, 'ammTargetOrders'), 64 | BufferLayout.blob(32, 'poolWithdrawQueue'), 65 | BufferLayout.blob(32, 'poolTempLpTokenAccount'), 66 | BufferLayout.blob(32, 'ammOwner'), 67 | BufferLayout.blob(32, 'pnlOwner'), 68 | ]); 69 | 70 | /** 71 | * ACCOUNT_FLAGS_LAYOUT 72 | * Originally defined as a bit‑structured layout with seven flag fields and 57 padding bits. 73 | * Here we choose to store the 8 bytes as a single blob; later you can decode the individual bit flags. 74 | */ 75 | export const ACCOUNT_FLAGS_LAYOUT = BufferLayout.blob(8, 'accountFlags'); 76 | 77 | /** 78 | * MARKET_STATE_LAYOUT_V3 79 | */ 80 | export const MARKET_STATE_LAYOUT_V3 = BufferLayout.struct([ 81 | BufferLayout.blob(5, 'padding1'), 82 | ACCOUNT_FLAGS_LAYOUT, // 8 bytes for account flags 83 | BufferLayout.blob(32, 'own_address'), 84 | uint64('vault_signer_nonce'), 85 | BufferLayout.blob(32, 'base_mint'), 86 | BufferLayout.blob(32, 'quote_mint'), 87 | BufferLayout.blob(32, 'base_vault'), 88 | uint64('base_deposits_total'), 89 | uint64('base_fees_accrued'), 90 | BufferLayout.blob(32, 'quote_vault'), 91 | uint64('quote_deposits_total'), 92 | uint64('quote_fees_accrued'), 93 | uint64('quote_dust_threshold'), 94 | BufferLayout.blob(32, 'request_queue'), 95 | BufferLayout.blob(32, 'event_queue'), 96 | BufferLayout.blob(32, 'bids'), 97 | BufferLayout.blob(32, 'asks'), 98 | uint64('base_lot_size'), 99 | uint64('quote_lot_size'), 100 | uint64('fee_rate_bps'), 101 | uint64('referrer_rebate_accrued'), 102 | BufferLayout.blob(7, 'padding2'), 103 | ]); 104 | 105 | /** 106 | * OPEN_ORDERS_LAYOUT 107 | */ 108 | export const OPEN_ORDERS_LAYOUT = BufferLayout.struct([ 109 | BufferLayout.blob(5, 'padding1'), 110 | ACCOUNT_FLAGS_LAYOUT, 111 | BufferLayout.blob(32, 'market'), 112 | BufferLayout.blob(32, 'owner'), 113 | uint64('base_token_free'), 114 | uint64('base_token_total'), 115 | uint64('quote_token_free'), 116 | uint64('quote_token_total'), 117 | BufferLayout.blob(16, 'free_slot_bits'), 118 | BufferLayout.blob(16, 'is_bid_bits'), 119 | // For orders, we replicate a 16-byte blob 128 times. 120 | BufferLayout.seq(BufferLayout.blob(16), 128, 'orders'), 121 | // For client_ids, we replicate a uint64 128 times. 122 | BufferLayout.seq(uint64(), 128, 'client_ids'), 123 | uint64('referrer_rebate_accrued'), 124 | BufferLayout.blob(7, 'padding2'), 125 | ]); 126 | 127 | /** 128 | * SWAP_LAYOUT 129 | */ 130 | export const SWAP_LAYOUT = BufferLayout.struct([ 131 | BufferLayout.u8('instruction'), 132 | uint64('amount_in'), 133 | uint64('min_amount_out'), 134 | ]); 135 | 136 | /** 137 | * PUBLIC_KEY_LAYOUT 138 | */ 139 | export const PUBLIC_KEY_LAYOUT = BufferLayout.blob(32, 'publicKey'); 140 | 141 | /** 142 | * ACCOUNT_LAYOUT 143 | */ 144 | export const ACCOUNT_LAYOUT = BufferLayout.struct([ 145 | BufferLayout.blob(32, 'mint'), 146 | BufferLayout.blob(32, 'owner'), 147 | uint64('amount'), 148 | BufferLayout.u32('delegate_option'), 149 | BufferLayout.blob(32, 'delegate'), 150 | BufferLayout.u8('state'), 151 | BufferLayout.u32('is_native_option'), 152 | uint64('is_native'), 153 | uint64('delegated_amount'), 154 | BufferLayout.u32('close_authority_option'), 155 | BufferLayout.blob(32, 'close_authority'), 156 | ]); 157 | -------------------------------------------------------------------------------- /raydium-copy-trading/layouts/clmm.ts: -------------------------------------------------------------------------------- 1 | import * as BufferLayout from '@solana/buffer-layout'; 2 | import BN from 'bn.js'; 3 | 4 | /* Helper functions for UInt128 conversion. 5 | UInt128 is represented as 16 bytes in little‐endian order: 6 | first 8 bytes: low part, next 8 bytes: high part. 7 | */ 8 | function decodeUInt128(buffer: Buffer): BN { 9 | const low = new BN(buffer.slice(0, 8), 'le'); 10 | const high = new BN(buffer.slice(8, 16), 'le'); 11 | return high.shln(64).add(low); 12 | } 13 | 14 | function encodeUInt128(num: BN): Buffer { 15 | const low = num.maskn(64).toArrayLike(Buffer, 'le', 8); 16 | const high = num.shrn(64).toArrayLike(Buffer, 'le', 8); 17 | return Buffer.concat([low, high]); 18 | } 19 | 20 | /* UInt128 layout adapter: 21 | Wraps a 16-byte blob layout with a custom decode/encode. 22 | */ 23 | export const UInt128Layout = (property?: string) => { 24 | const blob = BufferLayout.blob(16, property); 25 | return Object.assign(blob, { 26 | decode(b: Buffer, offset = 0): BN { 27 | const buf = blob.decode(b, offset) as Buffer; 28 | return decodeUInt128(buf); 29 | }, 30 | encode(src: BN, b: Buffer, offset = 0): number { 31 | const buf = encodeUInt128(src); 32 | return blob.encode(buf, b, offset); 33 | } 34 | }); 35 | }; 36 | 37 | /* OBSERVATION structure */ 38 | export const OBSERVATION_LAYOUT = BufferLayout.struct([ 39 | BufferLayout.blob(8, 'block_timestamp'), // Int64ul (8 bytes) 40 | UInt128Layout('cumulative_token_0_price_x32'), 41 | UInt128Layout('cumulative_token_1_price_x32') 42 | ]); 43 | 44 | /* AMM_CONFIG_LAYOUT structure */ 45 | export const AMM_CONFIG_LAYOUT = BufferLayout.struct([ 46 | BufferLayout.blob(8, 'padding'), 47 | BufferLayout.u8('bump'), 48 | BufferLayout.u16('index'), 49 | BufferLayout.blob(32, 'owner'), 50 | BufferLayout.u32('protocol_fee_rate'), 51 | BufferLayout.u32('trade_fee_rate'), 52 | BufferLayout.u16('tick_spacing'), 53 | BufferLayout.u32('fund_fee_rate'), 54 | BufferLayout.u32('padding_u32'), 55 | BufferLayout.blob(32, 'fund_owner'), 56 | // An array of three 8-byte unsigned ints. 57 | BufferLayout.seq(BufferLayout.blob(8), 3, 'padding') 58 | ]); 59 | 60 | /* OBSERVATION_STATE_LAYOUT structure */ 61 | export const OBSERVATION_STATE_LAYOUT = BufferLayout.struct([ 62 | // Flag: we use 1 byte to represent a boolean. 63 | BufferLayout.u8('initialized'), 64 | BufferLayout.blob(32, 'pool_id'), 65 | // Array of 1000 OBSERVATIONs. 66 | BufferLayout.seq(OBSERVATION_LAYOUT, 1000, 'observations'), 67 | // Array of 5 UInt128 values. 68 | BufferLayout.seq(UInt128Layout(), 5, 'padding') 69 | ]); 70 | 71 | /* POSITION_REWARD_INFO structure */ 72 | export const POSITION_REWARD_INFO_LAYOUT = BufferLayout.struct([ 73 | UInt128Layout('reward_amount'), 74 | UInt128Layout('reward_growth_inside') 75 | ]); 76 | 77 | /* PERSONAL_POSITION_STATE_LAYOUT structure */ 78 | export const PERSONAL_POSITION_STATE_LAYOUT = BufferLayout.struct([ 79 | BufferLayout.blob(8, 'padding'), 80 | BufferLayout.u8('bump'), 81 | BufferLayout.blob(32, 'nft_mint'), 82 | BufferLayout.blob(32, 'pool_id'), 83 | // For signed 32-bit integers, use a custom s32 layout if necessary. 84 | BufferLayout.s32('tick_lower_index'), 85 | BufferLayout.s32('tick_upper_index'), 86 | UInt128Layout('liquidity'), 87 | UInt128Layout('fee_growth_inside_0_last_x64'), 88 | UInt128Layout('fee_growth_inside_1_last_x64'), 89 | BufferLayout.blob(8, 'token_fees_owed_0'), // Int64ul as 8-byte blob 90 | BufferLayout.blob(8, 'token_fees_owed_1'), 91 | // Array of 3 POSITION_REWARD_INFO 92 | BufferLayout.seq(POSITION_REWARD_INFO_LAYOUT, 3, 'reward_infos'), 93 | // Array of 8 Int64ul values (8 bytes each) 94 | BufferLayout.seq(BufferLayout.blob(8), 8, 'padding') 95 | ]); 96 | 97 | /* CLMM_POOL_STATE_LAYOUT structure */ 98 | export const CLMM_POOL_STATE_LAYOUT = BufferLayout.struct([ 99 | BufferLayout.blob(8, 'padding'), 100 | BufferLayout.u8('bump'), 101 | BufferLayout.blob(32, 'amm_config'), 102 | BufferLayout.blob(32, 'owner'), 103 | BufferLayout.blob(32, 'token_mint_0'), 104 | BufferLayout.blob(32, 'token_mint_1'), 105 | BufferLayout.blob(32, 'token_vault_0'), 106 | BufferLayout.blob(32, 'token_vault_1'), 107 | BufferLayout.blob(32, 'observation_key'), 108 | BufferLayout.u8('mint_decimals_0'), 109 | BufferLayout.u8('mint_decimals_1'), 110 | BufferLayout.u16('tick_spacing'), 111 | UInt128Layout('liquidity'), 112 | UInt128Layout('sqrt_price_x64'), 113 | BufferLayout.s32('tick_current'), 114 | BufferLayout.u16('observation_index'), 115 | BufferLayout.u16('observation_update_duration'), 116 | UInt128Layout('fee_growth_global_0_x64'), 117 | UInt128Layout('fee_growth_global_1_x64'), 118 | BufferLayout.blob(8, 'protocol_fees_token_0'), 119 | BufferLayout.blob(8, 'protocol_fees_token_1'), 120 | UInt128Layout('swap_in_amount_token_0'), 121 | UInt128Layout('swap_out_amount_token_1'), 122 | UInt128Layout('swap_in_amount_token_1'), 123 | UInt128Layout('swap_out_amount_token_0'), 124 | BufferLayout.u8('status'), 125 | BufferLayout.blob(7, 'padding_before_bitmap'), 126 | // Skip decoding of unknown 507-byte block: 127 | BufferLayout.blob(507, 'skipped'), 128 | // Array of 16 Int64ul values 129 | BufferLayout.seq(BufferLayout.blob(8), 16, 'tick_array_bitmap'), 130 | BufferLayout.blob(8, 'total_fees_token_0'), 131 | BufferLayout.blob(8, 'total_fees_claimed_token_0'), 132 | BufferLayout.blob(8, 'total_fees_token_1'), 133 | BufferLayout.blob(8, 'total_fees_claimed_token_1'), 134 | BufferLayout.blob(8, 'fund_fees_token_0'), 135 | BufferLayout.blob(8, 'fund_fees_token_1'), 136 | BufferLayout.seq(BufferLayout.blob(8), 26, 'padding1'), 137 | BufferLayout.seq(BufferLayout.blob(8), 32, 'padding2') 138 | ]); 139 | 140 | /* TICK_ARRAY_STATE_LAYOUT structure */ 141 | export const TICK_ARRAY_STATE_LAYOUT = BufferLayout.struct([ 142 | BufferLayout.blob(32, 'pool_id'), 143 | BufferLayout.s32('start_tick_index'), 144 | // ticks is an array of 60 elements, each is a struct. 145 | BufferLayout.seq( 146 | BufferLayout.struct([ 147 | BufferLayout.s32('tick_index'), 148 | BufferLayout.blob(8, 'liquidity_net'), 149 | UInt128Layout('liquidity_gross') 150 | ]), 151 | 60, 152 | 'ticks' 153 | ), 154 | BufferLayout.u8('initialized_tick_count'), 155 | BufferLayout.seq(BufferLayout.u8(), 115, 'padding') 156 | ]); 157 | 158 | /* PROTOCOL_POSITION_STATE_LAYOUT structure */ 159 | export const PROTOCOL_POSITION_STATE_LAYOUT = BufferLayout.struct([ 160 | BufferLayout.blob(8, 'padding'), 161 | BufferLayout.u8('bump'), 162 | BufferLayout.blob(32, 'pool_id'), 163 | BufferLayout.s32('tick_lower_index'), 164 | BufferLayout.s32('tick_upper_index'), 165 | UInt128Layout('liquidity'), 166 | UInt128Layout('fee_growth_inside_0_last_x64'), 167 | UInt128Layout('fee_growth_inside_1_last_x64'), 168 | BufferLayout.blob(8, 'token_fees_owed_0'), 169 | BufferLayout.blob(8, 'token_fees_owed_1'), 170 | BufferLayout.seq(UInt128Layout(), 3, 'reward_growth_inside'), 171 | BufferLayout.seq(BufferLayout.blob(8), 8, 'padding') 172 | ]); 173 | -------------------------------------------------------------------------------- /pumpfun-copy-trading/pump_fun.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Transaction, 5 | TransactionInstruction, 6 | VersionedTransaction, 7 | MessageV0, 8 | AccountMeta 9 | } from '@solana/web3.js'; 10 | import { TOKEN_PROGRAM_ID, createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, createCloseAccountInstruction } from '@solana/spl-token'; 11 | import { Buffer } from 'buffer'; 12 | 13 | // Utility function to pack numbers into Buffer 14 | function packUint64LE(num: number): Buffer { 15 | const arr = new ArrayBuffer(8); 16 | const view = new DataView(arr); 17 | view.setBigUint64(0, BigInt(num), true); 18 | return Buffer.from(arr); 19 | } 20 | 21 | export async function buy( 22 | connection: Connection, 23 | mint: string, 24 | solIn: number = 0.01, 25 | slippage: number = 5 26 | ): Promise { 27 | try { 28 | const coinData = await getCoinData(mint); 29 | if (!coinData) { 30 | console.log(`Failed to retrieve coin data while buying!`); 31 | return false; 32 | } 33 | 34 | if (coinData.complete) { 35 | console.log(`Warning: This token has bonded and is only buyable on Raydium.`); 36 | return false; 37 | }4 38 | 39 | const MINT = new PublicKey(mint); 40 | const USER = payer.publicKey; 41 | 42 | // Get or create associated token account 43 | let ASSOCIATED_USER: PublicKey; 44 | let tokenAccountInstruction: TransactionInstruction | null = null; 45 | 46 | try { 47 | const tokenAccounts = await connection.getTokenAccountsByOwner(USER, { mint: MINT }); 48 | ASSOCIATED_USER = tokenAccounts.value[0].pubkey; 49 | } catch { 50 | ASSOCIATED_USER = await getAssociatedTokenAddress(MINT, USER); 51 | tokenAccountInstruction = createAssociatedTokenAccountInstruction( 52 | USER, 53 | ASSOCIATED_USER, 54 | USER, 55 | MINT 56 | ); 57 | }4 58 | 59 | // Calculate amounts 60 | const SOL_DECIMALS = 1e9; 61 | const TOKEN_DECIMALS = 1e6; 62 | 63 | const virtualSolReserves = coinData.virtualSolReserves / SOL_DECIMALS; 64 | const virtualTokenReserves = coinData.virtualTokenReserves / TOKEN_DECIMALS; 65 | 66 | const amount = solForTokens(solIn, virtualSolReserves, virtualTokenReserves); 67 | const amountWithDecimals = Math.floor(amount * TOKEN_DECIMALS); 68 | 69 | const slippageAdjustment = 1 + (slippage / 100); 70 | const maxSolCost = Math.floor((solIn * slippageAdjustment) * SOL_DECIMALS); 71 | 72 | // Create swap instruction 73 | const instructions: TransactionInstruction[] = [ 74 | ComputeBudgetProgram.setComputeUnitLimit({ units: UNIT_BUDGET }), 75 | ComputeBudgetProgram.setComputeUnitPrice({ microLamports: UNIT_PRICE }) 76 | ]; 77 | 78 | if (tokenAccountInstruction) { 79 | instructions.push(tokenAccountInstruction); 80 | }7 81 | 82 | // Construct swap data 83 | const data = Buffer.concat([ 84 | Buffer.from('66063d1201daebea', 'hex'), 85 | packUint64LE(amountWithDecimals), 86 | packUint64LE(maxSolCost) 87 | ]); 88 | 89 | const swapInstruction = new TransactionInstruction({ 90 | programId: PUMP_FUN_PROGRAM, 91 | keys: [ 92 | {pubkey: GLOBAL, isSigner: false, isWritable: false}, 93 | {pubkey: FEE_RECIPIENT, isSigner: false, isWritable: true}, 94 | // ... other account metas 95 | ], 96 | data 97 | }); 98 | 99 | instructions.push(swapInstruction); 100 | 101 | // Add priority fee if needed for transaction confirmation 102 | const priorityFee = ComputeBudgetProgram.setComputeUnitPrice({ 103 | microLamports: UNIT_PRICE 104 | }); 105 | instructions.push(priorityFee);7 106 | 107 | // Create and send transaction 108 | const latestBlockhash = await connection.getLatestBlockhash(); 109 | 110 | const messageV0 = MessageV0.compile({ 111 | payerKey: payer.publicKey, 112 | instructions, 113 | recentBlockhash: latestBlockhash.blockhash 114 | }); 115 | 116 | const transaction = new VersionedTransaction(messageV0); 117 | transaction.sign([payer]); 118 | 119 | const signature = await connection.sendTransaction(transaction, { 120 | skipPreflight: true, 121 | maxRetries: 3 122 | }); 123 | 124 | const confirmation = await connection.confirmTransaction(signature); 125 | 126 | console.log(`Buy transaction confirmed: ${confirmation.value.err === null}`); 127 | return confirmation.value.err === null; 128 | 129 | } catch (error) { 130 | console.error(`Error occurred during buy transaction: ${error}`); 131 | return false; 132 | } 133 | } 134 | 135 | // Sell function follows similar pattern with sell-specific logic 136 | export async function sell( 137 | connection: Connection, 138 | mint: string, 139 | percentage: number = 100, 140 | slippage: number = 5 141 | ): Promise { 142 | try { 143 | if (percentage < 1 || percentage > 100) { 144 | console.log('Percentage must be between 1 and 100'); 145 | return false; 146 | } 147 | 148 | const coinData = await getCoinData(mint); 149 | if (!coinData) { 150 | console.log('Failed to retrieve coin data while selling'); 151 | return false; 152 | } 153 | 154 | // Get token balance 155 | const tokenBalance = await getTokenBalance(payer.publicKey, mint); 156 | if (!tokenBalance || tokenBalance === 0) { 157 | console.log('Token balance is zero. Nothing to sell.'); 158 | return null; 159 | }4 160 | 161 | // Calculate sell amounts 162 | const adjustedBalance = tokenBalance * (percentage / 100); 163 | const amount = Math.floor(adjustedBalance * 1e6); 164 | 165 | const solOut = tokensForSol( 166 | adjustedBalance, 167 | coinData.virtualSolReserves / 1e9, 168 | coinData.virtualTokenReserves / 1e6 169 | ); 170 | 171 | const slippageAdjustment = 1 - (slippage / 100); 172 | const minSolOutput = Math.floor((solOut * slippageAdjustment) * 1e9); 173 | 174 | // Create instructions array similar to buy function 175 | const instructions: TransactionInstruction[] = []; 176 | 177 | // Add sell instruction 178 | const sellData = Buffer.concat([ 179 | Buffer.from('33e685a4017f83ad', 'hex'), 180 | packUint64LE(amount), 181 | packUint64LE(minSolOutput) 182 | ]); 183 | 184 | // ... rest of sell implementation following similar pattern to buy 185 | 186 | return true; 187 | 188 | } catch (error) { 189 | console.error(`Error occurred during sell transaction: ${error}`); 190 | return false; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /raydium-copy-trading/raydium/amm_v4.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | TransactionInstruction, 5 | SystemProgram, 6 | VersionedTransaction, 7 | MessageV0, 8 | ComputeBudgetProgram 9 | } from "@solana/web3.js"; 10 | import { 11 | TOKEN_PROGRAM_ID, 12 | ASSOCIATED_TOKEN_PROGRAM_ID, 13 | AccountLayout, 14 | createInitializeAccountInstruction, 15 | createAssociatedTokenAccountInstruction, 16 | createCloseAccountInstruction, 17 | getAssociatedTokenAddress, 18 | getMinimumBalanceForRentExemptAccount 19 | } from "@solana/spl-token"; 20 | import crypto from "crypto"; 21 | 22 | // These functions are assumed to be defined in your application’s utilities. 23 | import { 24 | fetchAmmV4PoolKeys, 25 | getAmmV4Reserves, 26 | makeAmmV4SwapInstruction 27 | } from "./pool_utils"; 28 | import { confirmTxn, getTokenBalance } from "./common_utils"; 29 | import { client, payerKeypair, UNIT_BUDGET, UNIT_PRICE, SOL_DECIMAL, WSOL } from "./config"; 30 | 31 | /** 32 | * Calculate the number of tokens received for a given SOL amount, 33 | * using the constant product invariant and accounting for the swap fee. 34 | */ 35 | function solForTokens( 36 | solAmount: number, 37 | baseVaultBalance: number, 38 | quoteVaultBalance: number, 39 | swapFee = 0.25 40 | ): number { 41 | const effectiveSolUsed = solAmount - solAmount * (swapFee / 100); 42 | const constantProduct = baseVaultBalance * quoteVaultBalance; 43 | const updatedBaseVaultBalance = constantProduct / (quoteVaultBalance + effectiveSolUsed); 44 | const tokensReceived = baseVaultBalance - updatedBaseVaultBalance; 45 | return Number(tokensReceived.toFixed(9)); 46 | } 47 | 48 | /** 49 | * Calculate the amount of SOL received for a given token amount, 50 | * using the constant product invariant and accounting for the swap fee. 51 | */ 52 | function tokensForSol( 53 | tokenAmount: number, 54 | baseVaultBalance: number, 55 | quoteVaultBalance: number, 56 | swapFee = 0.25 57 | ): number { 58 | const effectiveTokensSold = tokenAmount * (1 - swapFee / 100); 59 | const constantProduct = baseVaultBalance * quoteVaultBalance; 60 | const updatedQuoteVaultBalance = constantProduct / (baseVaultBalance + effectiveTokensSold); 61 | const solReceived = quoteVaultBalance - updatedQuoteVaultBalance; 62 | return Number(solReceived.toFixed(9)); 63 | } 64 | 65 | /** 66 | * Buy function: executes a swap from SOL to tokens via the AMM V4 pool. 67 | */ 68 | export async function buy(pairAddress: string, solIn = 0.01, slippage = 5): Promise { 69 | try { 70 | console.log(`Starting buy transaction for pair address: ${pairAddress}`); 71 | console.log("Fetching pool keys..."); 72 | const poolKeys = await fetchAmmV4PoolKeys(pairAddress); 73 | if (!poolKeys) { 74 | console.log("No pool keys found..."); 75 | return false; 76 | } 77 | console.log("Pool keys fetched successfully."); 78 | 79 | // Use the base mint unless it is WSOL; otherwise, use the quote mint. 80 | const mint = 81 | poolKeys.baseMint.toString() !== WSOL.toString() ? poolKeys.baseMint : poolKeys.quoteMint; 82 | 83 | console.log("Calculating transaction amounts..."); 84 | const amountIn = Math.floor(solIn * SOL_DECIMAL); 85 | const { baseReserve, quoteReserve, tokenDecimal } = await getAmmV4Reserves(poolKeys); 86 | const amountOut = solForTokens(solIn, baseReserve, quoteReserve); 87 | console.log(`Estimated Amount Out: ${amountOut}`); 88 | 89 | const slippageAdjustment = 1 - slippage / 100; 90 | const amountOutWithSlippage = amountOut * slippageAdjustment; 91 | const minimumAmountOut = Math.floor(amountOutWithSlippage * Math.pow(10, tokenDecimal)); 92 | console.log(`Amount In: ${amountIn} | Minimum Amount Out: ${minimumAmountOut}`); 93 | 94 | console.log("Checking for existing token account..."); 95 | const tokenAccounts = await client.getTokenAccountsByOwner(payerKeypair.publicKey, { 96 | mint: mint 97 | }); 98 | let tokenAccount: PublicKey; 99 | let createTokenAccountInstruction: TransactionInstruction | null = null; 100 | if (tokenAccounts.value.length > 0) { 101 | tokenAccount = new PublicKey(tokenAccounts.value[0].pubkey); 102 | console.log("Token account found."); 103 | } else { 104 | tokenAccount = await getAssociatedTokenAddress(payerKeypair.publicKey, mint); 105 | createTokenAccountInstruction = createAssociatedTokenAccountInstruction( 106 | payerKeypair.publicKey, 107 | tokenAccount, 108 | payerKeypair.publicKey, 109 | mint 110 | ); 111 | console.log("No existing token account found; creating associated token account."); 112 | } 113 | 114 | // Generate a random seed for the WSOL account. 115 | const seed = crypto.randomBytes(24).toString("base64url"); 116 | const wsolTokenAccount = await PublicKey.createWithSeed( 117 | payerKeypair.publicKey, 118 | seed, 119 | TOKEN_PROGRAM_ID 120 | ); 121 | const balanceNeeded = await getMinimumBalanceForRentExemptAccount(client); 122 | 123 | // Create and initialize the WSOL account. 124 | const createWsolAccountIx = SystemProgram.createAccountWithSeed({ 125 | fromPubkey: payerKeypair.publicKey, 126 | basePubkey: payerKeypair.publicKey, 127 | seed: seed, 128 | newAccountPubkey: wsolTokenAccount, 129 | lamports: balanceNeeded + amountIn, 130 | space: AccountLayout.span, 131 | programId: TOKEN_PROGRAM_ID 132 | }); 133 | const initWsolAccountIx = createInitializeAccountInstruction( 134 | wsolTokenAccount, 135 | WSOL, 136 | payerKeypair.publicKey, 137 | TOKEN_PROGRAM_ID 138 | ); 139 | 140 | console.log("Creating swap instruction..."); 141 | const swapInstruction = await makeAmmV4SwapInstruction({ 142 | amountIn, 143 | minimumAmountOut, 144 | tokenAccountIn: wsolTokenAccount, 145 | tokenAccountOut: tokenAccount, 146 | accounts: poolKeys, 147 | owner: payerKeypair.publicKey 148 | }); 149 | 150 | console.log("Preparing to close WSOL account after swap..."); 151 | const closeWsolAccountIx = createCloseAccountInstruction( 152 | wsolTokenAccount, 153 | payerKeypair.publicKey, 154 | payerKeypair.publicKey, 155 | [], 156 | TOKEN_PROGRAM_ID 157 | ); 158 | 159 | const instructions: TransactionInstruction[] = [ 160 | ComputeBudgetProgram.setComputeUnitLimit(UNIT_BUDGET), 161 | ComputeBudgetProgram.setComputeUnitPrice(UNIT_PRICE), 162 | createWsolAccountIx, 163 | initWsolAccountIx 164 | ]; 165 | if (createTokenAccountInstruction) { 166 | instructions.push(createTokenAccountInstruction); 167 | } 168 | instructions.push(swapInstruction); 169 | instructions.push(closeWsolAccountIx); 170 | 171 | // Compile the transaction message using the Versioned Transaction API. 172 | const latestBlockhash = (await client.getLatestBlockhash()).blockhash; 173 | const messageV0 = new MessageV0({ 174 | payerKey: payerKeypair.publicKey, 175 | instructions, 176 | recentBlockhash: latestBlockhash 177 | }); 178 | const txn = new VersionedTransaction(messageV0); 179 | txn.sign([payerKeypair]); 180 | 181 | console.log("Sending transaction..."); 182 | const txnSig = await client.sendTransaction(txn, { skipPreflight: true }); 183 | console.log(`[${new Date().toISOString()}] Buy [RA] transaction Signature: ${txnSig}`); 184 | 185 | const confirmed = await confirmTxn(txnSig); 186 | console.log(`[${new Date().toISOString()}] Buy [RA] transaction confirmed: ${confirmed}`); 187 | 188 | return confirmed; 189 | } catch (e) { 190 | console.error( 191 | `[${new Date().toISOString()}] Error [RA] occurred during buy transaction: ${e}` 192 | ); 193 | return false; 194 | } 195 | } 196 | 197 | /** 198 | * Sell function: executes a swap from tokens to SOL via the AMM V4 pool. 199 | */ 200 | export async function sell(pairAddress: string, percentage = 100, slippage = 5): Promise { 201 | try { 202 | if (percentage < 1 || percentage > 100) { 203 | console.log(`[${new Date().toISOString()}] Percentage [RA] must be between 1 and 100.`); 204 | return false; 205 | } 206 | 207 | const poolKeys = await fetchAmmV4PoolKeys(pairAddress); 208 | if (!poolKeys) { 209 | console.log(`[${new Date().toISOString()}] No pool keys [RA] found...`); 210 | return false; 211 | } 212 | const mint = 213 | poolKeys.baseMint.toString() !== WSOL.toString() ? poolKeys.baseMint : poolKeys.quoteMint; 214 | 215 | let tokenBalance = await getTokenBalance(mint.toString()); 216 | if (!tokenBalance || tokenBalance === 0) { 217 | console.log(`[${new Date().toISOString()}] Token balance [RA] is zero. Nothing to sell.`); 218 | return false; 219 | } 220 | console.log(`[${new Date().toISOString()}] Sell [RA] token Balance: ${tokenBalance}`); 221 | tokenBalance = tokenBalance * (percentage / 100); 222 | 223 | const { baseReserve, quoteReserve, tokenDecimal } = await getAmmV4Reserves(poolKeys); 224 | const amountOut = tokensForSol(tokenBalance, baseReserve, quoteReserve); 225 | const slippageAdjustment = 1 - slippage / 100; 226 | const amountOutWithSlippage = amountOut * slippageAdjustment; 227 | const minimumAmountOut = Math.floor(amountOutWithSlippage * SOL_DECIMAL); 228 | const amountIn = Math.floor(tokenBalance * Math.pow(10, tokenDecimal)); 229 | 230 | const tokenAccount = await getAssociatedTokenAddress(payerKeypair.publicKey, mint); 231 | 232 | const seed = crypto.randomBytes(24).toString("base64url"); 233 | const wsolTokenAccount = await PublicKey.createWithSeed( 234 | payerKeypair.publicKey, 235 | seed, 236 | TOKEN_PROGRAM_ID 237 | ); 238 | const balanceNeeded = await getMinimumBalanceForRentExemptAccount(client); 239 | 240 | const createWsolAccountIx = SystemProgram.createAccountWithSeed({ 241 | fromPubkey: payerKeypair.publicKey, 242 | basePubkey: payerKeypair.publicKey, 243 | seed: seed, 244 | newAccountPubkey: wsolTokenAccount, 245 | lamports: balanceNeeded, 246 | space: AccountLayout.span, 247 | programId: TOKEN_PROGRAM_ID 248 | }); 249 | const initWsolAccountIx = createInitializeAccountInstruction( 250 | wsolTokenAccount, 251 | WSOL, 252 | payerKeypair.publicKey, 253 | TOKEN_PROGRAM_ID 254 | ); 255 | 256 | const swapInstruction = await makeAmmV4SwapInstruction({ 257 | amountIn, 258 | minimumAmountOut, 259 | tokenAccountIn: tokenAccount, 260 | tokenAccountOut: wsolTokenAccount, 261 | accounts: poolKeys, 262 | owner: payerKeypair.publicKey 263 | }); 264 | const closeWsolAccountIx = createCloseAccountInstruction( 265 | wsolTokenAccount, 266 | payerKeypair.publicKey, 267 | payerKeypair.publicKey, 268 | [], 269 | TOKEN_PROGRAM_ID 270 | ); 271 | 272 | const instructions: TransactionInstruction[] = [ 273 | ComputeBudgetProgram.setComputeUnitLimit(UNIT_BUDGET), 274 | ComputeBudgetProgram.setComputeUnitPrice(UNIT_PRICE), 275 | createWsolAccountIx, 276 | initWsolAccountIx, 277 | swapInstruction, 278 | closeWsolAccountIx 279 | ]; 280 | 281 | if (percentage === 100) { 282 | const closeTokenAccountIx = createCloseAccountInstruction( 283 | tokenAccount, 284 | payerKeypair.publicKey, 285 | payerKeypair.publicKey, 286 | [], 287 | TOKEN_PROGRAM_ID 288 | ); 289 | instructions.push(closeTokenAccountIx); 290 | } 291 | 292 | const latestBlockhash = (await client.getLatestBlockhash()).blockhash; 293 | const messageV0 = new MessageV0({ 294 | payerKey: payerKeypair.publicKey, 295 | instructions, 296 | recentBlockhash: latestBlockhash 297 | }); 298 | const txn = new VersionedTransaction(messageV0); 299 | txn.sign([payerKeypair]); 300 | 301 | console.log("Sending transaction..."); 302 | const txnSig = await client.sendTransaction(txn, { skipPreflight: true }); 303 | console.log(`[${new Date().toISOString()}] Sell [RA] transaction Signature: ${txnSig}`); 304 | 305 | const confirmed = await confirmTxn(txnSig); 306 | console.log(`[${new Date().toISOString()}] Sell [RA] transaction confirmed: ${confirmed}`); 307 | 308 | return confirmed; 309 | } catch (e) { 310 | console.error( 311 | `[${new Date().toISOString()}] Error [RA] occurred during sell transaction: ${e}` 312 | ); 313 | return false; 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /raydium-copy-trading/raydium/clmm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | TransactionInstruction, 5 | SystemProgram, 6 | VersionedTransaction, 7 | Message0, 8 | ComputeBudgetProgram, 9 | TransactionOptions 10 | } from "@solana/web3.js"; 11 | import { 12 | TOKEN_PROGRAM_ID, 13 | ASSOCIATED_TOKEN_PROGRAM_ID, 14 | AccountLayout, 15 | createInitializeAccountInstruction, 16 | createAssociatedTokenAccountInstruction, 17 | createCloseAccountInstruction, 18 | getAssociatedTokenAddress, 19 | getMinimumBalanceForRentExemptAccount 20 | } from "@solana/spl-token"; 21 | import crypto from "crypto"; 22 | 23 | // These utility functions must be defined elsewhere in your project. 24 | import { 25 | fetchClmmPoolKeys, 26 | makeClmmSwapInstruction, 27 | DIRECTION 28 | } from "./clmm_pool_utils"; // adjust import path accordingly 29 | import { confirmTxn, getTokenBalance } from "./common_utils"; 30 | import { client, payerKeypair, UNIT_BUDGET, UNIT_PRICE, SOL_DECIMAL, WSOL } from "./config"; 31 | import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; 32 | 33 | // Helper: Convert sqrtPriceX64 to token price. 34 | export function sqrtPriceX64ToTokenPrice( 35 | sqrtPriceX64: bigint | number, 36 | mintDecimals0: number, 37 | mintDecimals1: number 38 | ): number { 39 | // Use bigint arithmetic if needed; here we convert to number. 40 | // tokenPrice = 2^128 / (sqrtPriceX64^2) * 10^(mint_decimals_1 - mint_decimals_0) 41 | const twoPow128 = Math.pow(2, 128); 42 | const sqrtPrice = typeof sqrtPriceX64 === "bigint" ? Number(sqrtPriceX64) : sqrtPriceX64; 43 | const tokenPrice = (twoPow128 / (sqrtPrice * sqrtPrice)) * Math.pow(10, mintDecimals1 - mintDecimals0); 44 | return tokenPrice; 45 | } 46 | 47 | // Helper: Calculate tokens received for given SOL input based on pool price. 48 | export function solForTokens( 49 | solIn: number, 50 | sqrtPriceX64: bigint | number, 51 | mintDecimals0: number, 52 | mintDecimals1: number 53 | ): number { 54 | const price = sqrtPriceX64ToTokenPrice(sqrtPriceX64, mintDecimals0, mintDecimals1); 55 | const tokensOut = solIn / price; 56 | return parseFloat(tokensOut.toFixed(9)); 57 | } 58 | 59 | // Helper: Calculate SOL received for given token input based on pool price. 60 | export function tokensForSol( 61 | tokensIn: number, 62 | sqrtPriceX64: bigint | number, 63 | mintDecimals0: number, 64 | mintDecimals1: number 65 | ): number { 66 | const price = sqrtPriceX64ToTokenPrice(sqrtPriceX64, mintDecimals0, mintDecimals1); 67 | const solOut = tokensIn * price; 68 | return parseFloat(solOut.toFixed(9)); 69 | } 70 | 71 | // Buy function: executes a swap from SOL to tokens using a CLMM pool. 72 | export async function buy(pairAddress: string, solIn: number = 0.1): Promise { 73 | try { 74 | console.log(`Starting buy transaction for pair address: ${pairAddress}`); 75 | 76 | console.log("Fetching pool keys..."); 77 | const poolKeys = await fetchClmmPoolKeys(pairAddress); 78 | if (!poolKeys) { 79 | console.log("No pool keys found..."); 80 | return false; 81 | } 82 | console.log("Pool keys fetched successfully."); 83 | 84 | // Determine the mint: if token_mint_0 equals WSOL then use token_mint_1, else use token_mint_0. 85 | let mint: PublicKey; 86 | if (poolKeys.token_mint_0.equals(WSOL)) { 87 | mint = poolKeys.token_mint_1; 88 | } else { 89 | mint = poolKeys.token_mint_0; 90 | } 91 | 92 | // Determine token program. Get account info using client.getAccountInfo and check the program. 93 | const tokenInfoResp = await client.getAccountInfo(mint); 94 | let tokenProgram: PublicKey; 95 | if (tokenInfoResp && tokenInfoResp.owner.equals(TOKEN_PROGRAM_ID)) { 96 | tokenProgram = TOKEN_PROGRAM_ID; 97 | } else { 98 | tokenProgram = TOKEN_2022_PROGRAM_ID; 99 | } 100 | 101 | console.log("Calculating transaction amounts..."); 102 | const amount = Math.floor(solIn * SOL_DECIMAL); 103 | const tokensOut = solForTokens(solIn, poolKeys.sqrt_price_x64, poolKeys.mint_decimals_0, poolKeys.mint_decimals_1); 104 | console.log(`Amount In: ${solIn} SOL | Estimated Amount Out: ${tokensOut}`); 105 | 106 | console.log("Checking for existing token account..."); 107 | const tokenAccounts = await client.getTokenAccountsByOwner(payerKeypair.publicKey, { mint: mint }); 108 | let tokenAccount: PublicKey; 109 | let tokenAccountInstruction: TransactionInstruction | null = null; 110 | if (tokenAccounts.value.length > 0) { 111 | tokenAccount = new PublicKey(tokenAccounts.value[0].pubkey); 112 | console.log("Token account found."); 113 | } else { 114 | tokenAccount = await getAssociatedTokenAddress(mint, payerKeypair.publicKey, false, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID); 115 | tokenAccountInstruction = createAssociatedTokenAccountInstruction( 116 | payerKeypair.publicKey, 117 | tokenAccount, 118 | payerKeypair.publicKey, 119 | mint, 120 | tokenProgram, 121 | ASSOCIATED_TOKEN_PROGRAM_ID 122 | ); 123 | console.log("No existing token account found; creating associated token account."); 124 | } 125 | 126 | console.log("Generating seed for WSOL account..."); 127 | const seed = crypto.randomBytes(24).toString("base64url"); 128 | const wsolTokenAccount = await PublicKey.createWithSeed(payerKeypair.publicKey, seed, TOKEN_PROGRAM_ID); 129 | const balanceNeeded = await getMinimumBalanceForRentExemptAccount(client, AccountLayout.span); 130 | 131 | console.log("Creating and initializing WSOL account..."); 132 | const createWsolAccountIx = SystemProgram.createAccountWithSeed({ 133 | fromPubkey: payerKeypair.publicKey, 134 | basePubkey: payerKeypair.publicKey, 135 | seed: seed, 136 | newAccountPubkey: wsolTokenAccount, 137 | lamports: balanceNeeded + amount, 138 | space: AccountLayout.span, 139 | programId: TOKEN_PROGRAM_ID, 140 | }); 141 | const initWsolAccountIx = createInitializeAccountInstruction( 142 | wsolTokenAccount, 143 | WSOL, 144 | payerKeypair.publicKey, 145 | TOKEN_PROGRAM_ID 146 | ); 147 | 148 | console.log("Creating swap instruction..."); 149 | const swapInstruction = await makeClmmSwapInstruction({ 150 | amount, 151 | tokenAccountIn: wsolTokenAccount, 152 | tokenAccountOut: tokenAccount, 153 | accounts: poolKeys, 154 | payer: payerKeypair.publicKey, 155 | action: DIRECTION.BUY 156 | }); 157 | 158 | console.log("Preparing to close WSOL account after swap..."); 159 | const closeWsolAccountIx = createCloseAccountInstruction( 160 | wsolTokenAccount, 161 | payerKeypair.publicKey, 162 | payerKeypair.publicKey, 163 | [], 164 | TOKEN_PROGRAM_ID 165 | ); 166 | 167 | const instructions: TransactionInstruction[] = [ 168 | ComputeBudgetProgram.setComputeUnitLimit(UNIT_BUDGET), 169 | ComputeBudgetProgram.setComputeUnitPrice(UNIT_PRICE), 170 | createWsolAccountIx, 171 | initWsolAccountIx, 172 | ]; 173 | if (tokenAccountInstruction) { 174 | instructions.push(tokenAccountInstruction); 175 | } 176 | instructions.push(swapInstruction); 177 | instructions.push(closeWsolAccountIx); 178 | 179 | console.log("Compiling transaction message..."); 180 | const latestBlockhashInfo = await client.getLatestBlockhash(); 181 | const messageV0 = MessageV0.compile({ 182 | payerKey: payerKeypair.publicKey, 183 | instructions, 184 | recentBlockhash: latestBlockhashInfo.blockhash, 185 | }); 186 | const txn = new VersionedTransaction(messageV0); 187 | txn.sign([payerKeypair]); 188 | 189 | console.log("Sending transaction..."); 190 | const txnSig = (await client.sendTransaction(txn, { skipPreflight: false })) as string; 191 | console.log("Transaction Signature:", txnSig); 192 | 193 | console.log("Confirming transaction..."); 194 | const confirmed = await confirmTxn(txnSig); 195 | console.log("Transaction confirmed:", confirmed); 196 | return confirmed; 197 | } catch (e) { 198 | console.error("Error occurred during buy transaction:", e); 199 | return false; 200 | } 201 | } 202 | 203 | // Sell function: executes a swap from tokens to SOL using a CLMM pool. 204 | export async function sell(pairAddress: string, percentage: number = 100): Promise { 205 | try { 206 | console.log("Fetching pool keys..."); 207 | const poolKeys = await fetchClmmPoolKeys(pairAddress); 208 | if (!poolKeys) { 209 | console.log("No pool keys found..."); 210 | return false; 211 | } 212 | console.log("Pool keys fetched successfully."); 213 | 214 | let mint: PublicKey; 215 | let tokenDecimal: number; 216 | if (poolKeys.token_mint_0.equals(WSOL)) { 217 | mint = poolKeys.token_mint_1; 218 | tokenDecimal = poolKeys.mint_decimals_1; 219 | } else { 220 | mint = poolKeys.token_mint_0; 221 | tokenDecimal = poolKeys.mint_decimals_0; 222 | } 223 | 224 | const tokenInfoResp = await client.getAccountInfo(mint); 225 | let tokenProgram: PublicKey; 226 | if (tokenInfoResp && tokenInfoResp.owner.equals(TOKEN_PROGRAM_ID)) { 227 | tokenProgram = TOKEN_PROGRAM_ID; 228 | } else { 229 | tokenProgram = TOKEN_2022_PROGRAM_ID; 230 | } 231 | 232 | console.log("Retrieving token balance..."); 233 | let tokenBalance = await getTokenBalance(mint.toString()); 234 | console.log("Token Balance:", tokenBalance); 235 | if (!tokenBalance || tokenBalance === 0) { 236 | console.log("No token balance available to sell."); 237 | return false; 238 | } 239 | tokenBalance = tokenBalance * (percentage / 100); 240 | console.log(`Selling ${percentage}% of the token balance, adjusted balance: ${tokenBalance}`); 241 | 242 | console.log("Calculating transaction amounts..."); 243 | const solOut = tokensForSol(tokenBalance, poolKeys.sqrt_price_x64, poolKeys.mint_decimals_0, poolKeys.mint_decimals_1); 244 | console.log(`Amount In: ${tokenBalance} tokens | Estimated Amount Out: ${solOut} SOL`); 245 | const amount = Math.floor(tokenBalance * Math.pow(10, tokenDecimal)); 246 | const tokenAccount = await getAssociatedTokenAddress(mint, payerKeypair.publicKey, false, tokenProgram, ASSOCIATED_TOKEN_PROGRAM_ID); 247 | 248 | console.log("Generating seed and creating WSOL account..."); 249 | const seed = crypto.randomBytes(24).toString("base64url"); 250 | const wsolTokenAccount = await PublicKey.createWithSeed(payerKeypair.publicKey, seed, TOKEN_PROGRAM_ID); 251 | const balanceNeeded = await getMinimumBalanceForRentExemptAccount(client, AccountLayout.span); 252 | const createWsolAccountIx = SystemProgram.createAccountWithSeed({ 253 | fromPubkey: payerKeypair.publicKey, 254 | basePubkey: payerKeypair.publicKey, 255 | seed: seed, 256 | newAccountPubkey: wsolTokenAccount, 257 | lamports: balanceNeeded, 258 | space: AccountLayout.span, 259 | programId: TOKEN_PROGRAM_ID, 260 | }); 261 | const initWsolAccountIx = createInitializeAccountInstruction( 262 | wsolTokenAccount, 263 | WSOL, 264 | payerKeypair.publicKey, 265 | TOKEN_PROGRAM_ID 266 | ); 267 | 268 | console.log("Creating swap instructions..."); 269 | const swapInstructions = await makeClmmSwapInstruction({ 270 | amount, 271 | tokenAccountIn: tokenAccount, 272 | tokenAccountOut: wsolTokenAccount, 273 | accounts: poolKeys, 274 | payer: payerKeypair.publicKey, 275 | action: DIRECTION.SELL 276 | }); 277 | 278 | console.log("Preparing to close WSOL account after swap..."); 279 | const closeWsolAccountIx = createCloseAccountInstruction( 280 | wsolTokenAccount, 281 | payerKeypair.publicKey, 282 | payerKeypair.publicKey, 283 | [], 284 | TOKEN_PROGRAM_ID 285 | ); 286 | 287 | const instructions: TransactionInstruction[] = [ 288 | ComputeBudgetProgram.setComputeUnitLimit(UNIT_BUDGET), 289 | ComputeBudgetProgram.setComputeUnitPrice(UNIT_PRICE), 290 | createWsolAccountIx, 291 | initWsolAccountIx, 292 | swapInstructions, 293 | closeWsolAccountIx, 294 | ]; 295 | if (percentage === 100) { 296 | console.log("Preparing to close token account after swap..."); 297 | const closeTokenAccountIx = createCloseAccountInstruction( 298 | tokenAccount, 299 | payerKeypair.publicKey, 300 | payerKeypair.publicKey, 301 | [], 302 | tokenProgram 303 | ); 304 | instructions.push(closeTokenAccountIx); 305 | } 306 | 307 | console.log("Compiling transaction message..."); 308 | const latestBlockhashInfo = await client.getLatestBlockhash(); 309 | const messageV0 = MessageV0.compile({ 310 | payerKey: payerKeypair.publicKey, 311 | instructions, 312 | recentBlockhash: latestBlockhashInfo.blockhash, 313 | }); 314 | const txn = new VersionedTransaction(messageV0); 315 | txn.sign([payerKeypair]); 316 | 317 | console.log("Sending transaction..."); 318 | const txnSig = (await client.sendTransaction(txn, { skipPreflight: true })) as string; 319 | console.log("Transaction Signature:", txnSig); 320 | 321 | console.log("Confirming transaction..."); 322 | const confirmed = await confirmTxn(txnSig); 323 | console.log("Transaction confirmed:", confirmed); 324 | return confirmed; 325 | } catch (e) { 326 | console.error("Error occurred during sell transaction:", e); 327 | return false; 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /HolderData/example_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "isBundler": false, 4 | "isInsider": true, 5 | "isProUser": false, 6 | "isSniper": false, 7 | "solBalance": 15.241810246, 8 | "tokenBalance": 221595816.377622, 9 | "walletAddress": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1" 10 | }, 11 | { 12 | "isBundler": false, 13 | "isInsider": false, 14 | "isProUser": false, 15 | "isSniper": false, 16 | "solBalance": 0.1011136, 17 | "tokenBalance": 111349412.060399, 18 | "walletAddress": "BF8AzSqs3jwgBUkjgHbz5oGPF1hDfBoj7vNMxvvDTCZd" 19 | }, 20 | { 21 | "isBundler": false, 22 | "isInsider": false, 23 | "isProUser": false, 24 | "isSniper": false, 25 | "solBalance": 0.093020929, 26 | "tokenBalance": 30166571.928315, 27 | "walletAddress": "QSged6mgdsqMwYiCB5MxzytFJVZWbWbUjdHUThmtohQ" 28 | }, 29 | { 30 | "isBundler": false, 31 | "isInsider": false, 32 | "isProUser": false, 33 | "isSniper": false, 34 | "solBalance": 0.074499844, 35 | "tokenBalance": 24320987.442683, 36 | "walletAddress": "BLraXeapwqq3Fdb2xF4Cv8caXuosMePZLBA2g1WCWB2D" 37 | }, 38 | { 39 | "isBundler": false, 40 | "isInsider": false, 41 | "isProUser": false, 42 | "isSniper": false, 43 | "solBalance": 0.030876157, 44 | "tokenBalance": 16411566.872461, 45 | "walletAddress": "6eYAqrx94TsAfY24Z2RHqo98FsD1gJ5qtn2XTm2Gt2Jc" 46 | }, 47 | { 48 | "isBundler": false, 49 | "isInsider": false, 50 | "isProUser": false, 51 | "isSniper": false, 52 | "solBalance": 15.480134042, 53 | "tokenBalance": 13113408.985729, 54 | "walletAddress": "G2HMxNyaQ3mFandoQ59tEUL9JuGLa9WpepujLyzZ6CwY" 55 | }, 56 | { 57 | "isBundler": false, 58 | "isInsider": true, 59 | "isProUser": false, 60 | "isSniper": false, 61 | "solBalance": 0.01163712, 62 | "tokenBalance": 12397349.90345, 63 | "walletAddress": "2hWjBcVQDqu5cHTpDbf9wCTYFkEjS2uxT3zNiM4x3VFe" 64 | }, 65 | { 66 | "isBundler": false, 67 | "isInsider": true, 68 | "isProUser": false, 69 | "isSniper": false, 70 | "solBalance": 0.01163712, 71 | "tokenBalance": 12294763.794982, 72 | "walletAddress": "6d6oVmL2TfmudXpkNCeKAkwUrKEtfA9RK1n5nDzbfQm1" 73 | }, 74 | { 75 | "isBundler": false, 76 | "isInsider": true, 77 | "isProUser": false, 78 | "isSniper": false, 79 | "solBalance": 0.01163712, 80 | "tokenBalance": 12280962.193831, 81 | "walletAddress": "EH3HoXwM2ewBcaWJMZQvftQQLn7r5WN3SAwfrUNegiuA" 82 | }, 83 | { 84 | "isBundler": false, 85 | "isInsider": true, 86 | "isProUser": false, 87 | "isSniper": false, 88 | "solBalance": 0.01163712, 89 | "tokenBalance": 12272468.177117, 90 | "walletAddress": "J8T11E5qzhvMgFPDoeNcdXtiDxJMdeL18XMogMboZuoy" 91 | }, 92 | { 93 | "isBundler": false, 94 | "isInsider": true, 95 | "isProUser": false, 96 | "isSniper": false, 97 | "solBalance": 0.01163712, 98 | "tokenBalance": 12264008.34573, 99 | "walletAddress": "55i7mD4zhzyf1PsPS1YA5CUtwtyTDYQqBc2Nxifpqs6x" 100 | }, 101 | { 102 | "isBundler": false, 103 | "isInsider": true, 104 | "isProUser": false, 105 | "isSniper": false, 106 | "solBalance": 0.01163712, 107 | "tokenBalance": 12238594.577424, 108 | "walletAddress": "8LVB2ZTk3jsY3TzD4N2bArmpzzcaBxnBASNY8k5r8bF9" 109 | }, 110 | { 111 | "isBundler": false, 112 | "isInsider": true, 113 | "isProUser": false, 114 | "isSniper": false, 115 | "solBalance": 0.01163712, 116 | "tokenBalance": 12229349.890547, 117 | "walletAddress": "AFKZ8asVmy16xwQpMFw39PPnhbr6BrxRDGHLo6S9ngcm" 118 | }, 119 | { 120 | "isBundler": false, 121 | "isInsider": true, 122 | "isProUser": false, 123 | "isSniper": false, 124 | "solBalance": 0.01163712, 125 | "tokenBalance": 12202135.822292, 126 | "walletAddress": "9uBPWJxfnFThMgWtJBqzxY7U5XNaQfkFBJPW8arnXF6k" 127 | }, 128 | { 129 | "isBundler": true, 130 | "isInsider": false, 131 | "isProUser": false, 132 | "isSniper": false, 133 | "solBalance": 36.25069951, 134 | "tokenBalance": 11741680.221636, 135 | "walletAddress": "DqvJi3eGKRPKmrdquk2pEVyGptmpDbyvu4xE1Vn2jeY3" 136 | }, 137 | { 138 | "isBundler": false, 139 | "isInsider": false, 140 | "isProUser": false, 141 | "isSniper": false, 142 | "solBalance": 22.48411852, 143 | "tokenBalance": 9453494.763857, 144 | "walletAddress": "HLdVpBJMA5nLvRq64n7dVGaLLoPVPRV9oPduUChNgSqr" 145 | }, 146 | { 147 | "isBundler": true, 148 | "isInsider": false, 149 | "isProUser": false, 150 | "isSniper": false, 151 | "solBalance": 0.0864097, 152 | "tokenBalance": 8663580.044299, 153 | "walletAddress": "CJX5rmStnxBPKbUfScTXmDquj7VUETFFpCuBVQMap7q" 154 | }, 155 | { 156 | "isBundler": false, 157 | "isInsider": false, 158 | "isProUser": false, 159 | "isSniper": false, 160 | "solBalance": 0.051136654, 161 | "tokenBalance": 7645649.321428, 162 | "walletAddress": "F2hc8L1J29EXK13XiDrqFLutaRdAVUpoRAvUR8m5ANp8" 163 | }, 164 | { 165 | "isBundler": true, 166 | "isInsider": false, 167 | "isProUser": false, 168 | "isSniper": false, 169 | "solBalance": 0.34464212, 170 | "tokenBalance": 7240187.486927, 171 | "walletAddress": "G3XNTDpx45DcxjarujM2nxhNyxnHESjyHJKuyLgyfyYo" 172 | }, 173 | { 174 | "isBundler": false, 175 | "isInsider": false, 176 | "isProUser": false, 177 | "isSniper": false, 178 | "solBalance": 0.1011136, 179 | "tokenBalance": 7149844.737431, 180 | "walletAddress": "448buj4CVZ5BGe7afK6ohcLeJPm6XjaiHUWyAqgiZRUX" 181 | }, 182 | { 183 | "isBundler": false, 184 | "isInsider": false, 185 | "isProUser": false, 186 | "isSniper": false, 187 | "solBalance": 0.052338235, 188 | "tokenBalance": 6878084.352084, 189 | "walletAddress": "J3W7X6aFKZ64DQvkMe3WKaQ2ND5MZxDopSKyTRm7czc6" 190 | }, 191 | { 192 | "isBundler": false, 193 | "isInsider": false, 194 | "isProUser": false, 195 | "isSniper": false, 196 | "solBalance": 0.15180659, 197 | "tokenBalance": 6646402.409406, 198 | "walletAddress": "57ajSpPojhkmpBGRfW1967ecAxuztjXFdjjpf3fYSvUb" 199 | }, 200 | { 201 | "isBundler": false, 202 | "isInsider": false, 203 | "isProUser": false, 204 | "isSniper": false, 205 | "solBalance": 0.137480624, 206 | "tokenBalance": 6536898.694232, 207 | "walletAddress": "7DKNdsnCq6vvn2ns4EKhNULySK5BXrmqMiDYPQobjjC1" 208 | }, 209 | { 210 | "isBundler": false, 211 | "isInsider": false, 212 | "isProUser": false, 213 | "isSniper": false, 214 | "solBalance": 0.098494559, 215 | "tokenBalance": 5666076.539353, 216 | "walletAddress": "3nN5jZYaFsKYLa5M4trbDRVkBVccBunnaYn3gkBvWxMw" 217 | }, 218 | { 219 | "isBundler": true, 220 | "isInsider": false, 221 | "isProUser": false, 222 | "isSniper": false, 223 | "solBalance": 0, 224 | "tokenBalance": 5098600.463805, 225 | "walletAddress": "AToeT9AvWe18SYPAqYgapWWDjhMAk5id1wjith3aQz1X" 226 | }, 227 | { 228 | "isBundler": false, 229 | "isInsider": false, 230 | "isProUser": false, 231 | "isSniper": false, 232 | "solBalance": 0, 233 | "tokenBalance": 5096667.527465, 234 | "walletAddress": "85vv77spqizn47rFUGbo7CXj3puyhpemDr2WT11z7dPs" 235 | }, 236 | { 237 | "isBundler": true, 238 | "isInsider": false, 239 | "isProUser": false, 240 | "isSniper": false, 241 | "solBalance": 0, 242 | "tokenBalance": 5094089.506814, 243 | "walletAddress": "94eNQBmBNz1rv3djedYNdgNjeMha1zY2wVuDuz2Fh5e9" 244 | }, 245 | { 246 | "isBundler": false, 247 | "isInsider": false, 248 | "isProUser": false, 249 | "isSniper": false, 250 | "solBalance": 0, 251 | "tokenBalance": 5094045.064176, 252 | "walletAddress": "CvsiWKqodjh95TrMCYoXbwq3hiR9xD7zi1ijy36z78mW" 253 | }, 254 | { 255 | "isBundler": false, 256 | "isInsider": false, 257 | "isProUser": false, 258 | "isSniper": false, 259 | "solBalance": 0, 260 | "tokenBalance": 5093400.367111, 261 | "walletAddress": "4nkAEK9GzWY79NDNUyD2yYxh7yycrg4j3zNdBH78VsCF" 262 | }, 263 | { 264 | "isBundler": false, 265 | "isInsider": false, 266 | "isProUser": false, 267 | "isSniper": false, 268 | "solBalance": 0, 269 | "tokenBalance": 5091364.069243, 270 | "walletAddress": "B4Ec1oDVye3npaL95aKkf8mUHT2rp8hhETKh11rohdTE" 271 | }, 272 | { 273 | "isBundler": true, 274 | "isInsider": false, 275 | "isProUser": false, 276 | "isSniper": false, 277 | "solBalance": 0, 278 | "tokenBalance": 5083345.823026, 279 | "walletAddress": "73qsjpR5zXPT396vKiEpsCBQ9s89DMKRBqvXvCmBuJn4" 280 | }, 281 | { 282 | "isBundler": false, 283 | "isInsider": false, 284 | "isProUser": false, 285 | "isSniper": false, 286 | "solBalance": 0, 287 | "tokenBalance": 5078299.885212, 288 | "walletAddress": "9Xvnr1EQ3bgwa6VM3LdJZRY15UiBJWnu2utPUZkVbT97" 289 | }, 290 | { 291 | "isBundler": false, 292 | "isInsider": false, 293 | "isProUser": false, 294 | "isSniper": false, 295 | "solBalance": 0, 296 | "tokenBalance": 5074793.723144, 297 | "walletAddress": "27Gmeoh99H6vjeU9dm7Sg95CPUSJg7ocR66jNPQVEa8z" 298 | }, 299 | { 300 | "isBundler": false, 301 | "isInsider": false, 302 | "isProUser": false, 303 | "isSniper": false, 304 | "solBalance": 0, 305 | "tokenBalance": 5073769.510298, 306 | "walletAddress": "8iKnacgpfqpi4nTDEMNfrsJEyQrU1yeu6E9axhZsRM8W" 307 | }, 308 | { 309 | "isBundler": true, 310 | "isInsider": false, 311 | "isProUser": false, 312 | "isSniper": false, 313 | "solBalance": 0, 314 | "tokenBalance": 5061853.042691, 315 | "walletAddress": "DomemETWitnr78Woapyngnxg7bNydaLjk4qft1QjRwRq" 316 | }, 317 | { 318 | "isBundler": true, 319 | "isInsider": false, 320 | "isProUser": false, 321 | "isSniper": false, 322 | "solBalance": 0, 323 | "tokenBalance": 5055992.987713, 324 | "walletAddress": "HovDvLsgxxiGXhYuuabfggL8BP25YsCp6uzuV83X3CxV" 325 | }, 326 | { 327 | "isBundler": true, 328 | "isInsider": false, 329 | "isProUser": false, 330 | "isSniper": false, 331 | "solBalance": 0, 332 | "tokenBalance": 5049959.026264, 333 | "walletAddress": "EQUKyGZRXqywesXrq7YVDfWBdahFteHuYZKcyUhjYUFp" 334 | }, 335 | { 336 | "isBundler": true, 337 | "isInsider": false, 338 | "isProUser": false, 339 | "isSniper": false, 340 | "solBalance": 0, 341 | "tokenBalance": 5045352.790539, 342 | "walletAddress": "7Raojw47J5UnS2jHSbCWPMjN5d3hBVdVvkqPArKpAW3t" 343 | }, 344 | { 345 | "isBundler": true, 346 | "isInsider": false, 347 | "isProUser": false, 348 | "isSniper": false, 349 | "solBalance": 0, 350 | "tokenBalance": 5038865.177988, 351 | "walletAddress": "ughfGbC1me5t7dNFkyGFWmxciPGroRBkM4AVvZ6fFVb" 352 | }, 353 | { 354 | "isBundler": true, 355 | "isInsider": false, 356 | "isProUser": false, 357 | "isSniper": false, 358 | "solBalance": 0, 359 | "tokenBalance": 5028710.076556, 360 | "walletAddress": "6gAemWYLMXZaWviyp6r3947SSnGqJi1DZqaWYJPTFSfJ" 361 | }, 362 | { 363 | "isBundler": false, 364 | "isInsider": false, 365 | "isProUser": false, 366 | "isSniper": false, 367 | "solBalance": 0, 368 | "tokenBalance": 5027170.797215, 369 | "walletAddress": "HertjMgdoLn8iE7rmTfn8tKke3jNeaibJhKR8MsiXLcr" 370 | }, 371 | { 372 | "isBundler": true, 373 | "isInsider": false, 374 | "isProUser": false, 375 | "isSniper": false, 376 | "solBalance": 0, 377 | "tokenBalance": 5016981.891512, 378 | "walletAddress": "AoG5Fv2sbbcQXz678kjq5HFAbkMiB8LtNmoiwH8UT2g5" 379 | }, 380 | { 381 | "isBundler": true, 382 | "isInsider": false, 383 | "isProUser": false, 384 | "isSniper": false, 385 | "solBalance": 0, 386 | "tokenBalance": 5016498.066285, 387 | "walletAddress": "3qF19cha92wpiULxXZRTZyp995qHoBWU7W6bZnKQYP36" 388 | }, 389 | { 390 | "isBundler": false, 391 | "isInsider": false, 392 | "isProUser": false, 393 | "isSniper": false, 394 | "solBalance": 0, 395 | "tokenBalance": 5010575.362725, 396 | "walletAddress": "C822ZAtizmAPM4Fr7Buvv121zAWef13uf15Wn2ALEEK4" 397 | }, 398 | { 399 | "isBundler": false, 400 | "isInsider": false, 401 | "isProUser": false, 402 | "isSniper": false, 403 | "solBalance": 0, 404 | "tokenBalance": 5009957.235976, 405 | "walletAddress": "JNv4SDDqejN8GyUSvD66p2QZJQZBcbhmHAgBnTo29zV" 406 | }, 407 | { 408 | "isBundler": false, 409 | "isInsider": false, 410 | "isProUser": false, 411 | "isSniper": false, 412 | "solBalance": 5.662344702, 413 | "tokenBalance": 5004014.264761, 414 | "walletAddress": "2biFNqUQaCtYL3Rc91HtuH7UpWX12tfzQ3n7rSaCRWKD" 415 | }, 416 | { 417 | "isBundler": true, 418 | "isInsider": false, 419 | "isProUser": false, 420 | "isSniper": false, 421 | "solBalance": 0, 422 | "tokenBalance": 5000464.530734, 423 | "walletAddress": "39wYgSr3t8sev5jexa42671hh4j5VZvM8BkJioR7uriw" 424 | }, 425 | { 426 | "isBundler": false, 427 | "isInsider": false, 428 | "isProUser": false, 429 | "isSniper": false, 430 | "solBalance": 0, 431 | "tokenBalance": 5000041.054631, 432 | "walletAddress": "HdEbmnPEgrBe1siWNgbeVVG9SdRtg6fqYGrXqQoorkue" 433 | }, 434 | { 435 | "isBundler": false, 436 | "isInsider": false, 437 | "isProUser": false, 438 | "isSniper": false, 439 | "solBalance": 2.169819605, 440 | "tokenBalance": 4840902.620857, 441 | "walletAddress": "HXBDynbKGbSHg5SYui7DfmQp8Hwq3aYoXfcRn1QCGdCy" 442 | }, 443 | { 444 | "isBundler": false, 445 | "isInsider": false, 446 | "isProUser": false, 447 | "isSniper": false, 448 | "solBalance": 0.084080737, 449 | "tokenBalance": 4770099.198351, 450 | "walletAddress": "DK8sE6J2oXywPYnHax1samKiQRWKX6rMhiNXNDJCjsUE" 451 | } 452 | ] -------------------------------------------------------------------------------- /raydium-copy-trading/raydium/cpmm.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * raydiumSwap.ts 3 | * 4 | * This file shows an example of converting the Python buy/sell logic to 5 | * TypeScript using @solana/web3.js, @solana/spl-token and hypothetical 6 | * Raydium helper modules. You may need to adjust helper functions, 7 | * types, and imported modules based on your actual libraries. 8 | */ 9 | 10 | import { 11 | Connection, 12 | PublicKey, 13 | Keypair, 14 | Transaction, 15 | TransactionInstruction, 16 | SystemProgram, 17 | LAMPORTS_PER_SOL, 18 | sendAndConfirmTransaction, 19 | } from '@solana/web3.js'; 20 | import { 21 | TOKEN_PROGRAM_ID, 22 | getAssociatedTokenAddress, 23 | createAssociatedTokenAccountInstruction, 24 | createInitializeAccountInstruction, 25 | createCloseAccountInstruction, 26 | } from '@solana/spl-token'; 27 | import { randomBytes } from 'crypto'; 28 | import base64url from 'base64url'; 29 | 30 | // Hypothetical Raydium helper modules – adjust based on your actual library names. 31 | import { 32 | fetchCpmmPoolKeys, 33 | getCpmmReserves, 34 | makeCpmmSwapInstruction, 35 | CpmmPoolKeys, 36 | DIRECTION, 37 | } from 'raydium-js/pool_utils'; 38 | import { confirmTxn, getTokenBalance } from 'raydium-js/utils/common_utils'; 39 | import { setComputeUnitLimit, setComputeUnitPrice } from 'raydium-js/computeBudget'; 40 | import { TxOpts } from 'raydium-js/types'; // if you have a dedicated type for transaction options 41 | 42 | // Constants and configuration 43 | const SOL_DECIMAL = 1_000_000_000; 44 | const ACCOUNT_LAYOUT_LEN = 165; // standard token account size for SPL tokens 45 | // WSOL is the Wrapped SOL mint address as a string 46 | export const WSOL = new PublicKey('So11111111111111111111111111111111111111112'); 47 | // These values can be configured externally 48 | const UNIT_BUDGET = 400000; // example: maximum compute units 49 | const UNIT_PRICE = 1; // example: compute unit price 50 | 51 | // Set up your connection and payer Keypair (ensure proper private key management) 52 | const connection = new Connection('https://api.mainnet-beta.solana.com'); 53 | const payer = Keypair.generate(); // In practice, load your keypair securely 54 | 55 | /** 56 | * Helper function. 57 | * 58 | * Returns the expected tokens received for a given SOL input. 59 | */ 60 | export function solForTokens( 61 | solAmount: number, 62 | baseVaultBalance: number, 63 | quoteVaultBalance: number, 64 | swapFee = 0.25 65 | ): number { 66 | const effectiveSolUsed = solAmount - solAmount * (swapFee / 100); 67 | const constantProduct = baseVaultBalance * quoteVaultBalance; 68 | const updatedBaseVaultBalance = constantProduct / (quoteVaultBalance + effectiveSolUsed); 69 | const tokensReceived = baseVaultBalance - updatedBaseVaultBalance; 70 | return parseFloat(tokensReceived.toFixed(9)); 71 | } 72 | 73 | /** 74 | * Helper function. 75 | * 76 | * Returns the expected SOL received for a given token amount input. 77 | */ 78 | export function tokensForSol( 79 | tokenAmount: number, 80 | baseVaultBalance: number, 81 | quoteVaultBalance: number, 82 | swapFee = 0.25 83 | ): number { 84 | const effectiveTokensSold = tokenAmount * (1 - swapFee / 100); 85 | const constantProduct = baseVaultBalance * quoteVaultBalance; 86 | const updatedQuoteVaultBalance = constantProduct / (baseVaultBalance + effectiveTokensSold); 87 | const solReceived = quoteVaultBalance - updatedQuoteVaultBalance; 88 | return parseFloat(solReceived.toFixed(9)); 89 | } 90 | 91 | /** 92 | * Executes a buy (swap SOL for tokens) transaction. 93 | * 94 | * @param pairAddress - The pool address as a string. 95 | * @param solIn - SOL input amount (in SOL, not lamports). 96 | * @param slippage - Allowed slippage percentage. 97 | * @returns Promise that resolves to true if the transaction was confirmed. 98 | */ 99 | export async function buy( 100 | pairAddress: string, 101 | solIn = 0.1, 102 | slippage = 1 103 | ): Promise { 104 | console.log(`Starting buy transaction for pair address: ${pairAddress}`); 105 | 106 | // Fetch pool keys (assumed to be implemented to return a CpmmPoolKeys object) 107 | console.log('Fetching pool keys...'); 108 | const poolKeys: CpmmPoolKeys | null = await fetchCpmmPoolKeys(pairAddress); 109 | if (!poolKeys) { 110 | console.log('No pool keys found...'); 111 | return false; 112 | } 113 | console.log('Pool keys fetched successfully.'); 114 | 115 | // Determine the proper mint and token program based on WSOL presence. 116 | let mint: PublicKey; 117 | let tokenProgram: PublicKey; 118 | if (poolKeys.token_0_mint.equals(WSOL)) { 119 | mint = poolKeys.token_1_mint; 120 | tokenProgram = poolKeys.token_1_program; 121 | } else { 122 | mint = poolKeys.token_0_mint; 123 | tokenProgram = poolKeys.token_0_program; 124 | } 125 | 126 | console.log('Calculating transaction amounts...'); 127 | const amountIn = Math.floor(solIn * SOL_DECIMAL); 128 | 129 | // Get current pool reserves and token decimal information. 130 | const { baseReserve, quoteReserve, tokenDecimal } = await getCpmmReserves(poolKeys); 131 | const estimatedAmount = solForTokens(solIn, baseReserve, quoteReserve); 132 | console.log(`Estimated Amount Out: ${estimatedAmount}`); 133 | 134 | const slippageAdjustment = 1 - slippage / 100; 135 | const amountOutWithSlippage = estimatedAmount * slippageAdjustment; 136 | const minimumAmountOut = Math.floor(amountOutWithSlippage * Math.pow(10, tokenDecimal)); 137 | console.log(`Amount In: ${amountIn} | Minimum Amount Out: ${minimumAmountOut}`); 138 | 139 | // Check for existing token account owned by the payer 140 | console.log('Checking for existing token account...'); 141 | const tokenAccounts = await connection.getTokenAccountsByOwner(payer.publicKey, { 142 | mint: mint, 143 | }); 144 | let tokenAccount: PublicKey; 145 | let tokenAccountIx: TransactionInstruction | null = null; 146 | if (tokenAccounts.value.length > 0) { 147 | tokenAccount = tokenAccounts.value[0].pubkey; 148 | console.log('Token account found.'); 149 | } else { 150 | // Calculate the associated token account address 151 | tokenAccount = await getAssociatedTokenAddress(mint, payer.publicKey, false, TOKEN_PROGRAM_ID); 152 | tokenAccountIx = createAssociatedTokenAccountInstruction( 153 | payer.publicKey, 154 | tokenAccount, 155 | payer.publicKey, 156 | mint, 157 | TOKEN_PROGRAM_ID 158 | ); 159 | console.log('No existing token account found; creating associated token account.'); 160 | } 161 | 162 | // Generate a seed for the WSOL account 163 | console.log('Generating seed for WSOL account...'); 164 | const seed = base64url(randomBytes(24)); 165 | const wsolTokenAccount = await PublicKey.createWithSeed(payer.publicKey, seed, TOKEN_PROGRAM_ID); 166 | 167 | // Get minimum lamports for rent exemption for an account 168 | const balanceNeeded = await connection.getMinimumBalanceForRentExemption(ACCOUNT_LAYOUT_LEN); 169 | 170 | console.log('Creating and initializing WSOL account...'); 171 | // Create account with seed (using SystemProgram.createAccountWithSeed) 172 | const createWsolAccountIx = SystemProgram.createAccountWithSeed({ 173 | fromPubkey: payer.publicKey, 174 | basePubkey: payer.publicKey, 175 | seed: seed, 176 | newAccountPubkey: wsolTokenAccount, 177 | lamports: balanceNeeded + amountIn, 178 | space: ACCOUNT_LAYOUT_LEN, 179 | programId: TOKEN_PROGRAM_ID, 180 | }); 181 | 182 | // Initialize account instruction for WSOL (using SPL Token helper) 183 | const initWsolAccountIx = createInitializeAccountInstruction( 184 | wsolTokenAccount, 185 | WSOL, 186 | payer.publicKey, 187 | TOKEN_PROGRAM_ID 188 | ); 189 | 190 | // Create swap instruction using the Raydium helper function (assumed signature) 191 | console.log('Creating swap instruction...'); 192 | const swapIx = await makeCpmmSwapInstruction({ 193 | amountIn, 194 | minimumAmountOut, 195 | tokenAccountIn: wsolTokenAccount, 196 | tokenAccountOut: tokenAccount, 197 | poolKeys, 198 | owner: payer.publicKey, 199 | action: DIRECTION.BUY, 200 | }); 201 | 202 | // Prepare instruction to close the WSOL account after swap 203 | console.log('Preparing to close WSOL account after swap...'); 204 | const closeWsolAccountIx = createCloseAccountInstruction( 205 | wsolTokenAccount, 206 | payer.publicKey, 207 | payer.publicKey, 208 | [], 209 | TOKEN_PROGRAM_ID 210 | ); 211 | 212 | // Build the transaction instructions array. 213 | const instructions: TransactionInstruction[] = [ 214 | // Custom compute unit instructions (assumed implemented in your project): 215 | setComputeUnitLimit(UNIT_BUDGET), 216 | setComputeUnitPrice(UNIT_PRICE), 217 | createWsolAccountIx, 218 | initWsolAccountIx, 219 | ]; 220 | 221 | if (tokenAccountIx) { 222 | instructions.push(tokenAccountIx); 223 | } 224 | instructions.push(swapIx); 225 | instructions.push(closeWsolAccountIx); 226 | 227 | console.log('Compiling transaction message...'); 228 | const transaction = new Transaction().add(...instructions); 229 | transaction.feePayer = payer.publicKey; 230 | transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; 231 | 232 | console.log('Sending transaction...'); 233 | try { 234 | const txSignature = await sendAndConfirmTransaction(connection, transaction, [payer], { 235 | skipPreflight: true, 236 | }); 237 | console.log('Transaction Signature:', txSignature); 238 | 239 | console.log('Confirming transaction...'); 240 | const confirmed = await confirmTxn(connection, txSignature); 241 | console.log('Transaction confirmed:', confirmed); 242 | return confirmed; 243 | } catch (error) { 244 | console.log('Error during transaction:', error); 245 | return false; 246 | } 247 | } 248 | 249 | /** 250 | * Executes a sell (swap tokens for SOL) transaction. 251 | * 252 | * @param pairAddress - The pool address as a string. 253 | * @param percentage - Percentage of the token balance to sell. 254 | * @param slippage - Allowed slippage percentage. 255 | * @returns Promise that resolves to true if the transaction was confirmed. 256 | */ 257 | export async function sell( 258 | pairAddress: string, 259 | percentage = 100, 260 | slippage = 1 261 | ): Promise { 262 | try { 263 | console.log('Fetching pool keys...'); 264 | const poolKeys: CpmmPoolKeys | null = await fetchCpmmPoolKeys(pairAddress); 265 | if (!poolKeys) { 266 | console.log('No pool keys found...'); 267 | return false; 268 | } 269 | console.log('Pool keys fetched successfully.'); 270 | 271 | // Determine mint and token program ID depending on which is WSOL. 272 | let mint: PublicKey; 273 | let tokenProgramId: PublicKey; 274 | if (poolKeys.token_0_mint.equals(WSOL)) { 275 | mint = poolKeys.token_1_mint; 276 | tokenProgramId = poolKeys.token_1_program; 277 | } else { 278 | mint = poolKeys.token_0_mint; 279 | tokenProgramId = poolKeys.token_0_program; 280 | } 281 | 282 | console.log('Retrieving token balance...'); 283 | let tokenBalance = await getTokenBalance(mint.toString()); 284 | console.log('Token Balance:', tokenBalance); 285 | 286 | if (!tokenBalance || tokenBalance === 0) { 287 | console.log('No token balance available to sell.'); 288 | return false; 289 | } 290 | tokenBalance = tokenBalance * (percentage / 100); 291 | console.log(`Selling ${percentage}% of the token balance, adjusted balance: ${tokenBalance}`); 292 | 293 | console.log('Calculating transaction amounts...'); 294 | const { baseReserve, quoteReserve, tokenDecimal } = await getCpmmReserves(poolKeys); 295 | const estimatedAmount = tokensForSol(tokenBalance, baseReserve, quoteReserve); 296 | console.log(`Estimated Amount Out: ${estimatedAmount}`); 297 | 298 | const slippageAdjustment = 1 - slippage / 100; 299 | const amountOutWithSlippage = estimatedAmount * slippageAdjustment; 300 | const minimumAmountOut = Math.floor(amountOutWithSlippage * SOL_DECIMAL); 301 | 302 | const amountIn = Math.floor(tokenBalance * Math.pow(10, tokenDecimal)); 303 | console.log(`Amount In: ${amountIn} | Minimum Amount Out: ${minimumAmountOut}`); 304 | 305 | // Get the associated token account address for the payer. 306 | const tokenAccount = await getAssociatedTokenAddress(mint, payer.publicKey, false, tokenProgramId); 307 | 308 | console.log('Generating seed and creating WSOL account...'); 309 | const seed = base64url(randomBytes(24)); 310 | const wsolTokenAccount = await PublicKey.createWithSeed(payer.publicKey, seed, TOKEN_PROGRAM_ID); 311 | const balanceNeeded = await connection.getMinimumBalanceForRentExemption(ACCOUNT_LAYOUT_LEN); 312 | 313 | const createWsolAccountIx = SystemProgram.createAccountWithSeed({ 314 | fromPubkey: payer.publicKey, 315 | basePubkey: payer.publicKey, 316 | seed: seed, 317 | newAccountPubkey: wsolTokenAccount, 318 | lamports: balanceNeeded, 319 | space: ACCOUNT_LAYOUT_LEN, 320 | programId: TOKEN_PROGRAM_ID, 321 | }); 322 | 323 | const initWsolAccountIx = createInitializeAccountInstruction( 324 | wsolTokenAccount, 325 | WSOL, 326 | payer.publicKey, 327 | TOKEN_PROGRAM_ID 328 | ); 329 | 330 | console.log('Creating swap instruction...'); 331 | const swapIx = await makeCpmmSwapInstruction({ 332 | amountIn, 333 | minimumAmountOut, 334 | tokenAccountIn: tokenAccount, 335 | tokenAccountOut: wsolTokenAccount, 336 | poolKeys, 337 | owner: payer.publicKey, 338 | action: DIRECTION.SELL, 339 | }); 340 | 341 | console.log('Preparing to close WSOL account after swap...'); 342 | const closeWsolAccountIx = createCloseAccountInstruction( 343 | wsolTokenAccount, 344 | payer.publicKey, 345 | payer.publicKey, 346 | [], 347 | TOKEN_PROGRAM_ID 348 | ); 349 | 350 | // If selling 100% of balance, add an instruction to close the token account. 351 | const instructions: TransactionInstruction[] = [ 352 | setComputeUnitLimit(UNIT_BUDGET), 353 | setComputeUnitPrice(UNIT_PRICE), 354 | createWsolAccountIx, 355 | initWsolAccountIx, 356 | swapIx, 357 | closeWsolAccountIx, 358 | ]; 359 | if (percentage === 100) { 360 | console.log('Preparing to close token account after swap...'); 361 | const closeTokenAccountIx = createCloseAccountInstruction( 362 | tokenAccount, 363 | payer.publicKey, 364 | payer.publicKey, 365 | [], 366 | tokenProgramId 367 | ); 368 | instructions.push(closeTokenAccountIx); 369 | } 370 | 371 | console.log('Compiling transaction message...'); 372 | const transaction = new Transaction().add(...instructions); 373 | transaction.feePayer = payer.publicKey; 374 | transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; 375 | 376 | console.log('Sending transaction...'); 377 | const txSignature = await sendAndConfirmTransaction(connection, transaction, [payer], { 378 | skipPreflight: true, 379 | }); 380 | console.log('Transaction Signature:', txSignature); 381 | 382 | console.log('Confirming transaction...'); 383 | const confirmed = await confirmTxn(connection, txSignature); 384 | console.log('Transaction confirmed:', confirmed); 385 | return confirmed; 386 | } catch (error) { 387 | console.error('Error occurred during transaction:', error); 388 | return false; 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import axios from 'axios'; 4 | import WebSocket from 'ws'; 5 | import dotenv from 'dotenv'; 6 | 7 | dotenv.config(); // load environment variables 1 8 | 9 | // --- Utility Functions --- 10 | function sleep(ms: number): Promise { 11 | return new Promise((resolve) => setTimeout(resolve, ms)); 12 | } 13 | 14 | function readCookie(): string { 15 | try { 16 | const cookiePath = path.join(__dirname, 'cookie.txt'); 17 | return fs.readFileSync(cookiePath, 'utf8').trim(); 18 | } catch (e) { 19 | return ''; 20 | } 21 | } 22 | 23 | function writeCookie(cookieStr: string): boolean { 24 | try { 25 | const cookiePath = path.join(__dirname, 'cookie.txt'); 26 | fs.writeFileSync(cookiePath, cookieStr.trim(), 'utf8'); 27 | return true; 28 | } catch (e) { 29 | return false; 30 | } 31 | } 32 | 33 | function loadInitialPayload(): string[] { 34 | const payloads: string[] = []; 35 | const joinPath = path.join(__dirname, 'join.txt'); 36 | const fileContent = fs.readFileSync(joinPath, 'utf8'); 37 | fileContent.split('\n').forEach((line) => { 38 | if (line.trim().length > 0) { 39 | payloads.push(line.trim()); 40 | } 41 | }); 42 | return payloads; 43 | } 44 | 45 | // --- Global Variables & Configuration --- 46 | const walletPublicKey: string = process.env.WALLET_PUBLIC_KEY || ''; 47 | const trackWallets: string = process.env.TRACK_WALLETS || ''; 48 | const maxSlippage: number = parseInt(process.env.MAX_SLIPPAGE || '0', 10); 49 | const solIn: number = parseFloat(process.env.SOL_IN || '0'); 50 | const maxSolSpend: number = parseFloat(process.env.MAX_SOL_SPEND || '0'); 51 | const allowRebuy: boolean = process.env.ALLOW_REBUY !== 'false'; 52 | const maxBuyAttempts: number = parseInt(process.env.MAX_BUY_ATTEMPTS || '0', 10); 53 | const debug: boolean = process.env.DEBUG !== 'false'; 54 | 55 | // Global runtime state 56 | let buyList: string[] = []; 57 | let startBalance: number = 0.0; 58 | let spent: number = 0.0; 59 | 60 | // Dummy implementations of external functions (should be replaced with actual imports) 61 | async function buyPumpFun(tokenAddress: string, solAmount: number, slippage: number): Promise { 62 | // Insert your actual trading logic here. 63 | if (debug) console.log(`Executing pump buy for ${tokenAddress} with ${solAmount} SOL at slippage ${slippage}`); 64 | return true; 65 | } 66 | 67 | async function sellPumpFun(tokenAddress: string, percentage: number, slippage: number): Promise { 68 | if (debug) console.log(`Executing pump sell for ${tokenAddress} of ${percentage}% at slippage ${slippage}`); 69 | return true; 70 | } 71 | 72 | async function buyRaydium(tokenAddress: string, solAmount: number, slippage: number): Promise { 73 | if (debug) console.log(`Executing raydium buy for ${tokenAddress} with ${solAmount} SOL at slippage ${slippage}`); 74 | return true; 75 | } 76 | 77 | async function sellRaydium(tokenAddress: string, percentage: number, slippage: number): Promise { 78 | if (debug) console.log(`Executing raydium sell for ${tokenAddress} of ${percentage}% at slippage ${slippage}`); 79 | return true; 80 | } 81 | 82 | // --- Network & Trading Functions --- 83 | async function refreshAccessToken(): Promise { 84 | try { 85 | console.log(`[${new Date().toLocaleString()}] Refreshing access token...`); 86 | const headers = { 87 | 'accept': 'application/json, text/plain, */*', 88 | 'accept-language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7', 89 | 'cache-control': 'no-cache', 90 | 'dnt': '1', 91 | 'origin': 'https://axiom.trade', 92 | 'pragma': 'no-cache', 93 | 'priority': 'u=1, i', 94 | 'referer': 'https://axiom.trade/', 95 | 'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"', 96 | 'sec-ch-ua-mobile': '?0', 97 | 'sec-ch-ua-platform': '"macOS"', 98 | 'sec-fetch-dest': 'empty', 99 | 'sec-fetch-mode': 'cors', 100 | 'sec-fetch-site': 'same-site', 101 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36', 102 | 'cookie': readCookie(), 103 | }; 104 | 105 | const response = await axios.post('https://api4.axiom.trade/refresh-access-token', null, { 106 | headers, 107 | // Disable certificate verification if necessary (not recommended in production) 108 | httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) 109 | }); 110 | 111 | if (response.status !== 200) { 112 | console.log(`[${new Date().toLocaleString()}] Could not refresh access token!`); 113 | console.log('Response:', response.data); 114 | process.exit(1); 115 | } 116 | 117 | const cookies: string = response.headers['set-cookie'] ? (response.headers['set-cookie'] as string[]).join(' ') : ''; 118 | let newCookie = ''; 119 | 120 | const reAuthRefresh = /auth-refresh-token=.*?;/s.exec(cookies); 121 | if (reAuthRefresh) { 122 | newCookie += reAuthRefresh[0]; 123 | } 124 | const reAuthAccess = /auth-access-token=.*?;/s.exec(cookies); 125 | if (reAuthAccess) { 126 | newCookie += ' ' + reAuthAccess[0]; 127 | } 128 | 129 | if (!writeCookie(newCookie)) { 130 | throw new Error('Could not write cookie!'); 131 | } 132 | console.log(`[${new Date().toLocaleString()}] Access token refreshed!`); 133 | } catch (e) { 134 | console.error(`[${new Date().toLocaleString()}] Could not refresh access token!`, e); 135 | process.exit(1); 136 | } 137 | } 138 | 139 | async function getBalance(): Promise { 140 | try { 141 | const payload = { 142 | jsonrpc: '2.0', 143 | id: 1, 144 | method: 'getBalance', 145 | params: [ 146 | walletPublicKey, 147 | { commitment: 'confirmed' } 148 | ] 149 | }; 150 | 151 | const headers = { 152 | 'accept': 'application/json, text/plain, */*', 153 | 'accept-language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7', 154 | 'cache-control': 'no-cache', 155 | 'dnt': '1', 156 | 'origin': 'https://axiom.trade', 157 | 'pragma': 'no-cache', 158 | 'priority': 'u=1, i', 159 | 'referer': 'https://axiom.trade/', 160 | 'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"', 161 | 'sec-ch-ua-mobile': '?0', 162 | 'sec-ch-ua-platform': '"macOS"', 163 | 'sec-fetch-dest': 'empty', 164 | 'sec-fetch-mode': 'cors', 165 | 'sec-fetch-site': 'same-site', 166 | 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36', 167 | 'cookie': readCookie(), 168 | }; 169 | 170 | const response = await axios.post('https://axiom.trade/api/sol-balance', payload, { headers }); 171 | if (response.status === 200 && response.data?.result?.value) { 172 | // Divide by 1_000_000_000 to convert lamports to SOL. 173 | return response.data.result.value / 1_000_000_000; 174 | } 175 | } catch (e) { 176 | return null; 177 | } 178 | return null; 179 | } 180 | 181 | function calculateMyShare(totalSol: number): number { 182 | return totalSol * solIn; 183 | } 184 | 185 | interface TransactionDetails { 186 | transaction_type?: string; 187 | trader_level?: string; 188 | token_amount?: string; 189 | maker_address?: string; 190 | price_sol?: string; 191 | price_usd?: string; 192 | total_sol?: string; 193 | total_usd?: string; 194 | created_at?: string; 195 | token_address?: string; 196 | pair_address?: string; 197 | token_name?: string; 198 | token_code?: string; 199 | protocol?: string; 200 | } 201 | 202 | function extractTransactionDetails(data: string): TransactionDetails { 203 | const details: TransactionDetails = {}; 204 | const patterns: { [key: string]: RegExp } = { 205 | transaction_type: /"type":"(buy|sell)"/, 206 | trader_level: /"trader_level":"(.*?)"/, 207 | token_amount: /"token_amount":([\d.]+)/, 208 | maker_address: /"maker_address":"(.*?)"/, 209 | price_sol: /"price_sol":([\d.]+)/, 210 | price_usd: /"price_usd":([\d.]+)/, 211 | total_sol: /"total_sol":([\d.]+)/, 212 | total_usd: /"total_usd":([\d.]+)/, 213 | created_at: /"created_at":"(.*?)"/, 214 | token_address: /"tokenAddress":"(.*?)"/, 215 | pair_address: /"pair_address":"(.*?)"/, 216 | token_name: /"tokenName":"(.*?)"/, 217 | token_code: /"tokenTicker":"(.*?)"/, 218 | protocol: /"protocol":"(.*?)"/, 219 | }; 220 | 221 | for (const key in patterns) { 222 | const match = patterns[key].exec(data); 223 | if (match) { 224 | details[key] = match[1]; 225 | } 226 | } 227 | return details; 228 | } 229 | 230 | async function buyToken( 231 | dex: string, 232 | name: string, 233 | address: string, 234 | solAmount: number, 235 | slippage: number 236 | ): Promise { 237 | let attempts = 0; 238 | while (true) { 239 | try { 240 | await sleep(500); 241 | if (!allowRebuy && buyList.includes(address)) { 242 | console.log(`[${new Date().toLocaleString()}] Already bought [${dex.toUpperCase()}] "${name}" [${attempts}]!`); 243 | break; 244 | } 245 | if (attempts > maxBuyAttempts) { 246 | console.log(`[${new Date().toLocaleString()}] Already attempted to buy [${dex.toUpperCase()}] "${name}" [${attempts}]!`); 247 | break; 248 | } 249 | attempts++; 250 | console.log(`[${new Date().toLocaleString()}] Trying to buy [${dex.toUpperCase()}] "${name}" [${attempts}]..`); 251 | buyList.push(address); 252 | let bought: boolean = false; 253 | if (dex === 'ra') { 254 | bought = await buyRaydium(address, solAmount, slippage); 255 | } else { 256 | bought = await buyPumpFun(address, solAmount, slippage); 257 | } 258 | if (bought) { 259 | spent += solAmount; 260 | console.log(`[${new Date().toLocaleString()}] Bought [${dex.toUpperCase()}] "${name}" [${attempts}]!`); 261 | break; 262 | } 263 | } catch (e) { 264 | console.log(`[${new Date().toLocaleString()}] FAILED to buy [${dex.toUpperCase()}] "${name}" [${attempts}]!`); 265 | } 266 | } 267 | } 268 | 269 | async function sellToken( 270 | dex: string, 271 | name: string, 272 | address: string, 273 | slippage: number 274 | ): Promise { 275 | let attempts = 0; 276 | while (true) { 277 | try { 278 | await sleep(500); 279 | console.log(`[${new Date().toLocaleString()}] Trying to sell [${dex.toUpperCase()}] "${name}" [${attempts}]..`); 280 | let sold: boolean | undefined; 281 | if (dex === 'ra') { 282 | sold = await sellRaydium(address, 100, slippage); 283 | } else { 284 | sold = await sellPumpFun(address, 100, slippage); 285 | } 286 | if (sold === undefined || sold) { 287 | break; 288 | } 289 | attempts++; 290 | } catch (e) { 291 | console.log(`[${new Date().toLocaleString()}] FAILED to sell [${dex.toUpperCase()}] "${name}" [${attempts}]! Trying again..`); 292 | attempts++; 293 | } 294 | } 295 | } 296 | 297 | async function sendPing(ws: WebSocket): Promise { 298 | while (ws.readyState === WebSocket.OPEN) { 299 | await sleep(25000); 300 | if (debug) console.log('Sending PING..'); 301 | const pingPayload = JSON.stringify({ method: "ping" }); 302 | ws.send(pingPayload); 303 | if (debug) console.log('Sent', pingPayload); 304 | } 305 | } 306 | 307 | async function getCurrentBalance(): Promise { 308 | while (true) { 309 | const balance = await getBalance(); 310 | if (balance !== null) { 311 | const diff = balance - startBalance; 312 | startBalance = balance; 313 | if (diff !== 0.0) { 314 | console.log(`[${new Date().toLocaleString()}] Balance: ${startBalance} | Diff: ${diff.toFixed(9)}`); 315 | } 316 | } 317 | await sleep(5000); 318 | } 319 | } 320 | 321 | function filterMessages(msg: string): TransactionDetails | false { 322 | const regex = new RegExp(`.*(${trackWallets}).*`, 's'); 323 | const match = regex.exec(msg); 324 | if (match) { 325 | return extractTransactionDetails(match[0]); 326 | } 327 | return false; 328 | } 329 | 330 | async function connect(): Promise { 331 | while (true) { 332 | try { 333 | const headers: { [key: string]: string } = { 334 | 'Origin': 'https://axiom.trade', 335 | 'Cache-Control': 'no-cache', 336 | 'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7', 337 | 'Pragma': 'no-cache', 338 | 'Connection': 'Upgrade', 339 | 'Sec-WebSocket-Key': 'NaBrA7Cq2xZiTicaYSIbTw==', 340 | 'Cookie': readCookie(), 341 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36', 342 | 'Sec-WebSocket-Version': '13', 343 | 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits' 344 | }; 345 | 346 | console.log(`[${new Date().toLocaleString()}] Connecting..`); 347 | const ws = new WebSocket('wss://cluster2.axiom.trade?', { headers }); 348 | 349 | ws.on('open', () => { 350 | console.log(`[${new Date().toLocaleString()}] Connected! Sending payload...`); 351 | const payloads = loadInitialPayload(); 352 | payloads.forEach((payload) => { 353 | ws.send(payload); 354 | if (debug) console.log('Sent', payload); 355 | }); 356 | console.log(`[${new Date().toLocaleString()}] Payload sent!`); 357 | 358 | // Kick off periodic tasks 359 | sendPing(ws).catch(console.error); 360 | getCurrentBalance().catch(console.error); 361 | }); 362 | 363 | ws.on('message', async (data: WebSocket.Data) => { 364 | // Assume data is string. 365 | const message = typeof data === 'string' ? data : data.toString(); 366 | if (spent >= maxSolSpend) { 367 | console.log(`[${new Date().toLocaleString()}] Already spent ${spent}! Max: ${maxSolSpend}`); 368 | return; 369 | } 370 | const filtered = filterMessages(message); 371 | if (filtered) { 372 | const { transaction_type, protocol, maker_address, token_name, token_address, pair_address } = filtered; 373 | if (!maker_address || !maker_address.includes(trackWallets)) return; 374 | // Map protocols to functions 375 | const protocolMap: { [key: string]: { buy?: () => Promise; sell?: () => Promise } } = { 376 | 'Pump V1': { 377 | sell: () => sellToken('pf', token_name || '', token_address || '', maxSlippage), 378 | buy: () => buyToken('pf', token_name || '', token_address || '', solIn, maxSlippage), 379 | }, 380 | 'Raydium V4': { 381 | sell: () => sellToken('ra', token_name || '', pair_address || '', maxSlippage), 382 | // Uncomment or implement buy if needed. 383 | // buy: () => buyToken('ra', token_name || '', token_address || '', solIn, maxSlippage), 384 | } 385 | }; 386 | const action = protocolMap[protocol || '']?.[transaction_type || '']; 387 | if (!action) { 388 | console.log(`[${new Date().toLocaleString()}] Not supported ${transaction_type?.toUpperCase()}: ${protocol}! "${token_name}" : ${token_address}`); 389 | return; 390 | } 391 | await action(); 392 | } 393 | }); 394 | 395 | ws.on('close', async (code, reason) => { 396 | console.log(`[${new Date().toLocaleString()}] Connection closed: ${code} ${reason.toString()}. Trying to reconnect in 5 seconds...`); 397 | await refreshAccessToken(); 398 | await sleep(5000); 399 | }); 400 | 401 | ws.on('error', async (err) => { 402 | console.error(`[${new Date().toLocaleString()}] WebSocket error: ${err.message}`); 403 | ws.close(); 404 | }); 405 | 406 | // Wait here until connection is closed. 407 | await new Promise((resolve) => ws.on('close', resolve)); 408 | } catch (e) { 409 | console.error(`[${new Date().toLocaleString()}] Error:`, e); 410 | break; 411 | } 412 | } 413 | } 414 | 415 | // --- Main Execution --- 416 | (async () => { 417 | await refreshAccessToken(); 418 | await connect(); 419 | })(); 420 | -------------------------------------------------------------------------------- /raydium-copy-trading/utils/clmm_utils.ts: -------------------------------------------------------------------------------- 1 | // Constants 2 | const MIN_TICK = -443636; 3 | const MAX_TICK = 443636; 4 | const TICK_ARRAY_SIZE = 60; 5 | const TICK_ARRAY_BITMAP_SIZE = 512; 6 | const TOTAL_BITS = 1024; 7 | const U1024_MASK = (BigInt(1) << BigInt(TOTAL_BITS)) - BigInt(1); 8 | 9 | // Helper function to pack a 32‐bit signed integer in big‐endian format. 10 | function packInt32BE(n: number): Buffer { 11 | const buf = Buffer.alloc(4); 12 | buf.writeInt32BE(n, 0); 13 | return buf; 14 | } 15 | 16 | // Placeholder types – adjust these as needed. 17 | type Pubkey = { 18 | toBuffer: () => Buffer; 19 | // stub for findProgramAddress 20 | // In practice, your library should supply an async method returning [Pubkey, number] 21 | // For this conversion we assume a synchronous call. 22 | // You can adapt these functions if findProgramAddress is asynchronous. 23 | // (For example, wrap these functions in an async helper.) 24 | // Here we use a static method below. 25 | }; 26 | 27 | class Pubkey { 28 | // dummy internal representation; replace with your actual implementation. 29 | value: Buffer; 30 | constructor(value: Buffer) { 31 | this.value = value; 32 | } 33 | static async findProgramAddress( 34 | seeds: Buffer[], 35 | programId: Pubkey 36 | ): Promise<[Pubkey, number]> { 37 | // Dummy implementation; replace with your actual PDA derivation code. 38 | // For our purposes, we simply join the seeds. 39 | const combined = Buffer.concat(seeds); 40 | return [new Pubkey(combined), 0]; 41 | } 42 | } 43 | 44 | // Assume RAYDIUM_CLMM is a Pubkey; adjust its type/import accordingly. 45 | const RAYDIUM_CLMM = new Pubkey(Buffer.from("RaydiumCLMM")); 46 | 47 | // 48 | // Conversion of Python functions into TypeScript 49 | // 50 | 51 | // 1. load_current_and_next_tick_arrays -> loadCurrentAndNextTickArrays 52 | export async function loadCurrentAndNextTickArrays( 53 | poolId: Pubkey, 54 | tickCurrent: number, 55 | tickSpacing: number, 56 | tickArrayBitmap: number[], // array of numbers (e.g. 16 × 64‐bit words for u1024) 57 | tickarrayBitmapExtension: [number[], number[]], // [positive: number[], negative: number[]] 58 | zeroForOne: boolean 59 | ): Promise { 60 | // getFirstInitializedTickArray returns a tuple: [isInitialized, startIndex] 61 | let [initialized, currentValidTickArrayStartIndex] = 62 | getFirstInitializedTickArray(tickCurrent, tickSpacing, tickArrayBitmap, tickarrayBitmapExtension, zeroForOne); 63 | let tickArrayKeys: Pubkey[] = []; 64 | let initialKey = getPdaTickArrayAddress(poolId, currentValidTickArrayStartIndex); 65 | tickArrayKeys.push(initialKey); 66 | 67 | // Fetch up to 5 subsequent initialized tick arrays. 68 | for (let i = 0; i < 5; i++) { 69 | const nextTickArrayIndex = nextInitializedTickArrayStartIndex( 70 | tickArrayBitmap, 71 | tickarrayBitmapExtension, 72 | currentValidTickArrayStartIndex, 73 | tickSpacing, 74 | zeroForOne 75 | ); 76 | if (nextTickArrayIndex === null) break; 77 | currentValidTickArrayStartIndex = nextTickArrayIndex; 78 | const nextKey = getPdaTickArrayAddress(poolId, currentValidTickArrayStartIndex); 79 | tickArrayKeys.push(nextKey); 80 | } 81 | return tickArrayKeys; 82 | } 83 | 84 | // 2. get_pda_tick_array_address -> getPdaTickArrayAddress 85 | export function getPdaTickArrayAddress(poolId: Pubkey, startIndex: number): Pubkey { 86 | const seed = Buffer.concat([ 87 | Buffer.from("tick_array"), 88 | poolId.toBuffer(), 89 | packInt32BE(startIndex), 90 | ]); 91 | // findProgramAddress returns a tuple [pda, bump]; we take the PDA. 92 | // If findProgramAddress is asynchronous, mark this function async. 93 | // For brevity we assume synchronous behavior or wrap in an async helper. 94 | // Here we use "await" inside an async immediate function. 95 | let pda: Pubkey; 96 | // In real code, use: const [pda] = await Pubkey.findProgramAddress(...) 97 | // For synchronous example: 98 | Pubkey.findProgramAddress([Buffer.from("tick_array"), poolId.toBuffer(), packInt32BE(startIndex)], RAYDIUM_CLMM) 99 | .then(([key]) => pda = key); 100 | // In this stub, we simply return a new Pubkey. 101 | return new Pubkey(seed); 102 | } 103 | 104 | // 3. get_pda_tick_array_bitmap_extension -> getPdaTickArrayBitmapExtension 105 | export async function getPdaTickArrayBitmapExtension(poolId: Pubkey): Promise { 106 | const [bitmapExtension] = await Pubkey.findProgramAddress( 107 | [Buffer.from("pool_tick_array_bitmap_extension"), poolId.toBuffer()], 108 | RAYDIUM_CLMM 109 | ); 110 | return bitmapExtension; 111 | } 112 | 113 | // 4. next_initialized_tick -> nextInitializedTick 114 | export function nextInitializedTick( 115 | currentTickIndex: number, 116 | tickSpacing: number, 117 | zeroForOne: boolean, 118 | tickArrayCurrent: { start_tick_index: number; ticks: { liquidity_gross: number }[] } 119 | ): { liquidity_gross: number } | null { 120 | const currentTickArrayStartIndex = getArrayStartIndex(currentTickIndex, tickSpacing); 121 | const startTickIndex = tickArrayCurrent.start_tick_index; 122 | if (currentTickArrayStartIndex !== startTickIndex) { 123 | return null; 124 | } 125 | let offsetInArray = Math.floor((currentTickIndex - startTickIndex) / tickSpacing); 126 | if (zeroForOne) { 127 | while (offsetInArray >= 0) { 128 | const tick = tickArrayCurrent.ticks[offsetInArray]; 129 | if (tick !== undefined && tick.liquidity_gross !== 0) { 130 | return tick; 131 | } 132 | offsetInArray--; 133 | } 134 | } else { 135 | offsetInArray++; 136 | while (offsetInArray < tickArrayCurrent.ticks.length) { 137 | const tick = tickArrayCurrent.ticks[offsetInArray]; 138 | if (tick !== undefined && tick.liquidity_gross !== 0) { 139 | return tick; 140 | } 141 | offsetInArray++; 142 | } 143 | } 144 | return null; 145 | } 146 | 147 | // 5. get_first_initialized_tick_array -> getFirstInitializedTickArray 148 | // Returns a tuple: [isInitialized: boolean, startIndex: number] 149 | export function getFirstInitializedTickArray( 150 | tickCurrent: number, 151 | tickSpacing: number, 152 | tickArrayBitmap: number[], 153 | tickarrayBitmapExtension: [number[], number[]], 154 | zeroForOne: boolean 155 | ): [boolean, number] { 156 | const tickArrayStartIndex = getArrayStartIndex(tickCurrent, tickSpacing); 157 | if (isOverflowDefaultTickarrayBitmap([tickCurrent], tickSpacing)) { 158 | const [initialized, startIndex] = checkTickArrayIsInitialized(tickArrayStartIndex, tickSpacing, tickarrayBitmapExtension); 159 | if (initialized) return [true, startIndex]; 160 | } else { 161 | const u1024Bitmap = bitmapListToU1024(tickArrayBitmap); 162 | const [initialized, startIndex] = checkCurrentTickArrayIsInitialized(u1024Bitmap, tickCurrent, tickSpacing); 163 | if (initialized) return [true, startIndex]; 164 | } 165 | const nextStartIndex = nextInitializedTickArrayStartIndex(tickArrayBitmap, tickarrayBitmapExtension, tickArrayStartIndex, tickSpacing, zeroForOne); 166 | return [false, nextStartIndex === null ? tickArrayStartIndex : nextStartIndex]; 167 | } 168 | 169 | // 6. is_overflow_default_tickarray_bitmap -> isOverflowDefaultTickarrayBitmap 170 | export function isOverflowDefaultTickarrayBitmap(tickIndices: number[], tickSpacing: number): boolean { 171 | function tickArrayStartIndexRange(tickSpacing: number): [number, number] { 172 | let maxTickBoundary = tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE; 173 | let minTickBoundary = -maxTickBoundary; 174 | if (maxTickBoundary > MAX_TICK) { 175 | maxTickBoundary = getArrayStartIndex(MAX_TICK, tickSpacing) + tickCount(tickSpacing); 176 | } 177 | if (minTickBoundary < MIN_TICK) { 178 | minTickBoundary = getArrayStartIndex(MIN_TICK, tickSpacing); 179 | } 180 | return [minTickBoundary, maxTickBoundary]; 181 | } 182 | const [minBound, maxBound] = tickArrayStartIndexRange(tickSpacing); 183 | for (const tickIndex of tickIndices) { 184 | const tickArrayStartIndex = getArrayStartIndex(tickIndex, tickSpacing); 185 | if (tickArrayStartIndex >= maxBound || tickArrayStartIndex < minBound) { 186 | return true; 187 | } 188 | } 189 | return false; 190 | } 191 | 192 | // 7. check_tick_array_is_initialized -> checkTickArrayIsInitialized 193 | // Returns [initialized: boolean, tickArrayStartIndex: number] 194 | export function checkTickArrayIsInitialized( 195 | tickArrayStartIndex: number, 196 | tickSpacing: number, 197 | tickarrayBitmapExtension: [number[], number[]] 198 | ): [boolean, number] { 199 | function calcTickArrayOffsetInBitmap(tickArrayStartIndex: number, tickSpacing: number): number { 200 | const m = Math.abs(tickArrayStartIndex) % (tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE); 201 | let tickArrayOffset = Math.floor(m / (TICK_ARRAY_SIZE * tickSpacing)); 202 | if (tickArrayStartIndex < 0 && m !== 0) { 203 | tickArrayOffset = TICK_ARRAY_BITMAP_SIZE - tickArrayOffset; 204 | } 205 | return tickArrayOffset; 206 | } 207 | const [positiveBitmap, negativeBitmap] = tickarrayBitmapExtension; 208 | const [_, bitmap] = getBitmap(tickArrayStartIndex, tickSpacing, positiveBitmap, negativeBitmap); 209 | const tickArrayOffset = calcTickArrayOffsetInBitmap(tickArrayStartIndex, tickSpacing); 210 | // Reconstruct 512-bit value from bitmap parts. Each part is 64 bits. 211 | let u512 = BigInt(0); 212 | for (let i = 0; i < bitmap.length; i++) { 213 | // Overwrite at offset (7 - i)*64. 214 | u512 |= BigInt(bitmap[i]) << BigInt((7 - i) * 64); 215 | } 216 | // Check the bit at position tickArrayOffset (from LSB) 217 | const bit = (u512 >> BigInt(tickArrayOffset)) & BigInt(1); 218 | const initialized = (bit === BigInt(1)); 219 | return [initialized, tickArrayStartIndex]; 220 | } 221 | 222 | // 8. bitmap_list_to_u1024 -> bitmapListToU1024 223 | export function bitmapListToU1024(bitmapList: number[]): bigint { 224 | if (bitmapList.length !== 16) { 225 | throw new Error("Bitmap list must have exactly 16 elements."); 226 | } 227 | let result = BigInt(0); 228 | for (let i = 0; i < bitmapList.length; i++) { 229 | result += BigInt(bitmapList[i]) << BigInt(64 * i); 230 | } 231 | return result; 232 | } 233 | 234 | // 9. check_current_tick_array_is_initialized -> checkCurrentTickArrayIsInitialized 235 | // Returns [initialized: boolean, resultTick: number] 236 | export function checkCurrentTickArrayIsInitialized( 237 | bitMap: bigint, 238 | tickCurrent: number, 239 | tickSpacing: number 240 | ): [boolean, number] { 241 | const multiplier = tickSpacing * TICK_ARRAY_SIZE; 242 | let compressed = Math.floor(tickCurrent / multiplier) + 512; 243 | if (tickCurrent < 0 && (tickCurrent % multiplier) !== 0) { 244 | compressed -= 1; 245 | } 246 | const bitPos = Math.abs(compressed); 247 | const mask = BigInt(1) << BigInt(bitPos); 248 | const masked = bitMap & mask; 249 | const initialized = (masked !== BigInt(0)); 250 | const resultTick = (compressed - 512) * multiplier; 251 | return [initialized, resultTick]; 252 | } 253 | 254 | // 10. next_initialized_tick_array_start_index -> nextInitializedTickArrayStartIndex 255 | export function nextInitializedTickArrayStartIndex( 256 | tickArrayBitmap: number[], 257 | tickarrayBitmapExtension: [number[], number[]], 258 | lastTickArrayStartIndex: number, 259 | tickSpacing: number, 260 | zeroForOne: boolean 261 | ): number | null { 262 | lastTickArrayStartIndex = getArrayStartIndex(lastTickArrayStartIndex, tickSpacing); 263 | let iteration = 0; 264 | while (true) { 265 | iteration++; 266 | const [found, startIndex] = nextInitializedTickArrayStartIndexInBitmap( 267 | u1024FromList(tickArrayBitmap), 268 | lastTickArrayStartIndex, 269 | tickSpacing, 270 | zeroForOne 271 | ); 272 | if (found) { 273 | return startIndex; 274 | } 275 | lastTickArrayStartIndex = startIndex; 276 | const [found2, startIndex2] = nextInitializedTickArrayFromOneBitmap( 277 | lastTickArrayStartIndex, 278 | tickSpacing, 279 | zeroForOne, 280 | tickarrayBitmapExtension 281 | ); 282 | if (found2) { 283 | return startIndex2; 284 | } 285 | lastTickArrayStartIndex = startIndex2; 286 | if (lastTickArrayStartIndex < MIN_TICK || lastTickArrayStartIndex > MAX_TICK) { 287 | return null; 288 | } 289 | } 290 | } 291 | 292 | // 11. u1024_from_list -> u1024FromList 293 | export function u1024FromList(words: number[]): bigint { 294 | let value = BigInt(0); 295 | for (let i = 0; i < words.length; i++) { 296 | value |= BigInt(words[i]) << BigInt(64 * i); 297 | } 298 | return value & U1024_MASK; 299 | } 300 | 301 | // 12. max_tick_in_tickarray_bitmap -> maxTickInTickarrayBitmap 302 | export function maxTickInTickarrayBitmap(tickSpacing: number): number { 303 | return tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE; 304 | } 305 | 306 | // 13. most_significant_bit -> mostSignificantBit 307 | export function mostSignificantBit(x: bigint): number | null { 308 | if (x === BigInt(0)) return null; 309 | // Use string representation in base 2 310 | const bitLength = x.toString(2).length; 311 | return TOTAL_BITS - bitLength; 312 | } 313 | 314 | // 14. least_significant_bit -> leastSignificantBit 315 | export function leastSignificantBit(x: bigint): number | null { 316 | if (x === BigInt(0)) return null; 317 | // (x & -x) isolates the lowest set bit. 318 | return ( (x & -x).toString(2).length ) - 1; 319 | } 320 | 321 | // 15. next_initialized_tick_array_start_index_in_bitmap -> nextInitializedTickArrayStartIndexInBitmap 322 | export function nextInitializedTickArrayStartIndexInBitmap( 323 | bitMap: bigint, 324 | lastTickArrayStartIndex: number, 325 | tickSpacing: number, 326 | zeroForOne: boolean 327 | ): [boolean, number] { 328 | // Ensure bitMap is masked correctly. 329 | bitMap &= U1024_MASK; 330 | const tickBoundary = maxTickInTickarrayBitmap(tickSpacing); 331 | let nextTickArrayStartIndex: number; 332 | if (zeroForOne) { 333 | nextTickArrayStartIndex = lastTickArrayStartIndex - tickCount(tickSpacing); 334 | } else { 335 | nextTickArrayStartIndex = lastTickArrayStartIndex + tickCount(tickSpacing); 336 | } 337 | if (nextTickArrayStartIndex < -tickBoundary || nextTickArrayStartIndex >= tickBoundary) { 338 | return [false, lastTickArrayStartIndex]; 339 | } 340 | const multiplier = tickSpacing * TICK_ARRAY_SIZE; 341 | let compressed = Math.floor(nextTickArrayStartIndex / multiplier) + 512; 342 | if (nextTickArrayStartIndex < 0 && (nextTickArrayStartIndex % multiplier) !== 0) { 343 | compressed -= 1; 344 | } 345 | const bitPos = Math.abs(compressed); 346 | if (zeroForOne) { 347 | const shiftAmount = BigInt(TOTAL_BITS - bitPos - 1); 348 | const offsetBitMap = (bitMap << shiftAmount) & U1024_MASK; 349 | const nextBit = mostSignificantBit(offsetBitMap); 350 | if (nextBit !== null) { 351 | const nextArrayStartIndex = (bitPos - nextBit - 512) * multiplier; 352 | return [true, nextArrayStartIndex]; 353 | } else { 354 | return [false, -tickBoundary]; 355 | } 356 | } else { 357 | const offsetBitMap = bitMap >> BigInt(bitPos); 358 | const nextBit = leastSignificantBit(offsetBitMap); 359 | if (nextBit !== null) { 360 | const nextArrayStartIndex = (bitPos + nextBit - 512) * multiplier; 361 | return [true, nextArrayStartIndex]; 362 | } else { 363 | const fallback = tickBoundary - tickCount(tickSpacing); 364 | return [false, fallback]; 365 | } 366 | } 367 | } 368 | 369 | // 16. next_initialized_tick_array_from_one_bitmap -> nextInitializedTickArrayFromOneBitmap 370 | export function nextInitializedTickArrayFromOneBitmap( 371 | lastTickArrayStartIndex: number, 372 | tickSpacing: number, 373 | zeroForOne: boolean, 374 | tickarrayBitmapExtension: [number[], number[]] 375 | ): [boolean, number] { 376 | const multiplier = tickCount(tickSpacing); 377 | let nextTickArrayStartIndex: number; 378 | if (zeroForOne) { 379 | nextTickArrayStartIndex = lastTickArrayStartIndex - multiplier; 380 | } else { 381 | nextTickArrayStartIndex = lastTickArrayStartIndex + multiplier; 382 | } 383 | const minTickArrayStartIndex = getArrayStartIndex(MIN_TICK, tickSpacing); 384 | const maxTickArrayStartIndex = getArrayStartIndex(MAX_TICK, tickSpacing); 385 | if (nextTickArrayStartIndex < minTickArrayStartIndex || nextTickArrayStartIndex > maxTickArrayStartIndex) { 386 | return [false, nextTickArrayStartIndex]; 387 | } 388 | const [positiveBitmap, negativeBitmap] = tickarrayBitmapExtension; 389 | const [_, tickarrayBitmap] = getBitmap(nextTickArrayStartIndex, tickSpacing, positiveBitmap, negativeBitmap); 390 | return nextInitializedTickArrayInBitmap(tickarrayBitmap, nextTickArrayStartIndex, tickSpacing, zeroForOne); 391 | } 392 | 393 | // 17. next_initialized_tick_array_in_bitmap -> nextInitializedTickArrayInBitmap 394 | export function nextInitializedTickArrayInBitmap( 395 | tickarrayBitmap: number[], 396 | nextTickArrayStartIndex: number, 397 | tickSpacing: number, 398 | zeroForOne: boolean 399 | ): [boolean, number] { 400 | function u512IsZero(x: bigint): boolean { 401 | return x === BigInt(0); 402 | } 403 | function u512LeadingZeros(x: bigint): number { 404 | if (x === BigInt(0)) return 512; 405 | return 512 - x.toString(2).length; 406 | } 407 | function u512TrailingZeros(x: bigint): number { 408 | if (x === BigInt(0)) return 512; 409 | return ((x & -x).toString(2).length) - 1; 410 | } 411 | function calcTickArrayOffsetInBitmap(tickArrayStartIndex: number, tickSpacing: number): number { 412 | const m = Math.abs(tickArrayStartIndex) % (tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE); 413 | let offset = Math.floor(m / (TICK_ARRAY_SIZE * tickSpacing)); 414 | if (tickArrayStartIndex < 0 && m !== 0) { 415 | offset = TICK_ARRAY_BITMAP_SIZE - offset; 416 | } 417 | return offset; 418 | } 419 | function getBitmapTickBoundary(tickArrayStartIndex: number, tickSpacing: number): [number, number] { 420 | const ticksInOneBitmap = tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE; 421 | let m = Math.floor(Math.abs(tickArrayStartIndex) / ticksInOneBitmap); 422 | if (tickArrayStartIndex < 0 && Math.abs(tickArrayStartIndex) % ticksInOneBitmap !== 0) { 423 | m += 1; 424 | } 425 | const minValue = ticksInOneBitmap * m; 426 | if (tickArrayStartIndex < 0) { 427 | return [-minValue, -minValue + ticksInOneBitmap]; 428 | } else { 429 | return [minValue, minValue + ticksInOneBitmap]; 430 | } 431 | } 432 | const [bitmapMinBoundary, bitmapMaxBoundary] = getBitmapTickBoundary(nextTickArrayStartIndex, tickSpacing); 433 | const tickArrayOffset = calcTickArrayOffsetInBitmap(nextTickArrayStartIndex, tickSpacing); 434 | // Reconstruct u512 from tickarrayBitmap parts (each is 64 bits). 435 | let u512TickarrayBitmap = BigInt(0); 436 | for (let i = 0; i < tickarrayBitmap.length; i++) { 437 | u512TickarrayBitmap |= BigInt(tickarrayBitmap[i]) << BigInt((7 - i) * 64); 438 | } 439 | if (zeroForOne) { 440 | const offsetBitMap = (u512TickarrayBitmap << BigInt(TICK_ARRAY_BITMAP_SIZE - 1 - tickArrayOffset)) & ((BigInt(1) << BigInt(TICK_ARRAY_BITMAP_SIZE)) - BigInt(1)); 441 | const offsetInt = offsetBitMap; 442 | let nextBit: number | null = null; 443 | if (u512IsZero(offsetInt)) { 444 | nextBit = null; 445 | } else { 446 | nextBit = u512LeadingZeros(offsetInt); 447 | } 448 | if (nextBit !== null) { 449 | const nextArrayStartIndex = nextTickArrayStartIndex - nextBit * tickCount(tickSpacing); 450 | return [true, nextArrayStartIndex]; 451 | } else { 452 | return [false, bitmapMinBoundary]; 453 | } 454 | } else { 455 | const offsetBitMap = u512TickarrayBitmap >> BigInt(tickArrayOffset); 456 | const offsetInt = offsetBitMap; 457 | let nextBit: number | null = null; 458 | if (u512IsZero(offsetInt)) { 459 | nextBit = null; 460 | } else { 461 | nextBit = u512TrailingZeros(offsetInt); 462 | } 463 | if (nextBit !== null) { 464 | const nextArrayStartIndex = nextTickArrayStartIndex + nextBit * tickCount(tickSpacing); 465 | return [true, nextArrayStartIndex]; 466 | } else { 467 | return [false, bitmapMaxBoundary - tickCount(tickSpacing)]; 468 | } 469 | } 470 | } 471 | 472 | // 18. get_bitmap -> getBitmap 473 | // Returns a tuple: [offset: number, bitmap: number[]] 474 | // Here, positiveBitmap and negativeBitmap are arrays of numbers. 475 | export function getBitmap( 476 | tickIndex: number, 477 | tickSpacing: number, 478 | positiveBitmap: number[], 479 | negativeBitmap: number[] 480 | ): [number, number[]] { 481 | const ticksInOneBitmap = tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE; 482 | let offset = Math.floor(Math.abs(tickIndex) / ticksInOneBitmap) - 1; 483 | if (tickIndex < 0 && Math.abs(tickIndex) % ticksInOneBitmap === 0) { 484 | offset -= 1; 485 | } 486 | if (tickIndex < 0) { 487 | return [offset, negativeBitmap[offset]]; 488 | } else { 489 | return [offset, positiveBitmap[offset]]; 490 | } 491 | } 492 | 493 | // 19. tick_count -> tickCount 494 | export function tickCount(tickSpacing: number): number { 495 | return TICK_ARRAY_SIZE * tickSpacing; 496 | } 497 | 498 | // 20. get_array_start_index -> getArrayStartIndex 499 | export function getArrayStartIndex(tickIndex: number, tickSpacing: number): number { 500 | const ticksInArray = tickCount(tickSpacing); 501 | let start = Math.floor(tickIndex / ticksInArray); 502 | if (tickIndex < 0 && tickIndex % ticksInArray !== 0) { 503 | start -= 1; 504 | } 505 | return start * ticksInArray; 506 | } 507 | -------------------------------------------------------------------------------- /raydium-copy-trading/utils/pool_utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | AccountInfo, 5 | TransactionInstruction, 6 | } from '@solana/web3.js'; 7 | import BN from "bn.js"; 8 | import { Buffer } from 'buffer'; 9 | // Import or implement layout decoders accordingly. For example: 10 | import { LIQUIDITY_STATE_LAYOUT_V4, MARKET_STATE_LAYOUT_V3 } from '@raydium-io/raydium-sdk'; 11 | import { TOKEN_PROGRAM_ID, WSOL, RAYDIUM_AMM_V4, RAYDIUM_CPMM, RAYDIUM_CLMM, MEMO_PROGRAM_V2, TOKEN_2022_PROGRAM_ID, DEFAULT_QUOTE_MINT } from './constants'; 12 | 13 | // Define TypeScript interfaces matching the dataclasses. 14 | 15 | export interface AmmV4PoolKeys { 16 | ammId: PublicKey; 17 | baseMint: PublicKey; 18 | quoteMint: PublicKey; 19 | baseDecimals: number; 20 | quoteDecimals: number; 21 | openOrders: PublicKey; 22 | targetOrders: PublicKey; 23 | baseVault: PublicKey; 24 | quoteVault: PublicKey; 25 | marketId: PublicKey; 26 | marketAuthority: PublicKey; 27 | marketBaseVault: PublicKey; 28 | marketQuoteVault: PublicKey; 29 | bids: PublicKey; 30 | asks: PublicKey; 31 | eventQueue: PublicKey; 32 | rayAuthorityV4: PublicKey; 33 | openBookProgram: PublicKey; 34 | tokenProgramId: PublicKey; 35 | } 36 | 37 | export interface CpmmPoolKeys { 38 | poolState: PublicKey; 39 | raydiumVaultAuth2: PublicKey; 40 | ammConfig: PublicKey; 41 | poolCreator: PublicKey; 42 | token0Vault: PublicKey; 43 | token1Vault: PublicKey; 44 | lpMint: PublicKey; 45 | token0Mint: PublicKey; 46 | token1Mint: PublicKey; 47 | token0Program: PublicKey; 48 | token1Program: PublicKey; 49 | observationKey: PublicKey; 50 | authBump: number; 51 | status: number; 52 | lpMintDecimals: number; 53 | mint0Decimals: number; 54 | mint1Decimals: number; 55 | lpSupply: number; 56 | protocolFeesToken0: number; 57 | protocolFeesToken1: number; 58 | fundFeesToken0: number; 59 | fundFeesToken1: number; 60 | openTime: number; 61 | } 62 | 63 | export interface ClmmPoolKeys { 64 | poolState: PublicKey; 65 | ammConfig: PublicKey; 66 | owner: PublicKey; 67 | tokenMint0: PublicKey; 68 | tokenMint1: PublicKey; 69 | tokenVault0: PublicKey; 70 | tokenVault1: PublicKey; 71 | observationKey: PublicKey; 72 | currentTickArray: PublicKey; 73 | nextTickArray1: PublicKey; 74 | nextTickArray2: PublicKey; 75 | bitmapExtension: PublicKey; 76 | mintDecimals0: number; 77 | mintDecimals1: number; 78 | tickSpacing: number; 79 | liquidity: number; 80 | sqrtPriceX64: BN; 81 | tickCurrent: number; 82 | observationIndex: number; 83 | observationUpdateDuration: number; 84 | feeGrowthGlobal0X64: BN; 85 | feeGrowthGlobal1X64: BN; 86 | protocolFeesToken0: number; 87 | protocolFeesToken1: number; 88 | swapInAmountToken0: number; 89 | swapOutAmountToken1: number; 90 | swapInAmountToken1: number; 91 | swapOutAmountToken0: number; 92 | status: number; 93 | totalFeesToken0: number; 94 | totalFeesClaimedToken0: number; 95 | totalFeesToken1: number; 96 | totalFeesClaimedToken1: number; 97 | fundFeesToken0: number; 98 | fundFeesToken1: number; 99 | } 100 | 101 | // For the direction of an order 102 | export enum Direction { 103 | BUY = 0, 104 | SELL = 1, 105 | } 106 | 107 | // Helper to pack a u64 value into a Buffer little-endian 108 | function packU64(value: number): Buffer { 109 | if (!(0 <= value && value < 2 ** 64)) { 110 | throw new Error("Value must be in the range of a u64 (0 to 2^64 - 1)."); 111 | } 112 | const bn = new BN(value); 113 | return bn.toArrayLike(Buffer, 'le', 8); 114 | } 115 | 116 | // fetchAmmV4PoolKeys mirrors the Python version: fetch account data, decode via layouts, and build keys. 117 | export async function fetchAmmV4PoolKeys(pairAddress: string, connection: Connection): Promise { 118 | try { 119 | const ammId = new PublicKey(pairAddress); 120 | const ammInfo = await connection.getParsedAccountInfo(ammId); 121 | if (!ammInfo.value || !ammInfo.value.data) { 122 | throw new Error("AMM account not found"); 123 | } 124 | // Note: here we assume the account data is a Buffer. You might need to adjust if using parsed JSON. 125 | const ammBuffer: Buffer = (ammInfo.value.data as Buffer); 126 | const ammDataDecoded = LIQUIDITY_STATE_LAYOUT_V4.decode(ammBuffer); 127 | const marketId = new PublicKey(ammDataDecoded.serumMarket); 128 | const marketInfo = await connection.getParsedAccountInfo(marketId); 129 | if (!marketInfo.value || !marketInfo.value.data) { 130 | throw new Error("Market account not found"); 131 | } 132 | const marketBuffer: Buffer = (marketInfo.value.data as Buffer); 133 | const marketDecoded = MARKET_STATE_LAYOUT_V3.decode(marketBuffer); 134 | const vaultSignerNonce: number = marketDecoded.vaultSignerNonce; 135 | // Hardcoded constants similar to Python code 136 | const rayAuthorityV4 = new PublicKey('5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1'); 137 | const openBookProgram = new PublicKey('srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX'); 138 | const tokenProgramId = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); 139 | // Derive market authority using createProgramAddress with seeds. 140 | const marketAuthority = await PublicKey.createProgramAddress( 141 | [marketId.toBuffer(), packU64(vaultSignerNonce)], 142 | openBookProgram 143 | ); 144 | const poolKeys: AmmV4PoolKeys = { 145 | ammId, 146 | baseMint: new PublicKey(ammDataDecoded.baseMint), 147 | quoteMint: new PublicKey(ammDataDecoded.quoteMint), 148 | baseDecimals: ammDataDecoded.coinDecimals, 149 | quoteDecimals: ammDataDecoded.pcDecimals, 150 | openOrders: new PublicKey(ammDataDecoded.ammOpenOrders), 151 | targetOrders: new PublicKey(ammDataDecoded.ammTargetOrders), 152 | baseVault: new PublicKey(ammDataDecoded.poolCoinTokenAccount), 153 | quoteVault: new PublicKey(ammDataDecoded.poolPcTokenAccount), 154 | marketId, 155 | marketAuthority, 156 | marketBaseVault: new PublicKey(marketDecoded.baseVault), 157 | marketQuoteVault: new PublicKey(marketDecoded.quoteVault), 158 | bids: new PublicKey(marketDecoded.bids), 159 | asks: new PublicKey(marketDecoded.asks), 160 | eventQueue: new PublicKey(marketDecoded.eventQueue), 161 | rayAuthorityV4, 162 | openBookProgram, 163 | tokenProgramId, 164 | }; 165 | return poolKeys; 166 | } catch (e) { 167 | console.error("Error fetching AMMv4 pool keys:", e); 168 | return null; 169 | } 170 | } 171 | 172 | // The following similar functions can be implemented. Below is a sample for fetchCpmmPoolKeys. 173 | export async function fetchCpmmPoolKeys(pairAddress: string, connection: Connection): Promise { 174 | try { 175 | const poolState = new PublicKey(pairAddress); 176 | // Hardcoded constant 177 | const raydiumVaultAuth2 = new PublicKey('GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL'); 178 | const accountInfo = await connection.getParsedAccountInfo(poolState); 179 | if (!accountInfo.value) throw new Error("CPMM pool account not found"); 180 | // Assume CPMM_POOL_STATE_LAYOUT is imported/implemented to decode the account Buffer. 181 | const poolBuffer: Buffer = (accountInfo.value.data as Buffer); 182 | const parsedData = CPMM_POOL_STATE_LAYOUT.decode(poolBuffer); 183 | const poolKeys: CpmmPoolKeys = { 184 | poolState, 185 | raydiumVaultAuth2, 186 | ammConfig: new PublicKey(parsedData.amm_config), 187 | poolCreator: new PublicKey(parsedData.pool_creator), 188 | token0Vault: new PublicKey(parsedData.token_0_vault), 189 | token1Vault: new PublicKey(parsedData.token_1_vault), 190 | lpMint: new PublicKey(parsedData.lp_mint), 191 | token0Mint: new PublicKey(parsedData.token_0_mint), 192 | token1Mint: new PublicKey(parsedData.token_1_mint), 193 | token0Program: new PublicKey(parsedData.token_0_program), 194 | token1Program: new PublicKey(parsedData.token_1_program), 195 | observationKey: new PublicKey(parsedData.observation_key), 196 | authBump: parsedData.auth_bump, 197 | status: parsedData.status, 198 | lpMintDecimals: parsedData.lp_mint_decimals, 199 | mint0Decimals: parsedData.mint_0_decimals, 200 | mint1Decimals: parsedData.mint_1_decimals, 201 | lpSupply: parsedData.lp_supply, 202 | protocolFeesToken0: parsedData.protocol_fees_token_0, 203 | protocolFeesToken1: parsedData.protocol_fees_token_1, 204 | fundFeesToken0: parsedData.fund_fees_token_0, 205 | fundFeesToken1: parsedData.fund_fees_token_1, 206 | openTime: parsedData.open_time, 207 | }; 208 | return poolKeys; 209 | } catch (e) { 210 | console.error("Error fetching CPMM pool keys:", e); 211 | return null; 212 | } 213 | } 214 | 215 | // Similarly, fetchClmmPoolKeys would fetch the account and use CLMM_POOL_STATE_LAYOUT to decode, 216 | // then derive tick arrays using helper functions (implementation not shown here). 217 | export async function fetchClmmPoolKeys(pairAddress: string, connection: Connection, zeroForOne = true): Promise { 218 | try { 219 | const poolState = new PublicKey(pairAddress); 220 | const accountInfo = await connection.getParsedAccountInfo(poolState); 221 | if (!accountInfo.value) throw new Error("CLMM pool account not found"); 222 | const poolBuffer: Buffer = (accountInfo.value.data as Buffer); 223 | const parsedPoolData = CLMM_POOL_STATE_LAYOUT.decode(poolBuffer); 224 | // The implementations for tick array bitmap parsing and loading tick arrays are assumed available. 225 | // For this example, we assume you have helper functions similar to getPDATickArrayBitmapExtension and loadCurrentAndNextTickArrays. 226 | const currentTickArray = await loadCurrentAndNextTickArrays(poolState, parsedPoolData.tick_current, parsedPoolData.tick_spacing, parsedPoolData.tick_array_bitmap, /* extension data */ null, zeroForOne) 227 | .then((arrays) => arrays[0]); 228 | const nextTickArray1 = (await loadCurrentAndNextTickArrays(poolState, parsedPoolData.tick_current, parsedPoolData.tick_spacing, parsedPoolData.tick_array_bitmap, /* extension data */ null, zeroForOne))[1]; 229 | const nextTickArray2 = (await loadCurrentAndNextTickArrays(poolState, parsedPoolData.tick_current, parsedPoolData.tick_spacing, parsedPoolData.tick_array_bitmap, /* extension data */ null, zeroForOne))[2]; 230 | const bitmapExtension = await getPdaTickArrayBitmapExtension(poolState); 231 | const poolKeys: ClmmPoolKeys = { 232 | poolState, 233 | ammConfig: new PublicKey(parsedPoolData.amm_config), 234 | owner: new PublicKey(parsedPoolData.owner), 235 | tokenMint0: new PublicKey(parsedPoolData.token_mint_0), 236 | tokenMint1: new PublicKey(parsedPoolData.token_mint_1), 237 | tokenVault0: new PublicKey(parsedPoolData.token_vault_0), 238 | tokenVault1: new PublicKey(parsedPoolData.token_vault_1), 239 | observationKey: new PublicKey(parsedPoolData.observation_key), 240 | currentTickArray, 241 | nextTickArray1, 242 | nextTickArray2, 243 | bitmapExtension, 244 | mintDecimals0: parsedPoolData.mint_decimals_0, 245 | mintDecimals1: parsedPoolData.mint_decimals_1, 246 | tickSpacing: parsedPoolData.tick_spacing, 247 | liquidity: parsedPoolData.liquidity, 248 | sqrtPriceX64: new BN(parsedPoolData.sqrt_price_x64), 249 | tickCurrent: parsedPoolData.tick_current, 250 | observationIndex: parsedPoolData.observation_index, 251 | observationUpdateDuration: parsedPoolData.observation_update_duration, 252 | feeGrowthGlobal0X64: new BN(parsedPoolData.fee_growth_global_0_x64), 253 | feeGrowthGlobal1X64: new BN(parsedPoolData.fee_growth_global_1_x64), 254 | protocolFeesToken0: parsedPoolData.protocol_fees_token_0, 255 | protocolFeesToken1: parsedPoolData.protocol_fees_token_1, 256 | swapInAmountToken0: parsedPoolData.swap_in_amount_token_0, 257 | swapOutAmountToken1: parsedPoolData.swap_out_amount_token_1, 258 | swapInAmountToken1: parsedPoolData.swap_in_amount_token_1, 259 | swapOutAmountToken0: parsedPoolData.swap_out_amount_token_0, 260 | status: parsedPoolData.status, 261 | totalFeesToken0: parsedPoolData.total_fees_token_0, 262 | totalFeesClaimedToken0: parsedPoolData.total_fees_claimed_token_0, 263 | totalFeesToken1: parsedPoolData.total_fees_token_1, 264 | totalFeesClaimedToken1: parsedPoolData.total_fees_claimed_token_1, 265 | fundFeesToken0: parsedPoolData.fund_fees_token_0, 266 | fundFeesToken1: parsedPoolData.fund_fees_token_1, 267 | }; 268 | return poolKeys; 269 | } catch (e) { 270 | console.error("Error fetching CLMM pool keys:", e); 271 | return null; 272 | } 273 | } 274 | 275 | // Make swap instructions. The following example shows how to create the AMMv4 swap instruction. 276 | export function makeAmmV4SwapInstruction( 277 | amountIn: number, 278 | minimumAmountOut: number, 279 | tokenAccountIn: PublicKey, 280 | tokenAccountOut: PublicKey, 281 | poolKeys: AmmV4PoolKeys, 282 | owner: PublicKey 283 | ): TransactionInstruction { 284 | // Create AccountMeta array in the same order as required 285 | const keys = [ 286 | { pubkey: poolKeys.tokenProgramId, isSigner: false, isWritable: false }, 287 | { pubkey: poolKeys.ammId, isSigner: false, isWritable: true }, 288 | { pubkey: poolKeys.rayAuthorityV4, isSigner: false, isWritable: false }, 289 | { pubkey: poolKeys.openOrders, isSigner: false, isWritable: true }, 290 | { pubkey: poolKeys.targetOrders, isSigner: false, isWritable: true }, 291 | { pubkey: poolKeys.baseVault, isSigner: false, isWritable: true }, 292 | { pubkey: poolKeys.quoteVault, isSigner: false, isWritable: true }, 293 | { pubkey: poolKeys.openBookProgram, isSigner: false, isWritable: false }, 294 | { pubkey: poolKeys.marketId, isSigner: false, isWritable: true }, 295 | { pubkey: poolKeys.bids, isSigner: false, isWritable: true }, 296 | { pubkey: poolKeys.asks, isSigner: false, isWritable: true }, 297 | { pubkey: poolKeys.eventQueue, isSigner: false, isWritable: true }, 298 | { pubkey: poolKeys.marketBaseVault, isSigner: false, isWritable: true }, 299 | { pubkey: poolKeys.marketQuoteVault, isSigner: false, isWritable: true }, 300 | { pubkey: poolKeys.marketAuthority, isSigner: false, isWritable: false }, 301 | { pubkey: tokenAccountIn, isSigner: false, isWritable: true }, 302 | { pubkey: tokenAccountOut, isSigner: false, isWritable: true }, 303 | { pubkey: owner, isSigner: true, isWritable: false }, 304 | ]; 305 | // Build data buffer 306 | const bufferArray: Buffer[] = []; 307 | // Discriminator is 9 308 | bufferArray.push(Buffer.from([9])); 309 | bufferArray.push(packU64(amountIn)); 310 | bufferArray.push(packU64(minimumAmountOut)); 311 | const data = Buffer.concat(bufferArray); 312 | return new TransactionInstruction({ 313 | keys, 314 | programId: RAYDIUM_AMM_V4, 315 | data, 316 | }); 317 | } 318 | 319 | // Similarly, create instructions for CPMM and CLMM swaps. 320 | export function makeCpmmSwapInstruction( 321 | amountIn: number, 322 | minimumAmountOut: number, 323 | tokenAccountIn: PublicKey, 324 | tokenAccountOut: PublicKey, 325 | poolKeys: CpmmPoolKeys, 326 | owner: PublicKey, 327 | action: Direction 328 | ): TransactionInstruction { 329 | let inputVault: PublicKey, outputVault: PublicKey; 330 | let inputTokenProgram: PublicKey, outputTokenProgram: PublicKey; 331 | let inputTokenMint: PublicKey, outputTokenMint: PublicKey; 332 | if (action === Direction.BUY) { 333 | inputVault = poolKeys.token0Vault; 334 | outputVault = poolKeys.token1Vault; 335 | inputTokenProgram = poolKeys.token0Program; 336 | outputTokenProgram = poolKeys.token1Program; 337 | inputTokenMint = poolKeys.token0Mint; 338 | outputTokenMint = poolKeys.token1Mint; 339 | } else { 340 | inputVault = poolKeys.token1Vault; 341 | outputVault = poolKeys.token0Vault; 342 | inputTokenProgram = poolKeys.token1Program; 343 | outputTokenProgram = poolKeys.token0Program; 344 | inputTokenMint = poolKeys.token1Mint; 345 | outputTokenMint = poolKeys.token0Mint; 346 | } 347 | const keys = [ 348 | { pubkey: owner, isSigner: true, isWritable: true }, 349 | { pubkey: poolKeys.raydiumVaultAuth2, isSigner: false, isWritable: false }, 350 | { pubkey: poolKeys.ammConfig, isSigner: false, isWritable: false }, 351 | { pubkey: poolKeys.poolState, isSigner: false, isWritable: true }, 352 | { pubkey: tokenAccountIn, isSigner: false, isWritable: true }, 353 | { pubkey: tokenAccountOut, isSigner: false, isWritable: true }, 354 | { pubkey: inputVault, isSigner: false, isWritable: true }, 355 | { pubkey: outputVault, isSigner: false, isWritable: true }, 356 | { pubkey: inputTokenProgram, isSigner: false, isWritable: false }, 357 | { pubkey: outputTokenProgram, isSigner: false, isWritable: false }, 358 | { pubkey: inputTokenMint, isSigner: false, isWritable: false }, 359 | { pubkey: outputTokenMint, isSigner: false, isWritable: false }, 360 | { pubkey: poolKeys.observationKey, isSigner: false, isWritable: true }, 361 | ]; 362 | const dataArray: Buffer[] = []; 363 | // Using a fixed 4-byte method signature (hex string) for CPMM swap. 364 | dataArray.push(Buffer.from('8fbe5adac41e33de', 'hex')); 365 | dataArray.push(packU64(amountIn)); 366 | dataArray.push(packU64(minimumAmountOut)); 367 | const data = Buffer.concat(dataArray); 368 | return new TransactionInstruction({ 369 | keys, 370 | programId: RAYDIUM_CPMM, 371 | data, 372 | }); 373 | } 374 | 375 | export function makeClmmSwapInstruction( 376 | amount: number, 377 | tokenAccountIn: PublicKey, 378 | tokenAccountOut: PublicKey, 379 | poolKeys: ClmmPoolKeys, 380 | payer: PublicKey, 381 | action: Direction 382 | ): TransactionInstruction { 383 | let inputVault: PublicKey, outputVault: PublicKey; 384 | let inputMint: PublicKey, outputMint: PublicKey; 385 | if (action === Direction.BUY) { 386 | inputVault = poolKeys.tokenVault0; 387 | outputVault = poolKeys.tokenVault1; 388 | inputMint = poolKeys.tokenMint0; 389 | outputMint = poolKeys.tokenMint1; 390 | } else { 391 | inputVault = poolKeys.tokenVault1; 392 | outputVault = poolKeys.tokenVault0; 393 | inputMint = poolKeys.tokenMint1; 394 | outputMint = poolKeys.tokenMint0; 395 | } 396 | const keys = [ 397 | { pubkey: payer, isSigner: true, isWritable: true }, 398 | { pubkey: poolKeys.ammConfig, isSigner: false, isWritable: false }, 399 | { pubkey: poolKeys.poolState, isSigner: false, isWritable: true }, 400 | { pubkey: tokenAccountIn, isSigner: false, isWritable: true }, 401 | { pubkey: tokenAccountOut, isSigner: false, isWritable: true }, 402 | { pubkey: inputVault, isSigner: false, isWritable: true }, 403 | { pubkey: outputVault, isSigner: false, isWritable: true }, 404 | { pubkey: poolKeys.observationKey, isSigner: false, isWritable: true }, 405 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 406 | { pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false }, 407 | { pubkey: MEMO_PROGRAM_V2, isSigner: false, isWritable: false }, 408 | { pubkey: inputMint, isSigner: false, isWritable: false }, 409 | { pubkey: outputMint, isSigner: false, isWritable: false }, 410 | { pubkey: poolKeys.currentTickArray, isSigner: false, isWritable: true }, 411 | { pubkey: poolKeys.bitmapExtension, isSigner: false, isWritable: true }, 412 | { pubkey: poolKeys.nextTickArray1, isSigner: false, isWritable: true }, 413 | { pubkey: poolKeys.nextTickArray2, isSigner: false, isWritable: true }, 414 | ]; 415 | const dataArray: Buffer[] = []; 416 | // Swap instruction discriminator for CLMM swap (example hex value) 417 | dataArray.push(Buffer.from('2b04ed0b1ac91e62', 'hex')); 418 | dataArray.push(packU64(amount)); 419 | dataArray.push(packU64(0)); 420 | // Append 16 zero bytes and a boolean flag 421 | dataArray.push(Buffer.alloc(16, 0)); 422 | dataArray.push(Buffer.from([1])); // true as a byte 423 | const data = Buffer.concat(dataArray); 424 | return new TransactionInstruction({ 425 | keys, 426 | programId: new PublicKey('CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK'), 427 | data, 428 | }); 429 | } 430 | 431 | // Functions for fetching reserves follow similar logic. They use connection.getMultipleAccountsInfo. 432 | // Below is an example for AMMv4 reserves. 433 | export async function getAmmV4Reserves(poolKeys: AmmV4PoolKeys, connection: Connection): Promise<{ baseReserve: number; quoteReserve: number; tokenDecimal: number } | null> { 434 | try { 435 | const accounts = await connection.getMultipleAccountsInfo([poolKeys.quoteVault, poolKeys.baseVault]); 436 | if (!accounts || accounts.some(a => a === null)) { 437 | console.error("One or more account infos not found"); 438 | return null; 439 | } 440 | // Here we assume accounts[0] and accounts[1] are parsed; adjust extraction as needed. 441 | // For example, if using getParsedAccountInfo: 442 | const quoteParsed = (accounts[0]?.data as any).parsed; 443 | const baseParsed = (accounts[1]?.data as any).parsed; 444 | const quoteAccountBalance = quoteParsed.info.tokenAmount.uiAmount; 445 | const baseAccountBalance = baseParsed.info.tokenAmount.uiAmount; 446 | if (quoteAccountBalance === null || baseAccountBalance === null) { 447 | console.error("Error: One of the account balances is null."); 448 | return null; 449 | } 450 | let baseReserve: number, quoteReserve: number, tokenDecimal: number; 451 | if (poolKeys.baseMint.equals(WSOL)) { 452 | baseReserve = quoteAccountBalance; 453 | quoteReserve = baseAccountBalance; 454 | tokenDecimal = poolKeys.quoteDecimals; 455 | } else { 456 | baseReserve = baseAccountBalance; 457 | quoteReserve = quoteAccountBalance; 458 | tokenDecimal = poolKeys.baseDecimals; 459 | } 460 | return { baseReserve, quoteReserve, tokenDecimal }; 461 | } catch (e) { 462 | console.error("Error occurred:", e); 463 | return null; 464 | } 465 | } 466 | 467 | // Likewise, implement getCpmmReserves and getClmmReserves using similar logic 468 | // Finally, the functions to fetch pair addresses would use connection.getProgramAccounts with filters. 469 | // Below is an example of fetching pair addresses from RPC. 470 | import { MemcmpFilter, DataSizeFilter } from '@solana/web3.js'; 471 | 472 | export async function fetchPairAddressFromRpc( 473 | connection: Connection, 474 | programId: PublicKey, 475 | tokenMint: string, 476 | quoteOffset: number, 477 | baseOffset: number, 478 | dataLength: number 479 | ): Promise { 480 | // filter definitions 481 | const dataSizeFilter: DataSizeFilter = { dataSize: dataLength }; 482 | const memcmpFilterBase: MemcmpFilter = { offset: quoteOffset, bytes: tokenMint }; 483 | const memcmpFilterQuote: MemcmpFilter = { offset: baseOffset, bytes: DEFAULT_QUOTE_MINT }; 484 | async function fetchPair(baseMint: string, quoteMint: string): Promise { 485 | try { 486 | console.log(`Fetching pair addresses for base_mint: ${baseMint}, quote_mint: ${quoteMint}`); 487 | const accounts = await connection.getProgramAccounts(programId, { 488 | filters: [dataSizeFilter, { offset: quoteOffset, bytes: baseMint }, { offset: baseOffset, bytes: quoteMint }], 489 | }); 490 | if (accounts.length > 0) { 491 | console.log(`Found ${accounts.length} matching AMM account(s).`); 492 | return accounts.map(account => account.pubkey.toBase58()); 493 | } else { 494 | console.log("No matching AMM accounts found."); 495 | return []; 496 | } 497 | } catch (e) { 498 | console.error("Error fetching AMM pair addresses:", e); 499 | return []; 500 | } 501 | } 502 | let pairAddresses = await fetchPair(tokenMint, DEFAULT_QUOTE_MINT); 503 | if (pairAddresses.length === 0) { 504 | console.log("Retrying with reversed base and quote mints..."); 505 | pairAddresses = await fetchPair(DEFAULT_QUOTE_MINT, tokenMint); 506 | } 507 | return pairAddresses; 508 | } 509 | 510 | // Helper functions to get pair addresses for AMMv4, CPMM, and CLMM. 511 | export async function getAmmV4PairFromRpc(connection: Connection, tokenMint: string): Promise { 512 | return fetchPairAddressFromRpc(connection, RAYDIUM_AMM_V4, tokenMint, 400, 432, 752); 513 | } 514 | 515 | export async function getCpmmPairAddressFromRpc(connection: Connection, tokenMint: string): Promise { 516 | return fetchPairAddressFromRpc(connection, RAYDIUM_CPMM, tokenMint, 168, 200, 637); 517 | } 518 | 519 | export async function getClmmPairAddressFromRpc(connection: Connection, tokenMint: string): Promise { 520 | return fetchPairAddressFromRpc(connection, RAYDIUM_CLMM, tokenMint, 73, 105, 1544); 521 | } 522 | 523 | // Placeholder implementations for tick array helpers used in fetchClmmPoolKeys 524 | async function getPdaTickArrayBitmapExtension(poolState: PublicKey): Promise { 525 | // Compute PDA from poolState using proper seed(s) 526 | // Implementation depends on your program's PDA derivation 527 | return poolState; // (dummy placeholder) 528 | } 529 | 530 | async function loadCurrentAndNextTickArrays( 531 | poolState: PublicKey, 532 | tickCurrent: number, 533 | tickSpacing: number, 534 | tickArrayBitmap: Uint8Array, 535 | extensionData: any, 536 | zeroForOne: boolean 537 | ): Promise { 538 | // Here you would implement logic to return an array of three PublicKey objects. 539 | // This is a placeholder. 540 | return [poolState, poolState, poolState]; 541 | } 542 | --------------------------------------------------------------------------------