four-flap-meme-sdk 4.3.22 → 4.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/bundle/four-meme/approve-tokenmanager.js +46 -16
- package/dist/bundle/four-meme/core/create-token-quote.d.ts +5 -1
- package/dist/bundle/four-meme/core/create-token-quote.js +29 -4
- package/dist/bundle/four-meme/core/sell-flow.js +3 -3
- package/dist/bundle/four-meme/pancake-proxy/approve.js +47 -10
- package/dist/bundle/four-meme/private/sell.js +2 -10
- package/dist/bundle/four-meme/swap/bundle.js +4 -4
- package/dist/bundle/four-meme/swap-buy-first/multi-wallet.js +25 -8
- package/dist/bundle/four-meme/swap-buy-first/single.js +30 -9
- package/dist/bundle/four-meme/swap-sign-helpers.js +56 -3
- package/dist/bundle/four-meme/utils-sweep/erc20-no-hop.js +18 -7
- package/dist/bundle/four-meme/utils-sweep/with-hops.js +10 -4
- package/dist/bundle/internal.d.ts +5 -0
- package/dist/bundle/internal.js +36 -0
- package/dist/chains/bsc/pancake/bundle-buy-first-quote.js +24 -8
- package/dist/chains/bsc/pancake/bundle-buy-first-types.d.ts +2 -0
- package/dist/chains/bsc/pancake/bundle-buy-first-validate.d.ts +1 -1
- package/dist/chains/bsc/pancake/bundle-buy-first-validate.js +2 -2
- package/dist/chains/bsc/pancake/bundle-buy-first.js +1 -0
- package/dist/chains/bsc/pancake/bundle-swap-types.d.ts +2 -0
- package/dist/chains/bsc/pancake/quote.d.ts +7 -0
- package/dist/chains/bsc/pancake/quote.js +9 -1
- package/dist/chains/bsc/pancake/swap-batch.js +15 -16
- package/dist/chains/bsc/pancake/swap-build.d.ts +1 -1
- package/dist/chains/bsc/pancake/swap-build.js +15 -9
- package/dist/chains/bsc/pancake/swap-quick.js +4 -4
- package/dist/chains/bsc/pancake/swap-single.js +1 -0
- package/dist/chains/bsc/platforms/iro/token.d.ts +1 -0
- package/dist/chains/bsc/platforms/iro/token.js +36 -1
- package/dist/chains/eni/batch-router/transfer-sweep.js +10 -14
- package/dist/chains/eni/platforms/daoaas/graduation.js +60 -14
- package/dist/chains/eni/platforms/daoaas/meta.d.ts +0 -4
- package/dist/chains/eni/platforms/daoaas/meta.js +112 -27
- package/dist/chains/eni/platforms/daoaas/portal-direct-submit.js +11 -6
- package/dist/chains/eni/platforms/daoaas/portal-direct-tx.js +4 -2
- package/dist/chains/eni/platforms/daoaas/portal.d.ts +5 -0
- package/dist/chains/eni/platforms/daoaas/portal.js +63 -9
- package/dist/chains/eni/platforms/dswap/liquidity.js +144 -49
- package/dist/chains/eni/platforms/fair-launch/launcher.js +2 -10
- package/dist/chains/eni/platforms/fair-launch/pool.d.ts +1 -0
- package/dist/chains/eni/platforms/fair-launch/pool.js +16 -2
- package/dist/chains/eni/platforms/iro/token.d.ts +1 -0
- package/dist/chains/eni/platforms/iro/token.js +36 -1
- package/dist/chains/robinhood/pons-v2/launch.js +49 -18
- package/dist/chains/xlayer/eip7702/bundle-approve.js +8 -8
- package/dist/chains/xlayer/eip7702/bundle-sell.js +3 -4
- package/dist/chains/xlayer/eip7702/bundle-swap-batch.js +4 -2
- package/dist/chains/xlayer/eip7702/bundle-swap-multi.js +3 -3
- package/dist/chains/xlayer/eip7702/multi-hop-transfer-sweep.js +4 -10
- package/dist/chains/xlayer/eip7702/volume-bundle.js +45 -24
- package/dist/chains/xlayer/eoa/eoa-wash-volume.js +4 -14
- package/dist/core/quote/quote-helpers.d.ts +11 -0
- package/dist/core/quote/quote-helpers.js +121 -20
- package/dist/core/quote/quote-helpers.test.js +11 -1
- package/dist/domains/dex/direct-router-v2-buy.js +4 -2
- package/dist/domains/dex/direct-router-v2-sell.js +63 -12
- package/dist/domains/dex/direct-router-v3.js +3 -4
- package/dist/domains/dex/quote.js +15 -5
- package/dist/domains/flap/portal-bundle-merkle/pancake-proxy/approve.js +48 -8
- package/dist/domains/flap/portal-bundle-merkle/swap-batch.js +8 -10
- package/dist/domains/flap/portal-bundle-merkle/swap-buy-first-quote.js +24 -9
- package/dist/domains/flap/portal-bundle-merkle/swap-planning.js +6 -2
- package/dist/domains/flap/portal-bundle-merkle/utils/sweep.js +14 -7
- package/dist/domains/flap/reader.d.ts +6 -0
- package/dist/domains/flap/reader.js +77 -1
- package/dist/domains/four/contracts/tm-bundle-batch.js +2 -10
- package/dist/domains/four/contracts/tm-bundle-create.js +5 -2
- package/dist/domains/four/contracts/tm.d.ts +2 -0
- package/dist/domains/four/contracts/tm.js +59 -2
- package/dist/domains/four/tax-token.js +60 -28
- package/dist/domains/lp-inspect/inspect-tax.js +98 -12
- package/dist/domains/lp-inspect/inspect.js +57 -44
- package/dist/domains/lp-inspect/registry.js +33 -7
- package/dist/domains/openfour/registry.js +15 -5
- package/dist/utils/airdrop-sweep.js +37 -17
- package/dist/utils/swap-helpers.js +35 -7
- package/dist/utils/wallet.js +10 -9
- package/package.json +1 -1
|
@@ -1,12 +1,51 @@
|
|
|
1
|
-
import { ethers, Wallet } from 'ethers';
|
|
1
|
+
import { ethers, Interface, Wallet } from 'ethers';
|
|
2
2
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
3
|
-
import { ADDRESSES, CHAINS } from '../../core/constants/index.js';
|
|
4
|
-
import { ERC20_ABI } from '../../abis/common.js';
|
|
3
|
+
import { ADDRESSES, CHAINS, getMulticall3Address } from '../../core/constants/index.js';
|
|
4
|
+
import { ERC20_ABI, MULTICALL3_ABI } from '../../abis/common.js';
|
|
5
5
|
import { getErrorMessageFromUnknown } from '../../core/index.js';
|
|
6
6
|
import { getTxType, getGasPriceConfig } from '../config-helpers.js';
|
|
7
7
|
import { createSignProvider } from '../../core/sign-context-helpers.js';
|
|
8
8
|
import { batchCheckAllowances } from '../../core/erc20/index.js';
|
|
9
9
|
const APPROVAL_THRESHOLD = ethers.MaxUint256 / 2n;
|
|
10
|
+
async function readApprovalViews(provider, chainId, tokenAddress, owners, spender, allMax) {
|
|
11
|
+
if (allMax) {
|
|
12
|
+
return {
|
|
13
|
+
decimals: 18,
|
|
14
|
+
allowances: await batchCheckAllowances(provider, tokenAddress, owners, spender),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const iface = new Interface(ERC20_ABI);
|
|
18
|
+
const multicall = new ethers.Contract(getMulticall3Address(chainId), MULTICALL3_ABI, provider);
|
|
19
|
+
const aggregate3 = multicall.aggregate3;
|
|
20
|
+
if (!aggregate3)
|
|
21
|
+
throw new Error('aggregate3 missing');
|
|
22
|
+
try {
|
|
23
|
+
const rows = (await aggregate3.staticCall([
|
|
24
|
+
{ target: tokenAddress, allowFailure: false, callData: iface.encodeFunctionData('decimals', []) },
|
|
25
|
+
...owners.map((owner) => ({
|
|
26
|
+
target: tokenAddress,
|
|
27
|
+
allowFailure: true,
|
|
28
|
+
callData: iface.encodeFunctionData('allowance', [owner, spender]),
|
|
29
|
+
})),
|
|
30
|
+
]));
|
|
31
|
+
const decimals = Number(iface.decodeFunctionResult('decimals', rows[0].returnData)[0]);
|
|
32
|
+
const allowances = owners.map((_, index) => {
|
|
33
|
+
const row = rows[index + 1];
|
|
34
|
+
if (!row?.success || row.returnData === '0x')
|
|
35
|
+
return 0n;
|
|
36
|
+
try {
|
|
37
|
+
return iface.decodeFunctionResult('allowance', row.returnData)[0];
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return 0n;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return { decimals, allowances };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new Error(`查询代币 decimals 失败: ${getErrorMessageFromUnknown(error)}`, { cause: error });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
10
49
|
function resolveApprovalGasLimit(config) {
|
|
11
50
|
if (config.gasLimit)
|
|
12
51
|
return BigInt(config.gasLimit);
|
|
@@ -28,21 +67,12 @@ export async function approveFourTokenManagerBatch(params) {
|
|
|
28
67
|
const provider = createSignProvider(config.rpcUrl, chainId);
|
|
29
68
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
30
69
|
const allMax = amounts.every((a) => a === 'max');
|
|
31
|
-
const
|
|
70
|
+
const owners = wallets.map((w) => w.address);
|
|
71
|
+
const [gasPrice, tokenViews] = await Promise.all([
|
|
32
72
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
33
|
-
allMax
|
|
34
|
-
? Promise.resolve(18)
|
|
35
|
-
: (async () => {
|
|
36
|
-
try {
|
|
37
|
-
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
|
|
38
|
-
return await tokenContract.decimals();
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
throw new Error(`查询代币 decimals 失败: ${getErrorMessageFromUnknown(error)}`, { cause: error });
|
|
42
|
-
}
|
|
43
|
-
})(),
|
|
44
|
-
batchCheckAllowances(provider, tokenAddress, wallets.map((w) => w.address), tmAddr),
|
|
73
|
+
readApprovalViews(provider, chainId, tokenAddress, owners, tmAddr, allMax),
|
|
45
74
|
]);
|
|
75
|
+
const { decimals, allowances } = tokenViews;
|
|
46
76
|
const amountsBigInt = amounts.map((a) => (a === 'max' ? ethers.MaxUint256 : ethers.parseUnits(a, decimals)));
|
|
47
77
|
const needApproval = wallets.filter((_, i) => allowances[i] < APPROVAL_THRESHOLD);
|
|
48
78
|
const needApprovalAmounts = amountsBigInt.filter((_, i) => allowances[i] < APPROVAL_THRESHOLD);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Four.meme 发币:计价币、create value、ERC20 预购授权
|
|
3
3
|
*/
|
|
4
|
-
import { ethers, Wallet } from 'ethers';
|
|
4
|
+
import { ethers, Wallet, type Provider } from 'ethers';
|
|
5
5
|
import { FourClient, buildCreateTokenReq, type CreateTokenCustom } from '../../../core/clients/four.js';
|
|
6
6
|
import { type FourRaisedToken } from '../../../domains/four/raised-token.js';
|
|
7
7
|
export type FourLaunchQuoteInfo = {
|
|
@@ -11,6 +11,10 @@ export type FourLaunchQuoteInfo = {
|
|
|
11
11
|
export declare function resolveCreateRaisedToken(fourClient: FourClient, tokenInfo: FourLaunchQuoteInfo): Promise<FourRaisedToken>;
|
|
12
12
|
export declare function readLaunchFeeWei(tm: ethers.Contract): Promise<bigint>;
|
|
13
13
|
export declare function fourCreateValueWei(raisedToken: FourRaisedToken, preSale: string, launchFeeWei: bigint): bigint;
|
|
14
|
+
export declare function readQuoteDecimalsAndAllowance(provider: Provider, tokenAddress: string, owner: string, spender: string): Promise<{
|
|
15
|
+
decimals: number;
|
|
16
|
+
allowance: bigint;
|
|
17
|
+
}>;
|
|
14
18
|
export declare function signQuoteApproveIfNeeded(params: {
|
|
15
19
|
raisedToken: FourRaisedToken;
|
|
16
20
|
preSale: string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Four.meme 发币:计价币、create value、ERC20 预购授权
|
|
3
3
|
*/
|
|
4
|
-
import { ethers } from 'ethers';
|
|
5
|
-
import { ERC20_ABI } from '../../../abis/common.js';
|
|
4
|
+
import { ethers, Interface } from 'ethers';
|
|
5
|
+
import { ERC20_ABI, MULTICALL3_ABI } from '../../../abis/common.js';
|
|
6
|
+
import { CHAINS, getMulticall3Address } from '../../../core/constants/index.js';
|
|
6
7
|
import { buildCreateTokenReq } from '../../../core/clients/four.js';
|
|
7
8
|
import { computeFourCreateValueWei, isNativeRaisedToken, } from '../../../domains/four/raised-token.js';
|
|
8
9
|
import { PLATFORM_CREATE_FEE } from './constants.js';
|
|
@@ -25,6 +26,28 @@ export async function readLaunchFeeWei(tm) {
|
|
|
25
26
|
export function fourCreateValueWei(raisedToken, preSale, launchFeeWei) {
|
|
26
27
|
return computeFourCreateValueWei({ raisedToken, preSale, launchFeeWei });
|
|
27
28
|
}
|
|
29
|
+
export async function readQuoteDecimalsAndAllowance(provider, tokenAddress, owner, spender) {
|
|
30
|
+
const iface = new Interface([
|
|
31
|
+
'function decimals() view returns (uint8)',
|
|
32
|
+
'function allowance(address,address) view returns (uint256)',
|
|
33
|
+
]);
|
|
34
|
+
const multicall = new ethers.Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
|
|
35
|
+
const aggregate3 = multicall.aggregate3;
|
|
36
|
+
if (!aggregate3)
|
|
37
|
+
throw new Error('aggregate3 missing');
|
|
38
|
+
const rows = (await aggregate3.staticCall([
|
|
39
|
+
{ target: tokenAddress, allowFailure: false, callData: iface.encodeFunctionData('decimals', []) },
|
|
40
|
+
{
|
|
41
|
+
target: tokenAddress,
|
|
42
|
+
allowFailure: false,
|
|
43
|
+
callData: iface.encodeFunctionData('allowance', [owner, spender]),
|
|
44
|
+
},
|
|
45
|
+
]));
|
|
46
|
+
return {
|
|
47
|
+
decimals: Number(iface.decodeFunctionResult('decimals', rows[0].returnData)[0]),
|
|
48
|
+
allowance: iface.decodeFunctionResult('allowance', rows[1].returnData)[0],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
28
51
|
export async function signQuoteApproveIfNeeded(params) {
|
|
29
52
|
if (isNativeRaisedToken(params.raisedToken))
|
|
30
53
|
return undefined;
|
|
@@ -32,9 +55,11 @@ export async function signQuoteApproveIfNeeded(params) {
|
|
|
32
55
|
if (preSale === '0')
|
|
33
56
|
return undefined;
|
|
34
57
|
const token = new ethers.Contract(params.raisedToken.symbolAddress, ERC20_ABI, params.wallet);
|
|
35
|
-
const
|
|
58
|
+
const provider = params.wallet.provider;
|
|
59
|
+
if (!provider)
|
|
60
|
+
throw new Error('wallet has no provider');
|
|
61
|
+
const { decimals, allowance } = await readQuoteDecimalsAndAllowance(provider, params.raisedToken.symbolAddress, params.wallet.address, params.spender);
|
|
36
62
|
const amount = ethers.parseUnits(preSale, decimals);
|
|
37
|
-
const allowance = await token.allowance(params.wallet.address, params.spender);
|
|
38
63
|
if (allowance >= amount)
|
|
39
64
|
return undefined;
|
|
40
65
|
const unsigned = await token.approve.populateTransaction(params.spender, amount);
|
|
@@ -5,7 +5,7 @@ import { ethers, Contract, Interface } from 'ethers';
|
|
|
5
5
|
import { getGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
6
6
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
|
|
7
7
|
import { ADDRESSES, CHAINS, applySlippageBps, reverseSlippageBps } from '../../../core/constants/index.js';
|
|
8
|
-
import { MULTICALL3_ABI
|
|
8
|
+
import { MULTICALL3_ABI } from '../../../abis/common.js';
|
|
9
9
|
import { getBundleErrorMessage } from '../../errors.js';
|
|
10
10
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient, getBribeAmount, } from '../../config-helpers.js';
|
|
11
11
|
import { createSignProvider, signBribeTransaction } from '../../../core/sign-context-helpers.js';
|
|
@@ -14,6 +14,7 @@ import { createChainContext, createWallets } from '../core-helpers.js';
|
|
|
14
14
|
import { TokenManagerHelper3 as Helper3Abi } from '../../../abis/contracts/index.js';
|
|
15
15
|
import { TM2_ABI, MULTICALL3_ADDRESS } from './constants.js';
|
|
16
16
|
import { prefixSumAmounts, sequentialFundsFromPrefixQuotes } from '../sequential-sell-quote.js';
|
|
17
|
+
import { batchGetBalances } from '../../internal.js';
|
|
17
18
|
export async function batchSellWithBundleMerkle(params) {
|
|
18
19
|
const { privateKeys, sellAmounts, tokenAddress, minOutputAmounts, config } = params;
|
|
19
20
|
if (privateKeys.length === 0 || sellAmounts.length !== privateKeys.length) {
|
|
@@ -178,8 +179,7 @@ async function resolveSellOutputs(params) {
|
|
|
178
179
|
}
|
|
179
180
|
async function ensureSellerBalances(params) {
|
|
180
181
|
const { provider, tokenAddress, sellers, amountsWei } = params;
|
|
181
|
-
const
|
|
182
|
-
const balances = await Promise.all(sellers.map((wallet) => tokenContract.balanceOf(wallet.address)));
|
|
182
|
+
const balances = await batchGetBalances(provider, sellers.map((wallet) => wallet.address), tokenAddress, CHAINS.BSC.chainId);
|
|
183
183
|
for (let i = 0; i < sellers.length; i++) {
|
|
184
184
|
if (balances[i] < amountsWei[i]) {
|
|
185
185
|
throw new Error(`钱包 ${i} 代币余额不足:需要 ${ethers.formatUnits(amountsWei[i], 18)},实际 ${ethers.formatUnits(balances[i], 18)}`);
|
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PancakeSwap 官方 Router 交易模块 — 授权
|
|
3
3
|
*/
|
|
4
|
-
import { ethers, Wallet, Contract } from 'ethers';
|
|
4
|
+
import { ethers, Wallet, Contract, Interface } from 'ethers';
|
|
5
5
|
import { getGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
6
6
|
import { createSignProvider } from '../../../core/sign-context-helpers.js';
|
|
7
7
|
import { NonceManager, getOptimizedGasPrice } from '../../../utils/bundle-helpers.js';
|
|
8
|
-
import { CHAINS } from '../../../core/constants/index.js';
|
|
9
|
-
import { ERC20_ABI } from '../../../abis/common.js';
|
|
8
|
+
import { CHAINS, getMulticall3Address } from '../../../core/constants/index.js';
|
|
9
|
+
import { ERC20_ABI, MULTICALL3_ABI } from '../../../abis/common.js';
|
|
10
10
|
import { getErrorMessageFromUnknown } from '../../../core/index.js';
|
|
11
11
|
import { getTxType, getGasPriceConfig } from '../../config-helpers.js';
|
|
12
12
|
import { getTokenDecimals, getApprovalTarget } from './helpers.js';
|
|
13
|
+
import { batchCheckAllowances } from '../../../core/erc20/allowances.js';
|
|
14
|
+
async function readDecimalsAndAllowances(provider, tokenAddress, owners, spender, needDecimals) {
|
|
15
|
+
const iface = new Interface(ERC20_ABI);
|
|
16
|
+
const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
|
|
17
|
+
const aggregate3 = multicall.aggregate3;
|
|
18
|
+
if (!aggregate3)
|
|
19
|
+
throw new Error('aggregate3 missing');
|
|
20
|
+
const rows = (await aggregate3.staticCall([
|
|
21
|
+
...(needDecimals
|
|
22
|
+
? [{ target: tokenAddress, allowFailure: false, callData: iface.encodeFunctionData('decimals', []) }]
|
|
23
|
+
: []),
|
|
24
|
+
...owners.map((owner) => ({
|
|
25
|
+
target: tokenAddress,
|
|
26
|
+
allowFailure: false,
|
|
27
|
+
callData: iface.encodeFunctionData('allowance', [owner, spender]),
|
|
28
|
+
})),
|
|
29
|
+
]));
|
|
30
|
+
const offset = needDecimals ? 1 : 0;
|
|
31
|
+
return {
|
|
32
|
+
decimals: needDecimals ? Number(iface.decodeFunctionResult('decimals', rows[0].returnData)[0]) : 18,
|
|
33
|
+
allowances: owners.map((_, index) => iface.decodeFunctionResult('allowance', rows[index + offset].returnData)[0]),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
13
36
|
/**
|
|
14
37
|
* ✅ 授权代币给 PancakeSwap Router(根据 routeType 选择 V2/V3)
|
|
15
38
|
*/
|
|
@@ -20,8 +43,18 @@ export async function approveFourPancakeProxy(params) {
|
|
|
20
43
|
const provider = createSignProvider(rpcUrl, 56, 'BSC');
|
|
21
44
|
const wallet = new Wallet(privateKey, provider);
|
|
22
45
|
const token = new Contract(tokenAddress, ERC20_ABI, wallet);
|
|
23
|
-
const
|
|
24
|
-
|
|
46
|
+
const needDecimals = amount !== 'max';
|
|
47
|
+
let decimals;
|
|
48
|
+
let currentAllowance;
|
|
49
|
+
try {
|
|
50
|
+
const views = await readDecimalsAndAllowances(provider, tokenAddress, [wallet.address], approvalTarget, needDecimals);
|
|
51
|
+
decimals = views.decimals;
|
|
52
|
+
currentAllowance = views.allowances[0];
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
decimals = needDecimals ? await getTokenDecimals(tokenAddress, provider) : 18;
|
|
56
|
+
currentAllowance = await token.allowance(wallet.address, approvalTarget);
|
|
57
|
+
}
|
|
25
58
|
const amountBigInt = amount === 'max' ? ethers.MaxUint256 : ethers.parseUnits(amount, decimals);
|
|
26
59
|
if (currentAllowance >= amountBigInt) {
|
|
27
60
|
return { txHash: '', approved: true };
|
|
@@ -44,13 +77,17 @@ export async function approveFourPancakeProxyBatch(params) {
|
|
|
44
77
|
// 根据 routeType 选择授权目标
|
|
45
78
|
const approvalTarget = getApprovalTarget(routeType || 'v2');
|
|
46
79
|
const provider = createSignProvider(config.rpcUrl, 56, 'BSC');
|
|
47
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
48
|
-
const decimals = await getTokenDecimals(tokenAddress, provider);
|
|
49
80
|
const wallets = privateKeys.map((k) => new Wallet(k, provider));
|
|
81
|
+
const needDecimals = amounts.some((amount) => amount !== 'max');
|
|
82
|
+
const [gasPrice, views] = await Promise.all([
|
|
83
|
+
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
84
|
+
readDecimalsAndAllowances(provider, tokenAddress, wallets.map((wallet) => wallet.address), approvalTarget, needDecimals).catch(async () => ({
|
|
85
|
+
decimals: needDecimals ? await getTokenDecimals(tokenAddress, provider) : 18,
|
|
86
|
+
allowances: await batchCheckAllowances(provider, tokenAddress, wallets.map((wallet) => wallet.address), approvalTarget),
|
|
87
|
+
})),
|
|
88
|
+
]);
|
|
89
|
+
const { decimals, allowances } = views;
|
|
50
90
|
const amountsBigInt = amounts.map((a) => (a === 'max' ? ethers.MaxUint256 : ethers.parseUnits(a, decimals)));
|
|
51
|
-
// 检查现有授权
|
|
52
|
-
const tokens = wallets.map((w) => new Contract(tokenAddress, ERC20_ABI, w));
|
|
53
|
-
const allowances = await Promise.all(tokens.map((token, i) => token.allowance(wallets[i].address, approvalTarget)));
|
|
54
91
|
// 只授权不足的
|
|
55
92
|
const needApproval = wallets.filter((_, i) => allowances[i] < amountsBigInt[i]);
|
|
56
93
|
const needApprovalAmounts = amountsBigInt.filter((amount, i) => allowances[i] < amount);
|
|
@@ -8,7 +8,7 @@ import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, CHAINS, applySlippageBps, reverseSli
|
|
|
8
8
|
import { prefixSumAmounts, sequentialFundsFromPrefixQuotes } from '../sequential-sell-quote.js';
|
|
9
9
|
import { TM2_ABI } from '../../../abis/common.js';
|
|
10
10
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, getProfitRecipient, getBribeAmount, } from '../../config-helpers.js';
|
|
11
|
-
import {
|
|
11
|
+
import { trySellFundsBatch } from '../../../domains/four/contracts/tm.js';
|
|
12
12
|
import { createSignProvider } from '../../../core/sign-context-helpers.js';
|
|
13
13
|
const CHAIN_ID = CHAINS.BSC.chainId;
|
|
14
14
|
/**
|
|
@@ -124,15 +124,7 @@ export async function fourBatchPrivateSellMerkle(params) {
|
|
|
124
124
|
})())
|
|
125
125
|
: (async () => {
|
|
126
126
|
const prefixes = prefixSumAmounts(amountsWei);
|
|
127
|
-
const prefixFunds = await
|
|
128
|
-
try {
|
|
129
|
-
const result = await trySell('BSC', rpcUrl, tokenAddress, amount);
|
|
130
|
-
return result.funds;
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return 0n;
|
|
134
|
-
}
|
|
135
|
-
}));
|
|
127
|
+
const prefixFunds = await trySellFundsBatch('BSC', rpcUrl, tokenAddress, prefixes);
|
|
136
128
|
return sequentialFundsFromPrefixQuotes(prefixFunds);
|
|
137
129
|
})(),
|
|
138
130
|
]);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Four.meme 内盘捆绑换手(Merkle Bundle)— 单卖单买流程
|
|
3
3
|
*/
|
|
4
4
|
import { ethers, Contract, Wallet } from 'ethers';
|
|
5
|
-
import { countSellerNonceSlots, createFourSwapGasContext,
|
|
5
|
+
import { countSellerNonceSlots, createFourSwapGasContext, fetchFourSwapSellQuoteAllowanceAndBalances, grossUpFourSellProceedsToBuySide, needsTmTokenApproval, planSellerNonces, signTmMaxApprovalTx, } from '../swap-sign-helpers.js';
|
|
6
6
|
import { signBribeTransaction } from '../../../core/sign-context-helpers.js';
|
|
7
7
|
import { calculateSellAmount } from '../../../utils/swap-helpers.js';
|
|
8
8
|
import { NonceManager, getOptimizedGasPrice, getGasPriceConfig, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
|
|
@@ -25,10 +25,10 @@ export async function fourBundleSwapMerkle(params) {
|
|
|
25
25
|
getOptimizedGasPrice(provider, getGasPriceConfig(bundleConfig)),
|
|
26
26
|
]);
|
|
27
27
|
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
provider.getBalance(buyer.address),
|
|
28
|
+
const { sellFunds: sellerWillGetBNB, allowance: currentAllowance, buyerBalances, } = await fetchFourSwapSellQuoteAllowanceAndBalances(provider, tokenAddress, seller.address, sellAmountWei, [
|
|
29
|
+
buyer.address,
|
|
31
30
|
]);
|
|
31
|
+
const buyerBalance = buyerBalances[0] ?? 0n;
|
|
32
32
|
const buyerFunds = sellerWillGetBNB;
|
|
33
33
|
// 验证买方余额
|
|
34
34
|
const reserveGas = ethers.parseEther((config.reserveGasBNB || 0.0005).toString());
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* Four.meme 内盘捆绑换手(Merkle Bundle)- 先买后卖 — 多钱包模式
|
|
3
3
|
*/
|
|
4
4
|
import { getGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
5
|
-
import { ethers, Contract, Wallet } from 'ethers';
|
|
5
|
+
import { ethers, Contract, Interface, Wallet } from 'ethers';
|
|
6
|
+
import { MULTICALL3_ABI } from '../../../abis/common.js';
|
|
6
7
|
import { createSignProvider } from '../../../core/sign-context-helpers.js';
|
|
7
8
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
|
|
8
|
-
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, CHAINS, calculateBundleSwapRoundTripProfit } from '../../../core/constants/index.js';
|
|
9
|
-
import { TM_ABI,
|
|
9
|
+
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, CHAINS, calculateBundleSwapRoundTripProfit, getMulticall3Address, } from '../../../core/constants/index.js';
|
|
10
|
+
import { TM_ABI, TM_ADDRESS } from '../swap-internal.js';
|
|
10
11
|
import { getTxType, getGasPriceConfig, getProfitRecipient, getBribeAmount } from '../../config-helpers.js';
|
|
11
12
|
import { trySell } from '../../../domains/four/contracts/tm.js';
|
|
12
13
|
import { splitAmountByWeight as splitAmount } from '../../../core/amount/split-by-weight.js';
|
|
@@ -49,13 +50,29 @@ export async function fourBundleBuyFirstMultiWallet(params) {
|
|
|
49
50
|
const bribeAmount = getBribeAmount(config);
|
|
50
51
|
const needBribeTx = bribeAmount > 0n;
|
|
51
52
|
// ✅ 第一批并行:报价 + Gas价格 + 代币精度
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const tryBuyIface = new Interface([
|
|
54
|
+
'function tryBuy(address token, uint256 amount, uint256 funds) view returns (address tokenManager, address quote, uint256 estimatedAmount, uint256 estimatedCost, uint256 estimatedFee, uint256 amountMsgValue, uint256 amountApproval, uint256 amountFunds)',
|
|
55
|
+
]);
|
|
56
|
+
const ercIface = new Interface(['function decimals() view returns (uint8)']);
|
|
57
|
+
const [quoteRows, gasPrice] = await Promise.all([
|
|
58
|
+
(async () => {
|
|
59
|
+
const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
|
|
60
|
+
const aggregate3 = multicall.aggregate3;
|
|
61
|
+
if (!aggregate3)
|
|
62
|
+
throw new Error('aggregate3 missing');
|
|
63
|
+
return (await aggregate3.staticCall([
|
|
64
|
+
{
|
|
65
|
+
target: ADDRESSES.BSC.TokenManagerHelper3,
|
|
66
|
+
allowFailure: false,
|
|
67
|
+
callData: tryBuyIface.encodeFunctionData('tryBuy', [tokenAddress, 0n, totalFundsWei]),
|
|
68
|
+
},
|
|
69
|
+
{ target: tokenAddress, allowFailure: false, callData: ercIface.encodeFunctionData('decimals', []) },
|
|
70
|
+
]));
|
|
71
|
+
})(),
|
|
56
72
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
57
|
-
erc20.decimals(),
|
|
58
73
|
]);
|
|
74
|
+
const buyQuote = tryBuyIface.decodeFunctionResult('tryBuy', quoteRows[0].returnData);
|
|
75
|
+
const decimals = Number(ercIface.decodeFunctionResult('decimals', quoteRows[1].returnData)[0]);
|
|
59
76
|
const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
|
|
60
77
|
if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
|
|
61
78
|
throw new Error('报价失败:无法估算可买入的代币数量');
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* 功能:钱包B先买入代币 → 钱包A卖出相同数量 → 原子执行
|
|
5
5
|
*/
|
|
6
6
|
import { getGasLimit } from '../../../core/gas/bundle-gas.js';
|
|
7
|
-
import { ethers, Contract, Wallet } from 'ethers';
|
|
7
|
+
import { ethers, Contract, Interface, Wallet } from 'ethers';
|
|
8
|
+
import { MULTICALL3_ABI } from '../../../abis/common.js';
|
|
8
9
|
import { createSignProvider } from '../../../core/sign-context-helpers.js';
|
|
9
10
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
|
|
10
|
-
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, CHAINS, calculateBundleSwapRoundTripProfit } from '../../../core/constants/index.js';
|
|
11
|
-
import { TM_ABI,
|
|
11
|
+
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA, CHAINS, calculateBundleSwapRoundTripProfit, getMulticall3Address, } from '../../../core/constants/index.js';
|
|
12
|
+
import { TM_ABI, TM_ADDRESS } from '../swap-internal.js';
|
|
12
13
|
import { getTxType, getGasPriceConfig, getProfitRecipient, getBribeAmount } from '../../config-helpers.js';
|
|
13
14
|
import { trySell } from '../../../domains/four/contracts/tm.js';
|
|
14
15
|
import { splitAmountByWeight as splitAmount } from '../../../core/amount/split-by-weight.js';
|
|
@@ -66,13 +67,33 @@ export async function fourBundleBuyFirstMerkle(params) {
|
|
|
66
67
|
throw new Error('buyerFunds 需要大于 0');
|
|
67
68
|
}
|
|
68
69
|
// ✅ 优化:第二批并行 - buyQuote、sellerTokenBal、decimals
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const [buyQuote, sellerTokenBal, decimals] = await Promise.all([
|
|
72
|
-
helper3.tryBuy(tokenAddress, 0n, buyerFundsWei),
|
|
73
|
-
erc20.balanceOf(seller.address),
|
|
74
|
-
erc20.decimals(),
|
|
70
|
+
const tryBuyIface = new Interface([
|
|
71
|
+
'function tryBuy(address token, uint256 amount, uint256 funds) view returns (address tokenManager, address quote, uint256 estimatedAmount, uint256 estimatedCost, uint256 estimatedFee, uint256 amountMsgValue, uint256 amountApproval, uint256 amountFunds)',
|
|
75
72
|
]);
|
|
73
|
+
const ercIface = new Interface([
|
|
74
|
+
'function balanceOf(address) view returns (uint256)',
|
|
75
|
+
'function decimals() view returns (uint8)',
|
|
76
|
+
]);
|
|
77
|
+
const multicall = new Contract(getMulticall3Address(CHAINS.BSC.chainId), MULTICALL3_ABI, provider);
|
|
78
|
+
const aggregate3 = multicall.aggregate3;
|
|
79
|
+
if (!aggregate3)
|
|
80
|
+
throw new Error('aggregate3 missing');
|
|
81
|
+
const quoteRows = (await aggregate3.staticCall([
|
|
82
|
+
{
|
|
83
|
+
target: ADDRESSES.BSC.TokenManagerHelper3,
|
|
84
|
+
allowFailure: false,
|
|
85
|
+
callData: tryBuyIface.encodeFunctionData('tryBuy', [tokenAddress, 0n, buyerFundsWei]),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
target: tokenAddress,
|
|
89
|
+
allowFailure: false,
|
|
90
|
+
callData: ercIface.encodeFunctionData('balanceOf', [seller.address]),
|
|
91
|
+
},
|
|
92
|
+
{ target: tokenAddress, allowFailure: false, callData: ercIface.encodeFunctionData('decimals', []) },
|
|
93
|
+
]));
|
|
94
|
+
const buyQuote = tryBuyIface.decodeFunctionResult('tryBuy', quoteRows[0].returnData);
|
|
95
|
+
const sellerTokenBal = ercIface.decodeFunctionResult('balanceOf', quoteRows[1].returnData)[0];
|
|
96
|
+
const decimals = Number(ercIface.decodeFunctionResult('decimals', quoteRows[2].returnData)[0]);
|
|
76
97
|
const estimatedTokenAmount = buyQuote.estimatedAmount ?? buyQuote[2];
|
|
77
98
|
if (!estimatedTokenAmount || estimatedTokenAmount <= 0n) {
|
|
78
99
|
throw new Error('报价失败:无法估算可买入的代币数量');
|
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
import { ethers, Contract } from 'ethers';
|
|
2
2
|
import { getGasLimit } from '../../utils/bundle-helpers.js';
|
|
3
|
-
import { ADDRESSES, CHAINS } from '../../core/constants/index.js';
|
|
3
|
+
import { ADDRESSES, CHAINS, getMulticall3Address } from '../../core/constants/index.js';
|
|
4
|
+
import { MULTICALL3_ABI } from '../../abis/common.js';
|
|
5
|
+
import { batchGetBalances } from '../internal.js';
|
|
4
6
|
import { getProfitRateBps as getSharedProfitRateBps } from '../../core/constants/index.js';
|
|
5
7
|
import { getTxType } from '../config-helpers.js';
|
|
6
8
|
import { createSignProvider } from '../../core/sign-context-helpers.js';
|
|
7
9
|
import { HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
|
|
8
10
|
const APPROVE_INTERFACE = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
|
|
9
11
|
const ERC20_ALLOWANCE_ABI = ['function allowance(address,address) view returns (uint256)'];
|
|
12
|
+
const TRY_SELL_IFACE = new ethers.Interface([
|
|
13
|
+
'function trySell(address token, uint256 amount) view returns (address tokenManager, address quote, uint256 funds, uint256 fee)',
|
|
14
|
+
]);
|
|
15
|
+
const BALANCE_IFACE = new ethers.Interface(['function getEthBalance(address) view returns (uint256)']);
|
|
16
|
+
async function readSellQuoteAllowanceBalances(provider, tokenAddress, sellerAddress, sellAmountWei, buyerAddresses) {
|
|
17
|
+
const multicallAddress = getMulticall3Address(CHAINS.BSC.chainId);
|
|
18
|
+
const multicall = new Contract(multicallAddress, MULTICALL3_ABI, provider);
|
|
19
|
+
const aggregate3 = multicall.aggregate3;
|
|
20
|
+
if (!aggregate3)
|
|
21
|
+
return null;
|
|
22
|
+
const calls = [
|
|
23
|
+
{
|
|
24
|
+
target: ADDRESSES.BSC.TokenManagerHelper3,
|
|
25
|
+
allowFailure: false,
|
|
26
|
+
callData: TRY_SELL_IFACE.encodeFunctionData('trySell', [tokenAddress, sellAmountWei]),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
target: tokenAddress,
|
|
30
|
+
allowFailure: false,
|
|
31
|
+
callData: new ethers.Interface(ERC20_ALLOWANCE_ABI).encodeFunctionData('allowance', [sellerAddress, TM_ADDRESS]),
|
|
32
|
+
},
|
|
33
|
+
...buyerAddresses.map((address) => ({
|
|
34
|
+
target: multicallAddress,
|
|
35
|
+
allowFailure: true,
|
|
36
|
+
callData: BALANCE_IFACE.encodeFunctionData('getEthBalance', [address]),
|
|
37
|
+
})),
|
|
38
|
+
];
|
|
39
|
+
const rows = (await aggregate3.staticCall(calls));
|
|
40
|
+
if (!rows[0]?.success || !rows[1]?.success)
|
|
41
|
+
return null;
|
|
42
|
+
const sellFunds = TRY_SELL_IFACE.decodeFunctionResult('trySell', rows[0].returnData).funds;
|
|
43
|
+
const allowance = new ethers.Interface(ERC20_ALLOWANCE_ABI).decodeFunctionResult('allowance', rows[1].returnData)[0];
|
|
44
|
+
const buyerBalances = buyerAddresses.map((_, index) => {
|
|
45
|
+
const row = rows[index + 2];
|
|
46
|
+
if (!row?.success || row.returnData === '0x')
|
|
47
|
+
return 0n;
|
|
48
|
+
try {
|
|
49
|
+
return BALANCE_IFACE.decodeFunctionResult('getEthBalance', row.returnData)[0];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return 0n;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return { sellFunds, allowance, buyerBalances };
|
|
56
|
+
}
|
|
10
57
|
export function getFourSwapProfitRateBps(_userType) {
|
|
11
58
|
return getSharedProfitRateBps('bundleSwap');
|
|
12
59
|
}
|
|
@@ -71,6 +118,9 @@ export async function grossUpFourSellProceedsToBuySide(provider, tokenAddress, s
|
|
|
71
118
|
}
|
|
72
119
|
}
|
|
73
120
|
export async function fetchFourSwapSellQuoteAndAllowance(provider, tokenAddress, sellerAddress, sellAmountWei) {
|
|
121
|
+
const batched = await readSellQuoteAllowanceBalances(provider, tokenAddress, sellerAddress, sellAmountWei, []).catch(() => null);
|
|
122
|
+
if (batched)
|
|
123
|
+
return { sellFunds: batched.sellFunds, allowance: batched.allowance };
|
|
74
124
|
const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
|
|
75
125
|
const erc20 = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
|
|
76
126
|
const [sellQuote, allowance] = await Promise.all([
|
|
@@ -80,12 +130,15 @@ export async function fetchFourSwapSellQuoteAndAllowance(provider, tokenAddress,
|
|
|
80
130
|
return { sellFunds: sellQuote.funds, allowance };
|
|
81
131
|
}
|
|
82
132
|
export async function fetchFourSwapSellQuoteAllowanceAndBalances(provider, tokenAddress, sellerAddress, sellAmountWei, buyerAddresses) {
|
|
133
|
+
const batched = await readSellQuoteAllowanceBalances(provider, tokenAddress, sellerAddress, sellAmountWei, buyerAddresses).catch(() => null);
|
|
134
|
+
if (batched)
|
|
135
|
+
return batched;
|
|
83
136
|
const helper3 = new Contract(ADDRESSES.BSC.TokenManagerHelper3, HELPER3_ABI, provider);
|
|
84
137
|
const erc20 = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
|
|
85
|
-
const [sellQuote, allowance,
|
|
138
|
+
const [sellQuote, allowance, buyerBalances] = await Promise.all([
|
|
86
139
|
helper3.trySell(tokenAddress, sellAmountWei),
|
|
87
140
|
erc20.allowance(sellerAddress, TM_ADDRESS),
|
|
88
|
-
|
|
141
|
+
batchGetBalances(provider, buyerAddresses, undefined, CHAINS.BSC.chainId),
|
|
89
142
|
]);
|
|
90
143
|
return {
|
|
91
144
|
sellFunds: sellQuote.funds,
|
|
@@ -5,7 +5,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
5
5
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
|
|
6
6
|
import { getGasPriceConfig } from '../../config-helpers.js';
|
|
7
7
|
import { calculateTransferFee, floorErc20TransferGasLimit } from '../../../core/constants/index.js';
|
|
8
|
-
import { batchGetBalances as _batchGetBalances, getErc20Decimals as _getErc20Decimals } from '../../internal.js';
|
|
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
11
|
const { provider, chainIdNum, txType, config, target, targetWallet, tokenAddress, tokenDecimals, skipIfInsufficient, extractProfit, profitAddr, actualKeys, sources, ratios, amounts, amount, finalGasLimit, ratio, } = ctx;
|
|
@@ -16,13 +16,26 @@ export async function sweepErc20NoHop(ctx) {
|
|
|
16
16
|
const wallets = actualKeys.map((pk) => new Wallet(pk, provider));
|
|
17
17
|
const addresses = wallets.map((w) => w.address);
|
|
18
18
|
const nonceManager = ctx.nonceManager ?? new NonceManager(provider);
|
|
19
|
+
const targetIndexInSources = addresses.findIndex((addr) => addr.toLowerCase() === target.toLowerCase());
|
|
20
|
+
const balanceAddresses = config.checkBnbForErc20NoHop && targetWallet && targetIndexInSources < 0 ? [...addresses, target] : addresses;
|
|
19
21
|
// ✅ ERC20:并行获取 gasPrice、decimals、余额(传入 chainIdNum 避免 NETWORK_ERROR)
|
|
20
|
-
const [gasPrice, decimals,
|
|
22
|
+
const [gasPrice, decimals, paired] = await Promise.all([
|
|
21
23
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
22
24
|
Promise.resolve(tokenDecimals ?? (await _getErc20Decimals(provider, tokenAddress, chainIdNum))),
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
config.checkBnbForErc20NoHop
|
|
26
|
+
? batchGetTokenAndNativeBalances(provider, balanceAddresses, tokenAddress, chainIdNum)
|
|
27
|
+
: _batchGetBalances(provider, addresses, tokenAddress, chainIdNum).then((token) => ({
|
|
28
|
+
token,
|
|
29
|
+
native: [],
|
|
30
|
+
})),
|
|
25
31
|
]);
|
|
32
|
+
const balances = paired.token.slice(0, addresses.length);
|
|
33
|
+
const bnbBalances = paired.native.slice(0, addresses.length);
|
|
34
|
+
const targetNativeBalance = !config.checkBnbForErc20NoHop || !targetWallet
|
|
35
|
+
? 0n
|
|
36
|
+
: targetIndexInSources >= 0
|
|
37
|
+
? (paired.native[targetIndexInSources] ?? 0n)
|
|
38
|
+
: (paired.native[addresses.length] ?? 0n);
|
|
26
39
|
const iface = new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
27
40
|
// ✅ 修复:利润多跳需要 PROFIT_HOP_COUNT + 1 笔交易的 gas
|
|
28
41
|
// ✅ 统一使用 21055n 作为利润多跳的 gas limit
|
|
@@ -92,9 +105,7 @@ export async function sweepErc20NoHop(ctx) {
|
|
|
92
105
|
: null;
|
|
93
106
|
// ✅ 如果需要提取费用,检查支付者是否有足够原生代币支付
|
|
94
107
|
if (extractProfit && nativeProfitAmount > 0n && profitPayerWalletErc20 && config.checkBnbForErc20NoHop) {
|
|
95
|
-
const payerBnbBalance = useTargetAsPayerErc20
|
|
96
|
-
? await provider.getBalance(target)
|
|
97
|
-
: (bnbBalances[maxSweepIndex] ?? 0n);
|
|
108
|
+
const payerBnbBalance = useTargetAsPayerErc20 ? targetNativeBalance : (bnbBalances[maxSweepIndex] ?? 0n);
|
|
98
109
|
const payerNeedGas = (useTargetAsPayerErc20 ? 0n : finalGasLimit * gasPrice) + profitTxGas + nativeProfitAmount;
|
|
99
110
|
// 如果支付者原生代币余额不足,跳过费用提取
|
|
100
111
|
if (payerBnbBalance < payerNeedGas) {
|
|
@@ -5,7 +5,7 @@ import { ethers, Wallet } from 'ethers';
|
|
|
5
5
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT, } from '../../../utils/bundle-helpers.js';
|
|
6
6
|
import { getGasPriceConfig } from '../../config-helpers.js';
|
|
7
7
|
import { calculateTransferFee, floorErc20TransferGasLimit, GAS_LIMITS } from '../../../core/constants/index.js';
|
|
8
|
-
import { batchGetBalances as _batchGetBalances, getErc20Decimals as _getErc20Decimals } from '../../internal.js';
|
|
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
11
|
const { provider, chainIdNum, txType, config, target, targetWallet, tokenAddress, tokenDecimals, skipIfInsufficient, isNative, extractProfit, profitAddr, actualKeys, sources, ratios, amounts, amount, finalGasLimit, nativeGasLimit, ratio, } = ctx;
|
|
@@ -27,14 +27,20 @@ export async function sweepWithHops(ctx, preparedHops) {
|
|
|
27
27
|
}
|
|
28
28
|
const sourceAddresses = sourceWallets.map((w) => w.address);
|
|
29
29
|
// ✅ 优化:并行获取 gasPrice、decimals、余额
|
|
30
|
-
const [gasPrice, decimals,
|
|
30
|
+
const [gasPrice, decimals, paired] = await Promise.all([
|
|
31
31
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
32
32
|
isNative
|
|
33
33
|
? Promise.resolve(18)
|
|
34
34
|
: Promise.resolve(tokenDecimals ?? (await _getErc20Decimals(provider, tokenAddress, chainIdNum))),
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
isNative || !tokenAddress
|
|
36
|
+
? _batchGetBalances(provider, sourceAddresses, tokenAddress, chainIdNum).then((token) => ({
|
|
37
|
+
token,
|
|
38
|
+
native: [],
|
|
39
|
+
}))
|
|
40
|
+
: batchGetTokenAndNativeBalances(provider, sourceAddresses, tokenAddress, chainIdNum),
|
|
37
41
|
]);
|
|
42
|
+
const balances = paired.token;
|
|
43
|
+
const bnbBalances = paired.native;
|
|
38
44
|
const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
39
45
|
// ✅ Gas limit 设置(与分散函数保持一致)
|
|
40
46
|
const nativeTransferGasLimit = GAS_LIMITS.BRIBE;
|
|
@@ -9,6 +9,11 @@ export declare function normalizeAmounts(recipients: string[], amount?: AmountLi
|
|
|
9
9
|
* 批量获取余额(原生代币或 ERC20,优先 Multicall3)
|
|
10
10
|
*/
|
|
11
11
|
export declare function batchGetBalances(provider: JsonRpcProvider, addresses: string[], tokenAddress?: string, chainId?: number): Promise<bigint[]>;
|
|
12
|
+
/** 同一批地址的 ERC20 余额和原生币余额,一次 aggregate3。 */
|
|
13
|
+
export declare function batchGetTokenAndNativeBalances(provider: JsonRpcProvider, addresses: string[], tokenAddress: string, chainId?: number): Promise<{
|
|
14
|
+
token: bigint[];
|
|
15
|
+
native: bigint[];
|
|
16
|
+
}>;
|
|
12
17
|
export declare function estimateErc20TransferGas(provider: JsonRpcProvider, tokenAddress: string, from: string, to: string, amount: bigint, bufferPercent?: number): Promise<bigint>;
|
|
13
18
|
export declare function estimateMaxErc20TransferGas(provider: JsonRpcProvider, tokenAddress: string, from: string, recipients: string[], amounts: bigint[], bufferPercent?: number): Promise<bigint>;
|
|
14
19
|
export declare function calculateGasLimit(config: BundleGasConfig, isNative: boolean, _hasHops: boolean, _hopCount?: number): bigint;
|