sol-trade-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +390 -0
  2. package/dist/chunk-MMQAMIKR.mjs +3735 -0
  3. package/dist/chunk-NEZDFAYA.mjs +7744 -0
  4. package/dist/clients-VITWK7B6.mjs +1370 -0
  5. package/dist/index-1BK_FXsW.d.mts +2327 -0
  6. package/dist/index-1BK_FXsW.d.ts +2327 -0
  7. package/dist/index.d.mts +2659 -0
  8. package/dist/index.d.ts +2659 -0
  9. package/dist/index.js +13265 -0
  10. package/dist/index.mjs +562 -0
  11. package/dist/perf/index.d.mts +2 -0
  12. package/dist/perf/index.d.ts +2 -0
  13. package/dist/perf/index.js +3742 -0
  14. package/dist/perf/index.mjs +214 -0
  15. package/package.json +101 -0
  16. package/src/__tests__/complete_sdk.test.ts +354 -0
  17. package/src/__tests__/hotpath.test.ts +486 -0
  18. package/src/__tests__/nonce.test.ts +45 -0
  19. package/src/__tests__/sdk.test.ts +425 -0
  20. package/src/address-lookup/index.ts +197 -0
  21. package/src/cache/cache.ts +308 -0
  22. package/src/calc/index.ts +1058 -0
  23. package/src/calc/pumpfun.ts +124 -0
  24. package/src/common/bonding_curve.ts +272 -0
  25. package/src/common/compute-budget.ts +148 -0
  26. package/src/common/confirm-any-signature.ts +184 -0
  27. package/src/common/fast-timing.ts +481 -0
  28. package/src/common/fast_fn.ts +150 -0
  29. package/src/common/gas-fee-strategy.ts +253 -0
  30. package/src/common/map-pool.ts +23 -0
  31. package/src/common/nonce.ts +40 -0
  32. package/src/common/sdk-log.ts +460 -0
  33. package/src/common/seed.ts +381 -0
  34. package/src/common/spl-token.ts +578 -0
  35. package/src/common/subscription-handle.ts +644 -0
  36. package/src/common/trading-utils.ts +239 -0
  37. package/src/common/wsol-manager.ts +325 -0
  38. package/src/compute/compute_budget_manager.ts +187 -0
  39. package/src/compute/index.ts +21 -0
  40. package/src/constants/index.ts +96 -0
  41. package/src/execution/execution.ts +532 -0
  42. package/src/execution/index.ts +42 -0
  43. package/src/hotpath/executor.ts +464 -0
  44. package/src/hotpath/index.ts +64 -0
  45. package/src/hotpath/state.ts +435 -0
  46. package/src/index.ts +2117 -0
  47. package/src/instruction/bonk_builder.ts +730 -0
  48. package/src/instruction/index.ts +24 -0
  49. package/src/instruction/meteora_damm_v2_builder.ts +509 -0
  50. package/src/instruction/pumpfun_builder.ts +1183 -0
  51. package/src/instruction/pumpswap.ts +1123 -0
  52. package/src/instruction/raydium_amm_v4_builder.ts +692 -0
  53. package/src/instruction/raydium_cpmm_builder.ts +795 -0
  54. package/src/middleware/traits.ts +407 -0
  55. package/src/params/index.ts +483 -0
  56. package/src/perf/compiler-optimization.ts +529 -0
  57. package/src/perf/hardware.ts +631 -0
  58. package/src/perf/index.ts +9 -0
  59. package/src/perf/kernel-bypass.ts +656 -0
  60. package/src/perf/protocol.ts +682 -0
  61. package/src/perf/realtime.ts +592 -0
  62. package/src/perf/simd.ts +668 -0
  63. package/src/perf/syscall-bypass.ts +331 -0
  64. package/src/perf/ultra-low-latency.ts +505 -0
  65. package/src/perf/zero-copy.ts +589 -0
  66. package/src/pool/pool.ts +294 -0
  67. package/src/rpc/client.ts +345 -0
  68. package/src/sdk-errors.ts +13 -0
  69. package/src/security/index.ts +26 -0
  70. package/src/security/secure-key.ts +303 -0
  71. package/src/security/validators.ts +281 -0
  72. package/src/seed/pda.ts +262 -0
  73. package/src/serialization/index.ts +28 -0
  74. package/src/serialization/serialization.ts +288 -0
  75. package/src/swqos/clients.ts +1754 -0
  76. package/src/swqos/index.ts +50 -0
  77. package/src/swqos/providers.ts +1707 -0
  78. package/src/trading/core/async-executor.ts +702 -0
  79. package/src/trading/core/confirmation-monitor.ts +711 -0
  80. package/src/trading/core/index.ts +82 -0
  81. package/src/trading/core/retry-handler.ts +683 -0
  82. package/src/trading/core/transaction-pool.ts +780 -0
  83. package/src/trading/executor.ts +385 -0
  84. package/src/trading/factory.ts +282 -0
  85. package/src/trading/index.ts +30 -0
  86. package/src/types.ts +8 -0
  87. package/src/utils/index.ts +155 -0
@@ -0,0 +1,795 @@
1
+ /**
2
+ * Raydium CPMM (Concentrated Pool Market Maker) Protocol Instruction Builder
3
+ *
4
+ * Production-grade instruction builder for Raydium CPMM protocol.
5
+ * 100% port of Rust implementation.
6
+ */
7
+
8
+ import {
9
+ PublicKey,
10
+ Keypair,
11
+ AccountMeta,
12
+ TransactionInstruction,
13
+ SystemProgram,
14
+ } from "@solana/web3.js";
15
+ import {
16
+ getAssociatedTokenAddressSync,
17
+ createAssociatedTokenAccountInstruction,
18
+ TOKEN_PROGRAM_ID,
19
+ createCloseAccountInstruction,
20
+ NATIVE_MINT,
21
+ createSyncNativeInstruction,
22
+ } from "@solana/spl-token";
23
+
24
+ // ============================================
25
+ // Program IDs and Constants
26
+ // ============================================
27
+
28
+ /** Raydium CPMM program ID */
29
+ export const RAYDIUM_CPMM_PROGRAM_ID = new PublicKey(
30
+ "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"
31
+ );
32
+
33
+ /** Authority */
34
+ export const RAYDIUM_CPMM_AUTHORITY = new PublicKey(
35
+ "GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL"
36
+ );
37
+
38
+ /** Fee rates */
39
+ export const RAYDIUM_CPMM_FEE_RATE_DENOMINATOR_VALUE = BigInt(1_000_000);
40
+ export const RAYDIUM_CPMM_TRADE_FEE_RATE = BigInt(2500);
41
+ export const RAYDIUM_CPMM_CREATOR_FEE_RATE = BigInt(0);
42
+ export const RAYDIUM_CPMM_PROTOCOL_FEE_RATE = BigInt(120000);
43
+ export const RAYDIUM_CPMM_FUND_FEE_RATE = BigInt(40000);
44
+
45
+ // ============================================
46
+ // Discriminators
47
+ // ============================================
48
+
49
+ /** Swap base in instruction discriminator */
50
+ export const RAYDIUM_CPMM_SWAP_BASE_IN_DISCRIMINATOR: Buffer = Buffer.from([
51
+ 143, 190, 90, 218, 196, 30, 51, 222,
52
+ ]);
53
+
54
+ /** Swap base out instruction discriminator */
55
+ export const RAYDIUM_CPMM_SWAP_BASE_OUT_DISCRIMINATOR: Buffer = Buffer.from([
56
+ 55, 217, 98, 86, 163, 74, 180, 173,
57
+ ]);
58
+
59
+ // ============================================
60
+ // Seeds
61
+ // ============================================
62
+
63
+ export const RAYDIUM_CPMM_POOL_SEED = Buffer.from("pool");
64
+ export const RAYDIUM_CPMM_POOL_VAULT_SEED = Buffer.from("pool_vault");
65
+ export const RAYDIUM_CPMM_OBSERVATION_STATE_SEED = Buffer.from("observation");
66
+
67
+ // ============================================
68
+ // PDA Derivation Functions
69
+ // ============================================
70
+
71
+ /**
72
+ * Derive the pool PDA for given config and mints
73
+ */
74
+ export function getRaydiumCpmmPoolPda(
75
+ ammConfig: PublicKey,
76
+ mint1: PublicKey,
77
+ mint2: PublicKey
78
+ ): PublicKey {
79
+ const [pda] = PublicKey.findProgramAddressSync(
80
+ [RAYDIUM_CPMM_POOL_SEED, ammConfig.toBuffer(), mint1.toBuffer(), mint2.toBuffer()],
81
+ RAYDIUM_CPMM_PROGRAM_ID
82
+ );
83
+ return pda;
84
+ }
85
+
86
+ /**
87
+ * Derive the vault PDA for a pool and mint
88
+ */
89
+ export function getRaydiumCpmmVaultPda(poolState: PublicKey, mint: PublicKey): PublicKey {
90
+ const [pda] = PublicKey.findProgramAddressSync(
91
+ [RAYDIUM_CPMM_POOL_VAULT_SEED, poolState.toBuffer(), mint.toBuffer()],
92
+ RAYDIUM_CPMM_PROGRAM_ID
93
+ );
94
+ return pda;
95
+ }
96
+
97
+ /**
98
+ * Derive the observation state PDA for a pool
99
+ */
100
+ export function getRaydiumCpmmObservationStatePda(poolState: PublicKey): PublicKey {
101
+ const [pda] = PublicKey.findProgramAddressSync(
102
+ [RAYDIUM_CPMM_OBSERVATION_STATE_SEED, poolState.toBuffer()],
103
+ RAYDIUM_CPMM_PROGRAM_ID
104
+ );
105
+ return pda;
106
+ }
107
+
108
+ // ============================================
109
+ // Helper Functions
110
+ // ============================================
111
+
112
+ /**
113
+ * Compute swap amount for CPMM
114
+ */
115
+ export function computeRaydiumCpmmSwapAmount(
116
+ baseReserve: bigint,
117
+ quoteReserve: bigint,
118
+ isBaseIn: boolean,
119
+ amountIn: bigint,
120
+ slippageBasisPoints: bigint
121
+ ): { amountOut: bigint; minAmountOut: bigint } {
122
+ // Apply trade fee (0.25%)
123
+ const feeRate = RAYDIUM_CPMM_TRADE_FEE_RATE;
124
+ const feeDenominator = RAYDIUM_CPMM_FEE_RATE_DENOMINATOR_VALUE;
125
+ const amountInAfterFee = amountIn - (amountIn * feeRate) / feeDenominator;
126
+
127
+ // Calculate output using constant product formula
128
+ let amountOut: bigint;
129
+ if (isBaseIn) {
130
+ // Selling base for quote: output = (quoteReserve * amountIn) / (baseReserve + amountIn)
131
+ const denominator = baseReserve + amountInAfterFee;
132
+ amountOut = (quoteReserve * amountInAfterFee) / denominator;
133
+ } else {
134
+ // Selling quote for base: output = (baseReserve * amountIn) / (quoteReserve + amountIn)
135
+ const denominator = quoteReserve + amountInAfterFee;
136
+ amountOut = (baseReserve * amountInAfterFee) / denominator;
137
+ }
138
+
139
+ // Apply slippage
140
+ const minAmountOut = amountOut - (amountOut * slippageBasisPoints) / BigInt(10000);
141
+
142
+ return { amountOut, minAmountOut };
143
+ }
144
+
145
+ // ============================================
146
+ // Types
147
+ // ============================================
148
+
149
+ export interface RaydiumCpmmParams {
150
+ poolState?: PublicKey;
151
+ ammConfig: PublicKey;
152
+ baseMint: PublicKey;
153
+ quoteMint: PublicKey;
154
+ baseTokenProgram: PublicKey;
155
+ quoteTokenProgram: PublicKey;
156
+ baseVault?: PublicKey;
157
+ quoteVault?: PublicKey;
158
+ baseReserve: bigint;
159
+ quoteReserve: bigint;
160
+ observationState?: PublicKey;
161
+ }
162
+
163
+ export interface BuildRaydiumCpmmBuyInstructionsParams {
164
+ payer: Keypair | PublicKey;
165
+ outputMint: PublicKey;
166
+ inputAmount: bigint;
167
+ slippageBasisPoints?: bigint;
168
+ fixedOutputAmount?: bigint;
169
+ createInputMintAta?: boolean;
170
+ createOutputMintAta?: boolean;
171
+ closeInputMintAta?: boolean;
172
+ protocolParams: RaydiumCpmmParams;
173
+ }
174
+
175
+ export interface BuildRaydiumCpmmSellInstructionsParams {
176
+ payer: Keypair | PublicKey;
177
+ inputMint: PublicKey;
178
+ inputAmount: bigint;
179
+ slippageBasisPoints?: bigint;
180
+ fixedOutputAmount?: bigint;
181
+ createOutputMintAta?: boolean;
182
+ closeOutputMintAta?: boolean;
183
+ closeInputMintAta?: boolean;
184
+ protocolParams: RaydiumCpmmParams;
185
+ }
186
+
187
+ // ============================================
188
+ // Instruction Builders
189
+ // ============================================
190
+
191
+ /**
192
+ * Build buy instructions for Raydium CPMM protocol
193
+ */
194
+ export function buildRaydiumCpmmBuyInstructions(
195
+ params: BuildRaydiumCpmmBuyInstructionsParams
196
+ ): TransactionInstruction[] {
197
+ const {
198
+ payer,
199
+ outputMint,
200
+ inputAmount,
201
+ slippageBasisPoints = BigInt(1000),
202
+ fixedOutputAmount,
203
+ createInputMintAta = true,
204
+ createOutputMintAta = true,
205
+ closeInputMintAta = false,
206
+ protocolParams,
207
+ } = params;
208
+
209
+ if (inputAmount === BigInt(0)) {
210
+ throw new Error("Amount cannot be zero");
211
+ }
212
+
213
+ const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
214
+ const instructions: TransactionInstruction[] = [];
215
+
216
+ const WSOL_TOKEN_ACCOUNT = new PublicKey("So11111111111111111111111111111111111111112");
217
+ const USDC_TOKEN_ACCOUNT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
218
+
219
+ const {
220
+ ammConfig,
221
+ baseMint,
222
+ quoteMint,
223
+ baseTokenProgram,
224
+ quoteTokenProgram,
225
+ baseVault,
226
+ quoteVault,
227
+ baseReserve,
228
+ quoteReserve,
229
+ observationState,
230
+ } = protocolParams;
231
+
232
+ // Check pool type
233
+ const isWsol = baseMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(WSOL_TOKEN_ACCOUNT);
234
+ const isUsdc = baseMint.equals(USDC_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
235
+
236
+ if (!isWsol && !isUsdc) {
237
+ throw new Error("Pool must contain WSOL or USDC");
238
+ }
239
+
240
+ // Determine swap direction
241
+ const isBaseIn = baseMint.equals(WSOL_TOKEN_ACCOUNT) || baseMint.equals(USDC_TOKEN_ACCOUNT);
242
+ const mintTokenProgram = isBaseIn ? quoteTokenProgram : baseTokenProgram;
243
+
244
+ // Derive pool state
245
+ const poolState = protocolParams.poolState && !protocolParams.poolState.equals(PublicKey.default)
246
+ ? protocolParams.poolState
247
+ : getRaydiumCpmmPoolPda(ammConfig, baseMint, quoteMint);
248
+
249
+ // Calculate output
250
+ const swapResult = computeRaydiumCpmmSwapAmount(
251
+ baseReserve,
252
+ quoteReserve,
253
+ isBaseIn,
254
+ inputAmount,
255
+ slippageBasisPoints
256
+ );
257
+ const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
258
+
259
+ // Determine input/output mints
260
+ const inputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
261
+
262
+ // Derive user token accounts
263
+ const inputTokenAccount = getAssociatedTokenAddressSync(
264
+ inputMint,
265
+ payerPubkey,
266
+ true,
267
+ TOKEN_PROGRAM_ID
268
+ );
269
+ const outputTokenAccount = getAssociatedTokenAddressSync(
270
+ outputMint,
271
+ payerPubkey,
272
+ true,
273
+ mintTokenProgram
274
+ );
275
+
276
+ // Derive vault accounts
277
+ const inputVaultAccount = ((): PublicKey => {
278
+ if (isWsol && baseMint.equals(inputMint) && baseVault && !baseVault.equals(PublicKey.default)) {
279
+ return baseVault;
280
+ }
281
+ if (!isWsol && quoteMint.equals(inputMint) && quoteVault && !quoteVault.equals(PublicKey.default)) {
282
+ return quoteVault;
283
+ }
284
+ return getRaydiumCpmmVaultPda(poolState, inputMint);
285
+ })();
286
+
287
+ const outputVaultAccount = ((): PublicKey => {
288
+ if (baseMint.equals(outputMint) && baseVault && !baseVault.equals(PublicKey.default)) {
289
+ return baseVault;
290
+ }
291
+ if (quoteMint.equals(outputMint) && quoteVault && !quoteVault.equals(PublicKey.default)) {
292
+ return quoteVault;
293
+ }
294
+ return getRaydiumCpmmVaultPda(poolState, outputMint);
295
+ })();
296
+
297
+ // Derive observation state
298
+ const observationStateAccount = observationState && !observationState.equals(PublicKey.default)
299
+ ? observationState
300
+ : getRaydiumCpmmObservationStatePda(poolState);
301
+
302
+ // Handle WSOL wrapping
303
+ if (createInputMintAta && isWsol) {
304
+ const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
305
+ instructions.push(
306
+ createAssociatedTokenAccountInstruction(
307
+ payerPubkey,
308
+ wsolAta,
309
+ payerPubkey,
310
+ NATIVE_MINT,
311
+ TOKEN_PROGRAM_ID
312
+ )
313
+ );
314
+ instructions.push(createSyncNativeInstruction(wsolAta));
315
+ }
316
+
317
+ // Create output mint ATA if needed
318
+ if (createOutputMintAta) {
319
+ instructions.push(
320
+ createAssociatedTokenAccountInstruction(
321
+ payerPubkey,
322
+ outputTokenAccount,
323
+ payerPubkey,
324
+ outputMint,
325
+ mintTokenProgram
326
+ )
327
+ );
328
+ }
329
+
330
+ // Build instruction data
331
+ const data = Buffer.alloc(24);
332
+ RAYDIUM_CPMM_SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
333
+ data.writeBigUInt64LE(inputAmount, 8);
334
+ data.writeBigUInt64LE(minimumAmountOut, 16);
335
+
336
+ // Build accounts
337
+ const accounts: AccountMeta[] = [
338
+ { pubkey: payerPubkey, isSigner: true, isWritable: true },
339
+ { pubkey: RAYDIUM_CPMM_AUTHORITY, isSigner: false, isWritable: false },
340
+ { pubkey: ammConfig, isSigner: false, isWritable: false },
341
+ { pubkey: poolState, isSigner: false, isWritable: true },
342
+ { pubkey: inputTokenAccount, isSigner: false, isWritable: true },
343
+ { pubkey: outputTokenAccount, isSigner: false, isWritable: true },
344
+ { pubkey: inputVaultAccount, isSigner: false, isWritable: true },
345
+ { pubkey: outputVaultAccount, isSigner: false, isWritable: true },
346
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
347
+ { pubkey: mintTokenProgram, isSigner: false, isWritable: false },
348
+ { pubkey: inputMint, isSigner: false, isWritable: false },
349
+ { pubkey: outputMint, isSigner: false, isWritable: false },
350
+ { pubkey: observationStateAccount, isSigner: false, isWritable: true },
351
+ ];
352
+
353
+ instructions.push(
354
+ new TransactionInstruction({
355
+ keys: accounts,
356
+ programId: RAYDIUM_CPMM_PROGRAM_ID,
357
+ data,
358
+ })
359
+ );
360
+
361
+ // Close WSOL ATA if requested
362
+ if (closeInputMintAta && isWsol) {
363
+ const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
364
+ instructions.push(
365
+ createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
366
+ );
367
+ }
368
+
369
+ return instructions;
370
+ }
371
+
372
+ /**
373
+ * Build sell instructions for Raydium CPMM protocol
374
+ */
375
+ export function buildRaydiumCpmmSellInstructions(
376
+ params: BuildRaydiumCpmmSellInstructionsParams
377
+ ): TransactionInstruction[] {
378
+ const {
379
+ payer,
380
+ inputMint,
381
+ inputAmount,
382
+ slippageBasisPoints = BigInt(1000),
383
+ fixedOutputAmount,
384
+ createOutputMintAta = true,
385
+ closeOutputMintAta = false,
386
+ closeInputMintAta = false,
387
+ protocolParams,
388
+ } = params;
389
+
390
+ if (inputAmount === BigInt(0)) {
391
+ throw new Error("Amount cannot be zero");
392
+ }
393
+
394
+ const payerPubkey = payer instanceof Keypair ? payer.publicKey : payer;
395
+ const instructions: TransactionInstruction[] = [];
396
+
397
+ const WSOL_TOKEN_ACCOUNT = new PublicKey("So11111111111111111111111111111111111111112");
398
+ const USDC_TOKEN_ACCOUNT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
399
+
400
+ const {
401
+ ammConfig,
402
+ baseMint,
403
+ quoteMint,
404
+ baseTokenProgram,
405
+ quoteTokenProgram,
406
+ baseVault,
407
+ quoteVault,
408
+ baseReserve,
409
+ quoteReserve,
410
+ observationState,
411
+ } = protocolParams;
412
+
413
+ // Check pool type
414
+ const isWsol = baseMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(WSOL_TOKEN_ACCOUNT);
415
+ const isUsdc = baseMint.equals(USDC_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
416
+
417
+ if (!isWsol && !isUsdc) {
418
+ throw new Error("Pool must contain WSOL or USDC");
419
+ }
420
+
421
+ // Determine swap direction
422
+ const isQuoteOut = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
423
+ const mintTokenProgram = isQuoteOut ? baseTokenProgram : quoteTokenProgram;
424
+
425
+ // Derive pool state
426
+ const poolState = protocolParams.poolState && !protocolParams.poolState.equals(PublicKey.default)
427
+ ? protocolParams.poolState
428
+ : getRaydiumCpmmPoolPda(ammConfig, baseMint, quoteMint);
429
+
430
+ // Calculate output
431
+ const swapResult = computeRaydiumCpmmSwapAmount(
432
+ baseReserve,
433
+ quoteReserve,
434
+ isQuoteOut,
435
+ inputAmount,
436
+ slippageBasisPoints
437
+ );
438
+ const minimumAmountOut = fixedOutputAmount || swapResult.minAmountOut;
439
+
440
+ // Determine output mint
441
+ const outputMint = isWsol ? WSOL_TOKEN_ACCOUNT : USDC_TOKEN_ACCOUNT;
442
+
443
+ // Derive user token accounts
444
+ const inputTokenAccount = getAssociatedTokenAddressSync(
445
+ inputMint,
446
+ payerPubkey,
447
+ true,
448
+ mintTokenProgram
449
+ );
450
+ const outputTokenAccount = getAssociatedTokenAddressSync(
451
+ outputMint,
452
+ payerPubkey,
453
+ true,
454
+ TOKEN_PROGRAM_ID
455
+ );
456
+
457
+ // Derive vault accounts
458
+ const inputVaultAccount = ((): PublicKey => {
459
+ if (baseMint.equals(inputMint) && baseVault && !baseVault.equals(PublicKey.default)) {
460
+ return baseVault;
461
+ }
462
+ if (quoteMint.equals(inputMint) && quoteVault && !quoteVault.equals(PublicKey.default)) {
463
+ return quoteVault;
464
+ }
465
+ return getRaydiumCpmmVaultPda(poolState, inputMint);
466
+ })();
467
+
468
+ const outputVaultAccount = ((): PublicKey => {
469
+ if (isWsol && baseMint.equals(outputMint) && baseVault && !baseVault.equals(PublicKey.default)) {
470
+ return baseVault;
471
+ }
472
+ if (!isWsol && quoteMint.equals(outputMint) && quoteVault && !quoteVault.equals(PublicKey.default)) {
473
+ return quoteVault;
474
+ }
475
+ return getRaydiumCpmmVaultPda(poolState, outputMint);
476
+ })();
477
+
478
+ // Derive observation state
479
+ const observationStateAccount = observationState && !observationState.equals(PublicKey.default)
480
+ ? observationState
481
+ : getRaydiumCpmmObservationStatePda(poolState);
482
+
483
+ // Create WSOL ATA for receiving if needed
484
+ if (createOutputMintAta && isWsol) {
485
+ const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
486
+ instructions.push(
487
+ createAssociatedTokenAccountInstruction(
488
+ payerPubkey,
489
+ wsolAta,
490
+ payerPubkey,
491
+ NATIVE_MINT,
492
+ TOKEN_PROGRAM_ID
493
+ )
494
+ );
495
+ }
496
+
497
+ // Build instruction data
498
+ const data = Buffer.alloc(24);
499
+ RAYDIUM_CPMM_SWAP_BASE_IN_DISCRIMINATOR.copy(data, 0);
500
+ data.writeBigUInt64LE(inputAmount, 8);
501
+ data.writeBigUInt64LE(minimumAmountOut, 16);
502
+
503
+ // Build accounts
504
+ const accounts: AccountMeta[] = [
505
+ { pubkey: payerPubkey, isSigner: true, isWritable: true },
506
+ { pubkey: RAYDIUM_CPMM_AUTHORITY, isSigner: false, isWritable: false },
507
+ { pubkey: ammConfig, isSigner: false, isWritable: false },
508
+ { pubkey: poolState, isSigner: false, isWritable: true },
509
+ { pubkey: inputTokenAccount, isSigner: false, isWritable: true },
510
+ { pubkey: outputTokenAccount, isSigner: false, isWritable: true },
511
+ { pubkey: inputVaultAccount, isSigner: false, isWritable: true },
512
+ { pubkey: outputVaultAccount, isSigner: false, isWritable: true },
513
+ { pubkey: mintTokenProgram, isSigner: false, isWritable: false },
514
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
515
+ { pubkey: inputMint, isSigner: false, isWritable: false },
516
+ { pubkey: outputMint, isSigner: false, isWritable: false },
517
+ { pubkey: observationStateAccount, isSigner: false, isWritable: true },
518
+ ];
519
+
520
+ instructions.push(
521
+ new TransactionInstruction({
522
+ keys: accounts,
523
+ programId: RAYDIUM_CPMM_PROGRAM_ID,
524
+ data,
525
+ })
526
+ );
527
+
528
+ // Close WSOL ATA if requested
529
+ if (closeOutputMintAta && isWsol) {
530
+ const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
531
+ instructions.push(
532
+ createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
533
+ );
534
+ }
535
+
536
+ // Close input token ATA if requested
537
+ if (closeInputMintAta) {
538
+ instructions.push(
539
+ createCloseAccountInstruction(
540
+ inputTokenAccount,
541
+ payerPubkey,
542
+ payerPubkey,
543
+ [],
544
+ mintTokenProgram
545
+ )
546
+ );
547
+ }
548
+
549
+ return instructions;
550
+ }
551
+
552
+ // ===== Pool State Decoder - from Rust: src/instruction/utils/raydium_cpmm_types.rs =====
553
+
554
+ export const RAYDIUM_CPMM_POOL_STATE_SIZE = 629;
555
+
556
+ export interface RaydiumCPMMpoolState {
557
+ ammConfig: PublicKey;
558
+ poolCreator: PublicKey;
559
+ token0Vault: PublicKey;
560
+ token1Vault: PublicKey;
561
+ lpMint: PublicKey;
562
+ token0Mint: PublicKey;
563
+ token1Mint: PublicKey;
564
+ token0Program: PublicKey;
565
+ token1Program: PublicKey;
566
+ observationKey: PublicKey;
567
+ authBump: number;
568
+ status: number;
569
+ lpMintDecimals: number;
570
+ mint0Decimals: number;
571
+ mint1Decimals: number;
572
+ lpSupply: bigint;
573
+ protocolFeesToken0: bigint;
574
+ protocolFeesToken1: bigint;
575
+ fundFeesToken0: bigint;
576
+ fundFeesToken1: bigint;
577
+ openTime: bigint;
578
+ recentEpoch: bigint;
579
+ }
580
+
581
+ /**
582
+ * Decode a Raydium CPMM pool state from account data
583
+ * 100% from Rust: src/instruction/utils/raydium_cpmm_types.rs pool_state_decode
584
+ */
585
+ export function decodeRaydiumCPMMpoolState(data: Buffer): RaydiumCPMMpoolState | null {
586
+ if (data.length < RAYDIUM_CPMM_POOL_STATE_SIZE) {
587
+ return null;
588
+ }
589
+
590
+ try {
591
+ let offset = 0;
592
+
593
+ // amm_config: Pubkey
594
+ const ammConfig = new PublicKey(data.subarray(offset, offset + 32));
595
+ offset += 32;
596
+
597
+ // pool_creator: Pubkey
598
+ const poolCreator = new PublicKey(data.subarray(offset, offset + 32));
599
+ offset += 32;
600
+
601
+ // token0_vault: Pubkey
602
+ const token0Vault = new PublicKey(data.subarray(offset, offset + 32));
603
+ offset += 32;
604
+
605
+ // token1_vault: Pubkey
606
+ const token1Vault = new PublicKey(data.subarray(offset, offset + 32));
607
+ offset += 32;
608
+
609
+ // lp_mint: Pubkey
610
+ const lpMint = new PublicKey(data.subarray(offset, offset + 32));
611
+ offset += 32;
612
+
613
+ // token0_mint: Pubkey
614
+ const token0Mint = new PublicKey(data.subarray(offset, offset + 32));
615
+ offset += 32;
616
+
617
+ // token1_mint: Pubkey
618
+ const token1Mint = new PublicKey(data.subarray(offset, offset + 32));
619
+ offset += 32;
620
+
621
+ // token0_program: Pubkey
622
+ const token0Program = new PublicKey(data.subarray(offset, offset + 32));
623
+ offset += 32;
624
+
625
+ // token1_program: Pubkey
626
+ const token1Program = new PublicKey(data.subarray(offset, offset + 32));
627
+ offset += 32;
628
+
629
+ // observation_key: Pubkey
630
+ const observationKey = new PublicKey(data.subarray(offset, offset + 32));
631
+ offset += 32;
632
+
633
+ // auth_bump: u8
634
+ const authBump = data.readUInt8(offset);
635
+ offset += 1;
636
+
637
+ // status: u8
638
+ const status = data.readUInt8(offset);
639
+ offset += 1;
640
+
641
+ // lp_mint_decimals: u8
642
+ const lpMintDecimals = data.readUInt8(offset);
643
+ offset += 1;
644
+
645
+ // mint0_decimals: u8
646
+ const mint0Decimals = data.readUInt8(offset);
647
+ offset += 1;
648
+
649
+ // mint1_decimals: u8
650
+ const mint1Decimals = data.readUInt8(offset);
651
+ offset += 1;
652
+
653
+ // lp_supply: u64
654
+ const lpSupply = data.readBigUInt64LE(offset);
655
+ offset += 8;
656
+
657
+ // protocol_fees_token0: u64
658
+ const protocolFeesToken0 = data.readBigUInt64LE(offset);
659
+ offset += 8;
660
+
661
+ // protocol_fees_token1: u64
662
+ const protocolFeesToken1 = data.readBigUInt64LE(offset);
663
+ offset += 8;
664
+
665
+ // fund_fees_token0: u64
666
+ const fundFeesToken0 = data.readBigUInt64LE(offset);
667
+ offset += 8;
668
+
669
+ // fund_fees_token1: u64
670
+ const fundFeesToken1 = data.readBigUInt64LE(offset);
671
+ offset += 8;
672
+
673
+ // open_time: u64
674
+ const openTime = data.readBigUInt64LE(offset);
675
+ offset += 8;
676
+
677
+ // recent_epoch: u64
678
+ const recentEpoch = data.readBigUInt64LE(offset);
679
+
680
+ return {
681
+ ammConfig,
682
+ poolCreator,
683
+ token0Vault,
684
+ token1Vault,
685
+ lpMint,
686
+ token0Mint,
687
+ token1Mint,
688
+ token0Program,
689
+ token1Program,
690
+ observationKey,
691
+ authBump,
692
+ status,
693
+ lpMintDecimals,
694
+ mint0Decimals,
695
+ mint1Decimals,
696
+ lpSupply,
697
+ protocolFeesToken0,
698
+ protocolFeesToken1,
699
+ fundFeesToken0,
700
+ fundFeesToken1,
701
+ openTime,
702
+ recentEpoch,
703
+ };
704
+ } catch {
705
+ return null;
706
+ }
707
+ }
708
+
709
+ // ===== Async Fetch Functions - from Rust: src/instruction/utils/raydium_cpmm.rs =====
710
+
711
+ /**
712
+ * Fetch a Raydium CPMM pool state from RPC.
713
+ * 100% from Rust: src/instruction/utils/raydium_cpmm.rs fetch_pool_state
714
+ */
715
+ export async function fetchRaydiumCPMMpoolState(
716
+ connection: { getAccountInfo: (pubkey: PublicKey) => Promise<{ value?: { data: Buffer } }> },
717
+ poolAddress: PublicKey
718
+ ): Promise<RaydiumCPMMpoolState | null> {
719
+ const account = await connection.getAccountInfo(poolAddress);
720
+ if (!account?.value?.data) {
721
+ return null;
722
+ }
723
+ return decodeRaydiumCPMMpoolState(account.value.data);
724
+ }
725
+
726
+ /**
727
+ * Get pool PDA for Raydium CPMM.
728
+ * Seeds: ["pool", amm_config, mint1, mint2]
729
+ */
730
+ export function getRaydiumCPMMpoolPDA(
731
+ ammConfig: PublicKey,
732
+ mint1: PublicKey,
733
+ mint2: PublicKey
734
+ ): PublicKey {
735
+ const POOL_SEED = Buffer.from('pool');
736
+ const [pda] = PublicKey.findProgramAddressSync(
737
+ [POOL_SEED, ammConfig.toBuffer(), mint1.toBuffer(), mint2.toBuffer()],
738
+ RAYDIUM_CPMM_PROGRAM_ID
739
+ );
740
+ return pda;
741
+ }
742
+
743
+ /**
744
+ * Get vault PDA for Raydium CPMM.
745
+ * Seeds: ["pool_vault", pool_state, mint]
746
+ */
747
+ export function getRaydiumCPMMvaultPDA(poolState: PublicKey, mint: PublicKey): PublicKey {
748
+ const POOL_VAULT_SEED = Buffer.from('pool_vault');
749
+ const [pda] = PublicKey.findProgramAddressSync(
750
+ [POOL_VAULT_SEED, poolState.toBuffer(), mint.toBuffer()],
751
+ RAYDIUM_CPMM_PROGRAM_ID
752
+ );
753
+ return pda;
754
+ }
755
+
756
+ /**
757
+ * Get observation state PDA for Raydium CPMM.
758
+ * Seeds: ["observation", pool_state]
759
+ */
760
+ export function getRaydiumCPMMobservationStatePDA(poolState: PublicKey): PublicKey {
761
+ const OBSERVATION_STATE_SEED = Buffer.from('observation');
762
+ const [pda] = PublicKey.findProgramAddressSync(
763
+ [OBSERVATION_STATE_SEED, poolState.toBuffer()],
764
+ RAYDIUM_CPMM_PROGRAM_ID
765
+ );
766
+ return pda;
767
+ }
768
+
769
+ /**
770
+ * Get token balances for a Raydium CPMM pool.
771
+ * 100% from Rust: src/instruction/utils/raydium_cpmm.rs get_pool_token_balances
772
+ */
773
+ export async function getRaydiumCPMMpoolTokenBalances(
774
+ connection: {
775
+ getTokenAccountBalance: (pubkey: PublicKey) => Promise<{ value?: { amount: string } }>
776
+ },
777
+ poolState: PublicKey,
778
+ token0Mint: PublicKey,
779
+ token1Mint: PublicKey
780
+ ): Promise<{ token0Balance: bigint; token1Balance: bigint } | null> {
781
+ try {
782
+ const token0Vault = getRaydiumCPMMvaultPDA(poolState, token0Mint);
783
+ const token1Vault = getRaydiumCPMMvaultPDA(poolState, token1Mint);
784
+
785
+ const token0Result = await connection.getTokenAccountBalance(token0Vault);
786
+ const token1Result = await connection.getTokenAccountBalance(token1Vault);
787
+
788
+ const token0Balance = BigInt(token0Result?.value?.amount ?? '0');
789
+ const token1Balance = BigInt(token1Result?.value?.amount ?? '0');
790
+
791
+ return { token0Balance, token1Balance };
792
+ } catch {
793
+ return null;
794
+ }
795
+ }