four-flap-meme-sdk 2.0.0 → 2.2.0
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/__tests__/subpath-exports.test.js +64 -0
- package/dist/chains/bsc/iro.d.ts +5 -0
- package/dist/chains/bsc/iro.js +4 -0
- package/dist/chains/eni/flat-aliases.d.ts +10 -0
- package/dist/chains/eni/flat-aliases.js +8 -0
- package/dist/chains/eni/index.d.ts +1 -0
- package/dist/chains/eni/index.js +1 -0
- package/dist/chains/index.d.ts +13 -0
- package/dist/chains/index.js +13 -0
- package/dist/chains/xlayer/eip7702/flat-aliases.d.ts +13 -0
- package/dist/chains/xlayer/eip7702/flat-aliases.js +10 -0
- package/dist/chains/xlayer/eip7702/index.d.ts +1 -0
- package/dist/chains/xlayer/eip7702/index.js +1 -0
- package/dist/chains/xlayer/index.d.ts +3 -2
- package/dist/chains/xlayer/index.js +4 -7
- package/dist/flap/index.d.ts +10 -0
- package/dist/flap/index.js +8 -0
- package/dist/merkle/index.d.ts +12 -0
- package/dist/merkle/index.js +11 -0
- package/dist/shared/constants/index.d.ts +2 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/vanity/index.d.ts +5 -0
- package/dist/vanity/index.js +5 -0
- package/package.json +93 -2
- package/dist/chains/bsc/four/disperse.d.ts +0 -12
- package/dist/chains/bsc/four/disperse.js +0 -470
- package/dist/chains/bsc/four/pairwise.d.ts +0 -7
- package/dist/chains/bsc/four/pairwise.js +0 -308
- package/dist/chains/bsc/four/submit/blockrazor.d.ts +0 -18
- package/dist/chains/bsc/four/submit/blockrazor.js +0 -86
- package/dist/chains/bsc/four/submit/direct.d.ts +0 -66
- package/dist/chains/bsc/four/submit/direct.js +0 -452
- package/dist/chains/bsc/four/submit/helpers.d.ts +0 -18
- package/dist/chains/bsc/four/submit/helpers.js +0 -57
- package/dist/chains/bsc/four/submit/index.d.ts +0 -12
- package/dist/chains/bsc/four/submit/index.js +0 -11
- package/dist/chains/bsc/four/submit/merkle.d.ts +0 -18
- package/dist/chains/bsc/four/submit/merkle.js +0 -74
- package/dist/chains/bsc/four/submit/types.d.ts +0 -143
- package/dist/chains/bsc/four/swap/index.d.ts +0 -32
- package/dist/chains/bsc/four/swap/index.js +0 -829
- package/dist/chains/bsc/four/swap/types.d.ts +0 -70
- package/dist/chains/bsc/four/swap/types.js +0 -1
- package/dist/chains/bsc/four/sweep.d.ts +0 -13
- package/dist/chains/bsc/four/sweep.js +0 -788
- package/dist/chains/bsc/four/utils/index.d.ts +0 -20
- package/dist/chains/bsc/four/utils/index.js +0 -1558
- package/dist/chains/bsc/four/utils/types.d.ts +0 -1
- package/dist/chains/bsc/four/utils/types.js +0 -1
- package/dist/chains/bsc/pancake/bundle-buy-first/index.d.ts +0 -8
- package/dist/chains/bsc/pancake/bundle-buy-first/index.js +0 -907
- package/dist/chains/bsc/pancake/bundle-buy-first/types.d.ts +0 -73
- package/dist/chains/bsc/pancake/bundle-buy-first/types.js +0 -1
- package/dist/chains/bsc/pancake/bundle-swap/helpers.d.ts +0 -102
- package/dist/chains/bsc/pancake/bundle-swap/helpers.js +0 -572
- package/dist/chains/bsc/pancake/bundle-swap/index.d.ts +0 -50
- package/dist/chains/bsc/pancake/bundle-swap/index.js +0 -1066
- package/dist/chains/bsc/pancake/bundle-swap/types.d.ts +0 -202
- package/dist/chains/bsc/pancake/bundle-swap/types.js +0 -3
- package/dist/chains/xlayer/eip7702/bundle-swap/index.d.ts +0 -72
- package/dist/chains/xlayer/eip7702/bundle-swap/index.js +0 -921
- package/dist/chains/xlayer/eip7702/bundle-swap/types.d.ts +0 -65
- package/dist/chains/xlayer/eip7702/bundle-swap/types.js +0 -1
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.d.ts +0 -128
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/index.js +0 -857
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.d.ts +0 -85
- package/dist/chains/xlayer/eip7702/multi-hop-transfer/types.js +0 -1
- package/dist/chains/xlayer/eip7702/volume/index.d.ts +0 -96
- package/dist/chains/xlayer/eip7702/volume/index.js +0 -793
- package/dist/chains/xlayer/eip7702/volume/types.d.ts +0 -124
- package/dist/chains/xlayer/eip7702/volume/types.js +0 -1
- package/dist/chains/xlayer/eoa/types-core.d.ts +0 -363
- package/dist/chains/xlayer/eoa/types-core.js +0 -53
- package/dist/chains/xlayer/eoa/types-create.d.ts +0 -413
- package/dist/chains/xlayer/eoa/types-create.js +0 -9
- package/dist/chains/xlayer/eoa/types-volume.d.ts +0 -209
- package/dist/chains/xlayer/eoa/types-volume.js +0 -13
- package/dist/dex/direct-router/index.d.ts +0 -70
- package/dist/dex/direct-router/index.js +0 -1410
- package/dist/dex/direct-router/types.d.ts +0 -81
- package/dist/dex/direct-router/types.js +0 -1
- package/dist/shared/abis/TaxToken.json +0 -969
- package/dist/shared/abis/TokenManager.json +0 -836
- package/dist/shared/abis/TokenManager2.json +0 -136
- package/dist/shared/abis/TokenManagerHelper3.json +0 -993
- package/dist/shared/abis 2/TaxToken.json +0 -105
- package/dist/shared/abis 2/TokenManager.json +0 -836
- package/dist/shared/abis 2/TokenManager2.json +0 -60
- package/dist/shared/abis 2/TokenManagerHelper3.json +0 -993
- package/dist/shared/abis 2/common.d.ts +0 -85
- package/dist/shared/abis 2/common.js +0 -254
- package/dist/shared/abis 2/index.d.ts +0 -8
- package/dist/shared/abis 2/index.js +0 -8
- package/dist/shared/clients 2/blockrazor.d.ts +0 -314
- package/dist/shared/clients 2/blockrazor.js +0 -596
- package/dist/shared/clients 2/club48.d.ts +0 -154
- package/dist/shared/clients 2/club48.js +0 -331
- package/dist/shared/clients 2/emitservice.d.ts +0 -47
- package/dist/shared/clients 2/emitservice.js +0 -44
- package/dist/shared/clients 2/four.d.ts +0 -132
- package/dist/shared/clients 2/four.js +0 -281
- package/dist/shared/clients 2/merkle.d.ts +0 -210
- package/dist/shared/clients 2/merkle.js +0 -400
- package/dist/shared/flap/__tests__/curve.test.d.ts +0 -1
- package/dist/shared/flap/__tests__/curve.test.js +0 -85
- package/dist/shared/flap/portal/index.d.ts +0 -12
- package/dist/shared/flap/portal/index.js +0 -11
- package/dist/shared/flap/portal/portal.d.ts +0 -47
- package/dist/shared/flap/portal/portal.js +0 -218
- package/dist/shared/flap/portal/types.d.ts +0 -227
- package/dist/shared/flap/portal/types.js +0 -80
- package/dist/shared/flap/portal/writer.d.ts +0 -121
- package/dist/shared/flap/portal/writer.js +0 -265
- package/dist/shared/flap/portal-bundle-merkle/core/index.d.ts +0 -18
- package/dist/shared/flap/portal-bundle-merkle/core/index.js +0 -938
- package/dist/shared/flap/portal-bundle-merkle/core/types.d.ts +0 -1
- package/dist/shared/flap/portal-bundle-merkle/core/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/swap/index.d.ts +0 -42
- package/dist/shared/flap/portal-bundle-merkle/swap/index.js +0 -1448
- package/dist/shared/flap/portal-bundle-merkle/swap/types.d.ts +0 -84
- package/dist/shared/flap/portal-bundle-merkle/swap/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/utils/index.d.ts +0 -17
- package/dist/shared/flap/portal-bundle-merkle/utils/index.js +0 -1024
- package/dist/shared/flap/portal-bundle-merkle/utils/types.d.ts +0 -16
- package/dist/shared/flap/portal-bundle-merkle/utils/types.js +0 -1
- package/dist/shared/flap/portal-bundle-merkle/utils-helpers.d.ts +0 -100
- package/dist/shared/flap/portal-bundle-merkle/utils-helpers.js +0 -133
- package/dist/shared/flap 2/__tests__/curve.test.d.ts +0 -1
- package/dist/shared/flap 2/__tests__/curve.test.js +0 -85
- package/dist/shared/flap 2/abi.d.ts +0 -4
- package/dist/shared/flap 2/abi.js +0 -4
- package/dist/shared/flap 2/constants.d.ts +0 -128
- package/dist/shared/flap 2/constants.js +0 -143
- package/dist/shared/flap 2/curve.d.ts +0 -33
- package/dist/shared/flap 2/curve.js +0 -84
- package/dist/shared/flap 2/errors.d.ts +0 -37
- package/dist/shared/flap 2/errors.js +0 -114
- package/dist/shared/flap 2/index.d.ts +0 -22
- package/dist/shared/flap 2/index.js +0 -33
- package/dist/shared/flap 2/ipfs.d.ts +0 -21
- package/dist/shared/flap 2/ipfs.js +0 -38
- package/dist/shared/flap 2/meta.d.ts +0 -30
- package/dist/shared/flap 2/meta.js +0 -195
- package/dist/shared/flap 2/permit.d.ts +0 -16
- package/dist/shared/flap 2/permit.js +0 -67
- package/dist/shared/flap 2/pinata.d.ts +0 -40
- package/dist/shared/flap 2/pinata.js +0 -106
- package/dist/shared/flap 2/portal-bundle-merkle/config.d.ts +0 -79
- package/dist/shared/flap 2/portal-bundle-merkle/config.js +0 -133
- package/dist/shared/flap 2/portal-bundle-merkle/core.d.ts +0 -18
- package/dist/shared/flap 2/portal-bundle-merkle/core.js +0 -938
- package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.d.ts +0 -125
- package/dist/shared/flap 2/portal-bundle-merkle/create-to-dex.js +0 -665
- package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.d.ts +0 -88
- package/dist/shared/flap 2/portal-bundle-merkle/curve-to-dex.js +0 -446
- package/dist/shared/flap 2/portal-bundle-merkle/index.d.ts +0 -14
- package/dist/shared/flap 2/portal-bundle-merkle/index.js +0 -26
- package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.d.ts +0 -28
- package/dist/shared/flap 2/portal-bundle-merkle/pancake-proxy.js +0 -701
- package/dist/shared/flap 2/portal-bundle-merkle/private.d.ts +0 -17
- package/dist/shared/flap 2/portal-bundle-merkle/private.js +0 -549
- package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.d.ts +0 -65
- package/dist/shared/flap 2/portal-bundle-merkle/swap-buy-first.js +0 -831
- package/dist/shared/flap 2/portal-bundle-merkle/swap.d.ts +0 -201
- package/dist/shared/flap 2/portal-bundle-merkle/swap.js +0 -1359
- package/dist/shared/flap 2/portal-bundle-merkle/types.d.ts +0 -358
- package/dist/shared/flap 2/portal-bundle-merkle/types.js +0 -1
- package/dist/shared/flap 2/portal-bundle-merkle/utils.d.ts +0 -89
- package/dist/shared/flap 2/portal-bundle-merkle/utils.js +0 -963
- package/dist/shared/flap 2/portal-bundle.d.ts +0 -119
- package/dist/shared/flap 2/portal-bundle.js +0 -584
- package/dist/shared/flap 2/portal.d.ts +0 -392
- package/dist/shared/flap 2/portal.js +0 -559
- package/dist/shared/flap 2/vanity.d.ts +0 -48
- package/dist/shared/flap 2/vanity.js +0 -110
- package/dist/shared/flap 2/vault.d.ts +0 -240
- package/dist/shared/flap 2/vault.js +0 -366
- package/dist/shared/four 2/index.d.ts +0 -7
- package/dist/shared/four 2/index.js +0 -22
- package/dist/shared/four 2/tax-token.d.ts +0 -176
- package/dist/shared/four 2/tax-token.js +0 -302
- package/dist/shared/index 2.js +0 -10
- package/dist/shared/index.d 2.ts +0 -10
- package/dist/types/distribute.d.ts +0 -72
- package/dist/types/distribute.js +0 -1
- package/dist/types 2/errors.d.ts +0 -27
- package/dist/types 2/errors.js +0 -34
- package/dist/utils/__tests__/errors.test.d.ts +0 -1
- package/dist/utils/__tests__/errors.test.js +0 -76
- package/dist/utils/airdrop-sweep-types.d.ts +0 -1
- package/dist/utils/airdrop-sweep-types.js +0 -1
- package/dist/utils/erc20/index.d.ts +0 -242
- package/dist/utils/erc20/index.js +0 -645
- package/dist/utils/erc20/types.d.ts +0 -77
- package/dist/utils/erc20/types.js +0 -1
- package/dist/utils/erc20-types.d.ts +0 -1
- package/dist/utils/erc20-types.js +0 -1
- package/dist/utils/holders-maker/helpers.d.ts +0 -43
- package/dist/utils/holders-maker/helpers.js +0 -371
- package/dist/utils/holders-maker/index.d.ts +0 -26
- package/dist/utils/holders-maker/index.js +0 -218
- package/dist/utils/holders-maker/types.d.ts +0 -72
- package/dist/utils/holders-maker/types.js +0 -4
- package/dist/utils/holders-maker-types.d.ts +0 -1
- package/dist/utils/holders-maker-types.js +0 -1
- package/dist/utils/lp-inspect/index.d.ts +0 -44
- package/dist/utils/lp-inspect/index.js +0 -937
- package/dist/utils/lp-inspect/types.d.ts +0 -100
- package/dist/utils/lp-inspect/types.js +0 -1
- package/dist/utils/lp-inspect-types.d.ts +0 -1
- package/dist/utils/lp-inspect-types.js +0 -1
- package/dist/utils/private-sale-types.d.ts +0 -1
- package/dist/utils/private-sale-types.js +0 -1
- package/dist/utils/quote-helpers/index.d.ts +0 -107
- package/dist/utils/quote-helpers/index.js +0 -346
- package/dist/utils/quote-helpers/types.d.ts +0 -16
- package/dist/utils/quote-helpers/types.js +0 -1
- package/dist/utils/quote-helpers-types.d.ts +0 -1
- package/dist/utils/quote-helpers-types.js +0 -1
- /package/dist/{chains/bsc/four/submit/types.js → __tests__/subpath-exports.test.d.ts} +0 -0
|
@@ -1,701 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PancakeSwap 官方 Router 交易模块(Flap 版本,支持多链)
|
|
3
|
-
*
|
|
4
|
-
* ✅ 使用官方 PancakeSwap V2/V3 Router(非代理合约)
|
|
5
|
-
* - V2 Router (BSC): 0x10ED43C718714eb63d5aA57B78B54704E256024E
|
|
6
|
-
* - V3 Router (BSC): 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4
|
|
7
|
-
* - V3 Quoter (BSC): 0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997
|
|
8
|
-
*/
|
|
9
|
-
import { ethers, Wallet, JsonRpcProvider, Contract, Interface } from 'ethers';
|
|
10
|
-
import { NonceManager, getOptimizedGasPrice, getDeadline, encodeV3Path, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../utils/bundle-helpers.js';
|
|
11
|
-
import { ADDRESSES, ZERO_ADDRESS } from '../../../utils/constants.js';
|
|
12
|
-
import { GAS_LIMITS } from '../../constants/index.js';
|
|
13
|
-
import { MULTICALL3_ABI, V2_ROUTER_ABI, V3_ROUTER02_ABI, V3_QUOTER_ABI, ERC20_ABI } from '../../abis/common.js';
|
|
14
|
-
import { CHAIN_ID_MAP, getTxType, getGasPriceConfig, shouldExtractProfit, calculateProfit, calculateBatchProfit, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA } from './config.js';
|
|
15
|
-
// ==================== 常量 ====================
|
|
16
|
-
const MULTICALL3_ADDRESS = ADDRESSES.BSC.Multicall3;
|
|
17
|
-
const WBNB_ADDRESS = ADDRESSES.BSC.WBNB;
|
|
18
|
-
const DEFAULT_GAS_LIMIT = 800000;
|
|
19
|
-
// ✅ PancakeSwap 官方合约地址
|
|
20
|
-
const PANCAKE_V2_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV2Router;
|
|
21
|
-
const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router;
|
|
22
|
-
const PANCAKE_V3_QUOTER_ADDRESS = ADDRESSES.BSC.PancakeV3Quoter;
|
|
23
|
-
// ==================== ABI 别名(从公共模块导入)====================
|
|
24
|
-
const V3_ROUTER_ABI = V3_ROUTER02_ABI;
|
|
25
|
-
// ==================== 官方 ABI ====================
|
|
26
|
-
// ✅ ABI 从公共模块导入:V2_ROUTER_ABI, V3_ROUTER02_ABI (as V3_ROUTER_ABI), V3_QUOTER_ABI, ERC20_ABI, MULTICALL3_ABI
|
|
27
|
-
// ✅ 工具函数从 bundle-helpers.js 导入:getDeadline, encodeV3Path
|
|
28
|
-
// ==================== Provider 缓存 ====================
|
|
29
|
-
const providerCache = new Map();
|
|
30
|
-
const PROVIDER_CACHE_TTL_MS = 60 * 1000;
|
|
31
|
-
// Token Decimals 缓存
|
|
32
|
-
const tokenDecimalsCache = new Map();
|
|
33
|
-
function getCachedProvider(chain, rpcUrl, chainId) {
|
|
34
|
-
const cacheKey = `${chain}-${rpcUrl}`;
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
const cached = providerCache.get(cacheKey);
|
|
37
|
-
if (cached && cached.expireAt > now) {
|
|
38
|
-
return cached.provider;
|
|
39
|
-
}
|
|
40
|
-
const resolvedChainId = chainId ?? (CHAIN_ID_MAP[chain] || 56);
|
|
41
|
-
const provider = new JsonRpcProvider(rpcUrl, { chainId: resolvedChainId, name: chain });
|
|
42
|
-
providerCache.set(cacheKey, { provider, expireAt: now + PROVIDER_CACHE_TTL_MS });
|
|
43
|
-
return provider;
|
|
44
|
-
}
|
|
45
|
-
// ==================== 辅助函数 ====================
|
|
46
|
-
function getGasLimit(config, defaultGas = DEFAULT_GAS_LIMIT) {
|
|
47
|
-
if (config.gasLimit !== undefined) {
|
|
48
|
-
return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
|
|
49
|
-
}
|
|
50
|
-
const multiplier = config.gasLimitMultiplier ?? 1.0;
|
|
51
|
-
const calculatedGas = Math.ceil(defaultGas * multiplier);
|
|
52
|
-
return BigInt(calculatedGas);
|
|
53
|
-
}
|
|
54
|
-
async function getTokenDecimals(tokenAddress, provider) {
|
|
55
|
-
const cacheKey = tokenAddress.toLowerCase();
|
|
56
|
-
const cached = tokenDecimalsCache.get(cacheKey);
|
|
57
|
-
if (cached !== undefined) {
|
|
58
|
-
return cached;
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
const token = new Contract(tokenAddress, ERC20_ABI, provider);
|
|
62
|
-
const decimals = await token.decimals();
|
|
63
|
-
const result = Number(decimals);
|
|
64
|
-
tokenDecimalsCache.set(cacheKey, result);
|
|
65
|
-
return result;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
tokenDecimalsCache.set(cacheKey, 18);
|
|
69
|
-
return 18;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function needSendBNB(routeType, params, useNativeToken = true) {
|
|
73
|
-
if (!useNativeToken) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
if (routeType === 'v2' && params.v2Path && params.v2Path[0].toLowerCase() === WBNB_ADDRESS.toLowerCase()) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
if (routeType === 'v3-single' && params.v3TokenIn && params.v3TokenIn.toLowerCase() === WBNB_ADDRESS.toLowerCase()) {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
if (routeType === 'v3-multi' && params.v3ExactTokenIn && params.v3ExactTokenIn.toLowerCase() === WBNB_ADDRESS.toLowerCase()) {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
function isUsingNativeToken(quoteToken) {
|
|
88
|
-
return !quoteToken || quoteToken === ZERO_ADDRESS || quoteToken.toLowerCase() === WBNB_ADDRESS.toLowerCase();
|
|
89
|
-
}
|
|
90
|
-
// ✅ getDeadline 从 bundle-helpers.js 导入
|
|
91
|
-
/**
|
|
92
|
-
* 根据 routeType 获取授权目标地址
|
|
93
|
-
*/
|
|
94
|
-
function getApprovalTarget(routeType) {
|
|
95
|
-
if (routeType === 'v2') {
|
|
96
|
-
return PANCAKE_V2_ROUTER_ADDRESS;
|
|
97
|
-
}
|
|
98
|
-
return PANCAKE_V3_ROUTER_ADDRESS;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* ✅ 构建 V2 交易(使用官方 Router)
|
|
102
|
-
*/
|
|
103
|
-
async function buildV2Transactions(routers, wallets, amountsWei, minOuts, path, isBuy, needBNB) {
|
|
104
|
-
const deadline = getDeadline();
|
|
105
|
-
return Promise.all(routers.map(async (router, i) => {
|
|
106
|
-
if (isBuy && needBNB) {
|
|
107
|
-
return router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(minOuts[i], path, wallets[i].address, deadline, { value: amountsWei[i] });
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
return router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amountsWei[i], minOuts[i], path, wallets[i].address, deadline);
|
|
111
|
-
}
|
|
112
|
-
}));
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* ✅ 构建 V3 单跳交易(使用官方 SwapRouter02 + multicall)
|
|
116
|
-
*/
|
|
117
|
-
async function buildV3SingleTransactions(routers, wallets, tokenIn, tokenOut, fee, amountsWei, minOuts, isBuy, needBNB) {
|
|
118
|
-
const deadline = getDeadline();
|
|
119
|
-
const v3RouterIface = new Interface(V3_ROUTER_ABI);
|
|
120
|
-
return Promise.all(routers.map(async (router, i) => {
|
|
121
|
-
const isTokenOutWBNB = tokenOut.toLowerCase() === WBNB_ADDRESS.toLowerCase();
|
|
122
|
-
const exactInputSingleData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
123
|
-
tokenIn: tokenIn,
|
|
124
|
-
tokenOut: tokenOut,
|
|
125
|
-
fee: fee,
|
|
126
|
-
recipient: isTokenOutWBNB ? PANCAKE_V3_ROUTER_ADDRESS : wallets[i].address,
|
|
127
|
-
amountIn: amountsWei[i],
|
|
128
|
-
amountOutMinimum: minOuts[i],
|
|
129
|
-
sqrtPriceLimitX96: 0n
|
|
130
|
-
}]);
|
|
131
|
-
if (isBuy && needBNB) {
|
|
132
|
-
return router.multicall.populateTransaction(deadline, [exactInputSingleData], { value: amountsWei[i] });
|
|
133
|
-
}
|
|
134
|
-
else if (!isBuy && isTokenOutWBNB) {
|
|
135
|
-
const unwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [
|
|
136
|
-
minOuts[i],
|
|
137
|
-
wallets[i].address
|
|
138
|
-
]);
|
|
139
|
-
return router.multicall.populateTransaction(deadline, [exactInputSingleData, unwrapData]);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
return router.multicall.populateTransaction(deadline, [exactInputSingleData]);
|
|
143
|
-
}
|
|
144
|
-
}));
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* ✅ 构建 V3 多跳交易(使用官方 SwapRouter02 的 exactInput)
|
|
148
|
-
*/
|
|
149
|
-
async function buildV3MultiHopTransactions(routers, wallets, tokens, fees, amountsWei, minOuts, isBuy, needBNB) {
|
|
150
|
-
if (!tokens || tokens.length < 2) {
|
|
151
|
-
throw new Error('V3 多跳需要至少 2 个代币(v3Tokens)');
|
|
152
|
-
}
|
|
153
|
-
if (!fees || fees.length !== tokens.length - 1) {
|
|
154
|
-
throw new Error(`V3 多跳费率数量 (${fees?.length || 0}) 必须等于代币数量 - 1 (${tokens.length - 1})(v3Fees)`);
|
|
155
|
-
}
|
|
156
|
-
const deadline = getDeadline();
|
|
157
|
-
const v3RouterIface = new Interface(V3_ROUTER_ABI);
|
|
158
|
-
const forwardPath = encodeV3Path(tokens, fees);
|
|
159
|
-
const reversedTokens = [...tokens].reverse();
|
|
160
|
-
const reversedFees = [...fees].reverse();
|
|
161
|
-
const reversePath = encodeV3Path(reversedTokens, reversedFees);
|
|
162
|
-
const tokenOut = tokens[tokens.length - 1];
|
|
163
|
-
const isTokenOutWBNB = tokenOut.toLowerCase() === WBNB_ADDRESS.toLowerCase();
|
|
164
|
-
return Promise.all(routers.map(async (router, i) => {
|
|
165
|
-
if (isBuy && needBNB) {
|
|
166
|
-
const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
|
|
167
|
-
path: forwardPath,
|
|
168
|
-
recipient: wallets[i].address,
|
|
169
|
-
amountIn: amountsWei[i],
|
|
170
|
-
amountOutMinimum: minOuts[i]
|
|
171
|
-
}]);
|
|
172
|
-
return router.multicall.populateTransaction(deadline, [swapData], { value: amountsWei[i] });
|
|
173
|
-
}
|
|
174
|
-
else if (!isBuy) {
|
|
175
|
-
const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
|
|
176
|
-
path: reversePath,
|
|
177
|
-
recipient: isTokenOutWBNB ? PANCAKE_V3_ROUTER_ADDRESS : wallets[i].address,
|
|
178
|
-
amountIn: amountsWei[i],
|
|
179
|
-
amountOutMinimum: minOuts[i]
|
|
180
|
-
}]);
|
|
181
|
-
if (isTokenOutWBNB) {
|
|
182
|
-
const unwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [minOuts[i], wallets[i].address]);
|
|
183
|
-
return router.multicall.populateTransaction(deadline, [swapData, unwrapData]);
|
|
184
|
-
}
|
|
185
|
-
return router.multicall.populateTransaction(deadline, [swapData]);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
const swapData = v3RouterIface.encodeFunctionData('exactInput', [{
|
|
189
|
-
path: forwardPath,
|
|
190
|
-
recipient: wallets[i].address,
|
|
191
|
-
amountIn: amountsWei[i],
|
|
192
|
-
amountOutMinimum: minOuts[i]
|
|
193
|
-
}]);
|
|
194
|
-
return router.multicall.populateTransaction(deadline, [swapData]);
|
|
195
|
-
}
|
|
196
|
-
}));
|
|
197
|
-
}
|
|
198
|
-
// ==================== 主要导出函数 ====================
|
|
199
|
-
/**
|
|
200
|
-
* ✅ 授权代币给 PancakeSwap Router
|
|
201
|
-
*/
|
|
202
|
-
export async function approvePancakeProxy(params) {
|
|
203
|
-
const { chain, privateKey, tokenAddress, amount, rpcUrl, routeType } = params;
|
|
204
|
-
const approvalTarget = getApprovalTarget(routeType || 'v2');
|
|
205
|
-
const provider = getCachedProvider(chain, rpcUrl);
|
|
206
|
-
const wallet = new Wallet(privateKey, provider);
|
|
207
|
-
const token = new Contract(tokenAddress, ERC20_ABI, wallet);
|
|
208
|
-
const decimals = await getTokenDecimals(tokenAddress, provider);
|
|
209
|
-
const currentAllowance = await token.allowance(wallet.address, approvalTarget);
|
|
210
|
-
const amountBigInt = amount === 'max' ? ethers.MaxUint256 : ethers.parseUnits(amount, decimals);
|
|
211
|
-
if (currentAllowance >= amountBigInt) {
|
|
212
|
-
return { txHash: '', approved: true };
|
|
213
|
-
}
|
|
214
|
-
const tx = await token.approve(approvalTarget, amountBigInt);
|
|
215
|
-
const receipt = await tx.wait();
|
|
216
|
-
return {
|
|
217
|
-
txHash: receipt.hash,
|
|
218
|
-
approved: receipt.status === 1
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* ✅ 批量授权代币给 PancakeSwap Router
|
|
223
|
-
*/
|
|
224
|
-
export async function approvePancakeProxyBatch(params) {
|
|
225
|
-
const { chain, privateKeys, tokenAddress, amounts, config, routeType } = params;
|
|
226
|
-
if (privateKeys.length === 0 || amounts.length !== privateKeys.length) {
|
|
227
|
-
throw new Error('Private key count and amount count must match');
|
|
228
|
-
}
|
|
229
|
-
const approvalTarget = getApprovalTarget(routeType || 'v2');
|
|
230
|
-
const chainId = CHAIN_ID_MAP[chain];
|
|
231
|
-
const provider = getCachedProvider(chain, config.rpcUrl, chainId);
|
|
232
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
233
|
-
const decimals = await getTokenDecimals(tokenAddress, provider);
|
|
234
|
-
const wallets = privateKeys.map(k => new Wallet(k, provider));
|
|
235
|
-
const amountsBigInt = amounts.map(a => a === 'max' ? ethers.MaxUint256 : ethers.parseUnits(a, decimals));
|
|
236
|
-
const tokens = wallets.map(w => new Contract(tokenAddress, ERC20_ABI, w));
|
|
237
|
-
const allowances = await Promise.all(tokens.map((token, i) => token.allowance(wallets[i].address, approvalTarget)));
|
|
238
|
-
const needApproval = wallets.filter((_, i) => allowances[i] < amountsBigInt[i]);
|
|
239
|
-
const needApprovalAmounts = amountsBigInt.filter((amount, i) => allowances[i] < amount);
|
|
240
|
-
if (needApproval.length === 0) {
|
|
241
|
-
return {
|
|
242
|
-
success: true,
|
|
243
|
-
approvedCount: 0,
|
|
244
|
-
signedTransactions: [],
|
|
245
|
-
message: '所有钱包已授权'
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
const needApprovalTokens = needApproval.map(w => new Contract(tokenAddress, ERC20_ABI, w));
|
|
249
|
-
const unsignedApprovals = await Promise.all(needApprovalTokens.map((token, i) => token.approve.populateTransaction(approvalTarget, needApprovalAmounts[i])));
|
|
250
|
-
const finalGasLimit = getGasLimit(config);
|
|
251
|
-
const nonceManager = new NonceManager(provider);
|
|
252
|
-
const nonces = await Promise.all(needApproval.map(w => nonceManager.getNextNonce(w)));
|
|
253
|
-
const signedTxs = await Promise.all(unsignedApprovals.map((unsigned, i) => needApproval[i].signTransaction({
|
|
254
|
-
...unsigned,
|
|
255
|
-
from: needApproval[i].address,
|
|
256
|
-
nonce: nonces[i],
|
|
257
|
-
gasLimit: finalGasLimit,
|
|
258
|
-
gasPrice,
|
|
259
|
-
chainId,
|
|
260
|
-
type: getTxType(config)
|
|
261
|
-
})));
|
|
262
|
-
nonceManager.clearTemp();
|
|
263
|
-
const txHashes = [];
|
|
264
|
-
const errors = [];
|
|
265
|
-
for (let i = 0; i < signedTxs.length; i++) {
|
|
266
|
-
try {
|
|
267
|
-
const tx = await provider.broadcastTransaction(signedTxs[i]);
|
|
268
|
-
txHashes.push(tx.hash);
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
errors.push(`钱包 ${i} 授权失败: ${error.message}`);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
const successCount = txHashes.length;
|
|
275
|
-
if (successCount > 0) {
|
|
276
|
-
return {
|
|
277
|
-
success: true,
|
|
278
|
-
approvedCount: successCount,
|
|
279
|
-
signedTransactions: signedTxs,
|
|
280
|
-
txHashes,
|
|
281
|
-
message: `授权成功,共 ${successCount} 个钱包${errors.length > 0 ? `,${errors.length} 个失败` : ''}`
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
return {
|
|
286
|
-
success: false,
|
|
287
|
-
approvedCount: 0,
|
|
288
|
-
signedTransactions: signedTxs,
|
|
289
|
-
txHashes: [],
|
|
290
|
-
message: `授权失败: ${errors.join('; ')}`
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* ✅ 使用 PancakeSwap 官方 Router 批量购买代币
|
|
296
|
-
*/
|
|
297
|
-
export async function pancakeProxyBatchBuyMerkle(params) {
|
|
298
|
-
const { chain, privateKeys, buyAmounts, tokenAddress, routeType, config, quoteToken, quoteTokenDecimals } = params;
|
|
299
|
-
if (privateKeys.length === 0 || buyAmounts.length !== privateKeys.length) {
|
|
300
|
-
throw new Error('Private key count and buy amount count must match');
|
|
301
|
-
}
|
|
302
|
-
const chainId = CHAIN_ID_MAP[chain];
|
|
303
|
-
const provider = getCachedProvider(chain, config.rpcUrl, chainId);
|
|
304
|
-
const buyers = privateKeys.map(k => new Wallet(k, provider));
|
|
305
|
-
const extractProfit = shouldExtractProfit(config);
|
|
306
|
-
const nonceManager = new NonceManager(provider);
|
|
307
|
-
const finalGasLimit = getGasLimit(config);
|
|
308
|
-
const useNativeToken = isUsingNativeToken(quoteToken);
|
|
309
|
-
const originalAmountsWei = buyAmounts.map(amount => ethers.parseEther(amount));
|
|
310
|
-
const { totalProfit, remainingAmounts } = calculateBatchProfit(originalAmountsWei, config);
|
|
311
|
-
const maxFundsIndex = findMaxAmountIndex(originalAmountsWei);
|
|
312
|
-
const shouldExtractProfitForBuy = extractProfit && useNativeToken;
|
|
313
|
-
const nativeProfitAmount = shouldExtractProfitForBuy ? totalProfit : 0n;
|
|
314
|
-
let actualAmountsWei = remainingAmounts;
|
|
315
|
-
if (!useNativeToken && quoteTokenDecimals !== undefined && quoteTokenDecimals !== 18) {
|
|
316
|
-
const decimalsDiff = 18 - quoteTokenDecimals;
|
|
317
|
-
const divisor = BigInt(10 ** decimalsDiff);
|
|
318
|
-
actualAmountsWei = remainingAmounts.map(amount => amount / divisor);
|
|
319
|
-
}
|
|
320
|
-
const presetGasPrice = config.gasPrice;
|
|
321
|
-
const presetNonces = config.nonces;
|
|
322
|
-
const [gasPrice, tokenDecimals, nonces] = await Promise.all([
|
|
323
|
-
presetGasPrice !== undefined
|
|
324
|
-
? Promise.resolve(presetGasPrice)
|
|
325
|
-
: getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
326
|
-
getTokenDecimals(tokenAddress, provider),
|
|
327
|
-
presetNonces && presetNonces.length === buyers.length
|
|
328
|
-
? Promise.resolve(presetNonces)
|
|
329
|
-
: allocateProfitAwareNonces(buyers, shouldExtractProfitForBuy, maxFundsIndex, nativeProfitAmount, nonceManager)
|
|
330
|
-
]);
|
|
331
|
-
const minOuts = new Array(buyers.length).fill(0n);
|
|
332
|
-
const needBNB = needSendBNB(routeType, params, useNativeToken);
|
|
333
|
-
// ✅ 使用官方 Router
|
|
334
|
-
let routers;
|
|
335
|
-
let unsignedBuys;
|
|
336
|
-
if (routeType === 'v2') {
|
|
337
|
-
if (!params.v2Path || params.v2Path.length < 2) {
|
|
338
|
-
throw new Error('v2Path is required for V2 routing');
|
|
339
|
-
}
|
|
340
|
-
routers = buyers.map(w => new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, w));
|
|
341
|
-
unsignedBuys = await buildV2Transactions(routers, buyers, actualAmountsWei, minOuts, params.v2Path, true, needBNB);
|
|
342
|
-
}
|
|
343
|
-
else if (routeType === 'v3-single') {
|
|
344
|
-
if (!params.v3TokenIn || !params.v3Fee) {
|
|
345
|
-
throw new Error('v3TokenIn and v3Fee are required for V3 single-hop');
|
|
346
|
-
}
|
|
347
|
-
routers = buyers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
|
|
348
|
-
unsignedBuys = await buildV3SingleTransactions(routers, buyers, params.v3TokenIn, tokenAddress, params.v3Fee, actualAmountsWei, minOuts, true, needBNB);
|
|
349
|
-
}
|
|
350
|
-
else if (routeType === 'v3-multi') {
|
|
351
|
-
// ✅ V3 多跳:需要 v3Tokens 和 v3Fees 参数
|
|
352
|
-
if (!params.v3Tokens || params.v3Tokens.length < 2) {
|
|
353
|
-
throw new Error('v3Tokens(至少 2 个代币)是 V3 多跳必需的');
|
|
354
|
-
}
|
|
355
|
-
if (!params.v3Fees || params.v3Fees.length !== params.v3Tokens.length - 1) {
|
|
356
|
-
throw new Error(`v3Fees 长度必须等于 v3Tokens 长度 - 1`);
|
|
357
|
-
}
|
|
358
|
-
routers = buyers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
|
|
359
|
-
unsignedBuys = await buildV3MultiHopTransactions(routers, buyers, params.v3Tokens, params.v3Fees, actualAmountsWei, minOuts, true, needBNB);
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
363
|
-
}
|
|
364
|
-
const bribeAmount = getBribeAmount(config);
|
|
365
|
-
const needBribeTx = bribeAmount > 0n && maxFundsIndex >= 0 && buyers.length > 0;
|
|
366
|
-
const txType = getTxType(config);
|
|
367
|
-
let bribeNonce;
|
|
368
|
-
const mutableNonces = [...nonces];
|
|
369
|
-
if (needBribeTx) {
|
|
370
|
-
bribeNonce = mutableNonces[maxFundsIndex];
|
|
371
|
-
mutableNonces[maxFundsIndex] = bribeNonce + 1;
|
|
372
|
-
}
|
|
373
|
-
const signPromises = [];
|
|
374
|
-
if (needBribeTx && bribeNonce !== undefined) {
|
|
375
|
-
signPromises.push(buyers[maxFundsIndex].signTransaction({
|
|
376
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
377
|
-
value: bribeAmount,
|
|
378
|
-
nonce: bribeNonce,
|
|
379
|
-
gasPrice,
|
|
380
|
-
gasLimit: GAS_LIMITS.BRIBE,
|
|
381
|
-
chainId,
|
|
382
|
-
type: txType
|
|
383
|
-
}));
|
|
384
|
-
}
|
|
385
|
-
unsignedBuys.forEach((unsigned, i) => {
|
|
386
|
-
const txValue = useNativeToken ? unsigned.value : 0n;
|
|
387
|
-
signPromises.push(buyers[i].signTransaction({
|
|
388
|
-
...unsigned,
|
|
389
|
-
from: buyers[i].address,
|
|
390
|
-
nonce: mutableNonces[i],
|
|
391
|
-
gasLimit: finalGasLimit,
|
|
392
|
-
gasPrice,
|
|
393
|
-
chainId,
|
|
394
|
-
type: txType,
|
|
395
|
-
value: txValue
|
|
396
|
-
}));
|
|
397
|
-
});
|
|
398
|
-
const signedTxs = await Promise.all(signPromises);
|
|
399
|
-
// 利润多跳转账(强制 2 跳中转)
|
|
400
|
-
let profitHopWallets;
|
|
401
|
-
if (shouldExtractProfitForBuy && nativeProfitAmount > 0n && maxFundsIndex >= 0) {
|
|
402
|
-
const profitNonce = mutableNonces[maxFundsIndex] + 1;
|
|
403
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
404
|
-
provider,
|
|
405
|
-
payerWallet: buyers[maxFundsIndex],
|
|
406
|
-
profitAmount: nativeProfitAmount,
|
|
407
|
-
profitRecipient: getProfitRecipient(),
|
|
408
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
409
|
-
gasPrice,
|
|
410
|
-
chainId,
|
|
411
|
-
txType,
|
|
412
|
-
startNonce: profitNonce
|
|
413
|
-
});
|
|
414
|
-
signedTxs.push(...profitHopResult.signedTransactions);
|
|
415
|
-
profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
416
|
-
}
|
|
417
|
-
nonceManager.clearTemp();
|
|
418
|
-
return {
|
|
419
|
-
signedTransactions: signedTxs,
|
|
420
|
-
profitHopWallets // ✅ 返回利润多跳钱包
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* ✅ 使用 PancakeSwap 官方 Router 批量卖出代币
|
|
425
|
-
*/
|
|
426
|
-
export async function pancakeProxyBatchSellMerkle(params) {
|
|
427
|
-
const { chain, privateKeys, sellAmounts, tokenAddress, routeType, config } = params;
|
|
428
|
-
if (privateKeys.length === 0 || sellAmounts.length !== privateKeys.length) {
|
|
429
|
-
throw new Error('Private key count and sell amount count must match');
|
|
430
|
-
}
|
|
431
|
-
const chainId = CHAIN_ID_MAP[chain];
|
|
432
|
-
const provider = getCachedProvider(chain, config.rpcUrl, chainId);
|
|
433
|
-
const sellers = privateKeys.map(k => new Wallet(k, provider));
|
|
434
|
-
const finalGasLimit = getGasLimit(config);
|
|
435
|
-
const extractProfit = shouldExtractProfit(config);
|
|
436
|
-
const nonceManager = new NonceManager(provider);
|
|
437
|
-
const presetGasPrice = config.gasPrice;
|
|
438
|
-
const presetNonces = config.nonces;
|
|
439
|
-
const [gasPrice, tokenDecimals] = await Promise.all([
|
|
440
|
-
presetGasPrice !== undefined
|
|
441
|
-
? Promise.resolve(presetGasPrice)
|
|
442
|
-
: getOptimizedGasPrice(provider, getGasPriceConfig(config)),
|
|
443
|
-
getTokenDecimals(tokenAddress, provider)
|
|
444
|
-
]);
|
|
445
|
-
const amountsWei = sellAmounts.map(amount => ethers.parseUnits(amount, tokenDecimals));
|
|
446
|
-
// 获取报价
|
|
447
|
-
let quotedOutputs;
|
|
448
|
-
if (routeType === 'v2' && params.v2Path && params.v2Path.length >= 2) {
|
|
449
|
-
quotedOutputs = await batchGetV2Quotes(provider, amountsWei, params.v2Path);
|
|
450
|
-
}
|
|
451
|
-
else if (routeType === 'v3-single') {
|
|
452
|
-
quotedOutputs = await Promise.all(amountsWei.map(async (amount) => {
|
|
453
|
-
try {
|
|
454
|
-
const quoter = new Contract(PANCAKE_V3_QUOTER_ADDRESS, V3_QUOTER_ABI, provider);
|
|
455
|
-
const result = await quoter.quoteExactInputSingle.staticCall({
|
|
456
|
-
tokenIn: tokenAddress,
|
|
457
|
-
tokenOut: params.v3TokenOut,
|
|
458
|
-
amountIn: amount,
|
|
459
|
-
fee: params.v3Fee,
|
|
460
|
-
sqrtPriceLimitX96: 0
|
|
461
|
-
});
|
|
462
|
-
return result[0];
|
|
463
|
-
}
|
|
464
|
-
catch {
|
|
465
|
-
return 0n;
|
|
466
|
-
}
|
|
467
|
-
}));
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
quotedOutputs = new Array(amountsWei.length).fill(0n);
|
|
471
|
-
}
|
|
472
|
-
const minOuts = new Array(sellers.length).fill(0n);
|
|
473
|
-
// 计算利润
|
|
474
|
-
let totalProfit = 0n;
|
|
475
|
-
let maxRevenueIndex = -1;
|
|
476
|
-
let maxRevenue = 0n;
|
|
477
|
-
if (extractProfit && quotedOutputs.length > 0) {
|
|
478
|
-
for (let i = 0; i < sellers.length; i++) {
|
|
479
|
-
const quoted = quotedOutputs[i];
|
|
480
|
-
if (quoted > 0n) {
|
|
481
|
-
const { profit } = calculateProfit(quoted, config);
|
|
482
|
-
totalProfit += profit;
|
|
483
|
-
if (quoted > maxRevenue) {
|
|
484
|
-
maxRevenue = quoted;
|
|
485
|
-
maxRevenueIndex = i;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
const bribeAmount = getBribeAmount(config);
|
|
491
|
-
const needBribeTx = bribeAmount > 0n && maxRevenueIndex >= 0;
|
|
492
|
-
const needProfitTx = extractProfit && totalProfit > 0n && maxRevenueIndex >= 0;
|
|
493
|
-
const maxRevenueNonceCount = 1 + (needBribeTx ? 1 : 0) + (needProfitTx ? 1 : 0);
|
|
494
|
-
let nonces;
|
|
495
|
-
let bribeNonce;
|
|
496
|
-
let profitNonce;
|
|
497
|
-
if (presetNonces && presetNonces.length === sellers.length) {
|
|
498
|
-
nonces = [...presetNonces];
|
|
499
|
-
if (needBribeTx) {
|
|
500
|
-
bribeNonce = nonces[maxRevenueIndex];
|
|
501
|
-
nonces[maxRevenueIndex] = bribeNonce + 1;
|
|
502
|
-
}
|
|
503
|
-
profitNonce = needProfitTx ? nonces[maxRevenueIndex] + 1 : undefined;
|
|
504
|
-
}
|
|
505
|
-
else if (maxRevenueNonceCount > 1 && maxRevenueIndex >= 0) {
|
|
506
|
-
const maxRevenueNonces = await nonceManager.getNextNonceBatch(sellers[maxRevenueIndex], maxRevenueNonceCount);
|
|
507
|
-
const otherSellers = sellers.filter((_, i) => i !== maxRevenueIndex);
|
|
508
|
-
const otherNonces = otherSellers.length > 0
|
|
509
|
-
? await nonceManager.getNextNoncesForWallets(otherSellers)
|
|
510
|
-
: [];
|
|
511
|
-
nonces = [];
|
|
512
|
-
let otherIdx = 0;
|
|
513
|
-
let nonceIdx = 0;
|
|
514
|
-
if (needBribeTx) {
|
|
515
|
-
bribeNonce = maxRevenueNonces[nonceIdx++];
|
|
516
|
-
}
|
|
517
|
-
for (let i = 0; i < sellers.length; i++) {
|
|
518
|
-
if (i === maxRevenueIndex) {
|
|
519
|
-
nonces.push(maxRevenueNonces[nonceIdx++]);
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
nonces.push(otherNonces[otherIdx++]);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (needProfitTx) {
|
|
526
|
-
profitNonce = maxRevenueNonces[nonceIdx];
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
else {
|
|
530
|
-
nonces = await nonceManager.getNextNoncesForWallets(sellers);
|
|
531
|
-
}
|
|
532
|
-
// ✅ 使用官方 Router 构建卖出交易
|
|
533
|
-
let routers;
|
|
534
|
-
let unsignedSells;
|
|
535
|
-
if (routeType === 'v2') {
|
|
536
|
-
if (!params.v2Path || params.v2Path.length < 2) {
|
|
537
|
-
throw new Error('v2Path is required for V2 routing');
|
|
538
|
-
}
|
|
539
|
-
routers = sellers.map(w => new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, w));
|
|
540
|
-
unsignedSells = await buildV2Transactions(routers, sellers, amountsWei, minOuts, params.v2Path, false, false);
|
|
541
|
-
}
|
|
542
|
-
else if (routeType === 'v3-single') {
|
|
543
|
-
if (!params.v3TokenOut || !params.v3Fee) {
|
|
544
|
-
throw new Error('v3TokenOut and v3Fee are required for V3 single-hop');
|
|
545
|
-
}
|
|
546
|
-
routers = sellers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
|
|
547
|
-
unsignedSells = await buildV3SingleTransactions(routers, sellers, tokenAddress, params.v3TokenOut, params.v3Fee, amountsWei, minOuts, false, false);
|
|
548
|
-
}
|
|
549
|
-
else if (routeType === 'v3-multi') {
|
|
550
|
-
// ✅ V3 多跳:需要 v3Tokens 和 v3Fees 参数
|
|
551
|
-
if (!params.v3Tokens || params.v3Tokens.length < 2) {
|
|
552
|
-
throw new Error('v3Tokens(至少 2 个代币)是 V3 多跳必需的');
|
|
553
|
-
}
|
|
554
|
-
if (!params.v3Fees || params.v3Fees.length !== params.v3Tokens.length - 1) {
|
|
555
|
-
throw new Error(`v3Fees 长度必须等于 v3Tokens 长度 - 1`);
|
|
556
|
-
}
|
|
557
|
-
routers = sellers.map(w => new Contract(PANCAKE_V3_ROUTER_ADDRESS, V3_ROUTER_ABI, w));
|
|
558
|
-
unsignedSells = await buildV3MultiHopTransactions(routers, sellers, params.v3Tokens, params.v3Fees, amountsWei, minOuts, false, false);
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
throw new Error(`Unsupported routeType: ${routeType}`);
|
|
562
|
-
}
|
|
563
|
-
const txType = getTxType(config);
|
|
564
|
-
const signPromises = [];
|
|
565
|
-
if (needBribeTx && bribeNonce !== undefined) {
|
|
566
|
-
signPromises.push(sellers[maxRevenueIndex].signTransaction({
|
|
567
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
568
|
-
value: bribeAmount,
|
|
569
|
-
nonce: bribeNonce,
|
|
570
|
-
gasPrice,
|
|
571
|
-
gasLimit: GAS_LIMITS.BRIBE,
|
|
572
|
-
chainId,
|
|
573
|
-
type: txType
|
|
574
|
-
}));
|
|
575
|
-
}
|
|
576
|
-
unsignedSells.forEach((unsigned, i) => {
|
|
577
|
-
const txValue = unsigned.value ?? 0n;
|
|
578
|
-
signPromises.push(sellers[i].signTransaction({
|
|
579
|
-
...unsigned,
|
|
580
|
-
from: sellers[i].address,
|
|
581
|
-
nonce: nonces[i],
|
|
582
|
-
gasLimit: finalGasLimit,
|
|
583
|
-
gasPrice,
|
|
584
|
-
chainId,
|
|
585
|
-
type: txType,
|
|
586
|
-
value: txValue
|
|
587
|
-
}));
|
|
588
|
-
});
|
|
589
|
-
const signedTxs = await Promise.all(signPromises);
|
|
590
|
-
// 利润多跳转账(强制 2 跳中转)
|
|
591
|
-
let profitHopWallets;
|
|
592
|
-
if (needProfitTx && profitNonce !== undefined) {
|
|
593
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
594
|
-
provider,
|
|
595
|
-
payerWallet: sellers[maxRevenueIndex],
|
|
596
|
-
profitAmount: totalProfit,
|
|
597
|
-
profitRecipient: getProfitRecipient(),
|
|
598
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
599
|
-
gasPrice,
|
|
600
|
-
chainId,
|
|
601
|
-
txType,
|
|
602
|
-
startNonce: profitNonce
|
|
603
|
-
});
|
|
604
|
-
signedTxs.push(...profitHopResult.signedTransactions);
|
|
605
|
-
profitHopWallets = profitHopResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
606
|
-
}
|
|
607
|
-
nonceManager.clearTemp();
|
|
608
|
-
return {
|
|
609
|
-
signedTransactions: signedTxs,
|
|
610
|
-
profitHopWallets // ✅ 返回利润多跳钱包
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
// ==================== 内部工具函数 ====================
|
|
614
|
-
/**
|
|
615
|
-
* ✅ 使用 Multicall3 批量获取 V2 报价
|
|
616
|
-
*/
|
|
617
|
-
async function batchGetV2Quotes(provider, amountsWei, v2Path) {
|
|
618
|
-
if (amountsWei.length === 0)
|
|
619
|
-
return [];
|
|
620
|
-
if (amountsWei.length === 1) {
|
|
621
|
-
try {
|
|
622
|
-
const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, provider);
|
|
623
|
-
const amounts = await v2Router.getAmountsOut(amountsWei[0], v2Path);
|
|
624
|
-
return [amounts[amounts.length - 1]];
|
|
625
|
-
}
|
|
626
|
-
catch {
|
|
627
|
-
return [0n];
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
try {
|
|
631
|
-
const v2RouterIface = new Interface(V2_ROUTER_ABI);
|
|
632
|
-
const multicall = new Contract(MULTICALL3_ADDRESS, MULTICALL3_ABI, provider);
|
|
633
|
-
const calls = amountsWei.map(amount => ({
|
|
634
|
-
target: PANCAKE_V2_ROUTER_ADDRESS,
|
|
635
|
-
allowFailure: true,
|
|
636
|
-
callData: v2RouterIface.encodeFunctionData('getAmountsOut', [amount, v2Path])
|
|
637
|
-
}));
|
|
638
|
-
const results = await multicall.aggregate3.staticCall(calls);
|
|
639
|
-
return results.map((r) => {
|
|
640
|
-
if (r.success && r.returnData && r.returnData !== '0x') {
|
|
641
|
-
try {
|
|
642
|
-
const decoded = v2RouterIface.decodeFunctionResult('getAmountsOut', r.returnData);
|
|
643
|
-
const amounts = decoded[0];
|
|
644
|
-
return amounts[amounts.length - 1];
|
|
645
|
-
}
|
|
646
|
-
catch {
|
|
647
|
-
return 0n;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
return 0n;
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
catch {
|
|
654
|
-
return await Promise.all(amountsWei.map(async (amount) => {
|
|
655
|
-
try {
|
|
656
|
-
const v2Router = new Contract(PANCAKE_V2_ROUTER_ADDRESS, V2_ROUTER_ABI, provider);
|
|
657
|
-
const amounts = await v2Router.getAmountsOut(amount, v2Path);
|
|
658
|
-
return amounts[amounts.length - 1];
|
|
659
|
-
}
|
|
660
|
-
catch {
|
|
661
|
-
return 0n;
|
|
662
|
-
}
|
|
663
|
-
}));
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
function findMaxAmountIndex(amounts) {
|
|
667
|
-
if (!amounts.length) {
|
|
668
|
-
return -1;
|
|
669
|
-
}
|
|
670
|
-
let maxIndex = 0;
|
|
671
|
-
let maxValue = amounts[0];
|
|
672
|
-
for (let i = 1; i < amounts.length; i++) {
|
|
673
|
-
if (amounts[i] > maxValue) {
|
|
674
|
-
maxValue = amounts[i];
|
|
675
|
-
maxIndex = i;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
return maxIndex;
|
|
679
|
-
}
|
|
680
|
-
async function allocateProfitAwareNonces(wallets, extractProfit, maxIndex, totalProfit, nonceManager) {
|
|
681
|
-
const needProfitTx = extractProfit && totalProfit > 0n && maxIndex >= 0 && maxIndex < wallets.length;
|
|
682
|
-
if (!needProfitTx) {
|
|
683
|
-
return await nonceManager.getNextNoncesForWallets(wallets);
|
|
684
|
-
}
|
|
685
|
-
const maxIndexNonces = await nonceManager.getNextNonceBatch(wallets[maxIndex], 2);
|
|
686
|
-
const otherWallets = wallets.filter((_, i) => i !== maxIndex);
|
|
687
|
-
const otherNonces = otherWallets.length > 0
|
|
688
|
-
? await nonceManager.getNextNoncesForWallets(otherWallets)
|
|
689
|
-
: [];
|
|
690
|
-
const nonces = [];
|
|
691
|
-
let otherIdx = 0;
|
|
692
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
693
|
-
if (i === maxIndex) {
|
|
694
|
-
nonces.push(maxIndexNonces[0]);
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
nonces.push(otherNonces[otherIdx++]);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
return nonces;
|
|
701
|
-
}
|