four-flap-meme-sdk 1.3.73 → 1.3.76
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/dex/direct-router.js +341 -290
- package/dist/flap/portal-bundle-merkle/index.d.ts +1 -0
- package/dist/flap/portal-bundle-merkle/index.js +2 -0
- package/dist/flap/portal-bundle-merkle/utils.d.ts +88 -0
- package/dist/flap/portal-bundle-merkle/utils.js +1019 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -16,6 +16,25 @@ import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
|
16
16
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
17
17
|
const DEFAULT_GAS_LIMIT = 300000;
|
|
18
18
|
const DEADLINE_MINUTES = 20;
|
|
19
|
+
// ✅ Router ABI(用于 ERC20 → 原生代币报价)
|
|
20
|
+
const QUOTE_ROUTER_ABI = [
|
|
21
|
+
'function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts)'
|
|
22
|
+
];
|
|
23
|
+
// ✅ 各链的报价 Router 和包装代币地址
|
|
24
|
+
const QUOTE_CONFIG = {
|
|
25
|
+
BSC: {
|
|
26
|
+
router: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // PancakeSwap V2
|
|
27
|
+
wrappedNative: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', // WBNB
|
|
28
|
+
},
|
|
29
|
+
MONAD: {
|
|
30
|
+
router: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9', // PancakeSwap V2
|
|
31
|
+
wrappedNative: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a', // WMON
|
|
32
|
+
},
|
|
33
|
+
XLAYER: {
|
|
34
|
+
router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // PotatoSwap V2
|
|
35
|
+
wrappedNative: '0xe538905cf8410324e03a5a23c1c177a474d59b2b', // WOKB
|
|
36
|
+
},
|
|
37
|
+
};
|
|
19
38
|
/**
|
|
20
39
|
* 截断小数位数,避免超过代币精度导致 parseUnits 报错
|
|
21
40
|
* 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
|
|
@@ -504,6 +523,52 @@ async function getGasPrice(provider, config) {
|
|
|
504
523
|
function isNativeToken(quoteToken) {
|
|
505
524
|
return !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
506
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* ✅ 获取 ERC20 代币 → 原生代币的报价
|
|
528
|
+
* 用于将 ERC20 利润转换为原生代币(和 core.ts 逻辑一致)
|
|
529
|
+
* @param provider - Provider 实例
|
|
530
|
+
* @param tokenAddress - ERC20 代币地址
|
|
531
|
+
* @param tokenAmount - 代币数量(wei)
|
|
532
|
+
* @param chain - 链名称
|
|
533
|
+
* @returns 等值的原生代币数量(wei),失败返回 0n
|
|
534
|
+
*/
|
|
535
|
+
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chain) {
|
|
536
|
+
if (tokenAmount <= 0n)
|
|
537
|
+
return 0n;
|
|
538
|
+
const chainUpper = chain.toUpperCase();
|
|
539
|
+
const config = QUOTE_CONFIG[chainUpper];
|
|
540
|
+
if (!config) {
|
|
541
|
+
console.warn(`[getTokenToNativeQuote] 不支持的链: ${chain}`);
|
|
542
|
+
return 0n;
|
|
543
|
+
}
|
|
544
|
+
try {
|
|
545
|
+
const router = new Contract(config.router, QUOTE_ROUTER_ABI, provider);
|
|
546
|
+
const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, config.wrappedNative]);
|
|
547
|
+
const nativeAmount = amounts[1];
|
|
548
|
+
console.log(`[getTokenToNativeQuote] ${ethers.formatEther(tokenAmount)} Token → ${ethers.formatEther(nativeAmount)} Native`);
|
|
549
|
+
return nativeAmount;
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
console.warn(`[getTokenToNativeQuote] 报价失败,跳过利润提取:`, error);
|
|
553
|
+
return 0n; // 报价失败返回 0(可能是流动性不足或路径不存在)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* ✅ 找到金额最大的钱包索引(和 core.ts 逻辑一致)
|
|
558
|
+
*/
|
|
559
|
+
function findMaxFlowIndex(amounts) {
|
|
560
|
+
if (amounts.length === 0)
|
|
561
|
+
return 0;
|
|
562
|
+
let maxIndex = 0;
|
|
563
|
+
let maxValue = amounts[0];
|
|
564
|
+
for (let i = 1; i < amounts.length; i++) {
|
|
565
|
+
if (amounts[i] > maxValue) {
|
|
566
|
+
maxValue = amounts[i];
|
|
567
|
+
maxIndex = i;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return maxIndex;
|
|
571
|
+
}
|
|
507
572
|
/** 计算利润金额 */
|
|
508
573
|
function calculateProfitAmount(totalFlowWei) {
|
|
509
574
|
return (totalFlowWei * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
|
|
@@ -602,98 +667,61 @@ export async function directV2BatchBuy(params) {
|
|
|
602
667
|
const routerIface = useSwapRouter02
|
|
603
668
|
? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
|
|
604
669
|
: new ethers.Interface(V2_ROUTER_ABI);
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
totalFlowWei += amountWei;
|
|
611
|
-
let txData;
|
|
612
|
-
let txValue;
|
|
670
|
+
// ✅ 优化:预先计算所有金额(同步操作)
|
|
671
|
+
const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
|
|
672
|
+
const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
673
|
+
// ✅ 优化:构建未签名交易的辅助函数
|
|
674
|
+
const buildTxData = (wallet, amountWei) => {
|
|
613
675
|
if (useDYORSwap) {
|
|
614
|
-
// ✅ DYORSwap: 使用手动编码的 calldata
|
|
615
|
-
//
|
|
616
|
-
// 根据成功交易分析,买入流程:
|
|
617
|
-
// 1. refundETH - 用于退还多余的 ETH (使用固定的 ETHBack_Dividend 地址)
|
|
618
|
-
// 2. swapExactTokensForTokens - 执行交换
|
|
619
676
|
const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
|
|
620
677
|
const multicallData = [];
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
const swapData = encodeDYORSwapExactTokensForTokens(useNative ? 0n : amountWei, // amountIn: ETH 买入时为 0
|
|
628
|
-
0n, // amountOutMin
|
|
629
|
-
path, // path: [WOKB, token]
|
|
630
|
-
[], // pools (不使用)
|
|
631
|
-
wallet.address, // to = 用户地址
|
|
632
|
-
1n, // flag = 1
|
|
633
|
-
DYORSWAP_FACTORY // factory
|
|
634
|
-
);
|
|
635
|
-
multicallData.push(swapData);
|
|
636
|
-
// 使用 multicall(uint256 deadline, bytes[])
|
|
637
|
-
txData = encodeDYORMulticall(BigInt(deadline), multicallData);
|
|
638
|
-
txValue = useNative ? amountWei : 0n;
|
|
678
|
+
multicallData.push(encodeDYORRefundETH(30));
|
|
679
|
+
multicallData.push(encodeDYORSwapExactTokensForTokens(useNative ? 0n : amountWei, 0n, path, [], wallet.address, 1n, DYORSWAP_FACTORY));
|
|
680
|
+
return {
|
|
681
|
+
txData: encodeDYORMulticall(BigInt(deadline), multicallData),
|
|
682
|
+
txValue: useNative ? amountWei : 0n
|
|
683
|
+
};
|
|
639
684
|
}
|
|
640
685
|
else if (useSwapRouter02) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
// ETH -> 代币:直接用 swapExactTokensForTokens(参考之前成功的交易)
|
|
645
|
-
// 之前成功的交易只用了一个 swapExactTokensForTokens,没有 wrapETH
|
|
646
|
-
// SwapRouter02 会自动处理 msg.value 的 ETH
|
|
647
|
-
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
|
|
648
|
-
amountWei,
|
|
649
|
-
0n, // amountOutMin
|
|
650
|
-
path, // path: [WETH, ..., tokenOut]
|
|
651
|
-
wallet.address, // to
|
|
652
|
-
]);
|
|
653
|
-
// 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
|
|
654
|
-
txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
|
|
655
|
-
deadline,
|
|
656
|
-
[swapData],
|
|
657
|
-
]);
|
|
658
|
-
txValue = amountWei;
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
// 代币 -> 代币:也用 multicall 包装
|
|
662
|
-
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
|
|
663
|
-
amountWei,
|
|
664
|
-
0n, // amountOutMin
|
|
665
|
-
path,
|
|
666
|
-
wallet.address, // to
|
|
667
|
-
]);
|
|
668
|
-
txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
|
|
669
|
-
deadline,
|
|
670
|
-
[swapData],
|
|
671
|
-
]);
|
|
672
|
-
txValue = 0n;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
else if (useNative) {
|
|
676
|
-
// 传统 V2 Router: 原生币 → Token
|
|
677
|
-
txData = routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [
|
|
678
|
-
0n, // amountOutMin
|
|
686
|
+
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
|
|
687
|
+
amountWei,
|
|
688
|
+
0n,
|
|
679
689
|
path,
|
|
680
690
|
wallet.address,
|
|
681
|
-
deadline,
|
|
682
691
|
]);
|
|
683
|
-
|
|
692
|
+
return {
|
|
693
|
+
txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]),
|
|
694
|
+
txValue: useNative ? amountWei : 0n
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
else if (useNative) {
|
|
698
|
+
return {
|
|
699
|
+
txData: routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [
|
|
700
|
+
0n,
|
|
701
|
+
path,
|
|
702
|
+
wallet.address,
|
|
703
|
+
deadline,
|
|
704
|
+
]),
|
|
705
|
+
txValue: amountWei
|
|
706
|
+
};
|
|
684
707
|
}
|
|
685
708
|
else {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
709
|
+
return {
|
|
710
|
+
txData: routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [
|
|
711
|
+
amountWei,
|
|
712
|
+
0n,
|
|
713
|
+
path,
|
|
714
|
+
wallet.address,
|
|
715
|
+
deadline,
|
|
716
|
+
]),
|
|
717
|
+
txValue: 0n
|
|
718
|
+
};
|
|
695
719
|
}
|
|
696
|
-
|
|
720
|
+
};
|
|
721
|
+
// ✅ 优化:并行签名所有交易
|
|
722
|
+
const signedTxs = await Promise.all(wallets.map(async (wallet, i) => {
|
|
723
|
+
const { txData, txValue } = buildTxData(wallet, flowAmounts[i]);
|
|
724
|
+
return wallet.signTransaction({
|
|
697
725
|
to: routerAddress,
|
|
698
726
|
data: txData,
|
|
699
727
|
value: txValue,
|
|
@@ -702,15 +730,26 @@ export async function directV2BatchBuy(params) {
|
|
|
702
730
|
gasPrice,
|
|
703
731
|
chainId,
|
|
704
732
|
type: txType,
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
|
|
733
|
+
});
|
|
734
|
+
}));
|
|
735
|
+
// ✅ 优化:选择金额最大的钱包支付利润(和 core.ts 逻辑一致)
|
|
736
|
+
const maxFlowIndex = findMaxFlowIndex(flowAmounts);
|
|
737
|
+
let profitWei = calculateProfitAmount(totalFlowWei);
|
|
738
|
+
// ✅ 修复:ERC20 交易时,将利润转换为原生代币
|
|
739
|
+
if (!useNative && profitWei > 0n && quoteToken) {
|
|
740
|
+
const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
|
|
741
|
+
if (nativeProfitWei > 0n) {
|
|
742
|
+
profitWei = nativeProfitWei;
|
|
743
|
+
console.log(`[V2 Buy] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalFlowWei))} Token → ${ethers.formatEther(profitWei)} Native`);
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
console.log(`[V2 Buy] ERC20 利润报价失败,跳过利润提取`);
|
|
747
|
+
profitWei = 0n; // 报价失败,跳过利润
|
|
748
|
+
}
|
|
708
749
|
}
|
|
709
|
-
// 生成利润交易(使用第一个钱包支付)
|
|
710
|
-
const profitWei = calculateProfitAmount(totalFlowWei);
|
|
711
750
|
if (profitWei > 0n) {
|
|
712
|
-
const profitNonce = nonces[
|
|
713
|
-
const profitTx = await buildProfitTransaction(wallets[
|
|
751
|
+
const profitNonce = nonces[maxFlowIndex] + 1; // ✅ 使用金额最大的钱包
|
|
752
|
+
const profitTx = await buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType);
|
|
714
753
|
signedTxs.push(profitTx);
|
|
715
754
|
}
|
|
716
755
|
return {
|
|
@@ -783,163 +822,129 @@ export async function directV2BatchSell(params) {
|
|
|
783
822
|
? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
|
|
784
823
|
: new ethers.Interface(V2_ROUTER_ABI);
|
|
785
824
|
const approveIface = new ethers.Interface(ERC20_ABI);
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
let currentNonceOffset = new Array(wallets.length).fill(0);
|
|
789
|
-
// 检查授权并构建交易
|
|
790
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
791
|
-
const wallet = wallets[i];
|
|
792
|
-
const sellAmount = sellAmountsWei[i];
|
|
793
|
-
if (sellAmount <= 0n)
|
|
794
|
-
continue;
|
|
795
|
-
// 检查授权
|
|
796
|
-
if (!config.skipApprovalCheck) {
|
|
797
|
-
const allowance = await tokenContract.allowance(wallet.address, routerAddress);
|
|
798
|
-
if (allowance < sellAmount) {
|
|
799
|
-
// 需要授权
|
|
800
|
-
const approveTx = {
|
|
801
|
-
to: tokenAddress,
|
|
802
|
-
data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
|
|
803
|
-
value: 0n,
|
|
804
|
-
nonce: nonces[i] + currentNonceOffset[i],
|
|
805
|
-
gasLimit: 60000n,
|
|
806
|
-
gasPrice,
|
|
807
|
-
chainId,
|
|
808
|
-
type: txType,
|
|
809
|
-
};
|
|
810
|
-
signedTxs.push(await wallet.signTransaction(approveTx));
|
|
811
|
-
currentNonceOffset[i]++;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
// 卖出交易
|
|
815
|
-
let txData;
|
|
825
|
+
// ✅ 优化:构建卖出交易数据的辅助函数(同步)
|
|
826
|
+
const buildSellTxData = (wallet, sellAmount) => {
|
|
816
827
|
if (useDYORSwap) {
|
|
817
|
-
// ✅ DYORSwap: 使用手动编码的 calldata
|
|
818
|
-
//
|
|
819
|
-
// 卖出流程 (根据成功交易分析):
|
|
820
|
-
// 1. swapExactTokensForTokens - 代币换成 WOKB,发到 address(2) = Router
|
|
821
|
-
// 2. unwrapWETH9 - 将 WOKB 解包为 OKB 发给用户
|
|
822
828
|
const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
|
|
823
|
-
const ADDRESS_THIS = 2n;
|
|
829
|
+
const ADDRESS_THIS = 2n;
|
|
824
830
|
const multicallData = [];
|
|
825
|
-
|
|
826
|
-
// 参数: amountIn, amountOutMin, path, pools, to, flag, factory
|
|
827
|
-
const swapData = encodeDYORSwapExactTokensForTokens(sellAmount, // amountIn
|
|
828
|
-
0n, // amountOutMin
|
|
829
|
-
path, // path: [token, WOKB]
|
|
830
|
-
[DYORSWAP_FACTORY], // pools
|
|
831
|
-
ADDRESS_THIS, // to = address(2) - 让 Router 接收 WOKB
|
|
832
|
-
1n, // flag = 1
|
|
833
|
-
DYORSWAP_FACTORY // factory
|
|
834
|
-
);
|
|
835
|
-
multicallData.push(swapData);
|
|
831
|
+
multicallData.push(encodeDYORSwapExactTokensForTokens(sellAmount, 0n, path, [DYORSWAP_FACTORY], ADDRESS_THIS, 1n, DYORSWAP_FACTORY));
|
|
836
832
|
if (useNativeOutput) {
|
|
837
|
-
|
|
838
|
-
const unwrapData = encodeDYORUnwrapWETH9(0n, // amountMinimum = 0
|
|
839
|
-
wallet.address, // recipient = 用户
|
|
840
|
-
'0x' // pools = 空 bytes
|
|
841
|
-
);
|
|
842
|
-
multicallData.push(unwrapData);
|
|
833
|
+
multicallData.push(encodeDYORUnwrapWETH9(0n, wallet.address, '0x'));
|
|
843
834
|
}
|
|
844
|
-
|
|
845
|
-
txData = encodeDYORMulticall(BigInt(deadline), multicallData);
|
|
835
|
+
return encodeDYORMulticall(BigInt(deadline), multicallData);
|
|
846
836
|
}
|
|
847
837
|
else if (useSwapRouter02) {
|
|
848
|
-
// ✅ SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
|
|
849
|
-
//
|
|
850
|
-
// 重要:SwapRouter02 的 V2 swapExactTokensForTokens 直接把代币发给 to 地址
|
|
851
|
-
// 如果需要 ETH,把 WETH 发给 Router,然后调用 unwrapWETH9
|
|
852
|
-
//
|
|
853
|
-
// 根据合约源码,address(2) = ADDRESS_THIS = Router 合约自己
|
|
854
838
|
const ADDRESS_THIS = '0x0000000000000000000000000000000000000002';
|
|
855
839
|
if (useNativeOutput) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
const routerRealAddress = routerAddress; // SwapRouter02 的真实地址
|
|
862
|
-
const multicallData = [];
|
|
863
|
-
// 1. swapExactTokensForTokens - 从用户转代币,换成 WETH 发到 Router 真实地址
|
|
864
|
-
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
|
|
865
|
-
sellAmount,
|
|
866
|
-
0n, // amountOutMin
|
|
867
|
-
path, // path: [token, ..., WETH]
|
|
868
|
-
ADDRESS_THIS, // to = address(2),让 Router 接收 WETH
|
|
869
|
-
]);
|
|
870
|
-
multicallData.push(swapData);
|
|
871
|
-
// 2. unwrapWETH9 - 将 Router 中的 WETH 解包为 ETH 发送给用户
|
|
872
|
-
const unwrapData = routerIface.encodeFunctionData('unwrapWETH9(uint256,address)', [
|
|
873
|
-
0n, // amountMinimum = 0,接受任意数量
|
|
874
|
-
wallet.address, // recipient = 用户
|
|
875
|
-
]);
|
|
876
|
-
multicallData.push(unwrapData);
|
|
877
|
-
// 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
|
|
878
|
-
txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
|
|
879
|
-
deadline,
|
|
880
|
-
multicallData,
|
|
881
|
-
]);
|
|
840
|
+
const multicallData = [
|
|
841
|
+
routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, ADDRESS_THIS]),
|
|
842
|
+
routerIface.encodeFunctionData('unwrapWETH9(uint256,address)', [0n, wallet.address]),
|
|
843
|
+
];
|
|
844
|
+
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, multicallData]);
|
|
882
845
|
}
|
|
883
846
|
else {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
sellAmount,
|
|
887
|
-
0n, // amountOutMin
|
|
888
|
-
path,
|
|
889
|
-
wallet.address, // to = 用户
|
|
890
|
-
]);
|
|
891
|
-
txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
|
|
892
|
-
deadline,
|
|
893
|
-
[swapData],
|
|
894
|
-
]);
|
|
847
|
+
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address]);
|
|
848
|
+
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]);
|
|
895
849
|
}
|
|
896
850
|
}
|
|
897
851
|
else if (useNativeOutput) {
|
|
898
|
-
|
|
899
|
-
sellAmount,
|
|
900
|
-
0n,
|
|
901
|
-
path,
|
|
902
|
-
wallet.address,
|
|
903
|
-
deadline,
|
|
904
|
-
]);
|
|
852
|
+
return routerIface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
|
|
905
853
|
}
|
|
906
854
|
else {
|
|
907
|
-
|
|
908
|
-
sellAmount,
|
|
909
|
-
0n,
|
|
910
|
-
path,
|
|
911
|
-
wallet.address,
|
|
912
|
-
deadline,
|
|
913
|
-
]);
|
|
855
|
+
return routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
|
|
914
856
|
}
|
|
915
|
-
|
|
857
|
+
};
|
|
858
|
+
// ✅ 优化:并行检查所有授权
|
|
859
|
+
let allowances = [];
|
|
860
|
+
if (!config.skipApprovalCheck) {
|
|
861
|
+
allowances = await Promise.all(wallets.map(w => tokenContract.allowance(w.address, routerAddress)));
|
|
862
|
+
}
|
|
863
|
+
// ✅ 优化:计算每个钱包的 nonce 偏移(是否需要授权)
|
|
864
|
+
const currentNonceOffset = wallets.map((_, i) => {
|
|
865
|
+
if (config.skipApprovalCheck)
|
|
866
|
+
return 0;
|
|
867
|
+
return allowances[i] < sellAmountsWei[i] ? 1 : 0;
|
|
868
|
+
});
|
|
869
|
+
// ✅ 优化:并行签名所有授权交易
|
|
870
|
+
const approvalTxPromises = wallets.map(async (wallet, i) => {
|
|
871
|
+
if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
|
|
872
|
+
return null;
|
|
873
|
+
if (allowances[i] >= sellAmountsWei[i])
|
|
874
|
+
return null;
|
|
875
|
+
return wallet.signTransaction({
|
|
876
|
+
to: tokenAddress,
|
|
877
|
+
data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
|
|
878
|
+
value: 0n,
|
|
879
|
+
nonce: nonces[i],
|
|
880
|
+
gasLimit: 60000n,
|
|
881
|
+
gasPrice,
|
|
882
|
+
chainId,
|
|
883
|
+
type: txType,
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
const approvalTxResults = await Promise.all(approvalTxPromises);
|
|
887
|
+
// ✅ 优化:并行签名所有卖出交易
|
|
888
|
+
const sellTxPromises = wallets.map(async (wallet, i) => {
|
|
889
|
+
if (sellAmountsWei[i] <= 0n)
|
|
890
|
+
return null;
|
|
891
|
+
return wallet.signTransaction({
|
|
916
892
|
to: routerAddress,
|
|
917
|
-
data:
|
|
893
|
+
data: buildSellTxData(wallet, sellAmountsWei[i]),
|
|
918
894
|
value: 0n,
|
|
919
895
|
nonce: nonces[i] + currentNonceOffset[i],
|
|
920
896
|
gasLimit,
|
|
921
897
|
gasPrice,
|
|
922
898
|
chainId,
|
|
923
899
|
type: txType,
|
|
924
|
-
};
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
900
|
+
});
|
|
901
|
+
});
|
|
902
|
+
const sellTxResults = await Promise.all(sellTxPromises);
|
|
903
|
+
// ✅ 优化:并行获取所有报价
|
|
904
|
+
const router = new Contract(routerAddress, V2_ROUTER_ABI, provider);
|
|
905
|
+
const quotePromises = wallets.map(async (_, i) => {
|
|
906
|
+
if (sellAmountsWei[i] <= 0n)
|
|
907
|
+
return sellAmountsWei[i];
|
|
928
908
|
try {
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
totalOutputEstimate += amounts[amounts.length - 1];
|
|
909
|
+
const amounts = await router.getAmountsOut(sellAmountsWei[i], path);
|
|
910
|
+
return amounts[amounts.length - 1];
|
|
932
911
|
}
|
|
933
912
|
catch {
|
|
934
|
-
|
|
935
|
-
|
|
913
|
+
return sellAmountsWei[i];
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
const outputEstimates = await Promise.all(quotePromises);
|
|
917
|
+
// ✅ 按顺序组装签名交易:先授权,后卖出
|
|
918
|
+
const signedTxs = [];
|
|
919
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
920
|
+
const approvalTx = approvalTxResults[i];
|
|
921
|
+
const sellTx = sellTxResults[i];
|
|
922
|
+
if (approvalTx)
|
|
923
|
+
signedTxs.push(approvalTx);
|
|
924
|
+
if (sellTx) {
|
|
925
|
+
signedTxs.push(sellTx);
|
|
926
|
+
currentNonceOffset[i]++; // 更新 offset 供利润交易使用
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const totalOutputEstimate = outputEstimates.reduce((sum, o) => sum + o, 0n);
|
|
930
|
+
// ✅ 优化:选择输出最大的钱包支付利润(和 core.ts 逻辑一致)
|
|
931
|
+
const maxOutputIndex = findMaxFlowIndex(outputEstimates);
|
|
932
|
+
let profitWei = calculateProfitAmount(totalOutputEstimate);
|
|
933
|
+
// ✅ 修复:ERC20 输出时,将利润转换为原生代币
|
|
934
|
+
if (!useNativeOutput && profitWei > 0n && quoteToken) {
|
|
935
|
+
const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
|
|
936
|
+
if (nativeProfitWei > 0n) {
|
|
937
|
+
profitWei = nativeProfitWei;
|
|
938
|
+
console.log(`[V2 Sell] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalOutputEstimate))} Token → ${ethers.formatEther(profitWei)} Native`);
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
console.log(`[V2 Sell] ERC20 利润报价失败,跳过利润提取`);
|
|
942
|
+
profitWei = 0n;
|
|
936
943
|
}
|
|
937
944
|
}
|
|
938
|
-
// 生成利润交易
|
|
939
|
-
const profitWei = calculateProfitAmount(totalOutputEstimate);
|
|
940
945
|
if (profitWei > 0n && wallets.length > 0) {
|
|
941
|
-
//
|
|
942
|
-
|
|
946
|
+
const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // ✅ 使用输出最大的钱包
|
|
947
|
+
profitWei, nonces[maxOutputIndex] + currentNonceOffset[maxOutputIndex], gasPrice, chainId, txType);
|
|
943
948
|
signedTxs.push(profitTx);
|
|
944
949
|
}
|
|
945
950
|
return {
|
|
@@ -982,41 +987,36 @@ export async function directV3BatchBuy(params) {
|
|
|
982
987
|
const txType = config.txType ?? 0;
|
|
983
988
|
const routerIface = new ethers.Interface(routerAbi);
|
|
984
989
|
const inputToken = useNative ? wrappedNative : quoteToken;
|
|
985
|
-
const signedTxs = [];
|
|
986
|
-
let totalFlowWei = 0n;
|
|
987
990
|
const deadline = getDeadline();
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
let txValue;
|
|
991
|
+
// ✅ 优化:预先计算所有金额
|
|
992
|
+
const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
|
|
993
|
+
const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
994
|
+
// ✅ 优化:构建交易数据的辅助函数
|
|
995
|
+
const buildV3BuyTxData = (wallet, amountWei) => {
|
|
994
996
|
if (useLegacyRouter) {
|
|
995
|
-
// ✅ 旧版 SwapRouter: exactInputSingle 包含 deadline
|
|
996
997
|
const swapParams = {
|
|
997
998
|
tokenIn: inputToken,
|
|
998
999
|
tokenOut: tokenAddress,
|
|
999
1000
|
fee: fee,
|
|
1000
1001
|
recipient: wallet.address,
|
|
1001
|
-
deadline: deadline,
|
|
1002
|
+
deadline: deadline,
|
|
1002
1003
|
amountIn: amountWei,
|
|
1003
|
-
amountOutMinimum: 0n,
|
|
1004
|
+
amountOutMinimum: 0n,
|
|
1004
1005
|
sqrtPriceLimitX96: 0n,
|
|
1005
1006
|
};
|
|
1006
1007
|
if (useNative) {
|
|
1007
|
-
// 旧版 multicall 不带 deadline
|
|
1008
1008
|
const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1009
1009
|
const refundData = routerIface.encodeFunctionData('refundETH', []);
|
|
1010
|
-
|
|
1011
|
-
|
|
1010
|
+
return {
|
|
1011
|
+
txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]),
|
|
1012
|
+
txValue: amountWei
|
|
1013
|
+
};
|
|
1012
1014
|
}
|
|
1013
1015
|
else {
|
|
1014
|
-
txData
|
|
1015
|
-
txValue = 0n;
|
|
1016
|
+
return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
|
|
1016
1017
|
}
|
|
1017
1018
|
}
|
|
1018
1019
|
else {
|
|
1019
|
-
// ✅ SwapRouter02: exactInputSingle 不含 deadline
|
|
1020
1020
|
const swapParams = {
|
|
1021
1021
|
tokenIn: inputToken,
|
|
1022
1022
|
tokenOut: tokenAddress,
|
|
@@ -1027,18 +1027,22 @@ export async function directV3BatchBuy(params) {
|
|
|
1027
1027
|
sqrtPriceLimitX96: 0n,
|
|
1028
1028
|
};
|
|
1029
1029
|
if (useNative) {
|
|
1030
|
-
// SwapRouter02 的 multicall 带 deadline
|
|
1031
1030
|
const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1032
1031
|
const refundData = routerIface.encodeFunctionData('refundETH', []);
|
|
1033
|
-
|
|
1034
|
-
|
|
1032
|
+
return {
|
|
1033
|
+
txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]),
|
|
1034
|
+
txValue: amountWei
|
|
1035
|
+
};
|
|
1035
1036
|
}
|
|
1036
1037
|
else {
|
|
1037
|
-
txData
|
|
1038
|
-
txValue = 0n;
|
|
1038
|
+
return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
|
|
1039
1039
|
}
|
|
1040
1040
|
}
|
|
1041
|
-
|
|
1041
|
+
};
|
|
1042
|
+
// ✅ 优化:并行签名所有交易
|
|
1043
|
+
const signedTxs = await Promise.all(wallets.map(async (wallet, i) => {
|
|
1044
|
+
const { txData, txValue } = buildV3BuyTxData(wallet, flowAmounts[i]);
|
|
1045
|
+
return wallet.signTransaction({
|
|
1042
1046
|
to: routerAddress,
|
|
1043
1047
|
data: txData,
|
|
1044
1048
|
value: txValue,
|
|
@@ -1047,13 +1051,26 @@ export async function directV3BatchBuy(params) {
|
|
|
1047
1051
|
gasPrice,
|
|
1048
1052
|
chainId,
|
|
1049
1053
|
type: txType,
|
|
1050
|
-
};
|
|
1051
|
-
|
|
1054
|
+
});
|
|
1055
|
+
}));
|
|
1056
|
+
// ✅ 优化:选择金额最大的钱包支付利润
|
|
1057
|
+
const maxFlowIndex = findMaxFlowIndex(flowAmounts);
|
|
1058
|
+
let profitWei = calculateProfitAmount(totalFlowWei);
|
|
1059
|
+
// ✅ 修复:ERC20 交易时,将利润转换为原生代币
|
|
1060
|
+
if (!useNative && profitWei > 0n && quoteToken) {
|
|
1061
|
+
const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
|
|
1062
|
+
if (nativeProfitWei > 0n) {
|
|
1063
|
+
profitWei = nativeProfitWei;
|
|
1064
|
+
console.log(`[V3 Buy] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalFlowWei))} Token → ${ethers.formatEther(profitWei)} Native`);
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
console.log(`[V3 Buy] ERC20 利润报价失败,跳过利润提取`);
|
|
1068
|
+
profitWei = 0n;
|
|
1069
|
+
}
|
|
1052
1070
|
}
|
|
1053
|
-
// 利润交易
|
|
1054
|
-
const profitWei = calculateProfitAmount(totalFlowWei);
|
|
1055
1071
|
if (profitWei > 0n) {
|
|
1056
|
-
const profitTx = await buildProfitTransaction(wallets[
|
|
1072
|
+
const profitTx = await buildProfitTransaction(wallets[maxFlowIndex], // ✅ 使用金额最大的钱包
|
|
1073
|
+
profitWei, nonces[maxFlowIndex] + 1, gasPrice, chainId, txType);
|
|
1057
1074
|
signedTxs.push(profitTx);
|
|
1058
1075
|
}
|
|
1059
1076
|
return {
|
|
@@ -1108,58 +1125,30 @@ export async function directV3BatchSell(params) {
|
|
|
1108
1125
|
const routerIface = new ethers.Interface(routerAbi);
|
|
1109
1126
|
const approveIface = new ethers.Interface(ERC20_ABI);
|
|
1110
1127
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|
|
1111
|
-
const signedTxs = [];
|
|
1112
|
-
let totalOutputEstimate = 0n;
|
|
1113
|
-
const currentNonceOffset = new Array(wallets.length).fill(0);
|
|
1114
1128
|
const deadline = getDeadline();
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const sellAmount = sellAmountsWei[i];
|
|
1118
|
-
if (sellAmount <= 0n)
|
|
1119
|
-
continue;
|
|
1120
|
-
// 检查授权
|
|
1121
|
-
if (!config.skipApprovalCheck) {
|
|
1122
|
-
const allowance = await tokenContract.allowance(wallet.address, routerAddress);
|
|
1123
|
-
if (allowance < sellAmount) {
|
|
1124
|
-
const approveTx = {
|
|
1125
|
-
to: tokenAddress,
|
|
1126
|
-
data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
|
|
1127
|
-
value: 0n,
|
|
1128
|
-
nonce: nonces[i] + currentNonceOffset[i],
|
|
1129
|
-
gasLimit: 60000n,
|
|
1130
|
-
gasPrice,
|
|
1131
|
-
chainId,
|
|
1132
|
-
type: txType,
|
|
1133
|
-
};
|
|
1134
|
-
signedTxs.push(await wallet.signTransaction(approveTx));
|
|
1135
|
-
currentNonceOffset[i]++;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
let txData;
|
|
1129
|
+
// ✅ 优化:构建卖出交易数据的辅助函数
|
|
1130
|
+
const buildV3SellTxData = (wallet, sellAmount) => {
|
|
1139
1131
|
if (useLegacyRouter) {
|
|
1140
|
-
// ✅ 旧版 SwapRouter: exactInputSingle 包含 deadline
|
|
1141
1132
|
const swapParams = {
|
|
1142
1133
|
tokenIn: tokenAddress,
|
|
1143
1134
|
tokenOut: outputToken,
|
|
1144
1135
|
fee: fee,
|
|
1145
1136
|
recipient: useNativeOutput ? routerAddress : wallet.address,
|
|
1146
|
-
deadline: deadline,
|
|
1137
|
+
deadline: deadline,
|
|
1147
1138
|
amountIn: sellAmount,
|
|
1148
|
-
amountOutMinimum: 0n,
|
|
1139
|
+
amountOutMinimum: 0n,
|
|
1149
1140
|
sqrtPriceLimitX96: 0n,
|
|
1150
1141
|
};
|
|
1151
1142
|
if (useNativeOutput) {
|
|
1152
|
-
// 旧版 multicall 不带 deadline
|
|
1153
1143
|
const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1154
1144
|
const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
|
|
1155
|
-
|
|
1145
|
+
return routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, unwrapData]]);
|
|
1156
1146
|
}
|
|
1157
1147
|
else {
|
|
1158
|
-
|
|
1148
|
+
return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1159
1149
|
}
|
|
1160
1150
|
}
|
|
1161
1151
|
else {
|
|
1162
|
-
// ✅ SwapRouter02: exactInputSingle 不含 deadline
|
|
1163
1152
|
const swapParams = {
|
|
1164
1153
|
tokenIn: tokenAddress,
|
|
1165
1154
|
tokenOut: outputToken,
|
|
@@ -1170,34 +1159,96 @@ export async function directV3BatchSell(params) {
|
|
|
1170
1159
|
sqrtPriceLimitX96: 0n,
|
|
1171
1160
|
};
|
|
1172
1161
|
if (useNativeOutput) {
|
|
1173
|
-
// SwapRouter02 的 multicall 带 deadline
|
|
1174
1162
|
const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1175
1163
|
const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
|
|
1176
|
-
|
|
1164
|
+
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, unwrapData]]);
|
|
1177
1165
|
}
|
|
1178
1166
|
else {
|
|
1179
|
-
|
|
1167
|
+
return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1180
1168
|
}
|
|
1181
1169
|
}
|
|
1182
|
-
|
|
1170
|
+
};
|
|
1171
|
+
// ✅ 优化:并行检查所有授权
|
|
1172
|
+
let allowances = [];
|
|
1173
|
+
if (!config.skipApprovalCheck) {
|
|
1174
|
+
allowances = await Promise.all(wallets.map(w => tokenContract.allowance(w.address, routerAddress)));
|
|
1175
|
+
}
|
|
1176
|
+
// ✅ 优化:计算每个钱包的 nonce 偏移
|
|
1177
|
+
const currentNonceOffset = wallets.map((_, i) => {
|
|
1178
|
+
if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
|
|
1179
|
+
return 0;
|
|
1180
|
+
return allowances[i] < sellAmountsWei[i] ? 1 : 0;
|
|
1181
|
+
});
|
|
1182
|
+
// ✅ 优化:并行签名所有授权交易
|
|
1183
|
+
const approvalTxPromises = wallets.map(async (wallet, i) => {
|
|
1184
|
+
if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
|
|
1185
|
+
return null;
|
|
1186
|
+
if (allowances[i] >= sellAmountsWei[i])
|
|
1187
|
+
return null;
|
|
1188
|
+
return wallet.signTransaction({
|
|
1189
|
+
to: tokenAddress,
|
|
1190
|
+
data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
|
|
1191
|
+
value: 0n,
|
|
1192
|
+
nonce: nonces[i],
|
|
1193
|
+
gasLimit: 60000n,
|
|
1194
|
+
gasPrice,
|
|
1195
|
+
chainId,
|
|
1196
|
+
type: txType,
|
|
1197
|
+
});
|
|
1198
|
+
});
|
|
1199
|
+
const approvalTxResults = await Promise.all(approvalTxPromises);
|
|
1200
|
+
// ✅ 优化:并行签名所有卖出交易
|
|
1201
|
+
const sellTxPromises = wallets.map(async (wallet, i) => {
|
|
1202
|
+
if (sellAmountsWei[i] <= 0n)
|
|
1203
|
+
return null;
|
|
1204
|
+
return wallet.signTransaction({
|
|
1183
1205
|
to: routerAddress,
|
|
1184
|
-
data:
|
|
1206
|
+
data: buildV3SellTxData(wallet, sellAmountsWei[i]),
|
|
1185
1207
|
value: 0n,
|
|
1186
1208
|
nonce: nonces[i] + currentNonceOffset[i],
|
|
1187
1209
|
gasLimit,
|
|
1188
1210
|
gasPrice,
|
|
1189
1211
|
chainId,
|
|
1190
1212
|
type: txType,
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
const sellTxResults = await Promise.all(sellTxPromises);
|
|
1216
|
+
// ✅ 按顺序组装签名交易:先授权,后卖出
|
|
1217
|
+
const signedTxs = [];
|
|
1218
|
+
const outputEstimates = [];
|
|
1219
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
1220
|
+
const approvalTx = approvalTxResults[i];
|
|
1221
|
+
const sellTx = sellTxResults[i];
|
|
1222
|
+
if (approvalTx)
|
|
1223
|
+
signedTxs.push(approvalTx);
|
|
1224
|
+
if (sellTx) {
|
|
1225
|
+
signedTxs.push(sellTx);
|
|
1226
|
+
outputEstimates.push(sellAmountsWei[i]);
|
|
1227
|
+
currentNonceOffset[i]++;
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
outputEstimates.push(0n);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
const totalOutputEstimate = outputEstimates.reduce((sum, o) => sum + o, 0n);
|
|
1234
|
+
// ✅ 优化:选择输出最大的钱包支付利润
|
|
1235
|
+
const maxOutputIndex = findMaxFlowIndex(outputEstimates);
|
|
1236
|
+
let profitWei = calculateProfitAmount(totalOutputEstimate);
|
|
1237
|
+
// ✅ 修复:ERC20 输出时,将利润转换为原生代币
|
|
1238
|
+
if (!useNativeOutput && profitWei > 0n && quoteToken) {
|
|
1239
|
+
const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
|
|
1240
|
+
if (nativeProfitWei > 0n) {
|
|
1241
|
+
profitWei = nativeProfitWei;
|
|
1242
|
+
console.log(`[V3 Sell] ERC20 利润转换: ${ethers.formatEther(calculateProfitAmount(totalOutputEstimate))} Token → ${ethers.formatEther(profitWei)} Native`);
|
|
1243
|
+
}
|
|
1244
|
+
else {
|
|
1245
|
+
console.log(`[V3 Sell] ERC20 利润报价失败,跳过利润提取`);
|
|
1246
|
+
profitWei = 0n;
|
|
1247
|
+
}
|
|
1196
1248
|
}
|
|
1197
|
-
// 利润交易
|
|
1198
|
-
const profitWei = calculateProfitAmount(totalOutputEstimate);
|
|
1199
1249
|
if (profitWei > 0n && wallets.length > 0) {
|
|
1200
|
-
const profitTx = await buildProfitTransaction(wallets[
|
|
1250
|
+
const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // ✅ 使用输出最大的钱包
|
|
1251
|
+
profitWei, nonces[maxOutputIndex] + currentNonceOffset[maxOutputIndex], gasPrice, chainId, txType);
|
|
1201
1252
|
signedTxs.push(profitTx);
|
|
1202
1253
|
}
|
|
1203
1254
|
return {
|