four-flap-meme-sdk 1.5.20 → 1.5.22

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.
@@ -6,7 +6,7 @@ export * from './config.js';
6
6
  export * from './types.js';
7
7
  export { createTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle, batchSellWithBundleMerkle } from './core.js';
8
8
  export { fourPrivateBuyMerkle, fourPrivateSellMerkle, fourBatchPrivateBuyMerkle, fourBatchPrivateSellMerkle } from './private.js';
9
- export { disperseWithBundleMerkle, sweepWithBundleMerkle } from './utils.js';
9
+ export { disperseWithBundleMerkle, sweepWithBundleMerkle, pairwiseTransferWithBundleMerkle } from './utils.js';
10
10
  export { fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch } from './pancake-proxy.js';
11
11
  export { approveFourTokenManagerBatch, type ApproveFourTokenManagerBatchParams, type ApproveFourTokenManagerBatchResult } from './approve-tokenmanager.js';
12
12
  export { submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, type MerkleSubmitConfig, type SubmitBundleResult, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, submitDirectToRpc, submitDirectToRpcSequential, // ✅ 新增:顺序广播并等待确认(用于多跳)
@@ -10,7 +10,7 @@ export { createTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle, batchSellWith
10
10
  // 私有交易方法
11
11
  export { fourPrivateBuyMerkle, fourPrivateSellMerkle, fourBatchPrivateBuyMerkle, fourBatchPrivateSellMerkle } from './private.js';
12
12
  // 工具方法
13
- export { disperseWithBundleMerkle, sweepWithBundleMerkle } from './utils.js';
13
+ export { disperseWithBundleMerkle, sweepWithBundleMerkle, pairwiseTransferWithBundleMerkle } from './utils.js';
14
14
  // PancakeSwapProxy 代理交易方法
15
15
  export { fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch } from './pancake-proxy.js';
16
16
  // TokenManager 授权方法
@@ -337,6 +337,30 @@ export type SweepSignParams = {
337
337
  * 包含 hopWallets 用于返回生成的多跳钱包私钥
338
338
  */
339
339
  export type SweepMerkleResult = MerkleSignedResult;
340
+ /**
341
+ * 多对多(地址一一对应)转账参数(仅签名版本)
342
+ * - senderPrivateKeys / receiverAddresses 必须等长
343
+ * - amount / amounts 二选一(amounts 优先)
344
+ * - 支持多跳:hopCount 可为 number 或逐对数组
345
+ * - 支持利润刮取:利润由第一个 sender 支付(作为额外费用,不扣减每对转账金额)
346
+ */
347
+ export type PairwiseTransferSignParams = {
348
+ senderPrivateKeys: string[];
349
+ receiverAddresses: string[];
350
+ amount?: AmountLike;
351
+ amounts?: AmountLike[];
352
+ tokenAddress?: string;
353
+ tokenDecimals?: number;
354
+ hopCount?: number | number[];
355
+ hopPrivateKeys?: string[][];
356
+ config: FourSignConfig;
357
+ /** ✅ 起始 nonce(仅对第一个 sender 生效,用于并行调用避免 nonce 冲突) */
358
+ startNonce?: number;
359
+ tokenPoolType?: TokenPoolType;
360
+ quoteToken?: QuoteTokenType;
361
+ userType?: UserType;
362
+ };
363
+ export type PairwiseTransferMerkleResult = MerkleSignedResult;
340
364
  /** ✅ PancakeProxy 批量购买参数(Merkle 版本 - 完整) */
341
365
  export type FourPancakeProxyBatchBuyParams = {
342
366
  privateKeys: string[];
@@ -1,4 +1,4 @@
1
- import { DisperseSignParams, DisperseMerkleResult, SweepSignParams, SweepMerkleResult } from './types.js';
1
+ import { DisperseSignParams, DisperseMerkleResult, SweepSignParams, SweepMerkleResult, PairwiseTransferSignParams, PairwiseTransferMerkleResult } from './types.js';
2
2
  /**
3
3
  * 分发(仅签名版本 - 不依赖 Merkle)
4
4
  * ✅ 精简版:只负责签名交易,不提交到 Merkle
@@ -11,3 +11,10 @@ export declare function disperseWithBundleMerkle(params: DisperseSignParams): Pr
11
11
  * ✅ 优化版:支持 startNonce 参数避免并行调用时的 nonce 冲突
12
12
  */
13
13
  export declare function sweepWithBundleMerkle(params: SweepSignParams): Promise<SweepMerkleResult>;
14
+ /**
15
+ * 多对多(地址一一对应)转账(仅签名版本 - 不依赖 Merkle)
16
+ * - 支持原生币 / ERC20
17
+ * - 支持多跳(逐对)
18
+ * - 支持利润刮取:利润由第一个 sender 支付(作为额外费用,不扣减每对转账金额)
19
+ */
20
+ export declare function pairwiseTransferWithBundleMerkle(params: PairwiseTransferSignParams): Promise<PairwiseTransferMerkleResult>;
@@ -1389,3 +1389,290 @@ export async function sweepWithBundleMerkle(params) {
1389
1389
  } : undefined
1390
1390
  };
1391
1391
  }
1392
+ /**
1393
+ * 多对多(地址一一对应)转账(仅签名版本 - 不依赖 Merkle)
1394
+ * - 支持原生币 / ERC20
1395
+ * - 支持多跳(逐对)
1396
+ * - 支持利润刮取:利润由第一个 sender 支付(作为额外费用,不扣减每对转账金额)
1397
+ */
1398
+ export async function pairwiseTransferWithBundleMerkle(params) {
1399
+ const { senderPrivateKeys, receiverAddresses, amount, amounts, tokenAddress, tokenDecimals, hopCount = 0, hopPrivateKeys, config, startNonce, tokenPoolType = 'v2', quoteToken = 'native', userType = 'v0', } = params;
1400
+ if (!senderPrivateKeys || senderPrivateKeys.length === 0) {
1401
+ return { signedTransactions: [], hopWallets: undefined };
1402
+ }
1403
+ if (!receiverAddresses || receiverAddresses.length === 0) {
1404
+ return { signedTransactions: [], hopWallets: undefined };
1405
+ }
1406
+ if (senderPrivateKeys.length !== receiverAddresses.length) {
1407
+ throw new Error(`senderPrivateKeys length (${senderPrivateKeys.length}) must match receiverAddresses length (${receiverAddresses.length})`);
1408
+ }
1409
+ const pairCount = senderPrivateKeys.length;
1410
+ const normalizedAmounts = (() => {
1411
+ if (amounts && amounts.length > 0) {
1412
+ if (amounts.length !== pairCount) {
1413
+ throw new Error(`amounts length (${amounts.length}) must match pair count (${pairCount})`);
1414
+ }
1415
+ return amounts.map(a => (typeof a === 'bigint' ? a.toString() : String(a)));
1416
+ }
1417
+ if (amount !== undefined && String(amount).trim().length > 0) {
1418
+ const v = typeof amount === 'bigint' ? amount.toString() : String(amount);
1419
+ return new Array(pairCount).fill(v);
1420
+ }
1421
+ throw new Error('Either amount or amounts must be provided');
1422
+ })();
1423
+ const chainIdNum = config.chainId ?? 56;
1424
+ const chainName = chainIdNum === 143 ? 'Monad' : chainIdNum === 56 ? 'BSC' : `Chain-${chainIdNum}`;
1425
+ const provider = new ethers.JsonRpcProvider(config.rpcUrl, { chainId: chainIdNum, name: chainName });
1426
+ const txType = getTxType(config);
1427
+ const isNative = _isNativeTokenAddress(tokenAddress);
1428
+ const nonceManager = new NonceManager(provider);
1429
+ // hop 私钥链优先使用传入
1430
+ const providedHops = (() => {
1431
+ if (hopPrivateKeys && hopPrivateKeys.length > 0) {
1432
+ if (hopPrivateKeys.length !== pairCount) {
1433
+ throw new Error(`hopPrivateKeys length (${hopPrivateKeys.length}) must match pair count (${pairCount})`);
1434
+ }
1435
+ return hopPrivateKeys.every(h => h.length === 0) ? null : hopPrivateKeys;
1436
+ }
1437
+ return null;
1438
+ })();
1439
+ const preparedHops = providedHops ?? _generateHopWallets(pairCount, hopCount);
1440
+ const hasHops = preparedHops !== null;
1441
+ const maxHopCount = hasHops ? Math.max(...preparedHops.map(h => h.length)) : 0;
1442
+ const finalGasLimit = _calculateGasLimit(config, isNative, hasHops, maxHopCount);
1443
+ const nativeGasLimit = (config.prefer21000ForNative ?? false) ? 21000n : finalGasLimit;
1444
+ // 与 disperse 保持一致:多跳场景固定 gas limit
1445
+ const nativeTransferGasLimit = 21000n;
1446
+ const erc20TransferGasLimit = 65000n;
1447
+ const [gasPrice, decimals] = await Promise.all([
1448
+ getOptimizedGasPrice(provider, getGasPriceConfig(config)),
1449
+ isNative ? Promise.resolve(18) : Promise.resolve(tokenDecimals ?? await _getErc20DecimalsMerkle(provider, tokenAddress, chainIdNum))
1450
+ ]);
1451
+ const iface = isNative ? null : new ethers.Interface(['function transfer(address,uint256) returns (bool)']);
1452
+ // 利润:按每对金额计算,但由第一个 sender 支付(额外费用)
1453
+ const extractProfit = shouldExtractProfit(config);
1454
+ let totalAmountBeforeProfit = 0n;
1455
+ let totalTokenProfit = 0n;
1456
+ let totalProfitNative = 0n;
1457
+ const parsedAmountsWei = normalizedAmounts.map(v => {
1458
+ const amt = isNative ? ethers.parseEther(v) : ethers.parseUnits(v, decimals);
1459
+ totalAmountBeforeProfit += amt;
1460
+ if (extractProfit && amt > 0n) {
1461
+ const { profit } = calculateProfit(amt, userType);
1462
+ if (isNative)
1463
+ totalProfitNative += profit;
1464
+ else
1465
+ totalTokenProfit += profit;
1466
+ }
1467
+ return amt;
1468
+ });
1469
+ if (!isNative && extractProfit && totalTokenProfit > 0n) {
1470
+ totalProfitNative = await getTokenToNativeQuote(provider, tokenAddress, totalTokenProfit, chainIdNum, tokenPoolType, quoteToken, config.rpcUrl);
1471
+ }
1472
+ // 计算每个 sender 需要的 nonce 数量(聚合到地址)
1473
+ const senderWallets = senderPrivateKeys.map(pk => new Wallet(pk, provider));
1474
+ const senderAddrLowerList = senderWallets.map(w => w.address.toLowerCase());
1475
+ const firstSenderAddrLower = senderAddrLowerList[0];
1476
+ const nonceNeedBySender = new Map();
1477
+ for (let i = 0; i < pairCount; i++) {
1478
+ const addrLower = senderAddrLowerList[i];
1479
+ const hopLen = preparedHops ? preparedHops[i].length : 0;
1480
+ const perPairTxCount = (() => {
1481
+ if (isNative)
1482
+ return 1;
1483
+ return hopLen > 0 ? 2 : 1;
1484
+ })();
1485
+ const cur = nonceNeedBySender.get(addrLower);
1486
+ if (cur)
1487
+ cur.count += perPairTxCount;
1488
+ else
1489
+ nonceNeedBySender.set(addrLower, { wallet: senderWallets[i], count: perPairTxCount });
1490
+ }
1491
+ if (extractProfit && totalProfitNative > 0n) {
1492
+ const cur = nonceNeedBySender.get(firstSenderAddrLower);
1493
+ if (cur)
1494
+ cur.count += 1;
1495
+ }
1496
+ const nonceQueueBySender = new Map();
1497
+ for (const [addrLower, entry] of nonceNeedBySender.entries()) {
1498
+ const count = entry.count;
1499
+ const nonces = (addrLower === firstSenderAddrLower && startNonce !== undefined)
1500
+ ? Array.from({ length: count }, (_, i) => startNonce + i)
1501
+ : await nonceManager.getNextNonceBatch(entry.wallet, count);
1502
+ nonceQueueBySender.set(addrLower, nonces);
1503
+ }
1504
+ const popNonce = (addrLower) => {
1505
+ const q = nonceQueueBySender.get(addrLower);
1506
+ if (!q || q.length === 0)
1507
+ throw new Error(`nonce queue empty for sender ${addrLower}`);
1508
+ return q.shift();
1509
+ };
1510
+ const txsToSign = [];
1511
+ const nativeHopGasFee = nativeTransferGasLimit * gasPrice;
1512
+ const erc20HopGasFee = erc20TransferGasLimit * gasPrice;
1513
+ const nativeHopGasFeeForErc20 = nativeTransferGasLimit * gasPrice;
1514
+ for (let i = 0; i < pairCount; i++) {
1515
+ const senderWallet = senderWallets[i];
1516
+ const senderLower = senderAddrLowerList[i];
1517
+ const to = receiverAddresses[i];
1518
+ const amtWei = parsedAmountsWei[i];
1519
+ const hopChain = preparedHops ? preparedHops[i] : [];
1520
+ if (!hopChain || hopChain.length === 0) {
1521
+ const nonce = popNonce(senderLower);
1522
+ if (isNative) {
1523
+ txsToSign.push({
1524
+ wallet: senderWallet,
1525
+ tx: { to, value: amtWei, nonce, gasPrice, gasLimit: nativeGasLimit, chainId: chainIdNum, type: txType }
1526
+ });
1527
+ }
1528
+ else {
1529
+ const data = iface.encodeFunctionData('transfer', [to, amtWei]);
1530
+ txsToSign.push({
1531
+ wallet: senderWallet,
1532
+ tx: { to: tokenAddress, data, value: 0n, nonce, gasPrice, gasLimit: finalGasLimit, chainId: chainIdNum, type: txType }
1533
+ });
1534
+ }
1535
+ continue;
1536
+ }
1537
+ const fullChain = [senderWallet, ...hopChain.map(w => new Wallet(typeof w === 'string' ? w : w.privateKey, provider))];
1538
+ const addresses = [...fullChain.map(w => w.address), to];
1539
+ if (isNative) {
1540
+ for (let j = 0; j < addresses.length - 1; j++) {
1541
+ const fromWallet = fullChain[j];
1542
+ const toAddress = addresses[j + 1];
1543
+ const nonce = j === 0 ? popNonce(senderLower) : 0;
1544
+ const remainingHops = addresses.length - 2 - j;
1545
+ const additionalGas = nativeHopGasFee * BigInt(remainingHops);
1546
+ const transferValue = amtWei + additionalGas;
1547
+ txsToSign.push({
1548
+ wallet: fromWallet,
1549
+ tx: { to: toAddress, value: transferValue, nonce, gasPrice, gasLimit: nativeTransferGasLimit, chainId: chainIdNum, type: txType }
1550
+ });
1551
+ }
1552
+ }
1553
+ else {
1554
+ // ERC20 多跳:gas + ERC20 逐层传递(与 disperse 一致)
1555
+ const hopGasNeeds = [];
1556
+ for (let j = hopChain.length - 1; j >= 0; j--) {
1557
+ if (j === hopChain.length - 1)
1558
+ hopGasNeeds.unshift(erc20HopGasFee);
1559
+ else {
1560
+ const nextHopGas = hopGasNeeds[0];
1561
+ hopGasNeeds.unshift(nativeHopGasFeeForErc20 + erc20HopGasFee + nextHopGas);
1562
+ }
1563
+ }
1564
+ // sender -> firstHop:转 gas
1565
+ txsToSign.push({
1566
+ wallet: senderWallet,
1567
+ tx: {
1568
+ to: fullChain[1].address,
1569
+ value: hopGasNeeds[0],
1570
+ nonce: popNonce(senderLower),
1571
+ gasPrice,
1572
+ gasLimit: nativeTransferGasLimit,
1573
+ chainId: chainIdNum,
1574
+ type: txType
1575
+ }
1576
+ });
1577
+ // sender -> firstHop:转 ERC20
1578
+ const mainToFirstHopData = iface.encodeFunctionData('transfer', [fullChain[1].address, amtWei]);
1579
+ txsToSign.push({
1580
+ wallet: senderWallet,
1581
+ tx: {
1582
+ to: tokenAddress,
1583
+ data: mainToFirstHopData,
1584
+ value: 0n,
1585
+ nonce: popNonce(senderLower),
1586
+ gasPrice,
1587
+ gasLimit: erc20TransferGasLimit,
1588
+ chainId: chainIdNum,
1589
+ type: txType
1590
+ }
1591
+ });
1592
+ // hop wallets:逐层传递
1593
+ for (let j = 1; j < fullChain.length; j++) {
1594
+ const fromWallet = fullChain[j];
1595
+ const toAddress = addresses[j + 1];
1596
+ const isLastHop = j === fullChain.length - 1;
1597
+ if (!isLastHop) {
1598
+ const gasToTransfer = hopGasNeeds[j];
1599
+ txsToSign.push({
1600
+ wallet: fromWallet,
1601
+ tx: {
1602
+ to: toAddress,
1603
+ value: gasToTransfer,
1604
+ nonce: 0,
1605
+ gasPrice,
1606
+ gasLimit: nativeTransferGasLimit,
1607
+ chainId: chainIdNum,
1608
+ type: txType
1609
+ }
1610
+ });
1611
+ const erc20Data = iface.encodeFunctionData('transfer', [toAddress, amtWei]);
1612
+ txsToSign.push({
1613
+ wallet: fromWallet,
1614
+ tx: {
1615
+ to: tokenAddress,
1616
+ data: erc20Data,
1617
+ value: 0n,
1618
+ nonce: 1,
1619
+ gasPrice,
1620
+ gasLimit: erc20TransferGasLimit,
1621
+ chainId: chainIdNum,
1622
+ type: txType
1623
+ }
1624
+ });
1625
+ }
1626
+ else {
1627
+ const erc20Data = iface.encodeFunctionData('transfer', [toAddress, amtWei]);
1628
+ txsToSign.push({
1629
+ wallet: fromWallet,
1630
+ tx: {
1631
+ to: tokenAddress,
1632
+ data: erc20Data,
1633
+ value: 0n,
1634
+ nonce: 0,
1635
+ gasPrice,
1636
+ gasLimit: erc20TransferGasLimit,
1637
+ chainId: chainIdNum,
1638
+ type: txType
1639
+ }
1640
+ });
1641
+ }
1642
+ }
1643
+ }
1644
+ }
1645
+ const signedTxs = await Promise.all(txsToSign.map(({ wallet, tx }) => wallet.signTransaction(tx)));
1646
+ // 利润多跳(固定 2 hop)
1647
+ let profitHopWallets;
1648
+ if (extractProfit && totalProfitNative > 0n) {
1649
+ const firstSenderWallet = senderWallets[0];
1650
+ const profitNonce = popNonce(firstSenderAddrLower);
1651
+ const profitHopResult = await buildProfitHopTransactions({
1652
+ provider,
1653
+ payerWallet: firstSenderWallet,
1654
+ profitAmount: totalProfitNative,
1655
+ profitRecipient: getProfitRecipient(),
1656
+ hopCount: PROFIT_HOP_COUNT,
1657
+ gasPrice,
1658
+ chainId: chainIdNum,
1659
+ txType,
1660
+ startNonce: profitNonce
1661
+ });
1662
+ signedTxs.push(...profitHopResult.signedTransactions);
1663
+ profitHopWallets = profitHopResult.hopWallets;
1664
+ }
1665
+ return {
1666
+ signedTransactions: signedTxs,
1667
+ hopWallets: preparedHops || undefined,
1668
+ profitHopWallets,
1669
+ metadata: extractProfit ? {
1670
+ pairCount,
1671
+ isNative,
1672
+ tokenAddress: isNative ? undefined : tokenAddress,
1673
+ totalAmount: isNative ? ethers.formatEther(totalAmountBeforeProfit) : ethers.formatUnits(totalAmountBeforeProfit, decimals),
1674
+ profitAmount: ethers.formatEther(totalProfitNative),
1675
+ profitRecipient: getProfitRecipient(),
1676
+ } : undefined
1677
+ };
1678
+ }
@@ -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
+ }
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export { createTokenWithBundleBuy as flapCreateTokenWithBundleBuy, batchBuyWithB
28
28
  export { fourPrivateBuy, fourPrivateSell, fourBatchPrivateBuy, fourBatchPrivateSell, type FourPrivateBuyParams, type FourPrivateSellParams, type FourBatchPrivateBuyParams, type FourBatchPrivateSellParams } from './contracts/tm-bundle.js';
29
29
  export { flapPrivateBuy, flapPrivateSell, flapBatchPrivateBuy, flapBatchPrivateSell, type FlapPrivateBuyParams, type FlapPrivateSellParams, type FlapBatchPrivateBuyParams, type FlapBatchPrivateSellParams, type FlapBatchPrivateSellResult } from './flap/portal-bundle.js';
30
30
  export { createTokenWithBundleBuyMerkle as flapCreateTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle as flapBatchBuyWithBundleMerkle, batchSellWithBundleMerkle as flapBatchSellWithBundleMerkle, flapPrivateBuyMerkle, flapPrivateSellMerkle, flapBatchPrivateBuyMerkle, flapBatchPrivateSellMerkle, pancakeProxyBatchBuyMerkle, pancakeProxyBatchSellMerkle, approvePancakeProxy, approvePancakeProxyBatch, flapDisperseWithBundleMerkle, flapSweepWithBundleMerkle, type FlapBundleMerkleConfig, type FlapSignConfig, type FlapChainForMerkleBundle, type FlapCreateWithBundleBuySignParams, type FlapCreateWithBundleBuyMerkleParams, type FlapCreateWithBundleBuyMerkleResult, type FlapBatchBuySignParams, type FlapBatchBuyMerkleParams, type FlapBatchBuyMerkleResult, type FlapBatchSellSignParams, type FlapBatchSellMerkleParams, type FlapBatchSellMerkleResult, type MerkleTransactionStatus, type MerkleBundleStatus, type FlapPrivateBuyMerkleParams, type FlapPrivateSellMerkleParams, type FlapBatchPrivateBuyMerkleParams, type FlapBatchPrivateSellMerkleParams, type FlapBatchPrivateMerkleResult, type FlapPrivateTransactionResult, type PancakeProxyBatchBuyParams, type PancakeProxyBatchBuyResult, type PancakeProxyBatchSellParams, type PancakeProxyBatchSellResult, type PancakeProxyApprovalParams, type PancakeProxyApprovalBatchParams, type PancakeProxyApprovalBatchResult, type FlapDisperseSignParams, type FlapDisperseMerkleResult, type FlapSweepSignParams, type FlapSweepMerkleResult } from './flap/portal-bundle-merkle/index.js';
31
- export { createTokenWithBundleBuyMerkle as fourCreateTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle as fourBatchBuyWithBundleMerkle, batchSellWithBundleMerkle as fourBatchSellWithBundleMerkle, fourPrivateBuyMerkle, fourPrivateSellMerkle, fourBatchPrivateBuyMerkle, fourBatchPrivateSellMerkle, disperseWithBundleMerkle, sweepWithBundleMerkle, fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch, approveFourTokenManagerBatch, submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type MerkleSubmitConfig, type SubmitBundleResult, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, type FourBundleMerkleConfig, type FourSignConfig, type FourCreateWithBundleBuySignParams, type FourBatchBuySignParams, type FourBatchSellSignParams, type FourPrivateBuySignParams, type FourPrivateSellSignParams, type FourBatchPrivateBuySignParams, type FourBatchPrivateSellSignParams, type FourPancakeProxyBatchBuySignParams, type FourPancakeProxyBatchSellSignParams, type DisperseSignParams as FourDisperseSignParams, type SweepSignParams as FourSweepSignParams, type FourCreateWithBundleBuyMerkleParams, type FourCreateWithBundleBuyMerkleResult, type FourBatchBuyMerkleParams, type FourBatchBuyMerkleResult, type FourBatchSellMerkleParams, type FourBatchSellMerkleResult, type FourPrivateBuyMerkleParams, type FourPrivateSellMerkleParams, type FourBatchPrivateBuyMerkleParams, type FourBatchPrivateSellMerkleParams, type FourBatchPrivateMerkleResult, type FourPrivateTransactionResult, type DisperseMerkleParams, type DisperseMerkleResult, type SweepMerkleParams, type SweepMerkleResult, type FourPancakeProxyBatchBuyParams, type FourPancakeProxyBatchBuyResult, type FourPancakeProxyBatchSellParams, type FourPancakeProxyBatchSellResult, type FourPancakeProxyApprovalParams, type FourPancakeProxyApprovalBatchParams, type FourPancakeProxyApprovalBatchResult, type ApproveFourTokenManagerBatchParams, type ApproveFourTokenManagerBatchResult } from './contracts/tm-bundle-merkle/index.js';
31
+ export { createTokenWithBundleBuyMerkle as fourCreateTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle as fourBatchBuyWithBundleMerkle, batchSellWithBundleMerkle as fourBatchSellWithBundleMerkle, fourPrivateBuyMerkle, fourPrivateSellMerkle, fourBatchPrivateBuyMerkle, fourBatchPrivateSellMerkle, disperseWithBundleMerkle, sweepWithBundleMerkle, pairwiseTransferWithBundleMerkle, fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch, approveFourTokenManagerBatch, submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel, type MerkleSubmitConfig, type SubmitBundleResult, type BlockRazorSubmitConfig, type BlockRazorSubmitResult, type FourBundleMerkleConfig, type FourSignConfig, type FourCreateWithBundleBuySignParams, type FourBatchBuySignParams, type FourBatchSellSignParams, type FourPrivateBuySignParams, type FourPrivateSellSignParams, type FourBatchPrivateBuySignParams, type FourBatchPrivateSellSignParams, type FourPancakeProxyBatchBuySignParams, type FourPancakeProxyBatchSellSignParams, type DisperseSignParams as FourDisperseSignParams, type SweepSignParams as FourSweepSignParams, type FourCreateWithBundleBuyMerkleParams, type FourCreateWithBundleBuyMerkleResult, type FourBatchBuyMerkleParams, type FourBatchBuyMerkleResult, type FourBatchSellMerkleParams, type FourBatchSellMerkleResult, type FourPrivateBuyMerkleParams, type FourPrivateSellMerkleParams, type FourBatchPrivateBuyMerkleParams, type FourBatchPrivateSellMerkleParams, type FourBatchPrivateMerkleResult, type FourPrivateTransactionResult, type DisperseMerkleParams, type DisperseMerkleResult, type SweepMerkleParams, type SweepMerkleResult, type FourPancakeProxyBatchBuyParams, type FourPancakeProxyBatchBuyResult, type FourPancakeProxyBatchSellParams, type FourPancakeProxyBatchSellResult, type FourPancakeProxyApprovalParams, type FourPancakeProxyApprovalBatchParams, type FourPancakeProxyApprovalBatchResult, type ApproveFourTokenManagerBatchParams, type ApproveFourTokenManagerBatchResult } from './contracts/tm-bundle-merkle/index.js';
32
32
  export { PinataClient, type PinataConfig } from './flap/pinata.js';
33
33
  export { pinFileToIPFSWithJWT, pinImageByPath, pinFileToIPFSWithJWTWeb, pinDataURLWithJWTWeb, dataURLToBlob, type PinataPinResp } from './flap/pinata.js';
34
34
  export { generateWallets, type GeneratedWallet } from './utils/wallet.js';
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ export { createTokenWithBundleBuy as flapCreateTokenWithBundleBuy, batchBuyWithB
47
47
  export { fourPrivateBuy, fourPrivateSell, fourBatchPrivateBuy, fourBatchPrivateSell } from './contracts/tm-bundle.js';
48
48
  export { flapPrivateBuy, flapPrivateSell, flapBatchPrivateBuy, flapBatchPrivateSell } from './flap/portal-bundle.js';
49
49
  export { createTokenWithBundleBuyMerkle as flapCreateTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle as flapBatchBuyWithBundleMerkle, batchSellWithBundleMerkle as flapBatchSellWithBundleMerkle, flapPrivateBuyMerkle, flapPrivateSellMerkle, flapBatchPrivateBuyMerkle, flapBatchPrivateSellMerkle, pancakeProxyBatchBuyMerkle, pancakeProxyBatchSellMerkle, approvePancakeProxy, approvePancakeProxyBatch, flapDisperseWithBundleMerkle, flapSweepWithBundleMerkle } from './flap/portal-bundle-merkle/index.js';
50
- export { createTokenWithBundleBuyMerkle as fourCreateTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle as fourBatchBuyWithBundleMerkle, batchSellWithBundleMerkle as fourBatchSellWithBundleMerkle, fourPrivateBuyMerkle, fourPrivateSellMerkle, fourBatchPrivateBuyMerkle, fourBatchPrivateSellMerkle, disperseWithBundleMerkle, sweepWithBundleMerkle, fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch, approveFourTokenManagerBatch, submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel } from './contracts/tm-bundle-merkle/index.js';
50
+ export { createTokenWithBundleBuyMerkle as fourCreateTokenWithBundleBuyMerkle, batchBuyWithBundleMerkle as fourBatchBuyWithBundleMerkle, batchSellWithBundleMerkle as fourBatchSellWithBundleMerkle, fourPrivateBuyMerkle, fourPrivateSellMerkle, fourBatchPrivateBuyMerkle, fourBatchPrivateSellMerkle, disperseWithBundleMerkle, sweepWithBundleMerkle, pairwiseTransferWithBundleMerkle, fourPancakeProxyBatchBuyMerkle, fourPancakeProxyBatchSellMerkle, approveFourPancakeProxy, approveFourPancakeProxyBatch, approveFourTokenManagerBatch, submitBundleToMerkle, submitMultipleBundles, submitMultipleBundlesParallel, submitBundleToBlockRazor, submitMultipleBundlesToBlockRazor, submitMultipleBundlesToBlockRazorParallel } from './contracts/tm-bundle-merkle/index.js';
51
51
  export { PinataClient } from './flap/pinata.js';
52
52
  export { pinFileToIPFSWithJWT, pinImageByPath, pinFileToIPFSWithJWTWeb, pinDataURLWithJWTWeb, dataURLToBlob } from './flap/pinata.js';
53
53
  export { generateWallets } from './utils/wallet.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 并发映射工具(SDK 内部使用)
3
+ * - 用于将可并行的异步任务按并发上限执行,避免 RPC/Bundler 限流或超时
4
+ */
5
+ export declare function mapWithConcurrency<T, R>(items: T[], limit: number, fn: (item: T, idx: number) => Promise<R>): Promise<R[]>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 并发映射工具(SDK 内部使用)
3
+ * - 用于将可并行的异步任务按并发上限执行,避免 RPC/Bundler 限流或超时
4
+ */
5
+ export async function mapWithConcurrency(items, limit, fn) {
6
+ const res = new Array(items.length);
7
+ let idx = 0;
8
+ const n = Math.max(1, Math.min(Number(limit || 1), items.length || 1));
9
+ const workers = Array.from({ length: n }).map(async () => {
10
+ while (true) {
11
+ const i = idx++;
12
+ if (i >= items.length)
13
+ return;
14
+ res[i] = await fn(items[i], i);
15
+ }
16
+ });
17
+ await Promise.all(workers);
18
+ return res;
19
+ }
@@ -21,6 +21,7 @@ import { BundlerClient } from './bundler.js';
21
21
  */
22
22
  export declare class AAAccountManager {
23
23
  private provider;
24
+ private rpcUrl;
24
25
  private chainId;
25
26
  private factory;
26
27
  private entryPoint;
@@ -31,6 +32,7 @@ export declare class AAAccountManager {
31
32
  private paymaster?;
32
33
  private paymasterData?;
33
34
  private gasLimitMultiplier;
35
+ private senderCache;
34
36
  constructor(config?: XLayerConfig);
35
37
  /**
36
38
  * 获取 Provider
@@ -60,6 +62,14 @@ export declare class AAAccountManager {
60
62
  * @returns 预测的 Sender 地址
61
63
  */
62
64
  predictSenderAddress(ownerAddress: string, salt?: bigint): Promise<string>;
65
+ private rpcBatch;
66
+ private multicallAggregate3;
67
+ private predictSendersByOwnersFast;
68
+ /**
69
+ * 批量预测 Sender 地址(只做地址预测,不额外查询 nonce/balance/code)
70
+ * - 用于“生成/导入钱包”场景:更快、更省 RPC
71
+ */
72
+ predictSendersBatch(ownerAddresses: string[], salt?: bigint): Promise<string[]>;
63
73
  /**
64
74
  * 获取完整的 AA 账户信息
65
75
  */