pump-trader 1.1.5 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -4
- package/dist/index.js +5 -28
- package/index.js +484 -270
- package/index.ts +1311 -277
- package/package.json +1 -1
- package/tests/instruction-accounts.test.ts +0 -92
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
TransactionInstruction,
|
|
6
6
|
SystemProgram,
|
|
7
7
|
ComputeBudgetProgram,
|
|
8
|
-
Keypair
|
|
8
|
+
Keypair,
|
|
9
9
|
} from "@solana/web3.js";
|
|
10
10
|
|
|
11
11
|
import {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
getTokenMetadata,
|
|
18
18
|
TOKEN_2022_PROGRAM_ID,
|
|
19
19
|
TOKEN_PROGRAM_ID,
|
|
20
|
-
getMint
|
|
20
|
+
getMint,
|
|
21
21
|
} from "@solana/spl-token";
|
|
22
22
|
|
|
23
23
|
import BN from "bn.js";
|
|
@@ -30,28 +30,30 @@ const PROGRAM_IDS = {
|
|
|
30
30
|
PUMP_AMM: new PublicKey("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"),
|
|
31
31
|
METADATA: new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
|
|
32
32
|
FEE: new PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
|
|
33
|
-
EVENT_AUTHORITY: new PublicKey(
|
|
33
|
+
EVENT_AUTHORITY: new PublicKey(
|
|
34
|
+
"Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1",
|
|
35
|
+
),
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
|
|
37
39
|
|
|
38
40
|
const SEEDS = {
|
|
39
41
|
FEE_CONFIG: new Uint8Array([
|
|
40
|
-
1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
|
|
41
|
-
|
|
42
|
+
1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81,
|
|
43
|
+
137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176,
|
|
42
44
|
]),
|
|
43
45
|
AMM_FEE_CONFIG: Buffer.from([
|
|
44
|
-
12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101,
|
|
45
|
-
|
|
46
|
+
12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101, 244,
|
|
47
|
+
41, 141, 49, 86, 213, 113, 180, 212, 248, 9, 12, 24, 233, 168, 99,
|
|
46
48
|
]),
|
|
47
49
|
GLOBAL: Buffer.from("global"),
|
|
48
|
-
BONDING: Buffer.from("bonding-curve")
|
|
50
|
+
BONDING: Buffer.from("bonding-curve"),
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
const DISCRIMINATORS = {
|
|
52
54
|
BUY: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]),
|
|
53
55
|
SELL: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]),
|
|
54
|
-
TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238])
|
|
56
|
+
TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238]),
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
const AMM_FEE_BPS = 100n; // 1%
|
|
@@ -64,7 +66,7 @@ const PUMP_NEW_FEE_RECIPIENTS = [
|
|
|
64
66
|
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
65
67
|
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
66
68
|
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
67
|
-
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
69
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW",
|
|
68
70
|
].map((value) => new PublicKey(value));
|
|
69
71
|
|
|
70
72
|
/* ================= 工具函数 ================= */
|
|
@@ -84,7 +86,9 @@ const readU32 = (buf, offsetObj) => {
|
|
|
84
86
|
|
|
85
87
|
const readString = (buf, offsetObj) => {
|
|
86
88
|
const len = readU32(buf, offsetObj);
|
|
87
|
-
const str = buf
|
|
89
|
+
const str = buf
|
|
90
|
+
.slice(offsetObj.offset, offsetObj.offset + len)
|
|
91
|
+
.toString("utf8");
|
|
88
92
|
offsetObj.offset += len;
|
|
89
93
|
return str;
|
|
90
94
|
};
|
|
@@ -94,10 +98,14 @@ const readString = (buf, offsetObj) => {
|
|
|
94
98
|
function parseMetadataAccount(data) {
|
|
95
99
|
const offsetObj = { offset: 1 }; // 跳过 key
|
|
96
100
|
|
|
97
|
-
const updateAuthority = new PublicKey(
|
|
101
|
+
const updateAuthority = new PublicKey(
|
|
102
|
+
data.slice(offsetObj.offset, offsetObj.offset + 32),
|
|
103
|
+
);
|
|
98
104
|
offsetObj.offset += 32;
|
|
99
105
|
|
|
100
|
-
const mint = new PublicKey(
|
|
106
|
+
const mint = new PublicKey(
|
|
107
|
+
data.slice(offsetObj.offset, offsetObj.offset + 32),
|
|
108
|
+
);
|
|
101
109
|
offsetObj.offset += 32;
|
|
102
110
|
|
|
103
111
|
const name = readString(data, offsetObj);
|
|
@@ -109,13 +117,13 @@ function parseMetadataAccount(data) {
|
|
|
109
117
|
mint: mint.toBase58(),
|
|
110
118
|
name,
|
|
111
119
|
symbol,
|
|
112
|
-
uri
|
|
120
|
+
uri,
|
|
113
121
|
};
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
function parsePoolKeys(data) {
|
|
117
125
|
if (!data || data.length < 280) {
|
|
118
|
-
throw new Error(
|
|
126
|
+
throw new Error("Invalid pool account data");
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
let offset = 8; // 跳过 discriminator
|
|
@@ -152,7 +160,8 @@ function parsePoolKeys(data) {
|
|
|
152
160
|
|
|
153
161
|
const isMayhemMode = data.readUInt8(offset) === 1;
|
|
154
162
|
offset += 1;
|
|
155
|
-
const isCashbackCoin =
|
|
163
|
+
const isCashbackCoin =
|
|
164
|
+
offset < data.length ? data.readUInt8(offset) === 1 : false;
|
|
156
165
|
|
|
157
166
|
return {
|
|
158
167
|
creator,
|
|
@@ -163,7 +172,7 @@ function parsePoolKeys(data) {
|
|
|
163
172
|
poolQuoteTokenAccount,
|
|
164
173
|
coinCreator,
|
|
165
174
|
isMayhemMode,
|
|
166
|
-
isCashbackCoin
|
|
175
|
+
isCashbackCoin,
|
|
167
176
|
};
|
|
168
177
|
}
|
|
169
178
|
|
|
@@ -173,7 +182,10 @@ export class PumpTrader {
|
|
|
173
182
|
constructor(rpc, privateKey) {
|
|
174
183
|
this.connection = new Connection(rpc, "confirmed");
|
|
175
184
|
this.wallet = Keypair.fromSecretKey(bs58.decode(privateKey));
|
|
176
|
-
this.global = PublicKey.findProgramAddressSync(
|
|
185
|
+
this.global = PublicKey.findProgramAddressSync(
|
|
186
|
+
[SEEDS.GLOBAL],
|
|
187
|
+
PROGRAM_IDS.PUMP,
|
|
188
|
+
)[0];
|
|
177
189
|
this.globalState = null;
|
|
178
190
|
this.tokenProgramCache = new Map(); // 缓存token program检测结果
|
|
179
191
|
}
|
|
@@ -195,29 +207,59 @@ export class PumpTrader {
|
|
|
195
207
|
|
|
196
208
|
try {
|
|
197
209
|
// 首先尝试获取 TOKEN_2022 的代币信息
|
|
198
|
-
const mintData = await getMint(
|
|
210
|
+
const mintData = await getMint(
|
|
211
|
+
this.connection,
|
|
212
|
+
mint,
|
|
213
|
+
"confirmed",
|
|
214
|
+
TOKEN_2022_PROGRAM_ID,
|
|
215
|
+
);
|
|
199
216
|
const result = {
|
|
200
217
|
type: "TOKEN_2022_PROGRAM_ID",
|
|
201
|
-
programId: TOKEN_2022_PROGRAM_ID
|
|
218
|
+
programId: TOKEN_2022_PROGRAM_ID,
|
|
202
219
|
};
|
|
203
220
|
this.tokenProgramCache.set(tokenAddr, result);
|
|
204
221
|
return result;
|
|
205
222
|
} catch (e) {
|
|
206
223
|
try {
|
|
207
224
|
// 如果失败,尝试标准 TOKEN_PROGRAM_ID
|
|
208
|
-
const mintData = await getMint(
|
|
225
|
+
const mintData = await getMint(
|
|
226
|
+
this.connection,
|
|
227
|
+
mint,
|
|
228
|
+
"confirmed",
|
|
229
|
+
TOKEN_PROGRAM_ID,
|
|
230
|
+
);
|
|
209
231
|
const result = {
|
|
210
232
|
type: "TOKEN_PROGRAM_ID",
|
|
211
|
-
programId: TOKEN_PROGRAM_ID
|
|
233
|
+
programId: TOKEN_PROGRAM_ID,
|
|
212
234
|
};
|
|
213
235
|
this.tokenProgramCache.set(tokenAddr, result);
|
|
214
236
|
return result;
|
|
215
237
|
} catch (error) {
|
|
216
|
-
throw new Error(
|
|
238
|
+
throw new Error(
|
|
239
|
+
`Failed to detect token program for ${tokenAddr}: ${error}`,
|
|
240
|
+
);
|
|
217
241
|
}
|
|
218
242
|
}
|
|
219
243
|
}
|
|
220
244
|
|
|
245
|
+
async detectQuoteTokenProgram(quoteMint) {
|
|
246
|
+
const quoteAddr = quoteMint.toBase58();
|
|
247
|
+
if (this.tokenProgramCache.has(quoteAddr)) {
|
|
248
|
+
return this.tokenProgramCache.get(quoteAddr).programId;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await getMint(
|
|
252
|
+
this.connection,
|
|
253
|
+
quoteMint,
|
|
254
|
+
"confirmed",
|
|
255
|
+
TOKEN_2022_PROGRAM_ID,
|
|
256
|
+
);
|
|
257
|
+
return TOKEN_2022_PROGRAM_ID;
|
|
258
|
+
} catch {
|
|
259
|
+
return TOKEN_PROGRAM_ID;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
221
263
|
/* ---------- 内盘/外盘检测 ---------- */
|
|
222
264
|
|
|
223
265
|
/**
|
|
@@ -282,7 +324,7 @@ export class PumpTrader {
|
|
|
282
324
|
initialVirtualSolReserves: readU64(),
|
|
283
325
|
initialRealTokenReserves: readU64(),
|
|
284
326
|
tokenTotalSupply: readU64(),
|
|
285
|
-
feeBasisPoints: readU64()
|
|
327
|
+
feeBasisPoints: readU64(),
|
|
286
328
|
};
|
|
287
329
|
|
|
288
330
|
return this.globalState;
|
|
@@ -293,14 +335,14 @@ export class PumpTrader {
|
|
|
293
335
|
getBondingPda(mint) {
|
|
294
336
|
return PublicKey.findProgramAddressSync(
|
|
295
337
|
[SEEDS.BONDING, mint.toBuffer()],
|
|
296
|
-
PROGRAM_IDS.PUMP
|
|
338
|
+
PROGRAM_IDS.PUMP,
|
|
297
339
|
)[0];
|
|
298
340
|
}
|
|
299
341
|
|
|
300
342
|
deriveBondingCurveV2(mint) {
|
|
301
343
|
return PublicKey.findProgramAddressSync(
|
|
302
344
|
[Buffer.from("bonding-curve-v2"), mint.toBuffer()],
|
|
303
|
-
PROGRAM_IDS.PUMP
|
|
345
|
+
PROGRAM_IDS.PUMP,
|
|
304
346
|
)[0];
|
|
305
347
|
}
|
|
306
348
|
|
|
@@ -316,7 +358,11 @@ export class PumpTrader {
|
|
|
316
358
|
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
317
359
|
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
318
360
|
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
319
|
-
{
|
|
361
|
+
{
|
|
362
|
+
pubkey: args.associatedBondingCurve,
|
|
363
|
+
isSigner: false,
|
|
364
|
+
isWritable: true,
|
|
365
|
+
},
|
|
320
366
|
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
321
367
|
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
322
368
|
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
@@ -324,12 +370,16 @@ export class PumpTrader {
|
|
|
324
370
|
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
325
371
|
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
326
372
|
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
327
|
-
{
|
|
373
|
+
{
|
|
374
|
+
pubkey: args.globalVolumeAccumulator,
|
|
375
|
+
isSigner: false,
|
|
376
|
+
isWritable: false,
|
|
377
|
+
},
|
|
328
378
|
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
329
379
|
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
330
380
|
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
331
381
|
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
332
|
-
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
382
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true },
|
|
333
383
|
];
|
|
334
384
|
}
|
|
335
385
|
|
|
@@ -340,7 +390,11 @@ export class PumpTrader {
|
|
|
340
390
|
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
341
391
|
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
342
392
|
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
343
|
-
{
|
|
393
|
+
{
|
|
394
|
+
pubkey: args.associatedBondingCurve,
|
|
395
|
+
isSigner: false,
|
|
396
|
+
isWritable: true,
|
|
397
|
+
},
|
|
344
398
|
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
345
399
|
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
346
400
|
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
@@ -349,16 +403,20 @@ export class PumpTrader {
|
|
|
349
403
|
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
350
404
|
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
351
405
|
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
352
|
-
{ pubkey: args.feeProgram, isSigner: false, isWritable: false }
|
|
406
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
353
407
|
];
|
|
354
408
|
|
|
355
409
|
if (args.isCashbackCoin) {
|
|
356
|
-
keys.push({
|
|
410
|
+
keys.push({
|
|
411
|
+
pubkey: args.userVolumeAccumulator,
|
|
412
|
+
isSigner: false,
|
|
413
|
+
isWritable: true,
|
|
414
|
+
});
|
|
357
415
|
}
|
|
358
416
|
|
|
359
417
|
keys.push(
|
|
360
418
|
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
361
|
-
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
419
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true },
|
|
362
420
|
);
|
|
363
421
|
|
|
364
422
|
return keys;
|
|
@@ -383,12 +441,6 @@ export class PumpTrader {
|
|
|
383
441
|
|
|
384
442
|
const creator = new PublicKey(data.slice(offset, offset + 32));
|
|
385
443
|
offset += 32;
|
|
386
|
-
|
|
387
|
-
if (offset + 32 <= data.length - 2) {
|
|
388
|
-
state.quoteMint = new PublicKey(data.slice(offset, offset + 32));
|
|
389
|
-
offset += 32;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
444
|
state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
|
|
393
445
|
offset += 1;
|
|
394
446
|
state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
|
|
@@ -400,25 +452,29 @@ export class PumpTrader {
|
|
|
400
452
|
|
|
401
453
|
calcBuy(solIn, state) {
|
|
402
454
|
const newVirtualSol = state.virtualSolReserves + solIn;
|
|
403
|
-
const newVirtualToken =
|
|
455
|
+
const newVirtualToken =
|
|
456
|
+
(state.virtualSolReserves * state.virtualTokenReserves) / newVirtualSol;
|
|
404
457
|
return state.virtualTokenReserves - newVirtualToken;
|
|
405
458
|
}
|
|
406
459
|
|
|
407
460
|
calcSell(tokenIn, state) {
|
|
408
461
|
const newVirtualToken = state.virtualTokenReserves + tokenIn;
|
|
409
|
-
const newVirtualSol =
|
|
462
|
+
const newVirtualSol =
|
|
463
|
+
(state.virtualSolReserves * state.virtualTokenReserves) / newVirtualToken;
|
|
410
464
|
return state.virtualSolReserves - newVirtualSol;
|
|
411
465
|
}
|
|
412
466
|
|
|
413
467
|
calculateAmmBuyOutput(quoteIn, reserves) {
|
|
414
|
-
const quoteInAfterFee =
|
|
468
|
+
const quoteInAfterFee =
|
|
469
|
+
(quoteIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
|
|
415
470
|
const numerator = reserves.baseAmount * quoteInAfterFee;
|
|
416
471
|
const denominator = reserves.quoteAmount + quoteInAfterFee;
|
|
417
472
|
return numerator / denominator;
|
|
418
473
|
}
|
|
419
474
|
|
|
420
475
|
calculateAmmSellOutput(baseIn, reserves) {
|
|
421
|
-
const baseInAfterFee =
|
|
476
|
+
const baseInAfterFee =
|
|
477
|
+
(baseIn * (BPS_DENOMINATOR - AMM_FEE_BPS)) / BPS_DENOMINATOR;
|
|
422
478
|
const numerator = reserves.quoteAmount * baseInAfterFee;
|
|
423
479
|
const denominator = reserves.baseAmount + baseInAfterFee;
|
|
424
480
|
return numerator / denominator;
|
|
@@ -429,30 +485,34 @@ export class PumpTrader {
|
|
|
429
485
|
async getPriceAndStatus(tokenAddr) {
|
|
430
486
|
const mint = new PublicKey(tokenAddr);
|
|
431
487
|
const { state } = await this.loadBonding(mint);
|
|
432
|
-
const quoteMint = this.getEffectiveQuoteMint(state.quoteMint);
|
|
433
488
|
|
|
434
489
|
if (state.complete) {
|
|
435
|
-
const price = await this.getAmmPrice(mint
|
|
490
|
+
const price = await this.getAmmPrice(mint);
|
|
436
491
|
return { price, completed: true };
|
|
437
492
|
}
|
|
438
493
|
|
|
439
494
|
const oneToken = BigInt(1_000_000);
|
|
440
|
-
const
|
|
441
|
-
const
|
|
442
|
-
const price = Number(quoteOut) / 10 ** quoteDecimals;
|
|
495
|
+
const solOut = this.calcSell(oneToken, state);
|
|
496
|
+
const price = Number(solOut) / 1e9;
|
|
443
497
|
return { price, completed: false };
|
|
444
498
|
}
|
|
445
499
|
|
|
446
|
-
async getAmmPrice(mint
|
|
500
|
+
async getAmmPrice(mint) {
|
|
447
501
|
const [poolCreator] = PublicKey.findProgramAddressSync(
|
|
448
502
|
[Buffer.from("pool-authority"), mint.toBuffer()],
|
|
449
|
-
PROGRAM_IDS.PUMP
|
|
503
|
+
PROGRAM_IDS.PUMP,
|
|
450
504
|
);
|
|
451
505
|
|
|
452
506
|
const indexBuffer = new BN(0).toArrayLike(Buffer, "le", 2);
|
|
453
507
|
const [pool] = PublicKey.findProgramAddressSync(
|
|
454
|
-
[
|
|
455
|
-
|
|
508
|
+
[
|
|
509
|
+
Buffer.from("pool"),
|
|
510
|
+
indexBuffer,
|
|
511
|
+
poolCreator.toBuffer(),
|
|
512
|
+
mint.toBuffer(),
|
|
513
|
+
SOL_MINT.toBuffer(),
|
|
514
|
+
],
|
|
515
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
456
516
|
);
|
|
457
517
|
|
|
458
518
|
const acc = await this.connection.getAccountInfo(pool);
|
|
@@ -461,34 +521,12 @@ export class PumpTrader {
|
|
|
461
521
|
const poolKeys = parsePoolKeys(acc.data);
|
|
462
522
|
const [baseInfo, quoteInfo] = await Promise.all([
|
|
463
523
|
this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
|
|
464
|
-
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
|
|
524
|
+
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
|
|
465
525
|
]);
|
|
466
526
|
|
|
467
527
|
return quoteInfo.value.uiAmount / baseInfo.value.uiAmount;
|
|
468
528
|
}
|
|
469
529
|
|
|
470
|
-
getEffectiveQuoteMint(quoteMint) {
|
|
471
|
-
if (!quoteMint || quoteMint.equals(PublicKey.default)) {
|
|
472
|
-
return SOL_MINT;
|
|
473
|
-
}
|
|
474
|
-
return quoteMint;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
async getMintDecimals(mint) {
|
|
478
|
-
if (mint.equals(SOL_MINT)) {
|
|
479
|
-
return 9;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const mintInfo = await this.connection.getParsedAccountInfo(mint);
|
|
483
|
-
const parsedData = mintInfo.value?.data;
|
|
484
|
-
|
|
485
|
-
if (parsedData && "parsed" in parsedData) {
|
|
486
|
-
return parsedData.parsed.info.decimals;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
throw new Error(`Unable to determine mint decimals for ${mint.toBase58()}`);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
530
|
/* ---------- 余额查询 ---------- */
|
|
493
531
|
|
|
494
532
|
/**
|
|
@@ -502,9 +540,12 @@ export class PumpTrader {
|
|
|
502
540
|
const mint = new PublicKey(tokenAddr);
|
|
503
541
|
const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
|
|
504
542
|
this.wallet.publicKey,
|
|
505
|
-
{ mint }
|
|
543
|
+
{ mint },
|
|
544
|
+
);
|
|
545
|
+
return (
|
|
546
|
+
tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount ||
|
|
547
|
+
0
|
|
506
548
|
);
|
|
507
|
-
return tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount || 0;
|
|
508
549
|
} else {
|
|
509
550
|
// 查询所有代币
|
|
510
551
|
return this.getAllTokenBalances();
|
|
@@ -518,13 +559,13 @@ export class PumpTrader {
|
|
|
518
559
|
async getAllTokenBalances() {
|
|
519
560
|
const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(
|
|
520
561
|
this.wallet.publicKey,
|
|
521
|
-
{ programId: TOKEN_PROGRAM_ID }
|
|
562
|
+
{ programId: TOKEN_PROGRAM_ID },
|
|
522
563
|
);
|
|
523
564
|
|
|
524
565
|
const balances = tokenAccounts.value
|
|
525
566
|
.map((account) => {
|
|
526
567
|
const parsed = account.account.data.parsed;
|
|
527
|
-
if (parsed.type !==
|
|
568
|
+
if (parsed.type !== "account") return null;
|
|
528
569
|
|
|
529
570
|
const tokenAmount = parsed.info.tokenAmount;
|
|
530
571
|
if (Number(tokenAmount.amount) === 0) return null;
|
|
@@ -533,21 +574,22 @@ export class PumpTrader {
|
|
|
533
574
|
mint: parsed.info.mint,
|
|
534
575
|
amount: BigInt(tokenAmount.amount),
|
|
535
576
|
decimals: tokenAmount.decimals,
|
|
536
|
-
uiAmount: tokenAmount.uiAmount || 0
|
|
577
|
+
uiAmount: tokenAmount.uiAmount || 0,
|
|
537
578
|
};
|
|
538
579
|
})
|
|
539
580
|
.filter((item) => item !== null);
|
|
540
581
|
|
|
541
582
|
// 同时查询 TOKEN_2022_PROGRAM_ID
|
|
542
|
-
const token2022Accounts =
|
|
543
|
-
this.
|
|
544
|
-
|
|
545
|
-
|
|
583
|
+
const token2022Accounts =
|
|
584
|
+
await this.connection.getParsedTokenAccountsByOwner(
|
|
585
|
+
this.wallet.publicKey,
|
|
586
|
+
{ programId: TOKEN_2022_PROGRAM_ID },
|
|
587
|
+
);
|
|
546
588
|
|
|
547
589
|
const token2022Balances = token2022Accounts.value
|
|
548
590
|
.map((account) => {
|
|
549
591
|
const parsed = account.account.data.parsed;
|
|
550
|
-
if (parsed.type !==
|
|
592
|
+
if (parsed.type !== "account") return null;
|
|
551
593
|
|
|
552
594
|
const tokenAmount = parsed.info.tokenAmount;
|
|
553
595
|
if (Number(tokenAmount.amount) === 0) return null;
|
|
@@ -556,7 +598,7 @@ export class PumpTrader {
|
|
|
556
598
|
mint: parsed.info.mint,
|
|
557
599
|
amount: BigInt(tokenAmount.amount),
|
|
558
600
|
decimals: tokenAmount.decimals,
|
|
559
|
-
uiAmount: tokenAmount.uiAmount || 0
|
|
601
|
+
uiAmount: tokenAmount.uiAmount || 0,
|
|
560
602
|
};
|
|
561
603
|
})
|
|
562
604
|
.filter((item) => item !== null);
|
|
@@ -574,7 +616,7 @@ export class PumpTrader {
|
|
|
574
616
|
mint: b.mint,
|
|
575
617
|
amount: Number(b.amount),
|
|
576
618
|
decimals: b.decimals,
|
|
577
|
-
uiAmount: b.uiAmount
|
|
619
|
+
uiAmount: b.uiAmount,
|
|
578
620
|
}));
|
|
579
621
|
|
|
580
622
|
return uniqueBalances;
|
|
@@ -594,7 +636,7 @@ export class PumpTrader {
|
|
|
594
636
|
this.wallet.publicKey,
|
|
595
637
|
false,
|
|
596
638
|
program,
|
|
597
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
639
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
598
640
|
);
|
|
599
641
|
|
|
600
642
|
const acc = await this.connection.getAccountInfo(ata);
|
|
@@ -606,8 +648,8 @@ export class PumpTrader {
|
|
|
606
648
|
this.wallet.publicKey,
|
|
607
649
|
mint,
|
|
608
650
|
program,
|
|
609
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
610
|
-
)
|
|
651
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
652
|
+
),
|
|
611
653
|
);
|
|
612
654
|
}
|
|
613
655
|
|
|
@@ -620,7 +662,7 @@ export class PumpTrader {
|
|
|
620
662
|
owner,
|
|
621
663
|
false,
|
|
622
664
|
TOKEN_PROGRAM_ID,
|
|
623
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
665
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
624
666
|
);
|
|
625
667
|
|
|
626
668
|
const acc = await this.connection.getAccountInfo(wsolAta);
|
|
@@ -633,18 +675,18 @@ export class PumpTrader {
|
|
|
633
675
|
owner,
|
|
634
676
|
SOL_MINT,
|
|
635
677
|
TOKEN_PROGRAM_ID,
|
|
636
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
637
|
-
)
|
|
678
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
679
|
+
),
|
|
638
680
|
);
|
|
639
681
|
}
|
|
640
682
|
|
|
641
|
-
if (mode ===
|
|
683
|
+
if (mode === "buy") {
|
|
642
684
|
tx.add(
|
|
643
685
|
SystemProgram.transfer({
|
|
644
686
|
fromPubkey: owner,
|
|
645
687
|
toPubkey: wsolAta,
|
|
646
|
-
lamports: Number(lamports)
|
|
647
|
-
})
|
|
688
|
+
lamports: Number(lamports),
|
|
689
|
+
}),
|
|
648
690
|
);
|
|
649
691
|
tx.add(createSyncNativeInstruction(wsolAta));
|
|
650
692
|
}
|
|
@@ -658,7 +700,9 @@ export class PumpTrader {
|
|
|
658
700
|
if (!priorityOpt?.enableRandom || !priorityOpt.randomRange) {
|
|
659
701
|
return priorityOpt.base;
|
|
660
702
|
}
|
|
661
|
-
return
|
|
703
|
+
return (
|
|
704
|
+
priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange)
|
|
705
|
+
);
|
|
662
706
|
}
|
|
663
707
|
|
|
664
708
|
calcSlippage({ tradeSize, reserve, slippageOpt }) {
|
|
@@ -706,25 +750,47 @@ export class PumpTrader {
|
|
|
706
750
|
|
|
707
751
|
/**
|
|
708
752
|
* 统一的自动买入接口,自动判断内盘/外盘
|
|
753
|
+
* @param {boolean} [useV2=false] - use buy_v2 instruction (supports USDC quote)
|
|
754
|
+
* @param {PublicKey} [quoteMint=SOL_MINT] - quote mint (SOL_MINT for SOL-paired, USDC mint for USDC-paired)
|
|
709
755
|
*/
|
|
710
|
-
async autoBuy(
|
|
756
|
+
async autoBuy(
|
|
757
|
+
tokenAddr,
|
|
758
|
+
totalSolIn,
|
|
759
|
+
tradeOpt,
|
|
760
|
+
useV2 = false,
|
|
761
|
+
quoteMint = SOL_MINT,
|
|
762
|
+
) {
|
|
711
763
|
const mode = await this.getTradeMode(tokenAddr);
|
|
712
764
|
if (mode === "bonding") {
|
|
765
|
+
if (useV2) {
|
|
766
|
+
return this.buyV2(tokenAddr, totalSolIn, tradeOpt, quoteMint);
|
|
767
|
+
}
|
|
713
768
|
return this.buy(tokenAddr, totalSolIn, tradeOpt);
|
|
714
769
|
} else {
|
|
715
|
-
return this.ammBuy(tokenAddr, totalSolIn, tradeOpt);
|
|
770
|
+
return this.ammBuy(tokenAddr, totalSolIn, tradeOpt, quoteMint);
|
|
716
771
|
}
|
|
717
772
|
}
|
|
718
773
|
|
|
719
774
|
/**
|
|
720
775
|
* 统一的自动卖出接口,自动判断内盘/外盘
|
|
776
|
+
* @param {boolean} [useV2=false] - use sell_v2 instruction
|
|
777
|
+
* @param {PublicKey} [quoteMint=SOL_MINT] - quote mint
|
|
721
778
|
*/
|
|
722
|
-
async autoSell(
|
|
779
|
+
async autoSell(
|
|
780
|
+
tokenAddr,
|
|
781
|
+
totalTokenIn,
|
|
782
|
+
tradeOpt,
|
|
783
|
+
useV2 = false,
|
|
784
|
+
quoteMint = SOL_MINT,
|
|
785
|
+
) {
|
|
723
786
|
const mode = await this.getTradeMode(tokenAddr);
|
|
724
787
|
if (mode === "bonding") {
|
|
788
|
+
if (useV2) {
|
|
789
|
+
return this.sellV2(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
|
|
790
|
+
}
|
|
725
791
|
return this.sell(tokenAddr, totalTokenIn, tradeOpt);
|
|
726
792
|
} else {
|
|
727
|
-
return this.ammSell(tokenAddr, totalTokenIn, tradeOpt);
|
|
793
|
+
return this.ammSell(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
|
|
728
794
|
}
|
|
729
795
|
}
|
|
730
796
|
|
|
@@ -745,27 +811,30 @@ export class PumpTrader {
|
|
|
745
811
|
bonding,
|
|
746
812
|
true,
|
|
747
813
|
tokenProgram.programId,
|
|
748
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
814
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
749
815
|
);
|
|
750
816
|
|
|
751
817
|
const [creatorVault] = PublicKey.findProgramAddressSync(
|
|
752
818
|
[Buffer.from("creator-vault"), creator.toBuffer()],
|
|
753
|
-
PROGRAM_IDS.PUMP
|
|
819
|
+
PROGRAM_IDS.PUMP,
|
|
754
820
|
);
|
|
755
821
|
|
|
756
822
|
const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
757
823
|
[Buffer.from("global_volume_accumulator")],
|
|
758
|
-
PROGRAM_IDS.PUMP
|
|
824
|
+
PROGRAM_IDS.PUMP,
|
|
759
825
|
);
|
|
760
826
|
|
|
761
827
|
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
762
|
-
[
|
|
763
|
-
|
|
828
|
+
[
|
|
829
|
+
Buffer.from("user_volume_accumulator"),
|
|
830
|
+
this.wallet.publicKey.toBuffer(),
|
|
831
|
+
],
|
|
832
|
+
PROGRAM_IDS.PUMP,
|
|
764
833
|
);
|
|
765
834
|
|
|
766
835
|
const [feeConfig] = PublicKey.findProgramAddressSync(
|
|
767
836
|
[Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
|
|
768
|
-
PROGRAM_IDS.FEE
|
|
837
|
+
PROGRAM_IDS.FEE,
|
|
769
838
|
);
|
|
770
839
|
const feeRecipient = this.pickFeeRecipient();
|
|
771
840
|
|
|
@@ -776,14 +845,14 @@ export class PumpTrader {
|
|
|
776
845
|
const slippageBps = this.calcSlippage({
|
|
777
846
|
tradeSize: solIn,
|
|
778
847
|
reserve: state.virtualSolReserves,
|
|
779
|
-
slippageOpt: tradeOpt.slippage
|
|
848
|
+
slippageOpt: tradeOpt.slippage,
|
|
780
849
|
});
|
|
781
850
|
const maxSol = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
|
|
782
851
|
const priority = this.genPriority(tradeOpt.priority);
|
|
783
852
|
|
|
784
853
|
const tx = new Transaction().add(
|
|
785
854
|
ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
|
|
786
|
-
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
|
|
855
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
|
|
787
856
|
);
|
|
788
857
|
|
|
789
858
|
const userAta = await this.ensureAta(tx, mint, tokenProgram.programId);
|
|
@@ -808,32 +877,39 @@ export class PumpTrader {
|
|
|
808
877
|
feeProgram: PROGRAM_IDS.FEE,
|
|
809
878
|
bondingCurveV2,
|
|
810
879
|
feeRecipient,
|
|
811
|
-
tokenProgramId: tokenProgram.programId
|
|
880
|
+
tokenProgramId: tokenProgram.programId,
|
|
812
881
|
}),
|
|
813
|
-
data: Buffer.concat([
|
|
814
|
-
|
|
882
|
+
data: Buffer.concat([
|
|
883
|
+
DISCRIMINATORS.BUY,
|
|
884
|
+
u64(tokenOut),
|
|
885
|
+
u64(maxSol),
|
|
886
|
+
]),
|
|
887
|
+
}),
|
|
815
888
|
);
|
|
816
889
|
|
|
817
890
|
const { blockhash, lastValidBlockHeight } =
|
|
818
|
-
await this.connection.getLatestBlockhash(
|
|
891
|
+
await this.connection.getLatestBlockhash("finalized");
|
|
819
892
|
tx.recentBlockhash = blockhash;
|
|
820
893
|
tx.feePayer = this.wallet.publicKey;
|
|
821
894
|
tx.sign(this.wallet);
|
|
822
895
|
|
|
823
|
-
const signature = await this.connection.sendRawTransaction(
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
896
|
+
const signature = await this.connection.sendRawTransaction(
|
|
897
|
+
tx.serialize(),
|
|
898
|
+
{
|
|
899
|
+
skipPreflight: false,
|
|
900
|
+
maxRetries: 2,
|
|
901
|
+
},
|
|
902
|
+
);
|
|
827
903
|
|
|
828
904
|
pendingTransactions.push({
|
|
829
905
|
signature,
|
|
830
906
|
lastValidBlockHeight,
|
|
831
|
-
index: i
|
|
907
|
+
index: i,
|
|
832
908
|
});
|
|
833
909
|
} catch (e) {
|
|
834
910
|
failedTransactions.push({
|
|
835
911
|
index: i,
|
|
836
|
-
error: e.message
|
|
912
|
+
error: e.message,
|
|
837
913
|
});
|
|
838
914
|
}
|
|
839
915
|
}
|
|
@@ -852,12 +928,15 @@ export class PumpTrader {
|
|
|
852
928
|
if (state.complete) throw new Error("Bonding curve already completed");
|
|
853
929
|
|
|
854
930
|
const totalSolOut = this.calcSell(totalTokenIn, state);
|
|
855
|
-
const tokenChunks =
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
931
|
+
const tokenChunks =
|
|
932
|
+
totalSolOut <= tradeOpt.maxSolPerTx
|
|
933
|
+
? [totalTokenIn]
|
|
934
|
+
: this.splitIntoN(
|
|
935
|
+
totalTokenIn,
|
|
936
|
+
Number(
|
|
937
|
+
(totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx,
|
|
938
|
+
),
|
|
939
|
+
);
|
|
861
940
|
|
|
862
941
|
const pendingTransactions = []; // 待确认的交易
|
|
863
942
|
const failedTransactions = []; // 发送失败的交易
|
|
@@ -867,7 +946,7 @@ export class PumpTrader {
|
|
|
867
946
|
bonding,
|
|
868
947
|
true,
|
|
869
948
|
tokenProgram.programId,
|
|
870
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
949
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
871
950
|
);
|
|
872
951
|
|
|
873
952
|
const userAta = getAssociatedTokenAddressSync(
|
|
@@ -875,21 +954,24 @@ export class PumpTrader {
|
|
|
875
954
|
this.wallet.publicKey,
|
|
876
955
|
false,
|
|
877
956
|
tokenProgram.programId,
|
|
878
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
957
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
879
958
|
);
|
|
880
959
|
|
|
881
960
|
const [creatorVault] = PublicKey.findProgramAddressSync(
|
|
882
961
|
[Buffer.from("creator-vault"), creator.toBuffer()],
|
|
883
|
-
PROGRAM_IDS.PUMP
|
|
962
|
+
PROGRAM_IDS.PUMP,
|
|
884
963
|
);
|
|
885
964
|
|
|
886
965
|
const [feeConfig] = PublicKey.findProgramAddressSync(
|
|
887
966
|
[Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
|
|
888
|
-
PROGRAM_IDS.FEE
|
|
967
|
+
PROGRAM_IDS.FEE,
|
|
889
968
|
);
|
|
890
969
|
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
891
|
-
[
|
|
892
|
-
|
|
970
|
+
[
|
|
971
|
+
Buffer.from("user_volume_accumulator"),
|
|
972
|
+
this.wallet.publicKey.toBuffer(),
|
|
973
|
+
],
|
|
974
|
+
PROGRAM_IDS.PUMP,
|
|
893
975
|
);
|
|
894
976
|
const feeRecipient = this.pickFeeRecipient();
|
|
895
977
|
|
|
@@ -900,14 +982,14 @@ export class PumpTrader {
|
|
|
900
982
|
const slippageBps = this.calcSlippage({
|
|
901
983
|
tradeSize: tokenIn,
|
|
902
984
|
reserve: state.virtualTokenReserves,
|
|
903
|
-
slippageOpt: tradeOpt.slippage
|
|
985
|
+
slippageOpt: tradeOpt.slippage,
|
|
904
986
|
});
|
|
905
987
|
const minSol = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
|
|
906
988
|
const priority = this.genPriority(tradeOpt.priority);
|
|
907
989
|
|
|
908
990
|
const tx = new Transaction().add(
|
|
909
991
|
ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
|
|
910
|
-
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
|
|
992
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
|
|
911
993
|
);
|
|
912
994
|
|
|
913
995
|
tx.add(
|
|
@@ -930,14 +1012,14 @@ export class PumpTrader {
|
|
|
930
1012
|
feeRecipient,
|
|
931
1013
|
isCashbackCoin: !!state.isCashbackCoin,
|
|
932
1014
|
userVolumeAccumulator,
|
|
933
|
-
tokenProgramId: tokenProgram.programId
|
|
1015
|
+
tokenProgramId: tokenProgram.programId,
|
|
934
1016
|
}),
|
|
935
1017
|
data: Buffer.concat([
|
|
936
1018
|
DISCRIMINATORS.SELL,
|
|
937
1019
|
u64(tokenIn),
|
|
938
|
-
u64(minSol > 0n ? minSol : 1n)
|
|
939
|
-
])
|
|
940
|
-
})
|
|
1020
|
+
u64(minSol > 0n ? minSol : 1n),
|
|
1021
|
+
]),
|
|
1022
|
+
}),
|
|
941
1023
|
);
|
|
942
1024
|
|
|
943
1025
|
const { blockhash, lastValidBlockHeight } =
|
|
@@ -946,16 +1028,18 @@ export class PumpTrader {
|
|
|
946
1028
|
tx.feePayer = this.wallet.publicKey;
|
|
947
1029
|
tx.sign(this.wallet);
|
|
948
1030
|
|
|
949
|
-
const signature = await this.connection.sendRawTransaction(
|
|
1031
|
+
const signature = await this.connection.sendRawTransaction(
|
|
1032
|
+
tx.serialize(),
|
|
1033
|
+
);
|
|
950
1034
|
pendingTransactions.push({
|
|
951
1035
|
signature,
|
|
952
1036
|
lastValidBlockHeight,
|
|
953
|
-
index: i
|
|
1037
|
+
index: i,
|
|
954
1038
|
});
|
|
955
1039
|
} catch (e) {
|
|
956
1040
|
failedTransactions.push({
|
|
957
1041
|
index: i,
|
|
958
|
-
error: e.message
|
|
1042
|
+
error: e.message,
|
|
959
1043
|
});
|
|
960
1044
|
}
|
|
961
1045
|
}
|
|
@@ -965,12 +1049,16 @@ export class PumpTrader {
|
|
|
965
1049
|
|
|
966
1050
|
/* ---------- 外盘交易 ---------- */
|
|
967
1051
|
|
|
968
|
-
async ammBuy(tokenAddr, totalSolIn, tradeOpt) {
|
|
1052
|
+
async ammBuy(tokenAddr, totalSolIn, tradeOpt, quoteMint = SOL_MINT) {
|
|
969
1053
|
const mint = new PublicKey(tokenAddr);
|
|
970
|
-
const poolInfo = await this.getAmmPoolInfo(mint);
|
|
1054
|
+
const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
|
|
971
1055
|
const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
|
|
972
1056
|
const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
|
|
973
1057
|
const tokenProgram = await this.detectTokenProgram(tokenAddr);
|
|
1058
|
+
const isSolQuote = quoteMint.equals(SOL_MINT);
|
|
1059
|
+
const quoteTokenProgramId = isSolQuote
|
|
1060
|
+
? TOKEN_PROGRAM_ID
|
|
1061
|
+
: await this.detectQuoteTokenProgram(quoteMint);
|
|
974
1062
|
const pendingTransactions = [];
|
|
975
1063
|
const failedTransactions = [];
|
|
976
1064
|
|
|
@@ -981,23 +1069,29 @@ export class PumpTrader {
|
|
|
981
1069
|
const slippageBps = this.calcSlippage({
|
|
982
1070
|
tradeSize: solIn,
|
|
983
1071
|
reserve: reserves.quoteAmount,
|
|
984
|
-
slippageOpt: tradeOpt.slippage
|
|
1072
|
+
slippageOpt: tradeOpt.slippage,
|
|
985
1073
|
});
|
|
986
1074
|
const maxQuoteIn = (solIn * BigInt(10_000 + slippageBps)) / 10_000n;
|
|
987
1075
|
const priority = this.genPriority(tradeOpt.priority);
|
|
988
1076
|
|
|
989
1077
|
const tx = new Transaction().add(
|
|
990
1078
|
ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
|
|
991
|
-
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
|
|
1079
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
|
|
992
1080
|
);
|
|
993
1081
|
|
|
994
|
-
const userBaseAta = await this.ensureAta(
|
|
995
|
-
const userQuoteAta = await this.ensureWSOLAta(
|
|
1082
|
+
const userBaseAta = await this.ensureAta(
|
|
996
1083
|
tx,
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
maxQuoteIn
|
|
1084
|
+
poolInfo.poolKeys.baseMint,
|
|
1085
|
+
tokenProgram.programId,
|
|
1000
1086
|
);
|
|
1087
|
+
const userQuoteAta = isSolQuote
|
|
1088
|
+
? await this.ensureWSOLAta(
|
|
1089
|
+
tx,
|
|
1090
|
+
this.wallet.publicKey,
|
|
1091
|
+
"buy",
|
|
1092
|
+
maxQuoteIn,
|
|
1093
|
+
)
|
|
1094
|
+
: await this.ensureAta(tx, quoteMint, quoteTokenProgramId);
|
|
1001
1095
|
|
|
1002
1096
|
const buyIx = this.createAmmBuyInstruction(
|
|
1003
1097
|
poolInfo,
|
|
@@ -1005,38 +1099,43 @@ export class PumpTrader {
|
|
|
1005
1099
|
userQuoteAta,
|
|
1006
1100
|
baseAmountOut,
|
|
1007
1101
|
maxQuoteIn,
|
|
1008
|
-
tokenProgram.programId
|
|
1102
|
+
tokenProgram.programId,
|
|
1009
1103
|
);
|
|
1010
1104
|
|
|
1011
1105
|
tx.add(buyIx);
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1106
|
+
if (isSolQuote) {
|
|
1107
|
+
tx.add(
|
|
1108
|
+
createCloseAccountInstruction(
|
|
1109
|
+
userQuoteAta,
|
|
1110
|
+
this.wallet.publicKey,
|
|
1111
|
+
this.wallet.publicKey,
|
|
1112
|
+
),
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1019
1115
|
|
|
1020
1116
|
const { blockhash, lastValidBlockHeight } =
|
|
1021
|
-
await this.connection.getLatestBlockhash(
|
|
1117
|
+
await this.connection.getLatestBlockhash("finalized");
|
|
1022
1118
|
tx.recentBlockhash = blockhash;
|
|
1023
1119
|
tx.feePayer = this.wallet.publicKey;
|
|
1024
1120
|
tx.sign(this.wallet);
|
|
1025
1121
|
|
|
1026
|
-
const signature = await this.connection.sendRawTransaction(
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1122
|
+
const signature = await this.connection.sendRawTransaction(
|
|
1123
|
+
tx.serialize(),
|
|
1124
|
+
{
|
|
1125
|
+
skipPreflight: false,
|
|
1126
|
+
maxRetries: 2,
|
|
1127
|
+
},
|
|
1128
|
+
);
|
|
1030
1129
|
|
|
1031
1130
|
pendingTransactions.push({
|
|
1032
1131
|
signature,
|
|
1033
1132
|
lastValidBlockHeight,
|
|
1034
|
-
index: i
|
|
1133
|
+
index: i,
|
|
1035
1134
|
});
|
|
1036
1135
|
} catch (e) {
|
|
1037
1136
|
failedTransactions.push({
|
|
1038
1137
|
index: i,
|
|
1039
|
-
error: e.message
|
|
1138
|
+
error: e.message,
|
|
1040
1139
|
});
|
|
1041
1140
|
}
|
|
1042
1141
|
}
|
|
@@ -1044,19 +1143,26 @@ export class PumpTrader {
|
|
|
1044
1143
|
return { pendingTransactions, failedTransactions };
|
|
1045
1144
|
}
|
|
1046
1145
|
|
|
1047
|
-
async ammSell(tokenAddr, totalTokenIn, tradeOpt) {
|
|
1146
|
+
async ammSell(tokenAddr, totalTokenIn, tradeOpt, quoteMint = SOL_MINT) {
|
|
1048
1147
|
const mint = new PublicKey(tokenAddr);
|
|
1049
|
-
const poolInfo = await this.getAmmPoolInfo(mint);
|
|
1148
|
+
const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
|
|
1050
1149
|
const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
|
|
1051
1150
|
const totalSolOut = this.calculateAmmSellOutput(totalTokenIn, reserves);
|
|
1052
1151
|
const tokenProgram = await this.detectTokenProgram(tokenAddr);
|
|
1053
|
-
|
|
1054
|
-
const
|
|
1055
|
-
?
|
|
1056
|
-
: this.
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1152
|
+
const isSolQuote = quoteMint.equals(SOL_MINT);
|
|
1153
|
+
const quoteTokenProgramId = isSolQuote
|
|
1154
|
+
? TOKEN_PROGRAM_ID
|
|
1155
|
+
: await this.detectQuoteTokenProgram(quoteMint);
|
|
1156
|
+
|
|
1157
|
+
const tokenChunks =
|
|
1158
|
+
totalSolOut <= tradeOpt.maxSolPerTx
|
|
1159
|
+
? [totalTokenIn]
|
|
1160
|
+
: this.splitIntoN(
|
|
1161
|
+
totalTokenIn,
|
|
1162
|
+
Number(
|
|
1163
|
+
(totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx,
|
|
1164
|
+
),
|
|
1165
|
+
);
|
|
1060
1166
|
|
|
1061
1167
|
const pendingTransactions = [];
|
|
1062
1168
|
const failedTransactions = [];
|
|
@@ -1068,18 +1174,24 @@ export class PumpTrader {
|
|
|
1068
1174
|
const slippageBps = this.calcSlippage({
|
|
1069
1175
|
tradeSize: tokenIn,
|
|
1070
1176
|
reserve: reserves.baseAmount,
|
|
1071
|
-
slippageOpt: tradeOpt.slippage
|
|
1177
|
+
slippageOpt: tradeOpt.slippage,
|
|
1072
1178
|
});
|
|
1073
1179
|
const minQuoteOut = (solOut * BigInt(10_000 - slippageBps)) / 10_000n;
|
|
1074
1180
|
const priority = this.genPriority(tradeOpt.priority);
|
|
1075
1181
|
|
|
1076
1182
|
const tx = new Transaction().add(
|
|
1077
1183
|
ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
|
|
1078
|
-
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority })
|
|
1184
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }),
|
|
1079
1185
|
);
|
|
1080
1186
|
|
|
1081
|
-
const userBaseAta = await this.ensureAta(
|
|
1082
|
-
|
|
1187
|
+
const userBaseAta = await this.ensureAta(
|
|
1188
|
+
tx,
|
|
1189
|
+
poolInfo.poolKeys.baseMint,
|
|
1190
|
+
tokenProgram.programId,
|
|
1191
|
+
);
|
|
1192
|
+
const userQuoteAta = isSolQuote
|
|
1193
|
+
? await this.ensureWSOLAta(tx, this.wallet.publicKey, "sell")
|
|
1194
|
+
: await this.ensureAta(tx, quoteMint, quoteTokenProgramId);
|
|
1083
1195
|
|
|
1084
1196
|
const sellIx = this.createAmmSellInstruction(
|
|
1085
1197
|
poolInfo,
|
|
@@ -1087,38 +1199,43 @@ export class PumpTrader {
|
|
|
1087
1199
|
userQuoteAta,
|
|
1088
1200
|
tokenIn,
|
|
1089
1201
|
minQuoteOut,
|
|
1090
|
-
tokenProgram.programId
|
|
1202
|
+
tokenProgram.programId,
|
|
1091
1203
|
);
|
|
1092
1204
|
|
|
1093
1205
|
tx.add(sellIx);
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1206
|
+
if (isSolQuote) {
|
|
1207
|
+
tx.add(
|
|
1208
|
+
createCloseAccountInstruction(
|
|
1209
|
+
userQuoteAta,
|
|
1210
|
+
this.wallet.publicKey,
|
|
1211
|
+
this.wallet.publicKey,
|
|
1212
|
+
),
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1101
1215
|
|
|
1102
1216
|
const { blockhash, lastValidBlockHeight } =
|
|
1103
|
-
await this.connection.getLatestBlockhash(
|
|
1217
|
+
await this.connection.getLatestBlockhash("finalized");
|
|
1104
1218
|
tx.recentBlockhash = blockhash;
|
|
1105
1219
|
tx.feePayer = this.wallet.publicKey;
|
|
1106
1220
|
tx.sign(this.wallet);
|
|
1107
1221
|
|
|
1108
|
-
const signature = await this.connection.sendRawTransaction(
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1222
|
+
const signature = await this.connection.sendRawTransaction(
|
|
1223
|
+
tx.serialize(),
|
|
1224
|
+
{
|
|
1225
|
+
skipPreflight: false,
|
|
1226
|
+
maxRetries: 2,
|
|
1227
|
+
},
|
|
1228
|
+
);
|
|
1112
1229
|
|
|
1113
1230
|
pendingTransactions.push({
|
|
1114
1231
|
signature,
|
|
1115
1232
|
lastValidBlockHeight,
|
|
1116
|
-
index: i
|
|
1233
|
+
index: i,
|
|
1117
1234
|
});
|
|
1118
1235
|
} catch (e) {
|
|
1119
1236
|
failedTransactions.push({
|
|
1120
1237
|
index: i,
|
|
1121
|
-
error: e.message
|
|
1238
|
+
error: e.message,
|
|
1122
1239
|
});
|
|
1123
1240
|
}
|
|
1124
1241
|
}
|
|
@@ -1128,10 +1245,10 @@ export class PumpTrader {
|
|
|
1128
1245
|
|
|
1129
1246
|
/* ---------- AMM 池信息 ---------- */
|
|
1130
1247
|
|
|
1131
|
-
async getAmmPoolInfo(mint) {
|
|
1248
|
+
async getAmmPoolInfo(mint, quoteMint = SOL_MINT) {
|
|
1132
1249
|
const [poolAuthority] = PublicKey.findProgramAddressSync(
|
|
1133
1250
|
[Buffer.from("pool-authority"), mint.toBuffer()],
|
|
1134
|
-
PROGRAM_IDS.PUMP
|
|
1251
|
+
PROGRAM_IDS.PUMP,
|
|
1135
1252
|
);
|
|
1136
1253
|
|
|
1137
1254
|
const [pool] = PublicKey.findProgramAddressSync(
|
|
@@ -1140,9 +1257,9 @@ export class PumpTrader {
|
|
|
1140
1257
|
new BN(0).toArrayLike(Buffer, "le", 2),
|
|
1141
1258
|
poolAuthority.toBuffer(),
|
|
1142
1259
|
mint.toBuffer(),
|
|
1143
|
-
|
|
1260
|
+
quoteMint.toBuffer(),
|
|
1144
1261
|
],
|
|
1145
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1262
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1146
1263
|
);
|
|
1147
1264
|
|
|
1148
1265
|
const acc = await this.connection.getAccountInfo(pool);
|
|
@@ -1152,13 +1269,17 @@ export class PumpTrader {
|
|
|
1152
1269
|
|
|
1153
1270
|
const [globalConfigPda] = PublicKey.findProgramAddressSync(
|
|
1154
1271
|
[Buffer.from("global_config")],
|
|
1155
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1272
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1156
1273
|
);
|
|
1157
1274
|
|
|
1158
|
-
const globalConfigAcc =
|
|
1275
|
+
const globalConfigAcc =
|
|
1276
|
+
await this.connection.getAccountInfo(globalConfigPda);
|
|
1159
1277
|
if (!globalConfigAcc) throw new Error("Global config not found");
|
|
1160
1278
|
|
|
1161
|
-
const globalConfig = this.parseAmmGlobalConfig(
|
|
1279
|
+
const globalConfig = this.parseAmmGlobalConfig(
|
|
1280
|
+
globalConfigAcc.data,
|
|
1281
|
+
globalConfigPda,
|
|
1282
|
+
);
|
|
1162
1283
|
|
|
1163
1284
|
return { pool, poolAuthority, poolKeys, globalConfig };
|
|
1164
1285
|
}
|
|
@@ -1175,7 +1296,9 @@ export class PumpTrader {
|
|
|
1175
1296
|
|
|
1176
1297
|
const protocolFeeRecipients = [];
|
|
1177
1298
|
for (let i = 0; i < 8; i++) {
|
|
1178
|
-
protocolFeeRecipients.push(
|
|
1299
|
+
protocolFeeRecipients.push(
|
|
1300
|
+
new PublicKey(data.slice(offset, offset + 32)),
|
|
1301
|
+
);
|
|
1179
1302
|
offset += 32;
|
|
1180
1303
|
}
|
|
1181
1304
|
|
|
@@ -1185,38 +1308,45 @@ export class PumpTrader {
|
|
|
1185
1308
|
async getAmmPoolReserves(poolKeys) {
|
|
1186
1309
|
const [baseInfo, quoteInfo] = await Promise.all([
|
|
1187
1310
|
this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
|
|
1188
|
-
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
|
|
1311
|
+
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
|
|
1189
1312
|
]);
|
|
1190
1313
|
|
|
1191
1314
|
return {
|
|
1192
1315
|
baseAmount: BigInt(baseInfo.value.amount),
|
|
1193
1316
|
quoteAmount: BigInt(quoteInfo.value.amount),
|
|
1194
1317
|
baseDecimals: baseInfo.value.decimals,
|
|
1195
|
-
quoteDecimals: quoteInfo.value.decimals
|
|
1318
|
+
quoteDecimals: quoteInfo.value.decimals,
|
|
1196
1319
|
};
|
|
1197
1320
|
}
|
|
1198
1321
|
|
|
1199
1322
|
deriveAmmPoolV2(baseMint) {
|
|
1200
1323
|
return PublicKey.findProgramAddressSync(
|
|
1201
1324
|
[Buffer.from("pool-v2"), baseMint.toBuffer()],
|
|
1202
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1325
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1203
1326
|
)[0];
|
|
1204
1327
|
}
|
|
1205
1328
|
|
|
1206
1329
|
/* ---------- AMM 指令构建 ---------- */
|
|
1207
1330
|
|
|
1208
|
-
createAmmBuyInstruction(
|
|
1331
|
+
createAmmBuyInstruction(
|
|
1332
|
+
poolInfo,
|
|
1333
|
+
userBaseAta,
|
|
1334
|
+
userQuoteAta,
|
|
1335
|
+
baseAmountOut,
|
|
1336
|
+
maxQuoteAmountIn,
|
|
1337
|
+
tokenProgramId,
|
|
1338
|
+
) {
|
|
1209
1339
|
const { pool, poolKeys, globalConfig } = poolInfo;
|
|
1210
1340
|
const poolV2 = this.deriveAmmPoolV2(poolKeys.baseMint);
|
|
1211
1341
|
|
|
1212
1342
|
const [eventAuthority] = PublicKey.findProgramAddressSync(
|
|
1213
1343
|
[Buffer.from("__event_authority")],
|
|
1214
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1344
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1215
1345
|
);
|
|
1216
1346
|
|
|
1217
1347
|
const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
|
|
1218
1348
|
[Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
|
|
1219
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1349
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1220
1350
|
);
|
|
1221
1351
|
|
|
1222
1352
|
const coinCreatorVaultAta = getAssociatedTokenAddressSync(
|
|
@@ -1224,22 +1354,25 @@ export class PumpTrader {
|
|
|
1224
1354
|
coinCreatorVaultAuthority,
|
|
1225
1355
|
true,
|
|
1226
1356
|
TOKEN_PROGRAM_ID,
|
|
1227
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1357
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1228
1358
|
);
|
|
1229
1359
|
|
|
1230
1360
|
const [globalVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
1231
1361
|
[Buffer.from("global_volume_accumulator")],
|
|
1232
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1362
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1233
1363
|
);
|
|
1234
1364
|
|
|
1235
1365
|
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
1236
|
-
[
|
|
1237
|
-
|
|
1366
|
+
[
|
|
1367
|
+
Buffer.from("user_volume_accumulator"),
|
|
1368
|
+
this.wallet.publicKey.toBuffer(),
|
|
1369
|
+
],
|
|
1370
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1238
1371
|
);
|
|
1239
1372
|
|
|
1240
1373
|
const [feeConfig] = PublicKey.findProgramAddressSync(
|
|
1241
1374
|
[Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
|
|
1242
|
-
PROGRAM_IDS.FEE
|
|
1375
|
+
PROGRAM_IDS.FEE,
|
|
1243
1376
|
);
|
|
1244
1377
|
|
|
1245
1378
|
const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
|
|
@@ -1248,7 +1381,7 @@ export class PumpTrader {
|
|
|
1248
1381
|
protocolFeeRecipient,
|
|
1249
1382
|
true,
|
|
1250
1383
|
TOKEN_PROGRAM_ID,
|
|
1251
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1384
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1252
1385
|
);
|
|
1253
1386
|
const newFeeRecipient = this.pickFeeRecipient();
|
|
1254
1387
|
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
@@ -1256,7 +1389,7 @@ export class PumpTrader {
|
|
|
1256
1389
|
newFeeRecipient,
|
|
1257
1390
|
true,
|
|
1258
1391
|
TOKEN_PROGRAM_ID,
|
|
1259
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1392
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1260
1393
|
);
|
|
1261
1394
|
|
|
1262
1395
|
const remainingKeys = [];
|
|
@@ -1266,14 +1399,22 @@ export class PumpTrader {
|
|
|
1266
1399
|
userVolumeAccumulator,
|
|
1267
1400
|
true,
|
|
1268
1401
|
TOKEN_PROGRAM_ID,
|
|
1269
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1402
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1270
1403
|
);
|
|
1271
|
-
remainingKeys.push({
|
|
1404
|
+
remainingKeys.push({
|
|
1405
|
+
pubkey: userVolumeAccumulatorWsolAta,
|
|
1406
|
+
isSigner: false,
|
|
1407
|
+
isWritable: true,
|
|
1408
|
+
});
|
|
1272
1409
|
}
|
|
1273
1410
|
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1274
1411
|
remainingKeys.push(
|
|
1275
1412
|
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1276
|
-
{
|
|
1413
|
+
{
|
|
1414
|
+
pubkey: newFeeRecipientTokenAccount,
|
|
1415
|
+
isSigner: false,
|
|
1416
|
+
isWritable: true,
|
|
1417
|
+
},
|
|
1277
1418
|
);
|
|
1278
1419
|
|
|
1279
1420
|
return new TransactionInstruction({
|
|
@@ -1286,45 +1427,72 @@ export class PumpTrader {
|
|
|
1286
1427
|
{ pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
|
|
1287
1428
|
{ pubkey: userBaseAta, isSigner: false, isWritable: true },
|
|
1288
1429
|
{ pubkey: userQuoteAta, isSigner: false, isWritable: true },
|
|
1289
|
-
{
|
|
1290
|
-
|
|
1430
|
+
{
|
|
1431
|
+
pubkey: poolKeys.poolBaseTokenAccount,
|
|
1432
|
+
isSigner: false,
|
|
1433
|
+
isWritable: true,
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
pubkey: poolKeys.poolQuoteTokenAccount,
|
|
1437
|
+
isSigner: false,
|
|
1438
|
+
isWritable: true,
|
|
1439
|
+
},
|
|
1291
1440
|
{ pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
|
|
1292
|
-
{
|
|
1441
|
+
{
|
|
1442
|
+
pubkey: protocolFeeRecipientTokenAccount,
|
|
1443
|
+
isSigner: false,
|
|
1444
|
+
isWritable: true,
|
|
1445
|
+
},
|
|
1293
1446
|
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
1294
1447
|
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1295
1448
|
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1296
|
-
{
|
|
1449
|
+
{
|
|
1450
|
+
pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1451
|
+
isSigner: false,
|
|
1452
|
+
isWritable: false,
|
|
1453
|
+
},
|
|
1297
1454
|
{ pubkey: eventAuthority, isSigner: false, isWritable: false },
|
|
1298
1455
|
{ pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
|
|
1299
1456
|
{ pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
|
|
1300
|
-
{
|
|
1457
|
+
{
|
|
1458
|
+
pubkey: coinCreatorVaultAuthority,
|
|
1459
|
+
isSigner: false,
|
|
1460
|
+
isWritable: false,
|
|
1461
|
+
},
|
|
1301
1462
|
{ pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
|
|
1302
1463
|
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
1303
1464
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
1304
1465
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
1305
|
-
...remainingKeys
|
|
1466
|
+
...remainingKeys,
|
|
1306
1467
|
],
|
|
1307
1468
|
data: Buffer.concat([
|
|
1308
1469
|
DISCRIMINATORS.BUY,
|
|
1309
1470
|
u64(baseAmountOut),
|
|
1310
1471
|
u64(maxQuoteAmountIn),
|
|
1311
|
-
Buffer.from([1, 1])
|
|
1312
|
-
])
|
|
1472
|
+
Buffer.from([1, 1]),
|
|
1473
|
+
]),
|
|
1313
1474
|
});
|
|
1314
1475
|
}
|
|
1315
1476
|
|
|
1316
|
-
createAmmSellInstruction(
|
|
1477
|
+
createAmmSellInstruction(
|
|
1478
|
+
poolInfo,
|
|
1479
|
+
userBaseAta,
|
|
1480
|
+
userQuoteAta,
|
|
1481
|
+
baseAmountIn,
|
|
1482
|
+
minQuoteAmountOut,
|
|
1483
|
+
tokenProgramId,
|
|
1484
|
+
) {
|
|
1317
1485
|
const { pool, poolKeys, globalConfig } = poolInfo;
|
|
1318
1486
|
const poolV2 = this.deriveAmmPoolV2(poolKeys.baseMint);
|
|
1319
1487
|
|
|
1320
1488
|
const [eventAuthority] = PublicKey.findProgramAddressSync(
|
|
1321
1489
|
[Buffer.from("__event_authority")],
|
|
1322
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1490
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1323
1491
|
);
|
|
1324
1492
|
|
|
1325
1493
|
const [coinCreatorVaultAuthority] = PublicKey.findProgramAddressSync(
|
|
1326
1494
|
[Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()],
|
|
1327
|
-
PROGRAM_IDS.PUMP_AMM
|
|
1495
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1328
1496
|
);
|
|
1329
1497
|
|
|
1330
1498
|
const coinCreatorVaultAta = getAssociatedTokenAddressSync(
|
|
@@ -1332,12 +1500,12 @@ export class PumpTrader {
|
|
|
1332
1500
|
coinCreatorVaultAuthority,
|
|
1333
1501
|
true,
|
|
1334
1502
|
TOKEN_PROGRAM_ID,
|
|
1335
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1503
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1336
1504
|
);
|
|
1337
1505
|
|
|
1338
1506
|
const [feeConfig] = PublicKey.findProgramAddressSync(
|
|
1339
1507
|
[Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG],
|
|
1340
|
-
PROGRAM_IDS.FEE
|
|
1508
|
+
PROGRAM_IDS.FEE,
|
|
1341
1509
|
);
|
|
1342
1510
|
|
|
1343
1511
|
const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
|
|
@@ -1346,7 +1514,7 @@ export class PumpTrader {
|
|
|
1346
1514
|
protocolFeeRecipient,
|
|
1347
1515
|
true,
|
|
1348
1516
|
TOKEN_PROGRAM_ID,
|
|
1349
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1517
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1350
1518
|
);
|
|
1351
1519
|
const newFeeRecipient = this.pickFeeRecipient();
|
|
1352
1520
|
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
@@ -1354,12 +1522,15 @@ export class PumpTrader {
|
|
|
1354
1522
|
newFeeRecipient,
|
|
1355
1523
|
true,
|
|
1356
1524
|
TOKEN_PROGRAM_ID,
|
|
1357
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1525
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1358
1526
|
);
|
|
1359
1527
|
|
|
1360
1528
|
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
1361
|
-
[
|
|
1362
|
-
|
|
1529
|
+
[
|
|
1530
|
+
Buffer.from("user_volume_accumulator"),
|
|
1531
|
+
this.wallet.publicKey.toBuffer(),
|
|
1532
|
+
],
|
|
1533
|
+
PROGRAM_IDS.PUMP_AMM,
|
|
1363
1534
|
);
|
|
1364
1535
|
|
|
1365
1536
|
const userVolumeAccumulatorWsolAta = getAssociatedTokenAddressSync(
|
|
@@ -1367,20 +1538,28 @@ export class PumpTrader {
|
|
|
1367
1538
|
userVolumeAccumulator,
|
|
1368
1539
|
true,
|
|
1369
1540
|
TOKEN_PROGRAM_ID,
|
|
1370
|
-
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1541
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1371
1542
|
);
|
|
1372
1543
|
|
|
1373
1544
|
const remainingKeys = [];
|
|
1374
1545
|
if (poolKeys.isCashbackCoin) {
|
|
1375
1546
|
remainingKeys.push(
|
|
1376
|
-
{
|
|
1377
|
-
|
|
1547
|
+
{
|
|
1548
|
+
pubkey: userVolumeAccumulatorWsolAta,
|
|
1549
|
+
isSigner: false,
|
|
1550
|
+
isWritable: true,
|
|
1551
|
+
},
|
|
1552
|
+
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
1378
1553
|
);
|
|
1379
1554
|
}
|
|
1380
1555
|
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1381
1556
|
remainingKeys.push(
|
|
1382
1557
|
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1383
|
-
{
|
|
1558
|
+
{
|
|
1559
|
+
pubkey: newFeeRecipientTokenAccount,
|
|
1560
|
+
isSigner: false,
|
|
1561
|
+
isWritable: true,
|
|
1562
|
+
},
|
|
1384
1563
|
);
|
|
1385
1564
|
|
|
1386
1565
|
return new TransactionInstruction({
|
|
@@ -1393,65 +1572,93 @@ export class PumpTrader {
|
|
|
1393
1572
|
{ pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
|
|
1394
1573
|
{ pubkey: userBaseAta, isSigner: false, isWritable: true },
|
|
1395
1574
|
{ pubkey: userQuoteAta, isSigner: false, isWritable: true },
|
|
1396
|
-
{
|
|
1397
|
-
|
|
1575
|
+
{
|
|
1576
|
+
pubkey: poolKeys.poolBaseTokenAccount,
|
|
1577
|
+
isSigner: false,
|
|
1578
|
+
isWritable: true,
|
|
1579
|
+
},
|
|
1580
|
+
{
|
|
1581
|
+
pubkey: poolKeys.poolQuoteTokenAccount,
|
|
1582
|
+
isSigner: false,
|
|
1583
|
+
isWritable: true,
|
|
1584
|
+
},
|
|
1398
1585
|
{ pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
|
|
1399
|
-
{
|
|
1586
|
+
{
|
|
1587
|
+
pubkey: protocolFeeRecipientTokenAccount,
|
|
1588
|
+
isSigner: false,
|
|
1589
|
+
isWritable: true,
|
|
1590
|
+
},
|
|
1400
1591
|
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
1401
1592
|
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
1402
1593
|
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1403
|
-
{
|
|
1594
|
+
{
|
|
1595
|
+
pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1596
|
+
isSigner: false,
|
|
1597
|
+
isWritable: false,
|
|
1598
|
+
},
|
|
1404
1599
|
{ pubkey: eventAuthority, isSigner: false, isWritable: false },
|
|
1405
1600
|
{ pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
|
|
1406
1601
|
{ pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
|
|
1407
|
-
{
|
|
1602
|
+
{
|
|
1603
|
+
pubkey: coinCreatorVaultAuthority,
|
|
1604
|
+
isSigner: false,
|
|
1605
|
+
isWritable: false,
|
|
1606
|
+
},
|
|
1408
1607
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
1409
1608
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
1410
|
-
...remainingKeys
|
|
1609
|
+
...remainingKeys,
|
|
1411
1610
|
],
|
|
1412
1611
|
data: Buffer.concat([
|
|
1413
1612
|
DISCRIMINATORS.SELL,
|
|
1414
1613
|
u64(baseAmountIn),
|
|
1415
|
-
u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n)
|
|
1416
|
-
])
|
|
1614
|
+
u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n),
|
|
1615
|
+
]),
|
|
1417
1616
|
});
|
|
1418
1617
|
}
|
|
1419
1618
|
|
|
1420
1619
|
/* ---------- 交易确认 ---------- */
|
|
1421
1620
|
|
|
1422
|
-
async confirmTransactionWithPolling(
|
|
1423
|
-
|
|
1424
|
-
|
|
1621
|
+
async confirmTransactionWithPolling(
|
|
1622
|
+
signature,
|
|
1623
|
+
lastValidBlockHeight,
|
|
1624
|
+
maxAttempts = 5,
|
|
1625
|
+
delayMs = 2000,
|
|
1626
|
+
) {
|
|
1627
|
+
console.log("✅ 交易已发送:", signature);
|
|
1628
|
+
console.log("🔗 查看交易: https://solscan.io/tx/" + signature);
|
|
1425
1629
|
|
|
1426
1630
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1427
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
1631
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1428
1632
|
|
|
1429
1633
|
try {
|
|
1430
1634
|
console.log(`🔍 检查交易状态 (${attempt}/${maxAttempts})...`);
|
|
1431
1635
|
|
|
1432
1636
|
const txInfo = await this.connection.getTransaction(signature, {
|
|
1433
|
-
commitment:
|
|
1434
|
-
maxSupportedTransactionVersion: 0
|
|
1637
|
+
commitment: "confirmed",
|
|
1638
|
+
maxSupportedTransactionVersion: 0,
|
|
1435
1639
|
});
|
|
1436
1640
|
|
|
1437
1641
|
if (txInfo) {
|
|
1438
1642
|
if (txInfo.meta?.err) {
|
|
1439
|
-
console.error(
|
|
1440
|
-
throw new Error(
|
|
1643
|
+
console.error("❌ 交易失败:", txInfo.meta.err);
|
|
1644
|
+
throw new Error("交易失败: " + JSON.stringify(txInfo.meta.err));
|
|
1441
1645
|
}
|
|
1442
1646
|
|
|
1443
|
-
console.log(
|
|
1647
|
+
console.log("✅ 交易已确认!");
|
|
1444
1648
|
return signature;
|
|
1445
1649
|
}
|
|
1446
1650
|
|
|
1447
|
-
const currentBlockHeight =
|
|
1651
|
+
const currentBlockHeight =
|
|
1652
|
+
await this.connection.getBlockHeight("finalized");
|
|
1448
1653
|
if (currentBlockHeight > lastValidBlockHeight) {
|
|
1449
|
-
console.log(
|
|
1450
|
-
throw new Error(
|
|
1654
|
+
console.log("⚠️ 交易已过期(超过有效区块高度)");
|
|
1655
|
+
throw new Error("交易过期:未在有效区块高度内确认");
|
|
1451
1656
|
}
|
|
1452
|
-
|
|
1453
1657
|
} catch (error) {
|
|
1454
|
-
if (
|
|
1658
|
+
if (
|
|
1659
|
+
error.message?.includes("交易失败") ||
|
|
1660
|
+
error.message?.includes("交易过期")
|
|
1661
|
+
) {
|
|
1455
1662
|
throw error;
|
|
1456
1663
|
}
|
|
1457
1664
|
console.log(`⚠️ 查询出错,继续重试: ${error.message}`);
|
|
@@ -1459,7 +1666,7 @@ export class PumpTrader {
|
|
|
1459
1666
|
}
|
|
1460
1667
|
|
|
1461
1668
|
throw new Error(
|
|
1462
|
-
`交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}
|
|
1669
|
+
`交易确认超时(已尝试 ${maxAttempts} 次),签名: ${signature}。请手动检查交易状态。`,
|
|
1463
1670
|
);
|
|
1464
1671
|
}
|
|
1465
1672
|
|
|
@@ -1472,7 +1679,10 @@ export class PumpTrader {
|
|
|
1472
1679
|
for (const logLine of log.logs) {
|
|
1473
1680
|
if (!logLine.startsWith("Program data: ")) continue;
|
|
1474
1681
|
|
|
1475
|
-
const buf = Buffer.from(
|
|
1682
|
+
const buf = Buffer.from(
|
|
1683
|
+
logLine.replace("Program data: ", ""),
|
|
1684
|
+
"base64",
|
|
1685
|
+
);
|
|
1476
1686
|
|
|
1477
1687
|
if (!buf.subarray(0, 8).equals(DISCRIMINATORS.TRADE_EVENT)) continue;
|
|
1478
1688
|
|
|
@@ -1498,11 +1708,11 @@ export class PumpTrader {
|
|
|
1498
1708
|
isBuy,
|
|
1499
1709
|
user: user.toBase58(),
|
|
1500
1710
|
timestamp,
|
|
1501
|
-
signature: log.signature
|
|
1711
|
+
signature: log.signature,
|
|
1502
1712
|
});
|
|
1503
1713
|
}
|
|
1504
1714
|
},
|
|
1505
|
-
"confirmed"
|
|
1715
|
+
"confirmed",
|
|
1506
1716
|
);
|
|
1507
1717
|
}
|
|
1508
1718
|
|
|
@@ -1515,18 +1725,22 @@ export class PumpTrader {
|
|
|
1515
1725
|
const metadata = await getTokenMetadata(
|
|
1516
1726
|
this.connection,
|
|
1517
1727
|
mint,
|
|
1518
|
-
TOKEN_2022_PROGRAM_ID
|
|
1728
|
+
TOKEN_2022_PROGRAM_ID,
|
|
1519
1729
|
);
|
|
1520
1730
|
|
|
1521
1731
|
return {
|
|
1522
1732
|
name: metadata?.name || "",
|
|
1523
1733
|
symbol: metadata?.symbol || "",
|
|
1524
|
-
uri: metadata?.uri || ""
|
|
1734
|
+
uri: metadata?.uri || "",
|
|
1525
1735
|
};
|
|
1526
1736
|
} catch (e) {
|
|
1527
1737
|
const metadataPda = PublicKey.findProgramAddressSync(
|
|
1528
|
-
[
|
|
1529
|
-
|
|
1738
|
+
[
|
|
1739
|
+
Buffer.from("metadata"),
|
|
1740
|
+
PROGRAM_IDS.METADATA.toBuffer(),
|
|
1741
|
+
mint.toBuffer(),
|
|
1742
|
+
],
|
|
1743
|
+
PROGRAM_IDS.METADATA,
|
|
1530
1744
|
)[0];
|
|
1531
1745
|
|
|
1532
1746
|
const acc = await this.connection.getAccountInfo(metadataPda);
|
|
@@ -1535,9 +1749,9 @@ export class PumpTrader {
|
|
|
1535
1749
|
const meta = parseMetadataAccount(acc.data);
|
|
1536
1750
|
|
|
1537
1751
|
return {
|
|
1538
|
-
name: meta?.name?.replace(/\u0000/g,
|
|
1539
|
-
symbol: meta?.symbol?.replace(/\u0000/g,
|
|
1540
|
-
uri: meta?.uri?.replace(/\u0000/g,
|
|
1752
|
+
name: meta?.name?.replace(/\u0000/g, "") || "",
|
|
1753
|
+
symbol: meta?.symbol?.replace(/\u0000/g, "") || "",
|
|
1754
|
+
uri: meta?.uri?.replace(/\u0000/g, "") || "",
|
|
1541
1755
|
};
|
|
1542
1756
|
}
|
|
1543
1757
|
}
|