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,907 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PancakeSwap V2/V3 通用捆绑换手(Merkle Bundle)- 先买后卖
|
|
3
|
-
*
|
|
4
|
-
* 功能:钱包B先买入代币 → 钱包A卖出相同数量 → 原子执行
|
|
5
|
-
*/
|
|
6
|
-
import { ethers, Contract, Wallet } from 'ethers';
|
|
7
|
-
import { NonceManager, getDeadline, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../../utils/bundle-helpers.js';
|
|
8
|
-
import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA, ZERO_ADDRESS } from '../../../../utils/constants.js';
|
|
9
|
-
import { GAS_LIMITS } from '../../../../shared/constants/index.js';
|
|
10
|
-
import { quoteV2, quoteV3, getTokenToNativeQuote } from '../../../../utils/quote-helpers.js';
|
|
11
|
-
import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_BALANCE_ABI } from '../../../../shared/abis/common.js';
|
|
12
|
-
// ✅ 简化的签名配置
|
|
13
|
-
/**
|
|
14
|
-
* 获取 Gas Limit
|
|
15
|
-
*/
|
|
16
|
-
function getGasLimit(config, defaultGas = 800000) {
|
|
17
|
-
if (config.gasLimit !== undefined) {
|
|
18
|
-
return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
|
|
19
|
-
}
|
|
20
|
-
const multiplier = config.gasLimitMultiplier ?? 1.0;
|
|
21
|
-
const calculatedGas = Math.ceil(defaultGas * multiplier);
|
|
22
|
-
return BigInt(calculatedGas);
|
|
23
|
-
}
|
|
24
|
-
async function getGasPrice(provider, config) {
|
|
25
|
-
const feeData = await provider.getFeeData();
|
|
26
|
-
let gasPrice = feeData.gasPrice || ethers.parseUnits('3', 'gwei');
|
|
27
|
-
if (config.minGasPriceGwei) {
|
|
28
|
-
const minGas = ethers.parseUnits(config.minGasPriceGwei.toString(), 'gwei');
|
|
29
|
-
if (gasPrice < minGas)
|
|
30
|
-
gasPrice = minGas;
|
|
31
|
-
}
|
|
32
|
-
if (config.maxGasPriceGwei) {
|
|
33
|
-
const maxGas = ethers.parseUnits(config.maxGasPriceGwei.toString(), 'gwei');
|
|
34
|
-
if (gasPrice > maxGas)
|
|
35
|
-
gasPrice = maxGas;
|
|
36
|
-
}
|
|
37
|
-
return gasPrice;
|
|
38
|
-
}
|
|
39
|
-
// ✅ ABI 别名(从公共模块导入)
|
|
40
|
-
const PANCAKE_V2_ROUTER_ABI = V2_ROUTER_ABI;
|
|
41
|
-
const PANCAKE_V3_ROUTER_ABI = V3_ROUTER02_ABI;
|
|
42
|
-
const ERC20_BALANCE_OF_ABI = ERC20_BALANCE_ABI;
|
|
43
|
-
// ✅ 使用官方 PancakeSwap Router 地址
|
|
44
|
-
const PANCAKE_V2_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV2Router;
|
|
45
|
-
const PANCAKE_V3_ROUTER_ADDRESS = ADDRESSES.BSC.PancakeV3Router;
|
|
46
|
-
// 常量
|
|
47
|
-
const FLAT_FEE = 0n;
|
|
48
|
-
// ==================== 多笔买卖常量 ====================
|
|
49
|
-
/** 最大 Bundle 签名数 */
|
|
50
|
-
const MAX_BUNDLE_SIGNATURES = 50;
|
|
51
|
-
/** 贿赂交易数 */
|
|
52
|
-
const BRIBE_TX_COUNT = 1;
|
|
53
|
-
/** 利润中转交易数(支付者 1 笔 + 中转钱包 PROFIT_HOP_COUNT 笔 + 最终接收 1 笔) */
|
|
54
|
-
const PROFIT_TX_COUNT = PROFIT_HOP_COUNT + 2; // 2 + 2 = 4
|
|
55
|
-
/** 最大买卖交易数 */
|
|
56
|
-
const MAX_SWAP_TX_COUNT = MAX_BUNDLE_SIGNATURES - BRIBE_TX_COUNT - PROFIT_TX_COUNT; // 50 - 1 - 4 = 45
|
|
57
|
-
// ✅ 用户类型(影响利润率)
|
|
58
|
-
/**
|
|
59
|
-
* 根据 userType 获取每笔交易的利润率
|
|
60
|
-
*/
|
|
61
|
-
function getProfitRatePerTxBps(userType) {
|
|
62
|
-
if (userType === 'v1') {
|
|
63
|
-
return PROFIT_CONFIG.RATE_BPS_V1_DOUBLE;
|
|
64
|
-
}
|
|
65
|
-
return PROFIT_CONFIG.RATE_BPS_V0_DOUBLE; // v0 或默认
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* 验证买卖笔数
|
|
69
|
-
*/
|
|
70
|
-
function validateSwapCounts(buyCount, sellCount) {
|
|
71
|
-
if (buyCount < 1) {
|
|
72
|
-
throw new Error(`买入笔数必须至少为 1,当前为 ${buyCount}`);
|
|
73
|
-
}
|
|
74
|
-
if (sellCount < 1) {
|
|
75
|
-
throw new Error(`卖出笔数必须至少为 1,当前为 ${sellCount}`);
|
|
76
|
-
}
|
|
77
|
-
const totalSwaps = buyCount + sellCount;
|
|
78
|
-
if (totalSwaps > MAX_SWAP_TX_COUNT) {
|
|
79
|
-
throw new Error(`买卖笔数总和 (${totalSwaps}) 超出限制 (${MAX_SWAP_TX_COUNT}),` +
|
|
80
|
-
`最大签名数为 ${MAX_BUNDLE_SIGNATURES}(${BRIBE_TX_COUNT} 贿赂 + ${PROFIT_TX_COUNT} 利润 + ${MAX_SWAP_TX_COUNT} 买卖)`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* ✅ 将金额随机拆分成多份(更自然,不像机器人)
|
|
85
|
-
* 每份金额在平均值的 50%-150% 之间随机浮动
|
|
86
|
-
*/
|
|
87
|
-
function splitAmount(totalAmount, count) {
|
|
88
|
-
if (count <= 0) {
|
|
89
|
-
throw new Error('拆分份数必须大于 0');
|
|
90
|
-
}
|
|
91
|
-
if (count === 1) {
|
|
92
|
-
return [totalAmount];
|
|
93
|
-
}
|
|
94
|
-
// 生成随机权重(0.5 - 1.5 之间)
|
|
95
|
-
const weights = [];
|
|
96
|
-
for (let i = 0; i < count; i++) {
|
|
97
|
-
// 随机值在 0.5 - 1.5 之间,使每份金额有 ±50% 的浮动
|
|
98
|
-
weights.push(0.5 + Math.random());
|
|
99
|
-
}
|
|
100
|
-
// 计算权重总和
|
|
101
|
-
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
|
102
|
-
// 根据权重分配金额
|
|
103
|
-
const amounts = [];
|
|
104
|
-
let allocated = 0n;
|
|
105
|
-
for (let i = 0; i < count - 1; i++) {
|
|
106
|
-
// 按权重比例分配
|
|
107
|
-
const ratio = weights[i] / totalWeight;
|
|
108
|
-
const amount = BigInt(Math.floor(Number(totalAmount) * ratio));
|
|
109
|
-
amounts.push(amount);
|
|
110
|
-
allocated += amount;
|
|
111
|
-
}
|
|
112
|
-
// 最后一份分配剩余的全部(确保总和精确)
|
|
113
|
-
amounts.push(totalAmount - allocated);
|
|
114
|
-
// 随机打乱顺序(让大小金额交错出现)
|
|
115
|
-
for (let i = amounts.length - 1; i > 0; i--) {
|
|
116
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
117
|
-
[amounts[i], amounts[j]] = [amounts[j], amounts[i]];
|
|
118
|
-
}
|
|
119
|
-
return amounts;
|
|
120
|
-
}
|
|
121
|
-
sellerNonces: number[];
|
|
122
|
-
bribeNonce ? : number;
|
|
123
|
-
profitNonce ? : number;
|
|
124
|
-
;
|
|
125
|
-
// ✅ getDeadline 从 bundle-helpers.js 导入
|
|
126
|
-
// ✅ Merkle 配置(保留兼容)
|
|
127
|
-
// ✅ 签名参数(不依赖 Merkle)
|
|
128
|
-
// ✅ Merkle 参数(保留兼容)
|
|
129
|
-
profitHopWallets ? : GeneratedWallet[]; // ✅ 利润多跳钱包
|
|
130
|
-
metadata ? : {
|
|
131
|
-
buyerAddress: string,
|
|
132
|
-
sellerAddress: string,
|
|
133
|
-
buyAmount: string,
|
|
134
|
-
sellAmount: string,
|
|
135
|
-
profitAmount: string, // 利润金额(BNB)
|
|
136
|
-
// ✅ 多笔买卖信息
|
|
137
|
-
buyCount: number,
|
|
138
|
-
sellCount: number,
|
|
139
|
-
buyAmounts: string[], // ✅ 每笔买入金额数组(随机分配)
|
|
140
|
-
sellAmounts: string[]
|
|
141
|
-
};
|
|
142
|
-
;
|
|
143
|
-
export async function pancakeBundleBuyFirstMerkle(params) {
|
|
144
|
-
const { buyerPrivateKey, buyerPrivateKeys, sellerPrivateKey, sellerPrivateKeys, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18, startNonces, // ✅ 可选:前端预获取的 nonces
|
|
145
|
-
buyCount: _buyCount, sellCount: _sellCount, taxRateBps = 0 // ✅ 税率(基点),用于计算实际可卖出代币数量
|
|
146
|
-
} = params;
|
|
147
|
-
// ✅ 判断是否为多钱包模式
|
|
148
|
-
const isMultiWalletMode = !!(buyerPrivateKeys && buyerPrivateKeys.length > 0) ||
|
|
149
|
-
!!(sellerPrivateKeys && sellerPrivateKeys.length > 0);
|
|
150
|
-
// ✅ 多钱包模式:buyCount/sellCount 代表钱包数量
|
|
151
|
-
// 单钱包模式(向后兼容):buyCount/sellCount 代表同一钱包执行的交易笔数
|
|
152
|
-
const buyCount = _buyCount ?? 1;
|
|
153
|
-
const sellCount = _sellCount ?? 1;
|
|
154
|
-
// ✅ 多钱包模式:使用单独的处理逻辑
|
|
155
|
-
if (isMultiWalletMode) {
|
|
156
|
-
return await pancakeBundleBuyFirstMultiWallet({
|
|
157
|
-
buyerPrivateKeys: buyerPrivateKeys || (buyerPrivateKey ? [buyerPrivateKey] : []),
|
|
158
|
-
sellerPrivateKeys: sellerPrivateKeys || (sellerPrivateKey ? [sellerPrivateKey] : []),
|
|
159
|
-
tokenAddress,
|
|
160
|
-
routeParams,
|
|
161
|
-
buyerFunds,
|
|
162
|
-
config,
|
|
163
|
-
quoteToken,
|
|
164
|
-
quoteTokenDecimals,
|
|
165
|
-
taxRateBps // ✅ 传递税率
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
// ✅ 单钱包模式(向后兼容):验证买卖笔数
|
|
169
|
-
if (!buyerPrivateKey || !sellerPrivateKey) {
|
|
170
|
-
throw new Error('单钱包模式需要提供 buyerPrivateKey 和 sellerPrivateKey');
|
|
171
|
-
}
|
|
172
|
-
validateSwapCounts(buyCount, sellCount);
|
|
173
|
-
// ✅ 计算利润比例(根据 userType 动态调整)
|
|
174
|
-
const totalTxCount = buyCount + sellCount;
|
|
175
|
-
const profitRateBps = getProfitRatePerTxBps(config.userType) * totalTxCount;
|
|
176
|
-
// ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
|
|
177
|
-
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
178
|
-
const context = createPancakeContext(config);
|
|
179
|
-
const buyer = new Wallet(buyerPrivateKey, context.provider);
|
|
180
|
-
const seller = new Wallet(sellerPrivateKey, context.provider);
|
|
181
|
-
const sameAddress = buyer.address.toLowerCase() === seller.address.toLowerCase();
|
|
182
|
-
const buyerFundsInfo = await calculateBuyerFunds({
|
|
183
|
-
buyer,
|
|
184
|
-
buyerFunds,
|
|
185
|
-
buyerFundsPercentage,
|
|
186
|
-
reserveGas: config.reserveGasBNB,
|
|
187
|
-
useNativeToken,
|
|
188
|
-
quoteToken,
|
|
189
|
-
quoteTokenDecimals,
|
|
190
|
-
provider: context.provider
|
|
191
|
-
});
|
|
192
|
-
const quoteResult = await quoteTokenOutput({
|
|
193
|
-
routeParams,
|
|
194
|
-
buyerFundsWei: buyerFundsInfo.buyerFundsWei,
|
|
195
|
-
provider: context.provider,
|
|
196
|
-
v2RouterOverride: config.v2RouterAddress
|
|
197
|
-
});
|
|
198
|
-
// ✅ 拆分买入和卖出金额
|
|
199
|
-
const buyAmountsWei = splitAmount(buyerFundsInfo.buyerFundsWei, buyCount);
|
|
200
|
-
// ✅ 税币支持:买入时会被扣税,实际获得的代币 = 报价 * (1 - 税率)
|
|
201
|
-
// 卖出金额需要按扣税后的实际代币数量来计算
|
|
202
|
-
let actualTokenOut = quoteResult.quotedTokenOut;
|
|
203
|
-
if (taxRateBps > 0) {
|
|
204
|
-
// 税率基点转换:500 bps = 5% = 0.05,实际获得 = 报价 * (10000 - 税率) / 10000
|
|
205
|
-
actualTokenOut = (quoteResult.quotedTokenOut * BigInt(10000 - taxRateBps)) / 10000n;
|
|
206
|
-
console.log(`[pancakeBundleBuyFirst] 税币模式: 税率=${taxRateBps}bps, 报价=${quoteResult.quotedTokenOut}, 实际可卖=${actualTokenOut}`);
|
|
207
|
-
}
|
|
208
|
-
const sellAmountsWei = splitAmount(actualTokenOut, sellCount);
|
|
209
|
-
// ✅ 构建多笔买入和卖出交易
|
|
210
|
-
const swapUnsignedArray = await buildMultiRouteTransactions({
|
|
211
|
-
routeParams,
|
|
212
|
-
buyAmountsWei,
|
|
213
|
-
sellAmountsWei,
|
|
214
|
-
buyer,
|
|
215
|
-
seller,
|
|
216
|
-
tokenAddress,
|
|
217
|
-
useNativeToken,
|
|
218
|
-
v2RouterAddress: config.v2RouterAddress
|
|
219
|
-
});
|
|
220
|
-
const finalGasLimit = getGasLimit(config);
|
|
221
|
-
const gasPrice = await getGasPrice(context.provider, config);
|
|
222
|
-
const txType = config.txType ?? 0;
|
|
223
|
-
const nonceManager = new NonceManager(context.provider);
|
|
224
|
-
// ✅ 直接使用买入金额作为利润基础(先买后卖模式)
|
|
225
|
-
// profitRateBps = 每笔交易利润率(bps) * 交易笔数
|
|
226
|
-
// 例如:v1用户,1买1卖 → 5 * 2 = 10 bps = 0.1%
|
|
227
|
-
const tokenProfitAmount = (buyerFundsInfo.buyerFundsWei * BigInt(profitRateBps)) / 10000n;
|
|
228
|
-
// ✅ ERC20 购买:将代币利润转换为等值 BNB(因为利润是用 BNB 转账的)
|
|
229
|
-
let profitAmount = tokenProfitAmount;
|
|
230
|
-
if (!useNativeToken && tokenProfitAmount > 0n && quoteToken) {
|
|
231
|
-
const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
|
|
232
|
-
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
233
|
-
profitAmount = await getTokenToNativeQuote(context.provider, quoteToken, tokenProfitAmount, 'BSC', version, fee);
|
|
234
|
-
// ERC20→BNB 转换完成
|
|
235
|
-
}
|
|
236
|
-
// ✅ 获取贿赂金额
|
|
237
|
-
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
238
|
-
? ethers.parseEther(String(config.bribeAmount))
|
|
239
|
-
: 0n;
|
|
240
|
-
const needBribeTx = bribeAmount > 0n;
|
|
241
|
-
// ✅ 规划多笔交易 nonce(忽略 startNonces,使用新的多笔规划)
|
|
242
|
-
const multiNoncePlan = await planMultiNonces({
|
|
243
|
-
buyer,
|
|
244
|
-
seller,
|
|
245
|
-
buyCount,
|
|
246
|
-
sellCount,
|
|
247
|
-
sameAddress,
|
|
248
|
-
extractProfit: profitAmount > 0n,
|
|
249
|
-
needBribeTx,
|
|
250
|
-
nonceManager
|
|
251
|
-
});
|
|
252
|
-
// ✅ 贿赂交易放在首位(由卖方发送,与利润交易同一钱包)
|
|
253
|
-
let bribeTx = null;
|
|
254
|
-
if (needBribeTx && multiNoncePlan.bribeNonce !== undefined) {
|
|
255
|
-
bribeTx = await seller.signTransaction({
|
|
256
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
257
|
-
value: bribeAmount,
|
|
258
|
-
nonce: multiNoncePlan.bribeNonce,
|
|
259
|
-
gasPrice,
|
|
260
|
-
gasLimit: GAS_LIMITS.BRIBE,
|
|
261
|
-
chainId: context.chainId,
|
|
262
|
-
type: txType
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
// ✅ 并行签名所有买入交易
|
|
266
|
-
const signedBuys = await Promise.all(swapUnsignedArray.buyUnsignedArray.map((unsigned, i) => buyer.signTransaction({
|
|
267
|
-
...unsigned,
|
|
268
|
-
from: buyer.address,
|
|
269
|
-
nonce: multiNoncePlan.buyerNonces[i],
|
|
270
|
-
gasLimit: finalGasLimit,
|
|
271
|
-
gasPrice,
|
|
272
|
-
chainId: context.chainId,
|
|
273
|
-
type: txType
|
|
274
|
-
})));
|
|
275
|
-
// ✅ 并行签名所有卖出交易
|
|
276
|
-
const signedSells = await Promise.all(swapUnsignedArray.sellUnsignedArray.map((unsigned, i) => seller.signTransaction({
|
|
277
|
-
...unsigned,
|
|
278
|
-
from: seller.address,
|
|
279
|
-
nonce: multiNoncePlan.sellerNonces[i],
|
|
280
|
-
gasLimit: finalGasLimit,
|
|
281
|
-
gasPrice,
|
|
282
|
-
chainId: context.chainId,
|
|
283
|
-
type: txType
|
|
284
|
-
})));
|
|
285
|
-
nonceManager.clearTemp();
|
|
286
|
-
validateFinalBalances({
|
|
287
|
-
sameAddress,
|
|
288
|
-
buyerFundsWei: buyerFundsInfo.buyerFundsWei,
|
|
289
|
-
buyerBalance: buyerFundsInfo.buyerBalance,
|
|
290
|
-
reserveGas: buyerFundsInfo.reserveGas,
|
|
291
|
-
gasLimit: finalGasLimit,
|
|
292
|
-
gasPrice,
|
|
293
|
-
useNativeToken,
|
|
294
|
-
quoteTokenDecimals,
|
|
295
|
-
provider: context.provider,
|
|
296
|
-
buyerAddress: buyer.address
|
|
297
|
-
});
|
|
298
|
-
// ✅ 组装顺序:贿赂 → 买入(多笔) → 卖出(多笔)(与 Flap 一致)
|
|
299
|
-
const allTransactions = [];
|
|
300
|
-
if (bribeTx)
|
|
301
|
-
allTransactions.push(bribeTx);
|
|
302
|
-
allTransactions.push(...signedBuys, ...signedSells);
|
|
303
|
-
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
304
|
-
const profitResult = await buildProfitTransaction({
|
|
305
|
-
provider: context.provider,
|
|
306
|
-
seller,
|
|
307
|
-
profitAmount,
|
|
308
|
-
profitNonce: multiNoncePlan.profitNonce,
|
|
309
|
-
gasPrice,
|
|
310
|
-
chainId: context.chainId,
|
|
311
|
-
txType
|
|
312
|
-
});
|
|
313
|
-
let profitHopWallets;
|
|
314
|
-
if (profitResult) {
|
|
315
|
-
allTransactions.push(...profitResult.signedTransactions);
|
|
316
|
-
profitHopWallets = profitResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
317
|
-
}
|
|
318
|
-
return {
|
|
319
|
-
signedTransactions: allTransactions,
|
|
320
|
-
profitHopWallets, // ✅ 返回利润多跳钱包
|
|
321
|
-
metadata: {
|
|
322
|
-
buyerAddress: buyer.address,
|
|
323
|
-
sellerAddress: seller.address,
|
|
324
|
-
buyAmount: useNativeToken
|
|
325
|
-
? ethers.formatEther(buyerFundsInfo.buyerFundsWei)
|
|
326
|
-
: ethers.formatUnits(buyerFundsInfo.buyerFundsWei, quoteTokenDecimals),
|
|
327
|
-
sellAmount: quoteResult.quotedTokenOut.toString(),
|
|
328
|
-
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
329
|
-
// ✅ 返回多笔买卖信息
|
|
330
|
-
buyCount,
|
|
331
|
-
sellCount,
|
|
332
|
-
// ✅ 返回每笔交易的具体金额(随机分配后的数组)
|
|
333
|
-
buyAmounts: buyAmountsWei.map(amt => useNativeToken
|
|
334
|
-
? ethers.formatEther(amt)
|
|
335
|
-
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
336
|
-
sellAmounts: sellAmountsWei.map(amt => amt.toString())
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
function createPancakeContext(config) {
|
|
341
|
-
const chainId = config.chainId ?? 56;
|
|
342
|
-
const provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
343
|
-
chainId,
|
|
344
|
-
name: 'BSC'
|
|
345
|
-
});
|
|
346
|
-
return { chainId, provider };
|
|
347
|
-
}
|
|
348
|
-
async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, reserveGas, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
|
|
349
|
-
const reserveGasWei = ethers.parseEther((reserveGas ?? 0.0005).toString());
|
|
350
|
-
// ✅ 根据是否使用原生代币获取不同的余额
|
|
351
|
-
let buyerBalance;
|
|
352
|
-
if (useNativeToken) {
|
|
353
|
-
buyerBalance = await buyer.provider.getBalance(buyer.address);
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
// ERC20 代币余额
|
|
357
|
-
const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
|
|
358
|
-
buyerBalance = await erc20.balanceOf(buyer.address);
|
|
359
|
-
}
|
|
360
|
-
let buyerFundsWei;
|
|
361
|
-
if (buyerFunds !== undefined) {
|
|
362
|
-
// ✅ 根据代币精度解析金额
|
|
363
|
-
buyerFundsWei = useNativeToken
|
|
364
|
-
? ethers.parseEther(String(buyerFunds))
|
|
365
|
-
: ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
|
|
366
|
-
}
|
|
367
|
-
else if (buyerFundsPercentage !== undefined) {
|
|
368
|
-
const pct = Math.max(0, Math.min(100, buyerFundsPercentage));
|
|
369
|
-
// ✅ 原生代币需要预留 Gas,ERC20 不需要
|
|
370
|
-
const spendable = useNativeToken
|
|
371
|
-
? (buyerBalance > reserveGasWei ? buyerBalance - reserveGasWei : 0n)
|
|
372
|
-
: buyerBalance;
|
|
373
|
-
buyerFundsWei = (spendable * BigInt(Math.floor(pct * 100))) / 10000n;
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
throw new Error('必须提供 buyerFunds 或 buyerFundsPercentage');
|
|
377
|
-
}
|
|
378
|
-
if (buyerFundsWei <= 0n) {
|
|
379
|
-
throw new Error('buyerFunds 需要大于 0');
|
|
380
|
-
}
|
|
381
|
-
// ✅ 余额检查
|
|
382
|
-
if (useNativeToken) {
|
|
383
|
-
if (buyerBalance < buyerFundsWei + reserveGasWei) {
|
|
384
|
-
throw new Error(`买方余额不足: 需要 ${ethers.formatEther(buyerFundsWei + reserveGasWei)} BNB,实际 ${ethers.formatEther(buyerBalance)} BNB`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
else {
|
|
388
|
-
// ERC20 购买:检查代币余额
|
|
389
|
-
if (buyerBalance < buyerFundsWei) {
|
|
390
|
-
throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(buyerFundsWei, quoteTokenDecimals)},实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
|
|
391
|
-
}
|
|
392
|
-
// ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas
|
|
393
|
-
const buyerBnbBalance = await buyer.provider.getBalance(buyer.address);
|
|
394
|
-
if (buyerBnbBalance < reserveGasWei) {
|
|
395
|
-
throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGasWei)} BNB,实际 ${ethers.formatEther(buyerBnbBalance)} BNB`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return { buyerFundsWei, buyerBalance, reserveGas: reserveGasWei };
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* ✅ 使用 quote-helpers 统一报价
|
|
402
|
-
*/
|
|
403
|
-
async function quoteTokenOutput({ routeParams, buyerFundsWei, provider, v2RouterOverride }) {
|
|
404
|
-
// V2 路由
|
|
405
|
-
if (routeParams.routeType === 'v2') {
|
|
406
|
-
const { v2Path } = routeParams;
|
|
407
|
-
const tokenIn = v2Path[0];
|
|
408
|
-
const tokenOut = v2Path[v2Path.length - 1];
|
|
409
|
-
const result = await quoteV2(provider, tokenIn, tokenOut, buyerFundsWei, 'BSC', v2RouterOverride);
|
|
410
|
-
if (result.amountOut <= 0n) {
|
|
411
|
-
throw new Error('V2 报价失败');
|
|
412
|
-
}
|
|
413
|
-
return { quotedTokenOut: result.amountOut };
|
|
414
|
-
}
|
|
415
|
-
// V3 Single 路由
|
|
416
|
-
if (routeParams.routeType === 'v3-single') {
|
|
417
|
-
const paramsV3 = routeParams;
|
|
418
|
-
const result = await quoteV3(provider, paramsV3.v3TokenIn, paramsV3.v3TokenOut, buyerFundsWei, 'BSC', paramsV3.v3Fee);
|
|
419
|
-
if (result.amountOut <= 0n) {
|
|
420
|
-
throw new Error('V3 报价失败');
|
|
421
|
-
}
|
|
422
|
-
return { quotedTokenOut: result.amountOut };
|
|
423
|
-
}
|
|
424
|
-
// V3 Multi 路由
|
|
425
|
-
const paramsV3m = routeParams;
|
|
426
|
-
if (!paramsV3m.v2Path || paramsV3m.v2Path.length < 2) {
|
|
427
|
-
throw new Error('V3 多跳需要提供 v2Path 用于路径推断');
|
|
428
|
-
}
|
|
429
|
-
const tokenIn = paramsV3m.v2Path[0];
|
|
430
|
-
const tokenOut = paramsV3m.v2Path[paramsV3m.v2Path.length - 1];
|
|
431
|
-
const result = await quoteV3(provider, tokenIn, tokenOut, buyerFundsWei, 'BSC');
|
|
432
|
-
if (result.amountOut <= 0n) {
|
|
433
|
-
throw new Error('V3 多跳报价失败');
|
|
434
|
-
}
|
|
435
|
-
return { quotedTokenOut: result.amountOut };
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* ✅ 构建多笔买入和卖出交易
|
|
439
|
-
*/
|
|
440
|
-
async function buildMultiRouteTransactions({ routeParams, buyAmountsWei, sellAmountsWei, buyer, seller, tokenAddress, useNativeToken = true, v2RouterAddress }) {
|
|
441
|
-
const deadline = getDeadline();
|
|
442
|
-
if (routeParams.routeType === 'v2') {
|
|
443
|
-
const { v2Path } = routeParams;
|
|
444
|
-
const reversePath = [...v2Path].reverse();
|
|
445
|
-
const v2Addr = v2RouterAddress || PANCAKE_V2_ROUTER_ADDRESS;
|
|
446
|
-
const v2RouterBuyer = new Contract(v2Addr, PANCAKE_V2_ROUTER_ABI, buyer);
|
|
447
|
-
const v2RouterSeller = new Contract(v2Addr, PANCAKE_V2_ROUTER_ABI, seller);
|
|
448
|
-
// ✅ 根据是否使用原生代币选择不同的函数
|
|
449
|
-
if (useNativeToken) {
|
|
450
|
-
// BNB 池子:使用 swapExactETHForTokens / swapExactTokensForETH
|
|
451
|
-
const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => {
|
|
452
|
-
const buyValue = amount + FLAT_FEE;
|
|
453
|
-
return v2RouterBuyer.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyValue });
|
|
454
|
-
}));
|
|
455
|
-
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => v2RouterSeller.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
|
|
456
|
-
return { buyUnsignedArray, sellUnsignedArray };
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
// ✅ ERC20 池子(如 USDT):使用 swapExactTokensForTokens
|
|
460
|
-
const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => v2RouterBuyer.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, v2Path, buyer.address, deadline)));
|
|
461
|
-
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => v2RouterSeller.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(amount, 0n, reversePath, seller.address, deadline)));
|
|
462
|
-
return { buyUnsignedArray, sellUnsignedArray };
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
if (routeParams.routeType === 'v3-single') {
|
|
466
|
-
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
467
|
-
const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
468
|
-
const v3RouterBuyer = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
|
|
469
|
-
const v3RouterSeller = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
|
|
470
|
-
// 构建多笔买入交易(ERC20 模式 value = 0,因为通过代币授权支付)
|
|
471
|
-
const buyUnsignedArray = await Promise.all(buyAmountsWei.map(amount => {
|
|
472
|
-
const buyValue = useNativeToken ? amount + FLAT_FEE : 0n;
|
|
473
|
-
const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
474
|
-
tokenIn: v3TokenIn,
|
|
475
|
-
tokenOut: v3TokenOut,
|
|
476
|
-
fee: v3Fee,
|
|
477
|
-
recipient: buyer.address,
|
|
478
|
-
amountIn: amount,
|
|
479
|
-
amountOutMinimum: 0n,
|
|
480
|
-
sqrtPriceLimitX96: 0n
|
|
481
|
-
}]);
|
|
482
|
-
return v3RouterBuyer.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
|
|
483
|
-
}));
|
|
484
|
-
// ✅ 根据是否使用原生代币构建不同的卖出交易
|
|
485
|
-
if (useNativeToken) {
|
|
486
|
-
// BNB 池子:卖出后 unwrap WBNB 为 BNB
|
|
487
|
-
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => {
|
|
488
|
-
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
489
|
-
tokenIn: v3TokenOut,
|
|
490
|
-
tokenOut: v3TokenIn,
|
|
491
|
-
fee: v3Fee,
|
|
492
|
-
recipient: PANCAKE_V3_ROUTER_ADDRESS,
|
|
493
|
-
amountIn: amount,
|
|
494
|
-
amountOutMinimum: 0n,
|
|
495
|
-
sqrtPriceLimitX96: 0n
|
|
496
|
-
}]);
|
|
497
|
-
const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
|
|
498
|
-
return v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
|
|
499
|
-
}));
|
|
500
|
-
return { buyUnsignedArray, sellUnsignedArray };
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
// ✅ ERC20 池子:卖出后直接获得 ERC20
|
|
504
|
-
const sellUnsignedArray = await Promise.all(sellAmountsWei.map(amount => {
|
|
505
|
-
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
506
|
-
tokenIn: v3TokenOut,
|
|
507
|
-
tokenOut: v3TokenIn,
|
|
508
|
-
fee: v3Fee,
|
|
509
|
-
recipient: seller.address, // 直接发送到卖方地址
|
|
510
|
-
amountIn: amount,
|
|
511
|
-
amountOutMinimum: 0n,
|
|
512
|
-
sqrtPriceLimitX96: 0n
|
|
513
|
-
}]);
|
|
514
|
-
return v3RouterSeller.multicall.populateTransaction(deadline, [sellSwapData]);
|
|
515
|
-
}));
|
|
516
|
-
return { buyUnsignedArray, sellUnsignedArray };
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
throw new Error('V3 多跳路由暂不支持官方合约,请使用 V2 路由或 V3 单跳');
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* ✅ 规划多笔交易 nonce(与 Flap 一致)
|
|
523
|
-
* 交易顺序:贿赂 → 买入(多笔) → 卖出(多笔) → 利润
|
|
524
|
-
*/
|
|
525
|
-
async function planMultiNonces({ buyer, seller, buyCount, sellCount, sameAddress, extractProfit, needBribeTx, nonceManager }) {
|
|
526
|
-
const profitNonceCount = extractProfit ? 1 : 0;
|
|
527
|
-
if (sameAddress) {
|
|
528
|
-
// 同一地址:贿赂(可选) + 买入(多笔) + 卖出(多笔) + 利润(可选)
|
|
529
|
-
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
530
|
-
const totalTxCount = bribeTxCount + buyCount + sellCount + profitNonceCount;
|
|
531
|
-
const nonces = await nonceManager.getNextNonceBatch(buyer, totalTxCount);
|
|
532
|
-
let idx = 0;
|
|
533
|
-
const bribeNonce = needBribeTx ? nonces[idx++] : undefined;
|
|
534
|
-
const buyerNonces = nonces.slice(idx, idx + buyCount);
|
|
535
|
-
idx += buyCount;
|
|
536
|
-
const sellerNonces = nonces.slice(idx, idx + sellCount);
|
|
537
|
-
idx += sellCount;
|
|
538
|
-
const profitNonce = extractProfit ? nonces[idx] : undefined;
|
|
539
|
-
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
540
|
-
}
|
|
541
|
-
// 不同地址
|
|
542
|
-
// 买方:buyCount 个 nonce
|
|
543
|
-
// 卖方:贿赂(可选) + sellCount + 利润(可选) 个 nonce
|
|
544
|
-
const bribeTxCount = needBribeTx ? 1 : 0;
|
|
545
|
-
const sellerTxCount = bribeTxCount + sellCount + profitNonceCount;
|
|
546
|
-
const [buyerNonces, sellerNoncesAll] = await Promise.all([
|
|
547
|
-
nonceManager.getNextNonceBatch(buyer, buyCount),
|
|
548
|
-
nonceManager.getNextNonceBatch(seller, sellerTxCount)
|
|
549
|
-
]);
|
|
550
|
-
let idx = 0;
|
|
551
|
-
const bribeNonce = needBribeTx ? sellerNoncesAll[idx++] : undefined;
|
|
552
|
-
const sellerNonces = sellerNoncesAll.slice(idx, idx + sellCount);
|
|
553
|
-
idx += sellCount;
|
|
554
|
-
const profitNonce = extractProfit ? sellerNoncesAll[idx] : undefined;
|
|
555
|
-
return { buyerNonces, sellerNonces, bribeNonce, profitNonce };
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* 构建利润多跳转账交易(强制 2 跳中转)
|
|
559
|
-
*/
|
|
560
|
-
async function buildProfitTransaction({ provider, seller, profitAmount, profitNonce, gasPrice, chainId, txType }) {
|
|
561
|
-
if (profitNonce === undefined || profitAmount === 0n) {
|
|
562
|
-
return null;
|
|
563
|
-
}
|
|
564
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
565
|
-
provider,
|
|
566
|
-
payerWallet: seller,
|
|
567
|
-
profitAmount,
|
|
568
|
-
profitRecipient: PROFIT_CONFIG.RECIPIENT,
|
|
569
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
570
|
-
gasPrice,
|
|
571
|
-
chainId,
|
|
572
|
-
txType,
|
|
573
|
-
startNonce: profitNonce
|
|
574
|
-
});
|
|
575
|
-
return { signedTransactions: profitHopResult.signedTransactions, hopWallets: profitHopResult.hopWallets };
|
|
576
|
-
}
|
|
577
|
-
async function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress }) {
|
|
578
|
-
const gasCost = gasLimit * gasPrice;
|
|
579
|
-
if (sameAddress) {
|
|
580
|
-
// 同一地址:需要足够的余额支付两笔交易
|
|
581
|
-
if (useNativeToken) {
|
|
582
|
-
const requiredCombined = buyerFundsWei + FLAT_FEE * 2n + gasCost * 2n;
|
|
583
|
-
if (buyerBalance < requiredCombined) {
|
|
584
|
-
throw new Error(`账户余额不足:\n - 需要: ${ethers.formatEther(requiredCombined)} BNB(含两笔Gas与两笔手续费)\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
// ERC20:检查代币余额 + BNB Gas 余额
|
|
589
|
-
const requiredToken = buyerFundsWei + FLAT_FEE * 2n;
|
|
590
|
-
if (buyerBalance < requiredToken) {
|
|
591
|
-
throw new Error(`账户代币余额不足:\n - 需要: ${ethers.formatUnits(requiredToken, quoteTokenDecimals)}\n - 实际: ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
|
|
592
|
-
}
|
|
593
|
-
// 检查 BNB Gas
|
|
594
|
-
if (provider && buyerAddress) {
|
|
595
|
-
const bnbBalance = await provider.getBalance(buyerAddress);
|
|
596
|
-
const requiredGas = gasCost * 2n;
|
|
597
|
-
if (bnbBalance < requiredGas) {
|
|
598
|
-
throw new Error(`账户 BNB 余额不足 (用于支付 Gas):\n - 需要: ${ethers.formatEther(requiredGas)} BNB\n - 实际: ${ethers.formatEther(bnbBalance)} BNB`);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
// 不同地址
|
|
605
|
-
if (useNativeToken) {
|
|
606
|
-
const requiredBuyer = buyerFundsWei + FLAT_FEE + reserveGas;
|
|
607
|
-
if (buyerBalance < requiredBuyer) {
|
|
608
|
-
throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(requiredBuyer)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
// ERC20 余额已在 calculateBuyerFunds 中检查过
|
|
612
|
-
}
|
|
613
|
-
// ==================== 多钱包模式 ====================
|
|
614
|
-
/**
|
|
615
|
-
* ✅ 多钱包捆绑换手
|
|
616
|
-
* - 多个买方钱包执行买入(每个钱包1笔)
|
|
617
|
-
* - 多个卖方钱包执行卖出(每个钱包1笔)
|
|
618
|
-
* - 买入总价值 = 卖出总价值
|
|
619
|
-
* - ✅ 优化:最大化并行操作
|
|
620
|
-
*/
|
|
621
|
-
async function pancakeBundleBuyFirstMultiWallet(params) {
|
|
622
|
-
const { buyerPrivateKeys, sellerPrivateKeys, tokenAddress, routeParams, buyerFunds, config, quoteToken, quoteTokenDecimals = 18, taxRateBps = 0 // ✅ 税率(基点)
|
|
623
|
-
} = params;
|
|
624
|
-
const buyCount = buyerPrivateKeys.length;
|
|
625
|
-
const sellCount = sellerPrivateKeys.length;
|
|
626
|
-
if (buyCount === 0)
|
|
627
|
-
throw new Error('买方钱包数量不能为0');
|
|
628
|
-
if (sellCount === 0)
|
|
629
|
-
throw new Error('卖方钱包数量不能为0');
|
|
630
|
-
// 验证总交易数不超过限制
|
|
631
|
-
validateSwapCounts(buyCount, sellCount);
|
|
632
|
-
// ✅ 计算利润比例(根据 userType 动态调整)
|
|
633
|
-
const totalTxCount = buyCount + sellCount;
|
|
634
|
-
const profitRateBps = getProfitRatePerTxBps(config.userType) * totalTxCount;
|
|
635
|
-
// ✅ 判断是否使用原生代币
|
|
636
|
-
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
637
|
-
const context = createPancakeContext(config);
|
|
638
|
-
const nonceManager = new NonceManager(context.provider);
|
|
639
|
-
// 创建所有钱包实例
|
|
640
|
-
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
|
|
641
|
-
const sellers = sellerPrivateKeys.map(pk => new Wallet(pk, context.provider));
|
|
642
|
-
// 使用第一个卖方作为主卖方(支付贿赂和利润)
|
|
643
|
-
const mainSeller = sellers[0];
|
|
644
|
-
// ✅ 计算总交易金额
|
|
645
|
-
let totalFundsWei;
|
|
646
|
-
if (buyerFunds) {
|
|
647
|
-
totalFundsWei = useNativeToken
|
|
648
|
-
? ethers.parseEther(String(buyerFunds))
|
|
649
|
-
: ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
throw new Error('多钱包模式必须提供 buyerFunds(总交易金额)');
|
|
653
|
-
}
|
|
654
|
-
if (totalFundsWei <= 0n) {
|
|
655
|
-
throw new Error('交易金额必须大于0');
|
|
656
|
-
}
|
|
657
|
-
const finalGasLimit = getGasLimit(config);
|
|
658
|
-
const txType = config.txType ?? 0;
|
|
659
|
-
const deadline = BigInt(getDeadline());
|
|
660
|
-
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
661
|
-
? ethers.parseEther(String(config.bribeAmount))
|
|
662
|
-
: 0n;
|
|
663
|
-
const needBribeTx = bribeAmount > 0n;
|
|
664
|
-
// ✅ 第一批并行:报价 + Gas价格 + 所有钱包 nonces
|
|
665
|
-
// 预先计算所有唯一钱包地址(去重)
|
|
666
|
-
const allWallets = [...sellers, ...buyers];
|
|
667
|
-
const uniqueWallets = allWallets.filter((w, i) => {
|
|
668
|
-
const addr = w.address.toLowerCase();
|
|
669
|
-
const firstIdx = allWallets.findIndex(x => x.address.toLowerCase() === addr);
|
|
670
|
-
return firstIdx === i;
|
|
671
|
-
});
|
|
672
|
-
const [quoteResult, gasPrice, noncesArray] = await Promise.all([
|
|
673
|
-
quoteTokenOutput({
|
|
674
|
-
routeParams,
|
|
675
|
-
buyerFundsWei: totalFundsWei,
|
|
676
|
-
provider: context.provider,
|
|
677
|
-
v2RouterOverride: config.v2RouterAddress
|
|
678
|
-
}),
|
|
679
|
-
getGasPrice(context.provider, config),
|
|
680
|
-
nonceManager.getNextNoncesForWallets(uniqueWallets)
|
|
681
|
-
]);
|
|
682
|
-
// 构建 nonces Map
|
|
683
|
-
const noncesMap = new Map();
|
|
684
|
-
uniqueWallets.forEach((wallet, i) => {
|
|
685
|
-
noncesMap.set(wallet.address.toLowerCase(), noncesArray[i]);
|
|
686
|
-
});
|
|
687
|
-
// ✅ 将总金额平均分配给买方
|
|
688
|
-
const buyAmountsWei = splitAmount(totalFundsWei, buyCount);
|
|
689
|
-
// ✅ 税币支持:买入时会被扣税,实际获得的代币 = 报价 * (1 - 税率)
|
|
690
|
-
let actualTokenOut = quoteResult.quotedTokenOut;
|
|
691
|
-
if (taxRateBps > 0) {
|
|
692
|
-
actualTokenOut = (quoteResult.quotedTokenOut * BigInt(10000 - taxRateBps)) / 10000n;
|
|
693
|
-
console.log(`[pancakeBundleBuyFirstMultiWallet] 税币模式: 税率=${taxRateBps}bps, 报价=${quoteResult.quotedTokenOut}, 实际可卖=${actualTokenOut}`);
|
|
694
|
-
}
|
|
695
|
-
// ✅ 将代币平均分配给卖方
|
|
696
|
-
const sellAmountsWei = splitAmount(actualTokenOut, sellCount);
|
|
697
|
-
// ✅ 直接使用总交易金额作为利润基础(多钱包模式)
|
|
698
|
-
// profitRateBps = 每笔交易利润率(bps) * 交易笔数
|
|
699
|
-
const tokenProfitAmount = (totalFundsWei * BigInt(profitRateBps)) / 10000n;
|
|
700
|
-
// ✅ ERC20 购买:将代币利润转换为等值 BNB(因为利润是用 BNB 转账的)
|
|
701
|
-
let profitAmount = tokenProfitAmount;
|
|
702
|
-
if (!useNativeToken && tokenProfitAmount > 0n && quoteToken) {
|
|
703
|
-
const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
|
|
704
|
-
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
705
|
-
profitAmount = await getTokenToNativeQuote(context.provider, quoteToken, tokenProfitAmount, 'BSC', version, fee);
|
|
706
|
-
// ERC20→BNB 转换完成
|
|
707
|
-
}
|
|
708
|
-
// ✅ 第三批并行:构建并签名所有交易
|
|
709
|
-
const allTransactions = [];
|
|
710
|
-
// 1. 贿赂交易(由主卖方支付)- 先处理以确定 nonce 偏移
|
|
711
|
-
let bribeTxPromise = null;
|
|
712
|
-
if (needBribeTx) {
|
|
713
|
-
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
714
|
-
const bribeNonce = noncesMap.get(mainSellerAddr);
|
|
715
|
-
noncesMap.set(mainSellerAddr, bribeNonce + 1);
|
|
716
|
-
bribeTxPromise = mainSeller.signTransaction({
|
|
717
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
718
|
-
value: bribeAmount,
|
|
719
|
-
nonce: bribeNonce,
|
|
720
|
-
gasPrice,
|
|
721
|
-
gasLimit: GAS_LIMITS.BRIBE,
|
|
722
|
-
chainId: context.chainId,
|
|
723
|
-
type: txType
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
// 2. 预分配所有 nonces(同步操作,避免竞态)
|
|
727
|
-
const buyerNonces = [];
|
|
728
|
-
const sellerNonces = [];
|
|
729
|
-
buyers.forEach(buyer => {
|
|
730
|
-
const addr = buyer.address.toLowerCase();
|
|
731
|
-
const nonce = noncesMap.get(addr);
|
|
732
|
-
buyerNonces.push(nonce);
|
|
733
|
-
noncesMap.set(addr, nonce + 1);
|
|
734
|
-
});
|
|
735
|
-
sellers.forEach(seller => {
|
|
736
|
-
const addr = seller.address.toLowerCase();
|
|
737
|
-
const nonce = noncesMap.get(addr);
|
|
738
|
-
sellerNonces.push(nonce);
|
|
739
|
-
noncesMap.set(addr, nonce + 1);
|
|
740
|
-
});
|
|
741
|
-
// 3. 并行构建所有买入交易
|
|
742
|
-
const buyTxPromises = buyers.map(async (buyer, i) => {
|
|
743
|
-
const unsigned = await buildSingleBuyTx({
|
|
744
|
-
routeParams,
|
|
745
|
-
buyAmount: buyAmountsWei[i],
|
|
746
|
-
buyer,
|
|
747
|
-
tokenAddress,
|
|
748
|
-
useNativeToken,
|
|
749
|
-
deadline,
|
|
750
|
-
v2RouterAddress: config.v2RouterAddress
|
|
751
|
-
});
|
|
752
|
-
return buyer.signTransaction({
|
|
753
|
-
...unsigned,
|
|
754
|
-
from: buyer.address,
|
|
755
|
-
nonce: buyerNonces[i],
|
|
756
|
-
gasLimit: finalGasLimit,
|
|
757
|
-
gasPrice,
|
|
758
|
-
chainId: context.chainId,
|
|
759
|
-
type: txType
|
|
760
|
-
});
|
|
761
|
-
});
|
|
762
|
-
// 4. 并行构建所有卖出交易
|
|
763
|
-
const sellTxPromises = sellers.map(async (seller, i) => {
|
|
764
|
-
const unsigned = await buildSingleSellTx({
|
|
765
|
-
routeParams,
|
|
766
|
-
sellAmount: sellAmountsWei[i],
|
|
767
|
-
seller,
|
|
768
|
-
tokenAddress,
|
|
769
|
-
useNativeToken,
|
|
770
|
-
deadline,
|
|
771
|
-
v2RouterAddress: config.v2RouterAddress
|
|
772
|
-
});
|
|
773
|
-
return seller.signTransaction({
|
|
774
|
-
...unsigned,
|
|
775
|
-
from: seller.address,
|
|
776
|
-
nonce: sellerNonces[i],
|
|
777
|
-
gasLimit: finalGasLimit,
|
|
778
|
-
gasPrice,
|
|
779
|
-
chainId: context.chainId,
|
|
780
|
-
type: txType
|
|
781
|
-
});
|
|
782
|
-
});
|
|
783
|
-
// ✅ 并行签名所有交易(贿赂 + 买入 + 卖出)
|
|
784
|
-
const [bribeTx, signedBuys, signedSells] = await Promise.all([
|
|
785
|
-
bribeTxPromise,
|
|
786
|
-
Promise.all(buyTxPromises),
|
|
787
|
-
Promise.all(sellTxPromises)
|
|
788
|
-
]);
|
|
789
|
-
// 组装交易顺序:贿赂 → 买入 → 卖出
|
|
790
|
-
if (bribeTx)
|
|
791
|
-
allTransactions.push(bribeTx);
|
|
792
|
-
allTransactions.push(...signedBuys, ...signedSells);
|
|
793
|
-
// 5. 利润多跳转账(由主卖方支付)
|
|
794
|
-
let profitHopWallets;
|
|
795
|
-
if (profitAmount > 0n) {
|
|
796
|
-
const mainSellerAddr = mainSeller.address.toLowerCase();
|
|
797
|
-
const profitNonce = noncesMap.get(mainSellerAddr);
|
|
798
|
-
const profitResult = await buildProfitTransaction({
|
|
799
|
-
provider: context.provider,
|
|
800
|
-
seller: mainSeller,
|
|
801
|
-
profitAmount,
|
|
802
|
-
profitNonce,
|
|
803
|
-
gasPrice,
|
|
804
|
-
chainId: context.chainId,
|
|
805
|
-
txType
|
|
806
|
-
});
|
|
807
|
-
if (profitResult) {
|
|
808
|
-
allTransactions.push(...profitResult.signedTransactions);
|
|
809
|
-
profitHopWallets = profitResult.hopWallets;
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
nonceManager.clearTemp();
|
|
813
|
-
return {
|
|
814
|
-
signedTransactions: allTransactions,
|
|
815
|
-
profitHopWallets,
|
|
816
|
-
metadata: {
|
|
817
|
-
buyerAddress: buyers.map(b => b.address).join(','),
|
|
818
|
-
sellerAddress: sellers.map(s => s.address).join(','),
|
|
819
|
-
buyAmount: useNativeToken
|
|
820
|
-
? ethers.formatEther(totalFundsWei)
|
|
821
|
-
: ethers.formatUnits(totalFundsWei, quoteTokenDecimals),
|
|
822
|
-
sellAmount: quoteResult.quotedTokenOut.toString(),
|
|
823
|
-
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
824
|
-
buyCount,
|
|
825
|
-
sellCount,
|
|
826
|
-
buyAmounts: buyAmountsWei.map(amt => useNativeToken
|
|
827
|
-
? ethers.formatEther(amt)
|
|
828
|
-
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
829
|
-
sellAmounts: sellAmountsWei.map(amt => amt.toString())
|
|
830
|
-
}
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
// ✅ 构建单笔买入交易
|
|
834
|
-
async function buildSingleBuyTx({ routeParams, buyAmount, buyer, tokenAddress, useNativeToken, deadline, v2RouterAddress }) {
|
|
835
|
-
if (routeParams.routeType === 'v2') {
|
|
836
|
-
const { v2Path } = routeParams;
|
|
837
|
-
const v2Router = new Contract(v2RouterAddress || PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, buyer);
|
|
838
|
-
if (useNativeToken) {
|
|
839
|
-
return await v2Router.swapExactETHForTokensSupportingFeeOnTransferTokens.populateTransaction(0n, v2Path, buyer.address, deadline, { value: buyAmount });
|
|
840
|
-
}
|
|
841
|
-
else {
|
|
842
|
-
return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(buyAmount, 0n, v2Path, buyer.address, deadline);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
if (routeParams.routeType === 'v3-single') {
|
|
846
|
-
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
847
|
-
const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
848
|
-
const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, buyer);
|
|
849
|
-
const buyValue = useNativeToken ? buyAmount : 0n;
|
|
850
|
-
const buySwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
851
|
-
tokenIn: v3TokenIn,
|
|
852
|
-
tokenOut: v3TokenOut,
|
|
853
|
-
fee: v3Fee,
|
|
854
|
-
recipient: buyer.address,
|
|
855
|
-
amountIn: buyAmount,
|
|
856
|
-
amountOutMinimum: 0n,
|
|
857
|
-
sqrtPriceLimitX96: 0n
|
|
858
|
-
}]);
|
|
859
|
-
return await v3Router.multicall.populateTransaction(deadline, [buySwapData], { value: buyValue });
|
|
860
|
-
}
|
|
861
|
-
throw new Error('V3 多跳路由暂不支持');
|
|
862
|
-
}
|
|
863
|
-
// ✅ 构建单笔卖出交易
|
|
864
|
-
async function buildSingleSellTx({ routeParams, sellAmount, seller, tokenAddress, useNativeToken, deadline, v2RouterAddress }) {
|
|
865
|
-
if (routeParams.routeType === 'v2') {
|
|
866
|
-
const { v2Path } = routeParams;
|
|
867
|
-
const reversePath = [...v2Path].reverse();
|
|
868
|
-
const v2Router = new Contract(v2RouterAddress || PANCAKE_V2_ROUTER_ADDRESS, PANCAKE_V2_ROUTER_ABI, seller);
|
|
869
|
-
if (useNativeToken) {
|
|
870
|
-
return await v2Router.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
|
|
871
|
-
}
|
|
872
|
-
else {
|
|
873
|
-
return await v2Router.swapExactTokensForTokensSupportingFeeOnTransferTokens.populateTransaction(sellAmount, 0n, reversePath, seller.address, deadline);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
if (routeParams.routeType === 'v3-single') {
|
|
877
|
-
const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
|
|
878
|
-
const v3RouterIface = new ethers.Interface(PANCAKE_V3_ROUTER_ABI);
|
|
879
|
-
const v3Router = new Contract(PANCAKE_V3_ROUTER_ADDRESS, PANCAKE_V3_ROUTER_ABI, seller);
|
|
880
|
-
if (useNativeToken) {
|
|
881
|
-
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
882
|
-
tokenIn: v3TokenOut,
|
|
883
|
-
tokenOut: v3TokenIn,
|
|
884
|
-
fee: v3Fee,
|
|
885
|
-
recipient: PANCAKE_V3_ROUTER_ADDRESS,
|
|
886
|
-
amountIn: sellAmount,
|
|
887
|
-
amountOutMinimum: 0n,
|
|
888
|
-
sqrtPriceLimitX96: 0n
|
|
889
|
-
}]);
|
|
890
|
-
const sellUnwrapData = v3RouterIface.encodeFunctionData('unwrapWETH9', [0n, seller.address]);
|
|
891
|
-
return await v3Router.multicall.populateTransaction(deadline, [sellSwapData, sellUnwrapData]);
|
|
892
|
-
}
|
|
893
|
-
else {
|
|
894
|
-
const sellSwapData = v3RouterIface.encodeFunctionData('exactInputSingle', [{
|
|
895
|
-
tokenIn: v3TokenOut,
|
|
896
|
-
tokenOut: v3TokenIn,
|
|
897
|
-
fee: v3Fee,
|
|
898
|
-
recipient: seller.address,
|
|
899
|
-
amountIn: sellAmount,
|
|
900
|
-
amountOutMinimum: 0n,
|
|
901
|
-
sqrtPriceLimitX96: 0n
|
|
902
|
-
}]);
|
|
903
|
-
return await v3Router.multicall.populateTransaction(deadline, [sellSwapData]);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
throw new Error('V3 多跳路由暂不支持');
|
|
907
|
-
}
|