sol-trade-sdk 0.1.0 → 0.1.2

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 (50) hide show
  1. package/README.md +12 -5
  2. package/dist/cache/index.d.mts +70 -0
  3. package/dist/cache/index.d.ts +70 -0
  4. package/dist/cache/index.js +259 -0
  5. package/dist/cache/index.mjs +19 -0
  6. package/dist/chunk-64JY4YH6.mjs +227 -0
  7. package/dist/chunk-6BWS3CLP.mjs +16 -0
  8. package/dist/{chunk-NEZDFAYA.mjs → chunk-H4ZUMTTP.mjs} +864 -336
  9. package/dist/{chunk-MMQAMIKR.mjs → chunk-L6RZIXXN.mjs} +4 -11
  10. package/dist/{clients-VITWK7B6.mjs → chunk-V7QDWUXJ.mjs} +39 -39
  11. package/dist/clients-PSG35HN4.mjs +107 -0
  12. package/dist/index-hZh5hrGj.d.mts +3089 -0
  13. package/dist/index-yJ5AsNfm.d.ts +3089 -0
  14. package/dist/index.d.mts +2 -2658
  15. package/dist/index.d.ts +2 -2658
  16. package/dist/index.js +794 -278
  17. package/dist/index.mjs +21 -4
  18. package/dist/perf/index.mjs +2 -1
  19. package/dist/pool/index.d.mts +88 -0
  20. package/dist/pool/index.d.ts +88 -0
  21. package/dist/pool/index.js +262 -0
  22. package/dist/pool/index.mjs +233 -0
  23. package/dist/rpc/index.d.mts +77 -0
  24. package/dist/rpc/index.d.ts +77 -0
  25. package/dist/rpc/index.js +447 -0
  26. package/dist/rpc/index.mjs +223 -0
  27. package/dist/swqos/index.d.mts +416 -0
  28. package/dist/swqos/index.d.ts +416 -0
  29. package/dist/swqos/index.js +3450 -0
  30. package/dist/swqos/index.mjs +1375 -0
  31. package/dist/trading/index.d.mts +998 -0
  32. package/dist/trading/index.d.ts +998 -0
  33. package/dist/trading/index.js +4302 -0
  34. package/dist/trading/index.mjs +2097 -0
  35. package/package.json +5 -4
  36. package/src/__tests__/hotpath.test.ts +33 -0
  37. package/src/__tests__/parser-compat.test.ts +82 -0
  38. package/src/__tests__/protocol-parity.test.ts +242 -0
  39. package/src/__tests__/sdk.test.ts +16 -0
  40. package/src/cache/index.ts +1 -0
  41. package/src/hotpath/executor.ts +25 -18
  42. package/src/index.ts +330 -92
  43. package/src/instruction/meteora_damm_v2_builder.ts +88 -20
  44. package/src/instruction/pumpfun_builder.ts +133 -43
  45. package/src/instruction/raydium_amm_v4_builder.ts +244 -35
  46. package/src/instruction/raydium_cpmm_builder.ts +87 -69
  47. package/src/params/index.ts +38 -5
  48. package/src/pool/index.ts +1 -0
  49. package/src/rpc/index.ts +1 -0
  50. package/src/trading/factory.ts +10 -0
@@ -25,6 +25,8 @@ import {
25
25
  // Program IDs and Constants
26
26
  // ============================================
27
27
 
28
+ const SOL_TOKEN_ACCOUNT = new PublicKey("So11111111111111111111111111111111111111111");
29
+
28
30
  /** Meteora DAMM V2 program ID */
29
31
  export const METEORA_DAMM_V2_PROGRAM_ID = new PublicKey(
30
32
  "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"
@@ -43,6 +45,10 @@ export const METEORA_DAMM_V2_AUTHORITY = new PublicKey(
43
45
  export const METEORA_DAMM_V2_SWAP_DISCRIMINATOR: Buffer = Buffer.from([
44
46
  248, 198, 158, 145, 225, 117, 135, 200,
45
47
  ]);
48
+ export const METEORA_DAMM_V2_SWAP2_DISCRIMINATOR: Buffer = Buffer.from([
49
+ 65, 75, 63, 76, 235, 91, 91, 136,
50
+ ]);
51
+ export const METEORA_DAMM_V2_SWAP_MODE_PARTIAL_FILL = 1;
46
52
 
47
53
  // ============================================
48
54
  // Seeds
@@ -109,6 +115,25 @@ export interface BuildMeteoraDammV2SellInstructionsParams {
109
115
  // Instruction Builders
110
116
  // ============================================
111
117
 
118
+ function isDefaultPublicKey(pubkey: PublicKey): boolean {
119
+ return pubkey.equals(PublicKey.default);
120
+ }
121
+
122
+ function isMintMatch(requested: PublicKey, expected: PublicKey): boolean {
123
+ return (
124
+ requested.equals(expected) ||
125
+ (expected.equals(NATIVE_MINT) && requested.equals(SOL_TOKEN_ACCOUNT))
126
+ );
127
+ }
128
+
129
+ function ensureExpectedMint(label: string, requested: PublicKey, expected: PublicKey): void {
130
+ if (!isDefaultPublicKey(requested) && !isMintMatch(requested, expected)) {
131
+ throw new Error(
132
+ `${label} must match the Meteora DAMM v2 pool side (${expected.toBase58()}), got ${requested.toBase58()}`
133
+ );
134
+ }
135
+ }
136
+
112
137
  /**
113
138
  * Build buy instructions for Meteora DAMM V2 protocol
114
139
  */
@@ -117,8 +142,8 @@ export function buildMeteoraDammV2BuyInstructions(
117
142
  ): TransactionInstruction[] {
118
143
  const {
119
144
  payer,
120
- inputMint,
121
- outputMint,
145
+ inputMint: requestedInputMint,
146
+ outputMint: requestedOutputMint,
122
147
  inputAmount,
123
148
  fixedOutputAmount,
124
149
  createInputMintAta = true,
@@ -162,6 +187,11 @@ export function buildMeteoraDammV2BuyInstructions(
162
187
  // Determine swap direction
163
188
  const isAIn = tokenAMint.equals(WSOL_TOKEN_ACCOUNT) || tokenAMint.equals(USDC_TOKEN_ACCOUNT);
164
189
 
190
+ const inputMint = isAIn ? tokenAMint : tokenBMint;
191
+ const outputMint = isAIn ? tokenBMint : tokenAMint;
192
+ ensureExpectedMint("inputMint", requestedInputMint, inputMint);
193
+ ensureExpectedMint("outputMint", requestedOutputMint, outputMint);
194
+
165
195
  // Derive user token accounts
166
196
  const inputTokenAccount = getAssociatedTokenAddressSync(
167
197
  inputMint,
@@ -179,8 +209,11 @@ export function buildMeteoraDammV2BuyInstructions(
179
209
  // Derive event authority
180
210
  const eventAuthority = getMeteoraDammV2EventAuthorityPda();
181
211
 
182
- // Handle WSOL wrapping
183
- if (createInputMintAta && isWsol) {
212
+ const inputTokenProgram = isAIn ? tokenAProgram : tokenBProgram;
213
+ const outputTokenProgram = isAIn ? tokenBProgram : tokenAProgram;
214
+
215
+ // Handle input account creation/wrapping
216
+ if (createInputMintAta && inputMint.equals(WSOL_TOKEN_ACCOUNT)) {
184
217
  const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
185
218
  instructions.push(
186
219
  createAssociatedTokenAccountInstruction(
@@ -191,7 +224,24 @@ export function buildMeteoraDammV2BuyInstructions(
191
224
  TOKEN_PROGRAM_ID
192
225
  )
193
226
  );
227
+ instructions.push(
228
+ SystemProgram.transfer({
229
+ fromPubkey: payerPubkey,
230
+ toPubkey: wsolAta,
231
+ lamports: Number(inputAmount),
232
+ })
233
+ );
194
234
  instructions.push(createSyncNativeInstruction(wsolAta));
235
+ } else if (createInputMintAta) {
236
+ instructions.push(
237
+ createAssociatedTokenAccountInstruction(
238
+ payerPubkey,
239
+ inputTokenAccount,
240
+ payerPubkey,
241
+ inputMint,
242
+ inputTokenProgram
243
+ )
244
+ );
195
245
  }
196
246
 
197
247
  // Create output mint ATA if needed
@@ -202,18 +252,19 @@ export function buildMeteoraDammV2BuyInstructions(
202
252
  outputTokenAccount,
203
253
  payerPubkey,
204
254
  outputMint,
205
- TOKEN_PROGRAM_ID
255
+ outputTokenProgram
206
256
  )
207
257
  );
208
258
  }
209
259
 
210
- // Build instruction data
211
- const data = Buffer.alloc(24);
212
- METEORA_DAMM_V2_SWAP_DISCRIMINATOR.copy(data, 0);
260
+ // Build swap2 instruction data
261
+ const data = Buffer.alloc(25);
262
+ METEORA_DAMM_V2_SWAP2_DISCRIMINATOR.copy(data, 0);
213
263
  data.writeBigUInt64LE(inputAmount, 8);
214
264
  data.writeBigUInt64LE(fixedOutputAmount, 16);
265
+ data.writeUInt8(METEORA_DAMM_V2_SWAP_MODE_PARTIAL_FILL, 24);
215
266
 
216
- // Build accounts (14 accounts)
267
+ // Build accounts (13 accounts)
217
268
  const accounts: AccountMeta[] = [
218
269
  { pubkey: METEORA_DAMM_V2_AUTHORITY, isSigner: false, isWritable: false },
219
270
  { pubkey: pool, isSigner: false, isWritable: true },
@@ -226,7 +277,6 @@ export function buildMeteoraDammV2BuyInstructions(
226
277
  { pubkey: payerPubkey, isSigner: true, isWritable: true },
227
278
  { pubkey: tokenAProgram, isSigner: false, isWritable: false },
228
279
  { pubkey: tokenBProgram, isSigner: false, isWritable: false },
229
- { pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false }, // Referral Token Account (placeholder)
230
280
  { pubkey: eventAuthority, isSigner: false, isWritable: false },
231
281
  { pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false },
232
282
  ];
@@ -240,7 +290,7 @@ export function buildMeteoraDammV2BuyInstructions(
240
290
  );
241
291
 
242
292
  // Close WSOL ATA if requested
243
- if (closeInputMintAta && isWsol) {
293
+ if (closeInputMintAta && inputMint.equals(WSOL_TOKEN_ACCOUNT)) {
244
294
  const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
245
295
  instructions.push(
246
296
  createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
@@ -258,8 +308,8 @@ export function buildMeteoraDammV2SellInstructions(
258
308
  ): TransactionInstruction[] {
259
309
  const {
260
310
  payer,
261
- inputMint,
262
- outputMint,
311
+ inputMint: requestedInputMint,
312
+ outputMint: requestedOutputMint,
263
313
  inputAmount,
264
314
  fixedOutputAmount,
265
315
  createOutputMintAta = true,
@@ -303,6 +353,11 @@ export function buildMeteoraDammV2SellInstructions(
303
353
  // Determine swap direction (selling token for WSOL/USDC)
304
354
  const isAIn = tokenBMint.equals(WSOL_TOKEN_ACCOUNT) || tokenBMint.equals(USDC_TOKEN_ACCOUNT);
305
355
 
356
+ const inputMint = isAIn ? tokenAMint : tokenBMint;
357
+ const outputMint = isAIn ? tokenBMint : tokenAMint;
358
+ ensureExpectedMint("inputMint", requestedInputMint, inputMint);
359
+ ensureExpectedMint("outputMint", requestedOutputMint, outputMint);
360
+
306
361
  // Derive user token accounts
307
362
  const inputTokenAccount = getAssociatedTokenAddressSync(
308
363
  inputMint,
@@ -320,8 +375,11 @@ export function buildMeteoraDammV2SellInstructions(
320
375
  // Derive event authority
321
376
  const eventAuthority = getMeteoraDammV2EventAuthorityPda();
322
377
 
323
- // Create WSOL ATA for receiving if needed
324
- if (createOutputMintAta && isWsol) {
378
+ const inputTokenProgram = isAIn ? tokenAProgram : tokenBProgram;
379
+ const outputTokenProgram = isAIn ? tokenBProgram : tokenAProgram;
380
+
381
+ // Create output ATA for receiving if needed
382
+ if (createOutputMintAta && outputMint.equals(WSOL_TOKEN_ACCOUNT)) {
325
383
  const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
326
384
  instructions.push(
327
385
  createAssociatedTokenAccountInstruction(
@@ -332,13 +390,24 @@ export function buildMeteoraDammV2SellInstructions(
332
390
  TOKEN_PROGRAM_ID
333
391
  )
334
392
  );
393
+ } else if (createOutputMintAta) {
394
+ instructions.push(
395
+ createAssociatedTokenAccountInstruction(
396
+ payerPubkey,
397
+ outputTokenAccount,
398
+ payerPubkey,
399
+ outputMint,
400
+ outputTokenProgram
401
+ )
402
+ );
335
403
  }
336
404
 
337
- // Build instruction data
338
- const data = Buffer.alloc(24);
339
- METEORA_DAMM_V2_SWAP_DISCRIMINATOR.copy(data, 0);
405
+ // Build swap2 instruction data
406
+ const data = Buffer.alloc(25);
407
+ METEORA_DAMM_V2_SWAP2_DISCRIMINATOR.copy(data, 0);
340
408
  data.writeBigUInt64LE(inputAmount, 8);
341
409
  data.writeBigUInt64LE(fixedOutputAmount, 16);
410
+ data.writeUInt8(METEORA_DAMM_V2_SWAP_MODE_PARTIAL_FILL, 24);
342
411
 
343
412
  // Build accounts
344
413
  const accounts: AccountMeta[] = [
@@ -353,7 +422,6 @@ export function buildMeteoraDammV2SellInstructions(
353
422
  { pubkey: payerPubkey, isSigner: true, isWritable: true },
354
423
  { pubkey: tokenAProgram, isSigner: false, isWritable: false },
355
424
  { pubkey: tokenBProgram, isSigner: false, isWritable: false },
356
- { pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false }, // Referral Token Account
357
425
  { pubkey: eventAuthority, isSigner: false, isWritable: false },
358
426
  { pubkey: METEORA_DAMM_V2_PROGRAM_ID, isSigner: false, isWritable: false },
359
427
  ];
@@ -367,7 +435,7 @@ export function buildMeteoraDammV2SellInstructions(
367
435
  );
368
436
 
369
437
  // Close WSOL ATA if requested
370
- if (closeOutputMintAta && isWsol) {
438
+ if (closeOutputMintAta && outputMint.equals(WSOL_TOKEN_ACCOUNT)) {
371
439
  const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, payerPubkey, true);
372
440
  instructions.push(
373
441
  createCloseAccountInstruction(wsolAta, payerPubkey, payerPubkey, [], TOKEN_PROGRAM_ID)
@@ -15,7 +15,6 @@ import {
15
15
  } from "@solana/web3.js";
16
16
  import {
17
17
  getAssociatedTokenAddressSync,
18
- createAssociatedTokenAccountInstruction,
19
18
  createAssociatedTokenAccountIdempotentInstruction,
20
19
  TOKEN_PROGRAM_ID,
21
20
  TOKEN_2022_PROGRAM_ID,
@@ -25,6 +24,8 @@ import {
25
24
  NATIVE_MINT,
26
25
  } from "@solana/spl-token";
27
26
 
27
+ const SOL_TOKEN_ACCOUNT = new PublicKey("So11111111111111111111111111111111111111111");
28
+
28
29
  // ============================================
29
30
  // Program IDs and Constants
30
31
  // ============================================
@@ -281,35 +282,34 @@ export interface PumpFunParams {
281
282
  closeTokenAccountWhenSell?: boolean;
282
283
  /** From parser/gRPC (`tradeEvent.feeRecipient`); default pubkey → random pool */
283
284
  feeRecipient?: PublicKey;
284
- /** Quote mint for V2 instructions; default means WSOL. */
285
+ /** Layout selector: default/Solscan SOL sentinel keeps legacy SOL; WSOL/USDC selects V2. */
285
286
  quoteMint?: PublicKey;
286
- /** Per-params V2 toggle; global `TradeConfig.usePumpfunV2` also maps here. */
287
- useV2Ix?: boolean;
288
287
  }
289
288
 
290
289
  export interface PumpFunBuildBuyParams {
291
290
  payer: Keypair | PublicKey;
291
+ inputMint?: PublicKey;
292
292
  outputMint: PublicKey;
293
293
  inputAmount: bigint;
294
294
  slippageBasisPoints?: bigint;
295
295
  fixedOutputAmount?: bigint;
296
296
  createOutputMintAta?: boolean;
297
297
  createInputMintAta?: boolean;
298
+ closeInputMintAta?: boolean;
298
299
  protocolParams: PumpFunParams;
299
300
  useExactSolAmount?: boolean;
300
- usePumpFunV2?: boolean;
301
301
  }
302
302
 
303
303
  export interface PumpFunBuildSellParams {
304
304
  payer: Keypair | PublicKey;
305
305
  inputMint: PublicKey;
306
+ outputMint?: PublicKey;
306
307
  inputAmount: bigint;
307
308
  slippageBasisPoints?: bigint;
308
309
  fixedOutputAmount?: bigint;
309
310
  createOutputMintAta?: boolean;
310
311
  closeInputMintAta?: boolean;
311
312
  protocolParams: PumpFunParams;
312
- usePumpFunV2?: boolean;
313
313
  }
314
314
 
315
315
  // ============================================
@@ -391,7 +391,40 @@ function effectivePumpMintTokenProgram(mint: PublicKey, protocolParams: PumpFunP
391
391
  }
392
392
 
393
393
  function effectiveQuoteMint(protocolParams: PumpFunParams): PublicKey {
394
- return isUsablePubkey(protocolParams.quoteMint) ? protocolParams.quoteMint : NATIVE_MINT;
394
+ if (!isUsablePubkey(protocolParams.quoteMint) || protocolParams.quoteMint.equals(SOL_TOKEN_ACCOUNT)) {
395
+ return NATIVE_MINT;
396
+ }
397
+ return protocolParams.quoteMint;
398
+ }
399
+
400
+ function usesPumpFunV2Layout(protocolParams: PumpFunParams): boolean {
401
+ return isUsablePubkey(protocolParams.quoteMint) && !protocolParams.quoteMint.equals(SOL_TOKEN_ACCOUNT);
402
+ }
403
+
404
+ function isSolQuoteMint(mint: PublicKey): boolean {
405
+ return mint.equals(SOL_TOKEN_ACCOUNT) || mint.equals(NATIVE_MINT);
406
+ }
407
+
408
+ function validateV2BuyQuoteMint(inputMint: PublicKey, quoteMint: PublicKey): void {
409
+ if (isSolQuoteMint(quoteMint)) {
410
+ if (inputMint.equals(SOL_TOKEN_ACCOUNT) || inputMint.equals(NATIVE_MINT)) return;
411
+ } else if (inputMint.equals(quoteMint)) {
412
+ return;
413
+ }
414
+ throw new Error(
415
+ `PumpFun V2 buy input_mint ${inputMint.toBase58()} does not match quote_mint ${quoteMint.toBase58()}; USDC quote pools must be bought with USDC, not SOL`
416
+ );
417
+ }
418
+
419
+ function validateV2SellQuoteMint(outputMint: PublicKey, quoteMint: PublicKey): void {
420
+ if (isSolQuoteMint(quoteMint)) {
421
+ if (outputMint.equals(SOL_TOKEN_ACCOUNT) || outputMint.equals(NATIVE_MINT)) return;
422
+ } else if (outputMint.equals(quoteMint)) {
423
+ return;
424
+ }
425
+ throw new Error(
426
+ `PumpFun V2 sell output_mint ${outputMint.toBase58()} does not match quote_mint ${quoteMint.toBase58()}; USDC quote pools settle to USDC, not SOL`
427
+ );
395
428
  }
396
429
 
397
430
  function associatedTokenAddress(mint: PublicKey, owner: PublicKey, tokenProgram: PublicKey): PublicKey {
@@ -404,6 +437,36 @@ function associatedTokenAddress(mint: PublicKey, owner: PublicKey, tokenProgram:
404
437
  );
405
438
  }
406
439
 
440
+ function pushCreateOrWrapUserTokenAccount(
441
+ instructions: TransactionInstruction[],
442
+ payer: PublicKey,
443
+ ata: PublicKey,
444
+ mint: PublicKey,
445
+ tokenProgram: PublicKey,
446
+ amount: bigint
447
+ ): void {
448
+ instructions.push(
449
+ createAssociatedTokenAccountIdempotentInstruction(
450
+ payer,
451
+ ata,
452
+ payer,
453
+ mint,
454
+ tokenProgram,
455
+ SPL_ASSOCIATED_TOKEN_PROGRAM_ID
456
+ )
457
+ );
458
+ if (mint.equals(NATIVE_MINT)) {
459
+ instructions.push(
460
+ SystemProgram.transfer({
461
+ fromPubkey: payer,
462
+ toPubkey: ata,
463
+ lamports: amount,
464
+ })
465
+ );
466
+ instructions.push(createSyncNativeInstruction(ata));
467
+ }
468
+ }
469
+
407
470
  function getBuyTokenAmountFromSolAmount(
408
471
  amount: bigint,
409
472
  bondingCurve: PumpFunBondingCurve,
@@ -457,22 +520,24 @@ export function buildPumpFunBuyInstructions(
457
520
  ): TransactionInstruction[] {
458
521
  const {
459
522
  payer,
523
+ inputMint = SOL_TOKEN_ACCOUNT,
460
524
  outputMint,
461
525
  inputAmount,
462
526
  slippageBasisPoints = BigInt(1000),
463
527
  fixedOutputAmount,
464
528
  createOutputMintAta = true,
465
529
  createInputMintAta = false,
530
+ closeInputMintAta = false,
466
531
  protocolParams,
467
532
  useExactSolAmount = true,
468
- usePumpFunV2 = false,
469
533
  } = params;
470
534
 
471
- if (usePumpFunV2 || protocolParams.useV2Ix || isUsablePubkey(protocolParams.quoteMint)) {
535
+ if (usesPumpFunV2Layout(protocolParams)) {
472
536
  return buildPumpFunBuyV2Instructions({
473
537
  ...params,
538
+ inputMint,
474
539
  createInputMintAta,
475
- usePumpFunV2: true,
540
+ closeInputMintAta,
476
541
  });
477
542
  }
478
543
 
@@ -518,12 +583,13 @@ export function buildPumpFunBuyInstructions(
518
583
  // Create ATA if needed
519
584
  if (createOutputMintAta) {
520
585
  instructions.push(
521
- createAssociatedTokenAccountInstruction(
586
+ createAssociatedTokenAccountIdempotentInstruction(
522
587
  payerPubkey,
523
588
  userTokenAccount,
524
589
  payerPubkey,
525
590
  outputMint,
526
- tokenProgramId
591
+ tokenProgramId,
592
+ SPL_ASSOCIATED_TOKEN_PROGRAM_ID
527
593
  )
528
594
  );
529
595
  }
@@ -534,9 +600,7 @@ export function buildPumpFunBuyInstructions(
534
600
  const bondingCurveV2 = getBondingCurveV2Pda(outputMint);
535
601
 
536
602
  // Track volume for cashback coins
537
- const trackVolume = bondingCurve.isCashbackCoin
538
- ? Buffer.from([1, 1])
539
- : Buffer.from([1, 0]);
603
+ const trackVolume = bondingCurve.isCashbackCoin ? 1 : 0;
540
604
 
541
605
  const buyTokenAmount = fixedOutputAmount
542
606
  ? fixedOutputAmount
@@ -545,23 +609,27 @@ export function buildPumpFunBuyInstructions(
545
609
 
546
610
  // Build instruction data
547
611
  let data: Buffer;
548
- if (useExactSolAmount) {
612
+ if (fixedOutputAmount !== undefined) {
613
+ data = Buffer.alloc(25);
614
+ PUMPFUN_BUY_DISCRIMINATOR.copy(data, 0);
615
+ data.writeBigUInt64LE(fixedOutputAmount, 8);
616
+ data.writeBigUInt64LE(inputAmount, 16);
617
+ data[24] = trackVolume;
618
+ } else if (useExactSolAmount) {
549
619
  // buy_exact_sol_in(spendable_sol_in: u64, min_tokens_out: u64, track_volume)
550
- const minTokensOut = fixedOutputAmount
551
- ? fixedOutputAmount
552
- : calculateWithSlippageSell(buyTokenAmount, slippageBasisPoints);
553
- data = Buffer.alloc(26);
620
+ const minTokensOut = calculateWithSlippageSell(buyTokenAmount, slippageBasisPoints);
621
+ data = Buffer.alloc(25);
554
622
  PUMPFUN_BUY_EXACT_SOL_IN_DISCRIMINATOR.copy(data, 0);
555
623
  data.writeBigUInt64LE(inputAmount, 8);
556
624
  data.writeBigUInt64LE(minTokensOut, 16);
557
- trackVolume.copy(data, 24);
625
+ data[24] = trackVolume;
558
626
  } else {
559
627
  // buy(token_amount: u64, max_sol_cost: u64, track_volume)
560
- data = Buffer.alloc(26);
628
+ data = Buffer.alloc(25);
561
629
  PUMPFUN_BUY_DISCRIMINATOR.copy(data, 0);
562
630
  data.writeBigUInt64LE(buyTokenAmount, 8);
563
631
  data.writeBigUInt64LE(maxSolCost, 16);
564
- trackVolume.copy(data, 24);
632
+ data[24] = trackVolume;
565
633
  }
566
634
 
567
635
  // Build accounts
@@ -607,20 +675,20 @@ export function buildPumpFunSellInstructions(
607
675
  const {
608
676
  payer,
609
677
  inputMint,
678
+ outputMint = SOL_TOKEN_ACCOUNT,
610
679
  inputAmount,
611
680
  slippageBasisPoints = BigInt(1000),
612
681
  fixedOutputAmount,
613
682
  createOutputMintAta = false,
614
683
  closeInputMintAta = false,
615
684
  protocolParams,
616
- usePumpFunV2 = false,
617
685
  } = params;
618
686
 
619
- if (usePumpFunV2 || protocolParams.useV2Ix || isUsablePubkey(protocolParams.quoteMint)) {
687
+ if (usesPumpFunV2Layout(protocolParams)) {
620
688
  return buildPumpFunSellV2Instructions({
621
689
  ...params,
690
+ outputMint,
622
691
  createOutputMintAta,
623
- usePumpFunV2: true,
624
692
  });
625
693
  }
626
694
 
@@ -740,12 +808,14 @@ export function buildPumpFunBuyV2Instructions(
740
808
  ): TransactionInstruction[] {
741
809
  const {
742
810
  payer,
811
+ inputMint = SOL_TOKEN_ACCOUNT,
743
812
  outputMint,
744
813
  inputAmount,
745
814
  slippageBasisPoints = BigInt(1000),
746
815
  fixedOutputAmount,
747
816
  createOutputMintAta = true,
748
817
  createInputMintAta = false,
818
+ closeInputMintAta = false,
749
819
  protocolParams,
750
820
  useExactSolAmount = true,
751
821
  } = params;
@@ -767,6 +837,7 @@ export function buildPumpFunBuyV2Instructions(
767
837
 
768
838
  const baseTokenProgram = effectivePumpMintTokenProgram(outputMint, protocolParams);
769
839
  const quoteMint = effectiveQuoteMint(protocolParams);
840
+ validateV2BuyQuoteMint(inputMint, quoteMint);
770
841
  const quoteTokenProgram = TOKEN_PROGRAM_ID;
771
842
 
772
843
  const associatedBaseBondingCurve = associatedTokenAddress(
@@ -824,37 +895,42 @@ export function buildPumpFunBuyV2Instructions(
824
895
  );
825
896
  }
826
897
 
827
- if (createInputMintAta) {
828
- instructions.push(
829
- createAssociatedTokenAccountIdempotentInstruction(
830
- payerPubkey,
831
- associatedQuoteUser,
832
- payerPubkey,
833
- quoteMint,
834
- quoteTokenProgram,
835
- SPL_ASSOCIATED_TOKEN_PROGRAM_ID
836
- )
837
- );
838
- }
839
-
840
898
  const buyTokenAmount = fixedOutputAmount
841
899
  ? fixedOutputAmount
842
900
  : getBuyTokenAmountFromSolAmount(inputAmount, bondingCurve, creator);
843
901
  const maxSolCost = calculateWithSlippageBuy(inputAmount, slippageBasisPoints);
844
902
  let data: Buffer;
845
- if (useExactSolAmount) {
846
- const minTokensOut = fixedOutputAmount
847
- ? fixedOutputAmount
848
- : calculateWithSlippageSell(buyTokenAmount, slippageBasisPoints);
903
+ let quoteAmountToFund: bigint;
904
+ if (fixedOutputAmount !== undefined) {
905
+ data = Buffer.alloc(24);
906
+ PUMPFUN_BUY_V2_DISCRIMINATOR.copy(data, 0);
907
+ data.writeBigUInt64LE(fixedOutputAmount, 8);
908
+ data.writeBigUInt64LE(inputAmount, 16);
909
+ quoteAmountToFund = inputAmount;
910
+ } else if (useExactSolAmount) {
911
+ const minTokensOut = calculateWithSlippageSell(buyTokenAmount, slippageBasisPoints);
849
912
  data = Buffer.alloc(24);
850
913
  PUMPFUN_BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR.copy(data, 0);
851
914
  data.writeBigUInt64LE(inputAmount, 8);
852
915
  data.writeBigUInt64LE(minTokensOut, 16);
916
+ quoteAmountToFund = inputAmount;
853
917
  } else {
854
918
  data = Buffer.alloc(24);
855
919
  PUMPFUN_BUY_V2_DISCRIMINATOR.copy(data, 0);
856
920
  data.writeBigUInt64LE(buyTokenAmount, 8);
857
921
  data.writeBigUInt64LE(maxSolCost, 16);
922
+ quoteAmountToFund = maxSolCost;
923
+ }
924
+
925
+ if (createInputMintAta) {
926
+ pushCreateOrWrapUserTokenAccount(
927
+ instructions,
928
+ payerPubkey,
929
+ associatedQuoteUser,
930
+ quoteMint,
931
+ quoteTokenProgram,
932
+ quoteAmountToFund
933
+ );
858
934
  }
859
935
 
860
936
  const keys: AccountMeta[] = [
@@ -895,6 +971,18 @@ export function buildPumpFunBuyV2Instructions(
895
971
  })
896
972
  );
897
973
 
974
+ if (closeInputMintAta && quoteMint.equals(NATIVE_MINT)) {
975
+ instructions.push(
976
+ createCloseAccountInstruction(
977
+ associatedQuoteUser,
978
+ payerPubkey,
979
+ payerPubkey,
980
+ [],
981
+ quoteTokenProgram
982
+ )
983
+ );
984
+ }
985
+
898
986
  return instructions;
899
987
  }
900
988
 
@@ -907,6 +995,7 @@ export function buildPumpFunSellV2Instructions(
907
995
  const {
908
996
  payer,
909
997
  inputMint,
998
+ outputMint = SOL_TOKEN_ACCOUNT,
910
999
  inputAmount,
911
1000
  slippageBasisPoints = BigInt(1000),
912
1001
  fixedOutputAmount,
@@ -932,6 +1021,7 @@ export function buildPumpFunSellV2Instructions(
932
1021
 
933
1022
  const baseTokenProgram = effectivePumpMintTokenProgram(inputMint, protocolParams);
934
1023
  const quoteMint = effectiveQuoteMint(protocolParams);
1024
+ validateV2SellQuoteMint(outputMint, quoteMint);
935
1025
  const quoteTokenProgram = TOKEN_PROGRAM_ID;
936
1026
 
937
1027
  const associatedBaseBondingCurve = associatedTokenAddress(