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