four-flap-meme-sdk 1.3.89 → 1.3.90
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 +1 -0
- package/dist/contracts/tm-bundle-merkle/core.js +3 -6
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +38 -30
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.d.ts +1 -0
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +3 -4
- package/dist/contracts/tm-bundle-merkle/swap.d.ts +3 -0
- package/dist/contracts/tm-bundle-merkle/swap.js +2 -2
- package/dist/flap/portal-bundle-merkle/core.js +6 -2
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +35 -55
- package/dist/flap/portal-bundle-merkle/swap-buy-first.d.ts +2 -0
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +11 -6
- package/dist/flap/portal-bundle-merkle/swap.d.ts +2 -0
- package/dist/flap/portal-bundle-merkle/swap.js +22 -10
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/pancake/bundle-buy-first.d.ts +1 -0
- package/dist/pancake/bundle-buy-first.js +9 -4
- package/dist/pancake/bundle-swap.d.ts +4 -0
- package/dist/pancake/bundle-swap.js +14 -7
- package/dist/sol/constants.d.ts +126 -0
- package/dist/sol/constants.js +145 -0
- package/dist/sol/dex/index.d.ts +8 -0
- package/dist/sol/dex/index.js +12 -0
- package/dist/sol/dex/meteora/client.d.ts +76 -0
- package/dist/sol/dex/meteora/client.js +219 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +61 -0
- package/dist/sol/dex/meteora/damm-v1-bundle.js +112 -0
- package/dist/sol/dex/meteora/damm-v1.d.ts +118 -0
- package/dist/sol/dex/meteora/damm-v1.js +315 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +82 -0
- package/dist/sol/dex/meteora/damm-v2-bundle.js +242 -0
- package/dist/sol/dex/meteora/damm-v2.d.ts +172 -0
- package/dist/sol/dex/meteora/damm-v2.js +632 -0
- package/dist/sol/dex/meteora/dbc-bundle.d.ts +123 -0
- package/dist/sol/dex/meteora/dbc-bundle.js +304 -0
- package/dist/sol/dex/meteora/dbc.d.ts +192 -0
- package/dist/sol/dex/meteora/dbc.js +619 -0
- package/dist/sol/dex/meteora/dlmm-bundle.d.ts +39 -0
- package/dist/sol/dex/meteora/dlmm-bundle.js +189 -0
- package/dist/sol/dex/meteora/dlmm.d.ts +146 -0
- package/dist/sol/dex/meteora/dlmm.js +593 -0
- package/dist/sol/dex/meteora/index.d.ts +25 -0
- package/dist/sol/dex/meteora/index.js +65 -0
- package/dist/sol/dex/meteora/types.d.ts +787 -0
- package/dist/sol/dex/meteora/types.js +110 -0
- package/dist/sol/dex/orca/index.d.ts +10 -0
- package/dist/sol/dex/orca/index.js +16 -0
- package/dist/sol/dex/orca/orca-bundle.d.ts +41 -0
- package/dist/sol/dex/orca/orca-bundle.js +173 -0
- package/dist/sol/dex/orca/orca.d.ts +65 -0
- package/dist/sol/dex/orca/orca.js +474 -0
- package/dist/sol/dex/orca/types.d.ts +263 -0
- package/dist/sol/dex/orca/types.js +38 -0
- package/dist/sol/dex/orca/wavebreak-bundle.d.ts +34 -0
- package/dist/sol/dex/orca/wavebreak-bundle.js +198 -0
- package/dist/sol/dex/orca/wavebreak-types.d.ts +227 -0
- package/dist/sol/dex/orca/wavebreak-types.js +23 -0
- package/dist/sol/dex/orca/wavebreak.d.ts +78 -0
- package/dist/sol/dex/orca/wavebreak.js +497 -0
- package/dist/sol/dex/pump/index.d.ts +9 -0
- package/dist/sol/dex/pump/index.js +14 -0
- package/dist/sol/dex/pump/pump-bundle.d.ts +92 -0
- package/dist/sol/dex/pump/pump-bundle.js +383 -0
- package/dist/sol/dex/pump/pump-swap-bundle.d.ts +103 -0
- package/dist/sol/dex/pump/pump-swap-bundle.js +380 -0
- package/dist/sol/dex/pump/pump-swap.d.ts +46 -0
- package/dist/sol/dex/pump/pump-swap.js +199 -0
- package/dist/sol/dex/pump/pump.d.ts +35 -0
- package/dist/sol/dex/pump/pump.js +352 -0
- package/dist/sol/dex/pump/types.d.ts +215 -0
- package/dist/sol/dex/pump/types.js +5 -0
- package/dist/sol/dex/raydium/index.d.ts +8 -0
- package/dist/sol/dex/raydium/index.js +12 -0
- package/dist/sol/dex/raydium/launchlab.d.ts +68 -0
- package/dist/sol/dex/raydium/launchlab.js +210 -0
- package/dist/sol/dex/raydium/raydium-bundle.d.ts +64 -0
- package/dist/sol/dex/raydium/raydium-bundle.js +324 -0
- package/dist/sol/dex/raydium/raydium.d.ts +40 -0
- package/dist/sol/dex/raydium/raydium.js +366 -0
- package/dist/sol/dex/raydium/types.d.ts +240 -0
- package/dist/sol/dex/raydium/types.js +5 -0
- package/dist/sol/index.d.ts +10 -0
- package/dist/sol/index.js +16 -0
- package/dist/sol/jito/bundle.d.ts +90 -0
- package/dist/sol/jito/bundle.js +263 -0
- package/dist/sol/jito/index.d.ts +7 -0
- package/dist/sol/jito/index.js +7 -0
- package/dist/sol/jito/tip.d.ts +51 -0
- package/dist/sol/jito/tip.js +83 -0
- package/dist/sol/jito/types.d.ts +100 -0
- package/dist/sol/jito/types.js +5 -0
- package/dist/sol/token/create-complete.d.ts +115 -0
- package/dist/sol/token/create-complete.js +235 -0
- package/dist/sol/token/create-token.d.ts +57 -0
- package/dist/sol/token/create-token.js +230 -0
- package/dist/sol/token/index.d.ts +9 -0
- package/dist/sol/token/index.js +14 -0
- package/dist/sol/token/metadata-upload.d.ts +86 -0
- package/dist/sol/token/metadata-upload.js +173 -0
- package/dist/sol/token/metadata.d.ts +92 -0
- package/dist/sol/token/metadata.js +274 -0
- package/dist/sol/token/types.d.ts +153 -0
- package/dist/sol/token/types.js +5 -0
- package/dist/sol/types.d.ts +176 -0
- package/dist/sol/types.js +7 -0
- package/dist/sol/utils/balance.d.ts +160 -0
- package/dist/sol/utils/balance.js +638 -0
- package/dist/sol/utils/connection.d.ts +78 -0
- package/dist/sol/utils/connection.js +168 -0
- package/dist/sol/utils/index.d.ts +9 -0
- package/dist/sol/utils/index.js +9 -0
- package/dist/sol/utils/lp-inspect.d.ts +129 -0
- package/dist/sol/utils/lp-inspect.js +521 -0
- package/dist/sol/utils/transfer.d.ts +125 -0
- package/dist/sol/utils/transfer.js +220 -0
- package/dist/sol/utils/wallet.d.ts +107 -0
- package/dist/sol/utils/wallet.js +210 -0
- package/dist/utils/erc20.d.ts +2 -108
- package/dist/utils/erc20.js +17 -65
- package/package.json +39 -4
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
- package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
|
@@ -0,0 +1,521 @@
|
|
|
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
|
+
// ============================================================================
|
|
20
|
+
// 动态导入(避免循环依赖和类型冲突)
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/** 懒加载 Pump.fun SDK */
|
|
23
|
+
async function loadPumpSdk() {
|
|
24
|
+
const { getBondingCurveInfo } = await import('../dex/pump/pump.js');
|
|
25
|
+
const { getPumpSwapPoolInfo } = await import('../dex/pump/pump-swap.js');
|
|
26
|
+
return { getBondingCurveInfo, getPumpSwapPoolInfo };
|
|
27
|
+
}
|
|
28
|
+
/** 懒加载 Meteora SDK */
|
|
29
|
+
async function loadMeteoraSdk() {
|
|
30
|
+
const { findDbcPoolByBaseMint, getDbcPoolInfo } = await import('../dex/meteora/dbc.js');
|
|
31
|
+
const { findDlmmPool, getDlmmPoolInfo } = await import('../dex/meteora/dlmm.js');
|
|
32
|
+
return { findDbcPoolByBaseMint, getDbcPoolInfo, findDlmmPool, getDlmmPoolInfo };
|
|
33
|
+
}
|
|
34
|
+
/** 懒加载 Orca SDK */
|
|
35
|
+
async function loadOrcaSdk() {
|
|
36
|
+
try {
|
|
37
|
+
const { getWavebreakBondingCurveInfo } = await import('../dex/orca/wavebreak.js');
|
|
38
|
+
const { findOrcaPool, getOrcaPoolInfo } = await import('../dex/orca/orca.js');
|
|
39
|
+
return { getWavebreakBondingCurveInfo, findOrcaPool, getOrcaPoolInfo };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** 懒加载 Raydium SDK */
|
|
46
|
+
async function loadRaydiumSdk() {
|
|
47
|
+
try {
|
|
48
|
+
const { initRaydium, getRaydiumPoolInfo } = await import('../dex/raydium/raydium.js');
|
|
49
|
+
return { initRaydium, getRaydiumPoolInfo };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// 内盘检测
|
|
57
|
+
// ============================================================================
|
|
58
|
+
/**
|
|
59
|
+
* 检测 Pump.fun Bonding Curve
|
|
60
|
+
*/
|
|
61
|
+
async function detectPumpBondingCurve(mint, connection, debug) {
|
|
62
|
+
try {
|
|
63
|
+
const { getBondingCurveInfo } = await loadPumpSdk();
|
|
64
|
+
const info = await getBondingCurveInfo(connection, mint);
|
|
65
|
+
if (!info)
|
|
66
|
+
return null;
|
|
67
|
+
// 计算价格 (virtualSolReserves / virtualTokenReserves)
|
|
68
|
+
const price = Number(info.virtualSolReserves) / Number(info.virtualTokenReserves);
|
|
69
|
+
// 计算进度 (realSolReserves / 85 SOL threshold)
|
|
70
|
+
const GRADUATION_THRESHOLD = 85000000000n; // 85 SOL in lamports
|
|
71
|
+
const progress = Math.min(100, Number(info.realSolReserves * 100n / GRADUATION_THRESHOLD));
|
|
72
|
+
return {
|
|
73
|
+
type: 'pump',
|
|
74
|
+
address: info.bondingCurve,
|
|
75
|
+
complete: info.complete,
|
|
76
|
+
reserveQuote: (Number(info.virtualSolReserves) / 1e9).toFixed(4),
|
|
77
|
+
reserveQuoteRaw: info.virtualSolReserves,
|
|
78
|
+
reserveToken: (Number(info.virtualTokenReserves) / 1e6).toFixed(0),
|
|
79
|
+
reserveTokenRaw: info.virtualTokenReserves,
|
|
80
|
+
price,
|
|
81
|
+
progress,
|
|
82
|
+
creator: info.creator,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (debug)
|
|
87
|
+
console.log('[LP Inspect] Pump bonding curve check failed:', err);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 检测 Meteora DBC
|
|
93
|
+
*/
|
|
94
|
+
async function detectMeteoraDbc(mint, connection, debug) {
|
|
95
|
+
try {
|
|
96
|
+
const { findDbcPoolByBaseMint, getDbcPoolInfo } = await loadMeteoraSdk();
|
|
97
|
+
const poolAddress = await findDbcPoolByBaseMint(mint, connection);
|
|
98
|
+
if (!poolAddress)
|
|
99
|
+
return null;
|
|
100
|
+
const poolInfo = await getDbcPoolInfo(poolAddress, connection);
|
|
101
|
+
return {
|
|
102
|
+
type: 'meteora_dbc',
|
|
103
|
+
address: poolAddress,
|
|
104
|
+
complete: poolInfo.isMigrated,
|
|
105
|
+
reserveQuote: poolInfo.quoteReserve,
|
|
106
|
+
reserveQuoteRaw: BigInt(poolInfo.quoteReserve),
|
|
107
|
+
reserveToken: poolInfo.baseReserve,
|
|
108
|
+
reserveTokenRaw: BigInt(poolInfo.baseReserve),
|
|
109
|
+
price: poolInfo.currentPrice,
|
|
110
|
+
progress: poolInfo.progressPercent,
|
|
111
|
+
creator: poolInfo.creator,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (debug)
|
|
116
|
+
console.log('[LP Inspect] Meteora DBC check failed:', err);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 检测 Orca Wavebreak
|
|
122
|
+
*/
|
|
123
|
+
async function detectOrcaWavebreak(mint, connection, debug) {
|
|
124
|
+
try {
|
|
125
|
+
const sdk = await loadOrcaSdk();
|
|
126
|
+
if (!sdk)
|
|
127
|
+
return null;
|
|
128
|
+
const { getWavebreakBondingCurveInfo } = sdk;
|
|
129
|
+
const info = await getWavebreakBondingCurveInfo(mint, connection);
|
|
130
|
+
if (!info)
|
|
131
|
+
return null;
|
|
132
|
+
return {
|
|
133
|
+
type: 'orca_wavebreak',
|
|
134
|
+
address: info.bondingCurveAddress,
|
|
135
|
+
complete: info.isGraduated,
|
|
136
|
+
reserveQuote: (Number(info.quoteAmount) / 1e9).toFixed(4),
|
|
137
|
+
reserveQuoteRaw: info.quoteAmount,
|
|
138
|
+
reserveToken: info.baseAmount.toString(),
|
|
139
|
+
reserveTokenRaw: info.baseAmount,
|
|
140
|
+
price: info.currentPrice,
|
|
141
|
+
progress: info.progressPercent,
|
|
142
|
+
creator: info.creator,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if (debug)
|
|
147
|
+
console.log('[LP Inspect] Orca Wavebreak check failed:', err);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 检测 Raydium LaunchLab
|
|
153
|
+
* 注意:LaunchLab API 可能需要特定的端点,这里做基础检测
|
|
154
|
+
*/
|
|
155
|
+
async function detectRaydiumLaunchLab(_mint, _connection, _debug) {
|
|
156
|
+
// Raydium LaunchLab 检测暂未实现
|
|
157
|
+
// 需要 Raydium 提供专门的 LaunchLab API
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// 外盘检测
|
|
162
|
+
// ============================================================================
|
|
163
|
+
/**
|
|
164
|
+
* 检测 Pump.fun Swap Pool
|
|
165
|
+
*/
|
|
166
|
+
async function detectPumpSwapPool(mint, connection, debug) {
|
|
167
|
+
try {
|
|
168
|
+
const { getPumpSwapPoolInfo } = await loadPumpSdk();
|
|
169
|
+
const info = await getPumpSwapPoolInfo(connection, mint);
|
|
170
|
+
if (!info)
|
|
171
|
+
return null;
|
|
172
|
+
// 获取池子余额
|
|
173
|
+
const poolPubkey = new PublicKey(info.poolAddress);
|
|
174
|
+
const balance = await connection.getBalance(poolPubkey);
|
|
175
|
+
return {
|
|
176
|
+
platform: 'PUMP_SWAP',
|
|
177
|
+
poolAddress: info.poolAddress,
|
|
178
|
+
quoteToken: info.quoteMint,
|
|
179
|
+
quoteSymbol: 'SOL',
|
|
180
|
+
quoteDecimals: 9,
|
|
181
|
+
baseToken: info.baseMint,
|
|
182
|
+
reserveQuote: (balance / 1e9).toFixed(4),
|
|
183
|
+
reserveQuoteRaw: BigInt(balance),
|
|
184
|
+
reserveBase: info.lpSupply.toString(),
|
|
185
|
+
reserveBaseRaw: info.lpSupply,
|
|
186
|
+
extra: {
|
|
187
|
+
lpMint: info.lpMint,
|
|
188
|
+
creator: info.creator,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
if (debug)
|
|
194
|
+
console.log('[LP Inspect] Pump Swap pool check failed:', err);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 检测 Meteora DLMM Pool
|
|
200
|
+
*/
|
|
201
|
+
async function detectMeteoraDlmm(mint, connection, debug) {
|
|
202
|
+
try {
|
|
203
|
+
const { findDlmmPool, getDlmmPoolInfo } = await loadMeteoraSdk();
|
|
204
|
+
// 尝试与 SOL 配对
|
|
205
|
+
const poolAddress = await findDlmmPool(mint, WSOL, connection);
|
|
206
|
+
if (!poolAddress)
|
|
207
|
+
return null;
|
|
208
|
+
const poolInfo = await getDlmmPoolInfo(poolAddress, connection);
|
|
209
|
+
// 判断哪个是 base token
|
|
210
|
+
const isTokenX = poolInfo.tokenXMint === mint;
|
|
211
|
+
const reserveQuote = isTokenX ? poolInfo.tokenYReserve : poolInfo.tokenXReserve;
|
|
212
|
+
const reserveBase = isTokenX ? poolInfo.tokenXReserve : poolInfo.tokenYReserve;
|
|
213
|
+
return {
|
|
214
|
+
platform: 'METEORA_DLMM',
|
|
215
|
+
poolAddress,
|
|
216
|
+
quoteToken: isTokenX ? poolInfo.tokenYMint : poolInfo.tokenXMint,
|
|
217
|
+
quoteSymbol: 'SOL',
|
|
218
|
+
quoteDecimals: 9,
|
|
219
|
+
baseToken: mint,
|
|
220
|
+
reserveQuote: reserveQuote || '0',
|
|
221
|
+
reserveQuoteRaw: BigInt(reserveQuote || 0),
|
|
222
|
+
reserveBase: reserveBase || '0',
|
|
223
|
+
reserveBaseRaw: BigInt(reserveBase || 0),
|
|
224
|
+
price: poolInfo.currentPrice,
|
|
225
|
+
feeBps: poolInfo.feeBps,
|
|
226
|
+
extra: {
|
|
227
|
+
binStep: poolInfo.binStep,
|
|
228
|
+
activeId: poolInfo.activeId,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
if (debug)
|
|
234
|
+
console.log('[LP Inspect] Meteora DLMM check failed:', err);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 检测 Orca Whirlpool
|
|
240
|
+
*/
|
|
241
|
+
async function detectOrcaWhirlpool(mint, connection, debug) {
|
|
242
|
+
try {
|
|
243
|
+
const sdk = await loadOrcaSdk();
|
|
244
|
+
if (!sdk)
|
|
245
|
+
return null;
|
|
246
|
+
const { findOrcaPool, getOrcaPoolInfo } = sdk;
|
|
247
|
+
// 尝试与 SOL 配对(常见的 tick spacing: 64, 128)
|
|
248
|
+
let poolAddress = await findOrcaPool(mint, WSOL, 64, connection);
|
|
249
|
+
if (!poolAddress) {
|
|
250
|
+
poolAddress = await findOrcaPool(mint, WSOL, 128, connection);
|
|
251
|
+
}
|
|
252
|
+
if (!poolAddress)
|
|
253
|
+
return null;
|
|
254
|
+
const poolInfo = await getOrcaPoolInfo(poolAddress, connection);
|
|
255
|
+
// 判断哪个是 base token
|
|
256
|
+
const isTokenA = poolInfo.tokenMintA === mint;
|
|
257
|
+
return {
|
|
258
|
+
platform: 'ORCA_WHIRLPOOL',
|
|
259
|
+
poolAddress,
|
|
260
|
+
quoteToken: isTokenA ? poolInfo.tokenMintB : poolInfo.tokenMintA,
|
|
261
|
+
quoteSymbol: 'SOL',
|
|
262
|
+
quoteDecimals: isTokenA ? poolInfo.tokenDecimalsB : poolInfo.tokenDecimalsA,
|
|
263
|
+
baseToken: mint,
|
|
264
|
+
reserveQuote: '0', // Whirlpool 需要通过 vault 查询
|
|
265
|
+
reserveQuoteRaw: 0n,
|
|
266
|
+
reserveBase: '0',
|
|
267
|
+
reserveBaseRaw: 0n,
|
|
268
|
+
price: poolInfo.price,
|
|
269
|
+
feeBps: poolInfo.feeRate,
|
|
270
|
+
extra: {
|
|
271
|
+
tickSpacing: poolInfo.tickSpacing,
|
|
272
|
+
currentTick: poolInfo.currentTick,
|
|
273
|
+
liquidity: poolInfo.liquidity,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
if (debug)
|
|
279
|
+
console.log('[LP Inspect] Orca Whirlpool check failed:', err);
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 检测 Raydium 池子
|
|
285
|
+
*/
|
|
286
|
+
async function detectRaydiumPools(mint, connection, debug) {
|
|
287
|
+
const pools = [];
|
|
288
|
+
try {
|
|
289
|
+
const sdk = await loadRaydiumSdk();
|
|
290
|
+
if (!sdk)
|
|
291
|
+
return pools;
|
|
292
|
+
const { initRaydium } = sdk;
|
|
293
|
+
const raydium = await initRaydium(connection);
|
|
294
|
+
// 通过 API 查找所有包含该代币的池子
|
|
295
|
+
const poolList = await raydium.api.fetchPoolByMints({
|
|
296
|
+
mint1: mint,
|
|
297
|
+
mint2: WSOL,
|
|
298
|
+
});
|
|
299
|
+
// poolList 可能是对象而不是数组,需要处理
|
|
300
|
+
const poolArray = poolList?.data || [];
|
|
301
|
+
if (!Array.isArray(poolArray))
|
|
302
|
+
return pools;
|
|
303
|
+
for (const pool of poolArray) {
|
|
304
|
+
// 判断池子类型
|
|
305
|
+
let platform = 'RAYDIUM_AMM';
|
|
306
|
+
if (pool.programId?.includes('CPMMoo')) {
|
|
307
|
+
platform = 'RAYDIUM_CPMM';
|
|
308
|
+
}
|
|
309
|
+
else if (pool.type === 'Concentrated') {
|
|
310
|
+
platform = 'RAYDIUM_CLMM';
|
|
311
|
+
}
|
|
312
|
+
const isBaseA = pool.mintA?.address === mint;
|
|
313
|
+
const quoteToken = isBaseA ? pool.mintB?.address : pool.mintA?.address;
|
|
314
|
+
const quoteSymbol = isBaseA ? pool.mintB?.symbol : pool.mintA?.symbol;
|
|
315
|
+
const quoteDecimals = isBaseA ? pool.mintB?.decimals : pool.mintA?.decimals;
|
|
316
|
+
const reserveQuoteNum = isBaseA ? pool.mintAmountB : pool.mintAmountA;
|
|
317
|
+
const reserveBaseNum = isBaseA ? pool.mintAmountA : pool.mintAmountB;
|
|
318
|
+
if (!quoteToken)
|
|
319
|
+
continue;
|
|
320
|
+
pools.push({
|
|
321
|
+
platform,
|
|
322
|
+
poolAddress: pool.id,
|
|
323
|
+
quoteToken: quoteToken || '',
|
|
324
|
+
quoteSymbol: quoteSymbol || 'UNKNOWN',
|
|
325
|
+
quoteDecimals: quoteDecimals || 9,
|
|
326
|
+
baseToken: mint,
|
|
327
|
+
reserveQuote: String(reserveQuoteNum || 0),
|
|
328
|
+
reserveQuoteRaw: BigInt(Math.floor((reserveQuoteNum || 0) * 1e9)),
|
|
329
|
+
reserveBase: String(reserveBaseNum || 0),
|
|
330
|
+
reserveBaseRaw: BigInt(Math.floor((reserveBaseNum || 0) * 1e9)),
|
|
331
|
+
price: pool.price,
|
|
332
|
+
feeBps: Math.floor((pool.feeRate || 0) * 10000),
|
|
333
|
+
extra: {
|
|
334
|
+
type: pool.type,
|
|
335
|
+
lpMint: pool.lpMint?.address,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
if (debug)
|
|
342
|
+
console.log('[LP Inspect] Raydium pools check failed:', err);
|
|
343
|
+
}
|
|
344
|
+
return pools;
|
|
345
|
+
}
|
|
346
|
+
// ============================================================================
|
|
347
|
+
// 主函数
|
|
348
|
+
// ============================================================================
|
|
349
|
+
/**
|
|
350
|
+
* 检测 Solana 代币的 LP 所在平台
|
|
351
|
+
*
|
|
352
|
+
* @param mint 代币 Mint 地址
|
|
353
|
+
* @param options 检测选项
|
|
354
|
+
* @returns LP 信息
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```typescript
|
|
358
|
+
* const lpInfo = await inspectSolanaTokenLP('TokenMintAddress...', {
|
|
359
|
+
* rpcUrl: 'https://api.mainnet-beta.solana.com',
|
|
360
|
+
* debug: true,
|
|
361
|
+
* })
|
|
362
|
+
*
|
|
363
|
+
* if (lpInfo.platform === 'PUMP_BONDING_CURVE') {
|
|
364
|
+
* console.log('代币在 Pump.fun 内盘')
|
|
365
|
+
* console.log('毕业进度:', lpInfo.bondingCurve?.progress)
|
|
366
|
+
* } else if (lpInfo.bestPool) {
|
|
367
|
+
* console.log('最佳池子:', lpInfo.bestPool.platform)
|
|
368
|
+
* console.log('流动性:', lpInfo.bestPool.reserveQuote)
|
|
369
|
+
* }
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
export async function inspectSolanaTokenLP(mint, options = {}) {
|
|
373
|
+
const startTime = Date.now();
|
|
374
|
+
if (!options.connection && !options.rpcUrl) {
|
|
375
|
+
throw new Error('Either connection or rpcUrl is required in options');
|
|
376
|
+
}
|
|
377
|
+
const connection = options.connection || createConnection(options.rpcUrl);
|
|
378
|
+
const debug = options.debug;
|
|
379
|
+
// 初始化结果
|
|
380
|
+
const result = {
|
|
381
|
+
mint,
|
|
382
|
+
platform: 'UNKNOWN',
|
|
383
|
+
pools: [],
|
|
384
|
+
};
|
|
385
|
+
// 获取代币信息
|
|
386
|
+
try {
|
|
387
|
+
const mintPubkey = new PublicKey(mint);
|
|
388
|
+
const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
|
|
389
|
+
if (mintInfo.value?.data && 'parsed' in mintInfo.value.data) {
|
|
390
|
+
const parsed = mintInfo.value.data.parsed;
|
|
391
|
+
const decimals = parsed.info.decimals;
|
|
392
|
+
result.decimals = decimals;
|
|
393
|
+
result.totalSupplyRaw = BigInt(parsed.info.supply);
|
|
394
|
+
result.totalSupply = (Number(result.totalSupplyRaw) / Math.pow(10, decimals)).toString();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
if (debug)
|
|
399
|
+
console.log('[LP Inspect] Failed to get mint info:', err);
|
|
400
|
+
}
|
|
401
|
+
// ========================================
|
|
402
|
+
// 1. 内盘检测(并行)
|
|
403
|
+
// ========================================
|
|
404
|
+
if (!options.skipBondingCurve) {
|
|
405
|
+
const bondingCurveResults = await Promise.allSettled([
|
|
406
|
+
detectPumpBondingCurve(mint, connection, debug),
|
|
407
|
+
detectMeteoraDbc(mint, connection, debug),
|
|
408
|
+
detectOrcaWavebreak(mint, connection, debug),
|
|
409
|
+
detectRaydiumLaunchLab(mint, connection, debug),
|
|
410
|
+
]);
|
|
411
|
+
// 查找第一个有效的内盘
|
|
412
|
+
for (const r of bondingCurveResults) {
|
|
413
|
+
if (r.status === 'fulfilled' && r.value && !r.value.complete) {
|
|
414
|
+
result.bondingCurve = r.value;
|
|
415
|
+
// 设置平台类型
|
|
416
|
+
switch (r.value.type) {
|
|
417
|
+
case 'pump':
|
|
418
|
+
result.platform = 'PUMP_BONDING_CURVE';
|
|
419
|
+
break;
|
|
420
|
+
case 'meteora_dbc':
|
|
421
|
+
result.platform = 'METEORA_DBC';
|
|
422
|
+
break;
|
|
423
|
+
case 'orca_wavebreak':
|
|
424
|
+
result.platform = 'ORCA_WAVEBREAK';
|
|
425
|
+
break;
|
|
426
|
+
case 'raydium_launchlab':
|
|
427
|
+
result.platform = 'RAYDIUM_LAUNCHLAB';
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
result.elapsed = Date.now() - startTime;
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// ========================================
|
|
436
|
+
// 2. 外盘检测(并行)
|
|
437
|
+
// ========================================
|
|
438
|
+
if (!options.skipPools) {
|
|
439
|
+
const poolResults = await Promise.allSettled([
|
|
440
|
+
detectPumpSwapPool(mint, connection, debug),
|
|
441
|
+
detectMeteoraDlmm(mint, connection, debug),
|
|
442
|
+
detectOrcaWhirlpool(mint, connection, debug),
|
|
443
|
+
detectRaydiumPools(mint, connection, debug),
|
|
444
|
+
]);
|
|
445
|
+
// 收集所有有效的池子
|
|
446
|
+
for (const r of poolResults) {
|
|
447
|
+
if (r.status === 'fulfilled' && r.value) {
|
|
448
|
+
if (Array.isArray(r.value)) {
|
|
449
|
+
result.pools.push(...r.value);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
result.pools.push(r.value);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// 按流动性排序
|
|
457
|
+
result.pools.sort((a, b) => {
|
|
458
|
+
const liquidityA = Number(a.reserveQuoteRaw);
|
|
459
|
+
const liquidityB = Number(b.reserveQuoteRaw);
|
|
460
|
+
return liquidityB - liquidityA;
|
|
461
|
+
});
|
|
462
|
+
// 设置最佳池子
|
|
463
|
+
if (result.pools.length > 0) {
|
|
464
|
+
result.bestPool = result.pools[0];
|
|
465
|
+
// 设置平台类型
|
|
466
|
+
if (result.pools.length > 1) {
|
|
467
|
+
result.platform = 'MULTI_POOL';
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
result.platform = result.bestPool.platform;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
result.elapsed = Date.now() - startTime;
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* 快速检测代币是否在内盘
|
|
479
|
+
*
|
|
480
|
+
* @param mint 代币 Mint 地址
|
|
481
|
+
* @param options 检测选项
|
|
482
|
+
* @returns 如果在内盘返回内盘信息,否则返回 null
|
|
483
|
+
*/
|
|
484
|
+
export async function detectBondingCurve(mint, options = {}) {
|
|
485
|
+
if (!options.connection && !options.rpcUrl) {
|
|
486
|
+
throw new Error('Either connection or rpcUrl is required in options');
|
|
487
|
+
}
|
|
488
|
+
const connection = options.connection || createConnection(options.rpcUrl);
|
|
489
|
+
const debug = options.debug;
|
|
490
|
+
// 并行检测所有内盘
|
|
491
|
+
const results = await Promise.allSettled([
|
|
492
|
+
detectPumpBondingCurve(mint, connection, debug),
|
|
493
|
+
detectMeteoraDbc(mint, connection, debug),
|
|
494
|
+
detectOrcaWavebreak(mint, connection, debug),
|
|
495
|
+
detectRaydiumLaunchLab(mint, connection, debug),
|
|
496
|
+
]);
|
|
497
|
+
// 返回第一个未毕业的内盘
|
|
498
|
+
for (const r of results) {
|
|
499
|
+
if (r.status === 'fulfilled' && r.value && !r.value.complete) {
|
|
500
|
+
return r.value;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* 判断代币是否已毕业(不在任何内盘)
|
|
507
|
+
*/
|
|
508
|
+
export async function isTokenGraduated(mint, options = {}) {
|
|
509
|
+
const bondingCurve = await detectBondingCurve(mint, options);
|
|
510
|
+
return bondingCurve === null;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* 获取代币的最佳交易池
|
|
514
|
+
*/
|
|
515
|
+
export async function getBestPool(mint, options = {}) {
|
|
516
|
+
const lpInfo = await inspectSolanaTokenLP(mint, {
|
|
517
|
+
...options,
|
|
518
|
+
skipBondingCurve: true,
|
|
519
|
+
});
|
|
520
|
+
return lpInfo.bestPool || null;
|
|
521
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solana SOL 转账功能
|
|
3
|
+
* SDK 只负责构建和签名交易,提交到后端服务器广播
|
|
4
|
+
* @module sol/utils/transfer
|
|
5
|
+
*/
|
|
6
|
+
import { Connection, Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
|
7
|
+
/**
|
|
8
|
+
* 转账项
|
|
9
|
+
*/
|
|
10
|
+
export interface TransferItem {
|
|
11
|
+
to: string;
|
|
12
|
+
amount: bigint;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 签名后的转账交易
|
|
16
|
+
*/
|
|
17
|
+
export interface SignedTransferTransaction {
|
|
18
|
+
/** Base64 编码的交易数据 */
|
|
19
|
+
data: string;
|
|
20
|
+
/** 交易签名 */
|
|
21
|
+
signature: string;
|
|
22
|
+
/** 转账详情 */
|
|
23
|
+
transfers: TransferItem[];
|
|
24
|
+
/** blockhash */
|
|
25
|
+
recentBlockhash: string;
|
|
26
|
+
/** 最后有效区块高度 */
|
|
27
|
+
lastValidBlockHeight: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 批量签名结果
|
|
31
|
+
*/
|
|
32
|
+
export interface BatchSignedTransfers {
|
|
33
|
+
/** 签名后的交易列表 */
|
|
34
|
+
transactions: SignedTransferTransaction[];
|
|
35
|
+
/** 总转账数 */
|
|
36
|
+
totalTransfers: number;
|
|
37
|
+
/** 总交易数 */
|
|
38
|
+
totalTransactions: number;
|
|
39
|
+
/** 创建时间 */
|
|
40
|
+
createdAt: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 构建转账指令
|
|
44
|
+
* @param from 发送者公钥
|
|
45
|
+
* @param to 接收者地址
|
|
46
|
+
* @param amount lamports
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildTransferInstruction(from: PublicKey, to: string, amount: bigint): TransactionInstruction;
|
|
49
|
+
/**
|
|
50
|
+
* 构建批量转账指令
|
|
51
|
+
* @param from 发送者公钥
|
|
52
|
+
* @param transfers 转账列表
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildTransferInstructions(from: PublicKey, transfers: TransferItem[]): TransactionInstruction[];
|
|
55
|
+
/**
|
|
56
|
+
* 构建并签名单笔 SOL 转账
|
|
57
|
+
* @param connection Solana 连接
|
|
58
|
+
* @param from 发送者 Keypair
|
|
59
|
+
* @param to 接收者地址
|
|
60
|
+
* @param amount lamports
|
|
61
|
+
*/
|
|
62
|
+
export declare function signTransferSOL(connection: Connection, from: Keypair, to: string, amount: bigint): Promise<SignedTransferTransaction>;
|
|
63
|
+
/**
|
|
64
|
+
* 构建并签名单笔 SOL 转账(使用 SOL 数量)
|
|
65
|
+
*/
|
|
66
|
+
export declare function signTransferSOLAmount(connection: Connection, from: Keypair, to: string, solAmount: number): Promise<SignedTransferTransaction>;
|
|
67
|
+
/**
|
|
68
|
+
* 构建并签名批量转账(单笔交易,多个指令)
|
|
69
|
+
* 单笔交易最多约 20-30 个转账
|
|
70
|
+
* @param connection Solana 连接
|
|
71
|
+
* @param from 发送者 Keypair
|
|
72
|
+
* @param transfers 转账列表
|
|
73
|
+
*/
|
|
74
|
+
export declare function signBatchTransferSOL(connection: Connection, from: Keypair, transfers: TransferItem[]): Promise<SignedTransferTransaction>;
|
|
75
|
+
/**
|
|
76
|
+
* 构建并签名批量转账(多笔交易)
|
|
77
|
+
* 自动分批处理大量转账
|
|
78
|
+
* @param connection Solana 连接
|
|
79
|
+
* @param from 发送者 Keypair
|
|
80
|
+
* @param transfers 转账列表
|
|
81
|
+
* @param batchSize 每批大小(默认 20)
|
|
82
|
+
*/
|
|
83
|
+
export declare function signBatchTransferSOLMultiple(connection: Connection, from: Keypair, transfers: TransferItem[], batchSize?: number): Promise<BatchSignedTransfers>;
|
|
84
|
+
/**
|
|
85
|
+
* 多钱包转账项
|
|
86
|
+
*/
|
|
87
|
+
export interface MultiWalletTransferItem {
|
|
88
|
+
from: Keypair;
|
|
89
|
+
to: string;
|
|
90
|
+
amount: bigint;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 签名多钱包转账(每个钱包一笔交易)
|
|
94
|
+
* 用于归集或分发场景
|
|
95
|
+
* @param connection Solana 连接
|
|
96
|
+
* @param transfers 转账列表
|
|
97
|
+
*/
|
|
98
|
+
export declare function signMultiWalletTransfers(connection: Connection, transfers: MultiWalletTransferItem[]): Promise<BatchSignedTransfers>;
|
|
99
|
+
/**
|
|
100
|
+
* 归集 SOL(多个钱包转到一个地址)
|
|
101
|
+
* @param connection Solana 连接
|
|
102
|
+
* @param wallets 钱包列表
|
|
103
|
+
* @param toAddress 目标地址
|
|
104
|
+
* @param amounts 每个钱包转账金额(lamports),如果为空则转全部(保留 5000 lamports 作为手续费)
|
|
105
|
+
*/
|
|
106
|
+
export declare function signCollectSOL(connection: Connection, wallets: Keypair[], toAddress: string, amounts?: bigint[]): Promise<BatchSignedTransfers>;
|
|
107
|
+
/**
|
|
108
|
+
* 分发 SOL(一个钱包转到多个地址)
|
|
109
|
+
* @param connection Solana 连接
|
|
110
|
+
* @param from 发送者 Keypair
|
|
111
|
+
* @param distributions 分发列表
|
|
112
|
+
*/
|
|
113
|
+
export declare function signDistributeSOL(connection: Connection, from: Keypair, distributions: TransferItem[]): Promise<BatchSignedTransfers>;
|
|
114
|
+
/**
|
|
115
|
+
* 将签名结果转换为后端 API 格式
|
|
116
|
+
*/
|
|
117
|
+
export declare function toBackendFormat(result: BatchSignedTransfers): {
|
|
118
|
+
transactions: Array<{
|
|
119
|
+
data: string;
|
|
120
|
+
signature: string;
|
|
121
|
+
}>;
|
|
122
|
+
totalTransfers: number;
|
|
123
|
+
totalTransactions: number;
|
|
124
|
+
timestamp: number;
|
|
125
|
+
};
|