four-flap-meme-sdk 1.4.55 → 1.4.57
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/contracts/tm-bundle-merkle/swap.d.ts +7 -2
- package/dist/contracts/tm-bundle-merkle/swap.js +127 -25
- package/dist/flap/portal-bundle-merkle/swap.d.ts +7 -2
- package/dist/flap/portal-bundle-merkle/swap.js +273 -52
- package/dist/pancake/bundle-swap.d.ts +7 -2
- package/dist/pancake/bundle-swap.js +335 -50
- package/dist/utils/holders-maker.d.ts +4 -0
- package/dist/utils/holders-maker.js +198 -34
- package/package.json +1 -1
|
@@ -1,3 +1,229 @@
|
|
|
1
|
+
// ==================== 多跳转账常量 ====================
|
|
2
|
+
const NATIVE_TRANSFER_GAS_LIMIT = 21055n;
|
|
3
|
+
const ERC20_TRANSFER_GAS_LIMIT_HOP = 65000n;
|
|
4
|
+
/**
|
|
5
|
+
* 生成分发多跳路径
|
|
6
|
+
*/
|
|
7
|
+
function generateDisperseHopPaths(targetAddresses, hopCount, provider) {
|
|
8
|
+
if (hopCount <= 0) {
|
|
9
|
+
return targetAddresses.map(addr => ({
|
|
10
|
+
targetAddress: addr,
|
|
11
|
+
hopWallets: [],
|
|
12
|
+
hopWalletsInfo: []
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
return targetAddresses.map(targetAddress => {
|
|
16
|
+
const hopWalletsInfo = generateWallets(hopCount);
|
|
17
|
+
const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
|
|
18
|
+
return { targetAddress, hopWallets, hopWalletsInfo };
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 构建原生代币多跳转账链
|
|
23
|
+
*/
|
|
24
|
+
async function buildNativeHopChain(payer, path, finalAmount, gasPrice, chainId, txType, payerNonce) {
|
|
25
|
+
const signedTxs = [];
|
|
26
|
+
const hopCount = path.hopWallets.length;
|
|
27
|
+
if (hopCount === 0) {
|
|
28
|
+
const tx = {
|
|
29
|
+
to: path.targetAddress,
|
|
30
|
+
value: finalAmount,
|
|
31
|
+
nonce: payerNonce,
|
|
32
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
33
|
+
chainId,
|
|
34
|
+
type: txType
|
|
35
|
+
};
|
|
36
|
+
if (txType === 2) {
|
|
37
|
+
tx.maxFeePerGas = gasPrice;
|
|
38
|
+
tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
tx.gasPrice = gasPrice;
|
|
42
|
+
}
|
|
43
|
+
signedTxs.push(await payer.signTransaction(tx));
|
|
44
|
+
return signedTxs;
|
|
45
|
+
}
|
|
46
|
+
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
47
|
+
// 计算每跳需要的金额
|
|
48
|
+
const amountsPerHop = [];
|
|
49
|
+
for (let i = 0; i < hopCount; i++) {
|
|
50
|
+
const remainingHops = hopCount - i;
|
|
51
|
+
amountsPerHop.push(finalAmount + hopGasCost * BigInt(remainingHops));
|
|
52
|
+
}
|
|
53
|
+
// payer → hop1
|
|
54
|
+
const firstTx = {
|
|
55
|
+
to: path.hopWallets[0].address,
|
|
56
|
+
value: amountsPerHop[0],
|
|
57
|
+
nonce: payerNonce,
|
|
58
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
59
|
+
chainId,
|
|
60
|
+
type: txType
|
|
61
|
+
};
|
|
62
|
+
if (txType === 2) {
|
|
63
|
+
firstTx.maxFeePerGas = gasPrice;
|
|
64
|
+
firstTx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
firstTx.gasPrice = gasPrice;
|
|
68
|
+
}
|
|
69
|
+
signedTxs.push(await payer.signTransaction(firstTx));
|
|
70
|
+
// hop1 → hop2 → ... → target
|
|
71
|
+
for (let i = 0; i < hopCount; i++) {
|
|
72
|
+
const fromWallet = path.hopWallets[i];
|
|
73
|
+
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
74
|
+
const amount = i === hopCount - 1 ? finalAmount : amountsPerHop[i + 1];
|
|
75
|
+
const tx = {
|
|
76
|
+
to: toAddress,
|
|
77
|
+
value: amount,
|
|
78
|
+
nonce: 0,
|
|
79
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
80
|
+
chainId,
|
|
81
|
+
type: txType
|
|
82
|
+
};
|
|
83
|
+
if (txType === 2) {
|
|
84
|
+
tx.maxFeePerGas = gasPrice;
|
|
85
|
+
tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
tx.gasPrice = gasPrice;
|
|
89
|
+
}
|
|
90
|
+
signedTxs.push(await fromWallet.signTransaction(tx));
|
|
91
|
+
}
|
|
92
|
+
return signedTxs;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 构建 ERC20 多跳转账链(需要配合 BNB 多跳使用)
|
|
96
|
+
*/
|
|
97
|
+
async function buildERC20HopChain(payer, path, erc20Address, erc20Amount, gasPrice, chainId, txType, payerNonce) {
|
|
98
|
+
const signedTxs = [];
|
|
99
|
+
const hopCount = path.hopWallets.length;
|
|
100
|
+
const erc20Interface = new ethers.Interface(ERC20_ABI);
|
|
101
|
+
if (hopCount === 0) {
|
|
102
|
+
const data = erc20Interface.encodeFunctionData('transfer', [path.targetAddress, erc20Amount]);
|
|
103
|
+
const tx = {
|
|
104
|
+
to: erc20Address,
|
|
105
|
+
data,
|
|
106
|
+
value: 0n,
|
|
107
|
+
nonce: payerNonce,
|
|
108
|
+
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
109
|
+
chainId,
|
|
110
|
+
type: txType
|
|
111
|
+
};
|
|
112
|
+
if (txType === 2) {
|
|
113
|
+
tx.maxFeePerGas = gasPrice;
|
|
114
|
+
tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
tx.gasPrice = gasPrice;
|
|
118
|
+
}
|
|
119
|
+
signedTxs.push(await payer.signTransaction(tx));
|
|
120
|
+
return signedTxs;
|
|
121
|
+
}
|
|
122
|
+
// payer → hop1
|
|
123
|
+
const firstData = erc20Interface.encodeFunctionData('transfer', [path.hopWallets[0].address, erc20Amount]);
|
|
124
|
+
const firstTx = {
|
|
125
|
+
to: erc20Address,
|
|
126
|
+
data: firstData,
|
|
127
|
+
value: 0n,
|
|
128
|
+
nonce: payerNonce,
|
|
129
|
+
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
130
|
+
chainId,
|
|
131
|
+
type: txType
|
|
132
|
+
};
|
|
133
|
+
if (txType === 2) {
|
|
134
|
+
firstTx.maxFeePerGas = gasPrice;
|
|
135
|
+
firstTx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
firstTx.gasPrice = gasPrice;
|
|
139
|
+
}
|
|
140
|
+
signedTxs.push(await payer.signTransaction(firstTx));
|
|
141
|
+
// hop1 → hop2 → ... → target (nonce=1,nonce=0 用于 BNB 转发)
|
|
142
|
+
for (let i = 0; i < hopCount; i++) {
|
|
143
|
+
const fromWallet = path.hopWallets[i];
|
|
144
|
+
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
145
|
+
const data = erc20Interface.encodeFunctionData('transfer', [toAddress, erc20Amount]);
|
|
146
|
+
const tx = {
|
|
147
|
+
to: erc20Address,
|
|
148
|
+
data,
|
|
149
|
+
value: 0n,
|
|
150
|
+
nonce: 1, // nonce=0 已用于 BNB 转发
|
|
151
|
+
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
152
|
+
chainId,
|
|
153
|
+
type: txType
|
|
154
|
+
};
|
|
155
|
+
if (txType === 2) {
|
|
156
|
+
tx.maxFeePerGas = gasPrice;
|
|
157
|
+
tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
tx.gasPrice = gasPrice;
|
|
161
|
+
}
|
|
162
|
+
signedTxs.push(await fromWallet.signTransaction(tx));
|
|
163
|
+
}
|
|
164
|
+
return signedTxs;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 构建 BNB 多跳转账链(为 ERC20 中间钱包预留 gas)
|
|
168
|
+
*/
|
|
169
|
+
async function buildBNBHopChainForERC20(payer, path, finalGasAmount, gasPrice, chainId, txType, payerNonce) {
|
|
170
|
+
const signedTxs = [];
|
|
171
|
+
const hopCount = path.hopWallets.length;
|
|
172
|
+
if (hopCount === 0) {
|
|
173
|
+
// 无多跳时不需要转 gas
|
|
174
|
+
return signedTxs;
|
|
175
|
+
}
|
|
176
|
+
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
177
|
+
const erc20GasCost = ERC20_TRANSFER_GAS_LIMIT_HOP * gasPrice;
|
|
178
|
+
// 每个中间钱包需要: BNB 转发 gas + ERC20 转发 gas
|
|
179
|
+
const gasPerHop = hopGasCost + erc20GasCost * 2n; // 2倍安全系数
|
|
180
|
+
// 计算每跳需要的金额
|
|
181
|
+
const amountsPerHop = [];
|
|
182
|
+
for (let i = 0; i < hopCount; i++) {
|
|
183
|
+
const remainingHops = hopCount - i;
|
|
184
|
+
amountsPerHop.push(finalGasAmount + gasPerHop * BigInt(remainingHops));
|
|
185
|
+
}
|
|
186
|
+
// payer → hop1
|
|
187
|
+
const firstTx = {
|
|
188
|
+
to: path.hopWallets[0].address,
|
|
189
|
+
value: amountsPerHop[0],
|
|
190
|
+
nonce: payerNonce,
|
|
191
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
192
|
+
chainId,
|
|
193
|
+
type: txType
|
|
194
|
+
};
|
|
195
|
+
if (txType === 2) {
|
|
196
|
+
firstTx.maxFeePerGas = gasPrice;
|
|
197
|
+
firstTx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
firstTx.gasPrice = gasPrice;
|
|
201
|
+
}
|
|
202
|
+
signedTxs.push(await payer.signTransaction(firstTx));
|
|
203
|
+
// hop1 → hop2 → ... → target (nonce=0)
|
|
204
|
+
for (let i = 0; i < hopCount; i++) {
|
|
205
|
+
const fromWallet = path.hopWallets[i];
|
|
206
|
+
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
207
|
+
const amount = i === hopCount - 1 ? finalGasAmount : amountsPerHop[i + 1];
|
|
208
|
+
const tx = {
|
|
209
|
+
to: toAddress,
|
|
210
|
+
value: amount,
|
|
211
|
+
nonce: 0,
|
|
212
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
213
|
+
chainId,
|
|
214
|
+
type: txType
|
|
215
|
+
};
|
|
216
|
+
if (txType === 2) {
|
|
217
|
+
tx.maxFeePerGas = gasPrice;
|
|
218
|
+
tx.maxPriorityFeePerGas = gasPrice / 10n || 1n;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
tx.gasPrice = gasPrice;
|
|
222
|
+
}
|
|
223
|
+
signedTxs.push(await fromWallet.signTransaction(tx));
|
|
224
|
+
}
|
|
225
|
+
return signedTxs;
|
|
226
|
+
}
|
|
1
227
|
// ==================== 工具函数 ====================
|
|
2
228
|
function createPancakeContext(config) {
|
|
3
229
|
const chainId = config.chainId ?? 56;
|
|
@@ -302,7 +528,8 @@ import { calculateSellAmount } from '../utils/swap-helpers.js';
|
|
|
302
528
|
import { NonceManager, getDeadline, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../utils/bundle-helpers.js';
|
|
303
529
|
import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../utils/constants.js';
|
|
304
530
|
import { quoteV2, quoteV3 } from '../utils/quote-helpers.js';
|
|
305
|
-
import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_BALANCE_ABI } from '../abis/common.js';
|
|
531
|
+
import { V2_ROUTER_ABI, V3_ROUTER02_ABI, ERC20_BALANCE_ABI, ERC20_ABI } from '../abis/common.js';
|
|
532
|
+
import { generateWallets } from '../utils/wallet.js';
|
|
306
533
|
/**
|
|
307
534
|
* 获取 Gas Limit
|
|
308
535
|
*/
|
|
@@ -788,32 +1015,49 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
788
1015
|
/**
|
|
789
1016
|
* PancakeSwap 快捷批量换手(资金自动流转)
|
|
790
1017
|
*
|
|
791
|
-
* 流程:[贿赂] → [卖出] → [
|
|
1018
|
+
* 流程:[贿赂] → [卖出] → [转账多跳...] → [买入1, 买入2, ...] → [利润]
|
|
792
1019
|
*
|
|
793
1020
|
* 特点:
|
|
794
1021
|
* - 子钱包不需要预先有余额
|
|
795
1022
|
* - 资金来自主钱包卖出代币所得
|
|
796
1023
|
* - 提升资金利用率
|
|
797
1024
|
* - 支持 BNB 和 ERC20(如 USDT)两种模式
|
|
1025
|
+
* - ✅ 支持转账多跳,隐藏资金流向
|
|
798
1026
|
*
|
|
799
|
-
*
|
|
1027
|
+
* 限制:根据多跳数动态计算最大买方数量
|
|
800
1028
|
*/
|
|
801
1029
|
export async function pancakeQuickBatchSwapMerkle(params) {
|
|
802
|
-
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18,
|
|
1030
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18, disperseHopCount = 0, // ✅ 转账多跳数(默认0=直接转账)
|
|
1031
|
+
startNonces // ✅ 可选:前端预获取的 nonces
|
|
803
1032
|
} = params;
|
|
804
1033
|
// ✅ 判断是否使用原生代币
|
|
805
1034
|
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
806
1035
|
const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'.toLowerCase();
|
|
807
|
-
// ✅
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
|
|
1036
|
+
// ✅ 动态计算最大买方数量(根据多跳数)
|
|
1037
|
+
// 固定开销: 贿赂(1) + 卖出(1) + 利润多跳(PROFIT_HOP_COUNT + 1)
|
|
1038
|
+
// BNB 模式(无多跳): 转账(N) + 买入(N) = 2N
|
|
1039
|
+
// BNB 模式(有多跳H): 转账(N*(H+1)) + 买入(N) = N*(H+2)
|
|
1040
|
+
// ERC20 模式(无多跳): 转账(N) + 买入(N) = 2N
|
|
1041
|
+
// ERC20 模式(有多跳H): BNB转账(N*(H+1)) + ERC20转账(N*(H+1)) + 买入(N) = N*(2H+3)
|
|
1042
|
+
const fixedOverhead = 1 + 1 + PROFIT_HOP_COUNT + 1; // 贿赂 + 卖出 + 利润多跳
|
|
1043
|
+
const maxTxs = 50 - fixedOverhead;
|
|
1044
|
+
let MAX_BUYERS;
|
|
1045
|
+
if (useNativeToken) {
|
|
1046
|
+
// BNB 模式: N*(H+2) <= maxTxs => N <= maxTxs / (H+2)
|
|
1047
|
+
MAX_BUYERS = Math.floor(maxTxs / (disperseHopCount + 2));
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
// ERC20 模式: N*(2H+3) <= maxTxs => N <= maxTxs / (2H+3)
|
|
1051
|
+
MAX_BUYERS = Math.floor(maxTxs / (2 * disperseHopCount + 3));
|
|
1052
|
+
}
|
|
1053
|
+
MAX_BUYERS = Math.max(1, MAX_BUYERS); // 至少1个
|
|
1054
|
+
console.log(`[pancakeQuickBatchSwapMerkle] 多跳数: ${disperseHopCount}, 最大买方数: ${MAX_BUYERS}`);
|
|
811
1055
|
if (buyerPrivateKeys.length === 0) {
|
|
812
1056
|
throw new Error('至少需要一个买方钱包');
|
|
813
1057
|
}
|
|
814
1058
|
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
815
1059
|
const mode = useNativeToken ? 'BNB' : 'ERC20';
|
|
816
|
-
throw new Error(`资金利用率模式(${mode})买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
1060
|
+
throw new Error(`资金利用率模式(${mode}, ${disperseHopCount}跳)买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
817
1061
|
}
|
|
818
1062
|
// ✅ 校验分配模式
|
|
819
1063
|
if (!buyerRatios && !buyerAmounts) {
|
|
@@ -1020,50 +1264,89 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
1020
1264
|
type: txType
|
|
1021
1265
|
});
|
|
1022
1266
|
console.log(`[pancakeQuickBatchSwapMerkle] 卖出交易已签名`);
|
|
1023
|
-
// ==================== 3.
|
|
1267
|
+
// ==================== 3. 转账交易(支持多跳)====================
|
|
1024
1268
|
const buyerGasCost = gasPrice * finalGasLimit;
|
|
1025
|
-
// ✅
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1269
|
+
// ✅ 生成多跳路径
|
|
1270
|
+
const hopPaths = generateDisperseHopPaths(buyers.map(b => b.address), disperseHopCount, context.provider);
|
|
1271
|
+
// 收集所有中间钱包信息
|
|
1272
|
+
const allHopWallets = [];
|
|
1273
|
+
hopPaths.forEach(path => {
|
|
1274
|
+
allHopWallets.push(...path.hopWalletsInfo);
|
|
1275
|
+
});
|
|
1276
|
+
let transferTxs = [];
|
|
1277
|
+
if (disperseHopCount === 0) {
|
|
1278
|
+
// ✅ 无多跳:直接转账
|
|
1279
|
+
const transferNonces = buyers.map((_, i) => sellerNonce + i);
|
|
1280
|
+
sellerNonce += buyers.length;
|
|
1281
|
+
if (useNativeToken) {
|
|
1282
|
+
transferTxs = await Promise.all(buyers.map((buyer, i) => {
|
|
1283
|
+
const transferValue = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
|
|
1284
|
+
return seller.signTransaction({
|
|
1285
|
+
to: buyer.address,
|
|
1286
|
+
value: transferValue,
|
|
1287
|
+
nonce: transferNonces[i],
|
|
1288
|
+
gasPrice,
|
|
1289
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
1290
|
+
chainId: context.chainId,
|
|
1291
|
+
type: txType
|
|
1292
|
+
});
|
|
1293
|
+
}));
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
const erc20Interface = new ethers.Interface(ERC20_ABI);
|
|
1297
|
+
transferTxs = await Promise.all(buyers.map((buyer, i) => {
|
|
1298
|
+
const transferData = erc20Interface.encodeFunctionData('transfer', [
|
|
1299
|
+
buyer.address,
|
|
1300
|
+
transferAmountsWei[i]
|
|
1301
|
+
]);
|
|
1302
|
+
return seller.signTransaction({
|
|
1303
|
+
to: quoteToken,
|
|
1304
|
+
data: transferData,
|
|
1305
|
+
value: 0n,
|
|
1306
|
+
nonce: transferNonces[i],
|
|
1307
|
+
gasPrice,
|
|
1308
|
+
gasLimit: ERC20_TRANSFER_GAS,
|
|
1309
|
+
chainId: context.chainId,
|
|
1310
|
+
type: txType
|
|
1311
|
+
});
|
|
1312
|
+
}));
|
|
1313
|
+
}
|
|
1043
1314
|
}
|
|
1044
1315
|
else {
|
|
1045
|
-
// ✅
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1316
|
+
// ✅ 有多跳:构建多跳转账链
|
|
1317
|
+
if (useNativeToken) {
|
|
1318
|
+
// BNB 多跳转账
|
|
1319
|
+
const hopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1320
|
+
const finalAmount = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
|
|
1321
|
+
const payerNonce = sellerNonce + i * (disperseHopCount + 1);
|
|
1322
|
+
return buildNativeHopChain(seller, path, finalAmount, gasPrice, context.chainId, txType, payerNonce);
|
|
1323
|
+
}));
|
|
1324
|
+
transferTxs = hopChains.flat();
|
|
1325
|
+
sellerNonce += buyers.length * (disperseHopCount + 1);
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
// ERC20 多跳转账:先转 BNB(给中间钱包 gas),再转 ERC20
|
|
1329
|
+
// 每个 buyer 需要 (H+1) 笔 BNB 转账 + (H+1) 笔 ERC20 转账
|
|
1330
|
+
// 1. 构建 BNB 多跳链(为中间钱包和最终钱包预留 gas)
|
|
1331
|
+
const bnbHopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1332
|
+
// 最终钱包需要的 gas(用于买入交易)
|
|
1333
|
+
const finalGasAmount = buyerGasCost;
|
|
1334
|
+
const payerNonce = sellerNonce + i * (disperseHopCount + 1);
|
|
1335
|
+
return buildBNBHopChainForERC20(seller, path, finalGasAmount, gasPrice, context.chainId, txType, payerNonce);
|
|
1336
|
+
}));
|
|
1337
|
+
const bnbTxs = bnbHopChains.flat();
|
|
1338
|
+
sellerNonce += buyers.length * (disperseHopCount + 1);
|
|
1339
|
+
// 2. 构建 ERC20 多跳链
|
|
1340
|
+
const erc20HopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1341
|
+
const payerNonce = sellerNonce + i * (disperseHopCount + 1);
|
|
1342
|
+
return buildERC20HopChain(seller, path, quoteToken, transferAmountsWei[i], gasPrice, context.chainId, txType, payerNonce);
|
|
1343
|
+
}));
|
|
1344
|
+
const erc20Txs = erc20HopChains.flat();
|
|
1345
|
+
sellerNonce += buyers.length * (disperseHopCount + 1);
|
|
1346
|
+
transferTxs = [...bnbTxs, ...erc20Txs];
|
|
1347
|
+
}
|
|
1065
1348
|
}
|
|
1066
|
-
console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length}
|
|
1349
|
+
console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名 (多跳数=${disperseHopCount})`);
|
|
1067
1350
|
// ==================== 4. 买入交易 ====================
|
|
1068
1351
|
// ✅ 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
|
|
1069
1352
|
const buyerNonces = startNonces && startNonces.length > 1
|
|
@@ -1150,6 +1433,7 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
1150
1433
|
const outputUnit = useNativeToken ? 'BNB' : 'ERC20';
|
|
1151
1434
|
return {
|
|
1152
1435
|
signedTransactions,
|
|
1436
|
+
disperseHopWallets: allHopWallets.length > 0 ? allHopWallets : undefined, // ✅ 返回中间钱包信息
|
|
1153
1437
|
metadata: {
|
|
1154
1438
|
sellerAddress: seller.address,
|
|
1155
1439
|
buyerAddresses: buyers.map(b => b.address),
|
|
@@ -1161,7 +1445,8 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
1161
1445
|
? ethers.formatEther(amt)
|
|
1162
1446
|
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
1163
1447
|
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
1164
|
-
useNativeToken
|
|
1448
|
+
useNativeToken,
|
|
1449
|
+
disperseHopCount: disperseHopCount > 0 ? disperseHopCount : undefined // ✅ 返回多跳数
|
|
1165
1450
|
}
|
|
1166
1451
|
};
|
|
1167
1452
|
}
|
|
@@ -34,6 +34,8 @@ export type HoldersMakerConfig = {
|
|
|
34
34
|
maxWalletsPerBatch?: number;
|
|
35
35
|
/** Four API URL */
|
|
36
36
|
fourApiUrl?: string;
|
|
37
|
+
/** 分发多跳数(0=直接转账,1=1跳,2=2跳...) */
|
|
38
|
+
disperseHopCount?: number;
|
|
37
39
|
/** V2 路由路径(如 [WBNB, TOKEN]) */
|
|
38
40
|
v2Path?: string[];
|
|
39
41
|
/** V3 单跳输入代币 */
|
|
@@ -79,6 +81,8 @@ export type HoldersMakerResult = {
|
|
|
79
81
|
success: boolean;
|
|
80
82
|
/** 生成的新钱包 */
|
|
81
83
|
newWallets: GeneratedWallet[];
|
|
84
|
+
/** 分发多跳中间钱包(可选,用于导出) */
|
|
85
|
+
disperseHopWallets?: GeneratedWallet[];
|
|
82
86
|
/** 签名交易(每批一个数组) */
|
|
83
87
|
signedTransactions: string[][];
|
|
84
88
|
/** 批次结果 */
|