four-flap-meme-sdk 1.3.92 → 1.3.93
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/clients/blockrazor.js +0 -1
- package/dist/contracts/tm-bundle-merkle/config.d.ts +5 -0
- package/dist/contracts/tm-bundle-merkle/config.js +10 -0
- package/dist/contracts/tm-bundle-merkle/core.js +92 -24
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +103 -54
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +0 -1
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +36 -6
- package/dist/contracts/tm-bundle-merkle/swap.d.ts +0 -3
- package/dist/contracts/tm-bundle-merkle/swap.js +59 -6
- package/dist/flap/portal-bundle-merkle/config.d.ts +8 -0
- package/dist/flap/portal-bundle-merkle/config.js +17 -0
- package/dist/flap/portal-bundle-merkle/core.js +120 -68
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
- package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +136 -78
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +0 -2
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +49 -30
- package/dist/flap/portal-bundle-merkle/swap.d.ts +0 -2
- package/dist/flap/portal-bundle-merkle/swap.js +75 -47
- package/dist/flap/portal-bundle-merkle/types.d.ts +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/pancake/bundle-buy-first.d.ts +1 -1
- package/dist/pancake/bundle-buy-first.js +49 -17
- package/dist/pancake/bundle-swap.d.ts +1 -4
- package/dist/pancake/bundle-swap.js +98 -33
- package/dist/utils/erc20.d.ts +108 -2
- package/dist/utils/erc20.js +65 -17
- package/package.json +4 -39
- package/dist/sol/constants.d.ts +0 -126
- package/dist/sol/constants.js +0 -145
- package/dist/sol/dex/index.d.ts +0 -8
- package/dist/sol/dex/index.js +0 -12
- package/dist/sol/dex/meteora/client.d.ts +0 -76
- package/dist/sol/dex/meteora/client.js +0 -219
- package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +0 -61
- package/dist/sol/dex/meteora/damm-v1-bundle.js +0 -112
- package/dist/sol/dex/meteora/damm-v1.d.ts +0 -118
- package/dist/sol/dex/meteora/damm-v1.js +0 -315
- package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +0 -82
- package/dist/sol/dex/meteora/damm-v2-bundle.js +0 -242
- package/dist/sol/dex/meteora/damm-v2.d.ts +0 -172
- package/dist/sol/dex/meteora/damm-v2.js +0 -632
- package/dist/sol/dex/meteora/dbc-bundle.d.ts +0 -123
- package/dist/sol/dex/meteora/dbc-bundle.js +0 -304
- package/dist/sol/dex/meteora/dbc.d.ts +0 -192
- package/dist/sol/dex/meteora/dbc.js +0 -619
- package/dist/sol/dex/meteora/dlmm-bundle.d.ts +0 -39
- package/dist/sol/dex/meteora/dlmm-bundle.js +0 -189
- package/dist/sol/dex/meteora/dlmm.d.ts +0 -146
- package/dist/sol/dex/meteora/dlmm.js +0 -593
- package/dist/sol/dex/meteora/index.d.ts +0 -25
- package/dist/sol/dex/meteora/index.js +0 -65
- package/dist/sol/dex/meteora/types.d.ts +0 -787
- package/dist/sol/dex/meteora/types.js +0 -110
- package/dist/sol/dex/orca/index.d.ts +0 -10
- package/dist/sol/dex/orca/index.js +0 -16
- package/dist/sol/dex/orca/orca-bundle.d.ts +0 -41
- package/dist/sol/dex/orca/orca-bundle.js +0 -173
- package/dist/sol/dex/orca/orca.d.ts +0 -65
- package/dist/sol/dex/orca/orca.js +0 -474
- package/dist/sol/dex/orca/types.d.ts +0 -263
- package/dist/sol/dex/orca/types.js +0 -38
- package/dist/sol/dex/orca/wavebreak-bundle.d.ts +0 -34
- package/dist/sol/dex/orca/wavebreak-bundle.js +0 -198
- package/dist/sol/dex/orca/wavebreak-types.d.ts +0 -227
- package/dist/sol/dex/orca/wavebreak-types.js +0 -23
- package/dist/sol/dex/orca/wavebreak.d.ts +0 -78
- package/dist/sol/dex/orca/wavebreak.js +0 -497
- package/dist/sol/dex/pump/index.d.ts +0 -9
- package/dist/sol/dex/pump/index.js +0 -14
- package/dist/sol/dex/pump/pump-bundle.d.ts +0 -92
- package/dist/sol/dex/pump/pump-bundle.js +0 -383
- package/dist/sol/dex/pump/pump-swap-bundle.d.ts +0 -103
- package/dist/sol/dex/pump/pump-swap-bundle.js +0 -380
- package/dist/sol/dex/pump/pump-swap.d.ts +0 -46
- package/dist/sol/dex/pump/pump-swap.js +0 -199
- package/dist/sol/dex/pump/pump.d.ts +0 -35
- package/dist/sol/dex/pump/pump.js +0 -352
- package/dist/sol/dex/pump/types.d.ts +0 -215
- package/dist/sol/dex/pump/types.js +0 -5
- package/dist/sol/dex/raydium/index.d.ts +0 -8
- package/dist/sol/dex/raydium/index.js +0 -12
- package/dist/sol/dex/raydium/launchlab.d.ts +0 -68
- package/dist/sol/dex/raydium/launchlab.js +0 -210
- package/dist/sol/dex/raydium/raydium-bundle.d.ts +0 -64
- package/dist/sol/dex/raydium/raydium-bundle.js +0 -324
- package/dist/sol/dex/raydium/raydium.d.ts +0 -40
- package/dist/sol/dex/raydium/raydium.js +0 -366
- package/dist/sol/dex/raydium/types.d.ts +0 -240
- package/dist/sol/dex/raydium/types.js +0 -5
- package/dist/sol/index.d.ts +0 -10
- package/dist/sol/index.js +0 -16
- package/dist/sol/jito/bundle.d.ts +0 -90
- package/dist/sol/jito/bundle.js +0 -263
- package/dist/sol/jito/index.d.ts +0 -7
- package/dist/sol/jito/index.js +0 -7
- package/dist/sol/jito/tip.d.ts +0 -51
- package/dist/sol/jito/tip.js +0 -83
- package/dist/sol/jito/types.d.ts +0 -100
- package/dist/sol/jito/types.js +0 -5
- package/dist/sol/token/create-complete.d.ts +0 -115
- package/dist/sol/token/create-complete.js +0 -235
- package/dist/sol/token/create-token.d.ts +0 -57
- package/dist/sol/token/create-token.js +0 -230
- package/dist/sol/token/index.d.ts +0 -9
- package/dist/sol/token/index.js +0 -14
- package/dist/sol/token/metadata-upload.d.ts +0 -86
- package/dist/sol/token/metadata-upload.js +0 -173
- package/dist/sol/token/metadata.d.ts +0 -92
- package/dist/sol/token/metadata.js +0 -274
- package/dist/sol/token/types.d.ts +0 -153
- package/dist/sol/token/types.js +0 -5
- package/dist/sol/types.d.ts +0 -176
- package/dist/sol/types.js +0 -7
- package/dist/sol/utils/balance.d.ts +0 -160
- package/dist/sol/utils/balance.js +0 -638
- package/dist/sol/utils/connection.d.ts +0 -78
- package/dist/sol/utils/connection.js +0 -168
- package/dist/sol/utils/index.d.ts +0 -9
- package/dist/sol/utils/index.js +0 -9
- package/dist/sol/utils/lp-inspect.d.ts +0 -129
- package/dist/sol/utils/lp-inspect.js +0 -796
- package/dist/sol/utils/transfer.d.ts +0 -125
- package/dist/sol/utils/transfer.js +0 -220
- package/dist/sol/utils/wallet.d.ts +0 -107
- package/dist/sol/utils/wallet.js +0 -210
|
@@ -1,796 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Solana LP 检测工具
|
|
3
|
-
* 自动判断代币在哪个平台(内盘/外盘)
|
|
4
|
-
* @module sol/utils/lp-inspect
|
|
5
|
-
*/
|
|
6
|
-
import { PublicKey } from '@solana/web3.js';
|
|
7
|
-
import { createConnection } from './connection.js';
|
|
8
|
-
// ============================================================================
|
|
9
|
-
// 常量
|
|
10
|
-
// ============================================================================
|
|
11
|
-
/** Wrapped SOL */
|
|
12
|
-
const WSOL = 'So11111111111111111111111111111111111111112';
|
|
13
|
-
/** 常用 Quote Token */
|
|
14
|
-
const QUOTE_TOKENS = {
|
|
15
|
-
[WSOL]: { symbol: 'SOL', decimals: 9 },
|
|
16
|
-
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': { symbol: 'USDC', decimals: 6 },
|
|
17
|
-
'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': { symbol: 'USDT', decimals: 6 },
|
|
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
|
-
};
|
|
32
|
-
// ============================================================================
|
|
33
|
-
// 动态导入(避免循环依赖和类型冲突)
|
|
34
|
-
// ============================================================================
|
|
35
|
-
/** 懒加载 Pump.fun SDK */
|
|
36
|
-
async function loadPumpSdk() {
|
|
37
|
-
const { getBondingCurveInfo } = await import('../dex/pump/pump.js');
|
|
38
|
-
const { getPumpSwapPoolInfo } = await import('../dex/pump/pump-swap.js');
|
|
39
|
-
return { getBondingCurveInfo, getPumpSwapPoolInfo };
|
|
40
|
-
}
|
|
41
|
-
/** 懒加载 Meteora SDK */
|
|
42
|
-
async function loadMeteoraSdk() {
|
|
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 };
|
|
46
|
-
}
|
|
47
|
-
/** 懒加载 Orca SDK */
|
|
48
|
-
async function loadOrcaSdk() {
|
|
49
|
-
try {
|
|
50
|
-
const { getWavebreakBondingCurveInfo } = await import('../dex/orca/wavebreak.js');
|
|
51
|
-
const { findOrcaPool, getOrcaPoolInfo } = await import('../dex/orca/orca.js');
|
|
52
|
-
return { getWavebreakBondingCurveInfo, findOrcaPool, getOrcaPoolInfo };
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/** 懒加载 Raydium SDK */
|
|
59
|
-
async function loadRaydiumSdk() {
|
|
60
|
-
try {
|
|
61
|
-
const { initRaydium, getRaydiumPoolInfo } = await import('../dex/raydium/raydium.js');
|
|
62
|
-
return { initRaydium, getRaydiumPoolInfo };
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// ============================================================================
|
|
69
|
-
// 内盘检测
|
|
70
|
-
// ============================================================================
|
|
71
|
-
/**
|
|
72
|
-
* 检测 Pump.fun Bonding Curve
|
|
73
|
-
*/
|
|
74
|
-
async function detectPumpBondingCurve(mint, connection, debug) {
|
|
75
|
-
try {
|
|
76
|
-
const { getBondingCurveInfo } = await loadPumpSdk();
|
|
77
|
-
const info = await getBondingCurveInfo(connection, mint);
|
|
78
|
-
if (!info)
|
|
79
|
-
return null;
|
|
80
|
-
// 计算价格 (virtualSolReserves / virtualTokenReserves)
|
|
81
|
-
const price = Number(info.virtualSolReserves) / Number(info.virtualTokenReserves);
|
|
82
|
-
// 计算进度 (realSolReserves / 85 SOL threshold)
|
|
83
|
-
const GRADUATION_THRESHOLD = 85000000000n; // 85 SOL in lamports
|
|
84
|
-
const progress = Math.min(100, Number(info.realSolReserves * 100n / GRADUATION_THRESHOLD));
|
|
85
|
-
return {
|
|
86
|
-
type: 'pump',
|
|
87
|
-
address: info.bondingCurve,
|
|
88
|
-
complete: info.complete,
|
|
89
|
-
reserveQuote: (Number(info.virtualSolReserves) / 1e9).toFixed(4),
|
|
90
|
-
reserveQuoteRaw: info.virtualSolReserves,
|
|
91
|
-
reserveToken: (Number(info.virtualTokenReserves) / 1e6).toFixed(0),
|
|
92
|
-
reserveTokenRaw: info.virtualTokenReserves,
|
|
93
|
-
price,
|
|
94
|
-
progress,
|
|
95
|
-
creator: info.creator,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
if (debug)
|
|
100
|
-
console.log('[LP Inspect] Pump bonding curve check failed:', err);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* 检测 Meteora DBC
|
|
106
|
-
*/
|
|
107
|
-
async function detectMeteoraDbc(mint, connection, debug) {
|
|
108
|
-
try {
|
|
109
|
-
const { findDbcPoolByBaseMint, getDbcPoolInfo } = await loadMeteoraSdk();
|
|
110
|
-
const poolAddress = await findDbcPoolByBaseMint(mint, connection);
|
|
111
|
-
if (!poolAddress)
|
|
112
|
-
return null;
|
|
113
|
-
const poolInfo = await getDbcPoolInfo(poolAddress, connection);
|
|
114
|
-
return {
|
|
115
|
-
type: 'meteora_dbc',
|
|
116
|
-
address: poolAddress,
|
|
117
|
-
complete: poolInfo.isMigrated,
|
|
118
|
-
reserveQuote: poolInfo.quoteReserve,
|
|
119
|
-
reserveQuoteRaw: BigInt(poolInfo.quoteReserve),
|
|
120
|
-
reserveToken: poolInfo.baseReserve,
|
|
121
|
-
reserveTokenRaw: BigInt(poolInfo.baseReserve),
|
|
122
|
-
price: poolInfo.currentPrice,
|
|
123
|
-
progress: poolInfo.progressPercent,
|
|
124
|
-
creator: poolInfo.creator,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
if (debug)
|
|
129
|
-
console.log('[LP Inspect] Meteora DBC check failed:', err);
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* 检测 Orca Wavebreak
|
|
135
|
-
*/
|
|
136
|
-
async function detectOrcaWavebreak(mint, connection, debug) {
|
|
137
|
-
try {
|
|
138
|
-
const sdk = await loadOrcaSdk();
|
|
139
|
-
if (!sdk)
|
|
140
|
-
return null;
|
|
141
|
-
const { getWavebreakBondingCurveInfo } = sdk;
|
|
142
|
-
const info = await getWavebreakBondingCurveInfo(mint, connection);
|
|
143
|
-
if (!info)
|
|
144
|
-
return null;
|
|
145
|
-
return {
|
|
146
|
-
type: 'orca_wavebreak',
|
|
147
|
-
address: info.bondingCurveAddress,
|
|
148
|
-
complete: info.isGraduated,
|
|
149
|
-
reserveQuote: (Number(info.quoteAmount) / 1e9).toFixed(4),
|
|
150
|
-
reserveQuoteRaw: info.quoteAmount,
|
|
151
|
-
reserveToken: info.baseAmount.toString(),
|
|
152
|
-
reserveTokenRaw: info.baseAmount,
|
|
153
|
-
price: info.currentPrice,
|
|
154
|
-
progress: info.progressPercent,
|
|
155
|
-
creator: info.creator,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
catch (err) {
|
|
159
|
-
if (debug)
|
|
160
|
-
console.log('[LP Inspect] Orca Wavebreak check failed:', err);
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* 检测 Raydium LaunchLab
|
|
166
|
-
* 使用 Raydium SDK 的官方 layout 解析链上数据
|
|
167
|
-
*/
|
|
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
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// ============================================================================
|
|
259
|
-
// 外盘检测
|
|
260
|
-
// ============================================================================
|
|
261
|
-
/**
|
|
262
|
-
* 检测 Pump.fun Swap Pool
|
|
263
|
-
*/
|
|
264
|
-
async function detectPumpSwapPool(mint, connection, debug) {
|
|
265
|
-
try {
|
|
266
|
-
const { getPumpSwapPoolInfo } = await loadPumpSdk();
|
|
267
|
-
const info = await getPumpSwapPoolInfo(connection, mint);
|
|
268
|
-
if (!info)
|
|
269
|
-
return null;
|
|
270
|
-
// 获取池子余额
|
|
271
|
-
const poolPubkey = new PublicKey(info.poolAddress);
|
|
272
|
-
const balance = await connection.getBalance(poolPubkey);
|
|
273
|
-
return {
|
|
274
|
-
platform: 'PUMP_SWAP',
|
|
275
|
-
poolAddress: info.poolAddress,
|
|
276
|
-
quoteToken: info.quoteMint,
|
|
277
|
-
quoteSymbol: 'SOL',
|
|
278
|
-
quoteDecimals: 9,
|
|
279
|
-
baseToken: info.baseMint,
|
|
280
|
-
reserveQuote: (balance / 1e9).toFixed(4),
|
|
281
|
-
reserveQuoteRaw: BigInt(balance),
|
|
282
|
-
reserveBase: info.lpSupply.toString(),
|
|
283
|
-
reserveBaseRaw: info.lpSupply,
|
|
284
|
-
extra: {
|
|
285
|
-
lpMint: info.lpMint,
|
|
286
|
-
creator: info.creator,
|
|
287
|
-
},
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
catch (err) {
|
|
291
|
-
if (debug)
|
|
292
|
-
console.log('[LP Inspect] Pump Swap pool check failed:', err);
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* 检测 Meteora DLMM Pool
|
|
298
|
-
*/
|
|
299
|
-
async function detectMeteoraDlmm(mint, connection, debug) {
|
|
300
|
-
try {
|
|
301
|
-
const { findDlmmPool, getDlmmPoolInfo } = await loadMeteoraSdk();
|
|
302
|
-
// 尝试与 SOL 配对
|
|
303
|
-
const poolAddress = await findDlmmPool(mint, WSOL, connection);
|
|
304
|
-
if (!poolAddress)
|
|
305
|
-
return null;
|
|
306
|
-
const poolInfo = await getDlmmPoolInfo(poolAddress, connection);
|
|
307
|
-
// 判断哪个是 base token
|
|
308
|
-
const isTokenX = poolInfo.tokenXMint === mint;
|
|
309
|
-
const reserveQuote = isTokenX ? poolInfo.tokenYReserve : poolInfo.tokenXReserve;
|
|
310
|
-
const reserveBase = isTokenX ? poolInfo.tokenXReserve : poolInfo.tokenYReserve;
|
|
311
|
-
return {
|
|
312
|
-
platform: 'METEORA_DLMM',
|
|
313
|
-
poolAddress,
|
|
314
|
-
quoteToken: isTokenX ? poolInfo.tokenYMint : poolInfo.tokenXMint,
|
|
315
|
-
quoteSymbol: 'SOL',
|
|
316
|
-
quoteDecimals: 9,
|
|
317
|
-
baseToken: mint,
|
|
318
|
-
reserveQuote: reserveQuote || '0',
|
|
319
|
-
reserveQuoteRaw: BigInt(reserveQuote || 0),
|
|
320
|
-
reserveBase: reserveBase || '0',
|
|
321
|
-
reserveBaseRaw: BigInt(reserveBase || 0),
|
|
322
|
-
price: poolInfo.currentPrice,
|
|
323
|
-
feeBps: poolInfo.feeBps,
|
|
324
|
-
extra: {
|
|
325
|
-
binStep: poolInfo.binStep,
|
|
326
|
-
activeId: poolInfo.activeId,
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
catch (err) {
|
|
331
|
-
if (debug)
|
|
332
|
-
console.log('[LP Inspect] Meteora DLMM check failed:', err);
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* 检测 Orca Whirlpool
|
|
338
|
-
*/
|
|
339
|
-
async function detectOrcaWhirlpool(mint, connection, debug) {
|
|
340
|
-
try {
|
|
341
|
-
const sdk = await loadOrcaSdk();
|
|
342
|
-
if (!sdk)
|
|
343
|
-
return null;
|
|
344
|
-
const { findOrcaPool, getOrcaPoolInfo } = sdk;
|
|
345
|
-
// 尝试与 SOL 配对(常见的 tick spacing: 64, 128)
|
|
346
|
-
let poolAddress = await findOrcaPool(mint, WSOL, 64, connection);
|
|
347
|
-
if (!poolAddress) {
|
|
348
|
-
poolAddress = await findOrcaPool(mint, WSOL, 128, connection);
|
|
349
|
-
}
|
|
350
|
-
if (!poolAddress)
|
|
351
|
-
return null;
|
|
352
|
-
const poolInfo = await getOrcaPoolInfo(poolAddress, connection);
|
|
353
|
-
// 判断哪个是 base token
|
|
354
|
-
const isTokenA = poolInfo.tokenMintA === mint;
|
|
355
|
-
return {
|
|
356
|
-
platform: 'ORCA_WHIRLPOOL',
|
|
357
|
-
poolAddress,
|
|
358
|
-
quoteToken: isTokenA ? poolInfo.tokenMintB : poolInfo.tokenMintA,
|
|
359
|
-
quoteSymbol: 'SOL',
|
|
360
|
-
quoteDecimals: isTokenA ? poolInfo.tokenDecimalsB : poolInfo.tokenDecimalsA,
|
|
361
|
-
baseToken: mint,
|
|
362
|
-
reserveQuote: '0', // Whirlpool 需要通过 vault 查询
|
|
363
|
-
reserveQuoteRaw: 0n,
|
|
364
|
-
reserveBase: '0',
|
|
365
|
-
reserveBaseRaw: 0n,
|
|
366
|
-
price: poolInfo.price,
|
|
367
|
-
feeBps: poolInfo.feeRate,
|
|
368
|
-
extra: {
|
|
369
|
-
tickSpacing: poolInfo.tickSpacing,
|
|
370
|
-
currentTick: poolInfo.currentTick,
|
|
371
|
-
liquidity: poolInfo.liquidity,
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
catch (err) {
|
|
376
|
-
if (debug)
|
|
377
|
-
console.log('[LP Inspect] Orca Whirlpool check failed:', err);
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* 检测 Raydium 池子(混合方案)
|
|
383
|
-
* 1. 先用 API 快速发现池子列表
|
|
384
|
-
* 2. 再用链上查询获取实时储备量
|
|
385
|
-
*/
|
|
386
|
-
async function detectRaydiumPools(mint, connection, debug) {
|
|
387
|
-
const pools = [];
|
|
388
|
-
try {
|
|
389
|
-
// ==================== 步骤 1: 用 API 发现池子 ====================
|
|
390
|
-
const apiUrl = `https://api-v3.raydium.io/pools/info/mint?mint1=${mint}&poolType=all&poolSortField=default&sortType=desc&pageSize=20&page=1`;
|
|
391
|
-
const response = await fetch(apiUrl);
|
|
392
|
-
if (!response.ok) {
|
|
393
|
-
if (debug)
|
|
394
|
-
console.log('[LP Inspect] Raydium API request failed:', response.status);
|
|
395
|
-
return pools;
|
|
396
|
-
}
|
|
397
|
-
const result = await response.json();
|
|
398
|
-
if (!result.success || !result.data?.data) {
|
|
399
|
-
if (debug)
|
|
400
|
-
console.log('[LP Inspect] Raydium API returned no data');
|
|
401
|
-
return pools;
|
|
402
|
-
}
|
|
403
|
-
const poolArray = result.data.data;
|
|
404
|
-
if (!Array.isArray(poolArray) || poolArray.length === 0)
|
|
405
|
-
return pools;
|
|
406
|
-
if (debug)
|
|
407
|
-
console.log('[LP Inspect] Raydium API found', poolArray.length, 'pools');
|
|
408
|
-
// ==================== 步骤 2: 链上查询实时数据 ====================
|
|
409
|
-
for (const pool of poolArray) {
|
|
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
|
-
});
|
|
482
|
-
}
|
|
483
|
-
catch (poolErr) {
|
|
484
|
-
if (debug)
|
|
485
|
-
console.log('[LP Inspect] Error processing pool:', pool.id, poolErr);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
catch (err) {
|
|
490
|
-
if (debug)
|
|
491
|
-
console.log('[LP Inspect] Raydium pools check failed:', err);
|
|
492
|
-
}
|
|
493
|
-
return pools;
|
|
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
|
-
}
|
|
621
|
-
// ============================================================================
|
|
622
|
-
// 主函数
|
|
623
|
-
// ============================================================================
|
|
624
|
-
/**
|
|
625
|
-
* 检测 Solana 代币的 LP 所在平台
|
|
626
|
-
*
|
|
627
|
-
* @param mint 代币 Mint 地址
|
|
628
|
-
* @param options 检测选项
|
|
629
|
-
* @returns LP 信息
|
|
630
|
-
*
|
|
631
|
-
* @example
|
|
632
|
-
* ```typescript
|
|
633
|
-
* const lpInfo = await inspectSolanaTokenLP('TokenMintAddress...', {
|
|
634
|
-
* rpcUrl: 'https://api.mainnet-beta.solana.com',
|
|
635
|
-
* debug: true,
|
|
636
|
-
* })
|
|
637
|
-
*
|
|
638
|
-
* if (lpInfo.platform === 'PUMP_BONDING_CURVE') {
|
|
639
|
-
* console.log('代币在 Pump.fun 内盘')
|
|
640
|
-
* console.log('毕业进度:', lpInfo.bondingCurve?.progress)
|
|
641
|
-
* } else if (lpInfo.bestPool) {
|
|
642
|
-
* console.log('最佳池子:', lpInfo.bestPool.platform)
|
|
643
|
-
* console.log('流动性:', lpInfo.bestPool.reserveQuote)
|
|
644
|
-
* }
|
|
645
|
-
* ```
|
|
646
|
-
*/
|
|
647
|
-
export async function inspectSolanaTokenLP(mint, options = {}) {
|
|
648
|
-
const startTime = Date.now();
|
|
649
|
-
if (!options.connection && !options.rpcUrl) {
|
|
650
|
-
throw new Error('Either connection or rpcUrl is required in options');
|
|
651
|
-
}
|
|
652
|
-
const connection = options.connection || createConnection(options.rpcUrl);
|
|
653
|
-
const debug = options.debug;
|
|
654
|
-
// 初始化结果
|
|
655
|
-
const result = {
|
|
656
|
-
mint,
|
|
657
|
-
platform: 'UNKNOWN',
|
|
658
|
-
pools: [],
|
|
659
|
-
};
|
|
660
|
-
// 获取代币信息
|
|
661
|
-
try {
|
|
662
|
-
const mintPubkey = new PublicKey(mint);
|
|
663
|
-
const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
|
|
664
|
-
if (mintInfo.value?.data && 'parsed' in mintInfo.value.data) {
|
|
665
|
-
const parsed = mintInfo.value.data.parsed;
|
|
666
|
-
const decimals = parsed.info.decimals;
|
|
667
|
-
result.decimals = decimals;
|
|
668
|
-
result.totalSupplyRaw = BigInt(parsed.info.supply);
|
|
669
|
-
result.totalSupply = (Number(result.totalSupplyRaw) / Math.pow(10, decimals)).toString();
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
catch (err) {
|
|
673
|
-
if (debug)
|
|
674
|
-
console.log('[LP Inspect] Failed to get mint info:', err);
|
|
675
|
-
}
|
|
676
|
-
// ========================================
|
|
677
|
-
// 1. 内盘检测(并行)
|
|
678
|
-
// ========================================
|
|
679
|
-
if (!options.skipBondingCurve) {
|
|
680
|
-
const bondingCurveResults = await Promise.allSettled([
|
|
681
|
-
detectPumpBondingCurve(mint, connection, debug),
|
|
682
|
-
detectMeteoraDbc(mint, connection, debug),
|
|
683
|
-
detectOrcaWavebreak(mint, connection, debug),
|
|
684
|
-
detectRaydiumLaunchLab(mint, connection, debug),
|
|
685
|
-
]);
|
|
686
|
-
// 查找第一个有效的内盘
|
|
687
|
-
for (const r of bondingCurveResults) {
|
|
688
|
-
if (r.status === 'fulfilled' && r.value && !r.value.complete) {
|
|
689
|
-
result.bondingCurve = r.value;
|
|
690
|
-
// 设置平台类型
|
|
691
|
-
switch (r.value.type) {
|
|
692
|
-
case 'pump':
|
|
693
|
-
result.platform = 'PUMP_BONDING_CURVE';
|
|
694
|
-
break;
|
|
695
|
-
case 'meteora_dbc':
|
|
696
|
-
result.platform = 'METEORA_DBC';
|
|
697
|
-
break;
|
|
698
|
-
case 'orca_wavebreak':
|
|
699
|
-
result.platform = 'ORCA_WAVEBREAK';
|
|
700
|
-
break;
|
|
701
|
-
case 'raydium_launchlab':
|
|
702
|
-
result.platform = 'RAYDIUM_LAUNCHLAB';
|
|
703
|
-
break;
|
|
704
|
-
}
|
|
705
|
-
result.elapsed = Date.now() - startTime;
|
|
706
|
-
return result;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
// ========================================
|
|
711
|
-
// 2. 外盘检测(并行)
|
|
712
|
-
// ========================================
|
|
713
|
-
if (!options.skipPools) {
|
|
714
|
-
const poolResults = await Promise.allSettled([
|
|
715
|
-
detectPumpSwapPool(mint, connection, debug),
|
|
716
|
-
detectMeteoraDlmm(mint, connection, debug),
|
|
717
|
-
detectOrcaWhirlpool(mint, connection, debug),
|
|
718
|
-
detectRaydiumPools(mint, connection, debug),
|
|
719
|
-
]);
|
|
720
|
-
// 收集所有有效的池子
|
|
721
|
-
for (const r of poolResults) {
|
|
722
|
-
if (r.status === 'fulfilled' && r.value) {
|
|
723
|
-
if (Array.isArray(r.value)) {
|
|
724
|
-
result.pools.push(...r.value);
|
|
725
|
-
}
|
|
726
|
-
else {
|
|
727
|
-
result.pools.push(r.value);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
// 按流动性排序
|
|
732
|
-
result.pools.sort((a, b) => {
|
|
733
|
-
const liquidityA = Number(a.reserveQuoteRaw);
|
|
734
|
-
const liquidityB = Number(b.reserveQuoteRaw);
|
|
735
|
-
return liquidityB - liquidityA;
|
|
736
|
-
});
|
|
737
|
-
// 设置最佳池子
|
|
738
|
-
if (result.pools.length > 0) {
|
|
739
|
-
result.bestPool = result.pools[0];
|
|
740
|
-
// 设置平台类型
|
|
741
|
-
if (result.pools.length > 1) {
|
|
742
|
-
result.platform = 'MULTI_POOL';
|
|
743
|
-
}
|
|
744
|
-
else {
|
|
745
|
-
result.platform = result.bestPool.platform;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
result.elapsed = Date.now() - startTime;
|
|
750
|
-
return result;
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* 快速检测代币是否在内盘
|
|
754
|
-
*
|
|
755
|
-
* @param mint 代币 Mint 地址
|
|
756
|
-
* @param options 检测选项
|
|
757
|
-
* @returns 如果在内盘返回内盘信息,否则返回 null
|
|
758
|
-
*/
|
|
759
|
-
export async function detectBondingCurve(mint, options = {}) {
|
|
760
|
-
if (!options.connection && !options.rpcUrl) {
|
|
761
|
-
throw new Error('Either connection or rpcUrl is required in options');
|
|
762
|
-
}
|
|
763
|
-
const connection = options.connection || createConnection(options.rpcUrl);
|
|
764
|
-
const debug = options.debug;
|
|
765
|
-
// 并行检测所有内盘
|
|
766
|
-
const results = await Promise.allSettled([
|
|
767
|
-
detectPumpBondingCurve(mint, connection, debug),
|
|
768
|
-
detectMeteoraDbc(mint, connection, debug),
|
|
769
|
-
detectOrcaWavebreak(mint, connection, debug),
|
|
770
|
-
detectRaydiumLaunchLab(mint, connection, debug),
|
|
771
|
-
]);
|
|
772
|
-
// 返回第一个未毕业的内盘
|
|
773
|
-
for (const r of results) {
|
|
774
|
-
if (r.status === 'fulfilled' && r.value && !r.value.complete) {
|
|
775
|
-
return r.value;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
return null;
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* 判断代币是否已毕业(不在任何内盘)
|
|
782
|
-
*/
|
|
783
|
-
export async function isTokenGraduated(mint, options = {}) {
|
|
784
|
-
const bondingCurve = await detectBondingCurve(mint, options);
|
|
785
|
-
return bondingCurve === null;
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* 获取代币的最佳交易池
|
|
789
|
-
*/
|
|
790
|
-
export async function getBestPool(mint, options = {}) {
|
|
791
|
-
const lpInfo = await inspectSolanaTokenLP(mint, {
|
|
792
|
-
...options,
|
|
793
|
-
skipBondingCurve: true,
|
|
794
|
-
});
|
|
795
|
-
return lpInfo.bestPool || null;
|
|
796
|
-
}
|