four-flap-meme-sdk 1.2.89 → 1.2.91
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/contracts/tm-bundle-merkle/types.d.ts +0 -4
- package/dist/contracts/tm-bundle-merkle/utils.js +2 -17
- package/dist/flap/constants.d.ts +8 -0
- package/dist/flap/constants.js +9 -0
- package/dist/flap/portal-bundle-merkle/config.d.ts +3 -1
- package/dist/flap/portal-bundle-merkle/config.js +7 -1
- package/dist/flap/portal-bundle-merkle/private.js +14 -1
- package/dist/flap/portal-bundle-merkle/types.d.ts +1 -1
- package/dist/flap/portal.d.ts +1 -1
- package/dist/pancake/bundle-swap.js +28 -1
- package/dist/utils/bundle-helpers.d.ts +0 -6
- package/dist/utils/bundle-helpers.js +0 -9
- package/dist/utils/constants.d.ts +13 -0
- package/dist/utils/constants.js +17 -0
- package/dist/utils/erc20.d.ts +11 -11
- package/dist/utils/erc20.js +14 -6
- package/dist/utils/lp-inspect.d.ts +2 -2
- package/dist/utils/lp-inspect.js +105 -53
- package/dist/utils/wallet.d.ts +1 -1
- package/dist/utils/wallet.js +2 -1
- 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
|
@@ -251,8 +251,6 @@ export type DisperseSignParams = {
|
|
|
251
251
|
hopPrivateKeys?: string[];
|
|
252
252
|
}>;
|
|
253
253
|
config: FourSignConfig;
|
|
254
|
-
/** ✅ 起始 Nonce(用于并行调用时控制 nonce 顺序) */
|
|
255
|
-
startNonce?: number;
|
|
256
254
|
};
|
|
257
255
|
/**
|
|
258
256
|
* ✅ 分发结果(简化版)
|
|
@@ -302,8 +300,6 @@ export type SweepSignParams = {
|
|
|
302
300
|
hopPrivateKeys?: string[];
|
|
303
301
|
}>;
|
|
304
302
|
config: FourSignConfig;
|
|
305
|
-
/** ✅ 起始 Nonce(用于并行调用时控制 nonce 顺序 - 仅当有多个源钱包且使用同一私钥时有效,通常归集不需要) */
|
|
306
|
-
startNonce?: number;
|
|
307
303
|
};
|
|
308
304
|
/**
|
|
309
305
|
* ✅ 归集结果(简化版)
|
|
@@ -8,7 +8,7 @@ import { getErc20DecimalsMerkle as _getErc20DecimalsMerkle, generateHopWallets a
|
|
|
8
8
|
* ✅ 精简版:只负责签名交易,不提交到 Merkle
|
|
9
9
|
*/
|
|
10
10
|
export async function disperseWithBundleMerkle(params) {
|
|
11
|
-
const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config
|
|
11
|
+
const { fromPrivateKey, recipients, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, items, config } = params;
|
|
12
12
|
// 快速返回空结果
|
|
13
13
|
if (!recipients || recipients.length === 0) {
|
|
14
14
|
return {
|
|
@@ -69,9 +69,6 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
69
69
|
if (!preparedHops) {
|
|
70
70
|
// ========== 无多跳:直接批量转账 ==========
|
|
71
71
|
const nonceManager = new NonceManager(provider);
|
|
72
|
-
if (startNonce !== undefined) {
|
|
73
|
-
nonceManager.setNonce(mainWallet.address, startNonce);
|
|
74
|
-
}
|
|
75
72
|
const extraTxCount = extractProfit ? 1 : 0; // ✅ 利润转账需要额外1个nonce
|
|
76
73
|
const nonces = await nonceManager.getNextNonceBatch(mainWallet, recipients.length + extraTxCount);
|
|
77
74
|
if (isNative) {
|
|
@@ -161,9 +158,6 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
161
158
|
const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
162
159
|
const gasFeePerHop = finalGasLimit * gasPrice;
|
|
163
160
|
const nonceManager = new NonceManager(provider);
|
|
164
|
-
if (startNonce !== undefined) {
|
|
165
|
-
nonceManager.setNonce(mainWallet.address, startNonce);
|
|
166
|
-
}
|
|
167
161
|
for (let i = 0; i < recipients.length; i++) {
|
|
168
162
|
const finalRecipient = recipients[i];
|
|
169
163
|
const originalAmountWei = isNative
|
|
@@ -321,7 +315,7 @@ export async function disperseWithBundleMerkle(params) {
|
|
|
321
315
|
* ✅ 精简版:只负责签名交易,不提交到 Merkle
|
|
322
316
|
*/
|
|
323
317
|
export async function sweepWithBundleMerkle(params) {
|
|
324
|
-
const { sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config
|
|
318
|
+
const { sourcePrivateKeys, target, ratioPct, ratios, amount, amounts, tokenAddress, tokenDecimals, skipIfInsufficient = true, hopCount = 0, hopPrivateKeys, sources, config } = params;
|
|
325
319
|
// 快速返回空结果
|
|
326
320
|
if (!sourcePrivateKeys || sourcePrivateKeys.length === 0) {
|
|
327
321
|
return {
|
|
@@ -389,10 +383,6 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
389
383
|
const wallets = actualKeys.map(pk => new Wallet(pk, provider));
|
|
390
384
|
const addresses = wallets.map(w => w.address);
|
|
391
385
|
const nonceManager = new NonceManager(provider);
|
|
392
|
-
// ✅ 如果提供了 startNonce,设置给第一个钱包(主要用于测试场景下单一源钱包的并发调用)
|
|
393
|
-
if (startNonce !== undefined && wallets.length > 0) {
|
|
394
|
-
nonceManager.setNonce(wallets[0].address, startNonce);
|
|
395
|
-
}
|
|
396
386
|
if (isNative) {
|
|
397
387
|
// ✅ 原生币:批量查询余额,统一扣除利润
|
|
398
388
|
const balances = await _batchGetBalances(provider, addresses);
|
|
@@ -671,11 +661,6 @@ export async function sweepWithBundleMerkle(params) {
|
|
|
671
661
|
const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
|
|
672
662
|
const gasFeePerHop = finalGasLimit * gasPrice;
|
|
673
663
|
const nonceManager = new NonceManager(provider);
|
|
674
|
-
// ✅ 如果提供了 startNonce,设置给第一个钱包(主要用于测试场景下单一源钱包的并发调用)
|
|
675
|
-
if (startNonce !== undefined && actualKeys.length > 0) {
|
|
676
|
-
const firstWallet = new Wallet(actualKeys[0], provider);
|
|
677
|
-
nonceManager.setNonce(firstWallet.address, startNonce);
|
|
678
|
-
}
|
|
679
664
|
// 分离有跳转和无跳转的地址
|
|
680
665
|
const sourceWallets = actualKeys.map(pk => new Wallet(pk, provider));
|
|
681
666
|
const withHopIndexes = [];
|
package/dist/flap/constants.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare const FLAP_PORTAL_ADDRESSES: {
|
|
|
8
8
|
readonly BASE: "0x00214f8C22A076575f4B67f3B4AA62f99E166e36";
|
|
9
9
|
readonly XLAYER: "0xA4003509fD2053c07034D45e91EDeDa0ab4d2F8e";
|
|
10
10
|
readonly MORPH: "0x6aB823408672c0Db1DE1a18F1750d62E5F995A58";
|
|
11
|
+
readonly MONAD: "0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23";
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
13
14
|
* Flap Protocol 平台原始 Portal 合约地址
|
|
@@ -19,6 +20,7 @@ export declare const FLAP_ORIGINAL_PORTAL_ADDRESSES: {
|
|
|
19
20
|
readonly BASE: "0xF3c514E04f83166E80718f29f0d34F206be40A0A";
|
|
20
21
|
readonly XLAYER: "0xb30D8c4216E1f21F27444D2FfAee3ad577808678";
|
|
21
22
|
readonly MORPH: "0x4267F317adee7C6478a5EE92985c2BD5D855E274";
|
|
23
|
+
readonly MONAD: "0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23";
|
|
22
24
|
};
|
|
23
25
|
/**
|
|
24
26
|
* Flap Protocol Token 实现合约地址
|
|
@@ -29,6 +31,8 @@ export declare const FLAP_TOKEN_IMPL_ADDRESSES: {
|
|
|
29
31
|
readonly BASE: "0xF3c514E04f83166E80718f29f0d34F206be40A0A";
|
|
30
32
|
readonly XLAYER: "0x12Dc83157Bf1cfCB8Db5952b3ba5bb56Cc38f8C9";
|
|
31
33
|
readonly MORPH: "0x8b4329947e34b6d56d71a3385cac122bade7d78d";
|
|
34
|
+
readonly MONAD_NORMAL: "0xB88189aA1162850D75A1c1e16F837b7979994184";
|
|
35
|
+
readonly MONAD_TAXED: "0x1C8847736521f5cD725dFB8f33c7c610826e7C42";
|
|
32
36
|
};
|
|
33
37
|
/**
|
|
34
38
|
* 不同链的默认手续费率
|
|
@@ -51,6 +55,10 @@ export declare const FLAP_DEFAULT_FEE_RATES: {
|
|
|
51
55
|
readonly buy: 0.025;
|
|
52
56
|
readonly sell: 0.025;
|
|
53
57
|
};
|
|
58
|
+
readonly MONAD: {
|
|
59
|
+
readonly buy: 0.01;
|
|
60
|
+
readonly sell: 0.01;
|
|
61
|
+
};
|
|
54
62
|
};
|
|
55
63
|
/**
|
|
56
64
|
* Flap IPFS API 端点
|
package/dist/flap/constants.js
CHANGED
|
@@ -8,6 +8,8 @@ export const FLAP_PORTAL_ADDRESSES = {
|
|
|
8
8
|
BASE: '0x00214f8C22A076575f4B67f3B4AA62f99E166e36',
|
|
9
9
|
XLAYER: '0xA4003509fD2053c07034D45e91EDeDa0ab4d2F8e',
|
|
10
10
|
MORPH: '0x6aB823408672c0Db1DE1a18F1750d62E5F995A58',
|
|
11
|
+
// ✅ 新增 Monad 链 Portal 地址
|
|
12
|
+
MONAD: '0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23',
|
|
11
13
|
};
|
|
12
14
|
/**
|
|
13
15
|
* Flap Protocol 平台原始 Portal 合约地址
|
|
@@ -19,6 +21,8 @@ export const FLAP_ORIGINAL_PORTAL_ADDRESSES = {
|
|
|
19
21
|
BASE: '0xF3c514E04f83166E80718f29f0d34F206be40A0A',
|
|
20
22
|
XLAYER: '0xb30D8c4216E1f21F27444D2FfAee3ad577808678',
|
|
21
23
|
MORPH: '0x4267F317adee7C6478a5EE92985c2BD5D855E274',
|
|
24
|
+
// ✅ 新增 Monad 链原始 Portal 地址
|
|
25
|
+
MONAD: '0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23',
|
|
22
26
|
};
|
|
23
27
|
/**
|
|
24
28
|
* Flap Protocol Token 实现合约地址
|
|
@@ -29,6 +33,9 @@ export const FLAP_TOKEN_IMPL_ADDRESSES = {
|
|
|
29
33
|
BASE: '0xF3c514E04f83166E80718f29f0d34F206be40A0A',
|
|
30
34
|
XLAYER: '0x12Dc83157Bf1cfCB8Db5952b3ba5bb56Cc38f8C9',
|
|
31
35
|
MORPH: '0x8b4329947e34b6d56d71a3385cac122bade7d78d', // 暂时使用 BSC NORMAL 地址
|
|
36
|
+
// ✅ 新增 Monad 链 Token 实现地址
|
|
37
|
+
MONAD_NORMAL: '0xB88189aA1162850D75A1c1e16F837b7979994184',
|
|
38
|
+
MONAD_TAXED: '0x1C8847736521f5cD725dFB8f33c7c610826e7C42',
|
|
32
39
|
};
|
|
33
40
|
/**
|
|
34
41
|
* 不同链的默认手续费率
|
|
@@ -39,6 +46,8 @@ export const FLAP_DEFAULT_FEE_RATES = {
|
|
|
39
46
|
MORPH: { buy: 0.025, sell: 0.025 }, // 2.5% (250 bps)
|
|
40
47
|
XLAYER: { buy: 0.015, sell: 0.015 }, // 1.5% (150 bps)
|
|
41
48
|
BASE: { buy: 0.025, sell: 0.025 }, // 2.5% (默认)
|
|
49
|
+
// ✅ 新增 Monad 链默认费率
|
|
50
|
+
MONAD: { buy: 0.01, sell: 0.01 }, // 1% (100 bps) - 与 BSC 相同
|
|
42
51
|
};
|
|
43
52
|
/**
|
|
44
53
|
* Flap IPFS API 端点
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { FlapBundleMerkleConfig, FlapSignConfig, FlapChainForMerkleBundle } from './types.js';
|
|
2
2
|
export type { FlapBundleMerkleConfig, FlapSignConfig, FlapChainForMerkleBundle };
|
|
3
3
|
export type FlapAnyConfig = FlapBundleMerkleConfig | FlapSignConfig;
|
|
4
|
-
export declare const CHAIN_ID_MAP: Record<FlapChainForMerkleBundle,
|
|
4
|
+
export declare const CHAIN_ID_MAP: Record<FlapChainForMerkleBundle, number>;
|
|
5
|
+
export declare const MERKLE_SUPPORTED_CHAIN_IDS: readonly [56, 1];
|
|
6
|
+
export type MerkleSupportedChainId = typeof MERKLE_SUPPORTED_CHAIN_IDS[number];
|
|
5
7
|
export declare const PORTAL_ABI: string[];
|
|
6
8
|
export declare const ERRORS: {
|
|
7
9
|
NO_PRIVATE_KEY: {
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
2
|
import { PROFIT_CONFIG } from '../../utils/constants.js';
|
|
3
|
+
// ✅ Chain ID 映射
|
|
4
|
+
// 注意:Merkle 捆绑服务目前只支持 BSC (56) 和 ETH (1)
|
|
5
|
+
// Monad 不支持捆绑交易,这里的 143 仅用于非捆绑的签名交易
|
|
3
6
|
export const CHAIN_ID_MAP = {
|
|
4
|
-
BSC: 56
|
|
7
|
+
BSC: 56,
|
|
8
|
+
MONAD: 143,
|
|
5
9
|
};
|
|
10
|
+
// Merkle 服务支持的 chainId(用于类型断言)
|
|
11
|
+
export const MERKLE_SUPPORTED_CHAIN_IDS = [56, 1];
|
|
6
12
|
// Portal ABI(统一交易接口)
|
|
7
13
|
export const PORTAL_ABI = [
|
|
8
14
|
'function newToken(string name, string symbol, string meta) external payable returns (address)',
|
|
@@ -232,9 +232,22 @@ export async function flapBatchPrivateSellMerkle(params) {
|
|
|
232
232
|
// ==================== 内部工具函数 ====================
|
|
233
233
|
function createMerkleContext(chain, config) {
|
|
234
234
|
const chainId = CHAIN_ID_MAP[chain];
|
|
235
|
+
// ✅ 检查是否为 Merkle 支持的链(BSC: 56, ETH: 1)
|
|
236
|
+
// Monad (143) 不支持 Merkle 捆绑服务,使用普通 RPC
|
|
237
|
+
if (chainId !== 56 && chainId !== 1) {
|
|
238
|
+
// 对于不支持 Merkle 的链(如 Monad),必须提供自定义 RPC
|
|
239
|
+
if (!config.customRpcUrl) {
|
|
240
|
+
throw new Error(`Chain ${chain} (chainId: ${chainId}) does not support Merkle bundle service. Please provide customRpcUrl.`);
|
|
241
|
+
}
|
|
242
|
+
const { JsonRpcProvider } = require('ethers');
|
|
243
|
+
return {
|
|
244
|
+
chainId,
|
|
245
|
+
provider: new JsonRpcProvider(config.customRpcUrl, { chainId, name: chain.toLowerCase() })
|
|
246
|
+
};
|
|
247
|
+
}
|
|
235
248
|
const merkle = new MerkleClient({
|
|
236
249
|
apiKey: config.apiKey,
|
|
237
|
-
chainId,
|
|
250
|
+
chainId: chainId, // 类型断言为 Merkle 支持的 chainId
|
|
238
251
|
customRpcUrl: config.customRpcUrl
|
|
239
252
|
});
|
|
240
253
|
return {
|
|
@@ -24,7 +24,7 @@ export type FlapBundleMerkleConfig = {
|
|
|
24
24
|
skipQuoteOnError?: boolean;
|
|
25
25
|
skipSubmit?: boolean;
|
|
26
26
|
};
|
|
27
|
-
export type FlapChainForMerkleBundle = 'BSC';
|
|
27
|
+
export type FlapChainForMerkleBundle = 'BSC' | 'MONAD';
|
|
28
28
|
export type MerkleTransactionStatus = {
|
|
29
29
|
index: number;
|
|
30
30
|
hash: string;
|
package/dist/flap/portal.d.ts
CHANGED
|
@@ -230,7 +230,34 @@ const PANCAKE_V3_QUOTER_ABI = [
|
|
|
230
230
|
'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) external returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
|
|
231
231
|
'function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate)'
|
|
232
232
|
];
|
|
233
|
-
|
|
233
|
+
const PANCAKE_CHAIN_ADDRESSES = {
|
|
234
|
+
BSC: {
|
|
235
|
+
PancakeProxy: ADDRESSES.BSC.PancakeProxy,
|
|
236
|
+
V2Router: '0x10ED43C718714eb63d5aA57B78B54704E256024E',
|
|
237
|
+
V3Router: '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4',
|
|
238
|
+
V3Quoter: '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997',
|
|
239
|
+
WETH: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
|
|
240
|
+
chainId: 56,
|
|
241
|
+
},
|
|
242
|
+
MONAD: {
|
|
243
|
+
PancakeProxy: '0x20B89e7e088db3e06e0893Ce23162E475b9d8c7c', // ✅ 已部署
|
|
244
|
+
V2Router: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9',
|
|
245
|
+
V3Router: '0x1b81d678ffb9c0263b24a97847620c99d213eb14',
|
|
246
|
+
V3Quoter: '0xb048bbc1ee6b733fffcfb9e9cef7375518e25997',
|
|
247
|
+
WETH: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a', // WMON
|
|
248
|
+
chainId: 143,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
// ✅ 根据链获取地址
|
|
252
|
+
function getChainAddresses(chainId) {
|
|
253
|
+
if (chainId === 56)
|
|
254
|
+
return PANCAKE_CHAIN_ADDRESSES.BSC;
|
|
255
|
+
if (chainId === 143)
|
|
256
|
+
return PANCAKE_CHAIN_ADDRESSES.MONAD;
|
|
257
|
+
// 默认返回 BSC
|
|
258
|
+
return PANCAKE_CHAIN_ADDRESSES.BSC;
|
|
259
|
+
}
|
|
260
|
+
// 兼容旧代码:默认使用 BSC 地址
|
|
234
261
|
const PANCAKE_PROXY_ADDRESS = ADDRESSES.BSC.PancakeProxy;
|
|
235
262
|
const PANCAKE_V2_ROUTER_ADDRESS = '0x10ED43C718714eb63d5aA57B78B54704E256024E';
|
|
236
263
|
const PANCAKE_V3_QUOTER_ADDRESS = '0xB048Bbc1Ee6b733FFfCFb9e9CeF7375518e25997';
|
|
@@ -16,12 +16,6 @@ export declare class NonceManager {
|
|
|
16
16
|
private provider;
|
|
17
17
|
private tempNonceCache;
|
|
18
18
|
constructor(provider: JsonRpcProvider);
|
|
19
|
-
/**
|
|
20
|
-
* 手动设置缓存的 nonce(用于外部控制起始 nonce)
|
|
21
|
-
* @param wallet 钱包对象或地址
|
|
22
|
-
* @param nonce 起始 nonce(getNextNonce 将返回此值)
|
|
23
|
-
*/
|
|
24
|
-
setNonce(wallet: Wallet | string, nonce: number): void;
|
|
25
19
|
/**
|
|
26
20
|
* 获取下一个可用的 nonce
|
|
27
21
|
* @param wallet 钱包对象或地址
|
|
@@ -18,15 +18,6 @@ export class NonceManager {
|
|
|
18
18
|
this.tempNonceCache = new Map();
|
|
19
19
|
this.provider = provider;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* 手动设置缓存的 nonce(用于外部控制起始 nonce)
|
|
23
|
-
* @param wallet 钱包对象或地址
|
|
24
|
-
* @param nonce 起始 nonce(getNextNonce 将返回此值)
|
|
25
|
-
*/
|
|
26
|
-
setNonce(wallet, nonce) {
|
|
27
|
-
const address = typeof wallet === 'string' ? wallet : wallet.address;
|
|
28
|
-
this.tempNonceCache.set(address.toLowerCase(), nonce);
|
|
29
|
-
}
|
|
30
21
|
/**
|
|
31
22
|
* 获取下一个可用的 nonce
|
|
32
23
|
* @param wallet 钱包对象或地址
|
|
@@ -25,6 +25,10 @@ export declare const CHAIN: {
|
|
|
25
25
|
chainId: number;
|
|
26
26
|
name: string;
|
|
27
27
|
};
|
|
28
|
+
MONAD: {
|
|
29
|
+
chainId: number;
|
|
30
|
+
name: string;
|
|
31
|
+
};
|
|
28
32
|
};
|
|
29
33
|
export declare const ADDRESSES: {
|
|
30
34
|
readonly BSC: {
|
|
@@ -51,4 +55,13 @@ export declare const ADDRESSES: {
|
|
|
51
55
|
readonly MORPH: {
|
|
52
56
|
readonly FlapPortal: "0x6aB823408672c0Db1DE1a18F1750d62E5F995A58";
|
|
53
57
|
};
|
|
58
|
+
readonly MONAD: {
|
|
59
|
+
readonly FlapPortal: "0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23";
|
|
60
|
+
readonly TokenV2Implementation: "0xB88189aA1162850D75A1c1e16F837b7979994184";
|
|
61
|
+
readonly TaxTokenImplementation: "0x1C8847736521f5cD725dFB8f33c7c610826e7C42";
|
|
62
|
+
readonly TaxTokenSplitterImplementation: "0x57Fed6832F12150a77D5952b49190d9447aCB5ee";
|
|
63
|
+
readonly WMON: "0x3bd359c1119da7da1d913d1c4d2b7c461115433a";
|
|
64
|
+
readonly Multicall3: "0xca11bde05977b3631167028862be2a173976ca11";
|
|
65
|
+
readonly PancakeProxy: "0x20B89e7e088db3e06e0893Ce23162E475b9d8c7c";
|
|
66
|
+
};
|
|
54
67
|
};
|
package/dist/utils/constants.js
CHANGED
|
@@ -14,6 +14,8 @@ export const CHAIN = {
|
|
|
14
14
|
XLAYER: { chainId: 196, name: 'X Layer' },
|
|
15
15
|
MORPH: { chainId: 2818, name: 'Morph' },
|
|
16
16
|
ARBITRUM_ONE: { chainId: 42161, name: 'Arbitrum One' },
|
|
17
|
+
// ✅ 新增 Monad 链支持
|
|
18
|
+
MONAD: { chainId: 143, name: 'Monad' },
|
|
17
19
|
};
|
|
18
20
|
export const ADDRESSES = {
|
|
19
21
|
BSC: {
|
|
@@ -50,4 +52,19 @@ export const ADDRESSES = {
|
|
|
50
52
|
// Flap Portal 代理合约(收费版)
|
|
51
53
|
FlapPortal: '0x6aB823408672c0Db1DE1a18F1750d62E5F995A58',
|
|
52
54
|
},
|
|
55
|
+
// ✅ 新增 Monad 链合约地址
|
|
56
|
+
MONAD: {
|
|
57
|
+
// Flap Portal 合约(主入口)
|
|
58
|
+
FlapPortal: '0x30e8ee7b5881bf2E158A0514f2150aabe2c68b23',
|
|
59
|
+
// Token 实现合约
|
|
60
|
+
TokenV2Implementation: '0xB88189aA1162850D75A1c1e16F837b7979994184',
|
|
61
|
+
TaxTokenImplementation: '0x1C8847736521f5cD725dFB8f33c7c610826e7C42',
|
|
62
|
+
TaxTokenSplitterImplementation: '0x57Fed6832F12150a77D5952b49190d9447aCB5ee',
|
|
63
|
+
// 原生包装代币
|
|
64
|
+
WMON: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a',
|
|
65
|
+
// Multicall3 合约
|
|
66
|
+
Multicall3: '0xca11bde05977b3631167028862be2a173976ca11',
|
|
67
|
+
// PancakeSwap 代理合约 ✅ 已部署
|
|
68
|
+
PancakeProxy: '0x20B89e7e088db3e06e0893Ce23162E475b9d8c7c',
|
|
69
|
+
},
|
|
53
70
|
};
|
package/dist/utils/erc20.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export declare function ensureSellApproval(chain: 'BSC' | 'BASE' | 'ARBITRUM_ONE
|
|
|
56
56
|
* 检查代币授权状态 - Flap Protocol(只读,不发送交易)
|
|
57
57
|
* @returns 是否已授权足够的额度
|
|
58
58
|
*/
|
|
59
|
-
export declare function checkFlapSellApproval(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', rpcUrl: string, token: string, owner: string, amount: bigint): Promise<{
|
|
59
|
+
export declare function checkFlapSellApproval(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, token: string, owner: string, amount: bigint): Promise<{
|
|
60
60
|
isApproved: boolean;
|
|
61
61
|
currentAllowance: bigint;
|
|
62
62
|
requiredAllowance: bigint;
|
|
@@ -66,17 +66,17 @@ export declare function checkFlapSellApproval(chain: 'BSC' | 'BASE' | 'XLAYER' |
|
|
|
66
66
|
* 用于 Flap Protocol 代币的卖出操作
|
|
67
67
|
* @returns 授权结果,包含是否已授权、当前额度、交易回执等信息
|
|
68
68
|
*/
|
|
69
|
-
export declare function ensureFlapSellApproval(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', rpcUrl: string, privateKey: string, token: string, owner: string, amount: bigint): Promise<EnsureAllowanceResult>;
|
|
69
|
+
export declare function ensureFlapSellApproval(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, privateKey: string, token: string, owner: string, amount: bigint): Promise<EnsureAllowanceResult>;
|
|
70
70
|
/**
|
|
71
71
|
* 批量确保代币已授权给 Flap Protocol 代理合约(默认授权上限 2^256-1)
|
|
72
72
|
* @param privateKeys 拥有者私钥数组(每个地址需自行签名)
|
|
73
73
|
* @returns 每个地址的授权结果(包含 owner 与交易回执等信息)
|
|
74
74
|
*/
|
|
75
|
-
export declare function ensureFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', rpcUrl: string, privateKeys: string[], token: string, required?: bigint): Promise<EnsureAllowanceBatchItemResult[]>;
|
|
75
|
+
export declare function ensureFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, privateKeys: string[], token: string, required?: bigint): Promise<EnsureAllowanceBatchItemResult[]>;
|
|
76
76
|
/**
|
|
77
77
|
* 批量检查 Flap Protocol 授权状态(默认按上限 2^256-1 判断)
|
|
78
78
|
*/
|
|
79
|
-
export declare function checkFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', rpcUrl: string, token: string, owners: string[], required?: bigint): Promise<Array<{
|
|
79
|
+
export declare function checkFlapSellApprovalBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', rpcUrl: string, token: string, owners: string[], required?: bigint): Promise<Array<{
|
|
80
80
|
owner: string;
|
|
81
81
|
isApproved: boolean;
|
|
82
82
|
currentAllowance: bigint;
|
|
@@ -94,14 +94,14 @@ export declare function batchCheckAllowances(provider: JsonRpcProvider, tokenAdd
|
|
|
94
94
|
/**
|
|
95
95
|
* ✅ 智能路由:检查单个 ERC20 授权额度(自动选择 spender)
|
|
96
96
|
*
|
|
97
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
97
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
98
98
|
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
99
99
|
* @param rpcUrl - RPC 节点地址
|
|
100
100
|
* @param tokenAddress - 代币合约地址
|
|
101
101
|
* @param ownerAddress - 代币持有者地址
|
|
102
102
|
* @returns 当前授权额度(bigint)
|
|
103
103
|
*/
|
|
104
|
-
export declare function checkAllowance(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, tokenAddress: string, ownerAddress: string): Promise<bigint>;
|
|
104
|
+
export declare function checkAllowance(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, tokenAddress: string, ownerAddress: string): Promise<bigint>;
|
|
105
105
|
/**
|
|
106
106
|
* ✅ 底层方法:检查 ERC20 代币授权额度(手动指定 spender)
|
|
107
107
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
@@ -116,7 +116,7 @@ export declare function checkAllowanceRaw(rpcUrl: string, tokenAddress: string,
|
|
|
116
116
|
/**
|
|
117
117
|
* ✅ 智能路由:授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易)
|
|
118
118
|
*
|
|
119
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
119
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
120
120
|
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
121
121
|
* @param rpcUrl - RPC 节点地址
|
|
122
122
|
* @param privateKey - 私钥
|
|
@@ -124,7 +124,7 @@ export declare function checkAllowanceRaw(rpcUrl: string, tokenAddress: string,
|
|
|
124
124
|
* @param amount - 授权数量(bigint),传 'max' 表示最大授权
|
|
125
125
|
* @returns 授权结果
|
|
126
126
|
*/
|
|
127
|
-
export declare function approveToken(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, privateKey: string, tokenAddress: string, amount: bigint | 'max'): Promise<EnsureAllowanceResult>;
|
|
127
|
+
export declare function approveToken(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, privateKey: string, tokenAddress: string, amount: bigint | 'max'): Promise<EnsureAllowanceResult>;
|
|
128
128
|
/**
|
|
129
129
|
* ✅ 底层方法:授权 ERC20 代币给指定合约(手动指定 spender)
|
|
130
130
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
@@ -140,14 +140,14 @@ export declare function approveTokenRaw(rpcUrl: string, privateKey: string, toke
|
|
|
140
140
|
/**
|
|
141
141
|
* ✅ 智能路由:批量检查多个钱包的授权额度(自动选择 spender)
|
|
142
142
|
*
|
|
143
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
143
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
144
144
|
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
145
145
|
* @param rpcUrl - RPC 节点地址
|
|
146
146
|
* @param tokenAddress - 代币合约地址
|
|
147
147
|
* @param ownerAddresses - 代币持有者地址数组
|
|
148
148
|
* @returns 每个地址的授权额度数组
|
|
149
149
|
*/
|
|
150
|
-
export declare function checkAllowanceBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, tokenAddress: string, ownerAddresses: string[]): Promise<bigint[]>;
|
|
150
|
+
export declare function checkAllowanceBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD', platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3', rpcUrl: string, tokenAddress: string, ownerAddresses: string[]): Promise<bigint[]>;
|
|
151
151
|
/**
|
|
152
152
|
* ✅ 底层方法:批量检查多个钱包的授权额度(手动指定 spender)
|
|
153
153
|
* 适用于任意 spender 地址(Flap、Four、PancakeSwap V2/V3 等)
|
|
@@ -160,7 +160,7 @@ export declare function checkAllowanceBatch(chain: 'BSC' | 'BASE' | 'XLAYER' | '
|
|
|
160
160
|
*/
|
|
161
161
|
export declare function checkAllowanceBatchRaw(rpcUrl: string, tokenAddress: string, ownerAddresses: string[], spenderAddress: string): Promise<bigint[]>;
|
|
162
162
|
export type ApproveTokenBatchParams = {
|
|
163
|
-
chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH';
|
|
163
|
+
chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD';
|
|
164
164
|
platform: 'flap' | 'four' | 'pancake-v2' | 'pancake-v3';
|
|
165
165
|
rpcUrl: string;
|
|
166
166
|
privateKeys: string[];
|
package/dist/utils/erc20.js
CHANGED
|
@@ -174,6 +174,7 @@ export async function checkFlapSellApproval(chain, rpcUrl, token, owner, amount)
|
|
|
174
174
|
BASE: ADDRESSES.BASE.FlapPortal, // Flap Portal 代理合约 (BASE)
|
|
175
175
|
XLAYER: ADDRESSES.XLAYER.FlapPortal, // Flap Portal 代理合约 (XLAYER)
|
|
176
176
|
MORPH: ADDRESSES.MORPH.FlapPortal, // Flap Portal 代理合约 (MORPH)
|
|
177
|
+
MONAD: ADDRESSES.MONAD.FlapPortal, // Flap Portal 代理合约 (MONAD)
|
|
177
178
|
};
|
|
178
179
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
179
180
|
// ✅ 验证 token 和代理合约地址
|
|
@@ -205,6 +206,7 @@ export async function ensureFlapSellApproval(chain, rpcUrl, privateKey, token, o
|
|
|
205
206
|
BASE: ADDRESSES.BASE.FlapPortal, // Flap Portal 代理合约 (BASE)
|
|
206
207
|
XLAYER: ADDRESSES.XLAYER.FlapPortal, // Flap Portal 代理合约 (XLAYER)
|
|
207
208
|
MORPH: ADDRESSES.MORPH.FlapPortal, // Flap Portal 代理合约 (MORPH)
|
|
209
|
+
MONAD: ADDRESSES.MONAD.FlapPortal, // Flap Portal 代理合约 (MONAD)
|
|
208
210
|
};
|
|
209
211
|
return await ensureAllowance(rpcUrl, privateKey, token, owner, proxyAddresses[chain], amount);
|
|
210
212
|
}
|
|
@@ -231,6 +233,7 @@ export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, r
|
|
|
231
233
|
BASE: ADDRESSES.BASE.FlapPortal,
|
|
232
234
|
XLAYER: ADDRESSES.XLAYER.FlapPortal,
|
|
233
235
|
MORPH: ADDRESSES.MORPH.FlapPortal,
|
|
236
|
+
MONAD: ADDRESSES.MONAD.FlapPortal,
|
|
234
237
|
};
|
|
235
238
|
const provider = new JsonRpcProvider(rpcUrl);
|
|
236
239
|
// ✅ 验证 token 和代理合约地址
|
|
@@ -259,7 +262,8 @@ export async function checkFlapSellApprovalBatch(chain, rpcUrl, token, owners, r
|
|
|
259
262
|
* @returns 每个地址的授权额度数组
|
|
260
263
|
*/
|
|
261
264
|
export async function batchCheckAllowances(provider, tokenAddress, owners, spender) {
|
|
262
|
-
|
|
265
|
+
// ✅ 动态获取 Multicall3 地址(所有链使用相同地址)
|
|
266
|
+
const multicall3Address = '0xca11bde05977b3631167028862be2a173976ca11';
|
|
263
267
|
const multicall3ABI = [
|
|
264
268
|
'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) view returns (tuple(bool success, bytes returnData)[] returnData)'
|
|
265
269
|
];
|
|
@@ -297,6 +301,7 @@ function resolveSpenderAddress(chain, platform) {
|
|
|
297
301
|
BASE: ADDRESSES.BASE.FlapPortal,
|
|
298
302
|
XLAYER: ADDRESSES.XLAYER.FlapPortal,
|
|
299
303
|
MORPH: ADDRESSES.MORPH.FlapPortal,
|
|
304
|
+
MONAD: ADDRESSES.MONAD.FlapPortal, // ✅ Monad Flap Portal
|
|
300
305
|
},
|
|
301
306
|
four: {
|
|
302
307
|
// Four.meme 使用 TokenManagerV2Proxy(默认)
|
|
@@ -304,20 +309,23 @@ function resolveSpenderAddress(chain, platform) {
|
|
|
304
309
|
BASE: ADDRESSES.BASE.TokenManagerHelper3,
|
|
305
310
|
XLAYER: '0x0000000000000000000000000000000000000000', // XLAYER 暂不支持 Four.meme
|
|
306
311
|
MORPH: '0x0000000000000000000000000000000000000000', // MORPH 暂不支持 Four.meme
|
|
312
|
+
MONAD: '0x0000000000000000000000000000000000000000', // ❌ MONAD 不支持 Four.meme
|
|
307
313
|
},
|
|
308
314
|
'pancake-v2': {
|
|
309
|
-
// PancakeSwap V2 Router
|
|
315
|
+
// PancakeSwap V2 Router 地址
|
|
310
316
|
BSC: '0x10ED43C718714eb63d5aA57B78B54704E256024E', // BSC PancakeSwap V2 Router
|
|
311
317
|
BASE: '0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb', // BASE PancakeSwap V2 Router
|
|
312
318
|
XLAYER: '0x0000000000000000000000000000000000000000', // XLAYER 暂不支持
|
|
313
319
|
MORPH: '0x0000000000000000000000000000000000000000', // MORPH 暂不支持
|
|
320
|
+
MONAD: '0xb1bc24c34e88f7d43d5923034e3a14b24daacff9', // ✅ Monad PancakeSwap V2 Router
|
|
314
321
|
},
|
|
315
322
|
'pancake-v3': {
|
|
316
|
-
// PancakeSwap V3 SmartRouter
|
|
323
|
+
// PancakeSwap V3 SmartRouter 地址
|
|
317
324
|
BSC: '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4', // BSC PancakeSwap V3 SmartRouter
|
|
318
325
|
BASE: '0x678Aa4bF4E210cf2166753e054d5b7c31cc7fa86', // BASE PancakeSwap V3 SmartRouter
|
|
319
326
|
XLAYER: '0x0000000000000000000000000000000000000000', // XLAYER 暂不支持
|
|
320
327
|
MORPH: '0x0000000000000000000000000000000000000000', // MORPH 暂不支持
|
|
328
|
+
MONAD: '0x1b81d678ffb9c0263b24a97847620c99d213eb14', // ✅ Monad PancakeSwap V3 Router
|
|
321
329
|
},
|
|
322
330
|
};
|
|
323
331
|
const spender = spenderMap[platform]?.[chain];
|
|
@@ -329,7 +337,7 @@ function resolveSpenderAddress(chain, platform) {
|
|
|
329
337
|
/**
|
|
330
338
|
* ✅ 智能路由:检查单个 ERC20 授权额度(自动选择 spender)
|
|
331
339
|
*
|
|
332
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
340
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
333
341
|
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
334
342
|
* @param rpcUrl - RPC 节点地址
|
|
335
343
|
* @param tokenAddress - 代币合约地址
|
|
@@ -371,7 +379,7 @@ export async function checkAllowanceRaw(rpcUrl, tokenAddress, ownerAddress, spen
|
|
|
371
379
|
/**
|
|
372
380
|
* ✅ 智能路由:授权 ERC20 代币(自动选择 spender,自动检查,只在不足时才发送交易)
|
|
373
381
|
*
|
|
374
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
382
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
375
383
|
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
376
384
|
* @param rpcUrl - RPC 节点地址
|
|
377
385
|
* @param privateKey - 私钥
|
|
@@ -440,7 +448,7 @@ export async function approveTokenRaw(rpcUrl, privateKey, tokenAddress, spenderA
|
|
|
440
448
|
/**
|
|
441
449
|
* ✅ 智能路由:批量检查多个钱包的授权额度(自动选择 spender)
|
|
442
450
|
*
|
|
443
|
-
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH')
|
|
451
|
+
* @param chain - 链名称 ('BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'MONAD')
|
|
444
452
|
* @param platform - 平台名称 ('flap' | 'four' | 'pancake-v2' | 'pancake-v3')
|
|
445
453
|
* @param rpcUrl - RPC 节点地址
|
|
446
454
|
* @param tokenAddress - 代币合约地址
|
|
@@ -28,7 +28,7 @@ export type LPInfo = {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
v3?: Array<{
|
|
31
|
-
base: 'WBNB' | 'USDT';
|
|
31
|
+
base: 'WBNB' | 'USDT' | 'WMON' | 'USDC';
|
|
32
32
|
fee: number;
|
|
33
33
|
pool: string;
|
|
34
34
|
tokenBalance: string;
|
|
@@ -36,7 +36,7 @@ export type LPInfo = {
|
|
|
36
36
|
}>;
|
|
37
37
|
};
|
|
38
38
|
export type InspectOptions = {
|
|
39
|
-
chain: 'BSC';
|
|
39
|
+
chain: 'BSC' | 'MONAD';
|
|
40
40
|
rpcUrl: string;
|
|
41
41
|
flapChain?: FlapChain;
|
|
42
42
|
v3FeeTiers?: Array<number>;
|
package/dist/utils/lp-inspect.js
CHANGED
|
@@ -2,11 +2,27 @@ import { Contract, JsonRpcProvider, Interface, formatUnits } from 'ethers';
|
|
|
2
2
|
import { ADDRESSES } from './constants.js';
|
|
3
3
|
import { Helper3 } from '../contracts/helper3.js';
|
|
4
4
|
import { FlapPortal } from '../flap/portal.js';
|
|
5
|
-
//
|
|
5
|
+
// 常量:各链常用代币与工厂
|
|
6
6
|
const TOKENS = {
|
|
7
7
|
BSC: {
|
|
8
|
-
WBNB: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
|
|
8
|
+
WBNB: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
|
|
9
9
|
USDT: '0x55d398326f99059fF775485246999027B3197955',
|
|
10
|
+
},
|
|
11
|
+
// ✅ Monad 链代币
|
|
12
|
+
MONAD: {
|
|
13
|
+
WMON: '0x3bd359c1119da7da1d913d1c4d2b7c461115433a', // Wrapped MON
|
|
14
|
+
USDC: '0x754704bc059f8c67012fed69bc8a327a5aafb603', // USDC on Monad
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
// ✅ 各链 PancakeSwap 工厂地址
|
|
18
|
+
const PANCAKE_FACTORIES = {
|
|
19
|
+
BSC: {
|
|
20
|
+
v2: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73',
|
|
21
|
+
v3: '0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865',
|
|
22
|
+
},
|
|
23
|
+
MONAD: {
|
|
24
|
+
v2: '0x44c90b66eb4c4f814cea6aaf2ac71ca88fa77308', // Monad PancakeSwap V2 Factory
|
|
25
|
+
v3: '0x40ce898c43df68c8c4229cd5ce21d3ce1f3ddcf1', // Monad PancakeSwap V3 Factory
|
|
10
26
|
}
|
|
11
27
|
};
|
|
12
28
|
// Pancake V2/V3 工厂(BSC,作兜底)
|
|
@@ -50,6 +66,24 @@ export async function inspectTokenLP(token, opts) {
|
|
|
50
66
|
multicall3 = '0xca11bde05977b3631167028862be2a173976ca11';
|
|
51
67
|
}
|
|
52
68
|
}
|
|
69
|
+
// ✅ 获取当前链的原生包装代币和稳定币地址
|
|
70
|
+
function getChainTokens() {
|
|
71
|
+
if (opts.chain === 'MONAD') {
|
|
72
|
+
return {
|
|
73
|
+
wrappedNative: TOKENS.MONAD.WMON,
|
|
74
|
+
wrappedNativeSymbol: 'WMON',
|
|
75
|
+
stableCoin: TOKENS.MONAD.USDC,
|
|
76
|
+
stableCoinSymbol: 'USDC',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// 默认 BSC
|
|
80
|
+
return {
|
|
81
|
+
wrappedNative: TOKENS.BSC.WBNB,
|
|
82
|
+
wrappedNativeSymbol: 'WBNB',
|
|
83
|
+
stableCoin: TOKENS.BSC.USDT,
|
|
84
|
+
stableCoinSymbol: 'USDT',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
53
87
|
async function resolveFactories() {
|
|
54
88
|
// 1) 直接给出工厂
|
|
55
89
|
if (opts.factoryV2 && opts.factoryV3)
|
|
@@ -70,19 +104,22 @@ export async function inspectTokenLP(token, opts) {
|
|
|
70
104
|
}
|
|
71
105
|
}
|
|
72
106
|
catch { }
|
|
73
|
-
// 3)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
res.v2
|
|
78
|
-
|
|
79
|
-
res.v3
|
|
107
|
+
// 3) ✅ 只在 BSC 链上通过 Helper3 读取(Four.meme 只支持 BSC)
|
|
108
|
+
if (opts.chain === 'BSC') {
|
|
109
|
+
try {
|
|
110
|
+
const helper = Helper3.connectByChain('BSC', opts.rpcUrl);
|
|
111
|
+
if (!res.v2 && helper.PANCAKE_FACTORY)
|
|
112
|
+
res.v2 = await helper.PANCAKE_FACTORY();
|
|
113
|
+
if (!res.v3 && helper.PANCAKE_V3_FACTORY)
|
|
114
|
+
res.v3 = await helper.PANCAKE_V3_FACTORY();
|
|
115
|
+
}
|
|
116
|
+
catch { }
|
|
80
117
|
}
|
|
81
|
-
|
|
82
|
-
|
|
118
|
+
// 4) ✅ 根据链使用对应的工厂地址
|
|
119
|
+
const chainFactories = PANCAKE_FACTORIES[opts.chain] || PANCAKE_FACTORIES.BSC;
|
|
83
120
|
return {
|
|
84
|
-
v2: res.v2 ||
|
|
85
|
-
v3: res.v3 ||
|
|
121
|
+
v2: res.v2 || chainFactories.v2,
|
|
122
|
+
v3: res.v3 || chainFactories.v3,
|
|
86
123
|
};
|
|
87
124
|
}
|
|
88
125
|
// 1) flap 内盘优先识别(如提供 flapChain)
|
|
@@ -108,46 +145,50 @@ export async function inspectTokenLP(token, opts) {
|
|
|
108
145
|
}
|
|
109
146
|
catch { }
|
|
110
147
|
}
|
|
111
|
-
// 2) four
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
// 2) four 内盘识别(严格条件)- ✅ 只在 BSC 链上执行(Monad 不支持 Four.meme)
|
|
149
|
+
if (opts.chain === 'BSC') {
|
|
150
|
+
try {
|
|
151
|
+
const helper = Helper3.connectByChain('BSC', opts.rpcUrl);
|
|
152
|
+
const info = await helper.getTokenInfo(token);
|
|
153
|
+
if (info) {
|
|
154
|
+
const tokenManager = info.tokenManager;
|
|
155
|
+
const versionNum = Number(info.version ?? 0);
|
|
156
|
+
const liquidityAdded = Boolean(info.liquidityAdded);
|
|
157
|
+
const isValid = tokenManager && tokenManager !== '0x0000000000000000000000000000000000000000' && (versionNum === 1 || versionNum === 2);
|
|
158
|
+
if (isValid) {
|
|
159
|
+
// 若已添加流动性(已上 DEX),则继续外盘检测
|
|
160
|
+
if (!liquidityAdded) {
|
|
161
|
+
out.platform = 'FOUR';
|
|
162
|
+
out.decimals = tokenDecimals;
|
|
163
|
+
const reserveNative = info.funds !== undefined ? formatBalance(info.funds, shouldFormat) : undefined;
|
|
164
|
+
const offerTokens = info.offers !== undefined ? formatBalance(info.offers, shouldFormat) : undefined;
|
|
165
|
+
const lastPrice = info.lastPrice !== undefined ? formatBalance(info.lastPrice, shouldFormat) : undefined;
|
|
166
|
+
out.four = { helper: ADDRESSES.BSC.TokenManagerHelper3, reserveNative, offerTokens, lastPrice };
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
// liquidityAdded=true 表示已外盘,不在此返回
|
|
130
170
|
}
|
|
131
|
-
// liquidityAdded=true 表示已外盘,不在此返回
|
|
132
171
|
}
|
|
133
172
|
}
|
|
173
|
+
catch { }
|
|
134
174
|
}
|
|
135
|
-
catch { }
|
|
136
175
|
// 3) 外盘 V2:Pancake V2 getPair(token, WBNB/USDT) → getReserves(不立即返回,允许继续查 V3)
|
|
176
|
+
// ✅ 根据链动态获取代币地址
|
|
177
|
+
const chainTokens = getChainTokens();
|
|
137
178
|
try {
|
|
138
179
|
const { v2: v2FactoryAddr } = await resolveFactories();
|
|
139
180
|
const v2Factory = new Contract(v2FactoryAddr, I_UNIV2_FACTORY_ABI, provider);
|
|
140
181
|
const v2 = {};
|
|
141
|
-
//
|
|
182
|
+
// 独立查询原生包装代币交易对(BSC: WBNB, Monad: WMON),避免一个失败影响另一个
|
|
142
183
|
try {
|
|
143
|
-
const v2PairWbnb = await v2Factory.getPair(token,
|
|
184
|
+
const v2PairWbnb = await v2Factory.getPair(token, chainTokens.wrappedNative);
|
|
144
185
|
if (v2PairWbnb && v2PairWbnb !== '0x0000000000000000000000000000000000000000') {
|
|
145
186
|
try {
|
|
146
187
|
const pair = new Contract(v2PairWbnb, I_UNIV2_PAIR_ABI, provider);
|
|
147
188
|
const [t0, t1] = await Promise.all([pair.token0(), pair.token1()]);
|
|
148
189
|
const [r0, r1] = await pair.getReserves();
|
|
149
190
|
const reserveToken = t0.toLowerCase() === token.toLowerCase() ? r0 : r1;
|
|
150
|
-
const reserveWBNB = t0.toLowerCase() ===
|
|
191
|
+
const reserveWBNB = t0.toLowerCase() === chainTokens.wrappedNative.toLowerCase() ? r0 : r1;
|
|
151
192
|
v2.wbnbPair = { address: v2PairWbnb, reserveToken: formatBalance(reserveToken, shouldFormat), reserveWBNB: formatBalance(reserveWBNB, shouldFormat) };
|
|
152
193
|
}
|
|
153
194
|
catch {
|
|
@@ -156,16 +197,16 @@ export async function inspectTokenLP(token, opts) {
|
|
|
156
197
|
}
|
|
157
198
|
}
|
|
158
199
|
catch { }
|
|
159
|
-
//
|
|
200
|
+
// 独立查询稳定币交易对(BSC: USDT, Monad: USDC)
|
|
160
201
|
try {
|
|
161
|
-
const v2PairUsdt = await v2Factory.getPair(token,
|
|
202
|
+
const v2PairUsdt = await v2Factory.getPair(token, chainTokens.stableCoin);
|
|
162
203
|
if (v2PairUsdt && v2PairUsdt !== '0x0000000000000000000000000000000000000000') {
|
|
163
204
|
try {
|
|
164
205
|
const pair = new Contract(v2PairUsdt, I_UNIV2_PAIR_ABI, provider);
|
|
165
206
|
const [t0, t1] = await Promise.all([pair.token0(), pair.token1()]);
|
|
166
207
|
const [r0, r1] = await pair.getReserves();
|
|
167
208
|
const reserveToken = t0.toLowerCase() === token.toLowerCase() ? r0 : r1;
|
|
168
|
-
const reserveUSDT = t0.toLowerCase() ===
|
|
209
|
+
const reserveUSDT = t0.toLowerCase() === chainTokens.stableCoin.toLowerCase() ? r0 : r1;
|
|
169
210
|
v2.usdtPair = { address: v2PairUsdt, reserveToken: formatBalance(reserveToken, shouldFormat), reserveUSDT: formatBalance(reserveUSDT, shouldFormat) };
|
|
170
211
|
}
|
|
171
212
|
catch {
|
|
@@ -179,7 +220,10 @@ export async function inspectTokenLP(token, opts) {
|
|
|
179
220
|
}
|
|
180
221
|
}
|
|
181
222
|
catch { }
|
|
182
|
-
|
|
223
|
+
const v3BasePairs = [
|
|
224
|
+
{ symbol: chainTokens.wrappedNativeSymbol, address: chainTokens.wrappedNative },
|
|
225
|
+
{ symbol: chainTokens.stableCoinSymbol, address: chainTokens.stableCoin },
|
|
226
|
+
];
|
|
183
227
|
try {
|
|
184
228
|
const fees = opts.v3FeeTiers ?? [100, 500, 2500, 3000, 10000];
|
|
185
229
|
const { v3: v3FactoryAddr } = await resolveFactories();
|
|
@@ -189,20 +233,20 @@ export async function inspectTokenLP(token, opts) {
|
|
|
189
233
|
if (opts.debug)
|
|
190
234
|
console.log('[V3 Debug] Factory:', v3FactoryAddr, 'Fees:', fees);
|
|
191
235
|
const poolCalls = [];
|
|
192
|
-
for (const
|
|
193
|
-
const baseAddr =
|
|
236
|
+
for (const basePair of v3BasePairs) {
|
|
237
|
+
const baseAddr = basePair.address;
|
|
194
238
|
for (const fee of fees) {
|
|
195
239
|
// V3 要求 token0 < token1 按地址字典序(必须全部用小写比较和赋值)
|
|
196
240
|
const tokenLower = token.toLowerCase();
|
|
197
241
|
const baseLower = baseAddr.toLowerCase();
|
|
198
242
|
const [token0, token1] = tokenLower < baseLower ? [tokenLower, baseLower] : [baseLower, tokenLower];
|
|
199
243
|
if (opts.debug)
|
|
200
|
-
console.log(`[V3 Debug] ${
|
|
244
|
+
console.log(`[V3 Debug] ${basePair.symbol} fee=${fee}: getPool(${token0}, ${token1})`);
|
|
201
245
|
// 只需一次调用,V3 getPool 要求严格顺序
|
|
202
246
|
poolCalls.push({
|
|
203
247
|
target: v3FactoryAddr,
|
|
204
248
|
callData: v3FactoryIface.encodeFunctionData('getPool', [token0, token1, fee]),
|
|
205
|
-
base,
|
|
249
|
+
base: basePair.symbol,
|
|
206
250
|
fee,
|
|
207
251
|
token0,
|
|
208
252
|
token1
|
|
@@ -226,7 +270,11 @@ export async function inspectTokenLP(token, opts) {
|
|
|
226
270
|
}
|
|
227
271
|
if (pool && pool.toLowerCase() !== '0x0000000000000000000000000000000000000000') {
|
|
228
272
|
// 直接添加,不去重(同一代币可能在不同 fee 档位都有池子)
|
|
229
|
-
|
|
273
|
+
// ✅ 根据 base symbol 获取对应的地址
|
|
274
|
+
const baseAddress = poolCalls[i].base === chainTokens.wrappedNativeSymbol
|
|
275
|
+
? chainTokens.wrappedNative
|
|
276
|
+
: chainTokens.stableCoin;
|
|
277
|
+
pools.push({ base: poolCalls[i].base, fee: poolCalls[i].fee, pool, baseAddress });
|
|
230
278
|
}
|
|
231
279
|
else if (opts.debug) {
|
|
232
280
|
console.log(` ⤷ Skipped (zero address)`);
|
|
@@ -244,7 +292,8 @@ export async function inspectTokenLP(token, opts) {
|
|
|
244
292
|
const calls = [];
|
|
245
293
|
for (const p of pools) {
|
|
246
294
|
calls.push({ target: token, callData: erc20Iface.encodeFunctionData('balanceOf', [p.pool]) });
|
|
247
|
-
|
|
295
|
+
// ✅ 使用动态的基础代币地址
|
|
296
|
+
calls.push({ target: p.baseAddress, callData: erc20Iface.encodeFunctionData('balanceOf', [p.pool]) });
|
|
248
297
|
}
|
|
249
298
|
if (opts.debug)
|
|
250
299
|
console.log(`[V3 Debug] Querying balances for ${pools.length} pools (${calls.length} calls)...`);
|
|
@@ -293,11 +342,12 @@ export async function inspectTokenLP(token, opts) {
|
|
|
293
342
|
const { v3: v3FactoryAddr } = await resolveFactories();
|
|
294
343
|
const v3Factory = new Contract(v3FactoryAddr, I_UNIV3_FACTORY_ABI, provider);
|
|
295
344
|
const erc20 = new Contract(token, I_ERC20_ABI, provider);
|
|
296
|
-
|
|
297
|
-
const
|
|
345
|
+
// ✅ 使用动态的基础代币地址
|
|
346
|
+
const wrappedNativeContract = new Contract(chainTokens.wrappedNative, I_ERC20_ABI, provider);
|
|
347
|
+
const stableCoinContract = new Contract(chainTokens.stableCoin, I_ERC20_ABI, provider);
|
|
298
348
|
const v3Results = [];
|
|
299
|
-
for (const
|
|
300
|
-
const baseAddr =
|
|
349
|
+
for (const basePair of v3BasePairs) {
|
|
350
|
+
const baseAddr = basePair.address;
|
|
301
351
|
for (const fee of fees) {
|
|
302
352
|
// V3 要求 token0 < token1 按地址字典序(必须全部用小写)
|
|
303
353
|
const tokenLower = token.toLowerCase();
|
|
@@ -314,11 +364,13 @@ export async function inspectTokenLP(token, opts) {
|
|
|
314
364
|
tb = await erc20.balanceOf(pool);
|
|
315
365
|
}
|
|
316
366
|
catch { }
|
|
367
|
+
// ✅ 使用正确的合约对象
|
|
368
|
+
const baseContract = basePair.symbol === chainTokens.wrappedNativeSymbol ? wrappedNativeContract : stableCoinContract;
|
|
317
369
|
try {
|
|
318
|
-
bb = await
|
|
370
|
+
bb = await baseContract.balanceOf(pool);
|
|
319
371
|
}
|
|
320
372
|
catch { }
|
|
321
|
-
v3Results.push({ base, fee, pool, tokenBalance: formatBalance(tb, shouldFormat), baseBalance: formatBalance(bb, shouldFormat) });
|
|
373
|
+
v3Results.push({ base: basePair.symbol, fee, pool, tokenBalance: formatBalance(tb, shouldFormat), baseBalance: formatBalance(bb, shouldFormat) });
|
|
322
374
|
}
|
|
323
375
|
}
|
|
324
376
|
}
|
package/dist/utils/wallet.d.ts
CHANGED
|
@@ -38,6 +38,6 @@ export type MultiTokenBalancesResult = {
|
|
|
38
38
|
};
|
|
39
39
|
export declare function getTokenBalancesWithMulticall(rpcUrl: string, token: string, holders: string[]): Promise<MulticallResult[]>;
|
|
40
40
|
export declare function getTokenBalancesWithMulticall(rpcUrl: string, multicall3: string, token: string, holders: string[]): Promise<MulticallResult[]>;
|
|
41
|
-
export declare function getTokenBalancesWithMulticall(rpcUrl: string, chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'ARBITRUM_ONE', tokenAddresses: string[], holders: string[]): Promise<MultiTokenBalancesResult[]>;
|
|
41
|
+
export declare function getTokenBalancesWithMulticall(rpcUrl: string, chain: 'BSC' | 'BASE' | 'XLAYER' | 'MORPH' | 'ARBITRUM_ONE' | 'MONAD', tokenAddresses: string[], holders: string[]): Promise<MultiTokenBalancesResult[]>;
|
|
42
42
|
export declare function getTokenBalancesWithMulticall(rpcUrl: string, tokenAddresses: string[], holders: string[]): Promise<MultiTokenBalancesResult[]>;
|
|
43
43
|
export declare function getTokenBalancesWithMulticall(rpcUrl: string, chainId: number, tokenAddresses: string[], holders: string[]): Promise<MultiTokenBalancesResult[]>;
|
package/dist/utils/wallet.js
CHANGED
|
@@ -95,7 +95,8 @@ const MULTICALL3_BY_CHAIN = {
|
|
|
95
95
|
8453: DEFAULT_MULTICALL3, // Base
|
|
96
96
|
42161: DEFAULT_MULTICALL3, // Arbitrum One
|
|
97
97
|
196: DEFAULT_MULTICALL3, // X Layer
|
|
98
|
-
2818: DEFAULT_MULTICALL3 // Morph
|
|
98
|
+
2818: DEFAULT_MULTICALL3, // Morph
|
|
99
|
+
143: DEFAULT_MULTICALL3, // ✅ Monad
|
|
99
100
|
};
|
|
100
101
|
export async function getTokenBalancesWithMulticall(rpcUrl, a, b, c) {
|
|
101
102
|
const provider = new JsonRpcProvider(rpcUrl);
|
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
|
-
}
|