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/dist/index.js
CHANGED
|
@@ -7,36 +7,60 @@ exports.PumpTrader = void 0;
|
|
|
7
7
|
const web3_js_1 = require("@solana/web3.js");
|
|
8
8
|
const spl_token_1 = require("@solana/spl-token");
|
|
9
9
|
const bn_js_1 = __importDefault(require("bn.js"));
|
|
10
|
-
const bs58_1 = __importDefault(require("bs58"));
|
|
11
10
|
/* ================= 常量定义 ================= */
|
|
12
11
|
const PROGRAM_IDS = {
|
|
13
12
|
PUMP: new web3_js_1.PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"),
|
|
14
13
|
PUMP_AMM: new web3_js_1.PublicKey("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"),
|
|
15
14
|
METADATA: new web3_js_1.PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"),
|
|
16
15
|
FEE: new web3_js_1.PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"),
|
|
17
|
-
EVENT_AUTHORITY: new web3_js_1.PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
|
|
16
|
+
EVENT_AUTHORITY: new web3_js_1.PublicKey("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1"),
|
|
18
17
|
};
|
|
19
18
|
const SOL_MINT = new web3_js_1.PublicKey("So11111111111111111111111111111111111111112");
|
|
20
19
|
const SEEDS = {
|
|
21
20
|
FEE_CONFIG: new Uint8Array([
|
|
22
|
-
1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170,
|
|
23
|
-
|
|
21
|
+
1, 86, 224, 246, 147, 102, 90, 207, 68, 219, 21, 104, 191, 23, 91, 170, 81,
|
|
22
|
+
137, 203, 151, 245, 210, 255, 59, 101, 93, 43, 182, 253, 109, 24, 176,
|
|
24
23
|
]),
|
|
25
24
|
AMM_FEE_CONFIG: Buffer.from([
|
|
26
|
-
12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101,
|
|
27
|
-
|
|
25
|
+
12, 20, 222, 252, 130, 94, 198, 118, 148, 37, 8, 24, 187, 101, 64, 101, 244,
|
|
26
|
+
41, 141, 49, 86, 213, 113, 180, 212, 248, 9, 12, 24, 233, 168, 99,
|
|
28
27
|
]),
|
|
29
28
|
GLOBAL: Buffer.from("global"),
|
|
30
|
-
BONDING: Buffer.from("bonding-curve")
|
|
29
|
+
BONDING: Buffer.from("bonding-curve"),
|
|
31
30
|
};
|
|
32
31
|
const DISCRIMINATORS = {
|
|
33
32
|
BUY: Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]),
|
|
34
33
|
SELL: Buffer.from([51, 230, 133, 164, 1, 127, 131, 173]),
|
|
35
|
-
TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238])
|
|
34
|
+
TRADE_EVENT: Buffer.from([189, 219, 127, 211, 78, 230, 97, 238]),
|
|
35
|
+
// V2 instructions
|
|
36
|
+
BUY_V2: Buffer.from([184, 23, 238, 97, 103, 197, 211, 61]),
|
|
37
|
+
SELL_V2: Buffer.from([93, 246, 130, 60, 231, 233, 64, 178]),
|
|
38
|
+
BUY_EXACT_QUOTE_IN_V2: Buffer.from([194, 171, 28, 70, 104, 77, 91, 47]),
|
|
39
|
+
COLLECT_CREATOR_FEE_V2: Buffer.from([207, 17, 138, 242, 4, 34, 19, 56]),
|
|
36
40
|
};
|
|
37
41
|
const AMM_FEE_BPS = 100n;
|
|
38
42
|
const BPS_DENOMINATOR = 10000n;
|
|
39
43
|
const PUMP_NEW_FEE_RECIPIENTS = [
|
|
44
|
+
"62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV",
|
|
45
|
+
"7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ",
|
|
46
|
+
"7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX",
|
|
47
|
+
"9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz",
|
|
48
|
+
"AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY",
|
|
49
|
+
"CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM",
|
|
50
|
+
"FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz",
|
|
51
|
+
"G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP",
|
|
52
|
+
].map((value) => new web3_js_1.PublicKey(value));
|
|
53
|
+
const PUMP_RESERVED_FEE_RECIPIENTS = [
|
|
54
|
+
"GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS",
|
|
55
|
+
"4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6",
|
|
56
|
+
"8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR",
|
|
57
|
+
"4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH",
|
|
58
|
+
"8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6",
|
|
59
|
+
"Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk",
|
|
60
|
+
"463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq",
|
|
61
|
+
"6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA",
|
|
62
|
+
].map((value) => new web3_js_1.PublicKey(value));
|
|
63
|
+
const PUMP_BUYBACK_FEE_RECIPIENTS = [
|
|
40
64
|
"5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
|
|
41
65
|
"9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
|
|
42
66
|
"GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
|
|
@@ -44,11 +68,11 @@ const PUMP_NEW_FEE_RECIPIENTS = [
|
|
|
44
68
|
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
45
69
|
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
46
70
|
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
47
|
-
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
71
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW",
|
|
48
72
|
].map((value) => new web3_js_1.PublicKey(value));
|
|
49
73
|
/* ================= 工具函数 ================= */
|
|
50
74
|
const u64 = (v) => {
|
|
51
|
-
const bn = typeof v ===
|
|
75
|
+
const bn = typeof v === "bigint" ? new bn_js_1.default(v.toString()) : new bn_js_1.default(v.toString());
|
|
52
76
|
return bn.toArrayLike(Buffer, "le", 8);
|
|
53
77
|
};
|
|
54
78
|
const readU64 = (buf, offset) => {
|
|
@@ -62,7 +86,9 @@ const readU32 = (buf, offsetObj) => {
|
|
|
62
86
|
};
|
|
63
87
|
const readString = (buf, offsetObj) => {
|
|
64
88
|
const len = readU32(buf, offsetObj);
|
|
65
|
-
const str = buf
|
|
89
|
+
const str = buf
|
|
90
|
+
.slice(offsetObj.offset, offsetObj.offset + len)
|
|
91
|
+
.toString("utf8");
|
|
66
92
|
offsetObj.offset += len;
|
|
67
93
|
return str;
|
|
68
94
|
};
|
|
@@ -81,12 +107,12 @@ function parseMetadataAccount(data) {
|
|
|
81
107
|
mint: mint.toBase58(),
|
|
82
108
|
name,
|
|
83
109
|
symbol,
|
|
84
|
-
uri
|
|
110
|
+
uri,
|
|
85
111
|
};
|
|
86
112
|
}
|
|
87
113
|
function parsePoolKeys(data) {
|
|
88
114
|
if (!data || data.length < 280) {
|
|
89
|
-
throw new Error(
|
|
115
|
+
throw new Error("Invalid pool account data");
|
|
90
116
|
}
|
|
91
117
|
let offset = 8;
|
|
92
118
|
const poolBump = data.readUInt8(offset);
|
|
@@ -121,18 +147,32 @@ function parsePoolKeys(data) {
|
|
|
121
147
|
poolQuoteTokenAccount,
|
|
122
148
|
coinCreator,
|
|
123
149
|
isMayhemMode,
|
|
124
|
-
isCashbackCoin
|
|
150
|
+
isCashbackCoin,
|
|
125
151
|
};
|
|
126
152
|
}
|
|
127
153
|
/* ================= PumpTrader 类 ================= */
|
|
128
154
|
class PumpTrader {
|
|
129
|
-
constructor(rpc,
|
|
155
|
+
constructor(rpc, wallet) {
|
|
130
156
|
this.connection = new web3_js_1.Connection(rpc, "confirmed");
|
|
131
|
-
|
|
157
|
+
if (wallet instanceof web3_js_1.Keypair) {
|
|
158
|
+
this._wallet = wallet;
|
|
159
|
+
this.publicKey = wallet.publicKey;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this._wallet = wallet;
|
|
163
|
+
this.publicKey = wallet.publicKey;
|
|
164
|
+
}
|
|
132
165
|
this.global = web3_js_1.PublicKey.findProgramAddressSync([SEEDS.GLOBAL], PROGRAM_IDS.PUMP)[0];
|
|
133
166
|
this.globalState = null;
|
|
134
167
|
this.tokenProgramCache = new Map();
|
|
135
168
|
}
|
|
169
|
+
async signTx(tx) {
|
|
170
|
+
if (this._wallet instanceof web3_js_1.Keypair) {
|
|
171
|
+
tx.sign(this._wallet);
|
|
172
|
+
return tx;
|
|
173
|
+
}
|
|
174
|
+
return this._wallet.signTransaction(tx);
|
|
175
|
+
}
|
|
136
176
|
/* ---------- Token Program 检测 ---------- */
|
|
137
177
|
/**
|
|
138
178
|
* 自动检测代币使用的 token program
|
|
@@ -148,7 +188,7 @@ class PumpTrader {
|
|
|
148
188
|
const mintData = await (0, spl_token_1.getMint)(this.connection, mint, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID);
|
|
149
189
|
const result = {
|
|
150
190
|
type: "TOKEN_2022_PROGRAM_ID",
|
|
151
|
-
programId: spl_token_1.TOKEN_2022_PROGRAM_ID
|
|
191
|
+
programId: spl_token_1.TOKEN_2022_PROGRAM_ID,
|
|
152
192
|
};
|
|
153
193
|
this.tokenProgramCache.set(tokenAddr, result);
|
|
154
194
|
return result;
|
|
@@ -159,7 +199,7 @@ class PumpTrader {
|
|
|
159
199
|
const mintData = await (0, spl_token_1.getMint)(this.connection, mint, "confirmed", spl_token_1.TOKEN_PROGRAM_ID);
|
|
160
200
|
const result = {
|
|
161
201
|
type: "TOKEN_PROGRAM_ID",
|
|
162
|
-
programId: spl_token_1.TOKEN_PROGRAM_ID
|
|
202
|
+
programId: spl_token_1.TOKEN_PROGRAM_ID,
|
|
163
203
|
};
|
|
164
204
|
this.tokenProgramCache.set(tokenAddr, result);
|
|
165
205
|
return result;
|
|
@@ -169,6 +209,19 @@ class PumpTrader {
|
|
|
169
209
|
}
|
|
170
210
|
}
|
|
171
211
|
}
|
|
212
|
+
async detectQuoteTokenProgram(quoteMint) {
|
|
213
|
+
const quoteAddr = quoteMint.toBase58();
|
|
214
|
+
if (this.tokenProgramCache.has(quoteAddr)) {
|
|
215
|
+
return this.tokenProgramCache.get(quoteAddr).programId;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
await (0, spl_token_1.getMint)(this.connection, quoteMint, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID);
|
|
219
|
+
return spl_token_1.TOKEN_2022_PROGRAM_ID;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return spl_token_1.TOKEN_PROGRAM_ID;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
172
225
|
/* ---------- 内盘/外盘检测 ---------- */
|
|
173
226
|
/**
|
|
174
227
|
* 检测代币是否在外盘 (AMM)
|
|
@@ -226,7 +279,7 @@ class PumpTrader {
|
|
|
226
279
|
initialVirtualSolReserves: readU64(),
|
|
227
280
|
initialRealTokenReserves: readU64(),
|
|
228
281
|
tokenTotalSupply: readU64(),
|
|
229
|
-
feeBasisPoints: readU64()
|
|
282
|
+
feeBasisPoints: readU64(),
|
|
230
283
|
};
|
|
231
284
|
return this.globalState;
|
|
232
285
|
}
|
|
@@ -240,6 +293,15 @@ class PumpTrader {
|
|
|
240
293
|
pickFeeRecipient(index = 0) {
|
|
241
294
|
return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
|
|
242
295
|
}
|
|
296
|
+
pickBuybackFeeRecipient(index = 0) {
|
|
297
|
+
return PUMP_BUYBACK_FEE_RECIPIENTS[index % PUMP_BUYBACK_FEE_RECIPIENTS.length];
|
|
298
|
+
}
|
|
299
|
+
pickReservedFeeRecipient(index = 0) {
|
|
300
|
+
return PUMP_RESERVED_FEE_RECIPIENTS[index % PUMP_RESERVED_FEE_RECIPIENTS.length];
|
|
301
|
+
}
|
|
302
|
+
getSharingConfigPda(mint) {
|
|
303
|
+
return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("sharing-config"), mint.toBuffer()], PROGRAM_IDS.FEE)[0];
|
|
304
|
+
}
|
|
243
305
|
buildBondingBuyKeys(args) {
|
|
244
306
|
const tokenProgramId = args.tokenProgramId ?? spl_token_1.TOKEN_PROGRAM_ID;
|
|
245
307
|
return [
|
|
@@ -247,7 +309,11 @@ class PumpTrader {
|
|
|
247
309
|
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
248
310
|
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
249
311
|
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
250
|
-
{
|
|
312
|
+
{
|
|
313
|
+
pubkey: args.associatedBondingCurve,
|
|
314
|
+
isSigner: false,
|
|
315
|
+
isWritable: true,
|
|
316
|
+
},
|
|
251
317
|
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
252
318
|
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
253
319
|
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
@@ -255,12 +321,16 @@ class PumpTrader {
|
|
|
255
321
|
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
256
322
|
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
257
323
|
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
258
|
-
{
|
|
324
|
+
{
|
|
325
|
+
pubkey: args.globalVolumeAccumulator,
|
|
326
|
+
isSigner: false,
|
|
327
|
+
isWritable: false,
|
|
328
|
+
},
|
|
259
329
|
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
260
330
|
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
261
331
|
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
262
332
|
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
263
|
-
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
333
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true },
|
|
264
334
|
];
|
|
265
335
|
}
|
|
266
336
|
buildBondingSellKeys(args) {
|
|
@@ -270,7 +340,11 @@ class PumpTrader {
|
|
|
270
340
|
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
271
341
|
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
272
342
|
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
273
|
-
{
|
|
343
|
+
{
|
|
344
|
+
pubkey: args.associatedBondingCurve,
|
|
345
|
+
isSigner: false,
|
|
346
|
+
isWritable: true,
|
|
347
|
+
},
|
|
274
348
|
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
275
349
|
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
276
350
|
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
@@ -279,10 +353,14 @@ class PumpTrader {
|
|
|
279
353
|
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
280
354
|
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
281
355
|
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
282
|
-
{ pubkey: args.feeProgram, isSigner: false, isWritable: false }
|
|
356
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
283
357
|
];
|
|
284
358
|
if (args.isCashbackCoin) {
|
|
285
|
-
keys.push({
|
|
359
|
+
keys.push({
|
|
360
|
+
pubkey: args.userVolumeAccumulator,
|
|
361
|
+
isSigner: false,
|
|
362
|
+
isWritable: true,
|
|
363
|
+
});
|
|
286
364
|
}
|
|
287
365
|
keys.push({ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false }, { pubkey: args.feeRecipient, isSigner: false, isWritable: true });
|
|
288
366
|
return keys;
|
|
@@ -304,10 +382,18 @@ class PumpTrader {
|
|
|
304
382
|
offset += 1;
|
|
305
383
|
const creator = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
|
|
306
384
|
offset += 32;
|
|
307
|
-
|
|
385
|
+
// V2 fields (115-byte bonding curve layout): quote_mint, virtual_quote_reserves, real_quote_reserves
|
|
386
|
+
// For legacy coins these may not be present, check remaining data length
|
|
387
|
+
if (offset + 32 <= data.length) {
|
|
308
388
|
state.quoteMint = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
|
|
309
389
|
offset += 32;
|
|
310
390
|
}
|
|
391
|
+
if (offset + 8 <= data.length) {
|
|
392
|
+
[state.virtualQuoteReserves, offset] = readU64(data, offset);
|
|
393
|
+
}
|
|
394
|
+
if (offset + 8 <= data.length) {
|
|
395
|
+
[state.realQuoteReserves, offset] = readU64(data, offset);
|
|
396
|
+
}
|
|
311
397
|
state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
|
|
312
398
|
offset += 1;
|
|
313
399
|
state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
|
|
@@ -340,48 +426,35 @@ class PumpTrader {
|
|
|
340
426
|
async getPriceAndStatus(tokenAddr) {
|
|
341
427
|
const mint = new web3_js_1.PublicKey(tokenAddr);
|
|
342
428
|
const { state } = await this.loadBonding(mint);
|
|
343
|
-
const quoteMint = this.getEffectiveQuoteMint(state.quoteMint);
|
|
344
429
|
if (state.complete) {
|
|
345
|
-
const price = await this.getAmmPrice(mint
|
|
430
|
+
const price = await this.getAmmPrice(mint);
|
|
346
431
|
return { price, completed: true };
|
|
347
432
|
}
|
|
348
433
|
const oneToken = BigInt(1_000_000);
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
const price = Number(quoteOut) / 10 ** quoteDecimals;
|
|
434
|
+
const solOut = this.calcSell(oneToken, state);
|
|
435
|
+
const price = Number(solOut) / 1e9;
|
|
352
436
|
return { price, completed: false };
|
|
353
437
|
}
|
|
354
|
-
async getAmmPrice(mint
|
|
438
|
+
async getAmmPrice(mint) {
|
|
355
439
|
const [poolCreator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("pool-authority"), mint.toBuffer()], PROGRAM_IDS.PUMP);
|
|
356
440
|
const indexBuffer = new bn_js_1.default(0).toArrayLike(Buffer, "le", 2);
|
|
357
|
-
const [pool] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
441
|
+
const [pool] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
442
|
+
Buffer.from("pool"),
|
|
443
|
+
indexBuffer,
|
|
444
|
+
poolCreator.toBuffer(),
|
|
445
|
+
mint.toBuffer(),
|
|
446
|
+
SOL_MINT.toBuffer(),
|
|
447
|
+
], PROGRAM_IDS.PUMP_AMM);
|
|
358
448
|
const acc = await this.connection.getAccountInfo(pool);
|
|
359
449
|
if (!acc)
|
|
360
450
|
throw new Error("Pool not found");
|
|
361
451
|
const poolKeys = parsePoolKeys(acc.data);
|
|
362
452
|
const [baseInfo, quoteInfo] = await Promise.all([
|
|
363
453
|
this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
|
|
364
|
-
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
|
|
454
|
+
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
|
|
365
455
|
]);
|
|
366
456
|
return quoteInfo.value.uiAmount / baseInfo.value.uiAmount;
|
|
367
457
|
}
|
|
368
|
-
getEffectiveQuoteMint(quoteMint) {
|
|
369
|
-
if (!quoteMint || quoteMint.equals(web3_js_1.PublicKey.default)) {
|
|
370
|
-
return SOL_MINT;
|
|
371
|
-
}
|
|
372
|
-
return quoteMint;
|
|
373
|
-
}
|
|
374
|
-
async getMintDecimals(mint) {
|
|
375
|
-
if (mint.equals(SOL_MINT)) {
|
|
376
|
-
return 9;
|
|
377
|
-
}
|
|
378
|
-
const mintInfo = await this.connection.getParsedAccountInfo(mint);
|
|
379
|
-
const parsedData = mintInfo.value?.data;
|
|
380
|
-
if (parsedData && "parsed" in parsedData) {
|
|
381
|
-
return parsedData.parsed.info.decimals;
|
|
382
|
-
}
|
|
383
|
-
throw new Error(`Unable to determine mint decimals for ${mint.toBase58()}`);
|
|
384
|
-
}
|
|
385
458
|
/* ---------- 余额查询 ---------- */
|
|
386
459
|
/**
|
|
387
460
|
* 查询代币余额
|
|
@@ -392,8 +465,9 @@ class PumpTrader {
|
|
|
392
465
|
if (tokenAddr) {
|
|
393
466
|
// 查询单个代币
|
|
394
467
|
const mint = new web3_js_1.PublicKey(tokenAddr);
|
|
395
|
-
const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(this.
|
|
396
|
-
return tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount ||
|
|
468
|
+
const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(this.publicKey, { mint });
|
|
469
|
+
return (tokenAccounts.value[0]?.account.data.parsed.info.tokenAmount.uiAmount ||
|
|
470
|
+
0);
|
|
397
471
|
}
|
|
398
472
|
else {
|
|
399
473
|
// 查询所有代币
|
|
@@ -405,11 +479,11 @@ class PumpTrader {
|
|
|
405
479
|
* @returns 代币信息数组,包含mint地址、余额等信息
|
|
406
480
|
*/
|
|
407
481
|
async getAllTokenBalances() {
|
|
408
|
-
const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(this.
|
|
482
|
+
const tokenAccounts = await this.connection.getParsedTokenAccountsByOwner(this.publicKey, { programId: spl_token_1.TOKEN_PROGRAM_ID });
|
|
409
483
|
const balances = tokenAccounts.value
|
|
410
484
|
.map((account) => {
|
|
411
485
|
const parsed = account.account.data.parsed;
|
|
412
|
-
if (parsed.type !==
|
|
486
|
+
if (parsed.type !== "account")
|
|
413
487
|
return null;
|
|
414
488
|
const tokenAmount = parsed.info.tokenAmount;
|
|
415
489
|
if (Number(tokenAmount.amount) === 0)
|
|
@@ -418,16 +492,16 @@ class PumpTrader {
|
|
|
418
492
|
mint: parsed.info.mint,
|
|
419
493
|
amount: BigInt(tokenAmount.amount),
|
|
420
494
|
decimals: tokenAmount.decimals,
|
|
421
|
-
uiAmount: tokenAmount.uiAmount || 0
|
|
495
|
+
uiAmount: tokenAmount.uiAmount || 0,
|
|
422
496
|
};
|
|
423
497
|
})
|
|
424
498
|
.filter((item) => item !== null);
|
|
425
499
|
// 同时查询 TOKEN_2022_PROGRAM_ID
|
|
426
|
-
const token2022Accounts = await this.connection.getParsedTokenAccountsByOwner(this.
|
|
500
|
+
const token2022Accounts = await this.connection.getParsedTokenAccountsByOwner(this.publicKey, { programId: spl_token_1.TOKEN_2022_PROGRAM_ID });
|
|
427
501
|
const token2022Balances = token2022Accounts.value
|
|
428
502
|
.map((account) => {
|
|
429
503
|
const parsed = account.account.data.parsed;
|
|
430
|
-
if (parsed.type !==
|
|
504
|
+
if (parsed.type !== "account")
|
|
431
505
|
return null;
|
|
432
506
|
const tokenAmount = parsed.info.tokenAmount;
|
|
433
507
|
if (Number(tokenAmount.amount) === 0)
|
|
@@ -436,7 +510,7 @@ class PumpTrader {
|
|
|
436
510
|
mint: parsed.info.mint,
|
|
437
511
|
amount: BigInt(tokenAmount.amount),
|
|
438
512
|
decimals: tokenAmount.decimals,
|
|
439
|
-
uiAmount: tokenAmount.uiAmount || 0
|
|
513
|
+
uiAmount: tokenAmount.uiAmount || 0,
|
|
440
514
|
};
|
|
441
515
|
})
|
|
442
516
|
.filter((item) => item !== null);
|
|
@@ -454,21 +528,21 @@ class PumpTrader {
|
|
|
454
528
|
mint: b.mint,
|
|
455
529
|
amount: Number(b.amount),
|
|
456
530
|
decimals: b.decimals,
|
|
457
|
-
uiAmount: b.uiAmount
|
|
531
|
+
uiAmount: b.uiAmount,
|
|
458
532
|
}));
|
|
459
533
|
return uniqueBalances;
|
|
460
534
|
}
|
|
461
535
|
async solBalance() {
|
|
462
|
-
const balance = await this.connection.getBalance(this.
|
|
536
|
+
const balance = await this.connection.getBalance(this.publicKey);
|
|
463
537
|
return balance / 1e9;
|
|
464
538
|
}
|
|
465
539
|
/* ---------- ATA 管理 ---------- */
|
|
466
540
|
async ensureAta(tx, mint, tokenProgram) {
|
|
467
541
|
const program = tokenProgram || spl_token_1.TOKEN_2022_PROGRAM_ID;
|
|
468
|
-
const ata = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.
|
|
542
|
+
const ata = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.publicKey, false, program, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
469
543
|
const acc = await this.connection.getAccountInfo(ata);
|
|
470
544
|
if (!acc) {
|
|
471
|
-
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(this.
|
|
545
|
+
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(this.publicKey, ata, this.publicKey, mint, program, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
|
|
472
546
|
}
|
|
473
547
|
return ata;
|
|
474
548
|
}
|
|
@@ -478,11 +552,11 @@ class PumpTrader {
|
|
|
478
552
|
if (!acc) {
|
|
479
553
|
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(owner, wsolAta, owner, SOL_MINT, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
|
|
480
554
|
}
|
|
481
|
-
if (mode ===
|
|
555
|
+
if (mode === "buy" && lamports) {
|
|
482
556
|
tx.add(web3_js_1.SystemProgram.transfer({
|
|
483
557
|
fromPubkey: owner,
|
|
484
558
|
toPubkey: wsolAta,
|
|
485
|
-
lamports: Number(lamports)
|
|
559
|
+
lamports: Number(lamports),
|
|
486
560
|
}));
|
|
487
561
|
tx.add((0, spl_token_1.createSyncNativeInstruction)(wsolAta));
|
|
488
562
|
}
|
|
@@ -493,7 +567,7 @@ class PumpTrader {
|
|
|
493
567
|
if (!priorityOpt?.enableRandom || !priorityOpt.randomRange) {
|
|
494
568
|
return priorityOpt.base;
|
|
495
569
|
}
|
|
496
|
-
return priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange);
|
|
570
|
+
return (priorityOpt.base + Math.floor(Math.random() * priorityOpt.randomRange));
|
|
497
571
|
}
|
|
498
572
|
calcSlippage({ tradeSize, reserve, slippageOpt }) {
|
|
499
573
|
const impact = Number(tradeSize) / Math.max(Number(reserve), 1);
|
|
@@ -531,26 +605,36 @@ class PumpTrader {
|
|
|
531
605
|
/* ---------- 统一交易接口 ---------- */
|
|
532
606
|
/**
|
|
533
607
|
* 自动判断内盘/外盘并执行买入
|
|
608
|
+
* @param useV2 - use buy_v2 instruction (supports USDC quote) instead of legacy buy
|
|
609
|
+
* @param quoteMint - quote mint for V2 (SOL_MINT for SOL-paired, or USDC mint for USDC-paired)
|
|
534
610
|
*/
|
|
535
|
-
async autoBuy(tokenAddr, totalSolIn, tradeOpt) {
|
|
611
|
+
async autoBuy(tokenAddr, totalSolIn, tradeOpt, useV2 = false, quoteMint = SOL_MINT) {
|
|
536
612
|
const mode = await this.getTradeMode(tokenAddr);
|
|
537
613
|
if (mode === "bonding") {
|
|
614
|
+
if (useV2) {
|
|
615
|
+
return this.buyV2(tokenAddr, totalSolIn, tradeOpt, quoteMint);
|
|
616
|
+
}
|
|
538
617
|
return this.buy(tokenAddr, totalSolIn, tradeOpt);
|
|
539
618
|
}
|
|
540
619
|
else {
|
|
541
|
-
return this.ammBuy(tokenAddr, totalSolIn, tradeOpt);
|
|
620
|
+
return this.ammBuy(tokenAddr, totalSolIn, tradeOpt, quoteMint);
|
|
542
621
|
}
|
|
543
622
|
}
|
|
544
623
|
/**
|
|
545
624
|
* 自动判断内盘/外盘并执行卖出
|
|
625
|
+
* @param useV2 - use sell_v2 instruction (supports USDC quote) instead of legacy sell
|
|
626
|
+
* @param quoteMint - quote mint for V2 (SOL_MINT for SOL-paired, or USDC mint for USDC-paired)
|
|
546
627
|
*/
|
|
547
|
-
async autoSell(tokenAddr, totalTokenIn, tradeOpt) {
|
|
628
|
+
async autoSell(tokenAddr, totalTokenIn, tradeOpt, useV2 = false, quoteMint = SOL_MINT) {
|
|
548
629
|
const mode = await this.getTradeMode(tokenAddr);
|
|
549
630
|
if (mode === "bonding") {
|
|
631
|
+
if (useV2) {
|
|
632
|
+
return this.sellV2(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
|
|
633
|
+
}
|
|
550
634
|
return this.sell(tokenAddr, totalTokenIn, tradeOpt);
|
|
551
635
|
}
|
|
552
636
|
else {
|
|
553
|
-
return this.ammSell(tokenAddr, totalTokenIn, tradeOpt);
|
|
637
|
+
return this.ammSell(tokenAddr, totalTokenIn, tradeOpt, quoteMint);
|
|
554
638
|
}
|
|
555
639
|
}
|
|
556
640
|
/* ---------- 内盘交易 ---------- */
|
|
@@ -569,7 +653,10 @@ class PumpTrader {
|
|
|
569
653
|
const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bonding, true, tokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
570
654
|
const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
|
|
571
655
|
const [globalVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("global_volume_accumulator")], PROGRAM_IDS.PUMP);
|
|
572
|
-
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
656
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
657
|
+
Buffer.from("user_volume_accumulator"),
|
|
658
|
+
this.publicKey.toBuffer(),
|
|
659
|
+
], PROGRAM_IDS.PUMP);
|
|
573
660
|
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
574
661
|
const feeRecipient = this.pickFeeRecipient();
|
|
575
662
|
for (let i = 0; i < solChunks.length; i++) {
|
|
@@ -579,7 +666,7 @@ class PumpTrader {
|
|
|
579
666
|
const slippageBps = this.calcSlippage({
|
|
580
667
|
tradeSize: solIn,
|
|
581
668
|
reserve: state.virtualSolReserves,
|
|
582
|
-
slippageOpt: tradeOpt.slippage
|
|
669
|
+
slippageOpt: tradeOpt.slippage,
|
|
583
670
|
});
|
|
584
671
|
const maxSol = (solIn * BigInt(10_000 + slippageBps)) / 10000n;
|
|
585
672
|
const priority = this.genPriority(tradeOpt.priority);
|
|
@@ -594,7 +681,7 @@ class PumpTrader {
|
|
|
594
681
|
bonding,
|
|
595
682
|
associatedBondingCurve,
|
|
596
683
|
userAta,
|
|
597
|
-
wallet: this.
|
|
684
|
+
wallet: this.publicKey,
|
|
598
685
|
creatorVault,
|
|
599
686
|
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
600
687
|
pumpProgram: PROGRAM_IDS.PUMP,
|
|
@@ -604,28 +691,32 @@ class PumpTrader {
|
|
|
604
691
|
feeProgram: PROGRAM_IDS.FEE,
|
|
605
692
|
bondingCurveV2,
|
|
606
693
|
feeRecipient,
|
|
607
|
-
tokenProgramId: tokenProgram.programId
|
|
694
|
+
tokenProgramId: tokenProgram.programId,
|
|
608
695
|
}),
|
|
609
|
-
data: Buffer.concat([
|
|
696
|
+
data: Buffer.concat([
|
|
697
|
+
DISCRIMINATORS.BUY,
|
|
698
|
+
u64(tokenOut),
|
|
699
|
+
u64(maxSol),
|
|
700
|
+
]),
|
|
610
701
|
}));
|
|
611
|
-
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash(
|
|
702
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
|
|
612
703
|
tx.recentBlockhash = blockhash;
|
|
613
|
-
tx.feePayer = this.
|
|
614
|
-
|
|
704
|
+
tx.feePayer = this.publicKey;
|
|
705
|
+
await this.signTx(tx);
|
|
615
706
|
const signature = await this.connection.sendRawTransaction(tx.serialize(), {
|
|
616
707
|
skipPreflight: false,
|
|
617
|
-
maxRetries: 2
|
|
708
|
+
maxRetries: 2,
|
|
618
709
|
});
|
|
619
710
|
pendingTransactions.push({
|
|
620
711
|
signature,
|
|
621
712
|
lastValidBlockHeight,
|
|
622
|
-
index: i
|
|
713
|
+
index: i,
|
|
623
714
|
});
|
|
624
715
|
}
|
|
625
716
|
catch (e) {
|
|
626
717
|
failedTransactions.push({
|
|
627
718
|
index: i,
|
|
628
|
-
error: e.message
|
|
719
|
+
error: e.message,
|
|
629
720
|
});
|
|
630
721
|
}
|
|
631
722
|
}
|
|
@@ -647,10 +738,13 @@ class PumpTrader {
|
|
|
647
738
|
const pendingTransactions = [];
|
|
648
739
|
const failedTransactions = [];
|
|
649
740
|
const associatedBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bonding, true, tokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
650
|
-
const userAta = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.
|
|
741
|
+
const userAta = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.publicKey, false, tokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
651
742
|
const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
|
|
652
743
|
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
653
|
-
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
744
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
745
|
+
Buffer.from("user_volume_accumulator"),
|
|
746
|
+
this.publicKey.toBuffer(),
|
|
747
|
+
], PROGRAM_IDS.PUMP);
|
|
654
748
|
const feeRecipient = this.pickFeeRecipient();
|
|
655
749
|
for (let i = 0; i < tokenChunks.length; i++) {
|
|
656
750
|
try {
|
|
@@ -659,7 +753,7 @@ class PumpTrader {
|
|
|
659
753
|
const slippageBps = this.calcSlippage({
|
|
660
754
|
tradeSize: tokenIn,
|
|
661
755
|
reserve: state.virtualTokenReserves,
|
|
662
|
-
slippageOpt: tradeOpt.slippage
|
|
756
|
+
slippageOpt: tradeOpt.slippage,
|
|
663
757
|
});
|
|
664
758
|
const minSol = (solOut * BigInt(10_000 - slippageBps)) / 10000n;
|
|
665
759
|
const priority = this.genPriority(tradeOpt.priority);
|
|
@@ -673,7 +767,7 @@ class PumpTrader {
|
|
|
673
767
|
bonding,
|
|
674
768
|
associatedBondingCurve,
|
|
675
769
|
userAta,
|
|
676
|
-
wallet: this.
|
|
770
|
+
wallet: this.publicKey,
|
|
677
771
|
creatorVault,
|
|
678
772
|
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
679
773
|
pumpProgram: PROGRAM_IDS.PUMP,
|
|
@@ -683,41 +777,45 @@ class PumpTrader {
|
|
|
683
777
|
feeRecipient,
|
|
684
778
|
isCashbackCoin: !!state.isCashbackCoin,
|
|
685
779
|
userVolumeAccumulator,
|
|
686
|
-
tokenProgramId: tokenProgram.programId
|
|
780
|
+
tokenProgramId: tokenProgram.programId,
|
|
687
781
|
}),
|
|
688
782
|
data: Buffer.concat([
|
|
689
783
|
DISCRIMINATORS.SELL,
|
|
690
784
|
u64(tokenIn),
|
|
691
|
-
u64(minSol > 0n ? minSol : 1n)
|
|
692
|
-
])
|
|
785
|
+
u64(minSol > 0n ? minSol : 1n),
|
|
786
|
+
]),
|
|
693
787
|
}));
|
|
694
788
|
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
|
|
695
789
|
tx.recentBlockhash = blockhash;
|
|
696
|
-
tx.feePayer = this.
|
|
697
|
-
|
|
790
|
+
tx.feePayer = this.publicKey;
|
|
791
|
+
await this.signTx(tx);
|
|
698
792
|
const signature = await this.connection.sendRawTransaction(tx.serialize());
|
|
699
793
|
pendingTransactions.push({
|
|
700
794
|
signature,
|
|
701
795
|
lastValidBlockHeight,
|
|
702
|
-
index: i
|
|
796
|
+
index: i,
|
|
703
797
|
});
|
|
704
798
|
}
|
|
705
799
|
catch (e) {
|
|
706
800
|
failedTransactions.push({
|
|
707
801
|
index: i,
|
|
708
|
-
error: e.message
|
|
802
|
+
error: e.message,
|
|
709
803
|
});
|
|
710
804
|
}
|
|
711
805
|
}
|
|
712
806
|
return { pendingTransactions, failedTransactions };
|
|
713
807
|
}
|
|
714
808
|
/* ---------- 外盘交易 ---------- */
|
|
715
|
-
async ammBuy(tokenAddr, totalSolIn, tradeOpt) {
|
|
809
|
+
async ammBuy(tokenAddr, totalSolIn, tradeOpt, quoteMint = SOL_MINT) {
|
|
716
810
|
const mint = new web3_js_1.PublicKey(tokenAddr);
|
|
717
|
-
const poolInfo = await this.getAmmPoolInfo(mint);
|
|
811
|
+
const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
|
|
718
812
|
const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
|
|
719
813
|
const solChunks = this.splitByMax(totalSolIn, tradeOpt.maxSolPerTx);
|
|
720
814
|
const tokenProgram = await this.detectTokenProgram(tokenAddr);
|
|
815
|
+
const isSolQuote = quoteMint.equals(SOL_MINT);
|
|
816
|
+
const quoteTokenProgramId = isSolQuote
|
|
817
|
+
? spl_token_1.TOKEN_PROGRAM_ID
|
|
818
|
+
: await this.detectQuoteTokenProgram(quoteMint);
|
|
721
819
|
const pendingTransactions = [];
|
|
722
820
|
const failedTransactions = [];
|
|
723
821
|
for (let i = 0; i < solChunks.length; i++) {
|
|
@@ -727,45 +825,53 @@ class PumpTrader {
|
|
|
727
825
|
const slippageBps = this.calcSlippage({
|
|
728
826
|
tradeSize: solIn,
|
|
729
827
|
reserve: reserves.quoteAmount,
|
|
730
|
-
slippageOpt: tradeOpt.slippage
|
|
828
|
+
slippageOpt: tradeOpt.slippage,
|
|
731
829
|
});
|
|
732
830
|
const maxQuoteIn = (solIn * BigInt(10_000 + slippageBps)) / 10000n;
|
|
733
831
|
const priority = this.genPriority(tradeOpt.priority);
|
|
734
832
|
const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
|
|
735
833
|
const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint, tokenProgram.programId);
|
|
736
|
-
const userQuoteAta =
|
|
834
|
+
const userQuoteAta = isSolQuote
|
|
835
|
+
? await this.ensureWSOLAta(tx, this.publicKey, "buy", maxQuoteIn)
|
|
836
|
+
: await this.ensureAta(tx, quoteMint, quoteTokenProgramId);
|
|
737
837
|
const buyIx = this.createAmmBuyInstruction(poolInfo, userBaseAta, userQuoteAta, baseAmountOut, maxQuoteIn, tokenProgram.programId);
|
|
738
838
|
tx.add(buyIx);
|
|
739
|
-
|
|
740
|
-
|
|
839
|
+
if (isSolQuote) {
|
|
840
|
+
tx.add((0, spl_token_1.createCloseAccountInstruction)(userQuoteAta, this.publicKey, this.publicKey));
|
|
841
|
+
}
|
|
842
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
|
|
741
843
|
tx.recentBlockhash = blockhash;
|
|
742
|
-
tx.feePayer = this.
|
|
743
|
-
|
|
844
|
+
tx.feePayer = this.publicKey;
|
|
845
|
+
await this.signTx(tx);
|
|
744
846
|
const signature = await this.connection.sendRawTransaction(tx.serialize(), {
|
|
745
847
|
skipPreflight: false,
|
|
746
|
-
maxRetries: 2
|
|
848
|
+
maxRetries: 2,
|
|
747
849
|
});
|
|
748
850
|
pendingTransactions.push({
|
|
749
851
|
signature,
|
|
750
852
|
lastValidBlockHeight,
|
|
751
|
-
index: i
|
|
853
|
+
index: i,
|
|
752
854
|
});
|
|
753
855
|
}
|
|
754
856
|
catch (e) {
|
|
755
857
|
failedTransactions.push({
|
|
756
858
|
index: i,
|
|
757
|
-
error: e.message
|
|
859
|
+
error: e.message,
|
|
758
860
|
});
|
|
759
861
|
}
|
|
760
862
|
}
|
|
761
863
|
return { pendingTransactions, failedTransactions };
|
|
762
864
|
}
|
|
763
|
-
async ammSell(tokenAddr, totalTokenIn, tradeOpt) {
|
|
865
|
+
async ammSell(tokenAddr, totalTokenIn, tradeOpt, quoteMint = SOL_MINT) {
|
|
764
866
|
const mint = new web3_js_1.PublicKey(tokenAddr);
|
|
765
|
-
const poolInfo = await this.getAmmPoolInfo(mint);
|
|
867
|
+
const poolInfo = await this.getAmmPoolInfo(mint, quoteMint);
|
|
766
868
|
const reserves = await this.getAmmPoolReserves(poolInfo.poolKeys);
|
|
767
869
|
const totalSolOut = this.calculateAmmSellOutput(totalTokenIn, reserves);
|
|
768
870
|
const tokenProgram = await this.detectTokenProgram(tokenAddr);
|
|
871
|
+
const isSolQuote = quoteMint.equals(SOL_MINT);
|
|
872
|
+
const quoteTokenProgramId = isSolQuote
|
|
873
|
+
? spl_token_1.TOKEN_PROGRAM_ID
|
|
874
|
+
: await this.detectQuoteTokenProgram(quoteMint);
|
|
769
875
|
const tokenChunks = totalSolOut <= tradeOpt.maxSolPerTx
|
|
770
876
|
? [totalTokenIn]
|
|
771
877
|
: this.splitIntoN(totalTokenIn, Number((totalSolOut + tradeOpt.maxSolPerTx - 1n) / tradeOpt.maxSolPerTx));
|
|
@@ -778,48 +884,52 @@ class PumpTrader {
|
|
|
778
884
|
const slippageBps = this.calcSlippage({
|
|
779
885
|
tradeSize: tokenIn,
|
|
780
886
|
reserve: reserves.baseAmount,
|
|
781
|
-
slippageOpt: tradeOpt.slippage
|
|
887
|
+
slippageOpt: tradeOpt.slippage,
|
|
782
888
|
});
|
|
783
889
|
const minQuoteOut = (solOut * BigInt(10_000 - slippageBps)) / 10000n;
|
|
784
890
|
const priority = this.genPriority(tradeOpt.priority);
|
|
785
891
|
const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
|
|
786
892
|
const userBaseAta = await this.ensureAta(tx, poolInfo.poolKeys.baseMint, tokenProgram.programId);
|
|
787
|
-
const userQuoteAta =
|
|
893
|
+
const userQuoteAta = isSolQuote
|
|
894
|
+
? await this.ensureWSOLAta(tx, this.publicKey, "sell")
|
|
895
|
+
: await this.ensureAta(tx, quoteMint, quoteTokenProgramId);
|
|
788
896
|
const sellIx = this.createAmmSellInstruction(poolInfo, userBaseAta, userQuoteAta, tokenIn, minQuoteOut, tokenProgram.programId);
|
|
789
897
|
tx.add(sellIx);
|
|
790
|
-
|
|
791
|
-
|
|
898
|
+
if (isSolQuote) {
|
|
899
|
+
tx.add((0, spl_token_1.createCloseAccountInstruction)(userQuoteAta, this.publicKey, this.publicKey));
|
|
900
|
+
}
|
|
901
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
|
|
792
902
|
tx.recentBlockhash = blockhash;
|
|
793
|
-
tx.feePayer = this.
|
|
794
|
-
|
|
903
|
+
tx.feePayer = this.publicKey;
|
|
904
|
+
await this.signTx(tx);
|
|
795
905
|
const signature = await this.connection.sendRawTransaction(tx.serialize(), {
|
|
796
906
|
skipPreflight: false,
|
|
797
|
-
maxRetries: 2
|
|
907
|
+
maxRetries: 2,
|
|
798
908
|
});
|
|
799
909
|
pendingTransactions.push({
|
|
800
910
|
signature,
|
|
801
911
|
lastValidBlockHeight,
|
|
802
|
-
index: i
|
|
912
|
+
index: i,
|
|
803
913
|
});
|
|
804
914
|
}
|
|
805
915
|
catch (e) {
|
|
806
916
|
failedTransactions.push({
|
|
807
917
|
index: i,
|
|
808
|
-
error: e.message
|
|
918
|
+
error: e.message,
|
|
809
919
|
});
|
|
810
920
|
}
|
|
811
921
|
}
|
|
812
922
|
return { pendingTransactions, failedTransactions };
|
|
813
923
|
}
|
|
814
924
|
/* ---------- AMM 池信息 ---------- */
|
|
815
|
-
async getAmmPoolInfo(mint) {
|
|
925
|
+
async getAmmPoolInfo(mint, quoteMint = SOL_MINT) {
|
|
816
926
|
const [poolAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("pool-authority"), mint.toBuffer()], PROGRAM_IDS.PUMP);
|
|
817
927
|
const [pool] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
818
928
|
Buffer.from("pool"),
|
|
819
929
|
new bn_js_1.default(0).toArrayLike(Buffer, "le", 2),
|
|
820
930
|
poolAuthority.toBuffer(),
|
|
821
931
|
mint.toBuffer(),
|
|
822
|
-
|
|
932
|
+
quoteMint.toBuffer(),
|
|
823
933
|
], PROGRAM_IDS.PUMP_AMM);
|
|
824
934
|
const acc = await this.connection.getAccountInfo(pool);
|
|
825
935
|
if (!acc)
|
|
@@ -849,13 +959,13 @@ class PumpTrader {
|
|
|
849
959
|
async getAmmPoolReserves(poolKeys) {
|
|
850
960
|
const [baseInfo, quoteInfo] = await Promise.all([
|
|
851
961
|
this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
|
|
852
|
-
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount)
|
|
962
|
+
this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
|
|
853
963
|
]);
|
|
854
964
|
return {
|
|
855
965
|
baseAmount: BigInt(baseInfo.value.amount),
|
|
856
966
|
quoteAmount: BigInt(quoteInfo.value.amount),
|
|
857
967
|
baseDecimals: baseInfo.value.decimals,
|
|
858
|
-
quoteDecimals: quoteInfo.value.decimals
|
|
968
|
+
quoteDecimals: quoteInfo.value.decimals,
|
|
859
969
|
};
|
|
860
970
|
}
|
|
861
971
|
deriveAmmPoolV2(baseMint) {
|
|
@@ -869,7 +979,10 @@ class PumpTrader {
|
|
|
869
979
|
const [coinCreatorVaultAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator_vault"), poolKeys.coinCreator.toBuffer()], PROGRAM_IDS.PUMP_AMM);
|
|
870
980
|
const coinCreatorVaultAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, coinCreatorVaultAuthority, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
871
981
|
const [globalVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("global_volume_accumulator")], PROGRAM_IDS.PUMP_AMM);
|
|
872
|
-
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
982
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
983
|
+
Buffer.from("user_volume_accumulator"),
|
|
984
|
+
this.publicKey.toBuffer(),
|
|
985
|
+
], PROGRAM_IDS.PUMP_AMM);
|
|
873
986
|
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
874
987
|
const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
|
|
875
988
|
const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
@@ -878,44 +991,72 @@ class PumpTrader {
|
|
|
878
991
|
const remainingKeys = [];
|
|
879
992
|
if (poolKeys.isCashbackCoin) {
|
|
880
993
|
const userVolumeAccumulatorWsolAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, userVolumeAccumulator, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
881
|
-
remainingKeys.push({
|
|
994
|
+
remainingKeys.push({
|
|
995
|
+
pubkey: userVolumeAccumulatorWsolAta,
|
|
996
|
+
isSigner: false,
|
|
997
|
+
isWritable: true,
|
|
998
|
+
});
|
|
882
999
|
}
|
|
883
1000
|
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
884
|
-
remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, {
|
|
1001
|
+
remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, {
|
|
1002
|
+
pubkey: newFeeRecipientTokenAccount,
|
|
1003
|
+
isSigner: false,
|
|
1004
|
+
isWritable: true,
|
|
1005
|
+
});
|
|
885
1006
|
return new web3_js_1.TransactionInstruction({
|
|
886
1007
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
887
1008
|
keys: [
|
|
888
1009
|
{ pubkey: pool, isSigner: false, isWritable: true },
|
|
889
|
-
{ pubkey: this.
|
|
1010
|
+
{ pubkey: this.publicKey, isSigner: true, isWritable: true },
|
|
890
1011
|
{ pubkey: globalConfig.address, isSigner: false, isWritable: false },
|
|
891
1012
|
{ pubkey: poolKeys.baseMint, isSigner: false, isWritable: false },
|
|
892
1013
|
{ pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
|
|
893
1014
|
{ pubkey: userBaseAta, isSigner: false, isWritable: true },
|
|
894
1015
|
{ pubkey: userQuoteAta, isSigner: false, isWritable: true },
|
|
895
|
-
{
|
|
896
|
-
|
|
1016
|
+
{
|
|
1017
|
+
pubkey: poolKeys.poolBaseTokenAccount,
|
|
1018
|
+
isSigner: false,
|
|
1019
|
+
isWritable: true,
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
pubkey: poolKeys.poolQuoteTokenAccount,
|
|
1023
|
+
isSigner: false,
|
|
1024
|
+
isWritable: true,
|
|
1025
|
+
},
|
|
897
1026
|
{ pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
|
|
898
|
-
{
|
|
1027
|
+
{
|
|
1028
|
+
pubkey: protocolFeeRecipientTokenAccount,
|
|
1029
|
+
isSigner: false,
|
|
1030
|
+
isWritable: true,
|
|
1031
|
+
},
|
|
899
1032
|
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
900
1033
|
{ pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
901
1034
|
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
902
|
-
{
|
|
1035
|
+
{
|
|
1036
|
+
pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1037
|
+
isSigner: false,
|
|
1038
|
+
isWritable: false,
|
|
1039
|
+
},
|
|
903
1040
|
{ pubkey: eventAuthority, isSigner: false, isWritable: false },
|
|
904
1041
|
{ pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
|
|
905
1042
|
{ pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
|
|
906
|
-
{
|
|
1043
|
+
{
|
|
1044
|
+
pubkey: coinCreatorVaultAuthority,
|
|
1045
|
+
isSigner: false,
|
|
1046
|
+
isWritable: false,
|
|
1047
|
+
},
|
|
907
1048
|
{ pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
|
|
908
1049
|
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
909
1050
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
910
1051
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
911
|
-
...remainingKeys
|
|
1052
|
+
...remainingKeys,
|
|
912
1053
|
],
|
|
913
1054
|
data: Buffer.concat([
|
|
914
1055
|
DISCRIMINATORS.BUY,
|
|
915
1056
|
u64(baseAmountOut),
|
|
916
1057
|
u64(maxQuoteAmountIn),
|
|
917
|
-
Buffer.from([1, 1])
|
|
918
|
-
])
|
|
1058
|
+
Buffer.from([1, 1]),
|
|
1059
|
+
]),
|
|
919
1060
|
});
|
|
920
1061
|
}
|
|
921
1062
|
createAmmSellInstruction(poolInfo, userBaseAta, userQuoteAta, baseAmountIn, minQuoteAmountOut, tokenProgramId) {
|
|
@@ -929,76 +1070,515 @@ class PumpTrader {
|
|
|
929
1070
|
const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
930
1071
|
const newFeeRecipient = this.pickFeeRecipient();
|
|
931
1072
|
const newFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(poolKeys.quoteMint, newFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
932
|
-
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
1073
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
1074
|
+
Buffer.from("user_volume_accumulator"),
|
|
1075
|
+
this.publicKey.toBuffer(),
|
|
1076
|
+
], PROGRAM_IDS.PUMP_AMM);
|
|
933
1077
|
const userVolumeAccumulatorWsolAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, userVolumeAccumulator, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
934
1078
|
const remainingKeys = [];
|
|
935
1079
|
if (poolKeys.isCashbackCoin) {
|
|
936
|
-
remainingKeys.push({
|
|
1080
|
+
remainingKeys.push({
|
|
1081
|
+
pubkey: userVolumeAccumulatorWsolAta,
|
|
1082
|
+
isSigner: false,
|
|
1083
|
+
isWritable: true,
|
|
1084
|
+
}, { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
937
1085
|
}
|
|
938
1086
|
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
939
|
-
remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, {
|
|
1087
|
+
remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, {
|
|
1088
|
+
pubkey: newFeeRecipientTokenAccount,
|
|
1089
|
+
isSigner: false,
|
|
1090
|
+
isWritable: true,
|
|
1091
|
+
});
|
|
940
1092
|
return new web3_js_1.TransactionInstruction({
|
|
941
1093
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
942
1094
|
keys: [
|
|
943
1095
|
{ pubkey: pool, isSigner: false, isWritable: true },
|
|
944
|
-
{ pubkey: this.
|
|
1096
|
+
{ pubkey: this.publicKey, isSigner: true, isWritable: true },
|
|
945
1097
|
{ pubkey: globalConfig.address, isSigner: false, isWritable: false },
|
|
946
1098
|
{ pubkey: poolKeys.baseMint, isSigner: false, isWritable: false },
|
|
947
1099
|
{ pubkey: poolKeys.quoteMint, isSigner: false, isWritable: false },
|
|
948
1100
|
{ pubkey: userBaseAta, isSigner: false, isWritable: true },
|
|
949
1101
|
{ pubkey: userQuoteAta, isSigner: false, isWritable: true },
|
|
950
|
-
{
|
|
951
|
-
|
|
1102
|
+
{
|
|
1103
|
+
pubkey: poolKeys.poolBaseTokenAccount,
|
|
1104
|
+
isSigner: false,
|
|
1105
|
+
isWritable: true,
|
|
1106
|
+
},
|
|
1107
|
+
{
|
|
1108
|
+
pubkey: poolKeys.poolQuoteTokenAccount,
|
|
1109
|
+
isSigner: false,
|
|
1110
|
+
isWritable: true,
|
|
1111
|
+
},
|
|
952
1112
|
{ pubkey: protocolFeeRecipient, isSigner: false, isWritable: false },
|
|
953
|
-
{
|
|
1113
|
+
{
|
|
1114
|
+
pubkey: protocolFeeRecipientTokenAccount,
|
|
1115
|
+
isSigner: false,
|
|
1116
|
+
isWritable: true,
|
|
1117
|
+
},
|
|
954
1118
|
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
955
1119
|
{ pubkey: spl_token_1.TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
956
1120
|
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
957
|
-
{
|
|
1121
|
+
{
|
|
1122
|
+
pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1123
|
+
isSigner: false,
|
|
1124
|
+
isWritable: false,
|
|
1125
|
+
},
|
|
958
1126
|
{ pubkey: eventAuthority, isSigner: false, isWritable: false },
|
|
959
1127
|
{ pubkey: PROGRAM_IDS.PUMP_AMM, isSigner: false, isWritable: false },
|
|
960
1128
|
{ pubkey: coinCreatorVaultAta, isSigner: false, isWritable: true },
|
|
961
|
-
{
|
|
1129
|
+
{
|
|
1130
|
+
pubkey: coinCreatorVaultAuthority,
|
|
1131
|
+
isSigner: false,
|
|
1132
|
+
isWritable: false,
|
|
1133
|
+
},
|
|
962
1134
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
963
1135
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
964
|
-
...remainingKeys
|
|
1136
|
+
...remainingKeys,
|
|
965
1137
|
],
|
|
966
1138
|
data: Buffer.concat([
|
|
967
1139
|
DISCRIMINATORS.SELL,
|
|
968
1140
|
u64(baseAmountIn),
|
|
969
|
-
u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n)
|
|
970
|
-
])
|
|
1141
|
+
u64(minQuoteAmountOut > 0n ? minQuoteAmountOut : 1n),
|
|
1142
|
+
]),
|
|
971
1143
|
});
|
|
972
1144
|
}
|
|
1145
|
+
/* ---------- V2 指令账户构建 ---------- */
|
|
1146
|
+
/**
|
|
1147
|
+
* Build accounts for buy_v2 instruction (27 accounts)
|
|
1148
|
+
* Ref: https://github.com/pump-fun/pump-public-docs/blob/main/docs/instructions/BUY.md
|
|
1149
|
+
*/
|
|
1150
|
+
buildBondingBuyV2Keys(args) {
|
|
1151
|
+
return [
|
|
1152
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
1153
|
+
{ pubkey: args.baseMint, isSigner: false, isWritable: false },
|
|
1154
|
+
{ pubkey: args.quoteMint, isSigner: false, isWritable: false },
|
|
1155
|
+
{ pubkey: args.baseTokenProgram, isSigner: false, isWritable: false },
|
|
1156
|
+
{ pubkey: args.quoteTokenProgram, isSigner: false, isWritable: false },
|
|
1157
|
+
{
|
|
1158
|
+
pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1159
|
+
isSigner: false,
|
|
1160
|
+
isWritable: false,
|
|
1161
|
+
},
|
|
1162
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true },
|
|
1163
|
+
{
|
|
1164
|
+
pubkey: args.associatedQuoteFeeRecipient,
|
|
1165
|
+
isSigner: false,
|
|
1166
|
+
isWritable: true,
|
|
1167
|
+
},
|
|
1168
|
+
{ pubkey: args.buybackFeeRecipient, isSigner: false, isWritable: true },
|
|
1169
|
+
{
|
|
1170
|
+
pubkey: args.associatedQuoteBuybackFeeRecipient,
|
|
1171
|
+
isSigner: false,
|
|
1172
|
+
isWritable: true,
|
|
1173
|
+
},
|
|
1174
|
+
{ pubkey: args.bondingCurve, isSigner: false, isWritable: true },
|
|
1175
|
+
{
|
|
1176
|
+
pubkey: args.associatedBaseBondingCurve,
|
|
1177
|
+
isSigner: false,
|
|
1178
|
+
isWritable: true,
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
pubkey: args.associatedQuoteBondingCurve,
|
|
1182
|
+
isSigner: false,
|
|
1183
|
+
isWritable: true,
|
|
1184
|
+
},
|
|
1185
|
+
{ pubkey: args.user, isSigner: true, isWritable: true },
|
|
1186
|
+
{ pubkey: args.associatedBaseUser, isSigner: false, isWritable: true },
|
|
1187
|
+
{ pubkey: args.associatedQuoteUser, isSigner: false, isWritable: true },
|
|
1188
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
1189
|
+
{
|
|
1190
|
+
pubkey: args.associatedCreatorVault,
|
|
1191
|
+
isSigner: false,
|
|
1192
|
+
isWritable: true,
|
|
1193
|
+
},
|
|
1194
|
+
{ pubkey: args.sharingConfig, isSigner: false, isWritable: false },
|
|
1195
|
+
{
|
|
1196
|
+
pubkey: args.globalVolumeAccumulator,
|
|
1197
|
+
isSigner: false,
|
|
1198
|
+
isWritable: false,
|
|
1199
|
+
},
|
|
1200
|
+
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
1201
|
+
{
|
|
1202
|
+
pubkey: args.associatedUserVolumeAccumulator,
|
|
1203
|
+
isSigner: false,
|
|
1204
|
+
isWritable: true,
|
|
1205
|
+
},
|
|
1206
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
1207
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
1208
|
+
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1209
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
1210
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
1211
|
+
];
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Build accounts for sell_v2 instruction (26 accounts)
|
|
1215
|
+
* Ref: https://github.com/pump-fun/pump-public-docs/blob/main/docs/instructions/SELL.md
|
|
1216
|
+
*/
|
|
1217
|
+
buildBondingSellV2Keys(args) {
|
|
1218
|
+
return [
|
|
1219
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
1220
|
+
{ pubkey: args.baseMint, isSigner: false, isWritable: false },
|
|
1221
|
+
{ pubkey: args.quoteMint, isSigner: false, isWritable: false },
|
|
1222
|
+
{ pubkey: args.baseTokenProgram, isSigner: false, isWritable: false },
|
|
1223
|
+
{ pubkey: args.quoteTokenProgram, isSigner: false, isWritable: false },
|
|
1224
|
+
{
|
|
1225
|
+
pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1226
|
+
isSigner: false,
|
|
1227
|
+
isWritable: false,
|
|
1228
|
+
},
|
|
1229
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true },
|
|
1230
|
+
{
|
|
1231
|
+
pubkey: args.associatedQuoteFeeRecipient,
|
|
1232
|
+
isSigner: false,
|
|
1233
|
+
isWritable: true,
|
|
1234
|
+
},
|
|
1235
|
+
{ pubkey: args.buybackFeeRecipient, isSigner: false, isWritable: true },
|
|
1236
|
+
{
|
|
1237
|
+
pubkey: args.associatedQuoteBuybackFeeRecipient,
|
|
1238
|
+
isSigner: false,
|
|
1239
|
+
isWritable: true,
|
|
1240
|
+
},
|
|
1241
|
+
{ pubkey: args.bondingCurve, isSigner: false, isWritable: true },
|
|
1242
|
+
{
|
|
1243
|
+
pubkey: args.associatedBaseBondingCurve,
|
|
1244
|
+
isSigner: false,
|
|
1245
|
+
isWritable: true,
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
pubkey: args.associatedQuoteBondingCurve,
|
|
1249
|
+
isSigner: false,
|
|
1250
|
+
isWritable: true,
|
|
1251
|
+
},
|
|
1252
|
+
{ pubkey: args.user, isSigner: true, isWritable: true },
|
|
1253
|
+
{ pubkey: args.associatedBaseUser, isSigner: false, isWritable: true },
|
|
1254
|
+
{ pubkey: args.associatedQuoteUser, isSigner: false, isWritable: true },
|
|
1255
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
1256
|
+
{
|
|
1257
|
+
pubkey: args.associatedCreatorVault,
|
|
1258
|
+
isSigner: false,
|
|
1259
|
+
isWritable: true,
|
|
1260
|
+
},
|
|
1261
|
+
{ pubkey: args.sharingConfig, isSigner: false, isWritable: false },
|
|
1262
|
+
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
1263
|
+
{
|
|
1264
|
+
pubkey: args.associatedUserVolumeAccumulator,
|
|
1265
|
+
isSigner: false,
|
|
1266
|
+
isWritable: true,
|
|
1267
|
+
},
|
|
1268
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
1269
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
1270
|
+
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
1271
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
1272
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
1273
|
+
];
|
|
1274
|
+
}
|
|
1275
|
+
/* ---------- V2 交易 ---------- */
|
|
1276
|
+
/**
|
|
1277
|
+
* Buy using buy_v2 instruction (supports both SOL-paired and USDC-paired coins)
|
|
1278
|
+
* For SOL-paired coins: quoteMint = SOL_MINT, quoteTokenProgram = TOKEN_PROGRAM_ID
|
|
1279
|
+
* For USDC-paired coins: quoteMint = USDC mint, quoteTokenProgram = TOKEN_PROGRAM_ID
|
|
1280
|
+
*/
|
|
1281
|
+
async buyV2(tokenAddr, totalQuoteIn, tradeOpt, quoteMint = SOL_MINT) {
|
|
1282
|
+
const baseMint = new web3_js_1.PublicKey(tokenAddr);
|
|
1283
|
+
const baseTokenProgram = await this.detectTokenProgram(tokenAddr);
|
|
1284
|
+
const quoteTokenProgramId = quoteMint.equals(SOL_MINT)
|
|
1285
|
+
? spl_token_1.TOKEN_PROGRAM_ID
|
|
1286
|
+
: spl_token_1.TOKEN_PROGRAM_ID;
|
|
1287
|
+
if (!this.globalState)
|
|
1288
|
+
await this.loadGlobal();
|
|
1289
|
+
const { bonding, state, creator } = await this.loadBonding(baseMint);
|
|
1290
|
+
if (state.complete)
|
|
1291
|
+
throw new Error("Bonding curve already completed");
|
|
1292
|
+
const solEquivalent = quoteMint.equals(SOL_MINT) ? totalQuoteIn : 0n;
|
|
1293
|
+
const quoteChunks = solEquivalent > 0n
|
|
1294
|
+
? this.splitByMax(solEquivalent, tradeOpt.maxSolPerTx)
|
|
1295
|
+
: this.splitByMax(totalQuoteIn, tradeOpt.maxSolPerTx);
|
|
1296
|
+
const pendingTransactions = [];
|
|
1297
|
+
const failedTransactions = [];
|
|
1298
|
+
// Pre-compute PDAs
|
|
1299
|
+
const associatedBaseBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(baseMint, bonding, true, baseTokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1300
|
+
const associatedQuoteBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, bonding, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1301
|
+
const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
|
|
1302
|
+
const associatedCreatorVault = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, creatorVault, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1303
|
+
const [globalVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("global_volume_accumulator")], PROGRAM_IDS.PUMP);
|
|
1304
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
1305
|
+
Buffer.from("user_volume_accumulator"),
|
|
1306
|
+
this.publicKey.toBuffer(),
|
|
1307
|
+
], PROGRAM_IDS.PUMP);
|
|
1308
|
+
const associatedUserVolumeAccumulator = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, userVolumeAccumulator, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1309
|
+
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
1310
|
+
const sharingConfig = this.getSharingConfigPda(baseMint);
|
|
1311
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
1312
|
+
const associatedQuoteFeeRecipient = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, feeRecipient, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1313
|
+
const buybackFeeRecipient = this.pickBuybackFeeRecipient();
|
|
1314
|
+
const associatedQuoteBuybackFeeRecipient = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, buybackFeeRecipient, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1315
|
+
const associatedQuoteUser = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, this.publicKey, false, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1316
|
+
for (let i = 0; i < quoteChunks.length; i++) {
|
|
1317
|
+
try {
|
|
1318
|
+
const quoteIn = quoteChunks[i];
|
|
1319
|
+
const tokenOut = this.calcBuy(quoteIn, state);
|
|
1320
|
+
const slippageBps = this.calcSlippage({
|
|
1321
|
+
tradeSize: quoteIn,
|
|
1322
|
+
reserve: state.virtualSolReserves,
|
|
1323
|
+
slippageOpt: tradeOpt.slippage,
|
|
1324
|
+
});
|
|
1325
|
+
const maxQuoteCost = (quoteIn * BigInt(10_000 + slippageBps)) / 10000n;
|
|
1326
|
+
const priority = this.genPriority(tradeOpt.priority);
|
|
1327
|
+
const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
|
|
1328
|
+
// For SOL-paired coins, need wSOL ATA for the user's quote account
|
|
1329
|
+
if (quoteMint.equals(SOL_MINT)) {
|
|
1330
|
+
await this.ensureWSOLAta(tx, this.publicKey, "buy", maxQuoteCost);
|
|
1331
|
+
}
|
|
1332
|
+
else {
|
|
1333
|
+
// For non-SOL quote (e.g. USDC), ensure user has the quote token ATA
|
|
1334
|
+
const userQuoteAta = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, this.publicKey, false, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1335
|
+
const acc = await this.connection.getAccountInfo(userQuoteAta);
|
|
1336
|
+
if (!acc) {
|
|
1337
|
+
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(this.publicKey, userQuoteAta, this.publicKey, quoteMint, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
const userBaseAta = await this.ensureAta(tx, baseMint, baseTokenProgram.programId);
|
|
1341
|
+
tx.add(new web3_js_1.TransactionInstruction({
|
|
1342
|
+
programId: PROGRAM_IDS.PUMP,
|
|
1343
|
+
keys: this.buildBondingBuyV2Keys({
|
|
1344
|
+
global: this.global,
|
|
1345
|
+
baseMint,
|
|
1346
|
+
quoteMint,
|
|
1347
|
+
baseTokenProgram: baseTokenProgram.programId,
|
|
1348
|
+
quoteTokenProgram: quoteTokenProgramId,
|
|
1349
|
+
feeRecipient,
|
|
1350
|
+
associatedQuoteFeeRecipient,
|
|
1351
|
+
buybackFeeRecipient,
|
|
1352
|
+
associatedQuoteBuybackFeeRecipient,
|
|
1353
|
+
bondingCurve: bonding,
|
|
1354
|
+
associatedBaseBondingCurve,
|
|
1355
|
+
associatedQuoteBondingCurve,
|
|
1356
|
+
user: this.publicKey,
|
|
1357
|
+
associatedBaseUser: userBaseAta,
|
|
1358
|
+
associatedQuoteUser,
|
|
1359
|
+
creatorVault,
|
|
1360
|
+
associatedCreatorVault,
|
|
1361
|
+
sharingConfig,
|
|
1362
|
+
globalVolumeAccumulator,
|
|
1363
|
+
userVolumeAccumulator,
|
|
1364
|
+
associatedUserVolumeAccumulator,
|
|
1365
|
+
feeConfig,
|
|
1366
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
1367
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
1368
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
1369
|
+
}),
|
|
1370
|
+
data: Buffer.concat([
|
|
1371
|
+
DISCRIMINATORS.BUY_V2,
|
|
1372
|
+
u64(tokenOut),
|
|
1373
|
+
u64(maxQuoteCost),
|
|
1374
|
+
]),
|
|
1375
|
+
}));
|
|
1376
|
+
// Close wSOL ATA after buy for SOL-paired coins
|
|
1377
|
+
if (quoteMint.equals(SOL_MINT)) {
|
|
1378
|
+
const wsolAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, this.publicKey, false, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1379
|
+
tx.add((0, spl_token_1.createCloseAccountInstruction)(wsolAta, this.publicKey, this.publicKey));
|
|
1380
|
+
}
|
|
1381
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
|
|
1382
|
+
tx.recentBlockhash = blockhash;
|
|
1383
|
+
tx.feePayer = this.publicKey;
|
|
1384
|
+
await this.signTx(tx);
|
|
1385
|
+
const signature = await this.connection.sendRawTransaction(tx.serialize(), {
|
|
1386
|
+
skipPreflight: false,
|
|
1387
|
+
maxRetries: 2,
|
|
1388
|
+
});
|
|
1389
|
+
pendingTransactions.push({ signature, lastValidBlockHeight, index: i });
|
|
1390
|
+
}
|
|
1391
|
+
catch (e) {
|
|
1392
|
+
failedTransactions.push({ index: i, error: e.message });
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return { pendingTransactions, failedTransactions };
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Sell using sell_v2 instruction (supports both SOL-paired and USDC-paired coins)
|
|
1399
|
+
*/
|
|
1400
|
+
async sellV2(tokenAddr, totalTokenIn, tradeOpt, quoteMint = SOL_MINT) {
|
|
1401
|
+
const baseMint = new web3_js_1.PublicKey(tokenAddr);
|
|
1402
|
+
const baseTokenProgram = await this.detectTokenProgram(tokenAddr);
|
|
1403
|
+
const quoteTokenProgramId = quoteMint.equals(SOL_MINT)
|
|
1404
|
+
? spl_token_1.TOKEN_PROGRAM_ID
|
|
1405
|
+
: spl_token_1.TOKEN_PROGRAM_ID;
|
|
1406
|
+
if (!this.globalState)
|
|
1407
|
+
await this.loadGlobal();
|
|
1408
|
+
const { bonding, state, creator } = await this.loadBonding(baseMint);
|
|
1409
|
+
if (state.complete)
|
|
1410
|
+
throw new Error("Bonding curve already completed");
|
|
1411
|
+
const totalQuoteOut = this.calcSell(totalTokenIn, state);
|
|
1412
|
+
const tokenChunks = totalQuoteOut <= tradeOpt.maxSolPerTx
|
|
1413
|
+
? [totalTokenIn]
|
|
1414
|
+
: this.splitIntoN(totalTokenIn, Number((totalQuoteOut + tradeOpt.maxSolPerTx - 1n) /
|
|
1415
|
+
tradeOpt.maxSolPerTx));
|
|
1416
|
+
const pendingTransactions = [];
|
|
1417
|
+
const failedTransactions = [];
|
|
1418
|
+
// Pre-compute PDAs
|
|
1419
|
+
const associatedBaseBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(baseMint, bonding, true, baseTokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1420
|
+
const associatedQuoteBondingCurve = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, bonding, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1421
|
+
const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
|
|
1422
|
+
const associatedCreatorVault = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, creatorVault, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1423
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
|
|
1424
|
+
Buffer.from("user_volume_accumulator"),
|
|
1425
|
+
this.publicKey.toBuffer(),
|
|
1426
|
+
], PROGRAM_IDS.PUMP);
|
|
1427
|
+
const associatedUserVolumeAccumulator = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, userVolumeAccumulator, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1428
|
+
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
1429
|
+
const sharingConfig = this.getSharingConfigPda(baseMint);
|
|
1430
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
1431
|
+
const associatedQuoteFeeRecipient = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, feeRecipient, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1432
|
+
const buybackFeeRecipient = this.pickBuybackFeeRecipient();
|
|
1433
|
+
const associatedQuoteBuybackFeeRecipient = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, buybackFeeRecipient, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1434
|
+
const associatedQuoteUser = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, this.publicKey, false, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1435
|
+
const userBaseAta = (0, spl_token_1.getAssociatedTokenAddressSync)(baseMint, this.publicKey, false, baseTokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1436
|
+
for (let i = 0; i < tokenChunks.length; i++) {
|
|
1437
|
+
try {
|
|
1438
|
+
const tokenIn = tokenChunks[i];
|
|
1439
|
+
const quoteOut = this.calcSell(tokenIn, state);
|
|
1440
|
+
const slippageBps = this.calcSlippage({
|
|
1441
|
+
tradeSize: tokenIn,
|
|
1442
|
+
reserve: state.virtualTokenReserves,
|
|
1443
|
+
slippageOpt: tradeOpt.slippage,
|
|
1444
|
+
});
|
|
1445
|
+
const minQuoteOut = (quoteOut * BigInt(10_000 - slippageBps)) / 10000n;
|
|
1446
|
+
const priority = this.genPriority(tradeOpt.priority);
|
|
1447
|
+
const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
|
|
1448
|
+
// For non-SOL quotes, ensure user has the quote token ATA (to receive proceeds)
|
|
1449
|
+
if (!quoteMint.equals(SOL_MINT)) {
|
|
1450
|
+
const userQuoteAta = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, this.publicKey, false, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1451
|
+
const acc = await this.connection.getAccountInfo(userQuoteAta);
|
|
1452
|
+
if (!acc) {
|
|
1453
|
+
tx.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(this.publicKey, userQuoteAta, this.publicKey, quoteMint, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
tx.add(new web3_js_1.TransactionInstruction({
|
|
1457
|
+
programId: PROGRAM_IDS.PUMP,
|
|
1458
|
+
keys: this.buildBondingSellV2Keys({
|
|
1459
|
+
global: this.global,
|
|
1460
|
+
baseMint,
|
|
1461
|
+
quoteMint,
|
|
1462
|
+
baseTokenProgram: baseTokenProgram.programId,
|
|
1463
|
+
quoteTokenProgram: quoteTokenProgramId,
|
|
1464
|
+
feeRecipient,
|
|
1465
|
+
associatedQuoteFeeRecipient,
|
|
1466
|
+
buybackFeeRecipient,
|
|
1467
|
+
associatedQuoteBuybackFeeRecipient,
|
|
1468
|
+
bondingCurve: bonding,
|
|
1469
|
+
associatedBaseBondingCurve,
|
|
1470
|
+
associatedQuoteBondingCurve,
|
|
1471
|
+
user: this.publicKey,
|
|
1472
|
+
associatedBaseUser: userBaseAta,
|
|
1473
|
+
associatedQuoteUser,
|
|
1474
|
+
creatorVault,
|
|
1475
|
+
associatedCreatorVault,
|
|
1476
|
+
sharingConfig,
|
|
1477
|
+
userVolumeAccumulator,
|
|
1478
|
+
associatedUserVolumeAccumulator,
|
|
1479
|
+
feeConfig,
|
|
1480
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
1481
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
1482
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
1483
|
+
}),
|
|
1484
|
+
data: Buffer.concat([
|
|
1485
|
+
DISCRIMINATORS.SELL_V2,
|
|
1486
|
+
u64(tokenIn),
|
|
1487
|
+
u64(minQuoteOut > 0n ? minQuoteOut : 1n),
|
|
1488
|
+
]),
|
|
1489
|
+
}));
|
|
1490
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
|
|
1491
|
+
tx.recentBlockhash = blockhash;
|
|
1492
|
+
tx.feePayer = this.publicKey;
|
|
1493
|
+
await this.signTx(tx);
|
|
1494
|
+
const signature = await this.connection.sendRawTransaction(tx.serialize());
|
|
1495
|
+
pendingTransactions.push({ signature, lastValidBlockHeight, index: i });
|
|
1496
|
+
}
|
|
1497
|
+
catch (e) {
|
|
1498
|
+
failedTransactions.push({ index: i, error: e.message });
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return { pendingTransactions, failedTransactions };
|
|
1502
|
+
}
|
|
1503
|
+
/* ---------- Collect Creator Fee V2 ---------- */
|
|
1504
|
+
/**
|
|
1505
|
+
* Collect creator fees from bonding curve creator vault (collect_creator_fee_v2)
|
|
1506
|
+
* Ref: https://github.com/pump-fun/pump-public-docs/blob/main/docs/instructions/COLLECT_CREATOR_FEE.md
|
|
1507
|
+
*/
|
|
1508
|
+
async collectCreatorFeeV2(creator, quoteMint = SOL_MINT) {
|
|
1509
|
+
const quoteTokenProgramId = quoteMint.equals(SOL_MINT)
|
|
1510
|
+
? spl_token_1.TOKEN_PROGRAM_ID
|
|
1511
|
+
: spl_token_1.TOKEN_PROGRAM_ID;
|
|
1512
|
+
const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
|
|
1513
|
+
const creatorTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, creator, false, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1514
|
+
const creatorVaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(quoteMint, creatorVault, true, quoteTokenProgramId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
1515
|
+
const [eventAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], PROGRAM_IDS.PUMP);
|
|
1516
|
+
const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), new web3_js_1.TransactionInstruction({
|
|
1517
|
+
programId: PROGRAM_IDS.PUMP,
|
|
1518
|
+
keys: [
|
|
1519
|
+
{ pubkey: creator, isSigner: false, isWritable: false },
|
|
1520
|
+
{ pubkey: creatorTokenAccount, isSigner: false, isWritable: true },
|
|
1521
|
+
{ pubkey: creatorVault, isSigner: false, isWritable: true },
|
|
1522
|
+
{
|
|
1523
|
+
pubkey: creatorVaultTokenAccount,
|
|
1524
|
+
isSigner: false,
|
|
1525
|
+
isWritable: true,
|
|
1526
|
+
},
|
|
1527
|
+
{ pubkey: quoteMint, isSigner: false, isWritable: false },
|
|
1528
|
+
{ pubkey: quoteTokenProgramId, isSigner: false, isWritable: false },
|
|
1529
|
+
{
|
|
1530
|
+
pubkey: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
1531
|
+
isSigner: false,
|
|
1532
|
+
isWritable: false,
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
pubkey: web3_js_1.SystemProgram.programId,
|
|
1536
|
+
isSigner: false,
|
|
1537
|
+
isWritable: false,
|
|
1538
|
+
},
|
|
1539
|
+
{ pubkey: eventAuthority, isSigner: false, isWritable: false },
|
|
1540
|
+
{ pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
|
|
1541
|
+
],
|
|
1542
|
+
data: DISCRIMINATORS.COLLECT_CREATOR_FEE_V2,
|
|
1543
|
+
}));
|
|
1544
|
+
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash("finalized");
|
|
1545
|
+
tx.recentBlockhash = blockhash;
|
|
1546
|
+
tx.feePayer = this.publicKey;
|
|
1547
|
+
await this.signTx(tx);
|
|
1548
|
+
const signature = await this.connection.sendRawTransaction(tx.serialize());
|
|
1549
|
+
await this.confirmTransactionWithPolling(signature, lastValidBlockHeight);
|
|
1550
|
+
return signature;
|
|
1551
|
+
}
|
|
973
1552
|
/* ---------- 交易确认 ---------- */
|
|
974
1553
|
async confirmTransactionWithPolling(signature, lastValidBlockHeight, maxAttempts = 5, delayMs = 2000) {
|
|
975
|
-
console.log(
|
|
976
|
-
console.log(
|
|
1554
|
+
console.log("✅ 交易已发送:", signature);
|
|
1555
|
+
console.log("🔗 查看交易: https://solscan.io/tx/" + signature);
|
|
977
1556
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
978
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
1557
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
979
1558
|
try {
|
|
980
1559
|
console.log(`🔍 检查交易状态 (${attempt}/${maxAttempts})...`);
|
|
981
1560
|
const txInfo = await this.connection.getTransaction(signature, {
|
|
982
|
-
commitment:
|
|
983
|
-
maxSupportedTransactionVersion: 0
|
|
1561
|
+
commitment: "confirmed",
|
|
1562
|
+
maxSupportedTransactionVersion: 0,
|
|
984
1563
|
});
|
|
985
1564
|
if (txInfo) {
|
|
986
1565
|
if (txInfo.meta?.err) {
|
|
987
|
-
console.error(
|
|
988
|
-
throw new Error(
|
|
1566
|
+
console.error("❌ 交易失败:", txInfo.meta.err);
|
|
1567
|
+
throw new Error("交易失败: " + JSON.stringify(txInfo.meta.err));
|
|
989
1568
|
}
|
|
990
|
-
console.log(
|
|
1569
|
+
console.log("✅ 交易已确认!");
|
|
991
1570
|
return signature;
|
|
992
1571
|
}
|
|
993
|
-
const currentBlockHeight = await this.connection.getBlockHeight(
|
|
1572
|
+
const currentBlockHeight = await this.connection.getBlockHeight("finalized");
|
|
994
1573
|
if (currentBlockHeight > lastValidBlockHeight) {
|
|
995
|
-
console.log(
|
|
996
|
-
throw new Error(
|
|
1574
|
+
console.log("⚠️ 交易已过期(超过有效区块高度)");
|
|
1575
|
+
throw new Error("交易过期:未在有效区块高度内确认");
|
|
997
1576
|
}
|
|
998
1577
|
}
|
|
999
1578
|
catch (error) {
|
|
1000
1579
|
const err = error;
|
|
1001
|
-
if (err.message?.includes(
|
|
1580
|
+
if (err.message?.includes("交易失败") ||
|
|
1581
|
+
err.message?.includes("交易过期")) {
|
|
1002
1582
|
throw error;
|
|
1003
1583
|
}
|
|
1004
1584
|
console.log(`⚠️ 查询出错,继续重试: ${err.message}`);
|
|
@@ -1035,7 +1615,7 @@ class PumpTrader {
|
|
|
1035
1615
|
isBuy,
|
|
1036
1616
|
user: user.toBase58(),
|
|
1037
1617
|
timestamp,
|
|
1038
|
-
signature: log.signature
|
|
1618
|
+
signature: log.signature,
|
|
1039
1619
|
});
|
|
1040
1620
|
}
|
|
1041
1621
|
}, "confirmed");
|
|
@@ -1048,25 +1628,34 @@ class PumpTrader {
|
|
|
1048
1628
|
return {
|
|
1049
1629
|
name: metadata?.name || "",
|
|
1050
1630
|
symbol: metadata?.symbol || "",
|
|
1051
|
-
uri: metadata?.uri || ""
|
|
1631
|
+
uri: metadata?.uri || "",
|
|
1052
1632
|
};
|
|
1053
1633
|
}
|
|
1054
1634
|
catch (e) {
|
|
1055
|
-
const metadataPda = web3_js_1.PublicKey.findProgramAddressSync([
|
|
1635
|
+
const metadataPda = web3_js_1.PublicKey.findProgramAddressSync([
|
|
1636
|
+
Buffer.from("metadata"),
|
|
1637
|
+
PROGRAM_IDS.METADATA.toBuffer(),
|
|
1638
|
+
mint.toBuffer(),
|
|
1639
|
+
], PROGRAM_IDS.METADATA)[0];
|
|
1056
1640
|
const acc = await this.connection.getAccountInfo(metadataPda);
|
|
1057
1641
|
if (!acc)
|
|
1058
1642
|
return null;
|
|
1059
1643
|
const meta = parseMetadataAccount(acc.data);
|
|
1060
1644
|
return {
|
|
1061
|
-
name: meta?.name?.replace(/\u0000/g,
|
|
1062
|
-
symbol: meta?.symbol?.replace(/\u0000/g,
|
|
1063
|
-
uri: meta?.uri?.replace(/\u0000/g,
|
|
1645
|
+
name: meta?.name?.replace(/\u0000/g, "") || "",
|
|
1646
|
+
symbol: meta?.symbol?.replace(/\u0000/g, "") || "",
|
|
1647
|
+
uri: meta?.uri?.replace(/\u0000/g, "") || "",
|
|
1064
1648
|
};
|
|
1065
1649
|
}
|
|
1066
1650
|
}
|
|
1067
|
-
|
|
1651
|
+
/**
|
|
1652
|
+
* 获取原始 wallet 对象(Keypair 或前端 WalletAdapter)
|
|
1653
|
+
*/
|
|
1068
1654
|
getWallet() {
|
|
1069
|
-
return this.
|
|
1655
|
+
return this._wallet;
|
|
1656
|
+
}
|
|
1657
|
+
getPublicKey() {
|
|
1658
|
+
return this.publicKey;
|
|
1070
1659
|
}
|
|
1071
1660
|
getConnection() {
|
|
1072
1661
|
return this.connection;
|