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.
- package/dist/sol/dex/meteora/dlmm.d.ts +13 -2
- package/dist/sol/dex/meteora/dlmm.js +91 -13
- package/dist/sol/utils/lp-inspect.js +130 -26
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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,
|
|
42
|
+
export async function findDlmmPool(tokenXMint, tokenYMint, _connection) {
|
|
42
43
|
try {
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
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 {
|
|
302
|
-
//
|
|
303
|
-
const
|
|
304
|
-
if (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
450
|
-
|
|
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]
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|