four-flap-meme-sdk 1.4.3 → 1.4.5
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/contracts/tm-bundle-merkle/swap.d.ts +1 -0
- package/dist/contracts/tm-bundle-merkle/types.d.ts +1 -0
- package/dist/dex/direct-router.js +219 -286
- package/dist/flap/portal-bundle-merkle/swap.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pancake/bundle-swap.d.ts +42 -0
- package/dist/pancake/bundle-swap.js +205 -0
- package/package.json +1 -1
|
@@ -14,6 +14,32 @@ import { PROFIT_CONFIG } from '../utils/constants.js';
|
|
|
14
14
|
// 常量配置
|
|
15
15
|
// ============================================================================
|
|
16
16
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// ✅ 方案 C:Provider 缓存(避免重复创建)
|
|
19
|
+
// ============================================================================
|
|
20
|
+
const providerCache = new Map();
|
|
21
|
+
const PROVIDER_CACHE_TTL = 60000; // 60秒后过期
|
|
22
|
+
const providerCacheTimestamps = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* 获取缓存的 Provider 实例
|
|
25
|
+
* - 根据 rpcUrl + chainId 作为缓存 key
|
|
26
|
+
* - 60秒后自动失效,避免长连接问题
|
|
27
|
+
*/
|
|
28
|
+
function getCachedProvider(rpcUrl, chainId, chainName) {
|
|
29
|
+
const cacheKey = `${rpcUrl}_${chainId}`;
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
// 检查缓存是否存在且未过期
|
|
32
|
+
const cachedProvider = providerCache.get(cacheKey);
|
|
33
|
+
const timestamp = providerCacheTimestamps.get(cacheKey) || 0;
|
|
34
|
+
if (cachedProvider && (now - timestamp) < PROVIDER_CACHE_TTL) {
|
|
35
|
+
return cachedProvider;
|
|
36
|
+
}
|
|
37
|
+
// 创建新 Provider 并缓存
|
|
38
|
+
const provider = new JsonRpcProvider(rpcUrl, { chainId, name: chainName });
|
|
39
|
+
providerCache.set(cacheKey, provider);
|
|
40
|
+
providerCacheTimestamps.set(cacheKey, now);
|
|
41
|
+
return provider;
|
|
42
|
+
}
|
|
17
43
|
const DEFAULT_GAS_LIMIT = 300000;
|
|
18
44
|
const DEADLINE_MINUTES = 20;
|
|
19
45
|
// ✅ BlockRazor Builder EOA 地址(用于贿赂,仅 BSC 链)
|
|
@@ -671,130 +697,102 @@ export async function directV2BatchBuy(params) {
|
|
|
671
697
|
throw new Error('privateKeys 和 buyAmounts 长度必须相同');
|
|
672
698
|
}
|
|
673
699
|
const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
|
|
674
|
-
|
|
700
|
+
// ✅ 方案 C:使用缓存的 Provider
|
|
701
|
+
const provider = getCachedProvider(config.rpcUrl, chainId, chain);
|
|
675
702
|
const useNative = isNativeToken(quoteToken);
|
|
676
703
|
const wrappedNative = getWrappedNative(chain);
|
|
677
704
|
// 创建钱包
|
|
678
705
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
679
|
-
// ✅
|
|
680
|
-
const
|
|
706
|
+
// ✅ 预先计算所有金额(同步操作,无 RPC)
|
|
707
|
+
const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
|
|
708
|
+
const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
709
|
+
const baseProfitWei = calculateProfitAmount(totalFlowWei);
|
|
710
|
+
// ✅ 方案 B:并行获取 nonces、gasPrice 和 ERC20 报价
|
|
711
|
+
const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
|
|
681
712
|
startNonces && startNonces.length === wallets.length
|
|
682
713
|
? Promise.resolve(startNonces)
|
|
683
714
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
684
|
-
getGasPrice(provider, config)
|
|
715
|
+
getGasPrice(provider, config),
|
|
716
|
+
// ERC20 报价提前并行获取
|
|
717
|
+
(!useNative && baseProfitWei > 0n && quoteToken)
|
|
718
|
+
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
|
|
719
|
+
: Promise.resolve(baseProfitWei)
|
|
685
720
|
]);
|
|
721
|
+
// 确定最终利润金额
|
|
722
|
+
const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
|
|
686
723
|
const gasLimit = getGasLimit(config, 250000);
|
|
687
724
|
const txType = config.txType ?? 0;
|
|
688
725
|
const deadline = getDeadline();
|
|
689
|
-
const slippageBps = config.slippageBps ?? 100; // 默认 1%
|
|
690
726
|
// 构建路径
|
|
691
727
|
const inputToken = useNative ? wrappedNative : quoteToken;
|
|
692
728
|
const path = [inputToken, tokenAddress];
|
|
693
|
-
//
|
|
729
|
+
// 判断 Router 类型
|
|
694
730
|
const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
|
|
695
731
|
const useDYORSwap = isDYORSwap(chain, routerAddress);
|
|
696
|
-
// 创建 Router 合约接口 (DYORSwap 使用手动编码,不需要 ABI)
|
|
697
732
|
const routerIface = useSwapRouter02
|
|
698
733
|
? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
|
|
699
734
|
: new ethers.Interface(V2_ROUTER_ABI);
|
|
700
|
-
//
|
|
701
|
-
const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
|
|
702
|
-
const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
703
|
-
// ✅ 优化:构建未签名交易的辅助函数
|
|
735
|
+
// 构建交易数据的辅助函数
|
|
704
736
|
const buildTxData = (wallet, amountWei) => {
|
|
705
737
|
if (useDYORSwap) {
|
|
706
738
|
const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
|
|
707
739
|
const multicallData = [];
|
|
708
740
|
multicallData.push(encodeDYORRefundETH(30));
|
|
709
741
|
multicallData.push(encodeDYORSwapExactTokensForTokens(useNative ? 0n : amountWei, 0n, path, [], wallet.address, 1n, DYORSWAP_FACTORY));
|
|
710
|
-
return {
|
|
711
|
-
txData: encodeDYORMulticall(BigInt(deadline), multicallData),
|
|
712
|
-
txValue: useNative ? amountWei : 0n
|
|
713
|
-
};
|
|
742
|
+
return { txData: encodeDYORMulticall(BigInt(deadline), multicallData), txValue: useNative ? amountWei : 0n };
|
|
714
743
|
}
|
|
715
744
|
else if (useSwapRouter02) {
|
|
716
|
-
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
|
|
717
|
-
|
|
718
|
-
0n,
|
|
719
|
-
path,
|
|
720
|
-
wallet.address,
|
|
721
|
-
]);
|
|
722
|
-
return {
|
|
723
|
-
txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]),
|
|
724
|
-
txValue: useNative ? amountWei : 0n
|
|
725
|
-
};
|
|
745
|
+
const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [amountWei, 0n, path, wallet.address]);
|
|
746
|
+
return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]), txValue: useNative ? amountWei : 0n };
|
|
726
747
|
}
|
|
727
748
|
else if (useNative) {
|
|
728
|
-
return {
|
|
729
|
-
txData: routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [
|
|
730
|
-
0n,
|
|
731
|
-
path,
|
|
732
|
-
wallet.address,
|
|
733
|
-
deadline,
|
|
734
|
-
]),
|
|
735
|
-
txValue: amountWei
|
|
736
|
-
};
|
|
749
|
+
return { txData: routerIface.encodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', [0n, path, wallet.address, deadline]), txValue: amountWei };
|
|
737
750
|
}
|
|
738
751
|
else {
|
|
739
|
-
return {
|
|
740
|
-
txData: routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [
|
|
741
|
-
amountWei,
|
|
742
|
-
0n,
|
|
743
|
-
path,
|
|
744
|
-
wallet.address,
|
|
745
|
-
deadline,
|
|
746
|
-
]),
|
|
747
|
-
txValue: 0n
|
|
748
|
-
};
|
|
752
|
+
return { txData: routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [amountWei, 0n, path, wallet.address, deadline]), txValue: 0n };
|
|
749
753
|
}
|
|
750
754
|
};
|
|
751
|
-
//
|
|
755
|
+
// 选择金额最大的钱包支付贿赂和利润
|
|
752
756
|
const maxFlowIndex = findMaxFlowIndex(flowAmounts);
|
|
753
|
-
//
|
|
757
|
+
// 计算贿赂金额(仅 BSC 链)
|
|
754
758
|
const bribeWei = getBribeAmount(config, chain);
|
|
755
759
|
const hasBribe = bribeWei > 0n && wallets.length > 0;
|
|
756
|
-
|
|
760
|
+
const hasProfit = profitWei > 0n;
|
|
761
|
+
// 计算 nonce 偏移
|
|
757
762
|
const nonceOffsets = wallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
|
|
758
|
-
// ✅
|
|
759
|
-
const
|
|
763
|
+
// ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
|
|
764
|
+
const signPromises = [];
|
|
765
|
+
// 贿赂交易
|
|
760
766
|
if (hasBribe) {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
bribeTxs.push(bribeTx);
|
|
767
|
+
signPromises.push(buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType)
|
|
768
|
+
.then(tx => ({ type: 'bribe', index: 0, tx })));
|
|
764
769
|
}
|
|
765
|
-
//
|
|
766
|
-
|
|
770
|
+
// 主交易(并行签名)
|
|
771
|
+
wallets.forEach((wallet, i) => {
|
|
767
772
|
const { txData, txValue } = buildTxData(wallet, flowAmounts[i]);
|
|
768
|
-
|
|
773
|
+
signPromises.push(wallet.signTransaction({
|
|
769
774
|
to: routerAddress,
|
|
770
775
|
data: txData,
|
|
771
776
|
value: txValue,
|
|
772
|
-
nonce: nonces[i] + nonceOffsets[i],
|
|
777
|
+
nonce: nonces[i] + nonceOffsets[i],
|
|
773
778
|
gasLimit,
|
|
774
779
|
gasPrice,
|
|
775
780
|
chainId,
|
|
776
781
|
type: txType,
|
|
777
|
-
});
|
|
778
|
-
})
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
profitWei = nativeProfitWei;
|
|
785
|
-
}
|
|
786
|
-
else {
|
|
787
|
-
profitWei = 0n; // 报价失败,跳过利润
|
|
788
|
-
}
|
|
782
|
+
}).then(tx => ({ type: 'swap', index: i, tx })));
|
|
783
|
+
});
|
|
784
|
+
// 利润交易
|
|
785
|
+
if (hasProfit) {
|
|
786
|
+
const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
|
|
787
|
+
signPromises.push(buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType)
|
|
788
|
+
.then(tx => ({ type: 'profit', index: 0, tx })));
|
|
789
789
|
}
|
|
790
|
-
// ✅
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
// ✅ 组装最终交易列表:贿赂 → 交易 → 利润
|
|
790
|
+
// ✅ 并行执行所有签名
|
|
791
|
+
const signedResults = await Promise.all(signPromises);
|
|
792
|
+
// 按类型分组并按顺序组装:贿赂 → 交易 → 利润
|
|
793
|
+
const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
|
|
794
|
+
const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
|
|
795
|
+
const profitTxs = signedResults.filter(r => r.type === 'profit').map(r => r.tx);
|
|
798
796
|
const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
|
|
799
797
|
return {
|
|
800
798
|
signedTransactions: signedTxs,
|
|
@@ -811,27 +809,22 @@ export async function directV2BatchBuy(params) {
|
|
|
811
809
|
export async function directV2BatchSell(params) {
|
|
812
810
|
const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals: inputDecimals, routerAddress, quoteToken, startNonces, config, } = params;
|
|
813
811
|
const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
|
|
814
|
-
|
|
812
|
+
// ✅ 方案 C:使用缓存的 Provider
|
|
813
|
+
const provider = getCachedProvider(config.rpcUrl, chainId, chain);
|
|
815
814
|
const useNativeOutput = isNativeToken(quoteToken);
|
|
816
815
|
const wrappedNative = getWrappedNative(chain);
|
|
817
816
|
// 创建钱包
|
|
818
817
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
819
|
-
// 获取代币合约
|
|
820
818
|
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
821
819
|
// ✅ 性能优化:并行获取所有独立的 RPC 数据
|
|
822
|
-
// ✅ 如果前端已传入 nonces,则跳过 RPC 获取
|
|
823
820
|
const [fetchedDecimals, balances, nonces, gasPrice] = await Promise.all([
|
|
824
|
-
// 1. 获取代币精度(如果未提供)
|
|
825
821
|
inputDecimals !== undefined
|
|
826
822
|
? Promise.resolve(inputDecimals)
|
|
827
823
|
: tokenContract.decimals().then((d) => Number(d)).catch(() => 18),
|
|
828
|
-
// 2. 批量获取余额
|
|
829
824
|
Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
|
|
830
|
-
// 3. 批量获取 nonce(如果前端已传入则跳过)
|
|
831
825
|
startNonces && startNonces.length === wallets.length
|
|
832
826
|
? Promise.resolve(startNonces)
|
|
833
827
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
834
|
-
// 4. 获取 gas price
|
|
835
828
|
getGasPrice(provider, config)
|
|
836
829
|
]);
|
|
837
830
|
const tokenDecimals = fetchedDecimals;
|
|
@@ -842,7 +835,6 @@ export async function directV2BatchSell(params) {
|
|
|
842
835
|
const sellAmountsWei = [];
|
|
843
836
|
for (let i = 0; i < wallets.length; i++) {
|
|
844
837
|
if (sellAmounts && sellAmounts[i]) {
|
|
845
|
-
// ✅ 截断小数位数,避免超过代币精度导致 parseUnits 报错
|
|
846
838
|
const truncatedAmount = truncateDecimals(sellAmounts[i], tokenDecimals);
|
|
847
839
|
sellAmountsWei.push(ethers.parseUnits(truncatedAmount, tokenDecimals));
|
|
848
840
|
}
|
|
@@ -851,29 +843,44 @@ export async function directV2BatchSell(params) {
|
|
|
851
843
|
sellAmountsWei.push((balances[i] * BigInt(pct)) / 100n);
|
|
852
844
|
}
|
|
853
845
|
else {
|
|
854
|
-
sellAmountsWei.push(balances[i]);
|
|
846
|
+
sellAmountsWei.push(balances[i]);
|
|
855
847
|
}
|
|
856
848
|
}
|
|
857
|
-
|
|
849
|
+
const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
|
|
850
|
+
// ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
|
|
851
|
+
// 需要先获取卖出报价,然后基于预估得到的原生代币数量计算利润
|
|
852
|
+
const nativeProfitPromise = (async () => {
|
|
853
|
+
if (useNativeOutput) {
|
|
854
|
+
// 卖出代币 → 得到 BNB:先获取报价,再计算利润
|
|
855
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
|
|
856
|
+
if (estimatedBNBOut <= 0n)
|
|
857
|
+
return 0n;
|
|
858
|
+
return calculateProfitAmount(estimatedBNBOut); // 0.3% × 得到的 BNB
|
|
859
|
+
}
|
|
860
|
+
else if (quoteToken) {
|
|
861
|
+
// 卖出代币 → 得到 ERC20:先计算 ERC20 利润,再转换为 BNB
|
|
862
|
+
const baseProfitWei = calculateProfitAmount(totalSellAmount);
|
|
863
|
+
if (baseProfitWei <= 0n)
|
|
864
|
+
return 0n;
|
|
865
|
+
return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
|
|
866
|
+
}
|
|
867
|
+
return 0n;
|
|
868
|
+
})();
|
|
869
|
+
// 构建路径和 Router
|
|
858
870
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|
|
859
871
|
const path = [tokenAddress, outputToken];
|
|
860
|
-
// ✅ 判断是否是 SwapRouter02 或 DYORSwap
|
|
861
872
|
const useSwapRouter02 = isSwapRouter02(chain, routerAddress);
|
|
862
873
|
const useDYORSwap = isDYORSwap(chain, routerAddress);
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
|
|
866
|
-
: new ethers.Interface(V2_ROUTER_ABI);
|
|
867
|
-
// ✅ 优化:构建卖出交易数据的辅助函数(同步)
|
|
874
|
+
const routerIface = useSwapRouter02 ? new ethers.Interface(SWAP_ROUTER02_V2_ABI) : new ethers.Interface(V2_ROUTER_ABI);
|
|
875
|
+
// 构建卖出交易数据的辅助函数
|
|
868
876
|
const buildSellTxData = (wallet, sellAmount) => {
|
|
869
877
|
if (useDYORSwap) {
|
|
870
878
|
const DYORSWAP_FACTORY = DIRECT_ROUTERS.XLAYER.DYORSWAP_FACTORY;
|
|
871
879
|
const ADDRESS_THIS = 2n;
|
|
872
880
|
const multicallData = [];
|
|
873
881
|
multicallData.push(encodeDYORSwapExactTokensForTokens(sellAmount, 0n, path, [DYORSWAP_FACTORY], ADDRESS_THIS, 1n, DYORSWAP_FACTORY));
|
|
874
|
-
if (useNativeOutput)
|
|
882
|
+
if (useNativeOutput)
|
|
875
883
|
multicallData.push(encodeDYORUnwrapWETH9(0n, wallet.address, '0x'));
|
|
876
|
-
}
|
|
877
884
|
return encodeDYORMulticall(BigInt(deadline), multicallData);
|
|
878
885
|
}
|
|
879
886
|
else if (useSwapRouter02) {
|
|
@@ -886,8 +893,7 @@ export async function directV2BatchSell(params) {
|
|
|
886
893
|
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, multicallData]);
|
|
887
894
|
}
|
|
888
895
|
else {
|
|
889
|
-
|
|
890
|
-
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData]]);
|
|
896
|
+
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('swapExactTokensForTokens', [sellAmount, 0n, path, wallet.address])]]);
|
|
891
897
|
}
|
|
892
898
|
}
|
|
893
899
|
else if (useNativeOutput) {
|
|
@@ -897,26 +903,22 @@ export async function directV2BatchSell(params) {
|
|
|
897
903
|
return routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
|
|
898
904
|
}
|
|
899
905
|
};
|
|
900
|
-
// ✅ 性能优化:直接用 sellAmountsWei 找最大输出钱包(跳过 N 次 getAmountsOut RPC 调用)
|
|
901
|
-
// 因为卖出金额越大,输出也越大(假设线性关系),无需额外报价
|
|
902
906
|
const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
|
|
903
|
-
// ✅ 计算贿赂金额(仅 BSC 链)
|
|
904
907
|
const bribeWei = getBribeAmount(config, chain);
|
|
905
908
|
const hasBribe = bribeWei > 0n && wallets.length > 0;
|
|
906
|
-
// ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
|
|
907
909
|
const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
|
|
908
|
-
// ✅
|
|
909
|
-
const
|
|
910
|
+
// ✅ 方案 A:并行签名所有交易(贿赂、卖出、利润)+ 并行获取 ERC20 报价
|
|
911
|
+
const signPromises = [];
|
|
912
|
+
// 贿赂交易
|
|
910
913
|
if (hasBribe) {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
bribeTxs.push(bribeTx);
|
|
914
|
+
signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
|
|
915
|
+
.then(tx => ({ type: 'bribe', index: 0, tx })));
|
|
914
916
|
}
|
|
915
|
-
//
|
|
916
|
-
|
|
917
|
+
// 卖出交易(并行签名)
|
|
918
|
+
wallets.forEach((wallet, i) => {
|
|
917
919
|
if (sellAmountsWei[i] <= 0n)
|
|
918
|
-
return
|
|
919
|
-
|
|
920
|
+
return;
|
|
921
|
+
signPromises.push(wallet.signTransaction({
|
|
920
922
|
to: routerAddress,
|
|
921
923
|
data: buildSellTxData(wallet, sellAmountsWei[i]),
|
|
922
924
|
value: 0n,
|
|
@@ -925,38 +927,32 @@ export async function directV2BatchSell(params) {
|
|
|
925
927
|
gasPrice,
|
|
926
928
|
chainId,
|
|
927
929
|
type: txType,
|
|
928
|
-
});
|
|
929
|
-
})
|
|
930
|
-
// ✅
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
else {
|
|
940
|
-
profitWei = 0n;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
// ✅ 利润交易
|
|
944
|
-
const profitTxs = [];
|
|
930
|
+
}).then(tx => ({ type: 'swap', index: i, tx })));
|
|
931
|
+
});
|
|
932
|
+
// ✅ 并行执行:签名 + ERC20 报价
|
|
933
|
+
const [signedResults, nativeProfitWei] = await Promise.all([
|
|
934
|
+
Promise.all(signPromises),
|
|
935
|
+
nativeProfitPromise
|
|
936
|
+
]);
|
|
937
|
+
const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
|
|
938
|
+
// 利润交易(需要等 ERC20 报价完成才能确定金额)
|
|
939
|
+
let profitTx = null;
|
|
945
940
|
if (profitWei > 0n && wallets.length > 0) {
|
|
946
|
-
// 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
|
|
947
941
|
const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
|
|
948
|
-
|
|
949
|
-
profitWei, profitNonce, gasPrice, chainId, txType);
|
|
950
|
-
profitTxs.push(profitTx);
|
|
942
|
+
profitTx = await buildProfitTransaction(wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType);
|
|
951
943
|
}
|
|
952
|
-
//
|
|
944
|
+
// 按类型分组并按顺序组装
|
|
945
|
+
const validResults = signedResults.filter((r) => r !== null);
|
|
946
|
+
const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
|
|
947
|
+
const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
|
|
948
|
+
const profitTxs = profitTx ? [profitTx] : [];
|
|
953
949
|
const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
|
|
954
950
|
return {
|
|
955
951
|
signedTransactions: signedTxs,
|
|
956
952
|
metadata: {
|
|
957
953
|
profitAmount: ethers.formatEther(profitWei),
|
|
958
954
|
profitRecipient: PROFIT_CONFIG.RECIPIENT,
|
|
959
|
-
totalFlow: ethers.formatEther(
|
|
955
|
+
totalFlow: ethers.formatEther(totalSellAmount),
|
|
960
956
|
},
|
|
961
957
|
};
|
|
962
958
|
}
|
|
@@ -976,121 +972,79 @@ export async function directV3BatchBuy(params) {
|
|
|
976
972
|
throw new Error('privateKeys 和 buyAmounts 长度必须相同');
|
|
977
973
|
}
|
|
978
974
|
const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
|
|
979
|
-
|
|
975
|
+
// ✅ 方案 C:使用缓存的 Provider
|
|
976
|
+
const provider = getCachedProvider(config.rpcUrl, chainId, chain);
|
|
980
977
|
const useNative = isNativeToken(quoteToken);
|
|
981
978
|
const wrappedNative = getWrappedNative(chain);
|
|
982
|
-
// ✅ 判断是否使用旧版 SwapRouter(Monad Uniswap V3)
|
|
983
979
|
const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
|
|
984
980
|
const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
|
|
985
981
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
986
|
-
// ✅
|
|
987
|
-
const
|
|
982
|
+
// ✅ 预先计算所有金额(同步操作,无 RPC)
|
|
983
|
+
const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
|
|
984
|
+
const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
985
|
+
const baseProfitWei = calculateProfitAmount(totalFlowWei);
|
|
986
|
+
// ✅ 方案 B:并行获取 nonces、gasPrice 和 ERC20 报价
|
|
987
|
+
const [nonces, gasPrice, nativeProfitWei] = await Promise.all([
|
|
988
988
|
startNonces && startNonces.length === wallets.length
|
|
989
989
|
? Promise.resolve(startNonces)
|
|
990
990
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
991
|
-
getGasPrice(provider, config)
|
|
991
|
+
getGasPrice(provider, config),
|
|
992
|
+
(!useNative && baseProfitWei > 0n && quoteToken)
|
|
993
|
+
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain)
|
|
994
|
+
: Promise.resolve(baseProfitWei)
|
|
992
995
|
]);
|
|
996
|
+
const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
|
|
993
997
|
const gasLimit = getGasLimit(config, 300000);
|
|
994
998
|
const txType = config.txType ?? 0;
|
|
995
999
|
const routerIface = new ethers.Interface(routerAbi);
|
|
996
1000
|
const inputToken = useNative ? wrappedNative : quoteToken;
|
|
997
1001
|
const deadline = getDeadline();
|
|
998
|
-
//
|
|
999
|
-
const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
|
|
1000
|
-
const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
1001
|
-
// ✅ 优化:构建交易数据的辅助函数
|
|
1002
|
+
// 构建交易数据的辅助函数
|
|
1002
1003
|
const buildV3BuyTxData = (wallet, amountWei) => {
|
|
1003
1004
|
if (useLegacyRouter) {
|
|
1004
|
-
const swapParams = {
|
|
1005
|
-
tokenIn: inputToken,
|
|
1006
|
-
tokenOut: tokenAddress,
|
|
1007
|
-
fee: fee,
|
|
1008
|
-
recipient: wallet.address,
|
|
1009
|
-
deadline: deadline,
|
|
1010
|
-
amountIn: amountWei,
|
|
1011
|
-
amountOutMinimum: 0n,
|
|
1012
|
-
sqrtPriceLimitX96: 0n,
|
|
1013
|
-
};
|
|
1005
|
+
const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, deadline, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
|
|
1014
1006
|
if (useNative) {
|
|
1015
1007
|
const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1016
1008
|
const refundData = routerIface.encodeFunctionData('refundETH', []);
|
|
1017
|
-
return {
|
|
1018
|
-
txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]),
|
|
1019
|
-
txValue: amountWei
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
else {
|
|
1023
|
-
return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
|
|
1009
|
+
return { txData: routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, refundData]]), txValue: amountWei };
|
|
1024
1010
|
}
|
|
1011
|
+
return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
|
|
1025
1012
|
}
|
|
1026
1013
|
else {
|
|
1027
|
-
const swapParams = {
|
|
1028
|
-
tokenIn: inputToken,
|
|
1029
|
-
tokenOut: tokenAddress,
|
|
1030
|
-
fee: fee,
|
|
1031
|
-
recipient: wallet.address,
|
|
1032
|
-
amountIn: amountWei,
|
|
1033
|
-
amountOutMinimum: 0n,
|
|
1034
|
-
sqrtPriceLimitX96: 0n,
|
|
1035
|
-
};
|
|
1014
|
+
const swapParams = { tokenIn: inputToken, tokenOut: tokenAddress, fee, recipient: wallet.address, amountIn: amountWei, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
|
|
1036
1015
|
if (useNative) {
|
|
1037
1016
|
const swapData = routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1038
1017
|
const refundData = routerIface.encodeFunctionData('refundETH', []);
|
|
1039
|
-
return {
|
|
1040
|
-
txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]),
|
|
1041
|
-
txValue: amountWei
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
else {
|
|
1045
|
-
return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
|
|
1018
|
+
return { txData: routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, refundData]]), txValue: amountWei };
|
|
1046
1019
|
}
|
|
1020
|
+
return { txData: routerIface.encodeFunctionData('exactInputSingle', [swapParams]), txValue: 0n };
|
|
1047
1021
|
}
|
|
1048
1022
|
};
|
|
1049
|
-
// ✅ 优化:选择金额最大的钱包支付贿赂和利润
|
|
1050
1023
|
const maxFlowIndex = findMaxFlowIndex(flowAmounts);
|
|
1051
|
-
// ✅ 计算贿赂金额(仅 BSC 链)
|
|
1052
1024
|
const bribeWei = getBribeAmount(config, chain);
|
|
1053
1025
|
const hasBribe = bribeWei > 0n && wallets.length > 0;
|
|
1054
|
-
|
|
1026
|
+
const hasProfit = profitWei > 0n;
|
|
1055
1027
|
const nonceOffsets = wallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
|
|
1056
|
-
// ✅
|
|
1057
|
-
const
|
|
1028
|
+
// ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
|
|
1029
|
+
const signPromises = [];
|
|
1058
1030
|
if (hasBribe) {
|
|
1059
|
-
|
|
1060
|
-
|
|
1031
|
+
signPromises.push(buildBribeTransaction(wallets[maxFlowIndex], bribeWei, nonces[maxFlowIndex], gasPrice, chainId, txType)
|
|
1032
|
+
.then(tx => ({ type: 'bribe', index: 0, tx })));
|
|
1061
1033
|
}
|
|
1062
|
-
|
|
1063
|
-
const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
|
|
1034
|
+
wallets.forEach((wallet, i) => {
|
|
1064
1035
|
const { txData, txValue } = buildV3BuyTxData(wallet, flowAmounts[i]);
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
chainId,
|
|
1073
|
-
type: txType,
|
|
1074
|
-
});
|
|
1075
|
-
}));
|
|
1076
|
-
let profitWei = calculateProfitAmount(totalFlowWei);
|
|
1077
|
-
// ✅ 修复:ERC20 交易时,将利润转换为原生代币
|
|
1078
|
-
if (!useNative && profitWei > 0n && quoteToken) {
|
|
1079
|
-
const nativeProfitWei = await getTokenToNativeQuote(provider, quoteToken, profitWei, chain);
|
|
1080
|
-
if (nativeProfitWei > 0n) {
|
|
1081
|
-
profitWei = nativeProfitWei;
|
|
1082
|
-
}
|
|
1083
|
-
else {
|
|
1084
|
-
profitWei = 0n;
|
|
1085
|
-
}
|
|
1036
|
+
signPromises.push(wallet.signTransaction({ to: routerAddress, data: txData, value: txValue, nonce: nonces[i] + nonceOffsets[i], gasLimit, gasPrice, chainId, type: txType })
|
|
1037
|
+
.then(tx => ({ type: 'swap', index: i, tx })));
|
|
1038
|
+
});
|
|
1039
|
+
if (hasProfit) {
|
|
1040
|
+
const profitNonce = nonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
|
|
1041
|
+
signPromises.push(buildProfitTransaction(wallets[maxFlowIndex], profitWei, profitNonce, gasPrice, chainId, txType)
|
|
1042
|
+
.then(tx => ({ type: 'profit', index: 0, tx })));
|
|
1086
1043
|
}
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
profitTxs.push(profitTx);
|
|
1092
|
-
}
|
|
1093
|
-
// ✅ 组装最终交易列表:贿赂 → 交易 → 利润
|
|
1044
|
+
const signedResults = await Promise.all(signPromises);
|
|
1045
|
+
const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
|
|
1046
|
+
const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
|
|
1047
|
+
const profitTxs = signedResults.filter(r => r.type === 'profit').map(r => r.tx);
|
|
1094
1048
|
const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
|
|
1095
1049
|
return {
|
|
1096
1050
|
signedTransactions: signedTxs,
|
|
@@ -1111,24 +1065,20 @@ export async function directV3BatchBuy(params) {
|
|
|
1111
1065
|
export async function directV3BatchSell(params) {
|
|
1112
1066
|
const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals = 18, routerAddress, fee, quoteToken, startNonces, config, } = params;
|
|
1113
1067
|
const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
|
|
1114
|
-
|
|
1068
|
+
// ✅ 方案 C:使用缓存的 Provider
|
|
1069
|
+
const provider = getCachedProvider(config.rpcUrl, chainId, chain);
|
|
1115
1070
|
const useNativeOutput = isNativeToken(quoteToken);
|
|
1116
1071
|
const wrappedNative = getWrappedNative(chain);
|
|
1117
|
-
// ✅ 判断是否使用旧版 SwapRouter(Monad Uniswap V3)
|
|
1118
1072
|
const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
|
|
1119
1073
|
const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
|
|
1120
1074
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
1121
1075
|
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
1122
|
-
// ✅
|
|
1123
|
-
// ✅ 如果前端已传入 nonces,则跳过 RPC 获取
|
|
1076
|
+
// ✅ 并行获取所有 RPC 数据
|
|
1124
1077
|
const [balances, nonces, gasPrice] = await Promise.all([
|
|
1125
|
-
// 1. 批量获取余额
|
|
1126
1078
|
Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
|
|
1127
|
-
// 2. 批量获取 nonce(如果前端已传入则跳过)
|
|
1128
1079
|
startNonces && startNonces.length === wallets.length
|
|
1129
1080
|
? Promise.resolve(startNonces)
|
|
1130
1081
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
1131
|
-
// 3. 获取 gas price
|
|
1132
1082
|
getGasPrice(provider, config)
|
|
1133
1083
|
]);
|
|
1134
1084
|
const gasLimit = getGasLimit(config, 350000);
|
|
@@ -1147,71 +1097,60 @@ export async function directV3BatchSell(params) {
|
|
|
1147
1097
|
sellAmountsWei.push(balances[i]);
|
|
1148
1098
|
}
|
|
1149
1099
|
}
|
|
1100
|
+
const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
|
|
1101
|
+
// ✅ 修复:对于卖出操作,利润应基于【得到的 BNB/原生代币】而不是【卖出的代币数量】
|
|
1102
|
+
// 需要先获取卖出报价,然后基于预估得到的原生代币数量计算利润
|
|
1103
|
+
const nativeProfitPromise = (async () => {
|
|
1104
|
+
if (useNativeOutput) {
|
|
1105
|
+
// 卖出代币 → 得到 BNB:先获取报价,再计算利润
|
|
1106
|
+
const estimatedBNBOut = await getTokenToNativeQuote(provider, tokenAddress, totalSellAmount, chain);
|
|
1107
|
+
if (estimatedBNBOut <= 0n)
|
|
1108
|
+
return 0n;
|
|
1109
|
+
return calculateProfitAmount(estimatedBNBOut); // 0.3% × 得到的 BNB
|
|
1110
|
+
}
|
|
1111
|
+
else if (quoteToken) {
|
|
1112
|
+
// 卖出代币 → 得到 ERC20:先计算 ERC20 利润,再转换为 BNB
|
|
1113
|
+
const baseProfitWei = calculateProfitAmount(totalSellAmount);
|
|
1114
|
+
if (baseProfitWei <= 0n)
|
|
1115
|
+
return 0n;
|
|
1116
|
+
return getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain);
|
|
1117
|
+
}
|
|
1118
|
+
return 0n;
|
|
1119
|
+
})();
|
|
1150
1120
|
const routerIface = new ethers.Interface(routerAbi);
|
|
1151
1121
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|
|
1152
1122
|
const deadline = getDeadline();
|
|
1153
|
-
//
|
|
1123
|
+
// 构建卖出交易数据的辅助函数
|
|
1154
1124
|
const buildV3SellTxData = (wallet, sellAmount) => {
|
|
1155
1125
|
if (useLegacyRouter) {
|
|
1156
|
-
const swapParams = {
|
|
1157
|
-
tokenIn: tokenAddress,
|
|
1158
|
-
tokenOut: outputToken,
|
|
1159
|
-
fee: fee,
|
|
1160
|
-
recipient: useNativeOutput ? routerAddress : wallet.address,
|
|
1161
|
-
deadline: deadline,
|
|
1162
|
-
amountIn: sellAmount,
|
|
1163
|
-
amountOutMinimum: 0n,
|
|
1164
|
-
sqrtPriceLimitX96: 0n,
|
|
1165
|
-
};
|
|
1126
|
+
const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, deadline, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
|
|
1166
1127
|
if (useNativeOutput) {
|
|
1167
|
-
|
|
1168
|
-
const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
|
|
1169
|
-
return routerIface.encodeFunctionData('multicall(bytes[])', [[swapData, unwrapData]]);
|
|
1170
|
-
}
|
|
1171
|
-
else {
|
|
1172
|
-
return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1128
|
+
return routerIface.encodeFunctionData('multicall(bytes[])', [[routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
|
|
1173
1129
|
}
|
|
1130
|
+
return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1174
1131
|
}
|
|
1175
1132
|
else {
|
|
1176
|
-
const swapParams = {
|
|
1177
|
-
tokenIn: tokenAddress,
|
|
1178
|
-
tokenOut: outputToken,
|
|
1179
|
-
fee: fee,
|
|
1180
|
-
recipient: useNativeOutput ? routerAddress : wallet.address,
|
|
1181
|
-
amountIn: sellAmount,
|
|
1182
|
-
amountOutMinimum: 0n,
|
|
1183
|
-
sqrtPriceLimitX96: 0n,
|
|
1184
|
-
};
|
|
1133
|
+
const swapParams = { tokenIn: tokenAddress, tokenOut: outputToken, fee, recipient: useNativeOutput ? routerAddress : wallet.address, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n };
|
|
1185
1134
|
if (useNativeOutput) {
|
|
1186
|
-
|
|
1187
|
-
const unwrapData = routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address]);
|
|
1188
|
-
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [swapData, unwrapData]]);
|
|
1189
|
-
}
|
|
1190
|
-
else {
|
|
1191
|
-
return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1135
|
+
return routerIface.encodeFunctionData('multicall(uint256,bytes[])', [deadline, [routerIface.encodeFunctionData('exactInputSingle', [swapParams]), routerIface.encodeFunctionData('unwrapWETH9', [0n, wallet.address])]]);
|
|
1192
1136
|
}
|
|
1137
|
+
return routerIface.encodeFunctionData('exactInputSingle', [swapParams]);
|
|
1193
1138
|
}
|
|
1194
1139
|
};
|
|
1195
|
-
// ✅ 先找出输出最大的钱包索引(用于支付贿赂和利润)
|
|
1196
|
-
// 对于 V3 卖出,使用卖出金额作为输出估计
|
|
1197
1140
|
const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
|
|
1198
|
-
// ✅ 计算贿赂金额(仅 BSC 链)
|
|
1199
1141
|
const bribeWei = getBribeAmount(config, chain);
|
|
1200
1142
|
const hasBribe = bribeWei > 0n && wallets.length > 0;
|
|
1201
|
-
// ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
|
|
1202
1143
|
const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
|
|
1203
|
-
// ✅
|
|
1204
|
-
const
|
|
1144
|
+
// ✅ 方案 A:并行签名所有交易(贿赂、卖出)+ 并行获取 ERC20 报价
|
|
1145
|
+
const signPromises = [];
|
|
1205
1146
|
if (hasBribe) {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
bribeTxs.push(bribeTx);
|
|
1147
|
+
signPromises.push(buildBribeTransaction(wallets[maxOutputIndex], bribeWei, nonces[maxOutputIndex], gasPrice, chainId, txType)
|
|
1148
|
+
.then(tx => ({ type: 'bribe', index: 0, tx })));
|
|
1209
1149
|
}
|
|
1210
|
-
|
|
1211
|
-
const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
|
|
1150
|
+
wallets.forEach((wallet, i) => {
|
|
1212
1151
|
if (sellAmountsWei[i] <= 0n)
|
|
1213
|
-
return
|
|
1214
|
-
|
|
1152
|
+
return;
|
|
1153
|
+
signPromises.push(wallet.signTransaction({
|
|
1215
1154
|
to: routerAddress,
|
|
1216
1155
|
data: buildV3SellTxData(wallet, sellAmountsWei[i]),
|
|
1217
1156
|
value: 0n,
|
|
@@ -1220,38 +1159,32 @@ export async function directV3BatchSell(params) {
|
|
|
1220
1159
|
gasPrice,
|
|
1221
1160
|
chainId,
|
|
1222
1161
|
type: txType,
|
|
1223
|
-
});
|
|
1224
|
-
})
|
|
1225
|
-
// ✅
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
else {
|
|
1235
|
-
profitWei = 0n;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
// ✅ 利润交易
|
|
1239
|
-
const profitTxs = [];
|
|
1162
|
+
}).then(tx => ({ type: 'swap', index: i, tx })));
|
|
1163
|
+
});
|
|
1164
|
+
// ✅ 并行执行:签名 + ERC20 报价
|
|
1165
|
+
const [signedResults, nativeProfitWei] = await Promise.all([
|
|
1166
|
+
Promise.all(signPromises),
|
|
1167
|
+
nativeProfitPromise
|
|
1168
|
+
]);
|
|
1169
|
+
const profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
|
|
1170
|
+
// 利润交易(需要等 ERC20 报价完成才能确定金额)
|
|
1171
|
+
let profitTx = null;
|
|
1240
1172
|
if (profitWei > 0n && wallets.length > 0) {
|
|
1241
|
-
// 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
|
|
1242
1173
|
const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
|
|
1243
|
-
|
|
1244
|
-
profitWei, profitNonce, gasPrice, chainId, txType);
|
|
1245
|
-
profitTxs.push(profitTx);
|
|
1174
|
+
profitTx = await buildProfitTransaction(wallets[maxOutputIndex], profitWei, profitNonce, gasPrice, chainId, txType);
|
|
1246
1175
|
}
|
|
1247
|
-
//
|
|
1176
|
+
// 按类型分组并按顺序组装
|
|
1177
|
+
const validResults = signedResults.filter((r) => r !== null);
|
|
1178
|
+
const bribeTxs = validResults.filter(r => r.type === 'bribe').map(r => r.tx);
|
|
1179
|
+
const swapTxs = validResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
|
|
1180
|
+
const profitTxs = profitTx ? [profitTx] : [];
|
|
1248
1181
|
const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
|
|
1249
1182
|
return {
|
|
1250
1183
|
signedTransactions: signedTxs,
|
|
1251
1184
|
metadata: {
|
|
1252
1185
|
profitAmount: ethers.formatEther(profitWei),
|
|
1253
1186
|
profitRecipient: PROFIT_CONFIG.RECIPIENT,
|
|
1254
|
-
totalFlow: ethers.formatEther(
|
|
1187
|
+
totalFlow: ethers.formatEther(totalSellAmount),
|
|
1255
1188
|
},
|
|
1256
1189
|
};
|
|
1257
1190
|
}
|
|
@@ -15,6 +15,7 @@ export interface FlapSwapSignConfig {
|
|
|
15
15
|
reserveGasETH?: number;
|
|
16
16
|
skipQuoteOnError?: boolean;
|
|
17
17
|
skipApprovalCheck?: boolean;
|
|
18
|
+
bribeAmount?: number;
|
|
18
19
|
}
|
|
19
20
|
export type FlapChain = 'bsc' | 'xlayer' | 'base';
|
|
20
21
|
export interface FlapSwapConfig extends CommonBundleConfig {
|
package/dist/index.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export { inspectTokenLP, getFactoryFromRouter, registerDYORSwap, registerDex, ge
|
|
|
38
38
|
export { disperseWithBundle, sweepWithBundle, type DisperseSignParams, type SweepSignParams, type SignedTransactionsResult, type DisperseParams, type SweepParams, type BundleSubmitResult } from './utils/airdrop-sweep.js';
|
|
39
39
|
export { fourBundleSwapMerkle, fourBatchSwapMerkle, type FourSwapSignConfig, type FourSwapConfig, type FourBundleSwapSignParams, type FourBundleSwapParams, type FourSwapResult, type FourBatchSwapSignParams, type FourBatchSwapResult } from './contracts/tm-bundle-merkle/swap.js';
|
|
40
40
|
export { flapBundleSwapMerkle, flapBatchSwapMerkle, type FlapSwapSignConfig, type FlapSwapConfig, type FlapBundleSwapSignParams, type FlapBundleSwapParams, type FlapSwapResult, type FlapBatchSwapSignParams, type FlapBatchSwapResult } from './flap/portal-bundle-merkle/swap.js';
|
|
41
|
-
export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, type PancakeSwapSignConfig, type PancakeBundleSwapSignParams, type PancakeSwapConfig, type PancakeBundleSwapParams, type PancakeSwapResult, type PancakeBatchSwapSignParams, type PancakeBatchSwapResult, type SwapRouteType, type V2RouteParams, type V3SingleRouteParams, type V3MultiRouteParams, type RouteParams } from './pancake/bundle-swap.js';
|
|
41
|
+
export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapMerkle, type PancakeSwapSignConfig, type PancakeBundleSwapSignParams, type PancakeSwapConfig, type PancakeBundleSwapParams, type PancakeSwapResult, type PancakeBatchSwapSignParams, type PancakeBatchSwapResult, type PancakeQuickBatchSwapParams, type PancakeQuickBatchSwapResult, type SwapRouteType, type V2RouteParams, type V3SingleRouteParams, type V3MultiRouteParams, type RouteParams } from './pancake/bundle-swap.js';
|
|
42
42
|
export { fourBundleBuyFirstMerkle, type FourBuyFirstConfig, type FourBundleBuyFirstParams, type FourBuyFirstSignConfig, type FourBundleBuyFirstSignParams, type FourBuyFirstResult } from './contracts/tm-bundle-merkle/swap-buy-first.js';
|
|
43
43
|
export { flapBundleBuyFirstMerkle, type FlapBuyFirstSignConfig, type FlapBuyFirstConfig, type FlapBundleBuyFirstSignParams, type FlapBundleBuyFirstParams, type FlapBuyFirstResult } from './flap/portal-bundle-merkle/swap-buy-first.js';
|
|
44
44
|
export { pancakeBundleBuyFirstMerkle, type PancakeBuyFirstSignConfig, type PancakeBuyFirstConfig, type PancakeBundleBuyFirstSignParams, type PancakeBundleBuyFirstParams, type PancakeBuyFirstResult } from './pancake/bundle-buy-first.js';
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ export { fourBundleSwapMerkle, fourBatchSwapMerkle } from './contracts/tm-bundle
|
|
|
56
56
|
// Flap内盘换手
|
|
57
57
|
export { flapBundleSwapMerkle, flapBatchSwapMerkle } from './flap/portal-bundle-merkle/swap.js';
|
|
58
58
|
// PancakeSwap V2/V3通用换手
|
|
59
|
-
export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle } from './pancake/bundle-swap.js';
|
|
59
|
+
export { pancakeBundleSwapMerkle, pancakeBatchSwapMerkle, pancakeQuickBatchSwapMerkle } from './pancake/bundle-swap.js';
|
|
60
60
|
// 先买后卖(Buy-First)入口
|
|
61
61
|
export { fourBundleBuyFirstMerkle } from './contracts/tm-bundle-merkle/swap-buy-first.js';
|
|
62
62
|
export { flapBundleBuyFirstMerkle } from './flap/portal-bundle-merkle/swap-buy-first.js';
|
|
@@ -115,3 +115,45 @@ export interface PancakeBatchSwapResult {
|
|
|
115
115
|
* 交易顺序:[授权(可选)] → [卖出] → [买入1, 买入2, ..., 买入N] → [利润]
|
|
116
116
|
*/
|
|
117
117
|
export declare function pancakeBatchSwapMerkle(params: PancakeBatchSwapSignParams): Promise<PancakeBatchSwapResult>;
|
|
118
|
+
/**
|
|
119
|
+
* 快捷批量换手参数
|
|
120
|
+
* 特点:子钱包不需要预先有余额,资金来自主钱包卖出所得
|
|
121
|
+
*/
|
|
122
|
+
export interface PancakeQuickBatchSwapParams {
|
|
123
|
+
sellerPrivateKey: string;
|
|
124
|
+
sellAmount?: string;
|
|
125
|
+
sellPercentage?: number;
|
|
126
|
+
buyerPrivateKeys: string[];
|
|
127
|
+
buyerRatios?: number[];
|
|
128
|
+
buyerAmounts?: string[];
|
|
129
|
+
tokenAddress: string;
|
|
130
|
+
routeParams: RouteParams;
|
|
131
|
+
config: PancakeSwapSignConfig;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 快捷批量换手结果
|
|
135
|
+
*/
|
|
136
|
+
export interface PancakeQuickBatchSwapResult {
|
|
137
|
+
signedTransactions: string[];
|
|
138
|
+
metadata?: {
|
|
139
|
+
sellerAddress: string;
|
|
140
|
+
buyerAddresses: string[];
|
|
141
|
+
sellAmount: string;
|
|
142
|
+
estimatedBNBOut: string;
|
|
143
|
+
transferAmounts: string[];
|
|
144
|
+
profitAmount?: string;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* PancakeSwap 快捷批量换手(资金自动流转)
|
|
149
|
+
*
|
|
150
|
+
* 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
|
|
151
|
+
*
|
|
152
|
+
* 特点:
|
|
153
|
+
* - 子钱包不需要预先有 BNB 余额
|
|
154
|
+
* - 资金来自主钱包卖出代币所得
|
|
155
|
+
* - 提升资金利用率
|
|
156
|
+
*
|
|
157
|
+
* 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
|
|
158
|
+
*/
|
|
159
|
+
export declare function pancakeQuickBatchSwapMerkle(params: PancakeQuickBatchSwapParams): Promise<PancakeQuickBatchSwapResult>;
|
|
@@ -667,3 +667,208 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
667
667
|
}
|
|
668
668
|
};
|
|
669
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* PancakeSwap 快捷批量换手(资金自动流转)
|
|
672
|
+
*
|
|
673
|
+
* 流程:[贿赂] → [卖出] → [转账到子钱包] → [买入] → [利润]
|
|
674
|
+
*
|
|
675
|
+
* 特点:
|
|
676
|
+
* - 子钱包不需要预先有 BNB 余额
|
|
677
|
+
* - 资金来自主钱包卖出代币所得
|
|
678
|
+
* - 提升资金利用率
|
|
679
|
+
*
|
|
680
|
+
* 限制:最多 12 个买方(转账 + 买入 = 24 笔,加上贿赂/卖出/利润接近 25 笔限制)
|
|
681
|
+
*/
|
|
682
|
+
export async function pancakeQuickBatchSwapMerkle(params) {
|
|
683
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
|
|
684
|
+
// ✅ 校验买方数量(最多 12 个,因为每个买方需要 2 笔交易:转账 + 买入)
|
|
685
|
+
const MAX_BUYERS = 12;
|
|
686
|
+
if (buyerPrivateKeys.length === 0) {
|
|
687
|
+
throw new Error('至少需要一个买方钱包');
|
|
688
|
+
}
|
|
689
|
+
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
690
|
+
throw new Error(`快捷模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
691
|
+
}
|
|
692
|
+
// ✅ 校验分配模式
|
|
693
|
+
if (!buyerRatios && !buyerAmounts) {
|
|
694
|
+
throw new Error('必须提供 buyerRatios 或 buyerAmounts');
|
|
695
|
+
}
|
|
696
|
+
if (buyerRatios && buyerRatios.length !== buyerPrivateKeys.length) {
|
|
697
|
+
throw new Error(`buyerRatios 长度 (${buyerRatios.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
|
|
698
|
+
}
|
|
699
|
+
if (buyerAmounts && buyerAmounts.length !== buyerPrivateKeys.length) {
|
|
700
|
+
throw new Error(`buyerAmounts 长度 (${buyerAmounts.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
|
|
701
|
+
}
|
|
702
|
+
const context = createPancakeContext(config);
|
|
703
|
+
const seller = new Wallet(sellerPrivateKey, context.provider);
|
|
704
|
+
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
|
|
705
|
+
// ✅ 创建共享资源
|
|
706
|
+
const nonceManager = new NonceManager(context.provider);
|
|
707
|
+
const finalGasLimit = getGasLimit(config);
|
|
708
|
+
const gasPrice = await getGasPrice(context.provider, config);
|
|
709
|
+
const txType = config.txType ?? 0;
|
|
710
|
+
// ✅ 计算卖出数量
|
|
711
|
+
const { amount: sellAmountWei, decimals } = await calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage);
|
|
712
|
+
// ✅ 获取卖出报价
|
|
713
|
+
const quoteResult = await quoteSellOutput({
|
|
714
|
+
routeParams,
|
|
715
|
+
sellAmountWei,
|
|
716
|
+
provider: context.provider
|
|
717
|
+
});
|
|
718
|
+
const estimatedBNBOut = quoteResult.estimatedBNBOut;
|
|
719
|
+
// ✅ 计算利润(从卖出所得中预扣)
|
|
720
|
+
const profitAmount = calculateProfitAmount(estimatedBNBOut);
|
|
721
|
+
const distributableBNB = estimatedBNBOut - profitAmount; // 可分配金额
|
|
722
|
+
// ✅ 计算每个子钱包分到的金额
|
|
723
|
+
let transferAmountsWei;
|
|
724
|
+
if (buyerAmounts && buyerAmounts.length === buyers.length) {
|
|
725
|
+
// 数量模式:使用指定的金额
|
|
726
|
+
transferAmountsWei = buyerAmounts.map(amt => ethers.parseEther(amt));
|
|
727
|
+
// 校验总金额不超过可分配金额
|
|
728
|
+
const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n);
|
|
729
|
+
if (totalTransfer > distributableBNB) {
|
|
730
|
+
throw new Error(`指定的买入总金额 (${ethers.formatEther(totalTransfer)} BNB) 超过可分配金额 (${ethers.formatEther(distributableBNB)} BNB)`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
else if (buyerRatios && buyerRatios.length === buyers.length) {
|
|
734
|
+
// 比例模式:按比例分配可分配金额
|
|
735
|
+
transferAmountsWei = buyerRatios.map(ratio => {
|
|
736
|
+
const amount = (distributableBNB * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
737
|
+
return amount;
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
throw new Error('必须提供 buyerRatios 或 buyerAmounts');
|
|
742
|
+
}
|
|
743
|
+
// ✅ 获取贿赂金额
|
|
744
|
+
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
745
|
+
? ethers.parseEther(String(config.bribeAmount))
|
|
746
|
+
: 0n;
|
|
747
|
+
// ✅ 验证主钱包余额(需要支付 Gas 费用)
|
|
748
|
+
const sellerBalance = await seller.provider.getBalance(seller.address);
|
|
749
|
+
const estimatedGasCost = gasPrice * finalGasLimit * BigInt(3 + buyers.length * 2); // 贿赂 + 卖出 + 转账 + 利润
|
|
750
|
+
if (sellerBalance < bribeAmount + estimatedGasCost) {
|
|
751
|
+
throw new Error(`主钱包 BNB 余额不足 (用于支付 Gas): 需要约 ${ethers.formatEther(bribeAmount + estimatedGasCost)} BNB, 实际 ${ethers.formatEther(sellerBalance)} BNB`);
|
|
752
|
+
}
|
|
753
|
+
// ==================== 规划 Nonce ====================
|
|
754
|
+
// 卖方: [贿赂(可选)] → [卖出] → [转账1, 转账2, ...] → [利润(可选)]
|
|
755
|
+
// 买方: [买入]
|
|
756
|
+
let sellerNonce = await nonceManager.getNextNonce(seller);
|
|
757
|
+
// ✅ 贿赂交易(首位)
|
|
758
|
+
let bribeTx = null;
|
|
759
|
+
if (bribeAmount > 0n) {
|
|
760
|
+
bribeTx = await seller.signTransaction({
|
|
761
|
+
to: BLOCKRAZOR_BUILDER_EOA,
|
|
762
|
+
value: bribeAmount,
|
|
763
|
+
nonce: sellerNonce++,
|
|
764
|
+
gasPrice,
|
|
765
|
+
gasLimit: 21000n,
|
|
766
|
+
chainId: context.chainId,
|
|
767
|
+
type: txType
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
// ✅ 卖出交易
|
|
771
|
+
const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
|
|
772
|
+
const deadline = Math.floor(Date.now() / 1000) + 600;
|
|
773
|
+
let sellUnsigned;
|
|
774
|
+
if (routeParams.routeType === 'v2') {
|
|
775
|
+
const { v2Path } = routeParams;
|
|
776
|
+
sellUnsigned = await proxySeller.swapV2.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline, { value: FLAT_FEE });
|
|
777
|
+
}
|
|
778
|
+
else if (routeParams.routeType === 'v3-single') {
|
|
779
|
+
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
780
|
+
sellUnsigned = await proxySeller.swapV3Single.populateTransaction(v3TokenIn, v3TokenOut, v3Fee, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
const { v3LpAddresses, v3ExactTokenIn } = routeParams;
|
|
784
|
+
sellUnsigned = await proxySeller.swapV3MultiHop.populateTransaction(v3LpAddresses, v3ExactTokenIn, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
|
|
785
|
+
}
|
|
786
|
+
const signedSell = await seller.signTransaction({
|
|
787
|
+
...sellUnsigned,
|
|
788
|
+
from: seller.address,
|
|
789
|
+
nonce: sellerNonce++,
|
|
790
|
+
gasLimit: finalGasLimit,
|
|
791
|
+
gasPrice,
|
|
792
|
+
chainId: context.chainId,
|
|
793
|
+
type: txType
|
|
794
|
+
});
|
|
795
|
+
// ✅ 转账交易(主钱包 → 各子钱包)
|
|
796
|
+
const transferTxs = [];
|
|
797
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
798
|
+
const transferTx = await seller.signTransaction({
|
|
799
|
+
to: buyers[i].address,
|
|
800
|
+
value: transferAmountsWei[i],
|
|
801
|
+
nonce: sellerNonce++,
|
|
802
|
+
gasPrice,
|
|
803
|
+
gasLimit: 21000n,
|
|
804
|
+
chainId: context.chainId,
|
|
805
|
+
type: txType
|
|
806
|
+
});
|
|
807
|
+
transferTxs.push(transferTx);
|
|
808
|
+
}
|
|
809
|
+
// ✅ 利润交易
|
|
810
|
+
let profitTx = null;
|
|
811
|
+
if (profitAmount > 0n) {
|
|
812
|
+
profitTx = await seller.signTransaction({
|
|
813
|
+
to: PROFIT_CONFIG.RECIPIENT,
|
|
814
|
+
value: profitAmount,
|
|
815
|
+
nonce: sellerNonce++,
|
|
816
|
+
gasPrice,
|
|
817
|
+
gasLimit: 21000n,
|
|
818
|
+
chainId: context.chainId,
|
|
819
|
+
type: txType
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
// ✅ 并行获取所有买方的 nonce
|
|
823
|
+
const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
|
|
824
|
+
// ✅ 并行构建买入交易
|
|
825
|
+
const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
|
|
826
|
+
const buyAmount = transferAmountsWei[i];
|
|
827
|
+
const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
|
|
828
|
+
const buyValue = buyAmount + FLAT_FEE;
|
|
829
|
+
let buyUnsigned;
|
|
830
|
+
if (routeParams.routeType === 'v2') {
|
|
831
|
+
const { v2Path } = routeParams;
|
|
832
|
+
const reversePath = [...v2Path].reverse();
|
|
833
|
+
buyUnsigned = await proxyBuyer.swapV2.populateTransaction(buyAmount, 0n, reversePath, buyer.address, deadline, { value: buyValue });
|
|
834
|
+
}
|
|
835
|
+
else if (routeParams.routeType === 'v3-single') {
|
|
836
|
+
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
837
|
+
buyUnsigned = await proxyBuyer.swapV3Single.populateTransaction(v3TokenOut, v3TokenIn, v3Fee, buyAmount, 0n, buyer.address, { value: buyValue });
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
const { v3LpAddresses } = routeParams;
|
|
841
|
+
buyUnsigned = await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, tokenAddress, buyAmount, 0n, buyer.address, { value: buyValue });
|
|
842
|
+
}
|
|
843
|
+
return buyer.signTransaction({
|
|
844
|
+
...buyUnsigned,
|
|
845
|
+
from: buyer.address,
|
|
846
|
+
nonce: buyerNonces[i],
|
|
847
|
+
gasLimit: finalGasLimit,
|
|
848
|
+
gasPrice,
|
|
849
|
+
chainId: context.chainId,
|
|
850
|
+
type: txType
|
|
851
|
+
});
|
|
852
|
+
}));
|
|
853
|
+
nonceManager.clearTemp();
|
|
854
|
+
// ✅ 按顺序组装交易数组:贿赂 → 卖出 → 转账 → 买入 → 利润
|
|
855
|
+
const signedTransactions = [];
|
|
856
|
+
if (bribeTx)
|
|
857
|
+
signedTransactions.push(bribeTx); // 贿赂(首位)
|
|
858
|
+
signedTransactions.push(signedSell); // 卖出
|
|
859
|
+
signedTransactions.push(...transferTxs); // 转账到各子钱包
|
|
860
|
+
signedTransactions.push(...signedBuys); // 各子钱包买入
|
|
861
|
+
if (profitTx)
|
|
862
|
+
signedTransactions.push(profitTx); // 利润(末尾)
|
|
863
|
+
return {
|
|
864
|
+
signedTransactions,
|
|
865
|
+
metadata: {
|
|
866
|
+
sellerAddress: seller.address,
|
|
867
|
+
buyerAddresses: buyers.map(b => b.address),
|
|
868
|
+
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
869
|
+
estimatedBNBOut: ethers.formatEther(estimatedBNBOut),
|
|
870
|
+
transferAmounts: transferAmountsWei.map(amt => ethers.formatEther(amt)),
|
|
871
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|