├── 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 |
--------------------------------------------------------------------------------