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/dist/index.d.ts +57 -13
- package/dist/index.js +751 -162
- package/index.js +524 -298
- package/index.ts +1361 -307
- package/package.json +1 -1
- package/tests/instruction-accounts.test.ts +0 -92
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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,
|
|
326
|
+
constructor(rpc: string, wallet: Wallet) {
|
|
282
327
|
this.connection = new Connection(rpc, "confirmed");
|
|
283
|
-
this.
|
|
284
|
-
this.
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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({
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
579
|
-
const
|
|
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
|
|
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
|
-
[
|
|
593
|
-
|
|
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<
|
|
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.
|
|
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<
|
|
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.
|
|
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 !==
|
|
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<{
|
|
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 =
|
|
681
|
-
this.
|
|
682
|
-
|
|
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 !==
|
|
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<{
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
888
|
+
this.publicKey,
|
|
743
889
|
ata,
|
|
744
|
-
this.
|
|
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 ===
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
[
|
|
910
|
-
|
|
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.
|
|
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([
|
|
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(
|
|
1146
|
+
await this.connection.getLatestBlockhash("finalized");
|
|
966
1147
|
tx.recentBlockhash = blockhash;
|
|
967
|
-
tx.feePayer = this.
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const signature = await this.connection.sendRawTransaction(
|
|
971
|
-
|
|
972
|
-
|
|
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(
|
|
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 =
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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.
|
|
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
|
-
[
|
|
1040
|
-
|
|
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.
|
|
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.
|
|
1095
|
-
|
|
1288
|
+
tx.feePayer = this.publicKey;
|
|
1289
|
+
await this.signTx(tx);
|
|
1096
1290
|
|
|
1097
|
-
const signature = await this.connection.sendRawTransaction(
|
|
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(
|
|
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(
|
|
1143
|
-
const userQuoteAta = await this.ensureWSOLAta(
|
|
1347
|
+
const userBaseAta = await this.ensureAta(
|
|
1144
1348
|
tx,
|
|
1145
|
-
|
|
1146
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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(
|
|
1382
|
+
await this.connection.getLatestBlockhash("finalized");
|
|
1170
1383
|
tx.recentBlockhash = blockhash;
|
|
1171
|
-
tx.feePayer = this.
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
const signature = await this.connection.sendRawTransaction(
|
|
1175
|
-
|
|
1176
|
-
|
|
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(
|
|
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
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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(
|
|
1229
|
-
|
|
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
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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(
|
|
1486
|
+
await this.connection.getLatestBlockhash("finalized");
|
|
1251
1487
|
tx.recentBlockhash = blockhash;
|
|
1252
|
-
tx.feePayer = this.
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
const signature = await this.connection.sendRawTransaction(
|
|
1256
|
-
|
|
1257
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
[
|
|
1391
|
-
|
|
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({
|
|
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
|
-
{
|
|
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.
|
|
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
|
-
{
|
|
1444
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
[
|
|
1523
|
-
|
|
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
|
-
{
|
|
1538
|
-
|
|
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
|
-
{
|
|
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.
|
|
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
|
-
{
|
|
1558
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
1590
|
-
console.log(
|
|
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:
|
|
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(
|
|
1606
|
-
throw new Error(
|
|
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 =
|
|
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 (
|
|
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(
|
|
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(
|
|
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
|
-
[
|
|
1697
|
-
|
|
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
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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
|
};
|