pump-trader 1.1.3 → 1.1.5

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/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,7 @@ interface BondingCurveState {
63
64
  realSolReserves: bigint;
64
65
  tokenTotalSupply: bigint;
65
66
  complete: boolean;
67
+ quoteMint?: PublicKey;
66
68
  isMayhemMode?: boolean;
67
69
  isCashbackCoin?: boolean;
68
70
  }
@@ -153,6 +155,16 @@ const DISCRIMINATORS = {
153
155
 
154
156
  const AMM_FEE_BPS = 100n;
155
157
  const BPS_DENOMINATOR = 10000n;
158
+ const PUMP_NEW_FEE_RECIPIENTS = [
159
+ "5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
160
+ "9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
161
+ "GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
162
+ "3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
163
+ "5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
164
+ "EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
165
+ "5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
166
+ "A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
167
+ ].map((value) => new PublicKey(value));
156
168
 
157
169
  /* ================= 工具函数 ================= */
158
170
 
@@ -394,6 +406,102 @@ export class PumpTrader {
394
406
  )[0];
395
407
  }
396
408
 
409
+ private pickFeeRecipient(index = 0): PublicKey {
410
+ return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
411
+ }
412
+
413
+ private buildBondingBuyKeys(args: {
414
+ global: PublicKey;
415
+ globalFeeRecipient: PublicKey;
416
+ mint: PublicKey;
417
+ bonding: PublicKey;
418
+ associatedBondingCurve: PublicKey;
419
+ userAta: PublicKey;
420
+ wallet: PublicKey;
421
+ creatorVault: PublicKey;
422
+ eventAuthority: PublicKey;
423
+ pumpProgram: PublicKey;
424
+ globalVolumeAccumulator: PublicKey;
425
+ userVolumeAccumulator: PublicKey;
426
+ feeConfig: PublicKey;
427
+ feeProgram: PublicKey;
428
+ bondingCurveV2: PublicKey;
429
+ feeRecipient: PublicKey;
430
+ tokenProgramId?: PublicKey;
431
+ }): AccountMeta[] {
432
+ const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
433
+
434
+ return [
435
+ { pubkey: args.global, isSigner: false, isWritable: false },
436
+ { pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
437
+ { pubkey: args.mint, isSigner: false, isWritable: false },
438
+ { pubkey: args.bonding, isSigner: false, isWritable: true },
439
+ { pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
440
+ { pubkey: args.userAta, isSigner: false, isWritable: true },
441
+ { pubkey: args.wallet, isSigner: true, isWritable: true },
442
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
443
+ { pubkey: tokenProgramId, isSigner: false, isWritable: false },
444
+ { pubkey: args.creatorVault, isSigner: false, isWritable: true },
445
+ { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
446
+ { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
447
+ { pubkey: args.globalVolumeAccumulator, isSigner: false, isWritable: false },
448
+ { pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
449
+ { pubkey: args.feeConfig, isSigner: false, isWritable: false },
450
+ { pubkey: args.feeProgram, isSigner: false, isWritable: false },
451
+ { pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
452
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true }
453
+ ];
454
+ }
455
+
456
+ private buildBondingSellKeys(args: {
457
+ global: PublicKey;
458
+ globalFeeRecipient: PublicKey;
459
+ mint: PublicKey;
460
+ bonding: PublicKey;
461
+ associatedBondingCurve: PublicKey;
462
+ userAta: PublicKey;
463
+ wallet: PublicKey;
464
+ creatorVault: PublicKey;
465
+ eventAuthority: PublicKey;
466
+ pumpProgram: PublicKey;
467
+ feeConfig: PublicKey;
468
+ feeProgram: PublicKey;
469
+ bondingCurveV2: PublicKey;
470
+ feeRecipient: PublicKey;
471
+ isCashbackCoin: boolean;
472
+ userVolumeAccumulator: PublicKey;
473
+ tokenProgramId?: PublicKey;
474
+ }): AccountMeta[] {
475
+ const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
476
+ const keys: AccountMeta[] = [
477
+ { pubkey: args.global, isSigner: false, isWritable: false },
478
+ { pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
479
+ { pubkey: args.mint, isSigner: false, isWritable: false },
480
+ { pubkey: args.bonding, isSigner: false, isWritable: true },
481
+ { pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
482
+ { pubkey: args.userAta, isSigner: false, isWritable: true },
483
+ { pubkey: args.wallet, isSigner: true, isWritable: true },
484
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
485
+ { pubkey: args.creatorVault, isSigner: false, isWritable: true },
486
+ { pubkey: tokenProgramId, isSigner: false, isWritable: false },
487
+ { pubkey: args.eventAuthority, isSigner: false, isWritable: false },
488
+ { pubkey: args.pumpProgram, isSigner: false, isWritable: false },
489
+ { pubkey: args.feeConfig, isSigner: false, isWritable: false },
490
+ { pubkey: args.feeProgram, isSigner: false, isWritable: false }
491
+ ];
492
+
493
+ if (args.isCashbackCoin) {
494
+ keys.push({ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true });
495
+ }
496
+
497
+ keys.push(
498
+ { pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
499
+ { pubkey: args.feeRecipient, isSigner: false, isWritable: true }
500
+ );
501
+
502
+ return keys;
503
+ }
504
+
397
505
  async loadBonding(mint: PublicKey): Promise<BondingInfo> {
398
506
  const bonding = this.getBondingPda(mint);
399
507
  const acc = await this.connection.getAccountInfo(bonding);
@@ -414,6 +522,11 @@ export class PumpTrader {
414
522
  const creator = new PublicKey(data.slice(offset, offset + 32));
415
523
  offset += 32;
416
524
 
525
+ if (offset + 32 <= data.length - 2) {
526
+ state.quoteMint = new PublicKey(data.slice(offset, offset + 32));
527
+ offset += 32;
528
+ }
529
+
417
530
  state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
418
531
  offset += 1;
419
532
  state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
@@ -454,19 +567,21 @@ export class PumpTrader {
454
567
  async getPriceAndStatus(tokenAddr: string): Promise<{ price: number; completed: boolean }> {
455
568
  const mint = new PublicKey(tokenAddr);
456
569
  const { state } = await this.loadBonding(mint);
570
+ const quoteMint = this.getEffectiveQuoteMint(state.quoteMint);
457
571
 
458
572
  if (state.complete) {
459
- const price = await this.getAmmPrice(mint);
573
+ const price = await this.getAmmPrice(mint, quoteMint);
460
574
  return { price, completed: true };
461
575
  }
462
576
 
463
577
  const oneToken = BigInt(1_000_000);
464
- const solOut = this.calcSell(oneToken, state);
465
- const price = Number(solOut) / 1e9;
578
+ const quoteOut = this.calcSell(oneToken, state);
579
+ const quoteDecimals = await this.getMintDecimals(quoteMint);
580
+ const price = Number(quoteOut) / 10 ** quoteDecimals;
466
581
  return { price, completed: false };
467
582
  }
468
583
 
469
- async getAmmPrice(mint: PublicKey): Promise<number> {
584
+ async getAmmPrice(mint: PublicKey, quoteMint: PublicKey = SOL_MINT): Promise<number> {
470
585
  const [poolCreator] = PublicKey.findProgramAddressSync(
471
586
  [Buffer.from("pool-authority"), mint.toBuffer()],
472
587
  PROGRAM_IDS.PUMP
@@ -474,7 +589,7 @@ export class PumpTrader {
474
589
 
475
590
  const indexBuffer = new BN(0).toArrayLike(Buffer, "le", 2);
476
591
  const [pool] = PublicKey.findProgramAddressSync(
477
- [Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(), SOL_MINT.toBuffer()],
592
+ [Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(), quoteMint.toBuffer()],
478
593
  PROGRAM_IDS.PUMP_AMM
479
594
  );
480
595
 
@@ -490,6 +605,28 @@ export class PumpTrader {
490
605
  return quoteInfo.value.uiAmount! / baseInfo.value.uiAmount!;
491
606
  }
492
607
 
608
+ private getEffectiveQuoteMint(quoteMint?: PublicKey): PublicKey {
609
+ if (!quoteMint || quoteMint.equals(PublicKey.default)) {
610
+ return SOL_MINT;
611
+ }
612
+ return quoteMint;
613
+ }
614
+
615
+ private async getMintDecimals(mint: PublicKey): Promise<number> {
616
+ if (mint.equals(SOL_MINT)) {
617
+ return 9;
618
+ }
619
+
620
+ const mintInfo = await this.connection.getParsedAccountInfo(mint);
621
+ const parsedData = mintInfo.value?.data;
622
+
623
+ if (parsedData && "parsed" in parsedData) {
624
+ return parsedData.parsed.info.decimals;
625
+ }
626
+
627
+ throw new Error(`Unable to determine mint decimals for ${mint.toBase58()}`);
628
+ }
629
+
493
630
  /* ---------- 余额查询 ---------- */
494
631
 
495
632
  /**
@@ -777,6 +914,7 @@ export class PumpTrader {
777
914
  [Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
778
915
  PROGRAM_IDS.FEE
779
916
  );
917
+ const feeRecipient = this.pickFeeRecipient();
780
918
 
781
919
  for (let i = 0; i < solChunks.length; i++) {
782
920
  try {
@@ -800,25 +938,25 @@ export class PumpTrader {
800
938
  tx.add(
801
939
  new TransactionInstruction({
802
940
  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: tokenProgram.programId, isSigner: false, isWritable: false },
813
- { pubkey: creatorVault, isSigner: false, isWritable: true },
814
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
815
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
816
- { pubkey: globalVolumeAccumulator, isSigner: false, isWritable: false },
817
- { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
818
- { pubkey: feeConfig, isSigner: false, isWritable: false },
819
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
820
- { pubkey: bondingCurveV2, isSigner: false, isWritable: false }
821
- ],
941
+ keys: this.buildBondingBuyKeys({
942
+ global: this.global,
943
+ globalFeeRecipient: this.globalState!.feeRecipient,
944
+ mint,
945
+ bonding,
946
+ associatedBondingCurve,
947
+ userAta,
948
+ wallet: this.wallet.publicKey,
949
+ creatorVault,
950
+ eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
951
+ pumpProgram: PROGRAM_IDS.PUMP,
952
+ globalVolumeAccumulator,
953
+ userVolumeAccumulator,
954
+ feeConfig,
955
+ feeProgram: PROGRAM_IDS.FEE,
956
+ bondingCurveV2,
957
+ feeRecipient,
958
+ tokenProgramId: tokenProgram.programId
959
+ }),
822
960
  data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
823
961
  })
824
962
  );
@@ -901,12 +1039,7 @@ export class PumpTrader {
901
1039
  [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
902
1040
  PROGRAM_IDS.PUMP
903
1041
  );
904
-
905
- const sellRemainingKeys = [];
906
- if (state.isCashbackCoin) {
907
- sellRemainingKeys.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
908
- }
909
- sellRemainingKeys.push({ pubkey: bondingCurveV2, isSigner: false, isWritable: false });
1042
+ const feeRecipient = this.pickFeeRecipient();
910
1043
 
911
1044
  for (let i = 0; i < tokenChunks.length; i++) {
912
1045
  try {
@@ -928,23 +1061,25 @@ export class PumpTrader {
928
1061
  tx.add(
929
1062
  new TransactionInstruction({
930
1063
  programId: PROGRAM_IDS.PUMP,
931
- keys: [
932
- { pubkey: this.global, isSigner: false, isWritable: false },
933
- { pubkey: this.globalState!.feeRecipient, isSigner: false, isWritable: true },
934
- { pubkey: mint, isSigner: false, isWritable: false },
935
- { pubkey: bonding, isSigner: false, isWritable: true },
936
- { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
937
- { pubkey: userAta, isSigner: false, isWritable: true },
938
- { pubkey: this.wallet.publicKey, isSigner: true, isWritable: true },
939
- { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
940
- { pubkey: creatorVault, isSigner: false, isWritable: true },
941
- { pubkey: tokenProgram.programId, isSigner: false, isWritable: false },
942
- { pubkey: PROGRAM_IDS.EVENT_AUTHORITY, isSigner: false, isWritable: false },
943
- { pubkey: PROGRAM_IDS.PUMP, isSigner: false, isWritable: false },
944
- { pubkey: feeConfig, isSigner: false, isWritable: false },
945
- { pubkey: PROGRAM_IDS.FEE, isSigner: false, isWritable: false },
946
- ...sellRemainingKeys
947
- ],
1064
+ keys: this.buildBondingSellKeys({
1065
+ global: this.global,
1066
+ globalFeeRecipient: this.globalState!.feeRecipient,
1067
+ mint,
1068
+ bonding,
1069
+ associatedBondingCurve,
1070
+ userAta,
1071
+ wallet: this.wallet.publicKey,
1072
+ creatorVault,
1073
+ eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
1074
+ pumpProgram: PROGRAM_IDS.PUMP,
1075
+ feeConfig,
1076
+ feeProgram: PROGRAM_IDS.FEE,
1077
+ bondingCurveV2,
1078
+ feeRecipient,
1079
+ isCashbackCoin: !!state.isCashbackCoin,
1080
+ userVolumeAccumulator,
1081
+ tokenProgramId: tokenProgram.programId
1082
+ }),
948
1083
  data: Buffer.concat([
949
1084
  DISCRIMINATORS.SELL,
950
1085
  u64(tokenIn),
@@ -1269,6 +1404,14 @@ export class PumpTrader {
1269
1404
  TOKEN_PROGRAM_ID,
1270
1405
  ASSOCIATED_TOKEN_PROGRAM_ID
1271
1406
  );
1407
+ const newFeeRecipient = this.pickFeeRecipient();
1408
+ const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1409
+ poolKeys.quoteMint,
1410
+ newFeeRecipient,
1411
+ true,
1412
+ TOKEN_PROGRAM_ID,
1413
+ ASSOCIATED_TOKEN_PROGRAM_ID
1414
+ );
1272
1415
 
1273
1416
  const remainingKeys = [];
1274
1417
  if (poolKeys.isCashbackCoin) {
@@ -1282,6 +1425,10 @@ export class PumpTrader {
1282
1425
  remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true });
1283
1426
  }
1284
1427
  remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1428
+ remainingKeys.push(
1429
+ { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1430
+ { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
1431
+ );
1285
1432
 
1286
1433
  return new TransactionInstruction({
1287
1434
  programId: PROGRAM_IDS.PUMP_AMM,
@@ -1362,6 +1509,14 @@ export class PumpTrader {
1362
1509
  TOKEN_PROGRAM_ID,
1363
1510
  ASSOCIATED_TOKEN_PROGRAM_ID
1364
1511
  );
1512
+ const newFeeRecipient = this.pickFeeRecipient();
1513
+ const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1514
+ poolKeys.quoteMint,
1515
+ newFeeRecipient,
1516
+ true,
1517
+ TOKEN_PROGRAM_ID,
1518
+ ASSOCIATED_TOKEN_PROGRAM_ID
1519
+ );
1365
1520
 
1366
1521
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
1367
1522
  [Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
@@ -1384,6 +1539,10 @@ export class PumpTrader {
1384
1539
  );
1385
1540
  }
1386
1541
  remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1542
+ remainingKeys.push(
1543
+ { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1544
+ { pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
1545
+ );
1387
1546
 
1388
1547
  return new TransactionInstruction({
1389
1548
  programId: PROGRAM_IDS.PUMP_AMM,
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "pump-trader",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
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,301 @@
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 USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
12
+ const FEE_RECIPIENTS = [
13
+ "5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
14
+ "9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
15
+ "GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
16
+ "3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
17
+ "5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
18
+ "EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
19
+ "5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
20
+ "A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
21
+ ].map((value) => new PublicKey(value));
22
+
23
+ function createTrader() {
24
+ return new PumpTrader("http://127.0.0.1:8899", bs58.encode(Keypair.generate().secretKey));
25
+ }
26
+
27
+ function createPoolInfo(isCashbackCoin: boolean) {
28
+ return {
29
+ pool: Keypair.generate().publicKey,
30
+ poolAuthority: Keypair.generate().publicKey,
31
+ poolKeys: {
32
+ baseMint: Keypair.generate().publicKey,
33
+ quoteMint: SOL_MINT,
34
+ poolBaseTokenAccount: Keypair.generate().publicKey,
35
+ poolQuoteTokenAccount: Keypair.generate().publicKey,
36
+ coinCreator: Keypair.generate().publicKey,
37
+ isCashbackCoin
38
+ },
39
+ globalConfig: {
40
+ address: Keypair.generate().publicKey,
41
+ protocolFeeRecipients: FEE_RECIPIENTS
42
+ }
43
+ };
44
+ }
45
+
46
+ test("bonding buy appends mutable fee recipient after bondingCurveV2", () => {
47
+ const trader = createTrader() as any;
48
+ const bondingCurveV2 = Keypair.generate().publicKey;
49
+ const feeRecipient = FEE_RECIPIENTS[0];
50
+
51
+ const keys = trader.buildBondingBuyKeys({
52
+ global: Keypair.generate().publicKey,
53
+ globalFeeRecipient: Keypair.generate().publicKey,
54
+ mint: Keypair.generate().publicKey,
55
+ bonding: Keypair.generate().publicKey,
56
+ associatedBondingCurve: Keypair.generate().publicKey,
57
+ userAta: Keypair.generate().publicKey,
58
+ wallet: Keypair.generate().publicKey,
59
+ creatorVault: Keypair.generate().publicKey,
60
+ eventAuthority: Keypair.generate().publicKey,
61
+ pumpProgram: Keypair.generate().publicKey,
62
+ globalVolumeAccumulator: Keypair.generate().publicKey,
63
+ userVolumeAccumulator: Keypair.generate().publicKey,
64
+ feeConfig: Keypair.generate().publicKey,
65
+ feeProgram: Keypair.generate().publicKey,
66
+ bondingCurveV2,
67
+ feeRecipient
68
+ });
69
+
70
+ assert.equal(keys.length, 18);
71
+ assert.equal(keys.at(-2).pubkey.toBase58(), bondingCurveV2.toBase58());
72
+ assert.equal(keys.at(-2).isWritable, false);
73
+ assert.equal(keys.at(-1).pubkey.toBase58(), feeRecipient.toBase58());
74
+ assert.equal(keys.at(-1).isWritable, true);
75
+ });
76
+
77
+ test("bonding sell appends mutable fee recipient after optional cashback and bondingCurveV2", () => {
78
+ const trader = createTrader() as any;
79
+ const baseArgs = {
80
+ global: Keypair.generate().publicKey,
81
+ globalFeeRecipient: Keypair.generate().publicKey,
82
+ mint: Keypair.generate().publicKey,
83
+ bonding: Keypair.generate().publicKey,
84
+ associatedBondingCurve: Keypair.generate().publicKey,
85
+ userAta: Keypair.generate().publicKey,
86
+ wallet: Keypair.generate().publicKey,
87
+ creatorVault: Keypair.generate().publicKey,
88
+ eventAuthority: Keypair.generate().publicKey,
89
+ pumpProgram: Keypair.generate().publicKey,
90
+ feeConfig: Keypair.generate().publicKey,
91
+ feeProgram: Keypair.generate().publicKey,
92
+ bondingCurveV2: Keypair.generate().publicKey,
93
+ feeRecipient: FEE_RECIPIENTS[0],
94
+ userVolumeAccumulator: Keypair.generate().publicKey
95
+ };
96
+
97
+ const nonCashback = trader.buildBondingSellKeys({
98
+ ...baseArgs,
99
+ isCashbackCoin: false
100
+ });
101
+ assert.equal(nonCashback.length, 16);
102
+ assert.equal(nonCashback.at(-2).pubkey.toBase58(), baseArgs.bondingCurveV2.toBase58());
103
+ assert.equal(nonCashback.at(-1).pubkey.toBase58(), baseArgs.feeRecipient.toBase58());
104
+ assert.equal(nonCashback.at(-1).isWritable, true);
105
+
106
+ const cashback = trader.buildBondingSellKeys({
107
+ ...baseArgs,
108
+ isCashbackCoin: true
109
+ });
110
+ assert.equal(cashback.length, 17);
111
+ assert.equal(cashback.at(-3).pubkey.toBase58(), baseArgs.userVolumeAccumulator.toBase58());
112
+ assert.equal(cashback.at(-2).pubkey.toBase58(), baseArgs.bondingCurveV2.toBase58());
113
+ assert.equal(cashback.at(-1).pubkey.toBase58(), baseArgs.feeRecipient.toBase58());
114
+ });
115
+
116
+ test("amm buy places poolV2 before fee recipient pair for non-cashback coins", () => {
117
+ const trader = createTrader();
118
+ const poolInfo = createPoolInfo(false);
119
+ const userBaseAta = Keypair.generate().publicKey;
120
+ const userQuoteAta = Keypair.generate().publicKey;
121
+
122
+ const instruction = trader.createAmmBuyInstruction(
123
+ poolInfo as any,
124
+ userBaseAta,
125
+ userQuoteAta,
126
+ 1n,
127
+ 2n,
128
+ TOKEN_PROGRAM_ID
129
+ );
130
+
131
+ const feeRecipient = poolInfo.globalConfig.protocolFeeRecipients[0];
132
+ const feeRecipientAta = getAssociatedTokenAddressSync(
133
+ SOL_MINT,
134
+ feeRecipient,
135
+ true,
136
+ TOKEN_PROGRAM_ID,
137
+ ASSOCIATED_TOKEN_PROGRAM_ID
138
+ );
139
+
140
+ assert.equal(instruction.keys.length, 26);
141
+ assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
142
+ assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), feeRecipient.toBase58());
143
+ assert.equal(instruction.keys.at(-2)?.isWritable, false);
144
+ assert.equal(instruction.keys.at(-1)?.pubkey.toBase58(), feeRecipientAta.toBase58());
145
+ assert.equal(instruction.keys.at(-1)?.isWritable, true);
146
+ });
147
+
148
+ test("amm buy places cashback account before poolV2 and fee recipient tail", () => {
149
+ const trader = createTrader();
150
+ const poolInfo = createPoolInfo(true);
151
+
152
+ const instruction = trader.createAmmBuyInstruction(
153
+ poolInfo as any,
154
+ Keypair.generate().publicKey,
155
+ Keypair.generate().publicKey,
156
+ 1n,
157
+ 2n,
158
+ TOKEN_PROGRAM_ID
159
+ );
160
+
161
+ assert.equal(instruction.keys.length, 27);
162
+ assert.equal(instruction.keys.at(-3)?.isWritable, false);
163
+ assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
164
+ assert.equal(instruction.keys.at(-1)?.isWritable, true);
165
+ });
166
+
167
+ test("amm sell places poolV2 before fee recipient pair for non-cashback coins", () => {
168
+ const trader = createTrader();
169
+ const poolInfo = createPoolInfo(false);
170
+
171
+ const instruction = trader.createAmmSellInstruction(
172
+ poolInfo as any,
173
+ Keypair.generate().publicKey,
174
+ Keypair.generate().publicKey,
175
+ 1n,
176
+ 2n,
177
+ TOKEN_PROGRAM_ID
178
+ );
179
+
180
+ assert.equal(instruction.keys.length, 24);
181
+ assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
182
+ assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
183
+ assert.equal(instruction.keys.at(-1)?.isWritable, true);
184
+ });
185
+
186
+ test("amm sell places cashback accounts before poolV2 and fee recipient tail", () => {
187
+ const trader = createTrader();
188
+ const poolInfo = createPoolInfo(true);
189
+
190
+ const instruction = trader.createAmmSellInstruction(
191
+ poolInfo as any,
192
+ Keypair.generate().publicKey,
193
+ Keypair.generate().publicKey,
194
+ 1n,
195
+ 2n,
196
+ TOKEN_PROGRAM_ID
197
+ );
198
+
199
+ assert.equal(instruction.keys.length, 26);
200
+ assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
201
+ assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
202
+ assert.equal(instruction.keys.at(-1)?.pubkey.toBase58(), getAssociatedTokenAddressSync(
203
+ SOL_MINT,
204
+ poolInfo.globalConfig.protocolFeeRecipients[0],
205
+ true,
206
+ TOKEN_PROGRAM_ID,
207
+ ASSOCIATED_TOKEN_PROGRAM_ID
208
+ ).toBase58());
209
+ assert.equal(instruction.keys.at(-1)?.isWritable, true);
210
+ });
211
+
212
+ test("loadBonding parses quote mint from v2 bonding curve accounts", async () => {
213
+ const trader = createTrader() as any;
214
+ const mint = Keypair.generate().publicKey;
215
+ const creator = Keypair.generate().publicKey;
216
+ const data = Buffer.alloc(115);
217
+
218
+ let offset = 8;
219
+ data.writeBigUInt64LE(11n, offset);
220
+ offset += 8;
221
+ data.writeBigUInt64LE(22n, offset);
222
+ offset += 8;
223
+ data.writeBigUInt64LE(33n, offset);
224
+ offset += 8;
225
+ data.writeBigUInt64LE(44n, offset);
226
+ offset += 8;
227
+ data.writeBigUInt64LE(55n, offset);
228
+ offset += 8;
229
+ data[offset] = 1;
230
+ offset += 1;
231
+ creator.toBuffer().copy(data, offset);
232
+ offset += 32;
233
+ USDC_MINT.toBuffer().copy(data, offset);
234
+ offset += 32;
235
+ data[offset] = 1;
236
+ offset += 1;
237
+ data[offset] = 0;
238
+
239
+ trader.connection.getAccountInfo = async () => ({ data });
240
+
241
+ const result = await trader.loadBonding(mint);
242
+
243
+ assert.equal(result.creator.toBase58(), creator.toBase58());
244
+ assert.equal(result.state.quoteMint?.toBase58(), USDC_MINT.toBase58());
245
+ assert.equal(result.state.isMayhemMode, true);
246
+ assert.equal(result.state.isCashbackCoin, false);
247
+ });
248
+
249
+ test("getPriceAndStatus uses quote mint decimals for incomplete bonding curves", async () => {
250
+ const trader = createTrader() as any;
251
+ trader.loadBonding = async () => ({
252
+ bonding: Keypair.generate().publicKey,
253
+ creator: Keypair.generate().publicKey,
254
+ state: {
255
+ complete: false,
256
+ quoteMint: USDC_MINT
257
+ }
258
+ });
259
+ trader.calcSell = () => 1_234_567n;
260
+ trader.connection.getParsedAccountInfo = async () => ({
261
+ value: {
262
+ data: {
263
+ parsed: {
264
+ info: {
265
+ decimals: 6
266
+ }
267
+ }
268
+ }
269
+ }
270
+ });
271
+
272
+ const result = await trader.getPriceAndStatus(Keypair.generate().publicKey.toBase58());
273
+
274
+ assert.equal(result.completed, false);
275
+ assert.equal(result.price, 1.234567);
276
+ });
277
+
278
+ test("getPriceAndStatus forwards quote mint to AMM pricing for completed curves", async () => {
279
+ const trader = createTrader() as any;
280
+ const calls: PublicKey[][] = [];
281
+
282
+ trader.loadBonding = async () => ({
283
+ bonding: Keypair.generate().publicKey,
284
+ creator: Keypair.generate().publicKey,
285
+ state: {
286
+ complete: true,
287
+ quoteMint: USDC_MINT
288
+ }
289
+ });
290
+ trader.getAmmPrice = async (...args: PublicKey[]) => {
291
+ calls.push(args);
292
+ return 0.42;
293
+ };
294
+
295
+ const result = await trader.getPriceAndStatus(Keypair.generate().publicKey.toBase58());
296
+
297
+ assert.equal(result.completed, true);
298
+ assert.equal(result.price, 0.42);
299
+ assert.equal(calls.length, 1);
300
+ assert.equal(calls[0][1]?.toBase58(), USDC_MINT.toBase58());
301
+ });