four-flap-meme-sdk 1.4.23 → 1.4.24
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.
|
@@ -236,6 +236,10 @@ export type DisperseMerkleParams = {
|
|
|
236
236
|
config: FourBundleMerkleConfig;
|
|
237
237
|
};
|
|
238
238
|
/** ✅ 分发参数(仅签名版本 - 精简) */
|
|
239
|
+
/** 代币池子类型 */
|
|
240
|
+
export type TokenPoolType = 'flap' | 'four' | 'v2' | 'v3';
|
|
241
|
+
/** 报价代币类型(池子的计价代币) */
|
|
242
|
+
export type QuoteTokenType = 'native' | 'usdt' | 'usdc';
|
|
239
243
|
export type DisperseSignParams = {
|
|
240
244
|
fromPrivateKey: string;
|
|
241
245
|
recipients: string[];
|
|
@@ -254,6 +258,10 @@ export type DisperseSignParams = {
|
|
|
254
258
|
config: FourSignConfig;
|
|
255
259
|
/** ✅ 新增:起始 nonce(用于并行调用时避免 nonce 冲突) */
|
|
256
260
|
startNonce?: number;
|
|
261
|
+
/** ✅ 新增:代币池子类型(用于利润报价),默认 'v2' */
|
|
262
|
+
tokenPoolType?: TokenPoolType;
|
|
263
|
+
/** ✅ 新增:报价代币类型(池子的计价代币),默认 'native'(BNB/MON) */
|
|
264
|
+
quoteToken?: QuoteTokenType;
|
|
257
265
|
};
|
|
258
266
|
/**
|
|
259
267
|
* ✅ 分发结果(简化版)
|
|
@@ -305,6 +313,10 @@ export type SweepSignParams = {
|
|
|
305
313
|
config: FourSignConfig;
|
|
306
314
|
/** ✅ 新增:起始 nonce(用于并行调用时避免 nonce 冲突,仅对第一个源钱包生效) */
|
|
307
315
|
startNonce?: number;
|
|
316
|
+
/** ✅ 新增:代币池子类型(用于利润报价),默认 'v2' */
|
|
317
|
+
tokenPoolType?: TokenPoolType;
|
|
318
|
+
/** ✅ 新增:报价代币类型(池子的计价代币),默认 'native'(BNB/MON) */
|
|
319
|
+
quoteToken?: QuoteTokenType;
|
|
308
320
|
};
|
|
309
321
|
/**
|
|
310
322
|
* ✅ 归集结果(简化版)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ethers, Wallet
|
|
2
|
-
// import { MerkleClient } from '../../clients/merkle.js';
|
|
1
|
+
import { ethers, Wallet } from 'ethers';
|
|
3
2
|
import { getOptimizedGasPrice, NonceManager } from '../../utils/bundle-helpers.js';
|
|
4
3
|
import { PROFIT_CONFIG } from '../../utils/constants.js';
|
|
5
4
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient } from './config.js';
|
|
@@ -15,46 +14,182 @@ function calculateProfit(amount) {
|
|
|
15
14
|
return { profit, remaining };
|
|
16
15
|
}
|
|
17
16
|
// ==================== ERC20 → 原生代币报价 ====================
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
'
|
|
26
|
-
|
|
17
|
+
import { quote, QUOTE_CONFIG } from '../../utils/quote-helpers.js';
|
|
18
|
+
import { Helper3 } from '../helper3.js';
|
|
19
|
+
import { FlapPortal } from '../../flap/portal.js';
|
|
20
|
+
/** 最低利润(Wei):无法获取报价时使用,0.0001 BNB */
|
|
21
|
+
const MIN_PROFIT_WEI = 100000000000000n; // 0.0001 BNB = 10^14 wei
|
|
22
|
+
/** 链 ID → 链名称 映射 */
|
|
23
|
+
const CHAIN_ID_TO_NAME = {
|
|
24
|
+
56: 'BSC',
|
|
25
|
+
143: 'MONAD',
|
|
26
|
+
196: 'XLAYER',
|
|
27
|
+
};
|
|
28
|
+
/** 各链的稳定币地址 */
|
|
29
|
+
const STABLE_COINS = {
|
|
30
|
+
BSC: {
|
|
31
|
+
usdt: '0x55d398326f99059fF775485246999027B3197955',
|
|
32
|
+
usdc: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d',
|
|
33
|
+
},
|
|
34
|
+
MONAD: {
|
|
35
|
+
// TODO: 添加 Monad 链的稳定币地址
|
|
36
|
+
},
|
|
37
|
+
XLAYER: {
|
|
38
|
+
usdt: '0x1E4a5963aBFD975d8c9021ce480b42188849D41d',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* 获取 FOUR 内盘代币 → BNB 的报价
|
|
43
|
+
*/
|
|
44
|
+
async function getFourInnerQuote(rpcUrl, tokenAddress, tokenAmount) {
|
|
45
|
+
try {
|
|
46
|
+
const helper = Helper3.connectByChain('BSC', rpcUrl);
|
|
47
|
+
const result = await helper.trySell(tokenAddress, tokenAmount);
|
|
48
|
+
// result = { tokenManager, quote, funds, fee }
|
|
49
|
+
// funds 是卖出代币能获得的 BNB 数量
|
|
50
|
+
const funds = result.funds;
|
|
51
|
+
console.log(`[getFourInnerQuote] FOUR 内盘报价成功: ${funds} wei`);
|
|
52
|
+
return funds;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.warn(`[getFourInnerQuote] FOUR 内盘报价失败:`, error);
|
|
56
|
+
return 0n;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** 零地址(原生代币) */
|
|
60
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
61
|
+
/**
|
|
62
|
+
* 获取 FLAP 内盘代币 → 原生代币的报价
|
|
63
|
+
* ✅ 与 portal-bundle-merkle/core.ts 使用相同的 quoteExactInput 方法
|
|
64
|
+
*/
|
|
65
|
+
async function getFlapInnerQuote(rpcUrl, chainId, tokenAddress, tokenAmount) {
|
|
66
|
+
try {
|
|
67
|
+
// 根据链 ID 确定链名称
|
|
68
|
+
const chainName = chainId === 56 ? 'BSC' : chainId === 143 ? 'MONAD' : chainId === 196 ? 'XLAYER' : null;
|
|
69
|
+
if (!chainName) {
|
|
70
|
+
console.warn(`[getFlapInnerQuote] 不支持的链 ID: ${chainId}`);
|
|
71
|
+
return 0n;
|
|
72
|
+
}
|
|
73
|
+
const portal = new FlapPortal({ chain: chainName, rpcUrl });
|
|
74
|
+
// ✅ 使用 quoteExactInput 与 core.ts 保持一致
|
|
75
|
+
const funds = await portal.quoteExactInput({
|
|
76
|
+
inputToken: tokenAddress,
|
|
77
|
+
outputToken: ZERO_ADDRESS, // 输出原生代币
|
|
78
|
+
inputAmount: tokenAmount
|
|
79
|
+
});
|
|
80
|
+
console.log(`[getFlapInnerQuote] FLAP 内盘报价成功: ${funds} wei`);
|
|
81
|
+
return funds;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.warn(`[getFlapInnerQuote] FLAP 内盘报价失败:`, error);
|
|
85
|
+
return 0n;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
27
88
|
/**
|
|
28
89
|
* 获取 ERC20 代币 → 原生代币的报价
|
|
90
|
+
*
|
|
29
91
|
* @param provider - Provider 实例
|
|
30
92
|
* @param tokenAddress - ERC20 代币地址
|
|
31
93
|
* @param tokenAmount - 代币数量(wei)
|
|
32
94
|
* @param chainId - 链 ID(56=BSC, 143=Monad)
|
|
33
|
-
* @
|
|
95
|
+
* @param poolType - 池子类型:'flap'/'four' 内盘,'v2'/'v3' 外盘
|
|
96
|
+
* @param quoteToken - 报价代币:'native'(BNB/MON),'usdt','usdc'
|
|
97
|
+
* @param rpcUrl - RPC URL(内盘报价需要)
|
|
98
|
+
* @returns 等值的原生代币数量(wei),失败返回 MIN_PROFIT_WEI
|
|
34
99
|
*/
|
|
35
|
-
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
|
|
100
|
+
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId, poolType = 'v2', quoteToken = 'native', rpcUrl) {
|
|
36
101
|
if (tokenAmount <= 0n)
|
|
37
|
-
return
|
|
102
|
+
return MIN_PROFIT_WEI;
|
|
103
|
+
// ✅ FOUR 内盘:通过 Helper3.trySell 获取真实报价
|
|
104
|
+
if (poolType === 'four') {
|
|
105
|
+
const url = rpcUrl || provider._getConnection?.()?.url || '';
|
|
106
|
+
if (!url) {
|
|
107
|
+
console.warn(`[getTokenToNativeQuote] FOUR 内盘需要 rpcUrl,使用最低利润`);
|
|
108
|
+
return MIN_PROFIT_WEI;
|
|
109
|
+
}
|
|
110
|
+
const funds = await getFourInnerQuote(url, tokenAddress, tokenAmount);
|
|
111
|
+
return funds > 0n ? funds : MIN_PROFIT_WEI;
|
|
112
|
+
}
|
|
113
|
+
// ✅ FLAP 内盘:通过 FlapPortal.previewSell 获取真实报价
|
|
114
|
+
if (poolType === 'flap') {
|
|
115
|
+
const url = rpcUrl || provider._getConnection?.()?.url || '';
|
|
116
|
+
if (!url) {
|
|
117
|
+
console.warn(`[getTokenToNativeQuote] FLAP 内盘需要 rpcUrl,使用最低利润`);
|
|
118
|
+
return MIN_PROFIT_WEI;
|
|
119
|
+
}
|
|
120
|
+
const funds = await getFlapInnerQuote(url, chainId, tokenAddress, tokenAmount);
|
|
121
|
+
return funds > 0n ? funds : MIN_PROFIT_WEI;
|
|
122
|
+
}
|
|
123
|
+
const chainName = CHAIN_ID_TO_NAME[chainId];
|
|
124
|
+
if (!chainName) {
|
|
125
|
+
console.warn(`[getTokenToNativeQuote] 不支持的链 ID: ${chainId},使用最低利润`);
|
|
126
|
+
return MIN_PROFIT_WEI;
|
|
127
|
+
}
|
|
128
|
+
const chainConfig = QUOTE_CONFIG[chainName];
|
|
129
|
+
if (!chainConfig) {
|
|
130
|
+
console.warn(`[getTokenToNativeQuote] 不支持的链: ${chainName},使用最低利润`);
|
|
131
|
+
return MIN_PROFIT_WEI;
|
|
132
|
+
}
|
|
38
133
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
134
|
+
const version = poolType === 'v3' ? 'v3' : 'v2';
|
|
135
|
+
let nativeAmount = 0n;
|
|
136
|
+
if (quoteToken === 'native') {
|
|
137
|
+
// ✅ 直接路径:TOKEN → WBNB
|
|
138
|
+
console.log(`[getTokenToNativeQuote] 使用 ${version} 直接路径报价 (TOKEN → WBNB)`);
|
|
139
|
+
const result = await quote({
|
|
140
|
+
provider,
|
|
141
|
+
tokenIn: tokenAddress,
|
|
142
|
+
tokenOut: chainConfig.wrappedNative,
|
|
143
|
+
amountIn: tokenAmount,
|
|
144
|
+
chain: chainName,
|
|
145
|
+
version
|
|
146
|
+
});
|
|
147
|
+
nativeAmount = result.amountOut;
|
|
44
148
|
}
|
|
45
|
-
|
|
46
|
-
// ✅
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
149
|
+
else {
|
|
150
|
+
// ✅ 稳定币路径:TOKEN → USDT/USDC → WBNB(两步报价)
|
|
151
|
+
const stableCoins = STABLE_COINS[chainName];
|
|
152
|
+
const stableCoinAddress = stableCoins?.[quoteToken];
|
|
153
|
+
if (!stableCoinAddress) {
|
|
154
|
+
console.warn(`[getTokenToNativeQuote] 链 ${chainName} 不支持 ${quoteToken},使用最低利润`);
|
|
155
|
+
return MIN_PROFIT_WEI;
|
|
156
|
+
}
|
|
157
|
+
console.log(`[getTokenToNativeQuote] 使用 ${version} 稳定币路径报价 (TOKEN → ${quoteToken.toUpperCase()} → WBNB)`);
|
|
158
|
+
// 第一步:TOKEN → USDT/USDC
|
|
159
|
+
const step1 = await quote({
|
|
160
|
+
provider,
|
|
161
|
+
tokenIn: tokenAddress,
|
|
162
|
+
tokenOut: stableCoinAddress,
|
|
163
|
+
amountIn: tokenAmount,
|
|
164
|
+
chain: chainName,
|
|
165
|
+
version
|
|
166
|
+
});
|
|
167
|
+
if (step1.amountOut <= 0n) {
|
|
168
|
+
console.warn(`[getTokenToNativeQuote] TOKEN → ${quoteToken.toUpperCase()} 报价失败`);
|
|
169
|
+
return MIN_PROFIT_WEI;
|
|
170
|
+
}
|
|
171
|
+
// 第二步:USDT/USDC → WBNB
|
|
172
|
+
const step2 = await quote({
|
|
173
|
+
provider,
|
|
174
|
+
tokenIn: stableCoinAddress,
|
|
175
|
+
tokenOut: chainConfig.wrappedNative,
|
|
176
|
+
amountIn: step1.amountOut,
|
|
177
|
+
chain: chainName,
|
|
178
|
+
version
|
|
179
|
+
});
|
|
180
|
+
nativeAmount = step2.amountOut;
|
|
181
|
+
}
|
|
182
|
+
if (nativeAmount > 0n) {
|
|
183
|
+
console.log(`[getTokenToNativeQuote] 报价成功: ${nativeAmount} wei`);
|
|
184
|
+
return nativeAmount;
|
|
50
185
|
}
|
|
51
|
-
// 其他链暂不支持报价,返回 0
|
|
52
|
-
return 0n;
|
|
53
186
|
}
|
|
54
|
-
catch {
|
|
55
|
-
|
|
56
|
-
return 0n;
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.warn(`[getTokenToNativeQuote] 报价失败:`, error);
|
|
57
189
|
}
|
|
190
|
+
// ✅ 报价失败,返回最低利润(0.0001 BNB)
|
|
191
|
+
console.log(`[getTokenToNativeQuote] 无法获取报价,使用最低利润: ${MIN_PROFIT_WEI}`);
|
|
192
|
+
return MIN_PROFIT_WEI;
|
|
58
193
|
}
|
|
59
194
|
/**
|
|
60
195
|
* 分发(仅签名版本 - 不依赖 Merkle)
|
|
@@ -62,7 +197,7 @@ async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainI
|
|
|
62
197
|
* ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突
|
|
63
198
|
*/
|
|
64
199
|
export async function disperseWithBundleMerkle(params) {
|
|
65
|
-
const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce } = params;
|
|
200
|
+
const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native' } = params;
|
|
66
201
|
// 快速返回空结果
|
|
67
202
|
if (!recipients || recipients.length === 0) {
|
|
68
203
|
return {
|
|
@@ -184,7 +319,7 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
184
319
|
// ✅ 获取 ERC20 利润等值的原生代币(BNB)报价
|
|
185
320
|
let nativeProfitAmount = 0n;
|
|
186
321
|
if (extractProfit && totalTokenProfit > 0n) {
|
|
187
|
-
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
|
|
322
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
|
|
188
323
|
totalProfit = nativeProfitAmount; // 更新为原生代币利润
|
|
189
324
|
}
|
|
190
325
|
// ✅ 并行签名所有交易
|
|
@@ -368,7 +503,7 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
368
503
|
}
|
|
369
504
|
// ✅ ERC20 多跳:获取代币利润等值的原生代币报价
|
|
370
505
|
if (!isNative && extractProfit && totalTokenProfit > 0n) {
|
|
371
|
-
const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
|
|
506
|
+
const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
|
|
372
507
|
totalProfit = nativeProfitAmount; // 更新为原生代币利润
|
|
373
508
|
}
|
|
374
509
|
// 利润转账(转等值原生代币)
|
|
@@ -411,7 +546,7 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
411
546
|
* ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突
|
|
412
547
|
*/
|
|
413
548
|
export async function sweepWithBundleMerkle(params) {
|
|
414
|
-
const { sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce } = params;
|
|
549
|
+
const { sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native' } = params;
|
|
415
550
|
// 快速返回空结果
|
|
416
551
|
if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) {
|
|
417
552
|
return {
|
|
@@ -684,7 +819,7 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
684
819
|
// ✅ ERC20 归集:获取代币利润等值的原生代币(BNB)报价
|
|
685
820
|
let nativeProfitAmount = 0n;
|
|
686
821
|
if (extractProfit && totalProfit > 0n) {
|
|
687
|
-
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum);
|
|
822
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
|
|
688
823
|
}
|
|
689
824
|
// ✅ 如果需要提取利润,检查支付者是否有足够 BNB 支付利润转账的额外 gas
|
|
690
825
|
if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && config.checkBnbForErc20NoHop) {
|
|
@@ -1036,7 +1171,7 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
1036
1171
|
// ✅ ERC20 多跳归集:获取代币利润等值的原生代币报价
|
|
1037
1172
|
let nativeProfitAmount = isNative ? totalProfit : 0n;
|
|
1038
1173
|
if (!isNative && totalTokenProfit > 0n) {
|
|
1039
|
-
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
|
|
1174
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
|
|
1040
1175
|
totalProfit = nativeProfitAmount; // 更新为原生代币利润
|
|
1041
1176
|
}
|
|
1042
1177
|
// 由归集金额最大的钱包支付利润(转等值原生代币)
|