four-flap-meme-sdk 1.3.54 → 1.3.55

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.
@@ -22,7 +22,7 @@ export declare const DIRECT_ROUTERS: {
22
22
  readonly WMON: "0x3bd359c1119da7da1d913d1c4d2b7c461115433a";
23
23
  };
24
24
  readonly XLAYER: {
25
- readonly POTATOSWAP_V2: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
25
+ readonly POTATOSWAP_V2: "0x881fb2f98c13d521009464e7d1cbf16e1b394e8e";
26
26
  readonly POTATOSWAP_V3: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
27
27
  readonly V3_ROUTER: "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
28
28
  readonly V3_FACTORY: "0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5";
@@ -33,12 +33,13 @@ export const DIRECT_ROUTERS = {
33
33
  // Wrapped Native
34
34
  WMON: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a',
35
35
  },
36
- // ✅ 新增 XLayer (PotatoSwap)
36
+ // ✅ XLayer (PotatoSwap)
37
37
  XLAYER: {
38
- // PotatoSwap - SwapRouter02 同时支持 V2 V3
39
- POTATOSWAP_V2: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V2+V3)
40
- POTATOSWAP_V3: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V2+V3)
41
- // V3 专用 Router(可选)
38
+ // PotatoSwap V2 Router - 标准 Uniswap V2 风格,支持 swapExactTokensForETHSupportingFeeOnTransferTokens
39
+ POTATOSWAP_V2: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // 真正的 V2 Router
40
+ // PotatoSwap SwapRouter02 - V3 风格,支持 multicall
41
+ POTATOSWAP_V3: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V3 风格)
42
+ // V3 专用 Router
42
43
  V3_ROUTER: '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41',
43
44
  V3_FACTORY: '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5',
44
45
  // Wrapped Native
@@ -82,14 +83,14 @@ const V2_ROUTER_ABI = [
82
83
  /**
83
84
  * SwapRouter02 的 V2 方法 ABI (PotatoSwap)
84
85
  *
85
- * 重要:SwapRouter02 的 V2 方法只有:
86
+ * 重要:根据实际 ABI,SwapRouter02 的 V2 方法只有:
86
87
  * - swapExactTokensForTokens(amountIn, amountOutMin, path[], to)
87
88
  * - swapTokensForExactTokens(amountOut, amountInMax, path[], to)
88
89
  *
89
90
  * 没有 swapExactETHForTokens / swapExactTokensForETH!
90
- * 需要手动处理 ETH/WETH 转换:
91
- * - 买入(ETH->Token): wrapETH + swapExactTokensForTokens
92
- * - 卖出(Token->ETH): swapExactTokensForTokens(to=ADDRESS_THIS) + unwrapWETH9
91
+ * 必须手动处理 ETH/WETH 转换:
92
+ * - 买入(ETHToken): wrapETH + swapExactTokensForTokens,通过 multicall 组合
93
+ * - 卖出(TokenETH): swapExactTokensForTokens(to=ADDRESS_THIS) + unwrapWETH9,通过 multicall 组合
93
94
  */
94
95
  const SWAP_ROUTER02_V2_ABI = [
95
96
  // V2 交换方法(只有 token-to-token)
@@ -97,15 +98,15 @@ const SWAP_ROUTER02_V2_ABI = [
97
98
  'function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, address[] calldata path, address to) external payable returns (uint256 amountIn)',
98
99
  // Multicall - 多种重载
99
100
  'function multicall(uint256 deadline, bytes[] calldata data) external payable returns (bytes[] memory results)',
101
+ 'function multicall(bytes32 previousBlockhash, bytes[] calldata data) external payable returns (bytes[] memory results)',
100
102
  'function multicall(bytes[] calldata data) external payable returns (bytes[] memory results)',
101
103
  // 辅助方法 - ETH/WETH 转换
102
104
  'function wrapETH(uint256 value) external payable',
103
105
  'function unwrapWETH9(uint256 amountMinimum, address recipient) external payable',
104
106
  'function unwrapWETH9(uint256 amountMinimum) external payable',
105
107
  'function refundETH() external payable',
106
- // pull 方法 - 从用户拉取代币到 Router
108
+ // 代币操作
107
109
  'function pull(address token, uint256 value) external payable',
108
- // sweepToken - 将 Router 中的代币发送给用户
109
110
  'function sweepToken(address token, uint256 amountMinimum, address recipient) external payable',
110
111
  'function sweepToken(address token, uint256 amountMinimum) external payable',
111
112
  ];
@@ -269,20 +270,26 @@ async function buildProfitTransaction(wallet, profitAmountWei, nonce, gasPrice,
269
270
  // V2 直接交易
270
271
  // ============================================================================
271
272
  /**
272
- * 判断是否是 SwapRouter02 (PotatoSwap, PancakeSwap V3 等)
273
- * SwapRouter02 的 V2 方法签名不同,需要特殊处理
273
+ * 判断是否是 SwapRouter02 ( XLayer PotatoSwap)
274
+ *
275
+ * 只有 XLayer PotatoSwap 使用 SwapRouter02 进行 V2 交易
276
+ * BSC/Monad 的 V2 交易使用传统的 V2 Router(带 deadline 参数)
277
+ *
278
+ * SwapRouter02 的 V2 方法签名不同:
279
+ * - swapExactETHForTokens(amountOutMin, path[], to) - 没有 deadline
280
+ * - swapExactTokensForETH(amountIn, amountOutMin, path[], to) - 没有 deadline
274
281
  */
275
282
  function isSwapRouter02(chain, routerAddress) {
276
283
  const chainUpper = chain.toUpperCase();
277
284
  const addrLower = routerAddress.toLowerCase();
278
- // XLayer PotatoSwap SwapRouter02
279
- if (chainUpper === 'XLAYER' && addrLower === DIRECT_ROUTERS.XLAYER.POTATOSWAP_V2.toLowerCase()) {
280
- return true;
281
- }
282
- // BSC PancakeSwap V3 SwapRouter02
283
- if (chainUpper === 'BSC' && addrLower === DIRECT_ROUTERS.BSC.PANCAKESWAP_V3.toLowerCase()) {
285
+ // ✅ 只有 XLayer PotatoSwap SwapRouter02 (V3 风格) 走这个逻辑
286
+ // 注意:POTATOSWAP_V2 现在是真正的 V2 Router,不需要 SwapRouter02 逻辑
287
+ if (chainUpper === 'XLAYER' && addrLower === DIRECT_ROUTERS.XLAYER.POTATOSWAP_V3.toLowerCase()) {
284
288
  return true;
285
289
  }
290
+ // ❌ XLayer POTATOSWAP_V2 (0x881fb...) 是标准 V2 Router,不走 SwapRouter02 逻辑
291
+ // ❌ BSC V2 交易使用传统 PancakeSwap V2 Router,不走 SwapRouter02 逻辑
292
+ // ❌ Monad V2 交易使用传统 V2 Router,不走 SwapRouter02 逻辑
286
293
  return false;
287
294
  }
288
295
  /**
@@ -326,42 +333,39 @@ export async function directV2BatchBuy(params) {
326
333
  let txData;
327
334
  let txValue;
328
335
  if (useSwapRouter02) {
329
- // ✅ SwapRouter02: 只有 swapExactTokensForTokens,需要手动处理 ETH wrap
330
- // 文档:没有 swapExactETHForTokens / swapExactTokensForETH
331
- const multicallData = [];
336
+ // ✅ SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
337
+ // ABI 中没有 swapExactETHForTokens,只有 swapExactTokensForTokens
332
338
  if (useNative) {
333
- // ETH -> 代币:
334
- // 1. wrapETH(msg.value) - 将 ETH 转为 WETH
335
- // 2. swapExactTokensForTokens(WETH -> Token)
336
- const wrapData = routerIface.encodeFunctionData('wrapETH', [amountWei]);
337
- multicallData.push(wrapData);
339
+ // ETH -> 代币:直接用 swapExactTokensForTokens(参考之前成功的交易)
340
+ // 之前成功的交易只用了一个 swapExactTokensForTokens,没有 wrapETH
341
+ // SwapRouter02 会自动处理 msg.value ETH
338
342
  const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
339
343
  amountWei,
340
344
  0n, // amountOutMin
341
345
  path, // path: [WETH, ..., tokenOut]
342
346
  wallet.address, // to
343
347
  ]);
344
- multicallData.push(swapData);
345
- // refundETH - 退回多余的 ETH(如果有)
346
- const refundData = routerIface.encodeFunctionData('refundETH', []);
347
- multicallData.push(refundData);
348
+ // 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
349
+ txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
350
+ deadline,
351
+ [swapData],
352
+ ]);
353
+ txValue = amountWei;
348
354
  }
349
355
  else {
350
- // 代币 -> 代币:直接 swapExactTokensForTokens
356
+ // 代币 -> 代币:也用 multicall 包装
351
357
  const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
352
358
  amountWei,
353
359
  0n, // amountOutMin
354
360
  path,
355
361
  wallet.address, // to
356
362
  ]);
357
- multicallData.push(swapData);
363
+ txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
364
+ deadline,
365
+ [swapData],
366
+ ]);
367
+ txValue = 0n;
358
368
  }
359
- // 使用 multicall 包装,传递 deadline
360
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
361
- deadline,
362
- multicallData,
363
- ]);
364
- txValue = useNative ? amountWei : 0n;
365
369
  }
366
370
  else if (useNative) {
367
371
  // 传统 V2 Router: 原生币 → Token
@@ -417,15 +421,28 @@ export async function directV2BatchBuy(params) {
417
421
  * V2 批量卖出(直接调用 Router)
418
422
  */
419
423
  export async function directV2BatchSell(params) {
420
- const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals = 18, routerAddress, quoteToken, config, } = params;
424
+ const { chain, privateKeys, sellPercentages, sellAmounts, tokenAddress, tokenDecimals: inputDecimals, routerAddress, quoteToken, config, } = params;
421
425
  const chainId = config.chainId || CHAIN_IDS[chain.toUpperCase()] || 56;
422
426
  const provider = new JsonRpcProvider(config.rpcUrl, { chainId, name: chain });
423
427
  const useNativeOutput = isNativeToken(quoteToken);
424
428
  const wrappedNative = getWrappedNative(chain);
425
429
  // 创建钱包
426
430
  const wallets = privateKeys.map(pk => new Wallet(pk, provider));
427
- // 获取代币余额
431
+ // 获取代币合约
428
432
  const tokenContract = new Contract(tokenAddress, ERC20_ABI, provider);
433
+ // ✅ 自动获取代币精度(如果未提供)
434
+ let tokenDecimals = inputDecimals;
435
+ if (tokenDecimals === undefined) {
436
+ try {
437
+ tokenDecimals = Number(await tokenContract.decimals());
438
+ console.log(`📊 自动获取代币精度: ${tokenDecimals}`);
439
+ }
440
+ catch {
441
+ tokenDecimals = 18; // 默认 18
442
+ console.log(`⚠️ 无法获取代币精度,使用默认值: ${tokenDecimals}`);
443
+ }
444
+ }
445
+ // 获取代币余额
429
446
  const balances = await Promise.all(wallets.map(w => tokenContract.balanceOf(w.address)));
430
447
  // 计算卖出数量
431
448
  const sellAmountsWei = [];
@@ -488,46 +505,54 @@ export async function directV2BatchSell(params) {
488
505
  // 卖出交易
489
506
  let txData;
490
507
  if (useSwapRouter02) {
491
- // ✅ SwapRouter02: 只有 swapExactTokensForTokens,需要手动处理 ETH unwrap
492
- const router02Iface = new ethers.Interface(SWAP_ROUTER02_V2_ABI);
493
- const multicallData = [];
494
- // SwapRouter02 特殊地址约定:
495
- // address(1) = ADDRESS_THIS = Router 合约自己
496
- // address(2) = MSG_SENDER = 调用者
497
- const ADDRESS_THIS = '0x0000000000000000000000000000000000000001'; // Router 自己
508
+ // ✅ SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
509
+ //
510
+ // 重要:SwapRouter02 V2 swapExactTokensForTokens 直接把代币发给 to 地址
511
+ // 如果需要 ETH,把 WETH 发给 Router,然后调用 unwrapWETH9
512
+ //
513
+ // 根据合约源码,address(2) = ADDRESS_THIS = Router 合约自己
514
+ const ADDRESS_THIS = '0x0000000000000000000000000000000000000002';
498
515
  if (useNativeOutput) {
499
516
  // 代币 -> ETH:
500
- // 1. swapExactTokensForTokens(token -> WETH, to = ADDRESS_THIS)
501
- // 2. unwrapWETH9(WETH -> ETH, to = 用户)
502
- const swapData = router02Iface.encodeFunctionData('swapExactTokensForTokens', [
517
+ // 方案1swapExactTokensForTokens(to=Router) + unwrapWETH9
518
+ // 方案2:直接发 WETH 给用户(用户需要手动 unwrap)
519
+ //
520
+ // 使用方案1:发送到 Router 地址(真实地址,不是 address(2))
521
+ const routerRealAddress = routerAddress; // SwapRouter02 的真实地址
522
+ const multicallData = [];
523
+ // 1. swapExactTokensForTokens - 从用户转代币,换成 WETH 发到 Router 真实地址
524
+ const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
503
525
  sellAmount,
504
526
  0n, // amountOutMin
505
527
  path, // path: [token, ..., WETH]
506
- ADDRESS_THIS, // to = Router 自己,WETH 留在 Router 中
528
+ ADDRESS_THIS, // to = address(2),让 Router 接收 WETH
507
529
  ]);
508
530
  multicallData.push(swapData);
509
- // unwrap WETH -> ETH 发送给用户
510
- const unwrapData = router02Iface.encodeFunctionData('unwrapWETH9(uint256,address)', [
511
- 0n, // amountMinimum
531
+ // 2. unwrapWETH9 - 将 Router 中的 WETH 解包为 ETH 发送给用户
532
+ const unwrapData = routerIface.encodeFunctionData('unwrapWETH9(uint256,address)', [
533
+ 0n, // amountMinimum = 0,接受任意数量
512
534
  wallet.address, // recipient = 用户
513
535
  ]);
514
536
  multicallData.push(unwrapData);
537
+ // 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
538
+ txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
539
+ deadline,
540
+ multicallData,
541
+ ]);
515
542
  }
516
543
  else {
517
544
  // 代币 -> 代币:直接 swapExactTokensForTokens
518
- const swapData = router02Iface.encodeFunctionData('swapExactTokensForTokens', [
545
+ const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
519
546
  sellAmount,
520
547
  0n, // amountOutMin
521
548
  path,
522
- wallet.address, // to
549
+ wallet.address, // to = 用户
550
+ ]);
551
+ txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
552
+ deadline,
553
+ [swapData],
523
554
  ]);
524
- multicallData.push(swapData);
525
555
  }
526
- // 使用 multicall 包装,传递 deadline
527
- txData = router02Iface.encodeFunctionData('multicall(uint256,bytes[])', [
528
- deadline,
529
- multicallData,
530
- ]);
531
556
  }
532
557
  else if (useNativeOutput) {
533
558
  txData = routerIface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [
@@ -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,7 +53,8 @@ export declare const ADDRESSES: {
53
53
  readonly FlapPortal: "0xb30D8c4216E1f21F27444D2FfAee3ad577808678";
54
54
  readonly WOKB: "0xe538905cf8410324e03a5a23c1c177a474d59b2b";
55
55
  readonly Multicall3: "0xca11bde05977b3631167028862be2a173976ca11";
56
- readonly PotatoSwapV2Router: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
56
+ readonly PotatoSwapV2Router: "0x881fb2f98c13d521009464e7d1cbf16e1b394e8e";
57
+ readonly PotatoSwapSwapRouter02: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
57
58
  readonly PotatoSwapV3Router: "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
58
59
  readonly PotatoSwapV3Factory: "0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5";
59
60
  readonly USDT: "0x1e4a5963abfd975d8c9021ce480b42188849d41d";
@@ -52,7 +52,8 @@ export const ADDRESSES = {
52
52
  // Multicall3 合约
53
53
  Multicall3: '0xca11bde05977b3631167028862be2a173976ca11',
54
54
  // PotatoSwap DEX 合约
55
- PotatoSwapV2Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (支持 V2+V3)
55
+ PotatoSwapV2Router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // V2 Router (标准 Uniswap V2 风格)
56
+ PotatoSwapSwapRouter02: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V3 风格)
56
57
  PotatoSwapV3Router: '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41', // V3 Router
57
58
  PotatoSwapV3Factory: '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5', // V3 Factory
58
59
  // 稳定币
@@ -60,10 +60,9 @@ const CHAIN_DEX_CONFIGS = {
60
60
  dexes: {
61
61
  POTATOSWAP: {
62
62
  name: 'PotatoSwap',
63
- // SwapRouter02 同时支持 V2 和 V3
64
- v2Factory: '0x630DB8E822805c82Ca40a54daE02dd5aC31f7fcF', // ✅ V2 Factory ( SwapRouter02.factoryV2() 获取)
65
- v2Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02
66
- v3Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02
63
+ v2Factory: '0x630DB8E822805c82Ca40a54daE02dd5aC31f7fcF', // V2 Factory
64
+ v2Router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // ✅ V2 Router (标准 Uniswap V2 风格)
65
+ v3Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V3 风格)
67
66
  v3Factory: '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5', // V3 Factory
68
67
  quoterV2: '0x5A6f3723346aF54a4D0693bfC1718D64d4915C3e', // QuoterV2
69
68
  enabled: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.3.54",
3
+ "version": "1.3.55",
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
- }