four-flap-meme-sdk 1.4.56 → 1.4.58
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 +128 -25
- package/dist/flap/portal-bundle-merkle/swap.d.ts +7 -2
- package/dist/flap/portal-bundle-merkle/swap.js +275 -52
- package/dist/pancake/bundle-swap.d.ts +7 -2
- package/dist/pancake/bundle-swap.js +336 -50
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 功能:钱包A卖出代币 → 钱包B买入相同数量 → 原子执行
|
|
5
5
|
*/
|
|
6
6
|
import { CommonBundleConfig } from '../../utils/bundle-helpers.js';
|
|
7
|
+
import { type GeneratedWallet } from '../../utils/wallet.js';
|
|
7
8
|
export interface FourSwapSignConfig {
|
|
8
9
|
rpcUrl: string;
|
|
9
10
|
gasLimit?: number | bigint;
|
|
@@ -105,6 +106,7 @@ export interface FourQuickBatchSwapSignParams {
|
|
|
105
106
|
buyerAmounts?: string[];
|
|
106
107
|
tokenAddress: string;
|
|
107
108
|
config: FourSwapSignConfig;
|
|
109
|
+
disperseHopCount?: number;
|
|
108
110
|
startNonces?: number[];
|
|
109
111
|
}
|
|
110
112
|
/**
|
|
@@ -112,6 +114,7 @@ export interface FourQuickBatchSwapSignParams {
|
|
|
112
114
|
*/
|
|
113
115
|
export interface FourQuickBatchSwapResult {
|
|
114
116
|
signedTransactions: string[];
|
|
117
|
+
disperseHopWallets?: GeneratedWallet[];
|
|
115
118
|
metadata?: {
|
|
116
119
|
sellerAddress: string;
|
|
117
120
|
buyerAddresses: string[];
|
|
@@ -119,18 +122,20 @@ export interface FourQuickBatchSwapResult {
|
|
|
119
122
|
estimatedBNBOut: string;
|
|
120
123
|
transferAmounts: string[];
|
|
121
124
|
profitAmount?: string;
|
|
125
|
+
disperseHopCount?: number;
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
128
|
/**
|
|
125
129
|
* Four 内盘快捷批量换手(资金利用率模式)
|
|
126
130
|
*
|
|
127
|
-
* 流程:[贿赂] → [卖出] → [
|
|
131
|
+
* 流程:[贿赂] → [卖出] → [转账多跳...] → [买入1, 买入2, ...] → [利润]
|
|
128
132
|
*
|
|
129
133
|
* 特点:
|
|
130
134
|
* - 子钱包不需要预先有 BNB
|
|
131
135
|
* - 资金来自主钱包卖出代币所得
|
|
132
136
|
* - 提升资金利用率
|
|
137
|
+
* - ✅ 支持转账多跳,隐藏资金流向
|
|
133
138
|
*
|
|
134
|
-
*
|
|
139
|
+
* 限制:根据多跳数动态计算最大买方数量
|
|
135
140
|
*/
|
|
136
141
|
export declare function fourQuickBatchSwapMerkle(params: FourQuickBatchSwapSignParams): Promise<FourQuickBatchSwapResult>;
|
|
@@ -9,6 +9,79 @@ import { NonceManager, getOptimizedGasPrice, getGasLimit, getGasPriceConfig, get
|
|
|
9
9
|
import { ADDRESSES, PROFIT_CONFIG, BLOCKRAZOR_BUILDER_EOA } from '../../utils/constants.js';
|
|
10
10
|
import { TM_ABI, HELPER3_ABI, TM_ADDRESS } from './swap-internal.js';
|
|
11
11
|
import { getBribeAmount, getProfitRecipient } from './config.js';
|
|
12
|
+
import { generateWallets } from '../../utils/wallet.js';
|
|
13
|
+
// ==================== 多跳转账常量 ====================
|
|
14
|
+
const NATIVE_TRANSFER_GAS_LIMIT = 21055n;
|
|
15
|
+
/**
|
|
16
|
+
* 生成分发多跳路径
|
|
17
|
+
*/
|
|
18
|
+
function generateDisperseHopPaths(targetAddresses, hopCount, provider) {
|
|
19
|
+
if (hopCount <= 0) {
|
|
20
|
+
return targetAddresses.map(addr => ({
|
|
21
|
+
targetAddress: addr,
|
|
22
|
+
hopWallets: [],
|
|
23
|
+
hopWalletsInfo: []
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
return targetAddresses.map(targetAddress => {
|
|
27
|
+
const hopWalletsInfo = generateWallets(hopCount);
|
|
28
|
+
const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
|
|
29
|
+
return { targetAddress, hopWallets, hopWalletsInfo };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 构建原生代币多跳转账链
|
|
34
|
+
*/
|
|
35
|
+
async function buildNativeHopChain(payer, path, finalAmount, gasPrice, chainId, txType, payerNonce) {
|
|
36
|
+
const signedTxs = [];
|
|
37
|
+
const hopCount = path.hopWallets.length;
|
|
38
|
+
if (hopCount === 0) {
|
|
39
|
+
const tx = {
|
|
40
|
+
to: path.targetAddress,
|
|
41
|
+
value: finalAmount,
|
|
42
|
+
nonce: payerNonce,
|
|
43
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
44
|
+
gasPrice,
|
|
45
|
+
chainId,
|
|
46
|
+
type: txType
|
|
47
|
+
};
|
|
48
|
+
signedTxs.push(await payer.signTransaction(tx));
|
|
49
|
+
return signedTxs;
|
|
50
|
+
}
|
|
51
|
+
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
52
|
+
// 计算每跳需要的金额
|
|
53
|
+
const amountsPerHop = [];
|
|
54
|
+
for (let i = 0; i < hopCount; i++) {
|
|
55
|
+
const remainingHops = hopCount - i;
|
|
56
|
+
amountsPerHop.push(finalAmount + hopGasCost * BigInt(remainingHops));
|
|
57
|
+
}
|
|
58
|
+
// payer → hop1
|
|
59
|
+
signedTxs.push(await payer.signTransaction({
|
|
60
|
+
to: path.hopWallets[0].address,
|
|
61
|
+
value: amountsPerHop[0],
|
|
62
|
+
nonce: payerNonce,
|
|
63
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
64
|
+
gasPrice,
|
|
65
|
+
chainId,
|
|
66
|
+
type: txType
|
|
67
|
+
}));
|
|
68
|
+
// hop1 → hop2 → ... → target
|
|
69
|
+
for (let i = 0; i < hopCount; i++) {
|
|
70
|
+
const fromWallet = path.hopWallets[i];
|
|
71
|
+
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
72
|
+
const amount = i === hopCount - 1 ? finalAmount : amountsPerHop[i + 1];
|
|
73
|
+
signedTxs.push(await fromWallet.signTransaction({
|
|
74
|
+
to: toAddress,
|
|
75
|
+
value: amount,
|
|
76
|
+
nonce: 0,
|
|
77
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
78
|
+
gasPrice,
|
|
79
|
+
chainId,
|
|
80
|
+
type: txType
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
return signedTxs;
|
|
84
|
+
}
|
|
12
85
|
/**
|
|
13
86
|
* Four内盘捆绑换手
|
|
14
87
|
*/
|
|
@@ -430,26 +503,33 @@ export async function fourBatchSwapMerkle(params) {
|
|
|
430
503
|
/**
|
|
431
504
|
* Four 内盘快捷批量换手(资金利用率模式)
|
|
432
505
|
*
|
|
433
|
-
* 流程:[贿赂] → [卖出] → [
|
|
506
|
+
* 流程:[贿赂] → [卖出] → [转账多跳...] → [买入1, 买入2, ...] → [利润]
|
|
434
507
|
*
|
|
435
508
|
* 特点:
|
|
436
509
|
* - 子钱包不需要预先有 BNB
|
|
437
510
|
* - 资金来自主钱包卖出代币所得
|
|
438
511
|
* - 提升资金利用率
|
|
512
|
+
* - ✅ 支持转账多跳,隐藏资金流向
|
|
439
513
|
*
|
|
440
|
-
*
|
|
514
|
+
* 限制:根据多跳数动态计算最大买方数量
|
|
441
515
|
*/
|
|
442
516
|
export async function fourQuickBatchSwapMerkle(params) {
|
|
443
|
-
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config,
|
|
517
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config, disperseHopCount = 0, // ✅ 转账多跳数(默认0=直接转账)
|
|
518
|
+
startNonces // ✅ 可选:前端预获取的 nonces
|
|
444
519
|
} = params;
|
|
445
|
-
// ✅
|
|
446
|
-
// 贿赂(1) + 卖出(1) +
|
|
447
|
-
|
|
520
|
+
// ✅ 动态计算最大买方数量(根据多跳数)
|
|
521
|
+
// 固定开销: 贿赂(1) + 卖出(1) + 利润多跳(PROFIT_HOP_COUNT + 1)
|
|
522
|
+
// 转账(N*(H+1)) + 买入(N) = N*(H+2)
|
|
523
|
+
const fixedOverhead = 1 + 1 + PROFIT_HOP_COUNT + 1;
|
|
524
|
+
const maxTxs = 50 - fixedOverhead;
|
|
525
|
+
let MAX_BUYERS = Math.floor(maxTxs / (disperseHopCount + 2));
|
|
526
|
+
MAX_BUYERS = Math.max(1, MAX_BUYERS);
|
|
527
|
+
console.log(`[fourQuickBatchSwapMerkle] 多跳数: ${disperseHopCount}, 最大买方数: ${MAX_BUYERS}`);
|
|
448
528
|
if (buyerPrivateKeys.length === 0) {
|
|
449
529
|
throw new Error('至少需要一个买方钱包');
|
|
450
530
|
}
|
|
451
531
|
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
452
|
-
throw new Error(
|
|
532
|
+
throw new Error(`资金利用率模式(${disperseHopCount}跳)买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
453
533
|
}
|
|
454
534
|
// ✅ 校验分配模式
|
|
455
535
|
if (!buyerRatios && !buyerAmounts) {
|
|
@@ -559,25 +639,46 @@ export async function fourQuickBatchSwapMerkle(params) {
|
|
|
559
639
|
type: txType
|
|
560
640
|
});
|
|
561
641
|
console.log(`[fourQuickBatchSwapMerkle] 卖出交易已签名`);
|
|
562
|
-
// ==================== 3.
|
|
642
|
+
// ==================== 3. 转账交易(支持多跳)====================
|
|
563
643
|
const reserveGas = ethers.parseEther((config.reserveGasBNB || 0.0005).toString());
|
|
564
644
|
const buyerGasCost = gasPrice * finalGasLimit;
|
|
565
|
-
// ✅
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
645
|
+
// ✅ 生成多跳路径
|
|
646
|
+
const hopPaths = generateDisperseHopPaths(buyers.map(b => b.address), disperseHopCount, provider);
|
|
647
|
+
// 收集所有中间钱包信息
|
|
648
|
+
const allHopWallets = [];
|
|
649
|
+
hopPaths.forEach(path => {
|
|
650
|
+
allHopWallets.push(...path.hopWalletsInfo);
|
|
651
|
+
});
|
|
652
|
+
let transferTxs = [];
|
|
653
|
+
if (disperseHopCount === 0) {
|
|
654
|
+
// ✅ 无多跳:直接转账
|
|
655
|
+
const transferNonces = buyers.map((_, i) => sellerNonce + i);
|
|
656
|
+
sellerNonce += buyers.length;
|
|
657
|
+
transferTxs = await Promise.all(buyers.map((buyer, i) => {
|
|
658
|
+
const transferValue = transferAmountsWei[i] + reserveGas + buyerGasCost;
|
|
659
|
+
return seller.signTransaction({
|
|
660
|
+
to: buyer.address,
|
|
661
|
+
value: transferValue,
|
|
662
|
+
nonce: transferNonces[i],
|
|
663
|
+
gasPrice,
|
|
664
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
665
|
+
chainId: chainIdNum,
|
|
666
|
+
type: txType
|
|
667
|
+
});
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
// ✅ 有多跳:构建多跳转账链
|
|
672
|
+
// ✅ 修复:payer 每次只发送 1 笔交易(到第一个hop钱包),hop钱包用自己的nonce=0
|
|
673
|
+
const hopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
674
|
+
const finalAmount = transferAmountsWei[i] + reserveGas + buyerGasCost;
|
|
675
|
+
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
676
|
+
return buildNativeHopChain(seller, path, finalAmount, gasPrice, chainIdNum, txType, payerNonce);
|
|
677
|
+
}));
|
|
678
|
+
transferTxs = hopChains.flat();
|
|
679
|
+
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
680
|
+
}
|
|
681
|
+
console.log(`[fourQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名 (多跳数=${disperseHopCount})`);
|
|
581
682
|
// ==================== 4. 买入交易 ====================
|
|
582
683
|
// ✅ 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
|
|
583
684
|
const buyerNonces = startNonces && startNonces.length > 1
|
|
@@ -631,13 +732,15 @@ export async function fourQuickBatchSwapMerkle(params) {
|
|
|
631
732
|
console.log(` - 利润多跳: ${profitAmount > 0n ? PROFIT_HOP_COUNT + 1 : 0}`);
|
|
632
733
|
return {
|
|
633
734
|
signedTransactions,
|
|
735
|
+
disperseHopWallets: allHopWallets.length > 0 ? allHopWallets : undefined, // ✅ 返回中间钱包信息
|
|
634
736
|
metadata: {
|
|
635
737
|
sellerAddress: seller.address,
|
|
636
738
|
buyerAddresses: buyers.map(b => b.address),
|
|
637
739
|
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
638
740
|
estimatedBNBOut: ethers.formatEther(estimatedBNBOut),
|
|
639
741
|
transferAmounts: transferAmountsWei.map(amt => ethers.formatEther(amt)),
|
|
640
|
-
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
|
|
742
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
743
|
+
disperseHopCount: disperseHopCount > 0 ? disperseHopCount : undefined // ✅ 返回多跳数
|
|
641
744
|
}
|
|
642
745
|
};
|
|
643
746
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* 功能:钱包A卖出代币 → 钱包B买入相同数量 → 原子执行
|
|
5
5
|
*/
|
|
6
6
|
import { CommonBundleConfig } from '../../utils/bundle-helpers.js';
|
|
7
|
+
import { type GeneratedWallet } from '../../utils/wallet.js';
|
|
7
8
|
export interface FlapSwapSignConfig {
|
|
8
9
|
rpcUrl: string;
|
|
9
10
|
gasLimit?: number | bigint;
|
|
@@ -118,6 +119,7 @@ export interface FlapQuickBatchSwapSignParams {
|
|
|
118
119
|
config: FlapSwapSignConfig;
|
|
119
120
|
quoteToken?: string;
|
|
120
121
|
quoteTokenDecimals?: number;
|
|
122
|
+
disperseHopCount?: number;
|
|
121
123
|
startNonces?: number[];
|
|
122
124
|
}
|
|
123
125
|
/**
|
|
@@ -125,6 +127,7 @@ export interface FlapQuickBatchSwapSignParams {
|
|
|
125
127
|
*/
|
|
126
128
|
export interface FlapQuickBatchSwapResult {
|
|
127
129
|
signedTransactions: string[];
|
|
130
|
+
disperseHopWallets?: GeneratedWallet[];
|
|
128
131
|
metadata?: {
|
|
129
132
|
sellerAddress: string;
|
|
130
133
|
buyerAddresses: string[];
|
|
@@ -133,19 +136,21 @@ export interface FlapQuickBatchSwapResult {
|
|
|
133
136
|
transferAmounts: string[];
|
|
134
137
|
profitAmount?: string;
|
|
135
138
|
useNativeToken: boolean;
|
|
139
|
+
disperseHopCount?: number;
|
|
136
140
|
};
|
|
137
141
|
}
|
|
138
142
|
/**
|
|
139
143
|
* Flap 内盘快捷批量换手(资金利用率模式)
|
|
140
144
|
*
|
|
141
|
-
* 流程:[贿赂] → [卖出] → [
|
|
145
|
+
* 流程:[贿赂] → [卖出] → [转账多跳...] → [买入1, 买入2, ...] → [利润]
|
|
142
146
|
*
|
|
143
147
|
* 特点:
|
|
144
148
|
* - 子钱包不需要预先有余额
|
|
145
149
|
* - 资金来自主钱包卖出代币所得
|
|
146
150
|
* - 提升资金利用率
|
|
147
151
|
* - 支持原生代币(BNB/OKB/ETH)和 ERC20(如 USDT)两种模式
|
|
152
|
+
* - ✅ 支持转账多跳,隐藏资金流向
|
|
148
153
|
*
|
|
149
|
-
*
|
|
154
|
+
* 限制:根据多跳数动态计算最大买方数量
|
|
150
155
|
*/
|
|
151
156
|
export declare function flapQuickBatchSwapMerkle(params: FlapQuickBatchSwapSignParams): Promise<FlapQuickBatchSwapResult>;
|
|
@@ -8,8 +8,176 @@ import { calculateSellAmount } from '../../utils/swap-helpers.js';
|
|
|
8
8
|
import { NonceManager, getOptimizedGasPrice, buildProfitHopTransactions, PROFIT_HOP_COUNT } from '../../utils/bundle-helpers.js';
|
|
9
9
|
import { FLAP_PORTAL_ADDRESSES } from '../constants.js';
|
|
10
10
|
import { PROFIT_CONFIG, ADDRESSES, ZERO_ADDRESS } from '../../utils/constants.js';
|
|
11
|
-
import { ERC20_ALLOWANCE_ABI, V2_ROUTER_QUOTE_ABI, ERC20_BALANCE_ABI } from '../../abis/common.js';
|
|
11
|
+
import { ERC20_ALLOWANCE_ABI, V2_ROUTER_QUOTE_ABI, ERC20_BALANCE_ABI, ERC20_ABI } from '../../abis/common.js';
|
|
12
12
|
import { getGasPriceConfig, getTxType, getProfitRecipient, getBribeAmount, BLOCKRAZOR_BUILDER_EOA, PORTAL_ABI } from './config.js';
|
|
13
|
+
import { generateWallets } from '../../utils/wallet.js';
|
|
14
|
+
// ==================== 多跳转账常量 ====================
|
|
15
|
+
const NATIVE_TRANSFER_GAS_LIMIT = 21055n;
|
|
16
|
+
const ERC20_TRANSFER_GAS_LIMIT_HOP = 65000n;
|
|
17
|
+
/**
|
|
18
|
+
* 生成分发多跳路径
|
|
19
|
+
*/
|
|
20
|
+
function generateDisperseHopPaths(targetAddresses, hopCount, provider) {
|
|
21
|
+
if (hopCount <= 0) {
|
|
22
|
+
return targetAddresses.map(addr => ({
|
|
23
|
+
targetAddress: addr,
|
|
24
|
+
hopWallets: [],
|
|
25
|
+
hopWalletsInfo: []
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
return targetAddresses.map(targetAddress => {
|
|
29
|
+
const hopWalletsInfo = generateWallets(hopCount);
|
|
30
|
+
const hopWallets = hopWalletsInfo.map(w => new Wallet(w.privateKey, provider));
|
|
31
|
+
return { targetAddress, hopWallets, hopWalletsInfo };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 构建原生代币多跳转账链
|
|
36
|
+
*/
|
|
37
|
+
async function buildNativeHopChain(payer, path, finalAmount, gasPrice, chainId, txType, payerNonce) {
|
|
38
|
+
const signedTxs = [];
|
|
39
|
+
const hopCount = path.hopWallets.length;
|
|
40
|
+
if (hopCount === 0) {
|
|
41
|
+
signedTxs.push(await payer.signTransaction({
|
|
42
|
+
to: path.targetAddress,
|
|
43
|
+
value: finalAmount,
|
|
44
|
+
nonce: payerNonce,
|
|
45
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
46
|
+
gasPrice,
|
|
47
|
+
chainId,
|
|
48
|
+
type: txType
|
|
49
|
+
}));
|
|
50
|
+
return signedTxs;
|
|
51
|
+
}
|
|
52
|
+
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
53
|
+
// 计算每跳需要的金额
|
|
54
|
+
const amountsPerHop = [];
|
|
55
|
+
for (let i = 0; i < hopCount; i++) {
|
|
56
|
+
const remainingHops = hopCount - i;
|
|
57
|
+
amountsPerHop.push(finalAmount + hopGasCost * BigInt(remainingHops));
|
|
58
|
+
}
|
|
59
|
+
// payer → hop1
|
|
60
|
+
signedTxs.push(await payer.signTransaction({
|
|
61
|
+
to: path.hopWallets[0].address,
|
|
62
|
+
value: amountsPerHop[0],
|
|
63
|
+
nonce: payerNonce,
|
|
64
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
65
|
+
gasPrice,
|
|
66
|
+
chainId,
|
|
67
|
+
type: txType
|
|
68
|
+
}));
|
|
69
|
+
// hop1 → hop2 → ... → target
|
|
70
|
+
for (let i = 0; i < hopCount; i++) {
|
|
71
|
+
const fromWallet = path.hopWallets[i];
|
|
72
|
+
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
73
|
+
const amount = i === hopCount - 1 ? finalAmount : amountsPerHop[i + 1];
|
|
74
|
+
signedTxs.push(await fromWallet.signTransaction({
|
|
75
|
+
to: toAddress,
|
|
76
|
+
value: amount,
|
|
77
|
+
nonce: 0,
|
|
78
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
79
|
+
gasPrice,
|
|
80
|
+
chainId,
|
|
81
|
+
type: txType
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
return signedTxs;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 构建 ERC20 多跳转账链
|
|
88
|
+
*/
|
|
89
|
+
async function buildERC20HopChain(payer, path, erc20Address, erc20Amount, gasPrice, chainId, txType, payerNonce) {
|
|
90
|
+
const signedTxs = [];
|
|
91
|
+
const hopCount = path.hopWallets.length;
|
|
92
|
+
const erc20Interface = new ethers.Interface(ERC20_ABI);
|
|
93
|
+
if (hopCount === 0) {
|
|
94
|
+
const data = erc20Interface.encodeFunctionData('transfer', [path.targetAddress, erc20Amount]);
|
|
95
|
+
signedTxs.push(await payer.signTransaction({
|
|
96
|
+
to: erc20Address,
|
|
97
|
+
data,
|
|
98
|
+
value: 0n,
|
|
99
|
+
nonce: payerNonce,
|
|
100
|
+
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
101
|
+
gasPrice,
|
|
102
|
+
chainId,
|
|
103
|
+
type: txType
|
|
104
|
+
}));
|
|
105
|
+
return signedTxs;
|
|
106
|
+
}
|
|
107
|
+
// payer → hop1
|
|
108
|
+
const firstData = erc20Interface.encodeFunctionData('transfer', [path.hopWallets[0].address, erc20Amount]);
|
|
109
|
+
signedTxs.push(await payer.signTransaction({
|
|
110
|
+
to: erc20Address,
|
|
111
|
+
data: firstData,
|
|
112
|
+
value: 0n,
|
|
113
|
+
nonce: payerNonce,
|
|
114
|
+
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
115
|
+
gasPrice,
|
|
116
|
+
chainId,
|
|
117
|
+
type: txType
|
|
118
|
+
}));
|
|
119
|
+
// hop1 → hop2 → ... → target (nonce=1,nonce=0 用于 BNB 转发)
|
|
120
|
+
for (let i = 0; i < hopCount; i++) {
|
|
121
|
+
const fromWallet = path.hopWallets[i];
|
|
122
|
+
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
123
|
+
const data = erc20Interface.encodeFunctionData('transfer', [toAddress, erc20Amount]);
|
|
124
|
+
signedTxs.push(await fromWallet.signTransaction({
|
|
125
|
+
to: erc20Address,
|
|
126
|
+
data,
|
|
127
|
+
value: 0n,
|
|
128
|
+
nonce: 1, // nonce=0 已用于 BNB 转发
|
|
129
|
+
gasLimit: ERC20_TRANSFER_GAS_LIMIT_HOP,
|
|
130
|
+
gasPrice,
|
|
131
|
+
chainId,
|
|
132
|
+
type: txType
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
return signedTxs;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 构建 BNB 多跳转账链(为 ERC20 中间钱包预留 gas)
|
|
139
|
+
*/
|
|
140
|
+
async function buildBNBHopChainForERC20(payer, path, finalGasAmount, gasPrice, chainId, txType, payerNonce) {
|
|
141
|
+
const signedTxs = [];
|
|
142
|
+
const hopCount = path.hopWallets.length;
|
|
143
|
+
if (hopCount === 0) {
|
|
144
|
+
return signedTxs;
|
|
145
|
+
}
|
|
146
|
+
const hopGasCost = NATIVE_TRANSFER_GAS_LIMIT * gasPrice;
|
|
147
|
+
const erc20GasCost = ERC20_TRANSFER_GAS_LIMIT_HOP * gasPrice;
|
|
148
|
+
const gasPerHop = hopGasCost + erc20GasCost * 2n;
|
|
149
|
+
const amountsPerHop = [];
|
|
150
|
+
for (let i = 0; i < hopCount; i++) {
|
|
151
|
+
const remainingHops = hopCount - i;
|
|
152
|
+
amountsPerHop.push(finalGasAmount + gasPerHop * BigInt(remainingHops));
|
|
153
|
+
}
|
|
154
|
+
// payer → hop1
|
|
155
|
+
signedTxs.push(await payer.signTransaction({
|
|
156
|
+
to: path.hopWallets[0].address,
|
|
157
|
+
value: amountsPerHop[0],
|
|
158
|
+
nonce: payerNonce,
|
|
159
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
160
|
+
gasPrice,
|
|
161
|
+
chainId,
|
|
162
|
+
type: txType
|
|
163
|
+
}));
|
|
164
|
+
// hop1 → hop2 → ... → target (nonce=0)
|
|
165
|
+
for (let i = 0; i < hopCount; i++) {
|
|
166
|
+
const fromWallet = path.hopWallets[i];
|
|
167
|
+
const toAddress = i === hopCount - 1 ? path.targetAddress : path.hopWallets[i + 1].address;
|
|
168
|
+
const amount = i === hopCount - 1 ? finalGasAmount : amountsPerHop[i + 1];
|
|
169
|
+
signedTxs.push(await fromWallet.signTransaction({
|
|
170
|
+
to: toAddress,
|
|
171
|
+
value: amount,
|
|
172
|
+
nonce: 0,
|
|
173
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
174
|
+
gasPrice,
|
|
175
|
+
chainId,
|
|
176
|
+
type: txType
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
return signedTxs;
|
|
180
|
+
}
|
|
13
181
|
/**
|
|
14
182
|
* 获取 Gas Limit
|
|
15
183
|
*/
|
|
@@ -696,28 +864,44 @@ export async function flapBatchSwapMerkle(params) {
|
|
|
696
864
|
/**
|
|
697
865
|
* Flap 内盘快捷批量换手(资金利用率模式)
|
|
698
866
|
*
|
|
699
|
-
* 流程:[贿赂] → [卖出] → [
|
|
867
|
+
* 流程:[贿赂] → [卖出] → [转账多跳...] → [买入1, 买入2, ...] → [利润]
|
|
700
868
|
*
|
|
701
869
|
* 特点:
|
|
702
870
|
* - 子钱包不需要预先有余额
|
|
703
871
|
* - 资金来自主钱包卖出代币所得
|
|
704
872
|
* - 提升资金利用率
|
|
705
873
|
* - 支持原生代币(BNB/OKB/ETH)和 ERC20(如 USDT)两种模式
|
|
874
|
+
* - ✅ 支持转账多跳,隐藏资金流向
|
|
706
875
|
*
|
|
707
|
-
*
|
|
876
|
+
* 限制:根据多跳数动态计算最大买方数量
|
|
708
877
|
*/
|
|
709
878
|
export async function flapQuickBatchSwapMerkle(params) {
|
|
710
|
-
const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config, quoteToken, quoteTokenDecimals = 18,
|
|
879
|
+
const { chain, sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, config, quoteToken, quoteTokenDecimals = 18, disperseHopCount = 0, // ✅ 转账多跳数(默认0=直接转账)
|
|
880
|
+
startNonces // ✅ 可选:前端预获取的 nonces
|
|
711
881
|
} = params;
|
|
712
|
-
// ✅
|
|
713
|
-
|
|
714
|
-
//
|
|
715
|
-
|
|
882
|
+
// ✅ 判断是否使用原生代币
|
|
883
|
+
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
884
|
+
// ✅ 动态计算最大买方数量(根据多跳数)
|
|
885
|
+
// 固定开销: 贿赂(1) + 卖出(1) + 利润多跳(PROFIT_HOP_COUNT + 1)
|
|
886
|
+
const fixedOverhead = 1 + 1 + PROFIT_HOP_COUNT + 1;
|
|
887
|
+
const maxTxs = 50 - fixedOverhead;
|
|
888
|
+
let MAX_BUYERS;
|
|
889
|
+
if (useNativeToken) {
|
|
890
|
+
// 原生代币模式: N*(H+2) <= maxTxs
|
|
891
|
+
MAX_BUYERS = Math.floor(maxTxs / (disperseHopCount + 2));
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
// ERC20 模式: N*(2H+3) <= maxTxs
|
|
895
|
+
MAX_BUYERS = Math.floor(maxTxs / (2 * disperseHopCount + 3));
|
|
896
|
+
}
|
|
897
|
+
MAX_BUYERS = Math.max(1, MAX_BUYERS);
|
|
898
|
+
console.log(`[flapQuickBatchSwapMerkle] 多跳数: ${disperseHopCount}, 最大买方数: ${MAX_BUYERS}`);
|
|
716
899
|
if (buyerPrivateKeys.length === 0) {
|
|
717
900
|
throw new Error('至少需要一个买方钱包');
|
|
718
901
|
}
|
|
719
902
|
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
720
|
-
|
|
903
|
+
const mode = useNativeToken ? '原生代币' : 'ERC20';
|
|
904
|
+
throw new Error(`资金利用率模式(${mode}, ${disperseHopCount}跳)买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
721
905
|
}
|
|
722
906
|
// ✅ 校验分配模式
|
|
723
907
|
if (!buyerRatios && !buyerAmounts) {
|
|
@@ -729,8 +913,6 @@ export async function flapQuickBatchSwapMerkle(params) {
|
|
|
729
913
|
if (buyerAmounts && buyerAmounts.length !== buyerPrivateKeys.length) {
|
|
730
914
|
throw new Error(`buyerAmounts 长度 (${buyerAmounts.length}) 与 buyerPrivateKeys 长度 (${buyerPrivateKeys.length}) 不匹配`);
|
|
731
915
|
}
|
|
732
|
-
// ✅ 判断是否使用原生代币
|
|
733
|
-
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
734
916
|
const outputToken = useNativeToken ? ZERO_ADDRESS : quoteToken;
|
|
735
917
|
const ERC20_TRANSFER_GAS = 65000n;
|
|
736
918
|
const chainContext = createChainContext(chain, config);
|
|
@@ -856,51 +1038,90 @@ export async function flapQuickBatchSwapMerkle(params) {
|
|
|
856
1038
|
});
|
|
857
1039
|
const signedSell = await seller.signTransaction(sellTx);
|
|
858
1040
|
console.log(`[flapQuickBatchSwapMerkle] 卖出交易已签名`);
|
|
859
|
-
// ==================== 3.
|
|
1041
|
+
// ==================== 3. 转账交易(支持多跳)====================
|
|
860
1042
|
const reserveGas = ethers.parseEther((config.reserveGasETH || 0.0005).toString());
|
|
861
1043
|
const buyerGasCost = gasPrice * finalGasLimit;
|
|
862
|
-
// ✅
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1044
|
+
// ✅ 生成多跳路径
|
|
1045
|
+
const hopPaths = generateDisperseHopPaths(buyers.map(b => b.address), disperseHopCount, chainContext.provider);
|
|
1046
|
+
// 收集所有中间钱包信息
|
|
1047
|
+
const allHopWallets = [];
|
|
1048
|
+
hopPaths.forEach(path => {
|
|
1049
|
+
allHopWallets.push(...path.hopWalletsInfo);
|
|
1050
|
+
});
|
|
1051
|
+
let transferTxs = [];
|
|
1052
|
+
if (disperseHopCount === 0) {
|
|
1053
|
+
// ✅ 无多跳:直接转账
|
|
1054
|
+
const transferNonces = buyers.map((_, i) => sellerNonce + i);
|
|
1055
|
+
sellerNonce += buyers.length;
|
|
1056
|
+
if (useNativeToken) {
|
|
1057
|
+
transferTxs = await Promise.all(buyers.map((buyer, i) => {
|
|
1058
|
+
const transferValue = transferAmountsWei[i] + reserveGas + buyerGasCost;
|
|
1059
|
+
return seller.signTransaction({
|
|
1060
|
+
to: buyer.address,
|
|
1061
|
+
value: transferValue,
|
|
1062
|
+
nonce: transferNonces[i],
|
|
1063
|
+
gasPrice,
|
|
1064
|
+
gasLimit: NATIVE_TRANSFER_GAS_LIMIT,
|
|
1065
|
+
chainId: chainContext.chainId,
|
|
1066
|
+
type: txType
|
|
1067
|
+
});
|
|
1068
|
+
}));
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
const erc20Interface = new ethers.Interface(ERC20_ABI);
|
|
1072
|
+
transferTxs = await Promise.all(buyers.map((buyer, i) => {
|
|
1073
|
+
const transferData = erc20Interface.encodeFunctionData('transfer', [
|
|
1074
|
+
buyer.address,
|
|
1075
|
+
transferAmountsWei[i]
|
|
1076
|
+
]);
|
|
1077
|
+
return seller.signTransaction({
|
|
1078
|
+
to: quoteToken,
|
|
1079
|
+
data: transferData,
|
|
1080
|
+
value: 0n,
|
|
1081
|
+
nonce: transferNonces[i],
|
|
1082
|
+
gasPrice,
|
|
1083
|
+
gasLimit: ERC20_TRANSFER_GAS,
|
|
1084
|
+
chainId: chainContext.chainId,
|
|
1085
|
+
type: txType
|
|
1086
|
+
});
|
|
1087
|
+
}));
|
|
1088
|
+
}
|
|
880
1089
|
}
|
|
881
1090
|
else {
|
|
882
|
-
//
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
buyer
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1091
|
+
// ✅ 有多跳:构建多跳转账链
|
|
1092
|
+
if (useNativeToken) {
|
|
1093
|
+
// 原生代币多跳转账
|
|
1094
|
+
// ✅ 修复:payer 每次只发送 1 笔交易(到第一个hop钱包),hop钱包用自己的nonce=0
|
|
1095
|
+
const hopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1096
|
+
const finalAmount = transferAmountsWei[i] + reserveGas + buyerGasCost;
|
|
1097
|
+
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1098
|
+
return buildNativeHopChain(seller, path, finalAmount, gasPrice, chainContext.chainId, txType, payerNonce);
|
|
1099
|
+
}));
|
|
1100
|
+
transferTxs = hopChains.flat();
|
|
1101
|
+
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
// ERC20 多跳转账:先转 BNB(给中间钱包 gas),再转 ERC20
|
|
1105
|
+
// ✅ 修复:payer 每次只发送 1 笔交易(到第一个hop钱包),hop钱包用自己的nonce=0/1
|
|
1106
|
+
// 1. 构建 BNB 多跳链
|
|
1107
|
+
const bnbHopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1108
|
+
const finalGasAmount = buyerGasCost;
|
|
1109
|
+
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1110
|
+
return buildBNBHopChainForERC20(seller, path, finalGasAmount, gasPrice, chainContext.chainId, txType, payerNonce);
|
|
1111
|
+
}));
|
|
1112
|
+
const bnbTxs = bnbHopChains.flat();
|
|
1113
|
+
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1114
|
+
// 2. 构建 ERC20 多跳链
|
|
1115
|
+
const erc20HopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1116
|
+
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1117
|
+
return buildERC20HopChain(seller, path, quoteToken, transferAmountsWei[i], gasPrice, chainContext.chainId, txType, payerNonce);
|
|
1118
|
+
}));
|
|
1119
|
+
const erc20Txs = erc20HopChains.flat();
|
|
1120
|
+
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1121
|
+
transferTxs = [...bnbTxs, ...erc20Txs];
|
|
1122
|
+
}
|
|
902
1123
|
}
|
|
903
|
-
console.log(`[flapQuickBatchSwapMerkle] ${transferTxs.length}
|
|
1124
|
+
console.log(`[flapQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名 (多跳数=${disperseHopCount})`);
|
|
904
1125
|
// ==================== 4. 买入交易 ====================
|
|
905
1126
|
// ✅ 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
|
|
906
1127
|
const buyerNonces = startNonces && startNonces.length > 1
|
|
@@ -960,6 +1181,7 @@ export async function flapQuickBatchSwapMerkle(params) {
|
|
|
960
1181
|
console.log(` - 利润多跳: ${nativeProfitAmount > 0n ? PROFIT_HOP_COUNT + 1 : 0}`);
|
|
961
1182
|
return {
|
|
962
1183
|
signedTransactions,
|
|
1184
|
+
disperseHopWallets: allHopWallets.length > 0 ? allHopWallets : undefined, // ✅ 返回中间钱包信息
|
|
963
1185
|
metadata: {
|
|
964
1186
|
sellerAddress: seller.address,
|
|
965
1187
|
buyerAddresses: buyers.map(b => b.address),
|
|
@@ -971,7 +1193,8 @@ export async function flapQuickBatchSwapMerkle(params) {
|
|
|
971
1193
|
? ethers.formatEther(amt)
|
|
972
1194
|
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
973
1195
|
profitAmount: nativeProfitAmount > 0n ? ethers.formatEther(nativeProfitAmount) : undefined,
|
|
974
|
-
useNativeToken
|
|
1196
|
+
useNativeToken,
|
|
1197
|
+
disperseHopCount: disperseHopCount > 0 ? disperseHopCount : undefined // ✅ 返回多跳数
|
|
975
1198
|
}
|
|
976
1199
|
};
|
|
977
1200
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CommonBundleConfig } from '../utils/bundle-helpers.js';
|
|
2
|
+
import { type GeneratedWallet } from '../utils/wallet.js';
|
|
2
3
|
export interface PancakeSwapSignConfig {
|
|
3
4
|
rpcUrl: string;
|
|
4
5
|
gasLimit?: number | bigint;
|
|
@@ -129,6 +130,7 @@ export interface PancakeQuickBatchSwapParams {
|
|
|
129
130
|
config: PancakeSwapSignConfig;
|
|
130
131
|
quoteToken?: string;
|
|
131
132
|
quoteTokenDecimals?: number;
|
|
133
|
+
disperseHopCount?: number;
|
|
132
134
|
startNonces?: number[];
|
|
133
135
|
}
|
|
134
136
|
/**
|
|
@@ -136,6 +138,7 @@ export interface PancakeQuickBatchSwapParams {
|
|
|
136
138
|
*/
|
|
137
139
|
export interface PancakeQuickBatchSwapResult {
|
|
138
140
|
signedTransactions: string[];
|
|
141
|
+
disperseHopWallets?: GeneratedWallet[];
|
|
139
142
|
metadata?: {
|
|
140
143
|
sellerAddress: string;
|
|
141
144
|
buyerAddresses: string[];
|
|
@@ -144,19 +147,21 @@ export interface PancakeQuickBatchSwapResult {
|
|
|
144
147
|
transferAmounts: string[];
|
|
145
148
|
profitAmount?: string;
|
|
146
149
|
useNativeToken: boolean;
|
|
150
|
+
disperseHopCount?: number;
|
|
147
151
|
};
|
|
148
152
|
}
|
|
149
153
|
/**
|
|
150
154
|
* PancakeSwap 快捷批量换手(资金自动流转)
|
|
151
155
|
*
|
|
152
|
-
* 流程:[贿赂] → [卖出] → [
|
|
156
|
+
* 流程:[贿赂] → [卖出] → [转账多跳...] → [买入1, 买入2, ...] → [利润]
|
|
153
157
|
*
|
|
154
158
|
* 特点:
|
|
155
159
|
* - 子钱包不需要预先有余额
|
|
156
160
|
* - 资金来自主钱包卖出代币所得
|
|
157
161
|
* - 提升资金利用率
|
|
158
162
|
* - 支持 BNB 和 ERC20(如 USDT)两种模式
|
|
163
|
+
* - ✅ 支持转账多跳,隐藏资金流向
|
|
159
164
|
*
|
|
160
|
-
*
|
|
165
|
+
* 限制:根据多跳数动态计算最大买方数量
|
|
161
166
|
*/
|
|
162
167
|
export declare function pancakeQuickBatchSwapMerkle(params: PancakeQuickBatchSwapParams): Promise<PancakeQuickBatchSwapResult>;
|
|
@@ -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,90 @@ 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
|
-
buyer
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1316
|
+
// ✅ 有多跳:构建多跳转账链
|
|
1317
|
+
if (useNativeToken) {
|
|
1318
|
+
// BNB 多跳转账
|
|
1319
|
+
// ✅ 修复:payer 每次只发送 1 笔交易(到第一个hop钱包),hop钱包用自己的nonce=0
|
|
1320
|
+
const hopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1321
|
+
const finalAmount = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
|
|
1322
|
+
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1323
|
+
return buildNativeHopChain(seller, path, finalAmount, gasPrice, context.chainId, txType, payerNonce);
|
|
1324
|
+
}));
|
|
1325
|
+
transferTxs = hopChains.flat();
|
|
1326
|
+
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
// ERC20 多跳转账:先转 BNB(给中间钱包 gas),再转 ERC20
|
|
1330
|
+
// ✅ 修复:payer 每次只发送 1 笔交易(到第一个hop钱包),hop钱包用自己的nonce=0/1
|
|
1331
|
+
// 1. 构建 BNB 多跳链(为中间钱包和最终钱包预留 gas)
|
|
1332
|
+
const bnbHopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1333
|
+
// 最终钱包需要的 gas(用于买入交易)
|
|
1334
|
+
const finalGasAmount = buyerGasCost;
|
|
1335
|
+
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1336
|
+
return buildBNBHopChainForERC20(seller, path, finalGasAmount, gasPrice, context.chainId, txType, payerNonce);
|
|
1337
|
+
}));
|
|
1338
|
+
const bnbTxs = bnbHopChains.flat();
|
|
1339
|
+
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1340
|
+
// 2. 构建 ERC20 多跳链
|
|
1341
|
+
const erc20HopChains = await Promise.all(hopPaths.map((path, i) => {
|
|
1342
|
+
const payerNonce = sellerNonce + i; // ✅ payer 每个 buyer 只用 1 个 nonce
|
|
1343
|
+
return buildERC20HopChain(seller, path, quoteToken, transferAmountsWei[i], gasPrice, context.chainId, txType, payerNonce);
|
|
1344
|
+
}));
|
|
1345
|
+
const erc20Txs = erc20HopChains.flat();
|
|
1346
|
+
sellerNonce += buyers.length; // ✅ payer 只增加 buyers.length 个 nonce
|
|
1347
|
+
transferTxs = [...bnbTxs, ...erc20Txs];
|
|
1348
|
+
}
|
|
1065
1349
|
}
|
|
1066
|
-
console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length}
|
|
1350
|
+
console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名 (多跳数=${disperseHopCount})`);
|
|
1067
1351
|
// ==================== 4. 买入交易 ====================
|
|
1068
1352
|
// ✅ 如果前端传入了 startNonces,使用 buyer 部分(从索引 1 开始)
|
|
1069
1353
|
const buyerNonces = startNonces && startNonces.length > 1
|
|
@@ -1150,6 +1434,7 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
1150
1434
|
const outputUnit = useNativeToken ? 'BNB' : 'ERC20';
|
|
1151
1435
|
return {
|
|
1152
1436
|
signedTransactions,
|
|
1437
|
+
disperseHopWallets: allHopWallets.length > 0 ? allHopWallets : undefined, // ✅ 返回中间钱包信息
|
|
1153
1438
|
metadata: {
|
|
1154
1439
|
sellerAddress: seller.address,
|
|
1155
1440
|
buyerAddresses: buyers.map(b => b.address),
|
|
@@ -1161,7 +1446,8 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
1161
1446
|
? ethers.formatEther(amt)
|
|
1162
1447
|
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
1163
1448
|
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
1164
|
-
useNativeToken
|
|
1449
|
+
useNativeToken,
|
|
1450
|
+
disperseHopCount: disperseHopCount > 0 ? disperseHopCount : undefined // ✅ 返回多跳数
|
|
1165
1451
|
}
|
|
1166
1452
|
};
|
|
1167
1453
|
}
|