pump-trader 1.1.4 → 1.1.8

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 (3) hide show
  1. package/index.js +480 -236
  2. package/index.ts +1311 -247
  3. package/package.json +1 -1
package/index.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  AccountMeta,
7
7
  SystemProgram,
8
8
  ComputeBudgetProgram,
9
- Keypair
9
+ Keypair,
10
10
  } from "@solana/web3.js";
11
11
 
12
12
  import {
@@ -18,7 +18,7 @@ import {
18
18
  getTokenMetadata,
19
19
  TOKEN_2022_PROGRAM_ID,
20
20
  TOKEN_PROGRAM_ID,
21
- getMint
21
+ getMint,
22
22
  } from "@solana/spl-token";
23
23
 
24
24
  import BN from "bn.js";
@@ -66,6 +66,10 @@ interface BondingCurveState {
66
66
  complete: boolean;
67
67
  isMayhemMode?: boolean;
68
68
  isCashbackCoin?: boolean;
69
+ // V2 fields
70
+ quoteMint?: PublicKey;
71
+ virtualQuoteReserves?: bigint;
72
+ realQuoteReserves?: bigint;
69
73
  }
70
74
 
71
75
  interface BondingInfo {
@@ -128,33 +132,62 @@ const PROGRAM_IDS = {
128
132
  PUMP_AMM: new PublicKey("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"),
129
133
  METADATA: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
130
134
  FEE: new PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
131
- EVENT_AUTHORITY: new PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
135
+ EVENT_AUTHORITY: new PublicKey(
136
+ "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1",
137
+ ),
132
138
  };
133
139
 
134
140
  const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
135
141
 
136
142
  const SEEDS = {
137
143
  FEE_CONFIG: new Uint8Array([
138
- 1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
139
- 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176
144
+ 1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81,
145
+ 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176,
140
146
  ]),
141
147
  AMM_FEE_CONFIG: Buffer.from([
142
- 12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101,
143
- 244, 41, 141, 49, 86, 213, 113, 180, 212, 248, 9, 12, 24, 233, 168, 99
148
+ 12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101, 244,
149
+ 41, 141, 49, 86, 213, 113, 180, 212, 248, 9, 12, 24, 233, 168, 99,
144
150
  ]),
145
151
  GLOBAL: Buffer.from("global"),
146
- BONDING: Buffer.from("bonding-curve")
152
+ BONDING: Buffer.from("bonding-curve"),
147
153
  };
148
154
 
149
155
  const DISCRIMINATORS = {
150
156
  BUY: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]),
151
157
  SELL: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]),
152
- TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238])
158
+ TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238]),
159
+ // V2 instructions
160
+ BUY_V2: Buffer.from([184, 23, 238, 97, 103, 197, 211, 61]),
161
+ SELL_V2: Buffer.from([93, 246, 130, 60, 231, 233, 64, 178]),
162
+ BUY_EXACT_QUOTE_IN_V2: Buffer.from([194, 171, 28, 70, 104, 77, 91, 47]),
163
+ COLLECT_CREATOR_FEE_V2: Buffer.from([207, 17, 138, 242, 4, 34, 19, 56]),
153
164
  };
154
165
 
155
166
  const AMM_FEE_BPS = 100n;
156
167
  const BPS_DENOMINATOR = 10000n;
157
168
  const PUMP_NEW_FEE_RECIPIENTS = [
169
+ "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV",
170
+ "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ",
171
+ "7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX",
172
+ "9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz",
173
+ "AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY",
174
+ "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM",
175
+ "FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz",
176
+ "G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP",
177
+ ].map((value) => new PublicKey(value));
178
+
179
+ const PUMP_RESERVED_FEE_RECIPIENTS = [
180
+ "GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS",
181
+ "4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6",
182
+ "8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR",
183
+ "4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH",
184
+ "8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6",
185
+ "Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk",
186
+ "463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq",
187
+ "6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA",
188
+ ].map((value) => new PublicKey(value));
189
+
190
+ const PUMP_BUYBACK_FEE_RECIPIENTS = [
158
191
  "5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
159
192
  "9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
160
193
  "GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
@@ -162,13 +195,14 @@ const PUMP_NEW_FEE_RECIPIENTS = [
162
195
  "5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
163
196
  "EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
164
197
  "5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
165
- "A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
198
+ "A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW",
166
199
  ].map((value) => new PublicKey(value));
167
200
 
168
201
  /* ================= 工具函数 ================= */
169
202
 
170
203
  const u64 = (v: bigint | BN | number): Buffer => {
171
- const bn = typeof v === 'bigint' ? new BN(v.toString()) : new BN(v.toString());
204
+ const bn =
205
+ typeof v === "bigint" ? new BN(v.toString()) : new BN(v.toString());
172
206
  return bn.toArrayLike(Buffer, "le", 8);
173
207
  };
174
208
 
@@ -185,7 +219,9 @@ const readU32 = (buf: Buffer, offsetObj: { offset: number }): number => {
185
219
 
186
220
  const readString = (buf: Buffer, offsetObj: { offset: number }): string => {
187
221
  const len = readU32(buf, offsetObj);
188
- const str = buf.slice(offsetObj.offset, offsetObj.offset + len).toString("utf8");
222
+ const str = buf
223
+ .slice(offsetObj.offset, offsetObj.offset + len)
224
+ .toString("utf8");
189
225
  offsetObj.offset += len;
190
226
  return str;
191
227
  };
@@ -195,10 +231,14 @@ const readString = (buf: Buffer, offsetObj: { offset: number }): string => {
195
231
  function parseMetadataAccount(data: Buffer) {
196
232
  const offsetObj = { offset: 1 };
197
233
 
198
- const updateAuthority = new PublicKey(data.slice(offsetObj.offset, offsetObj.offset + 32));
234
+ const updateAuthority = new PublicKey(
235
+ data.slice(offsetObj.offset, offsetObj.offset + 32),
236
+ );
199
237
  offsetObj.offset += 32;
200
238
 
201
- const mint = new PublicKey(data.slice(offsetObj.offset, offsetObj.offset + 32));
239
+ const mint = new PublicKey(
240
+ data.slice(offsetObj.offset, offsetObj.offset + 32),
241
+ );
202
242
  offsetObj.offset += 32;
203
243
 
204
244
  const name = readString(data, offsetObj);
@@ -210,13 +250,13 @@ function parseMetadataAccount(data: Buffer) {
210
250
  mint: mint.toBase58(),
211
251
  name,
212
252
  symbol,
213
- uri
253
+ uri,
214
254
  };
215
255
  }
216
256
 
217
257
  function parsePoolKeys(data: Buffer) {
218
258
  if (!data || data.length < 280) {
219
- throw new Error('Invalid pool account data');
259
+ throw new Error("Invalid pool account data");
220
260
  }
221
261
 
222
262
  let offset = 8;
@@ -253,7 +293,8 @@ function parsePoolKeys(data: Buffer) {
253
293
 
254
294
  const isMayhemMode = data.readUInt8(offset) === 1;
255
295
  offset += 1;
256
- const isCashbackCoin = offset < data.length ? data.readUInt8(offset) === 1 : false;
296
+ const isCashbackCoin =
297
+ offset < data.length ? data.readUInt8(offset) === 1 : false;
257
298
 
258
299
  return {
259
300
  creator,
@@ -264,7 +305,7 @@ function parsePoolKeys(data: Buffer) {
264
305
  poolQuoteTokenAccount,
265
306
  coinCreator,
266
307
  isMayhemMode,
267
- isCashbackCoin
308
+ isCashbackCoin,
268
309
  };
269
310
  }
270
311
 
@@ -280,7 +321,10 @@ export class PumpTrader {
280
321
  constructor(rpc: string, privateKey: string) {
281
322
  this.connection = new Connection(rpc, "confirmed");
282
323
  this.wallet = Keypair.fromSecretKey(bs58.decode(privateKey));
283
- this.global = PublicKey.findProgramAddressSync([SEEDS.GLOBAL], PROGRAM_IDS.PUMP)[0];
324
+ this.global = PublicKey.findProgramAddressSync(
325
+ [SEEDS.GLOBAL],
326
+ PROGRAM_IDS.PUMP,
327
+ )[0];
284
328
  this.globalState = null;
285
329
  this.tokenProgramCache = new Map();
286
330
  }
@@ -300,29 +344,59 @@ export class PumpTrader {
300
344
 
301
345
  try {
302
346
  // 首先尝试获取 TOKEN_2022 的代币信息
303
- const mintData = await getMint(this.connection, mint, "confirmed", TOKEN_2022_PROGRAM_ID);
347
+ const mintData = await getMint(
348
+ this.connection,
349
+ mint,
350
+ "confirmed",
351
+ TOKEN_2022_PROGRAM_ID,
352
+ );
304
353
  const result: TokenProgramType = {
305
354
  type: "TOKEN_2022_PROGRAM_ID",
306
- programId: TOKEN_2022_PROGRAM_ID
355
+ programId: TOKEN_2022_PROGRAM_ID,
307
356
  };
308
357
  this.tokenProgramCache.set(tokenAddr, result);
309
358
  return result;
310
359
  } catch (e) {
311
360
  try {
312
361
  // 如果失败,尝试标准 TOKEN_PROGRAM_ID
313
- const mintData = await getMint(this.connection, mint, "confirmed", TOKEN_PROGRAM_ID);
362
+ const mintData = await getMint(
363
+ this.connection,
364
+ mint,
365
+ "confirmed",
366
+ TOKEN_PROGRAM_ID,
367
+ );
314
368
  const result: TokenProgramType = {
315
369
  type: "TOKEN_PROGRAM_ID",
316
- programId: TOKEN_PROGRAM_ID
370
+ programId: TOKEN_PROGRAM_ID,
317
371
  };
318
372
  this.tokenProgramCache.set(tokenAddr, result);
319
373
  return result;
320
374
  } catch (error) {
321
- throw new Error(`Failed to detect token program for ${tokenAddr}: ${error}`);
375
+ throw new Error(
376
+ `Failed to detect token program for ${tokenAddr}: ${error}`,
377
+ );
322
378
  }
323
379
  }
324
380
  }
325
381
 
382
+ async detectQuoteTokenProgram(quoteMint: PublicKey): Promise<PublicKey> {
383
+ const quoteAddr = quoteMint.toBase58();
384
+ if (this.tokenProgramCache.has(quoteAddr)) {
385
+ return this.tokenProgramCache.get(quoteAddr)!.programId;
386
+ }
387
+ try {
388
+ await getMint(
389
+ this.connection,
390
+ quoteMint,
391
+ "confirmed",
392
+ TOKEN_2022_PROGRAM_ID,
393
+ );
394
+ return TOKEN_2022_PROGRAM_ID;
395
+ } catch {
396
+ return TOKEN_PROGRAM_ID;
397
+ }
398
+ }
399
+
326
400
  /* ---------- 内盘/外盘检测 ---------- */
327
401
 
328
402
  /**
@@ -383,7 +457,7 @@ export class PumpTrader {
383
457
  initialVirtualSolReserves: readU64(),
384
458
  initialRealTokenReserves: readU64(),
385
459
  tokenTotalSupply: readU64(),
386
- feeBasisPoints: readU64()
460
+ feeBasisPoints: readU64(),
387
461
  };
388
462
 
389
463
  return this.globalState;
@@ -394,14 +468,14 @@ export class PumpTrader {
394
468
  getBondingPda(mint: PublicKey): PublicKey {
395
469
  return PublicKey.findProgramAddressSync(
396
470
  [SEEDS.BONDING, mint.toBuffer()],
397
- PROGRAM_IDS.PUMP
471
+ PROGRAM_IDS.PUMP,
398
472
  )[0];
399
473
  }
400
474
 
401
475
  deriveBondingCurveV2(mint: PublicKey): PublicKey {
402
476
  return PublicKey.findProgramAddressSync(
403
477
  [Buffer.from("bonding-curve-v2"), mint.toBuffer()],
404
- PROGRAM_IDS.PUMP
478
+ PROGRAM_IDS.PUMP,
405
479
  )[0];
406
480
  }
407
481
 
@@ -409,6 +483,25 @@ export class PumpTrader {
409
483
  return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
410
484
  }
411
485
 
486
+ private pickBuybackFeeRecipient(index = 0): PublicKey {
487
+ return PUMP_BUYBACK_FEE_RECIPIENTS[
488
+ index % PUMP_BUYBACK_FEE_RECIPIENTS.length
489
+ ];
490
+ }
491
+
492
+ private pickReservedFeeRecipient(index = 0): PublicKey {
493
+ return PUMP_RESERVED_FEE_RECIPIENTS[
494
+ index % PUMP_RESERVED_FEE_RECIPIENTS.length
495
+ ];
496
+ }
497
+
498
+ getSharingConfigPda(mint: PublicKey): PublicKey {
499
+ return PublicKey.findProgramAddressSync(
500
+ [Buffer.from("sharing-config"), mint.toBuffer()],
501
+ PROGRAM_IDS.FEE,
502
+ )[0];
503
+ }
504
+
412
505
  private buildBondingBuyKeys(args: {
413
506
  global: PublicKey;
414
507
  globalFeeRecipient: PublicKey;
@@ -435,7 +528,11 @@ export class PumpTrader {
435
528
  { pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
436
529
  { pubkey: args.mint, isSigner: false, isWritable: false },
437
530
  { pubkey: args.bonding, isSigner: false, isWritable: true },
438
- { pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
531
+ {
532
+ pubkey: args.associatedBondingCurve,
533
+ isSigner: false,
534
+ isWritable: true,
535
+ },
439
536
  { pubkey: args.userAta, isSigner: false, isWritable: true },
440
537
  { pubkey: args.wallet, isSigner: true, isWritable: true },
441
538
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
@@ -443,12 +540,16 @@ export class PumpTrader {
443
540
  { pubkey: args.creatorVault, isSigner: false, isWritable: true },
444
541
  { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
445
542
  { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
446
- { pubkey: args.globalVolumeAccumulator, isSigner: false, isWritable: false },
543
+ {
544
+ pubkey: args.globalVolumeAccumulator,
545
+ isSigner: false,
546
+ isWritable: false,
547
+ },
447
548
  { pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
448
549
  { pubkey: args.feeConfig, isSigner: false, isWritable: false },
449
550
  { pubkey: args.feeProgram, isSigner: false, isWritable: false },
450
551
  { pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
451
- { pubkey: args.feeRecipient, isSigner: false, isWritable: true }
552
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true },
452
553
  ];
453
554
  }
454
555
 
@@ -477,7 +578,11 @@ export class PumpTrader {
477
578
  { pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
478
579
  { pubkey: args.mint, isSigner: false, isWritable: false },
479
580
  { pubkey: args.bonding, isSigner: false, isWritable: true },
480
- { pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
581
+ {
582
+ pubkey: args.associatedBondingCurve,
583
+ isSigner: false,
584
+ isWritable: true,
585
+ },
481
586
  { pubkey: args.userAta, isSigner: false, isWritable: true },
482
587
  { pubkey: args.wallet, isSigner: true, isWritable: true },
483
588
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
@@ -486,16 +591,20 @@ export class PumpTrader {
486
591
  { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
487
592
  { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
488
593
  { pubkey: args.feeConfig, isSigner: false, isWritable: false },
489
- { pubkey: args.feeProgram, isSigner: false, isWritable: false }
594
+ { pubkey: args.feeProgram, isSigner: false, isWritable: false },
490
595
  ];
491
596
 
492
597
  if (args.isCashbackCoin) {
493
- keys.push({ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true });
598
+ keys.push({
599
+ pubkey: args.userVolumeAccumulator,
600
+ isSigner: false,
601
+ isWritable: true,
602
+ });
494
603
  }
495
604
 
496
605
  keys.push(
497
606
  { pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
498
- { pubkey: args.feeRecipient, isSigner: false, isWritable: true }
607
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true },
499
608
  );
500
609
 
501
610
  return keys;
@@ -521,6 +630,19 @@ export class PumpTrader {
521
630
  const creator = new PublicKey(data.slice(offset, offset + 32));
522
631
  offset += 32;
523
632
 
633
+ // V2 fields (115-byte bonding curve layout): quote_mint, virtual_quote_reserves, real_quote_reserves
634
+ // For legacy coins these may not be present, check remaining data length
635
+ if (offset + 32 <= data.length) {
636
+ state.quoteMint = new PublicKey(data.slice(offset, offset + 32));
637
+ offset += 32;
638
+ }
639
+ if (offset + 8 <= data.length) {
640
+ [state.virtualQuoteReserves, offset] = readU64(data, offset);
641
+ }
642
+ if (offset + 8 <= data.length) {
643
+ [state.realQuoteReserves, offset] = readU64(data, offset);
644
+ }
645
+
524
646
  state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
525
647
  offset += 1;
526
648
  state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
@@ -532,25 +654,29 @@ export class PumpTrader {
532
654
 
533
655
  calcBuy(solIn: bigint, state: BondingCurveState): bigint {
534
656
  const newVirtualSol = state.virtualSolReserves + solIn;
535
- const newVirtualToken = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualSol;
657
+ const newVirtualToken =
658
+ (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualSol;
536
659
  return state.virtualTokenReserves - newVirtualToken;
537
660
  }
538
661
 
539
662
  calcSell(tokenIn: bigint, state: BondingCurveState): bigint {
540
663
  const newVirtualToken = state.virtualTokenReserves + tokenIn;
541
- const newVirtualSol = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualToken;
664
+ const newVirtualSol =
665
+ (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualToken;
542
666
  return state.virtualSolReserves - newVirtualSol;
543
667
  }
544
668
 
545
669
  calculateAmmBuyOutput(quoteIn: bigint, reserves: PoolReserves): bigint {
546
- const quoteInAfterFee = (quoteIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
670
+ const quoteInAfterFee =
671
+ (quoteIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
547
672
  const numerator = reserves.baseAmount * quoteInAfterFee;
548
673
  const denominator = reserves.quoteAmount + quoteInAfterFee;
549
674
  return numerator / denominator;
550
675
  }
551
676
 
552
677
  calculateAmmSellOutput(baseIn: bigint, reserves: PoolReserves): bigint {
553
- const baseInAfterFee = (baseIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
678
+ const baseInAfterFee =
679
+ (baseIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
554
680
  const numerator = reserves.quoteAmount * baseInAfterFee;
555
681
  const denominator = reserves.baseAmount + baseInAfterFee;
556
682
  return numerator / denominator;
@@ -558,7 +684,9 @@ export class PumpTrader {
558
684
 
559
685
  /* ---------- 价格查询 ---------- */
560
686
 
561
- async getPriceAndStatus(tokenAddr: string): Promise<{ price: number; completed: boolean }> {
687
+ async getPriceAndStatus(
688
+ tokenAddr: string,
689
+ ): Promise<{ price: number; completed: boolean }> {
562
690
  const mint = new PublicKey(tokenAddr);
563
691
  const { state } = await this.loadBonding(mint);
564
692
 
@@ -576,13 +704,19 @@ export class PumpTrader {
576
704
  async getAmmPrice(mint: PublicKey): Promise<number> {
577
705
  const [poolCreator] = PublicKey.findProgramAddressSync(
578
706
  [Buffer.from("pool-authority"), mint.toBuffer()],
579
- PROGRAM_IDS.PUMP
707
+ PROGRAM_IDS.PUMP,
580
708
  );
581
709
 
582
710
  const indexBuffer = new BN(0).toArrayLike(Buffer, "le", 2);
583
711
  const [pool] = PublicKey.findProgramAddressSync(
584
- [Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(), SOL_MINT.toBuffer()],
585
- PROGRAM_IDS.PUMP_AMM
712
+ [
713
+ Buffer.from("pool"),
714
+ indexBuffer,
715
+ poolCreator.toBuffer(),
716
+ mint.toBuffer(),
717
+ SOL_MINT.toBuffer(),
718
+ ],
719
+ PROGRAM_IDS.PUMP_AMM,
586
720
  );
587
721
 
588
722
  const acc = await this.connection.getAccountInfo(pool);
@@ -591,7 +725,7 @@ export class PumpTrader {
591
725
  const poolKeys = parsePoolKeys(acc.data);
592
726
  const [baseInfo, quoteInfo] = await Promise.all([
593
727
  this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
594
- this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
728
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
595
729
  ]);
596
730
 
597
731
  return quoteInfo.value.uiAmount! / baseInfo.value.uiAmount!;
@@ -604,15 +738,26 @@ export class PumpTrader {
604
738
  * @param tokenAddr - 代币地址(可选),如果不传则返回所有代币
605
739
  * @returns 如果传入地址则返回该代币的余额数字,否则返回所有代币的详细信息
606
740
  */
607
- async tokenBalance(tokenAddr?: string): Promise<number | Array<{ mint: string; amount: number; decimals: number; uiAmount: number }>> {
741
+ async tokenBalance(tokenAddr?: string): Promise<
742
+ | number
743
+ | Array<{
744
+ mint: string;
745
+ amount: number;
746
+ decimals: number;
747
+ uiAmount: number;
748
+ }>
749
+ > {
608
750
  if (tokenAddr) {
609
751
  // 查询单个代币
610
752
  const mint = new PublicKey(tokenAddr);
611
753
  const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
612
754
  this.wallet.publicKey,
613
- { mint }
755
+ { mint },
756
+ );
757
+ return (
758
+ tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount ||
759
+ 0
614
760
  );
615
- return tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount || 0;
616
761
  } else {
617
762
  // 查询所有代币
618
763
  return this.getAllTokenBalances();
@@ -623,16 +768,18 @@ export class PumpTrader {
623
768
  * 获取账户所有代币余额(仅显示余额 > 0 的)
624
769
  * @returns 代币信息数组,包含mint地址、余额等信息
625
770
  */
626
- async getAllTokenBalances(): Promise<Array<{ mint: string; amount: number; decimals: number; uiAmount: number }>> {
771
+ async getAllTokenBalances(): Promise<
772
+ Array<{ mint: string; amount: number; decimals: number; uiAmount: number }>
773
+ > {
627
774
  const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
628
775
  this.wallet.publicKey,
629
- { programId: TOKEN_PROGRAM_ID }
776
+ { programId: TOKEN_PROGRAM_ID },
630
777
  );
631
778
 
632
779
  const balances = tokenAccounts.value
633
780
  .map((account) => {
634
781
  const parsed = account.account.data.parsed;
635
- if (parsed.type !== 'account') return null;
782
+ if (parsed.type !== "account") return null;
636
783
 
637
784
  const tokenAmount = parsed.info.tokenAmount;
638
785
  if (Number(tokenAmount.amount) === 0) return null; // 跳过余额为0的
@@ -641,21 +788,27 @@ export class PumpTrader {
641
788
  mint: parsed.info.mint,
642
789
  amount: BigInt(tokenAmount.amount),
643
790
  decimals: tokenAmount.decimals,
644
- uiAmount: tokenAmount.uiAmount || 0
791
+ uiAmount: tokenAmount.uiAmount || 0,
645
792
  };
646
793
  })
647
- .filter((item) => item !== null) as Array<{ mint: string; amount: bigint; decimals: number; uiAmount: number }>;
794
+ .filter((item) => item !== null) as Array<{
795
+ mint: string;
796
+ amount: bigint;
797
+ decimals: number;
798
+ uiAmount: number;
799
+ }>;
648
800
 
649
801
  // 同时查询 TOKEN_2022_PROGRAM_ID
650
- const token2022Accounts = await this.connection.getParsedTokenAccountsByOwner(
651
- this.wallet.publicKey,
652
- { programId: TOKEN_2022_PROGRAM_ID }
653
- );
802
+ const token2022Accounts =
803
+ await this.connection.getParsedTokenAccountsByOwner(
804
+ this.wallet.publicKey,
805
+ { programId: TOKEN_2022_PROGRAM_ID },
806
+ );
654
807
 
655
808
  const token2022Balances = token2022Accounts.value
656
809
  .map((account) => {
657
810
  const parsed = account.account.data.parsed;
658
- if (parsed.type !== 'account') return null;
811
+ if (parsed.type !== "account") return null;
659
812
 
660
813
  const tokenAmount = parsed.info.tokenAmount;
661
814
  if (Number(tokenAmount.amount) === 0) return null; // 跳过余额为0的
@@ -664,10 +817,15 @@ export class PumpTrader {
664
817
  mint: parsed.info.mint,
665
818
  amount: BigInt(tokenAmount.amount),
666
819
  decimals: tokenAmount.decimals,
667
- uiAmount: tokenAmount.uiAmount || 0
820
+ uiAmount: tokenAmount.uiAmount || 0,
668
821
  };
669
822
  })
670
- .filter((item) => item !== null) as Array<{ mint: string; amount: bigint; decimals: number; uiAmount: number }>;
823
+ .filter((item) => item !== null) as Array<{
824
+ mint: string;
825
+ amount: bigint;
826
+ decimals: number;
827
+ uiAmount: number;
828
+ }>;
671
829
 
672
830
  // 合并并去重
673
831
  const allBalances = [...balances, ...token2022Balances];
@@ -682,7 +840,7 @@ export class PumpTrader {
682
840
  mint: b.mint,
683
841
  amount: Number(b.amount),
684
842
  decimals: b.decimals,
685
- uiAmount: b.uiAmount
843
+ uiAmount: b.uiAmount,
686
844
  }));
687
845
 
688
846
  return uniqueBalances;
@@ -695,14 +853,18 @@ export class PumpTrader {
695
853
 
696
854
  /* ---------- ATA 管理 ---------- */
697
855
 
698
- async ensureAta(tx: Transaction, mint: PublicKey, tokenProgram?: PublicKey): Promise<PublicKey> {
856
+ async ensureAta(
857
+ tx: Transaction,
858
+ mint: PublicKey,
859
+ tokenProgram?: PublicKey,
860
+ ): Promise<PublicKey> {
699
861
  const program = tokenProgram || TOKEN_2022_PROGRAM_ID;
700
862
  const ata = getAssociatedTokenAddressSync(
701
863
  mint,
702
864
  this.wallet.publicKey,
703
865
  false,
704
866
  program,
705
- ASSOCIATED_TOKEN_PROGRAM_ID
867
+ ASSOCIATED_TOKEN_PROGRAM_ID,
706
868
  );
707
869
 
708
870
  const acc = await this.connection.getAccountInfo(ata);
@@ -714,8 +876,8 @@ export class PumpTrader {
714
876
  this.wallet.publicKey,
715
877
  mint,
716
878
  program,
717
- ASSOCIATED_TOKEN_PROGRAM_ID
718
- )
879
+ ASSOCIATED_TOKEN_PROGRAM_ID,
880
+ ),
719
881
  );
720
882
  }
721
883
 
@@ -726,14 +888,14 @@ export class PumpTrader {
726
888
  tx: Transaction,
727
889
  owner: PublicKey,
728
890
  mode: "buy" | "sell",
729
- lamports?: bigint
891
+ lamports?: bigint,
730
892
  ): Promise<PublicKey> {
731
893
  const wsolAta = getAssociatedTokenAddressSync(
732
894
  SOL_MINT,
733
895
  owner,
734
896
  false,
735
897
  TOKEN_PROGRAM_ID,
736
- ASSOCIATED_TOKEN_PROGRAM_ID
898
+ ASSOCIATED_TOKEN_PROGRAM_ID,
737
899
  );
738
900
 
739
901
  const acc = await this.connection.getAccountInfo(wsolAta);
@@ -746,18 +908,18 @@ export class PumpTrader {
746
908
  owner,
747
909
  SOL_MINT,
748
910
  TOKEN_PROGRAM_ID,
749
- ASSOCIATED_TOKEN_PROGRAM_ID
750
- )
911
+ ASSOCIATED_TOKEN_PROGRAM_ID,
912
+ ),
751
913
  );
752
914
  }
753
915
 
754
- if (mode === 'buy' && lamports) {
916
+ if (mode === "buy" && lamports) {
755
917
  tx.add(
756
918
  SystemProgram.transfer({
757
919
  fromPubkey: owner,
758
920
  toPubkey: wsolAta,
759
- lamports: Number(lamports)
760
- })
921
+ lamports: Number(lamports),
922
+ }),
761
923
  );
762
924
  tx.add(createSyncNativeInstruction(wsolAta));
763
925
  }
@@ -771,7 +933,9 @@ export class PumpTrader {
771
933
  if (!priorityOpt?.enableRandom || !priorityOpt.randomRange) {
772
934
  return priorityOpt.base;
773
935
  }
774
- return priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange);
936
+ return (
937
+ priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange)
938
+ );
775
939
  }
776
940
 
777
941
  calcSlippage({ tradeSize, reserve, slippageOpt }: any): number {
@@ -819,31 +983,57 @@ export class PumpTrader {
819
983
 
820
984
  /**
821
985
  * 自动判断内盘/外盘并执行买入
986
+ * @param useV2 - use buy_v2 instruction (supports USDC quote) instead of legacy buy
987
+ * @param quoteMint - quote mint for V2 (SOL_MINT for SOL-paired, or USDC mint for USDC-paired)
822
988
  */
823
- async autoBuy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
989
+ async autoBuy(
990
+ tokenAddr: string,
991
+ totalSolIn: bigint,
992
+ tradeOpt: TradeOptions,
993
+ useV2: boolean = false,
994
+ quoteMint: PublicKey = SOL_MINT,
995
+ ): Promise<TradeResult> {
824
996
  const mode = await this.getTradeMode(tokenAddr);
825
997
  if (mode === "bonding") {
998
+ if (useV2) {
999
+ return this.buyV2(tokenAddr, totalSolIn, tradeOpt, quoteMint);
1000
+ }
826
1001
  return this.buy(tokenAddr, totalSolIn, tradeOpt);
827
1002
  } else {
828
- return this.ammBuy(tokenAddr, totalSolIn, tradeOpt);
1003
+ return this.ammBuy(tokenAddr, totalSolIn, tradeOpt, quoteMint);
829
1004
  }
830
1005
  }
831
1006
 
832
1007
  /**
833
1008
  * 自动判断内盘/外盘并执行卖出
1009
+ * @param useV2 - use sell_v2 instruction (supports USDC quote) instead of legacy sell
1010
+ * @param quoteMint - quote mint for V2 (SOL_MINT for SOL-paired, or USDC mint for USDC-paired)
834
1011
  */
835
- async autoSell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
1012
+ async autoSell(
1013
+ tokenAddr: string,
1014
+ totalTokenIn: bigint,
1015
+ tradeOpt: TradeOptions,
1016
+ useV2: boolean = false,
1017
+ quoteMint: PublicKey = SOL_MINT,
1018
+ ): Promise<TradeResult> {
836
1019
  const mode = await this.getTradeMode(tokenAddr);
837
1020
  if (mode === "bonding") {
1021
+ if (useV2) {
1022
+ return this.sellV2(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
1023
+ }
838
1024
  return this.sell(tokenAddr, totalTokenIn, tradeOpt);
839
1025
  } else {
840
- return this.ammSell(tokenAddr, totalTokenIn, tradeOpt);
1026
+ return this.ammSell(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
841
1027
  }
842
1028
  }
843
1029
 
844
1030
  /* ---------- 内盘交易 ---------- */
845
1031
 
846
- async buy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
1032
+ async buy(
1033
+ tokenAddr: string,
1034
+ totalSolIn: bigint,
1035
+ tradeOpt: TradeOptions,
1036
+ ): Promise<TradeResult> {
847
1037
  const mint = new PublicKey(tokenAddr);
848
1038
  const tokenProgram = await this.detectTokenProgram(tokenAddr);
849
1039
  const bondingCurveV2 = this.deriveBondingCurveV2(mint);
@@ -862,27 +1052,30 @@ export class PumpTrader {
862
1052
  bonding,
863
1053
  true,
864
1054
  tokenProgram.programId,
865
- ASSOCIATED_TOKEN_PROGRAM_ID
1055
+ ASSOCIATED_TOKEN_PROGRAM_ID,
866
1056
  );
867
1057
 
868
1058
  const [creatorVault] = PublicKey.findProgramAddressSync(
869
1059
  [Buffer.from("creator-vault"), creator.toBuffer()],
870
- PROGRAM_IDS.PUMP
1060
+ PROGRAM_IDS.PUMP,
871
1061
  );
872
1062
 
873
1063
  const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
874
1064
  [Buffer.from("global_volume_accumulator")],
875
- PROGRAM_IDS.PUMP
1065
+ PROGRAM_IDS.PUMP,
876
1066
  );
877
1067
 
878
1068
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
879
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
880
- PROGRAM_IDS.PUMP
1069
+ [
1070
+ Buffer.from("user_volume_accumulator"),
1071
+ this.wallet.publicKey.toBuffer(),
1072
+ ],
1073
+ PROGRAM_IDS.PUMP,
881
1074
  );
882
1075
 
883
1076
  const [feeConfig] = PublicKey.findProgramAddressSync(
884
1077
  [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
885
- PROGRAM_IDS.FEE
1078
+ PROGRAM_IDS.FEE,
886
1079
  );
887
1080
  const feeRecipient = this.pickFeeRecipient();
888
1081
 
@@ -893,14 +1086,14 @@ export class PumpTrader {
893
1086
  const slippageBps = this.calcSlippage({
894
1087
  tradeSize: solIn,
895
1088
  reserve: state.virtualSolReserves,
896
- slippageOpt: tradeOpt.slippage
1089
+ slippageOpt: tradeOpt.slippage,
897
1090
  });
898
1091
  const maxSol = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
899
1092
  const priority = this.genPriority(tradeOpt.priority);
900
1093
 
901
1094
  const tx = new Transaction().add(
902
1095
  ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
903
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1096
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
904
1097
  );
905
1098
 
906
1099
  const userAta = await this.ensureAta(tx, mint, tokenProgram.programId);
@@ -925,32 +1118,39 @@ export class PumpTrader {
925
1118
  feeProgram: PROGRAM_IDS.FEE,
926
1119
  bondingCurveV2,
927
1120
  feeRecipient,
928
- tokenProgramId: tokenProgram.programId
1121
+ tokenProgramId: tokenProgram.programId,
929
1122
  }),
930
- data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
931
- })
1123
+ data: Buffer.concat([
1124
+ DISCRIMINATORS.BUY,
1125
+ u64(tokenOut),
1126
+ u64(maxSol),
1127
+ ]),
1128
+ }),
932
1129
  );
933
1130
 
934
1131
  const { blockhash, lastValidBlockHeight } =
935
- await this.connection.getLatestBlockhash('finalized');
1132
+ await this.connection.getLatestBlockhash("finalized");
936
1133
  tx.recentBlockhash = blockhash;
937
1134
  tx.feePayer = this.wallet.publicKey;
938
1135
  tx.sign(this.wallet);
939
1136
 
940
- const signature = await this.connection.sendRawTransaction(tx.serialize(), {
941
- skipPreflight: false,
942
- maxRetries: 2
943
- });
1137
+ const signature = await this.connection.sendRawTransaction(
1138
+ tx.serialize(),
1139
+ {
1140
+ skipPreflight: false,
1141
+ maxRetries: 2,
1142
+ },
1143
+ );
944
1144
 
945
1145
  pendingTransactions.push({
946
1146
  signature,
947
1147
  lastValidBlockHeight,
948
- index: i
1148
+ index: i,
949
1149
  });
950
1150
  } catch (e) {
951
1151
  failedTransactions.push({
952
1152
  index: i,
953
- error: (e as Error).message
1153
+ error: (e as Error).message,
954
1154
  });
955
1155
  }
956
1156
  }
@@ -958,7 +1158,11 @@ export class PumpTrader {
958
1158
  return { pendingTransactions, failedTransactions };
959
1159
  }
960
1160
 
961
- async sell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
1161
+ async sell(
1162
+ tokenAddr: string,
1163
+ totalTokenIn: bigint,
1164
+ tradeOpt: TradeOptions,
1165
+ ): Promise<TradeResult> {
962
1166
  const mint = new PublicKey(tokenAddr);
963
1167
  const tokenProgram = await this.detectTokenProgram(tokenAddr);
964
1168
  const bondingCurveV2 = this.deriveBondingCurveV2(mint);
@@ -969,12 +1173,15 @@ export class PumpTrader {
969
1173
  if (state.complete) throw new Error("Bonding curve already completed");
970
1174
 
971
1175
  const totalSolOut = this.calcSell(totalTokenIn, state);
972
- const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
973
- ? [totalTokenIn]
974
- : this.splitIntoN(
975
- totalTokenIn,
976
- Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx)
977
- );
1176
+ const tokenChunks =
1177
+ totalSolOut <= tradeOpt.maxSolPerTx
1178
+ ? [totalTokenIn]
1179
+ : this.splitIntoN(
1180
+ totalTokenIn,
1181
+ Number(
1182
+ (totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx,
1183
+ ),
1184
+ );
978
1185
 
979
1186
  const pendingTransactions: PendingTransaction[] = [];
980
1187
  const failedTransactions: FailedTransaction[] = [];
@@ -984,7 +1191,7 @@ export class PumpTrader {
984
1191
  bonding,
985
1192
  true,
986
1193
  tokenProgram.programId,
987
- ASSOCIATED_TOKEN_PROGRAM_ID
1194
+ ASSOCIATED_TOKEN_PROGRAM_ID,
988
1195
  );
989
1196
 
990
1197
  const userAta = getAssociatedTokenAddressSync(
@@ -992,22 +1199,25 @@ export class PumpTrader {
992
1199
  this.wallet.publicKey,
993
1200
  false,
994
1201
  tokenProgram.programId,
995
- ASSOCIATED_TOKEN_PROGRAM_ID
1202
+ ASSOCIATED_TOKEN_PROGRAM_ID,
996
1203
  );
997
1204
 
998
1205
  const [creatorVault] = PublicKey.findProgramAddressSync(
999
1206
  [Buffer.from("creator-vault"), creator.toBuffer()],
1000
- PROGRAM_IDS.PUMP
1207
+ PROGRAM_IDS.PUMP,
1001
1208
  );
1002
1209
 
1003
1210
  const [feeConfig] = PublicKey.findProgramAddressSync(
1004
1211
  [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
1005
- PROGRAM_IDS.FEE
1212
+ PROGRAM_IDS.FEE,
1006
1213
  );
1007
1214
 
1008
1215
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1009
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
1010
- PROGRAM_IDS.PUMP
1216
+ [
1217
+ Buffer.from("user_volume_accumulator"),
1218
+ this.wallet.publicKey.toBuffer(),
1219
+ ],
1220
+ PROGRAM_IDS.PUMP,
1011
1221
  );
1012
1222
  const feeRecipient = this.pickFeeRecipient();
1013
1223
 
@@ -1018,14 +1228,14 @@ export class PumpTrader {
1018
1228
  const slippageBps = this.calcSlippage({
1019
1229
  tradeSize: tokenIn,
1020
1230
  reserve: state.virtualTokenReserves,
1021
- slippageOpt: tradeOpt.slippage
1231
+ slippageOpt: tradeOpt.slippage,
1022
1232
  });
1023
1233
  const minSol = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
1024
1234
  const priority = this.genPriority(tradeOpt.priority);
1025
1235
 
1026
1236
  const tx = new Transaction().add(
1027
1237
  ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
1028
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1238
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
1029
1239
  );
1030
1240
 
1031
1241
  tx.add(
@@ -1048,14 +1258,14 @@ export class PumpTrader {
1048
1258
  feeRecipient,
1049
1259
  isCashbackCoin: !!state.isCashbackCoin,
1050
1260
  userVolumeAccumulator,
1051
- tokenProgramId: tokenProgram.programId
1261
+ tokenProgramId: tokenProgram.programId,
1052
1262
  }),
1053
1263
  data: Buffer.concat([
1054
1264
  DISCRIMINATORS.SELL,
1055
1265
  u64(tokenIn),
1056
- u64(minSol > 0n ? minSol : 1n)
1057
- ])
1058
- })
1266
+ u64(minSol > 0n ? minSol : 1n),
1267
+ ]),
1268
+ }),
1059
1269
  );
1060
1270
 
1061
1271
  const { blockhash, lastValidBlockHeight } =
@@ -1064,16 +1274,18 @@ export class PumpTrader {
1064
1274
  tx.feePayer = this.wallet.publicKey;
1065
1275
  tx.sign(this.wallet);
1066
1276
 
1067
- const signature = await this.connection.sendRawTransaction(tx.serialize());
1277
+ const signature = await this.connection.sendRawTransaction(
1278
+ tx.serialize(),
1279
+ );
1068
1280
  pendingTransactions.push({
1069
1281
  signature,
1070
1282
  lastValidBlockHeight,
1071
- index: i
1283
+ index: i,
1072
1284
  });
1073
1285
  } catch (e) {
1074
1286
  failedTransactions.push({
1075
1287
  index: i,
1076
- error: (e as Error).message
1288
+ error: (e as Error).message,
1077
1289
  });
1078
1290
  }
1079
1291
  }
@@ -1083,12 +1295,21 @@ export class PumpTrader {
1083
1295
 
1084
1296
  /* ---------- 外盘交易 ---------- */
1085
1297
 
1086
- async ammBuy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
1298
+ async ammBuy(
1299
+ tokenAddr: string,
1300
+ totalSolIn: bigint,
1301
+ tradeOpt: TradeOptions,
1302
+ quoteMint: PublicKey = SOL_MINT,
1303
+ ): Promise<TradeResult> {
1087
1304
  const mint = new PublicKey(tokenAddr);
1088
- const poolInfo = await this.getAmmPoolInfo(mint);
1305
+ const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
1089
1306
  const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
1090
1307
  const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
1091
1308
  const tokenProgram = await this.detectTokenProgram(tokenAddr);
1309
+ const isSolQuote = quoteMint.equals(SOL_MINT);
1310
+ const quoteTokenProgramId = isSolQuote
1311
+ ? TOKEN_PROGRAM_ID
1312
+ : await this.detectQuoteTokenProgram(quoteMint);
1092
1313
  const pendingTransactions: PendingTransaction[] = [];
1093
1314
  const failedTransactions: FailedTransaction[] = [];
1094
1315
 
@@ -1099,23 +1320,29 @@ export class PumpTrader {
1099
1320
  const slippageBps = this.calcSlippage({
1100
1321
  tradeSize: solIn,
1101
1322
  reserve: reserves.quoteAmount,
1102
- slippageOpt: tradeOpt.slippage
1323
+ slippageOpt: tradeOpt.slippage,
1103
1324
  });
1104
1325
  const maxQuoteIn = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
1105
1326
  const priority = this.genPriority(tradeOpt.priority);
1106
1327
 
1107
1328
  const tx = new Transaction().add(
1108
1329
  ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
1109
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1330
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
1110
1331
  );
1111
1332
 
1112
- const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint, tokenProgram.programId);
1113
- const userQuoteAta = await this.ensureWSOLAta(
1333
+ const userBaseAta = await this.ensureAta(
1114
1334
  tx,
1115
- this.wallet.publicKey,
1116
- "buy",
1117
- maxQuoteIn
1335
+ poolInfo.poolKeys.baseMint,
1336
+ tokenProgram.programId,
1118
1337
  );
1338
+ const userQuoteAta = isSolQuote
1339
+ ? await this.ensureWSOLAta(
1340
+ tx,
1341
+ this.wallet.publicKey,
1342
+ "buy",
1343
+ maxQuoteIn,
1344
+ )
1345
+ : await this.ensureAta(tx, quoteMint, quoteTokenProgramId);
1119
1346
 
1120
1347
  const buyIx = this.createAmmBuyInstruction(
1121
1348
  poolInfo,
@@ -1123,38 +1350,43 @@ export class PumpTrader {
1123
1350
  userQuoteAta,
1124
1351
  baseAmountOut,
1125
1352
  maxQuoteIn,
1126
- tokenProgram.programId
1353
+ tokenProgram.programId,
1127
1354
  );
1128
1355
 
1129
1356
  tx.add(buyIx);
1130
- tx.add(
1131
- createCloseAccountInstruction(
1132
- userQuoteAta,
1133
- this.wallet.publicKey,
1134
- this.wallet.publicKey
1135
- )
1136
- );
1357
+ if (isSolQuote) {
1358
+ tx.add(
1359
+ createCloseAccountInstruction(
1360
+ userQuoteAta,
1361
+ this.wallet.publicKey,
1362
+ this.wallet.publicKey,
1363
+ ),
1364
+ );
1365
+ }
1137
1366
 
1138
1367
  const { blockhash, lastValidBlockHeight } =
1139
- await this.connection.getLatestBlockhash('finalized');
1368
+ await this.connection.getLatestBlockhash("finalized");
1140
1369
  tx.recentBlockhash = blockhash;
1141
1370
  tx.feePayer = this.wallet.publicKey;
1142
1371
  tx.sign(this.wallet);
1143
1372
 
1144
- const signature = await this.connection.sendRawTransaction(tx.serialize(), {
1145
- skipPreflight: false,
1146
- maxRetries: 2
1147
- });
1373
+ const signature = await this.connection.sendRawTransaction(
1374
+ tx.serialize(),
1375
+ {
1376
+ skipPreflight: false,
1377
+ maxRetries: 2,
1378
+ },
1379
+ );
1148
1380
 
1149
1381
  pendingTransactions.push({
1150
1382
  signature,
1151
1383
  lastValidBlockHeight,
1152
- index: i
1384
+ index: i,
1153
1385
  });
1154
1386
  } catch (e) {
1155
1387
  failedTransactions.push({
1156
1388
  index: i,
1157
- error: (e as Error).message
1389
+ error: (e as Error).message,
1158
1390
  });
1159
1391
  }
1160
1392
  }
@@ -1162,18 +1394,30 @@ export class PumpTrader {
1162
1394
  return { pendingTransactions, failedTransactions };
1163
1395
  }
1164
1396
 
1165
- async ammSell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
1397
+ async ammSell(
1398
+ tokenAddr: string,
1399
+ totalTokenIn: bigint,
1400
+ tradeOpt: TradeOptions,
1401
+ quoteMint: PublicKey = SOL_MINT,
1402
+ ): Promise<TradeResult> {
1166
1403
  const mint = new PublicKey(tokenAddr);
1167
- const poolInfo = await this.getAmmPoolInfo(mint);
1404
+ const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
1168
1405
  const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
1169
1406
  const totalSolOut = this.calculateAmmSellOutput(totalTokenIn, reserves);
1170
1407
  const tokenProgram = await this.detectTokenProgram(tokenAddr);
1171
- const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
1172
- ? [totalTokenIn]
1173
- : this.splitIntoN(
1174
- totalTokenIn,
1175
- Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx)
1176
- );
1408
+ const isSolQuote = quoteMint.equals(SOL_MINT);
1409
+ const quoteTokenProgramId = isSolQuote
1410
+ ? TOKEN_PROGRAM_ID
1411
+ : await this.detectQuoteTokenProgram(quoteMint);
1412
+ const tokenChunks =
1413
+ totalSolOut <= tradeOpt.maxSolPerTx
1414
+ ? [totalTokenIn]
1415
+ : this.splitIntoN(
1416
+ totalTokenIn,
1417
+ Number(
1418
+ (totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx,
1419
+ ),
1420
+ );
1177
1421
 
1178
1422
  const pendingTransactions: PendingTransaction[] = [];
1179
1423
  const failedTransactions: FailedTransaction[] = [];
@@ -1185,18 +1429,24 @@ export class PumpTrader {
1185
1429
  const slippageBps = this.calcSlippage({
1186
1430
  tradeSize: tokenIn,
1187
1431
  reserve: reserves.baseAmount,
1188
- slippageOpt: tradeOpt.slippage
1432
+ slippageOpt: tradeOpt.slippage,
1189
1433
  });
1190
1434
  const minQuoteOut = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
1191
1435
  const priority = this.genPriority(tradeOpt.priority);
1192
1436
 
1193
1437
  const tx = new Transaction().add(
1194
1438
  ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
1195
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1439
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
1196
1440
  );
1197
1441
 
1198
- const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint, tokenProgram.programId);
1199
- const userQuoteAta = await this.ensureWSOLAta(tx, this.wallet.publicKey, "sell");
1442
+ const userBaseAta = await this.ensureAta(
1443
+ tx,
1444
+ poolInfo.poolKeys.baseMint,
1445
+ tokenProgram.programId,
1446
+ );
1447
+ const userQuoteAta = isSolQuote
1448
+ ? await this.ensureWSOLAta(tx, this.wallet.publicKey, "sell")
1449
+ : await this.ensureAta(tx, quoteMint, quoteTokenProgramId);
1200
1450
 
1201
1451
  const sellIx = this.createAmmSellInstruction(
1202
1452
  poolInfo,
@@ -1204,38 +1454,43 @@ export class PumpTrader {
1204
1454
  userQuoteAta,
1205
1455
  tokenIn,
1206
1456
  minQuoteOut,
1207
- tokenProgram.programId
1457
+ tokenProgram.programId,
1208
1458
  );
1209
1459
 
1210
1460
  tx.add(sellIx);
1211
- tx.add(
1212
- createCloseAccountInstruction(
1213
- userQuoteAta,
1214
- this.wallet.publicKey,
1215
- this.wallet.publicKey
1216
- )
1217
- );
1461
+ if (isSolQuote) {
1462
+ tx.add(
1463
+ createCloseAccountInstruction(
1464
+ userQuoteAta,
1465
+ this.wallet.publicKey,
1466
+ this.wallet.publicKey,
1467
+ ),
1468
+ );
1469
+ }
1218
1470
 
1219
1471
  const { blockhash, lastValidBlockHeight } =
1220
- await this.connection.getLatestBlockhash('finalized');
1472
+ await this.connection.getLatestBlockhash("finalized");
1221
1473
  tx.recentBlockhash = blockhash;
1222
1474
  tx.feePayer = this.wallet.publicKey;
1223
1475
  tx.sign(this.wallet);
1224
1476
 
1225
- const signature = await this.connection.sendRawTransaction(tx.serialize(), {
1226
- skipPreflight: false,
1227
- maxRetries: 2
1228
- });
1477
+ const signature = await this.connection.sendRawTransaction(
1478
+ tx.serialize(),
1479
+ {
1480
+ skipPreflight: false,
1481
+ maxRetries: 2,
1482
+ },
1483
+ );
1229
1484
 
1230
1485
  pendingTransactions.push({
1231
1486
  signature,
1232
1487
  lastValidBlockHeight,
1233
- index: i
1488
+ index: i,
1234
1489
  });
1235
1490
  } catch (e) {
1236
1491
  failedTransactions.push({
1237
1492
  index: i,
1238
- error: (e as Error).message
1493
+ error: (e as Error).message,
1239
1494
  });
1240
1495
  }
1241
1496
  }
@@ -1245,10 +1500,13 @@ export class PumpTrader {
1245
1500
 
1246
1501
  /* ---------- AMM 池信息 ---------- */
1247
1502
 
1248
- async getAmmPoolInfo(mint: PublicKey): Promise<PoolInfo> {
1503
+ async getAmmPoolInfo(
1504
+ mint: PublicKey,
1505
+ quoteMint: PublicKey = SOL_MINT,
1506
+ ): Promise<PoolInfo> {
1249
1507
  const [poolAuthority] = PublicKey.findProgramAddressSync(
1250
1508
  [Buffer.from("pool-authority"), mint.toBuffer()],
1251
- PROGRAM_IDS.PUMP
1509
+ PROGRAM_IDS.PUMP,
1252
1510
  );
1253
1511
 
1254
1512
  const [pool] = PublicKey.findProgramAddressSync(
@@ -1257,9 +1515,9 @@ export class PumpTrader {
1257
1515
  new BN(0).toArrayLike(Buffer, "le", 2),
1258
1516
  poolAuthority.toBuffer(),
1259
1517
  mint.toBuffer(),
1260
- SOL_MINT.toBuffer()
1518
+ quoteMint.toBuffer(),
1261
1519
  ],
1262
- PROGRAM_IDS.PUMP_AMM
1520
+ PROGRAM_IDS.PUMP_AMM,
1263
1521
  );
1264
1522
 
1265
1523
  const acc = await this.connection.getAccountInfo(pool);
@@ -1269,13 +1527,17 @@ export class PumpTrader {
1269
1527
 
1270
1528
  const [globalConfigPda] = PublicKey.findProgramAddressSync(
1271
1529
  [Buffer.from("global_config")],
1272
- PROGRAM_IDS.PUMP_AMM
1530
+ PROGRAM_IDS.PUMP_AMM,
1273
1531
  );
1274
1532
 
1275
- const globalConfigAcc = await this.connection.getAccountInfo(globalConfigPda);
1533
+ const globalConfigAcc =
1534
+ await this.connection.getAccountInfo(globalConfigPda);
1276
1535
  if (!globalConfigAcc) throw new Error("Global config not found");
1277
1536
 
1278
- const globalConfig = this.parseAmmGlobalConfig(globalConfigAcc.data, globalConfigPda);
1537
+ const globalConfig = this.parseAmmGlobalConfig(
1538
+ globalConfigAcc.data,
1539
+ globalConfigPda,
1540
+ );
1279
1541
 
1280
1542
  return { pool, poolAuthority, poolKeys, globalConfig };
1281
1543
  }
@@ -1292,7 +1554,9 @@ export class PumpTrader {
1292
1554
 
1293
1555
  const protocolFeeRecipients: PublicKey[] = [];
1294
1556
  for (let i = 0; i < 8; i++) {
1295
- protocolFeeRecipients.push(new PublicKey(data.slice(offset, offset + 32)));
1557
+ protocolFeeRecipients.push(
1558
+ new PublicKey(data.slice(offset, offset + 32)),
1559
+ );
1296
1560
  offset += 32;
1297
1561
  }
1298
1562
 
@@ -1302,21 +1566,21 @@ export class PumpTrader {
1302
1566
  async getAmmPoolReserves(poolKeys: any): Promise<PoolReserves> {
1303
1567
  const [baseInfo, quoteInfo] = await Promise.all([
1304
1568
  this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
1305
- this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
1569
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
1306
1570
  ]);
1307
1571
 
1308
1572
  return {
1309
1573
  baseAmount: BigInt(baseInfo.value.amount),
1310
1574
  quoteAmount: BigInt(quoteInfo.value.amount),
1311
1575
  baseDecimals: baseInfo.value.decimals,
1312
- quoteDecimals: quoteInfo.value.decimals
1576
+ quoteDecimals: quoteInfo.value.decimals,
1313
1577
  };
1314
1578
  }
1315
1579
 
1316
1580
  deriveAmmPoolV2(baseMint: PublicKey): PublicKey {
1317
1581
  return PublicKey.findProgramAddressSync(
1318
1582
  [Buffer.from("pool-v2"), baseMint.toBuffer()],
1319
- PROGRAM_IDS.PUMP_AMM
1583
+ PROGRAM_IDS.PUMP_AMM,
1320
1584
  )[0];
1321
1585
  }
1322
1586
 
@@ -1328,19 +1592,19 @@ export class PumpTrader {
1328
1592
  userQuoteAta: PublicKey,
1329
1593
  baseAmountOut: bigint,
1330
1594
  maxQuoteAmountIn: bigint,
1331
- tokenProgramId: PublicKey
1595
+ tokenProgramId: PublicKey,
1332
1596
  ): TransactionInstruction {
1333
1597
  const { pool, poolKeys, globalConfig } = poolInfo;
1334
1598
  const poolV2 = this.deriveAmmPoolV2(poolKeys.baseMint);
1335
1599
 
1336
1600
  const [eventAuthority] = PublicKey.findProgramAddressSync(
1337
1601
  [Buffer.from("__event_authority")],
1338
- PROGRAM_IDS.PUMP_AMM
1602
+ PROGRAM_IDS.PUMP_AMM,
1339
1603
  );
1340
1604
 
1341
1605
  const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
1342
1606
  [Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
1343
- PROGRAM_IDS.PUMP_AMM
1607
+ PROGRAM_IDS.PUMP_AMM,
1344
1608
  );
1345
1609
 
1346
1610
  const coinCreatorVaultAta = getAssociatedTokenAddressSync(
@@ -1348,22 +1612,25 @@ export class PumpTrader {
1348
1612
  coinCreatorVaultAuthority,
1349
1613
  true,
1350
1614
  TOKEN_PROGRAM_ID,
1351
- ASSOCIATED_TOKEN_PROGRAM_ID
1615
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1352
1616
  );
1353
1617
 
1354
1618
  const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
1355
1619
  [Buffer.from("global_volume_accumulator")],
1356
- PROGRAM_IDS.PUMP_AMM
1620
+ PROGRAM_IDS.PUMP_AMM,
1357
1621
  );
1358
1622
 
1359
1623
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1360
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
1361
- PROGRAM_IDS.PUMP_AMM
1624
+ [
1625
+ Buffer.from("user_volume_accumulator"),
1626
+ this.wallet.publicKey.toBuffer(),
1627
+ ],
1628
+ PROGRAM_IDS.PUMP_AMM,
1362
1629
  );
1363
1630
 
1364
1631
  const [feeConfig] = PublicKey.findProgramAddressSync(
1365
1632
  [Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
1366
- PROGRAM_IDS.FEE
1633
+ PROGRAM_IDS.FEE,
1367
1634
  );
1368
1635
 
1369
1636
  const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
@@ -1372,7 +1639,7 @@ export class PumpTrader {
1372
1639
  protocolFeeRecipient,
1373
1640
  true,
1374
1641
  TOKEN_PROGRAM_ID,
1375
- ASSOCIATED_TOKEN_PROGRAM_ID
1642
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1376
1643
  );
1377
1644
  const newFeeRecipient = this.pickFeeRecipient();
1378
1645
  const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
@@ -1380,7 +1647,7 @@ export class PumpTrader {
1380
1647
  newFeeRecipient,
1381
1648
  true,
1382
1649
  TOKEN_PROGRAM_ID,
1383
- ASSOCIATED_TOKEN_PROGRAM_ID
1650
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1384
1651
  );
1385
1652
 
1386
1653
  const remainingKeys = [];
@@ -1390,14 +1657,22 @@ export class PumpTrader {
1390
1657
  userVolumeAccumulator,
1391
1658
  true,
1392
1659
  TOKEN_PROGRAM_ID,
1393
- ASSOCIATED_TOKEN_PROGRAM_ID
1660
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1394
1661
  );
1395
- remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true });
1662
+ remainingKeys.push({
1663
+ pubkey: userVolumeAccumulatorWsolAta,
1664
+ isSigner: false,
1665
+ isWritable: true,
1666
+ });
1396
1667
  }
1397
1668
  remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1398
1669
  remainingKeys.push(
1399
1670
  { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1400
- { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
1671
+ {
1672
+ pubkey: newFeeRecipientTokenAccount,
1673
+ isSigner: false,
1674
+ isWritable: true,
1675
+ },
1401
1676
  );
1402
1677
 
1403
1678
  return new TransactionInstruction({
@@ -1410,30 +1685,50 @@ export class PumpTrader {
1410
1685
  { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
1411
1686
  { pubkey: userBaseAta, isSigner: false, isWritable: true },
1412
1687
  { pubkey: userQuoteAta, isSigner: false, isWritable: true },
1413
- { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
1414
- { pubkey: poolKeys.poolQuoteTokenAccount, isSigner: false, isWritable: true },
1688
+ {
1689
+ pubkey: poolKeys.poolBaseTokenAccount,
1690
+ isSigner: false,
1691
+ isWritable: true,
1692
+ },
1693
+ {
1694
+ pubkey: poolKeys.poolQuoteTokenAccount,
1695
+ isSigner: false,
1696
+ isWritable: true,
1697
+ },
1415
1698
  { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
1416
- { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
1699
+ {
1700
+ pubkey: protocolFeeRecipientTokenAccount,
1701
+ isSigner: false,
1702
+ isWritable: true,
1703
+ },
1417
1704
  { pubkey: tokenProgramId, isSigner: false, isWritable: false },
1418
1705
  { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1419
1706
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1420
- { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1707
+ {
1708
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
1709
+ isSigner: false,
1710
+ isWritable: false,
1711
+ },
1421
1712
  { pubkey: eventAuthority, isSigner: false, isWritable: false },
1422
1713
  { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
1423
1714
  { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
1424
- { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
1715
+ {
1716
+ pubkey: coinCreatorVaultAuthority,
1717
+ isSigner: false,
1718
+ isWritable: false,
1719
+ },
1425
1720
  { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
1426
1721
  { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1427
1722
  { pubkey: feeConfig, isSigner: false, isWritable: false },
1428
1723
  { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
1429
- ...remainingKeys
1724
+ ...remainingKeys,
1430
1725
  ],
1431
1726
  data: Buffer.concat([
1432
1727
  DISCRIMINATORS.BUY,
1433
1728
  u64(baseAmountOut),
1434
1729
  u64(maxQuoteAmountIn),
1435
- Buffer.from([1, 1])
1436
- ])
1730
+ Buffer.from([1, 1]),
1731
+ ]),
1437
1732
  });
1438
1733
  }
1439
1734
 
@@ -1443,19 +1738,19 @@ export class PumpTrader {
1443
1738
  userQuoteAta: PublicKey,
1444
1739
  baseAmountIn: bigint,
1445
1740
  minQuoteAmountOut: bigint,
1446
- tokenProgramId: PublicKey
1741
+ tokenProgramId: PublicKey,
1447
1742
  ): TransactionInstruction {
1448
1743
  const { pool, poolKeys, globalConfig } = poolInfo;
1449
1744
  const poolV2 = this.deriveAmmPoolV2(poolKeys.baseMint);
1450
1745
 
1451
1746
  const [eventAuthority] = PublicKey.findProgramAddressSync(
1452
1747
  [Buffer.from("__event_authority")],
1453
- PROGRAM_IDS.PUMP_AMM
1748
+ PROGRAM_IDS.PUMP_AMM,
1454
1749
  );
1455
1750
 
1456
1751
  const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
1457
1752
  [Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
1458
- PROGRAM_IDS.PUMP_AMM
1753
+ PROGRAM_IDS.PUMP_AMM,
1459
1754
  );
1460
1755
 
1461
1756
  const coinCreatorVaultAta = getAssociatedTokenAddressSync(
@@ -1463,12 +1758,12 @@ export class PumpTrader {
1463
1758
  coinCreatorVaultAuthority,
1464
1759
  true,
1465
1760
  TOKEN_PROGRAM_ID,
1466
- ASSOCIATED_TOKEN_PROGRAM_ID
1761
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1467
1762
  );
1468
1763
 
1469
1764
  const [feeConfig] = PublicKey.findProgramAddressSync(
1470
1765
  [Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
1471
- PROGRAM_IDS.FEE
1766
+ PROGRAM_IDS.FEE,
1472
1767
  );
1473
1768
 
1474
1769
  const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
@@ -1477,7 +1772,7 @@ export class PumpTrader {
1477
1772
  protocolFeeRecipient,
1478
1773
  true,
1479
1774
  TOKEN_PROGRAM_ID,
1480
- ASSOCIATED_TOKEN_PROGRAM_ID
1775
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1481
1776
  );
1482
1777
  const newFeeRecipient = this.pickFeeRecipient();
1483
1778
  const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
@@ -1485,12 +1780,15 @@ export class PumpTrader {
1485
1780
  newFeeRecipient,
1486
1781
  true,
1487
1782
  TOKEN_PROGRAM_ID,
1488
- ASSOCIATED_TOKEN_PROGRAM_ID
1783
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1489
1784
  );
1490
1785
 
1491
1786
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1492
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
1493
- PROGRAM_IDS.PUMP_AMM
1787
+ [
1788
+ Buffer.from("user_volume_accumulator"),
1789
+ this.wallet.publicKey.toBuffer(),
1790
+ ],
1791
+ PROGRAM_IDS.PUMP_AMM,
1494
1792
  );
1495
1793
 
1496
1794
  const userVolumeAccumulatorWsolAta = getAssociatedTokenAddressSync(
@@ -1498,20 +1796,28 @@ export class PumpTrader {
1498
1796
  userVolumeAccumulator,
1499
1797
  true,
1500
1798
  TOKEN_PROGRAM_ID,
1501
- ASSOCIATED_TOKEN_PROGRAM_ID
1799
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1502
1800
  );
1503
1801
 
1504
1802
  const remainingKeys = [];
1505
1803
  if (poolKeys.isCashbackCoin) {
1506
1804
  remainingKeys.push(
1507
- { pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true },
1508
- { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true }
1805
+ {
1806
+ pubkey: userVolumeAccumulatorWsolAta,
1807
+ isSigner: false,
1808
+ isWritable: true,
1809
+ },
1810
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1509
1811
  );
1510
1812
  }
1511
1813
  remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1512
1814
  remainingKeys.push(
1513
1815
  { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1514
- { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
1816
+ {
1817
+ pubkey: newFeeRecipientTokenAccount,
1818
+ isSigner: false,
1819
+ isWritable: true,
1820
+ },
1515
1821
  );
1516
1822
 
1517
1823
  return new TransactionInstruction({
@@ -1524,71 +1830,819 @@ export class PumpTrader {
1524
1830
  { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
1525
1831
  { pubkey: userBaseAta, isSigner: false, isWritable: true },
1526
1832
  { pubkey: userQuoteAta, isSigner: false, isWritable: true },
1527
- { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
1528
- { pubkey: poolKeys.poolQuoteTokenAccount, isSigner: false, isWritable: true },
1833
+ {
1834
+ pubkey: poolKeys.poolBaseTokenAccount,
1835
+ isSigner: false,
1836
+ isWritable: true,
1837
+ },
1838
+ {
1839
+ pubkey: poolKeys.poolQuoteTokenAccount,
1840
+ isSigner: false,
1841
+ isWritable: true,
1842
+ },
1529
1843
  { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
1530
- { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
1844
+ {
1845
+ pubkey: protocolFeeRecipientTokenAccount,
1846
+ isSigner: false,
1847
+ isWritable: true,
1848
+ },
1531
1849
  { pubkey: tokenProgramId, isSigner: false, isWritable: false },
1532
1850
  { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1533
1851
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1534
- { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1852
+ {
1853
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
1854
+ isSigner: false,
1855
+ isWritable: false,
1856
+ },
1535
1857
  { pubkey: eventAuthority, isSigner: false, isWritable: false },
1536
1858
  { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
1537
1859
  { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
1538
- { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
1860
+ {
1861
+ pubkey: coinCreatorVaultAuthority,
1862
+ isSigner: false,
1863
+ isWritable: false,
1864
+ },
1539
1865
  { pubkey: feeConfig, isSigner: false, isWritable: false },
1540
1866
  { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
1541
- ...remainingKeys
1867
+ ...remainingKeys,
1542
1868
  ],
1543
1869
  data: Buffer.concat([
1544
1870
  DISCRIMINATORS.SELL,
1545
1871
  u64(baseAmountIn),
1546
- u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n)
1547
- ])
1872
+ u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n),
1873
+ ]),
1548
1874
  });
1549
1875
  }
1550
1876
 
1877
+ /* ---------- V2 指令账户构建 ---------- */
1878
+
1879
+ /**
1880
+ * Build accounts for buy_v2 instruction (27 accounts)
1881
+ * Ref: https://github.com/pump-fun/pump-public-docs/blob/main/docs/instructions/BUY.md
1882
+ */
1883
+ private buildBondingBuyV2Keys(args: {
1884
+ global: PublicKey;
1885
+ baseMint: PublicKey;
1886
+ quoteMint: PublicKey;
1887
+ baseTokenProgram: PublicKey;
1888
+ quoteTokenProgram: PublicKey;
1889
+ feeRecipient: PublicKey;
1890
+ associatedQuoteFeeRecipient: PublicKey;
1891
+ buybackFeeRecipient: PublicKey;
1892
+ associatedQuoteBuybackFeeRecipient: PublicKey;
1893
+ bondingCurve: PublicKey;
1894
+ associatedBaseBondingCurve: PublicKey;
1895
+ associatedQuoteBondingCurve: PublicKey;
1896
+ user: PublicKey;
1897
+ associatedBaseUser: PublicKey;
1898
+ associatedQuoteUser: PublicKey;
1899
+ creatorVault: PublicKey;
1900
+ associatedCreatorVault: PublicKey;
1901
+ sharingConfig: PublicKey;
1902
+ globalVolumeAccumulator: PublicKey;
1903
+ userVolumeAccumulator: PublicKey;
1904
+ associatedUserVolumeAccumulator: PublicKey;
1905
+ feeConfig: PublicKey;
1906
+ feeProgram: PublicKey;
1907
+ eventAuthority: PublicKey;
1908
+ pumpProgram: PublicKey;
1909
+ }): AccountMeta[] {
1910
+ return [
1911
+ { pubkey: args.global, isSigner: false, isWritable: false },
1912
+ { pubkey: args.baseMint, isSigner: false, isWritable: false },
1913
+ { pubkey: args.quoteMint, isSigner: false, isWritable: false },
1914
+ { pubkey: args.baseTokenProgram, isSigner: false, isWritable: false },
1915
+ { pubkey: args.quoteTokenProgram, isSigner: false, isWritable: false },
1916
+ {
1917
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
1918
+ isSigner: false,
1919
+ isWritable: false,
1920
+ },
1921
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true },
1922
+ {
1923
+ pubkey: args.associatedQuoteFeeRecipient,
1924
+ isSigner: false,
1925
+ isWritable: true,
1926
+ },
1927
+ { pubkey: args.buybackFeeRecipient, isSigner: false, isWritable: true },
1928
+ {
1929
+ pubkey: args.associatedQuoteBuybackFeeRecipient,
1930
+ isSigner: false,
1931
+ isWritable: true,
1932
+ },
1933
+ { pubkey: args.bondingCurve, isSigner: false, isWritable: true },
1934
+ {
1935
+ pubkey: args.associatedBaseBondingCurve,
1936
+ isSigner: false,
1937
+ isWritable: true,
1938
+ },
1939
+ {
1940
+ pubkey: args.associatedQuoteBondingCurve,
1941
+ isSigner: false,
1942
+ isWritable: true,
1943
+ },
1944
+ { pubkey: args.user, isSigner: true, isWritable: true },
1945
+ { pubkey: args.associatedBaseUser, isSigner: false, isWritable: true },
1946
+ { pubkey: args.associatedQuoteUser, isSigner: false, isWritable: true },
1947
+ { pubkey: args.creatorVault, isSigner: false, isWritable: true },
1948
+ {
1949
+ pubkey: args.associatedCreatorVault,
1950
+ isSigner: false,
1951
+ isWritable: true,
1952
+ },
1953
+ { pubkey: args.sharingConfig, isSigner: false, isWritable: false },
1954
+ {
1955
+ pubkey: args.globalVolumeAccumulator,
1956
+ isSigner: false,
1957
+ isWritable: false,
1958
+ },
1959
+ { pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
1960
+ {
1961
+ pubkey: args.associatedUserVolumeAccumulator,
1962
+ isSigner: false,
1963
+ isWritable: true,
1964
+ },
1965
+ { pubkey: args.feeConfig, isSigner: false, isWritable: false },
1966
+ { pubkey: args.feeProgram, isSigner: false, isWritable: false },
1967
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1968
+ { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
1969
+ { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
1970
+ ];
1971
+ }
1972
+
1973
+ /**
1974
+ * Build accounts for sell_v2 instruction (26 accounts)
1975
+ * Ref: https://github.com/pump-fun/pump-public-docs/blob/main/docs/instructions/SELL.md
1976
+ */
1977
+ private buildBondingSellV2Keys(args: {
1978
+ global: PublicKey;
1979
+ baseMint: PublicKey;
1980
+ quoteMint: PublicKey;
1981
+ baseTokenProgram: PublicKey;
1982
+ quoteTokenProgram: PublicKey;
1983
+ feeRecipient: PublicKey;
1984
+ associatedQuoteFeeRecipient: PublicKey;
1985
+ buybackFeeRecipient: PublicKey;
1986
+ associatedQuoteBuybackFeeRecipient: PublicKey;
1987
+ bondingCurve: PublicKey;
1988
+ associatedBaseBondingCurve: PublicKey;
1989
+ associatedQuoteBondingCurve: PublicKey;
1990
+ user: PublicKey;
1991
+ associatedBaseUser: PublicKey;
1992
+ associatedQuoteUser: PublicKey;
1993
+ creatorVault: PublicKey;
1994
+ associatedCreatorVault: PublicKey;
1995
+ sharingConfig: PublicKey;
1996
+ userVolumeAccumulator: PublicKey;
1997
+ associatedUserVolumeAccumulator: PublicKey;
1998
+ feeConfig: PublicKey;
1999
+ feeProgram: PublicKey;
2000
+ eventAuthority: PublicKey;
2001
+ pumpProgram: PublicKey;
2002
+ }): AccountMeta[] {
2003
+ return [
2004
+ { pubkey: args.global, isSigner: false, isWritable: false },
2005
+ { pubkey: args.baseMint, isSigner: false, isWritable: false },
2006
+ { pubkey: args.quoteMint, isSigner: false, isWritable: false },
2007
+ { pubkey: args.baseTokenProgram, isSigner: false, isWritable: false },
2008
+ { pubkey: args.quoteTokenProgram, isSigner: false, isWritable: false },
2009
+ {
2010
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
2011
+ isSigner: false,
2012
+ isWritable: false,
2013
+ },
2014
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true },
2015
+ {
2016
+ pubkey: args.associatedQuoteFeeRecipient,
2017
+ isSigner: false,
2018
+ isWritable: true,
2019
+ },
2020
+ { pubkey: args.buybackFeeRecipient, isSigner: false, isWritable: true },
2021
+ {
2022
+ pubkey: args.associatedQuoteBuybackFeeRecipient,
2023
+ isSigner: false,
2024
+ isWritable: true,
2025
+ },
2026
+ { pubkey: args.bondingCurve, isSigner: false, isWritable: true },
2027
+ {
2028
+ pubkey: args.associatedBaseBondingCurve,
2029
+ isSigner: false,
2030
+ isWritable: true,
2031
+ },
2032
+ {
2033
+ pubkey: args.associatedQuoteBondingCurve,
2034
+ isSigner: false,
2035
+ isWritable: true,
2036
+ },
2037
+ { pubkey: args.user, isSigner: true, isWritable: true },
2038
+ { pubkey: args.associatedBaseUser, isSigner: false, isWritable: true },
2039
+ { pubkey: args.associatedQuoteUser, isSigner: false, isWritable: true },
2040
+ { pubkey: args.creatorVault, isSigner: false, isWritable: true },
2041
+ {
2042
+ pubkey: args.associatedCreatorVault,
2043
+ isSigner: false,
2044
+ isWritable: true,
2045
+ },
2046
+ { pubkey: args.sharingConfig, isSigner: false, isWritable: false },
2047
+ { pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
2048
+ {
2049
+ pubkey: args.associatedUserVolumeAccumulator,
2050
+ isSigner: false,
2051
+ isWritable: true,
2052
+ },
2053
+ { pubkey: args.feeConfig, isSigner: false, isWritable: false },
2054
+ { pubkey: args.feeProgram, isSigner: false, isWritable: false },
2055
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
2056
+ { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
2057
+ { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
2058
+ ];
2059
+ }
2060
+
2061
+ /* ---------- V2 交易 ---------- */
2062
+
2063
+ /**
2064
+ * Buy using buy_v2 instruction (supports both SOL-paired and USDC-paired coins)
2065
+ * For SOL-paired coins: quoteMint = SOL_MINT, quoteTokenProgram = TOKEN_PROGRAM_ID
2066
+ * For USDC-paired coins: quoteMint = USDC mint, quoteTokenProgram = TOKEN_PROGRAM_ID
2067
+ */
2068
+ async buyV2(
2069
+ tokenAddr: string,
2070
+ totalQuoteIn: bigint,
2071
+ tradeOpt: TradeOptions,
2072
+ quoteMint: PublicKey = SOL_MINT,
2073
+ ): Promise<TradeResult> {
2074
+ const baseMint = new PublicKey(tokenAddr);
2075
+ const baseTokenProgram = await this.detectTokenProgram(tokenAddr);
2076
+ const quoteTokenProgramId = quoteMint.equals(SOL_MINT)
2077
+ ? TOKEN_PROGRAM_ID
2078
+ : TOKEN_PROGRAM_ID;
2079
+
2080
+ if (!this.globalState) await this.loadGlobal();
2081
+
2082
+ const { bonding, state, creator } = await this.loadBonding(baseMint);
2083
+ if (state.complete) throw new Error("Bonding curve already completed");
2084
+
2085
+ const solEquivalent = quoteMint.equals(SOL_MINT) ? totalQuoteIn : 0n;
2086
+ const quoteChunks =
2087
+ solEquivalent > 0n
2088
+ ? this.splitByMax(solEquivalent, tradeOpt.maxSolPerTx)
2089
+ : this.splitByMax(totalQuoteIn, tradeOpt.maxSolPerTx);
2090
+
2091
+ const pendingTransactions: PendingTransaction[] = [];
2092
+ const failedTransactions: FailedTransaction[] = [];
2093
+
2094
+ // Pre-compute PDAs
2095
+ const associatedBaseBondingCurve = getAssociatedTokenAddressSync(
2096
+ baseMint,
2097
+ bonding,
2098
+ true,
2099
+ baseTokenProgram.programId,
2100
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2101
+ );
2102
+ const associatedQuoteBondingCurve = getAssociatedTokenAddressSync(
2103
+ quoteMint,
2104
+ bonding,
2105
+ true,
2106
+ quoteTokenProgramId,
2107
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2108
+ );
2109
+
2110
+ const [creatorVault] = PublicKey.findProgramAddressSync(
2111
+ [Buffer.from("creator-vault"), creator.toBuffer()],
2112
+ PROGRAM_IDS.PUMP,
2113
+ );
2114
+ const associatedCreatorVault = getAssociatedTokenAddressSync(
2115
+ quoteMint,
2116
+ creatorVault,
2117
+ true,
2118
+ quoteTokenProgramId,
2119
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2120
+ );
2121
+
2122
+ const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
2123
+ [Buffer.from("global_volume_accumulator")],
2124
+ PROGRAM_IDS.PUMP,
2125
+ );
2126
+ const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
2127
+ [
2128
+ Buffer.from("user_volume_accumulator"),
2129
+ this.wallet.publicKey.toBuffer(),
2130
+ ],
2131
+ PROGRAM_IDS.PUMP,
2132
+ );
2133
+ const associatedUserVolumeAccumulator = getAssociatedTokenAddressSync(
2134
+ quoteMint,
2135
+ userVolumeAccumulator,
2136
+ true,
2137
+ quoteTokenProgramId,
2138
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2139
+ );
2140
+
2141
+ const [feeConfig] = PublicKey.findProgramAddressSync(
2142
+ [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
2143
+ PROGRAM_IDS.FEE,
2144
+ );
2145
+
2146
+ const sharingConfig = this.getSharingConfigPda(baseMint);
2147
+ const feeRecipient = this.pickFeeRecipient();
2148
+ const associatedQuoteFeeRecipient = getAssociatedTokenAddressSync(
2149
+ quoteMint,
2150
+ feeRecipient,
2151
+ true,
2152
+ quoteTokenProgramId,
2153
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2154
+ );
2155
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
2156
+ const associatedQuoteBuybackFeeRecipient = getAssociatedTokenAddressSync(
2157
+ quoteMint,
2158
+ buybackFeeRecipient,
2159
+ true,
2160
+ quoteTokenProgramId,
2161
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2162
+ );
2163
+
2164
+ const associatedQuoteUser = getAssociatedTokenAddressSync(
2165
+ quoteMint,
2166
+ this.wallet.publicKey,
2167
+ false,
2168
+ quoteTokenProgramId,
2169
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2170
+ );
2171
+
2172
+ for (let i = 0; i < quoteChunks.length; i++) {
2173
+ try {
2174
+ const quoteIn = quoteChunks[i];
2175
+ const tokenOut = this.calcBuy(quoteIn, state);
2176
+ const slippageBps = this.calcSlippage({
2177
+ tradeSize: quoteIn,
2178
+ reserve: state.virtualSolReserves,
2179
+ slippageOpt: tradeOpt.slippage,
2180
+ });
2181
+ const maxQuoteCost = (quoteIn * BigInt(10_000 + slippageBps)) / 10_000n;
2182
+ const priority = this.genPriority(tradeOpt.priority);
2183
+
2184
+ const tx = new Transaction().add(
2185
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
2186
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
2187
+ );
2188
+
2189
+ // For SOL-paired coins, need wSOL ATA for the user's quote account
2190
+ if (quoteMint.equals(SOL_MINT)) {
2191
+ await this.ensureWSOLAta(
2192
+ tx,
2193
+ this.wallet.publicKey,
2194
+ "buy",
2195
+ maxQuoteCost,
2196
+ );
2197
+ } else {
2198
+ // For non-SOL quote (e.g. USDC), ensure user has the quote token ATA
2199
+ const userQuoteAta = getAssociatedTokenAddressSync(
2200
+ quoteMint,
2201
+ this.wallet.publicKey,
2202
+ false,
2203
+ quoteTokenProgramId,
2204
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2205
+ );
2206
+ const acc = await this.connection.getAccountInfo(userQuoteAta);
2207
+ if (!acc) {
2208
+ tx.add(
2209
+ createAssociatedTokenAccountInstruction(
2210
+ this.wallet.publicKey,
2211
+ userQuoteAta,
2212
+ this.wallet.publicKey,
2213
+ quoteMint,
2214
+ quoteTokenProgramId,
2215
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2216
+ ),
2217
+ );
2218
+ }
2219
+ }
2220
+
2221
+ const userBaseAta = await this.ensureAta(
2222
+ tx,
2223
+ baseMint,
2224
+ baseTokenProgram.programId,
2225
+ );
2226
+
2227
+ tx.add(
2228
+ new TransactionInstruction({
2229
+ programId: PROGRAM_IDS.PUMP,
2230
+ keys: this.buildBondingBuyV2Keys({
2231
+ global: this.global,
2232
+ baseMint,
2233
+ quoteMint,
2234
+ baseTokenProgram: baseTokenProgram.programId,
2235
+ quoteTokenProgram: quoteTokenProgramId,
2236
+ feeRecipient,
2237
+ associatedQuoteFeeRecipient,
2238
+ buybackFeeRecipient,
2239
+ associatedQuoteBuybackFeeRecipient,
2240
+ bondingCurve: bonding,
2241
+ associatedBaseBondingCurve,
2242
+ associatedQuoteBondingCurve,
2243
+ user: this.wallet.publicKey,
2244
+ associatedBaseUser: userBaseAta,
2245
+ associatedQuoteUser,
2246
+ creatorVault,
2247
+ associatedCreatorVault,
2248
+ sharingConfig,
2249
+ globalVolumeAccumulator,
2250
+ userVolumeAccumulator,
2251
+ associatedUserVolumeAccumulator,
2252
+ feeConfig,
2253
+ feeProgram: PROGRAM_IDS.FEE,
2254
+ eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
2255
+ pumpProgram: PROGRAM_IDS.PUMP,
2256
+ }),
2257
+ data: Buffer.concat([
2258
+ DISCRIMINATORS.BUY_V2,
2259
+ u64(tokenOut),
2260
+ u64(maxQuoteCost),
2261
+ ]),
2262
+ }),
2263
+ );
2264
+
2265
+ // Close wSOL ATA after buy for SOL-paired coins
2266
+ if (quoteMint.equals(SOL_MINT)) {
2267
+ const wsolAta = getAssociatedTokenAddressSync(
2268
+ SOL_MINT,
2269
+ this.wallet.publicKey,
2270
+ false,
2271
+ TOKEN_PROGRAM_ID,
2272
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2273
+ );
2274
+ tx.add(
2275
+ createCloseAccountInstruction(
2276
+ wsolAta,
2277
+ this.wallet.publicKey,
2278
+ this.wallet.publicKey,
2279
+ ),
2280
+ );
2281
+ }
2282
+
2283
+ const { blockhash, lastValidBlockHeight } =
2284
+ await this.connection.getLatestBlockhash("finalized");
2285
+ tx.recentBlockhash = blockhash;
2286
+ tx.feePayer = this.wallet.publicKey;
2287
+ tx.sign(this.wallet);
2288
+
2289
+ const signature = await this.connection.sendRawTransaction(
2290
+ tx.serialize(),
2291
+ {
2292
+ skipPreflight: false,
2293
+ maxRetries: 2,
2294
+ },
2295
+ );
2296
+
2297
+ pendingTransactions.push({ signature, lastValidBlockHeight, index: i });
2298
+ } catch (e) {
2299
+ failedTransactions.push({ index: i, error: (e as Error).message });
2300
+ }
2301
+ }
2302
+
2303
+ return { pendingTransactions, failedTransactions };
2304
+ }
2305
+
2306
+ /**
2307
+ * Sell using sell_v2 instruction (supports both SOL-paired and USDC-paired coins)
2308
+ */
2309
+ async sellV2(
2310
+ tokenAddr: string,
2311
+ totalTokenIn: bigint,
2312
+ tradeOpt: TradeOptions,
2313
+ quoteMint: PublicKey = SOL_MINT,
2314
+ ): Promise<TradeResult> {
2315
+ const baseMint = new PublicKey(tokenAddr);
2316
+ const baseTokenProgram = await this.detectTokenProgram(tokenAddr);
2317
+ const quoteTokenProgramId = quoteMint.equals(SOL_MINT)
2318
+ ? TOKEN_PROGRAM_ID
2319
+ : TOKEN_PROGRAM_ID;
2320
+
2321
+ if (!this.globalState) await this.loadGlobal();
2322
+
2323
+ const { bonding, state, creator } = await this.loadBonding(baseMint);
2324
+ if (state.complete) throw new Error("Bonding curve already completed");
2325
+
2326
+ const totalQuoteOut = this.calcSell(totalTokenIn, state);
2327
+ const tokenChunks =
2328
+ totalQuoteOut <= tradeOpt.maxSolPerTx
2329
+ ? [totalTokenIn]
2330
+ : this.splitIntoN(
2331
+ totalTokenIn,
2332
+ Number(
2333
+ (totalQuoteOut + tradeOpt.maxSolPerTx - 1n) /
2334
+ tradeOpt.maxSolPerTx,
2335
+ ),
2336
+ );
2337
+
2338
+ const pendingTransactions: PendingTransaction[] = [];
2339
+ const failedTransactions: FailedTransaction[] = [];
2340
+
2341
+ // Pre-compute PDAs
2342
+ const associatedBaseBondingCurve = getAssociatedTokenAddressSync(
2343
+ baseMint,
2344
+ bonding,
2345
+ true,
2346
+ baseTokenProgram.programId,
2347
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2348
+ );
2349
+ const associatedQuoteBondingCurve = getAssociatedTokenAddressSync(
2350
+ quoteMint,
2351
+ bonding,
2352
+ true,
2353
+ quoteTokenProgramId,
2354
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2355
+ );
2356
+
2357
+ const [creatorVault] = PublicKey.findProgramAddressSync(
2358
+ [Buffer.from("creator-vault"), creator.toBuffer()],
2359
+ PROGRAM_IDS.PUMP,
2360
+ );
2361
+ const associatedCreatorVault = getAssociatedTokenAddressSync(
2362
+ quoteMint,
2363
+ creatorVault,
2364
+ true,
2365
+ quoteTokenProgramId,
2366
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2367
+ );
2368
+
2369
+ const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
2370
+ [
2371
+ Buffer.from("user_volume_accumulator"),
2372
+ this.wallet.publicKey.toBuffer(),
2373
+ ],
2374
+ PROGRAM_IDS.PUMP,
2375
+ );
2376
+ const associatedUserVolumeAccumulator = getAssociatedTokenAddressSync(
2377
+ quoteMint,
2378
+ userVolumeAccumulator,
2379
+ true,
2380
+ quoteTokenProgramId,
2381
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2382
+ );
2383
+
2384
+ const [feeConfig] = PublicKey.findProgramAddressSync(
2385
+ [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
2386
+ PROGRAM_IDS.FEE,
2387
+ );
2388
+
2389
+ const sharingConfig = this.getSharingConfigPda(baseMint);
2390
+ const feeRecipient = this.pickFeeRecipient();
2391
+ const associatedQuoteFeeRecipient = getAssociatedTokenAddressSync(
2392
+ quoteMint,
2393
+ feeRecipient,
2394
+ true,
2395
+ quoteTokenProgramId,
2396
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2397
+ );
2398
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
2399
+ const associatedQuoteBuybackFeeRecipient = getAssociatedTokenAddressSync(
2400
+ quoteMint,
2401
+ buybackFeeRecipient,
2402
+ true,
2403
+ quoteTokenProgramId,
2404
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2405
+ );
2406
+
2407
+ const associatedQuoteUser = getAssociatedTokenAddressSync(
2408
+ quoteMint,
2409
+ this.wallet.publicKey,
2410
+ false,
2411
+ quoteTokenProgramId,
2412
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2413
+ );
2414
+
2415
+ const userBaseAta = getAssociatedTokenAddressSync(
2416
+ baseMint,
2417
+ this.wallet.publicKey,
2418
+ false,
2419
+ baseTokenProgram.programId,
2420
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2421
+ );
2422
+
2423
+ for (let i = 0; i < tokenChunks.length; i++) {
2424
+ try {
2425
+ const tokenIn = tokenChunks[i];
2426
+ const quoteOut = this.calcSell(tokenIn, state);
2427
+ const slippageBps = this.calcSlippage({
2428
+ tradeSize: tokenIn,
2429
+ reserve: state.virtualTokenReserves,
2430
+ slippageOpt: tradeOpt.slippage,
2431
+ });
2432
+ const minQuoteOut = (quoteOut * BigInt(10_000 - slippageBps)) / 10_000n;
2433
+ const priority = this.genPriority(tradeOpt.priority);
2434
+
2435
+ const tx = new Transaction().add(
2436
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
2437
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
2438
+ );
2439
+
2440
+ // For non-SOL quotes, ensure user has the quote token ATA (to receive proceeds)
2441
+ if (!quoteMint.equals(SOL_MINT)) {
2442
+ const userQuoteAta = getAssociatedTokenAddressSync(
2443
+ quoteMint,
2444
+ this.wallet.publicKey,
2445
+ false,
2446
+ quoteTokenProgramId,
2447
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2448
+ );
2449
+ const acc = await this.connection.getAccountInfo(userQuoteAta);
2450
+ if (!acc) {
2451
+ tx.add(
2452
+ createAssociatedTokenAccountInstruction(
2453
+ this.wallet.publicKey,
2454
+ userQuoteAta,
2455
+ this.wallet.publicKey,
2456
+ quoteMint,
2457
+ quoteTokenProgramId,
2458
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2459
+ ),
2460
+ );
2461
+ }
2462
+ }
2463
+
2464
+ tx.add(
2465
+ new TransactionInstruction({
2466
+ programId: PROGRAM_IDS.PUMP,
2467
+ keys: this.buildBondingSellV2Keys({
2468
+ global: this.global,
2469
+ baseMint,
2470
+ quoteMint,
2471
+ baseTokenProgram: baseTokenProgram.programId,
2472
+ quoteTokenProgram: quoteTokenProgramId,
2473
+ feeRecipient,
2474
+ associatedQuoteFeeRecipient,
2475
+ buybackFeeRecipient,
2476
+ associatedQuoteBuybackFeeRecipient,
2477
+ bondingCurve: bonding,
2478
+ associatedBaseBondingCurve,
2479
+ associatedQuoteBondingCurve,
2480
+ user: this.wallet.publicKey,
2481
+ associatedBaseUser: userBaseAta,
2482
+ associatedQuoteUser,
2483
+ creatorVault,
2484
+ associatedCreatorVault,
2485
+ sharingConfig,
2486
+ userVolumeAccumulator,
2487
+ associatedUserVolumeAccumulator,
2488
+ feeConfig,
2489
+ feeProgram: PROGRAM_IDS.FEE,
2490
+ eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
2491
+ pumpProgram: PROGRAM_IDS.PUMP,
2492
+ }),
2493
+ data: Buffer.concat([
2494
+ DISCRIMINATORS.SELL_V2,
2495
+ u64(tokenIn),
2496
+ u64(minQuoteOut > 0n ? minQuoteOut : 1n),
2497
+ ]),
2498
+ }),
2499
+ );
2500
+
2501
+ const { blockhash, lastValidBlockHeight } =
2502
+ await this.connection.getLatestBlockhash("finalized");
2503
+ tx.recentBlockhash = blockhash;
2504
+ tx.feePayer = this.wallet.publicKey;
2505
+ tx.sign(this.wallet);
2506
+
2507
+ const signature = await this.connection.sendRawTransaction(
2508
+ tx.serialize(),
2509
+ );
2510
+ pendingTransactions.push({ signature, lastValidBlockHeight, index: i });
2511
+ } catch (e) {
2512
+ failedTransactions.push({ index: i, error: (e as Error).message });
2513
+ }
2514
+ }
2515
+
2516
+ return { pendingTransactions, failedTransactions };
2517
+ }
2518
+
2519
+ /* ---------- Collect Creator Fee V2 ---------- */
2520
+
2521
+ /**
2522
+ * Collect creator fees from bonding curve creator vault (collect_creator_fee_v2)
2523
+ * Ref: https://github.com/pump-fun/pump-public-docs/blob/main/docs/instructions/COLLECT_CREATOR_FEE.md
2524
+ */
2525
+ async collectCreatorFeeV2(
2526
+ creator: PublicKey,
2527
+ quoteMint: PublicKey = SOL_MINT,
2528
+ ): Promise<string> {
2529
+ const quoteTokenProgramId = quoteMint.equals(SOL_MINT)
2530
+ ? TOKEN_PROGRAM_ID
2531
+ : TOKEN_PROGRAM_ID;
2532
+
2533
+ const [creatorVault] = PublicKey.findProgramAddressSync(
2534
+ [Buffer.from("creator-vault"), creator.toBuffer()],
2535
+ PROGRAM_IDS.PUMP,
2536
+ );
2537
+
2538
+ const creatorTokenAccount = getAssociatedTokenAddressSync(
2539
+ quoteMint,
2540
+ creator,
2541
+ false,
2542
+ quoteTokenProgramId,
2543
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2544
+ );
2545
+
2546
+ const creatorVaultTokenAccount = getAssociatedTokenAddressSync(
2547
+ quoteMint,
2548
+ creatorVault,
2549
+ true,
2550
+ quoteTokenProgramId,
2551
+ ASSOCIATED_TOKEN_PROGRAM_ID,
2552
+ );
2553
+
2554
+ const [eventAuthority] = PublicKey.findProgramAddressSync(
2555
+ [Buffer.from("__event_authority")],
2556
+ PROGRAM_IDS.PUMP,
2557
+ );
2558
+
2559
+ const tx = new Transaction().add(
2560
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
2561
+ new TransactionInstruction({
2562
+ programId: PROGRAM_IDS.PUMP,
2563
+ keys: [
2564
+ { pubkey: creator, isSigner: false, isWritable: false },
2565
+ { pubkey: creatorTokenAccount, isSigner: false, isWritable: true },
2566
+ { pubkey: creatorVault, isSigner: false, isWritable: true },
2567
+ {
2568
+ pubkey: creatorVaultTokenAccount,
2569
+ isSigner: false,
2570
+ isWritable: true,
2571
+ },
2572
+ { pubkey: quoteMint, isSigner: false, isWritable: false },
2573
+ { pubkey: quoteTokenProgramId, isSigner: false, isWritable: false },
2574
+ {
2575
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
2576
+ isSigner: false,
2577
+ isWritable: false,
2578
+ },
2579
+ {
2580
+ pubkey: SystemProgram.programId,
2581
+ isSigner: false,
2582
+ isWritable: false,
2583
+ },
2584
+ { pubkey: eventAuthority, isSigner: false, isWritable: false },
2585
+ { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
2586
+ ],
2587
+ data: DISCRIMINATORS.COLLECT_CREATOR_FEE_V2,
2588
+ }),
2589
+ );
2590
+
2591
+ const { blockhash, lastValidBlockHeight } =
2592
+ await this.connection.getLatestBlockhash("finalized");
2593
+ tx.recentBlockhash = blockhash;
2594
+ tx.feePayer = this.wallet.publicKey;
2595
+ tx.sign(this.wallet);
2596
+
2597
+ const signature = await this.connection.sendRawTransaction(tx.serialize());
2598
+ await this.confirmTransactionWithPolling(signature, lastValidBlockHeight);
2599
+ return signature;
2600
+ }
2601
+
1551
2602
  /* ---------- 交易确认 ---------- */
1552
2603
 
1553
2604
  async confirmTransactionWithPolling(
1554
2605
  signature: string,
1555
2606
  lastValidBlockHeight: number,
1556
2607
  maxAttempts: number = 5,
1557
- delayMs: number = 2000
2608
+ delayMs: number = 2000,
1558
2609
  ): Promise<string> {
1559
- console.log('✅ 交易已发送:', signature);
1560
- console.log('🔗 查看交易: https://solscan.io/tx/' + signature);
2610
+ console.log("✅ 交易已发送:", signature);
2611
+ console.log("🔗 查看交易: https://solscan.io/tx/" + signature);
1561
2612
 
1562
2613
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1563
- await new Promise(resolve => setTimeout(resolve, delayMs));
2614
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1564
2615
 
1565
2616
  try {
1566
2617
  console.log(`🔍 检查交易状态 (${attempt}/${maxAttempts})...`);
1567
2618
 
1568
2619
  const txInfo = await this.connection.getTransaction(signature, {
1569
- commitment: 'confirmed',
1570
- maxSupportedTransactionVersion: 0
2620
+ commitment: "confirmed",
2621
+ maxSupportedTransactionVersion: 0,
1571
2622
  });
1572
2623
 
1573
2624
  if (txInfo) {
1574
2625
  if (txInfo.meta?.err) {
1575
- console.error('❌ 交易失败:', txInfo.meta.err);
1576
- throw new Error('交易失败: ' + JSON.stringify(txInfo.meta.err));
2626
+ console.error("❌ 交易失败:", txInfo.meta.err);
2627
+ throw new Error("交易失败: " + JSON.stringify(txInfo.meta.err));
1577
2628
  }
1578
2629
 
1579
- console.log('✅ 交易已确认!');
2630
+ console.log("✅ 交易已确认!");
1580
2631
  return signature;
1581
2632
  }
1582
2633
 
1583
- const currentBlockHeight = await this.connection.getBlockHeight('finalized');
2634
+ const currentBlockHeight =
2635
+ await this.connection.getBlockHeight("finalized");
1584
2636
  if (currentBlockHeight > lastValidBlockHeight) {
1585
- console.log('⚠️ 交易已过期(超过有效区块高度)');
1586
- throw new Error('交易过期:未在有效区块高度内确认');
2637
+ console.log("⚠️ 交易已过期(超过有效区块高度)");
2638
+ throw new Error("交易过期:未在有效区块高度内确认");
1587
2639
  }
1588
-
1589
2640
  } catch (error) {
1590
2641
  const err = error as Error;
1591
- if (err.message?.includes('交易失败') || err.message?.includes('交易过期')) {
2642
+ if (
2643
+ err.message?.includes("交易失败") ||
2644
+ err.message?.includes("交易过期")
2645
+ ) {
1592
2646
  throw error;
1593
2647
  }
1594
2648
  console.log(`⚠️ 查询出错,继续重试: ${err.message}`);
@@ -1596,20 +2650,26 @@ export class PumpTrader {
1596
2650
  }
1597
2651
 
1598
2652
  throw new Error(
1599
- `交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}。请手动检查交易状态。`
2653
+ `交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}。请手动检查交易状态。`,
1600
2654
  );
1601
2655
  }
1602
2656
 
1603
2657
  /* ---------- 事件监听 ---------- */
1604
2658
 
1605
- listenTrades(callback: (event: TradeEvent) => void, mintFilter?: PublicKey | null) {
2659
+ listenTrades(
2660
+ callback: (event: TradeEvent) => void,
2661
+ mintFilter?: PublicKey | null,
2662
+ ) {
1606
2663
  return this.connection.onLogs(
1607
2664
  PROGRAM_IDS.PUMP,
1608
2665
  (log) => {
1609
2666
  for (const logLine of log.logs) {
1610
2667
  if (!logLine.startsWith("Program data: ")) continue;
1611
2668
 
1612
- const buf = Buffer.from(logLine.replace("Program data: ", ""), "base64");
2669
+ const buf = Buffer.from(
2670
+ logLine.replace("Program data: ", ""),
2671
+ "base64",
2672
+ );
1613
2673
 
1614
2674
  if (!buf.subarray(0, 8).equals(DISCRIMINATORS.TRADE_EVENT)) continue;
1615
2675
 
@@ -1635,11 +2695,11 @@ export class PumpTrader {
1635
2695
  isBuy,
1636
2696
  user: user.toBase58(),
1637
2697
  timestamp,
1638
- signature: log.signature
2698
+ signature: log.signature,
1639
2699
  });
1640
2700
  }
1641
2701
  },
1642
- "confirmed"
2702
+ "confirmed",
1643
2703
  );
1644
2704
  }
1645
2705
 
@@ -1653,18 +2713,22 @@ export class PumpTrader {
1653
2713
  this.connection,
1654
2714
  mint,
1655
2715
  "confirmed",
1656
- TOKEN_2022_PROGRAM_ID
2716
+ TOKEN_2022_PROGRAM_ID,
1657
2717
  );
1658
2718
 
1659
2719
  return {
1660
2720
  name: metadata?.name || "",
1661
2721
  symbol: metadata?.symbol || "",
1662
- uri: metadata?.uri || ""
2722
+ uri: metadata?.uri || "",
1663
2723
  };
1664
2724
  } catch (e) {
1665
2725
  const metadataPda = PublicKey.findProgramAddressSync(
1666
- [Buffer.from("metadata"), PROGRAM_IDS.METADATA.toBuffer(), mint.toBuffer()],
1667
- PROGRAM_IDS.METADATA
2726
+ [
2727
+ Buffer.from("metadata"),
2728
+ PROGRAM_IDS.METADATA.toBuffer(),
2729
+ mint.toBuffer(),
2730
+ ],
2731
+ PROGRAM_IDS.METADATA,
1668
2732
  )[0];
1669
2733
 
1670
2734
  const acc = await this.connection.getAccountInfo(metadataPda);
@@ -1673,9 +2737,9 @@ export class PumpTrader {
1673
2737
  const meta = parseMetadataAccount(acc.data);
1674
2738
 
1675
2739
  return {
1676
- name: meta?.name?.replace(/\u0000/g, '') || "",
1677
- symbol: meta?.symbol?.replace(/\u0000/g, '') || "",
1678
- uri: meta?.uri?.replace(/\u0000/g, '') || ""
2740
+ name: meta?.name?.replace(/\u0000/g, "") || "",
2741
+ symbol: meta?.symbol?.replace(/\u0000/g, "") || "",
2742
+ uri: meta?.uri?.replace(/\u0000/g, "") || "",
1679
2743
  };
1680
2744
  }
1681
2745
  }
@@ -1721,5 +2785,5 @@ export type {
1721
2785
  GlobalState,
1722
2786
  TokenProgramType,
1723
2787
  PoolInfo,
1724
- MetadataInfo
2788
+ MetadataInfo,
1725
2789
  };