four-flap-meme-sdk 1.3.92 → 1.3.94

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.
@@ -15,11 +15,22 @@ import type { DlmmPoolInfo, DlmmSwapParams, DlmmAddLiquidityParams, DlmmRemoveLi
15
15
  export declare function getDlmmPoolInfo(poolAddress: string, connection?: Connection): Promise<DlmmPoolInfo>;
16
16
  /**
17
17
  * 通过 Token Mint 查找 DLMM 池
18
+ * 优化: 使用 Meteora API 替代全量链上查询
18
19
  * @param tokenXMint Token X Mint 地址
19
20
  * @param tokenYMint Token Y Mint 地址
20
- * @param connection 可选连接
21
+ * @param connection 可选连接(备用)
22
+ */
23
+ export declare function findDlmmPool(tokenXMint: string, tokenYMint: string, _connection?: Connection): Promise<string | null>;
24
+ /**
25
+ * 通过代币地址获取所有相关的 DLMM 池子
26
+ * 使用 Meteora API,不再全量查询链上数据
21
27
  */
22
- export declare function findDlmmPool(tokenXMint: string, tokenYMint: string, connection?: Connection): Promise<string | null>;
28
+ export declare function findDlmmPoolsByToken(tokenMint: string): Promise<Array<{
29
+ address: string;
30
+ tokenX: string;
31
+ tokenY: string;
32
+ binStep: number;
33
+ }>>;
23
34
  /**
24
35
  * 签名 DLMM Swap 交易
25
36
  * @param params Swap 参数
@@ -7,7 +7,7 @@
7
7
  import { PublicKey, Keypair, Transaction, } from '@solana/web3.js';
8
8
  import BN from 'bn.js';
9
9
  import { StrategyType, ActivationType } from '@meteora-ag/dlmm';
10
- import { getConnection, getDlmmPool, getAllDlmmPools } from './client.js';
10
+ import { getConnection, getDlmmPool } from './client.js';
11
11
  // ============================================================================
12
12
  // 池信息查询
13
13
  // ============================================================================
@@ -34,27 +34,105 @@ export async function getDlmmPoolInfo(poolAddress, connection) {
34
34
  }
35
35
  /**
36
36
  * 通过 Token Mint 查找 DLMM 池
37
+ * 优化: 使用 Meteora API 替代全量链上查询
37
38
  * @param tokenXMint Token X Mint 地址
38
39
  * @param tokenYMint Token Y Mint 地址
39
- * @param connection 可选连接
40
+ * @param connection 可选连接(备用)
40
41
  */
41
- export async function findDlmmPool(tokenXMint, tokenYMint, connection) {
42
+ export async function findDlmmPool(tokenXMint, tokenYMint, _connection) {
42
43
  try {
43
- const lbPairs = await getAllDlmmPools(connection);
44
- const matchingPair = lbPairs.find((pair) => {
45
- const pairTokenX = pair.account.tokenXMint.toBase58();
46
- const pairTokenY = pair.account.tokenYMint.toBase58();
47
- return ((pairTokenX === tokenXMint && pairTokenY === tokenYMint) ||
48
- (pairTokenX === tokenYMint && pairTokenY === tokenXMint));
49
- });
50
- if (matchingPair) {
51
- return matchingPair.publicKey.toBase58();
44
+ // 方法1: 使用 Meteora API 查询 (推荐,速度快)
45
+ const apiResult = await findDlmmPoolByApi(tokenXMint, tokenYMint);
46
+ if (apiResult) {
47
+ return apiResult;
52
48
  }
49
+ // 方法2: 如果 API 失败,尝试计算 PDA(某些池子可能有固定 PDA)
50
+ // 注意:DLMM 池子使用 seeds = [tokenX, tokenY, binStep] 计算 PDA
51
+ // 但 binStep 不确定,所以这里不做 PDA 计算
52
+ return null;
53
53
  }
54
54
  catch (e) {
55
55
  console.error('查找 DLMM 池失败:', e);
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * 通过 Meteora API 查找 DLMM 池
61
+ * API 文档: https://dlmm-api.meteora.ag/swagger-ui/
62
+ */
63
+ async function findDlmmPoolByApi(tokenXMint, tokenYMint) {
64
+ try {
65
+ // 使用 Meteora 官方 API 查询池子
66
+ const response = await fetch(`https://dlmm-api.meteora.ag/pair/all_by_groups?include_token_mints=${tokenXMint},${tokenYMint}`, {
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ },
70
+ });
71
+ if (!response.ok) {
72
+ console.warn('[Meteora API] Request failed:', response.status);
73
+ return null;
74
+ }
75
+ const data = await response.json();
76
+ // 查找匹配的池子
77
+ if (data?.groups) {
78
+ for (const group of data.groups) {
79
+ if (group.pairs) {
80
+ for (const pair of group.pairs) {
81
+ const mintX = pair.mint_x;
82
+ const mintY = pair.mint_y;
83
+ if ((mintX === tokenXMint && mintY === tokenYMint) ||
84
+ (mintX === tokenYMint && mintY === tokenXMint)) {
85
+ return pair.address;
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ catch (e) {
94
+ console.warn('[Meteora API] 查询失败:', e);
95
+ return null;
96
+ }
97
+ }
98
+ /**
99
+ * 通过代币地址获取所有相关的 DLMM 池子
100
+ * 使用 Meteora API,不再全量查询链上数据
101
+ */
102
+ export async function findDlmmPoolsByToken(tokenMint) {
103
+ try {
104
+ const response = await fetch(`https://dlmm-api.meteora.ag/pair/all_by_groups?include_token_mints=${tokenMint}`, {
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ },
108
+ });
109
+ if (!response.ok) {
110
+ return [];
111
+ }
112
+ const data = await response.json();
113
+ const pools = [];
114
+ if (data?.groups) {
115
+ for (const group of data.groups) {
116
+ if (group.pairs) {
117
+ for (const pair of group.pairs) {
118
+ if (pair.mint_x === tokenMint || pair.mint_y === tokenMint) {
119
+ pools.push({
120
+ address: pair.address,
121
+ tokenX: pair.mint_x,
122
+ tokenY: pair.mint_y,
123
+ binStep: pair.bin_step || 0,
124
+ });
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ return pools;
131
+ }
132
+ catch (e) {
133
+ console.warn('[Meteora API] 查询池子列表失败:', e);
134
+ return [];
56
135
  }
57
- return null;
58
136
  }
59
137
  // ============================================================================
60
138
  // Swap
@@ -41,8 +41,8 @@ async function loadPumpSdk() {
41
41
  /** 懒加载 Meteora SDK */
42
42
  async function loadMeteoraSdk() {
43
43
  const { findDbcPoolByBaseMint, getDbcPoolInfo } = await import('../dex/meteora/dbc.js');
44
- const { findDlmmPool, getDlmmPoolInfo } = await import('../dex/meteora/dlmm.js');
45
- return { findDbcPoolByBaseMint, getDbcPoolInfo, findDlmmPool, getDlmmPoolInfo };
44
+ const { findDlmmPool, getDlmmPoolInfo, findDlmmPoolsByToken } = await import('../dex/meteora/dlmm.js');
45
+ return { findDbcPoolByBaseMint, getDbcPoolInfo, findDlmmPool, getDlmmPoolInfo, findDlmmPoolsByToken };
46
46
  }
47
47
  /** 懒加载 Orca SDK */
48
48
  async function loadOrcaSdk() {
@@ -295,25 +295,38 @@ async function detectPumpSwapPool(mint, connection, debug) {
295
295
  }
296
296
  /**
297
297
  * 检测 Meteora DLMM Pool
298
+ * 优化: 使用 Meteora API 替代全量链上查询
298
299
  */
299
300
  async function detectMeteoraDlmm(mint, connection, debug) {
300
301
  try {
301
- const { findDlmmPool, getDlmmPoolInfo } = await loadMeteoraSdk();
302
- // 尝试与 SOL 配对
303
- const poolAddress = await findDlmmPool(mint, WSOL, connection);
304
- if (!poolAddress)
302
+ const { findDlmmPoolsByToken, getDlmmPoolInfo } = await loadMeteoraSdk();
303
+ // 使用 API 查询所有包含该代币的池子
304
+ const pools = await findDlmmPoolsByToken(mint);
305
+ if (pools.length === 0) {
306
+ if (debug)
307
+ console.log('[LP Inspect] No Meteora DLMM pools found for mint:', mint);
305
308
  return null;
306
- const poolInfo = await getDlmmPoolInfo(poolAddress, connection);
309
+ }
310
+ // 优先选择与 SOL 配对的池子
311
+ let selectedPool = pools.find(p => p.tokenX === WSOL || p.tokenY === WSOL);
312
+ if (!selectedPool) {
313
+ selectedPool = pools[0]; // 如果没有 SOL 池子,选择第一个
314
+ }
315
+ if (debug)
316
+ console.log('[LP Inspect] Found Meteora DLMM pool:', selectedPool.address);
317
+ // 获取池子详细信息
318
+ const poolInfo = await getDlmmPoolInfo(selectedPool.address, connection);
307
319
  // 判断哪个是 base token
308
320
  const isTokenX = poolInfo.tokenXMint === mint;
309
321
  const reserveQuote = isTokenX ? poolInfo.tokenYReserve : poolInfo.tokenXReserve;
310
322
  const reserveBase = isTokenX ? poolInfo.tokenXReserve : poolInfo.tokenYReserve;
323
+ const quoteToken = isTokenX ? poolInfo.tokenYMint : poolInfo.tokenXMint;
311
324
  return {
312
325
  platform: 'METEORA_DLMM',
313
- poolAddress,
314
- quoteToken: isTokenX ? poolInfo.tokenYMint : poolInfo.tokenXMint,
315
- quoteSymbol: 'SOL',
316
- quoteDecimals: 9,
326
+ poolAddress: selectedPool.address,
327
+ quoteToken,
328
+ quoteSymbol: quoteToken === WSOL ? 'SOL' : 'UNKNOWN',
329
+ quoteDecimals: quoteToken === WSOL ? 9 : 6,
317
330
  baseToken: mint,
318
331
  reserveQuote: reserveQuote || '0',
319
332
  reserveQuoteRaw: BigInt(reserveQuote || 0),
@@ -437,26 +450,94 @@ async function detectRaydiumPools(mint, connection, debug) {
437
450
  }
438
451
  // 尝试从链上获取实时数据(支持 CPMM, AMM V4, CLMM)
439
452
  let onChainPrice;
453
+ let usedOnChainData = false;
440
454
  try {
441
- const onChainData = await fetchRaydiumPoolOnChain(connection, pool.id, programId, debug);
455
+ const onChainData = await fetchRaydiumPoolOnChain(connection, pool.id, programId, debug); // 添加类型断言以访问额外属性
442
456
  if (onChainData) {
443
- // 使用链上数据更新储备量
444
- if (isBaseA) {
445
- reserveQuote = onChainData.reserveB;
446
- reserveBase = onChainData.reserveA;
457
+ usedOnChainData = true;
458
+ // CLMM 需要特殊处理:合约中 token 按地址排序
459
+ if (platform === 'RAYDIUM_CLMM' && onChainData.mintA && onChainData.mintB) {
460
+ // 合约中:mintA 是地址较小的 token,mintB 是地址较大的 token
461
+ // sqrtPriceX64ToPrice 返回 mintB/mintA
462
+ // 判断我们查询的代币在合约中的位置
463
+ const queryMintIsA = onChainData.mintA === mint;
464
+ const queryMintIsB = onChainData.mintB === mint;
465
+ if (queryMintIsA) {
466
+ // 查询的代币是 mintA(地址较小)
467
+ // sqrtPrice 返回 mintB/mintA = quote/base
468
+ reserveBase = onChainData.reserveA;
469
+ reserveQuote = onChainData.reserveB;
470
+ // 我们需要 quote/base 价格,sqrtPrice 已经是这个
471
+ onChainPrice = onChainData.price;
472
+ }
473
+ else if (queryMintIsB) {
474
+ // 查询的代币是 mintB(地址较大)
475
+ // sqrtPrice 返回 mintB/mintA = base/quote(反了)
476
+ reserveBase = onChainData.reserveB;
477
+ reserveQuote = onChainData.reserveA;
478
+ // 我们需要 quote/base = 1 / (base/quote) = 1 / sqrtPrice
479
+ onChainPrice = 1 / onChainData.price;
480
+ }
481
+ else {
482
+ // mint 不在池子中,跳过
483
+ if (debug)
484
+ console.log('[LP Inspect] CLMM: mint not found in pool');
485
+ }
486
+ if (debug) {
487
+ console.log('[LP Inspect] CLMM 链上数据:', {
488
+ poolId: pool.id,
489
+ contractMintA: onChainData.mintA,
490
+ contractMintB: onChainData.mintB,
491
+ queryMint: mint,
492
+ queryMintIsA,
493
+ queryMintIsB,
494
+ rawPrice: onChainData.price,
495
+ adjustedPrice: onChainPrice,
496
+ reserveQuote,
497
+ reserveBase,
498
+ });
499
+ }
447
500
  }
448
501
  else {
449
- reserveQuote = onChainData.reserveA;
450
- reserveBase = onChainData.reserveB;
502
+ // CPMM 和 AMM V4:使用原有逻辑
503
+ if (isBaseA) {
504
+ reserveQuote = onChainData.reserveB;
505
+ reserveBase = onChainData.reserveA;
506
+ }
507
+ else {
508
+ reserveQuote = onChainData.reserveA;
509
+ reserveBase = onChainData.reserveB;
510
+ }
511
+ // price 已经是 reserveB/reserveA
512
+ onChainPrice = isBaseA ? onChainData.price : (onChainData.price ? 1 / onChainData.price : undefined);
513
+ }
514
+ if (debug) {
515
+ console.log('[LP Inspect] 链上数据 (实时):', {
516
+ poolId: pool.id,
517
+ platform,
518
+ reserveQuote,
519
+ reserveBase,
520
+ onChainPrice,
521
+ });
451
522
  }
452
- onChainPrice = onChainData.price;
453
- if (debug)
454
- console.log('[LP Inspect] Got on-chain data:', { reserveQuote, reserveBase, price: onChainPrice });
455
523
  }
456
524
  }
457
525
  catch (e) {
458
526
  if (debug)
459
- console.log('[LP Inspect] Failed to get on-chain data, using API data:', e);
527
+ console.log('[LP Inspect] 链上数据获取失败,使用 API 数据:', e);
528
+ }
529
+ // 计算最终价格:优先使用链上价格,否则用 reserve 比例
530
+ let finalPrice;
531
+ if (onChainPrice !== undefined) {
532
+ finalPrice = onChainPrice;
533
+ }
534
+ else if (reserveBase > 0 && reserveQuote > 0) {
535
+ // 用储备量比例计算: SOL/代币
536
+ finalPrice = reserveQuote / reserveBase;
537
+ }
538
+ else {
539
+ // API 返回的价格是 代币/SOL,需要取倒数
540
+ finalPrice = pool.price ? (1 / pool.price) : undefined;
460
541
  }
461
542
  pools.push({
462
543
  platform,
@@ -469,14 +550,15 @@ async function detectRaydiumPools(mint, connection, debug) {
469
550
  reserveQuoteRaw: BigInt(Math.floor((reserveQuote || 0) * Math.pow(10, quoteDecimals || 9))),
470
551
  reserveBase: String(reserveBase || 0),
471
552
  reserveBaseRaw: BigInt(Math.floor((reserveBase || 0) * Math.pow(10, baseDecimals || 6))),
472
- price: onChainPrice ?? pool.price, // 优先使用链上价格
553
+ price: finalPrice,
473
554
  feeBps: Math.floor((pool.feeRate || 0) * 10000),
474
555
  extra: {
475
556
  type: pool.type,
476
557
  programId,
477
558
  tvl: pool.tvl,
478
559
  lpMint: pool.lpMint?.address,
479
- onChainData: onChainPrice !== undefined, // 标记是否使用了链上数据
560
+ onChainData: usedOnChainData,
561
+ apiPrice: pool.price, // 保留 API 价格用于对比
480
562
  },
481
563
  });
482
564
  }
@@ -583,7 +665,9 @@ async function fetchRaydiumPoolOnChain(connection, poolId, programId, debug) {
583
665
  try {
584
666
  const poolInfo = PoolInfoLayout.decode(data);
585
667
  // CLMM 使用 sqrtPriceX64 计算价格
586
- const price = SqrtPriceMath.sqrtPriceX64ToPrice(poolInfo.sqrtPriceX64, poolInfo.mintDecimalsA, poolInfo.mintDecimalsB);
668
+ // 注意:合约中 token 是按地址排序的 (token_mint_0 < token_mint_1)
669
+ // sqrtPriceX64ToPrice 返回 token_1 / token_0
670
+ const priceFromSqrt = SqrtPriceMath.sqrtPriceX64ToPrice(poolInfo.sqrtPriceX64, poolInfo.mintDecimalsA, poolInfo.mintDecimalsB);
587
671
  // CLMM 的储备量需要从 vault 读取
588
672
  const [vaultAInfo, vaultBInfo] = await connection.getMultipleAccountsInfo([
589
673
  poolInfo.vaultA,
@@ -596,12 +680,32 @@ async function fetchRaydiumPoolOnChain(connection, poolId, programId, debug) {
596
680
  reserveA = Number(vaultAData.amount) / Math.pow(10, poolInfo.mintDecimalsA);
597
681
  reserveB = Number(vaultBData.amount) / Math.pow(10, poolInfo.mintDecimalsB);
598
682
  }
683
+ // 获取 token mint 地址来判断 token 顺序
684
+ // poolInfo.mintA 是地址较小的 token (token_mint_0)
685
+ // poolInfo.mintB 是地址较大的 token (token_mint_1)
686
+ const mintA = poolInfo.mintA?.toBase58?.() || '';
687
+ const mintB = poolInfo.mintB?.toBase58?.() || '';
688
+ if (debug) {
689
+ console.log('[LP Inspect] CLMM pool data:', {
690
+ mintA,
691
+ mintB,
692
+ reserveA,
693
+ reserveB,
694
+ sqrtPriceX64: poolInfo.sqrtPriceX64?.toString(),
695
+ priceFromSqrt: priceFromSqrt.toNumber(),
696
+ });
697
+ }
599
698
  return {
600
699
  reserveA,
601
700
  reserveB,
602
701
  decimalsA: poolInfo.mintDecimalsA,
603
702
  decimalsB: poolInfo.mintDecimalsB,
604
- price: price.toNumber(),
703
+ // 返回 token_1/token_0 的价格
704
+ // detectRaydiumPools 会根据查询的代币位置来决定是否取倒数
705
+ price: priceFromSqrt.toNumber(),
706
+ // 额外返回 mint 信息,便于 detectRaydiumPools 判断
707
+ mintA,
708
+ mintB,
605
709
  };
606
710
  }
607
711
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.3.92",
3
+ "version": "1.3.94",
4
4
  "description": "SDK for Flap bonding curve, four.meme TokenManager, and Solana DEX",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",