four-flap-meme-sdk 1.4.24 → 1.4.26
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/abis/common.d.ts +85 -0
- package/dist/abis/common.js +242 -0
- package/dist/abis/index.d.ts +1 -0
- package/dist/abis/index.js +2 -0
- package/dist/contracts/tm-bundle-merkle/approve-tokenmanager.js +1 -6
- package/dist/contracts/tm-bundle-merkle/core.js +9 -16
- package/dist/contracts/tm-bundle-merkle/internal.js +6 -8
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.d.ts +12 -6
- package/dist/contracts/tm-bundle-merkle/pancake-proxy.js +224 -166
- package/dist/contracts/tm-bundle-merkle/private.js +6 -19
- package/dist/contracts/tm-bundle-merkle/swap-buy-first.js +2 -7
- package/dist/contracts/tm-bundle-merkle/swap-internal.d.ts +1 -1
- package/dist/contracts/tm-bundle-merkle/swap-internal.js +9 -2
- package/dist/contracts/tm-bundle-merkle/swap.js +1 -3
- package/dist/contracts/tm-bundle-merkle/types.d.ts +20 -0
- package/dist/contracts/tm-bundle-merkle/utils.js +164 -175
- package/dist/dex/direct-router.d.ts +2 -1
- package/dist/dex/direct-router.js +25 -140
- package/dist/flap/constants.d.ts +2 -1
- package/dist/flap/constants.js +2 -1
- package/dist/flap/meta.js +6 -4
- package/dist/flap/portal-bundle-merkle/config.js +6 -12
- package/dist/flap/portal-bundle-merkle/core.js +8 -11
- package/dist/flap/portal-bundle-merkle/pancake-proxy.d.ts +12 -10
- package/dist/flap/portal-bundle-merkle/pancake-proxy.js +307 -370
- package/dist/flap/portal-bundle-merkle/private.js +1 -1
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +12 -30
- package/dist/flap/portal-bundle-merkle/swap.js +13 -26
- package/dist/flap/portal-bundle-merkle/types.d.ts +22 -2
- package/dist/flap/portal-bundle-merkle/utils.js +11 -16
- package/dist/index.d.ts +3 -2
- package/dist/index.js +9 -2
- package/dist/pancake/bundle-buy-first.js +56 -38
- package/dist/pancake/bundle-swap.js +114 -61
- package/dist/utils/bundle-helpers.d.ts +28 -0
- package/dist/utils/bundle-helpers.js +64 -0
- package/dist/utils/constants.d.ts +23 -1
- package/dist/utils/constants.js +37 -7
- package/dist/utils/erc20.js +17 -25
- package/dist/utils/lp-inspect.js +9 -20
- package/dist/utils/private-sale.js +1 -2
- package/dist/utils/quote-helpers.js +3 -29
- package/dist/utils/swap-helpers.js +1 -6
- package/dist/utils/wallet.d.ts +8 -13
- package/dist/utils/wallet.js +154 -342
- package/package.json +1 -1
|
@@ -1,32 +1,19 @@
|
|
|
1
1
|
import { ethers, Wallet } from 'ethers';
|
|
2
2
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
3
|
-
import { ADDRESSES } from '../../utils/constants.js';
|
|
4
|
-
import {
|
|
5
|
-
} from './config.js';
|
|
6
|
-
// ✅ BlockRazor Builder EOA 地址(用于贿赂)
|
|
7
|
-
const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
|
|
8
|
-
// ✅ 已移除授权检查(前端负责确保授权)
|
|
3
|
+
import { ADDRESSES, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
|
|
4
|
+
import { TM2_ABI } from '../../abis/common.js';
|
|
5
|
+
import { getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount } from './config.js';
|
|
9
6
|
import { trySell } from '../tm.js';
|
|
10
7
|
const CHAIN_ID = 56;
|
|
11
8
|
const DEFAULT_GAS_LIMIT = 800000;
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable',
|
|
15
|
-
'function sellToken(uint256 origin, address token, uint256 amount, uint256 minFunds)'
|
|
16
|
-
];
|
|
17
|
-
// ==================== ERC20 ABI ====================
|
|
18
|
-
const ERC20_ABI = [
|
|
19
|
-
'function approve(address spender, uint256 amount) returns (bool)',
|
|
20
|
-
'function allowance(address owner, address spender) view returns (uint256)',
|
|
21
|
-
'function decimals() view returns (uint8)'
|
|
22
|
-
];
|
|
9
|
+
// ✅ TM2_ABI 从公共模块导入
|
|
10
|
+
// ✅ 本地 getGasLimit(FourAnyConfig 支持 bigint gasLimit)
|
|
23
11
|
function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
|
|
24
12
|
if (config.gasLimit !== undefined) {
|
|
25
13
|
return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
|
|
26
14
|
}
|
|
27
15
|
const multiplier = config.gasLimitMultiplier ?? 1.0;
|
|
28
|
-
|
|
29
|
-
return BigInt(calculatedGas);
|
|
16
|
+
return BigInt(Math.ceil(defaultGas * multiplier));
|
|
30
17
|
}
|
|
31
18
|
/**
|
|
32
19
|
* 私有购买(单笔)(仅签名版本 - 不依赖 Merkle)
|
|
@@ -5,16 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { ethers, Contract, Wallet } from 'ethers';
|
|
7
7
|
import { NonceManager, getOptimizedGasPrice } from '../../utils/bundle-helpers.js';
|
|
8
|
-
import { ADDRESSES } from '../../utils/constants.js';
|
|
8
|
+
import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
|
|
9
9
|
import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
|
|
10
10
|
import { getTxType, getGasPriceConfig, getProfitRecipient, getBribeAmount } from './config.js';
|
|
11
|
-
import { PROFIT_CONFIG } from '../../utils/constants.js';
|
|
12
11
|
import { trySell } from '../tm.js';
|
|
13
|
-
// ✅
|
|
14
|
-
const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
|
|
15
|
-
/**
|
|
16
|
-
* 获取 Gas Limit(支持 FourAnyConfig)
|
|
17
|
-
*/
|
|
12
|
+
// ✅ 本地 getGasLimit(FourAnyConfig 支持 bigint gasLimit)
|
|
18
13
|
function getGasLimit(config, defaultGas = 800000) {
|
|
19
14
|
if (config.gasLimit !== undefined) {
|
|
20
15
|
return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
// 共享的 Swap 内部常量与 ABI
|
|
1
|
+
// 共享的 Swap 内部常量与 ABI
|
|
2
|
+
// ✅ 从公共模块重新导出
|
|
3
|
+
import { TM2_ABI, HELPER3_ABI as _HELPER3_ABI } from '../../abis/common.js';
|
|
4
|
+
import { ADDRESSES } from '../../utils/constants.js';
|
|
5
|
+
// ✅ Four.meme 内盘 ABI(与公共 TM2_ABI 扩展)
|
|
2
6
|
export const TM_ABI = [
|
|
7
|
+
...TM2_ABI,
|
|
3
8
|
'function sellToken(uint256 origin, address token, uint256 amount, uint256 minFunds) returns (uint256)',
|
|
4
9
|
'function buyTokenAMAP(uint256 origin, address token, address to, uint256 funds, uint256 minAmount) payable returns (uint256)'
|
|
5
10
|
];
|
|
11
|
+
// ✅ Helper3 ABI(与公共 HELPER3_ABI 扩展)
|
|
6
12
|
export const HELPER3_ABI = [
|
|
13
|
+
..._HELPER3_ABI,
|
|
7
14
|
'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)',
|
|
8
15
|
'function trySell(address token, uint256 amount) view returns (address tokenManager, address quote, uint256 funds, uint256 fee)'
|
|
9
16
|
];
|
|
10
|
-
export const TM_ADDRESS =
|
|
17
|
+
export const TM_ADDRESS = ADDRESSES.BSC.TokenManagerV2Proxy;
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
import { ethers, Contract, Wallet } from 'ethers';
|
|
7
7
|
import { calculateSellAmount } from '../../utils/swap-helpers.js';
|
|
8
8
|
import { NonceManager, getOptimizedGasPrice, getGasLimit, getGasPriceConfig, getTxType } from '../../utils/bundle-helpers.js';
|
|
9
|
-
import { ADDRESSES, PROFIT_CONFIG } from '../../utils/constants.js';
|
|
9
|
+
import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
|
|
10
10
|
import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
|
|
11
11
|
import { getBribeAmount } from './config.js';
|
|
12
|
-
// ✅ BlockRazor Builder EOA 地址(用于贿赂)
|
|
13
|
-
const BLOCKRAZOR_BUILDER_EOA = '0x1266C6bE60392A8Ff346E8d5ECCd3E69dD9c5F20';
|
|
14
12
|
/**
|
|
15
13
|
* Four内盘捆绑换手
|
|
16
14
|
*/
|
|
@@ -334,6 +334,10 @@ export type FourPancakeProxyBatchBuyParams = {
|
|
|
334
334
|
v3Fee?: number;
|
|
335
335
|
v3LpAddresses?: string[];
|
|
336
336
|
v3ExactTokenIn?: string;
|
|
337
|
+
/** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
|
|
338
|
+
v3Tokens?: string[];
|
|
339
|
+
/** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
|
|
340
|
+
v3Fees?: number[];
|
|
337
341
|
minOutputAmounts?: (string | bigint)[];
|
|
338
342
|
config: FourBundleMerkleConfig;
|
|
339
343
|
};
|
|
@@ -348,6 +352,10 @@ export type FourPancakeProxyBatchBuySignParams = {
|
|
|
348
352
|
v3Fee?: number;
|
|
349
353
|
v3LpAddresses?: string[];
|
|
350
354
|
v3ExactTokenIn?: string;
|
|
355
|
+
/** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
|
|
356
|
+
v3Tokens?: string[];
|
|
357
|
+
/** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
|
|
358
|
+
v3Fees?: number[];
|
|
351
359
|
minOutputAmounts?: (string | bigint)[];
|
|
352
360
|
config: FourSignConfig;
|
|
353
361
|
};
|
|
@@ -364,6 +372,10 @@ export type FourPancakeProxyBatchSellParams = {
|
|
|
364
372
|
v3Fee?: number;
|
|
365
373
|
v3LpAddresses?: string[];
|
|
366
374
|
v3ExactTokenIn?: string;
|
|
375
|
+
/** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
|
|
376
|
+
v3Tokens?: string[];
|
|
377
|
+
/** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
|
|
378
|
+
v3Fees?: number[];
|
|
367
379
|
minOutputAmounts?: (string | bigint)[];
|
|
368
380
|
config: FourBundleMerkleConfig;
|
|
369
381
|
};
|
|
@@ -378,6 +390,10 @@ export type FourPancakeProxyBatchSellSignParams = {
|
|
|
378
390
|
v3Fee?: number;
|
|
379
391
|
v3LpAddresses?: string[];
|
|
380
392
|
v3ExactTokenIn?: string;
|
|
393
|
+
/** V3 多跳:代币路径 [tokenIn, tokenMid1, ..., tokenOut] */
|
|
394
|
+
v3Tokens?: string[];
|
|
395
|
+
/** V3 多跳:费率路径 [fee0, fee1, ...] - 长度 = v3Tokens.length - 1 */
|
|
396
|
+
v3Fees?: number[];
|
|
381
397
|
minOutputAmounts?: (string | bigint)[];
|
|
382
398
|
config: FourSignConfig;
|
|
383
399
|
};
|
|
@@ -388,12 +404,16 @@ export type FourPancakeProxyApprovalParams = {
|
|
|
388
404
|
tokenAddress: string;
|
|
389
405
|
amount: string | 'max';
|
|
390
406
|
rpcUrl: string;
|
|
407
|
+
/** 路由类型,用于确定授权目标(V2 或 V3 Router) */
|
|
408
|
+
routeType?: 'v2' | 'v3-single' | 'v3-multi';
|
|
391
409
|
};
|
|
392
410
|
export type FourPancakeProxyApprovalBatchParams = {
|
|
393
411
|
privateKeys: string[];
|
|
394
412
|
tokenAddress: string;
|
|
395
413
|
amounts: (string | 'max')[];
|
|
396
414
|
config: FourSignConfig;
|
|
415
|
+
/** 路由类型,用于确定授权目标(V2 或 V3 Router) */
|
|
416
|
+
routeType?: 'v2' | 'v3-single' | 'v3-multi';
|
|
397
417
|
};
|
|
398
418
|
/** ✅ PancakeProxy 批量授权结果 */
|
|
399
419
|
export type FourPancakeProxyApprovalBatchResult = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ethers, Wallet } from 'ethers';
|
|
2
2
|
import { getOptimizedGasPrice, NonceManager } from '../../utils/bundle-helpers.js';
|
|
3
|
-
import { PROFIT_CONFIG } from '../../utils/constants.js';
|
|
3
|
+
import { PROFIT_CONFIG, ZERO_ADDRESS } from '../../utils/constants.js';
|
|
4
4
|
import { getTxType, getGasPriceConfig, shouldExtractProfit, getProfitRecipient } from './config.js';
|
|
5
5
|
import { getErc20DecimalsMerkle as _getErc20DecimalsMerkle, generateHopWallets as _generateHopWallets, normalizeAmounts as _normalizeAmounts, batchGetBalances as _batchGetBalances, calculateGasLimit as _calculateGasLimit, isNativeTokenAddress as _isNativeTokenAddress } from './internal.js';
|
|
6
6
|
// ==================== 本地利润计算(万分之三)====================
|
|
@@ -56,8 +56,7 @@ async function getFourInnerQuote(rpcUrl, tokenAddress, tokenAmount) {
|
|
|
56
56
|
return 0n;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
59
|
+
// ✅ ZERO_ADDRESS 从公共模块导入
|
|
61
60
|
/**
|
|
62
61
|
* 获取 FLAP 内盘代币 → 原生代币的报价
|
|
63
62
|
* ✅ 与 portal-bundle-merkle/core.ts 使用相同的 quoteExactInput 方法
|
|
@@ -893,7 +892,7 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
893
892
|
}
|
|
894
893
|
else {
|
|
895
894
|
// ========== 有多跳:构建跳转链归集 ==========
|
|
896
|
-
//
|
|
895
|
+
// ✅ 优化版:批量计算 + 批量获取 nonce + 并行签名
|
|
897
896
|
const sourceWallets = actualKeys.map(pk => new Wallet(pk, provider));
|
|
898
897
|
const withHopIndexes = [];
|
|
899
898
|
const withoutHopIndexes = [];
|
|
@@ -906,7 +905,7 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
906
905
|
}
|
|
907
906
|
}
|
|
908
907
|
const sourceAddresses = sourceWallets.map(w => w.address);
|
|
909
|
-
// ✅ 优化:并行获取 gasPrice、decimals
|
|
908
|
+
// ✅ 优化:并行获取 gasPrice、decimals、余额
|
|
910
909
|
const [gasPrice, decimals, balances, bnbBalances] = await Promise.all([
|
|
911
910
|
getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
912
911
|
isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum)),
|
|
@@ -916,122 +915,107 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
916
915
|
const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
917
916
|
const gasFeePerHop = finalGasLimit * gasPrice;
|
|
918
917
|
const nonceManager = new NonceManager(provider);
|
|
919
|
-
// ✅
|
|
918
|
+
// ✅ 用于记录每个钱包的归集金额
|
|
920
919
|
const sweepAmounts = new Array(sourceWallets.length).fill(0n);
|
|
921
|
-
//
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
950
|
-
const amt = ethers.parseEther(amountStrForI);
|
|
951
|
-
const need = amt + gasCost;
|
|
952
|
-
if (!skipIfInsufficient || bal >= need)
|
|
953
|
-
toSend = amt;
|
|
954
|
-
}
|
|
955
|
-
if (toSend > 0n) {
|
|
956
|
-
sweepAmounts[i] = toSend; // ✅ 记录归集金额
|
|
957
|
-
totalAmountBeforeProfit += toSend;
|
|
958
|
-
const nonce = await nonceManager.getNextNonce(sourceWallet);
|
|
959
|
-
const tx = await sourceWallet.signTransaction({
|
|
960
|
-
to: target,
|
|
961
|
-
value: toSend,
|
|
962
|
-
nonce,
|
|
963
|
-
gasPrice,
|
|
964
|
-
gasLimit: nativeGasLimit,
|
|
965
|
-
chainId: chainIdNum,
|
|
966
|
-
type: txType
|
|
967
|
-
});
|
|
968
|
-
signedTxs.push(tx);
|
|
969
|
-
}
|
|
920
|
+
// ✅ 辅助函数:获取比例和金额
|
|
921
|
+
const getRatioForI = (i) => {
|
|
922
|
+
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
923
|
+
return clamp(sources[i].ratioPct);
|
|
924
|
+
if (ratios && ratios[i] !== undefined)
|
|
925
|
+
return clamp(ratios[i]);
|
|
926
|
+
return ratio;
|
|
927
|
+
};
|
|
928
|
+
const getAmountStrForI = (i) => {
|
|
929
|
+
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
930
|
+
return String(sources[i].amount);
|
|
931
|
+
if (amounts && amounts[i] !== undefined)
|
|
932
|
+
return String(amounts[i]);
|
|
933
|
+
return amount !== undefined ? String(amount) : undefined;
|
|
934
|
+
};
|
|
935
|
+
const noHopTxDataList = [];
|
|
936
|
+
for (const i of withoutHopIndexes) {
|
|
937
|
+
const bal = balances[i];
|
|
938
|
+
let toSend = 0n;
|
|
939
|
+
const ratioForI = getRatioForI(i);
|
|
940
|
+
const amountStrForI = getAmountStrForI(i);
|
|
941
|
+
if (isNative) {
|
|
942
|
+
const gasCost = nativeGasLimit * gasPrice;
|
|
943
|
+
if (ratioForI !== undefined) {
|
|
944
|
+
const want = (bal * BigInt(ratioForI)) / 100n;
|
|
945
|
+
const maxSendable = bal > gasCost ? (bal - gasCost) : 0n;
|
|
946
|
+
toSend = want > maxSendable ? maxSendable : want;
|
|
970
947
|
}
|
|
971
|
-
else {
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
return clamp(ratios[i]);
|
|
977
|
-
return ratio;
|
|
978
|
-
})();
|
|
979
|
-
const amountStrForI = (() => {
|
|
980
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
981
|
-
return String(sources[i].amount);
|
|
982
|
-
if (amounts && amounts[i] !== undefined)
|
|
983
|
-
return String(amounts[i]);
|
|
984
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
985
|
-
})();
|
|
986
|
-
if (ratioForI !== undefined) {
|
|
987
|
-
toSend = (bal * BigInt(ratioForI)) / 100n;
|
|
988
|
-
}
|
|
989
|
-
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
990
|
-
toSend = ethers.parseUnits(amountStrForI, decimals);
|
|
991
|
-
}
|
|
992
|
-
if (toSend > 0n && (!skipIfInsufficient || bal >= toSend)) {
|
|
993
|
-
sweepAmounts[i] = toSend; // ✅ 记录归集金额
|
|
994
|
-
totalAmountBeforeProfit += toSend;
|
|
995
|
-
const nonce = await nonceManager.getNextNonce(sourceWallet);
|
|
996
|
-
const data = iface.encodeFunctionData('transfer', [target, toSend]);
|
|
997
|
-
const tx = await sourceWallet.signTransaction({
|
|
998
|
-
to: tokenAddress,
|
|
999
|
-
data,
|
|
1000
|
-
value: 0n,
|
|
1001
|
-
nonce,
|
|
1002
|
-
gasPrice,
|
|
1003
|
-
gasLimit: finalGasLimit,
|
|
1004
|
-
chainId: chainIdNum,
|
|
1005
|
-
type: txType
|
|
1006
|
-
});
|
|
1007
|
-
signedTxs.push(tx);
|
|
1008
|
-
}
|
|
948
|
+
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
949
|
+
const amt = ethers.parseEther(amountStrForI);
|
|
950
|
+
const need = amt + gasCost;
|
|
951
|
+
if (!skipIfInsufficient || bal >= need)
|
|
952
|
+
toSend = amt;
|
|
1009
953
|
}
|
|
1010
954
|
}
|
|
955
|
+
else {
|
|
956
|
+
if (ratioForI !== undefined) {
|
|
957
|
+
toSend = (bal * BigInt(ratioForI)) / 100n;
|
|
958
|
+
}
|
|
959
|
+
else if (amountStrForI && amountStrForI.trim().length > 0) {
|
|
960
|
+
toSend = ethers.parseUnits(amountStrForI, decimals);
|
|
961
|
+
}
|
|
962
|
+
if (skipIfInsufficient && bal < toSend)
|
|
963
|
+
toSend = 0n;
|
|
964
|
+
}
|
|
965
|
+
if (toSend > 0n) {
|
|
966
|
+
sweepAmounts[i] = toSend;
|
|
967
|
+
totalAmountBeforeProfit += toSend;
|
|
968
|
+
noHopTxDataList.push({ index: i, wallet: sourceWallets[i], toSend });
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// ========== 第2步:批量获取无跳转钱包的 nonces ==========
|
|
972
|
+
const noHopWallets = noHopTxDataList.map(d => d.wallet);
|
|
973
|
+
const noHopNonces = noHopWallets.length > 0
|
|
974
|
+
? await nonceManager.getNextNoncesForWallets(noHopWallets)
|
|
975
|
+
: [];
|
|
976
|
+
// ========== 第3步:并行签名无跳转交易 ==========
|
|
977
|
+
const noHopTxPromises = noHopTxDataList.map((data, idx) => {
|
|
978
|
+
const { wallet, toSend } = data;
|
|
979
|
+
const nonce = noHopNonces[idx];
|
|
980
|
+
if (isNative) {
|
|
981
|
+
return wallet.signTransaction({
|
|
982
|
+
to: target,
|
|
983
|
+
value: toSend,
|
|
984
|
+
nonce,
|
|
985
|
+
gasPrice,
|
|
986
|
+
gasLimit: nativeGasLimit,
|
|
987
|
+
chainId: chainIdNum,
|
|
988
|
+
type: txType
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
const txData = iface.encodeFunctionData('transfer', [target, toSend]);
|
|
993
|
+
return wallet.signTransaction({
|
|
994
|
+
to: tokenAddress,
|
|
995
|
+
data: txData,
|
|
996
|
+
value: 0n,
|
|
997
|
+
nonce,
|
|
998
|
+
gasPrice,
|
|
999
|
+
gasLimit: finalGasLimit,
|
|
1000
|
+
chainId: chainIdNum,
|
|
1001
|
+
type: txType
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
if (noHopTxPromises.length > 0) {
|
|
1006
|
+
const noHopSignedTxs = await Promise.all(noHopTxPromises);
|
|
1007
|
+
signedTxs.push(...noHopSignedTxs);
|
|
1011
1008
|
}
|
|
1012
|
-
|
|
1009
|
+
const hopTxDataList = [];
|
|
1013
1010
|
for (const i of withHopIndexes) {
|
|
1014
1011
|
const sourceWallet = sourceWallets[i];
|
|
1015
1012
|
const hopChain = preparedHops[i];
|
|
1016
|
-
// 计算要归集的金额
|
|
1017
|
-
let toSend = 0n;
|
|
1018
1013
|
const bal = balances[i];
|
|
1014
|
+
let toSend = 0n;
|
|
1015
|
+
const ratioForI = getRatioForI(i);
|
|
1016
|
+
const amountStrForI = getAmountStrForI(i);
|
|
1019
1017
|
if (isNative) {
|
|
1020
1018
|
const totalGasCost = finalGasLimit * gasPrice * BigInt(hopChain.length + 1);
|
|
1021
|
-
const ratioForI = (() => {
|
|
1022
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
1023
|
-
return clamp(sources[i].ratioPct);
|
|
1024
|
-
if (ratios && ratios[i] !== undefined)
|
|
1025
|
-
return clamp(ratios[i]);
|
|
1026
|
-
return ratio;
|
|
1027
|
-
})();
|
|
1028
|
-
const amountStrForI = (() => {
|
|
1029
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
1030
|
-
return String(sources[i].amount);
|
|
1031
|
-
if (amounts && amounts[i] !== undefined)
|
|
1032
|
-
return String(amounts[i]);
|
|
1033
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
1034
|
-
})();
|
|
1035
1019
|
if (ratioForI !== undefined) {
|
|
1036
1020
|
const want = (bal * BigInt(ratioForI)) / 100n;
|
|
1037
1021
|
const maxSendable = bal > totalGasCost ? (bal - totalGasCost) : 0n;
|
|
@@ -1045,25 +1029,10 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
1045
1029
|
}
|
|
1046
1030
|
}
|
|
1047
1031
|
else {
|
|
1048
|
-
// ERC20:检查 BNB 余额是否足够支付 gas
|
|
1049
1032
|
const bnbBal = bnbBalances[i];
|
|
1050
1033
|
const bnbNeeded = (gasFeePerHop * BigInt(hopChain.length)) + (finalGasLimit * gasPrice);
|
|
1051
1034
|
if (bnbBal < bnbNeeded && skipIfInsufficient)
|
|
1052
1035
|
continue;
|
|
1053
|
-
const ratioForI = (() => {
|
|
1054
|
-
if (sources && sources[i] && sources[i].ratioPct !== undefined)
|
|
1055
|
-
return clamp(sources[i].ratioPct);
|
|
1056
|
-
if (ratios && ratios[i] !== undefined)
|
|
1057
|
-
return clamp(ratios[i]);
|
|
1058
|
-
return ratio;
|
|
1059
|
-
})();
|
|
1060
|
-
const amountStrForI = (() => {
|
|
1061
|
-
if (sources && sources[i] && sources[i].amount !== undefined)
|
|
1062
|
-
return String(sources[i].amount);
|
|
1063
|
-
if (amounts && amounts[i] !== undefined)
|
|
1064
|
-
return String(amounts[i]);
|
|
1065
|
-
return amount !== undefined ? String(amount) : undefined;
|
|
1066
|
-
})();
|
|
1067
1036
|
if (ratioForI !== undefined) {
|
|
1068
1037
|
toSend = (bal * BigInt(ratioForI)) / 100n;
|
|
1069
1038
|
}
|
|
@@ -1075,76 +1044,96 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
1075
1044
|
}
|
|
1076
1045
|
if (toSend <= 0n)
|
|
1077
1046
|
continue;
|
|
1078
|
-
// ✅ 记录归集金额
|
|
1079
1047
|
sweepAmounts[i] = toSend;
|
|
1080
1048
|
totalAmountBeforeProfit += toSend;
|
|
1081
|
-
// 构建跳转链: 子钱包 -> 中转1 -> 中转2 -> ... -> 目标地址
|
|
1082
1049
|
const fullChain = [sourceWallet, ...hopChain.map(pk => new Wallet(pk, provider))];
|
|
1083
1050
|
const addresses = [...fullChain.map(w => w.address), target];
|
|
1051
|
+
hopTxDataList.push({ index: i, sourceWallet, toSend, hopChain, fullChain, addresses });
|
|
1052
|
+
}
|
|
1053
|
+
// ========== 第5步:计算每个有跳转钱包需要的 nonce 数量并批量获取 ==========
|
|
1054
|
+
// 每个源钱包需要的 nonce 数量:
|
|
1055
|
+
// - ERC20: hopChain.length (gas费交易) + 1 (主转账)
|
|
1056
|
+
// - Native: 1 (主转账)
|
|
1057
|
+
const hopNonceNeeds = hopTxDataList.map(d => isNative ? 1 : d.hopChain.length + 1);
|
|
1058
|
+
const hopSourceWallets = hopTxDataList.map(d => d.sourceWallet);
|
|
1059
|
+
// ✅ 批量获取所有有跳转钱包的 nonces
|
|
1060
|
+
const hopNoncesFlat = [];
|
|
1061
|
+
if (hopSourceWallets.length > 0) {
|
|
1062
|
+
const batchNoncePromises = hopSourceWallets.map((w, idx) => nonceManager.getNextNonceBatch(w, hopNonceNeeds[idx]));
|
|
1063
|
+
const batchNonces = await Promise.all(batchNoncePromises);
|
|
1064
|
+
batchNonces.forEach(nonces => hopNoncesFlat.push(...nonces));
|
|
1065
|
+
}
|
|
1066
|
+
const hopTxsToSign = [];
|
|
1067
|
+
let nonceOffset = 0;
|
|
1068
|
+
for (let dataIdx = 0; dataIdx < hopTxDataList.length; dataIdx++) {
|
|
1069
|
+
const { sourceWallet, toSend, hopChain, fullChain, addresses } = hopTxDataList[dataIdx];
|
|
1070
|
+
const nonceCount = hopNonceNeeds[dataIdx];
|
|
1071
|
+
const nonces = hopNoncesFlat.slice(nonceOffset, nonceOffset + nonceCount);
|
|
1072
|
+
nonceOffset += nonceCount;
|
|
1073
|
+
let nonceIdx = 0;
|
|
1084
1074
|
// ERC20 多跳:先给中转钱包转 gas 费
|
|
1085
1075
|
if (!isNative) {
|
|
1086
|
-
const gasNonces = await nonceManager.getNextNonceBatch(sourceWallet, hopChain.length);
|
|
1087
|
-
const gasTxs = [];
|
|
1088
1076
|
for (let j = 0; j < hopChain.length; j++) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1077
|
+
hopTxsToSign.push({
|
|
1078
|
+
wallet: sourceWallet,
|
|
1079
|
+
tx: {
|
|
1080
|
+
to: fullChain[j + 1].address,
|
|
1081
|
+
value: gasFeePerHop,
|
|
1082
|
+
nonce: nonces[nonceIdx++],
|
|
1083
|
+
gasPrice,
|
|
1084
|
+
gasLimit: nativeGasLimit,
|
|
1085
|
+
chainId: chainIdNum,
|
|
1086
|
+
type: txType
|
|
1087
|
+
}
|
|
1097
1088
|
});
|
|
1098
|
-
gasTxs.push(tx);
|
|
1099
1089
|
}
|
|
1100
|
-
signedTxs.push(...gasTxs);
|
|
1101
1090
|
}
|
|
1102
1091
|
// 执行主要的归集链
|
|
1103
1092
|
for (let j = 0; j < addresses.length - 1; j++) {
|
|
1104
1093
|
const fromWallet = fullChain[j];
|
|
1105
1094
|
const toAddress = addresses[j + 1];
|
|
1106
|
-
|
|
1107
|
-
const nonce = j === 0
|
|
1108
|
-
? await nonceManager.getNextNonce(sourceWallet)
|
|
1109
|
-
: 0;
|
|
1095
|
+
const nonce = j === 0 ? nonces[nonceIdx++] : 0;
|
|
1110
1096
|
if (isNative) {
|
|
1111
|
-
// 原生币归集:每一跳需要附加后续跳数的gas费
|
|
1112
|
-
// toSend = 最终主钱包收到的净金额
|
|
1113
|
-
// 剩余跳数 = 还需要几跳才能到目标地址
|
|
1114
1097
|
const remainingHops = addresses.length - 2 - j;
|
|
1115
1098
|
const additionalGas = gasFeePerHop * BigInt(remainingHops);
|
|
1116
1099
|
const valueToTransfer = toSend + additionalGas;
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1100
|
+
hopTxsToSign.push({
|
|
1101
|
+
wallet: fromWallet,
|
|
1102
|
+
tx: {
|
|
1103
|
+
to: toAddress,
|
|
1104
|
+
value: valueToTransfer,
|
|
1105
|
+
nonce,
|
|
1106
|
+
gasPrice,
|
|
1107
|
+
gasLimit: finalGasLimit,
|
|
1108
|
+
chainId: chainIdNum,
|
|
1109
|
+
type: txType
|
|
1110
|
+
}
|
|
1128
1111
|
});
|
|
1129
|
-
signedTxs.push(tx);
|
|
1130
1112
|
}
|
|
1131
1113
|
else {
|
|
1132
1114
|
const data = iface.encodeFunctionData('transfer', [toAddress, toSend]);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1115
|
+
hopTxsToSign.push({
|
|
1116
|
+
wallet: fromWallet,
|
|
1117
|
+
tx: {
|
|
1118
|
+
to: tokenAddress,
|
|
1119
|
+
data,
|
|
1120
|
+
value: 0n,
|
|
1121
|
+
nonce,
|
|
1122
|
+
gasPrice,
|
|
1123
|
+
gasLimit: finalGasLimit,
|
|
1124
|
+
chainId: chainIdNum,
|
|
1125
|
+
type: txType
|
|
1126
|
+
}
|
|
1142
1127
|
});
|
|
1143
|
-
signedTxs.push(tx);
|
|
1144
1128
|
}
|
|
1145
1129
|
}
|
|
1146
1130
|
}
|
|
1147
|
-
// ✅
|
|
1131
|
+
// ✅ 并行签名所有有跳转的交易
|
|
1132
|
+
if (hopTxsToSign.length > 0) {
|
|
1133
|
+
const hopSignedTxs = await Promise.all(hopTxsToSign.map(({ wallet, tx }) => wallet.signTransaction(tx)));
|
|
1134
|
+
signedTxs.push(...hopSignedTxs);
|
|
1135
|
+
}
|
|
1136
|
+
// ========== 第7步:计算利润并添加利润转账 ==========
|
|
1148
1137
|
if (extractProfit && totalAmountBeforeProfit > 0n) {
|
|
1149
1138
|
// 找出归集金额最大的钱包作为支付者
|
|
1150
1139
|
let maxSweepIndex = -1;
|
|
@@ -1161,26 +1150,26 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
1161
1150
|
if (sweepAmounts[i] > 0n) {
|
|
1162
1151
|
const { profit } = calculateProfit(sweepAmounts[i]);
|
|
1163
1152
|
if (isNative) {
|
|
1164
|
-
totalProfit += profit;
|
|
1153
|
+
totalProfit += profit;
|
|
1165
1154
|
}
|
|
1166
1155
|
else {
|
|
1167
|
-
totalTokenProfit += profit;
|
|
1156
|
+
totalTokenProfit += profit;
|
|
1168
1157
|
}
|
|
1169
1158
|
}
|
|
1170
1159
|
}
|
|
1171
|
-
// ✅ ERC20
|
|
1160
|
+
// ✅ ERC20:获取代币利润等值的原生代币报价
|
|
1172
1161
|
let nativeProfitAmount = isNative ? totalProfit : 0n;
|
|
1173
1162
|
if (!isNative && totalTokenProfit > 0n) {
|
|
1174
1163
|
nativeProfitAmount = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
|
|
1175
|
-
totalProfit = nativeProfitAmount;
|
|
1164
|
+
totalProfit = nativeProfitAmount;
|
|
1176
1165
|
}
|
|
1177
|
-
//
|
|
1166
|
+
// 由归集金额最大的钱包支付利润
|
|
1178
1167
|
if (nativeProfitAmount > 0n && maxSweepIndex >= 0) {
|
|
1179
1168
|
const payerWallet = sourceWallets[maxSweepIndex];
|
|
1180
1169
|
const profitNonce = await nonceManager.getNextNonce(payerWallet);
|
|
1181
1170
|
const profitTx = await payerWallet.signTransaction({
|
|
1182
1171
|
to: getProfitRecipient(),
|
|
1183
|
-
value: nativeProfitAmount,
|
|
1172
|
+
value: nativeProfitAmount,
|
|
1184
1173
|
nonce: profitNonce,
|
|
1185
1174
|
gasPrice,
|
|
1186
1175
|
gasLimit: 21000n,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 收费方式:交易末尾附加利润提取交易(和内盘一致)
|
|
9
9
|
*/
|
|
10
|
-
/** Router 地址配置 */
|
|
10
|
+
/** Router 地址配置 - ✅ 使用公共模块中的地址 */
|
|
11
11
|
export declare const DIRECT_ROUTERS: {
|
|
12
12
|
readonly BSC: {
|
|
13
13
|
readonly PANCAKESWAP_V2: "0x10ED43C718714eb63d5aA57B78B54704E256024E";
|
|
@@ -31,6 +31,7 @@ export declare const DIRECT_ROUTERS: {
|
|
|
31
31
|
readonly WOKB: "0xe538905cf8410324e03a5a23c1c177a474d59b2b";
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
|
+
/** ERC20 ABI - ✅ 从公共模块导入 */
|
|
34
35
|
export interface DirectRouterSignConfig {
|
|
35
36
|
rpcUrl: string;
|
|
36
37
|
chainId?: number;
|