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,1123 @@
1
+ /**
2
+ * PumpSwap instruction builder - Production-grade implementation
3
+ * 100% port from Rust sol-trade-sdk
4
+ */
5
+
6
+ import {
7
+ PublicKey,
8
+ TransactionInstruction,
9
+ SystemProgram,
10
+ SYSVAR_RENT_PUBKEY,
11
+ } from '@solana/web3.js';
12
+ import {
13
+ TOKEN_PROGRAM,
14
+ TOKEN_PROGRAM_2022,
15
+ ASSOCIATED_TOKEN_PROGRAM,
16
+ WSOL_TOKEN_ACCOUNT,
17
+ USDC_TOKEN_ACCOUNT,
18
+ } from '../constants';
19
+ import {
20
+ calculateWithSlippageBuy,
21
+ calculateWithSlippageSell,
22
+ ceilDiv,
23
+ computeFee,
24
+ buyQuoteInputInternal,
25
+ sellBaseInputInternal,
26
+ PUMPSWAP_CONSTANTS,
27
+ } from '../calc';
28
+
29
+ // ===== Constants from Rust: src/instruction/utils/pumpswap.rs =====
30
+
31
+ export const PUMPSWAP_PROGRAM = new PublicKey('pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA');
32
+ export const PUMPSWAP_PUMP_PROGRAM_ID = new PublicKey('6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P');
33
+ export const PUMPSWAP_FEE_PROGRAM = new PublicKey('pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ');
34
+
35
+ // Accounts
36
+ export const PUMPSWAP_FEE_RECIPIENT = new PublicKey('62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV');
37
+ export const PUMPSWAP_GLOBAL_ACCOUNT = new PublicKey('ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw');
38
+ export const PUMPSWAP_EVENT_AUTHORITY = new PublicKey('GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR');
39
+ export const PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR = new PublicKey('C2aFPdENg4A2HQsmrd5rTw5TaYBX5Ku887cWjbFKtZpw');
40
+ export const PUMPSWAP_FEE_CONFIG = new PublicKey('5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx');
41
+ export const PUMPSWAP_DEFAULT_COIN_CREATOR_VAULT_AUTHORITY = new PublicKey('8N3GDaZ2iwN65oxVatKTLPNooAVUJTbfiVJ1ahyqwjSk');
42
+
43
+ // Mayhem fee recipients (use any one randomly)
44
+ export const PUMPSWAP_MAYHEM_FEE_RECIPIENTS: PublicKey[] = [
45
+ new PublicKey('GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS'),
46
+ new PublicKey('4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6'),
47
+ new PublicKey('8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR'),
48
+ new PublicKey('4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH'),
49
+ new PublicKey('8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6'),
50
+ new PublicKey('Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk'),
51
+ new PublicKey('463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq'),
52
+ new PublicKey('6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA'),
53
+ ];
54
+
55
+ /** Protocol extra fee recipients (Apr 2026); after pool-v2: readonly, then quote ATA (mutable). */
56
+ export const PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS: PublicKey[] = [
57
+ new PublicKey('5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD'),
58
+ new PublicKey('9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7'),
59
+ new PublicKey('GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL'),
60
+ new PublicKey('3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR'),
61
+ new PublicKey('5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6'),
62
+ new PublicKey('EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL'),
63
+ new PublicKey('5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD'),
64
+ new PublicKey('A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW'),
65
+ ];
66
+
67
+ // Discriminators
68
+ export const PUMPSWAP_BUY_DISCRIMINATOR = Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]);
69
+ export const PUMPSWAP_BUY_EXACT_QUOTE_IN_DISCRIMINATOR = Buffer.from([198, 46, 21, 82, 180, 217, 232, 112]);
70
+ export const PUMPSWAP_SELL_DISCRIMINATOR = Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]);
71
+ export const PUMPSWAP_CLAIM_CASHBACK_DISCRIMINATOR = Buffer.from([37, 58, 35, 126, 190, 53, 228, 197]);
72
+
73
+ // Seeds
74
+ const POOL_V2_SEED = Buffer.from('pool-v2');
75
+ const POOL_SEED = Buffer.from('pool');
76
+ const POOL_AUTHORITY_SEED = Buffer.from('pool-authority');
77
+ const USER_VOLUME_ACCUMULATOR_SEED = Buffer.from('user_volume_accumulator');
78
+ const CREATOR_VAULT_SEED = Buffer.from('creator_vault');
79
+ const FEE_CONFIG_SEED = Buffer.from('fee_config');
80
+ const GLOBAL_VOLUME_ACCUMULATOR_SEED = Buffer.from('global_volume_accumulator');
81
+
82
+ // ===== PDA Derivation Functions =====
83
+
84
+ /**
85
+ * Get a random Mayhem fee recipient
86
+ */
87
+ export function getMayhemFeeRecipientRandom(): PublicKey {
88
+ const index = Math.floor(Math.random() * PUMPSWAP_MAYHEM_FEE_RECIPIENTS.length);
89
+ const recipient = PUMPSWAP_MAYHEM_FEE_RECIPIENTS[index];
90
+ if (!recipient) {
91
+ return PUMPSWAP_MAYHEM_FEE_RECIPIENTS[0]!;
92
+ }
93
+ return recipient;
94
+ }
95
+
96
+ export function getPumpSwapProtocolExtraFeeRecipientRandom(): PublicKey {
97
+ const index = Math.floor(Math.random() * PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS.length);
98
+ return PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS[index] ?? PUMPSWAP_PROTOCOL_EXTRA_FEE_RECIPIENTS[0]!;
99
+ }
100
+
101
+ /**
102
+ * Pool v2 PDA (seeds: ["pool-v2", base_mint])
103
+ */
104
+ export function getPoolV2PDA(baseMint: PublicKey): PublicKey {
105
+ const [pda] = PublicKey.findProgramAddressSync(
106
+ [POOL_V2_SEED, baseMint.toBuffer()],
107
+ PUMPSWAP_PROGRAM
108
+ );
109
+ return pda;
110
+ }
111
+
112
+ /**
113
+ * Pump program pool-authority PDA (for canonical pool)
114
+ */
115
+ export function getPumpPoolAuthorityPDA(mint: PublicKey): PublicKey {
116
+ const [pda] = PublicKey.findProgramAddressSync(
117
+ [POOL_AUTHORITY_SEED, mint.toBuffer()],
118
+ PUMPSWAP_PUMP_PROGRAM_ID
119
+ );
120
+ return pda;
121
+ }
122
+
123
+ /**
124
+ * Canonical Pump pool PDA
125
+ */
126
+ export function getCanonicalPoolPDA(mint: PublicKey): PublicKey {
127
+ const authority = getPumpPoolAuthorityPDA(mint);
128
+ const index = Buffer.alloc(2);
129
+ index.writeUInt16LE(0);
130
+ const [pda] = PublicKey.findProgramAddressSync(
131
+ [POOL_SEED, index, authority.toBuffer(), mint.toBuffer(), WSOL_TOKEN_ACCOUNT.toBuffer()],
132
+ PUMPSWAP_PROGRAM
133
+ );
134
+ return pda;
135
+ }
136
+
137
+ /**
138
+ * Coin creator vault authority PDA
139
+ */
140
+ export function getCoinCreatorVaultAuthority(coinCreator: PublicKey): PublicKey {
141
+ const [pda] = PublicKey.findProgramAddressSync(
142
+ [CREATOR_VAULT_SEED, coinCreator.toBuffer()],
143
+ PUMPSWAP_PROGRAM
144
+ );
145
+ return pda;
146
+ }
147
+
148
+ /**
149
+ * Coin creator vault ATA
150
+ */
151
+ export function getCoinCreatorVaultAta(coinCreator: PublicKey, quoteMint: PublicKey): PublicKey {
152
+ const authority = getCoinCreatorVaultAuthority(coinCreator);
153
+ return getAssociatedTokenAddress(authority, quoteMint, TOKEN_PROGRAM);
154
+ }
155
+
156
+ /**
157
+ * Fee recipient ATA
158
+ */
159
+ export function getFeeRecipientAta(feeRecipient: PublicKey, quoteMint: PublicKey): PublicKey {
160
+ return getAssociatedTokenAddress(feeRecipient, quoteMint, TOKEN_PROGRAM);
161
+ }
162
+
163
+ /**
164
+ * User volume accumulator PDA
165
+ */
166
+ export function getUserVolumeAccumulatorPDA(user: PublicKey): PublicKey {
167
+ const [pda] = PublicKey.findProgramAddressSync(
168
+ [USER_VOLUME_ACCUMULATOR_SEED, user.toBuffer()],
169
+ PUMPSWAP_PROGRAM
170
+ );
171
+ return pda;
172
+ }
173
+
174
+ /**
175
+ * WSOL ATA of UserVolumeAccumulator (for buy cashback)
176
+ */
177
+ export function getUserVolumeAccumulatorWsolAta(user: PublicKey): PublicKey {
178
+ const accumulator = getUserVolumeAccumulatorPDA(user);
179
+ return getAssociatedTokenAddress(accumulator, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM);
180
+ }
181
+
182
+ /**
183
+ * Quote-mint ATA of UserVolumeAccumulator (for sell cashback)
184
+ */
185
+ export function getUserVolumeAccumulatorQuoteAta(
186
+ user: PublicKey,
187
+ quoteMint: PublicKey,
188
+ quoteTokenProgram: PublicKey
189
+ ): PublicKey {
190
+ const accumulator = getUserVolumeAccumulatorPDA(user);
191
+ return getAssociatedTokenAddress(accumulator, quoteMint, quoteTokenProgram);
192
+ }
193
+
194
+ /**
195
+ * Global volume accumulator PDA
196
+ * Seeds: ["global_volume_accumulator"], owner: PUMPSWAP_PROGRAM
197
+ */
198
+ export function getGlobalVolumeAccumulatorPDA(): PublicKey {
199
+ const [pda] = PublicKey.findProgramAddressSync(
200
+ [GLOBAL_VOLUME_ACCUMULATOR_SEED],
201
+ PUMPSWAP_PROGRAM
202
+ );
203
+ return pda;
204
+ }
205
+
206
+ /**
207
+ * Get associated token address
208
+ */
209
+ export function getAssociatedTokenAddress(
210
+ owner: PublicKey,
211
+ mint: PublicKey,
212
+ tokenProgram: PublicKey = TOKEN_PROGRAM
213
+ ): PublicKey {
214
+ const [ata] = PublicKey.findProgramAddressSync(
215
+ [owner.toBuffer(), tokenProgram.toBuffer(), mint.toBuffer()],
216
+ ASSOCIATED_TOKEN_PROGRAM
217
+ );
218
+ return ata;
219
+ }
220
+
221
+ // ===== WSOL Manager =====
222
+
223
+ /**
224
+ * Create WSOL ATA and wrap SOL
225
+ * Returns instructions for: create ATA (idempotent), transfer SOL, sync_native
226
+ */
227
+ export function handleWsol(owner: PublicKey, amount: bigint): TransactionInstruction[] {
228
+ const wsolAta = getAssociatedTokenAddress(owner, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM);
229
+ const instructions: TransactionInstruction[] = [];
230
+
231
+ // Create ATA (idempotent)
232
+ instructions.push(
233
+ createAssociatedTokenAccountIdempotent(owner, owner, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM)
234
+ );
235
+
236
+ // Transfer SOL to WSOL ATA
237
+ instructions.push(
238
+ SystemProgram.transfer({
239
+ fromPubkey: owner,
240
+ toPubkey: wsolAta,
241
+ lamports: Number(amount),
242
+ })
243
+ );
244
+
245
+ // Sync native
246
+ instructions.push(
247
+ new TransactionInstruction({
248
+ keys: [{ pubkey: wsolAta, isSigner: false, isWritable: true }],
249
+ programId: TOKEN_PROGRAM,
250
+ data: Buffer.from([17]), // sync_native discriminator
251
+ })
252
+ );
253
+
254
+ return instructions;
255
+ }
256
+
257
+ /**
258
+ * Close WSOL ATA and reclaim rent
259
+ */
260
+ export function closeWsol(owner: PublicKey): TransactionInstruction {
261
+ const wsolAta = getAssociatedTokenAddress(owner, WSOL_TOKEN_ACCOUNT, TOKEN_PROGRAM);
262
+ return new TransactionInstruction({
263
+ keys: [
264
+ { pubkey: wsolAta, isSigner: false, isWritable: true },
265
+ { pubkey: owner, isSigner: false, isWritable: true },
266
+ { pubkey: owner, isSigner: true, isWritable: false },
267
+ ],
268
+ programId: TOKEN_PROGRAM,
269
+ data: Buffer.from([9, 0, 0, 0, 0, 0, 0, 0]), // close_account discriminator
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Create associated token account idempotent
275
+ */
276
+ export function createAssociatedTokenAccountIdempotent(
277
+ payer: PublicKey,
278
+ owner: PublicKey,
279
+ mint: PublicKey,
280
+ tokenProgram: PublicKey = TOKEN_PROGRAM
281
+ ): TransactionInstruction {
282
+ const ata = getAssociatedTokenAddress(owner, mint, tokenProgram);
283
+
284
+ return new TransactionInstruction({
285
+ keys: [
286
+ { pubkey: payer, isSigner: true, isWritable: true },
287
+ { pubkey: ata, isSigner: false, isWritable: true },
288
+ { pubkey: owner, isSigner: false, isWritable: false },
289
+ { pubkey: mint, isSigner: false, isWritable: false },
290
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
291
+ { pubkey: tokenProgram, isSigner: false, isWritable: false },
292
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM, isSigner: false, isWritable: false },
293
+ { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
294
+ ],
295
+ programId: ASSOCIATED_TOKEN_PROGRAM,
296
+ data: Buffer.from([1]), // Idempotent discriminator
297
+ });
298
+ }
299
+
300
+ // ===== Params Interface =====
301
+
302
+ export interface PumpSwapParams {
303
+ pool: PublicKey;
304
+ baseMint: PublicKey;
305
+ quoteMint: PublicKey;
306
+ poolBaseTokenAccount: PublicKey;
307
+ poolQuoteTokenAccount: PublicKey;
308
+ poolBaseTokenReserves: bigint;
309
+ poolQuoteTokenReserves: bigint;
310
+ coinCreatorVaultAta: PublicKey;
311
+ coinCreatorVaultAuthority: PublicKey;
312
+ baseTokenProgram: PublicKey;
313
+ quoteTokenProgram: PublicKey;
314
+ isMayhemMode: boolean;
315
+ isCashbackCoin: boolean;
316
+ }
317
+
318
+ export interface BuildBuyParams {
319
+ payer: PublicKey;
320
+ inputAmount: bigint;
321
+ slippageBasisPoints: bigint;
322
+ protocolParams: PumpSwapParams;
323
+ createInputMintAta?: boolean;
324
+ closeInputMintAta?: boolean;
325
+ createOutputMintAta?: boolean;
326
+ useExactQuoteAmount?: boolean;
327
+ fixedOutputAmount?: bigint;
328
+ }
329
+
330
+ export interface BuildSellParams {
331
+ payer: PublicKey;
332
+ inputAmount: bigint;
333
+ slippageBasisPoints: bigint;
334
+ protocolParams: PumpSwapParams;
335
+ createOutputMintAta?: boolean;
336
+ closeOutputMintAta?: boolean;
337
+ closeInputMintAta?: boolean;
338
+ fixedOutputAmount?: bigint;
339
+ }
340
+
341
+ // ===== Instruction Builders =====
342
+
343
+ /**
344
+ * Build buy instructions for PumpSwap
345
+ * 100% port from Rust: src/instruction/pumpswap.rs build_buy_instructions
346
+ */
347
+ export function buildBuyInstructions(params: BuildBuyParams): TransactionInstruction[] {
348
+ const {
349
+ payer,
350
+ inputAmount,
351
+ slippageBasisPoints,
352
+ protocolParams,
353
+ createInputMintAta = false,
354
+ closeInputMintAta = false,
355
+ createOutputMintAta = true,
356
+ useExactQuoteAmount = true,
357
+ fixedOutputAmount,
358
+ } = params;
359
+
360
+ if (inputAmount === 0n) {
361
+ throw new Error('Amount cannot be zero');
362
+ }
363
+
364
+ const {
365
+ pool,
366
+ baseMint,
367
+ quoteMint,
368
+ poolBaseTokenAccount,
369
+ poolQuoteTokenAccount,
370
+ poolBaseTokenReserves,
371
+ poolQuoteTokenReserves,
372
+ coinCreatorVaultAta,
373
+ coinCreatorVaultAuthority,
374
+ baseTokenProgram,
375
+ quoteTokenProgram,
376
+ isMayhemMode,
377
+ isCashbackCoin,
378
+ } = protocolParams;
379
+
380
+ // Check if pool contains WSOL or USDC
381
+ const isWsol = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || baseMint.equals(WSOL_TOKEN_ACCOUNT);
382
+ const isUsdc = quoteMint.equals(USDC_TOKEN_ACCOUNT) || baseMint.equals(USDC_TOKEN_ACCOUNT);
383
+
384
+ if (!isWsol && !isUsdc) {
385
+ throw new Error('Pool must contain WSOL or USDC');
386
+ }
387
+
388
+ const quoteIsWsolOrUsdc = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
389
+
390
+ // Determine creator for fee calculation
391
+ let creator = PublicKey.default;
392
+ if (!coinCreatorVaultAuthority.equals(PUMPSWAP_DEFAULT_COIN_CREATOR_VAULT_AUTHORITY)) {
393
+ creator = coinCreatorVaultAuthority;
394
+ }
395
+ const hasCoinCreator = !creator.equals(PublicKey.default);
396
+
397
+ // Calculate trade amounts
398
+ let tokenAmount: bigint;
399
+ let solAmount: bigint;
400
+
401
+ if (quoteIsWsolOrUsdc) {
402
+ // Buying base with quote (WSOL/USDC)
403
+ const result = buyQuoteInputInternal(
404
+ inputAmount,
405
+ slippageBasisPoints,
406
+ poolBaseTokenReserves,
407
+ poolQuoteTokenReserves,
408
+ hasCoinCreator
409
+ );
410
+ tokenAmount = result.base;
411
+ solAmount = result.maxQuote;
412
+ } else {
413
+ // This would be selling base for quote - shouldn't happen in buy
414
+ throw new Error('Invalid configuration for buy');
415
+ }
416
+
417
+ // Override token amount if fixed output is specified
418
+ if (fixedOutputAmount !== undefined) {
419
+ tokenAmount = fixedOutputAmount;
420
+ }
421
+
422
+ // Get user token accounts
423
+ const userBaseTokenAccount = getAssociatedTokenAddress(payer, baseMint, baseTokenProgram);
424
+ const userQuoteTokenAccount = getAssociatedTokenAddress(payer, quoteMint, quoteTokenProgram);
425
+
426
+ // Determine fee recipient
427
+ const feeRecipient = isMayhemMode ? getMayhemFeeRecipientRandom() : PUMPSWAP_FEE_RECIPIENT;
428
+ const feeRecipientAta = getFeeRecipientAta(feeRecipient, quoteMint);
429
+
430
+ // Build instructions
431
+ const instructions: TransactionInstruction[] = [];
432
+
433
+ // Handle WSOL wrapping if needed
434
+ if (createInputMintAta && quoteIsWsolOrUsdc) {
435
+ // Determine wrap amount based on instruction type:
436
+ // - buy_exact_quote_in: program spends exactly input_amount, wrap input_amount
437
+ // - buy: program may spend up to max_quote, wrap max_quote
438
+ const wrapAmount = useExactQuoteAmount ? inputAmount : solAmount;
439
+ instructions.push(...handleWsol(payer, wrapAmount));
440
+ }
441
+
442
+ // Create output token ATA if needed
443
+ if (createOutputMintAta) {
444
+ instructions.push(
445
+ createAssociatedTokenAccountIdempotent(payer, payer, baseMint, baseTokenProgram)
446
+ );
447
+ }
448
+
449
+ // Build accounts array
450
+ const accounts = [
451
+ { pubkey: pool, isSigner: false, isWritable: true },
452
+ { pubkey: payer, isSigner: true, isWritable: true },
453
+ { pubkey: PUMPSWAP_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
454
+ { pubkey: baseMint, isSigner: false, isWritable: false },
455
+ { pubkey: quoteMint, isSigner: false, isWritable: false },
456
+ { pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
457
+ { pubkey: userQuoteTokenAccount, isSigner: false, isWritable: true },
458
+ { pubkey: poolBaseTokenAccount, isSigner: false, isWritable: true },
459
+ { pubkey: poolQuoteTokenAccount, isSigner: false, isWritable: true },
460
+ { pubkey: feeRecipient, isSigner: false, isWritable: false },
461
+ { pubkey: feeRecipientAta, isSigner: false, isWritable: true },
462
+ { pubkey: baseTokenProgram, isSigner: false, isWritable: false },
463
+ { pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
464
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
465
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM, isSigner: false, isWritable: false },
466
+ { pubkey: PUMPSWAP_EVENT_AUTHORITY, isSigner: false, isWritable: false },
467
+ { pubkey: PUMPSWAP_PROGRAM, isSigner: false, isWritable: false },
468
+ { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
469
+ { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
470
+ ];
471
+
472
+ // Add volume accumulator accounts for quote (WSOL/USDC) buy
473
+ if (quoteIsWsolOrUsdc) {
474
+ accounts.push(
475
+ { pubkey: PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR, isSigner: false, isWritable: true }
476
+ );
477
+ const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
478
+ accounts.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
479
+ }
480
+
481
+ // Add fee config and program
482
+ accounts.push(
483
+ { pubkey: PUMPSWAP_FEE_CONFIG, isSigner: false, isWritable: false },
484
+ { pubkey: PUMPSWAP_FEE_PROGRAM, isSigner: false, isWritable: false }
485
+ );
486
+
487
+ // Add cashback WSOL ATA if needed
488
+ if (isCashbackCoin) {
489
+ const wsolAta = getUserVolumeAccumulatorWsolAta(payer);
490
+ accounts.push({ pubkey: wsolAta, isSigner: false, isWritable: true });
491
+ }
492
+
493
+ // Add pool v2 PDA
494
+ const poolV2 = getPoolV2PDA(baseMint);
495
+ accounts.push({ pubkey: poolV2, isSigner: false, isWritable: false });
496
+ const protocolExtraFee = getPumpSwapProtocolExtraFeeRecipientRandom();
497
+ accounts.push({ pubkey: protocolExtraFee, isSigner: false, isWritable: false });
498
+ accounts.push({
499
+ pubkey: getAssociatedTokenAddress(protocolExtraFee, quoteMint, TOKEN_PROGRAM),
500
+ isSigner: false,
501
+ isWritable: true,
502
+ });
503
+
504
+ // Build instruction data
505
+ const trackVolume = isCashbackCoin ? Buffer.from([1, 1]) : Buffer.from([1, 0]);
506
+ let data: Buffer;
507
+
508
+ if (useExactQuoteAmount) {
509
+ // buy_exact_quote_in(spendable_quote_in, min_base_amount_out, track_volume)
510
+ const minBaseAmountOut = calculateWithSlippageSell(tokenAmount, slippageBasisPoints);
511
+ data = Buffer.alloc(26);
512
+ PUMPSWAP_BUY_EXACT_QUOTE_IN_DISCRIMINATOR.copy(data, 0);
513
+ data.writeBigUInt64LE(inputAmount, 8);
514
+ data.writeBigUInt64LE(minBaseAmountOut, 16);
515
+ trackVolume.copy(data, 24);
516
+ } else {
517
+ // buy(token_amount, max_quote, track_volume)
518
+ data = Buffer.alloc(26);
519
+ PUMPSWAP_BUY_DISCRIMINATOR.copy(data, 0);
520
+ data.writeBigUInt64LE(tokenAmount, 8);
521
+ data.writeBigUInt64LE(solAmount, 16);
522
+ trackVolume.copy(data, 24);
523
+ }
524
+
525
+ instructions.push(
526
+ new TransactionInstruction({
527
+ keys: accounts,
528
+ programId: PUMPSWAP_PROGRAM,
529
+ data,
530
+ })
531
+ );
532
+
533
+ // Close WSOL ATA if requested
534
+ if (closeInputMintAta) {
535
+ instructions.push(closeWsol(payer));
536
+ }
537
+
538
+ return instructions;
539
+ }
540
+
541
+ /**
542
+ * Build sell instructions for PumpSwap
543
+ * 100% port from Rust: src/instruction/pumpswap.rs build_sell_instructions
544
+ */
545
+ export function buildSellInstructions(params: BuildSellParams): TransactionInstruction[] {
546
+ const {
547
+ payer,
548
+ inputAmount,
549
+ slippageBasisPoints,
550
+ protocolParams,
551
+ createOutputMintAta = false,
552
+ closeOutputMintAta = false,
553
+ closeInputMintAta = false,
554
+ fixedOutputAmount,
555
+ } = params;
556
+
557
+ if (inputAmount === 0n) {
558
+ throw new Error('Amount cannot be zero');
559
+ }
560
+
561
+ const {
562
+ pool,
563
+ baseMint,
564
+ quoteMint,
565
+ poolBaseTokenAccount,
566
+ poolQuoteTokenAccount,
567
+ poolBaseTokenReserves,
568
+ poolQuoteTokenReserves,
569
+ coinCreatorVaultAta,
570
+ coinCreatorVaultAuthority,
571
+ baseTokenProgram,
572
+ quoteTokenProgram,
573
+ isMayhemMode,
574
+ isCashbackCoin,
575
+ } = protocolParams;
576
+
577
+ // Check if pool contains WSOL or USDC
578
+ const isWsol = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || baseMint.equals(WSOL_TOKEN_ACCOUNT);
579
+ const isUsdc = quoteMint.equals(USDC_TOKEN_ACCOUNT) || baseMint.equals(USDC_TOKEN_ACCOUNT);
580
+
581
+ if (!isWsol && !isUsdc) {
582
+ throw new Error('Pool must contain WSOL or USDC');
583
+ }
584
+
585
+ const quoteIsWsolOrUsdc = quoteMint.equals(WSOL_TOKEN_ACCOUNT) || quoteMint.equals(USDC_TOKEN_ACCOUNT);
586
+
587
+ // Determine creator for fee calculation
588
+ let creator = PublicKey.default;
589
+ if (!coinCreatorVaultAuthority.equals(PUMPSWAP_DEFAULT_COIN_CREATOR_VAULT_AUTHORITY)) {
590
+ creator = coinCreatorVaultAuthority;
591
+ }
592
+ const hasCoinCreator = !creator.equals(PublicKey.default);
593
+
594
+ // Calculate trade amounts
595
+ let tokenAmount: bigint;
596
+ let solAmount: bigint;
597
+
598
+ if (quoteIsWsolOrUsdc) {
599
+ // Selling base for quote (WSOL/USDC)
600
+ tokenAmount = inputAmount;
601
+ const result = sellBaseInputInternal(
602
+ inputAmount,
603
+ slippageBasisPoints,
604
+ poolBaseTokenReserves,
605
+ poolQuoteTokenReserves,
606
+ hasCoinCreator
607
+ );
608
+ solAmount = result.minQuote;
609
+ } else {
610
+ // Selling quote for base - unusual case
611
+ tokenAmount = inputAmount;
612
+ solAmount = 0n; // Would need different calculation
613
+ }
614
+
615
+ // Override sol amount if fixed output is specified
616
+ if (fixedOutputAmount !== undefined) {
617
+ solAmount = fixedOutputAmount;
618
+ }
619
+
620
+ // Get user token accounts
621
+ const userBaseTokenAccount = getAssociatedTokenAddress(payer, baseMint, baseTokenProgram);
622
+ const userQuoteTokenAccount = getAssociatedTokenAddress(payer, quoteMint, quoteTokenProgram);
623
+
624
+ // Determine fee recipient
625
+ const feeRecipient = isMayhemMode ? getMayhemFeeRecipientRandom() : PUMPSWAP_FEE_RECIPIENT;
626
+ const feeRecipientAta = getFeeRecipientAta(feeRecipient, quoteMint);
627
+
628
+ // Build instructions
629
+ const instructions: TransactionInstruction[] = [];
630
+
631
+ // Create WSOL/USDC ATA if needed for receiving
632
+ if (createOutputMintAta && quoteIsWsolOrUsdc) {
633
+ instructions.push(
634
+ createAssociatedTokenAccountIdempotent(payer, payer, quoteMint, quoteTokenProgram)
635
+ );
636
+ }
637
+
638
+ // Build accounts array
639
+ const accounts = [
640
+ { pubkey: pool, isSigner: false, isWritable: true },
641
+ { pubkey: payer, isSigner: true, isWritable: true },
642
+ { pubkey: PUMPSWAP_GLOBAL_ACCOUNT, isSigner: false, isWritable: false },
643
+ { pubkey: baseMint, isSigner: false, isWritable: false },
644
+ { pubkey: quoteMint, isSigner: false, isWritable: false },
645
+ { pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
646
+ { pubkey: userQuoteTokenAccount, isSigner: false, isWritable: true },
647
+ { pubkey: poolBaseTokenAccount, isSigner: false, isWritable: true },
648
+ { pubkey: poolQuoteTokenAccount, isSigner: false, isWritable: true },
649
+ { pubkey: feeRecipient, isSigner: false, isWritable: false },
650
+ { pubkey: feeRecipientAta, isSigner: false, isWritable: true },
651
+ { pubkey: baseTokenProgram, isSigner: false, isWritable: false },
652
+ { pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
653
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
654
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM, isSigner: false, isWritable: false },
655
+ { pubkey: PUMPSWAP_EVENT_AUTHORITY, isSigner: false, isWritable: false },
656
+ { pubkey: PUMPSWAP_PROGRAM, isSigner: false, isWritable: false },
657
+ { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
658
+ { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
659
+ ];
660
+
661
+ // Add volume accumulator accounts for non-quote sell
662
+ if (!quoteIsWsolOrUsdc) {
663
+ accounts.push(
664
+ { pubkey: PUMPSWAP_GLOBAL_VOLUME_ACCUMULATOR, isSigner: false, isWritable: true }
665
+ );
666
+ const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
667
+ accounts.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
668
+ }
669
+
670
+ // Add fee config and program
671
+ accounts.push(
672
+ { pubkey: PUMPSWAP_FEE_CONFIG, isSigner: false, isWritable: false },
673
+ { pubkey: PUMPSWAP_FEE_PROGRAM, isSigner: false, isWritable: false }
674
+ );
675
+
676
+ // Add cashback accounts if needed (sell uses quote ATA)
677
+ if (isCashbackCoin) {
678
+ const quoteAta = getUserVolumeAccumulatorQuoteAta(payer, quoteMint, quoteTokenProgram);
679
+ const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
680
+ accounts.push(
681
+ { pubkey: quoteAta, isSigner: false, isWritable: true },
682
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true }
683
+ );
684
+ }
685
+
686
+ // Add pool v2 PDA
687
+ const poolV2 = getPoolV2PDA(baseMint);
688
+ accounts.push({ pubkey: poolV2, isSigner: false, isWritable: false });
689
+ const protocolExtraFee = getPumpSwapProtocolExtraFeeRecipientRandom();
690
+ accounts.push({ pubkey: protocolExtraFee, isSigner: false, isWritable: false });
691
+ accounts.push({
692
+ pubkey: getAssociatedTokenAddress(protocolExtraFee, quoteMint, TOKEN_PROGRAM),
693
+ isSigner: false,
694
+ isWritable: true,
695
+ });
696
+
697
+ // Build instruction data
698
+ const data = Buffer.alloc(24);
699
+ if (quoteIsWsolOrUsdc) {
700
+ PUMPSWAP_SELL_DISCRIMINATOR.copy(data, 0);
701
+ data.writeBigUInt64LE(tokenAmount, 8);
702
+ data.writeBigUInt64LE(solAmount, 16);
703
+ } else {
704
+ PUMPSWAP_SELL_DISCRIMINATOR.copy(data, 0);
705
+ data.writeBigUInt64LE(solAmount, 8);
706
+ data.writeBigUInt64LE(tokenAmount, 16);
707
+ }
708
+
709
+ instructions.push(
710
+ new TransactionInstruction({
711
+ keys: accounts,
712
+ programId: PUMPSWAP_PROGRAM,
713
+ data,
714
+ })
715
+ );
716
+
717
+ // Close WSOL ATA if requested
718
+ if (closeOutputMintAta && quoteIsWsolOrUsdc) {
719
+ instructions.push(closeWsol(payer));
720
+ }
721
+
722
+ // Close base token account if requested
723
+ if (closeInputMintAta) {
724
+ const closeIx = new TransactionInstruction({
725
+ keys: [
726
+ { pubkey: userBaseTokenAccount, isSigner: false, isWritable: true },
727
+ { pubkey: payer, isSigner: false, isWritable: true },
728
+ { pubkey: payer, isSigner: true, isWritable: false },
729
+ ],
730
+ programId: baseTokenProgram,
731
+ data: Buffer.from([9, 0, 0, 0, 0, 0, 0, 0]),
732
+ });
733
+ instructions.push(closeIx);
734
+ }
735
+
736
+ return instructions;
737
+ }
738
+
739
+ /**
740
+ * Build claim cashback instruction for PumpSwap
741
+ */
742
+ export function buildClaimCashbackInstruction(
743
+ payer: PublicKey,
744
+ quoteMint: PublicKey,
745
+ quoteTokenProgram: PublicKey
746
+ ): TransactionInstruction {
747
+ const userVolumeAccumulator = getUserVolumeAccumulatorPDA(payer);
748
+ const userVolumeAccumulatorWsolAta = getUserVolumeAccumulatorWsolAta(payer);
749
+ const userWsolAta = getAssociatedTokenAddress(payer, quoteMint, quoteTokenProgram);
750
+
751
+ const accounts = [
752
+ { pubkey: payer, isSigner: true, isWritable: true },
753
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
754
+ { pubkey: quoteMint, isSigner: false, isWritable: false },
755
+ { pubkey: quoteTokenProgram, isSigner: false, isWritable: false },
756
+ { pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true },
757
+ { pubkey: userWsolAta, isSigner: false, isWritable: true },
758
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
759
+ { pubkey: PUMPSWAP_EVENT_AUTHORITY, isSigner: false, isWritable: false },
760
+ { pubkey: PUMPSWAP_PROGRAM, isSigner: false, isWritable: false },
761
+ ];
762
+
763
+ return new TransactionInstruction({
764
+ keys: accounts,
765
+ programId: PUMPSWAP_PROGRAM,
766
+ data: PUMPSWAP_CLAIM_CASHBACK_DISCRIMINATOR,
767
+ });
768
+ }
769
+
770
+ // ===== Pool Types and Decoding - from Rust: src/instruction/utils/pumpswap_types.rs =====
771
+
772
+ /**
773
+ * Pool size in bytes (244 bytes as per pump-public-docs)
774
+ */
775
+ export const POOL_SIZE = 244;
776
+
777
+ /**
778
+ * PumpSwap Pool structure
779
+ * Matches Rust: src/instruction/utils/pumpswap_types.rs Pool struct
780
+ */
781
+ export interface PumpSwapPool {
782
+ poolBump: number;
783
+ index: number;
784
+ creator: PublicKey;
785
+ baseMint: PublicKey;
786
+ quoteMint: PublicKey;
787
+ lpMint: PublicKey;
788
+ poolBaseTokenAccount: PublicKey;
789
+ poolQuoteTokenAccount: PublicKey;
790
+ lpSupply: bigint;
791
+ coinCreator: PublicKey;
792
+ isMayhemMode: boolean;
793
+ isCashbackCoin: boolean;
794
+ }
795
+
796
+ /**
797
+ * Decode a PumpSwap pool from account data
798
+ * Uses Borsh deserialization
799
+ */
800
+ export function decodePool(data: Buffer): PumpSwapPool | null {
801
+ if (data.length < POOL_SIZE) {
802
+ return null;
803
+ }
804
+
805
+ try {
806
+ let offset = 0;
807
+
808
+ // pool_bump: u8
809
+ const poolBump = data.readUInt8(offset);
810
+ offset += 1;
811
+
812
+ // index: u16
813
+ const index = data.readUInt16LE(offset);
814
+ offset += 2;
815
+
816
+ // creator: Pubkey (32 bytes)
817
+ const creator = new PublicKey(data.subarray(offset, offset + 32));
818
+ offset += 32;
819
+
820
+ // base_mint: Pubkey
821
+ const baseMint = new PublicKey(data.subarray(offset, offset + 32));
822
+ offset += 32;
823
+
824
+ // quote_mint: Pubkey
825
+ const quoteMint = new PublicKey(data.subarray(offset, offset + 32));
826
+ offset += 32;
827
+
828
+ // lp_mint: Pubkey
829
+ const lpMint = new PublicKey(data.subarray(offset, offset + 32));
830
+ offset += 32;
831
+
832
+ // pool_base_token_account: Pubkey
833
+ const poolBaseTokenAccount = new PublicKey(data.subarray(offset, offset + 32));
834
+ offset += 32;
835
+
836
+ // pool_quote_token_account: Pubkey
837
+ const poolQuoteTokenAccount = new PublicKey(data.subarray(offset, offset + 32));
838
+ offset += 32;
839
+
840
+ // lp_supply: u64
841
+ const lpSupply = data.readBigUInt64LE(offset);
842
+ offset += 8;
843
+
844
+ // coin_creator: Pubkey
845
+ const coinCreator = new PublicKey(data.subarray(offset, offset + 32));
846
+ offset += 32;
847
+
848
+ // is_mayhem_mode: bool
849
+ const isMayhemMode = data.readUInt8(offset) === 1;
850
+ offset += 1;
851
+
852
+ // is_cashback_coin: bool
853
+ const isCashbackCoin = data.readUInt8(offset) === 1;
854
+ offset += 1;
855
+
856
+ return {
857
+ poolBump,
858
+ index,
859
+ creator,
860
+ baseMint,
861
+ quoteMint,
862
+ lpMint,
863
+ poolBaseTokenAccount,
864
+ poolQuoteTokenAccount,
865
+ lpSupply,
866
+ coinCreator,
867
+ isMayhemMode,
868
+ isCashbackCoin,
869
+ };
870
+ } catch {
871
+ return null;
872
+ }
873
+ }
874
+
875
+ // ===== Pool Finder Functions - from Rust: src/instruction/utils/pumpswap.rs =====
876
+
877
+ /**
878
+ * Find a PumpSwap pool by mint
879
+ *
880
+ * Search order (matches @pump-fun/pump-swap-sdk):
881
+ * 1. Pool v2 PDA ["pool-v2", base_mint]
882
+ * 2. Canonical pool PDA ["pool", 0, pumpPoolAuthority(mint), mint, WSOL]
883
+ * 3. getProgramAccounts by base_mint / quote_mint
884
+ */
885
+ export async function findPoolByMint(
886
+ connection: { getAccountInfo: (pubkey: PublicKey) => Promise<{ value: { data: Buffer } | null }> },
887
+ mint: PublicKey
888
+ ): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
889
+ // 1. Try Pool v2 PDA
890
+ const poolV2 = getPoolV2PDA(mint);
891
+ const poolV2Account = await connection.getAccountInfo(poolV2);
892
+ if (poolV2Account?.value?.data) {
893
+ const pool = decodePool(poolV2Account.value.data);
894
+ if (pool && pool.baseMint.equals(mint)) {
895
+ return { poolAddress: poolV2, pool };
896
+ }
897
+ }
898
+
899
+ // 2. Try canonical pool PDA
900
+ const canonicalAddress = getCanonicalPoolPDA(mint);
901
+ const canonicalAccount = await connection.getAccountInfo(canonicalAddress);
902
+ if (canonicalAccount?.value?.data) {
903
+ const pool = decodePool(canonicalAccount.value.data);
904
+ if (pool && pool.baseMint.equals(mint)) {
905
+ return { poolAddress: canonicalAddress, pool };
906
+ }
907
+ }
908
+
909
+ return null;
910
+ }
911
+
912
+ /**
913
+ * Get fee config PDA
914
+ */
915
+ export function getFeeConfigPDA(): PublicKey {
916
+ const [pda] = PublicKey.findProgramAddressSync(
917
+ [FEE_CONFIG_SEED, PUMPSWAP_PROGRAM.toBuffer()],
918
+ new PublicKey('pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ')
919
+ );
920
+ return pda;
921
+ }
922
+
923
+ // ===== Async Fetch Functions - from Rust: src/instruction/utils/pumpswap.rs =====
924
+
925
+ /**
926
+ * Fetch a PumpSwap pool from RPC.
927
+ * 100% from Rust: src/instruction/utils/pumpswap.rs fetch_pool
928
+ */
929
+ export async function fetchPool(
930
+ connection: { getAccountInfo: (pubkey: PublicKey) => Promise<{ value?: { data: Buffer } }> },
931
+ poolAddress: PublicKey
932
+ ): Promise<PumpSwapPool | null> {
933
+ const account = await connection.getAccountInfo(poolAddress);
934
+ if (!account?.value?.data) {
935
+ return null;
936
+ }
937
+ const pool = decodePool(account.value.data);
938
+ return pool;
939
+ }
940
+
941
+ /**
942
+ * Get token balances for a pool's token accounts.
943
+ * 100% from Rust: src/instruction/utils/pumpswap.rs get_token_balances
944
+ */
945
+ export async function getTokenBalances(
946
+ connection: {
947
+ getTokenAccountBalance: (pubkey: PublicKey) => Promise<{ value?: { amount: string } }>
948
+ },
949
+ pool: PumpSwapPool
950
+ ): Promise<{ baseBalance: bigint; quoteBalance: bigint } | null> {
951
+ try {
952
+ const baseBalanceResult = await connection.getTokenAccountBalance(pool.poolBaseTokenAccount);
953
+ const quoteBalanceResult = await connection.getTokenAccountBalance(pool.poolQuoteTokenAccount);
954
+
955
+ const baseBalance = BigInt(baseBalanceResult?.value?.amount ?? '0');
956
+ const quoteBalance = BigInt(quoteBalanceResult?.value?.amount ?? '0');
957
+
958
+ return { baseBalance, quoteBalance };
959
+ } catch {
960
+ return null;
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Find a PumpSwap pool by mint with full RPC lookup.
966
+ * 100% from Rust: src/instruction/utils/pumpswap.rs find_by_mint
967
+ * Search order:
968
+ * 1. Pool v2 PDA ["pool-v2", base_mint]
969
+ * 2. Canonical pool PDA
970
+ * 3. getProgramAccounts by base_mint / quote_mint (optional fallback)
971
+ */
972
+ export async function findByMint(
973
+ connection: {
974
+ getAccountInfo: (pubkey: PublicKey) => Promise<{ value?: { data: Buffer } }>;
975
+ getProgramAccounts?: (programId: PublicKey, config?: unknown) => Promise<Array<{ pubkey: PublicKey; account: { data: Buffer } }>>;
976
+ },
977
+ mint: PublicKey
978
+ ): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
979
+ // 1. Try v2 PDA
980
+ const poolV2 = getPoolV2PDA(mint);
981
+ const poolV2Account = await connection.getAccountInfo(poolV2);
982
+ if (poolV2Account?.value?.data) {
983
+ const pool = decodePool(poolV2Account.value.data);
984
+ if (pool && pool.baseMint.equals(mint)) {
985
+ return { poolAddress: poolV2, pool };
986
+ }
987
+ }
988
+
989
+ // 2. Try canonical pool PDA
990
+ const canonicalAddress = getCanonicalPoolPDA(mint);
991
+ const canonicalAccount = await connection.getAccountInfo(canonicalAddress);
992
+ if (canonicalAccount?.value?.data) {
993
+ const pool = decodePool(canonicalAccount.value.data);
994
+ if (pool && pool.baseMint.equals(mint)) {
995
+ return { poolAddress: canonicalAddress, pool };
996
+ }
997
+ }
998
+
999
+ // 3. Optional: getProgramAccounts fallback (if available)
1000
+ // This would require more complex implementation with memcmp filters
1001
+
1002
+ return null;
1003
+ }
1004
+
1005
+ // ===== Pool Size Constants - from Rust: src/instruction/utils/pumpswap.rs =====
1006
+
1007
+ /** Pool data size for SPL Token (8 discriminator + 244 data) */
1008
+ const POOL_DATA_LEN_SPL = 8 + 244;
1009
+ /** Pool data size for Token2022 */
1010
+ const POOL_DATA_LEN_T22 = 643;
1011
+
1012
+ /**
1013
+ * Find a PumpSwap pool by base mint using getProgramAccounts.
1014
+ * 100% from Rust: src/instruction/utils/pumpswap.rs find_by_base_mint
1015
+ * base_mint offset: 8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43
1016
+ */
1017
+ export async function findByBaseMint(
1018
+ connection: {
1019
+ getProgramAccounts: (
1020
+ programId: PublicKey,
1021
+ config?: {
1022
+ filters?: Array<{ dataSize?: number; memcmp?: { offset: number; bytes: string } }>;
1023
+ encoding?: string;
1024
+ }
1025
+ ) => Promise<Array<{ pubkey: PublicKey; account: { data: Buffer } }>>;
1026
+ },
1027
+ baseMint: PublicKey
1028
+ ): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
1029
+ // base_mint offset: 8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43
1030
+ const memcmpOffset = 43;
1031
+
1032
+ // Query both pool sizes in parallel (SPL Token and Token2022)
1033
+ const filters = [
1034
+ { memcmp: { offset: memcmpOffset, bytes: baseMint.toBase58() } }
1035
+ ];
1036
+
1037
+ try {
1038
+ const results = await connection.getProgramAccounts(PUMPSWAP_PROGRAM, {
1039
+ filters,
1040
+ encoding: 'base64'
1041
+ });
1042
+
1043
+ if (!results || results.length === 0) {
1044
+ return null;
1045
+ }
1046
+
1047
+ // Decode and sort by lp_supply (highest first)
1048
+ const pools: { poolAddress: PublicKey; pool: PumpSwapPool }[] = [];
1049
+ for (const { pubkey, account } of results) {
1050
+ const pool = decodePool(account.data);
1051
+ if (pool) {
1052
+ pools.push({ poolAddress: pubkey, pool });
1053
+ }
1054
+ }
1055
+
1056
+ if (pools.length === 0) {
1057
+ return null;
1058
+ }
1059
+
1060
+ // Sort by lp_supply descending
1061
+ pools.sort((a, b) => Number(b.pool.lpSupply - a.pool.lpSupply));
1062
+
1063
+ return pools[0] ?? null;
1064
+ } catch {
1065
+ return null;
1066
+ }
1067
+ }
1068
+
1069
+ /**
1070
+ * Find a PumpSwap pool by quote mint using getProgramAccounts.
1071
+ * 100% from Rust: src/instruction/utils/pumpswap.rs find_by_quote_mint
1072
+ * quote_mint offset: 8 + 1 + 2 + 32 + 32 = 75
1073
+ */
1074
+ export async function findByQuoteMint(
1075
+ connection: {
1076
+ getProgramAccounts: (
1077
+ programId: PublicKey,
1078
+ config?: {
1079
+ filters?: Array<{ dataSize?: number; memcmp?: { offset: number; bytes: string } }>;
1080
+ encoding?: string;
1081
+ }
1082
+ ) => Promise<Array<{ pubkey: PublicKey; account: { data: Buffer } }>>;
1083
+ },
1084
+ quoteMint: PublicKey
1085
+ ): Promise<{ poolAddress: PublicKey; pool: PumpSwapPool } | null> {
1086
+ // quote_mint offset: 8 + 1 + 2 + 32 + 32 = 75
1087
+ const memcmpOffset = 75;
1088
+
1089
+ const filters = [
1090
+ { memcmp: { offset: memcmpOffset, bytes: quoteMint.toBase58() } }
1091
+ ];
1092
+
1093
+ try {
1094
+ const results = await connection.getProgramAccounts(PUMPSWAP_PROGRAM, {
1095
+ filters,
1096
+ encoding: 'base64'
1097
+ });
1098
+
1099
+ if (!results || results.length === 0) {
1100
+ return null;
1101
+ }
1102
+
1103
+ // Decode and sort by lp_supply (highest first)
1104
+ const pools: { poolAddress: PublicKey; pool: PumpSwapPool }[] = [];
1105
+ for (const { pubkey, account } of results) {
1106
+ const pool = decodePool(account.data);
1107
+ if (pool) {
1108
+ pools.push({ poolAddress: pubkey, pool });
1109
+ }
1110
+ }
1111
+
1112
+ if (pools.length === 0) {
1113
+ return null;
1114
+ }
1115
+
1116
+ // Sort by lp_supply descending
1117
+ pools.sort((a, b) => Number(b.pool.lpSupply - a.pool.lpSupply));
1118
+
1119
+ return pools[0] ?? null;
1120
+ } catch {
1121
+ return null;
1122
+ }
1123
+ }