four-flap-meme-sdk 1.3.98 → 1.4.1
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.d.ts +4 -0
- package/dist/dex/direct-router.js +81 -174
- package/package.json +1 -1
|
@@ -55,6 +55,7 @@ export interface DirectV2BuyParams {
|
|
|
55
55
|
routerAddress: string;
|
|
56
56
|
quoteToken?: string;
|
|
57
57
|
quoteTokenDecimals?: number;
|
|
58
|
+
startNonces?: number[];
|
|
58
59
|
config: DirectRouterSignConfig;
|
|
59
60
|
}
|
|
60
61
|
export interface DirectV2SellParams {
|
|
@@ -62,6 +63,7 @@ export interface DirectV2SellParams {
|
|
|
62
63
|
privateKeys: string[];
|
|
63
64
|
sellPercentages?: number[];
|
|
64
65
|
sellAmounts?: string[];
|
|
66
|
+
startNonces?: number[];
|
|
65
67
|
tokenAddress: string;
|
|
66
68
|
tokenDecimals?: number;
|
|
67
69
|
routerAddress: string;
|
|
@@ -77,6 +79,7 @@ export interface DirectV3BuyParams {
|
|
|
77
79
|
fee: number;
|
|
78
80
|
quoteToken?: string;
|
|
79
81
|
quoteTokenDecimals?: number;
|
|
82
|
+
startNonces?: number[];
|
|
80
83
|
config: DirectRouterSignConfig;
|
|
81
84
|
}
|
|
82
85
|
export interface DirectV3SellParams {
|
|
@@ -89,6 +92,7 @@ export interface DirectV3SellParams {
|
|
|
89
92
|
routerAddress: string;
|
|
90
93
|
fee: number;
|
|
91
94
|
quoteToken?: string;
|
|
95
|
+
startNonces?: number[];
|
|
92
96
|
config: DirectRouterSignConfig;
|
|
93
97
|
}
|
|
94
98
|
export interface DirectRouterResult {
|
|
@@ -666,7 +666,7 @@ function isDYORSwap(chain, routerAddress) {
|
|
|
666
666
|
* V2 批量买入(直接调用 Router)
|
|
667
667
|
*/
|
|
668
668
|
export async function directV2BatchBuy(params) {
|
|
669
|
-
const { chain, privateKeys, buyAmounts, tokenAddress, routerAddress, quoteToken, quoteTokenDecimals = 18, config, } = params;
|
|
669
|
+
const { chain, privateKeys, buyAmounts, tokenAddress, routerAddress, quoteToken, quoteTokenDecimals = 18, startNonces, config, } = params;
|
|
670
670
|
if (privateKeys.length !== buyAmounts.length) {
|
|
671
671
|
throw new Error('privateKeys 和 buyAmounts 长度必须相同');
|
|
672
672
|
}
|
|
@@ -676,11 +676,13 @@ export async function directV2BatchBuy(params) {
|
|
|
676
676
|
const wrappedNative = getWrappedNative(chain);
|
|
677
677
|
// 创建钱包
|
|
678
678
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
679
|
-
// 获取
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
679
|
+
// ✅ 性能优化:如果前端已传入 nonces,则跳过 RPC 获取
|
|
680
|
+
const [nonces, gasPrice] = await Promise.all([
|
|
681
|
+
startNonces && startNonces.length === wallets.length
|
|
682
|
+
? Promise.resolve(startNonces)
|
|
683
|
+
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
684
|
+
getGasPrice(provider, config)
|
|
685
|
+
]);
|
|
684
686
|
const gasLimit = getGasLimit(config, 250000);
|
|
685
687
|
const txType = config.txType ?? 0;
|
|
686
688
|
const deadline = getDeadline();
|
|
@@ -807,7 +809,7 @@ export async function directV2BatchBuy(params) {
|
|
|
807
809
|
* V2 批量卖出(直接调用 Router)
|
|
808
810
|
*/
|
|
809
811
|
export async function directV2BatchSell(params) {
|
|
810
|
-
const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals: inputDecimals, routerAddress, quoteToken, config, } = params;
|
|
812
|
+
const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals: inputDecimals, routerAddress, quoteToken, startNonces, config, } = params;
|
|
811
813
|
const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
|
|
812
814
|
const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
|
|
813
815
|
const useNativeOutput = isNativeToken(quoteToken);
|
|
@@ -816,19 +818,27 @@ export async function directV2BatchSell(params) {
|
|
|
816
818
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
817
819
|
// 获取代币合约
|
|
818
820
|
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
819
|
-
// ✅
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
821
|
+
// ✅ 性能优化:并行获取所有独立的 RPC 数据
|
|
822
|
+
// ✅ 如果前端已传入 nonces,则跳过 RPC 获取
|
|
823
|
+
const [fetchedDecimals, balances, nonces, gasPrice] = await Promise.all([
|
|
824
|
+
// 1. 获取代币精度(如果未提供)
|
|
825
|
+
inputDecimals !== undefined
|
|
826
|
+
? Promise.resolve(inputDecimals)
|
|
827
|
+
: tokenContract.decimals().then((d) => Number(d)).catch(() => 18),
|
|
828
|
+
// 2. 批量获取余额
|
|
829
|
+
Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
|
|
830
|
+
// 3. 批量获取 nonce(如果前端已传入则跳过)
|
|
831
|
+
startNonces && startNonces.length === wallets.length
|
|
832
|
+
? Promise.resolve(startNonces)
|
|
833
|
+
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
834
|
+
// 4. 获取 gas price
|
|
835
|
+
getGasPrice(provider, config)
|
|
836
|
+
]);
|
|
837
|
+
const tokenDecimals = fetchedDecimals;
|
|
838
|
+
const gasLimit = getGasLimit(config, 300000);
|
|
839
|
+
const txType = config.txType ?? 0;
|
|
840
|
+
const deadline = getDeadline();
|
|
841
|
+
// 计算卖出数量(同步操作,无 RPC)
|
|
832
842
|
const sellAmountsWei = [];
|
|
833
843
|
for (let i = 0; i < wallets.length; i++) {
|
|
834
844
|
if (sellAmounts && sellAmounts[i]) {
|
|
@@ -844,13 +854,6 @@ export async function directV2BatchSell(params) {
|
|
|
844
854
|
sellAmountsWei.push(balances[i]); // 默认全部卖出
|
|
845
855
|
}
|
|
846
856
|
}
|
|
847
|
-
// 获取 nonce(每个钱包可能需要 2 个:授权 + 卖出)
|
|
848
|
-
const nonceManager = new NonceManager(provider);
|
|
849
|
-
const nonces = await nonceManager.getNextNoncesForWallets(wallets);
|
|
850
|
-
const gasPrice = await getGasPrice(provider, config);
|
|
851
|
-
const gasLimit = getGasLimit(config, 300000);
|
|
852
|
-
const txType = config.txType ?? 0;
|
|
853
|
-
const deadline = getDeadline();
|
|
854
857
|
// 构建路径
|
|
855
858
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|
|
856
859
|
const path = [tokenAddress, outputToken];
|
|
@@ -861,7 +864,6 @@ export async function directV2BatchSell(params) {
|
|
|
861
864
|
const routerIface = useSwapRouter02
|
|
862
865
|
? new ethers.Interface(SWAP_ROUTER02_V2_ABI)
|
|
863
866
|
: new ethers.Interface(V2_ROUTER_ABI);
|
|
864
|
-
const approveIface = new ethers.Interface(ERC20_ABI);
|
|
865
867
|
// ✅ 优化:构建卖出交易数据的辅助函数(同步)
|
|
866
868
|
const buildSellTxData = (wallet, sellAmount) => {
|
|
867
869
|
if (useDYORSwap) {
|
|
@@ -895,40 +897,14 @@ export async function directV2BatchSell(params) {
|
|
|
895
897
|
return routerIface.encodeFunctionData('swapExactTokensForTokensSupportingFeeOnTransferTokens', [sellAmount, 0n, path, wallet.address, deadline]);
|
|
896
898
|
}
|
|
897
899
|
};
|
|
898
|
-
// ✅
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
allowances = await Promise.all(wallets.map(w => tokenContract.allowance(w.address, routerAddress)));
|
|
902
|
-
}
|
|
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];
|
|
908
|
-
try {
|
|
909
|
-
const amounts = await router.getAmountsOut(sellAmountsWei[i], path);
|
|
910
|
-
return amounts[amounts.length - 1];
|
|
911
|
-
}
|
|
912
|
-
catch {
|
|
913
|
-
return sellAmountsWei[i];
|
|
914
|
-
}
|
|
915
|
-
});
|
|
916
|
-
const outputEstimates = await Promise.all(quotePromises);
|
|
917
|
-
const maxOutputIndex = findMaxFlowIndex(outputEstimates);
|
|
900
|
+
// ✅ 性能优化:直接用 sellAmountsWei 找最大输出钱包(跳过 N 次 getAmountsOut RPC 调用)
|
|
901
|
+
// 因为卖出金额越大,输出也越大(假设线性关系),无需额外报价
|
|
902
|
+
const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
|
|
918
903
|
// ✅ 计算贿赂金额(仅 BSC 链)
|
|
919
904
|
const bribeWei = getBribeAmount(config, chain);
|
|
920
905
|
const hasBribe = bribeWei > 0n && wallets.length > 0;
|
|
921
|
-
// ✅
|
|
922
|
-
const
|
|
923
|
-
let offset = 0;
|
|
924
|
-
// 贿赂交易偏移(只有 maxOutputIndex 钱包需要)
|
|
925
|
-
if (hasBribe && i === maxOutputIndex)
|
|
926
|
-
offset++;
|
|
927
|
-
// 授权交易偏移
|
|
928
|
-
if (!config.skipApprovalCheck && allowances[i] < sellAmountsWei[i])
|
|
929
|
-
offset++;
|
|
930
|
-
return offset;
|
|
931
|
-
});
|
|
906
|
+
// ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
|
|
907
|
+
const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
|
|
932
908
|
// ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
|
|
933
909
|
const bribeTxs = [];
|
|
934
910
|
if (hasBribe) {
|
|
@@ -936,55 +912,23 @@ export async function directV2BatchSell(params) {
|
|
|
936
912
|
gasPrice, chainId, txType);
|
|
937
913
|
bribeTxs.push(bribeTx);
|
|
938
914
|
}
|
|
939
|
-
// ✅
|
|
940
|
-
const
|
|
941
|
-
if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
|
|
942
|
-
return null;
|
|
943
|
-
if (allowances[i] >= sellAmountsWei[i])
|
|
944
|
-
return null;
|
|
945
|
-
// 授权交易的 nonce = 原始 nonce + 贿赂偏移
|
|
946
|
-
const approvalNonce = nonces[i] + (hasBribe && i === maxOutputIndex ? 1 : 0);
|
|
947
|
-
return wallet.signTransaction({
|
|
948
|
-
to: tokenAddress,
|
|
949
|
-
data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
|
|
950
|
-
value: 0n,
|
|
951
|
-
nonce: approvalNonce,
|
|
952
|
-
gasLimit: 60000n,
|
|
953
|
-
gasPrice,
|
|
954
|
-
chainId,
|
|
955
|
-
type: txType,
|
|
956
|
-
});
|
|
957
|
-
});
|
|
958
|
-
const approvalTxResults = await Promise.all(approvalTxPromises);
|
|
959
|
-
// ✅ 优化:并行签名所有卖出交易
|
|
960
|
-
const sellTxPromises = wallets.map(async (wallet, i) => {
|
|
915
|
+
// ✅ 优化:并行签名所有卖出交易(已移除授权交易)
|
|
916
|
+
const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
|
|
961
917
|
if (sellAmountsWei[i] <= 0n)
|
|
962
918
|
return null;
|
|
963
919
|
return wallet.signTransaction({
|
|
964
920
|
to: routerAddress,
|
|
965
921
|
data: buildSellTxData(wallet, sellAmountsWei[i]),
|
|
966
922
|
value: 0n,
|
|
967
|
-
nonce: nonces[i] +
|
|
923
|
+
nonce: nonces[i] + nonceOffsets[i],
|
|
968
924
|
gasLimit,
|
|
969
925
|
gasPrice,
|
|
970
926
|
chainId,
|
|
971
927
|
type: txType,
|
|
972
928
|
});
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
const swapTxs = [];
|
|
977
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
978
|
-
const approvalTx = approvalTxResults[i];
|
|
979
|
-
const sellTx = sellTxResults[i];
|
|
980
|
-
if (approvalTx)
|
|
981
|
-
swapTxs.push(approvalTx);
|
|
982
|
-
if (sellTx) {
|
|
983
|
-
swapTxs.push(sellTx);
|
|
984
|
-
currentNonceOffset[i]++; // 更新 offset 供利润交易使用
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
const totalOutputEstimate = outputEstimates.reduce((sum, o) => sum + o, 0n);
|
|
929
|
+
})).then(results => results.filter((tx) => tx !== null));
|
|
930
|
+
// ✅ 使用 sellAmountsWei 作为输出估计(因为已跳过 getAmountsOut 调用)
|
|
931
|
+
const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
|
|
988
932
|
let profitWei = calculateProfitAmount(totalOutputEstimate);
|
|
989
933
|
// ✅ 修复:ERC20 输出时,将利润转换为原生代币
|
|
990
934
|
if (!useNativeOutput && profitWei > 0n && quoteToken) {
|
|
@@ -999,11 +943,13 @@ export async function directV2BatchSell(params) {
|
|
|
999
943
|
// ✅ 利润交易
|
|
1000
944
|
const profitTxs = [];
|
|
1001
945
|
if (profitWei > 0n && wallets.length > 0) {
|
|
946
|
+
// 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
|
|
947
|
+
const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
|
|
1002
948
|
const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // ✅ 使用输出最大的钱包
|
|
1003
|
-
profitWei,
|
|
949
|
+
profitWei, profitNonce, gasPrice, chainId, txType);
|
|
1004
950
|
profitTxs.push(profitTx);
|
|
1005
951
|
}
|
|
1006
|
-
// ✅ 组装最终交易列表:贿赂 →
|
|
952
|
+
// ✅ 组装最终交易列表:贿赂 → 卖出 → 利润
|
|
1007
953
|
const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
|
|
1008
954
|
return {
|
|
1009
955
|
signedTransactions: signedTxs,
|
|
@@ -1025,7 +971,7 @@ export async function directV2BatchSell(params) {
|
|
|
1025
971
|
* - SwapRouter (旧版 Uniswap V3): exactInputSingle 含 deadline,multicall 不含 deadline
|
|
1026
972
|
*/
|
|
1027
973
|
export async function directV3BatchBuy(params) {
|
|
1028
|
-
const { chain, privateKeys, buyAmounts, tokenAddress, routerAddress, fee, quoteToken, quoteTokenDecimals = 18, config, } = params;
|
|
974
|
+
const { chain, privateKeys, buyAmounts, tokenAddress, routerAddress, fee, quoteToken, quoteTokenDecimals = 18, startNonces, config, } = params;
|
|
1029
975
|
if (privateKeys.length !== buyAmounts.length) {
|
|
1030
976
|
throw new Error('privateKeys 和 buyAmounts 长度必须相同');
|
|
1031
977
|
}
|
|
@@ -1037,9 +983,13 @@ export async function directV3BatchBuy(params) {
|
|
|
1037
983
|
const useLegacyRouter = isLegacySwapRouter(chain, routerAddress);
|
|
1038
984
|
const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
|
|
1039
985
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
1040
|
-
|
|
1041
|
-
const nonces = await
|
|
1042
|
-
|
|
986
|
+
// ✅ 性能优化:如果前端已传入 nonces,则跳过 RPC 获取
|
|
987
|
+
const [nonces, gasPrice] = await Promise.all([
|
|
988
|
+
startNonces && startNonces.length === wallets.length
|
|
989
|
+
? Promise.resolve(startNonces)
|
|
990
|
+
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
991
|
+
getGasPrice(provider, config)
|
|
992
|
+
]);
|
|
1043
993
|
const gasLimit = getGasLimit(config, 300000);
|
|
1044
994
|
const txType = config.txType ?? 0;
|
|
1045
995
|
const routerIface = new ethers.Interface(routerAbi);
|
|
@@ -1159,7 +1109,7 @@ export async function directV3BatchBuy(params) {
|
|
|
1159
1109
|
* - SwapRouter (旧版 Uniswap V3): exactInputSingle 含 deadline,multicall 不含 deadline
|
|
1160
1110
|
*/
|
|
1161
1111
|
export async function directV3BatchSell(params) {
|
|
1162
|
-
const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals = 18, routerAddress, fee, quoteToken, config, } = params;
|
|
1112
|
+
const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals = 18, routerAddress, fee, quoteToken, startNonces, config, } = params;
|
|
1163
1113
|
const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
|
|
1164
1114
|
const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
|
|
1165
1115
|
const useNativeOutput = isNativeToken(quoteToken);
|
|
@@ -1169,9 +1119,21 @@ export async function directV3BatchSell(params) {
|
|
|
1169
1119
|
const routerAbi = useLegacyRouter ? V3_ROUTER_LEGACY_ABI : V3_ROUTER02_ABI;
|
|
1170
1120
|
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
1171
1121
|
const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
1172
|
-
//
|
|
1173
|
-
|
|
1174
|
-
|
|
1122
|
+
// ✅ 性能优化:并行获取所有独立的 RPC 数据
|
|
1123
|
+
// ✅ 如果前端已传入 nonces,则跳过 RPC 获取
|
|
1124
|
+
const [balances, nonces, gasPrice] = await Promise.all([
|
|
1125
|
+
// 1. 批量获取余额
|
|
1126
|
+
Promise.all(wallets.map(w => tokenContract.balanceOf(w.address))),
|
|
1127
|
+
// 2. 批量获取 nonce(如果前端已传入则跳过)
|
|
1128
|
+
startNonces && startNonces.length === wallets.length
|
|
1129
|
+
? Promise.resolve(startNonces)
|
|
1130
|
+
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
1131
|
+
// 3. 获取 gas price
|
|
1132
|
+
getGasPrice(provider, config)
|
|
1133
|
+
]);
|
|
1134
|
+
const gasLimit = getGasLimit(config, 350000);
|
|
1135
|
+
const txType = config.txType ?? 0;
|
|
1136
|
+
// 计算卖出数量(同步操作,无 RPC)
|
|
1175
1137
|
const sellAmountsWei = [];
|
|
1176
1138
|
for (let i = 0; i < wallets.length; i++) {
|
|
1177
1139
|
if (sellAmounts && sellAmounts[i]) {
|
|
@@ -1185,13 +1147,7 @@ export async function directV3BatchSell(params) {
|
|
|
1185
1147
|
sellAmountsWei.push(balances[i]);
|
|
1186
1148
|
}
|
|
1187
1149
|
}
|
|
1188
|
-
const nonceManager = new NonceManager(provider);
|
|
1189
|
-
const nonces = await nonceManager.getNextNoncesForWallets(wallets);
|
|
1190
|
-
const gasPrice = await getGasPrice(provider, config);
|
|
1191
|
-
const gasLimit = getGasLimit(config, 350000);
|
|
1192
|
-
const txType = config.txType ?? 0;
|
|
1193
1150
|
const routerIface = new ethers.Interface(routerAbi);
|
|
1194
|
-
const approveIface = new ethers.Interface(ERC20_ABI);
|
|
1195
1151
|
const outputToken = useNativeOutput ? wrappedNative : quoteToken;
|
|
1196
1152
|
const deadline = getDeadline();
|
|
1197
1153
|
// ✅ 优化:构建卖出交易数据的辅助函数
|
|
@@ -1236,28 +1192,14 @@ export async function directV3BatchSell(params) {
|
|
|
1236
1192
|
}
|
|
1237
1193
|
}
|
|
1238
1194
|
};
|
|
1239
|
-
// ✅ 优化:并行检查所有授权
|
|
1240
|
-
let allowances = [];
|
|
1241
|
-
if (!config.skipApprovalCheck) {
|
|
1242
|
-
allowances = await Promise.all(wallets.map(w => tokenContract.allowance(w.address, routerAddress)));
|
|
1243
|
-
}
|
|
1244
1195
|
// ✅ 先找出输出最大的钱包索引(用于支付贿赂和利润)
|
|
1245
1196
|
// 对于 V3 卖出,使用卖出金额作为输出估计
|
|
1246
1197
|
const maxOutputIndex = findMaxFlowIndex(sellAmountsWei);
|
|
1247
1198
|
// ✅ 计算贿赂金额(仅 BSC 链)
|
|
1248
1199
|
const bribeWei = getBribeAmount(config, chain);
|
|
1249
1200
|
const hasBribe = bribeWei > 0n && wallets.length > 0;
|
|
1250
|
-
// ✅
|
|
1251
|
-
const
|
|
1252
|
-
let offset = 0;
|
|
1253
|
-
// 贿赂交易偏移(只有 maxOutputIndex 钱包需要)
|
|
1254
|
-
if (hasBribe && i === maxOutputIndex)
|
|
1255
|
-
offset++;
|
|
1256
|
-
// 授权交易偏移
|
|
1257
|
-
if (!config.skipApprovalCheck && sellAmountsWei[i] > 0n && allowances[i] < sellAmountsWei[i])
|
|
1258
|
-
offset++;
|
|
1259
|
-
return offset;
|
|
1260
|
-
});
|
|
1201
|
+
// ✅ 计算 nonce 偏移(仅贿赂,已移除授权)
|
|
1202
|
+
const nonceOffsets = wallets.map((_, i) => i === maxOutputIndex && hasBribe ? 1 : 0);
|
|
1261
1203
|
// ✅ 贿赂交易放在首位(提高 BlockRazor 打包优先级)
|
|
1262
1204
|
const bribeTxs = [];
|
|
1263
1205
|
if (hasBribe) {
|
|
@@ -1265,60 +1207,23 @@ export async function directV3BatchSell(params) {
|
|
|
1265
1207
|
gasPrice, chainId, txType);
|
|
1266
1208
|
bribeTxs.push(bribeTx);
|
|
1267
1209
|
}
|
|
1268
|
-
// ✅
|
|
1269
|
-
const
|
|
1270
|
-
if (config.skipApprovalCheck || sellAmountsWei[i] <= 0n)
|
|
1271
|
-
return null;
|
|
1272
|
-
if (allowances[i] >= sellAmountsWei[i])
|
|
1273
|
-
return null;
|
|
1274
|
-
// 授权交易的 nonce = 原始 nonce + 贿赂偏移
|
|
1275
|
-
const approvalNonce = nonces[i] + (hasBribe && i === maxOutputIndex ? 1 : 0);
|
|
1276
|
-
return wallet.signTransaction({
|
|
1277
|
-
to: tokenAddress,
|
|
1278
|
-
data: approveIface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]),
|
|
1279
|
-
value: 0n,
|
|
1280
|
-
nonce: approvalNonce,
|
|
1281
|
-
gasLimit: 60000n,
|
|
1282
|
-
gasPrice,
|
|
1283
|
-
chainId,
|
|
1284
|
-
type: txType,
|
|
1285
|
-
});
|
|
1286
|
-
});
|
|
1287
|
-
const approvalTxResults = await Promise.all(approvalTxPromises);
|
|
1288
|
-
// ✅ 优化:并行签名所有卖出交易
|
|
1289
|
-
const sellTxPromises = wallets.map(async (wallet, i) => {
|
|
1210
|
+
// ✅ 优化:并行签名所有卖出交易(已移除授权交易)
|
|
1211
|
+
const swapTxs = await Promise.all(wallets.map(async (wallet, i) => {
|
|
1290
1212
|
if (sellAmountsWei[i] <= 0n)
|
|
1291
1213
|
return null;
|
|
1292
1214
|
return wallet.signTransaction({
|
|
1293
1215
|
to: routerAddress,
|
|
1294
1216
|
data: buildV3SellTxData(wallet, sellAmountsWei[i]),
|
|
1295
1217
|
value: 0n,
|
|
1296
|
-
nonce: nonces[i] +
|
|
1218
|
+
nonce: nonces[i] + nonceOffsets[i],
|
|
1297
1219
|
gasLimit,
|
|
1298
1220
|
gasPrice,
|
|
1299
1221
|
chainId,
|
|
1300
1222
|
type: txType,
|
|
1301
1223
|
});
|
|
1302
|
-
});
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
const swapTxs = [];
|
|
1306
|
-
const outputEstimates = [];
|
|
1307
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
1308
|
-
const approvalTx = approvalTxResults[i];
|
|
1309
|
-
const sellTx = sellTxResults[i];
|
|
1310
|
-
if (approvalTx)
|
|
1311
|
-
swapTxs.push(approvalTx);
|
|
1312
|
-
if (sellTx) {
|
|
1313
|
-
swapTxs.push(sellTx);
|
|
1314
|
-
outputEstimates.push(sellAmountsWei[i]);
|
|
1315
|
-
currentNonceOffset[i]++;
|
|
1316
|
-
}
|
|
1317
|
-
else {
|
|
1318
|
-
outputEstimates.push(0n);
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
const totalOutputEstimate = outputEstimates.reduce((sum, o) => sum + o, 0n);
|
|
1224
|
+
})).then(results => results.filter((tx) => tx !== null));
|
|
1225
|
+
// ✅ 使用 sellAmountsWei 计算总输出
|
|
1226
|
+
const totalOutputEstimate = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
|
|
1322
1227
|
let profitWei = calculateProfitAmount(totalOutputEstimate);
|
|
1323
1228
|
// ✅ 修复:ERC20 输出时,将利润转换为原生代币
|
|
1324
1229
|
if (!useNativeOutput && profitWei > 0n && quoteToken) {
|
|
@@ -1333,11 +1238,13 @@ export async function directV3BatchSell(params) {
|
|
|
1333
1238
|
// ✅ 利润交易
|
|
1334
1239
|
const profitTxs = [];
|
|
1335
1240
|
if (profitWei > 0n && wallets.length > 0) {
|
|
1241
|
+
// 利润交易 nonce = 原始 nonce + 贿赂偏移 + 1(卖出交易)
|
|
1242
|
+
const profitNonce = nonces[maxOutputIndex] + nonceOffsets[maxOutputIndex] + 1;
|
|
1336
1243
|
const profitTx = await buildProfitTransaction(wallets[maxOutputIndex], // ✅ 使用输出最大的钱包
|
|
1337
|
-
profitWei,
|
|
1244
|
+
profitWei, profitNonce, gasPrice, chainId, txType);
|
|
1338
1245
|
profitTxs.push(profitTx);
|
|
1339
1246
|
}
|
|
1340
|
-
// ✅ 组装最终交易列表:贿赂 →
|
|
1247
|
+
// ✅ 组装最终交易列表:贿赂 → 卖出 → 利润
|
|
1341
1248
|
const signedTxs = [...bribeTxs, ...swapTxs, ...profitTxs];
|
|
1342
1249
|
return {
|
|
1343
1250
|
signedTransactions: signedTxs,
|