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,1448 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flap Protocol 内盘捆绑换手(Merkle Bundle)
|
|
3
|
-
*
|
|
4
|
-
* 功能:钱包A卖出代币 → 钱包B买入相同数量 → 原子执行
|
|
5
|
-
*/
|
|
6
|
-
import { ethers, Contract, Wallet } from 'ethers';
|
|
7
|
-
import { calculateSellAmount } from '../../../../utils/swap-helpers.js';
|
|
8
|
-
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../../../utils/bundle-helpers.js';
|
|
9
|
-
import { FLAP_PORTAL_ADDRESSES } from '../../constants.js';
|
|
10
|
-
import { PROFIT_CONFIG, ADDRESSES, ZERO_ADDRESS } from '../../../../utils/constants.js';
|
|
11
|
-
import { ERC20_ALLOWANCE_ABI, V2_ROUTER_QUOTE_ABI, ERC20_BALANCE_ABI, ERC20_ABI } from '../../../abis/common.js';
|
|
12
|
-
import { getGasPriceConfig, getTxType, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI } from '../config.js';
|
|
13
|
-
import { generateWallets } from '../../../../utils/wallet.js';
|
|
14
|
-
import { GAS_LIMITS, CHAINS } from '../../../constants/index.js';
|
|
15
|
-
// ==================== 多跳转账常量(使用统一常量) ====================
|
|
16
|
-
const NATIVE_TRANSFER_GAS_LIMIT = GAS_LIMITS.NATIVE_TRANSFER;
|
|
17
|
-
const ERC20_TRANSFER_GAS_LIMIT_HOP = GAS_LIMITS.ERC20_TRANSFER;
|
|
18
|
-
const BRIBE_GAS_LIMIT = GAS_LIMITS.BRIBE;
|
|
19
|
-
hopWallets: Wallet[];
|
|
20
|
-
hopWalletsInfo: GeneratedWallet[];
|
|
21
|
-
;
|
|
22
|
-
/**
|
|
23
|
-
* 生成分发多跳路径
|
|
24
|
-
*/
|
|
25
|
-
function generateDisperseHopPaths(targetAddresses, hopCount, provider) {
|
|
26
|
-
if (hopCount <= 0) {
|
|
27
|
-
return targetAddresses.map(addr => ({
|
|
28
|
-
targetAddress: addr,
|
|
29
|
-
hopWallets: [],
|
|
30
|
-
hopWalletsInfo: []
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
return targetAddresses.map(targetAddress => {
|
|
34
|
-
const hopWalletsInfo = generateWallets(hopCount);
|
|
35
|
-
const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
|
|
36
|
-
return { targetAddress, hopWallets, hopWalletsInfo };
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* 构建原生代币多跳转账链
|
|
41
|
-
*/
|
|
42
|
-
async function buildNativeHopChain(payer, path, finalAmount, gasPrice, chainId, txType, payerNonce) {
|
|
43
|
-
const signedTxs = [];
|
|
44
|
-
const hopCount = path.hopWallets.length;
|
|
45
|
-
if (hopCount === 0) {
|
|
46
|
-
signedTxs.push(await payer.signTransaction({
|
|
47
|
-
to: path.targetAddress,
|
|
48
|
-
value: finalAmount,
|
|
49
|
-
nonce: payerNonce,
|
|
50
|
-
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
51
|
-
gasPrice,
|
|
52
|
-
chainId,
|
|
53
|
-
type: txType
|
|
54
|
-
}));
|
|
55
|
-
return signedTxs;
|
|
56
|
-
}
|
|
57
|
-
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
58
|
-
// 计算每跳需要的金额
|
|
59
|
-
const amountsPerHop = [];
|
|
60
|
-
for (let i = 0; i < hopCount; i++) {
|
|
61
|
-
const remainingHops = hopCount - i;
|
|
62
|
-
amountsPerHop.push(finalAmount + hopGasCost * BigInt(remainingHops));
|
|
63
|
-
}
|
|
64
|
-
// payer → hop1
|
|
65
|
-
signedTxs.push(await payer.signTransaction({
|
|
66
|
-
to: path.hopWallets[0].address,
|
|
67
|
-
value: amountsPerHop[0],
|
|
68
|
-
nonce: payerNonce,
|
|
69
|
-
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
70
|
-
gasPrice,
|
|
71
|
-
chainId,
|
|
72
|
-
type: txType
|
|
73
|
-
}));
|
|
74
|
-
// hop1 → hop2 → ... → target
|
|
75
|
-
for (let i = 0; i < hopCount; i++) {
|
|
76
|
-
const fromWallet = path.hopWallets[i];
|
|
77
|
-
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
78
|
-
const amount = i === hopCount - 1 ? finalAmount : amountsPerHop[i + 1];
|
|
79
|
-
signedTxs.push(await fromWallet.signTransaction({
|
|
80
|
-
to: toAddress,
|
|
81
|
-
value: amount,
|
|
82
|
-
nonce: 0,
|
|
83
|
-
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
84
|
-
gasPrice,
|
|
85
|
-
chainId,
|
|
86
|
-
type: txType
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
return signedTxs;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* 构建 ERC20 多跳转账链
|
|
93
|
-
*/
|
|
94
|
-
async function buildERC20HopChain(payer, path, erc20Address, erc20Amount, gasPrice, chainId, txType, payerNonce) {
|
|
95
|
-
const signedTxs = [];
|
|
96
|
-
const hopCount = path.hopWallets.length;
|
|
97
|
-
const erc20Interface = new ethers.Interface(ERC20_ABI);
|
|
98
|
-
if (hopCount === 0) {
|
|
99
|
-
const data = erc20Interface.encodeFunctionData('transfer', [path.targetAddress, erc20Amount]);
|
|
100
|
-
signedTxs.push(await payer.signTransaction({
|
|
101
|
-
to: erc20Address,
|
|
102
|
-
data,
|
|
103
|
-
value: 0n,
|
|
104
|
-
nonce: payerNonce,
|
|
105
|
-
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
106
|
-
gasPrice,
|
|
107
|
-
chainId,
|
|
108
|
-
type: txType
|
|
109
|
-
}));
|
|
110
|
-
return signedTxs;
|
|
111
|
-
}
|
|
112
|
-
// payer → hop1
|
|
113
|
-
const firstData = erc20Interface.encodeFunctionData('transfer', [path.hopWallets[0].address, erc20Amount]);
|
|
114
|
-
signedTxs.push(await payer.signTransaction({
|
|
115
|
-
to: erc20Address,
|
|
116
|
-
data: firstData,
|
|
117
|
-
value: 0n,
|
|
118
|
-
nonce: payerNonce,
|
|
119
|
-
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
120
|
-
gasPrice,
|
|
121
|
-
chainId,
|
|
122
|
-
type: txType
|
|
123
|
-
}));
|
|
124
|
-
// hop1 → hop2 → ... → target (nonce=1,nonce=0 用于 BNB 转发)
|
|
125
|
-
for (let i = 0; i < hopCount; i++) {
|
|
126
|
-
const fromWallet = path.hopWallets[i];
|
|
127
|
-
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
128
|
-
const data = erc20Interface.encodeFunctionData('transfer', [toAddress, erc20Amount]);
|
|
129
|
-
signedTxs.push(await fromWallet.signTransaction({
|
|
130
|
-
to: erc20Address,
|
|
131
|
-
data,
|
|
132
|
-
value: 0n,
|
|
133
|
-
nonce: 1, // nonce=0 已用于 BNB 转发
|
|
134
|
-
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
135
|
-
gasPrice,
|
|
136
|
-
chainId,
|
|
137
|
-
type: txType
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
140
|
-
return signedTxs;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* 构建 BNB 多跳转账链(为 ERC20 中间钱包预留 gas)
|
|
144
|
-
*/
|
|
145
|
-
async function buildBNBHopChainForERC20(payer, path, finalGasAmount, gasPrice, chainId, txType, payerNonce) {
|
|
146
|
-
const signedTxs = [];
|
|
147
|
-
const hopCount = path.hopWallets.length;
|
|
148
|
-
if (hopCount === 0) {
|
|
149
|
-
return signedTxs;
|
|
150
|
-
}
|
|
151
|
-
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
152
|
-
const erc20GasCost = ERC20_TRANSFER_GAS_LIMIT_HOP * gasPrice;
|
|
153
|
-
const gasPerHop = hopGasCost + erc20GasCost * 2n;
|
|
154
|
-
const amountsPerHop = [];
|
|
155
|
-
for (let i = 0; i < hopCount; i++) {
|
|
156
|
-
const remainingHops = hopCount - i;
|
|
157
|
-
amountsPerHop.push(finalGasAmount + gasPerHop * BigInt(remainingHops));
|
|
158
|
-
}
|
|
159
|
-
// payer → hop1
|
|
160
|
-
signedTxs.push(await payer.signTransaction({
|
|
161
|
-
to: path.hopWallets[0].address,
|
|
162
|
-
value: amountsPerHop[0],
|
|
163
|
-
nonce: payerNonce,
|
|
164
|
-
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
165
|
-
gasPrice,
|
|
166
|
-
chainId,
|
|
167
|
-
type: txType
|
|
168
|
-
}));
|
|
169
|
-
// hop1 → hop2 → ... → target (nonce=0)
|
|
170
|
-
for (let i = 0; i < hopCount; i++) {
|
|
171
|
-
const fromWallet = path.hopWallets[i];
|
|
172
|
-
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
173
|
-
const amount = i === hopCount - 1 ? finalGasAmount : amountsPerHop[i + 1];
|
|
174
|
-
signedTxs.push(await fromWallet.signTransaction({
|
|
175
|
-
to: toAddress,
|
|
176
|
-
value: amount,
|
|
177
|
-
nonce: 0,
|
|
178
|
-
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
179
|
-
gasPrice,
|
|
180
|
-
chainId,
|
|
181
|
-
type: txType
|
|
182
|
-
}));
|
|
183
|
-
}
|
|
184
|
-
return signedTxs;
|
|
185
|
-
}
|
|
186
|
-
// ✅ 用户类型(影响利润率)
|
|
187
|
-
// ✅ 简化的签名配置
|
|
188
|
-
/**
|
|
189
|
-
* 获取 Gas Limit
|
|
190
|
-
*/
|
|
191
|
-
function getGasLimit(config, defaultGas = 500000) {
|
|
192
|
-
if (config.gasLimit !== undefined) {
|
|
193
|
-
return typeof config.gasLimit === 'bigint' ? config.gasLimit : BigInt(config.gasLimit);
|
|
194
|
-
}
|
|
195
|
-
const multiplier = config.gasLimitMultiplier ?? 1.0;
|
|
196
|
-
return BigInt(Math.ceil(defaultGas * multiplier));
|
|
197
|
-
}
|
|
198
|
-
// ✅ ABI 别名
|
|
199
|
-
const ROUTER_ABI = V2_ROUTER_QUOTE_ABI;
|
|
200
|
-
const ERC20_BALANCE_OF_ABI = ERC20_BALANCE_ABI;
|
|
201
|
-
const APPROVE_INTERFACE = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
|
|
202
|
-
// ✅ 链常量
|
|
203
|
-
const BSC_PANCAKE_V2_ROUTER = ADDRESSES.BSC.PancakeV2Router;
|
|
204
|
-
const BSC_WBNB = ADDRESSES.BSC.WBNB;
|
|
205
|
-
const MONAD_PANCAKE_V2_ROUTER = '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9';
|
|
206
|
-
const MONAD_WMON = ADDRESSES.MONAD.WMON;
|
|
207
|
-
/**
|
|
208
|
-
* 获取 ERC20 代币 → 原生代币的报价
|
|
209
|
-
* @param provider - Provider 实例
|
|
210
|
-
* @param tokenAddress - ERC20 代币地址
|
|
211
|
-
* @param tokenAmount - 代币数量(wei)
|
|
212
|
-
* @param chainId - 链 ID(56=BSC, 143=Monad)
|
|
213
|
-
* @returns 等值的原生代币数量(wei),失败返回 0n
|
|
214
|
-
*/
|
|
215
|
-
async function getTokenToNativeQuote(provider, tokenAddress, tokenAmount, chainId) {
|
|
216
|
-
if (tokenAmount <= 0n)
|
|
217
|
-
return 0n;
|
|
218
|
-
try {
|
|
219
|
-
if (chainId === CHAINS.BSC.chainId) {
|
|
220
|
-
// BSC: 使用 PancakeSwap V2 Router
|
|
221
|
-
const router = new Contract(BSC_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
|
|
222
|
-
const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, BSC_WBNB]);
|
|
223
|
-
return amounts[1];
|
|
224
|
-
}
|
|
225
|
-
if (chainId === CHAINS.MONAD.chainId) {
|
|
226
|
-
// ✅ Monad: 使用 PancakeSwap V2 Router
|
|
227
|
-
const router = new Contract(MONAD_PANCAKE_V2_ROUTER, ROUTER_ABI, provider);
|
|
228
|
-
const amounts = await router.getAmountsOut(tokenAmount, [tokenAddress, MONAD_WMON]);
|
|
229
|
-
return amounts[1];
|
|
230
|
-
}
|
|
231
|
-
// ✅ Base (8453) / XLayer (196) 等链暂不支持 V2 报价,返回 0
|
|
232
|
-
return 0n;
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
// 报价失败返回 0(可能是流动性不足或路径不存在)
|
|
236
|
-
return 0n;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
// ✅ Merkle 配置(保留兼容)
|
|
240
|
-
// ✅ 签名参数(不依赖 Merkle)
|
|
241
|
-
// ✅ Merkle 参数(保留兼容)
|
|
242
|
-
profitHopWallets ? : GeneratedWallet[]; // ✅ 利润多跳中间钱包
|
|
243
|
-
metadata ? : {
|
|
244
|
-
sellerAddress: string,
|
|
245
|
-
buyerAddress: string,
|
|
246
|
-
sellAmount: string,
|
|
247
|
-
buyAmount: string,
|
|
248
|
-
hasApproval: boolean, // 是否包含授权交易
|
|
249
|
-
profitAmount: string
|
|
250
|
-
};
|
|
251
|
-
;
|
|
252
|
-
/**
|
|
253
|
-
* 获取链ID
|
|
254
|
-
*/
|
|
255
|
-
function getChainId(chain) {
|
|
256
|
-
switch (chain) {
|
|
257
|
-
case 'bsc': return 56;
|
|
258
|
-
case 'xlayer': return 196;
|
|
259
|
-
case 'base': return 8453;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* 获取原生代币名称
|
|
264
|
-
*/
|
|
265
|
-
function getNativeTokenName(chain) {
|
|
266
|
-
switch (chain) {
|
|
267
|
-
case 'bsc': return 'BNB';
|
|
268
|
-
case 'xlayer': return 'OKB';
|
|
269
|
-
case 'base': return 'ETH';
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Flap内盘捆绑换手
|
|
274
|
-
* ✅ 支持 quoteToken:传入 USDT 等地址时,卖出得到该代币,买入使用该代币
|
|
275
|
-
*/
|
|
276
|
-
export async function flapBundleSwapMerkle(params) {
|
|
277
|
-
const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, config, quoteToken, quoteTokenDecimals, startNonces // ✅ 可选:前端预获取的 nonces
|
|
278
|
-
} = params;
|
|
279
|
-
const chainContext = createChainContext(chain, config);
|
|
280
|
-
const seller = new Wallet(sellerPrivateKey, chainContext.provider);
|
|
281
|
-
const buyer = new Wallet(buyerPrivateKey, chainContext.provider);
|
|
282
|
-
const nonceManager = new NonceManager(chainContext.provider);
|
|
283
|
-
const finalGasLimit = getGasLimit(config);
|
|
284
|
-
const txType = getTxType(config);
|
|
285
|
-
// ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
|
|
286
|
-
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
287
|
-
const outputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
|
|
288
|
-
// ✅ 优化:第一批并行 - calculateSellAmount、gasPrice
|
|
289
|
-
const [sellAmountResult, gasPrice] = await Promise.all([
|
|
290
|
-
calculateSellAmount(chainContext.provider, tokenAddress, seller.address, sellAmount, sellPercentage),
|
|
291
|
-
getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config))
|
|
292
|
-
]);
|
|
293
|
-
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
294
|
-
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
295
|
-
// ✅ 优化:第二批并行 - approval、quote(已移除滑点保护)
|
|
296
|
-
const [approvalTx, quote] = await Promise.all([
|
|
297
|
-
config.skipApprovalCheck
|
|
298
|
-
? Promise.resolve(null)
|
|
299
|
-
: buildApprovalTransaction({
|
|
300
|
-
tokenAddress,
|
|
301
|
-
seller,
|
|
302
|
-
provider: chainContext.provider,
|
|
303
|
-
decimals,
|
|
304
|
-
portalAddress: chainContext.portalAddress,
|
|
305
|
-
chainId: chainContext.chainId,
|
|
306
|
-
config
|
|
307
|
-
}),
|
|
308
|
-
quoteSellOutput({
|
|
309
|
-
portalAddress: chainContext.portalAddress,
|
|
310
|
-
tokenAddress,
|
|
311
|
-
sellAmountWei,
|
|
312
|
-
provider: chainContext.provider,
|
|
313
|
-
skipQuoteOnError: config.skipQuoteOnError,
|
|
314
|
-
outputToken // ✅ 传递输出代币
|
|
315
|
-
})
|
|
316
|
-
]);
|
|
317
|
-
// ✅ 优化:第三批并行 - buyerNeed、validateBalances
|
|
318
|
-
const buyerNeed = await calculateBuyerNeed({
|
|
319
|
-
buyer,
|
|
320
|
-
quotedNative: quote.quotedNative,
|
|
321
|
-
reserveGasEth: config.reserveGasETH,
|
|
322
|
-
nativeToken: chainContext.nativeToken,
|
|
323
|
-
useNativeToken,
|
|
324
|
-
quoteToken,
|
|
325
|
-
quoteTokenDecimals,
|
|
326
|
-
provider: chainContext.provider
|
|
327
|
-
});
|
|
328
|
-
// ✅ 计算利润(基于卖出报价,根据 userType 动态调整)
|
|
329
|
-
const tokenProfitAmount = calculateProfitAmount(quote.quotedNative, config.userType);
|
|
330
|
-
// ✅ ERC20 输出:获取代币利润等值的原生代币(BNB)报价
|
|
331
|
-
let nativeProfitAmount = tokenProfitAmount; // 原生代币输出时直接使用
|
|
332
|
-
if (!useNativeToken && tokenProfitAmount > 0n) {
|
|
333
|
-
// 将代币利润转换为等值 BNB
|
|
334
|
-
nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, quoteToken, tokenProfitAmount, chainContext.chainId);
|
|
335
|
-
}
|
|
336
|
-
// ✅ 获取贿赂金额
|
|
337
|
-
const bribeAmount = getBribeAmount(config);
|
|
338
|
-
const needBribeTx = bribeAmount > 0n;
|
|
339
|
-
// ✅ 优化:第四批并行 - 构建交易、获取 nonces、验证余额
|
|
340
|
-
const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
|
|
341
|
-
const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
|
|
342
|
-
const [sellUnsigned, buyUnsigned, noncePlan] = await Promise.all([
|
|
343
|
-
portalSeller.swapExactInput.populateTransaction({
|
|
344
|
-
inputToken: tokenAddress,
|
|
345
|
-
outputToken, // ✅ 使用动态输出代币
|
|
346
|
-
inputAmount: sellAmountWei,
|
|
347
|
-
minOutputAmount: 0,
|
|
348
|
-
permitData: '0x'
|
|
349
|
-
}),
|
|
350
|
-
portalBuyer.swapExactInput.populateTransaction({
|
|
351
|
-
inputToken: outputToken, // ✅ 使用动态输入代币
|
|
352
|
-
outputToken: tokenAddress,
|
|
353
|
-
inputAmount: buyerNeed.maxBuyerValue,
|
|
354
|
-
minOutputAmount: 0,
|
|
355
|
-
permitData: '0x'
|
|
356
|
-
}, useNativeToken ? { value: buyerNeed.maxBuyerValue } : {} // ✅ ERC20 购买时 value=0
|
|
357
|
-
),
|
|
358
|
-
// ✅ 如果前端传入了 startNonces,直接使用(性能优化)
|
|
359
|
-
startNonces && startNonces.length >= 2
|
|
360
|
-
? buildNoncePlan(startNonces, !!approvalTx, nativeProfitAmount > 0n, needBribeTx)
|
|
361
|
-
: planNonces({
|
|
362
|
-
seller,
|
|
363
|
-
buyer,
|
|
364
|
-
approvalExists: !!approvalTx,
|
|
365
|
-
extractProfit: nativeProfitAmount > 0n,
|
|
366
|
-
needBribeTx,
|
|
367
|
-
nonceManager
|
|
368
|
-
}),
|
|
369
|
-
validateBalances({
|
|
370
|
-
buyerNeed,
|
|
371
|
-
buyerAddress: buyer.address,
|
|
372
|
-
portalGasCost: finalGasLimit * gasPrice,
|
|
373
|
-
provider: chainContext.provider,
|
|
374
|
-
chainContext,
|
|
375
|
-
seller,
|
|
376
|
-
useNativeToken, // ✅ 传递是否使用原生代币
|
|
377
|
-
quoteTokenDecimals // ✅ 传递代币精度
|
|
378
|
-
})
|
|
379
|
-
]);
|
|
380
|
-
// 构建交易请求
|
|
381
|
-
// ✅ 卖出交易 value 必须为 0,不能发送原生代币
|
|
382
|
-
const sellTx = buildTransactionRequest(sellUnsigned, {
|
|
383
|
-
from: seller.address,
|
|
384
|
-
nonce: noncePlan.sellerNonce,
|
|
385
|
-
gasLimit: finalGasLimit,
|
|
386
|
-
gasPrice,
|
|
387
|
-
priorityFee,
|
|
388
|
-
chainId: chainContext.chainId,
|
|
389
|
-
txType,
|
|
390
|
-
value: 0n // ✅ 卖出交易不发送原生代币
|
|
391
|
-
});
|
|
392
|
-
const buyTx = buildTransactionRequest(buyUnsigned, {
|
|
393
|
-
from: buyer.address,
|
|
394
|
-
nonce: noncePlan.buyerNonce,
|
|
395
|
-
gasLimit: finalGasLimit,
|
|
396
|
-
gasPrice,
|
|
397
|
-
priorityFee,
|
|
398
|
-
chainId: chainContext.chainId,
|
|
399
|
-
txType,
|
|
400
|
-
value: useNativeToken ? buyerNeed.maxBuyerValue : 0n // ✅ ERC20 购买时 value=0
|
|
401
|
-
});
|
|
402
|
-
// ✅ 贿赂交易放在首位
|
|
403
|
-
let bribeTx = null;
|
|
404
|
-
if (needBribeTx && noncePlan.bribeNonce !== undefined) {
|
|
405
|
-
bribeTx = await seller.signTransaction({
|
|
406
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
407
|
-
value: bribeAmount,
|
|
408
|
-
nonce: noncePlan.bribeNonce,
|
|
409
|
-
gasPrice,
|
|
410
|
-
gasLimit: BRIBE_GAS_LIMIT,
|
|
411
|
-
chainId: chainContext.chainId,
|
|
412
|
-
type: txType
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
// ✅ 并行签名卖出和买入交易
|
|
416
|
-
const [signedSell, signedBuy] = await Promise.all([
|
|
417
|
-
seller.signTransaction(sellTx),
|
|
418
|
-
buyer.signTransaction(buyTx)
|
|
419
|
-
]);
|
|
420
|
-
nonceManager.clearTemp();
|
|
421
|
-
// ✅ 组装顺序:贿赂 → 授权 → 卖出 → 买入
|
|
422
|
-
const allTransactions = [];
|
|
423
|
-
if (bribeTx)
|
|
424
|
-
allTransactions.push(bribeTx);
|
|
425
|
-
if (approvalTx)
|
|
426
|
-
allTransactions.push(approvalTx);
|
|
427
|
-
allTransactions.push(signedSell, signedBuy);
|
|
428
|
-
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
429
|
-
const profitResult = await buildProfitTransaction({
|
|
430
|
-
provider: chainContext.provider,
|
|
431
|
-
seller,
|
|
432
|
-
profitAmount: nativeProfitAmount,
|
|
433
|
-
profitNonce: noncePlan.profitNonce,
|
|
434
|
-
gasPrice,
|
|
435
|
-
chainId: chainContext.chainId,
|
|
436
|
-
txType
|
|
437
|
-
});
|
|
438
|
-
if (profitResult)
|
|
439
|
-
allTransactions.push(...profitResult.signedTransactions);
|
|
440
|
-
return {
|
|
441
|
-
signedTransactions: allTransactions,
|
|
442
|
-
profitHopWallets: profitResult?.hopWallets, // ✅ 导出利润多跳钱包
|
|
443
|
-
metadata: {
|
|
444
|
-
sellerAddress: seller.address,
|
|
445
|
-
buyerAddress: buyer.address,
|
|
446
|
-
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
447
|
-
buyAmount: useNativeToken
|
|
448
|
-
? ethers.formatEther(buyerNeed.maxBuyerValue)
|
|
449
|
-
: ethers.formatUnits(buyerNeed.maxBuyerValue, quoteTokenDecimals ?? 18),
|
|
450
|
-
hasApproval: !!approvalTx,
|
|
451
|
-
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined // ✅ 显示原生代币利润
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
// ==================== 工具函数 ====================
|
|
456
|
-
// ✅ Provider 缓存(复用连接,减少初始化开销)
|
|
457
|
-
const providerCache = new Map();
|
|
458
|
-
const PROVIDER_CACHE_TTL_MS = 60 * 1000; // 60秒缓存
|
|
459
|
-
function createChainContext(chain, config) {
|
|
460
|
-
const chainId = config.chainId ?? getChainId(chain);
|
|
461
|
-
const nativeToken = getNativeTokenName(chain);
|
|
462
|
-
const portalAddress = FLAP_PORTAL_ADDRESSES[chain.toUpperCase()];
|
|
463
|
-
// ✅ Provider 缓存
|
|
464
|
-
const cacheKey = `${chain}-${config.rpcUrl}`;
|
|
465
|
-
const now = Date.now();
|
|
466
|
-
const cached = providerCache.get(cacheKey);
|
|
467
|
-
let provider;
|
|
468
|
-
if (cached && cached.expireAt > now) {
|
|
469
|
-
provider = cached.provider;
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
provider = new ethers.JsonRpcProvider(config.rpcUrl, {
|
|
473
|
-
chainId,
|
|
474
|
-
name: chain.toUpperCase()
|
|
475
|
-
});
|
|
476
|
-
providerCache.set(cacheKey, { provider, expireAt: now + PROVIDER_CACHE_TTL_MS });
|
|
477
|
-
}
|
|
478
|
-
return { chainId, nativeToken, portalAddress, provider };
|
|
479
|
-
}
|
|
480
|
-
async function buildApprovalTransaction({ tokenAddress, seller, provider, decimals, portalAddress, chainId, config }) {
|
|
481
|
-
const erc20 = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
|
|
482
|
-
const currentAllowance = await erc20.allowance(seller.address, portalAddress);
|
|
483
|
-
const threshold = ethers.parseUnits('1000000000', decimals);
|
|
484
|
-
if (currentAllowance >= threshold) {
|
|
485
|
-
return null;
|
|
486
|
-
}
|
|
487
|
-
const nonceManager = new NonceManager(provider);
|
|
488
|
-
const approvalNonce = await nonceManager.getNextNonce(seller);
|
|
489
|
-
const gasPrice = await getOptimizedGasPrice(provider, getGasPriceConfig(config));
|
|
490
|
-
const txType = getTxType(config);
|
|
491
|
-
return await seller.signTransaction({
|
|
492
|
-
to: tokenAddress,
|
|
493
|
-
data: APPROVE_INTERFACE.encodeFunctionData('approve', [portalAddress, ethers.MaxUint256]),
|
|
494
|
-
value: 0n,
|
|
495
|
-
nonce: approvalNonce,
|
|
496
|
-
gasLimit: 80000n,
|
|
497
|
-
gasPrice,
|
|
498
|
-
chainId,
|
|
499
|
-
type: txType
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
async function quoteSellOutput({ portalAddress, tokenAddress, sellAmountWei, provider, skipQuoteOnError, outputToken = ZERO_ADDRESS // ✅ 默认使用原生代币
|
|
503
|
-
}) {
|
|
504
|
-
const portal = new Contract(portalAddress, PORTAL_ABI, provider);
|
|
505
|
-
try {
|
|
506
|
-
const quotedNative = await portal.quoteExactInput.staticCall({
|
|
507
|
-
inputToken: tokenAddress,
|
|
508
|
-
outputToken, // ✅ 使用动态输出代币
|
|
509
|
-
inputAmount: sellAmountWei
|
|
510
|
-
});
|
|
511
|
-
// ✅ 已移除滑点保护:minOutNative 固定为 0
|
|
512
|
-
return { quotedNative, minOutNative: 0n };
|
|
513
|
-
}
|
|
514
|
-
catch (err) {
|
|
515
|
-
if (skipQuoteOnError ?? true) {
|
|
516
|
-
return { quotedNative: 0n, minOutNative: 0n };
|
|
517
|
-
}
|
|
518
|
-
throw new Error(`卖出报价失败: ${err}`);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
// ✅ ERC20_BALANCE_OF_ABI 已在文件开头定义
|
|
522
|
-
async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, nativeToken, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
|
|
523
|
-
const reserveGas = ethers.parseEther((reserveGasEth || 0.0005).toString());
|
|
524
|
-
// ✅ 根据是否使用原生代币获取不同的余额
|
|
525
|
-
let buyerBalance;
|
|
526
|
-
if (useNativeToken) {
|
|
527
|
-
buyerBalance = await buyer.provider.getBalance(buyer.address);
|
|
528
|
-
}
|
|
529
|
-
else {
|
|
530
|
-
// ERC20 代币余额
|
|
531
|
-
const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
|
|
532
|
-
buyerBalance = await erc20.balanceOf(buyer.address);
|
|
533
|
-
}
|
|
534
|
-
// ✅ 已移除滑点保护:直接使用报价金额
|
|
535
|
-
const estimatedBuyerNeed = quotedNative;
|
|
536
|
-
// ✅ 原生代币需要预留 Gas,ERC20 不需要
|
|
537
|
-
const buyerNeedTotal = useNativeToken
|
|
538
|
-
? estimatedBuyerNeed + reserveGas
|
|
539
|
-
: estimatedBuyerNeed;
|
|
540
|
-
if (buyerBalance < buyerNeedTotal) {
|
|
541
|
-
if (useNativeToken) {
|
|
542
|
-
throw new Error(`买方余额不足:\n` +
|
|
543
|
-
` - 需要: ${ethers.formatEther(buyerNeedTotal)} ${nativeToken}\n` +
|
|
544
|
-
` - 实际: ${ethers.formatEther(buyerBalance)} ${nativeToken}`);
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
throw new Error(`买方代币余额不足:\n` +
|
|
548
|
-
` - 需要: ${ethers.formatUnits(buyerNeedTotal, quoteTokenDecimals)}\n` +
|
|
549
|
-
` - 实际: ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const maxBuyerValue = estimatedBuyerNeed > 0n
|
|
553
|
-
? estimatedBuyerNeed
|
|
554
|
-
: (useNativeToken ? buyerBalance - reserveGas : buyerBalance);
|
|
555
|
-
return { reserveGas, buyerBalance, buyerNeedTotal, maxBuyerValue };
|
|
556
|
-
}
|
|
557
|
-
async function validateBalances({ buyerNeed, buyerAddress, portalGasCost, provider, chainContext, seller, useNativeToken, quoteTokenDecimals = 18 }) {
|
|
558
|
-
const sellerBalance = await provider.getBalance(seller.address);
|
|
559
|
-
// ✅ 修复:买方余额已在 calculateBuyerNeed 中检查过
|
|
560
|
-
// 这里只需要检查:
|
|
561
|
-
// 1. 使用原生代币时:buyerNeed.buyerBalance 已包含购买金额 + Gas
|
|
562
|
-
// 2. 使用 ERC20 时:需要额外检查买方是否有足够 BNB 支付 Gas
|
|
563
|
-
if (!useNativeToken) {
|
|
564
|
-
// ERC20 购买时,买方仍需要 BNB 支付 Gas
|
|
565
|
-
const buyerBnbBalance = await provider.getBalance(buyerAddress);
|
|
566
|
-
const buyerGasCost = portalGasCost; // 买方交易的 Gas 费用
|
|
567
|
-
if (buyerBnbBalance < buyerGasCost) {
|
|
568
|
-
throw new Error(`买方 BNB 余额不足 (用于支付 Gas):\n` +
|
|
569
|
-
` - 需要: ${ethers.formatEther(buyerGasCost)} ${chainContext.nativeToken}\n` +
|
|
570
|
-
` - 实际: ${ethers.formatEther(buyerBnbBalance)} ${chainContext.nativeToken}`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (sellerBalance < portalGasCost) {
|
|
574
|
-
throw new Error(`卖方余额不足: 需要 ${ethers.formatEther(portalGasCost)} ${chainContext.nativeToken} (Gas),实际 ${ethers.formatEther(sellerBalance)} ${chainContext.nativeToken}`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* ✅ 从前端传入的 startNonces 构建 NoncePlan(用于性能优化)
|
|
579
|
-
* 顺序:[sellerBaseNonce, buyerNonce]
|
|
580
|
-
*/
|
|
581
|
-
function buildNoncePlan(startNonces, approvalExists, extractProfit, needBribeTx) {
|
|
582
|
-
let sellerNonce = startNonces[0];
|
|
583
|
-
const buyerNonce = startNonces[1];
|
|
584
|
-
let bribeNonce;
|
|
585
|
-
let profitNonce;
|
|
586
|
-
if (needBribeTx)
|
|
587
|
-
bribeNonce = sellerNonce++;
|
|
588
|
-
if (approvalExists)
|
|
589
|
-
sellerNonce++; // 授权交易占用一个 nonce
|
|
590
|
-
const sellNonce = sellerNonce++;
|
|
591
|
-
if (extractProfit)
|
|
592
|
-
profitNonce = sellerNonce;
|
|
593
|
-
return { sellerNonce: sellNonce, buyerNonce, bribeNonce, profitNonce };
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* ✅ 优化:使用批量 nonce 获取(JSON-RPC 批量请求)
|
|
597
|
-
* 交易顺序:贿赂 → 授权 → 卖出 → 买入 → 利润
|
|
598
|
-
*/
|
|
599
|
-
async function planNonces({ seller, buyer, approvalExists, extractProfit, needBribeTx, nonceManager }) {
|
|
600
|
-
if (needBribeTx || approvalExists || extractProfit) {
|
|
601
|
-
// 卖方需要多个 nonce:贿赂(可选) + 授权(可选) + 卖出 + 利润(可选)
|
|
602
|
-
const sellerTxCount = countTruthy([needBribeTx, approvalExists, true, extractProfit]);
|
|
603
|
-
// ✅ 优化:并行获取 seller 和 buyer 的 nonce
|
|
604
|
-
const [sellerNonces, buyerNonces] = await Promise.all([
|
|
605
|
-
nonceManager.getNextNonceBatch(seller, sellerTxCount),
|
|
606
|
-
nonceManager.getNextNoncesForWallets([buyer])
|
|
607
|
-
]);
|
|
608
|
-
let idx = 0;
|
|
609
|
-
const bribeNonce = needBribeTx ? sellerNonces[idx++] : undefined;
|
|
610
|
-
if (approvalExists)
|
|
611
|
-
idx++;
|
|
612
|
-
const sellerNonce = sellerNonces[idx++];
|
|
613
|
-
const profitNonce = extractProfit ? sellerNonces[idx] : undefined;
|
|
614
|
-
const buyerNonce = buyerNonces[0];
|
|
615
|
-
return { sellerNonce, buyerNonce, bribeNonce, profitNonce };
|
|
616
|
-
}
|
|
617
|
-
// ✅ 优化:使用 getNextNoncesForWallets 批量获取(单次网络往返)
|
|
618
|
-
const nonces = await nonceManager.getNextNoncesForWallets([seller, buyer]);
|
|
619
|
-
return { sellerNonce: nonces[0], buyerNonce: nonces[1] };
|
|
620
|
-
}
|
|
621
|
-
function buildTransactionRequest(unsigned, { from, nonce, gasLimit, gasPrice, priorityFee, chainId, txType, value }) {
|
|
622
|
-
const tx = {
|
|
623
|
-
...unsigned,
|
|
624
|
-
from,
|
|
625
|
-
nonce,
|
|
626
|
-
gasLimit,
|
|
627
|
-
chainId,
|
|
628
|
-
type: txType
|
|
629
|
-
};
|
|
630
|
-
if (value !== undefined) {
|
|
631
|
-
tx.value = value;
|
|
632
|
-
}
|
|
633
|
-
if (txType === 2) {
|
|
634
|
-
tx.maxFeePerGas = gasPrice;
|
|
635
|
-
tx.maxPriorityFeePerGas = priorityFee;
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
tx.gasPrice = gasPrice;
|
|
639
|
-
}
|
|
640
|
-
return tx;
|
|
641
|
-
}
|
|
642
|
-
hopWallets: GeneratedWallet[];
|
|
643
|
-
/**
|
|
644
|
-
* 构建利润多跳转账交易(强制 2 跳中转)
|
|
645
|
-
*/
|
|
646
|
-
async function buildProfitTransaction({ provider, seller, profitAmount, profitNonce, gasPrice, chainId, txType }) {
|
|
647
|
-
if (!profitNonce || profitAmount === 0n) {
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
const profitHopResult = await buildProfitHopTransactions({
|
|
651
|
-
provider,
|
|
652
|
-
payerWallet: seller,
|
|
653
|
-
profitAmount,
|
|
654
|
-
profitRecipient: getProfitRecipient(),
|
|
655
|
-
hopCount: PROFIT_HOP_COUNT,
|
|
656
|
-
gasPrice,
|
|
657
|
-
chainId,
|
|
658
|
-
txType,
|
|
659
|
-
startNonce: profitNonce
|
|
660
|
-
});
|
|
661
|
-
return {
|
|
662
|
-
signedTransactions: profitHopResult.signedTransactions,
|
|
663
|
-
hopWallets: profitHopResult.hopWallets
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
/**
|
|
667
|
-
* 根据 userType 计算利润率
|
|
668
|
-
*/
|
|
669
|
-
function getProfitRateBps(userType) {
|
|
670
|
-
if (userType === 'v0') {
|
|
671
|
-
return PROFIT_CONFIG.RATE_BPS_V0; // 0.06%
|
|
672
|
-
}
|
|
673
|
-
else if (userType === 'v1') {
|
|
674
|
-
return PROFIT_CONFIG.RATE_BPS_V1; // 0.05%
|
|
675
|
-
}
|
|
676
|
-
return PROFIT_CONFIG.RATE_BPS_V0; // 默认 0.06%
|
|
677
|
-
}
|
|
678
|
-
function calculateProfitAmount(quotedNative, userType) {
|
|
679
|
-
if (quotedNative <= 0n) {
|
|
680
|
-
return 0n;
|
|
681
|
-
}
|
|
682
|
-
const rateBps = getProfitRateBps(userType);
|
|
683
|
-
return (quotedNative * BigInt(rateBps)) / 10000n;
|
|
684
|
-
}
|
|
685
|
-
function countTruthy(values) {
|
|
686
|
-
return values.filter(Boolean).length;
|
|
687
|
-
}
|
|
688
|
-
// ==================== 批量换手(一卖多买)====================
|
|
689
|
-
sellerPrivateKey: string; // 卖方私钥(主钱包)
|
|
690
|
-
sellAmount ? : string; // 卖出数量
|
|
691
|
-
sellPercentage ? : number; // 卖出比例(0-100)
|
|
692
|
-
buyerPrivateKeys: string[]; // 买方私钥数组(子钱包)
|
|
693
|
-
buyerRatios ? : number[]; // 每个买方的分配比例(如 [0.3, 0.5, 0.2])
|
|
694
|
-
tokenAddress: string; // 代币地址
|
|
695
|
-
config: FlapSwapSignConfig; // 配置
|
|
696
|
-
quoteToken ? : string; // 报价代币(USDT 等)
|
|
697
|
-
quoteTokenDecimals ? : number; // 报价代币精度
|
|
698
|
-
// ✅ 可选:前端预获取的 nonces(性能优化)
|
|
699
|
-
// 顺序:[sellerNonce, buyer1Nonce, buyer2Nonce, ...]
|
|
700
|
-
startNonces ? : number[];
|
|
701
|
-
profitHopWallets ? : GeneratedWallet[]; // ✅ 利润多跳中间钱包
|
|
702
|
-
metadata ? : {
|
|
703
|
-
sellerAddress: string,
|
|
704
|
-
buyerAddresses: string[],
|
|
705
|
-
sellAmount: string,
|
|
706
|
-
buyAmounts: string[],
|
|
707
|
-
hasApproval: boolean,
|
|
708
|
-
profitAmount: string
|
|
709
|
-
};
|
|
710
|
-
/**
|
|
711
|
-
* Flap 内盘批量换手(一卖多买)
|
|
712
|
-
*
|
|
713
|
-
* 功能:主钱包一次卖出 → 多个子钱包同时买入 → 在同一个区块中完成
|
|
714
|
-
* 限制:最多 24 个买方(服务器限制 25 笔交易,包含 1 笔利润交易)
|
|
715
|
-
*/
|
|
716
|
-
export async function flapBatchSwapMerkle(params) {
|
|
717
|
-
const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, tokenAddress, config, quoteToken, quoteTokenDecimals = 18, startNonces // ✅ 可选:前端预获取的 nonces
|
|
718
|
-
} = params;
|
|
719
|
-
// ✅ 校验买方数量(最多 24 个)
|
|
720
|
-
const MAX_BUYERS = 24;
|
|
721
|
-
if (buyerPrivateKeys.length === 0) {
|
|
722
|
-
throw new Error('至少需要一个买方钱包');
|
|
723
|
-
}
|
|
724
|
-
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
725
|
-
throw new Error(`买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
726
|
-
}
|
|
727
|
-
const chainContext = createChainContext(chain, config);
|
|
728
|
-
const seller = new Wallet(sellerPrivateKey, chainContext.provider);
|
|
729
|
-
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
|
|
730
|
-
const nonceManager = new NonceManager(chainContext.provider);
|
|
731
|
-
const finalGasLimit = getGasLimit(config);
|
|
732
|
-
const txType = getTxType(config);
|
|
733
|
-
// ✅ 判断是否使用原生代币
|
|
734
|
-
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
735
|
-
const outputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
|
|
736
|
-
// ✅ 并行获取:卖出数量、gasPrice
|
|
737
|
-
const [sellAmountResult, gasPrice] = await Promise.all([
|
|
738
|
-
calculateSellAmount(chainContext.provider, tokenAddress, seller.address, sellAmount, sellPercentage),
|
|
739
|
-
getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config))
|
|
740
|
-
]);
|
|
741
|
-
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
742
|
-
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
743
|
-
// ✅ 并行获取:授权、报价(已移除滑点保护)
|
|
744
|
-
const [approvalTx, quote] = await Promise.all([
|
|
745
|
-
config.skipApprovalCheck
|
|
746
|
-
? Promise.resolve(null)
|
|
747
|
-
: buildApprovalTransaction({
|
|
748
|
-
tokenAddress,
|
|
749
|
-
seller,
|
|
750
|
-
provider: chainContext.provider,
|
|
751
|
-
decimals,
|
|
752
|
-
portalAddress: chainContext.portalAddress,
|
|
753
|
-
chainId: chainContext.chainId,
|
|
754
|
-
config
|
|
755
|
-
}),
|
|
756
|
-
quoteSellOutput({
|
|
757
|
-
portalAddress: chainContext.portalAddress,
|
|
758
|
-
tokenAddress,
|
|
759
|
-
sellAmountWei,
|
|
760
|
-
provider: chainContext.provider,
|
|
761
|
-
skipQuoteOnError: config.skipQuoteOnError,
|
|
762
|
-
outputToken
|
|
763
|
-
})
|
|
764
|
-
]);
|
|
765
|
-
// ✅ 计算每个买方的买入金额(按比例分配,已移除滑点保护)
|
|
766
|
-
const totalBuyAmount = quote.quotedNative;
|
|
767
|
-
let buyAmountsWei;
|
|
768
|
-
if (params.buyerRatios && params.buyerRatios.length === buyers.length) {
|
|
769
|
-
// 按比例分配
|
|
770
|
-
buyAmountsWei = params.buyerRatios.map((ratio, index) => {
|
|
771
|
-
const amount = (totalBuyAmount * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
772
|
-
return amount;
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
else {
|
|
776
|
-
// 平均分配
|
|
777
|
-
const amountPerBuyer = totalBuyAmount / BigInt(buyers.length);
|
|
778
|
-
buyAmountsWei = buyers.map(() => amountPerBuyer);
|
|
779
|
-
}
|
|
780
|
-
// ✅ 并行验证所有买方余额
|
|
781
|
-
const reserveGas = ethers.parseEther((config.reserveGasETH || 0.0005).toString());
|
|
782
|
-
const erc20Contract = useNativeToken ? null : new Contract(quoteToken, ERC20_BALANCE_OF_ABI, chainContext.provider);
|
|
783
|
-
await Promise.all(buyers.map(async (buyer, i) => {
|
|
784
|
-
const buyAmount = buyAmountsWei[i];
|
|
785
|
-
if (useNativeToken) {
|
|
786
|
-
const buyerBalance = await buyer.provider.getBalance(buyer.address);
|
|
787
|
-
const required = buyAmount + reserveGas;
|
|
788
|
-
if (buyerBalance < required) {
|
|
789
|
-
throw new Error(`买方 ${i + 1} 余额不足: 需要 ${ethers.formatEther(required)} ${chainContext.nativeToken}, 实际 ${ethers.formatEther(buyerBalance)}`);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
else {
|
|
793
|
-
const buyerBalance = await erc20Contract.balanceOf(buyer.address);
|
|
794
|
-
if (buyerBalance < buyAmount) {
|
|
795
|
-
throw new Error(`买方 ${i + 1} 代币余额不足: 需要 ${ethers.formatUnits(buyAmount, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}));
|
|
799
|
-
// ✅ 计算利润(根据 userType 动态调整)
|
|
800
|
-
const tokenProfitAmount = calculateProfitAmount(quote.quotedNative, config.userType);
|
|
801
|
-
let nativeProfitAmount = tokenProfitAmount;
|
|
802
|
-
if (!useNativeToken && tokenProfitAmount > 0n) {
|
|
803
|
-
nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, quoteToken, tokenProfitAmount, chainContext.chainId);
|
|
804
|
-
}
|
|
805
|
-
// ✅ 并行构建所有买入交易
|
|
806
|
-
const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
|
|
807
|
-
const [sellUnsigned, ...buyUnsignedList] = await Promise.all([
|
|
808
|
-
// 卖出交易
|
|
809
|
-
portalSeller.swapExactInput.populateTransaction({
|
|
810
|
-
inputToken: tokenAddress,
|
|
811
|
-
outputToken,
|
|
812
|
-
inputAmount: sellAmountWei,
|
|
813
|
-
minOutputAmount: 0,
|
|
814
|
-
permitData: '0x'
|
|
815
|
-
}),
|
|
816
|
-
// 所有买入交易
|
|
817
|
-
...buyers.map((buyer, i) => {
|
|
818
|
-
const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
|
|
819
|
-
const buyAmount = buyAmountsWei[i];
|
|
820
|
-
return portalBuyer.swapExactInput.populateTransaction({
|
|
821
|
-
inputToken: outputToken,
|
|
822
|
-
outputToken: tokenAddress,
|
|
823
|
-
inputAmount: buyAmount,
|
|
824
|
-
minOutputAmount: 0,
|
|
825
|
-
permitData: '0x'
|
|
826
|
-
}, useNativeToken ? { value: buyAmount } : {});
|
|
827
|
-
})
|
|
828
|
-
]);
|
|
829
|
-
// ✅ 并行获取所有 nonce(如果前端传入了 startNonces,直接使用)
|
|
830
|
-
const allWallets = [seller, ...buyers];
|
|
831
|
-
let sellNonce;
|
|
832
|
-
let buyerNonces;
|
|
833
|
-
if (startNonces && startNonces.length >= allWallets.length) {
|
|
834
|
-
sellNonce = startNonces[0];
|
|
835
|
-
buyerNonces = startNonces.slice(1);
|
|
836
|
-
}
|
|
837
|
-
else {
|
|
838
|
-
sellNonce = await nonceManager.getNextNonce(seller);
|
|
839
|
-
buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
|
|
840
|
-
}
|
|
841
|
-
// ✅ 贿赂交易和利润交易都由卖方发送(保持一致)
|
|
842
|
-
const bribeAmount = getBribeAmount(config);
|
|
843
|
-
let bribeTx = null;
|
|
844
|
-
let bribeNonceOffset = 0;
|
|
845
|
-
if (bribeAmount > 0n) {
|
|
846
|
-
const bribeNonce = sellNonce; // 使用卖方的第一个 nonce
|
|
847
|
-
bribeTx = await seller.signTransaction({
|
|
848
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
849
|
-
value: bribeAmount,
|
|
850
|
-
nonce: bribeNonce,
|
|
851
|
-
gasPrice,
|
|
852
|
-
gasLimit: BRIBE_GAS_LIMIT,
|
|
853
|
-
chainId: chainContext.chainId,
|
|
854
|
-
type: txType
|
|
855
|
-
});
|
|
856
|
-
bribeNonceOffset = 1; // 卖出交易的 nonce 需要 +1
|
|
857
|
-
}
|
|
858
|
-
nonceManager.clearTemp();
|
|
859
|
-
// 利润多跳交易 nonce 计算
|
|
860
|
-
const profitNonce = nativeProfitAmount > 0n ? sellNonce + bribeNonceOffset + 1 : undefined;
|
|
861
|
-
// ✅ 并行签名所有交易
|
|
862
|
-
const sellTx = buildTransactionRequest(sellUnsigned, {
|
|
863
|
-
from: seller.address,
|
|
864
|
-
nonce: sellNonce + bribeNonceOffset, // ✅ 考虑贿赂交易的 nonce 偏移
|
|
865
|
-
gasLimit: finalGasLimit,
|
|
866
|
-
gasPrice,
|
|
867
|
-
priorityFee,
|
|
868
|
-
chainId: chainContext.chainId,
|
|
869
|
-
txType,
|
|
870
|
-
value: 0n
|
|
871
|
-
});
|
|
872
|
-
const [signedSell, ...signedBuys] = await Promise.all([
|
|
873
|
-
seller.signTransaction(sellTx),
|
|
874
|
-
...buyers.map((buyer, i) => {
|
|
875
|
-
const buyTx = buildTransactionRequest(buyUnsignedList[i], {
|
|
876
|
-
from: buyer.address,
|
|
877
|
-
nonce: buyerNonces[i],
|
|
878
|
-
gasLimit: finalGasLimit,
|
|
879
|
-
gasPrice,
|
|
880
|
-
priorityFee,
|
|
881
|
-
chainId: chainContext.chainId,
|
|
882
|
-
txType,
|
|
883
|
-
value: useNativeToken ? buyAmountsWei[i] : 0n
|
|
884
|
-
});
|
|
885
|
-
return buyer.signTransaction(buyTx);
|
|
886
|
-
})
|
|
887
|
-
]);
|
|
888
|
-
// ✅ 按顺序组装交易数组:贿赂 → 授权 → 卖出 → 买入
|
|
889
|
-
const signedTransactions = [];
|
|
890
|
-
if (bribeTx)
|
|
891
|
-
signedTransactions.push(bribeTx);
|
|
892
|
-
if (approvalTx)
|
|
893
|
-
signedTransactions.push(approvalTx);
|
|
894
|
-
signedTransactions.push(signedSell);
|
|
895
|
-
signedTransactions.push(...signedBuys);
|
|
896
|
-
// ✅ 利润多跳转账(强制 2 跳中转)
|
|
897
|
-
let profitHopWallets;
|
|
898
|
-
if (profitNonce !== undefined && nativeProfitAmount > 0n) {
|
|
899
|
-
const profitResult = await buildProfitTransaction({
|
|
900
|
-
provider: chainContext.provider,
|
|
901
|
-
seller,
|
|
902
|
-
profitAmount: nativeProfitAmount,
|
|
903
|
-
profitNonce,
|
|
904
|
-
gasPrice,
|
|
905
|
-
chainId: chainContext.chainId,
|
|
906
|
-
txType
|
|
907
|
-
});
|
|
908
|
-
if (profitResult) {
|
|
909
|
-
signedTransactions.push(...profitResult.signedTransactions);
|
|
910
|
-
profitHopWallets = profitResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return {
|
|
914
|
-
signedTransactions,
|
|
915
|
-
profitHopWallets, // ✅ 导出利润多跳钱包
|
|
916
|
-
metadata: {
|
|
917
|
-
sellerAddress: seller.address,
|
|
918
|
-
buyerAddresses: buyers.map(b => b.address),
|
|
919
|
-
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
920
|
-
buyAmounts: buyAmountsWei.map(amt => useNativeToken
|
|
921
|
-
? ethers.formatEther(amt)
|
|
922
|
-
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
923
|
-
hasApproval: !!approvalTx,
|
|
924
|
-
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined
|
|
925
|
-
}
|
|
926
|
-
};
|
|
927
|
-
}
|
|
928
|
-
// ==================== 快捷批量换手(资金利用率模式)====================
|
|
929
|
-
sellerPrivateKey: string; // 卖方私钥(主钱包)
|
|
930
|
-
sellAmount ? : string; // 卖出数量
|
|
931
|
-
sellPercentage ? : number; // 卖出比例(0-100)
|
|
932
|
-
buyerPrivateKeys: string[]; // 买方私钥数组(子钱包)
|
|
933
|
-
buyerRatios ? : number[]; // 每个买方的分配比例(如 [0.3, 0.5, 0.2])
|
|
934
|
-
buyerAmounts ? : string[]; // 每个买方的固定金额(原生代币/ERC20)
|
|
935
|
-
tokenAddress: string; // 代币地址
|
|
936
|
-
config: FlapSwapSignConfig; // 配置
|
|
937
|
-
quoteToken ? : string; // 报价代币(USDT 等),不传则使用原生代币
|
|
938
|
-
quoteTokenDecimals ? : number; // 报价代币精度
|
|
939
|
-
// ✅ 转账多跳配置(可选)
|
|
940
|
-
disperseHopCount ? : number; // 转账多跳数(0=直接转账,1=1跳,2=2跳...)
|
|
941
|
-
// ✅ 可选:前端预获取的 nonces(性能优化)
|
|
942
|
-
// 顺序:[sellerNonce, buyer1Nonce, buyer2Nonce, ...]
|
|
943
|
-
startNonces ? : number[];
|
|
944
|
-
disperseHopWallets ? : GeneratedWallet[]; // ✅ 转账多跳中间钱包
|
|
945
|
-
profitHopWallets ? : GeneratedWallet[]; // ✅ 利润多跳中间钱包
|
|
946
|
-
metadata ? : {
|
|
947
|
-
sellerAddress: string,
|
|
948
|
-
buyerAddresses: string[],
|
|
949
|
-
sellAmount: string,
|
|
950
|
-
estimatedOutput: string,
|
|
951
|
-
transferAmounts: string[],
|
|
952
|
-
profitAmount: string,
|
|
953
|
-
useNativeToken: boolean,
|
|
954
|
-
disperseHopCount: number
|
|
955
|
-
};
|
|
956
|
-
// ==================== 交叉换手(多卖多买,循环执行)====================
|
|
957
|
-
/** 卖方私钥列表(按顺序轮流卖出) */
|
|
958
|
-
sellerPrivateKeys: string[];
|
|
959
|
-
/** 每个卖方的卖出数量(与 sellerPrivateKeys 长度一致) */
|
|
960
|
-
sellAmounts: string[];
|
|
961
|
-
/** 买方私钥列表(按顺序轮流分配,每笔平分) */
|
|
962
|
-
buyerPrivateKeys: string[];
|
|
963
|
-
tokenAddress: string;
|
|
964
|
-
config: FlapSwapSignConfig;
|
|
965
|
-
quoteToken ? : string;
|
|
966
|
-
quoteTokenDecimals ? : number;
|
|
967
|
-
/** 每次卖出分配给多少个买方(默认使用全部买方平分) */
|
|
968
|
-
buyersPerSell ? : number;
|
|
969
|
-
/** 转账多跳数(可选) */
|
|
970
|
-
disperseHopCount ? : number;
|
|
971
|
-
/** 每轮的元数据列表 */
|
|
972
|
-
rounds: Array;
|
|
973
|
-
/** 汇总的中间钱包信息(转账/利润) */
|
|
974
|
-
disperseHopWallets ? : GeneratedWallet[];
|
|
975
|
-
profitHopWallets ? : GeneratedWallet[];
|
|
976
|
-
/**
|
|
977
|
-
* Flap 内盘快捷批量换手(资金利用率模式)
|
|
978
|
-
*
|
|
979
|
-
* 流程:[贿赂] → [卖出] → [转账多跳...] → [买入1, 买入2, ...] → [利润]
|
|
980
|
-
*
|
|
981
|
-
* 特点:
|
|
982
|
-
* - 子钱包不需要预先有余额
|
|
983
|
-
* - 资金来自主钱包卖出代币所得
|
|
984
|
-
* - 提升资金利用率
|
|
985
|
-
* - 支持原生代币(BNB/OKB/ETH)和 ERC20(如 USDT)两种模式
|
|
986
|
-
* - ✅ 支持转账多跳,隐藏资金流向
|
|
987
|
-
*
|
|
988
|
-
* 限制:根据多跳数动态计算最大买方数量
|
|
989
|
-
*/
|
|
990
|
-
export async function flapQuickBatchSwapMerkle(params) {
|
|
991
|
-
const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config, quoteToken, quoteTokenDecimals = 18, disperseHopCount = 0, // ✅ 转账多跳数(默认0=直接转账)
|
|
992
|
-
startNonces // ✅ 可选:前端预获取的 nonces
|
|
993
|
-
} = params;
|
|
994
|
-
// ✅ 判断是否使用原生代币
|
|
995
|
-
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
996
|
-
// ✅ 动态计算最大买方数量(根据多跳数)
|
|
997
|
-
// 固定开销: 贿赂(1) + 卖出(1) + 利润多跳(PROFIT_HOP_COUNT + 1)
|
|
998
|
-
const fixedOverhead = 1 + 1 + PROFIT_HOP_COUNT + 1;
|
|
999
|
-
const maxTxs = 50 - fixedOverhead;
|
|
1000
|
-
let MAX_BUYERS;
|
|
1001
|
-
if (useNativeToken) {
|
|
1002
|
-
// 原生代币模式: N*(H+2) <= maxTxs
|
|
1003
|
-
MAX_BUYERS = Math.floor(maxTxs / (disperseHopCount + 2));
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
// ERC20 模式: N*(2H+3) <= maxTxs
|
|
1007
|
-
MAX_BUYERS = Math.floor(maxTxs / (2 * disperseHopCount + 3));
|
|
1008
|
-
}
|
|
1009
|
-
MAX_BUYERS = Math.max(1, MAX_BUYERS);
|
|
1010
|
-
console.log(`[flapQuickBatchSwapMerkle] 多跳数: ${disperseHopCount}, 最大买方数: ${MAX_BUYERS}`);
|
|
1011
|
-
if (buyerPrivateKeys.length === 0) {
|
|
1012
|
-
throw new Error('至少需要一个买方钱包');
|
|
1013
|
-
}
|
|
1014
|
-
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
1015
|
-
const mode = useNativeToken ? '原生代币' : 'ERC20';
|
|
1016
|
-
throw new Error(`资金利用率模式(${mode}, ${disperseHopCount}跳)买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
1017
|
-
}
|
|
1018
|
-
// ✅ 校验分配模式
|
|
1019
|
-
if (!buyerRatios && !buyerAmounts) {
|
|
1020
|
-
throw new Error('必须提供 buyerRatios 或 buyerAmounts');
|
|
1021
|
-
}
|
|
1022
|
-
if (buyerRatios && buyerRatios.length !== buyerPrivateKeys.length) {
|
|
1023
|
-
throw new Error(`buyerRatios 长度 (${buyerRatios.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
|
|
1024
|
-
}
|
|
1025
|
-
if (buyerAmounts && buyerAmounts.length !== buyerPrivateKeys.length) {
|
|
1026
|
-
throw new Error(`buyerAmounts 长度 (${buyerAmounts.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
|
|
1027
|
-
}
|
|
1028
|
-
const outputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
|
|
1029
|
-
const ERC20_TRANSFER_GAS = GAS_LIMITS.ERC20_TRANSFER;
|
|
1030
|
-
const chainContext = createChainContext(chain, config);
|
|
1031
|
-
const seller = new Wallet(sellerPrivateKey, chainContext.provider);
|
|
1032
|
-
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
|
|
1033
|
-
const nonceManager = new NonceManager(chainContext.provider);
|
|
1034
|
-
const finalGasLimit = getGasLimit(config);
|
|
1035
|
-
const txType = getTxType(config);
|
|
1036
|
-
// ✅ 并行获取:卖出数量、gasPrice
|
|
1037
|
-
const [sellAmountResult, gasPrice] = await Promise.all([
|
|
1038
|
-
calculateSellAmount(chainContext.provider, tokenAddress, seller.address, sellAmount, sellPercentage),
|
|
1039
|
-
getOptimizedGasPrice(chainContext.provider, getGasPriceConfig(config))
|
|
1040
|
-
]);
|
|
1041
|
-
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
1042
|
-
const priorityFee = gasPrice / 10n === 0n ? 1n : gasPrice / 10n;
|
|
1043
|
-
// ✅ 获取卖出报价
|
|
1044
|
-
const quote = await quoteSellOutput({
|
|
1045
|
-
portalAddress: chainContext.portalAddress,
|
|
1046
|
-
tokenAddress,
|
|
1047
|
-
sellAmountWei,
|
|
1048
|
-
provider: chainContext.provider,
|
|
1049
|
-
skipQuoteOnError: config.skipQuoteOnError,
|
|
1050
|
-
outputToken
|
|
1051
|
-
});
|
|
1052
|
-
const estimatedOutput = quote.quotedNative;
|
|
1053
|
-
const outputFormatted = useNativeToken
|
|
1054
|
-
? ethers.formatEther(estimatedOutput)
|
|
1055
|
-
: ethers.formatUnits(estimatedOutput, quoteTokenDecimals);
|
|
1056
|
-
console.log(`[flapQuickBatchSwapMerkle] 模式: ${useNativeToken ? '原生代币' : 'ERC20'}`);
|
|
1057
|
-
console.log(`[flapQuickBatchSwapMerkle] 卖出数量: ${ethers.formatUnits(sellAmountWei, decimals)}`);
|
|
1058
|
-
console.log(`[flapQuickBatchSwapMerkle] 预估卖出所得: ${outputFormatted}`);
|
|
1059
|
-
// ✅ 计算利润(根据 userType 动态调整)
|
|
1060
|
-
let tokenProfitAmount = calculateProfitAmount(estimatedOutput, config.userType);
|
|
1061
|
-
let nativeProfitAmount = tokenProfitAmount;
|
|
1062
|
-
if (!useNativeToken && tokenProfitAmount > 0n) {
|
|
1063
|
-
// ERC20 模式:将代币利润转换为等值原生代币
|
|
1064
|
-
nativeProfitAmount = await getTokenToNativeQuote(chainContext.provider, quoteToken, tokenProfitAmount, chainContext.chainId);
|
|
1065
|
-
console.log(`[flapQuickBatchSwapMerkle] ERC20→原生代币 报价: ${ethers.formatUnits(tokenProfitAmount, quoteTokenDecimals)} → ${ethers.formatEther(nativeProfitAmount)}`);
|
|
1066
|
-
}
|
|
1067
|
-
const distributableAmount = useNativeToken
|
|
1068
|
-
? estimatedOutput - tokenProfitAmount
|
|
1069
|
-
: estimatedOutput; // ERC20 模式利润从原生代币扣
|
|
1070
|
-
// ✅ 计算每个买方分到的金额
|
|
1071
|
-
let transferAmountsWei;
|
|
1072
|
-
if (buyerAmounts && buyerAmounts.length === buyers.length) {
|
|
1073
|
-
// 数量模式
|
|
1074
|
-
transferAmountsWei = buyerAmounts.map(amt => useNativeToken
|
|
1075
|
-
? ethers.parseEther(amt)
|
|
1076
|
-
: ethers.parseUnits(amt, quoteTokenDecimals));
|
|
1077
|
-
const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n);
|
|
1078
|
-
if (totalTransfer > distributableAmount) {
|
|
1079
|
-
const formatted = useNativeToken
|
|
1080
|
-
? ethers.formatEther(distributableAmount)
|
|
1081
|
-
: ethers.formatUnits(distributableAmount, quoteTokenDecimals);
|
|
1082
|
-
throw new Error(`指定的买入总金额超过可分配金额 (${formatted})`);
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
else if (buyerRatios && buyerRatios.length === buyers.length) {
|
|
1086
|
-
// 比例模式
|
|
1087
|
-
transferAmountsWei = buyerRatios.map(ratio => {
|
|
1088
|
-
return (distributableAmount * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
else {
|
|
1092
|
-
throw new Error('必须提供 buyerRatios 或 buyerAmounts');
|
|
1093
|
-
}
|
|
1094
|
-
// ✅ 获取贿赂金额
|
|
1095
|
-
const bribeAmount = getBribeAmount(config);
|
|
1096
|
-
// ✅ 验证主钱包余额
|
|
1097
|
-
const sellerBalance = await seller.provider.getBalance(seller.address);
|
|
1098
|
-
let sellerGasCost;
|
|
1099
|
-
let sellerRequired;
|
|
1100
|
-
if (useNativeToken) {
|
|
1101
|
-
// 原生代币模式:贿赂 + Gas(卖出 + 转账 + 利润)
|
|
1102
|
-
sellerGasCost = gasPrice * (BRIBE_GAS_LIMIT + finalGasLimit + BRIBE_GAS_LIMIT * BigInt(buyers.length) + BRIBE_GAS_LIMIT);
|
|
1103
|
-
sellerRequired = bribeAmount + sellerGasCost;
|
|
1104
|
-
}
|
|
1105
|
-
else {
|
|
1106
|
-
// ERC20 模式:贿赂 + Gas(卖出 + ERC20转账 + 利润)
|
|
1107
|
-
sellerGasCost = gasPrice * (BRIBE_GAS_LIMIT + finalGasLimit + ERC20_TRANSFER_GAS * BigInt(buyers.length) + BRIBE_GAS_LIMIT);
|
|
1108
|
-
sellerRequired = bribeAmount + sellerGasCost;
|
|
1109
|
-
}
|
|
1110
|
-
if (sellerBalance < sellerRequired) {
|
|
1111
|
-
throw new Error(`主钱包 ${chainContext.nativeToken} 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)}`);
|
|
1112
|
-
}
|
|
1113
|
-
// ==================== 规划 Nonce ====================
|
|
1114
|
-
// ✅ 如果前端传入了 startNonces,直接使用(性能优化)
|
|
1115
|
-
let sellerNonce = startNonces && startNonces.length > 0
|
|
1116
|
-
? startNonces[0]
|
|
1117
|
-
: await nonceManager.getNextNonce(seller);
|
|
1118
|
-
// ==================== 1. 贿赂交易 ====================
|
|
1119
|
-
let bribeTx = null;
|
|
1120
|
-
if (bribeAmount > 0n) {
|
|
1121
|
-
bribeTx = await seller.signTransaction({
|
|
1122
|
-
to: BLOCKRAZOR_BUILDER_EOA,
|
|
1123
|
-
value: bribeAmount,
|
|
1124
|
-
nonce: sellerNonce++,
|
|
1125
|
-
gasPrice,
|
|
1126
|
-
gasLimit: BRIBE_GAS_LIMIT,
|
|
1127
|
-
chainId: chainContext.chainId,
|
|
1128
|
-
type: txType
|
|
1129
|
-
});
|
|
1130
|
-
console.log(`[flapQuickBatchSwapMerkle] 贿赂交易已签名`);
|
|
1131
|
-
}
|
|
1132
|
-
// ==================== 2. 卖出交易 ====================
|
|
1133
|
-
const portalSeller = new Contract(chainContext.portalAddress, PORTAL_ABI, seller);
|
|
1134
|
-
const sellUnsigned = await portalSeller.swapExactInput.populateTransaction({
|
|
1135
|
-
inputToken: tokenAddress,
|
|
1136
|
-
outputToken,
|
|
1137
|
-
inputAmount: sellAmountWei,
|
|
1138
|
-
minOutputAmount: 0,
|
|
1139
|
-
permitData: '0x'
|
|
1140
|
-
});
|
|
1141
|
-
const sellTx = buildTransactionRequest(sellUnsigned, {
|
|
1142
|
-
from: seller.address,
|
|
1143
|
-
nonce: sellerNonce++,
|
|
1144
|
-
gasLimit: finalGasLimit,
|
|
1145
|
-
gasPrice,
|
|
1146
|
-
priorityFee,
|
|
1147
|
-
chainId: chainContext.chainId,
|
|
1148
|
-
txType,
|
|
1149
|
-
value: 0n
|
|
1150
|
-
});
|
|
1151
|
-
const signedSell = await seller.signTransaction(sellTx);
|
|
1152
|
-
console.log(`[flapQuickBatchSwapMerkle] 卖出交易已签名`);
|
|
1153
|
-
// ==================== 3. 转账交易(支持多跳)====================
|
|
1154
|
-
const reserveGas = ethers.parseEther((config.reserveGasETH || 0.0005).toString());
|
|
1155
|
-
const buyerGasCost = gasPrice * finalGasLimit;
|
|
1156
|
-
// ✅ 生成多跳路径
|
|
1157
|
-
const hopPaths = generateDisperseHopPaths(buyers.map(b => b.address), disperseHopCount, chainContext.provider);
|
|
1158
|
-
// 收集所有中间钱包信息
|
|
1159
|
-
const allHopWallets = [];
|
|
1160
|
-
hopPaths.forEach(path => {
|
|
1161
|
-
allHopWallets.push(...path.hopWalletsInfo);
|
|
1162
|
-
});
|
|
1163
|
-
let transferTxs = [];
|
|
1164
|
-
if (disperseHopCount === 0) {
|
|
1165
|
-
// ✅ 无多跳:直接转账
|
|
1166
|
-
const transferNonces = buyers.map((_, i) => sellerNonce + i);
|
|
1167
|
-
sellerNonce += buyers.length;
|
|
1168
|
-
if (useNativeToken) {
|
|
1169
|
-
transferTxs = await Promise.all(buyers.map((buyer, i) => {
|
|
1170
|
-
const transferValue = transferAmountsWei[i] + reserveGas + buyerGasCost;
|
|
1171
|
-
return seller.signTransaction({
|
|
1172
|
-
to: buyer.address,
|
|
1173
|
-
value: transferValue,
|
|
1174
|
-
nonce: transferNonces[i],
|
|
1175
|
-
gasPrice,
|
|
1176
|
-
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
1177
|
-
chainId: chainContext.chainId,
|
|
1178
|
-
type: txType
|
|
1179
|
-
});
|
|
1180
|
-
}));
|
|
1181
|
-
}
|
|
1182
|
-
else {
|
|
1183
|
-
const erc20Interface = new ethers.Interface(ERC20_ABI);
|
|
1184
|
-
transferTxs = await Promise.all(buyers.map((buyer, i) => {
|
|
1185
|
-
const transferData = erc20Interface.encodeFunctionData('transfer', [
|
|
1186
|
-
buyer.address,
|
|
1187
|
-
transferAmountsWei[i]
|
|
1188
|
-
]);
|
|
1189
|
-
return seller.signTransaction({
|
|
1190
|
-
to: quoteToken,
|
|
1191
|
-
data: transferData,
|
|
1192
|
-
value: 0n,
|
|
1193
|
-
nonce: transferNonces[i],
|
|
1194
|
-
gasPrice,
|
|
1195
|
-
gasLimit: ERC20_TRANSFER_GAS,
|
|
1196
|
-
chainId: chainContext.chainId,
|
|
1197
|
-
type: txType
|
|
1198
|
-
});
|
|
1199
|
-
}));
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
else {
|
|
1203
|
-
// ✅ 有多跳:构建多跳转账链
|
|
1204
|
-
if (useNativeToken) {
|
|
1205
|
-
// 原生代币多跳转账
|
|
1206
|
-
// ✅ 修复:payer 每次只发送 1 笔交易(到第一个hop钱包),hop钱包用自己的nonce=0
|
|
1207
|
-
const hopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1208
|
-
const finalAmount = transferAmountsWei[i] + reserveGas + buyerGasCost;
|
|
1209
|
-
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1210
|
-
return buildNativeHopChain(seller, path, finalAmount, gasPrice, chainContext.chainId, txType, payerNonce);
|
|
1211
|
-
}));
|
|
1212
|
-
transferTxs = hopChains.flat();
|
|
1213
|
-
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1214
|
-
}
|
|
1215
|
-
else {
|
|
1216
|
-
// ERC20 多跳转账:先转 BNB(给中间钱包 gas),再转 ERC20
|
|
1217
|
-
// ✅ 修复:payer 每次只发送 1 笔交易(到第一个hop钱包),hop钱包用自己的nonce=0/1
|
|
1218
|
-
// 1. 构建 BNB 多跳链
|
|
1219
|
-
const bnbHopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1220
|
-
const finalGasAmount = buyerGasCost;
|
|
1221
|
-
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1222
|
-
return buildBNBHopChainForERC20(seller, path, finalGasAmount, gasPrice, chainContext.chainId, txType, payerNonce);
|
|
1223
|
-
}));
|
|
1224
|
-
const bnbTxs = bnbHopChains.flat();
|
|
1225
|
-
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1226
|
-
// 2. 构建 ERC20 多跳链
|
|
1227
|
-
const erc20HopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1228
|
-
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1229
|
-
return buildERC20HopChain(seller, path, quoteToken, transferAmountsWei[i], gasPrice, chainContext.chainId, txType, payerNonce);
|
|
1230
|
-
}));
|
|
1231
|
-
const erc20Txs = erc20HopChains.flat();
|
|
1232
|
-
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1233
|
-
transferTxs = [...bnbTxs, ...erc20Txs];
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
console.log(`[flapQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名 (多跳数=${disperseHopCount})`);
|
|
1237
|
-
// ==================== 4. 买入交易 ====================
|
|
1238
|
-
// ✅ 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
|
|
1239
|
-
const buyerNonces = startNonces && startNonces.length > 1
|
|
1240
|
-
? startNonces.slice(1)
|
|
1241
|
-
: await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
|
|
1242
|
-
const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
|
|
1243
|
-
const buyAmount = transferAmountsWei[i];
|
|
1244
|
-
const portalBuyer = new Contract(chainContext.portalAddress, PORTAL_ABI, buyer);
|
|
1245
|
-
const buyUnsigned = await portalBuyer.swapExactInput.populateTransaction({
|
|
1246
|
-
inputToken: outputToken,
|
|
1247
|
-
outputToken: tokenAddress,
|
|
1248
|
-
inputAmount: buyAmount,
|
|
1249
|
-
minOutputAmount: 0,
|
|
1250
|
-
permitData: '0x'
|
|
1251
|
-
}, useNativeToken ? { value: buyAmount } : {});
|
|
1252
|
-
const buyTx = buildTransactionRequest(buyUnsigned, {
|
|
1253
|
-
from: buyer.address,
|
|
1254
|
-
nonce: buyerNonces[i],
|
|
1255
|
-
gasLimit: finalGasLimit,
|
|
1256
|
-
gasPrice,
|
|
1257
|
-
priorityFee,
|
|
1258
|
-
chainId: chainContext.chainId,
|
|
1259
|
-
txType,
|
|
1260
|
-
value: useNativeToken ? buyAmount : 0n
|
|
1261
|
-
});
|
|
1262
|
-
return buyer.signTransaction(buyTx);
|
|
1263
|
-
}));
|
|
1264
|
-
console.log(`[flapQuickBatchSwapMerkle] ${signedBuys.length} 笔买入交易已签名`);
|
|
1265
|
-
nonceManager.clearTemp();
|
|
1266
|
-
// ==================== 组装交易数组 ====================
|
|
1267
|
-
const signedTransactions = [];
|
|
1268
|
-
if (bribeTx)
|
|
1269
|
-
signedTransactions.push(bribeTx);
|
|
1270
|
-
signedTransactions.push(signedSell);
|
|
1271
|
-
signedTransactions.push(...transferTxs);
|
|
1272
|
-
signedTransactions.push(...signedBuys);
|
|
1273
|
-
// ==================== 5. 利润多跳转账(强制 2 跳中转)====================
|
|
1274
|
-
let profitHopWallets;
|
|
1275
|
-
if (nativeProfitAmount > 0n) {
|
|
1276
|
-
const profitResult = await buildProfitTransaction({
|
|
1277
|
-
provider: chainContext.provider,
|
|
1278
|
-
seller,
|
|
1279
|
-
profitAmount: nativeProfitAmount,
|
|
1280
|
-
profitNonce: sellerNonce++,
|
|
1281
|
-
gasPrice,
|
|
1282
|
-
chainId: chainContext.chainId,
|
|
1283
|
-
txType
|
|
1284
|
-
});
|
|
1285
|
-
if (profitResult) {
|
|
1286
|
-
signedTransactions.push(...profitResult.signedTransactions);
|
|
1287
|
-
profitHopWallets = profitResult.hopWallets; // ✅ 收集利润多跳钱包
|
|
1288
|
-
}
|
|
1289
|
-
// 多跳交易已签名
|
|
1290
|
-
}
|
|
1291
|
-
console.log(`[flapQuickBatchSwapMerkle] 交易组装完成: ${signedTransactions.length} 笔`);
|
|
1292
|
-
console.log(` - 贿赂: ${bribeTx ? 1 : 0}`);
|
|
1293
|
-
console.log(` - 卖出: 1`);
|
|
1294
|
-
console.log(` - 转账: ${transferTxs.length}`);
|
|
1295
|
-
console.log(` - 买入: ${signedBuys.length}`);
|
|
1296
|
-
return {
|
|
1297
|
-
signedTransactions,
|
|
1298
|
-
disperseHopWallets: allHopWallets.length > 0 ? allHopWallets : undefined, // ✅ 返回转账多跳钱包
|
|
1299
|
-
profitHopWallets, // ✅ 返回利润多跳钱包
|
|
1300
|
-
metadata: {
|
|
1301
|
-
sellerAddress: seller.address,
|
|
1302
|
-
buyerAddresses: buyers.map(b => b.address),
|
|
1303
|
-
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
1304
|
-
estimatedOutput: useNativeToken
|
|
1305
|
-
? ethers.formatEther(estimatedOutput)
|
|
1306
|
-
: ethers.formatUnits(estimatedOutput, quoteTokenDecimals),
|
|
1307
|
-
transferAmounts: transferAmountsWei.map(amt => useNativeToken
|
|
1308
|
-
? ethers.formatEther(amt)
|
|
1309
|
-
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
1310
|
-
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
|
|
1311
|
-
useNativeToken,
|
|
1312
|
-
disperseHopCount: disperseHopCount > 0 ? disperseHopCount : undefined // ✅ 返回多跳数
|
|
1313
|
-
}
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
/**
|
|
1317
|
-
* Flap 交叉换手(多卖多买,循环执行)
|
|
1318
|
-
* - 每个卖方单独卖出,买入金额由卖出所得等分分配给当轮买方
|
|
1319
|
-
* - 利润刮取、转账多跳逻辑继承 flapQuickBatchSwapMerkle
|
|
1320
|
-
* - 结果按顺序拼接 signedTransactions(可交由前端/后端顺序广播)
|
|
1321
|
-
* - ✅ 正确处理 nonce:同一钱包在多轮中使用时 nonce 自动递增
|
|
1322
|
-
*/
|
|
1323
|
-
export async function flapCrossSwapMerkle(params) {
|
|
1324
|
-
const { chain, sellerPrivateKeys, sellAmounts, buyerPrivateKeys, tokenAddress, config, quoteToken, quoteTokenDecimals = 18, buyersPerSell, disperseHopCount = 0 } = params;
|
|
1325
|
-
if (sellerPrivateKeys.length === 0) {
|
|
1326
|
-
throw new Error('至少需要一个卖方');
|
|
1327
|
-
}
|
|
1328
|
-
if (sellerPrivateKeys.length !== sellAmounts.length) {
|
|
1329
|
-
throw new Error(`sellAmounts 长度 (${sellAmounts.length}) 必须与卖方数量 (${sellerPrivateKeys.length}) 一致`);
|
|
1330
|
-
}
|
|
1331
|
-
if (buyerPrivateKeys.length === 0) {
|
|
1332
|
-
throw new Error('至少需要一个买方');
|
|
1333
|
-
}
|
|
1334
|
-
// ✅ 创建 Provider 和 NonceManager
|
|
1335
|
-
const chainContext = createChainContext(chain, config);
|
|
1336
|
-
const nonceManager = new NonceManager(chainContext.provider);
|
|
1337
|
-
// ✅ 预先获取所有钱包的初始 nonce
|
|
1338
|
-
const allSellerWallets = sellerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
|
|
1339
|
-
const allBuyerWallets = buyerPrivateKeys.map(pk => new Wallet(pk, chainContext.provider));
|
|
1340
|
-
// 使用 Map 去重(同一私钥可能出现多次)
|
|
1341
|
-
const addressToNonce = new Map();
|
|
1342
|
-
// 获取所有卖方 nonce
|
|
1343
|
-
for (const wallet of allSellerWallets) {
|
|
1344
|
-
if (!addressToNonce.has(wallet.address)) {
|
|
1345
|
-
const nonce = await nonceManager.getNextNonce(wallet);
|
|
1346
|
-
addressToNonce.set(wallet.address, nonce);
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
// 获取所有买方 nonce
|
|
1350
|
-
for (const wallet of allBuyerWallets) {
|
|
1351
|
-
if (!addressToNonce.has(wallet.address)) {
|
|
1352
|
-
const nonce = await nonceManager.getNextNonce(wallet);
|
|
1353
|
-
addressToNonce.set(wallet.address, nonce);
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
console.log(`[flapCrossSwapMerkle] 初始化完成: ${sellerPrivateKeys.length} 卖方, ${buyerPrivateKeys.length} 买方`);
|
|
1357
|
-
const allSigned = [];
|
|
1358
|
-
const allDisperse = [];
|
|
1359
|
-
const allProfit = [];
|
|
1360
|
-
const rounds = [];
|
|
1361
|
-
// 轮流分配买方:每轮取 buyersPerSell 个(默认所有买方),不足则从头继续
|
|
1362
|
-
let buyerCursor = 0;
|
|
1363
|
-
const buyerBatchSize = Math.max(1, buyersPerSell ?? buyerPrivateKeys.length);
|
|
1364
|
-
for (let i = 0; i < sellerPrivateKeys.length; i++) {
|
|
1365
|
-
const sellerPk = sellerPrivateKeys[i];
|
|
1366
|
-
const sellAmount = sellAmounts[i];
|
|
1367
|
-
const sellerWallet = allSellerWallets[i];
|
|
1368
|
-
// 选取本轮买方私钥和钱包
|
|
1369
|
-
const roundBuyerPks = [];
|
|
1370
|
-
const roundBuyerWallets = [];
|
|
1371
|
-
for (let k = 0; k < buyerBatchSize; k++) {
|
|
1372
|
-
const idx = buyerCursor % buyerPrivateKeys.length;
|
|
1373
|
-
roundBuyerPks.push(buyerPrivateKeys[idx]);
|
|
1374
|
-
roundBuyerWallets.push(allBuyerWallets[idx]);
|
|
1375
|
-
buyerCursor++;
|
|
1376
|
-
}
|
|
1377
|
-
// ✅ 获取卖方当前 nonce
|
|
1378
|
-
const sellerNonce = addressToNonce.get(sellerWallet.address);
|
|
1379
|
-
// ✅ 获取并更新买方 nonces(每个买方本轮需要 1 个 nonce)
|
|
1380
|
-
const roundBuyerNonces = roundBuyerWallets.map(w => {
|
|
1381
|
-
const nonce = addressToNonce.get(w.address);
|
|
1382
|
-
addressToNonce.set(w.address, nonce + 1); // 每个买方用 1 个 nonce
|
|
1383
|
-
return nonce;
|
|
1384
|
-
});
|
|
1385
|
-
// 平分买入金额(使用比例模式,避免固定金额超出可分配金额)
|
|
1386
|
-
const ratio = 1 / roundBuyerPks.length;
|
|
1387
|
-
const buyerRatios = roundBuyerPks.map(() => ratio);
|
|
1388
|
-
// ✅ 构建 startNonces: [sellerNonce, buyer1Nonce, buyer2Nonce, ...]
|
|
1389
|
-
const startNonces = [sellerNonce, ...roundBuyerNonces];
|
|
1390
|
-
// ✅ 预先计算卖方 nonce 消耗(精确计算)
|
|
1391
|
-
// 卖方 nonce 消耗 = 贿赂(0/1) + 卖出(1) + 转账(N 或 N*2) + 利润(1)
|
|
1392
|
-
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
1393
|
-
const hasBribe = getBribeAmount(config) > 0n;
|
|
1394
|
-
const hasProfit = true; // 默认有利润刮取
|
|
1395
|
-
let sellerNonceConsumed = 0;
|
|
1396
|
-
if (hasBribe)
|
|
1397
|
-
sellerNonceConsumed += 1; // 贿赂
|
|
1398
|
-
sellerNonceConsumed += 1; // 卖出
|
|
1399
|
-
if (disperseHopCount === 0) {
|
|
1400
|
-
sellerNonceConsumed += roundBuyerPks.length; // 直接转账
|
|
1401
|
-
}
|
|
1402
|
-
else if (useNativeToken) {
|
|
1403
|
-
sellerNonceConsumed += roundBuyerPks.length; // 原生多跳:seller→hop1
|
|
1404
|
-
}
|
|
1405
|
-
else {
|
|
1406
|
-
sellerNonceConsumed += roundBuyerPks.length * 2; // ERC20 多跳:BNB + ERC20 两批
|
|
1407
|
-
}
|
|
1408
|
-
if (hasProfit)
|
|
1409
|
-
sellerNonceConsumed += 1; // 利润
|
|
1410
|
-
// 调用单卖多买的签名函数
|
|
1411
|
-
const res = await flapQuickBatchSwapMerkle({
|
|
1412
|
-
chain,
|
|
1413
|
-
sellerPrivateKey: sellerPk,
|
|
1414
|
-
sellAmount,
|
|
1415
|
-
buyerPrivateKeys: roundBuyerPks,
|
|
1416
|
-
buyerRatios,
|
|
1417
|
-
tokenAddress,
|
|
1418
|
-
config,
|
|
1419
|
-
quoteToken,
|
|
1420
|
-
quoteTokenDecimals,
|
|
1421
|
-
disperseHopCount,
|
|
1422
|
-
startNonces // ✅ 传入预计算的 nonces
|
|
1423
|
-
});
|
|
1424
|
-
// ✅ 更新卖方 nonce(使用精确计算的值)
|
|
1425
|
-
addressToNonce.set(sellerWallet.address, sellerNonce + sellerNonceConsumed);
|
|
1426
|
-
console.log(`[flapCrossSwapMerkle] 轮次 ${i + 1}: 卖方=${sellerWallet.address.slice(0, 10)}..., 买方=${roundBuyerPks.length}, 交易=${res.signedTransactions.length}`);
|
|
1427
|
-
// 累积签名与中间钱包
|
|
1428
|
-
allSigned.push(...res.signedTransactions);
|
|
1429
|
-
if (res.disperseHopWallets)
|
|
1430
|
-
allDisperse.push(...res.disperseHopWallets);
|
|
1431
|
-
if (res.profitHopWallets)
|
|
1432
|
-
allProfit.push(...res.profitHopWallets);
|
|
1433
|
-
rounds.push({
|
|
1434
|
-
sellerAddress: res.metadata?.sellerAddress || '',
|
|
1435
|
-
buyerAddresses: res.metadata?.buyerAddresses || roundBuyerPks.map(() => ''),
|
|
1436
|
-
sellAmount,
|
|
1437
|
-
bundleHash: undefined
|
|
1438
|
-
});
|
|
1439
|
-
}
|
|
1440
|
-
nonceManager.clearTemp();
|
|
1441
|
-
console.log(`[flapCrossSwapMerkle] 完成: ${rounds.length} 轮, ${allSigned.length} 笔交易`);
|
|
1442
|
-
return {
|
|
1443
|
-
signedTransactions: allSigned,
|
|
1444
|
-
rounds,
|
|
1445
|
-
disperseHopWallets: allDisperse.length > 0 ? allDisperse : undefined,
|
|
1446
|
-
profitHopWallets: allProfit.length > 0 ? allProfit : undefined
|
|
1447
|
-
};
|
|
1448
|
-
}
|