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.
- package/dist/dex/direct-router.d.ts +1 -1
- package/dist/dex/direct-router.js +88 -63
- package/dist/flap/portal-bundle-merkle/swap-buy-first.js +0 -6
- package/dist/flap/portal-bundle-merkle/swap.js +7 -17
- package/dist/utils/constants.d.ts +2 -1
- package/dist/utils/constants.js +2 -1
- package/dist/utils/lp-inspect.js +3 -4
- package/package.json +1 -1
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
- package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
|
@@ -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: "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
|
-
// ✅
|
|
36
|
+
// ✅ XLayer (PotatoSwap)
|
|
37
37
|
XLAYER: {
|
|
38
|
-
// PotatoSwap -
|
|
39
|
-
POTATOSWAP_V2: '
|
|
40
|
-
|
|
41
|
-
// V3
|
|
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
|
-
*
|
|
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
|
-
*
|
|
91
|
-
* - 买入(ETH
|
|
92
|
-
* - 卖出(Token
|
|
91
|
+
* 必须手动处理 ETH/WETH 转换:
|
|
92
|
+
* - 买入(ETH→Token): wrapETH + swapExactTokensForTokens,通过 multicall 组合
|
|
93
|
+
* - 卖出(Token→ETH): 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
|
-
//
|
|
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 (
|
|
273
|
-
*
|
|
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
|
-
|
|
280
|
-
|
|
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:
|
|
330
|
-
//
|
|
331
|
-
const multicallData = [];
|
|
336
|
+
// ✅ SwapRouter02: 使用 multicall(deadline, bytes[]) 组合调用
|
|
337
|
+
// ABI 中没有 swapExactETHForTokens,只有 swapExactTokensForTokens
|
|
332
338
|
if (useNative) {
|
|
333
|
-
// ETH ->
|
|
334
|
-
//
|
|
335
|
-
//
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
// 代币 ->
|
|
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
|
-
|
|
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
|
|
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:
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
//
|
|
495
|
-
//
|
|
496
|
-
// address(2) =
|
|
497
|
-
const ADDRESS_THIS = '
|
|
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
|
|
501
|
-
// 2
|
|
502
|
-
|
|
517
|
+
// 方案1:swapExactTokensForTokens(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
|
|
528
|
+
ADDRESS_THIS, // to = address(2),让 Router 接收 WETH
|
|
507
529
|
]);
|
|
508
530
|
multicallData.push(swapData);
|
|
509
|
-
//
|
|
510
|
-
const unwrapData =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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: "
|
|
56
|
+
readonly PotatoSwapV2Router: "0x881fb2f98c13d521009464e7d1cbf16e1b394e8e";
|
|
57
|
+
readonly PotatoSwapSwapRouter02: "0xB45D0149249488333E3F3f9F359807F4b810C1FC";
|
|
57
58
|
readonly PotatoSwapV3Router: "0xBB069e9465BcabC4F488d21e793BDEf0F2d41D41";
|
|
58
59
|
readonly PotatoSwapV3Factory: "0xa1415fAe79c4B196d087F02b8aD5a622B8A827E5";
|
|
59
60
|
readonly USDT: "0x1e4a5963abfd975d8c9021ce480b42188849d41d";
|
package/dist/utils/constants.js
CHANGED
|
@@ -52,7 +52,8 @@ export const ADDRESSES = {
|
|
|
52
52
|
// Multicall3 合约
|
|
53
53
|
Multicall3: '0xca11bde05977b3631167028862be2a173976ca11',
|
|
54
54
|
// PotatoSwap DEX 合约
|
|
55
|
-
PotatoSwapV2Router: '
|
|
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
|
// 稳定币
|
package/dist/utils/lp-inspect.js
CHANGED
|
@@ -60,10 +60,9 @@ const CHAIN_DEX_CONFIGS = {
|
|
|
60
60
|
dexes: {
|
|
61
61
|
POTATOSWAP: {
|
|
62
62
|
name: 'PotatoSwap',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,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
|
-
}
|