four-flap-meme-sdk 4.4.1 → 4.4.3
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/bundle/four-meme/approve-tokenmanager.js +2 -2
- package/dist/bundle/four-meme/core/create-token-quote.js +10 -1
- package/dist/bundle/four-meme/core/sell-flow.js +15 -1
- package/dist/bundle/four-meme/private/buy-quote.js +10 -1
- package/dist/bundle/four-meme/swap/bundle.js +17 -2
- package/dist/bundle/four-meme/swap-sign-helpers.js +6 -1
- package/dist/bundle/four-meme/utils-disperse.js +21 -5
- package/dist/bundle/four-meme/utils-sweep/context.d.ts +2 -0
- package/dist/bundle/four-meme/utils-sweep/erc20-no-hop.js +6 -7
- package/dist/bundle/four-meme/utils-sweep/main.js +21 -4
- package/dist/bundle/four-meme/utils-sweep/native-no-hop.js +2 -3
- package/dist/bundle/four-meme/utils-sweep/with-hops.js +3 -3
- package/dist/bundle/internal.d.ts +13 -0
- package/dist/bundle/internal.js +42 -0
- package/dist/chains/eni/batch-router/bundle-approve.js +2 -1
- package/dist/chains/eni/batch-router/transfer-disperse.js +2 -1
- package/dist/chains/eni/batch-router/transfer-sweep.js +14 -3
- package/dist/chains/eni/platforms/dswap/liquidity.js +4 -4
- package/dist/chains/robinhood/sign.js +15 -1
- package/dist/core/gas/bundle-gas.d.ts +8 -0
- package/dist/core/gas/bundle-gas.js +13 -1
- package/dist/core/tx/wallet-sign-patch.js +50 -4
- package/dist/domains/dex/direct-router-v2-buy.js +3 -2
- package/dist/domains/dex/direct-router-v2-sell.js +3 -2
- package/dist/domains/flap/portal-bundle/helpers.js +1 -1
- package/dist/domains/flap/portal-bundle-merkle/swap-planning.js +5 -2
- package/dist/domains/four/contracts/tm-bundle-create.js +13 -1
- package/dist/domains/four/contracts/tm-bundle-helpers.js +1 -1
- package/dist/domains/holders/internal/constants.d.ts +1 -1
- package/dist/domains/holders/internal/constants.js +1 -1
- package/package.json +1 -1
|
@@ -50,8 +50,8 @@ function resolveApprovalGasLimit(config) {
|
|
|
50
50
|
if (config.gasLimit)
|
|
51
51
|
return BigInt(config.gasLimit);
|
|
52
52
|
if (config.gasLimitMultiplier)
|
|
53
|
-
return BigInt(Math.ceil(
|
|
54
|
-
return
|
|
53
|
+
return BigInt(Math.ceil(150000 * config.gasLimitMultiplier));
|
|
54
|
+
return 150000n;
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
57
|
* 批量授权代币给 TokenManager(用于 Private Buy/Sell)
|
|
@@ -63,11 +63,20 @@ export async function signQuoteApproveIfNeeded(params) {
|
|
|
63
63
|
if (allowance >= amount)
|
|
64
64
|
return undefined;
|
|
65
65
|
const unsigned = await token.approve.populateTransaction(params.spender, amount);
|
|
66
|
+
let gasLimit = 150000n;
|
|
67
|
+
try {
|
|
68
|
+
const estimated = await provider.estimateGas({ ...unsigned, from: params.wallet.address });
|
|
69
|
+
if (estimated > 0n)
|
|
70
|
+
gasLimit = estimated;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* 估算失败用 15 万,避免代理报价币 8 万不够 */
|
|
74
|
+
}
|
|
66
75
|
return params.wallet.signTransaction({
|
|
67
76
|
...unsigned,
|
|
68
77
|
from: params.wallet.address,
|
|
69
78
|
nonce: await params.getNonce(),
|
|
70
|
-
gasLimit
|
|
79
|
+
gasLimit,
|
|
71
80
|
gasPrice: params.gasPrice,
|
|
72
81
|
chainId: params.chainId,
|
|
73
82
|
type: params.txType,
|
|
@@ -90,12 +90,26 @@ async function executeSellFlow(options) {
|
|
|
90
90
|
amountsWei,
|
|
91
91
|
minOuts: sellOutputs.minOuts,
|
|
92
92
|
});
|
|
93
|
+
let sellGasLimit = finalGasLimit;
|
|
94
|
+
if (unsignedSells[0] && wallets[0]) {
|
|
95
|
+
try {
|
|
96
|
+
const estimated = await provider.estimateGas({
|
|
97
|
+
...unsignedSells[0],
|
|
98
|
+
from: wallets[0].address,
|
|
99
|
+
});
|
|
100
|
+
if (estimated > 0n)
|
|
101
|
+
sellGasLimit = estimated;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
/* 估算失败继续用配置上限 */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
93
107
|
// ✅ 并行签名所有卖出交易
|
|
94
108
|
const signedSells = await Promise.all(unsignedSells.map((unsigned, index) => wallets[index].signTransaction({
|
|
95
109
|
...unsigned,
|
|
96
110
|
from: wallets[index].address,
|
|
97
111
|
nonce: adjustedNonces[index],
|
|
98
|
-
gasLimit:
|
|
112
|
+
gasLimit: sellGasLimit,
|
|
99
113
|
gasPrice,
|
|
100
114
|
chainId,
|
|
101
115
|
type: txType,
|
|
@@ -56,11 +56,20 @@ export async function signQuoteApproveTxs(params) {
|
|
|
56
56
|
continue;
|
|
57
57
|
const token = new Contract(quoteAddress, ERC20_ABI, wallets[i]);
|
|
58
58
|
const unsigned = await token.approve.populateTransaction(spender, amounts[i]);
|
|
59
|
+
let gasLimit = 150000n;
|
|
60
|
+
try {
|
|
61
|
+
const estimated = await provider.estimateGas({ ...unsigned, from: wallets[i].address });
|
|
62
|
+
if (estimated > 0n)
|
|
63
|
+
gasLimit = estimated;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* 估算失败用 15 万 */
|
|
67
|
+
}
|
|
59
68
|
signed.push(await wallets[i].signTransaction({
|
|
60
69
|
...unsigned,
|
|
61
70
|
from: wallets[i].address,
|
|
62
71
|
nonce: await nonceManager.getNextNonce(wallets[i]),
|
|
63
|
-
gasLimit
|
|
72
|
+
gasLimit,
|
|
64
73
|
gasPrice,
|
|
65
74
|
chainId,
|
|
66
75
|
type: txType,
|
|
@@ -74,6 +74,21 @@ export async function fourBundleSwapMerkle(params) {
|
|
|
74
74
|
]);
|
|
75
75
|
const { sellerNonces, buyerNonce } = noncesResult;
|
|
76
76
|
const noncePlan = planSellerNonces(sellerNonces, needBribeTx, needApproval, extractProfit);
|
|
77
|
+
let sellGasLimit = finalGasLimit;
|
|
78
|
+
let buyGasLimit = finalGasLimit;
|
|
79
|
+
try {
|
|
80
|
+
const [sellGas, buyGas] = await Promise.all([
|
|
81
|
+
provider.estimateGas({ ...sellUnsigned, from: seller.address }),
|
|
82
|
+
provider.estimateGas({ ...buyUnsigned, from: buyer.address, value: buyerFunds }),
|
|
83
|
+
]);
|
|
84
|
+
if (sellGas > 0n)
|
|
85
|
+
sellGasLimit = sellGas;
|
|
86
|
+
if (buyGas > 0n)
|
|
87
|
+
buyGasLimit = buyGas;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* 估算失败继续用配置上限 */
|
|
91
|
+
}
|
|
77
92
|
const signPromises = [];
|
|
78
93
|
if (needBribeTx && noncePlan.bribeNonce !== undefined) {
|
|
79
94
|
signPromises.push(signBribeTransaction(seller, bribeAmount, noncePlan.bribeNonce, gasPrice, chainIdNum, txType));
|
|
@@ -85,7 +100,7 @@ export async function fourBundleSwapMerkle(params) {
|
|
|
85
100
|
...sellUnsigned,
|
|
86
101
|
from: seller.address,
|
|
87
102
|
nonce: noncePlan.sellerNonce,
|
|
88
|
-
gasLimit:
|
|
103
|
+
gasLimit: sellGasLimit,
|
|
89
104
|
gasPrice,
|
|
90
105
|
chainId: chainIdNum,
|
|
91
106
|
type: txType,
|
|
@@ -93,7 +108,7 @@ export async function fourBundleSwapMerkle(params) {
|
|
|
93
108
|
...buyUnsigned,
|
|
94
109
|
from: buyer.address,
|
|
95
110
|
nonce: buyerNonce,
|
|
96
|
-
gasLimit:
|
|
111
|
+
gasLimit: buyGasLimit,
|
|
97
112
|
gasPrice,
|
|
98
113
|
chainId: chainIdNum,
|
|
99
114
|
type: txType,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ethers, Contract } from 'ethers';
|
|
2
2
|
import { getGasLimit } from '../../utils/bundle-helpers.js';
|
|
3
|
+
import { estimateApproveGasLimit } from '../../core/gas/bundle-gas.js';
|
|
3
4
|
import { ADDRESSES, CHAINS, getMulticall3Address } from '../../core/constants/index.js';
|
|
4
5
|
import { MULTICALL3_ABI } from '../../abis/common.js';
|
|
5
6
|
import { batchGetBalances } from '../internal.js';
|
|
@@ -85,12 +86,16 @@ export function planSellerNonces(sellerNonces, needBribeTx, needApproval, extrac
|
|
|
85
86
|
}
|
|
86
87
|
export async function signTmMaxApprovalTx(seller, tokenAddress, approvalNonce, gasPrice, chainIdNum, txType) {
|
|
87
88
|
const approveData = APPROVE_INTERFACE.encodeFunctionData('approve', [TM_ADDRESS, ethers.MaxUint256]);
|
|
89
|
+
const linked = seller.provider;
|
|
90
|
+
const gasLimit = linked
|
|
91
|
+
? await estimateApproveGasLimit(linked, seller.address, tokenAddress, approveData)
|
|
92
|
+
: 150000n;
|
|
88
93
|
return seller.signTransaction({
|
|
89
94
|
to: tokenAddress,
|
|
90
95
|
data: approveData,
|
|
91
96
|
value: 0n,
|
|
92
97
|
nonce: approvalNonce,
|
|
93
|
-
gasLimit
|
|
98
|
+
gasLimit,
|
|
94
99
|
gasPrice,
|
|
95
100
|
chainId: chainIdNum,
|
|
96
101
|
type: txType,
|
|
@@ -2,7 +2,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
2
2
|
import { getOptimizedGasPrice, NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../utils/bundle-helpers.js';
|
|
3
3
|
import { calculateTransferFee, floorErc20TransferGasLimit, GAS_LIMITS, isBscChain } from '../../core/constants/index.js';
|
|
4
4
|
import { getGasPriceConfig, shouldExtractProfit, getProfitRecipient, getBribeAmount } from '../config-helpers.js';
|
|
5
|
-
import { getErc20Decimals as _getErc20Decimals, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress, } from '../internal.js';
|
|
5
|
+
import { getErc20Decimals as _getErc20Decimals, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, calculateGasLimit as _calculateGasLimit, estimateSweepLimits, isNativeTokenAddress as _isNativeTokenAddress, } from '../internal.js';
|
|
6
6
|
import { normalizeItemsOrRecipientsAmounts, resolveHopCountInput, resolveProvidedHops, resolveSignContext, signBribeTransaction, } from '../../core/sign-context-helpers.js';
|
|
7
7
|
// ==================== 钱包转账按地址收费 ====================
|
|
8
8
|
/**
|
|
@@ -29,8 +29,24 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
29
29
|
const preparedHops = providedHops ?? _generateHopWallets(recipients.length, hopCountInput);
|
|
30
30
|
const hasHops = preparedHops !== null;
|
|
31
31
|
const maxHopCount = hasHops ? Math.max(...preparedHops.map((h) => h.length)) : 0;
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
let finalGasLimit = _calculateGasLimit(config, isNative, hasHops, maxHopCount);
|
|
33
|
+
let nativeGasLimit = (config.prefer21000ForNative ?? false) ? 21000n : finalGasLimit;
|
|
34
|
+
let gasEstimated = false;
|
|
35
|
+
if (recipients[0]) {
|
|
36
|
+
const limits = await estimateSweepLimits({
|
|
37
|
+
provider,
|
|
38
|
+
from: mainWallet.address,
|
|
39
|
+
to: recipients[0],
|
|
40
|
+
tokenAddress: isNative ? undefined : tokenAddress,
|
|
41
|
+
nativeFallback: nativeGasLimit,
|
|
42
|
+
tokenFallback: finalGasLimit,
|
|
43
|
+
});
|
|
44
|
+
if (limits.estimated) {
|
|
45
|
+
gasEstimated = true;
|
|
46
|
+
nativeGasLimit = limits.nativeGasLimit;
|
|
47
|
+
finalGasLimit = isNative ? limits.nativeGasLimit : limits.tokenGasLimit;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
34
50
|
const signedTxs = [];
|
|
35
51
|
const extractProfit = shouldExtractProfit();
|
|
36
52
|
const profitAddr = getProfitRecipient();
|
|
@@ -137,8 +153,8 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
137
153
|
// ✅ Gas limit 设置
|
|
138
154
|
// - 原生代币转账:贿赂档
|
|
139
155
|
// - ERC20 转账:走 GAS_LIMITS(含股票币代理)
|
|
140
|
-
const nativeTransferGasLimit = GAS_LIMITS.BRIBE;
|
|
141
|
-
const erc20TransferGasLimit = floorErc20TransferGasLimit(finalGasLimit);
|
|
156
|
+
const nativeTransferGasLimit = gasEstimated ? nativeGasLimit : GAS_LIMITS.BRIBE;
|
|
157
|
+
const erc20TransferGasLimit = gasEstimated ? finalGasLimit : floorErc20TransferGasLimit(finalGasLimit);
|
|
142
158
|
// ✅ 原生代币多跳的 gas 费(每跳只需要 21000)
|
|
143
159
|
const nativeHopGasFee = nativeTransferGasLimit * gasPrice;
|
|
144
160
|
// ✅ ERC20 多跳时,中转钱包需要执行 2 笔交易(转 gas + 转 ERC20)
|
|
@@ -28,6 +28,8 @@ export interface SweepBranchContext {
|
|
|
28
28
|
amount: SweepSignParams['amount'];
|
|
29
29
|
finalGasLimit: bigint;
|
|
30
30
|
nativeGasLimit: bigint;
|
|
31
|
+
/** 节点估算成功时不再把 ERC20 gas 抬到 12 万 */
|
|
32
|
+
gasEstimated?: boolean;
|
|
31
33
|
/** clampRatio(ratioPct) 的结果 */
|
|
32
34
|
ratio: number | undefined;
|
|
33
35
|
/** 共享 NonceManager(用于统一协调 贿赂/利润提取 等 targetWallet 交易的 nonce,避免 nonce 冲突) */
|
|
@@ -8,7 +8,7 @@ import { calculateTransferFee, floorErc20TransferGasLimit } from '../../../core/
|
|
|
8
8
|
import { batchGetBalances as _batchGetBalances, batchGetTokenAndNativeBalances, getErc20Decimals as _getErc20Decimals, } from '../../internal.js';
|
|
9
9
|
import { clampRatio } from './context.js';
|
|
10
10
|
export async function sweepErc20NoHop(ctx) {
|
|
11
|
-
const { provider, chainIdNum, txType, config, target, targetWallet, tokenAddress, tokenDecimals, skipIfInsufficient, extractProfit, profitAddr, actualKeys, sources, ratios, amounts, amount, finalGasLimit, ratio, } = ctx;
|
|
11
|
+
const { provider, chainIdNum, txType, config, target, targetWallet, tokenAddress, tokenDecimals, skipIfInsufficient, extractProfit, profitAddr, actualKeys, sources, ratios, amounts, amount, finalGasLimit, nativeGasLimit, gasEstimated, ratio, } = ctx;
|
|
12
12
|
const signedTxs = [];
|
|
13
13
|
let totalProfit = 0n;
|
|
14
14
|
let totalAmountBeforeProfit = 0n;
|
|
@@ -37,9 +37,8 @@ export async function sweepErc20NoHop(ctx) {
|
|
|
37
37
|
? (paired.native[targetIndexInSources] ?? 0n)
|
|
38
38
|
: (paired.native[addresses.length] ?? 0n);
|
|
39
39
|
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const PROFIT_HOP_GAS_LIMIT_ERC20 = 21055n;
|
|
40
|
+
const transferGasLimit = gasEstimated ? finalGasLimit : floorErc20TransferGasLimit(finalGasLimit);
|
|
41
|
+
const PROFIT_HOP_GAS_LIMIT_ERC20 = nativeGasLimit;
|
|
43
42
|
const profitTxGas = PROFIT_HOP_GAS_LIMIT_ERC20 * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
44
43
|
// ✅ 第一步:计算所有钱包的归集金额和利润,找出归集金额最大的钱包
|
|
45
44
|
const sweepAmounts = [];
|
|
@@ -73,7 +72,7 @@ export async function sweepErc20NoHop(ctx) {
|
|
|
73
72
|
continue;
|
|
74
73
|
}
|
|
75
74
|
// ✅ 检查 BNB 余额(只需主转账的 gas,支付者后面单独检查)
|
|
76
|
-
const totalGasNeeded =
|
|
75
|
+
const totalGasNeeded = transferGasLimit * gasPrice;
|
|
77
76
|
if (config.checkBnbForErc20NoHop) {
|
|
78
77
|
const bnb = bnbBalances[i] ?? 0n;
|
|
79
78
|
if (skipIfInsufficient && bnb < totalGasNeeded) {
|
|
@@ -106,7 +105,7 @@ export async function sweepErc20NoHop(ctx) {
|
|
|
106
105
|
// ✅ 如果需要提取费用,检查支付者是否有足够原生代币支付
|
|
107
106
|
if (extractProfit && nativeProfitAmount > 0n && profitPayerWalletErc20 && config.checkBnbForErc20NoHop) {
|
|
108
107
|
const payerBnbBalance = useTargetAsPayerErc20 ? targetNativeBalance : (bnbBalances[maxSweepIndex] ?? 0n);
|
|
109
|
-
const payerNeedGas = (useTargetAsPayerErc20 ? 0n :
|
|
108
|
+
const payerNeedGas = (useTargetAsPayerErc20 ? 0n : transferGasLimit * gasPrice) + profitTxGas + nativeProfitAmount;
|
|
110
109
|
// 如果支付者原生代币余额不足,跳过费用提取
|
|
111
110
|
if (payerBnbBalance < payerNeedGas) {
|
|
112
111
|
nativeProfitAmount = 0n;
|
|
@@ -145,7 +144,7 @@ export async function sweepErc20NoHop(ctx) {
|
|
|
145
144
|
value: 0n,
|
|
146
145
|
nonce,
|
|
147
146
|
gasPrice,
|
|
148
|
-
gasLimit:
|
|
147
|
+
gasLimit: transferGasLimit,
|
|
149
148
|
chainId: BigInt(chainIdNum),
|
|
150
149
|
type: txType,
|
|
151
150
|
});
|
|
@@ -8,7 +8,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
8
8
|
import { getOptimizedGasPrice } from '../../../utils/bundle-helpers.js';
|
|
9
9
|
import { CHAINS, isBscChain } from '../../../core/constants/index.js';
|
|
10
10
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient, getBribeAmount } from '../../config-helpers.js';
|
|
11
|
-
import { generateHopWallets as _generateHopWallets, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress, } from '../../internal.js';
|
|
11
|
+
import { generateHopWallets as _generateHopWallets, calculateGasLimit as _calculateGasLimit, estimateSweepLimits, isNativeTokenAddress as _isNativeTokenAddress, } from '../../internal.js';
|
|
12
12
|
import { createSignProvider, resolveHopCountInput, resolveProvidedHops, signBribeTransaction } from '../../../core/sign-context-helpers.js';
|
|
13
13
|
import { NonceManager } from '../../../core/nonce/nonce-manager.js';
|
|
14
14
|
import { clampRatio } from './context.js';
|
|
@@ -46,9 +46,25 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
46
46
|
const preparedHops = providedHops ?? _generateHopWallets(actualKeys.length, hopCountInput);
|
|
47
47
|
const hasHops = preparedHops !== null;
|
|
48
48
|
const maxHopCount = hasHops ? Math.max(...preparedHops.map((h) => h.length)) : 0;
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// 先按配置算上限,再向节点估算。估算成功就用估算值,归集才不会把没用完的 gas 退回钱包。
|
|
50
|
+
let finalGasLimit = _calculateGasLimit(config, isNative, hasHops, maxHopCount);
|
|
51
|
+
let nativeGasLimit = (config.prefer21000ForNative ?? false) ? 21000n : finalGasLimit;
|
|
52
|
+
let gasEstimated = false;
|
|
53
|
+
if (actualKeys[0]) {
|
|
54
|
+
const limits = await estimateSweepLimits({
|
|
55
|
+
provider,
|
|
56
|
+
from: new Wallet(actualKeys[0]).address,
|
|
57
|
+
to: target,
|
|
58
|
+
tokenAddress: isNative ? undefined : tokenAddress,
|
|
59
|
+
nativeFallback: nativeGasLimit,
|
|
60
|
+
tokenFallback: finalGasLimit,
|
|
61
|
+
});
|
|
62
|
+
if (limits.estimated) {
|
|
63
|
+
gasEstimated = true;
|
|
64
|
+
nativeGasLimit = limits.nativeGasLimit;
|
|
65
|
+
finalGasLimit = isNative ? limits.nativeGasLimit : limits.tokenGasLimit;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
52
68
|
// ✅ 利润提取配置
|
|
53
69
|
const extractProfit = shouldExtractProfit();
|
|
54
70
|
const profitAddr = getProfitRecipient();
|
|
@@ -86,6 +102,7 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
86
102
|
amount,
|
|
87
103
|
finalGasLimit,
|
|
88
104
|
nativeGasLimit,
|
|
105
|
+
gasEstimated,
|
|
89
106
|
ratio,
|
|
90
107
|
nonceManager,
|
|
91
108
|
};
|
|
@@ -23,9 +23,8 @@ export async function sweepNativeNoHop(ctx) {
|
|
|
23
23
|
_batchGetBalances(provider, addresses),
|
|
24
24
|
]);
|
|
25
25
|
const gasCostBase = nativeGasLimit * gasPrice;
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
const PROFIT_HOP_GAS_LIMIT = 21055n;
|
|
26
|
+
// 利润多跳也是原生转账,跟归集用同一个估算值
|
|
27
|
+
const PROFIT_HOP_GAS_LIMIT = nativeGasLimit;
|
|
29
28
|
const profitTxGas = PROFIT_HOP_GAS_LIMIT * gasPrice * BigInt(PROFIT_HOP_COUNT + 1);
|
|
30
29
|
// ✅ 第一步:计算所有钱包的归集金额和利润,找出归集金额最大的钱包
|
|
31
30
|
const sweepAmounts = [];
|
|
@@ -8,7 +8,7 @@ import { calculateTransferFee, floorErc20TransferGasLimit, GAS_LIMITS } from '..
|
|
|
8
8
|
import { batchGetBalances as _batchGetBalances, batchGetTokenAndNativeBalances, getErc20Decimals as _getErc20Decimals, } from '../../internal.js';
|
|
9
9
|
import { clampRatio } from './context.js';
|
|
10
10
|
export async function sweepWithHops(ctx, preparedHops) {
|
|
11
|
-
const { provider, chainIdNum, txType, config, target, targetWallet, tokenAddress, tokenDecimals, skipIfInsufficient, isNative, extractProfit, profitAddr, actualKeys, sources, ratios, amounts, amount, finalGasLimit, nativeGasLimit, ratio, } = ctx;
|
|
11
|
+
const { provider, chainIdNum, txType, config, target, targetWallet, tokenAddress, tokenDecimals, skipIfInsufficient, isNative, extractProfit, profitAddr, actualKeys, sources, ratios, amounts, amount, finalGasLimit, nativeGasLimit, gasEstimated, ratio, } = ctx;
|
|
12
12
|
const signedTxs = [];
|
|
13
13
|
let totalProfit = 0n;
|
|
14
14
|
let totalAmountBeforeProfit = 0n;
|
|
@@ -43,8 +43,8 @@ export async function sweepWithHops(ctx, preparedHops) {
|
|
|
43
43
|
const bnbBalances = paired.native;
|
|
44
44
|
const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
45
45
|
// ✅ Gas limit 设置(与分散函数保持一致)
|
|
46
|
-
const nativeTransferGasLimit = GAS_LIMITS.BRIBE;
|
|
47
|
-
const erc20TransferGasLimit = floorErc20TransferGasLimit(finalGasLimit);
|
|
46
|
+
const nativeTransferGasLimit = gasEstimated ? nativeGasLimit : GAS_LIMITS.BRIBE;
|
|
47
|
+
const erc20TransferGasLimit = gasEstimated ? finalGasLimit : floorErc20TransferGasLimit(finalGasLimit);
|
|
48
48
|
// ✅ 原生代币多跳的 gas 费(每跳只需要 21000)
|
|
49
49
|
const nativeHopGasFee = nativeTransferGasLimit * gasPrice;
|
|
50
50
|
// ✅ ERC20 多跳时,中转钱包需要执行 2 笔交易(转 gas + 转 ERC20)
|
|
@@ -14,6 +14,19 @@ export declare function batchGetTokenAndNativeBalances(provider: JsonRpcProvider
|
|
|
14
14
|
token: bigint[];
|
|
15
15
|
native: bigint[];
|
|
16
16
|
}>;
|
|
17
|
+
/** 归集/分发:按节点估算这笔转账的 gas。失败返回 null,调用方再用固定上限。 */
|
|
18
|
+
export declare function estimateSweepLimits(params: {
|
|
19
|
+
provider: JsonRpcProvider;
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
tokenAddress?: string;
|
|
23
|
+
nativeFallback: bigint;
|
|
24
|
+
tokenFallback: bigint;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
nativeGasLimit: bigint;
|
|
27
|
+
tokenGasLimit: bigint;
|
|
28
|
+
estimated: boolean;
|
|
29
|
+
}>;
|
|
17
30
|
export declare function estimateErc20TransferGas(provider: JsonRpcProvider, tokenAddress: string, from: string, to: string, amount: bigint, bufferPercent?: number): Promise<bigint>;
|
|
18
31
|
export declare function estimateMaxErc20TransferGas(provider: JsonRpcProvider, tokenAddress: string, from: string, recipients: string[], amounts: bigint[], bufferPercent?: number): Promise<bigint>;
|
|
19
32
|
export declare function calculateGasLimit(config: BundleGasConfig, isNative: boolean, _hasHops: boolean, _hopCount?: number): bigint;
|
package/dist/bundle/internal.js
CHANGED
|
@@ -129,6 +129,48 @@ export async function batchGetTokenAndNativeBalances(provider, addresses, tokenA
|
|
|
129
129
|
return { token, native };
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
+
/** 归集/分发:按节点估算这笔转账的 gas。失败返回 null,调用方再用固定上限。 */
|
|
133
|
+
export async function estimateSweepLimits(params) {
|
|
134
|
+
try {
|
|
135
|
+
if (!params.tokenAddress) {
|
|
136
|
+
const gas = await params.provider.estimateGas({
|
|
137
|
+
from: params.from,
|
|
138
|
+
to: params.to,
|
|
139
|
+
value: 0n,
|
|
140
|
+
});
|
|
141
|
+
if (gas > 0n)
|
|
142
|
+
return { nativeGasLimit: gas, tokenGasLimit: gas, estimated: true };
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
146
|
+
const data = iface.encodeFunctionData('transfer', [params.to, 1n]);
|
|
147
|
+
const [tokenGas, nativeGas] = await Promise.all([
|
|
148
|
+
params.provider.estimateGas({
|
|
149
|
+
from: params.from,
|
|
150
|
+
to: params.tokenAddress,
|
|
151
|
+
data,
|
|
152
|
+
value: 0n,
|
|
153
|
+
}),
|
|
154
|
+
params.provider.estimateGas({ from: params.from, to: params.to, value: 0n }).catch(() => 0n),
|
|
155
|
+
]);
|
|
156
|
+
if (tokenGas > 0n) {
|
|
157
|
+
return {
|
|
158
|
+
nativeGasLimit: nativeGas > 0n ? nativeGas : params.nativeFallback,
|
|
159
|
+
tokenGasLimit: tokenGas,
|
|
160
|
+
estimated: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* 估算失败就用原来的固定上限 */
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
nativeGasLimit: params.nativeFallback,
|
|
170
|
+
tokenGasLimit: params.tokenFallback,
|
|
171
|
+
estimated: false,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
132
174
|
export async function estimateErc20TransferGas(provider, tokenAddress, from, to, amount, bufferPercent = 5) {
|
|
133
175
|
try {
|
|
134
176
|
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
@@ -9,6 +9,7 @@ import { Wallet, Interface } from 'ethers';
|
|
|
9
9
|
import { ENI_CHAIN_ID, ENI_RPC_URL } from '../constants.js';
|
|
10
10
|
import { createEniProvider } from '../../../utils/provider-factory.js';
|
|
11
11
|
import { ERC20_ABI } from '../../../abis/common.js';
|
|
12
|
+
import { estimateApproveGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
12
13
|
const MAX_UINT256 = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
|
|
13
14
|
const erc20Iface = new Interface(ERC20_ABI);
|
|
14
15
|
/**
|
|
@@ -33,7 +34,7 @@ export async function batchApprove(params) {
|
|
|
33
34
|
type: 2,
|
|
34
35
|
maxFeePerGas,
|
|
35
36
|
maxPriorityFeePerGas,
|
|
36
|
-
gasLimit:
|
|
37
|
+
gasLimit: await estimateApproveGasLimit(provider, wallet.address, token, calldata),
|
|
37
38
|
});
|
|
38
39
|
return { signedTx, from: wallet.address, nonce };
|
|
39
40
|
}));
|
|
@@ -14,6 +14,7 @@ import { ERC20_ABI } from '../../../abis/common.js';
|
|
|
14
14
|
import { getProfitRecipient, calculateTransferFee } from '../../../core/constants/profit.js';
|
|
15
15
|
import { GAS_LIMITS } from '../../../core/constants/gas.js';
|
|
16
16
|
import { erc20Iface, DEFAULT_GAS_PER_SWEEP, GAS_NATIVE_TRANSFER } from './transfer-shared.js';
|
|
17
|
+
import { estimateApproveGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
17
18
|
// ============================================================================
|
|
18
19
|
// 批量分发 EGAS (通过 BatchRouter 合约, 1 笔 tx)
|
|
19
20
|
// ============================================================================
|
|
@@ -118,7 +119,7 @@ export async function eniDisperseForSubmit(params) {
|
|
|
118
119
|
type: 2,
|
|
119
120
|
maxFeePerGas,
|
|
120
121
|
maxPriorityFeePerGas,
|
|
121
|
-
gasLimit:
|
|
122
|
+
gasLimit: await estimateApproveGasLimit(provider, mainWallet.address, tokenAddress, erc20Iface.encodeFunctionData('approve', [routerAddr, ethers.MaxUint256])),
|
|
122
123
|
});
|
|
123
124
|
signedTxs.push(approveTx);
|
|
124
125
|
}
|
|
@@ -47,7 +47,6 @@ export async function batchSweepEgas(params) {
|
|
|
47
47
|
const provider = createEniProvider(rpcUrl, ENI_CHAIN_ID);
|
|
48
48
|
const feeData = await provider.getFeeData();
|
|
49
49
|
const maxFeePerGas = gasPrice ?? feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
|
|
50
|
-
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
|
|
51
50
|
const transactions = await Promise.all(privateKeys.map(async (key, i) => {
|
|
52
51
|
const wallet = new Wallet(key, provider);
|
|
53
52
|
const nonce = await wallet.getNonce();
|
|
@@ -58,7 +57,7 @@ export async function batchSweepEgas(params) {
|
|
|
58
57
|
chainId: ENI_CHAIN_ID,
|
|
59
58
|
type: 2,
|
|
60
59
|
maxFeePerGas,
|
|
61
|
-
maxPriorityFeePerGas,
|
|
60
|
+
maxPriorityFeePerGas: maxFeePerGas,
|
|
62
61
|
gasLimit: GAS_NATIVE_TRANSFER,
|
|
63
62
|
});
|
|
64
63
|
return { signedTx, from: wallet.address, nonce };
|
|
@@ -69,8 +68,20 @@ export async function eniSweepForSubmit(params) {
|
|
|
69
68
|
const { rpcUrl = ENI_RPC_URL, sourcePrivateKeys, target, targetPrivateKey, tokenAddress, hopCount = 0 } = params;
|
|
70
69
|
const isNative = !tokenAddress || tokenAddress === ethers.ZeroAddress;
|
|
71
70
|
const ratioPct = params.ratioPct ?? 100;
|
|
72
|
-
const erc20GasLimit = params.gasLimit ? BigInt(params.gasLimit) : DEFAULT_GAS_PER_SWEEP;
|
|
73
71
|
const provider = createEniProvider(rpcUrl, ENI_CHAIN_ID);
|
|
72
|
+
let erc20GasLimit = params.gasLimit ? BigInt(params.gasLimit) : DEFAULT_GAS_PER_SWEEP;
|
|
73
|
+
if (!isNative && tokenAddress && sourcePrivateKeys[0]) {
|
|
74
|
+
try {
|
|
75
|
+
const from = new Wallet(sourcePrivateKeys[0]).address;
|
|
76
|
+
const data = erc20Iface.encodeFunctionData('transfer', [target, 1n]);
|
|
77
|
+
const estimated = await provider.estimateGas({ from, to: tokenAddress, data });
|
|
78
|
+
if (estimated > 0n)
|
|
79
|
+
erc20GasLimit = estimated;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* 估算失败再用表单或 20 万上限 */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
74
85
|
const feeData = await provider.getFeeData();
|
|
75
86
|
const maxFeePerGas = feeData.maxFeePerGas ?? feeData.gasPrice ?? 1000000000n;
|
|
76
87
|
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
|
|
@@ -173,7 +173,7 @@ export async function addLiquidityETHForSubmit(params) {
|
|
|
173
173
|
type: 2,
|
|
174
174
|
maxFeePerGas,
|
|
175
175
|
maxPriorityFeePerGas,
|
|
176
|
-
gasLimit:
|
|
176
|
+
gasLimit: 150000n,
|
|
177
177
|
});
|
|
178
178
|
signedTxs.push(approveTx);
|
|
179
179
|
}
|
|
@@ -280,7 +280,7 @@ export async function removeLiquidityETHForSubmit(params) {
|
|
|
280
280
|
type: 2,
|
|
281
281
|
maxFeePerGas,
|
|
282
282
|
maxPriorityFeePerGas,
|
|
283
|
-
gasLimit:
|
|
283
|
+
gasLimit: 150000n,
|
|
284
284
|
});
|
|
285
285
|
signedTxs.push(approveTx);
|
|
286
286
|
}
|
|
@@ -364,7 +364,7 @@ export async function addLiquidityForSubmit(params) {
|
|
|
364
364
|
type: 2,
|
|
365
365
|
maxFeePerGas,
|
|
366
366
|
maxPriorityFeePerGas,
|
|
367
|
-
gasLimit:
|
|
367
|
+
gasLimit: 150000n,
|
|
368
368
|
});
|
|
369
369
|
signedTxs.push(approveTx);
|
|
370
370
|
}
|
|
@@ -439,7 +439,7 @@ export async function removeLiquidityForSubmit(params) {
|
|
|
439
439
|
type: 2,
|
|
440
440
|
maxFeePerGas,
|
|
441
441
|
maxPriorityFeePerGas,
|
|
442
|
-
gasLimit:
|
|
442
|
+
gasLimit: 150000n,
|
|
443
443
|
});
|
|
444
444
|
signedTxs.push(approveTx);
|
|
445
445
|
}
|
|
@@ -17,6 +17,20 @@ export async function signRobinhoodTx(params) {
|
|
|
17
17
|
if (maxFeePerGas < floor)
|
|
18
18
|
maxFeePerGas = floor;
|
|
19
19
|
const maxPriorityFeePerGas = priority > maxFeePerGas ? maxFeePerGas : priority;
|
|
20
|
+
let gasLimit = params.gasLimit;
|
|
21
|
+
try {
|
|
22
|
+
const estimated = await provider.estimateGas({
|
|
23
|
+
from: wallet.address,
|
|
24
|
+
to: params.to,
|
|
25
|
+
data: params.data,
|
|
26
|
+
value: params.value ?? 0n,
|
|
27
|
+
});
|
|
28
|
+
if (estimated > 0n)
|
|
29
|
+
gasLimit = estimated;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
/* 节点没给出上限时用服务端配置 */
|
|
33
|
+
}
|
|
20
34
|
const signedTx = await wallet.signTransaction({
|
|
21
35
|
to: params.to,
|
|
22
36
|
value: params.value ?? 0n,
|
|
@@ -26,7 +40,7 @@ export async function signRobinhoodTx(params) {
|
|
|
26
40
|
type: 2,
|
|
27
41
|
maxFeePerGas,
|
|
28
42
|
maxPriorityFeePerGas,
|
|
29
|
-
gasLimit
|
|
43
|
+
gasLimit,
|
|
30
44
|
});
|
|
31
45
|
const tx = Transaction.from(signedTx);
|
|
32
46
|
return {
|
|
@@ -52,3 +52,11 @@ export type Eip1559GasFields = {
|
|
|
52
52
|
};
|
|
53
53
|
export type TxGasFields = LegacyGasFields | Eip1559GasFields;
|
|
54
54
|
export declare function buildGasFields(txType: number, gasPrice: bigint): TxGasFields;
|
|
55
|
+
/** 向节点 eth_estimateGas 要这笔 approve 的 gas 上限。节点没返回时才用 fallback。 */
|
|
56
|
+
export declare function estimateApproveGasLimit(provider: {
|
|
57
|
+
estimateGas: (tx: {
|
|
58
|
+
from?: string;
|
|
59
|
+
to?: string;
|
|
60
|
+
data?: string;
|
|
61
|
+
}) => Promise<bigint>;
|
|
62
|
+
}, from: string, to: string, data: string, fallback?: bigint): Promise<bigint>;
|
|
@@ -23,7 +23,7 @@ export async function getOptimizedGasPrice(provider, config) {
|
|
|
23
23
|
return gasPrice;
|
|
24
24
|
}
|
|
25
25
|
export async function estimateGasWithSafety(provider, txRequest, config) {
|
|
26
|
-
const multiplier = config?.multiplier ?? 1
|
|
26
|
+
const multiplier = config?.multiplier ?? 1;
|
|
27
27
|
const fallback = config?.fallbackGasLimit ?? 600000n;
|
|
28
28
|
const allowFailure = config?.allowFailure ?? true;
|
|
29
29
|
try {
|
|
@@ -91,3 +91,15 @@ export function buildGasFields(txType, gasPrice) {
|
|
|
91
91
|
}
|
|
92
92
|
return { type: 0, gasPrice };
|
|
93
93
|
}
|
|
94
|
+
/** 向节点 eth_estimateGas 要这笔 approve 的 gas 上限。节点没返回时才用 fallback。 */
|
|
95
|
+
export async function estimateApproveGasLimit(provider, from, to, data, fallback = 150000n) {
|
|
96
|
+
try {
|
|
97
|
+
const estimated = await provider.estimateGas({ from, to, data });
|
|
98
|
+
if (estimated > 0n)
|
|
99
|
+
return estimated;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* 节点没给出上限 */
|
|
103
|
+
}
|
|
104
|
+
return fallback;
|
|
105
|
+
}
|
|
@@ -3,16 +3,62 @@
|
|
|
3
3
|
* 模块加载时 patch Wallet.prototype.signTransaction(全局一次)
|
|
4
4
|
*/
|
|
5
5
|
import { Wallet } from 'ethers';
|
|
6
|
+
function chainGasPrice(fee) {
|
|
7
|
+
const candidates = [fee.maxFeePerGas, fee.gasPrice].filter((v) => v != null && v > 0n);
|
|
8
|
+
if (candidates.length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
return candidates.reduce((a, b) => (a > b ? a : b));
|
|
11
|
+
}
|
|
6
12
|
const _origWalletSign = Wallet.prototype.signTransaction;
|
|
7
13
|
Wallet.prototype.signTransaction = async function (tx) {
|
|
8
|
-
|
|
14
|
+
let signable = tx;
|
|
9
15
|
if (signable.type === 2 && signable.gasPrice != null && signable.maxFeePerGas == null) {
|
|
10
16
|
const { gasPrice: gp, ...rest } = signable;
|
|
11
|
-
|
|
17
|
+
signable = {
|
|
12
18
|
...rest,
|
|
13
19
|
maxFeePerGas: gp,
|
|
14
20
|
maxPriorityFeePerGas: rest.maxPriorityFeePerGas ?? 0n,
|
|
15
|
-
}
|
|
21
|
+
};
|
|
16
22
|
}
|
|
17
|
-
|
|
23
|
+
const provider = this.provider;
|
|
24
|
+
if (provider) {
|
|
25
|
+
const hasPrice = (signable.gasPrice != null && signable.gasPrice > 0n)
|
|
26
|
+
|| (signable.maxFeePerGas != null && signable.maxFeePerGas > 0n);
|
|
27
|
+
if (!hasPrice) {
|
|
28
|
+
try {
|
|
29
|
+
const fee = await provider.getFeeData();
|
|
30
|
+
const chainPrice = chainGasPrice(fee);
|
|
31
|
+
if (chainPrice) {
|
|
32
|
+
if (signable.type === 2) {
|
|
33
|
+
let priority = fee.maxPriorityFeePerGas && fee.maxPriorityFeePerGas > 0n
|
|
34
|
+
? fee.maxPriorityFeePerGas
|
|
35
|
+
: chainPrice / 10n || 1n;
|
|
36
|
+
if (priority > chainPrice)
|
|
37
|
+
priority = chainPrice;
|
|
38
|
+
signable = { ...signable, maxFeePerGas: chainPrice, maxPriorityFeePerGas: priority };
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
signable = { ...signable, gasPrice: chainPrice };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
/* 节点没给出 gas 价格时保留交易上已有的出价 */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const estimated = await provider.estimateGas({
|
|
51
|
+
from: signable.from ?? this.address,
|
|
52
|
+
to: signable.to,
|
|
53
|
+
data: signable.data,
|
|
54
|
+
value: signable.value,
|
|
55
|
+
});
|
|
56
|
+
if (estimated > 0n)
|
|
57
|
+
signable = { ...signable, gasLimit: estimated };
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* 节点没给出 gas 上限时保留原来的上限,避免发不出去 */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return _origWalletSign.call(this, signable);
|
|
18
64
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 从 direct-router-v2.ts 抽取,direct-router-v2.ts 通过 re-export 保持对外不变。
|
|
4
4
|
*/
|
|
5
5
|
import { ethers, Wallet, Contract } from 'ethers';
|
|
6
|
-
import { getGasLimit } from '../../core/gas/bundle-gas.js';
|
|
6
|
+
import { estimateApproveGasLimit, getGasLimit } from '../../core/gas/bundle-gas.js';
|
|
7
7
|
import { NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT, buildGasFields, } from '../../utils/bundle-helpers.js';
|
|
8
8
|
import { getProfitRecipient, CHAINS, GAS_LIMITS } from '../../core/constants/index.js';
|
|
9
9
|
import { ERC20_ABI } from '../../abis/common.js';
|
|
@@ -88,12 +88,13 @@ export async function directV2BatchBuy(params) {
|
|
|
88
88
|
continue;
|
|
89
89
|
if (activeBuyAllowances[i] < activeFlowAmounts[i]) {
|
|
90
90
|
const approveData = quoteTokenContract.interface.encodeFunctionData('approve', [router, ethers.MaxUint256]);
|
|
91
|
+
const approveGasLimit = await estimateApproveGasLimit(provider, activeWallets[i].address, inputToken, approveData);
|
|
91
92
|
const approveTx = await activeWallets[i].signTransaction({
|
|
92
93
|
to: inputToken,
|
|
93
94
|
data: approveData,
|
|
94
95
|
value: 0n,
|
|
95
96
|
nonce: activeNonces[i],
|
|
96
|
-
gasLimit:
|
|
97
|
+
gasLimit: approveGasLimit,
|
|
97
98
|
...buildGasFields(txType, gasPrice),
|
|
98
99
|
chainId,
|
|
99
100
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 从 direct-router-v2.ts 抽取,direct-router-v2.ts 通过 re-export 保持对外不变。
|
|
4
4
|
*/
|
|
5
5
|
import { ethers, Interface, Wallet, Contract } from 'ethers';
|
|
6
|
-
import { getGasLimit } from '../../core/gas/bundle-gas.js';
|
|
6
|
+
import { estimateApproveGasLimit, getGasLimit } from '../../core/gas/bundle-gas.js';
|
|
7
7
|
import { NonceManager, buildProfitHopTransactions, PROFIT_HOP_COUNT, buildGasFields, } from '../../utils/bundle-helpers.js';
|
|
8
8
|
import { getProfitRecipient, CHAINS, GAS_LIMITS, getMulticall3Address } from '../../core/constants/index.js';
|
|
9
9
|
import { MULTICALL3_ABI } from '../../abis/common.js';
|
|
@@ -124,12 +124,13 @@ export async function directV2BatchSell(params) {
|
|
|
124
124
|
continue;
|
|
125
125
|
if (allowances[i] < sellAmountsWei[i]) {
|
|
126
126
|
const approveData = tokenContract.interface.encodeFunctionData('approve', [router, ethers.MaxUint256]);
|
|
127
|
+
const approveGasLimit = await estimateApproveGasLimit(provider, wallets[i].address, tokenIn, approveData);
|
|
127
128
|
const approveTx = await wallets[i].signTransaction({
|
|
128
129
|
to: tokenIn,
|
|
129
130
|
data: approveData,
|
|
130
131
|
value: 0n,
|
|
131
132
|
nonce: nonces[i],
|
|
132
|
-
gasLimit:
|
|
133
|
+
gasLimit: approveGasLimit,
|
|
133
134
|
...buildGasFields(txType, gasPrice),
|
|
134
135
|
chainId,
|
|
135
136
|
});
|
|
@@ -38,7 +38,7 @@ export function getErrorMessage(key, ...args) {
|
|
|
38
38
|
*/
|
|
39
39
|
export async function estimateGasWithMultiplier(provider, txRequest, multiplier) {
|
|
40
40
|
const estimated = await provider.estimateGas(txRequest);
|
|
41
|
-
const mult = multiplier ?? 1
|
|
41
|
+
const mult = multiplier ?? 1;
|
|
42
42
|
return BigInt(Math.ceil(Number(estimated) * mult));
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
@@ -5,6 +5,7 @@ import { ZERO_ADDRESS, calculateBundleSwapRoundTripProfit } from '../../../core/
|
|
|
5
5
|
import { ERC20_ALLOWANCE_ABI } from '../../../core/shared-abis/common.js';
|
|
6
6
|
import { getGasPriceConfig, getTxType, getProfitRecipient, PORTAL_ABI } from './config.js';
|
|
7
7
|
import { getErrorMessageFromUnknown } from '../../../core/index.js';
|
|
8
|
+
import { estimateApproveGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
8
9
|
import { createSignProvider } from '../../../bundle/sign-context-helpers.js';
|
|
9
10
|
import { batchGetBalances } from '../../../bundle/internal.js';
|
|
10
11
|
import { APPROVE_INTERFACE, ERC20_BALANCE_OF_ABI, getChainId, getNativeTokenName, } from './swap-helpers.js';
|
|
@@ -38,12 +39,14 @@ export async function buildApprovalTransaction({ tokenAddress, seller, provider,
|
|
|
38
39
|
const approvalNonce = await nonceManager.getNextNonce(seller);
|
|
39
40
|
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
40
41
|
const txType = getTxType(config);
|
|
42
|
+
const approveData = APPROVE_INTERFACE.encodeFunctionData('approve', [portalAddress, ethers.MaxUint256]);
|
|
43
|
+
const gasLimit = await estimateApproveGasLimit(provider, seller.address, tokenAddress, approveData);
|
|
41
44
|
return await seller.signTransaction({
|
|
42
45
|
to: tokenAddress,
|
|
43
|
-
data:
|
|
46
|
+
data: approveData,
|
|
44
47
|
value: 0n,
|
|
45
48
|
nonce: approvalNonce,
|
|
46
|
-
gasLimit
|
|
49
|
+
gasLimit,
|
|
47
50
|
gasPrice,
|
|
48
51
|
chainId,
|
|
49
52
|
type: txType,
|
|
@@ -136,11 +136,23 @@ export async function createTokenWithBundleBuy(params) {
|
|
|
136
136
|
const amount = ethers.parseUnits(preSaleStr, decimals);
|
|
137
137
|
if (allowance < amount) {
|
|
138
138
|
const approveUnsigned = await quote.approve.populateTransaction(tmAddr, amount);
|
|
139
|
+
let approveGasLimit = 150000n;
|
|
140
|
+
try {
|
|
141
|
+
const estimatedApprove = await provider.estimateGas({
|
|
142
|
+
...approveUnsigned,
|
|
143
|
+
from: devWallet.address,
|
|
144
|
+
});
|
|
145
|
+
if (estimatedApprove > 0n)
|
|
146
|
+
approveGasLimit = estimatedApprove;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
/* 估算失败用 15 万 */
|
|
150
|
+
}
|
|
139
151
|
const signedApprove = await devWallet.signTransaction({
|
|
140
152
|
...approveUnsigned,
|
|
141
153
|
from: devWallet.address,
|
|
142
154
|
nonce: await getNextNonce(devWallet),
|
|
143
|
-
gasLimit:
|
|
155
|
+
gasLimit: approveGasLimit,
|
|
144
156
|
gasPrice,
|
|
145
157
|
chainId: CHAINS.BSC.chainId,
|
|
146
158
|
type: 0,
|
|
@@ -41,7 +41,7 @@ export async function waitForBundleWithProvider(provider, firstTxSigned, _bundle
|
|
|
41
41
|
*/
|
|
42
42
|
export async function estimateGasWithMultiplier(provider, txRequest, multiplier) {
|
|
43
43
|
const estimated = await provider.estimateGas(txRequest);
|
|
44
|
-
const mult = multiplier ?? 1
|
|
44
|
+
const mult = multiplier ?? 1;
|
|
45
45
|
return BigInt(Math.ceil(Number(estimated) * mult));
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const ERC20_TRANSFER_GAS_LIMIT: 120000n;
|
|
2
2
|
/** SPCXB 等股票币 approve 实测 >50k,空钱包 eth_estimate ~55k */
|
|
3
|
-
export declare const ERC20_APPROVE_GAS_LIMIT =
|
|
3
|
+
export declare const ERC20_APPROVE_GAS_LIMIT = 150000n;
|
|
4
4
|
export declare const DEFAULT_GAS_LIMIT = 800000;
|
|
5
5
|
export declare const DEFAULT_GAS_PRICE_GWEI = 3;
|
|
6
6
|
export declare const GAS_BUFFER_MULTIPLIER = 2;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GAS_LIMITS } from '../../../core/constants/index.js';
|
|
2
2
|
export const ERC20_TRANSFER_GAS_LIMIT = GAS_LIMITS.ERC20_TRANSFER;
|
|
3
3
|
/** SPCXB 等股票币 approve 实测 >50k,空钱包 eth_estimate ~55k */
|
|
4
|
-
export const ERC20_APPROVE_GAS_LIMIT =
|
|
4
|
+
export const ERC20_APPROVE_GAS_LIMIT = 150000n;
|
|
5
5
|
export const DEFAULT_GAS_LIMIT = 800000;
|
|
6
6
|
export const DEFAULT_GAS_PRICE_GWEI = 3;
|
|
7
7
|
export const GAS_BUFFER_MULTIPLIER = 2;
|