four-flap-meme-sdk 1.3.91 → 1.3.92
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/utils/lp-inspect.js +311 -44
- package/package.json +1 -1
|
@@ -16,6 +16,19 @@ const QUOTE_TOKENS = {
|
|
|
16
16
|
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': { symbol: 'USDC', decimals: 6 },
|
|
17
17
|
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': { symbol: 'USDT', decimals: 6 },
|
|
18
18
|
};
|
|
19
|
+
/** Raydium 程序地址 */
|
|
20
|
+
const RAYDIUM_PROGRAMS = {
|
|
21
|
+
/** LaunchLab */
|
|
22
|
+
LAUNCHLAB: 'LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj',
|
|
23
|
+
/** CPMM (Constant Product) */
|
|
24
|
+
CPMM: 'CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C',
|
|
25
|
+
/** Legacy AMM v4 */
|
|
26
|
+
AMM_V4: '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8',
|
|
27
|
+
/** Stable Swap AMM */
|
|
28
|
+
STABLE_SWAP: '5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h',
|
|
29
|
+
/** CLMM (Concentrated Liquidity) */
|
|
30
|
+
CLMM: 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK',
|
|
31
|
+
};
|
|
19
32
|
// ============================================================================
|
|
20
33
|
// 动态导入(避免循环依赖和类型冲突)
|
|
21
34
|
// ============================================================================
|
|
@@ -150,12 +163,97 @@ async function detectOrcaWavebreak(mint, connection, debug) {
|
|
|
150
163
|
}
|
|
151
164
|
/**
|
|
152
165
|
* 检测 Raydium LaunchLab
|
|
153
|
-
*
|
|
166
|
+
* 使用 Raydium SDK 的官方 layout 解析链上数据
|
|
154
167
|
*/
|
|
155
|
-
async function detectRaydiumLaunchLab(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
async function detectRaydiumLaunchLab(mint, connection, debug) {
|
|
169
|
+
try {
|
|
170
|
+
// 动态导入 Raydium SDK
|
|
171
|
+
const { LaunchpadPool, LaunchpadConfig, getPdaLaunchpadPoolId, LAUNCHPAD_PROGRAM, Curve, } = await import('@raydium-io/raydium-sdk-v2');
|
|
172
|
+
const { NATIVE_MINT } = await import('@solana/spl-token');
|
|
173
|
+
const mintA = new PublicKey(mint);
|
|
174
|
+
const mintB = NATIVE_MINT; // 默认与 SOL 配对
|
|
175
|
+
// 获取池子 PDA
|
|
176
|
+
const poolId = getPdaLaunchpadPoolId(LAUNCHPAD_PROGRAM, mintA, mintB).publicKey;
|
|
177
|
+
// 获取池子账户数据
|
|
178
|
+
const poolAccountInfo = await connection.getAccountInfo(poolId);
|
|
179
|
+
if (!poolAccountInfo) {
|
|
180
|
+
if (debug)
|
|
181
|
+
console.log('[LP Inspect] LaunchLab pool not found for mint:', mint);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
// 验证程序 ID
|
|
185
|
+
if (poolAccountInfo.owner.toBase58() !== LAUNCHPAD_PROGRAM.toBase58()) {
|
|
186
|
+
if (debug)
|
|
187
|
+
console.log('[LP Inspect] Pool owner is not LaunchLab program');
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
// 解码池子数据
|
|
191
|
+
const poolInfo = LaunchpadPool.decode(poolAccountInfo.data);
|
|
192
|
+
// 获取配置信息
|
|
193
|
+
const configAccountInfo = await connection.getAccountInfo(poolInfo.configId);
|
|
194
|
+
if (!configAccountInfo) {
|
|
195
|
+
if (debug)
|
|
196
|
+
console.log('[LP Inspect] LaunchLab config not found');
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const configInfo = LaunchpadConfig.decode(configAccountInfo.data);
|
|
200
|
+
// 计算当前价格
|
|
201
|
+
const currentPrice = Curve.getPrice({
|
|
202
|
+
poolInfo,
|
|
203
|
+
curveType: configInfo.curveType,
|
|
204
|
+
decimalA: poolInfo.mintDecimalsA,
|
|
205
|
+
decimalB: poolInfo.mintDecimalsB,
|
|
206
|
+
}).toNumber();
|
|
207
|
+
// 计算毕业进度
|
|
208
|
+
let progress = 0;
|
|
209
|
+
try {
|
|
210
|
+
const initPrice = Curve.getPoolInitPriceByPool({
|
|
211
|
+
poolInfo,
|
|
212
|
+
decimalA: poolInfo.mintDecimalsA,
|
|
213
|
+
decimalB: poolInfo.mintDecimalsB,
|
|
214
|
+
curveType: configInfo.curveType,
|
|
215
|
+
}).toNumber();
|
|
216
|
+
const endPrice = Curve.getPoolEndPriceReal({
|
|
217
|
+
poolInfo,
|
|
218
|
+
curveType: configInfo.curveType,
|
|
219
|
+
decimalA: poolInfo.mintDecimalsA,
|
|
220
|
+
decimalB: poolInfo.mintDecimalsB,
|
|
221
|
+
}).toNumber();
|
|
222
|
+
const priceDiff = currentPrice - initPrice;
|
|
223
|
+
const totalDiff = endPrice - initPrice;
|
|
224
|
+
progress = totalDiff === 0 ? 0 : Math.min((priceDiff / totalDiff) * 100, 100);
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
if (debug)
|
|
228
|
+
console.log('[LP Inspect] Failed to calculate LaunchLab progress:', e);
|
|
229
|
+
}
|
|
230
|
+
// 判断是否已毕业(迁移到 AMM/CPMM)
|
|
231
|
+
// 当池子状态变为已迁移或 progress >= 100 时为已完成
|
|
232
|
+
const isMigrated = poolInfo.status !== undefined && poolInfo.status >= 2; // status: 0=active, 1=waiting, 2+=migrated
|
|
233
|
+
// 计算储备量
|
|
234
|
+
// poolInfo 中包含 totalSellA (已卖出的代币) 和 totalFundRaisingB (已筹集的 SOL)
|
|
235
|
+
const reserveQuote = Number(poolInfo.totalFundRaisingB || 0) / Math.pow(10, poolInfo.mintDecimalsB);
|
|
236
|
+
const totalSupply = Number(poolInfo.supply || 0) / Math.pow(10, poolInfo.mintDecimalsA);
|
|
237
|
+
const soldAmount = Number(poolInfo.totalSellA || 0) / Math.pow(10, poolInfo.mintDecimalsA);
|
|
238
|
+
const reserveToken = totalSupply - soldAmount;
|
|
239
|
+
return {
|
|
240
|
+
type: 'raydium_launchlab',
|
|
241
|
+
address: poolId.toBase58(),
|
|
242
|
+
complete: isMigrated || progress >= 100,
|
|
243
|
+
reserveQuote: reserveQuote.toFixed(4),
|
|
244
|
+
reserveQuoteRaw: BigInt(poolInfo.totalFundRaisingB?.toString() || '0'),
|
|
245
|
+
reserveToken: reserveToken.toFixed(4),
|
|
246
|
+
reserveTokenRaw: BigInt(Math.floor(reserveToken * Math.pow(10, poolInfo.mintDecimalsA))),
|
|
247
|
+
price: currentPrice,
|
|
248
|
+
progress,
|
|
249
|
+
creator: poolInfo.creator?.toBase58(),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
if (debug)
|
|
254
|
+
console.log('[LP Inspect] Raydium LaunchLab check failed:', err?.message || err);
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
159
257
|
}
|
|
160
258
|
// ============================================================================
|
|
161
259
|
// 外盘检测
|
|
@@ -281,13 +379,14 @@ async function detectOrcaWhirlpool(mint, connection, debug) {
|
|
|
281
379
|
}
|
|
282
380
|
}
|
|
283
381
|
/**
|
|
284
|
-
* 检测 Raydium
|
|
285
|
-
*
|
|
382
|
+
* 检测 Raydium 池子(混合方案)
|
|
383
|
+
* 1. 先用 API 快速发现池子列表
|
|
384
|
+
* 2. 再用链上查询获取实时储备量
|
|
286
385
|
*/
|
|
287
386
|
async function detectRaydiumPools(mint, connection, debug) {
|
|
288
387
|
const pools = [];
|
|
289
388
|
try {
|
|
290
|
-
//
|
|
389
|
+
// ==================== 步骤 1: 用 API 发现池子 ====================
|
|
291
390
|
const apiUrl = `https://api-v3.raydium.io/pools/info/mint?mint1=${mint}&poolType=all&poolSortField=default&sortType=desc&pageSize=20&page=1`;
|
|
292
391
|
const response = await fetch(apiUrl);
|
|
293
392
|
if (!response.ok) {
|
|
@@ -302,47 +401,89 @@ async function detectRaydiumPools(mint, connection, debug) {
|
|
|
302
401
|
return pools;
|
|
303
402
|
}
|
|
304
403
|
const poolArray = result.data.data;
|
|
305
|
-
if (!Array.isArray(poolArray))
|
|
404
|
+
if (!Array.isArray(poolArray) || poolArray.length === 0)
|
|
306
405
|
return pools;
|
|
307
406
|
if (debug)
|
|
308
|
-
console.log('[LP Inspect] Raydium found', poolArray.length, 'pools');
|
|
407
|
+
console.log('[LP Inspect] Raydium API found', poolArray.length, 'pools');
|
|
408
|
+
// ==================== 步骤 2: 链上查询实时数据 ====================
|
|
309
409
|
for (const pool of poolArray) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
410
|
+
try {
|
|
411
|
+
// 判断池子类型和程序
|
|
412
|
+
let platform = 'RAYDIUM_AMM';
|
|
413
|
+
const programId = pool.programId;
|
|
414
|
+
if (programId === RAYDIUM_PROGRAMS.CPMM) {
|
|
415
|
+
platform = 'RAYDIUM_CPMM';
|
|
416
|
+
}
|
|
417
|
+
else if (programId === RAYDIUM_PROGRAMS.CLMM) {
|
|
418
|
+
platform = 'RAYDIUM_CLMM';
|
|
419
|
+
}
|
|
420
|
+
else if (programId === RAYDIUM_PROGRAMS.AMM_V4) {
|
|
421
|
+
platform = 'RAYDIUM_AMM';
|
|
422
|
+
}
|
|
423
|
+
// 判断哪个是 base token
|
|
424
|
+
const isBaseA = pool.mintA?.address === mint;
|
|
425
|
+
const quoteToken = isBaseA ? pool.mintB?.address : pool.mintA?.address;
|
|
426
|
+
const quoteSymbol = isBaseA ? pool.mintB?.symbol : pool.mintA?.symbol;
|
|
427
|
+
const quoteDecimals = isBaseA ? pool.mintB?.decimals : pool.mintA?.decimals;
|
|
428
|
+
const baseDecimals = isBaseA ? pool.mintA?.decimals : pool.mintB?.decimals;
|
|
429
|
+
if (!quoteToken)
|
|
430
|
+
continue;
|
|
431
|
+
// 尝试从链上获取实时储备量
|
|
432
|
+
let reserveQuote = pool.mintAmountA;
|
|
433
|
+
let reserveBase = pool.mintAmountB;
|
|
434
|
+
if (isBaseA) {
|
|
435
|
+
reserveQuote = pool.mintAmountB;
|
|
436
|
+
reserveBase = pool.mintAmountA;
|
|
437
|
+
}
|
|
438
|
+
// 尝试从链上获取实时数据(支持 CPMM, AMM V4, CLMM)
|
|
439
|
+
let onChainPrice;
|
|
440
|
+
try {
|
|
441
|
+
const onChainData = await fetchRaydiumPoolOnChain(connection, pool.id, programId, debug);
|
|
442
|
+
if (onChainData) {
|
|
443
|
+
// 使用链上数据更新储备量
|
|
444
|
+
if (isBaseA) {
|
|
445
|
+
reserveQuote = onChainData.reserveB;
|
|
446
|
+
reserveBase = onChainData.reserveA;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
reserveQuote = onChainData.reserveA;
|
|
450
|
+
reserveBase = onChainData.reserveB;
|
|
451
|
+
}
|
|
452
|
+
onChainPrice = onChainData.price;
|
|
453
|
+
if (debug)
|
|
454
|
+
console.log('[LP Inspect] Got on-chain data:', { reserveQuote, reserveBase, price: onChainPrice });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch (e) {
|
|
458
|
+
if (debug)
|
|
459
|
+
console.log('[LP Inspect] Failed to get on-chain data, using API data:', e);
|
|
460
|
+
}
|
|
461
|
+
pools.push({
|
|
462
|
+
platform,
|
|
463
|
+
poolAddress: pool.id,
|
|
464
|
+
quoteToken: quoteToken || '',
|
|
465
|
+
quoteSymbol: quoteSymbol || 'UNKNOWN',
|
|
466
|
+
quoteDecimals: quoteDecimals || 9,
|
|
467
|
+
baseToken: mint,
|
|
468
|
+
reserveQuote: String(reserveQuote || 0),
|
|
469
|
+
reserveQuoteRaw: BigInt(Math.floor((reserveQuote || 0) * Math.pow(10, quoteDecimals || 9))),
|
|
470
|
+
reserveBase: String(reserveBase || 0),
|
|
471
|
+
reserveBaseRaw: BigInt(Math.floor((reserveBase || 0) * Math.pow(10, baseDecimals || 6))),
|
|
472
|
+
price: onChainPrice ?? pool.price, // 优先使用链上价格
|
|
473
|
+
feeBps: Math.floor((pool.feeRate || 0) * 10000),
|
|
474
|
+
extra: {
|
|
475
|
+
type: pool.type,
|
|
476
|
+
programId,
|
|
477
|
+
tvl: pool.tvl,
|
|
478
|
+
lpMint: pool.lpMint?.address,
|
|
479
|
+
onChainData: onChainPrice !== undefined, // 标记是否使用了链上数据
|
|
480
|
+
},
|
|
481
|
+
});
|
|
314
482
|
}
|
|
315
|
-
|
|
316
|
-
|
|
483
|
+
catch (poolErr) {
|
|
484
|
+
if (debug)
|
|
485
|
+
console.log('[LP Inspect] Error processing pool:', pool.id, poolErr);
|
|
317
486
|
}
|
|
318
|
-
// 判断哪个是 base token
|
|
319
|
-
const isBaseA = pool.mintA?.address === mint;
|
|
320
|
-
const quoteToken = isBaseA ? pool.mintB?.address : pool.mintA?.address;
|
|
321
|
-
const quoteSymbol = isBaseA ? pool.mintB?.symbol : pool.mintA?.symbol;
|
|
322
|
-
const quoteDecimals = isBaseA ? pool.mintB?.decimals : pool.mintA?.decimals;
|
|
323
|
-
const reserveQuoteNum = isBaseA ? pool.mintAmountB : pool.mintAmountA;
|
|
324
|
-
const reserveBaseNum = isBaseA ? pool.mintAmountA : pool.mintAmountB;
|
|
325
|
-
if (!quoteToken)
|
|
326
|
-
continue;
|
|
327
|
-
pools.push({
|
|
328
|
-
platform,
|
|
329
|
-
poolAddress: pool.id,
|
|
330
|
-
quoteToken: quoteToken || '',
|
|
331
|
-
quoteSymbol: quoteSymbol || 'UNKNOWN',
|
|
332
|
-
quoteDecimals: quoteDecimals || 9,
|
|
333
|
-
baseToken: mint,
|
|
334
|
-
reserveQuote: String(reserveQuoteNum || 0),
|
|
335
|
-
reserveQuoteRaw: BigInt(Math.floor((reserveQuoteNum || 0) * Math.pow(10, quoteDecimals || 9))),
|
|
336
|
-
reserveBase: String(reserveBaseNum || 0),
|
|
337
|
-
reserveBaseRaw: BigInt(Math.floor((reserveBaseNum || 0) * Math.pow(10, pool.mintB?.decimals || 6))),
|
|
338
|
-
price: pool.price,
|
|
339
|
-
feeBps: Math.floor((pool.feeRate || 0) * 10000),
|
|
340
|
-
extra: {
|
|
341
|
-
type: pool.type,
|
|
342
|
-
tvl: pool.tvl,
|
|
343
|
-
lpMint: pool.lpMint?.address,
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
487
|
}
|
|
347
488
|
}
|
|
348
489
|
catch (err) {
|
|
@@ -351,6 +492,132 @@ async function detectRaydiumPools(mint, connection, debug) {
|
|
|
351
492
|
}
|
|
352
493
|
return pools;
|
|
353
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* 从链上获取 Raydium 池子的实时数据
|
|
497
|
+
* 使用 Raydium SDK 的官方 layout 解析
|
|
498
|
+
*/
|
|
499
|
+
async function fetchRaydiumPoolOnChain(connection, poolId, programId, debug) {
|
|
500
|
+
try {
|
|
501
|
+
// 动态导入 Raydium SDK 的 layout
|
|
502
|
+
const { liquidityStateV4Layout, CpmmPoolInfoLayout, PoolInfoLayout, splAccountLayout, SqrtPriceMath, } = await import('@raydium-io/raydium-sdk-v2');
|
|
503
|
+
const poolPubkey = new PublicKey(poolId);
|
|
504
|
+
const accountInfo = await connection.getAccountInfo(poolPubkey);
|
|
505
|
+
if (!accountInfo) {
|
|
506
|
+
if (debug)
|
|
507
|
+
console.log('[LP Inspect] Pool account not found:', poolId);
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
// 验证程序 ID
|
|
511
|
+
if (accountInfo.owner.toBase58() !== programId) {
|
|
512
|
+
if (debug)
|
|
513
|
+
console.log('[LP Inspect] Pool owner mismatch');
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
const data = accountInfo.data;
|
|
517
|
+
// ==================== CPMM 池子 ====================
|
|
518
|
+
if (programId === RAYDIUM_PROGRAMS.CPMM) {
|
|
519
|
+
try {
|
|
520
|
+
const poolInfo = CpmmPoolInfoLayout.decode(data);
|
|
521
|
+
// 读取 vault 账户余额
|
|
522
|
+
const [vaultAInfo, vaultBInfo] = await connection.getMultipleAccountsInfo([
|
|
523
|
+
poolInfo.vaultA,
|
|
524
|
+
poolInfo.vaultB,
|
|
525
|
+
]);
|
|
526
|
+
if (!vaultAInfo || !vaultBInfo)
|
|
527
|
+
return null;
|
|
528
|
+
const vaultAData = splAccountLayout.decode(vaultAInfo.data);
|
|
529
|
+
const vaultBData = splAccountLayout.decode(vaultBInfo.data);
|
|
530
|
+
// 计算实际储备 (扣除手续费)
|
|
531
|
+
const reserveA = Number(vaultAData.amount
|
|
532
|
+
.sub(poolInfo.fundFeesMintA)
|
|
533
|
+
.sub(poolInfo.protocolFeesMintA)) / Math.pow(10, poolInfo.mintDecimalA);
|
|
534
|
+
const reserveB = Number(vaultBData.amount
|
|
535
|
+
.sub(poolInfo.fundFeesMintB)
|
|
536
|
+
.sub(poolInfo.protocolFeesMintB)) / Math.pow(10, poolInfo.mintDecimalB);
|
|
537
|
+
return {
|
|
538
|
+
reserveA,
|
|
539
|
+
reserveB,
|
|
540
|
+
decimalsA: poolInfo.mintDecimalA,
|
|
541
|
+
decimalsB: poolInfo.mintDecimalB,
|
|
542
|
+
price: reserveB / reserveA,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
if (debug)
|
|
547
|
+
console.log('[LP Inspect] CPMM decode error:', e);
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// ==================== AMM V4 池子 ====================
|
|
552
|
+
else if (programId === RAYDIUM_PROGRAMS.AMM_V4) {
|
|
553
|
+
try {
|
|
554
|
+
const poolInfo = liquidityStateV4Layout.decode(data);
|
|
555
|
+
// 读取 vault 账户余额
|
|
556
|
+
const [baseVaultInfo, quoteVaultInfo] = await connection.getMultipleAccountsInfo([
|
|
557
|
+
poolInfo.baseVault,
|
|
558
|
+
poolInfo.quoteVault,
|
|
559
|
+
]);
|
|
560
|
+
if (!baseVaultInfo || !quoteVaultInfo)
|
|
561
|
+
return null;
|
|
562
|
+
const baseVaultData = splAccountLayout.decode(baseVaultInfo.data);
|
|
563
|
+
const quoteVaultData = splAccountLayout.decode(quoteVaultInfo.data);
|
|
564
|
+
// 计算实际储备 (扣除待提取的 PnL)
|
|
565
|
+
const reserveA = Number(baseVaultData.amount.sub(poolInfo.baseNeedTakePnl)) / Math.pow(10, poolInfo.baseDecimal.toNumber());
|
|
566
|
+
const reserveB = Number(quoteVaultData.amount.sub(poolInfo.quoteNeedTakePnl)) / Math.pow(10, poolInfo.quoteDecimal.toNumber());
|
|
567
|
+
return {
|
|
568
|
+
reserveA,
|
|
569
|
+
reserveB,
|
|
570
|
+
decimalsA: poolInfo.baseDecimal.toNumber(),
|
|
571
|
+
decimalsB: poolInfo.quoteDecimal.toNumber(),
|
|
572
|
+
price: reserveB / reserveA,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
catch (e) {
|
|
576
|
+
if (debug)
|
|
577
|
+
console.log('[LP Inspect] AMM V4 decode error:', e);
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// ==================== CLMM 池子 ====================
|
|
582
|
+
else if (programId === RAYDIUM_PROGRAMS.CLMM) {
|
|
583
|
+
try {
|
|
584
|
+
const poolInfo = PoolInfoLayout.decode(data);
|
|
585
|
+
// CLMM 使用 sqrtPriceX64 计算价格
|
|
586
|
+
const price = SqrtPriceMath.sqrtPriceX64ToPrice(poolInfo.sqrtPriceX64, poolInfo.mintDecimalsA, poolInfo.mintDecimalsB);
|
|
587
|
+
// CLMM 的储备量需要从 vault 读取
|
|
588
|
+
const [vaultAInfo, vaultBInfo] = await connection.getMultipleAccountsInfo([
|
|
589
|
+
poolInfo.vaultA,
|
|
590
|
+
poolInfo.vaultB,
|
|
591
|
+
]);
|
|
592
|
+
let reserveA = 0, reserveB = 0;
|
|
593
|
+
if (vaultAInfo && vaultBInfo) {
|
|
594
|
+
const vaultAData = splAccountLayout.decode(vaultAInfo.data);
|
|
595
|
+
const vaultBData = splAccountLayout.decode(vaultBInfo.data);
|
|
596
|
+
reserveA = Number(vaultAData.amount) / Math.pow(10, poolInfo.mintDecimalsA);
|
|
597
|
+
reserveB = Number(vaultBData.amount) / Math.pow(10, poolInfo.mintDecimalsB);
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
reserveA,
|
|
601
|
+
reserveB,
|
|
602
|
+
decimalsA: poolInfo.mintDecimalsA,
|
|
603
|
+
decimalsB: poolInfo.mintDecimalsB,
|
|
604
|
+
price: price.toNumber(),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
catch (e) {
|
|
608
|
+
if (debug)
|
|
609
|
+
console.log('[LP Inspect] CLMM decode error:', e);
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
catch (e) {
|
|
616
|
+
if (debug)
|
|
617
|
+
console.log('[LP Inspect] On-chain fetch error:', e);
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
354
621
|
// ============================================================================
|
|
355
622
|
// 主函数
|
|
356
623
|
// ============================================================================
|