pump-trader 1.2.1 → 1.2.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
@@ -126,11 +126,13 @@ export declare class PumpTrader {
126
126
  calcSell(tokenIn: bigint, state: BondingCurveState): bigint;
127
127
  calculateAmmBuyOutput(quoteIn: bigint, reserves: PoolReserves): bigint;
128
128
  calculateAmmSellOutput(baseIn: bigint, reserves: PoolReserves): bigint;
129
- getPriceAndStatus(tokenAddr: string): Promise<{
129
+ private solPriceCache;
130
+ getSolPriceInUsdc(): Promise<number>;
131
+ getPriceAndStatus(tokenAddr: string, quoteMint?: PublicKey): Promise<{
130
132
  price: number;
131
133
  completed: boolean;
132
134
  }>;
133
- getAmmPrice(mint: PublicKey): Promise<number>;
135
+ getAmmPrice(mint: PublicKey, quoteMint?: PublicKey): Promise<number>;
134
136
  /**
135
137
  * 查询代币余额
136
138
  * @param tokenAddr - 代币地址(可选),如果不传则返回所有代币
package/dist/index.js CHANGED
@@ -153,6 +153,8 @@ function parsePoolKeys(data) {
153
153
  /* ================= PumpTrader 类 ================= */
154
154
  class PumpTrader {
155
155
  constructor(rpc, wallet) {
156
+ /* ---------- 价格查询 ---------- */
157
+ this.solPriceCache = null;
156
158
  this.connection = new web3_js_1.Connection(rpc, "confirmed");
157
159
  this._wallet = wallet;
158
160
  this.publicKey = wallet.publicKey;
@@ -419,20 +421,77 @@ class PumpTrader {
419
421
  const denominator = reserves.baseAmount + baseInAfterFee;
420
422
  return numerator / denominator;
421
423
  }
422
- /* ---------- 价格查询 ---------- */
423
- async getPriceAndStatus(tokenAddr) {
424
+ async getSolPriceInUsdc() {
425
+ if (this.solPriceCache && Date.now() - this.solPriceCache.timestamp < 60000) {
426
+ return this.solPriceCache.price;
427
+ }
428
+ try {
429
+ // Use Orca USDC/SOL whirlpool (7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm)
430
+ // Read token vault balances directly to compute price
431
+ const poolAddr = new web3_js_1.PublicKey("7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm");
432
+ const acc = await this.connection.getAccountInfo(poolAddr);
433
+ if (!acc || acc.data.length < 304)
434
+ throw new Error("Invalid pool data");
435
+ // Whirlpool: tokenMintA at offset 40, tokenMintB at offset 72, vaultA at 104, vaultB at 136
436
+ const tokenMintA = new web3_js_1.PublicKey(acc.data.slice(40, 72));
437
+ const tokenVaultA = new web3_js_1.PublicKey(acc.data.slice(104, 136));
438
+ const tokenVaultB = new web3_js_1.PublicKey(acc.data.slice(136, 168));
439
+ const [balanceA, balanceB] = await Promise.all([
440
+ this.connection.getTokenAccountBalance(tokenVaultA),
441
+ this.connection.getTokenAccountBalance(tokenVaultB),
442
+ ]);
443
+ // Determine which vault holds SOL by checking tokenMintA
444
+ const SOL_ADDR = "So11111111111111111111111111111111111111112";
445
+ const solBalance = tokenMintA.toBase58() === SOL_ADDR
446
+ ? Number(balanceA.value.amount)
447
+ : Number(balanceB.value.amount);
448
+ const usdcBalance = tokenMintA.toBase58() === SOL_ADDR
449
+ ? Number(balanceB.value.amount)
450
+ : Number(balanceA.value.amount);
451
+ if (solBalance === 0)
452
+ throw new Error("Zero balance");
453
+ const price = usdcBalance / solBalance;
454
+ this.solPriceCache = { price, timestamp: Date.now() };
455
+ return price;
456
+ }
457
+ catch {
458
+ return 175;
459
+ }
460
+ }
461
+ async getPriceAndStatus(tokenAddr, quoteMint) {
424
462
  const mint = new web3_js_1.PublicKey(tokenAddr);
425
463
  const { state } = await this.loadBonding(mint);
426
464
  if (state.complete) {
427
- const price = await this.getAmmPrice(mint);
465
+ const qm = quoteMint || (state.quoteMint && !state.quoteMint.equals(SOL_MINT) ? state.quoteMint : SOL_MINT);
466
+ const price = await this.getAmmPrice(mint, qm);
428
467
  return { price, completed: true };
429
468
  }
469
+ const qm = quoteMint || state.quoteMint || SOL_MINT;
470
+ const isSolQuote = qm.equals(SOL_MINT);
430
471
  const oneToken = BigInt(1_000_000);
431
- const solOut = this.calcSell(oneToken, state);
432
- const price = Number(solOut) / 1e9;
472
+ if (isSolQuote) {
473
+ const solOut = this.calcSell(oneToken, state);
474
+ const price = Number(solOut) / 1e9;
475
+ return { price, completed: false };
476
+ }
477
+ // USDC-paired bonding curve: pump stores USDC raw amount (6 decimals) in the
478
+ // virtualSolReserves field. calcSell returns USDC raw, divide by 1e6 for USDC price.
479
+ // Then convert to SOL using Orca USDC/SOL pool.
480
+ let quotePrice;
481
+ if (state.virtualQuoteReserves !== undefined) {
482
+ const newVirtualToken = state.virtualTokenReserves + oneToken;
483
+ const quoteOut = state.virtualQuoteReserves - (state.virtualQuoteReserves * state.virtualTokenReserves) / newVirtualToken;
484
+ quotePrice = Number(quoteOut) / 1e6;
485
+ }
486
+ else {
487
+ const rawOut = this.calcSell(oneToken, state);
488
+ quotePrice = Number(rawOut) / 1e6;
489
+ }
490
+ const solPrice = await this.getSolPriceInUsdc();
491
+ const price = quotePrice / solPrice;
433
492
  return { price, completed: false };
434
493
  }
435
- async getAmmPrice(mint) {
494
+ async getAmmPrice(mint, quoteMint = SOL_MINT) {
436
495
  const [poolCreator] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("pool-authority"), mint.toBuffer()], PROGRAM_IDS.PUMP);
437
496
  const indexBuffer = new bn_js_1.default(0).toArrayLike(Buffer, "le", 2);
438
497
  const [pool] = web3_js_1.PublicKey.findProgramAddressSync([
@@ -440,7 +499,7 @@ class PumpTrader {
440
499
  indexBuffer,
441
500
  poolCreator.toBuffer(),
442
501
  mint.toBuffer(),
443
- SOL_MINT.toBuffer(),
502
+ quoteMint.toBuffer(),
444
503
  ], PROGRAM_IDS.PUMP_AMM);
445
504
  const acc = await this.connection.getAccountInfo(pool);
446
505
  if (!acc)
@@ -450,7 +509,13 @@ class PumpTrader {
450
509
  this.connection.getTokenAccountBalance(poolKeys.poolBaseTokenAccount),
451
510
  this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
452
511
  ]);
453
- return quoteInfo.value.uiAmount / baseInfo.value.uiAmount;
512
+ let price = quoteInfo.value.uiAmount / baseInfo.value.uiAmount;
513
+ // If pool is not SOL-quoted, convert to SOL price
514
+ if (!quoteMint.equals(SOL_MINT)) {
515
+ const solPrice = await this.getSolPriceInUsdc();
516
+ price = price / solPrice;
517
+ }
518
+ return price;
454
519
  }
455
520
  /* ---------- 余额查询 ---------- */
456
521
  /**
@@ -983,8 +1048,8 @@ class PumpTrader {
983
1048
  const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG], PROGRAM_IDS.FEE);
984
1049
  const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
985
1050
  const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
986
- const newFeeRecipient = this.pickFeeRecipient();
987
- const newFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(poolKeys.quoteMint, newFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
1051
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
1052
+ const buybackFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(poolKeys.quoteMint, buybackFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
988
1053
  const remainingKeys = [];
989
1054
  if (poolKeys.isCashbackCoin) {
990
1055
  const userVolumeAccumulatorWsolAta = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, userVolumeAccumulator, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
@@ -994,9 +1059,19 @@ class PumpTrader {
994
1059
  isWritable: true,
995
1060
  });
996
1061
  }
997
- remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
998
- remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, {
999
- pubkey: newFeeRecipientTokenAccount,
1062
+ const POOL_DEFAULT_COIN_CREATOR = new web3_js_1.PublicKey("11111111111111111111111111111111");
1063
+ if (poolKeys.coinCreator && !poolKeys.coinCreator.equals(POOL_DEFAULT_COIN_CREATOR)) {
1064
+ remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1065
+ }
1066
+ else {
1067
+ remainingKeys.push({
1068
+ pubkey: PUMP_BUYBACK_FEE_RECIPIENTS[0],
1069
+ isSigner: false,
1070
+ isWritable: true,
1071
+ });
1072
+ }
1073
+ remainingKeys.push({ pubkey: buybackFeeRecipient, isSigner: false, isWritable: false }, {
1074
+ pubkey: buybackFeeRecipientTokenAccount,
1000
1075
  isSigner: false,
1001
1076
  isWritable: true,
1002
1077
  });
@@ -1065,8 +1140,8 @@ class PumpTrader {
1065
1140
  const [feeConfig] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fee_config"), SEEDS.AMM_FEE_CONFIG], PROGRAM_IDS.FEE);
1066
1141
  const protocolFeeRecipient = globalConfig.protocolFeeRecipients[0];
1067
1142
  const protocolFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(SOL_MINT, protocolFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
1068
- const newFeeRecipient = this.pickFeeRecipient();
1069
- const newFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(poolKeys.quoteMint, newFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
1143
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
1144
+ const buybackFeeRecipientTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(poolKeys.quoteMint, buybackFeeRecipient, true, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID);
1070
1145
  const [userVolumeAccumulator] = web3_js_1.PublicKey.findProgramAddressSync([
1071
1146
  Buffer.from("user_volume_accumulator"),
1072
1147
  this.publicKey.toBuffer(),
@@ -1080,9 +1155,19 @@ class PumpTrader {
1080
1155
  isWritable: true,
1081
1156
  }, { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
1082
1157
  }
1083
- remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1084
- remainingKeys.push({ pubkey: newFeeRecipient, isSigner: false, isWritable: false }, {
1085
- pubkey: newFeeRecipientTokenAccount,
1158
+ const POOL_DEFAULT_COIN_CREATOR = new web3_js_1.PublicKey("11111111111111111111111111111111");
1159
+ if (poolKeys.coinCreator && !poolKeys.coinCreator.equals(POOL_DEFAULT_COIN_CREATOR)) {
1160
+ remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1161
+ }
1162
+ else {
1163
+ remainingKeys.push({
1164
+ pubkey: PUMP_BUYBACK_FEE_RECIPIENTS[0],
1165
+ isSigner: false,
1166
+ isWritable: true,
1167
+ });
1168
+ }
1169
+ remainingKeys.push({ pubkey: buybackFeeRecipient, isSigner: false, isWritable: false }, {
1170
+ pubkey: buybackFeeRecipientTokenAccount,
1086
1171
  isSigner: false,
1087
1172
  isWritable: true,
1088
1173
  });
package/index.js CHANGED
@@ -58,6 +58,17 @@ const DISCRIMINATORS = {
58
58
  const AMM_FEE_BPS = 100n; // 1%
59
59
  const BPS_DENOMINATOR = 10000n;
60
60
  const PUMP_NEW_FEE_RECIPIENTS = [
61
+ "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV",
62
+ "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ",
63
+ "7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX",
64
+ "9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz",
65
+ "AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY",
66
+ "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM",
67
+ "FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz",
68
+ "G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP",
69
+ ].map((value) => new PublicKey(value));
70
+
71
+ const PUMP_BUYBACK_FEE_RECIPIENTS = [
61
72
  "5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
62
73
  "9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
63
74
  "GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
@@ -360,6 +371,12 @@ export class PumpTrader {
360
371
  return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
361
372
  }
362
373
 
374
+ pickBuybackFeeRecipient(index = 0) {
375
+ return PUMP_BUYBACK_FEE_RECIPIENTS[
376
+ index % PUMP_BUYBACK_FEE_RECIPIENTS.length
377
+ ];
378
+ }
379
+
363
380
  buildBondingBuyKeys(args) {
364
381
  const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
365
382
 
@@ -492,22 +509,74 @@ export class PumpTrader {
492
509
 
493
510
  /* ---------- 价格查询 ---------- */
494
511
 
495
- async getPriceAndStatus(tokenAddr) {
512
+ async getSolPriceInUsdc() {
513
+ if (this._solPriceCache && Date.now() - this._solPriceCache.timestamp < 60000) {
514
+ return this._solPriceCache.price;
515
+ }
516
+ try {
517
+ const poolAddr = new PublicKey("7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm");
518
+ const acc = await this.connection.getAccountInfo(poolAddr);
519
+ if (!acc || acc.data.length < 304) throw new Error("Invalid pool data");
520
+ const tokenMintA = new PublicKey(acc.data.slice(40, 72));
521
+ const tokenVaultA = new PublicKey(acc.data.slice(104, 136));
522
+ const tokenVaultB = new PublicKey(acc.data.slice(136, 168));
523
+ const [balanceA, balanceB] = await Promise.all([
524
+ this.connection.getTokenAccountBalance(tokenVaultA),
525
+ this.connection.getTokenAccountBalance(tokenVaultB),
526
+ ]);
527
+ const SOL_ADDR = "So11111111111111111111111111111111111111112";
528
+ const solBalance = tokenMintA.toBase58() === SOL_ADDR
529
+ ? Number(balanceA.value.amount)
530
+ : Number(balanceB.value.amount);
531
+ const usdcBalance = tokenMintA.toBase58() === SOL_ADDR
532
+ ? Number(balanceB.value.amount)
533
+ : Number(balanceA.value.amount);
534
+ if (solBalance === 0) throw new Error("Zero balance");
535
+ const price = usdcBalance / solBalance;
536
+ this._solPriceCache = { price, timestamp: Date.now() };
537
+ return price;
538
+ } catch {
539
+ return 175;
540
+ }
541
+ }
542
+
543
+ async getPriceAndStatus(tokenAddr, quoteMint = null) {
496
544
  const mint = new PublicKey(tokenAddr);
497
545
  const { state } = await this.loadBonding(mint);
498
546
 
499
547
  if (state.complete) {
500
- const price = await this.getAmmPrice(mint);
548
+ const qm = quoteMint || (state.quoteMint && !state.quoteMint.equals(SOL_MINT) ? state.quoteMint : SOL_MINT);
549
+ const price = await this.getAmmPrice(mint, qm);
501
550
  return { price, completed: true };
502
551
  }
503
552
 
553
+ const qm = quoteMint || state.quoteMint || SOL_MINT;
554
+ const isSolQuote = qm.equals(SOL_MINT);
555
+
504
556
  const oneToken = BigInt(1_000_000);
505
- const solOut = this.calcSell(oneToken, state);
506
- const price = Number(solOut) / 1e9;
557
+
558
+ if (isSolQuote) {
559
+ const solOut = this.calcSell(oneToken, state);
560
+ const price = Number(solOut) / 1e9;
561
+ return { price, completed: false };
562
+ }
563
+
564
+ let quotePrice;
565
+ if (state.virtualQuoteReserves !== undefined) {
566
+ const newVirtualToken = state.virtualTokenReserves + oneToken;
567
+ const quoteOut = state.virtualQuoteReserves - (state.virtualQuoteReserves * state.virtualTokenReserves) / newVirtualToken;
568
+ quotePrice = Number(quoteOut) / 1e6;
569
+ } else {
570
+ const rawOut = this.calcSell(oneToken, state);
571
+ quotePrice = Number(rawOut) / 1e6;
572
+ }
573
+
574
+ const solPrice = await this.getSolPriceInUsdc();
575
+ const price = quotePrice / solPrice;
507
576
  return { price, completed: false };
508
577
  }
509
578
 
510
- async getAmmPrice(mint) {
579
+ async getAmmPrice(mint, quoteMint = SOL_MINT) {
511
580
  const [poolCreator] = PublicKey.findProgramAddressSync(
512
581
  [Buffer.from("pool-authority"), mint.toBuffer()],
513
582
  PROGRAM_IDS.PUMP,
@@ -520,7 +589,7 @@ export class PumpTrader {
520
589
  indexBuffer,
521
590
  poolCreator.toBuffer(),
522
591
  mint.toBuffer(),
523
- SOL_MINT.toBuffer(),
592
+ quoteMint.toBuffer(),
524
593
  ],
525
594
  PROGRAM_IDS.PUMP_AMM,
526
595
  );
@@ -534,7 +603,14 @@ export class PumpTrader {
534
603
  this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
535
604
  ]);
536
605
 
537
- return quoteInfo.value.uiAmount / baseInfo.value.uiAmount;
606
+ let price = quoteInfo.value.uiAmount / baseInfo.value.uiAmount;
607
+
608
+ if (!quoteMint.equals(SOL_MINT)) {
609
+ const solPrice = await this.getSolPriceInUsdc();
610
+ price = price / solPrice;
611
+ }
612
+
613
+ return price;
538
614
  }
539
615
 
540
616
  /* ---------- 余额查询 ---------- */
@@ -1393,10 +1469,10 @@ export class PumpTrader {
1393
1469
  TOKEN_PROGRAM_ID,
1394
1470
  ASSOCIATED_TOKEN_PROGRAM_ID,
1395
1471
  );
1396
- const newFeeRecipient = this.pickFeeRecipient();
1397
- const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1472
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
1473
+ const buybackFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1398
1474
  poolKeys.quoteMint,
1399
- newFeeRecipient,
1475
+ buybackFeeRecipient,
1400
1476
  true,
1401
1477
  TOKEN_PROGRAM_ID,
1402
1478
  ASSOCIATED_TOKEN_PROGRAM_ID,
@@ -1417,11 +1493,20 @@ export class PumpTrader {
1417
1493
  isWritable: true,
1418
1494
  });
1419
1495
  }
1420
- remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1496
+ const POOL_DEFAULT_COIN_CREATOR = new PublicKey("11111111111111111111111111111111");
1497
+ if (poolKeys.coinCreator && !poolKeys.coinCreator.equals(POOL_DEFAULT_COIN_CREATOR)) {
1498
+ remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1499
+ } else {
1500
+ remainingKeys.push({
1501
+ pubkey: PUMP_BUYBACK_FEE_RECIPIENTS[0],
1502
+ isSigner: false,
1503
+ isWritable: true,
1504
+ });
1505
+ }
1421
1506
  remainingKeys.push(
1422
- { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1507
+ { pubkey: buybackFeeRecipient, isSigner: false, isWritable: false },
1423
1508
  {
1424
- pubkey: newFeeRecipientTokenAccount,
1509
+ pubkey: buybackFeeRecipientTokenAccount,
1425
1510
  isSigner: false,
1426
1511
  isWritable: true,
1427
1512
  },
@@ -1526,10 +1611,10 @@ export class PumpTrader {
1526
1611
  TOKEN_PROGRAM_ID,
1527
1612
  ASSOCIATED_TOKEN_PROGRAM_ID,
1528
1613
  );
1529
- const newFeeRecipient = this.pickFeeRecipient();
1530
- const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1614
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
1615
+ const buybackFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1531
1616
  poolKeys.quoteMint,
1532
- newFeeRecipient,
1617
+ buybackFeeRecipient,
1533
1618
  true,
1534
1619
  TOKEN_PROGRAM_ID,
1535
1620
  ASSOCIATED_TOKEN_PROGRAM_ID,
@@ -1564,9 +1649,9 @@ export class PumpTrader {
1564
1649
  }
1565
1650
  remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1566
1651
  remainingKeys.push(
1567
- { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1652
+ { pubkey: buybackFeeRecipient, isSigner: false, isWritable: false },
1568
1653
  {
1569
- pubkey: newFeeRecipientTokenAccount,
1654
+ pubkey: buybackFeeRecipientTokenAccount,
1570
1655
  isSigner: false,
1571
1656
  isWritable: true,
1572
1657
  },
package/index.ts CHANGED
@@ -700,24 +700,90 @@ export class PumpTrader {
700
700
 
701
701
  /* ---------- 价格查询 ---------- */
702
702
 
703
+ private solPriceCache: { price: number; timestamp: number } | null = null;
704
+
705
+ async getSolPriceInUsdc(): Promise<number> {
706
+ if (this.solPriceCache && Date.now() - this.solPriceCache.timestamp < 60000) {
707
+ return this.solPriceCache.price;
708
+ }
709
+ try {
710
+ // Use Orca USDC/SOL whirlpool (7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm)
711
+ // Read token vault balances directly to compute price
712
+ const poolAddr = new PublicKey("7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm");
713
+ const acc = await this.connection.getAccountInfo(poolAddr);
714
+ if (!acc || acc.data.length < 304) throw new Error("Invalid pool data");
715
+
716
+ // Whirlpool: tokenMintA at offset 40, tokenMintB at offset 72, vaultA at 104, vaultB at 136
717
+ const tokenMintA = new PublicKey(acc.data.slice(40, 72));
718
+ const tokenVaultA = new PublicKey(acc.data.slice(104, 136));
719
+ const tokenVaultB = new PublicKey(acc.data.slice(136, 168));
720
+
721
+ const [balanceA, balanceB] = await Promise.all([
722
+ this.connection.getTokenAccountBalance(tokenVaultA),
723
+ this.connection.getTokenAccountBalance(tokenVaultB),
724
+ ]);
725
+
726
+ // Determine which vault holds SOL by checking tokenMintA
727
+ const SOL_ADDR = "So11111111111111111111111111111111111111112";
728
+ const solBalance = tokenMintA.toBase58() === SOL_ADDR
729
+ ? Number(balanceA.value.amount)
730
+ : Number(balanceB.value.amount);
731
+ const usdcBalance = tokenMintA.toBase58() === SOL_ADDR
732
+ ? Number(balanceB.value.amount)
733
+ : Number(balanceA.value.amount);
734
+
735
+ if (solBalance === 0) throw new Error("Zero balance");
736
+ const price = usdcBalance / solBalance;
737
+ this.solPriceCache = { price, timestamp: Date.now() };
738
+ return price;
739
+ } catch {
740
+ return 175;
741
+ }
742
+ }
743
+
703
744
  async getPriceAndStatus(
704
745
  tokenAddr: string,
746
+ quoteMint?: PublicKey,
705
747
  ): Promise<{ price: number; completed: boolean }> {
706
748
  const mint = new PublicKey(tokenAddr);
707
749
  const { state } = await this.loadBonding(mint);
708
750
 
709
751
  if (state.complete) {
710
- const price = await this.getAmmPrice(mint);
752
+ const qm = quoteMint || (state.quoteMint && !state.quoteMint.equals(SOL_MINT) ? state.quoteMint : SOL_MINT);
753
+ const price = await this.getAmmPrice(mint, qm);
711
754
  return { price, completed: true };
712
755
  }
713
756
 
757
+ const qm = quoteMint || state.quoteMint || SOL_MINT;
758
+ const isSolQuote = qm.equals(SOL_MINT);
759
+
714
760
  const oneToken = BigInt(1_000_000);
715
- const solOut = this.calcSell(oneToken, state);
716
- const price = Number(solOut) / 1e9;
761
+
762
+ if (isSolQuote) {
763
+ const solOut = this.calcSell(oneToken, state);
764
+ const price = Number(solOut) / 1e9;
765
+ return { price, completed: false };
766
+ }
767
+
768
+ // USDC-paired bonding curve: pump stores USDC raw amount (6 decimals) in the
769
+ // virtualSolReserves field. calcSell returns USDC raw, divide by 1e6 for USDC price.
770
+ // Then convert to SOL using Orca USDC/SOL pool.
771
+ let quotePrice: number;
772
+ if (state.virtualQuoteReserves !== undefined) {
773
+ const newVirtualToken = state.virtualTokenReserves + oneToken;
774
+ const quoteOut = state.virtualQuoteReserves - (state.virtualQuoteReserves * state.virtualTokenReserves) / newVirtualToken;
775
+ quotePrice = Number(quoteOut) / 1e6;
776
+ } else {
777
+ const rawOut = this.calcSell(oneToken, state);
778
+ quotePrice = Number(rawOut) / 1e6;
779
+ }
780
+
781
+ const solPrice = await this.getSolPriceInUsdc();
782
+ const price = quotePrice / solPrice;
717
783
  return { price, completed: false };
718
784
  }
719
785
 
720
- async getAmmPrice(mint: PublicKey): Promise<number> {
786
+ async getAmmPrice(mint: PublicKey, quoteMint: PublicKey = SOL_MINT): Promise<number> {
721
787
  const [poolCreator] = PublicKey.findProgramAddressSync(
722
788
  [Buffer.from("pool-authority"), mint.toBuffer()],
723
789
  PROGRAM_IDS.PUMP,
@@ -730,7 +796,7 @@ export class PumpTrader {
730
796
  indexBuffer,
731
797
  poolCreator.toBuffer(),
732
798
  mint.toBuffer(),
733
- SOL_MINT.toBuffer(),
799
+ quoteMint.toBuffer(),
734
800
  ],
735
801
  PROGRAM_IDS.PUMP_AMM,
736
802
  );
@@ -744,7 +810,15 @@ export class PumpTrader {
744
810
  this.connection.getTokenAccountBalance(poolKeys.poolQuoteTokenAccount),
745
811
  ]);
746
812
 
747
- return quoteInfo.value.uiAmount! / baseInfo.value.uiAmount!;
813
+ let price = quoteInfo.value.uiAmount! / baseInfo.value.uiAmount!;
814
+
815
+ // If pool is not SOL-quoted, convert to SOL price
816
+ if (!quoteMint.equals(SOL_MINT)) {
817
+ const solPrice = await this.getSolPriceInUsdc();
818
+ price = price / solPrice;
819
+ }
820
+
821
+ return price;
748
822
  }
749
823
 
750
824
  /* ---------- 余额查询 ---------- */
@@ -1657,10 +1731,10 @@ export class PumpTrader {
1657
1731
  TOKEN_PROGRAM_ID,
1658
1732
  ASSOCIATED_TOKEN_PROGRAM_ID,
1659
1733
  );
1660
- const newFeeRecipient = this.pickFeeRecipient();
1661
- const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1734
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
1735
+ const buybackFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1662
1736
  poolKeys.quoteMint,
1663
- newFeeRecipient,
1737
+ buybackFeeRecipient,
1664
1738
  true,
1665
1739
  TOKEN_PROGRAM_ID,
1666
1740
  ASSOCIATED_TOKEN_PROGRAM_ID,
@@ -1681,11 +1755,20 @@ export class PumpTrader {
1681
1755
  isWritable: true,
1682
1756
  });
1683
1757
  }
1684
- remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1758
+ const POOL_DEFAULT_COIN_CREATOR = new PublicKey("11111111111111111111111111111111");
1759
+ if (poolKeys.coinCreator && !poolKeys.coinCreator.equals(POOL_DEFAULT_COIN_CREATOR)) {
1760
+ remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1761
+ } else {
1762
+ remainingKeys.push({
1763
+ pubkey: PUMP_BUYBACK_FEE_RECIPIENTS[0],
1764
+ isSigner: false,
1765
+ isWritable: true,
1766
+ });
1767
+ }
1685
1768
  remainingKeys.push(
1686
- { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1769
+ { pubkey: buybackFeeRecipient, isSigner: false, isWritable: false },
1687
1770
  {
1688
- pubkey: newFeeRecipientTokenAccount,
1771
+ pubkey: buybackFeeRecipientTokenAccount,
1689
1772
  isSigner: false,
1690
1773
  isWritable: true,
1691
1774
  },
@@ -1790,10 +1873,10 @@ export class PumpTrader {
1790
1873
  TOKEN_PROGRAM_ID,
1791
1874
  ASSOCIATED_TOKEN_PROGRAM_ID,
1792
1875
  );
1793
- const newFeeRecipient = this.pickFeeRecipient();
1794
- const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1876
+ const buybackFeeRecipient = this.pickBuybackFeeRecipient();
1877
+ const buybackFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
1795
1878
  poolKeys.quoteMint,
1796
- newFeeRecipient,
1879
+ buybackFeeRecipient,
1797
1880
  true,
1798
1881
  TOKEN_PROGRAM_ID,
1799
1882
  ASSOCIATED_TOKEN_PROGRAM_ID,
@@ -1826,11 +1909,20 @@ export class PumpTrader {
1826
1909
  { pubkey: userVolumeAccumulator, isSigner: false, isWritable: true },
1827
1910
  );
1828
1911
  }
1829
- remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1912
+ const POOL_DEFAULT_COIN_CREATOR = new PublicKey("11111111111111111111111111111111");
1913
+ if (poolKeys.coinCreator && !poolKeys.coinCreator.equals(POOL_DEFAULT_COIN_CREATOR)) {
1914
+ remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
1915
+ } else {
1916
+ remainingKeys.push({
1917
+ pubkey: PUMP_BUYBACK_FEE_RECIPIENTS[0],
1918
+ isSigner: false,
1919
+ isWritable: true,
1920
+ });
1921
+ }
1830
1922
  remainingKeys.push(
1831
- { pubkey: newFeeRecipient, isSigner: false, isWritable: false },
1923
+ { pubkey: buybackFeeRecipient, isSigner: false, isWritable: false },
1832
1924
  {
1833
- pubkey: newFeeRecipientTokenAccount,
1925
+ pubkey: buybackFeeRecipientTokenAccount,
1834
1926
  isSigner: false,
1835
1927
  isWritable: true,
1836
1928
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pump-trader",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "description": "PumpFun 交易库 - 自动判断 Token Program 和内盘/外盘",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,7 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
 
4
- import bs58 from "bs58";
5
4
  import { Keypair, PublicKey } from "@solana/web3.js";
6
5
  import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from "@solana/spl-token";
7
6
 
@@ -9,6 +8,17 @@ import { PumpTrader } from "../index";
9
8
 
10
9
  const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
11
10
  const FEE_RECIPIENTS = [
11
+ "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV",
12
+ "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ",
13
+ "7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX",
14
+ "9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz",
15
+ "AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY",
16
+ "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM",
17
+ "FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz",
18
+ "G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"
19
+ ].map((value) => new PublicKey(value));
20
+
21
+ const BUYBACK_FEE_RECIPIENTS = [
12
22
  "5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
13
23
  "9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
14
24
  "GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
@@ -20,7 +30,7 @@ const FEE_RECIPIENTS = [
20
30
  ].map((value) => new PublicKey(value));
21
31
 
22
32
  function createTrader() {
23
- return new PumpTrader("http://127.0.0.1:8899", bs58.encode(Keypair.generate().secretKey));
33
+ return new PumpTrader("http://127.0.0.1:8899", Keypair.generate());
24
34
  }
25
35
 
26
36
  function createPoolInfo(isCashbackCoin: boolean) {
@@ -127,7 +137,7 @@ test("amm buy places poolV2 before fee recipient pair for non-cashback coins", (
127
137
  TOKEN_PROGRAM_ID
128
138
  );
129
139
 
130
- const feeRecipient = poolInfo.globalConfig.protocolFeeRecipients[0];
140
+ const feeRecipient = BUYBACK_FEE_RECIPIENTS[0];
131
141
  const feeRecipientAta = getAssociatedTokenAddressSync(
132
142
  SOL_MINT,
133
143
  feeRecipient,
@@ -136,6 +146,7 @@ test("amm buy places poolV2 before fee recipient pair for non-cashback coins", (
136
146
  ASSOCIATED_TOKEN_PROGRAM_ID
137
147
  );
138
148
 
149
+ assert.equal(instruction.keys[9]?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
139
150
  assert.equal(instruction.keys.length, 26);
140
151
  assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
141
152
  assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), feeRecipient.toBase58());
@@ -159,7 +170,7 @@ test("amm buy places cashback account before poolV2 and fee recipient tail", ()
159
170
 
160
171
  assert.equal(instruction.keys.length, 27);
161
172
  assert.equal(instruction.keys.at(-3)?.isWritable, false);
162
- assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
173
+ assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), BUYBACK_FEE_RECIPIENTS[0].toBase58());
163
174
  assert.equal(instruction.keys.at(-1)?.isWritable, true);
164
175
  });
165
176
 
@@ -178,7 +189,7 @@ test("amm sell places poolV2 before fee recipient pair for non-cashback coins",
178
189
 
179
190
  assert.equal(instruction.keys.length, 24);
180
191
  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());
192
+ assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), BUYBACK_FEE_RECIPIENTS[0].toBase58());
182
193
  assert.equal(instruction.keys.at(-1)?.isWritable, true);
183
194
  });
184
195
 
@@ -197,10 +208,10 @@ test("amm sell places cashback accounts before poolV2 and fee recipient tail", (
197
208
 
198
209
  assert.equal(instruction.keys.length, 26);
199
210
  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());
211
+ assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), BUYBACK_FEE_RECIPIENTS[0].toBase58());
201
212
  assert.equal(instruction.keys.at(-1)?.pubkey.toBase58(), getAssociatedTokenAddressSync(
202
213
  SOL_MINT,
203
- poolInfo.globalConfig.protocolFeeRecipients[0],
214
+ BUYBACK_FEE_RECIPIENTS[0],
204
215
  true,
205
216
  TOKEN_PROGRAM_ID,
206
217
  ASSOCIATED_TOKEN_PROGRAM_ID