four-flap-meme-sdk 1.3.56 → 1.3.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,27 @@ import { PROFIT_CONFIG } from '../utils/constants.js';
16
16
  const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
17
17
  const DEFAULT_GAS_LIMIT = 300000;
18
18
  const DEADLINE_MINUTES = 20;
19
+ /**
20
+ * 截断小数位数,避免超过代币精度导致 parseUnits 报错
21
+ * 例如:truncateDecimals("21906.025000000000000000", 1) => "21906.0"
22
+ */
23
+ function truncateDecimals(value, decimals) {
24
+ if (!value || decimals < 0)
25
+ return value;
26
+ const parts = value.split('.');
27
+ if (parts.length === 1)
28
+ return value; // 没有小数点
29
+ const integerPart = parts[0];
30
+ const decimalPart = parts[1];
31
+ if (decimals === 0)
32
+ return integerPart;
33
+ // 截断到指定小数位数
34
+ const truncatedDecimal = decimalPart.slice(0, decimals);
35
+ // 如果截断后没有小数部分,只返回整数部分
36
+ if (!truncatedDecimal || truncatedDecimal === '')
37
+ return integerPart;
38
+ return `${integerPart}.${truncatedDecimal}`;
39
+ }
19
40
  /** Router 地址配置 */
20
41
  export const DIRECT_ROUTERS = {
21
42
  BSC: {
@@ -448,7 +469,9 @@ export async function directV2BatchSell(params) {
448
469
  const sellAmountsWei = [];
449
470
  for (let i = 0; i < wallets.length; i++) {
450
471
  if (sellAmounts && sellAmounts[i]) {
451
- sellAmountsWei.push(ethers.parseUnits(sellAmounts[i], tokenDecimals));
472
+ // ✅ 截断小数位数,避免超过代币精度导致 parseUnits 报错
473
+ const truncatedAmount = truncateDecimals(sellAmounts[i], tokenDecimals);
474
+ sellAmountsWei.push(ethers.parseUnits(truncatedAmount, tokenDecimals));
452
475
  }
453
476
  else if (sellPercentages && sellPercentages[i]) {
454
477
  const pct = Math.min(100, Math.max(0, sellPercentages[i]));
@@ -300,15 +300,9 @@ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, re
300
300
  }
301
301
  }
302
302
  else {
303
- // ERC20 购买:检查代币余额
304
303
  if (buyerBalance < buyerFundsWei) {
305
304
  throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(buyerFundsWei, quoteTokenDecimals)},实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
306
305
  }
307
- // ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas
308
- const buyerBnbBalance = await buyer.provider.getBalance(buyer.address);
309
- if (buyerBnbBalance < reserveGas) {
310
- throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGas)} ${nativeToken},实际 ${ethers.formatEther(buyerBnbBalance)} ${nativeToken}`);
311
- }
312
306
  }
313
307
  return { buyerFundsWei, buyerBalance };
314
308
  }
@@ -194,9 +194,7 @@ export async function flapBundleSwapMerkle(params) {
194
194
  portalGasCost: finalGasLimit * gasPrice,
195
195
  provider: chainContext.provider,
196
196
  chainContext,
197
- seller,
198
- useNativeToken, // ✅ 传递是否使用原生代币
199
- quoteTokenDecimals // ✅ 传递代币精度
197
+ seller
200
198
  })
201
199
  ]);
202
200
  // 构建交易请求
@@ -366,21 +364,13 @@ async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, slippage
366
364
  : (useNativeToken ? buyerBalance - reserveGas : buyerBalance);
367
365
  return { reserveGas, buyerBalance, buyerNeedTotal, maxBuyerValue };
368
366
  }
369
- async function validateBalances({ buyerNeed, buyerAddress, portalGasCost, provider, chainContext, seller, useNativeToken, quoteTokenDecimals = 18 }) {
367
+ async function validateBalances({ buyerNeed, buyerAddress, portalGasCost, provider, chainContext, seller }) {
368
+ const buyerBalance = buyerNeed.buyerBalance;
370
369
  const sellerBalance = await provider.getBalance(seller.address);
371
- // 修复:买方余额已在 calculateBuyerNeed 中检查过
372
- // 这里只需要检查:
373
- // 1. 使用原生代币时:buyerNeed.buyerBalance 已包含购买金额 + Gas
374
- // 2. 使用 ERC20 时:需要额外检查买方是否有足够 BNB 支付 Gas
375
- if (!useNativeToken) {
376
- // ERC20 购买时,买方仍需要 BNB 支付 Gas
377
- const buyerBnbBalance = await provider.getBalance(buyerAddress);
378
- const buyerGasCost = portalGasCost; // 买方交易的 Gas 费用
379
- if (buyerBnbBalance < buyerGasCost) {
380
- throw new Error(`买方 BNB 余额不足 (用于支付 Gas):\n` +
381
- ` - 需要: ${ethers.formatEther(buyerGasCost)} ${chainContext.nativeToken}\n` +
382
- ` - 实际: ${ethers.formatEther(buyerBnbBalance)} ${chainContext.nativeToken}`);
383
- }
370
+ if (buyerBalance < buyerNeed.buyerNeedTotal) {
371
+ throw new Error(`买方余额不足:\n` +
372
+ ` - 需要: ${ethers.formatEther(buyerNeed.buyerNeedTotal)} ${chainContext.nativeToken}\n` +
373
+ ` - 实际: ${ethers.formatEther(buyerBalance)} ${chainContext.nativeToken}`);
384
374
  }
385
375
  if (sellerBalance < portalGasCost) {
386
376
  throw new Error(`卖方余额不足: 需要 ${ethers.formatEther(portalGasCost)} ${chainContext.nativeToken} (Gas),实际 ${ethers.formatEther(sellerBalance)} ${chainContext.nativeToken}`);
@@ -53,8 +53,6 @@ export interface PancakeBundleBuyFirstSignParams {
53
53
  buyerFunds?: string;
54
54
  buyerFundsPercentage?: number;
55
55
  config: PancakeBuyFirstSignConfig;
56
- quoteToken?: string;
57
- quoteTokenDecimals?: number;
58
56
  }
59
57
  export interface PancakeBundleBuyFirstParams {
60
58
  buyerPrivateKey: string;
@@ -49,8 +49,6 @@ const PANCAKE_V2_ROUTER_ADDRESS = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
49
49
  const PANCAKE_V3_QUOTER_ADDRESS = '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997';
50
50
  const FLAT_FEE = ethers.parseEther('0.0001');
51
51
  const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
52
- const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
53
- const ERC20_BALANCE_OF_ABI = ['function balanceOf(address) view returns (uint256)'];
54
52
  const ERC20_ALLOWANCE_ABI = [
55
53
  'function allowance(address,address) view returns (uint256)',
56
54
  'function approve(address,uint256) returns (bool)',
@@ -58,11 +56,7 @@ const ERC20_ALLOWANCE_ABI = [
58
56
  ];
59
57
  const APPROVE_INTERFACE = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
60
58
  export async function pancakeBundleBuyFirstMerkle(params) {
61
- const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config, quoteToken, quoteTokenDecimals = 18 } = params;
62
- // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
63
- const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
64
- console.log('🔍 PancakeSwap BuyFirst - quoteToken:', quoteToken);
65
- console.log('🔍 PancakeSwap BuyFirst - useNativeToken:', useNativeToken);
59
+ const { buyerPrivateKey, sellerPrivateKey, tokenAddress, routeParams, buyerFunds, buyerFundsPercentage, config } = params;
66
60
  const context = createPancakeContext(config);
67
61
  const buyer = new Wallet(buyerPrivateKey, context.provider);
68
62
  const seller = new Wallet(sellerPrivateKey, context.provider);
@@ -71,11 +65,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
71
65
  buyer,
72
66
  buyerFunds,
73
67
  buyerFundsPercentage,
74
- reserveGas: config.reserveGasBNB,
75
- useNativeToken,
76
- quoteToken,
77
- quoteTokenDecimals,
78
- provider: context.provider
68
+ reserveGas: config.reserveGasBNB
79
69
  });
80
70
  const quoteResult = await quoteTokenOutput({
81
71
  routeParams,
@@ -89,8 +79,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
89
79
  sellAmountToken: quoteResult.quotedTokenOut,
90
80
  buyer,
91
81
  seller,
92
- tokenAddress,
93
- useNativeToken
82
+ tokenAddress
94
83
  });
95
84
  const quotedNative = await quoteSellerNative({
96
85
  provider: context.provider,
@@ -168,11 +157,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
168
157
  buyerBalance: buyerFundsInfo.buyerBalance,
169
158
  reserveGas: buyerFundsInfo.reserveGas,
170
159
  gasLimit: finalGasLimit,
171
- gasPrice,
172
- useNativeToken,
173
- quoteTokenDecimals,
174
- provider: context.provider,
175
- buyerAddress: buyer.address
160
+ gasPrice
176
161
  });
177
162
  const allTransactions = [];
178
163
  if (approvalTx)
@@ -185,9 +170,7 @@ export async function pancakeBundleBuyFirstMerkle(params) {
185
170
  metadata: {
186
171
  buyerAddress: buyer.address,
187
172
  sellerAddress: seller.address,
188
- buyAmount: useNativeToken
189
- ? ethers.formatEther(buyerFundsInfo.buyerFundsWei)
190
- : ethers.formatUnits(buyerFundsInfo.buyerFundsWei, quoteTokenDecimals),
173
+ buyAmount: ethers.formatEther(buyerFundsInfo.buyerFundsWei),
191
174
  sellAmount: quoteResult.quotedTokenOut.toString(),
192
175
  hasApproval: !!approvalTx,
193
176
  profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
@@ -202,31 +185,16 @@ function createPancakeContext(config) {
202
185
  });
203
186
  return { chainId, provider };
204
187
  }
205
- async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, reserveGas, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
188
+ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, reserveGas }) {
189
+ const buyerBalance = await buyer.provider.getBalance(buyer.address);
206
190
  const reserveGasWei = ethers.parseEther((reserveGas ?? 0.0005).toString());
207
- // ✅ 根据是否使用原生代币获取不同的余额
208
- let buyerBalance;
209
- if (useNativeToken) {
210
- buyerBalance = await buyer.provider.getBalance(buyer.address);
211
- }
212
- else {
213
- // ERC20 代币余额
214
- const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
215
- buyerBalance = await erc20.balanceOf(buyer.address);
216
- }
217
191
  let buyerFundsWei;
218
192
  if (buyerFunds !== undefined) {
219
- // 根据代币精度解析金额
220
- buyerFundsWei = useNativeToken
221
- ? ethers.parseEther(String(buyerFunds))
222
- : ethers.parseUnits(String(buyerFunds), quoteTokenDecimals);
193
+ buyerFundsWei = ethers.parseEther(String(buyerFunds));
223
194
  }
224
195
  else if (buyerFundsPercentage !== undefined) {
225
196
  const pct = Math.max(0, Math.min(100, buyerFundsPercentage));
226
- // 原生代币需要预留 Gas,ERC20 不需要
227
- const spendable = useNativeToken
228
- ? (buyerBalance > reserveGasWei ? buyerBalance - reserveGasWei : 0n)
229
- : buyerBalance;
197
+ const spendable = buyerBalance > reserveGasWei ? buyerBalance - reserveGasWei : 0n;
230
198
  buyerFundsWei = (spendable * BigInt(Math.floor(pct * 100))) / 10000n;
231
199
  }
232
200
  else {
@@ -235,23 +203,6 @@ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, re
235
203
  if (buyerFundsWei <= 0n) {
236
204
  throw new Error('buyerFunds 需要大于 0');
237
205
  }
238
- // ✅ 余额检查
239
- if (useNativeToken) {
240
- if (buyerBalance < buyerFundsWei + reserveGasWei) {
241
- throw new Error(`买方余额不足: 需要 ${ethers.formatEther(buyerFundsWei + reserveGasWei)} BNB,实际 ${ethers.formatEther(buyerBalance)} BNB`);
242
- }
243
- }
244
- else {
245
- // ERC20 购买:检查代币余额
246
- if (buyerBalance < buyerFundsWei) {
247
- throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(buyerFundsWei, quoteTokenDecimals)},实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
248
- }
249
- // ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas
250
- const buyerBnbBalance = await buyer.provider.getBalance(buyer.address);
251
- if (buyerBnbBalance < reserveGasWei) {
252
- throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGasWei)} BNB,实际 ${ethers.formatEther(buyerBnbBalance)} BNB`);
253
- }
254
- }
255
206
  return { buyerFundsWei, buyerBalance, reserveGas: reserveGasWei };
256
207
  }
257
208
  async function quoteTokenOutput({ routeParams, buyerFundsWei, provider }) {
@@ -343,31 +294,26 @@ async function ensureSellerApproval({ tokenAddress, seller, provider, decimals,
343
294
  type: txType
344
295
  });
345
296
  }
346
- async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountToken, buyer, seller, tokenAddress, useNativeToken = true }) {
297
+ async function buildRouteTransactions({ routeParams, buyerFundsWei, sellAmountToken, buyer, seller, tokenAddress }) {
347
298
  const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
348
299
  const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
349
300
  const deadline = Math.floor(Date.now() / 1000) + 600;
350
- // ✅ ERC20 购买时,value 只需要 FLAT_FEE
351
- const buyValue = useNativeToken ? buyerFundsWei + FLAT_FEE : FLAT_FEE;
352
301
  if (routeParams.routeType === 'v2') {
353
302
  const { v2Path } = routeParams;
354
303
  const reversePath = [...v2Path].reverse();
355
- const buyUnsigned = await proxyBuyer.swapV2.populateTransaction(buyerFundsWei, 0n, v2Path, buyer.address, deadline, { value: buyValue } // ✅ 使用动态 value
356
- );
304
+ const buyUnsigned = await proxyBuyer.swapV2.populateTransaction(buyerFundsWei, 0n, v2Path, buyer.address, deadline, { value: buyerFundsWei + FLAT_FEE });
357
305
  const sellUnsigned = await proxySeller.swapV2.populateTransaction(sellAmountToken, 0n, reversePath, seller.address, deadline, { value: FLAT_FEE });
358
306
  return { buyUnsigned, sellUnsigned };
359
307
  }
360
308
  if (routeParams.routeType === 'v3-single') {
361
309
  const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
362
- const buyUnsigned = await proxyBuyer.swapV3Single.populateTransaction(v3TokenIn, v3TokenOut, v3Fee, buyerFundsWei, 0n, buyer.address, { value: buyValue } // ✅ 使用动态 value
363
- );
310
+ const buyUnsigned = await proxyBuyer.swapV3Single.populateTransaction(v3TokenIn, v3TokenOut, v3Fee, buyerFundsWei, 0n, buyer.address, { value: buyerFundsWei + FLAT_FEE });
364
311
  const sellUnsigned = await proxySeller.swapV3Single.populateTransaction(v3TokenOut, v3TokenIn, v3Fee, sellAmountToken, 0n, seller.address, { value: FLAT_FEE });
365
312
  return { buyUnsigned, sellUnsigned };
366
313
  }
367
314
  const { v3LpAddresses, v3ExactTokenIn } = routeParams;
368
315
  const exactTokenOut = v3ExactTokenIn.toLowerCase() === WBNB_ADDRESS.toLowerCase() ? tokenAddress : WBNB_ADDRESS;
369
- const buyUnsigned = await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, v3ExactTokenIn, buyerFundsWei, 0n, buyer.address, { value: buyValue } // ✅ 使用动态 value
370
- );
316
+ const buyUnsigned = await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, v3ExactTokenIn, buyerFundsWei, 0n, buyer.address, { value: buyerFundsWei + FLAT_FEE });
371
317
  const sellUnsigned = await proxySeller.swapV3MultiHop.populateTransaction(v3LpAddresses, exactTokenOut, sellAmountToken, 0n, seller.address, { value: FLAT_FEE });
372
318
  return { buyUnsigned, sellUnsigned };
373
319
  }
@@ -425,41 +371,19 @@ async function buildProfitTransaction({ seller, profitAmount, profitNonce, gasPr
425
371
  type: txType
426
372
  });
427
373
  }
428
- async function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress }) {
429
- const gasCost = gasLimit * gasPrice;
374
+ function validateFinalBalances({ sameAddress, buyerFundsWei, buyerBalance, reserveGas, gasLimit, gasPrice }) {
430
375
  if (sameAddress) {
431
- // 同一地址:需要足够的余额支付两笔交易
432
- if (useNativeToken) {
433
- const requiredCombined = buyerFundsWei + FLAT_FEE * 2n + gasCost * 2n;
434
- if (buyerBalance < requiredCombined) {
435
- throw new Error(`账户余额不足:\n - 需要: ${ethers.formatEther(requiredCombined)} BNB(含两笔Gas与两笔手续费)\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
436
- }
437
- }
438
- else {
439
- // ERC20:检查代币余额 + BNB Gas 余额
440
- const requiredToken = buyerFundsWei + FLAT_FEE * 2n;
441
- if (buyerBalance < requiredToken) {
442
- throw new Error(`账户代币余额不足:\n - 需要: ${ethers.formatUnits(requiredToken, quoteTokenDecimals)}\n - 实际: ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
443
- }
444
- // 检查 BNB Gas
445
- if (provider && buyerAddress) {
446
- const bnbBalance = await provider.getBalance(buyerAddress);
447
- const requiredGas = gasCost * 2n;
448
- if (bnbBalance < requiredGas) {
449
- throw new Error(`账户 BNB 余额不足 (用于支付 Gas):\n - 需要: ${ethers.formatEther(requiredGas)} BNB\n - 实际: ${ethers.formatEther(bnbBalance)} BNB`);
450
- }
451
- }
376
+ const gasCost = gasLimit * gasPrice;
377
+ const requiredCombined = buyerFundsWei + FLAT_FEE * 2n + gasCost * 2n;
378
+ if (buyerBalance < requiredCombined) {
379
+ throw new Error(`账户余额不足:\n - 需要: ${ethers.formatEther(requiredCombined)} BNB(含两笔Gas与两笔手续费)\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
452
380
  }
453
381
  return;
454
382
  }
455
- // 不同地址
456
- if (useNativeToken) {
457
- const requiredBuyer = buyerFundsWei + FLAT_FEE + reserveGas;
458
- if (buyerBalance < requiredBuyer) {
459
- throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(requiredBuyer)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
460
- }
383
+ const requiredBuyer = buyerFundsWei + FLAT_FEE + reserveGas;
384
+ if (buyerBalance < requiredBuyer) {
385
+ throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(requiredBuyer)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
461
386
  }
462
- // ERC20 余额已在 calculateBuyerFunds 中检查过
463
387
  }
464
388
  function countTruthy(values) {
465
389
  return values.filter(Boolean).length;
@@ -48,8 +48,6 @@ export interface PancakeBundleSwapSignParams {
48
48
  routeParams: RouteParams;
49
49
  slippageTolerance?: number;
50
50
  config: PancakeSwapSignConfig;
51
- quoteToken?: string;
52
- quoteTokenDecimals?: number;
53
51
  }
54
52
  export interface PancakeBundleSwapParams {
55
53
  sellerPrivateKey: string;
@@ -75,6 +73,5 @@ export type PancakeSwapResult = {
75
73
  };
76
74
  /**
77
75
  * PancakeSwap捆绑换手(V2/V3通用)
78
- * ✅ 支持 quoteToken:传入 USDT 等地址时,卖出得到该代币,买入使用该代币
79
76
  */
80
77
  export declare function pancakeBundleSwapMerkle(params: PancakeBundleSwapSignParams): Promise<PancakeSwapResult>;
@@ -69,25 +69,21 @@ async function quoteSellOutput({ routeParams, sellAmountWei, provider }) {
69
69
  }
70
70
  throw new Error('V3 多跳需要提供 v2Path 用于价格预估');
71
71
  }
72
- async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress, useNativeToken = true }) {
72
+ async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB, buyer, seller, tokenAddress }) {
73
73
  const proxySeller = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, seller);
74
74
  const proxyBuyer = new Contract(PANCAKE_PROXY_ADDRESS, PANCAKE_PROXY_ABI, buyer);
75
75
  const deadline = Math.floor(Date.now() / 1000) + 600;
76
- // ✅ ERC20 购买时,value 只需要 FLAT_FEE
77
- const buyValue = useNativeToken ? buyAmountBNB + FLAT_FEE : FLAT_FEE;
78
76
  if (routeParams.routeType === 'v2') {
79
77
  const { v2Path } = routeParams;
80
78
  const reversePath = [...v2Path].reverse();
81
79
  const sellUnsigned = await proxySeller.swapV2.populateTransaction(sellAmountWei, 0n, v2Path, seller.address, deadline, { value: FLAT_FEE });
82
- const buyUnsigned = await proxyBuyer.swapV2.populateTransaction(buyAmountBNB, 0n, reversePath, buyer.address, deadline, { value: buyValue } // ✅ 使用动态 value
83
- );
80
+ const buyUnsigned = await proxyBuyer.swapV2.populateTransaction(buyAmountBNB, 0n, reversePath, buyer.address, deadline, { value: buyAmountBNB + FLAT_FEE });
84
81
  return { sellUnsigned, buyUnsigned };
85
82
  }
86
83
  if (routeParams.routeType === 'v3-single') {
87
84
  const { v3TokenIn, v3TokenOut, v3Fee } = routeParams;
88
85
  const sellUnsigned = await proxySeller.swapV3Single.populateTransaction(v3TokenIn, v3TokenOut, v3Fee, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
89
- const buyUnsigned = await proxyBuyer.swapV3Single.populateTransaction(v3TokenOut, v3TokenIn, v3Fee, buyAmountBNB, 0n, buyer.address, { value: buyValue } // ✅ 使用动态 value
90
- );
86
+ const buyUnsigned = await proxyBuyer.swapV3Single.populateTransaction(v3TokenOut, v3TokenIn, v3Fee, buyAmountBNB, 0n, buyer.address, { value: buyAmountBNB + FLAT_FEE });
91
87
  return { sellUnsigned, buyUnsigned };
92
88
  }
93
89
  const { v3LpAddresses, v3ExactTokenIn } = routeParams;
@@ -95,38 +91,18 @@ async function buildSwapTransactions({ routeParams, sellAmountWei, buyAmountBNB,
95
91
  ? tokenAddress
96
92
  : WBNB_ADDRESS;
97
93
  const sellUnsigned = await proxySeller.swapV3MultiHop.populateTransaction(v3LpAddresses, v3ExactTokenIn, sellAmountWei, 0n, seller.address, { value: FLAT_FEE });
98
- const buyUnsigned = await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, exactTokenOut, buyAmountBNB, 0n, buyer.address, { value: buyValue } // ✅ 使用动态 value
99
- );
94
+ const buyUnsigned = await proxyBuyer.swapV3MultiHop.populateTransaction(v3LpAddresses, exactTokenOut, buyAmountBNB, 0n, buyer.address, { value: buyAmountBNB + FLAT_FEE });
100
95
  return { sellUnsigned, buyUnsigned };
101
96
  }
102
- async function calculateBuyerBudget({ buyer, quotedBNBOut, reserveGasBNB, slippageTolerance, useNativeToken = true, quoteToken, quoteTokenDecimals = 18, provider }) {
97
+ async function calculateBuyerBudget({ buyer, quotedBNBOut, reserveGasBNB, slippageTolerance }) {
98
+ const buyerBalance = await buyer.provider.getBalance(buyer.address);
103
99
  const reserveGas = ethers.parseEther((reserveGasBNB ?? 0.0005).toString());
104
100
  const buyAmountBNB = applySlippage(quotedBNBOut, slippageTolerance);
105
- // 根据是否使用原生代币获取不同的余额
106
- let buyerBalance;
107
- if (useNativeToken) {
108
- buyerBalance = await buyer.provider.getBalance(buyer.address);
109
- const requiredBalance = buyAmountBNB + FLAT_FEE + reserveGas;
110
- if (buyerBalance < requiredBalance) {
111
- throw new Error(`买方余额不足: 需要 ${ethers.formatEther(requiredBalance)} BNB, 实际 ${ethers.formatEther(buyerBalance)} BNB`);
112
- }
113
- return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB, useNativeToken };
114
- }
115
- else {
116
- // ERC20 代币余额
117
- const erc20 = new Contract(quoteToken, ERC20_BALANCE_OF_ABI, provider || buyer.provider);
118
- buyerBalance = await erc20.balanceOf(buyer.address);
119
- const requiredBalance = buyAmountBNB + FLAT_FEE; // ERC20 不需要预留 Gas 在代币余额中
120
- if (buyerBalance < requiredBalance) {
121
- throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(requiredBalance, quoteTokenDecimals)}, 实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
122
- }
123
- // ✅ ERC20 购买时,还需要检查买方是否有足够 BNB 支付 Gas
124
- const buyerBnbBalance = await buyer.provider.getBalance(buyer.address);
125
- if (buyerBnbBalance < reserveGas) {
126
- throw new Error(`买方 BNB 余额不足 (用于支付 Gas): 需要 ${ethers.formatEther(reserveGas)} BNB, 实际 ${ethers.formatEther(buyerBnbBalance)} BNB`);
127
- }
128
- return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB, useNativeToken };
101
+ const requiredBalance = buyAmountBNB + FLAT_FEE + reserveGas;
102
+ if (buyerBalance < requiredBalance) {
103
+ throw new Error(`买方余额不足: 需要 ${ethers.formatEther(requiredBalance)} BNB, 实际 ${ethers.formatEther(buyerBalance)} BNB`);
129
104
  }
105
+ return { buyerBalance, reserveGas, requiredBalance, buyAmountBNB };
130
106
  }
131
107
  function applySlippage(amount, tolerancePercent = 0.5) {
132
108
  if (amount === 0n) {
@@ -175,41 +151,19 @@ function calculateProfitAmount(estimatedBNBOut) {
175
151
  }
176
152
  return (estimatedBNBOut * BigInt(PROFIT_CONFIG.RATE_BPS)) / 10000n;
177
153
  }
178
- async function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice, useNativeToken = true, quoteTokenDecimals = 18, provider, buyerAddress }) {
179
- const gasCost = gasLimit * gasPrice;
154
+ function validateFinalBalances({ sameAddress, buyerBalance, buyAmountBNB, reserveGas, gasLimit, gasPrice }) {
180
155
  if (sameAddress) {
181
- // 同一地址:需要足够的余额支付两笔交易
182
- if (useNativeToken) {
183
- const requiredCombined = buyAmountBNB + FLAT_FEE * 2n + gasCost * 2n;
184
- if (buyerBalance < requiredCombined) {
185
- throw new Error(`账户余额不足:\n - 需要: ${ethers.formatEther(requiredCombined)} BNB(含两笔Gas与两笔手续费)\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
186
- }
187
- }
188
- else {
189
- // ERC20:检查代币余额 + BNB Gas 余额
190
- const requiredToken = buyAmountBNB + FLAT_FEE * 2n;
191
- if (buyerBalance < requiredToken) {
192
- throw new Error(`账户代币余额不足:\n - 需要: ${ethers.formatUnits(requiredToken, quoteTokenDecimals)}\n - 实际: ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
193
- }
194
- // 检查 BNB Gas
195
- if (provider && buyerAddress) {
196
- const bnbBalance = await provider.getBalance(buyerAddress);
197
- const requiredGas = gasCost * 2n;
198
- if (bnbBalance < requiredGas) {
199
- throw new Error(`账户 BNB 余额不足 (用于支付 Gas):\n - 需要: ${ethers.formatEther(requiredGas)} BNB\n - 实际: ${ethers.formatEther(bnbBalance)} BNB`);
200
- }
201
- }
156
+ const gasCost = gasLimit * gasPrice;
157
+ const requiredCombined = buyAmountBNB + FLAT_FEE * 2n + gasCost * 2n;
158
+ if (buyerBalance < requiredCombined) {
159
+ throw new Error(`账户余额不足:\n - 需要: ${ethers.formatEther(requiredCombined)} BNB(含两笔Gas与两笔手续费)\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
202
160
  }
203
161
  return;
204
162
  }
205
- // 不同地址
206
- if (useNativeToken) {
207
- const requiredBuyer = buyAmountBNB + FLAT_FEE + reserveGas;
208
- if (buyerBalance < requiredBuyer) {
209
- throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(requiredBuyer)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
210
- }
163
+ const requiredBuyer = buyAmountBNB + FLAT_FEE + reserveGas;
164
+ if (buyerBalance < requiredBuyer) {
165
+ throw new Error(`买方余额不足:\n - 需要: ${ethers.formatEther(requiredBuyer)} BNB\n - 实际: ${ethers.formatEther(buyerBalance)} BNB`);
211
166
  }
212
- // ERC20 余额已在 calculateBuyerBudget 中检查过
213
167
  }
214
168
  function countTruthy(values) {
215
169
  return values.filter(Boolean).length;
@@ -316,18 +270,11 @@ const ERC20_ALLOWANCE_ABI = [
316
270
  'function decimals() view returns (uint8)'
317
271
  ];
318
272
  const APPROVE_INTERFACE = new ethers.Interface(['function approve(address,uint256) returns (bool)']);
319
- const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
320
- const ERC20_BALANCE_OF_ABI = ['function balanceOf(address) view returns (uint256)'];
321
273
  /**
322
274
  * PancakeSwap捆绑换手(V2/V3通用)
323
- * ✅ 支持 quoteToken:传入 USDT 等地址时,卖出得到该代币,买入使用该代币
324
275
  */
325
276
  export async function pancakeBundleSwapMerkle(params) {
326
- const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, routeParams, slippageTolerance = 0.5, config, quoteToken, quoteTokenDecimals = 18 } = params;
327
- // ✅ 判断是否使用原生代币(BNB)或 ERC20 代币(如 USDT)
328
- const useNativeToken = !quoteToken || quoteToken === ZERO_ADDRESS;
329
- console.log('🔍 PancakeSwap Swap - quoteToken:', quoteToken);
330
- console.log('🔍 PancakeSwap Swap - useNativeToken:', useNativeToken);
277
+ const { sellerPrivateKey, sellAmount, sellPercentage, buyerPrivateKey, tokenAddress, routeParams, slippageTolerance = 0.5, config } = params;
331
278
  const context = createPancakeContext(config);
332
279
  const seller = new Wallet(sellerPrivateKey, context.provider);
333
280
  const buyer = new Wallet(buyerPrivateKey, context.provider);
@@ -350,11 +297,7 @@ export async function pancakeBundleSwapMerkle(params) {
350
297
  buyer,
351
298
  quotedBNBOut: quoteResult.estimatedBNBOut,
352
299
  reserveGasBNB: config.reserveGasBNB,
353
- slippageTolerance,
354
- useNativeToken,
355
- quoteToken,
356
- quoteTokenDecimals,
357
- provider: context.provider
300
+ slippageTolerance
358
301
  });
359
302
  const swapUnsigned = await buildSwapTransactions({
360
303
  routeParams,
@@ -362,8 +305,7 @@ export async function pancakeBundleSwapMerkle(params) {
362
305
  buyAmountBNB: buyerBudget.buyAmountBNB,
363
306
  buyer,
364
307
  seller,
365
- tokenAddress,
366
- useNativeToken
308
+ tokenAddress
367
309
  });
368
310
  const finalGasLimit = getGasLimit(config);
369
311
  const gasPrice = await getGasPrice(context.provider, config);
@@ -411,11 +353,7 @@ export async function pancakeBundleSwapMerkle(params) {
411
353
  buyAmountBNB: buyerBudget.buyAmountBNB,
412
354
  reserveGas: buyerBudget.reserveGas,
413
355
  gasLimit: finalGasLimit,
414
- gasPrice,
415
- useNativeToken,
416
- quoteTokenDecimals,
417
- provider: context.provider,
418
- buyerAddress: buyer.address
356
+ gasPrice
419
357
  });
420
358
  const signedTransactions = [];
421
359
  if (approvalTx)
@@ -429,9 +367,7 @@ export async function pancakeBundleSwapMerkle(params) {
429
367
  sellerAddress: seller.address,
430
368
  buyerAddress: buyer.address,
431
369
  sellAmount: ethers.formatUnits(sellAmountWei, decimals),
432
- buyAmount: useNativeToken
433
- ? ethers.formatEther(buyerBudget.buyAmountBNB)
434
- : ethers.formatUnits(buyerBudget.buyAmountBNB, quoteTokenDecimals),
370
+ buyAmount: ethers.formatEther(buyerBudget.buyAmountBNB),
435
371
  hasApproval: !!approvalTx,
436
372
  profitAmount: profitAmount > 0n ? ethers.formatEther(profitAmount) : undefined
437
373
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.3.56",
3
+ "version": "1.3.57",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,16 +0,0 @@
1
- /**
2
- * ECDH + AES-GCM 加密工具(浏览器兼容)
3
- * 用于将签名交易用服务器公钥加密
4
- */
5
- /**
6
- * 用服务器公钥加密签名交易(ECDH + AES-GCM)
7
- *
8
- * @param signedTransactions 签名后的交易数组
9
- * @param publicKeyBase64 服务器提供的公钥(Base64 格式)
10
- * @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
11
- */
12
- export declare function encryptWithPublicKey(signedTransactions: string[], publicKeyBase64: string): Promise<string>;
13
- /**
14
- * 验证公钥格式(Base64)
15
- */
16
- export declare function validatePublicKey(publicKeyBase64: string): boolean;
@@ -1,146 +0,0 @@
1
- /**
2
- * ECDH + AES-GCM 加密工具(浏览器兼容)
3
- * 用于将签名交易用服务器公钥加密
4
- */
5
- /**
6
- * 获取全局 crypto 对象(最简单直接的方式)
7
- */
8
- function getCryptoAPI() {
9
- // 尝试所有可能的全局对象,优先浏览器环境
10
- const cryptoObj = (typeof window !== 'undefined' && window.crypto) ||
11
- (typeof self !== 'undefined' && self.crypto) ||
12
- (typeof global !== 'undefined' && global.crypto) ||
13
- (typeof globalThis !== 'undefined' && globalThis.crypto);
14
- if (!cryptoObj) {
15
- const env = typeof window !== 'undefined' ? 'Browser' : 'Node.js';
16
- const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
17
- throw new Error(`❌ Crypto API 不可用。环境: ${env}, 协议: ${protocol}. ` +
18
- '请确保在 HTTPS 或 localhost 下运行');
19
- }
20
- return cryptoObj;
21
- }
22
- /**
23
- * 获取 SubtleCrypto(用于加密操作)
24
- */
25
- function getSubtleCrypto() {
26
- const crypto = getCryptoAPI();
27
- if (!crypto.subtle) {
28
- const protocol = typeof location !== 'undefined' ? location.protocol : 'unknown';
29
- const hostname = typeof location !== 'undefined' ? location.hostname : 'unknown';
30
- throw new Error(`❌ SubtleCrypto API 不可用。协议: ${protocol}, 主机: ${hostname}. ` +
31
- '请确保:1) 使用 HTTPS (或 localhost);2) 浏览器支持 Web Crypto API;' +
32
- '3) 不在无痕/隐私浏览模式下');
33
- }
34
- return crypto.subtle;
35
- }
36
- /**
37
- * Base64 转 ArrayBuffer(优先使用浏览器 API)
38
- */
39
- function base64ToArrayBuffer(base64) {
40
- // 浏览器环境(优先)
41
- if (typeof atob !== 'undefined') {
42
- const binaryString = atob(base64);
43
- const bytes = new Uint8Array(binaryString.length);
44
- for (let i = 0; i < binaryString.length; i++) {
45
- bytes[i] = binaryString.charCodeAt(i);
46
- }
47
- return bytes.buffer;
48
- }
49
- // Node.js 环境(fallback)
50
- if (typeof Buffer !== 'undefined') {
51
- return Buffer.from(base64, 'base64').buffer;
52
- }
53
- throw new Error('❌ Base64 解码不可用');
54
- }
55
- /**
56
- * ArrayBuffer 转 Base64(优先使用浏览器 API)
57
- */
58
- function arrayBufferToBase64(buffer) {
59
- // 浏览器环境(优先)
60
- if (typeof btoa !== 'undefined') {
61
- const bytes = new Uint8Array(buffer);
62
- let binary = '';
63
- for (let i = 0; i < bytes.length; i++) {
64
- binary += String.fromCharCode(bytes[i]);
65
- }
66
- return btoa(binary);
67
- }
68
- // Node.js 环境(fallback)
69
- if (typeof Buffer !== 'undefined') {
70
- return Buffer.from(buffer).toString('base64');
71
- }
72
- throw new Error('❌ Base64 编码不可用');
73
- }
74
- /**
75
- * 生成随机 Hex 字符串
76
- */
77
- function randomHex(length) {
78
- const crypto = getCryptoAPI();
79
- const array = new Uint8Array(length);
80
- crypto.getRandomValues(array);
81
- return Array.from(array)
82
- .map(b => b.toString(16).padStart(2, '0'))
83
- .join('');
84
- }
85
- /**
86
- * 用服务器公钥加密签名交易(ECDH + AES-GCM)
87
- *
88
- * @param signedTransactions 签名后的交易数组
89
- * @param publicKeyBase64 服务器提供的公钥(Base64 格式)
90
- * @returns JSON 字符串 {e: 临时公钥, i: IV, d: 密文}
91
- */
92
- export async function encryptWithPublicKey(signedTransactions, publicKeyBase64) {
93
- try {
94
- // 0. 获取 SubtleCrypto 和 Crypto API
95
- const subtle = getSubtleCrypto();
96
- const crypto = getCryptoAPI();
97
- // 1. 准备数据
98
- const payload = {
99
- signedTransactions,
100
- timestamp: Date.now(),
101
- nonce: randomHex(8)
102
- };
103
- const plaintext = JSON.stringify(payload);
104
- // 2. 生成临时 ECDH 密钥对
105
- const ephemeralKeyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
106
- // 3. 导入服务器公钥
107
- const publicKeyBuffer = base64ToArrayBuffer(publicKeyBase64);
108
- const publicKey = await subtle.importKey('raw', publicKeyBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false, []);
109
- // 4. 派生共享密钥(AES-256)
110
- const sharedKey = await subtle.deriveKey({ name: 'ECDH', public: publicKey }, ephemeralKeyPair.privateKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']);
111
- // 5. AES-GCM 加密
112
- const iv = crypto.getRandomValues(new Uint8Array(12));
113
- const encrypted = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, new TextEncoder().encode(plaintext));
114
- // 6. 导出临时公钥
115
- const ephemeralPublicKeyRaw = await subtle.exportKey('raw', ephemeralKeyPair.publicKey);
116
- // 7. 返回加密包(JSON 格式)
117
- return JSON.stringify({
118
- e: arrayBufferToBase64(ephemeralPublicKeyRaw), // 临时公钥
119
- i: arrayBufferToBase64(iv.buffer), // IV
120
- d: arrayBufferToBase64(encrypted) // 密文
121
- });
122
- }
123
- catch (error) {
124
- throw new Error(`加密失败: ${error?.message || String(error)}`);
125
- }
126
- }
127
- /**
128
- * 验证公钥格式(Base64)
129
- */
130
- export function validatePublicKey(publicKeyBase64) {
131
- try {
132
- if (!publicKeyBase64)
133
- return false;
134
- // Base64 字符集验证
135
- if (!/^[A-Za-z0-9+/=]+$/.test(publicKeyBase64))
136
- return false;
137
- // ECDH P-256 公钥固定长度 65 字节(未压缩)
138
- // Base64 编码后约 88 字符
139
- if (publicKeyBase64.length < 80 || publicKeyBase64.length > 100)
140
- return false;
141
- return true;
142
- }
143
- catch {
144
- return false;
145
- }
146
- }