four-flap-meme-sdk 1.3.16 → 1.3.18
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.
|
@@ -1,8 +1,43 @@
|
|
|
1
|
-
import { ethers, Wallet } from 'ethers';
|
|
1
|
+
import { ethers, Wallet, Contract } from 'ethers';
|
|
2
2
|
// import { MerkleClient } from '../../clients/merkle.js';
|
|
3
3
|
import { getOptimizedGasPrice, NonceManager } from '../../utils/bundle-helpers.js';
|
|
4
4
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient } from './config.js';
|
|
5
5
|
import { getErc20DecimalsMerkle as _getErc20DecimalsMerkle, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, batchGetBalances as _batchGetBalances, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress } from './internal.js';
|
|
6
|
+
// ==================== ERC20 → 原生代币报价 ====================
|
|
7
|
+
// BSC 链常量
|
|
8
|
+
const BSC_PANCAKE_V2_ROUTER = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
|
|
9
|
+
const BSC_WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
|
|
10
|
+
// Monad 链常量(如果有 DEX)
|
|
11
|
+
const MONAD_WMON = '0x760AfE86e5de5fa0Ee542fc7B7B713e1c5425701';
|
|
12
|
+
const ROUTER_ABI = [
|
|
13
|
+
'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* 获取 ERC20 代币 → 原生代币的报价
|
|
17
|
+
* @param provider - Provider 实例
|
|
18
|
+
* @param tokenAddress - ERC20 代币地址
|
|
19
|
+
* @param tokenAmount - 代币数量(wei)
|
|
20
|
+
* @param chainId - 链 ID(56=BSC, 143=Monad)
|
|
21
|
+
* @returns 等值的原生代币数量(wei),失败返回 0n
|
|
22
|
+
*/
|
|
23
|
+
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
|
|
24
|
+
if (tokenAmount <= 0n)
|
|
25
|
+
return 0n;
|
|
26
|
+
try {
|
|
27
|
+
if (chainId === 56) {
|
|
28
|
+
// BSC: 使用 PancakeSwap V2 Router
|
|
29
|
+
const router = new Contract(BSC_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
|
|
30
|
+
const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, BSC_WBNB]);
|
|
31
|
+
return amounts[1];
|
|
32
|
+
}
|
|
33
|
+
// 其他链暂不支持报价,返回 0
|
|
34
|
+
return 0n;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// 报价失败返回 0(可能是流动性不足或路径不存在)
|
|
38
|
+
return 0n;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
6
41
|
/**
|
|
7
42
|
* 分发(仅签名版本 - 不依赖 Merkle)
|
|
8
43
|
* ✅ 精简版:只负责签名交易,不提交到 Merkle
|
|
@@ -114,7 +149,8 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
114
149
|
// ✅ ERC20:并行获取 decimals
|
|
115
150
|
const decimals = tokenDecimals ?? (await _getErc20DecimalsMerkle(provider, tokenAddress));
|
|
116
151
|
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
117
|
-
//
|
|
152
|
+
// 先计算所有金额和利润(同步)- ERC20 利润以代币计
|
|
153
|
+
let totalTokenProfit = 0n;
|
|
118
154
|
const txDataList = recipients.map((to, i) => {
|
|
119
155
|
const originalAmount = ethers.parseUnits(normalizedAmounts[i], decimals);
|
|
120
156
|
totalAmountBeforeProfit += originalAmount;
|
|
@@ -122,11 +158,17 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
122
158
|
if (extractProfit) {
|
|
123
159
|
const { profit, remaining } = calculateProfit(originalAmount, config);
|
|
124
160
|
actualAmount = remaining;
|
|
125
|
-
|
|
161
|
+
totalTokenProfit += profit; // 累计 ERC20 代币利润
|
|
126
162
|
}
|
|
127
163
|
const data = iface.encodeFunctionData('transfer', [to, actualAmount]);
|
|
128
164
|
return { data, nonce: nonces[i] };
|
|
129
165
|
});
|
|
166
|
+
// ✅ 获取 ERC20 利润等值的原生代币(BNB)报价
|
|
167
|
+
let nativeProfitAmount = 0n;
|
|
168
|
+
if (extractProfit && totalTokenProfit > 0n) {
|
|
169
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
|
|
170
|
+
totalProfit = nativeProfitAmount; // 更新为原生代币利润
|
|
171
|
+
}
|
|
130
172
|
// ✅ 并行签名所有交易
|
|
131
173
|
const txPromises = txDataList.map(({ data, nonce }) => mainWallet.signTransaction({
|
|
132
174
|
to: tokenAddress,
|
|
@@ -138,14 +180,14 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
138
180
|
chainId: chainIdNum,
|
|
139
181
|
type: txType
|
|
140
182
|
}));
|
|
141
|
-
//
|
|
142
|
-
if (extractProfit &&
|
|
183
|
+
// 利润交易:转等值的原生代币(BNB)
|
|
184
|
+
if (extractProfit && nativeProfitAmount > 0n) {
|
|
143
185
|
txPromises.push(mainWallet.signTransaction({
|
|
144
186
|
to: getProfitRecipient(),
|
|
145
|
-
value:
|
|
187
|
+
value: nativeProfitAmount, // ✅ 转等值 BNB
|
|
146
188
|
nonce: nonces[recipients.length],
|
|
147
189
|
gasPrice,
|
|
148
|
-
gasLimit: 21000n,
|
|
190
|
+
gasLimit: 21000n,
|
|
149
191
|
chainId: chainIdNum,
|
|
150
192
|
type: txType
|
|
151
193
|
}));
|
|
@@ -190,6 +232,8 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
190
232
|
: await nonceManager.getNextNonceBatch(mainWallet, mainWalletNonceCount);
|
|
191
233
|
let mainNonceIdx = 0;
|
|
192
234
|
const txsToSign = [];
|
|
235
|
+
// ✅ ERC20 多跳:累计代币利润,最后统一转换为原生代币
|
|
236
|
+
let totalTokenProfit = 0n;
|
|
193
237
|
for (let i = 0; i < recipients.length; i++) {
|
|
194
238
|
const finalRecipient = recipients[i];
|
|
195
239
|
const originalAmountWei = isNative
|
|
@@ -200,7 +244,12 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
200
244
|
if (extractProfit) {
|
|
201
245
|
const { profit, remaining } = calculateProfit(originalAmountWei, config);
|
|
202
246
|
amountWei = remaining;
|
|
203
|
-
|
|
247
|
+
if (isNative) {
|
|
248
|
+
totalProfit += profit; // 原生币直接累加
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
totalTokenProfit += profit; // ERC20 累计代币利润
|
|
252
|
+
}
|
|
204
253
|
}
|
|
205
254
|
const hopChain = preparedHops[i];
|
|
206
255
|
if (hopChain.length === 0) {
|
|
@@ -299,17 +348,22 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
299
348
|
}
|
|
300
349
|
}
|
|
301
350
|
}
|
|
302
|
-
//
|
|
351
|
+
// ✅ ERC20 多跳:获取代币利润等值的原生代币报价
|
|
352
|
+
if (!isNative && extractProfit && totalTokenProfit > 0n) {
|
|
353
|
+
const nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
|
|
354
|
+
totalProfit = nativeProfitAmount; // 更新为原生代币利润
|
|
355
|
+
}
|
|
356
|
+
// 利润转账(转等值原生代币)
|
|
303
357
|
if (extractProfit && totalProfit > 0n) {
|
|
304
358
|
const profitNonce = allMainNonces[mainNonceIdx++];
|
|
305
359
|
txsToSign.push({
|
|
306
360
|
wallet: mainWallet,
|
|
307
361
|
tx: {
|
|
308
362
|
to: getProfitRecipient(),
|
|
309
|
-
value: totalProfit, // ✅
|
|
363
|
+
value: totalProfit, // ✅ 转等值原生代币
|
|
310
364
|
nonce: profitNonce,
|
|
311
365
|
gasPrice,
|
|
312
|
-
gasLimit: 21000n,
|
|
366
|
+
gasLimit: 21000n,
|
|
313
367
|
chainId: chainIdNum,
|
|
314
368
|
type: txType
|
|
315
369
|
}
|
|
@@ -603,42 +657,30 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
603
657
|
maxSweepAmount = toSend;
|
|
604
658
|
maxSweepIndex = i;
|
|
605
659
|
}
|
|
606
|
-
//
|
|
660
|
+
// 累计总代币利润(ERC20 归集)
|
|
607
661
|
if (extractProfit && toSend > 0n) {
|
|
608
662
|
const { profit } = calculateProfit(toSend, config);
|
|
609
|
-
totalProfit += profit;
|
|
663
|
+
totalProfit += profit; // 先累计代币利润
|
|
610
664
|
}
|
|
611
665
|
}
|
|
666
|
+
// ✅ ERC20 归集:获取代币利润等值的原生代币(BNB)报价
|
|
667
|
+
let nativeProfitAmount = 0n;
|
|
668
|
+
if (extractProfit && totalProfit > 0n) {
|
|
669
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalProfit, chainIdNum);
|
|
670
|
+
}
|
|
612
671
|
// ✅ 如果需要提取利润,检查支付者是否有足够 BNB 支付利润转账的额外 gas
|
|
613
|
-
if (extractProfit &&
|
|
672
|
+
if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && config.checkBnbForErc20NoHop) {
|
|
614
673
|
const payerBnbBalance = bnbBalances[maxSweepIndex] ?? 0n;
|
|
615
|
-
const payerNeedGas = finalGasLimit * gasPrice + profitTxGas; // 支付者需要
|
|
616
|
-
// 如果支付者 BNB
|
|
674
|
+
const payerNeedGas = finalGasLimit * gasPrice + profitTxGas + nativeProfitAmount; // 支付者需要 gas + 利润
|
|
675
|
+
// 如果支付者 BNB 余额不足,跳过利润提取
|
|
617
676
|
if (payerBnbBalance < payerNeedGas) {
|
|
618
|
-
|
|
619
|
-
// 重新寻找最大归集金额的钱包
|
|
620
|
-
maxSweepIndex = -1;
|
|
621
|
-
maxSweepAmount = 0n;
|
|
622
|
-
for (let i = 0; i < sweepAmounts.length; i++) {
|
|
623
|
-
if (sweepAmounts[i] > maxSweepAmount) {
|
|
624
|
-
maxSweepAmount = sweepAmounts[i];
|
|
625
|
-
maxSweepIndex = i;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
// 重新计算总金额和总利润
|
|
629
|
-
totalAmountBeforeProfit = sweepAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
630
|
-
totalProfit = 0n;
|
|
631
|
-
for (let i = 0; i < sweepAmounts.length; i++) {
|
|
632
|
-
if (sweepAmounts[i] > 0n) {
|
|
633
|
-
totalProfit += calculateProfit(sweepAmounts[i], config).profit;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
677
|
+
nativeProfitAmount = 0n; // 跳过利润提取
|
|
636
678
|
}
|
|
637
679
|
}
|
|
638
|
-
// ✅
|
|
680
|
+
// ✅ 第二步:生成归集交易(ERC20 归集不扣除代币,利润从 BNB 支付)
|
|
639
681
|
// ✅ 先为支付者预留 nonce,确保 nonce 连续
|
|
640
682
|
let payerProfitNonce;
|
|
641
|
-
if (extractProfit &&
|
|
683
|
+
if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0) {
|
|
642
684
|
const payerWallet = wallets[maxSweepIndex];
|
|
643
685
|
const nonces = await nonceManager.getNextNonceBatch(payerWallet, 2);
|
|
644
686
|
payerProfitNonce = nonces[1]; // 利润交易的 nonce
|
|
@@ -654,16 +696,8 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
654
696
|
const toSend = sweepAmounts[i];
|
|
655
697
|
if (toSend <= 0n)
|
|
656
698
|
return null;
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
// ✅ 如果是支付者,扣除所有利润总和;其他钱包不扣利润,归集干净
|
|
660
|
-
if (i === maxSweepIndex && totalProfit > 0n) {
|
|
661
|
-
actualToSend = toSend - totalProfit; // 支付者扣除所有利润总和
|
|
662
|
-
}
|
|
663
|
-
else {
|
|
664
|
-
actualToSend = toSend; // 其他钱包不扣利润,归集全部
|
|
665
|
-
}
|
|
666
|
-
}
|
|
699
|
+
// ✅ ERC20 归集:全部归集,不扣除代币(利润从 BNB 支付)
|
|
700
|
+
const actualToSend = toSend;
|
|
667
701
|
// ✅ 支付者使用预留的第一个 nonce,其他钱包使用批量获取的 nonce
|
|
668
702
|
let nonce;
|
|
669
703
|
if (i === maxSweepIndex && payerProfitNonce !== undefined) {
|
|
@@ -687,15 +721,16 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
687
721
|
});
|
|
688
722
|
const allTxs = (await Promise.all(txPromises)).filter(tx => tx !== null);
|
|
689
723
|
signedTxs.push(...allTxs);
|
|
690
|
-
// ✅
|
|
691
|
-
if (extractProfit &&
|
|
724
|
+
// ✅ 第三步:生成利润交易(转等值原生代币)
|
|
725
|
+
if (extractProfit && nativeProfitAmount > 0n && maxSweepIndex >= 0 && payerProfitNonce !== undefined) {
|
|
692
726
|
const payerWallet = wallets[maxSweepIndex];
|
|
727
|
+
totalProfit = nativeProfitAmount; // 更新为原生代币利润
|
|
693
728
|
const profitTx = await payerWallet.signTransaction({
|
|
694
729
|
to: getProfitRecipient(),
|
|
695
|
-
value:
|
|
730
|
+
value: nativeProfitAmount, // ✅ 转等值原生代币
|
|
696
731
|
nonce: payerProfitNonce,
|
|
697
732
|
gasPrice,
|
|
698
|
-
gasLimit: 21000n,
|
|
733
|
+
gasLimit: 21000n,
|
|
699
734
|
chainId: chainIdNum,
|
|
700
735
|
type: txType
|
|
701
736
|
});
|
|
@@ -967,23 +1002,35 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
967
1002
|
maxSweepIndex = i;
|
|
968
1003
|
}
|
|
969
1004
|
}
|
|
970
|
-
//
|
|
1005
|
+
// 计算总代币利润
|
|
1006
|
+
let totalTokenProfit = 0n;
|
|
971
1007
|
for (let i = 0; i < sweepAmounts.length; i++) {
|
|
972
1008
|
if (sweepAmounts[i] > 0n) {
|
|
973
1009
|
const { profit } = calculateProfit(sweepAmounts[i], config);
|
|
974
|
-
|
|
1010
|
+
if (isNative) {
|
|
1011
|
+
totalProfit += profit; // 原生币直接累加
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
totalTokenProfit += profit; // ERC20 累计代币利润
|
|
1015
|
+
}
|
|
975
1016
|
}
|
|
976
1017
|
}
|
|
977
|
-
//
|
|
978
|
-
|
|
1018
|
+
// ✅ ERC20 多跳归集:获取代币利润等值的原生代币报价
|
|
1019
|
+
let nativeProfitAmount = isNative ? totalProfit : 0n;
|
|
1020
|
+
if (!isNative && totalTokenProfit > 0n) {
|
|
1021
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum);
|
|
1022
|
+
totalProfit = nativeProfitAmount; // 更新为原生代币利润
|
|
1023
|
+
}
|
|
1024
|
+
// 由归集金额最大的钱包支付利润(转等值原生代币)
|
|
1025
|
+
if (nativeProfitAmount > 0n && maxSweepIndex >= 0) {
|
|
979
1026
|
const payerWallet = sourceWallets[maxSweepIndex];
|
|
980
1027
|
const profitNonce = await nonceManager.getNextNonce(payerWallet);
|
|
981
1028
|
const profitTx = await payerWallet.signTransaction({
|
|
982
1029
|
to: getProfitRecipient(),
|
|
983
|
-
value:
|
|
1030
|
+
value: nativeProfitAmount, // ✅ 转等值原生代币
|
|
984
1031
|
nonce: profitNonce,
|
|
985
1032
|
gasPrice,
|
|
986
|
-
gasLimit: 21000n,
|
|
1033
|
+
gasLimit: 21000n,
|
|
987
1034
|
chainId: chainIdNum,
|
|
988
1035
|
type: txType
|
|
989
1036
|
});
|
|
@@ -997,7 +1044,7 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
997
1044
|
hopWallets: preparedHops || undefined,
|
|
998
1045
|
metadata: extractProfit ? {
|
|
999
1046
|
totalAmount: isNative ? ethers.formatEther(totalAmountBeforeProfit) : ethers.formatUnits(totalAmountBeforeProfit, tokenDecimals ?? 18),
|
|
1000
|
-
profitAmount:
|
|
1047
|
+
profitAmount: ethers.formatEther(totalProfit), // ✅ 利润统一用原生代币(BNB/ETH)显示
|
|
1001
1048
|
profitRecipient: getProfitRecipient(),
|
|
1002
1049
|
sourceCount: actualKeys.length,
|
|
1003
1050
|
isNative,
|
|
@@ -7,6 +7,39 @@ const MULTICALL3_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11';
|
|
|
7
7
|
const MULTICALL3_ABI = [
|
|
8
8
|
'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) external payable returns (tuple(bool success, bytes returnData)[])'
|
|
9
9
|
];
|
|
10
|
+
// ==================== ERC20 → 原生代币报价 ====================
|
|
11
|
+
// BSC 链常量
|
|
12
|
+
const BSC_PANCAKE_V2_ROUTER = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
|
|
13
|
+
const BSC_WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
|
|
14
|
+
const ROUTER_ABI = [
|
|
15
|
+
'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* 获取 ERC20 代币 → 原生代币的报价
|
|
19
|
+
* @param provider - Provider 实例
|
|
20
|
+
* @param tokenAddress - ERC20 代币地址
|
|
21
|
+
* @param tokenAmount - 代币数量(wei)
|
|
22
|
+
* @param chainId - 链 ID(56=BSC)
|
|
23
|
+
* @returns 等值的原生代币数量(wei),失败返回 0n
|
|
24
|
+
*/
|
|
25
|
+
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
|
|
26
|
+
if (tokenAmount <= 0n)
|
|
27
|
+
return 0n;
|
|
28
|
+
try {
|
|
29
|
+
if (chainId === 56) {
|
|
30
|
+
// BSC: 使用 PancakeSwap V2 Router
|
|
31
|
+
const router = new Contract(BSC_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
|
|
32
|
+
const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, BSC_WBNB]);
|
|
33
|
+
return amounts[1];
|
|
34
|
+
}
|
|
35
|
+
// 其他链暂不支持报价,返回 0
|
|
36
|
+
return 0n;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// 报价失败返回 0(可能是流动性不足或路径不存在)
|
|
40
|
+
return 0n;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
10
43
|
/**
|
|
11
44
|
* 获取 Gas Limit
|
|
12
45
|
* 优先使用 config.gasLimit,否则使用默认值 * multiplier
|
|
@@ -182,11 +215,18 @@ export async function batchBuyWithBundleMerkle(params) {
|
|
|
182
215
|
console.log('🔍 SDK inputToken 计算结果:', inputToken);
|
|
183
216
|
console.log('🔍 SDK useNativeToken:', useNativeToken);
|
|
184
217
|
console.log('🔍 SDK adjustedFundsList:', adjustedFundsList);
|
|
218
|
+
// ✅ ERC20 购买:获取代币利润等值的原生代币(BNB)报价
|
|
219
|
+
let nativeProfitAmount = totalProfit; // 原生代币购买时直接使用
|
|
220
|
+
if (!useNativeToken && extractProfit && totalProfit > 0n) {
|
|
221
|
+
// 将代币利润转换为等值 BNB
|
|
222
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, inputToken, totalProfit, chainId);
|
|
223
|
+
console.log('🔍 SDK ERC20 利润转换: ', ethers.formatEther(totalProfit), ' Token -> ', ethers.formatEther(nativeProfitAmount), ' BNB');
|
|
224
|
+
}
|
|
185
225
|
// ✅ 优化:并行执行 gasPrice、populateBuyTransactions 和 allocateBuyerNonces(三个最耗时的 RPC 操作)
|
|
186
226
|
const [gasPrice, unsignedBuys, buyerNonces] = await Promise.all([
|
|
187
227
|
resolveGasPrice(provider, config),
|
|
188
228
|
populateBuyTransactionsWithQuote(buyers, FLAP_PORTAL_ADDRESSES[chain], tokenAddress, adjustedFundsList, inputToken, useNativeToken),
|
|
189
|
-
allocateBuyerNonces(buyers, extractProfit, maxFundsIndex,
|
|
229
|
+
allocateBuyerNonces(buyers, extractProfit, maxFundsIndex, nativeProfitAmount, nonceManager)
|
|
190
230
|
]);
|
|
191
231
|
const signedBuys = await signBuyTransactions({
|
|
192
232
|
unsignedBuys,
|
|
@@ -201,7 +241,7 @@ export async function batchBuyWithBundleMerkle(params) {
|
|
|
201
241
|
signedTxs.push(...signedBuys);
|
|
202
242
|
await appendProfitTransaction({
|
|
203
243
|
extractProfit,
|
|
204
|
-
totalProfit,
|
|
244
|
+
totalProfit: nativeProfitAmount, // ✅ 使用转换后的原生代币利润
|
|
205
245
|
buyers,
|
|
206
246
|
maxIndex: maxFundsIndex,
|
|
207
247
|
nonces: buyerNonces,
|
|
@@ -213,7 +253,7 @@ export async function batchBuyWithBundleMerkle(params) {
|
|
|
213
253
|
nonceManager.clearTemp();
|
|
214
254
|
return {
|
|
215
255
|
signedTransactions: signedTxs,
|
|
216
|
-
metadata: buildProfitMetadata(extractProfit, totalBuyAmount,
|
|
256
|
+
metadata: buildProfitMetadata(extractProfit, totalBuyAmount, nativeProfitAmount, buyers.length)
|
|
217
257
|
};
|
|
218
258
|
}
|
|
219
259
|
/**
|
|
@@ -237,6 +277,7 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
237
277
|
const portals = wallets.map(w => new ethers.Contract(portalAddr, PORTAL_ABI, w));
|
|
238
278
|
// ✅ 确定 outputToken:如果传入了 quoteToken 且非零地址,则使用它;否则使用零地址(原生代币)
|
|
239
279
|
const outputToken = quoteToken && quoteToken !== ZERO_ADDRESS ? quoteToken : ZERO_ADDRESS;
|
|
280
|
+
const useNativeOutput = outputToken === ZERO_ADDRESS;
|
|
240
281
|
// ✅ 优化:并行执行 gasPrice、quoteSellOutputs(Multicall3)和 nonces(批量获取)
|
|
241
282
|
const [gasPrice, quotedOutputs, nonces] = await Promise.all([
|
|
242
283
|
resolveGasPrice(provider, config),
|
|
@@ -271,7 +312,10 @@ export async function batchSellWithBundleMerkle(params) {
|
|
|
271
312
|
signedTxs,
|
|
272
313
|
chainId,
|
|
273
314
|
gasPrice,
|
|
274
|
-
config
|
|
315
|
+
config,
|
|
316
|
+
outputToken, // ✅ 传递 outputToken
|
|
317
|
+
useNativeOutput, // ✅ 传递是否原生代币输出
|
|
318
|
+
provider // ✅ 传递 provider 用于报价
|
|
275
319
|
});
|
|
276
320
|
nonceManager.clearTemp();
|
|
277
321
|
return {
|
|
@@ -474,31 +518,40 @@ function resolveMinOutputs(provided, walletCount, quotedOutputs) {
|
|
|
474
518
|
}
|
|
475
519
|
return quotedOutputs.map(quoted => quoted * 95n / 100n);
|
|
476
520
|
}
|
|
477
|
-
async function appendSellProfitTransaction({ extractProfit, quotedOutputs, wallets, nonceManager, signedTxs, chainId, gasPrice, config }) {
|
|
521
|
+
async function appendSellProfitTransaction({ extractProfit, quotedOutputs, wallets, nonceManager, signedTxs, chainId, gasPrice, config, outputToken, useNativeOutput = true, provider }) {
|
|
478
522
|
if (!extractProfit || quotedOutputs.length === 0) {
|
|
479
523
|
return;
|
|
480
524
|
}
|
|
481
|
-
let
|
|
525
|
+
let totalTokenProfit = 0n; // 代币利润
|
|
482
526
|
let maxRevenueIndex = -1;
|
|
483
527
|
let maxRevenue = 0n;
|
|
484
528
|
for (let i = 0; i < wallets.length; i++) {
|
|
485
529
|
const quoted = quotedOutputs[i];
|
|
486
530
|
if (quoted > 0n) {
|
|
487
531
|
const { profit } = calculateProfit(quoted, config);
|
|
488
|
-
|
|
532
|
+
totalTokenProfit += profit;
|
|
489
533
|
if (quoted > maxRevenue) {
|
|
490
534
|
maxRevenue = quoted;
|
|
491
535
|
maxRevenueIndex = i;
|
|
492
536
|
}
|
|
493
537
|
}
|
|
494
538
|
}
|
|
495
|
-
if (
|
|
539
|
+
if (totalTokenProfit === 0n || maxRevenueIndex < 0) {
|
|
496
540
|
return;
|
|
497
541
|
}
|
|
542
|
+
// ✅ ERC20 输出:获取代币利润等值的原生代币(BNB)报价
|
|
543
|
+
let nativeProfitAmount = totalTokenProfit; // 原生代币输出时直接使用
|
|
544
|
+
if (!useNativeOutput && outputToken && provider) {
|
|
545
|
+
nativeProfitAmount = await getTokenToNativeQuote(provider, outputToken, totalTokenProfit, chainId);
|
|
546
|
+
// 如果报价失败(返回 0),跳过利润提取
|
|
547
|
+
if (nativeProfitAmount === 0n) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
498
551
|
const profitNonce = await nonceManager.getNextNonce(wallets[maxRevenueIndex]);
|
|
499
552
|
const profitTx = await wallets[maxRevenueIndex].signTransaction({
|
|
500
553
|
to: getProfitRecipient(),
|
|
501
|
-
value:
|
|
554
|
+
value: nativeProfitAmount, // ✅ 转等值原生代币
|
|
502
555
|
nonce: profitNonce,
|
|
503
556
|
gasPrice,
|
|
504
557
|
gasLimit: 21000n,
|