pump-trader 1.1.2 → 1.1.4
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 +5 -0
- package/dist/index.js +130 -39
- package/docs/plans/2026-04-29-pump-fee-recipient-design.md +34 -0
- package/docs/plans/2026-04-29-pump-fee-recipient.md +143 -0
- package/index.js +183 -39
- package/index.ts +224 -39
- package/package.json +2 -1
- package/tests/instruction-accounts.test.ts +209 -0
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ interface BondingCurveState {
|
|
|
33
33
|
realSolReserves: bigint;
|
|
34
34
|
tokenTotalSupply: bigint;
|
|
35
35
|
complete: boolean;
|
|
36
|
+
isMayhemMode?: boolean;
|
|
37
|
+
isCashbackCoin?: boolean;
|
|
36
38
|
}
|
|
37
39
|
interface BondingInfo {
|
|
38
40
|
bonding: PublicKey;
|
|
@@ -102,6 +104,9 @@ export declare class PumpTrader {
|
|
|
102
104
|
loadGlobal(): Promise<GlobalState>;
|
|
103
105
|
getBondingPda(mint: PublicKey): PublicKey;
|
|
104
106
|
deriveBondingCurveV2(mint: PublicKey): PublicKey;
|
|
107
|
+
private pickFeeRecipient;
|
|
108
|
+
private buildBondingBuyKeys;
|
|
109
|
+
private buildBondingSellKeys;
|
|
105
110
|
loadBonding(mint: PublicKey): Promise<BondingInfo>;
|
|
106
111
|
calcBuy(solIn: bigint, state: BondingCurveState): bigint;
|
|
107
112
|
calcSell(tokenIn: bigint, state: BondingCurveState): bigint;
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,16 @@ const DISCRIMINATORS = {
|
|
|
36
36
|
};
|
|
37
37
|
const AMM_FEE_BPS = 100n;
|
|
38
38
|
const BPS_DENOMINATOR = 10000n;
|
|
39
|
+
const PUMP_NEW_FEE_RECIPIENTS = [
|
|
40
|
+
"5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
|
|
41
|
+
"9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
|
|
42
|
+
"GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
|
|
43
|
+
"3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
|
|
44
|
+
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
45
|
+
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
46
|
+
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
47
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
48
|
+
].map((value) => new web3_js_1.PublicKey(value));
|
|
39
49
|
/* ================= 工具函数 ================= */
|
|
40
50
|
const u64 = (v) => {
|
|
41
51
|
const bn = typeof v === 'bigint' ? new bn_js_1.default(v.toString()) : new bn_js_1.default(v.toString());
|
|
@@ -100,6 +110,8 @@ function parsePoolKeys(data) {
|
|
|
100
110
|
const coinCreator = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
|
|
101
111
|
offset += 32;
|
|
102
112
|
const isMayhemMode = data.readUInt8(offset) === 1;
|
|
113
|
+
offset += 1;
|
|
114
|
+
const isCashbackCoin = offset < data.length ? data.readUInt8(offset) === 1 : false;
|
|
103
115
|
return {
|
|
104
116
|
creator,
|
|
105
117
|
baseMint,
|
|
@@ -108,7 +120,8 @@ function parsePoolKeys(data) {
|
|
|
108
120
|
poolBaseTokenAccount,
|
|
109
121
|
poolQuoteTokenAccount,
|
|
110
122
|
coinCreator,
|
|
111
|
-
isMayhemMode
|
|
123
|
+
isMayhemMode,
|
|
124
|
+
isCashbackCoin
|
|
112
125
|
};
|
|
113
126
|
}
|
|
114
127
|
/* ================= PumpTrader 类 ================= */
|
|
@@ -224,6 +237,56 @@ class PumpTrader {
|
|
|
224
237
|
deriveBondingCurveV2(mint) {
|
|
225
238
|
return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("bonding-curve-v2"), mint.toBuffer()], PROGRAM_IDS.PUMP)[0];
|
|
226
239
|
}
|
|
240
|
+
pickFeeRecipient(index = 0) {
|
|
241
|
+
return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
|
|
242
|
+
}
|
|
243
|
+
buildBondingBuyKeys(args) {
|
|
244
|
+
const tokenProgramId = args.tokenProgramId ?? spl_token_1.TOKEN_PROGRAM_ID;
|
|
245
|
+
return [
|
|
246
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
247
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
248
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
249
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
250
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
251
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
252
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
253
|
+
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
254
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
255
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
256
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
257
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
258
|
+
{ pubkey: args.globalVolumeAccumulator, isSigner: false, isWritable: false },
|
|
259
|
+
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
260
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
261
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
262
|
+
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
263
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
buildBondingSellKeys(args) {
|
|
267
|
+
const tokenProgramId = args.tokenProgramId ?? spl_token_1.TOKEN_PROGRAM_ID;
|
|
268
|
+
const keys = [
|
|
269
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
270
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
271
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
272
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
273
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
274
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
275
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
276
|
+
{ pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
|
|
277
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
278
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
279
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
280
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
281
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
282
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false }
|
|
283
|
+
];
|
|
284
|
+
if (args.isCashbackCoin) {
|
|
285
|
+
keys.push({ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
286
|
+
}
|
|
287
|
+
keys.push({ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false }, { pubkey: args.feeRecipient, isSigner: false, isWritable: true });
|
|
288
|
+
return keys;
|
|
289
|
+
}
|
|
227
290
|
async loadBonding(mint) {
|
|
228
291
|
const bonding = this.getBondingPda(mint);
|
|
229
292
|
const acc = await this.connection.getAccountInfo(bonding);
|
|
@@ -240,6 +303,10 @@ class PumpTrader {
|
|
|
240
303
|
state.complete = data[offset] === 1;
|
|
241
304
|
offset += 1;
|
|
242
305
|
const creator = new web3_js_1.PublicKey(data.slice(offset, offset + 32));
|
|
306
|
+
offset += 32;
|
|
307
|
+
state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
|
|
308
|
+
offset += 1;
|
|
309
|
+
state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
|
|
243
310
|
return { bonding, state, creator };
|
|
244
311
|
}
|
|
245
312
|
/* ---------- 价格计算 ---------- */
|
|
@@ -481,6 +548,7 @@ class PumpTrader {
|
|
|
481
548
|
const [globalVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("global_volume_accumulator")], PROGRAM_IDS.PUMP);
|
|
482
549
|
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()], PROGRAM_IDS.PUMP);
|
|
483
550
|
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
551
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
484
552
|
for (let i = 0; i < solChunks.length; i++) {
|
|
485
553
|
try {
|
|
486
554
|
const solIn = solChunks[i];
|
|
@@ -496,25 +564,25 @@ class PumpTrader {
|
|
|
496
564
|
const userAta = await this.ensureAta(tx, mint, tokenProgram.programId);
|
|
497
565
|
tx.add(new web3_js_1.TransactionInstruction({
|
|
498
566
|
programId: PROGRAM_IDS.PUMP,
|
|
499
|
-
keys:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
567
|
+
keys: this.buildBondingBuyKeys({
|
|
568
|
+
global: this.global,
|
|
569
|
+
globalFeeRecipient: this.globalState.feeRecipient,
|
|
570
|
+
mint,
|
|
571
|
+
bonding,
|
|
572
|
+
associatedBondingCurve,
|
|
573
|
+
userAta,
|
|
574
|
+
wallet: this.wallet.publicKey,
|
|
575
|
+
creatorVault,
|
|
576
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
577
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
578
|
+
globalVolumeAccumulator,
|
|
579
|
+
userVolumeAccumulator,
|
|
580
|
+
feeConfig,
|
|
581
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
582
|
+
bondingCurveV2,
|
|
583
|
+
feeRecipient,
|
|
584
|
+
tokenProgramId: tokenProgram.programId
|
|
585
|
+
}),
|
|
518
586
|
data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
|
|
519
587
|
}));
|
|
520
588
|
const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash('finalized');
|
|
@@ -559,6 +627,8 @@ class PumpTrader {
|
|
|
559
627
|
const userAta = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, this.wallet.publicKey, false, tokenProgram.programId, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
560
628
|
const [creatorVault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("creator-vault"), creator.toBuffer()], PROGRAM_IDS.PUMP);
|
|
561
629
|
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
630
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()], PROGRAM_IDS.PUMP);
|
|
631
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
562
632
|
for (let i = 0; i < tokenChunks.length; i++) {
|
|
563
633
|
try {
|
|
564
634
|
const tokenIn = tokenChunks[i];
|
|
@@ -573,23 +643,25 @@ class PumpTrader {
|
|
|
573
643
|
const tx = new web3_js_1.Transaction().add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priority }));
|
|
574
644
|
tx.add(new web3_js_1.TransactionInstruction({
|
|
575
645
|
programId: PROGRAM_IDS.PUMP,
|
|
576
|
-
keys:
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
646
|
+
keys: this.buildBondingSellKeys({
|
|
647
|
+
global: this.global,
|
|
648
|
+
globalFeeRecipient: this.globalState.feeRecipient,
|
|
649
|
+
mint,
|
|
650
|
+
bonding,
|
|
651
|
+
associatedBondingCurve,
|
|
652
|
+
userAta,
|
|
653
|
+
wallet: this.wallet.publicKey,
|
|
654
|
+
creatorVault,
|
|
655
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
656
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
657
|
+
feeConfig,
|
|
658
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
659
|
+
bondingCurveV2,
|
|
660
|
+
feeRecipient,
|
|
661
|
+
isCashbackCoin: !!state.isCashbackCoin,
|
|
662
|
+
userVolumeAccumulator,
|
|
663
|
+
tokenProgramId: tokenProgram.programId
|
|
664
|
+
}),
|
|
593
665
|
data: Buffer.concat([
|
|
594
666
|
DISCRIMINATORS.SELL,
|
|
595
667
|
u64(tokenIn),
|
|
@@ -778,6 +850,15 @@ class PumpTrader {
|
|
|
778
850
|
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
779
851
|
const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
|
|
780
852
|
const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
853
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
854
|
+
const newFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(poolKeys.quoteMint, newFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
855
|
+
const remainingKeys = [];
|
|
856
|
+
if (poolKeys.isCashbackCoin) {
|
|
857
|
+
const userVolumeAccumulatorWsolAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, userVolumeAccumulator, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
858
|
+
remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true });
|
|
859
|
+
}
|
|
860
|
+
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
861
|
+
remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true });
|
|
781
862
|
return new web3_js_1.TransactionInstruction({
|
|
782
863
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
783
864
|
keys: [
|
|
@@ -804,7 +885,7 @@ class PumpTrader {
|
|
|
804
885
|
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
805
886
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
806
887
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
807
|
-
|
|
888
|
+
...remainingKeys
|
|
808
889
|
],
|
|
809
890
|
data: Buffer.concat([
|
|
810
891
|
DISCRIMINATORS.BUY,
|
|
@@ -823,6 +904,16 @@ class PumpTrader {
|
|
|
823
904
|
const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG], PROGRAM_IDS.FEE);
|
|
824
905
|
const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
|
|
825
906
|
const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
907
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
908
|
+
const newFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(poolKeys.quoteMint, newFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
909
|
+
const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()], PROGRAM_IDS.PUMP_AMM);
|
|
910
|
+
const userVolumeAccumulatorWsolAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, userVolumeAccumulator, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
911
|
+
const remainingKeys = [];
|
|
912
|
+
if (poolKeys.isCashbackCoin) {
|
|
913
|
+
remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true }, { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
914
|
+
}
|
|
915
|
+
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
916
|
+
remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true });
|
|
826
917
|
return new web3_js_1.TransactionInstruction({
|
|
827
918
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
828
919
|
keys: [
|
|
@@ -847,7 +938,7 @@ class PumpTrader {
|
|
|
847
938
|
{ pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
|
|
848
939
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
849
940
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
850
|
-
|
|
941
|
+
...remainingKeys
|
|
851
942
|
],
|
|
852
943
|
data: Buffer.concat([
|
|
853
944
|
DISCRIMINATORS.SELL,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Pump Fee Recipient Upgrade Design
|
|
2
|
+
|
|
3
|
+
**Context**
|
|
4
|
+
|
|
5
|
+
`pump-trader@1.1.3` currently hardcodes Pump bonding and AMM instruction account layouts that matched the pre-upgrade programs. Pump announced a breaking account layout change effective on April 28, 2026 at 16:00 UTC. After that upgrade, the existing instruction builders send the wrong account count and wrong trailing account order.
|
|
6
|
+
|
|
7
|
+
**Design**
|
|
8
|
+
|
|
9
|
+
Use a compatibility patch inside the existing handwritten instruction builders instead of migrating the project to the official SDKs. The patch adds the 8 published fee recipient addresses as constants, centralizes fee recipient selection behind a small helper, and updates the trailing account order for both bonding and AMM flows.
|
|
10
|
+
|
|
11
|
+
For bonding:
|
|
12
|
+
- append one mutable fee recipient after `bonding-curve-v2`
|
|
13
|
+
- keep all existing accounts before `bonding-curve-v2` unchanged
|
|
14
|
+
|
|
15
|
+
For AMM:
|
|
16
|
+
- keep existing accounts through `pool-v2`
|
|
17
|
+
- move the fee recipient pair to the end
|
|
18
|
+
- pass fee recipient as readonly
|
|
19
|
+
- pass quote mint ATA for that recipient as mutable
|
|
20
|
+
|
|
21
|
+
**Why This Approach**
|
|
22
|
+
|
|
23
|
+
This is the smallest change that restores runtime compatibility while preserving the library's current API and handwritten transaction flow. It also gives one future extension point for recipient selection without forcing an SDK dependency or wider refactor.
|
|
24
|
+
|
|
25
|
+
**Validation**
|
|
26
|
+
|
|
27
|
+
Add regression tests that assert instruction key count and tail ordering for:
|
|
28
|
+
- bonding buy
|
|
29
|
+
- bonding sell without cashback
|
|
30
|
+
- bonding sell with cashback
|
|
31
|
+
- AMM buy without cashback
|
|
32
|
+
- AMM buy with cashback
|
|
33
|
+
- AMM sell without cashback
|
|
34
|
+
- AMM sell with cashback
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Pump Fee Recipient Upgrade Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Update `pump-trader@1.1.3` so Pump bonding and AMM instructions remain compatible with the April 28, 2026 fee recipient account layout upgrade.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep the existing handwritten instruction builders, add a centralized fee recipient selector, and rebuild the bonding and AMM account arrays so their tail order matches the upgraded programs. Use narrow regression tests that assert exact key ordering and account counts rather than broad network integration tests.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js built-in test runner, `tsx`, `@solana/web3.js`, `@solana/spl-token`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Add regression tests for upgraded account layouts
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `package.json`
|
|
17
|
+
- Create: `tests/instruction-accounts.test.ts`
|
|
18
|
+
|
|
19
|
+
**Step 1: Write the failing test**
|
|
20
|
+
|
|
21
|
+
Create tests that expect:
|
|
22
|
+
- bonding buy has 18 keys and the final two are `bondingCurveV2`, fee recipient
|
|
23
|
+
- bonding sell has 16 keys without cashback and 17 with cashback, with fee recipient last
|
|
24
|
+
- AMM buy has 26 keys without cashback and 27 with cashback, with `poolV2`, fee recipient, fee recipient quote ATA at the tail
|
|
25
|
+
- AMM sell has 24 keys without cashback and 26 with cashback, with the same tail order
|
|
26
|
+
|
|
27
|
+
**Step 2: Run test to verify it fails**
|
|
28
|
+
|
|
29
|
+
Run: `npm test`
|
|
30
|
+
Expected: FAIL because the new helper methods or expected key layout do not yet exist.
|
|
31
|
+
|
|
32
|
+
**Step 3: Write minimal implementation**
|
|
33
|
+
|
|
34
|
+
Add helper methods in `index.ts` that build the account arrays and are reused by the existing transaction builders.
|
|
35
|
+
|
|
36
|
+
**Step 4: Run test to verify it passes**
|
|
37
|
+
|
|
38
|
+
Run: `npm test`
|
|
39
|
+
Expected: PASS
|
|
40
|
+
|
|
41
|
+
**Step 5: Commit**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git add package.json tests/instruction-accounts.test.ts index.ts index.js dist docs/plans
|
|
45
|
+
git commit -m "fix: support upgraded pump fee recipient accounts"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Task 2: Update bonding instruction account assembly
|
|
49
|
+
|
|
50
|
+
**Files:**
|
|
51
|
+
- Modify: `index.ts`
|
|
52
|
+
- Modify: `index.js`
|
|
53
|
+
- Modify: `dist/index.js`
|
|
54
|
+
- Modify: `dist/index.d.ts`
|
|
55
|
+
|
|
56
|
+
**Step 1: Write the failing test**
|
|
57
|
+
|
|
58
|
+
Use the bonding tests from Task 1 as the failing coverage.
|
|
59
|
+
|
|
60
|
+
**Step 2: Run test to verify it fails**
|
|
61
|
+
|
|
62
|
+
Run: `npm test -- --test-name-pattern bonding`
|
|
63
|
+
Expected: FAIL because bonding key count and fee recipient tail order are still old.
|
|
64
|
+
|
|
65
|
+
**Step 3: Write minimal implementation**
|
|
66
|
+
|
|
67
|
+
Add the 8 fee recipient constants, selector helper, bonding key builders, and switch `buy()` / `sell()` to use them.
|
|
68
|
+
|
|
69
|
+
**Step 4: Run test to verify it passes**
|
|
70
|
+
|
|
71
|
+
Run: `npm test -- --test-name-pattern bonding`
|
|
72
|
+
Expected: PASS
|
|
73
|
+
|
|
74
|
+
**Step 5: Commit**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git add index.ts index.js dist
|
|
78
|
+
git commit -m "fix: update bonding fee recipient accounts"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Task 3: Update AMM instruction account assembly
|
|
82
|
+
|
|
83
|
+
**Files:**
|
|
84
|
+
- Modify: `index.ts`
|
|
85
|
+
- Modify: `index.js`
|
|
86
|
+
- Modify: `dist/index.js`
|
|
87
|
+
- Modify: `dist/index.d.ts`
|
|
88
|
+
|
|
89
|
+
**Step 1: Write the failing test**
|
|
90
|
+
|
|
91
|
+
Use the AMM tests from Task 1 as the failing coverage.
|
|
92
|
+
|
|
93
|
+
**Step 2: Run test to verify it fails**
|
|
94
|
+
|
|
95
|
+
Run: `npm test -- --test-name-pattern amm`
|
|
96
|
+
Expected: FAIL because the fee recipient pair is still placed before `pool-v2`.
|
|
97
|
+
|
|
98
|
+
**Step 3: Write minimal implementation**
|
|
99
|
+
|
|
100
|
+
Make AMM key assembly place `pool-v2` before the new trailing fee recipient pair for both buy and sell, preserving cashback-only extras.
|
|
101
|
+
|
|
102
|
+
**Step 4: Run test to verify it passes**
|
|
103
|
+
|
|
104
|
+
Run: `npm test -- --test-name-pattern amm`
|
|
105
|
+
Expected: PASS
|
|
106
|
+
|
|
107
|
+
**Step 5: Commit**
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
git add index.ts index.js dist
|
|
111
|
+
git commit -m "fix: update amm fee recipient accounts"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Task 4: Build and verify distributable output
|
|
115
|
+
|
|
116
|
+
**Files:**
|
|
117
|
+
- Modify: `dist/index.js`
|
|
118
|
+
- Modify: `dist/index.d.ts`
|
|
119
|
+
|
|
120
|
+
**Step 1: Write the failing test**
|
|
121
|
+
|
|
122
|
+
Use the build command as the verification target for generated output drift.
|
|
123
|
+
|
|
124
|
+
**Step 2: Run test to verify it fails**
|
|
125
|
+
|
|
126
|
+
Run: `npm run build`
|
|
127
|
+
Expected: PASS after source changes, with generated `dist` updated.
|
|
128
|
+
|
|
129
|
+
**Step 3: Write minimal implementation**
|
|
130
|
+
|
|
131
|
+
No extra implementation beyond generating `dist`.
|
|
132
|
+
|
|
133
|
+
**Step 4: Run test to verify it passes**
|
|
134
|
+
|
|
135
|
+
Run: `npm test && npm run build`
|
|
136
|
+
Expected: PASS
|
|
137
|
+
|
|
138
|
+
**Step 5: Commit**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git add dist
|
|
142
|
+
git commit -m "build: refresh distribution output"
|
|
143
|
+
```
|
package/index.js
CHANGED
|
@@ -56,6 +56,16 @@ const DISCRIMINATORS = {
|
|
|
56
56
|
|
|
57
57
|
const AMM_FEE_BPS = 100n; // 1%
|
|
58
58
|
const BPS_DENOMINATOR = 10000n;
|
|
59
|
+
const PUMP_NEW_FEE_RECIPIENTS = [
|
|
60
|
+
"5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
|
|
61
|
+
"9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
|
|
62
|
+
"GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
|
|
63
|
+
"3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
|
|
64
|
+
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
65
|
+
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
66
|
+
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
67
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
68
|
+
].map((value) => new PublicKey(value));
|
|
59
69
|
|
|
60
70
|
/* ================= 工具函数 ================= */
|
|
61
71
|
|
|
@@ -141,6 +151,8 @@ function parsePoolKeys(data) {
|
|
|
141
151
|
offset += 32;
|
|
142
152
|
|
|
143
153
|
const isMayhemMode = data.readUInt8(offset) === 1;
|
|
154
|
+
offset += 1;
|
|
155
|
+
const isCashbackCoin = offset < data.length ? data.readUInt8(offset) === 1 : false;
|
|
144
156
|
|
|
145
157
|
return {
|
|
146
158
|
creator,
|
|
@@ -150,7 +162,8 @@ function parsePoolKeys(data) {
|
|
|
150
162
|
poolBaseTokenAccount,
|
|
151
163
|
poolQuoteTokenAccount,
|
|
152
164
|
coinCreator,
|
|
153
|
-
isMayhemMode
|
|
165
|
+
isMayhemMode,
|
|
166
|
+
isCashbackCoin
|
|
154
167
|
};
|
|
155
168
|
}
|
|
156
169
|
|
|
@@ -291,6 +304,66 @@ export class PumpTrader {
|
|
|
291
304
|
)[0];
|
|
292
305
|
}
|
|
293
306
|
|
|
307
|
+
pickFeeRecipient(index = 0) {
|
|
308
|
+
return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
buildBondingBuyKeys(args) {
|
|
312
|
+
const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
|
|
313
|
+
|
|
314
|
+
return [
|
|
315
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
316
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
317
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
318
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
319
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
320
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
321
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
322
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
323
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
324
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
325
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
326
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
327
|
+
{ pubkey: args.globalVolumeAccumulator, isSigner: false, isWritable: false },
|
|
328
|
+
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
329
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
330
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
331
|
+
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
332
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
333
|
+
];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
buildBondingSellKeys(args) {
|
|
337
|
+
const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
|
|
338
|
+
const keys = [
|
|
339
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
340
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
341
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
342
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
343
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
344
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
345
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
346
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
347
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
348
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
349
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
350
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
351
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
352
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false }
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
if (args.isCashbackCoin) {
|
|
356
|
+
keys.push({ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
keys.push(
|
|
360
|
+
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
361
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
return keys;
|
|
365
|
+
}
|
|
366
|
+
|
|
294
367
|
async loadBonding(mint) {
|
|
295
368
|
const bonding = this.getBondingPda(mint);
|
|
296
369
|
const acc = await this.connection.getAccountInfo(bonding);
|
|
@@ -309,6 +382,10 @@ export class PumpTrader {
|
|
|
309
382
|
offset += 1;
|
|
310
383
|
|
|
311
384
|
const creator = new PublicKey(data.slice(offset, offset + 32));
|
|
385
|
+
offset += 32;
|
|
386
|
+
state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
|
|
387
|
+
offset += 1;
|
|
388
|
+
state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
|
|
312
389
|
|
|
313
390
|
return { bonding, state, creator };
|
|
314
391
|
}
|
|
@@ -660,6 +737,7 @@ export class PumpTrader {
|
|
|
660
737
|
[Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
|
|
661
738
|
PROGRAM_IDS.FEE
|
|
662
739
|
);
|
|
740
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
663
741
|
|
|
664
742
|
for (let i = 0; i < solChunks.length; i++) {
|
|
665
743
|
try {
|
|
@@ -683,25 +761,25 @@ export class PumpTrader {
|
|
|
683
761
|
tx.add(
|
|
684
762
|
new TransactionInstruction({
|
|
685
763
|
programId: PROGRAM_IDS.PUMP,
|
|
686
|
-
keys:
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
764
|
+
keys: this.buildBondingBuyKeys({
|
|
765
|
+
global: this.global,
|
|
766
|
+
globalFeeRecipient: this.globalState.feeRecipient,
|
|
767
|
+
mint,
|
|
768
|
+
bonding,
|
|
769
|
+
associatedBondingCurve,
|
|
770
|
+
userAta,
|
|
771
|
+
wallet: this.wallet.publicKey,
|
|
772
|
+
creatorVault,
|
|
773
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
774
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
775
|
+
globalVolumeAccumulator,
|
|
776
|
+
userVolumeAccumulator,
|
|
777
|
+
feeConfig,
|
|
778
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
779
|
+
bondingCurveV2,
|
|
780
|
+
feeRecipient,
|
|
781
|
+
tokenProgramId: tokenProgram.programId
|
|
782
|
+
}),
|
|
705
783
|
data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
|
|
706
784
|
})
|
|
707
785
|
);
|
|
@@ -779,6 +857,11 @@ export class PumpTrader {
|
|
|
779
857
|
[Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
|
|
780
858
|
PROGRAM_IDS.FEE
|
|
781
859
|
);
|
|
860
|
+
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
861
|
+
[Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
|
|
862
|
+
PROGRAM_IDS.PUMP
|
|
863
|
+
);
|
|
864
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
782
865
|
|
|
783
866
|
for (let i = 0; i < tokenChunks.length; i++) {
|
|
784
867
|
try {
|
|
@@ -800,23 +883,25 @@ export class PumpTrader {
|
|
|
800
883
|
tx.add(
|
|
801
884
|
new TransactionInstruction({
|
|
802
885
|
programId: PROGRAM_IDS.PUMP,
|
|
803
|
-
keys:
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
886
|
+
keys: this.buildBondingSellKeys({
|
|
887
|
+
global: this.global,
|
|
888
|
+
globalFeeRecipient: this.globalState.feeRecipient,
|
|
889
|
+
mint,
|
|
890
|
+
bonding,
|
|
891
|
+
associatedBondingCurve,
|
|
892
|
+
userAta,
|
|
893
|
+
wallet: this.wallet.publicKey,
|
|
894
|
+
creatorVault,
|
|
895
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
896
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
897
|
+
feeConfig,
|
|
898
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
899
|
+
bondingCurveV2,
|
|
900
|
+
feeRecipient,
|
|
901
|
+
isCashbackCoin: !!state.isCashbackCoin,
|
|
902
|
+
userVolumeAccumulator,
|
|
903
|
+
tokenProgramId: tokenProgram.programId
|
|
904
|
+
}),
|
|
820
905
|
data: Buffer.concat([
|
|
821
906
|
DISCRIMINATORS.SELL,
|
|
822
907
|
u64(tokenIn),
|
|
@@ -1135,6 +1220,31 @@ export class PumpTrader {
|
|
|
1135
1220
|
TOKEN_PROGRAM_ID,
|
|
1136
1221
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1137
1222
|
);
|
|
1223
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
1224
|
+
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
1225
|
+
poolKeys.quoteMint,
|
|
1226
|
+
newFeeRecipient,
|
|
1227
|
+
true,
|
|
1228
|
+
TOKEN_PROGRAM_ID,
|
|
1229
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1230
|
+
);
|
|
1231
|
+
|
|
1232
|
+
const remainingKeys = [];
|
|
1233
|
+
if (poolKeys.isCashbackCoin) {
|
|
1234
|
+
const userVolumeAccumulatorWsolAta = getAssociatedTokenAddressSync(
|
|
1235
|
+
SOL_MINT,
|
|
1236
|
+
userVolumeAccumulator,
|
|
1237
|
+
true,
|
|
1238
|
+
TOKEN_PROGRAM_ID,
|
|
1239
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1240
|
+
);
|
|
1241
|
+
remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true });
|
|
1242
|
+
}
|
|
1243
|
+
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1244
|
+
remainingKeys.push(
|
|
1245
|
+
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1246
|
+
{ pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
|
|
1247
|
+
);
|
|
1138
1248
|
|
|
1139
1249
|
return new TransactionInstruction({
|
|
1140
1250
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
@@ -1162,7 +1272,7 @@ export class PumpTrader {
|
|
|
1162
1272
|
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
1163
1273
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
1164
1274
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
1165
|
-
|
|
1275
|
+
...remainingKeys
|
|
1166
1276
|
],
|
|
1167
1277
|
data: Buffer.concat([
|
|
1168
1278
|
DISCRIMINATORS.BUY,
|
|
@@ -1208,6 +1318,40 @@ export class PumpTrader {
|
|
|
1208
1318
|
TOKEN_PROGRAM_ID,
|
|
1209
1319
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1210
1320
|
);
|
|
1321
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
1322
|
+
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
1323
|
+
poolKeys.quoteMint,
|
|
1324
|
+
newFeeRecipient,
|
|
1325
|
+
true,
|
|
1326
|
+
TOKEN_PROGRAM_ID,
|
|
1327
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1328
|
+
);
|
|
1329
|
+
|
|
1330
|
+
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
1331
|
+
[Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
|
|
1332
|
+
PROGRAM_IDS.PUMP_AMM
|
|
1333
|
+
);
|
|
1334
|
+
|
|
1335
|
+
const userVolumeAccumulatorWsolAta = getAssociatedTokenAddressSync(
|
|
1336
|
+
SOL_MINT,
|
|
1337
|
+
userVolumeAccumulator,
|
|
1338
|
+
true,
|
|
1339
|
+
TOKEN_PROGRAM_ID,
|
|
1340
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1341
|
+
);
|
|
1342
|
+
|
|
1343
|
+
const remainingKeys = [];
|
|
1344
|
+
if (poolKeys.isCashbackCoin) {
|
|
1345
|
+
remainingKeys.push(
|
|
1346
|
+
{ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true },
|
|
1347
|
+
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true }
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1351
|
+
remainingKeys.push(
|
|
1352
|
+
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1353
|
+
{ pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
|
|
1354
|
+
);
|
|
1211
1355
|
|
|
1212
1356
|
return new TransactionInstruction({
|
|
1213
1357
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
@@ -1233,7 +1377,7 @@ export class PumpTrader {
|
|
|
1233
1377
|
{ pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
|
|
1234
1378
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
1235
1379
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
1236
|
-
|
|
1380
|
+
...remainingKeys
|
|
1237
1381
|
],
|
|
1238
1382
|
data: Buffer.concat([
|
|
1239
1383
|
DISCRIMINATORS.SELL,
|
package/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
PublicKey,
|
|
4
4
|
Transaction,
|
|
5
5
|
TransactionInstruction,
|
|
6
|
+
AccountMeta,
|
|
6
7
|
SystemProgram,
|
|
7
8
|
ComputeBudgetProgram,
|
|
8
9
|
Keypair
|
|
@@ -63,6 +64,8 @@ interface BondingCurveState {
|
|
|
63
64
|
realSolReserves: bigint;
|
|
64
65
|
tokenTotalSupply: bigint;
|
|
65
66
|
complete: boolean;
|
|
67
|
+
isMayhemMode?: boolean;
|
|
68
|
+
isCashbackCoin?: boolean;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
interface BondingInfo {
|
|
@@ -151,6 +154,16 @@ const DISCRIMINATORS = {
|
|
|
151
154
|
|
|
152
155
|
const AMM_FEE_BPS = 100n;
|
|
153
156
|
const BPS_DENOMINATOR = 10000n;
|
|
157
|
+
const PUMP_NEW_FEE_RECIPIENTS = [
|
|
158
|
+
"5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
|
|
159
|
+
"9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
|
|
160
|
+
"GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
|
|
161
|
+
"3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
|
|
162
|
+
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
163
|
+
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
164
|
+
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
165
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
166
|
+
].map((value) => new PublicKey(value));
|
|
154
167
|
|
|
155
168
|
/* ================= 工具函数 ================= */
|
|
156
169
|
|
|
@@ -239,6 +252,8 @@ function parsePoolKeys(data: Buffer) {
|
|
|
239
252
|
offset += 32;
|
|
240
253
|
|
|
241
254
|
const isMayhemMode = data.readUInt8(offset) === 1;
|
|
255
|
+
offset += 1;
|
|
256
|
+
const isCashbackCoin = offset < data.length ? data.readUInt8(offset) === 1 : false;
|
|
242
257
|
|
|
243
258
|
return {
|
|
244
259
|
creator,
|
|
@@ -248,7 +263,8 @@ function parsePoolKeys(data: Buffer) {
|
|
|
248
263
|
poolBaseTokenAccount,
|
|
249
264
|
poolQuoteTokenAccount,
|
|
250
265
|
coinCreator,
|
|
251
|
-
isMayhemMode
|
|
266
|
+
isMayhemMode,
|
|
267
|
+
isCashbackCoin
|
|
252
268
|
};
|
|
253
269
|
}
|
|
254
270
|
|
|
@@ -389,6 +405,102 @@ export class PumpTrader {
|
|
|
389
405
|
)[0];
|
|
390
406
|
}
|
|
391
407
|
|
|
408
|
+
private pickFeeRecipient(index = 0): PublicKey {
|
|
409
|
+
return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private buildBondingBuyKeys(args: {
|
|
413
|
+
global: PublicKey;
|
|
414
|
+
globalFeeRecipient: PublicKey;
|
|
415
|
+
mint: PublicKey;
|
|
416
|
+
bonding: PublicKey;
|
|
417
|
+
associatedBondingCurve: PublicKey;
|
|
418
|
+
userAta: PublicKey;
|
|
419
|
+
wallet: PublicKey;
|
|
420
|
+
creatorVault: PublicKey;
|
|
421
|
+
eventAuthority: PublicKey;
|
|
422
|
+
pumpProgram: PublicKey;
|
|
423
|
+
globalVolumeAccumulator: PublicKey;
|
|
424
|
+
userVolumeAccumulator: PublicKey;
|
|
425
|
+
feeConfig: PublicKey;
|
|
426
|
+
feeProgram: PublicKey;
|
|
427
|
+
bondingCurveV2: PublicKey;
|
|
428
|
+
feeRecipient: PublicKey;
|
|
429
|
+
tokenProgramId?: PublicKey;
|
|
430
|
+
}): AccountMeta[] {
|
|
431
|
+
const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
|
|
432
|
+
|
|
433
|
+
return [
|
|
434
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
435
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
436
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
437
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
438
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
439
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
440
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
441
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
442
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
443
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
444
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
445
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
446
|
+
{ pubkey: args.globalVolumeAccumulator, isSigner: false, isWritable: false },
|
|
447
|
+
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
448
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
449
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
450
|
+
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
451
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
452
|
+
];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private buildBondingSellKeys(args: {
|
|
456
|
+
global: PublicKey;
|
|
457
|
+
globalFeeRecipient: PublicKey;
|
|
458
|
+
mint: PublicKey;
|
|
459
|
+
bonding: PublicKey;
|
|
460
|
+
associatedBondingCurve: PublicKey;
|
|
461
|
+
userAta: PublicKey;
|
|
462
|
+
wallet: PublicKey;
|
|
463
|
+
creatorVault: PublicKey;
|
|
464
|
+
eventAuthority: PublicKey;
|
|
465
|
+
pumpProgram: PublicKey;
|
|
466
|
+
feeConfig: PublicKey;
|
|
467
|
+
feeProgram: PublicKey;
|
|
468
|
+
bondingCurveV2: PublicKey;
|
|
469
|
+
feeRecipient: PublicKey;
|
|
470
|
+
isCashbackCoin: boolean;
|
|
471
|
+
userVolumeAccumulator: PublicKey;
|
|
472
|
+
tokenProgramId?: PublicKey;
|
|
473
|
+
}): AccountMeta[] {
|
|
474
|
+
const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
|
|
475
|
+
const keys: AccountMeta[] = [
|
|
476
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
477
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
478
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
479
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
480
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
481
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
482
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
483
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
484
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
485
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
486
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
487
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
488
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
489
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false }
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
if (args.isCashbackCoin) {
|
|
493
|
+
keys.push({ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
keys.push(
|
|
497
|
+
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
498
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
return keys;
|
|
502
|
+
}
|
|
503
|
+
|
|
392
504
|
async loadBonding(mint: PublicKey): Promise<BondingInfo> {
|
|
393
505
|
const bonding = this.getBondingPda(mint);
|
|
394
506
|
const acc = await this.connection.getAccountInfo(bonding);
|
|
@@ -407,6 +519,11 @@ export class PumpTrader {
|
|
|
407
519
|
offset += 1;
|
|
408
520
|
|
|
409
521
|
const creator = new PublicKey(data.slice(offset, offset + 32));
|
|
522
|
+
offset += 32;
|
|
523
|
+
|
|
524
|
+
state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
|
|
525
|
+
offset += 1;
|
|
526
|
+
state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
|
|
410
527
|
|
|
411
528
|
return { bonding, state, creator };
|
|
412
529
|
}
|
|
@@ -767,6 +884,7 @@ export class PumpTrader {
|
|
|
767
884
|
[Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
|
|
768
885
|
PROGRAM_IDS.FEE
|
|
769
886
|
);
|
|
887
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
770
888
|
|
|
771
889
|
for (let i = 0; i < solChunks.length; i++) {
|
|
772
890
|
try {
|
|
@@ -790,25 +908,25 @@ export class PumpTrader {
|
|
|
790
908
|
tx.add(
|
|
791
909
|
new TransactionInstruction({
|
|
792
910
|
programId: PROGRAM_IDS.PUMP,
|
|
793
|
-
keys:
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
911
|
+
keys: this.buildBondingBuyKeys({
|
|
912
|
+
global: this.global,
|
|
913
|
+
globalFeeRecipient: this.globalState!.feeRecipient,
|
|
914
|
+
mint,
|
|
915
|
+
bonding,
|
|
916
|
+
associatedBondingCurve,
|
|
917
|
+
userAta,
|
|
918
|
+
wallet: this.wallet.publicKey,
|
|
919
|
+
creatorVault,
|
|
920
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
921
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
922
|
+
globalVolumeAccumulator,
|
|
923
|
+
userVolumeAccumulator,
|
|
924
|
+
feeConfig,
|
|
925
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
926
|
+
bondingCurveV2,
|
|
927
|
+
feeRecipient,
|
|
928
|
+
tokenProgramId: tokenProgram.programId
|
|
929
|
+
}),
|
|
812
930
|
data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
|
|
813
931
|
})
|
|
814
932
|
);
|
|
@@ -887,6 +1005,12 @@ export class PumpTrader {
|
|
|
887
1005
|
PROGRAM_IDS.FEE
|
|
888
1006
|
);
|
|
889
1007
|
|
|
1008
|
+
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
1009
|
+
[Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
|
|
1010
|
+
PROGRAM_IDS.PUMP
|
|
1011
|
+
);
|
|
1012
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
1013
|
+
|
|
890
1014
|
for (let i = 0; i < tokenChunks.length; i++) {
|
|
891
1015
|
try {
|
|
892
1016
|
const tokenIn = tokenChunks[i];
|
|
@@ -907,23 +1031,25 @@ export class PumpTrader {
|
|
|
907
1031
|
tx.add(
|
|
908
1032
|
new TransactionInstruction({
|
|
909
1033
|
programId: PROGRAM_IDS.PUMP,
|
|
910
|
-
keys:
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1034
|
+
keys: this.buildBondingSellKeys({
|
|
1035
|
+
global: this.global,
|
|
1036
|
+
globalFeeRecipient: this.globalState!.feeRecipient,
|
|
1037
|
+
mint,
|
|
1038
|
+
bonding,
|
|
1039
|
+
associatedBondingCurve,
|
|
1040
|
+
userAta,
|
|
1041
|
+
wallet: this.wallet.publicKey,
|
|
1042
|
+
creatorVault,
|
|
1043
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
1044
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
1045
|
+
feeConfig,
|
|
1046
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
1047
|
+
bondingCurveV2,
|
|
1048
|
+
feeRecipient,
|
|
1049
|
+
isCashbackCoin: !!state.isCashbackCoin,
|
|
1050
|
+
userVolumeAccumulator,
|
|
1051
|
+
tokenProgramId: tokenProgram.programId
|
|
1052
|
+
}),
|
|
927
1053
|
data: Buffer.concat([
|
|
928
1054
|
DISCRIMINATORS.SELL,
|
|
929
1055
|
u64(tokenIn),
|
|
@@ -1248,6 +1374,31 @@ export class PumpTrader {
|
|
|
1248
1374
|
TOKEN_PROGRAM_ID,
|
|
1249
1375
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1250
1376
|
);
|
|
1377
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
1378
|
+
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
1379
|
+
poolKeys.quoteMint,
|
|
1380
|
+
newFeeRecipient,
|
|
1381
|
+
true,
|
|
1382
|
+
TOKEN_PROGRAM_ID,
|
|
1383
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1384
|
+
);
|
|
1385
|
+
|
|
1386
|
+
const remainingKeys = [];
|
|
1387
|
+
if (poolKeys.isCashbackCoin) {
|
|
1388
|
+
const userVolumeAccumulatorWsolAta = getAssociatedTokenAddressSync(
|
|
1389
|
+
SOL_MINT,
|
|
1390
|
+
userVolumeAccumulator,
|
|
1391
|
+
true,
|
|
1392
|
+
TOKEN_PROGRAM_ID,
|
|
1393
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1394
|
+
);
|
|
1395
|
+
remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true });
|
|
1396
|
+
}
|
|
1397
|
+
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1398
|
+
remainingKeys.push(
|
|
1399
|
+
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1400
|
+
{ pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
|
|
1401
|
+
);
|
|
1251
1402
|
|
|
1252
1403
|
return new TransactionInstruction({
|
|
1253
1404
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
@@ -1275,7 +1426,7 @@ export class PumpTrader {
|
|
|
1275
1426
|
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
1276
1427
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
1277
1428
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
1278
|
-
|
|
1429
|
+
...remainingKeys
|
|
1279
1430
|
],
|
|
1280
1431
|
data: Buffer.concat([
|
|
1281
1432
|
DISCRIMINATORS.BUY,
|
|
@@ -1328,6 +1479,40 @@ export class PumpTrader {
|
|
|
1328
1479
|
TOKEN_PROGRAM_ID,
|
|
1329
1480
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1330
1481
|
);
|
|
1482
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
1483
|
+
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
1484
|
+
poolKeys.quoteMint,
|
|
1485
|
+
newFeeRecipient,
|
|
1486
|
+
true,
|
|
1487
|
+
TOKEN_PROGRAM_ID,
|
|
1488
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1489
|
+
);
|
|
1490
|
+
|
|
1491
|
+
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
1492
|
+
[Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
|
|
1493
|
+
PROGRAM_IDS.PUMP_AMM
|
|
1494
|
+
);
|
|
1495
|
+
|
|
1496
|
+
const userVolumeAccumulatorWsolAta = getAssociatedTokenAddressSync(
|
|
1497
|
+
SOL_MINT,
|
|
1498
|
+
userVolumeAccumulator,
|
|
1499
|
+
true,
|
|
1500
|
+
TOKEN_PROGRAM_ID,
|
|
1501
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1502
|
+
);
|
|
1503
|
+
|
|
1504
|
+
const remainingKeys = [];
|
|
1505
|
+
if (poolKeys.isCashbackCoin) {
|
|
1506
|
+
remainingKeys.push(
|
|
1507
|
+
{ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true },
|
|
1508
|
+
{ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true }
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1512
|
+
remainingKeys.push(
|
|
1513
|
+
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1514
|
+
{ pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
|
|
1515
|
+
);
|
|
1331
1516
|
|
|
1332
1517
|
return new TransactionInstruction({
|
|
1333
1518
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
@@ -1353,7 +1538,7 @@ export class PumpTrader {
|
|
|
1353
1538
|
{ pubkey: coinCreatorVaultAuthority, isSigner: false, isWritable: false },
|
|
1354
1539
|
{ pubkey: feeConfig, isSigner: false, isWritable: false },
|
|
1355
1540
|
{ pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
|
|
1356
|
-
|
|
1541
|
+
...remainingKeys
|
|
1357
1542
|
],
|
|
1358
1543
|
data: Buffer.concat([
|
|
1359
1544
|
DISCRIMINATORS.SELL,
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pump-trader",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "PumpFun 交易库 - 自动判断 Token Program 和内盘/外盘",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
|
+
"test": "node --test --import tsx tests/**/*.test.ts",
|
|
9
10
|
"format": "prettier --write ."
|
|
10
11
|
},
|
|
11
12
|
"keywords": [
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import bs58 from "bs58";
|
|
5
|
+
import { Keypair, PublicKey } from "@solana/web3.js";
|
|
6
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
7
|
+
|
|
8
|
+
import { PumpTrader } from "../index";
|
|
9
|
+
|
|
10
|
+
const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
|
|
11
|
+
const FEE_RECIPIENTS = [
|
|
12
|
+
"5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
|
|
13
|
+
"9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
|
|
14
|
+
"GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
|
|
15
|
+
"3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
|
|
16
|
+
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
17
|
+
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
18
|
+
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
19
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
20
|
+
].map((value) => new PublicKey(value));
|
|
21
|
+
|
|
22
|
+
function createTrader() {
|
|
23
|
+
return new PumpTrader("http://127.0.0.1:8899", bs58.encode(Keypair.generate().secretKey));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createPoolInfo(isCashbackCoin: boolean) {
|
|
27
|
+
return {
|
|
28
|
+
pool: Keypair.generate().publicKey,
|
|
29
|
+
poolAuthority: Keypair.generate().publicKey,
|
|
30
|
+
poolKeys: {
|
|
31
|
+
baseMint: Keypair.generate().publicKey,
|
|
32
|
+
quoteMint: SOL_MINT,
|
|
33
|
+
poolBaseTokenAccount: Keypair.generate().publicKey,
|
|
34
|
+
poolQuoteTokenAccount: Keypair.generate().publicKey,
|
|
35
|
+
coinCreator: Keypair.generate().publicKey,
|
|
36
|
+
isCashbackCoin
|
|
37
|
+
},
|
|
38
|
+
globalConfig: {
|
|
39
|
+
address: Keypair.generate().publicKey,
|
|
40
|
+
protocolFeeRecipients: FEE_RECIPIENTS
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test("bonding buy appends mutable fee recipient after bondingCurveV2", () => {
|
|
46
|
+
const trader = createTrader() as any;
|
|
47
|
+
const bondingCurveV2 = Keypair.generate().publicKey;
|
|
48
|
+
const feeRecipient = FEE_RECIPIENTS[0];
|
|
49
|
+
|
|
50
|
+
const keys = trader.buildBondingBuyKeys({
|
|
51
|
+
global: Keypair.generate().publicKey,
|
|
52
|
+
globalFeeRecipient: Keypair.generate().publicKey,
|
|
53
|
+
mint: Keypair.generate().publicKey,
|
|
54
|
+
bonding: Keypair.generate().publicKey,
|
|
55
|
+
associatedBondingCurve: Keypair.generate().publicKey,
|
|
56
|
+
userAta: Keypair.generate().publicKey,
|
|
57
|
+
wallet: Keypair.generate().publicKey,
|
|
58
|
+
creatorVault: Keypair.generate().publicKey,
|
|
59
|
+
eventAuthority: Keypair.generate().publicKey,
|
|
60
|
+
pumpProgram: Keypair.generate().publicKey,
|
|
61
|
+
globalVolumeAccumulator: Keypair.generate().publicKey,
|
|
62
|
+
userVolumeAccumulator: Keypair.generate().publicKey,
|
|
63
|
+
feeConfig: Keypair.generate().publicKey,
|
|
64
|
+
feeProgram: Keypair.generate().publicKey,
|
|
65
|
+
bondingCurveV2,
|
|
66
|
+
feeRecipient
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assert.equal(keys.length, 18);
|
|
70
|
+
assert.equal(keys.at(-2).pubkey.toBase58(), bondingCurveV2.toBase58());
|
|
71
|
+
assert.equal(keys.at(-2).isWritable, false);
|
|
72
|
+
assert.equal(keys.at(-1).pubkey.toBase58(), feeRecipient.toBase58());
|
|
73
|
+
assert.equal(keys.at(-1).isWritable, true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("bonding sell appends mutable fee recipient after optional cashback and bondingCurveV2", () => {
|
|
77
|
+
const trader = createTrader() as any;
|
|
78
|
+
const baseArgs = {
|
|
79
|
+
global: Keypair.generate().publicKey,
|
|
80
|
+
globalFeeRecipient: Keypair.generate().publicKey,
|
|
81
|
+
mint: Keypair.generate().publicKey,
|
|
82
|
+
bonding: Keypair.generate().publicKey,
|
|
83
|
+
associatedBondingCurve: Keypair.generate().publicKey,
|
|
84
|
+
userAta: Keypair.generate().publicKey,
|
|
85
|
+
wallet: Keypair.generate().publicKey,
|
|
86
|
+
creatorVault: Keypair.generate().publicKey,
|
|
87
|
+
eventAuthority: Keypair.generate().publicKey,
|
|
88
|
+
pumpProgram: Keypair.generate().publicKey,
|
|
89
|
+
feeConfig: Keypair.generate().publicKey,
|
|
90
|
+
feeProgram: Keypair.generate().publicKey,
|
|
91
|
+
bondingCurveV2: Keypair.generate().publicKey,
|
|
92
|
+
feeRecipient: FEE_RECIPIENTS[0],
|
|
93
|
+
userVolumeAccumulator: Keypair.generate().publicKey
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const nonCashback = trader.buildBondingSellKeys({
|
|
97
|
+
...baseArgs,
|
|
98
|
+
isCashbackCoin: false
|
|
99
|
+
});
|
|
100
|
+
assert.equal(nonCashback.length, 16);
|
|
101
|
+
assert.equal(nonCashback.at(-2).pubkey.toBase58(), baseArgs.bondingCurveV2.toBase58());
|
|
102
|
+
assert.equal(nonCashback.at(-1).pubkey.toBase58(), baseArgs.feeRecipient.toBase58());
|
|
103
|
+
assert.equal(nonCashback.at(-1).isWritable, true);
|
|
104
|
+
|
|
105
|
+
const cashback = trader.buildBondingSellKeys({
|
|
106
|
+
...baseArgs,
|
|
107
|
+
isCashbackCoin: true
|
|
108
|
+
});
|
|
109
|
+
assert.equal(cashback.length, 17);
|
|
110
|
+
assert.equal(cashback.at(-3).pubkey.toBase58(), baseArgs.userVolumeAccumulator.toBase58());
|
|
111
|
+
assert.equal(cashback.at(-2).pubkey.toBase58(), baseArgs.bondingCurveV2.toBase58());
|
|
112
|
+
assert.equal(cashback.at(-1).pubkey.toBase58(), baseArgs.feeRecipient.toBase58());
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("amm buy places poolV2 before fee recipient pair for non-cashback coins", () => {
|
|
116
|
+
const trader = createTrader();
|
|
117
|
+
const poolInfo = createPoolInfo(false);
|
|
118
|
+
const userBaseAta = Keypair.generate().publicKey;
|
|
119
|
+
const userQuoteAta = Keypair.generate().publicKey;
|
|
120
|
+
|
|
121
|
+
const instruction = trader.createAmmBuyInstruction(
|
|
122
|
+
poolInfo as any,
|
|
123
|
+
userBaseAta,
|
|
124
|
+
userQuoteAta,
|
|
125
|
+
1n,
|
|
126
|
+
2n,
|
|
127
|
+
TOKEN_PROGRAM_ID
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const feeRecipient = poolInfo.globalConfig.protocolFeeRecipients[0];
|
|
131
|
+
const feeRecipientAta = getAssociatedTokenAddressSync(
|
|
132
|
+
SOL_MINT,
|
|
133
|
+
feeRecipient,
|
|
134
|
+
true,
|
|
135
|
+
TOKEN_PROGRAM_ID,
|
|
136
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
assert.equal(instruction.keys.length, 26);
|
|
140
|
+
assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
|
|
141
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), feeRecipient.toBase58());
|
|
142
|
+
assert.equal(instruction.keys.at(-2)?.isWritable, false);
|
|
143
|
+
assert.equal(instruction.keys.at(-1)?.pubkey.toBase58(), feeRecipientAta.toBase58());
|
|
144
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("amm buy places cashback account before poolV2 and fee recipient tail", () => {
|
|
148
|
+
const trader = createTrader();
|
|
149
|
+
const poolInfo = createPoolInfo(true);
|
|
150
|
+
|
|
151
|
+
const instruction = trader.createAmmBuyInstruction(
|
|
152
|
+
poolInfo as any,
|
|
153
|
+
Keypair.generate().publicKey,
|
|
154
|
+
Keypair.generate().publicKey,
|
|
155
|
+
1n,
|
|
156
|
+
2n,
|
|
157
|
+
TOKEN_PROGRAM_ID
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
assert.equal(instruction.keys.length, 27);
|
|
161
|
+
assert.equal(instruction.keys.at(-3)?.isWritable, false);
|
|
162
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
|
|
163
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("amm sell places poolV2 before fee recipient pair for non-cashback coins", () => {
|
|
167
|
+
const trader = createTrader();
|
|
168
|
+
const poolInfo = createPoolInfo(false);
|
|
169
|
+
|
|
170
|
+
const instruction = trader.createAmmSellInstruction(
|
|
171
|
+
poolInfo as any,
|
|
172
|
+
Keypair.generate().publicKey,
|
|
173
|
+
Keypair.generate().publicKey,
|
|
174
|
+
1n,
|
|
175
|
+
2n,
|
|
176
|
+
TOKEN_PROGRAM_ID
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
assert.equal(instruction.keys.length, 24);
|
|
180
|
+
assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
|
|
181
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
|
|
182
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("amm sell places cashback accounts before poolV2 and fee recipient tail", () => {
|
|
186
|
+
const trader = createTrader();
|
|
187
|
+
const poolInfo = createPoolInfo(true);
|
|
188
|
+
|
|
189
|
+
const instruction = trader.createAmmSellInstruction(
|
|
190
|
+
poolInfo as any,
|
|
191
|
+
Keypair.generate().publicKey,
|
|
192
|
+
Keypair.generate().publicKey,
|
|
193
|
+
1n,
|
|
194
|
+
2n,
|
|
195
|
+
TOKEN_PROGRAM_ID
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
assert.equal(instruction.keys.length, 26);
|
|
199
|
+
assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
|
|
200
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
|
|
201
|
+
assert.equal(instruction.keys.at(-1)?.pubkey.toBase58(), getAssociatedTokenAddressSync(
|
|
202
|
+
SOL_MINT,
|
|
203
|
+
poolInfo.globalConfig.protocolFeeRecipients[0],
|
|
204
|
+
true,
|
|
205
|
+
TOKEN_PROGRAM_ID,
|
|
206
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
207
|
+
).toBase58());
|
|
208
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
209
|
+
});
|