four-flap-meme-sdk 1.4.12 → 1.4.14
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/pancake/bundle-swap.d.ts +6 -6
- package/dist/pancake/bundle-swap.js +169 -122
- package/package.json +1 -1
|
@@ -8,7 +8,6 @@ export interface PancakeSwapSignConfig {
|
|
|
8
8
|
txType?: 0 | 2;
|
|
9
9
|
chainId?: number;
|
|
10
10
|
reserveGasBNB?: number;
|
|
11
|
-
skipApprovalCheck?: boolean;
|
|
12
11
|
bribeAmount?: number;
|
|
13
12
|
}
|
|
14
13
|
export type SwapRouteType = 'v2' | 'v3-single' | 'v3-multi';
|
|
@@ -19,7 +18,6 @@ export interface PancakeSwapConfig extends CommonBundleConfig {
|
|
|
19
18
|
reserveGasBNB?: number;
|
|
20
19
|
waitForConfirmation?: boolean;
|
|
21
20
|
waitTimeoutMs?: number;
|
|
22
|
-
skipApprovalCheck?: boolean;
|
|
23
21
|
}
|
|
24
22
|
export interface V2RouteParams {
|
|
25
23
|
routeType: 'v2';
|
|
@@ -67,7 +65,6 @@ export type PancakeSwapResult = {
|
|
|
67
65
|
buyerAddress: string;
|
|
68
66
|
sellAmount: string;
|
|
69
67
|
buyAmount: string;
|
|
70
|
-
hasApproval?: boolean;
|
|
71
68
|
profitAmount?: string;
|
|
72
69
|
};
|
|
73
70
|
};
|
|
@@ -102,7 +99,6 @@ export interface PancakeBatchSwapResult {
|
|
|
102
99
|
buyerAddresses: string[];
|
|
103
100
|
sellAmount: string;
|
|
104
101
|
buyAmounts: string[];
|
|
105
|
-
hasApproval?: boolean;
|
|
106
102
|
profitAmount?: string;
|
|
107
103
|
};
|
|
108
104
|
}
|
|
@@ -129,6 +125,8 @@ export interface PancakeQuickBatchSwapParams {
|
|
|
129
125
|
tokenAddress: string;
|
|
130
126
|
routeParams: RouteParams;
|
|
131
127
|
config: PancakeSwapSignConfig;
|
|
128
|
+
quoteToken?: string;
|
|
129
|
+
quoteTokenDecimals?: number;
|
|
132
130
|
}
|
|
133
131
|
/**
|
|
134
132
|
* 快捷批量换手结果
|
|
@@ -139,9 +137,10 @@ export interface PancakeQuickBatchSwapResult {
|
|
|
139
137
|
sellerAddress: string;
|
|
140
138
|
buyerAddresses: string[];
|
|
141
139
|
sellAmount: string;
|
|
142
|
-
|
|
140
|
+
estimatedOutput: string;
|
|
143
141
|
transferAmounts: string[];
|
|
144
142
|
profitAmount?: string;
|
|
143
|
+
useNativeToken: boolean;
|
|
145
144
|
};
|
|
146
145
|
}
|
|
147
146
|
/**
|
|
@@ -150,9 +149,10 @@ export interface PancakeQuickBatchSwapResult {
|
|
|
150
149
|
* 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
|
|
151
150
|
*
|
|
152
151
|
* 特点:
|
|
153
|
-
* -
|
|
152
|
+
* - 子钱包不需要预先有余额
|
|
154
153
|
* - 资金来自主钱包卖出代币所得
|
|
155
154
|
* - 提升资金利用率
|
|
155
|
+
* - 支持 BNB 和 ERC20(如 USDT)两种模式
|
|
156
156
|
*
|
|
157
157
|
* 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
|
|
158
158
|
*/
|
|
@@ -7,31 +7,6 @@ function createPancakeContext(config) {
|
|
|
7
7
|
});
|
|
8
8
|
return { chainId, provider };
|
|
9
9
|
}
|
|
10
|
-
async function ensureSellerApproval({ tokenAddress, seller, provider, decimals, chainId, config, nonceManager, gasPrice, txType }) {
|
|
11
|
-
if (config.skipApprovalCheck) {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
const erc20 = new Contract(tokenAddress, ERC20_ALLOWANCE_ABI, provider);
|
|
15
|
-
const currentAllowance = await erc20.allowance(seller.address, PANCAKE_PROXY_ADDRESS);
|
|
16
|
-
// ✅ 阈值:MaxUint256 / 2,如果授权额度超过这个值,认为是"无限授权"
|
|
17
|
-
// 这样可以检测到用户之前的 MaxUint256 授权
|
|
18
|
-
const halfMaxUint = ethers.MaxUint256 / 2n;
|
|
19
|
-
if (currentAllowance >= halfMaxUint) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
// ✅ 使用共享的 NonceManager
|
|
23
|
-
const approvalNonce = await nonceManager.getNextNonce(seller);
|
|
24
|
-
return await seller.signTransaction({
|
|
25
|
-
to: tokenAddress,
|
|
26
|
-
data: APPROVE_INTERFACE.encodeFunctionData('approve', [PANCAKE_PROXY_ADDRESS, ethers.MaxUint256]),
|
|
27
|
-
value: 0n,
|
|
28
|
-
nonce: approvalNonce,
|
|
29
|
-
gasLimit: 80000n,
|
|
30
|
-
gasPrice,
|
|
31
|
-
chainId,
|
|
32
|
-
type: txType
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
10
|
async function quoteSellOutput({ routeParams, sellAmountWei, provider }) {
|
|
36
11
|
console.log(`[quoteSellOutput] 开始报价, routeType=${routeParams.routeType}, sellAmount=${sellAmountWei} wei`);
|
|
37
12
|
// ==================== V2 报价 ====================
|
|
@@ -312,13 +287,6 @@ const PANCAKE_PROXY_ADDRESS = ADDRESSES.BSC.PancakeProxy;
|
|
|
312
287
|
// 代理合约手续费
|
|
313
288
|
const FLAT_FEE = 0n; // ✅ 已移除合约固定手续费
|
|
314
289
|
const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
|
|
315
|
-
// ✅ STABLE_COINS 和 V3_FEE_TIERS 已移至 ../utils/quote-helpers.ts
|
|
316
|
-
const ERC20_ALLOWANCE_ABI = [
|
|
317
|
-
'function allowance(address,address) view returns (uint256)',
|
|
318
|
-
'function approve(address spender,uint256 amount) returns (bool)',
|
|
319
|
-
'function decimals() view returns (uint8)'
|
|
320
|
-
];
|
|
321
|
-
const APPROVE_INTERFACE = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
|
|
322
290
|
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
323
291
|
const ERC20_BALANCE_OF_ABI = ['function balanceOf(address) view returns (uint256)'];
|
|
324
292
|
/**
|
|
@@ -343,18 +311,6 @@ export async function pancakeBundleSwapMerkle(params) {
|
|
|
343
311
|
calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
|
|
344
312
|
]);
|
|
345
313
|
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
346
|
-
// ✅ 先构建授权交易(会消耗 nonce)
|
|
347
|
-
const approvalTx = await ensureSellerApproval({
|
|
348
|
-
tokenAddress,
|
|
349
|
-
seller,
|
|
350
|
-
provider: context.provider,
|
|
351
|
-
decimals,
|
|
352
|
-
chainId: context.chainId,
|
|
353
|
-
config,
|
|
354
|
-
nonceManager, // ✅ 共享 NonceManager
|
|
355
|
-
gasPrice,
|
|
356
|
-
txType
|
|
357
|
-
});
|
|
358
314
|
const quoteResult = await quoteSellOutput({
|
|
359
315
|
routeParams,
|
|
360
316
|
sellAmountWei,
|
|
@@ -401,14 +357,14 @@ export async function pancakeBundleSwapMerkle(params) {
|
|
|
401
357
|
? ethers.parseEther(String(config.bribeAmount))
|
|
402
358
|
: 0n;
|
|
403
359
|
const needBribeTx = bribeAmount > 0n;
|
|
404
|
-
// ✅ 使用共享的 NonceManager 规划 nonce
|
|
360
|
+
// ✅ 使用共享的 NonceManager 规划 nonce
|
|
405
361
|
const noncePlan = await planNonces({
|
|
406
362
|
seller,
|
|
407
363
|
buyer,
|
|
408
364
|
sameAddress,
|
|
409
|
-
approvalExists:
|
|
365
|
+
approvalExists: false, // ✅ 已移除授权
|
|
410
366
|
profitNeeded: profitAmount > 0n,
|
|
411
|
-
needBribeTx,
|
|
367
|
+
needBribeTx,
|
|
412
368
|
nonceManager
|
|
413
369
|
});
|
|
414
370
|
// ✅ 并行签名所有交易
|
|
@@ -477,12 +433,10 @@ export async function pancakeBundleSwapMerkle(params) {
|
|
|
477
433
|
provider: context.provider,
|
|
478
434
|
buyerAddress: buyer.address
|
|
479
435
|
});
|
|
480
|
-
// ✅ 组装顺序:贿赂 →
|
|
436
|
+
// ✅ 组装顺序:贿赂 → 卖出 → 买入 → 利润
|
|
481
437
|
const signedTransactions = [];
|
|
482
438
|
if (bribeTx)
|
|
483
439
|
signedTransactions.push(bribeTx);
|
|
484
|
-
if (approvalTx)
|
|
485
|
-
signedTransactions.push(approvalTx);
|
|
486
440
|
signedTransactions.push(signedSell, signedBuy);
|
|
487
441
|
if (profitTx)
|
|
488
442
|
signedTransactions.push(profitTx);
|
|
@@ -495,7 +449,6 @@ export async function pancakeBundleSwapMerkle(params) {
|
|
|
495
449
|
buyAmount: useNativeToken
|
|
496
450
|
? ethers.formatEther(buyerBudget.buyAmountBNB)
|
|
497
451
|
: ethers.formatUnits(buyerBudget.buyAmountBNB, quoteTokenDecimals),
|
|
498
|
-
hasApproval: !!approvalTx,
|
|
499
452
|
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
|
|
500
453
|
}
|
|
501
454
|
};
|
|
@@ -532,18 +485,6 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
532
485
|
calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
|
|
533
486
|
]);
|
|
534
487
|
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
535
|
-
// ✅ 构建授权交易(如果需要)
|
|
536
|
-
const approvalTx = await ensureSellerApproval({
|
|
537
|
-
tokenAddress,
|
|
538
|
-
seller,
|
|
539
|
-
provider: context.provider,
|
|
540
|
-
decimals,
|
|
541
|
-
chainId: context.chainId,
|
|
542
|
-
config,
|
|
543
|
-
nonceManager,
|
|
544
|
-
gasPrice,
|
|
545
|
-
txType
|
|
546
|
-
});
|
|
547
488
|
// ✅ 获取卖出报价
|
|
548
489
|
const quoteResult = await quoteSellOutput({
|
|
549
490
|
routeParams,
|
|
@@ -713,12 +654,10 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
713
654
|
signedSellPromise,
|
|
714
655
|
...signedBuyPromises
|
|
715
656
|
]);
|
|
716
|
-
// 4. 按顺序组装交易数组:贿赂 →
|
|
657
|
+
// 4. 按顺序组装交易数组:贿赂 → 卖出 → 买入 → 利润
|
|
717
658
|
const signedTransactions = [];
|
|
718
659
|
if (bribeTx)
|
|
719
660
|
signedTransactions.push(bribeTx); // 贿赂(首位)
|
|
720
|
-
if (approvalTx)
|
|
721
|
-
signedTransactions.push(approvalTx); // 授权(如果有)
|
|
722
661
|
signedTransactions.push(signedSell); // 卖出
|
|
723
662
|
signedTransactions.push(...signedBuys); // 多个买入
|
|
724
663
|
if (profitTx)
|
|
@@ -732,7 +671,6 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
732
671
|
buyAmounts: buyAmountsWei.map(amt => useNativeToken
|
|
733
672
|
? ethers.formatEther(amt)
|
|
734
673
|
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
735
|
-
hasApproval: !!approvalTx,
|
|
736
674
|
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
|
|
737
675
|
}
|
|
738
676
|
};
|
|
@@ -743,22 +681,30 @@ export async function pancakeBatchSwapMerkle(params) {
|
|
|
743
681
|
* 流程:[贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
|
|
744
682
|
*
|
|
745
683
|
* 特点:
|
|
746
|
-
* -
|
|
684
|
+
* - 子钱包不需要预先有余额
|
|
747
685
|
* - 资金来自主钱包卖出代币所得
|
|
748
686
|
* - 提升资金利用率
|
|
687
|
+
* - 支持 BNB 和 ERC20(如 USDT)两种模式
|
|
749
688
|
*
|
|
750
689
|
* 限制:最多 23 个买方(转账 + 买入 = 46 笔,加上贿赂/卖出/利润 = 50 笔限制)
|
|
751
690
|
*/
|
|
752
691
|
export async function pancakeQuickBatchSwapMerkle(params) {
|
|
753
|
-
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config } = params;
|
|
754
|
-
// ✅
|
|
755
|
-
|
|
756
|
-
const
|
|
692
|
+
const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKeys, buyerRatios, buyerAmounts, tokenAddress, routeParams, config, quoteToken, quoteTokenDecimals = 18 } = params;
|
|
693
|
+
// ✅ 判断是否使用原生代币
|
|
694
|
+
const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
|
|
695
|
+
const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'.toLowerCase();
|
|
696
|
+
// ✅ 校验买方数量(已移除授权交易)
|
|
697
|
+
// BNB 模式:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50 → 2N + 3 ≤ 50 → N ≤ 23
|
|
698
|
+
// ERC20 模式:贿赂(1) + 卖出(1) + ERC20转账(N) + BNB Gas转账(N) + 买入(N) + 利润(1) ≤ 50 → 3N + 3 ≤ 50 → N ≤ 15
|
|
699
|
+
const MAX_BUYERS_NATIVE = 23;
|
|
700
|
+
const MAX_BUYERS_ERC20 = 15;
|
|
701
|
+
const MAX_BUYERS = useNativeToken ? MAX_BUYERS_NATIVE : MAX_BUYERS_ERC20;
|
|
757
702
|
if (buyerPrivateKeys.length === 0) {
|
|
758
703
|
throw new Error('至少需要一个买方钱包');
|
|
759
704
|
}
|
|
760
705
|
if (buyerPrivateKeys.length > MAX_BUYERS) {
|
|
761
|
-
|
|
706
|
+
const mode = useNativeToken ? 'BNB' : 'ERC20';
|
|
707
|
+
throw new Error(`资金利用率模式(${mode})买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
|
|
762
708
|
}
|
|
763
709
|
// ✅ 校验分配模式
|
|
764
710
|
if (!buyerRatios && !buyerAmounts) {
|
|
@@ -772,20 +718,51 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
772
718
|
}
|
|
773
719
|
const context = createPancakeContext(config);
|
|
774
720
|
const seller = new Wallet(sellerPrivateKey, context.provider);
|
|
775
|
-
// ✅ 买方需要私钥(因为买方自己执行买入交易)
|
|
776
721
|
const buyers = buyerPrivateKeys.map(pk => new Wallet(pk, context.provider));
|
|
722
|
+
// ✅ 校验卖出路径输出代币是否匹配
|
|
723
|
+
let sellOutputToken;
|
|
724
|
+
if (routeParams.routeType === 'v2') {
|
|
725
|
+
const { v2Path } = routeParams;
|
|
726
|
+
sellOutputToken = v2Path[v2Path.length - 1];
|
|
727
|
+
}
|
|
728
|
+
else if (routeParams.routeType === 'v3-single') {
|
|
729
|
+
const { v3TokenOut } = routeParams;
|
|
730
|
+
sellOutputToken = v3TokenOut;
|
|
731
|
+
}
|
|
732
|
+
else if (routeParams.routeType === 'v3-multi') {
|
|
733
|
+
const { v2Path } = routeParams;
|
|
734
|
+
if (v2Path && v2Path.length > 0) {
|
|
735
|
+
sellOutputToken = v2Path[v2Path.length - 1];
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
// 校验输出代币
|
|
739
|
+
if (useNativeToken) {
|
|
740
|
+
// 原生代币模式:输出必须是 WBNB
|
|
741
|
+
if (!sellOutputToken || sellOutputToken.toLowerCase() !== WBNB) {
|
|
742
|
+
throw new Error(`原生代币模式要求卖出路径以 WBNB 结尾(当前输出: ${sellOutputToken || '未知'})。` +
|
|
743
|
+
`请切换交易对或使用 ERC20 模式。`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
// ERC20 模式:输出必须是指定的 quoteToken
|
|
748
|
+
if (!sellOutputToken || sellOutputToken.toLowerCase() !== quoteToken.toLowerCase()) {
|
|
749
|
+
throw new Error(`ERC20 模式要求卖出路径以 ${quoteToken} 结尾(当前输出: ${sellOutputToken || '未知'})。`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
777
752
|
// ✅ 创建共享资源
|
|
778
753
|
const nonceManager = new NonceManager(context.provider);
|
|
779
754
|
const finalGasLimit = getGasLimit(config);
|
|
780
755
|
const txType = config.txType ?? 0;
|
|
756
|
+
const ERC20_TRANSFER_GAS = 65000n; // ERC20 transfer 的 gas 限制
|
|
781
757
|
// ✅ 并行获取 gasPrice 和卖出数量
|
|
782
758
|
const [gasPrice, sellAmountResult] = await Promise.all([
|
|
783
759
|
getGasPrice(context.provider, config),
|
|
784
760
|
calculateSellAmount(context.provider, tokenAddress, seller.address, sellAmount, sellPercentage)
|
|
785
761
|
]);
|
|
786
762
|
const { amount: sellAmountWei, decimals } = sellAmountResult;
|
|
787
|
-
// ✅
|
|
788
|
-
console.log(`[pancakeQuickBatchSwapMerkle]
|
|
763
|
+
// ✅ 调试日志
|
|
764
|
+
console.log(`[pancakeQuickBatchSwapMerkle] 模式: ${useNativeToken ? 'BNB' : 'ERC20'}`);
|
|
765
|
+
console.log(`[pancakeQuickBatchSwapMerkle] 卖出数量: ${ethers.formatUnits(sellAmountWei, decimals)}`);
|
|
789
766
|
console.log(`[pancakeQuickBatchSwapMerkle] routeParams:`, JSON.stringify({
|
|
790
767
|
routeType: routeParams.routeType,
|
|
791
768
|
v2Path: routeParams.v2Path,
|
|
@@ -799,31 +776,44 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
799
776
|
sellAmountWei,
|
|
800
777
|
provider: context.provider
|
|
801
778
|
});
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
779
|
+
const estimatedOutput = quoteResult.estimatedBNBOut;
|
|
780
|
+
const outputFormatted = useNativeToken
|
|
781
|
+
? ethers.formatEther(estimatedOutput)
|
|
782
|
+
: ethers.formatUnits(estimatedOutput, quoteTokenDecimals);
|
|
783
|
+
console.log(`[pancakeQuickBatchSwapMerkle] 预估卖出所得: ${outputFormatted} ${useNativeToken ? 'BNB' : 'ERC20'}`);
|
|
784
|
+
// ✅ 计算利润(基于 BNB 价值)
|
|
785
|
+
let profitAmount;
|
|
786
|
+
if (useNativeToken) {
|
|
787
|
+
profitAmount = calculateProfitAmount(estimatedOutput);
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
// ERC20 模式:需要将 ERC20 价值转换为 BNB
|
|
791
|
+
const version = routeParams.routeType === 'v2' ? 'v2' : 'v3';
|
|
792
|
+
const fee = routeParams.routeType === 'v3-single' ? routeParams.v3Fee : undefined;
|
|
793
|
+
const estimatedBNBValue = await getERC20ToNativeQuote(context.provider, quoteToken, estimatedOutput, version, fee);
|
|
794
|
+
profitAmount = calculateProfitAmount(estimatedBNBValue);
|
|
795
|
+
console.log(`[pancakeQuickBatchSwapMerkle] ERC20→BNB 报价: ${outputFormatted} → ${ethers.formatEther(estimatedBNBValue)} BNB`);
|
|
796
|
+
}
|
|
797
|
+
const distributableAmount = estimatedOutput - (useNativeToken ? profitAmount : 0n);
|
|
798
|
+
// ✅ 计算每个买方分到的金额
|
|
812
799
|
let transferAmountsWei;
|
|
813
800
|
if (buyerAmounts && buyerAmounts.length === buyers.length) {
|
|
814
|
-
//
|
|
815
|
-
transferAmountsWei = buyerAmounts.map(amt =>
|
|
816
|
-
|
|
801
|
+
// 数量模式
|
|
802
|
+
transferAmountsWei = buyerAmounts.map(amt => useNativeToken
|
|
803
|
+
? ethers.parseEther(amt)
|
|
804
|
+
: ethers.parseUnits(amt, quoteTokenDecimals));
|
|
817
805
|
const totalTransfer = transferAmountsWei.reduce((a, b) => a + b, 0n);
|
|
818
|
-
if (totalTransfer >
|
|
819
|
-
|
|
806
|
+
if (totalTransfer > distributableAmount) {
|
|
807
|
+
const formatted = useNativeToken
|
|
808
|
+
? ethers.formatEther(distributableAmount)
|
|
809
|
+
: ethers.formatUnits(distributableAmount, quoteTokenDecimals);
|
|
810
|
+
throw new Error(`指定的买入总金额超过可分配金额 (${formatted})`);
|
|
820
811
|
}
|
|
821
812
|
}
|
|
822
813
|
else if (buyerRatios && buyerRatios.length === buyers.length) {
|
|
823
|
-
//
|
|
814
|
+
// 比例模式
|
|
824
815
|
transferAmountsWei = buyerRatios.map(ratio => {
|
|
825
|
-
|
|
826
|
-
return amount;
|
|
816
|
+
return (distributableAmount * BigInt(Math.round(ratio * 10000))) / 10000n;
|
|
827
817
|
});
|
|
828
818
|
}
|
|
829
819
|
else {
|
|
@@ -833,16 +823,27 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
833
823
|
const bribeAmount = config.bribeAmount && config.bribeAmount > 0
|
|
834
824
|
? ethers.parseEther(String(config.bribeAmount))
|
|
835
825
|
: 0n;
|
|
836
|
-
// ✅
|
|
826
|
+
// ✅ 验证主钱包余额
|
|
837
827
|
const sellerBalance = await seller.provider.getBalance(seller.address);
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
828
|
+
let sellerGasCost;
|
|
829
|
+
let sellerRequired;
|
|
830
|
+
if (useNativeToken) {
|
|
831
|
+
// BNB 模式:贿赂(21000) + 卖出(gasLimit) + N个原生转账(21000 each) + 利润(21000)
|
|
832
|
+
sellerGasCost = gasPrice * (21000n + finalGasLimit + 21000n * BigInt(buyers.length) + 21000n);
|
|
833
|
+
sellerRequired = bribeAmount + sellerGasCost;
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
// ERC20 模式(已移除授权):
|
|
837
|
+
// - 卖方 Gas: 贿赂(21000) + 卖出(gasLimit) + N个ERC20转账(65000 each) + N个BNB Gas转账(21000 each) + 利润(21000)
|
|
838
|
+
// - 买方 Gas: N个买入(gasLimit each) + N个FLAT_FEE
|
|
839
|
+
sellerGasCost = gasPrice * (21000n + finalGasLimit + (ERC20_TRANSFER_GAS + 21000n) * BigInt(buyers.length) + 21000n);
|
|
840
|
+
const buyerGasNeeded = gasPrice * finalGasLimit * BigInt(buyers.length) + FLAT_FEE * BigInt(buyers.length);
|
|
841
|
+
sellerRequired = bribeAmount + sellerGasCost + buyerGasNeeded;
|
|
842
|
+
}
|
|
841
843
|
if (sellerBalance < sellerRequired) {
|
|
842
844
|
throw new Error(`主钱包 BNB 余额不足: 需要约 ${ethers.formatEther(sellerRequired)} BNB (贿赂: ${ethers.formatEther(bribeAmount)}, Gas: ${ethers.formatEther(sellerGasCost)}), 实际 ${ethers.formatEther(sellerBalance)} BNB`);
|
|
843
845
|
}
|
|
844
846
|
// ==================== 规划 Nonce ====================
|
|
845
|
-
// 流程: [贿赂] → [卖出] → [转账1, 转账2, ...] → [买入1, 买入2, ...] → [利润]
|
|
846
847
|
let sellerNonce = await nonceManager.getNextNonce(seller);
|
|
847
848
|
const deadline = Math.floor(Date.now() / 1000) + 600;
|
|
848
849
|
// ==================== 1. 贿赂交易 ====================
|
|
@@ -884,32 +885,71 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
884
885
|
type: txType
|
|
885
886
|
});
|
|
886
887
|
console.log(`[pancakeQuickBatchSwapMerkle] 卖出交易已签名`);
|
|
887
|
-
// ==================== 3.
|
|
888
|
+
// ==================== 3. 转账交易 ====================
|
|
888
889
|
const transferTxs = [];
|
|
889
|
-
|
|
890
|
-
//
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
890
|
+
if (useNativeToken) {
|
|
891
|
+
// ✅ 原生代币模式:直接 BNB 转账
|
|
892
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
893
|
+
const buyerGasCost = gasPrice * finalGasLimit;
|
|
894
|
+
const transferValue = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
|
|
895
|
+
const transferTx = await seller.signTransaction({
|
|
896
|
+
to: buyers[i].address,
|
|
897
|
+
value: transferValue,
|
|
898
|
+
nonce: sellerNonce++,
|
|
899
|
+
gasPrice,
|
|
900
|
+
gasLimit: 21000n,
|
|
901
|
+
chainId: context.chainId,
|
|
902
|
+
type: txType
|
|
903
|
+
});
|
|
904
|
+
transferTxs.push(transferTx);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
// ✅ ERC20 模式:ERC20 transfer 调用
|
|
909
|
+
const erc20Interface = new ethers.Interface([
|
|
910
|
+
'function transfer(address to, uint256 amount) returns (bool)'
|
|
911
|
+
]);
|
|
912
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
913
|
+
const transferData = erc20Interface.encodeFunctionData('transfer', [
|
|
914
|
+
buyers[i].address,
|
|
915
|
+
transferAmountsWei[i]
|
|
916
|
+
]);
|
|
917
|
+
const transferTx = await seller.signTransaction({
|
|
918
|
+
to: quoteToken,
|
|
919
|
+
data: transferData,
|
|
920
|
+
value: 0n,
|
|
921
|
+
nonce: sellerNonce++,
|
|
922
|
+
gasPrice,
|
|
923
|
+
gasLimit: ERC20_TRANSFER_GAS,
|
|
924
|
+
chainId: context.chainId,
|
|
925
|
+
type: txType
|
|
926
|
+
});
|
|
927
|
+
transferTxs.push(transferTx);
|
|
928
|
+
}
|
|
929
|
+
// ERC20 模式:额外转账 Gas 费用给买家(用于支付买入 Gas 和 FLAT_FEE)
|
|
930
|
+
for (let i = 0; i < buyers.length; i++) {
|
|
931
|
+
const buyerGasCost = gasPrice * finalGasLimit + FLAT_FEE;
|
|
932
|
+
const gasTx = await seller.signTransaction({
|
|
933
|
+
to: buyers[i].address,
|
|
934
|
+
value: buyerGasCost,
|
|
935
|
+
nonce: sellerNonce++,
|
|
936
|
+
gasPrice,
|
|
937
|
+
gasLimit: 21000n,
|
|
938
|
+
chainId: context.chainId,
|
|
939
|
+
type: txType
|
|
940
|
+
});
|
|
941
|
+
transferTxs.push(gasTx);
|
|
942
|
+
}
|
|
903
943
|
}
|
|
904
944
|
console.log(`[pancakeQuickBatchSwapMerkle] ${transferTxs.length} 笔转账交易已签名`);
|
|
905
|
-
// ==================== 4.
|
|
906
|
-
// 并行获取所有买方的 nonce
|
|
945
|
+
// ==================== 4. 买入交易 ====================
|
|
907
946
|
const buyerNonces = await Promise.all(buyers.map(buyer => nonceManager.getNextNonce(buyer)));
|
|
908
|
-
// 并行签名所有买入交易
|
|
909
947
|
const signedBuys = await Promise.all(buyers.map(async (buyer, i) => {
|
|
910
948
|
const buyAmount = transferAmountsWei[i];
|
|
911
949
|
const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
|
|
912
|
-
|
|
950
|
+
// BNB 模式:value = buyAmount + FLAT_FEE
|
|
951
|
+
// ERC20 模式:value = FLAT_FEE(买入金额通过授权支付)
|
|
952
|
+
const buyValue = useNativeToken ? buyAmount + FLAT_FEE : FLAT_FEE;
|
|
913
953
|
let buyUnsigned;
|
|
914
954
|
if (routeParams.routeType === 'v2') {
|
|
915
955
|
const { v2Path } = routeParams;
|
|
@@ -951,7 +991,8 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
951
991
|
}
|
|
952
992
|
nonceManager.clearTemp();
|
|
953
993
|
// ==================== 组装交易数组 ====================
|
|
954
|
-
//
|
|
994
|
+
// BNB 模式:贿赂 → 卖出 → 转账 → 买入 → 利润
|
|
995
|
+
// ERC20 模式:贿赂 → 卖出 → ERC20转账 → BNB Gas转账 → 买入 → 利润
|
|
955
996
|
const signedTransactions = [];
|
|
956
997
|
if (bribeTx)
|
|
957
998
|
signedTransactions.push(bribeTx);
|
|
@@ -966,15 +1007,21 @@ export async function pancakeQuickBatchSwapMerkle(params) {
|
|
|
966
1007
|
console.log(` - 转账: ${transferTxs.length}`);
|
|
967
1008
|
console.log(` - 买入: ${signedBuys.length}`);
|
|
968
1009
|
console.log(` - 利润: ${profitTx ? 1 : 0}`);
|
|
1010
|
+
const outputUnit = useNativeToken ? 'BNB' : 'ERC20';
|
|
969
1011
|
return {
|
|
970
1012
|
signedTransactions,
|
|
971
1013
|
metadata: {
|
|
972
1014
|
sellerAddress: seller.address,
|
|
973
1015
|
buyerAddresses: buyers.map(b => b.address),
|
|
974
1016
|
sellAmount: ethers.formatUnits(sellAmountWei, decimals),
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1017
|
+
estimatedOutput: useNativeToken
|
|
1018
|
+
? ethers.formatEther(estimatedOutput)
|
|
1019
|
+
: ethers.formatUnits(estimatedOutput, quoteTokenDecimals),
|
|
1020
|
+
transferAmounts: transferAmountsWei.map(amt => useNativeToken
|
|
1021
|
+
? ethers.formatEther(amt)
|
|
1022
|
+
: ethers.formatUnits(amt, quoteTokenDecimals)),
|
|
1023
|
+
profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined,
|
|
1024
|
+
useNativeToken
|
|
978
1025
|
}
|
|
979
1026
|
};
|
|
980
1027
|
}
|