pump-trader 1.1.5 → 1.1.9

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