four-flap-meme-sdk 1.3.16 → 1.3.17
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,
|