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.
@@ -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
- estimatedBNBOut: string;
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
- * - 子钱包不需要预先有 BNB 余额
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(授权已消耗一个 nonce)
360
+ // ✅ 使用共享的 NonceManager 规划 nonce
405
361
  const noncePlan = await planNonces({
406
362
  seller,
407
363
  buyer,
408
364
  sameAddress,
409
- approvalExists: !!approvalTx,
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
- * - 子钱包不需要预先有 BNB 余额
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
- // ✅ 校验买方数量(最多 23 个:贿赂(1) + 卖出(1) + 转账(N) + 买入(N) + 利润(1) ≤ 50)
755
- // 2N + 3 50 → N ≤ 23
756
- const MAX_BUYERS = 23;
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
- throw new Error(`资金利用率模式买方钱包数量超过限制: ${buyerPrivateKeys.length} > ${MAX_BUYERS}`);
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] 卖出数量: ${ethers.formatUnits(sellAmountWei, decimals)} (${sellAmountWei} wei, decimals=${decimals})`);
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 estimatedBNBOut = quoteResult.estimatedBNBOut;
803
- console.log(`[pancakeQuickBatchSwapMerkle] 预估卖出所得: ${ethers.formatEther(estimatedBNBOut)} BNB (${estimatedBNBOut} wei)`);
804
- // ✅ 安全检查:如果报价超过 10 BNB,警告可能有问题
805
- if (estimatedBNBOut > ethers.parseEther('10')) {
806
- console.warn(`[pancakeQuickBatchSwapMerkle] ⚠️ 报价异常高: ${ethers.formatEther(estimatedBNBOut)} BNB,请检查路径和数量!`);
807
- }
808
- // ✅ 计算利润(从卖出所得中预扣)
809
- const profitAmount = calculateProfitAmount(estimatedBNBOut);
810
- const distributableBNB = estimatedBNBOut - profitAmount; // 可分配金额
811
- // ✅ 计算每个买方分到的 BNB(用于转账和买入)
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 => ethers.parseEther(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 > distributableBNB) {
819
- throw new Error(`指定的买入总金额 (${ethers.formatEther(totalTransfer)} BNB) 超过可分配金额 (${ethers.formatEther(distributableBNB)} BNB)`);
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
- const amount = (distributableBNB * BigInt(Math.round(ratio * 10000))) / 10000n;
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
- // ✅ 验证主钱包余额(需要支付 Gas 费用 + 贿赂)
826
+ // ✅ 验证主钱包余额
837
827
  const sellerBalance = await seller.provider.getBalance(seller.address);
838
- // 卖方需要的 Gas:贿赂(21000) + 卖出(gasLimit) + N个转账(21000 each) + 利润(21000)
839
- const sellerGasCost = gasPrice * (21000n + finalGasLimit + 21000n * BigInt(buyers.length) + 21000n);
840
- const sellerRequired = bribeAmount + sellerGasCost;
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
- for (let i = 0; i < buyers.length; i++) {
890
- // 转账金额 = 买入金额 + FLAT_FEE + 买方 Gas 费用
891
- const buyerGasCost = gasPrice * finalGasLimit;
892
- const transferValue = transferAmountsWei[i] + FLAT_FEE + buyerGasCost;
893
- const transferTx = await seller.signTransaction({
894
- to: buyers[i].address,
895
- value: transferValue,
896
- nonce: sellerNonce++,
897
- gasPrice,
898
- gasLimit: 21000n,
899
- chainId: context.chainId,
900
- type: txType
901
- });
902
- transferTxs.push(transferTx);
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
- const buyValue = buyAmount + FLAT_FEE;
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
- // 顺序:贿赂 → 卖出 → 转账1, 转账2, ... → 买入1, 买入2, ... → 利润
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
- estimatedBNBOut: ethers.formatEther(estimatedBNBOut),
976
- transferAmounts: transferAmountsWei.map(amt => ethers.formatEther(amt)),
977
- profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.4.12",
3
+ "version": "1.4.14",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",