pump-trader 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts ADDED
@@ -0,0 +1,1429 @@
1
+ import {
2
+ Connection,
3
+ PublicKey,
4
+ Transaction,
5
+ TransactionInstruction,
6
+ SystemProgram,
7
+ ComputeBudgetProgram,
8
+ Keypair
9
+ } from "@solana/web3.js";
10
+
11
+ import {
12
+ ASSOCIATED_TOKEN_PROGRAM_ID,
13
+ getAssociatedTokenAddressSync,
14
+ createAssociatedTokenAccountInstruction,
15
+ createSyncNativeInstruction,
16
+ createCloseAccountInstruction,
17
+ getTokenMetadata,
18
+ TOKEN_2022_PROGRAM_ID,
19
+ TOKEN_PROGRAM_ID,
20
+ getMint
21
+ } from "@solana/spl-token";
22
+
23
+ import BN from "bn.js";
24
+ import bs58 from "bs58";
25
+
26
+ /* ================= 类型定义 ================= */
27
+
28
+ interface TradeOptions {
29
+ maxSolPerTx: bigint;
30
+ slippage: {
31
+ base: number;
32
+ max?: number;
33
+ min?: number;
34
+ impactFactor?: number;
35
+ };
36
+ priority: {
37
+ base: number;
38
+ enableRandom?: boolean;
39
+ randomRange?: number;
40
+ };
41
+ }
42
+
43
+ interface PendingTransaction {
44
+ signature: string;
45
+ lastValidBlockHeight: number;
46
+ index: number;
47
+ }
48
+
49
+ interface FailedTransaction {
50
+ index: number;
51
+ error: string;
52
+ }
53
+
54
+ interface TradeResult {
55
+ pendingTransactions: PendingTransaction[];
56
+ failedTransactions: FailedTransaction[];
57
+ }
58
+
59
+ interface BondingCurveState {
60
+ virtualTokenReserves: bigint;
61
+ virtualSolReserves: bigint;
62
+ realTokenReserves: bigint;
63
+ realSolReserves: bigint;
64
+ tokenTotalSupply: bigint;
65
+ complete: boolean;
66
+ }
67
+
68
+ interface BondingInfo {
69
+ bonding: PublicKey;
70
+ state: BondingCurveState;
71
+ creator: PublicKey;
72
+ }
73
+
74
+ interface PoolReserves {
75
+ baseAmount: bigint;
76
+ quoteAmount: bigint;
77
+ baseDecimals: number;
78
+ quoteDecimals: number;
79
+ }
80
+
81
+ interface TradeEvent {
82
+ mint: string;
83
+ solAmount: bigint;
84
+ tokenAmount: bigint;
85
+ isBuy: boolean;
86
+ user: string;
87
+ timestamp: number;
88
+ signature: string;
89
+ }
90
+
91
+ interface GlobalState {
92
+ initialized: boolean;
93
+ authority: PublicKey;
94
+ feeRecipient: PublicKey;
95
+ withdrawAuthority: PublicKey;
96
+ initialVirtualTokenReserves: bigint;
97
+ initialVirtualSolReserves: bigint;
98
+ initialRealTokenReserves: bigint;
99
+ tokenTotalSupply: bigint;
100
+ feeBasisPoints: bigint;
101
+ }
102
+
103
+ interface TokenProgramType {
104
+ type: "TOKEN_PROGRAM_ID" | "TOKEN_2022_PROGRAM_ID";
105
+ programId: PublicKey;
106
+ }
107
+
108
+ interface PoolInfo {
109
+ pool: PublicKey;
110
+ poolAuthority: PublicKey;
111
+ poolKeys: any;
112
+ globalConfig: any;
113
+ }
114
+
115
+ interface MetadataInfo {
116
+ name: string;
117
+ symbol: string;
118
+ uri: string;
119
+ }
120
+
121
+ /* ================= 常量定义 ================= */
122
+
123
+ const PROGRAM_IDS = {
124
+ PUMP: new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"),
125
+ PUMP_AMM: new PublicKey("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"),
126
+ METADATA: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
127
+ FEE: new PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
128
+ EVENT_AUTHORITY: new PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
129
+ };
130
+
131
+ const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
132
+
133
+ const SEEDS = {
134
+ FEE_CONFIG: new Uint8Array([
135
+ 1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
136
+ 81, 137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176
137
+ ]),
138
+ AMM_FEE_CONFIG: Buffer.from([
139
+ 12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101,
140
+ 244, 41, 141, 49, 86, 213, 113, 180, 212, 248, 9, 12, 24, 233, 168, 99
141
+ ]),
142
+ GLOBAL: Buffer.from("global"),
143
+ BONDING: Buffer.from("bonding-curve")
144
+ };
145
+
146
+ const DISCRIMINATORS = {
147
+ BUY: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]),
148
+ SELL: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]),
149
+ TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238])
150
+ };
151
+
152
+ const AMM_FEE_BPS = 100n;
153
+ const BPS_DENOMINATOR = 10000n;
154
+
155
+ /* ================= 工具函数 ================= */
156
+
157
+ const u64 = (v: bigint | BN | number): Buffer => {
158
+ const bn = typeof v === 'bigint' ? new BN(v.toString()) : new BN(v.toString());
159
+ return bn.toArrayLike(Buffer, "le", 8);
160
+ };
161
+
162
+ const readU64 = (buf: Buffer, offset: number): [bigint, number] => {
163
+ const value = buf.readBigUInt64LE(offset);
164
+ return [value, offset + 8];
165
+ };
166
+
167
+ const readU32 = (buf: Buffer, offsetObj: { offset: number }): number => {
168
+ const value = buf.readUInt32LE(offsetObj.offset);
169
+ offsetObj.offset += 4;
170
+ return value;
171
+ };
172
+
173
+ const readString = (buf: Buffer, offsetObj: { offset: number }): string => {
174
+ const len = readU32(buf, offsetObj);
175
+ const str = buf.slice(offsetObj.offset, offsetObj.offset + len).toString("utf8");
176
+ offsetObj.offset += len;
177
+ return str;
178
+ };
179
+
180
+ /* ================= 解析函数 ================= */
181
+
182
+ function parseMetadataAccount(data: Buffer) {
183
+ const offsetObj = { offset: 1 };
184
+
185
+ const updateAuthority = new PublicKey(data.slice(offsetObj.offset, offsetObj.offset + 32));
186
+ offsetObj.offset += 32;
187
+
188
+ const mint = new PublicKey(data.slice(offsetObj.offset, offsetObj.offset + 32));
189
+ offsetObj.offset += 32;
190
+
191
+ const name = readString(data, offsetObj);
192
+ const symbol = readString(data, offsetObj);
193
+ const uri = readString(data, offsetObj);
194
+
195
+ return {
196
+ updateAuthority: updateAuthority.toBase58(),
197
+ mint: mint.toBase58(),
198
+ name,
199
+ symbol,
200
+ uri
201
+ };
202
+ }
203
+
204
+ function parsePoolKeys(data: Buffer) {
205
+ if (!data || data.length < 280) {
206
+ throw new Error('Invalid pool account data');
207
+ }
208
+
209
+ let offset = 8;
210
+
211
+ const poolBump = data.readUInt8(offset);
212
+ offset += 1;
213
+
214
+ const index = data.readUInt16LE(offset);
215
+ offset += 2;
216
+
217
+ const creator = new PublicKey(data.slice(offset, offset + 32));
218
+ offset += 32;
219
+
220
+ const baseMint = new PublicKey(data.slice(offset, offset + 32));
221
+ offset += 32;
222
+
223
+ const quoteMint = new PublicKey(data.slice(offset, offset + 32));
224
+ offset += 32;
225
+
226
+ const lpMint = new PublicKey(data.slice(offset, offset + 32));
227
+ offset += 32;
228
+
229
+ const poolBaseTokenAccount = new PublicKey(data.slice(offset, offset + 32));
230
+ offset += 32;
231
+
232
+ const poolQuoteTokenAccount = new PublicKey(data.slice(offset, offset + 32));
233
+ offset += 32;
234
+
235
+ const lpSupply = data.readBigUInt64LE(offset);
236
+ offset += 8;
237
+
238
+ const coinCreator = new PublicKey(data.slice(offset, offset + 32));
239
+ offset += 32;
240
+
241
+ const isMayhemMode = data.readUInt8(offset) === 1;
242
+
243
+ return {
244
+ creator,
245
+ baseMint,
246
+ quoteMint,
247
+ lpMint,
248
+ poolBaseTokenAccount,
249
+ poolQuoteTokenAccount,
250
+ coinCreator,
251
+ isMayhemMode
252
+ };
253
+ }
254
+
255
+ /* ================= PumpTrader 类 ================= */
256
+
257
+ export class PumpTrader {
258
+ private connection: Connection;
259
+ private wallet: Keypair;
260
+ private global: PublicKey;
261
+ private globalState: GlobalState | null;
262
+ private tokenProgramCache: Map<string, TokenProgramType>;
263
+
264
+ constructor(rpc: string, privateKey: string) {
265
+ this.connection = new Connection(rpc, "confirmed");
266
+ this.wallet = Keypair.fromSecretKey(bs58.decode(privateKey));
267
+ this.global = PublicKey.findProgramAddressSync([SEEDS.GLOBAL], PROGRAM_IDS.PUMP)[0];
268
+ this.globalState = null;
269
+ this.tokenProgramCache = new Map();
270
+ }
271
+
272
+ /* ---------- Token Program 检测 ---------- */
273
+
274
+ /**
275
+ * 自动检测代币使用的 token program
276
+ */
277
+ async detectTokenProgram(tokenAddr: string): Promise<TokenProgramType> {
278
+ // 检查缓存
279
+ if (this.tokenProgramCache.has(tokenAddr)) {
280
+ return this.tokenProgramCache.get(tokenAddr)!;
281
+ }
282
+
283
+ const mint = new PublicKey(tokenAddr);
284
+
285
+ try {
286
+ // 首先尝试获取 TOKEN_2022 的代币信息
287
+ const mintData = await getMint(this.connection, mint, "confirmed", TOKEN_2022_PROGRAM_ID);
288
+ const result: TokenProgramType = {
289
+ type: "TOKEN_2022_PROGRAM_ID",
290
+ programId: TOKEN_2022_PROGRAM_ID
291
+ };
292
+ this.tokenProgramCache.set(tokenAddr, result);
293
+ return result;
294
+ } catch (e) {
295
+ try {
296
+ // 如果失败,尝试标准 TOKEN_PROGRAM_ID
297
+ const mintData = await getMint(this.connection, mint, "confirmed", TOKEN_PROGRAM_ID);
298
+ const result: TokenProgramType = {
299
+ type: "TOKEN_PROGRAM_ID",
300
+ programId: TOKEN_PROGRAM_ID
301
+ };
302
+ this.tokenProgramCache.set(tokenAddr, result);
303
+ return result;
304
+ } catch (error) {
305
+ throw new Error(`Failed to detect token program for ${tokenAddr}: ${error}`);
306
+ }
307
+ }
308
+ }
309
+
310
+ /* ---------- 内盘/外盘检测 ---------- */
311
+
312
+ /**
313
+ * 检测代币是否在外盘 (AMM)
314
+ */
315
+ async isAmmCompleted(tokenAddr: string): Promise<boolean> {
316
+ try {
317
+ const mint = new PublicKey(tokenAddr);
318
+ const { state } = await this.loadBonding(mint);
319
+ return state.complete;
320
+ } catch (error) {
321
+ // 如果无法加载内盘,说明可能已经在外盘
322
+ return true;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * 自动判断应该使用内盘还是外盘
328
+ */
329
+ async getTradeMode(tokenAddr: string): Promise<"bonding" | "amm"> {
330
+ const isAmmMode = await this.isAmmCompleted(tokenAddr);
331
+ return isAmmMode ? "amm" : "bonding";
332
+ }
333
+
334
+ /* ---------- Global State ---------- */
335
+
336
+ async loadGlobal(): Promise<GlobalState> {
337
+ const acc = await this.connection.getAccountInfo(this.global);
338
+ if (!acc) throw new Error("Global account not found");
339
+
340
+ const data = acc.data;
341
+ let offset = 8;
342
+
343
+ const readBool = () => data[offset++] === 1;
344
+ const readPk = () => {
345
+ const pk = new PublicKey(data.slice(offset, offset + 32));
346
+ offset += 32;
347
+ return pk;
348
+ };
349
+ const readU64 = () => {
350
+ const v = data.readBigUInt64LE(offset);
351
+ offset += 8;
352
+ return v;
353
+ };
354
+
355
+ this.globalState = {
356
+ initialized: readBool(),
357
+ authority: readPk(),
358
+ feeRecipient: readPk(),
359
+ withdrawAuthority: readPk(),
360
+ initialVirtualTokenReserves: readU64(),
361
+ initialVirtualSolReserves: readU64(),
362
+ initialRealTokenReserves: readU64(),
363
+ tokenTotalSupply: readU64(),
364
+ feeBasisPoints: readU64()
365
+ };
366
+
367
+ return this.globalState;
368
+ }
369
+
370
+ /* ---------- Bonding Curve ---------- */
371
+
372
+ getBondingPda(mint: PublicKey): PublicKey {
373
+ return PublicKey.findProgramAddressSync(
374
+ [SEEDS.BONDING, mint.toBuffer()],
375
+ PROGRAM_IDS.PUMP
376
+ )[0];
377
+ }
378
+
379
+ async loadBonding(mint: PublicKey): Promise<BondingInfo> {
380
+ const bonding = this.getBondingPda(mint);
381
+ const acc = await this.connection.getAccountInfo(bonding);
382
+ if (!acc) throw new Error("Bonding curve not found");
383
+
384
+ let offset = 8;
385
+ const data = acc.data;
386
+
387
+ const state: BondingCurveState = {} as any;
388
+ [state.virtualTokenReserves, offset] = readU64(data, offset);
389
+ [state.virtualSolReserves, offset] = readU64(data, offset);
390
+ [state.realTokenReserves, offset] = readU64(data, offset);
391
+ [state.realSolReserves, offset] = readU64(data, offset);
392
+ [state.tokenTotalSupply, offset] = readU64(data, offset);
393
+ state.complete = data[offset] === 1;
394
+ offset += 1;
395
+
396
+ const creator = new PublicKey(data.slice(offset, offset + 32));
397
+
398
+ return { bonding, state, creator };
399
+ }
400
+
401
+ /* ---------- 价格计算 ---------- */
402
+
403
+ calcBuy(solIn: bigint, state: BondingCurveState): bigint {
404
+ const newVirtualSol = state.virtualSolReserves + solIn;
405
+ const newVirtualToken = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualSol;
406
+ return state.virtualTokenReserves - newVirtualToken;
407
+ }
408
+
409
+ calcSell(tokenIn: bigint, state: BondingCurveState): bigint {
410
+ const newVirtualToken = state.virtualTokenReserves + tokenIn;
411
+ const newVirtualSol = (state.virtualSolReserves * state.virtualTokenReserves) / newVirtualToken;
412
+ return state.virtualSolReserves - newVirtualSol;
413
+ }
414
+
415
+ calculateAmmBuyOutput(quoteIn: bigint, reserves: PoolReserves): bigint {
416
+ const quoteInAfterFee = (quoteIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
417
+ const numerator = reserves.baseAmount * quoteInAfterFee;
418
+ const denominator = reserves.quoteAmount + quoteInAfterFee;
419
+ return numerator / denominator;
420
+ }
421
+
422
+ calculateAmmSellOutput(baseIn: bigint, reserves: PoolReserves): bigint {
423
+ const baseInAfterFee = (baseIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
424
+ const numerator = reserves.quoteAmount * baseInAfterFee;
425
+ const denominator = reserves.baseAmount + baseInAfterFee;
426
+ return numerator / denominator;
427
+ }
428
+
429
+ /* ---------- 价格查询 ---------- */
430
+
431
+ async getPriceAndStatus(tokenAddr: string): Promise<{ price: number; completed: boolean }> {
432
+ const mint = new PublicKey(tokenAddr);
433
+ const { state } = await this.loadBonding(mint);
434
+
435
+ if (state.complete) {
436
+ const price = await this.getAmmPrice(mint);
437
+ return { price, completed: true };
438
+ }
439
+
440
+ const oneToken = BigInt(1_000_000);
441
+ const solOut = this.calcSell(oneToken, state);
442
+ const price = Number(solOut) / 1e9;
443
+ return { price, completed: false };
444
+ }
445
+
446
+ async getAmmPrice(mint: PublicKey): Promise<number> {
447
+ const [poolCreator] = PublicKey.findProgramAddressSync(
448
+ [Buffer.from("pool-authority"), mint.toBuffer()],
449
+ PROGRAM_IDS.PUMP
450
+ );
451
+
452
+ const indexBuffer = new BN(0).toArrayLike(Buffer, "le", 2);
453
+ const [pool] = PublicKey.findProgramAddressSync(
454
+ [Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(), SOL_MINT.toBuffer()],
455
+ PROGRAM_IDS.PUMP_AMM
456
+ );
457
+
458
+ const acc = await this.connection.getAccountInfo(pool);
459
+ if (!acc) throw new Error("Pool not found");
460
+
461
+ const poolKeys = parsePoolKeys(acc.data);
462
+ const [baseInfo, quoteInfo] = await Promise.all([
463
+ this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
464
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
465
+ ]);
466
+
467
+ return quoteInfo.value.uiAmount! / baseInfo.value.uiAmount!;
468
+ }
469
+
470
+ /* ---------- 余额查询 ---------- */
471
+
472
+ async tokenBalance(tokenAddr: string): Promise<number> {
473
+ const mint = new PublicKey(tokenAddr);
474
+ const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
475
+ this.wallet.publicKey,
476
+ { mint }
477
+ );
478
+
479
+ return tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount || 0;
480
+ }
481
+
482
+ async solBalance(): Promise<number> {
483
+ const balance = await this.connection.getBalance(this.wallet.publicKey);
484
+ return balance / 1e9;
485
+ }
486
+
487
+ /* ---------- ATA 管理 ---------- */
488
+
489
+ async ensureAta(tx: Transaction, mint: PublicKey, tokenProgram?: PublicKey): Promise<PublicKey> {
490
+ const program = tokenProgram || TOKEN_2022_PROGRAM_ID;
491
+ const ata = getAssociatedTokenAddressSync(
492
+ mint,
493
+ this.wallet.publicKey,
494
+ false,
495
+ program,
496
+ ASSOCIATED_TOKEN_PROGRAM_ID
497
+ );
498
+
499
+ const acc = await this.connection.getAccountInfo(ata);
500
+ if (!acc) {
501
+ tx.add(
502
+ createAssociatedTokenAccountInstruction(
503
+ this.wallet.publicKey,
504
+ ata,
505
+ this.wallet.publicKey,
506
+ mint,
507
+ program,
508
+ ASSOCIATED_TOKEN_PROGRAM_ID
509
+ )
510
+ );
511
+ }
512
+
513
+ return ata;
514
+ }
515
+
516
+ async ensureWSOLAta(
517
+ tx: Transaction,
518
+ owner: PublicKey,
519
+ mode: "buy" | "sell",
520
+ lamports?: bigint
521
+ ): Promise<PublicKey> {
522
+ const wsolAta = getAssociatedTokenAddressSync(
523
+ SOL_MINT,
524
+ owner,
525
+ false,
526
+ TOKEN_PROGRAM_ID,
527
+ ASSOCIATED_TOKEN_PROGRAM_ID
528
+ );
529
+
530
+ const acc = await this.connection.getAccountInfo(wsolAta);
531
+
532
+ if (!acc) {
533
+ tx.add(
534
+ createAssociatedTokenAccountInstruction(
535
+ owner,
536
+ wsolAta,
537
+ owner,
538
+ SOL_MINT,
539
+ TOKEN_PROGRAM_ID,
540
+ ASSOCIATED_TOKEN_PROGRAM_ID
541
+ )
542
+ );
543
+ }
544
+
545
+ if (mode === 'buy' && lamports) {
546
+ tx.add(
547
+ SystemProgram.transfer({
548
+ fromPubkey: owner,
549
+ toPubkey: wsolAta,
550
+ lamports: Number(lamports)
551
+ })
552
+ );
553
+ tx.add(createSyncNativeInstruction(wsolAta));
554
+ }
555
+
556
+ return wsolAta;
557
+ }
558
+
559
+ /* ---------- 交易参数处理 ---------- */
560
+
561
+ genPriority(priorityOpt: any): number {
562
+ if (!priorityOpt?.enableRandom || !priorityOpt.randomRange) {
563
+ return priorityOpt.base;
564
+ }
565
+ return priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange);
566
+ }
567
+
568
+ calcSlippage({ tradeSize, reserve, slippageOpt }: any): number {
569
+ const impact = Number(tradeSize) / Math.max(Number(reserve), 1);
570
+ const factor = slippageOpt.impactFactor ?? 1;
571
+
572
+ let slip = slippageOpt.base + Math.floor(impact * 10_000 * factor);
573
+
574
+ if (slippageOpt.max !== undefined) {
575
+ slip = Math.min(slip, slippageOpt.max);
576
+ }
577
+ if (slippageOpt.min !== undefined) {
578
+ slip = Math.max(slip, slippageOpt.min);
579
+ }
580
+
581
+ return slip;
582
+ }
583
+
584
+ splitByMax(total: bigint, max: bigint): bigint[] {
585
+ const chunks: bigint[] = [];
586
+ let remaining = total;
587
+
588
+ while (remaining > 0n) {
589
+ const chunk = remaining > max ? max : remaining;
590
+ chunks.push(chunk);
591
+ remaining -= chunk;
592
+ }
593
+ return chunks;
594
+ }
595
+
596
+ splitIntoN(total: bigint, n: number): bigint[] {
597
+ const chunks: bigint[] = [];
598
+ const part = total / BigInt(n);
599
+ let remaining = total;
600
+
601
+ for (let i = 0; i < n; i++) {
602
+ const chunk = i === n - 1 ? remaining : part;
603
+ chunks.push(chunk);
604
+ remaining -= chunk;
605
+ }
606
+ return chunks;
607
+ }
608
+
609
+ /* ---------- 统一交易接口 ---------- */
610
+
611
+ /**
612
+ * 自动判断内盘/外盘并执行买入
613
+ */
614
+ async autoBuy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
615
+ const mode = await this.getTradeMode(tokenAddr);
616
+ if (mode === "bonding") {
617
+ return this.buy(tokenAddr, totalSolIn, tradeOpt);
618
+ } else {
619
+ return this.ammBuy(tokenAddr, totalSolIn, tradeOpt);
620
+ }
621
+ }
622
+
623
+ /**
624
+ * 自动判断内盘/外盘并执行卖出
625
+ */
626
+ async autoSell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
627
+ const mode = await this.getTradeMode(tokenAddr);
628
+ if (mode === "bonding") {
629
+ return this.sell(tokenAddr, totalTokenIn, tradeOpt);
630
+ } else {
631
+ return this.ammSell(tokenAddr, totalTokenIn, tradeOpt);
632
+ }
633
+ }
634
+
635
+ /* ---------- 内盘交易 ---------- */
636
+
637
+ async buy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
638
+ const mint = new PublicKey(tokenAddr);
639
+ const tokenProgram = await this.detectTokenProgram(tokenAddr);
640
+
641
+ if (!this.globalState) await this.loadGlobal();
642
+
643
+ const { bonding, state, creator } = await this.loadBonding(mint);
644
+ if (state.complete) throw new Error("Bonding curve already completed");
645
+
646
+ const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
647
+ const pendingTransactions: PendingTransaction[] = [];
648
+ const failedTransactions: FailedTransaction[] = [];
649
+
650
+ const associatedBondingCurve = getAssociatedTokenAddressSync(
651
+ mint,
652
+ bonding,
653
+ true,
654
+ tokenProgram.programId,
655
+ ASSOCIATED_TOKEN_PROGRAM_ID
656
+ );
657
+
658
+ const [creatorVault] = PublicKey.findProgramAddressSync(
659
+ [Buffer.from("creator-vault"), creator.toBuffer()],
660
+ PROGRAM_IDS.PUMP
661
+ );
662
+
663
+ const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
664
+ [Buffer.from("global_volume_accumulator")],
665
+ PROGRAM_IDS.PUMP
666
+ );
667
+
668
+ const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
669
+ [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
670
+ PROGRAM_IDS.PUMP
671
+ );
672
+
673
+ const [feeConfig] = PublicKey.findProgramAddressSync(
674
+ [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
675
+ PROGRAM_IDS.FEE
676
+ );
677
+
678
+ for (let i = 0; i < solChunks.length; i++) {
679
+ try {
680
+ const solIn = solChunks[i];
681
+ const tokenOut = this.calcBuy(solIn, state);
682
+ const slippageBps = this.calcSlippage({
683
+ tradeSize: solIn,
684
+ reserve: state.virtualSolReserves,
685
+ slippageOpt: tradeOpt.slippage
686
+ });
687
+ const maxSol = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
688
+ const priority = this.genPriority(tradeOpt.priority);
689
+
690
+ const tx = new Transaction().add(
691
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
692
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
693
+ );
694
+
695
+ const userAta = await this.ensureAta(tx, mint, tokenProgram.programId);
696
+
697
+ tx.add(
698
+ new TransactionInstruction({
699
+ programId: PROGRAM_IDS.PUMP,
700
+ keys: [
701
+ { pubkey: this.global, isSigner: false, isWritable: false },
702
+ { pubkey: this.globalState!.feeRecipient, isSigner: false, isWritable: true },
703
+ { pubkey: mint, isSigner: false, isWritable: false },
704
+ { pubkey: bonding, isSigner: false, isWritable: true },
705
+ { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
706
+ { pubkey: userAta, isSigner: false, isWritable: true },
707
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
708
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
709
+ { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
710
+ { pubkey: creatorVault, isSigner: false, isWritable: true },
711
+ { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
712
+ { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
713
+ { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
714
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
715
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
716
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
717
+ ],
718
+ data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
719
+ })
720
+ );
721
+
722
+ const { blockhash, lastValidBlockHeight } =
723
+ await this.connection.getLatestBlockhash('finalized');
724
+ tx.recentBlockhash = blockhash;
725
+ tx.feePayer = this.wallet.publicKey;
726
+ tx.sign(this.wallet);
727
+
728
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
729
+ skipPreflight: false,
730
+ maxRetries: 2
731
+ });
732
+
733
+ pendingTransactions.push({
734
+ signature,
735
+ lastValidBlockHeight,
736
+ index: i
737
+ });
738
+ } catch (e) {
739
+ failedTransactions.push({
740
+ index: i,
741
+ error: (e as Error).message
742
+ });
743
+ }
744
+ }
745
+
746
+ return { pendingTransactions, failedTransactions };
747
+ }
748
+
749
+ async sell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
750
+ const mint = new PublicKey(tokenAddr);
751
+ const tokenProgram = await this.detectTokenProgram(tokenAddr);
752
+
753
+ if (!this.globalState) await this.loadGlobal();
754
+
755
+ const { bonding, state, creator } = await this.loadBonding(mint);
756
+ if (state.complete) throw new Error("Bonding curve already completed");
757
+
758
+ const totalSolOut = this.calcSell(totalTokenIn, state);
759
+ const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
760
+ ? [totalTokenIn]
761
+ : this.splitIntoN(
762
+ totalTokenIn,
763
+ Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx)
764
+ );
765
+
766
+ const pendingTransactions: PendingTransaction[] = [];
767
+ const failedTransactions: FailedTransaction[] = [];
768
+
769
+ const associatedBondingCurve = getAssociatedTokenAddressSync(
770
+ mint,
771
+ bonding,
772
+ true,
773
+ tokenProgram.programId,
774
+ ASSOCIATED_TOKEN_PROGRAM_ID
775
+ );
776
+
777
+ const userAta = getAssociatedTokenAddressSync(
778
+ mint,
779
+ this.wallet.publicKey,
780
+ false,
781
+ tokenProgram.programId,
782
+ ASSOCIATED_TOKEN_PROGRAM_ID
783
+ );
784
+
785
+ const [creatorVault] = PublicKey.findProgramAddressSync(
786
+ [Buffer.from("creator-vault"), creator.toBuffer()],
787
+ PROGRAM_IDS.PUMP
788
+ );
789
+
790
+ const [feeConfig] = PublicKey.findProgramAddressSync(
791
+ [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
792
+ PROGRAM_IDS.FEE
793
+ );
794
+
795
+ for (let i = 0; i < tokenChunks.length; i++) {
796
+ try {
797
+ const tokenIn = tokenChunks[i];
798
+ const solOut = this.calcSell(tokenIn, state);
799
+ const slippageBps = this.calcSlippage({
800
+ tradeSize: tokenIn,
801
+ reserve: state.virtualTokenReserves,
802
+ slippageOpt: tradeOpt.slippage
803
+ });
804
+ const minSol = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
805
+ const priority = this.genPriority(tradeOpt.priority);
806
+
807
+ const tx = new Transaction().add(
808
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
809
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
810
+ );
811
+
812
+ tx.add(
813
+ new TransactionInstruction({
814
+ programId: PROGRAM_IDS.PUMP,
815
+ keys: [
816
+ { pubkey: this.global, isSigner: false, isWritable: false },
817
+ { pubkey: this.globalState!.feeRecipient, isSigner: false, isWritable: true },
818
+ { pubkey: mint, isSigner: false, isWritable: false },
819
+ { pubkey: bonding, isSigner: false, isWritable: true },
820
+ { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
821
+ { pubkey: userAta, isSigner: false, isWritable: true },
822
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
823
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
824
+ { pubkey: creatorVault, isSigner: false, isWritable: true },
825
+ { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
826
+ { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
827
+ { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
828
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
829
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
830
+ ],
831
+ data: Buffer.concat([
832
+ DISCRIMINATORS.SELL,
833
+ u64(tokenIn),
834
+ u64(minSol > 0n ? minSol : 1n)
835
+ ])
836
+ })
837
+ );
838
+
839
+ const { blockhash, lastValidBlockHeight } =
840
+ await this.connection.getLatestBlockhash("finalized");
841
+ tx.recentBlockhash = blockhash;
842
+ tx.feePayer = this.wallet.publicKey;
843
+ tx.sign(this.wallet);
844
+
845
+ const signature = await this.connection.sendRawTransaction(tx.serialize());
846
+ pendingTransactions.push({
847
+ signature,
848
+ lastValidBlockHeight,
849
+ index: i
850
+ });
851
+ } catch (e) {
852
+ failedTransactions.push({
853
+ index: i,
854
+ error: (e as Error).message
855
+ });
856
+ }
857
+ }
858
+
859
+ return { pendingTransactions, failedTransactions };
860
+ }
861
+
862
+ /* ---------- 外盘交易 ---------- */
863
+
864
+ async ammBuy(tokenAddr: string, totalSolIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
865
+ const mint = new PublicKey(tokenAddr);
866
+ const poolInfo = await this.getAmmPoolInfo(mint);
867
+ const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
868
+ const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
869
+
870
+ const pendingTransactions: PendingTransaction[] = [];
871
+ const failedTransactions: FailedTransaction[] = [];
872
+
873
+ for (let i = 0; i < solChunks.length; i++) {
874
+ try {
875
+ const solIn = solChunks[i];
876
+ const baseAmountOut = this.calculateAmmBuyOutput(solIn, reserves);
877
+ const slippageBps = this.calcSlippage({
878
+ tradeSize: solIn,
879
+ reserve: reserves.quoteAmount,
880
+ slippageOpt: tradeOpt.slippage
881
+ });
882
+ const maxQuoteIn = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
883
+ const priority = this.genPriority(tradeOpt.priority);
884
+
885
+ const tx = new Transaction().add(
886
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
887
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
888
+ );
889
+
890
+ const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint);
891
+ const userQuoteAta = await this.ensureWSOLAta(
892
+ tx,
893
+ this.wallet.publicKey,
894
+ "buy",
895
+ maxQuoteIn
896
+ );
897
+
898
+ const buyIx = this.createAmmBuyInstruction(
899
+ poolInfo,
900
+ userBaseAta,
901
+ userQuoteAta,
902
+ baseAmountOut,
903
+ maxQuoteIn
904
+ );
905
+
906
+ tx.add(buyIx);
907
+ tx.add(
908
+ createCloseAccountInstruction(
909
+ userQuoteAta,
910
+ this.wallet.publicKey,
911
+ this.wallet.publicKey
912
+ )
913
+ );
914
+
915
+ const { blockhash, lastValidBlockHeight } =
916
+ await this.connection.getLatestBlockhash('finalized');
917
+ tx.recentBlockhash = blockhash;
918
+ tx.feePayer = this.wallet.publicKey;
919
+ tx.sign(this.wallet);
920
+
921
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
922
+ skipPreflight: false,
923
+ maxRetries: 2
924
+ });
925
+
926
+ pendingTransactions.push({
927
+ signature,
928
+ lastValidBlockHeight,
929
+ index: i
930
+ });
931
+ } catch (e) {
932
+ failedTransactions.push({
933
+ index: i,
934
+ error: (e as Error).message
935
+ });
936
+ }
937
+ }
938
+
939
+ return { pendingTransactions, failedTransactions };
940
+ }
941
+
942
+ async ammSell(tokenAddr: string, totalTokenIn: bigint, tradeOpt: TradeOptions): Promise<TradeResult> {
943
+ const mint = new PublicKey(tokenAddr);
944
+ const poolInfo = await this.getAmmPoolInfo(mint);
945
+ const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
946
+ const totalSolOut = this.calculateAmmSellOutput(totalTokenIn, reserves);
947
+
948
+ const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
949
+ ? [totalTokenIn]
950
+ : this.splitIntoN(
951
+ totalTokenIn,
952
+ Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx)
953
+ );
954
+
955
+ const pendingTransactions: PendingTransaction[] = [];
956
+ const failedTransactions: FailedTransaction[] = [];
957
+
958
+ for (let i = 0; i < tokenChunks.length; i++) {
959
+ try {
960
+ const tokenIn = tokenChunks[i];
961
+ const solOut = this.calculateAmmSellOutput(tokenIn, reserves);
962
+ const slippageBps = this.calcSlippage({
963
+ tradeSize: tokenIn,
964
+ reserve: reserves.baseAmount,
965
+ slippageOpt: tradeOpt.slippage
966
+ });
967
+ const minQuoteOut = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
968
+ const priority = this.genPriority(tradeOpt.priority);
969
+
970
+ const tx = new Transaction().add(
971
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
972
+ ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
973
+ );
974
+
975
+ const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint);
976
+ const userQuoteAta = await this.ensureWSOLAta(tx, this.wallet.publicKey, "sell");
977
+
978
+ const sellIx = this.createAmmSellInstruction(
979
+ poolInfo,
980
+ userBaseAta,
981
+ userQuoteAta,
982
+ tokenIn,
983
+ minQuoteOut
984
+ );
985
+
986
+ tx.add(sellIx);
987
+ tx.add(
988
+ createCloseAccountInstruction(
989
+ userQuoteAta,
990
+ this.wallet.publicKey,
991
+ this.wallet.publicKey
992
+ )
993
+ );
994
+
995
+ const { blockhash, lastValidBlockHeight } =
996
+ await this.connection.getLatestBlockhash('finalized');
997
+ tx.recentBlockhash = blockhash;
998
+ tx.feePayer = this.wallet.publicKey;
999
+ tx.sign(this.wallet);
1000
+
1001
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
1002
+ skipPreflight: false,
1003
+ maxRetries: 2
1004
+ });
1005
+
1006
+ pendingTransactions.push({
1007
+ signature,
1008
+ lastValidBlockHeight,
1009
+ index: i
1010
+ });
1011
+ } catch (e) {
1012
+ failedTransactions.push({
1013
+ index: i,
1014
+ error: (e as Error).message
1015
+ });
1016
+ }
1017
+ }
1018
+
1019
+ return { pendingTransactions, failedTransactions };
1020
+ }
1021
+
1022
+ /* ---------- AMM 池信息 ---------- */
1023
+
1024
+ async getAmmPoolInfo(mint: PublicKey): Promise<PoolInfo> {
1025
+ const [poolAuthority] = PublicKey.findProgramAddressSync(
1026
+ [Buffer.from("pool-authority"), mint.toBuffer()],
1027
+ PROGRAM_IDS.PUMP
1028
+ );
1029
+
1030
+ const [pool] = PublicKey.findProgramAddressSync(
1031
+ [
1032
+ Buffer.from("pool"),
1033
+ new BN(0).toArrayLike(Buffer, "le", 2),
1034
+ poolAuthority.toBuffer(),
1035
+ mint.toBuffer(),
1036
+ SOL_MINT.toBuffer()
1037
+ ],
1038
+ PROGRAM_IDS.PUMP_AMM
1039
+ );
1040
+
1041
+ const acc = await this.connection.getAccountInfo(pool);
1042
+ if (!acc) throw new Error("AMM pool not found");
1043
+
1044
+ const poolKeys = parsePoolKeys(acc.data);
1045
+
1046
+ const [globalConfigPda] = PublicKey.findProgramAddressSync(
1047
+ [Buffer.from("global_config")],
1048
+ PROGRAM_IDS.PUMP_AMM
1049
+ );
1050
+
1051
+ const globalConfigAcc = await this.connection.getAccountInfo(globalConfigPda);
1052
+ if (!globalConfigAcc) throw new Error("Global config not found");
1053
+
1054
+ const globalConfig = this.parseAmmGlobalConfig(globalConfigAcc.data, globalConfigPda);
1055
+
1056
+ return { pool, poolAuthority, poolKeys, globalConfig };
1057
+ }
1058
+
1059
+ parseAmmGlobalConfig(data: Buffer, address: PublicKey) {
1060
+ let offset = 8;
1061
+
1062
+ const admin = new PublicKey(data.slice(offset, offset + 32));
1063
+ offset += 32;
1064
+
1065
+ offset += 8;
1066
+ offset += 8;
1067
+ offset += 1;
1068
+
1069
+ const protocolFeeRecipients: PublicKey[] = [];
1070
+ for (let i = 0; i < 8; i++) {
1071
+ protocolFeeRecipients.push(new PublicKey(data.slice(offset, offset + 32)));
1072
+ offset += 32;
1073
+ }
1074
+
1075
+ return { address, admin, protocolFeeRecipients };
1076
+ }
1077
+
1078
+ async getAmmPoolReserves(poolKeys: any): Promise<PoolReserves> {
1079
+ const [baseInfo, quoteInfo] = await Promise.all([
1080
+ this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
1081
+ this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
1082
+ ]);
1083
+
1084
+ return {
1085
+ baseAmount: BigInt(baseInfo.value.amount),
1086
+ quoteAmount: BigInt(quoteInfo.value.amount),
1087
+ baseDecimals: baseInfo.value.decimals,
1088
+ quoteDecimals: quoteInfo.value.decimals
1089
+ };
1090
+ }
1091
+
1092
+ /* ---------- AMM 指令构建 ---------- */
1093
+
1094
+ createAmmBuyInstruction(
1095
+ poolInfo: PoolInfo,
1096
+ userBaseAta: PublicKey,
1097
+ userQuoteAta: PublicKey,
1098
+ baseAmountOut: bigint,
1099
+ maxQuoteAmountIn: bigint
1100
+ ): TransactionInstruction {
1101
+ const { pool, poolKeys, globalConfig } = poolInfo;
1102
+
1103
+ const [eventAuthority] = PublicKey.findProgramAddressSync(
1104
+ [Buffer.from("__event_authority")],
1105
+ PROGRAM_IDS.PUMP_AMM
1106
+ );
1107
+
1108
+ const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
1109
+ [Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
1110
+ PROGRAM_IDS.PUMP_AMM
1111
+ );
1112
+
1113
+ const coinCreatorVaultAta = getAssociatedTokenAddressSync(
1114
+ SOL_MINT,
1115
+ coinCreatorVaultAuthority,
1116
+ true,
1117
+ TOKEN_PROGRAM_ID,
1118
+ ASSOCIATED_TOKEN_PROGRAM_ID
1119
+ );
1120
+
1121
+ const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
1122
+ [Buffer.from("global_volume_accumulator")],
1123
+ PROGRAM_IDS.PUMP_AMM
1124
+ );
1125
+
1126
+ const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1127
+ [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
1128
+ PROGRAM_IDS.PUMP_AMM
1129
+ );
1130
+
1131
+ const [feeConfig] = PublicKey.findProgramAddressSync(
1132
+ [Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
1133
+ PROGRAM_IDS.FEE
1134
+ );
1135
+
1136
+ const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
1137
+ const protocolFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1138
+ SOL_MINT,
1139
+ protocolFeeRecipient,
1140
+ true,
1141
+ TOKEN_PROGRAM_ID,
1142
+ ASSOCIATED_TOKEN_PROGRAM_ID
1143
+ );
1144
+
1145
+ return new TransactionInstruction({
1146
+ programId: PROGRAM_IDS.PUMP_AMM,
1147
+ keys: [
1148
+ { pubkey: pool, isSigner: false, isWritable: true },
1149
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
1150
+ { pubkey: globalConfig.address, isSigner: false, isWritable: false },
1151
+ { pubkey: poolKeys.baseMint, isSigner: false, isWritable: false },
1152
+ { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
1153
+ { pubkey: userBaseAta, isSigner: false, isWritable: true },
1154
+ { pubkey: userQuoteAta, isSigner: false, isWritable: true },
1155
+ { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
1156
+ { pubkey: poolKeys.poolQuoteTokenAccount, isSigner: false, isWritable: true },
1157
+ { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
1158
+ { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
1159
+ { pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
1160
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1161
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1162
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1163
+ { pubkey: eventAuthority, isSigner: false, isWritable: false },
1164
+ { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
1165
+ { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
1166
+ { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
1167
+ { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
1168
+ { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1169
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
1170
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
1171
+ ],
1172
+ data: Buffer.concat([
1173
+ DISCRIMINATORS.BUY,
1174
+ u64(baseAmountOut),
1175
+ u64(maxQuoteAmountIn),
1176
+ Buffer.from([1, 1])
1177
+ ])
1178
+ });
1179
+ }
1180
+
1181
+ createAmmSellInstruction(
1182
+ poolInfo: PoolInfo,
1183
+ userBaseAta: PublicKey,
1184
+ userQuoteAta: PublicKey,
1185
+ baseAmountIn: bigint,
1186
+ minQuoteAmountOut: bigint
1187
+ ): TransactionInstruction {
1188
+ const { pool, poolKeys, globalConfig } = poolInfo;
1189
+
1190
+ const [eventAuthority] = PublicKey.findProgramAddressSync(
1191
+ [Buffer.from("__event_authority")],
1192
+ PROGRAM_IDS.PUMP_AMM
1193
+ );
1194
+
1195
+ const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
1196
+ [Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
1197
+ PROGRAM_IDS.PUMP_AMM
1198
+ );
1199
+
1200
+ const coinCreatorVaultAta = getAssociatedTokenAddressSync(
1201
+ SOL_MINT,
1202
+ coinCreatorVaultAuthority,
1203
+ true,
1204
+ TOKEN_PROGRAM_ID,
1205
+ ASSOCIATED_TOKEN_PROGRAM_ID
1206
+ );
1207
+
1208
+ const [feeConfig] = PublicKey.findProgramAddressSync(
1209
+ [Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
1210
+ PROGRAM_IDS.FEE
1211
+ );
1212
+
1213
+ const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
1214
+ const protocolFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1215
+ SOL_MINT,
1216
+ protocolFeeRecipient,
1217
+ true,
1218
+ TOKEN_PROGRAM_ID,
1219
+ ASSOCIATED_TOKEN_PROGRAM_ID
1220
+ );
1221
+
1222
+ return new TransactionInstruction({
1223
+ programId: PROGRAM_IDS.PUMP_AMM,
1224
+ keys: [
1225
+ { pubkey: pool, isSigner: false, isWritable: true },
1226
+ { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
1227
+ { pubkey: globalConfig.address, isSigner: false, isWritable: false },
1228
+ { pubkey: poolKeys.baseMint, isSigner: false, isWritable: false },
1229
+ { pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
1230
+ { pubkey: userBaseAta, isSigner: false, isWritable: true },
1231
+ { pubkey: userQuoteAta, isSigner: false, isWritable: true },
1232
+ { pubkey: poolKeys.poolBaseTokenAccount, isSigner: false, isWritable: true },
1233
+ { pubkey: poolKeys.poolQuoteTokenAccount, isSigner: false, isWritable: true },
1234
+ { pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
1235
+ { pubkey: protocolFeeRecipientTokenAccount, isSigner: false, isWritable: true },
1236
+ { pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
1237
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1238
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
1239
+ { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1240
+ { pubkey: eventAuthority, isSigner: false, isWritable: false },
1241
+ { pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
1242
+ { pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
1243
+ { pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
1244
+ { pubkey: feeConfig, isSigner: false, isWritable: false },
1245
+ { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false }
1246
+ ],
1247
+ data: Buffer.concat([
1248
+ DISCRIMINATORS.SELL,
1249
+ u64(baseAmountIn),
1250
+ u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n)
1251
+ ])
1252
+ });
1253
+ }
1254
+
1255
+ /* ---------- 交易确认 ---------- */
1256
+
1257
+ async confirmTransactionWithPolling(
1258
+ signature: string,
1259
+ lastValidBlockHeight: number,
1260
+ maxAttempts: number = 5,
1261
+ delayMs: number = 2000
1262
+ ): Promise<string> {
1263
+ console.log('✅ 交易已发送:', signature);
1264
+ console.log('🔗 查看交易: https://solscan.io/tx/' + signature);
1265
+
1266
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1267
+ await new Promise(resolve => setTimeout(resolve, delayMs));
1268
+
1269
+ try {
1270
+ console.log(`🔍 检查交易状态 (${attempt}/${maxAttempts})...`);
1271
+
1272
+ const txInfo = await this.connection.getTransaction(signature, {
1273
+ commitment: 'confirmed',
1274
+ maxSupportedTransactionVersion: 0
1275
+ });
1276
+
1277
+ if (txInfo) {
1278
+ if (txInfo.meta?.err) {
1279
+ console.error('❌ 交易失败:', txInfo.meta.err);
1280
+ throw new Error('交易失败: ' + JSON.stringify(txInfo.meta.err));
1281
+ }
1282
+
1283
+ console.log('✅ 交易已确认!');
1284
+ return signature;
1285
+ }
1286
+
1287
+ const currentBlockHeight = await this.connection.getBlockHeight('finalized');
1288
+ if (currentBlockHeight > lastValidBlockHeight) {
1289
+ console.log('⚠️ 交易已过期(超过有效区块高度)');
1290
+ throw new Error('交易过期:未在有效区块高度内确认');
1291
+ }
1292
+
1293
+ } catch (error) {
1294
+ const err = error as Error;
1295
+ if (err.message?.includes('交易失败') || err.message?.includes('交易过期')) {
1296
+ throw error;
1297
+ }
1298
+ console.log(`⚠️ 查询出错,继续重试: ${err.message}`);
1299
+ }
1300
+ }
1301
+
1302
+ throw new Error(
1303
+ `交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}。请手动检查交易状态。`
1304
+ );
1305
+ }
1306
+
1307
+ /* ---------- 事件监听 ---------- */
1308
+
1309
+ listenTrades(callback: (event: TradeEvent) => void, mintFilter?: PublicKey | null) {
1310
+ return this.connection.onLogs(
1311
+ PROGRAM_IDS.PUMP,
1312
+ (log) => {
1313
+ for (const logLine of log.logs) {
1314
+ if (!logLine.startsWith("Program data: ")) continue;
1315
+
1316
+ const buf = Buffer.from(logLine.replace("Program data: ", ""), "base64");
1317
+
1318
+ if (!buf.subarray(0, 8).equals(DISCRIMINATORS.TRADE_EVENT)) continue;
1319
+
1320
+ let offset = 8;
1321
+ const mint = new PublicKey(buf.slice(offset, offset + 32));
1322
+ offset += 32;
1323
+
1324
+ if (mintFilter && !mint.equals(mintFilter)) return;
1325
+
1326
+ const solAmount = buf.readBigUInt64LE(offset);
1327
+ offset += 8;
1328
+ const tokenAmount = buf.readBigUInt64LE(offset);
1329
+ offset += 8;
1330
+ const isBuy = buf[offset++] === 1;
1331
+ const user = new PublicKey(buf.slice(offset, offset + 32));
1332
+ offset += 32;
1333
+ const timestamp = Number(buf.readBigInt64LE(offset));
1334
+
1335
+ callback({
1336
+ mint: mint.toBase58(),
1337
+ solAmount,
1338
+ tokenAmount,
1339
+ isBuy,
1340
+ user: user.toBase58(),
1341
+ timestamp,
1342
+ signature: log.signature
1343
+ });
1344
+ }
1345
+ },
1346
+ "confirmed"
1347
+ );
1348
+ }
1349
+
1350
+ /* ---------- 元数据查询 ---------- */
1351
+
1352
+ async fetchMeta(tokenAddr: string): Promise<MetadataInfo | null> {
1353
+ const mint = new PublicKey(tokenAddr);
1354
+
1355
+ try {
1356
+ const metadata = await getTokenMetadata(
1357
+ this.connection,
1358
+ mint,
1359
+ "confirmed",
1360
+ TOKEN_2022_PROGRAM_ID
1361
+ );
1362
+
1363
+ return {
1364
+ name: metadata?.name || "",
1365
+ symbol: metadata?.symbol || "",
1366
+ uri: metadata?.uri || ""
1367
+ };
1368
+ } catch (e) {
1369
+ const metadataPda = PublicKey.findProgramAddressSync(
1370
+ [Buffer.from("metadata"), PROGRAM_IDS.METADATA.toBuffer(), mint.toBuffer()],
1371
+ PROGRAM_IDS.METADATA
1372
+ )[0];
1373
+
1374
+ const acc = await this.connection.getAccountInfo(metadataPda);
1375
+ if (!acc) return null;
1376
+
1377
+ const meta = parseMetadataAccount(acc.data);
1378
+
1379
+ return {
1380
+ name: meta?.name?.replace(/\u0000/g, '') || "",
1381
+ symbol: meta?.symbol?.replace(/\u0000/g, '') || "",
1382
+ uri: meta?.uri?.replace(/\u0000/g, '') || ""
1383
+ };
1384
+ }
1385
+ }
1386
+
1387
+ // 公开wallet方法
1388
+ getWallet() {
1389
+ return this.wallet;
1390
+ }
1391
+
1392
+ getConnection() {
1393
+ return this.connection;
1394
+ }
1395
+
1396
+ /**
1397
+ * 清除token program缓存
1398
+ */
1399
+ clearTokenProgramCache(tokenAddr: string) {
1400
+ if (tokenAddr) {
1401
+ this.tokenProgramCache.delete(tokenAddr);
1402
+ } else {
1403
+ this.tokenProgramCache.clear();
1404
+ }
1405
+ }
1406
+
1407
+ /**
1408
+ * 获取缓存的token program信息
1409
+ */
1410
+ getCachedTokenProgram(tokenAddr: string) {
1411
+ return this.tokenProgramCache.get(tokenAddr);
1412
+ }
1413
+ }
1414
+
1415
+ // 导出类型
1416
+ export type {
1417
+ TradeOptions,
1418
+ PendingTransaction,
1419
+ FailedTransaction,
1420
+ TradeResult,
1421
+ BondingCurveState,
1422
+ BondingInfo,
1423
+ PoolReserves,
1424
+ TradeEvent,
1425
+ GlobalState,
1426
+ TokenProgramType,
1427
+ PoolInfo,
1428
+ MetadataInfo
1429
+ };