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 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
- { pubkey: this.global, isSigner: false, isWritable: false },
501
- { pubkey: this.globalState.feeRecipient, isSigner: false, isWritable: true },
502
- { pubkey: mint, isSigner: false, isWritable: false },
503
- { pubkey: bonding, isSigner: false, isWritable: true },
504
- { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
505
- { pubkey: userAta, isSigner: false, isWritable: true },
506
- { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
507
- { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
508
- { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
509
- { pubkey: creatorVault, isSigner: false, isWritable: true },
510
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
511
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
512
- { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
513
- { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
514
- { pubkey: feeConfig, isSigner: false, isWritable: false },
515
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
516
- { pubkey: bondingCurveV2, isSigner: false, isWritable: false }
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
- { pubkey: this.global, isSigner: false, isWritable: false },
578
- { pubkey: this.globalState.feeRecipient, isSigner: false, isWritable: true },
579
- { pubkey: mint, isSigner: false, isWritable: false },
580
- { pubkey: bonding, isSigner: false, isWritable: true },
581
- { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
582
- { pubkey: userAta, isSigner: false, isWritable: true },
583
- { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
584
- { pubkey: web3_js_1.SystemProgram.programId, isSigner: false, isWritable: false },
585
- { pubkey: creatorVault, isSigner: false, isWritable: true },
586
- { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
587
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
588
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
589
- { pubkey: feeConfig, isSigner: false, isWritable: false },
590
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
591
- { pubkey: bondingCurveV2, isSigner: false, isWritable: false }
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
- { pubkey: poolV2, isSigner: false, isWritable: false }
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
- { pubkey: poolV2, isSigner: false, isWritable: false }
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
- { pubkey: this.global, isSigner: false, isWritable: false },
688
- { pubkey: this.globalState.feeRecipient, isSigner: false, isWritable: true },
689
- { pubkey: mint, isSigner: false, isWritable: false },
690
- { pubkey: bonding, isSigner: false, isWritable: true },
691
- { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
692
- { pubkey: userAta, isSigner: false, isWritable: true },
693
- { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
694
- { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
695
- { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
696
- { pubkey: creatorVault, isSigner: false, isWritable: true },
697
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
698
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
699
- { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
700
- { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
701
- { pubkey: feeConfig, isSigner: false, isWritable: false },
702
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
703
- { pubkey: bondingCurveV2, isSigner: false, isWritable: false }
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
- { pubkey: this.global, isSigner: false, isWritable: false },
805
- { pubkey: this.globalState.feeRecipient, isSigner: false, isWritable: true },
806
- { pubkey: mint, isSigner: false, isWritable: false },
807
- { pubkey: bonding, isSigner: false, isWritable: true },
808
- { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
809
- { pubkey: userAta, isSigner: false, isWritable: true },
810
- { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
811
- { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
812
- { pubkey: creatorVault, isSigner: false, isWritable: true },
813
- { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
814
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
815
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
816
- { pubkey: feeConfig, isSigner: false, isWritable: false },
817
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
818
- { pubkey: bondingCurveV2, isSigner: false, isWritable: false }
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
- { pubkey: poolV2, isSigner: false, isWritable: false }
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
- { pubkey: poolV2, isSigner: false, isWritable: false }
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
- { pubkey: this.global, isSigner: false, isWritable: false },
795
- { pubkey: this.globalState!.feeRecipient, isSigner: false, isWritable: true },
796
- { pubkey: mint, isSigner: false, isWritable: false },
797
- { pubkey: bonding, isSigner: false, isWritable: true },
798
- { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
799
- { pubkey: userAta, isSigner: false, isWritable: true },
800
- { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
801
- { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
802
- { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
803
- { pubkey: creatorVault, isSigner: false, isWritable: true },
804
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
805
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
806
- { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
807
- { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
808
- { pubkey: feeConfig, isSigner: false, isWritable: false },
809
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
810
- { pubkey: bondingCurveV2, isSigner: false, isWritable: false }
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
- { pubkey: this.global, isSigner: false, isWritable: false },
912
- { pubkey: this.globalState!.feeRecipient, isSigner: false, isWritable: true },
913
- { pubkey: mint, isSigner: false, isWritable: false },
914
- { pubkey: bonding, isSigner: false, isWritable: true },
915
- { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
916
- { pubkey: userAta, isSigner: false, isWritable: true },
917
- { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
918
- { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
919
- { pubkey: creatorVault, isSigner: false, isWritable: true },
920
- { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
921
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
922
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
923
- { pubkey: feeConfig, isSigner: false, isWritable: false },
924
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
925
- { pubkey: bondingCurveV2, isSigner: false, isWritable: false }
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
- { pubkey: poolV2, isSigner: false, isWritable: false }
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
- { pubkey: poolV2, isSigner: false, isWritable: false }
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.2",
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
+ });