four-flap-meme-sdk 1.3.53 → 1.3.54

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: "0x881fb2f98c13d521009464e7d1cbf16e1b394e8e";
25
+ readonly POTATOSWAP_V2: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
26
26
  readonly POTATOSWAP_V3: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
27
27
  readonly V3_ROUTER: "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
28
28
  readonly V3_FACTORY: "0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5";
@@ -33,13 +33,12 @@ 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 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
38
+ // PotatoSwap - SwapRouter02 同时支持 V2 V3
39
+ POTATOSWAP_V2: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V2+V3)
40
+ POTATOSWAP_V3: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V2+V3)
41
+ // V3 专用 Router(可选)
43
42
  V3_ROUTER: '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41',
44
43
  V3_FACTORY: '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5',
45
44
  // Wrapped Native
@@ -83,14 +82,14 @@ const V2_ROUTER_ABI = [
83
82
  /**
84
83
  * SwapRouter02 的 V2 方法 ABI (PotatoSwap)
85
84
  *
86
- * 重要:根据实际 ABI,SwapRouter02 的 V2 方法只有:
85
+ * 重要:SwapRouter02 的 V2 方法只有:
87
86
  * - swapExactTokensForTokens(amountIn, amountOutMin, path[], to)
88
87
  * - swapTokensForExactTokens(amountOut, amountInMax, path[], to)
89
88
  *
90
89
  * 没有 swapExactETHForTokens / swapExactTokensForETH!
91
- * 必须手动处理 ETH/WETH 转换:
92
- * - 买入(ETHToken): wrapETH + swapExactTokensForTokens,通过 multicall 组合
93
- * - 卖出(TokenETH): swapExactTokensForTokens(to=ADDRESS_THIS) + unwrapWETH9,通过 multicall 组合
90
+ * 需要手动处理 ETH/WETH 转换:
91
+ * - 买入(ETH->Token): wrapETH + swapExactTokensForTokens
92
+ * - 卖出(Token->ETH): swapExactTokensForTokens(to=ADDRESS_THIS) + unwrapWETH9
94
93
  */
95
94
  const SWAP_ROUTER02_V2_ABI = [
96
95
  // V2 交换方法(只有 token-to-token)
@@ -98,15 +97,15 @@ const SWAP_ROUTER02_V2_ABI = [
98
97
  'function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, address[] calldata path, address to) external payable returns (uint256 amountIn)',
99
98
  // Multicall - 多种重载
100
99
  '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)',
102
100
  'function multicall(bytes[] calldata data) external payable returns (bytes[] memory results)',
103
101
  // 辅助方法 - ETH/WETH 转换
104
102
  'function wrapETH(uint256 value) external payable',
105
103
  'function unwrapWETH9(uint256 amountMinimum, address recipient) external payable',
106
104
  'function unwrapWETH9(uint256 amountMinimum) external payable',
107
105
  'function refundETH() external payable',
108
- // 代币操作
106
+ // pull 方法 - 从用户拉取代币到 Router
109
107
  'function pull(address token, uint256 value) external payable',
108
+ // sweepToken - 将 Router 中的代币发送给用户
110
109
  'function sweepToken(address token, uint256 amountMinimum, address recipient) external payable',
111
110
  'function sweepToken(address token, uint256 amountMinimum) external payable',
112
111
  ];
@@ -270,26 +269,20 @@ async function buildProfitTransaction(wallet, profitAmountWei, nonce, gasPrice,
270
269
  // V2 直接交易
271
270
  // ============================================================================
272
271
  /**
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
272
+ * 判断是否是 SwapRouter02 (PotatoSwap, PancakeSwap V3 等)
273
+ * SwapRouter02 的 V2 方法签名不同,需要特殊处理
281
274
  */
282
275
  function isSwapRouter02(chain, routerAddress) {
283
276
  const chainUpper = chain.toUpperCase();
284
277
  const addrLower = routerAddress.toLowerCase();
285
- // ✅ 只有 XLayer PotatoSwap SwapRouter02 (V3 风格) 走这个逻辑
286
- // 注意:POTATOSWAP_V2 现在是真正的 V2 Router,不需要 SwapRouter02 逻辑
287
- if (chainUpper === 'XLAYER' && addrLower === DIRECT_ROUTERS.XLAYER.POTATOSWAP_V3.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()) {
288
284
  return true;
289
285
  }
290
- // ❌ XLayer POTATOSWAP_V2 (0x881fb...) 是标准 V2 Router,不走 SwapRouter02 逻辑
291
- // ❌ BSC V2 交易使用传统 PancakeSwap V2 Router,不走 SwapRouter02 逻辑
292
- // ❌ Monad V2 交易使用传统 V2 Router,不走 SwapRouter02 逻辑
293
286
  return false;
294
287
  }
295
288
  /**
@@ -333,39 +326,42 @@ export async function directV2BatchBuy(params) {
333
326
  let txData;
334
327
  let txValue;
335
328
  if (useSwapRouter02) {
336
- // ✅ SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
337
- // ABI 中没有 swapExactETHForTokens,只有 swapExactTokensForTokens
329
+ // ✅ SwapRouter02: 只有 swapExactTokensForTokens,需要手动处理 ETH wrap
330
+ // 文档:没有 swapExactETHForTokens / swapExactTokensForETH
331
+ const multicallData = [];
338
332
  if (useNative) {
339
- // ETH -> 代币:直接用 swapExactTokensForTokens(参考之前成功的交易)
340
- // 之前成功的交易只用了一个 swapExactTokensForTokens,没有 wrapETH
341
- // SwapRouter02 会自动处理 msg.value 的 ETH
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);
342
338
  const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
343
339
  amountWei,
344
340
  0n, // amountOutMin
345
341
  path, // path: [WETH, ..., tokenOut]
346
342
  wallet.address, // to
347
343
  ]);
348
- // 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
349
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
350
- deadline,
351
- [swapData],
352
- ]);
353
- txValue = amountWei;
344
+ multicallData.push(swapData);
345
+ // refundETH - 退回多余的 ETH(如果有)
346
+ const refundData = routerIface.encodeFunctionData('refundETH', []);
347
+ multicallData.push(refundData);
354
348
  }
355
349
  else {
356
- // 代币 -> 代币:也用 multicall 包装
350
+ // 代币 -> 代币:直接 swapExactTokensForTokens
357
351
  const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
358
352
  amountWei,
359
353
  0n, // amountOutMin
360
354
  path,
361
355
  wallet.address, // to
362
356
  ]);
363
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
364
- deadline,
365
- [swapData],
366
- ]);
367
- txValue = 0n;
357
+ multicallData.push(swapData);
368
358
  }
359
+ // 使用 multicall 包装,传递 deadline
360
+ txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
361
+ deadline,
362
+ multicallData,
363
+ ]);
364
+ txValue = useNative ? amountWei : 0n;
369
365
  }
370
366
  else if (useNative) {
371
367
  // 传统 V2 Router: 原生币 → Token
@@ -492,54 +488,46 @@ export async function directV2BatchSell(params) {
492
488
  // 卖出交易
493
489
  let txData;
494
490
  if (useSwapRouter02) {
495
- // ✅ SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
496
- //
497
- // 重要:SwapRouter02 V2 swapExactTokensForTokens 直接把代币发给 to 地址
498
- // 如果需要 ETH,把 WETH 发给 Router,然后调用 unwrapWETH9
499
- //
500
- // 根据合约源码,address(2) = ADDRESS_THIS = Router 合约自己
501
- const ADDRESS_THIS = '0x0000000000000000000000000000000000000002';
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 自己
502
498
  if (useNativeOutput) {
503
499
  // 代币 -> ETH:
504
- // 方案1swapExactTokensForTokens(to=Router) + unwrapWETH9
505
- // 方案2:直接发 WETH 给用户(用户需要手动 unwrap)
506
- //
507
- // 使用方案1:发送到 Router 地址(真实地址,不是 address(2))
508
- const routerRealAddress = routerAddress; // SwapRouter02 的真实地址
509
- const multicallData = [];
510
- // 1. swapExactTokensForTokens - 从用户转代币,换成 WETH 发到 Router 真实地址
511
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
500
+ // 1. swapExactTokensForTokens(token -> WETH, to = ADDRESS_THIS)
501
+ // 2. unwrapWETH9(WETH -> ETH, to = 用户)
502
+ const swapData = router02Iface.encodeFunctionData('swapExactTokensForTokens', [
512
503
  sellAmount,
513
504
  0n, // amountOutMin
514
505
  path, // path: [token, ..., WETH]
515
- ADDRESS_THIS, // to = address(2),让 Router 接收 WETH
506
+ ADDRESS_THIS, // to = Router 自己,WETH 留在 Router
516
507
  ]);
517
508
  multicallData.push(swapData);
518
- // 2. unwrapWETH9 - 将 Router 中的 WETH 解包为 ETH 发送给用户
519
- const unwrapData = routerIface.encodeFunctionData('unwrapWETH9(uint256,address)', [
520
- 0n, // amountMinimum = 0,接受任意数量
509
+ // unwrap WETH -> ETH 发送给用户
510
+ const unwrapData = router02Iface.encodeFunctionData('unwrapWETH9(uint256,address)', [
511
+ 0n, // amountMinimum
521
512
  wallet.address, // recipient = 用户
522
513
  ]);
523
514
  multicallData.push(unwrapData);
524
- // 使用 multicall(uint256 deadline, bytes[]) - 带 deadline 的版本
525
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
526
- deadline,
527
- multicallData,
528
- ]);
529
515
  }
530
516
  else {
531
517
  // 代币 -> 代币:直接 swapExactTokensForTokens
532
- const swapData = routerIface.encodeFunctionData('swapExactTokensForTokens', [
518
+ const swapData = router02Iface.encodeFunctionData('swapExactTokensForTokens', [
533
519
  sellAmount,
534
520
  0n, // amountOutMin
535
521
  path,
536
- wallet.address, // to = 用户
537
- ]);
538
- txData = routerIface.encodeFunctionData('multicall(uint256,bytes[])', [
539
- deadline,
540
- [swapData],
522
+ wallet.address, // to
541
523
  ]);
524
+ multicallData.push(swapData);
542
525
  }
526
+ // 使用 multicall 包装,传递 deadline
527
+ txData = router02Iface.encodeFunctionData('multicall(uint256,bytes[])', [
528
+ deadline,
529
+ multicallData,
530
+ ]);
543
531
  }
544
532
  else if (useNativeOutput) {
545
533
  txData = routerIface.encodeFunctionData('swapExactTokensForETHSupportingFeeOnTransferTokens', [
@@ -0,0 +1,16 @@
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;
@@ -0,0 +1,146 @@
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
+ }
@@ -300,9 +300,15 @@ async function calculateBuyerFunds({ buyer, buyerFunds, buyerFundsPercentage, re
300
300
  }
301
301
  }
302
302
  else {
303
+ // ERC20 购买:检查代币余额
303
304
  if (buyerBalance < buyerFundsWei) {
304
305
  throw new Error(`买方代币余额不足: 需要 ${ethers.formatUnits(buyerFundsWei, quoteTokenDecimals)},实际 ${ethers.formatUnits(buyerBalance, quoteTokenDecimals)}`);
305
306
  }
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
+ }
306
312
  }
307
313
  return { buyerFundsWei, buyerBalance };
308
314
  }
@@ -194,7 +194,9 @@ export async function flapBundleSwapMerkle(params) {
194
194
  portalGasCost: finalGasLimit * gasPrice,
195
195
  provider: chainContext.provider,
196
196
  chainContext,
197
- seller
197
+ seller,
198
+ useNativeToken, // ✅ 传递是否使用原生代币
199
+ quoteTokenDecimals // ✅ 传递代币精度
198
200
  })
199
201
  ]);
200
202
  // 构建交易请求
@@ -364,13 +366,21 @@ async function calculateBuyerNeed({ buyer, quotedNative, reserveGasEth, slippage
364
366
  : (useNativeToken ? buyerBalance - reserveGas : buyerBalance);
365
367
  return { reserveGas, buyerBalance, buyerNeedTotal, maxBuyerValue };
366
368
  }
367
- async function validateBalances({ buyerNeed, buyerAddress, portalGasCost, provider, chainContext, seller }) {
368
- const buyerBalance = buyerNeed.buyerBalance;
369
+ async function validateBalances({ buyerNeed, buyerAddress, portalGasCost, provider, chainContext, seller, useNativeToken, quoteTokenDecimals = 18 }) {
369
370
  const sellerBalance = await provider.getBalance(seller.address);
370
- if (buyerBalance < buyerNeed.buyerNeedTotal) {
371
- throw new Error(`买方余额不足:\n` +
372
- ` - 需要: ${ethers.formatEther(buyerNeed.buyerNeedTotal)} ${chainContext.nativeToken}\n` +
373
- ` - 实际: ${ethers.formatEther(buyerBalance)} ${chainContext.nativeToken}`);
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
+ }
374
384
  }
375
385
  if (sellerBalance < portalGasCost) {
376
386
  throw new Error(`卖方余额不足: 需要 ${ethers.formatEther(portalGasCost)} ${chainContext.nativeToken} (Gas),实际 ${ethers.formatEther(sellerBalance)} ${chainContext.nativeToken}`);
@@ -53,8 +53,7 @@ export declare const ADDRESSES: {
53
53
  readonly FlapPortal: "0xb30D8c4216E1f21F27444D2FfAee3ad577808678";
54
54
  readonly WOKB: "0xe538905cf8410324e03a5a23c1c177a474d59b2b";
55
55
  readonly Multicall3: "0xca11bde05977b3631167028862be2a173976ca11";
56
- readonly PotatoSwapV2Router: "0x881fb2f98c13d521009464e7d1cbf16e1b394e8e";
57
- readonly PotatoSwapSwapRouter02: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
56
+ readonly PotatoSwapV2Router: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
58
57
  readonly PotatoSwapV3Router: "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
59
58
  readonly PotatoSwapV3Factory: "0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5";
60
59
  readonly USDT: "0x1e4a5963abfd975d8c9021ce480b42188849d41d";
@@ -52,8 +52,7 @@ export const ADDRESSES = {
52
52
  // Multicall3 合约
53
53
  Multicall3: '0xca11bde05977b3631167028862be2a173976ca11',
54
54
  // PotatoSwap DEX 合约
55
- PotatoSwapV2Router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // V2 Router (标准 Uniswap V2 风格)
56
- PotatoSwapSwapRouter02: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V3 风格)
55
+ PotatoSwapV2Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (支持 V2+V3)
57
56
  PotatoSwapV3Router: '0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41', // V3 Router
58
57
  PotatoSwapV3Factory: '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5', // V3 Factory
59
58
  // 稳定币
@@ -60,9 +60,10 @@ const CHAIN_DEX_CONFIGS = {
60
60
  dexes: {
61
61
  POTATOSWAP: {
62
62
  name: 'PotatoSwap',
63
- v2Factory: '0x630DB8E822805c82Ca40a54daE02dd5aC31f7fcF', // V2 Factory
64
- v2Router: '0x881fb2f98c13d521009464e7d1cbf16e1b394e8e', // ✅ V2 Router (标准 Uniswap V2 风格)
65
- v3Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02 (V3 风格)
63
+ // SwapRouter02 同时支持 V2 和 V3
64
+ v2Factory: '0x630DB8E822805c82Ca40a54daE02dd5aC31f7fcF', // ✅ V2 Factory ( SwapRouter02.factoryV2() 获取)
65
+ v2Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02
66
+ v3Router: '0xB45D0149249488333E3F3f9F359807F4b810C1FC', // SwapRouter02
66
67
  v3Factory: '0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5', // V3 Factory
67
68
  quoterV2: '0x5A6f3723346aF54a4D0693bfC1718D64d4915C3e', // QuoterV2
68
69
  enabled: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.3.53",
3
+ "version": "1.3.54",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",