pump-trader 1.1.5 → 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.
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";
@@ -64,9 +64,12 @@ interface BondingCurveState {
64
64
  realSolReserves: bigint;
65
65
  tokenTotalSupply: bigint;
66
66
  complete: boolean;
67
- quoteMint?: PublicKey;
68
67
  isMayhemMode?: boolean;
69
68
  isCashbackCoin?: boolean;
69
+ // V2 fields
70
+ quoteMint?: PublicKey;
71
+ virtualQuoteReserves?: bigint;
72
+ realQuoteReserves?: bigint;
70
73
  }
71
74
 
72
75
  interface BondingInfo {
@@ -129,33 +132,62 @@ const PROGRAM_IDS = {
129
132
  PUMP_AMM: new PublicKey("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"),
130
133
  METADATA: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
131
134
  FEE: new PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
132
- EVENT_AUTHORITY: new PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
135
+ EVENT_AUTHORITY: new PublicKey(
136
+ "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1",
137
+ ),
133
138
  };
134
139
 
135
140
  const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
136
141
 
137
142
  const SEEDS = {
138
143
  FEE_CONFIG: new Uint8Array([
139
- 1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
140
- 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,
141
146
  ]),
142
147
  AMM_FEE_CONFIG: Buffer.from([
143
- 12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101,
144
- 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,
145
150
  ]),
146
151
  GLOBAL: Buffer.from("global"),
147
- BONDING: Buffer.from("bonding-curve")
152
+ BONDING: Buffer.from("bonding-curve"),
148
153
  };
149
154
 
150
155
  const DISCRIMINATORS = {
151
156
  BUY: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]),
152
157
  SELL: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]),
153
- 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]),
154
164
  };
155
165
 
156
166
  const AMM_FEE_BPS = 100n;
157
167
  const BPS_DENOMINATOR = 10000n;
158
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 = [
159
191
  "5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
160
192
  "9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
161
193
  "GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
@@ -163,13 +195,14 @@ const PUMP_NEW_FEE_RECIPIENTS = [
163
195
  "5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
164
196
  "EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
165
197
  "5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
166
- "A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
198
+ "A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW",
167
199
  ].map((value) => new PublicKey(value));
168
200
 
169
201
  /* ================= 工具函数 ================= */
170
202
 
171
203
  const u64 = (v: bigint | BN | number): Buffer => {
172
- 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());
173
206
  return bn.toArrayLike(Buffer, "le", 8);
174
207
  };
175
208
 
@@ -186,7 +219,9 @@ const readU32 = (buf: Buffer, offsetObj: { offset: number }): number => {
186
219
 
187
220
  const readString = (buf: Buffer, offsetObj: { offset: number }): string => {
188
221
  const len = readU32(buf, offsetObj);
189
- 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");
190
225
  offsetObj.offset += len;
191
226
  return str;
192
227
  };
@@ -196,10 +231,14 @@ const readString = (buf: Buffer, offsetObj: { offset: number }): string => {
196
231
  function parseMetadataAccount(data: Buffer) {
197
232
  const offsetObj = { offset: 1 };
198
233
 
199
- 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
+ );
200
237
  offsetObj.offset += 32;
201
238
 
202
- 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
+ );
203
242
  offsetObj.offset += 32;
204
243
 
205
244
  const name = readString(data, offsetObj);
@@ -211,13 +250,13 @@ function parseMetadataAccount(data: Buffer) {
211
250
  mint: mint.toBase58(),
212
251
  name,
213
252
  symbol,
214
- uri
253
+ uri,
215
254
  };
216
255
  }
217
256
 
218
257
  function parsePoolKeys(data: Buffer) {
219
258
  if (!data || data.length < 280) {
220
- throw new Error('Invalid pool account data');
259
+ throw new Error("Invalid pool account data");
221
260
  }
222
261
 
223
262
  let offset = 8;
@@ -254,7 +293,8 @@ function parsePoolKeys(data: Buffer) {
254
293
 
255
294
  const isMayhemMode = data.readUInt8(offset) === 1;
256
295
  offset += 1;
257
- const isCashbackCoin = offset < data.length ? data.readUInt8(offset) === 1 : false;
296
+ const isCashbackCoin =
297
+ offset < data.length ? data.readUInt8(offset) === 1 : false;
258
298
 
259
299
  return {
260
300
  creator,
@@ -265,7 +305,7 @@ function parsePoolKeys(data: Buffer) {
265
305
  poolQuoteTokenAccount,
266
306
  coinCreator,
267
307
  isMayhemMode,
268
- isCashbackCoin
308
+ isCashbackCoin,
269
309
  };
270
310
  }
271
311
 
@@ -281,7 +321,10 @@ export class PumpTrader {
281
321
  constructor(rpc: string, privateKey: string) {
282
322
  this.connection = new Connection(rpc, "confirmed");
283
323
  this.wallet = Keypair.fromSecretKey(bs58.decode(privateKey));
284
- this.global = PublicKey.findProgramAddressSync([SEEDS.GLOBAL], PROGRAM_IDS.PUMP)[0];
324
+ this.global = PublicKey.findProgramAddressSync(
325
+ [SEEDS.GLOBAL],
326
+ PROGRAM_IDS.PUMP,
327
+ )[0];
285
328
  this.globalState = null;
286
329
  this.tokenProgramCache = new Map();
287
330
  }
@@ -301,29 +344,59 @@ export class PumpTrader {
301
344
 
302
345
  try {
303
346
  // 首先尝试获取 TOKEN_2022 的代币信息
304
- 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
+ );
305
353
  const result: TokenProgramType = {
306
354
  type: "TOKEN_2022_PROGRAM_ID",
307
- programId: TOKEN_2022_PROGRAM_ID
355
+ programId: TOKEN_2022_PROGRAM_ID,
308
356
  };
309
357
  this.tokenProgramCache.set(tokenAddr, result);
310
358
  return result;
311
359
  } catch (e) {
312
360
  try {
313
361
  // 如果失败,尝试标准 TOKEN_PROGRAM_ID
314
- 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
+ );
315
368
  const result: TokenProgramType = {
316
369
  type: "TOKEN_PROGRAM_ID",
317
- programId: TOKEN_PROGRAM_ID
370
+ programId: TOKEN_PROGRAM_ID,
318
371
  };
319
372
  this.tokenProgramCache.set(tokenAddr, result);
320
373
  return result;
321
374
  } catch (error) {
322
- 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
+ );
323
378
  }
324
379
  }
325
380
  }
326
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
+
327
400
  /* ---------- 内盘/外盘检测 ---------- */
328
401
 
329
402
  /**
@@ -384,7 +457,7 @@ export class PumpTrader {
384
457
  initialVirtualSolReserves: readU64(),
385
458
  initialRealTokenReserves: readU64(),
386
459
  tokenTotalSupply: readU64(),
387
- feeBasisPoints: readU64()
460
+ feeBasisPoints: readU64(),
388
461
  };
389
462
 
390
463
  return this.globalState;
@@ -395,14 +468,14 @@ export class PumpTrader {
395
468
  getBondingPda(mint: PublicKey): PublicKey {
396
469
  return PublicKey.findProgramAddressSync(
397
470
  [SEEDS.BONDING, mint.toBuffer()],
398
- PROGRAM_IDS.PUMP
471
+ PROGRAM_IDS.PUMP,
399
472
  )[0];
400
473
  }
401
474
 
402
475
  deriveBondingCurveV2(mint: PublicKey): PublicKey {
403
476
  return PublicKey.findProgramAddressSync(
404
477
  [Buffer.from("bonding-curve-v2"), mint.toBuffer()],
405
- PROGRAM_IDS.PUMP
478
+ PROGRAM_IDS.PUMP,
406
479
  )[0];
407
480
  }
408
481
 
@@ -410,6 +483,25 @@ export class PumpTrader {
410
483
  return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
411
484
  }
412
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
+
413
505
  private buildBondingBuyKeys(args: {
414
506
  global: PublicKey;
415
507
  globalFeeRecipient: PublicKey;
@@ -436,7 +528,11 @@ export class PumpTrader {
436
528
  { pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
437
529
  { pubkey: args.mint, isSigner: false, isWritable: false },
438
530
  { pubkey: args.bonding, isSigner: false, isWritable: true },
439
- { pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
531
+ {
532
+ pubkey: args.associatedBondingCurve,
533
+ isSigner: false,
534
+ isWritable: true,
535
+ },
440
536
  { pubkey: args.userAta, isSigner: false, isWritable: true },
441
537
  { pubkey: args.wallet, isSigner: true, isWritable: true },
442
538
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
@@ -444,12 +540,16 @@ export class PumpTrader {
444
540
  { pubkey: args.creatorVault, isSigner: false, isWritable: true },
445
541
  { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
446
542
  { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
447
- { pubkey: args.globalVolumeAccumulator, isSigner: false, isWritable: false },
543
+ {
544
+ pubkey: args.globalVolumeAccumulator,
545
+ isSigner: false,
546
+ isWritable: false,
547
+ },
448
548
  { pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
449
549
  { pubkey: args.feeConfig, isSigner: false, isWritable: false },
450
550
  { pubkey: args.feeProgram, isSigner: false, isWritable: false },
451
551
  { pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
452
- { pubkey: args.feeRecipient, isSigner: false, isWritable: true }
552
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true },
453
553
  ];
454
554
  }
455
555
 
@@ -478,7 +578,11 @@ export class PumpTrader {
478
578
  { pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
479
579
  { pubkey: args.mint, isSigner: false, isWritable: false },
480
580
  { pubkey: args.bonding, isSigner: false, isWritable: true },
481
- { pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
581
+ {
582
+ pubkey: args.associatedBondingCurve,
583
+ isSigner: false,
584
+ isWritable: true,
585
+ },
482
586
  { pubkey: args.userAta, isSigner: false, isWritable: true },
483
587
  { pubkey: args.wallet, isSigner: true, isWritable: true },
484
588
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
@@ -487,16 +591,20 @@ export class PumpTrader {
487
591
  { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
488
592
  { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
489
593
  { pubkey: args.feeConfig, isSigner: false, isWritable: false },
490
- { pubkey: args.feeProgram, isSigner: false, isWritable: false }
594
+ { pubkey: args.feeProgram, isSigner: false, isWritable: false },
491
595
  ];
492
596
 
493
597
  if (args.isCashbackCoin) {
494
- keys.push({ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true });
598
+ keys.push({
599
+ pubkey: args.userVolumeAccumulator,
600
+ isSigner: false,
601
+ isWritable: true,
602
+ });
495
603
  }
496
604
 
497
605
  keys.push(
498
606
  { pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
499
- { pubkey: args.feeRecipient, isSigner: false, isWritable: true }
607
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true },
500
608
  );
501
609
 
502
610
  return keys;
@@ -522,10 +630,18 @@ export class PumpTrader {
522
630
  const creator = new PublicKey(data.slice(offset, offset + 32));
523
631
  offset += 32;
524
632
 
525
- if (offset + 32 <= data.length - 2) {
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) {
526
636
  state.quoteMint = new PublicKey(data.slice(offset, offset + 32));
527
637
  offset += 32;
528
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
+ }
529
645
 
530
646
  state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
531
647
  offset += 1;
@@ -538,25 +654,29 @@ export class PumpTrader {
538
654
 
539
655
  calcBuy(solIn: bigint, state: BondingCurveState): bigint {
540
656
  const newVirtualSol = state.virtualSolReserves + solIn;
541
- const newVirtualToken = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualSol;
657
+ const newVirtualToken =
658
+ (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualSol;
542
659
  return state.virtualTokenReserves - newVirtualToken;
543
660
  }
544
661
 
545
662
  calcSell(tokenIn: bigint, state: BondingCurveState): bigint {
546
663
  const newVirtualToken = state.virtualTokenReserves + tokenIn;
547
- const newVirtualSol = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualToken;
664
+ const newVirtualSol =
665
+ (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualToken;
548
666
  return state.virtualSolReserves - newVirtualSol;
549
667
  }
550
668
 
551
669
  calculateAmmBuyOutput(quoteIn: bigint, reserves: PoolReserves): bigint {
552
- const quoteInAfterFee = (quoteIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
670
+ const quoteInAfterFee =
671
+ (quoteIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
553
672
  const numerator = reserves.baseAmount * quoteInAfterFee;
554
673
  const denominator = reserves.quoteAmount + quoteInAfterFee;
555
674
  return numerator / denominator;
556
675
  }
557
676
 
558
677
  calculateAmmSellOutput(baseIn: bigint, reserves: PoolReserves): bigint {
559
- const baseInAfterFee = (baseIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
678
+ const baseInAfterFee =
679
+ (baseIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
560
680
  const numerator = reserves.quoteAmount * baseInAfterFee;
561
681
  const denominator = reserves.baseAmount + baseInAfterFee;
562
682
  return numerator / denominator;
@@ -564,33 +684,39 @@ export class PumpTrader {
564
684
 
565
685
  /* ---------- 价格查询 ---------- */
566
686
 
567
- async getPriceAndStatus(tokenAddr: string): Promise<{ price: number; completed: boolean }> {
687
+ async getPriceAndStatus(
688
+ tokenAddr: string,
689
+ ): Promise<{ price: number; completed: boolean }> {
568
690
  const mint = new PublicKey(tokenAddr);
569
691
  const { state } = await this.loadBonding(mint);
570
- const quoteMint = this.getEffectiveQuoteMint(state.quoteMint);
571
692
 
572
693
  if (state.complete) {
573
- const price = await this.getAmmPrice(mint, quoteMint);
694
+ const price = await this.getAmmPrice(mint);
574
695
  return { price, completed: true };
575
696
  }
576
697
 
577
698
  const oneToken = BigInt(1_000_000);
578
- const quoteOut = this.calcSell(oneToken, state);
579
- const quoteDecimals = await this.getMintDecimals(quoteMint);
580
- const price = Number(quoteOut) / 10 ** quoteDecimals;
699
+ const solOut = this.calcSell(oneToken, state);
700
+ const price = Number(solOut) / 1e9;
581
701
  return { price, completed: false };
582
702
  }
583
703
 
584
- async getAmmPrice(mint: PublicKey, quoteMint: PublicKey = SOL_MINT): Promise<number> {
704
+ async getAmmPrice(mint: PublicKey): Promise<number> {
585
705
  const [poolCreator] = PublicKey.findProgramAddressSync(
586
706
  [Buffer.from("pool-authority"), mint.toBuffer()],
587
- PROGRAM_IDS.PUMP
707
+ PROGRAM_IDS.PUMP,
588
708
  );
589
709
 
590
710
  const indexBuffer = new BN(0).toArrayLike(Buffer, "le", 2);
591
711
  const [pool] = PublicKey.findProgramAddressSync(
592
- [Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(), quoteMint.toBuffer()],
593
- 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,
594
720
  );
595
721
 
596
722
  const acc = await this.connection.getAccountInfo(pool);
@@ -599,34 +725,12 @@ export class PumpTrader {
599
725
  const poolKeys = parsePoolKeys(acc.data);
600
726
  const [baseInfo, quoteInfo] = await Promise.all([
601
727
  this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
602
- this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
728
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
603
729
  ]);
604
730
 
605
731
  return quoteInfo.value.uiAmount! / baseInfo.value.uiAmount!;
606
732
  }
607
733
 
608
- private getEffectiveQuoteMint(quoteMint?: PublicKey): PublicKey {
609
- if (!quoteMint || quoteMint.equals(PublicKey.default)) {
610
- return SOL_MINT;
611
- }
612
- return quoteMint;
613
- }
614
-
615
- private async getMintDecimals(mint: PublicKey): Promise<number> {
616
- if (mint.equals(SOL_MINT)) {
617
- return 9;
618
- }
619
-
620
- const mintInfo = await this.connection.getParsedAccountInfo(mint);
621
- const parsedData = mintInfo.value?.data;
622
-
623
- if (parsedData && "parsed" in parsedData) {
624
- return parsedData.parsed.info.decimals;
625
- }
626
-
627
- throw new Error(`Unable to determine mint decimals for ${mint.toBase58()}`);
628
- }
629
-
630
734
  /* ---------- 余额查询 ---------- */
631
735
 
632
736
  /**
@@ -634,15 +738,26 @@ export class PumpTrader {
634
738
  * @param tokenAddr - 代币地址(可选),如果不传则返回所有代币
635
739
  * @returns 如果传入地址则返回该代币的余额数字,否则返回所有代币的详细信息
636
740
  */
637
- 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
+ > {
638
750
  if (tokenAddr) {
639
751
  // 查询单个代币
640
752
  const mint = new PublicKey(tokenAddr);
641
753
  const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
642
754
  this.wallet.publicKey,
643
- { mint }
755
+ { mint },
756
+ );
757
+ return (
758
+ tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount ||
759
+ 0
644
760
  );
645
- return tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount || 0;
646
761
  } else {
647
762
  // 查询所有代币
648
763
  return this.getAllTokenBalances();
@@ -653,16 +768,18 @@ export class PumpTrader {
653
768
  * 获取账户所有代币余额(仅显示余额 > 0 的)
654
769
  * @returns 代币信息数组,包含mint地址、余额等信息
655
770
  */
656
- 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
+ > {
657
774
  const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
658
775
  this.wallet.publicKey,
659
- { programId: TOKEN_PROGRAM_ID }
776
+ { programId: TOKEN_PROGRAM_ID },
660
777
  );
661
778
 
662
779
  const balances = tokenAccounts.value
663
780
  .map((account) => {
664
781
  const parsed = account.account.data.parsed;
665
- if (parsed.type !== 'account') return null;
782
+ if (parsed.type !== "account") return null;
666
783
 
667
784
  const tokenAmount = parsed.info.tokenAmount;
668
785
  if (Number(tokenAmount.amount) === 0) return null; // 跳过余额为0的
@@ -671,21 +788,27 @@ export class PumpTrader {
671
788
  mint: parsed.info.mint,
672
789
  amount: BigInt(tokenAmount.amount),
673
790
  decimals: tokenAmount.decimals,
674
- uiAmount: tokenAmount.uiAmount || 0
791
+ uiAmount: tokenAmount.uiAmount || 0,
675
792
  };
676
793
  })
677
- .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
+ }>;
678
800
 
679
801
  // 同时查询 TOKEN_2022_PROGRAM_ID
680
- const token2022Accounts = await this.connection.getParsedTokenAccountsByOwner(
681
- this.wallet.publicKey,
682
- { programId: TOKEN_2022_PROGRAM_ID }
683
- );
802
+ const token2022Accounts =
803
+ await this.connection.getParsedTokenAccountsByOwner(
804
+ this.wallet.publicKey,
805
+ { programId: TOKEN_2022_PROGRAM_ID },
806
+ );
684
807
 
685
808
  const token2022Balances = token2022Accounts.value
686
809
  .map((account) => {
687
810
  const parsed = account.account.data.parsed;
688
- if (parsed.type !== 'account') return null;
811
+ if (parsed.type !== "account") return null;
689
812
 
690
813
  const tokenAmount = parsed.info.tokenAmount;
691
814
  if (Number(tokenAmount.amount) === 0) return null; // 跳过余额为0的
@@ -694,10 +817,15 @@ export class PumpTrader {
694
817
  mint: parsed.info.mint,
695
818
  amount: BigInt(tokenAmount.amount),
696
819
  decimals: tokenAmount.decimals,
697
- uiAmount: tokenAmount.uiAmount || 0
820
+ uiAmount: tokenAmount.uiAmount || 0,
698
821
  };
699
822
  })
700
- .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
+ }>;
701
829
 
702
830
  // 合并并去重
703
831
  const allBalances = [...balances, ...token2022Balances];
@@ -712,7 +840,7 @@ export class PumpTrader {
712
840
  mint: b.mint,
713
841
  amount: Number(b.amount),
714
842
  decimals: b.decimals,
715
- uiAmount: b.uiAmount
843
+ uiAmount: b.uiAmount,
716
844
  }));
717
845
 
718
846
  return uniqueBalances;
@@ -725,14 +853,18 @@ export class PumpTrader {
725
853
 
726
854
  /* ---------- ATA 管理 ---------- */
727
855
 
728
- 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> {
729
861
  const program = tokenProgram || TOKEN_2022_PROGRAM_ID;
730
862
  const ata = getAssociatedTokenAddressSync(
731
863
  mint,
732
864
  this.wallet.publicKey,
733
865
  false,
734
866
  program,
735
- ASSOCIATED_TOKEN_PROGRAM_ID
867
+ ASSOCIATED_TOKEN_PROGRAM_ID,
736
868
  );
737
869
 
738
870
  const acc = await this.connection.getAccountInfo(ata);
@@ -744,8 +876,8 @@ export class PumpTrader {
744
876
  this.wallet.publicKey,
745
877
  mint,
746
878
  program,
747
- ASSOCIATED_TOKEN_PROGRAM_ID
748
- )
879
+ ASSOCIATED_TOKEN_PROGRAM_ID,
880
+ ),
749
881
  );
750
882
  }
751
883
 
@@ -756,14 +888,14 @@ export class PumpTrader {
756
888
  tx: Transaction,
757
889
  owner: PublicKey,
758
890
  mode: "buy" | "sell",
759
- lamports?: bigint
891
+ lamports?: bigint,
760
892
  ): Promise<PublicKey> {
761
893
  const wsolAta = getAssociatedTokenAddressSync(
762
894
  SOL_MINT,
763
895
  owner,
764
896
  false,
765
897
  TOKEN_PROGRAM_ID,
766
- ASSOCIATED_TOKEN_PROGRAM_ID
898
+ ASSOCIATED_TOKEN_PROGRAM_ID,
767
899
  );
768
900
 
769
901
  const acc = await this.connection.getAccountInfo(wsolAta);
@@ -776,18 +908,18 @@ export class PumpTrader {
776
908
  owner,
777
909
  SOL_MINT,
778
910
  TOKEN_PROGRAM_ID,
779
- ASSOCIATED_TOKEN_PROGRAM_ID
780
- )
911
+ ASSOCIATED_TOKEN_PROGRAM_ID,
912
+ ),
781
913
  );
782
914
  }
783
915
 
784
- if (mode === 'buy' && lamports) {
916
+ if (mode === "buy" && lamports) {
785
917
  tx.add(
786
918
  SystemProgram.transfer({
787
919
  fromPubkey: owner,
788
920
  toPubkey: wsolAta,
789
- lamports: Number(lamports)
790
- })
921
+ lamports: Number(lamports),
922
+ }),
791
923
  );
792
924
  tx.add(createSyncNativeInstruction(wsolAta));
793
925
  }
@@ -801,7 +933,9 @@ export class PumpTrader {
801
933
  if (!priorityOpt?.enableRandom || !priorityOpt.randomRange) {
802
934
  return priorityOpt.base;
803
935
  }
804
- return priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange);
936
+ return (
937
+ priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange)
938
+ );
805
939
  }
806
940
 
807
941
  calcSlippage({ tradeSize, reserve, slippageOpt }: any): number {
@@ -849,31 +983,57 @@ export class PumpTrader {
849
983
 
850
984
  /**
851
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)
852
988
  */
853
- 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> {
854
996
  const mode = await this.getTradeMode(tokenAddr);
855
997
  if (mode === "bonding") {
998
+ if (useV2) {
999
+ return this.buyV2(tokenAddr, totalSolIn, tradeOpt, quoteMint);
1000
+ }
856
1001
  return this.buy(tokenAddr, totalSolIn, tradeOpt);
857
1002
  } else {
858
- return this.ammBuy(tokenAddr, totalSolIn, tradeOpt);
1003
+ return this.ammBuy(tokenAddr, totalSolIn, tradeOpt, quoteMint);
859
1004
  }
860
1005
  }
861
1006
 
862
1007
  /**
863
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)
864
1011
  */
865
- 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> {
866
1019
  const mode = await this.getTradeMode(tokenAddr);
867
1020
  if (mode === "bonding") {
1021
+ if (useV2) {
1022
+ return this.sellV2(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
1023
+ }
868
1024
  return this.sell(tokenAddr, totalTokenIn, tradeOpt);
869
1025
  } else {
870
- return this.ammSell(tokenAddr, totalTokenIn, tradeOpt);
1026
+ return this.ammSell(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
871
1027
  }
872
1028
  }
873
1029
 
874
1030
  /* ---------- 内盘交易 ---------- */
875
1031
 
876
- 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> {
877
1037
  const mint = new PublicKey(tokenAddr);
878
1038
  const tokenProgram = await this.detectTokenProgram(tokenAddr);
879
1039
  const bondingCurveV2 = this.deriveBondingCurveV2(mint);
@@ -892,27 +1052,30 @@ export class PumpTrader {
892
1052
  bonding,
893
1053
  true,
894
1054
  tokenProgram.programId,
895
- ASSOCIATED_TOKEN_PROGRAM_ID
1055
+ ASSOCIATED_TOKEN_PROGRAM_ID,
896
1056
  );
897
1057
 
898
1058
  const [creatorVault] = PublicKey.findProgramAddressSync(
899
1059
  [Buffer.from("creator-vault"), creator.toBuffer()],
900
- PROGRAM_IDS.PUMP
1060
+ PROGRAM_IDS.PUMP,
901
1061
  );
902
1062
 
903
1063
  const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
904
1064
  [Buffer.from("global_volume_accumulator")],
905
- PROGRAM_IDS.PUMP
1065
+ PROGRAM_IDS.PUMP,
906
1066
  );
907
1067
 
908
1068
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
909
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
910
- PROGRAM_IDS.PUMP
1069
+ [
1070
+ Buffer.from("user_volume_accumulator"),
1071
+ this.wallet.publicKey.toBuffer(),
1072
+ ],
1073
+ PROGRAM_IDS.PUMP,
911
1074
  );
912
1075
 
913
1076
  const [feeConfig] = PublicKey.findProgramAddressSync(
914
1077
  [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
915
- PROGRAM_IDS.FEE
1078
+ PROGRAM_IDS.FEE,
916
1079
  );
917
1080
  const feeRecipient = this.pickFeeRecipient();
918
1081
 
@@ -923,14 +1086,14 @@ export class PumpTrader {
923
1086
  const slippageBps = this.calcSlippage({
924
1087
  tradeSize: solIn,
925
1088
  reserve: state.virtualSolReserves,
926
- slippageOpt: tradeOpt.slippage
1089
+ slippageOpt: tradeOpt.slippage,
927
1090
  });
928
1091
  const maxSol = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
929
1092
  const priority = this.genPriority(tradeOpt.priority);
930
1093
 
931
1094
  const tx = new Transaction().add(
932
1095
  ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
933
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1096
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
934
1097
  );
935
1098
 
936
1099
  const userAta = await this.ensureAta(tx, mint, tokenProgram.programId);
@@ -955,32 +1118,39 @@ export class PumpTrader {
955
1118
  feeProgram: PROGRAM_IDS.FEE,
956
1119
  bondingCurveV2,
957
1120
  feeRecipient,
958
- tokenProgramId: tokenProgram.programId
1121
+ tokenProgramId: tokenProgram.programId,
959
1122
  }),
960
- data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
961
- })
1123
+ data: Buffer.concat([
1124
+ DISCRIMINATORS.BUY,
1125
+ u64(tokenOut),
1126
+ u64(maxSol),
1127
+ ]),
1128
+ }),
962
1129
  );
963
1130
 
964
1131
  const { blockhash, lastValidBlockHeight } =
965
- await this.connection.getLatestBlockhash('finalized');
1132
+ await this.connection.getLatestBlockhash("finalized");
966
1133
  tx.recentBlockhash = blockhash;
967
1134
  tx.feePayer = this.wallet.publicKey;
968
1135
  tx.sign(this.wallet);
969
1136
 
970
- const signature = await this.connection.sendRawTransaction(tx.serialize(), {
971
- skipPreflight: false,
972
- maxRetries: 2
973
- });
1137
+ const signature = await this.connection.sendRawTransaction(
1138
+ tx.serialize(),
1139
+ {
1140
+ skipPreflight: false,
1141
+ maxRetries: 2,
1142
+ },
1143
+ );
974
1144
 
975
1145
  pendingTransactions.push({
976
1146
  signature,
977
1147
  lastValidBlockHeight,
978
- index: i
1148
+ index: i,
979
1149
  });
980
1150
  } catch (e) {
981
1151
  failedTransactions.push({
982
1152
  index: i,
983
- error: (e as Error).message
1153
+ error: (e as Error).message,
984
1154
  });
985
1155
  }
986
1156
  }
@@ -988,7 +1158,11 @@ export class PumpTrader {
988
1158
  return { pendingTransactions, failedTransactions };
989
1159
  }
990
1160
 
991
- 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> {
992
1166
  const mint = new PublicKey(tokenAddr);
993
1167
  const tokenProgram = await this.detectTokenProgram(tokenAddr);
994
1168
  const bondingCurveV2 = this.deriveBondingCurveV2(mint);
@@ -999,12 +1173,15 @@ export class PumpTrader {
999
1173
  if (state.complete) throw new Error("Bonding curve already completed");
1000
1174
 
1001
1175
  const totalSolOut = this.calcSell(totalTokenIn, state);
1002
- const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
1003
- ? [totalTokenIn]
1004
- : this.splitIntoN(
1005
- totalTokenIn,
1006
- Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx)
1007
- );
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
+ );
1008
1185
 
1009
1186
  const pendingTransactions: PendingTransaction[] = [];
1010
1187
  const failedTransactions: FailedTransaction[] = [];
@@ -1014,7 +1191,7 @@ export class PumpTrader {
1014
1191
  bonding,
1015
1192
  true,
1016
1193
  tokenProgram.programId,
1017
- ASSOCIATED_TOKEN_PROGRAM_ID
1194
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1018
1195
  );
1019
1196
 
1020
1197
  const userAta = getAssociatedTokenAddressSync(
@@ -1022,22 +1199,25 @@ export class PumpTrader {
1022
1199
  this.wallet.publicKey,
1023
1200
  false,
1024
1201
  tokenProgram.programId,
1025
- ASSOCIATED_TOKEN_PROGRAM_ID
1202
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1026
1203
  );
1027
1204
 
1028
1205
  const [creatorVault] = PublicKey.findProgramAddressSync(
1029
1206
  [Buffer.from("creator-vault"), creator.toBuffer()],
1030
- PROGRAM_IDS.PUMP
1207
+ PROGRAM_IDS.PUMP,
1031
1208
  );
1032
1209
 
1033
1210
  const [feeConfig] = PublicKey.findProgramAddressSync(
1034
1211
  [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
1035
- PROGRAM_IDS.FEE
1212
+ PROGRAM_IDS.FEE,
1036
1213
  );
1037
1214
 
1038
1215
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1039
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
1040
- PROGRAM_IDS.PUMP
1216
+ [
1217
+ Buffer.from("user_volume_accumulator"),
1218
+ this.wallet.publicKey.toBuffer(),
1219
+ ],
1220
+ PROGRAM_IDS.PUMP,
1041
1221
  );
1042
1222
  const feeRecipient = this.pickFeeRecipient();
1043
1223
 
@@ -1048,14 +1228,14 @@ export class PumpTrader {
1048
1228
  const slippageBps = this.calcSlippage({
1049
1229
  tradeSize: tokenIn,
1050
1230
  reserve: state.virtualTokenReserves,
1051
- slippageOpt: tradeOpt.slippage
1231
+ slippageOpt: tradeOpt.slippage,
1052
1232
  });
1053
1233
  const minSol = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
1054
1234
  const priority = this.genPriority(tradeOpt.priority);
1055
1235
 
1056
1236
  const tx = new Transaction().add(
1057
1237
  ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
1058
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1238
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
1059
1239
  );
1060
1240
 
1061
1241
  tx.add(
@@ -1078,14 +1258,14 @@ export class PumpTrader {
1078
1258
  feeRecipient,
1079
1259
  isCashbackCoin: !!state.isCashbackCoin,
1080
1260
  userVolumeAccumulator,
1081
- tokenProgramId: tokenProgram.programId
1261
+ tokenProgramId: tokenProgram.programId,
1082
1262
  }),
1083
1263
  data: Buffer.concat([
1084
1264
  DISCRIMINATORS.SELL,
1085
1265
  u64(tokenIn),
1086
- u64(minSol > 0n ? minSol : 1n)
1087
- ])
1088
- })
1266
+ u64(minSol > 0n ? minSol : 1n),
1267
+ ]),
1268
+ }),
1089
1269
  );
1090
1270
 
1091
1271
  const { blockhash, lastValidBlockHeight } =
@@ -1094,16 +1274,18 @@ export class PumpTrader {
1094
1274
  tx.feePayer = this.wallet.publicKey;
1095
1275
  tx.sign(this.wallet);
1096
1276
 
1097
- const signature = await this.connection.sendRawTransaction(tx.serialize());
1277
+ const signature = await this.connection.sendRawTransaction(
1278
+ tx.serialize(),
1279
+ );
1098
1280
  pendingTransactions.push({
1099
1281
  signature,
1100
1282
  lastValidBlockHeight,
1101
- index: i
1283
+ index: i,
1102
1284
  });
1103
1285
  } catch (e) {
1104
1286
  failedTransactions.push({
1105
1287
  index: i,
1106
- error: (e as Error).message
1288
+ error: (e as Error).message,
1107
1289
  });
1108
1290
  }
1109
1291
  }
@@ -1113,12 +1295,21 @@ export class PumpTrader {
1113
1295
 
1114
1296
  /* ---------- 外盘交易 ---------- */
1115
1297
 
1116
- 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> {
1117
1304
  const mint = new PublicKey(tokenAddr);
1118
- const poolInfo = await this.getAmmPoolInfo(mint);
1305
+ const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
1119
1306
  const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
1120
1307
  const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
1121
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);
1122
1313
  const pendingTransactions: PendingTransaction[] = [];
1123
1314
  const failedTransactions: FailedTransaction[] = [];
1124
1315
 
@@ -1129,23 +1320,29 @@ export class PumpTrader {
1129
1320
  const slippageBps = this.calcSlippage({
1130
1321
  tradeSize: solIn,
1131
1322
  reserve: reserves.quoteAmount,
1132
- slippageOpt: tradeOpt.slippage
1323
+ slippageOpt: tradeOpt.slippage,
1133
1324
  });
1134
1325
  const maxQuoteIn = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
1135
1326
  const priority = this.genPriority(tradeOpt.priority);
1136
1327
 
1137
1328
  const tx = new Transaction().add(
1138
1329
  ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
1139
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1330
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
1140
1331
  );
1141
1332
 
1142
- const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint, tokenProgram.programId);
1143
- const userQuoteAta = await this.ensureWSOLAta(
1333
+ const userBaseAta = await this.ensureAta(
1144
1334
  tx,
1145
- this.wallet.publicKey,
1146
- "buy",
1147
- maxQuoteIn
1335
+ poolInfo.poolKeys.baseMint,
1336
+ tokenProgram.programId,
1148
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);
1149
1346
 
1150
1347
  const buyIx = this.createAmmBuyInstruction(
1151
1348
  poolInfo,
@@ -1153,38 +1350,43 @@ export class PumpTrader {
1153
1350
  userQuoteAta,
1154
1351
  baseAmountOut,
1155
1352
  maxQuoteIn,
1156
- tokenProgram.programId
1353
+ tokenProgram.programId,
1157
1354
  );
1158
1355
 
1159
1356
  tx.add(buyIx);
1160
- tx.add(
1161
- createCloseAccountInstruction(
1162
- userQuoteAta,
1163
- this.wallet.publicKey,
1164
- this.wallet.publicKey
1165
- )
1166
- );
1357
+ if (isSolQuote) {
1358
+ tx.add(
1359
+ createCloseAccountInstruction(
1360
+ userQuoteAta,
1361
+ this.wallet.publicKey,
1362
+ this.wallet.publicKey,
1363
+ ),
1364
+ );
1365
+ }
1167
1366
 
1168
1367
  const { blockhash, lastValidBlockHeight } =
1169
- await this.connection.getLatestBlockhash('finalized');
1368
+ await this.connection.getLatestBlockhash("finalized");
1170
1369
  tx.recentBlockhash = blockhash;
1171
1370
  tx.feePayer = this.wallet.publicKey;
1172
1371
  tx.sign(this.wallet);
1173
1372
 
1174
- const signature = await this.connection.sendRawTransaction(tx.serialize(), {
1175
- skipPreflight: false,
1176
- maxRetries: 2
1177
- });
1373
+ const signature = await this.connection.sendRawTransaction(
1374
+ tx.serialize(),
1375
+ {
1376
+ skipPreflight: false,
1377
+ maxRetries: 2,
1378
+ },
1379
+ );
1178
1380
 
1179
1381
  pendingTransactions.push({
1180
1382
  signature,
1181
1383
  lastValidBlockHeight,
1182
- index: i
1384
+ index: i,
1183
1385
  });
1184
1386
  } catch (e) {
1185
1387
  failedTransactions.push({
1186
1388
  index: i,
1187
- error: (e as Error).message
1389
+ error: (e as Error).message,
1188
1390
  });
1189
1391
  }
1190
1392
  }
@@ -1192,18 +1394,30 @@ export class PumpTrader {
1192
1394
  return { pendingTransactions, failedTransactions };
1193
1395
  }
1194
1396
 
1195
- 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> {
1196
1403
  const mint = new PublicKey(tokenAddr);
1197
- const poolInfo = await this.getAmmPoolInfo(mint);
1404
+ const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
1198
1405
  const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
1199
1406
  const totalSolOut = this.calculateAmmSellOutput(totalTokenIn, reserves);
1200
1407
  const tokenProgram = await this.detectTokenProgram(tokenAddr);
1201
- const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
1202
- ? [totalTokenIn]
1203
- : this.splitIntoN(
1204
- totalTokenIn,
1205
- Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx)
1206
- );
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
+ );
1207
1421
 
1208
1422
  const pendingTransactions: PendingTransaction[] = [];
1209
1423
  const failedTransactions: FailedTransaction[] = [];
@@ -1215,18 +1429,24 @@ export class PumpTrader {
1215
1429
  const slippageBps = this.calcSlippage({
1216
1430
  tradeSize: tokenIn,
1217
1431
  reserve: reserves.baseAmount,
1218
- slippageOpt: tradeOpt.slippage
1432
+ slippageOpt: tradeOpt.slippage,
1219
1433
  });
1220
1434
  const minQuoteOut = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
1221
1435
  const priority = this.genPriority(tradeOpt.priority);
1222
1436
 
1223
1437
  const tx = new Transaction().add(
1224
1438
  ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
1225
- ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
1439
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
1226
1440
  );
1227
1441
 
1228
- const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint, tokenProgram.programId);
1229
- 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);
1230
1450
 
1231
1451
  const sellIx = this.createAmmSellInstruction(
1232
1452
  poolInfo,
@@ -1234,38 +1454,43 @@ export class PumpTrader {
1234
1454
  userQuoteAta,
1235
1455
  tokenIn,
1236
1456
  minQuoteOut,
1237
- tokenProgram.programId
1457
+ tokenProgram.programId,
1238
1458
  );
1239
1459
 
1240
1460
  tx.add(sellIx);
1241
- tx.add(
1242
- createCloseAccountInstruction(
1243
- userQuoteAta,
1244
- this.wallet.publicKey,
1245
- this.wallet.publicKey
1246
- )
1247
- );
1461
+ if (isSolQuote) {
1462
+ tx.add(
1463
+ createCloseAccountInstruction(
1464
+ userQuoteAta,
1465
+ this.wallet.publicKey,
1466
+ this.wallet.publicKey,
1467
+ ),
1468
+ );
1469
+ }
1248
1470
 
1249
1471
  const { blockhash, lastValidBlockHeight } =
1250
- await this.connection.getLatestBlockhash('finalized');
1472
+ await this.connection.getLatestBlockhash("finalized");
1251
1473
  tx.recentBlockhash = blockhash;
1252
1474
  tx.feePayer = this.wallet.publicKey;
1253
1475
  tx.sign(this.wallet);
1254
1476
 
1255
- const signature = await this.connection.sendRawTransaction(tx.serialize(), {
1256
- skipPreflight: false,
1257
- maxRetries: 2
1258
- });
1477
+ const signature = await this.connection.sendRawTransaction(
1478
+ tx.serialize(),
1479
+ {
1480
+ skipPreflight: false,
1481
+ maxRetries: 2,
1482
+ },
1483
+ );
1259
1484
 
1260
1485
  pendingTransactions.push({
1261
1486
  signature,
1262
1487
  lastValidBlockHeight,
1263
- index: i
1488
+ index: i,
1264
1489
  });
1265
1490
  } catch (e) {
1266
1491
  failedTransactions.push({
1267
1492
  index: i,
1268
- error: (e as Error).message
1493
+ error: (e as Error).message,
1269
1494
  });
1270
1495
  }
1271
1496
  }
@@ -1275,10 +1500,13 @@ export class PumpTrader {
1275
1500
 
1276
1501
  /* ---------- AMM 池信息 ---------- */
1277
1502
 
1278
- async getAmmPoolInfo(mint: PublicKey): Promise<PoolInfo> {
1503
+ async getAmmPoolInfo(
1504
+ mint: PublicKey,
1505
+ quoteMint: PublicKey = SOL_MINT,
1506
+ ): Promise<PoolInfo> {
1279
1507
  const [poolAuthority] = PublicKey.findProgramAddressSync(
1280
1508
  [Buffer.from("pool-authority"), mint.toBuffer()],
1281
- PROGRAM_IDS.PUMP
1509
+ PROGRAM_IDS.PUMP,
1282
1510
  );
1283
1511
 
1284
1512
  const [pool] = PublicKey.findProgramAddressSync(
@@ -1287,9 +1515,9 @@ export class PumpTrader {
1287
1515
  new BN(0).toArrayLike(Buffer, "le", 2),
1288
1516
  poolAuthority.toBuffer(),
1289
1517
  mint.toBuffer(),
1290
- SOL_MINT.toBuffer()
1518
+ quoteMint.toBuffer(),
1291
1519
  ],
1292
- PROGRAM_IDS.PUMP_AMM
1520
+ PROGRAM_IDS.PUMP_AMM,
1293
1521
  );
1294
1522
 
1295
1523
  const acc = await this.connection.getAccountInfo(pool);
@@ -1299,13 +1527,17 @@ export class PumpTrader {
1299
1527
 
1300
1528
  const [globalConfigPda] = PublicKey.findProgramAddressSync(
1301
1529
  [Buffer.from("global_config")],
1302
- PROGRAM_IDS.PUMP_AMM
1530
+ PROGRAM_IDS.PUMP_AMM,
1303
1531
  );
1304
1532
 
1305
- const globalConfigAcc = await this.connection.getAccountInfo(globalConfigPda);
1533
+ const globalConfigAcc =
1534
+ await this.connection.getAccountInfo(globalConfigPda);
1306
1535
  if (!globalConfigAcc) throw new Error("Global config not found");
1307
1536
 
1308
- const globalConfig = this.parseAmmGlobalConfig(globalConfigAcc.data, globalConfigPda);
1537
+ const globalConfig = this.parseAmmGlobalConfig(
1538
+ globalConfigAcc.data,
1539
+ globalConfigPda,
1540
+ );
1309
1541
 
1310
1542
  return { pool, poolAuthority, poolKeys, globalConfig };
1311
1543
  }
@@ -1322,7 +1554,9 @@ export class PumpTrader {
1322
1554
 
1323
1555
  const protocolFeeRecipients: PublicKey[] = [];
1324
1556
  for (let i = 0; i < 8; i++) {
1325
- protocolFeeRecipients.push(new PublicKey(data.slice(offset, offset + 32)));
1557
+ protocolFeeRecipients.push(
1558
+ new PublicKey(data.slice(offset, offset + 32)),
1559
+ );
1326
1560
  offset += 32;
1327
1561
  }
1328
1562
 
@@ -1332,21 +1566,21 @@ export class PumpTrader {
1332
1566
  async getAmmPoolReserves(poolKeys: any): Promise<PoolReserves> {
1333
1567
  const [baseInfo, quoteInfo] = await Promise.all([
1334
1568
  this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
1335
- this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
1569
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
1336
1570
  ]);
1337
1571
 
1338
1572
  return {
1339
1573
  baseAmount: BigInt(baseInfo.value.amount),
1340
1574
  quoteAmount: BigInt(quoteInfo.value.amount),
1341
1575
  baseDecimals: baseInfo.value.decimals,
1342
- quoteDecimals: quoteInfo.value.decimals
1576
+ quoteDecimals: quoteInfo.value.decimals,
1343
1577
  };
1344
1578
  }
1345
1579
 
1346
1580
  deriveAmmPoolV2(baseMint: PublicKey): PublicKey {
1347
1581
  return PublicKey.findProgramAddressSync(
1348
1582
  [Buffer.from("pool-v2"), baseMint.toBuffer()],
1349
- PROGRAM_IDS.PUMP_AMM
1583
+ PROGRAM_IDS.PUMP_AMM,
1350
1584
  )[0];
1351
1585
  }
1352
1586
 
@@ -1358,19 +1592,19 @@ export class PumpTrader {
1358
1592
  userQuoteAta: PublicKey,
1359
1593
  baseAmountOut: bigint,
1360
1594
  maxQuoteAmountIn: bigint,
1361
- tokenProgramId: PublicKey
1595
+ tokenProgramId: PublicKey,
1362
1596
  ): TransactionInstruction {
1363
1597
  const { pool, poolKeys, globalConfig } = poolInfo;
1364
1598
  const poolV2 = this.deriveAmmPoolV2(poolKeys.baseMint);
1365
1599
 
1366
1600
  const [eventAuthority] = PublicKey.findProgramAddressSync(
1367
1601
  [Buffer.from("__event_authority")],
1368
- PROGRAM_IDS.PUMP_AMM
1602
+ PROGRAM_IDS.PUMP_AMM,
1369
1603
  );
1370
1604
 
1371
1605
  const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
1372
1606
  [Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
1373
- PROGRAM_IDS.PUMP_AMM
1607
+ PROGRAM_IDS.PUMP_AMM,
1374
1608
  );
1375
1609
 
1376
1610
  const coinCreatorVaultAta = getAssociatedTokenAddressSync(
@@ -1378,22 +1612,25 @@ export class PumpTrader {
1378
1612
  coinCreatorVaultAuthority,
1379
1613
  true,
1380
1614
  TOKEN_PROGRAM_ID,
1381
- ASSOCIATED_TOKEN_PROGRAM_ID
1615
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1382
1616
  );
1383
1617
 
1384
1618
  const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
1385
1619
  [Buffer.from("global_volume_accumulator")],
1386
- PROGRAM_IDS.PUMP_AMM
1620
+ PROGRAM_IDS.PUMP_AMM,
1387
1621
  );
1388
1622
 
1389
1623
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1390
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
1391
- PROGRAM_IDS.PUMP_AMM
1624
+ [
1625
+ Buffer.from("user_volume_accumulator"),
1626
+ this.wallet.publicKey.toBuffer(),
1627
+ ],
1628
+ PROGRAM_IDS.PUMP_AMM,
1392
1629
  );
1393
1630
 
1394
1631
  const [feeConfig] = PublicKey.findProgramAddressSync(
1395
1632
  [Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
1396
- PROGRAM_IDS.FEE
1633
+ PROGRAM_IDS.FEE,
1397
1634
  );
1398
1635
 
1399
1636
  const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
@@ -1402,7 +1639,7 @@ export class PumpTrader {
1402
1639
  protocolFeeRecipient,
1403
1640
  true,
1404
1641
  TOKEN_PROGRAM_ID,
1405
- ASSOCIATED_TOKEN_PROGRAM_ID
1642
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1406
1643
  );
1407
1644
  const newFeeRecipient = this.pickFeeRecipient();
1408
1645
  const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
@@ -1410,7 +1647,7 @@ export class PumpTrader {
1410
1647
  newFeeRecipient,
1411
1648
  true,
1412
1649
  TOKEN_PROGRAM_ID,
1413
- ASSOCIATED_TOKEN_PROGRAM_ID
1650
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1414
1651
  );
1415
1652
 
1416
1653
  const remainingKeys = [];
@@ -1420,14 +1657,22 @@ export class PumpTrader {
1420
1657
  userVolumeAccumulator,
1421
1658
  true,
1422
1659
  TOKEN_PROGRAM_ID,
1423
- ASSOCIATED_TOKEN_PROGRAM_ID
1660
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1424
1661
  );
1425
- remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true });
1662
+ remainingKeys.push({
1663
+ pubkey: userVolumeAccumulatorWsolAta,
1664
+ isSigner: false,
1665
+ isWritable: true,
1666
+ });
1426
1667
  }
1427
1668
  remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1428
1669
  remainingKeys.push(
1429
1670
  { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1430
- { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
1671
+ {
1672
+ pubkey: newFeeRecipientTokenAccount,
1673
+ isSigner: false,
1674
+ isWritable: true,
1675
+ },
1431
1676
  );
1432
1677
 
1433
1678
  return new TransactionInstruction({
@@ -1440,30 +1685,50 @@ export class PumpTrader {
1440
1685
  { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
1441
1686
  { pubkey: userBaseAta, isSigner: false, isWritable: true },
1442
1687
  { pubkey: userQuoteAta, isSigner: false, isWritable: true },
1443
- { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
1444
- { 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
+ },
1445
1698
  { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
1446
- { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
1699
+ {
1700
+ pubkey: protocolFeeRecipientTokenAccount,
1701
+ isSigner: false,
1702
+ isWritable: true,
1703
+ },
1447
1704
  { pubkey: tokenProgramId, isSigner: false, isWritable: false },
1448
1705
  { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1449
1706
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1450
- { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1707
+ {
1708
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
1709
+ isSigner: false,
1710
+ isWritable: false,
1711
+ },
1451
1712
  { pubkey: eventAuthority, isSigner: false, isWritable: false },
1452
1713
  { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
1453
1714
  { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
1454
- { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
1715
+ {
1716
+ pubkey: coinCreatorVaultAuthority,
1717
+ isSigner: false,
1718
+ isWritable: false,
1719
+ },
1455
1720
  { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
1456
1721
  { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1457
1722
  { pubkey: feeConfig, isSigner: false, isWritable: false },
1458
1723
  { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
1459
- ...remainingKeys
1724
+ ...remainingKeys,
1460
1725
  ],
1461
1726
  data: Buffer.concat([
1462
1727
  DISCRIMINATORS.BUY,
1463
1728
  u64(baseAmountOut),
1464
1729
  u64(maxQuoteAmountIn),
1465
- Buffer.from([1, 1])
1466
- ])
1730
+ Buffer.from([1, 1]),
1731
+ ]),
1467
1732
  });
1468
1733
  }
1469
1734
 
@@ -1473,19 +1738,19 @@ export class PumpTrader {
1473
1738
  userQuoteAta: PublicKey,
1474
1739
  baseAmountIn: bigint,
1475
1740
  minQuoteAmountOut: bigint,
1476
- tokenProgramId: PublicKey
1741
+ tokenProgramId: PublicKey,
1477
1742
  ): TransactionInstruction {
1478
1743
  const { pool, poolKeys, globalConfig } = poolInfo;
1479
1744
  const poolV2 = this.deriveAmmPoolV2(poolKeys.baseMint);
1480
1745
 
1481
1746
  const [eventAuthority] = PublicKey.findProgramAddressSync(
1482
1747
  [Buffer.from("__event_authority")],
1483
- PROGRAM_IDS.PUMP_AMM
1748
+ PROGRAM_IDS.PUMP_AMM,
1484
1749
  );
1485
1750
 
1486
1751
  const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
1487
1752
  [Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
1488
- PROGRAM_IDS.PUMP_AMM
1753
+ PROGRAM_IDS.PUMP_AMM,
1489
1754
  );
1490
1755
 
1491
1756
  const coinCreatorVaultAta = getAssociatedTokenAddressSync(
@@ -1493,12 +1758,12 @@ export class PumpTrader {
1493
1758
  coinCreatorVaultAuthority,
1494
1759
  true,
1495
1760
  TOKEN_PROGRAM_ID,
1496
- ASSOCIATED_TOKEN_PROGRAM_ID
1761
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1497
1762
  );
1498
1763
 
1499
1764
  const [feeConfig] = PublicKey.findProgramAddressSync(
1500
1765
  [Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
1501
- PROGRAM_IDS.FEE
1766
+ PROGRAM_IDS.FEE,
1502
1767
  );
1503
1768
 
1504
1769
  const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
@@ -1507,7 +1772,7 @@ export class PumpTrader {
1507
1772
  protocolFeeRecipient,
1508
1773
  true,
1509
1774
  TOKEN_PROGRAM_ID,
1510
- ASSOCIATED_TOKEN_PROGRAM_ID
1775
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1511
1776
  );
1512
1777
  const newFeeRecipient = this.pickFeeRecipient();
1513
1778
  const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
@@ -1515,12 +1780,15 @@ export class PumpTrader {
1515
1780
  newFeeRecipient,
1516
1781
  true,
1517
1782
  TOKEN_PROGRAM_ID,
1518
- ASSOCIATED_TOKEN_PROGRAM_ID
1783
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1519
1784
  );
1520
1785
 
1521
1786
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1522
- [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
1523
- PROGRAM_IDS.PUMP_AMM
1787
+ [
1788
+ Buffer.from("user_volume_accumulator"),
1789
+ this.wallet.publicKey.toBuffer(),
1790
+ ],
1791
+ PROGRAM_IDS.PUMP_AMM,
1524
1792
  );
1525
1793
 
1526
1794
  const userVolumeAccumulatorWsolAta = getAssociatedTokenAddressSync(
@@ -1528,20 +1796,28 @@ export class PumpTrader {
1528
1796
  userVolumeAccumulator,
1529
1797
  true,
1530
1798
  TOKEN_PROGRAM_ID,
1531
- ASSOCIATED_TOKEN_PROGRAM_ID
1799
+ ASSOCIATED_TOKEN_PROGRAM_ID,
1532
1800
  );
1533
1801
 
1534
1802
  const remainingKeys = [];
1535
1803
  if (poolKeys.isCashbackCoin) {
1536
1804
  remainingKeys.push(
1537
- { pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true },
1538
- { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true }
1805
+ {
1806
+ pubkey: userVolumeAccumulatorWsolAta,
1807
+ isSigner: false,
1808
+ isWritable: true,
1809
+ },
1810
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1539
1811
  );
1540
1812
  }
1541
1813
  remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1542
1814
  remainingKeys.push(
1543
1815
  { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1544
- { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
1816
+ {
1817
+ pubkey: newFeeRecipientTokenAccount,
1818
+ isSigner: false,
1819
+ isWritable: true,
1820
+ },
1545
1821
  );
1546
1822
 
1547
1823
  return new TransactionInstruction({
@@ -1554,71 +1830,819 @@ export class PumpTrader {
1554
1830
  { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
1555
1831
  { pubkey: userBaseAta, isSigner: false, isWritable: true },
1556
1832
  { pubkey: userQuoteAta, isSigner: false, isWritable: true },
1557
- { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
1558
- { 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
+ },
1559
1843
  { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
1560
- { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
1844
+ {
1845
+ pubkey: protocolFeeRecipientTokenAccount,
1846
+ isSigner: false,
1847
+ isWritable: true,
1848
+ },
1561
1849
  { pubkey: tokenProgramId, isSigner: false, isWritable: false },
1562
1850
  { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1563
1851
  { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1564
- { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1852
+ {
1853
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
1854
+ isSigner: false,
1855
+ isWritable: false,
1856
+ },
1565
1857
  { pubkey: eventAuthority, isSigner: false, isWritable: false },
1566
1858
  { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
1567
1859
  { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
1568
- { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
1860
+ {
1861
+ pubkey: coinCreatorVaultAuthority,
1862
+ isSigner: false,
1863
+ isWritable: false,
1864
+ },
1569
1865
  { pubkey: feeConfig, isSigner: false, isWritable: false },
1570
1866
  { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
1571
- ...remainingKeys
1867
+ ...remainingKeys,
1572
1868
  ],
1573
1869
  data: Buffer.concat([
1574
1870
  DISCRIMINATORS.SELL,
1575
1871
  u64(baseAmountIn),
1576
- u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n)
1577
- ])
1872
+ u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n),
1873
+ ]),
1578
1874
  });
1579
1875
  }
1580
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
+
1581
2602
  /* ---------- 交易确认 ---------- */
1582
2603
 
1583
2604
  async confirmTransactionWithPolling(
1584
2605
  signature: string,
1585
2606
  lastValidBlockHeight: number,
1586
2607
  maxAttempts: number = 5,
1587
- delayMs: number = 2000
2608
+ delayMs: number = 2000,
1588
2609
  ): Promise<string> {
1589
- console.log('✅ 交易已发送:', signature);
1590
- console.log('🔗 查看交易: https://solscan.io/tx/' + signature);
2610
+ console.log("✅ 交易已发送:", signature);
2611
+ console.log("🔗 查看交易: https://solscan.io/tx/" + signature);
1591
2612
 
1592
2613
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1593
- await new Promise(resolve => setTimeout(resolve, delayMs));
2614
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1594
2615
 
1595
2616
  try {
1596
2617
  console.log(`🔍 检查交易状态 (${attempt}/${maxAttempts})...`);
1597
2618
 
1598
2619
  const txInfo = await this.connection.getTransaction(signature, {
1599
- commitment: 'confirmed',
1600
- maxSupportedTransactionVersion: 0
2620
+ commitment: "confirmed",
2621
+ maxSupportedTransactionVersion: 0,
1601
2622
  });
1602
2623
 
1603
2624
  if (txInfo) {
1604
2625
  if (txInfo.meta?.err) {
1605
- console.error('❌ 交易失败:', txInfo.meta.err);
1606
- throw new Error('交易失败: ' + JSON.stringify(txInfo.meta.err));
2626
+ console.error("❌ 交易失败:", txInfo.meta.err);
2627
+ throw new Error("交易失败: " + JSON.stringify(txInfo.meta.err));
1607
2628
  }
1608
2629
 
1609
- console.log('✅ 交易已确认!');
2630
+ console.log("✅ 交易已确认!");
1610
2631
  return signature;
1611
2632
  }
1612
2633
 
1613
- const currentBlockHeight = await this.connection.getBlockHeight('finalized');
2634
+ const currentBlockHeight =
2635
+ await this.connection.getBlockHeight("finalized");
1614
2636
  if (currentBlockHeight > lastValidBlockHeight) {
1615
- console.log('⚠️ 交易已过期(超过有效区块高度)');
1616
- throw new Error('交易过期:未在有效区块高度内确认');
2637
+ console.log("⚠️ 交易已过期(超过有效区块高度)");
2638
+ throw new Error("交易过期:未在有效区块高度内确认");
1617
2639
  }
1618
-
1619
2640
  } catch (error) {
1620
2641
  const err = error as Error;
1621
- if (err.message?.includes('交易失败') || err.message?.includes('交易过期')) {
2642
+ if (
2643
+ err.message?.includes("交易失败") ||
2644
+ err.message?.includes("交易过期")
2645
+ ) {
1622
2646
  throw error;
1623
2647
  }
1624
2648
  console.log(`⚠️ 查询出错,继续重试: ${err.message}`);
@@ -1626,20 +2650,26 @@ export class PumpTrader {
1626
2650
  }
1627
2651
 
1628
2652
  throw new Error(
1629
- `交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}。请手动检查交易状态。`
2653
+ `交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}。请手动检查交易状态。`,
1630
2654
  );
1631
2655
  }
1632
2656
 
1633
2657
  /* ---------- 事件监听 ---------- */
1634
2658
 
1635
- listenTrades(callback: (event: TradeEvent) => void, mintFilter?: PublicKey | null) {
2659
+ listenTrades(
2660
+ callback: (event: TradeEvent) => void,
2661
+ mintFilter?: PublicKey | null,
2662
+ ) {
1636
2663
  return this.connection.onLogs(
1637
2664
  PROGRAM_IDS.PUMP,
1638
2665
  (log) => {
1639
2666
  for (const logLine of log.logs) {
1640
2667
  if (!logLine.startsWith("Program data: ")) continue;
1641
2668
 
1642
- const buf = Buffer.from(logLine.replace("Program data: ", ""), "base64");
2669
+ const buf = Buffer.from(
2670
+ logLine.replace("Program data: ", ""),
2671
+ "base64",
2672
+ );
1643
2673
 
1644
2674
  if (!buf.subarray(0, 8).equals(DISCRIMINATORS.TRADE_EVENT)) continue;
1645
2675
 
@@ -1665,11 +2695,11 @@ export class PumpTrader {
1665
2695
  isBuy,
1666
2696
  user: user.toBase58(),
1667
2697
  timestamp,
1668
- signature: log.signature
2698
+ signature: log.signature,
1669
2699
  });
1670
2700
  }
1671
2701
  },
1672
- "confirmed"
2702
+ "confirmed",
1673
2703
  );
1674
2704
  }
1675
2705
 
@@ -1683,18 +2713,22 @@ export class PumpTrader {
1683
2713
  this.connection,
1684
2714
  mint,
1685
2715
  "confirmed",
1686
- TOKEN_2022_PROGRAM_ID
2716
+ TOKEN_2022_PROGRAM_ID,
1687
2717
  );
1688
2718
 
1689
2719
  return {
1690
2720
  name: metadata?.name || "",
1691
2721
  symbol: metadata?.symbol || "",
1692
- uri: metadata?.uri || ""
2722
+ uri: metadata?.uri || "",
1693
2723
  };
1694
2724
  } catch (e) {
1695
2725
  const metadataPda = PublicKey.findProgramAddressSync(
1696
- [Buffer.from("metadata"), PROGRAM_IDS.METADATA.toBuffer(), mint.toBuffer()],
1697
- PROGRAM_IDS.METADATA
2726
+ [
2727
+ Buffer.from("metadata"),
2728
+ PROGRAM_IDS.METADATA.toBuffer(),
2729
+ mint.toBuffer(),
2730
+ ],
2731
+ PROGRAM_IDS.METADATA,
1698
2732
  )[0];
1699
2733
 
1700
2734
  const acc = await this.connection.getAccountInfo(metadataPda);
@@ -1703,9 +2737,9 @@ export class PumpTrader {
1703
2737
  const meta = parseMetadataAccount(acc.data);
1704
2738
 
1705
2739
  return {
1706
- name: meta?.name?.replace(/\u0000/g, '') || "",
1707
- symbol: meta?.symbol?.replace(/\u0000/g, '') || "",
1708
- 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, "") || "",
1709
2743
  };
1710
2744
  }
1711
2745
  }
@@ -1751,5 +2785,5 @@ export type {
1751
2785
  GlobalState,
1752
2786
  TokenProgramType,
1753
2787
  PoolInfo,
1754
- MetadataInfo
2788
+ MetadataInfo,
1755
2789
  };