four-flap-meme-sdk 1.9.41 → 1.9.43
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.
|
@@ -260,11 +260,11 @@ export async function directSellForSubmit(params) {
|
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
262
|
export async function directBatchBuyForSubmit(params) {
|
|
263
|
-
const { rpcUrl = ENI_RPC_URL, token, privateKeys, amounts, slippageBps = 100, gasPrice } = params;
|
|
264
|
-
if (
|
|
265
|
-
throw new Error(`privateKeys.length (${
|
|
263
|
+
const { rpcUrl = ENI_RPC_URL, token, privateKeys: rawKeys, amounts: rawAmounts, slippageBps = 100, gasPrice } = params;
|
|
264
|
+
if (rawKeys.length !== rawAmounts.length) {
|
|
265
|
+
throw new Error(`privateKeys.length (${rawKeys.length}) !== amounts.length (${rawAmounts.length})`);
|
|
266
266
|
}
|
|
267
|
-
if (
|
|
267
|
+
if (rawKeys.length === 0) {
|
|
268
268
|
throw new Error('privateKeys 不能为空');
|
|
269
269
|
}
|
|
270
270
|
const provider = new JsonRpcProvider(rpcUrl, ENI_CHAIN_ID, { staticNetwork: true });
|
|
@@ -272,6 +272,34 @@ export async function directBatchBuyForSubmit(params) {
|
|
|
272
272
|
const feeData = await provider.getFeeData();
|
|
273
273
|
const maxFeePerGas = gasPrice ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
|
|
274
274
|
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
|
|
275
|
+
const swapGasLimit = 300000n;
|
|
276
|
+
const profitTxGasCost = NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
|
|
277
|
+
const totalFlowRaw = rawAmounts.reduce((sum, amount) => sum + amount, 0n);
|
|
278
|
+
const profitAmountFull = calculateProfitAmount(totalFlowRaw, NORMAL_PROFIT_BPS) + profitTxGasCost;
|
|
279
|
+
const maxPayerIdx = pickMaxAmountIndex(rawAmounts);
|
|
280
|
+
const wallets = rawKeys.map(pk => new Wallet(pk, provider));
|
|
281
|
+
const balances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
|
|
282
|
+
let privateKeys = rawKeys;
|
|
283
|
+
let amounts = rawAmounts;
|
|
284
|
+
const validIndices = [];
|
|
285
|
+
for (let i = 0; i < rawKeys.length; i++) {
|
|
286
|
+
let required = rawAmounts[i] + swapGasLimit * maxFeePerGas;
|
|
287
|
+
if (i === maxPayerIdx)
|
|
288
|
+
required += profitAmountFull + NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
|
|
289
|
+
if (balances[i] < required) {
|
|
290
|
+
console.warn(`[DAOAAS BatchBuy] 钱包 ${wallets[i].address.slice(0, 10)} 余额不足: 余额=${balances[i]}, 需要=${required},跳过`);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
validIndices.push(i);
|
|
294
|
+
}
|
|
295
|
+
if (validIndices.length === 0) {
|
|
296
|
+
throw new Error('所有钱包余额不足(含 swap + gas),无法执行 DAOAAS 买入');
|
|
297
|
+
}
|
|
298
|
+
if (validIndices.length < rawKeys.length) {
|
|
299
|
+
privateKeys = validIndices.map(i => rawKeys[i]);
|
|
300
|
+
amounts = validIndices.map(i => rawAmounts[i]);
|
|
301
|
+
console.log(`[DAOAAS BatchBuy] 余额预检: ${rawKeys.length - validIndices.length} 个钱包被跳过, 剩余 ${validIndices.length} 个`);
|
|
302
|
+
}
|
|
275
303
|
const transactions = [];
|
|
276
304
|
const totalFlow = amounts.reduce((sum, amount) => sum + amount, 0n);
|
|
277
305
|
const isBatch = privateKeys.length > 1;
|
|
@@ -293,11 +321,10 @@ export async function directBatchBuyForSubmit(params) {
|
|
|
293
321
|
type: 2,
|
|
294
322
|
maxFeePerGas,
|
|
295
323
|
maxPriorityFeePerGas,
|
|
296
|
-
gasLimit:
|
|
324
|
+
gasLimit: swapGasLimit,
|
|
297
325
|
});
|
|
298
326
|
transactions.push({ hash: '', signedTx, from: wallet.address, nonce });
|
|
299
327
|
}
|
|
300
|
-
const profitTxGasCost = NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
|
|
301
328
|
const profitAmount = calculateProfitAmount(totalFlow, NORMAL_PROFIT_BPS) + profitTxGasCost;
|
|
302
329
|
const payerIndex = pickMaxAmountIndex(amounts);
|
|
303
330
|
const profitTx = await buildRevenueTail({
|
|
@@ -311,7 +338,7 @@ export async function directBatchBuyForSubmit(params) {
|
|
|
311
338
|
return {
|
|
312
339
|
transactions,
|
|
313
340
|
signedTransactions: [...transactions.map(tx => tx.signedTx), profitTx.signedTx],
|
|
314
|
-
totalGasCost:
|
|
341
|
+
totalGasCost: swapGasLimit * maxFeePerGas * BigInt(transactions.length) + NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas,
|
|
315
342
|
metadata: formatSubmitMetadata(totalFlow, profitAmount),
|
|
316
343
|
};
|
|
317
344
|
}
|
|
@@ -328,11 +355,25 @@ export async function directBatchSellForSubmit(params) {
|
|
|
328
355
|
const feeData = await provider.getFeeData();
|
|
329
356
|
const maxFeePerGas = gasPrice ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
|
|
330
357
|
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
|
|
358
|
+
const swapGasLimit = 300000n;
|
|
359
|
+
const swapGasCost = swapGasLimit * maxFeePerGas;
|
|
360
|
+
const profitGasCost = NATIVE_TRANSFER_GAS_LIMIT * maxFeePerGas;
|
|
361
|
+
const wallets = privateKeys.map(pk => new Wallet(pk, provider));
|
|
362
|
+
const balances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
|
|
363
|
+
const maxPayerIdx = pickMaxAmountIndex(tokenAmounts);
|
|
364
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
365
|
+
let required = swapGasCost;
|
|
366
|
+
if (i === maxPayerIdx)
|
|
367
|
+
required += profitGasCost;
|
|
368
|
+
if (balances[i] < required) {
|
|
369
|
+
console.warn(`[DAOAAS BatchSell] 钱包 ${wallets[i].address.slice(0, 10)} 原生余额不足 gas: 余额=${balances[i]}, 需要=${required},可能导致交易失败`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
331
372
|
const transactions = [];
|
|
332
373
|
const estimatedOuts = [];
|
|
333
374
|
const isBatch = privateKeys.length > 1;
|
|
334
375
|
for (let i = 0; i < privateKeys.length; i++) {
|
|
335
|
-
const wallet =
|
|
376
|
+
const wallet = wallets[i];
|
|
336
377
|
const preview = await query.previewSell(token, tokenAmounts[i]);
|
|
337
378
|
estimatedOuts.push(preview);
|
|
338
379
|
const effectiveSlippage = isBatch
|
|
@@ -349,7 +390,7 @@ export async function directBatchSellForSubmit(params) {
|
|
|
349
390
|
type: 2,
|
|
350
391
|
maxFeePerGas,
|
|
351
392
|
maxPriorityFeePerGas,
|
|
352
|
-
gasLimit:
|
|
393
|
+
gasLimit: swapGasLimit,
|
|
353
394
|
});
|
|
354
395
|
transactions.push({ hash: '', signedTx, from: wallet.address, nonce });
|
|
355
396
|
}
|
|
@@ -535,16 +535,19 @@ export async function directV2BatchBuy(params) {
|
|
|
535
535
|
const flowAmounts = buyAmounts.map(amount => ethers.parseUnits(amount, quoteTokenDecimals));
|
|
536
536
|
const totalFlowWei = flowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
537
537
|
const baseProfitWei = calculateProfitAmount(totalFlowWei);
|
|
538
|
-
// ✅ 方案 B:并行获取 nonces、gasPrice
|
|
539
|
-
const
|
|
538
|
+
// ✅ 方案 B:并行获取 nonces、gasPrice、ERC20 报价 以及(非原生代币时)授权额度
|
|
539
|
+
const quoteTokenContract = (!useNative && quoteToken) ? new Contract(quoteToken, ERC20_ABI, provider) : null;
|
|
540
|
+
const [nonces, gasPrice, nativeProfitWei, buyAllowances] = await Promise.all([
|
|
540
541
|
startNonces && startNonces.length === wallets.length
|
|
541
542
|
? Promise.resolve(startNonces)
|
|
542
543
|
: new NonceManager(provider).getNextNoncesForWallets(wallets),
|
|
543
544
|
getGasPrice(provider, config),
|
|
544
|
-
// ERC20 报价提前并行获取(V2 买入用 V2 报价)
|
|
545
545
|
(!useNative && baseProfitWei > 0n && quoteToken)
|
|
546
546
|
? getTokenToNativeQuote(provider, quoteToken, baseProfitWei, chain, 'v2')
|
|
547
|
-
: Promise.resolve(baseProfitWei)
|
|
547
|
+
: Promise.resolve(baseProfitWei),
|
|
548
|
+
quoteTokenContract
|
|
549
|
+
? Promise.all(wallets.map(w => quoteTokenContract.allowance(w.address, routerAddress).catch(() => 0n)))
|
|
550
|
+
: Promise.resolve(wallets.map(() => ethers.MaxUint256)),
|
|
548
551
|
]);
|
|
549
552
|
// 确定最终利润金额(ENI 链额外补偿利润转账的 gas 成本)
|
|
550
553
|
let profitWei = nativeProfitWei > 0n ? nativeProfitWei : 0n;
|
|
@@ -554,6 +557,75 @@ export async function directV2BatchBuy(params) {
|
|
|
554
557
|
const gasLimit = getGasLimit(config, 250000);
|
|
555
558
|
const txType = config.txType ?? 0;
|
|
556
559
|
const deadline = getDeadline();
|
|
560
|
+
// ENI 链原生代币买入:链上余额预检,跳过余额不足的钱包
|
|
561
|
+
let activeIndices = null;
|
|
562
|
+
if (useNative && chain.toUpperCase() === 'ENI') {
|
|
563
|
+
const eniBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
|
|
564
|
+
const maxFlowIdx = findMaxFlowIndex(flowAmounts);
|
|
565
|
+
const profitGasCost = GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
|
|
566
|
+
const validIdx = [];
|
|
567
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
568
|
+
let required = flowAmounts[i] + BigInt(gasLimit) * gasPrice;
|
|
569
|
+
if (i === maxFlowIdx && profitWei > 0n) {
|
|
570
|
+
required += profitWei + profitGasCost;
|
|
571
|
+
}
|
|
572
|
+
if (eniBalances[i] < required) {
|
|
573
|
+
console.warn(`[V2 BatchBuy ENI] 钱包 ${wallets[i].address.slice(0, 10)} 余额不足: 余额=${eniBalances[i]}, 需要=${required},跳过`);
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
validIdx.push(i);
|
|
577
|
+
}
|
|
578
|
+
if (validIdx.length === 0) {
|
|
579
|
+
throw new Error('[V2 BatchBuy ENI] 所有钱包余额不足(含 swap + gas + 利润费),无法执行');
|
|
580
|
+
}
|
|
581
|
+
if (validIdx.length < wallets.length) {
|
|
582
|
+
console.log(`[V2 BatchBuy ENI] 余额预检: ${wallets.length - validIdx.length} 个钱包被跳过, 剩余 ${validIdx.length} 个`);
|
|
583
|
+
activeIndices = validIdx;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
// 如果有钱包被跳过,重建数组(仅 ENI)
|
|
587
|
+
let activeWallets = wallets;
|
|
588
|
+
let activeFlowAmounts = flowAmounts;
|
|
589
|
+
let activeNonces = nonces;
|
|
590
|
+
let activeBuyAllowances = buyAllowances;
|
|
591
|
+
if (activeIndices) {
|
|
592
|
+
activeWallets = activeIndices.map(i => wallets[i]);
|
|
593
|
+
activeFlowAmounts = activeIndices.map(i => flowAmounts[i]);
|
|
594
|
+
activeNonces = activeIndices.map(i => nonces[i]);
|
|
595
|
+
activeBuyAllowances = activeIndices.map(i => buyAllowances[i]);
|
|
596
|
+
}
|
|
597
|
+
const activeTotalFlowWei = activeFlowAmounts.reduce((sum, amt) => sum + amt, 0n);
|
|
598
|
+
const activeBaseProfitWei = calculateProfitAmount(activeTotalFlowWei);
|
|
599
|
+
let activeProfitWei = activeBaseProfitWei > 0n ? activeBaseProfitWei : 0n;
|
|
600
|
+
if (activeProfitWei > 0n && chain.toUpperCase() === 'ENI') {
|
|
601
|
+
activeProfitWei += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
|
|
602
|
+
}
|
|
603
|
+
if (!activeIndices) {
|
|
604
|
+
activeProfitWei = profitWei;
|
|
605
|
+
}
|
|
606
|
+
// ✅ 自动授权:ERC20 报价代币买入时,检查钱包对 Router 的授权额度,不足则插入 approve
|
|
607
|
+
const buyApproveTxs = [];
|
|
608
|
+
if (!useNative && quoteTokenContract) {
|
|
609
|
+
for (let i = 0; i < activeWallets.length; i++) {
|
|
610
|
+
if (activeFlowAmounts[i] <= 0n)
|
|
611
|
+
continue;
|
|
612
|
+
if (activeBuyAllowances[i] < activeFlowAmounts[i]) {
|
|
613
|
+
console.log(`🔓 [V2 Buy] 钱包 ${i} 报价代币授权不足 (${activeBuyAllowances[i]} < ${activeFlowAmounts[i]}), 自动 approve`);
|
|
614
|
+
const approveData = quoteTokenContract.interface.encodeFunctionData('approve', [routerAddress, ethers.MaxUint256]);
|
|
615
|
+
const approveTx = await activeWallets[i].signTransaction({
|
|
616
|
+
to: quoteToken,
|
|
617
|
+
data: approveData,
|
|
618
|
+
value: 0n,
|
|
619
|
+
nonce: activeNonces[i],
|
|
620
|
+
gasLimit: 60000n,
|
|
621
|
+
...buildGasFields(txType, gasPrice),
|
|
622
|
+
chainId,
|
|
623
|
+
});
|
|
624
|
+
buyApproveTxs.push({ walletIndex: i, tx: approveTx });
|
|
625
|
+
activeNonces[i]++;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
557
629
|
// 构建路径
|
|
558
630
|
const inputToken = useNative ? wrappedNative : quoteToken;
|
|
559
631
|
const path = [inputToken, tokenAddress];
|
|
@@ -584,28 +656,28 @@ export async function directV2BatchBuy(params) {
|
|
|
584
656
|
}
|
|
585
657
|
};
|
|
586
658
|
// 选择金额最大的钱包支付贿赂和利润
|
|
587
|
-
const maxFlowIndex = findMaxFlowIndex(
|
|
659
|
+
const maxFlowIndex = findMaxFlowIndex(activeFlowAmounts);
|
|
588
660
|
// 计算贿赂金额(仅 BSC 链)
|
|
589
661
|
const bribeWei = getBribeAmount(config, chain);
|
|
590
|
-
const hasBribe = bribeWei > 0n &&
|
|
591
|
-
const hasProfit =
|
|
662
|
+
const hasBribe = bribeWei > 0n && activeWallets.length > 0;
|
|
663
|
+
const hasProfit = activeProfitWei > 0n;
|
|
592
664
|
// 计算 nonce 偏移
|
|
593
|
-
const nonceOffsets =
|
|
665
|
+
const nonceOffsets = activeWallets.map((_, i) => i === maxFlowIndex && hasBribe ? 1 : 0);
|
|
594
666
|
// ✅ 方案 A:并行签名所有交易(贿赂、主交易、利润)
|
|
595
667
|
const signPromises = [];
|
|
596
668
|
// 贿赂交易
|
|
597
669
|
if (hasBribe) {
|
|
598
|
-
signPromises.push(buildBribeTransaction(
|
|
670
|
+
signPromises.push(buildBribeTransaction(activeWallets[maxFlowIndex], bribeWei, activeNonces[maxFlowIndex], gasPrice, chainId, txType)
|
|
599
671
|
.then(tx => ({ type: 'bribe', index: 0, tx })));
|
|
600
672
|
}
|
|
601
673
|
// 主交易(并行签名)
|
|
602
|
-
|
|
603
|
-
const { txData, txValue } = buildTxData(wallet,
|
|
674
|
+
activeWallets.forEach((wallet, i) => {
|
|
675
|
+
const { txData, txValue } = buildTxData(wallet, activeFlowAmounts[i]);
|
|
604
676
|
signPromises.push(wallet.signTransaction({
|
|
605
677
|
to: routerAddress,
|
|
606
678
|
data: txData,
|
|
607
679
|
value: txValue,
|
|
608
|
-
nonce:
|
|
680
|
+
nonce: activeNonces[i] + nonceOffsets[i],
|
|
609
681
|
gasLimit,
|
|
610
682
|
...buildGasFields(txType, gasPrice),
|
|
611
683
|
chainId,
|
|
@@ -613,34 +685,35 @@ export async function directV2BatchBuy(params) {
|
|
|
613
685
|
});
|
|
614
686
|
// ✅ 并行执行所有签名
|
|
615
687
|
const signedResults = await Promise.all(signPromises);
|
|
616
|
-
//
|
|
688
|
+
// 按类型分组并按顺序组装:approve → 贿赂 → 交易
|
|
689
|
+
const approveSignedTxs = buyApproveTxs.sort((a, b) => a.walletIndex - b.walletIndex).map(a => a.tx);
|
|
617
690
|
const bribeTxs = signedResults.filter(r => r.type === 'bribe').map(r => r.tx);
|
|
618
691
|
const swapTxs = signedResults.filter(r => r.type === 'swap').sort((a, b) => a.index - b.index).map(r => r.tx);
|
|
619
|
-
const signedTxs = [...bribeTxs, ...swapTxs];
|
|
692
|
+
const signedTxs = [...approveSignedTxs, ...bribeTxs, ...swapTxs];
|
|
620
693
|
// ✅ 检查是否使用分布式利润模式
|
|
621
694
|
const profitMode = config.profitMode || 'single';
|
|
622
695
|
const skipProfit = config.skipProfit === true;
|
|
623
|
-
console.log('🔧 [SDK directV2BatchBuy] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:',
|
|
696
|
+
console.log('🔧 [SDK directV2BatchBuy] profitMode:', profitMode, 'skipProfit:', skipProfit, 'wallets:', activeWallets.length);
|
|
624
697
|
// 利润多跳转账
|
|
625
698
|
let profitHopWallets;
|
|
626
699
|
if (hasProfit && !skipProfit) {
|
|
627
700
|
if (profitMode === 'distributed') {
|
|
628
701
|
// ✅ 分布式模式:每个钱包独立扣除自己的利润
|
|
629
702
|
const MAX_DISTRIBUTED_WALLETS = 12;
|
|
630
|
-
if (
|
|
631
|
-
throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${
|
|
703
|
+
if (activeWallets.length > MAX_DISTRIBUTED_WALLETS) {
|
|
704
|
+
throw new Error(`分布式利润模式最多支持 ${MAX_DISTRIBUTED_WALLETS} 个钱包,当前 ${activeWallets.length} 个`);
|
|
632
705
|
}
|
|
633
706
|
profitHopWallets = [];
|
|
634
707
|
// 计算每个钱包的利润(按比例分配)
|
|
635
|
-
for (let i = 0; i <
|
|
636
|
-
const walletProfit =
|
|
637
|
-
? (
|
|
708
|
+
for (let i = 0; i < activeWallets.length; i++) {
|
|
709
|
+
const walletProfit = activeTotalFlowWei > 0n
|
|
710
|
+
? (activeProfitWei * activeFlowAmounts[i]) / activeTotalFlowWei
|
|
638
711
|
: 0n;
|
|
639
712
|
if (walletProfit > 0n) {
|
|
640
|
-
const walletProfitNonce =
|
|
713
|
+
const walletProfitNonce = activeNonces[i] + nonceOffsets[i] + 1;
|
|
641
714
|
const profitResult = await buildProfitHopTransactions({
|
|
642
715
|
provider,
|
|
643
|
-
payerWallet:
|
|
716
|
+
payerWallet: activeWallets[i],
|
|
644
717
|
profitAmount: walletProfit,
|
|
645
718
|
profitRecipient: PROFIT_CONFIG.RECIPIENT,
|
|
646
719
|
hopCount: PROFIT_HOP_COUNT,
|
|
@@ -658,8 +731,8 @@ export async function directV2BatchBuy(params) {
|
|
|
658
731
|
}
|
|
659
732
|
else {
|
|
660
733
|
// ✅ 单一模式(默认):从金额最大的钱包统一扣除
|
|
661
|
-
const profitNonce =
|
|
662
|
-
const profitResult = await buildProfitTransactionWithHops(provider,
|
|
734
|
+
const profitNonce = activeNonces[maxFlowIndex] + nonceOffsets[maxFlowIndex] + 1;
|
|
735
|
+
const profitResult = await buildProfitTransactionWithHops(provider, activeWallets[maxFlowIndex], activeProfitWei, profitNonce, gasPrice, chainId, txType);
|
|
663
736
|
signedTxs.push(...profitResult.signedTransactions);
|
|
664
737
|
profitHopWallets = profitResult.hopWallets;
|
|
665
738
|
}
|
|
@@ -668,10 +741,10 @@ export async function directV2BatchBuy(params) {
|
|
|
668
741
|
signedTransactions: signedTxs,
|
|
669
742
|
profitHopWallets,
|
|
670
743
|
metadata: {
|
|
671
|
-
profitMode,
|
|
672
|
-
profitAmount: ethers.formatEther(
|
|
744
|
+
profitMode,
|
|
745
|
+
profitAmount: ethers.formatEther(activeProfitWei),
|
|
673
746
|
profitRecipient: PROFIT_CONFIG.RECIPIENT,
|
|
674
|
-
totalFlow: ethers.formatUnits(
|
|
747
|
+
totalFlow: ethers.formatUnits(activeTotalFlowWei, quoteTokenDecimals),
|
|
675
748
|
},
|
|
676
749
|
};
|
|
677
750
|
}
|
|
@@ -704,22 +777,42 @@ export async function directV2BatchSell(params) {
|
|
|
704
777
|
const gasLimit = getGasLimit(config, 300000);
|
|
705
778
|
const txType = config.txType ?? 0;
|
|
706
779
|
const deadline = getDeadline();
|
|
707
|
-
// 计算卖出数量(同步操作,无 RPC
|
|
780
|
+
// 计算卖出数量(同步操作,无 RPC),并安全兜底不超过链上实际余额
|
|
708
781
|
const sellAmountsWei = [];
|
|
709
782
|
for (let i = 0; i < wallets.length; i++) {
|
|
783
|
+
let amount;
|
|
710
784
|
if (sellAmounts && sellAmounts[i]) {
|
|
711
785
|
const truncatedAmount = truncateDecimals(sellAmounts[i], tokenDecimals);
|
|
712
|
-
|
|
786
|
+
amount = ethers.parseUnits(truncatedAmount, tokenDecimals);
|
|
713
787
|
}
|
|
714
788
|
else if (sellPercentages && sellPercentages[i]) {
|
|
715
789
|
const pct = Math.min(100, Math.max(0, sellPercentages[i]));
|
|
716
|
-
|
|
790
|
+
amount = (balances[i] * BigInt(pct)) / 100n;
|
|
717
791
|
}
|
|
718
792
|
else {
|
|
719
|
-
|
|
793
|
+
amount = balances[i];
|
|
720
794
|
}
|
|
795
|
+
if (amount > balances[i]) {
|
|
796
|
+
console.log(`⚠️ [V2 Sell] 钱包 ${i} 卖出量(${amount}) > 链上余额(${balances[i]}),已自动调整为实际余额`);
|
|
797
|
+
amount = balances[i];
|
|
798
|
+
}
|
|
799
|
+
sellAmountsWei.push(amount);
|
|
721
800
|
}
|
|
722
801
|
const totalSellAmount = sellAmountsWei.reduce((sum, o) => sum + o, 0n);
|
|
802
|
+
// ENI 链卖出:检查 maxPayer 的原生余额是否足够覆盖 gas(swap gas + profit hop gas)
|
|
803
|
+
if (chain.toUpperCase() === 'ENI') {
|
|
804
|
+
const maxSellIdx = findMaxFlowIndex(sellAmountsWei);
|
|
805
|
+
const nativeBalances = await Promise.all(wallets.map(w => provider.getBalance(w.address)));
|
|
806
|
+
for (let i = 0; i < wallets.length; i++) {
|
|
807
|
+
let requiredGas = BigInt(gasLimit) * gasPrice;
|
|
808
|
+
if (i === maxSellIdx) {
|
|
809
|
+
requiredGas += GAS_LIMITS.NATIVE_TRANSFER * gasPrice;
|
|
810
|
+
}
|
|
811
|
+
if (nativeBalances[i] < requiredGas) {
|
|
812
|
+
console.warn(`[V2 BatchSell ENI] 钱包 ${wallets[i].address.slice(0, 10)} 原生余额不足 gas: 余额=${nativeBalances[i]}, 需要=${requiredGas},交易可能失败`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
723
816
|
// ✅ 自动授权:检查每个钱包对 Router 的授权额度,不足则插入 approve 交易
|
|
724
817
|
const approveTxs = [];
|
|
725
818
|
for (let i = 0; i < wallets.length; i++) {
|