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.
- package/dist/dex/direct-router.d.ts +1 -1
- package/dist/dex/direct-router.js +61 -73
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
- package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +6 -0
- package/dist/flap/portal-bundle-merkle/swap.js +17 -7
- package/dist/utils/constants.d.ts +1 -2
- package/dist/utils/constants.js +1 -2
- package/dist/utils/lp-inspect.js +4 -3
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ export declare const DIRECT_ROUTERS: {
|
|
|
22
22
|
readonly WMON: "0x3bd359c1119da7da1d913d1c4d2b7c461115433a";
|
|
23
23
|
};
|
|
24
24
|
readonly XLAYER: {
|
|
25
|
-
readonly POTATOSWAP_V2: "
|
|
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
|
|
39
|
-
POTATOSWAP_V2: '
|
|
40
|
-
//
|
|
41
|
-
|
|
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
|
-
*
|
|
85
|
+
* 重要:SwapRouter02 的 V2 方法只有:
|
|
87
86
|
* - swapExactTokensForTokens(amountIn, amountOutMin, path[], to)
|
|
88
87
|
* - swapTokensForExactTokens(amountOut, amountInMax, path[], to)
|
|
89
88
|
*
|
|
90
89
|
* 没有 swapExactETHForTokens / swapExactTokensForETH!
|
|
91
|
-
*
|
|
92
|
-
* - 买入(ETH
|
|
93
|
-
* - 卖出(Token
|
|
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 (
|
|
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
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
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:
|
|
337
|
-
//
|
|
329
|
+
// ✅ SwapRouter02: 只有 swapExactTokensForTokens,需要手动处理 ETH wrap
|
|
330
|
+
// 文档:没有 swapExactETHForTokens / swapExactTokensForETH
|
|
331
|
+
const multicallData = [];
|
|
338
332
|
if (useNative) {
|
|
339
|
-
// ETH ->
|
|
340
|
-
//
|
|
341
|
-
//
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
// 代币 ->
|
|
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
|
-
|
|
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:
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
const ADDRESS_THIS = '
|
|
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
|
-
//
|
|
505
|
-
//
|
|
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 =
|
|
506
|
+
ADDRESS_THIS, // to = Router 自己,WETH 留在 Router 中
|
|
516
507
|
]);
|
|
517
508
|
multicallData.push(swapData);
|
|
518
|
-
//
|
|
519
|
-
const unwrapData =
|
|
520
|
-
0n, // amountMinimum
|
|
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 =
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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: "
|
|
57
|
-
readonly PotatoSwapSwapRouter02: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
|
|
56
|
+
readonly PotatoSwapV2Router: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
|
|
58
57
|
readonly PotatoSwapV3Router: "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
|
|
59
58
|
readonly PotatoSwapV3Factory: "0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5";
|
|
60
59
|
readonly USDT: "0x1e4a5963abfd975d8c9021ce480b42188849d41d";
|
package/dist/utils/constants.js
CHANGED
|
@@ -52,8 +52,7 @@ export const ADDRESSES = {
|
|
|
52
52
|
// Multicall3 合约
|
|
53
53
|
Multicall3: '0xca11bde05977b3631167028862be2a173976ca11',
|
|
54
54
|
// PotatoSwap DEX 合约
|
|
55
|
-
PotatoSwapV2Router: '
|
|
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
|
// 稳定币
|
package/dist/utils/lp-inspect.js
CHANGED
|
@@ -60,9 +60,10 @@ const CHAIN_DEX_CONFIGS = {
|
|
|
60
60
|
dexes: {
|
|
61
61
|
POTATOSWAP: {
|
|
62
62
|
name: 'PotatoSwap',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,
|