four-flap-meme-sdk 1.5.23 → 1.5.24

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.
@@ -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
+ }
@@ -8,7 +8,7 @@
8
8
  * - initCode 生成
9
9
  */
10
10
  import { ethers, JsonRpcProvider, Wallet, Contract } from 'ethers';
11
- import type { UserOperation, SignedUserOp, AAAccount, XLayerConfig, GasPolicy, FixedGasConfig } from './types.js';
11
+ import type { UserOperation, SignedUserOp, AAAccount, XLayerConfig } from './types.js';
12
12
  import { BundlerClient } from './bundler.js';
13
13
  /**
14
14
  * AA 账户管理器
@@ -33,11 +33,6 @@ export declare class AAAccountManager {
33
33
  private paymasterData?;
34
34
  private gasLimitMultiplier;
35
35
  private senderCache;
36
- private deployedSenderSet;
37
- private feeDataCache?;
38
- private readonly feeDataCacheTtlMs;
39
- private defaultGasPolicy?;
40
- private defaultFixedGas?;
41
36
  constructor(config?: XLayerConfig);
42
37
  /**
43
38
  * 获取 Provider
@@ -132,23 +127,6 @@ export declare class AAAccountManager {
132
127
  userOp: UserOperation;
133
128
  prefundWei: bigint;
134
129
  }>;
135
- /**
136
- * 构建未签名的 UserOperation(固定 Gas,不做任何 estimate)
137
- *
138
- * 适用于大规模(1000 地址)场景:尽量减少 RPC 调用量。
139
- */
140
- buildUserOpWithFixedGas(params: {
141
- ownerWallet: Wallet;
142
- sender: string;
143
- callData: string;
144
- nonce: bigint;
145
- initCode?: string;
146
- deployed?: boolean;
147
- fixedGas?: FixedGasConfig;
148
- }): Promise<{
149
- userOp: UserOperation;
150
- prefundWei: bigint;
151
- }>;
152
130
  /**
153
131
  * 签名 UserOperation
154
132
  */
@@ -160,8 +138,6 @@ export declare class AAAccountManager {
160
138
  ownerWallet: Wallet;
161
139
  callData: string;
162
140
  value?: bigint;
163
- gasPolicy?: GasPolicy;
164
- fixedGas?: FixedGasConfig;
165
141
  useBundlerEstimate?: boolean;
166
142
  callGasLimit?: bigint;
167
143
  }): Promise<SignedUserOp>;
@@ -183,13 +159,6 @@ export declare class AAAccountManager {
183
159
  */
184
160
  getMultipleAccountInfo(ownerAddresses: string[]): Promise<AAAccount[]>;
185
161
  }
186
- /**
187
- * ERC-4337 v0.6 userOpHash 本地计算
188
- *
189
- * pack 规则(v0.6):对 (sender,nonce,keccak(initCode),keccak(callData),callGasLimit,verificationGasLimit,preVerificationGas,maxFeePerGas,maxPriorityFeePerGas,keccak(paymasterAndData))
190
- * 做 abi.encode 后 keccak 得到 packHash;再 abi.encode(packHash, entryPoint, chainId) 后 keccak 得到 userOpHash。
191
- */
192
- export declare function computeUserOpHashV06(userOp: UserOperation, entryPoint: string, chainId: number | bigint): string;
193
162
  /**
194
163
  * 编码 SimpleAccount.execute 调用
195
164
  */
@@ -8,7 +8,7 @@
8
8
  * - initCode 生成
9
9
  */
10
10
  import { ethers, JsonRpcProvider, Wallet, Contract, Interface } from 'ethers';
11
- import { XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, DEFAULT_SALT, DEFAULT_GAS_PRICE, DEFAULT_CALL_GAS_LIMIT_SELL, GAS_LIMIT_MULTIPLIER, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, FACTORY_ABI, ENTRYPOINT_ABI, SIMPLE_ACCOUNT_ABI, ZERO_ADDRESS, MULTICALL3, } from './constants.js';
11
+ import { XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, DEFAULT_SALT, DEFAULT_GAS_PRICE, GAS_LIMIT_MULTIPLIER, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, FACTORY_ABI, ENTRYPOINT_ABI, SIMPLE_ACCOUNT_ABI, ZERO_ADDRESS, MULTICALL3, } from './constants.js';
12
12
  import { BundlerClient } from './bundler.js';
13
13
  import { mapWithConcurrency } from '../utils/concurrency.js';
14
14
  // ============================================================================
@@ -26,16 +26,12 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
26
26
  export class AAAccountManager {
27
27
  constructor(config = {}) {
28
28
  this.senderCache = new Map(); // key: ownerLower -> sender
29
- // PERF: 本文件的改造会尽量减少逐地址 RPC 调用
30
- this.deployedSenderSet = new Set(); // key: senderLower(只缓存 deployed=true)
31
- this.feeDataCacheTtlMs = 1200;
32
29
  this.chainId = config.chainId ?? XLAYER_CHAIN_ID;
33
30
  const rpcUrl = config.rpcUrl ?? DEFAULT_RPC_URL;
34
31
  this.rpcUrl = rpcUrl;
35
- this.provider = new JsonRpcProvider(rpcUrl, { chainId: this.chainId, name: 'xlayer' }, {
36
- // 防止 ethers 自动攒太大的 JSON-RPC batch(XLayer 节点对“大 batch”很敏感)
37
- batchMaxCount: 20,
38
- batchStallTime: 30,
32
+ this.provider = new JsonRpcProvider(rpcUrl, {
33
+ chainId: this.chainId,
34
+ name: 'xlayer',
39
35
  });
40
36
  this.factoryAddress = config.factory ?? SIMPLE_ACCOUNT_FACTORY;
41
37
  this.entryPointAddress = config.entryPoint ?? ENTRYPOINT_V06;
@@ -43,9 +39,6 @@ export class AAAccountManager {
43
39
  this.paymaster = config.paymaster;
44
40
  this.paymasterData = config.paymasterData;
45
41
  this.gasLimitMultiplier = config.gasLimitMultiplier ?? GAS_LIMIT_MULTIPLIER;
46
- // 默认 fixed:最大化降低 RPC / bundler 请求;如需更稳可显式传 bundlerEstimate
47
- this.defaultGasPolicy = config.gasPolicy ?? 'fixed';
48
- this.defaultFixedGas = config.fixedGas;
49
42
  this.factory = new Contract(this.factoryAddress, FACTORY_ABI, this.provider);
50
43
  this.entryPoint = new Contract(this.entryPointAddress, ENTRYPOINT_ABI, this.provider);
51
44
  this.bundler = new BundlerClient({
@@ -83,14 +76,7 @@ export class AAAccountManager {
83
76
  * 获取当前 Fee Data
84
77
  */
85
78
  async getFeeData() {
86
- // 费率在“批量构建 userOp”时会被大量复用;加短 TTL 缓存可显著减少 RPC
87
- const now = Date.now();
88
- const hit = this.feeDataCache;
89
- if (hit && hit.expiresAt > now)
90
- return hit.value;
91
- const value = await this.provider.getFeeData();
92
- this.feeDataCache = { expiresAt: now + this.feeDataCacheTtlMs, value };
93
- return value;
79
+ return await this.provider.getFeeData();
94
80
  }
95
81
  /**
96
82
  * 预测 AA Sender 地址
@@ -289,7 +275,7 @@ export class AAAccountManager {
289
275
  * 构建未签名的 UserOperation(使用 Bundler 估算 Gas)
290
276
  */
291
277
  async buildUserOpWithBundlerEstimate(params) {
292
- const feeData = await this.getFeeData();
278
+ const feeData = await this.provider.getFeeData();
293
279
  const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
294
280
  const paymasterAndData = this.buildPaymasterAndData();
295
281
  // 构建初步 UserOp(Gas 字段为 0,等待估算)
@@ -338,7 +324,7 @@ export class AAAccountManager {
338
324
  if (params.ops.length === 0) {
339
325
  return { userOps: [], prefundWeis: [] };
340
326
  }
341
- const feeData = await this.getFeeData();
327
+ const feeData = await this.provider.getFeeData();
342
328
  const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
343
329
  const paymasterAndData = this.buildPaymasterAndData();
344
330
  // 先构建 skeleton(Gas=0),然后批量估算
@@ -389,7 +375,7 @@ export class AAAccountManager {
389
375
  * 适用于无法通过 Bundler 估算的场景(如尚未授权的 sell 操作)
390
376
  */
391
377
  async buildUserOpWithLocalEstimate(params) {
392
- const feeData = await this.getFeeData();
378
+ const feeData = await this.provider.getFeeData();
393
379
  const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
394
380
  const paymasterAndData = this.buildPaymasterAndData();
395
381
  // 估算 callGasLimit
@@ -431,43 +417,11 @@ export class AAAccountManager {
431
417
  const prefundWei = paymasterAndData !== '0x' ? 0n : gasTotal * legacyGasPrice;
432
418
  return { userOp, prefundWei };
433
419
  }
434
- /**
435
- * 构建未签名的 UserOperation(固定 Gas,不做任何 estimate)
436
- *
437
- * 适用于大规模(1000 地址)场景:尽量减少 RPC 调用量。
438
- */
439
- async buildUserOpWithFixedGas(params) {
440
- const feeData = await this.getFeeData();
441
- const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
442
- const paymasterAndData = this.buildPaymasterAndData();
443
- const callGasLimit = params.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL;
444
- const verificationGasLimit = params.deployed
445
- ? (params.fixedGas?.verificationGasLimitDeployed ?? VERIFICATION_GAS_LIMIT_NORMAL)
446
- : (params.fixedGas?.verificationGasLimitUndeployed ?? VERIFICATION_GAS_LIMIT_DEPLOY);
447
- const preVerificationGas = params.fixedGas?.preVerificationGas ?? PRE_VERIFICATION_GAS;
448
- const userOp = {
449
- sender: params.sender,
450
- nonce: ethers.toBeHex(params.nonce),
451
- initCode: params.initCode ?? '0x',
452
- callData: params.callData,
453
- callGasLimit: ethers.toBeHex(callGasLimit),
454
- verificationGasLimit: ethers.toBeHex(verificationGasLimit),
455
- preVerificationGas: ethers.toBeHex(preVerificationGas),
456
- // XLayer 更像 legacy gasPrice 语义
457
- maxFeePerGas: ethers.toBeHex(legacyGasPrice),
458
- maxPriorityFeePerGas: ethers.toBeHex(legacyGasPrice),
459
- paymasterAndData,
460
- signature: '0x',
461
- };
462
- const gasTotal = callGasLimit + verificationGasLimit + preVerificationGas;
463
- const prefundWei = paymasterAndData !== '0x' ? 0n : gasTotal * legacyGasPrice;
464
- return { userOp, prefundWei };
465
- }
466
420
  /**
467
421
  * 签名 UserOperation
468
422
  */
469
423
  async signUserOp(userOp, ownerWallet) {
470
- const userOpHash = computeUserOpHashV06(userOp, this.entryPointAddress, this.chainId);
424
+ const userOpHash = await this.entryPoint.getUserOpHash(userOp);
471
425
  const signature = await ownerWallet.signMessage(ethers.getBytes(userOpHash));
472
426
  return {
473
427
  userOp: { ...userOp, signature },
@@ -492,25 +446,7 @@ export class AAAccountManager {
492
446
  };
493
447
  let userOp;
494
448
  let prefundWei;
495
- const policy = params.gasPolicy ??
496
- this.defaultGasPolicy ??
497
- (params.useBundlerEstimate === false ? 'localEstimate' : 'bundlerEstimate');
498
- if (policy === 'fixed') {
499
- const fixedGasMerged = {
500
- ...(this.defaultFixedGas ?? {}),
501
- ...(params.fixedGas ?? {}),
502
- ...(params.callGasLimit ? { callGasLimit: params.callGasLimit } : {}),
503
- };
504
- const fixedGas = Object.keys(fixedGasMerged).length > 0 ? fixedGasMerged : undefined;
505
- const result = await this.buildUserOpWithFixedGas({
506
- ...buildParams,
507
- deployed: accountInfo.deployed,
508
- fixedGas,
509
- });
510
- userOp = result.userOp;
511
- prefundWei = result.prefundWei;
512
- }
513
- else if (policy === 'bundlerEstimate') {
449
+ if (params.useBundlerEstimate !== false) {
514
450
  const result = await this.buildUserOpWithBundlerEstimate(buildParams);
515
451
  userOp = result.userOp;
516
452
  prefundWei = result.prefundWei;
@@ -584,133 +520,59 @@ export class AAAccountManager {
584
520
  catch { /* ignore */ }
585
521
  }
586
522
  }
587
- // 3) deployed(getCode):小分片 + 并发上限 + 缓存 deployed=true,避免 -32014 batch request too many”
588
- const deployed = new Array(senders.length).fill(false);
589
- const needCode = [];
590
- for (let i = 0; i < senders.length; i++) {
591
- const sender = senders[i];
592
- const key = sender.toLowerCase();
593
- if (this.deployedSenderSet.has(key)) {
594
- deployed[i] = true;
595
- continue;
596
- }
597
- needCode.push({ idx: i, sender, key });
598
- }
599
- if (needCode.length > 0) {
600
- const CHUNK = 20;
601
- const chunks = [];
602
- for (let c = 0; c < needCode.length; c += CHUNK)
603
- chunks.push(needCode.slice(c, c + CHUNK));
604
- const batchResults = await mapWithConcurrency(chunks, 2, async (chunk) => {
605
- try {
606
- const reqs = chunk.map((x) => ({ method: 'eth_getCode', params: [x.sender, 'latest'] }));
607
- const codes = await this.rpcBatch(reqs);
608
- return { ok: true, chunk, codes };
523
+ // 3) 批量 getCode + getBalance(JSON-RPC batch;分块避免请求体过大)
524
+ const codes = new Array(senders.length).fill('0x');
525
+ const balances = new Array(senders.length).fill(0n);
526
+ const CHUNK = 200;
527
+ for (let cursor = 0; cursor < senders.length; cursor += CHUNK) {
528
+ const slice = senders.slice(cursor, cursor + CHUNK);
529
+ try {
530
+ const reqs = [];
531
+ for (const s of slice) {
532
+ reqs.push({ method: 'eth_getCode', params: [s, 'latest'] });
609
533
  }
610
- catch (err) {
611
- return { ok: false, chunk, err };
534
+ for (const s of slice) {
535
+ reqs.push({ method: 'eth_getBalance', params: [s, 'latest'] });
612
536
  }
613
- });
614
- for (const r of batchResults) {
615
- if (r.ok) {
616
- for (let i = 0; i < r.chunk.length; i++) {
617
- const it = r.chunk[i];
618
- const code = r.codes[i];
619
- const isDeployed = !!code && code !== '0x';
620
- deployed[it.idx] = isDeployed;
621
- if (isDeployed)
622
- this.deployedSenderSet.add(it.key);
537
+ const results = await this.rpcBatch(reqs);
538
+ const codePart = results.slice(0, slice.length);
539
+ const balPart = results.slice(slice.length);
540
+ for (let i = 0; i < slice.length; i++) {
541
+ const idx = cursor + i;
542
+ const c = codePart[i];
543
+ const b = balPart[i];
544
+ if (typeof c === 'string')
545
+ codes[idx] = c;
546
+ try {
547
+ balances[idx] = typeof b === 'string' ? BigInt(b) : 0n;
548
+ }
549
+ catch {
550
+ balances[idx] = 0n;
623
551
  }
624
- continue;
625
- }
626
- // fallback:单请求但受控并发
627
- const fetched = await mapWithConcurrency(r.chunk, 4, async (it) => {
628
- const code = await this.provider.getCode(it.sender);
629
- return { it, code };
630
- });
631
- for (const f of fetched) {
632
- const isDeployed = !!f.code && f.code !== '0x';
633
- deployed[f.it.idx] = isDeployed;
634
- if (isDeployed)
635
- this.deployedSenderSet.add(f.it.key);
636
552
  }
637
553
  }
638
- }
639
- // 4) balance(getEthBalance):用 Multicall3.getEthBalance 分片批量查询 OKB(减少 N 次 eth_getBalance)
640
- const balances = new Array(senders.length).fill(0n);
641
- const ethBalIface = new Interface(['function getEthBalance(address addr) view returns (uint256)']);
642
- const balCalls = senders.map((sender) => ({
643
- target: MULTICALL3,
644
- allowFailure: true,
645
- callData: ethBalIface.encodeFunctionData('getEthBalance', [sender]),
646
- }));
647
- const BAL_BATCH = 300;
648
- try {
649
- for (let cursor = 0; cursor < balCalls.length; cursor += BAL_BATCH) {
650
- const sliceCalls = balCalls.slice(cursor, cursor + BAL_BATCH);
651
- const res = await this.multicallAggregate3({ calls: sliceCalls });
652
- for (let i = 0; i < res.length; i++) {
653
- const r = res[i];
554
+ catch {
555
+ // fallback:节点不支持 batch 时,改为并发单请求(控制并发)
556
+ const fetched = await mapWithConcurrency(slice, 8, async (s) => {
557
+ const [code, bal] = await Promise.all([this.provider.getCode(s), this.provider.getBalance(s)]);
558
+ return { s, code, bal };
559
+ });
560
+ for (let i = 0; i < fetched.length; i++) {
654
561
  const idx = cursor + i;
655
- if (!r?.success || !r.returnData || r.returnData === '0x')
656
- continue;
657
- try {
658
- const decoded = ethBalIface.decodeFunctionResult('getEthBalance', r.returnData);
659
- balances[idx] = BigInt(decoded?.[0] ?? 0n);
660
- }
661
- catch { /* ignore */ }
562
+ codes[idx] = fetched[i].code ?? '0x';
563
+ balances[idx] = fetched[i].bal ?? 0n;
662
564
  }
663
565
  }
664
566
  }
665
- catch {
666
- const fetched = await mapWithConcurrency(senders, 6, async (s) => await this.provider.getBalance(s));
667
- for (let i = 0; i < fetched.length; i++)
668
- balances[i] = fetched[i] ?? 0n;
669
- }
670
567
  return owners.map((owner, i) => ({
671
568
  owner,
672
569
  sender: senders[i],
673
- deployed: deployed[i] ?? false,
570
+ deployed: codes[i] != null && String(codes[i]) !== '0x',
674
571
  balance: balances[i] ?? 0n,
675
572
  nonce: nonces[i] ?? 0n,
676
573
  }));
677
574
  }
678
575
  }
679
- /**
680
- * ERC-4337 v0.6 userOpHash 本地计算
681
- *
682
- * pack 规则(v0.6):对 (sender,nonce,keccak(initCode),keccak(callData),callGasLimit,verificationGasLimit,preVerificationGas,maxFeePerGas,maxPriorityFeePerGas,keccak(paymasterAndData))
683
- * 做 abi.encode 后 keccak 得到 packHash;再 abi.encode(packHash, entryPoint, chainId) 后 keccak 得到 userOpHash。
684
- */
685
- export function computeUserOpHashV06(userOp, entryPoint, chainId) {
686
- const coder = ethers.AbiCoder.defaultAbiCoder();
687
- const packed = coder.encode([
688
- 'address',
689
- 'uint256',
690
- 'bytes32',
691
- 'bytes32',
692
- 'uint256',
693
- 'uint256',
694
- 'uint256',
695
- 'uint256',
696
- 'uint256',
697
- 'bytes32',
698
- ], [
699
- userOp.sender,
700
- BigInt(userOp.nonce),
701
- ethers.keccak256(userOp.initCode ?? '0x'),
702
- ethers.keccak256(userOp.callData ?? '0x'),
703
- BigInt(userOp.callGasLimit),
704
- BigInt(userOp.verificationGasLimit),
705
- BigInt(userOp.preVerificationGas),
706
- BigInt(userOp.maxFeePerGas),
707
- BigInt(userOp.maxPriorityFeePerGas),
708
- ethers.keccak256(userOp.paymasterAndData ?? '0x'),
709
- ]);
710
- const packHash = ethers.keccak256(packed);
711
- const enc = coder.encode(['bytes32', 'address', 'uint256'], [packHash, entryPoint, BigInt(chainId)]);
712
- return ethers.keccak256(enc);
713
- }
714
576
  // ============================================================================
715
577
  // 工具函数
716
578
  // ============================================================================