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.
@@ -8,8 +8,9 @@
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, GAS_LIMIT_MULTIPLIER, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, FACTORY_ABI, ENTRYPOINT_ABI, SIMPLE_ACCOUNT_ABI, } 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
+ import { mapWithConcurrency } from '../utils/concurrency.js';
13
14
  // ============================================================================
14
15
  // AA 账户管理器
15
16
  // ============================================================================
@@ -24,8 +25,10 @@ import { BundlerClient } from './bundler.js';
24
25
  */
25
26
  export class AAAccountManager {
26
27
  constructor(config = {}) {
28
+ this.senderCache = new Map(); // key: ownerLower -> sender
27
29
  this.chainId = config.chainId ?? XLAYER_CHAIN_ID;
28
30
  const rpcUrl = config.rpcUrl ?? DEFAULT_RPC_URL;
31
+ this.rpcUrl = rpcUrl;
29
32
  this.provider = new JsonRpcProvider(rpcUrl, {
30
33
  chainId: this.chainId,
31
34
  name: 'xlayer',
@@ -88,6 +91,148 @@ export class AAAccountManager {
88
91
  // 这比 getAddress 更可靠(某些链上 getAddress 有问题)
89
92
  return await this.factory.createAccount.staticCall(ownerAddress, useSalt);
90
93
  }
94
+ // ============================================================================
95
+ // 内部:RPC batch / multicall(性能优化)
96
+ // ============================================================================
97
+ async rpcBatch(items, timeoutMs = 20000) {
98
+ if (items.length === 0)
99
+ return [];
100
+ const controller = new AbortController();
101
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
102
+ try {
103
+ const body = items.map((it, i) => ({
104
+ jsonrpc: '2.0',
105
+ id: i + 1,
106
+ method: it.method,
107
+ params: it.params,
108
+ }));
109
+ const res = await fetch(this.rpcUrl, {
110
+ method: 'POST',
111
+ headers: { 'content-type': 'application/json' },
112
+ body: JSON.stringify(body),
113
+ signal: controller.signal,
114
+ });
115
+ const text = await res.text();
116
+ let data;
117
+ try {
118
+ data = JSON.parse(text);
119
+ }
120
+ catch {
121
+ throw new Error(`RPC batch 非 JSON 响应 (HTTP ${res.status}): ${text.slice(0, 400)}`);
122
+ }
123
+ if (!res.ok) {
124
+ throw new Error(`RPC batch HTTP ${res.status}: ${JSON.stringify(data).slice(0, 800)}`);
125
+ }
126
+ if (!Array.isArray(data)) {
127
+ throw new Error(`RPC batch 响应格式错误: ${JSON.stringify(data).slice(0, 800)}`);
128
+ }
129
+ const byId = new Map();
130
+ for (const r of data) {
131
+ const id = Number(r?.id);
132
+ if (r?.error)
133
+ throw new Error(`RPC batch error: ${JSON.stringify(r.error)}`);
134
+ if (Number.isFinite(id))
135
+ byId.set(id, r?.result);
136
+ }
137
+ return items.map((_, i) => byId.get(i + 1));
138
+ }
139
+ finally {
140
+ clearTimeout(timer);
141
+ }
142
+ }
143
+ async multicallAggregate3(params) {
144
+ const mcIface = new Interface([
145
+ 'function aggregate3((address target,bool allowFailure,bytes callData)[] calls) view returns ((bool success,bytes returnData)[] returnData)',
146
+ ]);
147
+ const data = mcIface.encodeFunctionData('aggregate3', [params.calls]);
148
+ const raw = await this.provider.call({ to: MULTICALL3, data });
149
+ const decoded = mcIface.decodeFunctionResult('aggregate3', raw)?.[0];
150
+ return decoded || [];
151
+ }
152
+ async predictSendersByOwnersFast(ownerAddresses, salt) {
153
+ const useSalt = salt ?? this.salt;
154
+ const owners = ownerAddresses.map((a) => String(a || '').trim()).filter(Boolean);
155
+ if (owners.length === 0)
156
+ return [];
157
+ const out = new Array(owners.length).fill('');
158
+ const need = [];
159
+ // 缓存命中(owner->sender)
160
+ for (let i = 0; i < owners.length; i++) {
161
+ const key = owners[i].toLowerCase();
162
+ const hit = this.senderCache.get(key);
163
+ if (hit)
164
+ out[i] = hit;
165
+ else
166
+ need.push({ idx: i, owner: owners[i] });
167
+ }
168
+ if (need.length === 0)
169
+ return out;
170
+ const factoryIface = new Interface(FACTORY_ABI);
171
+ const calls = need.map((x) => ({
172
+ target: this.factoryAddress,
173
+ allowFailure: true,
174
+ callData: factoryIface.encodeFunctionData('getAddress', [x.owner, useSalt]),
175
+ }));
176
+ // 分批 multicall,避免 callData 过大
177
+ const BATCH = 300;
178
+ for (let cursor = 0; cursor < calls.length; cursor += BATCH) {
179
+ const sliceCalls = calls.slice(cursor, cursor + BATCH);
180
+ const sliceNeed = need.slice(cursor, cursor + BATCH);
181
+ try {
182
+ const res = await this.multicallAggregate3({ calls: sliceCalls });
183
+ for (let i = 0; i < res.length; i++) {
184
+ const r = res[i];
185
+ const { idx, owner } = sliceNeed[i];
186
+ if (!r?.success || !r.returnData || r.returnData === '0x')
187
+ continue;
188
+ try {
189
+ const decoded = factoryIface.decodeFunctionResult('getAddress', r.returnData);
190
+ const addr = String(decoded?.[0] || '').trim();
191
+ if (addr && addr !== ZERO_ADDRESS) {
192
+ out[idx] = addr;
193
+ this.senderCache.set(owner.toLowerCase(), addr);
194
+ }
195
+ }
196
+ catch { /* ignore */ }
197
+ }
198
+ }
199
+ catch {
200
+ // ignore:fallback below
201
+ }
202
+ }
203
+ // fallback:对仍然缺失的,使用 createAccount.staticCall(更可靠但更慢),并控制并发
204
+ const missingIdxs = [];
205
+ for (let i = 0; i < owners.length; i++) {
206
+ if (!out[i])
207
+ missingIdxs.push(i);
208
+ }
209
+ if (missingIdxs.length > 0) {
210
+ const filled = await mapWithConcurrency(missingIdxs, 6, async (idx) => {
211
+ const owner = owners[idx];
212
+ try {
213
+ const sender = await this.factory.createAccount.staticCall(owner, useSalt);
214
+ return { idx, owner, sender: String(sender) };
215
+ }
216
+ catch {
217
+ return { idx, owner, sender: '' };
218
+ }
219
+ });
220
+ for (const it of filled) {
221
+ if (it.sender) {
222
+ out[it.idx] = it.sender;
223
+ this.senderCache.set(it.owner.toLowerCase(), it.sender);
224
+ }
225
+ }
226
+ }
227
+ return out;
228
+ }
229
+ /**
230
+ * 批量预测 Sender 地址(只做地址预测,不额外查询 nonce/balance/code)
231
+ * - 用于“生成/导入钱包”场景:更快、更省 RPC
232
+ */
233
+ async predictSendersBatch(ownerAddresses, salt) {
234
+ return await this.predictSendersByOwnersFast(ownerAddresses, salt);
235
+ }
91
236
  /**
92
237
  * 获取完整的 AA 账户信息
93
238
  */
@@ -346,7 +491,86 @@ export class AAAccountManager {
346
491
  * 批量获取多个 owner 的 AA 账户信息
347
492
  */
348
493
  async getMultipleAccountInfo(ownerAddresses) {
349
- return Promise.all(ownerAddresses.map((addr) => this.getAccountInfo(addr)));
494
+ const owners = ownerAddresses.map((a) => String(a || '').trim()).filter(Boolean);
495
+ if (owners.length === 0)
496
+ return [];
497
+ // 1) 批量预测 sender(优先 getAddress+multicall,失败回退 createAccount.staticCall)
498
+ const senders = await this.predictSendersByOwnersFast(owners);
499
+ // 2) 批量 getNonce(multicall EntryPoint.getNonce)
500
+ const epIface = new Interface(ENTRYPOINT_ABI);
501
+ const nonceCalls = senders.map((sender) => ({
502
+ target: this.entryPointAddress,
503
+ allowFailure: true,
504
+ callData: epIface.encodeFunctionData('getNonce', [sender, 0]),
505
+ }));
506
+ const nonces = new Array(senders.length).fill(0n);
507
+ const BATCH = 350;
508
+ for (let cursor = 0; cursor < nonceCalls.length; cursor += BATCH) {
509
+ const sliceCalls = nonceCalls.slice(cursor, cursor + BATCH);
510
+ const res = await this.multicallAggregate3({ calls: sliceCalls });
511
+ for (let i = 0; i < res.length; i++) {
512
+ const r = res[i];
513
+ const idx = cursor + i;
514
+ if (!r?.success || !r.returnData || r.returnData === '0x')
515
+ continue;
516
+ try {
517
+ const decoded = epIface.decodeFunctionResult('getNonce', r.returnData);
518
+ nonces[idx] = BigInt(decoded?.[0] ?? 0n);
519
+ }
520
+ catch { /* ignore */ }
521
+ }
522
+ }
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'] });
533
+ }
534
+ for (const s of slice) {
535
+ reqs.push({ method: 'eth_getBalance', params: [s, 'latest'] });
536
+ }
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;
551
+ }
552
+ }
553
+ }
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++) {
561
+ const idx = cursor + i;
562
+ codes[idx] = fetched[i].code ?? '0x';
563
+ balances[idx] = fetched[i].bal ?? 0n;
564
+ }
565
+ }
566
+ }
567
+ return owners.map((owner, i) => ({
568
+ owner,
569
+ sender: senders[i],
570
+ deployed: codes[i] != null && String(codes[i]) !== '0x',
571
+ balance: balances[i] ?? 0n,
572
+ nonce: nonces[i] ?? 0n,
573
+ }));
350
574
  }
351
575
  }
352
576
  // ============================================================================
@@ -428,20 +652,24 @@ export async function generateAAWallets(params) {
428
652
  const wallet = Wallet.createRandom();
429
653
  const owner = wallet.address;
430
654
  const privateKey = wallet.privateKey;
431
- // 预测 AA 地址
432
- const sender = await manager.predictSenderAddress(owner);
433
655
  wallets.push({
434
656
  index: i + 1,
435
657
  owner,
436
658
  privateKey,
437
- sender,
659
+ sender: '',
438
660
  });
439
661
  owners.push(owner);
440
662
  privateKeys.push(privateKey);
441
- senders.push(sender);
663
+ senders.push('');
664
+ }
665
+ // ✅ 批量预测 sender(比逐个 staticCall 快很多)
666
+ const sendersPredicted = await manager.predictSendersBatch(owners);
667
+ for (let i = 0; i < wallets.length; i++) {
668
+ wallets[i].sender = sendersPredicted[i] || '';
669
+ senders[i] = sendersPredicted[i] || '';
442
670
  console.log(`#${i + 1}`);
443
- console.log(` Owner: ${owner}`);
444
- console.log(` Sender: ${sender}`);
671
+ console.log(` Owner: ${owners[i]}`);
672
+ console.log(` Sender: ${senders[i]}`);
445
673
  }
446
674
  // 生成格式化输出
447
675
  const formatted = formatWalletOutput(wallets);
@@ -468,29 +696,34 @@ export async function generateAAWalletsFromMnemonic(mnemonic, count, startIndex
468
696
  const privateKeys = [];
469
697
  const senders = [];
470
698
  console.log(`\n从助记词派生 ${count} 个 XLayer AA 钱包 (起始索引: ${startIndex})...\n`);
699
+ const paths = [];
471
700
  for (let i = 0; i < count; i++) {
472
701
  const index = startIndex + i;
473
702
  const path = `m/44'/60'/0'/0/${index}`;
703
+ paths.push(path);
474
704
  // 从助记词派生
475
705
  const hdNode = ethers.HDNodeWallet.fromPhrase(mnemonic, undefined, path);
476
706
  const owner = hdNode.address;
477
707
  const privateKey = hdNode.privateKey;
478
- // 预测 AA 地址
479
- const sender = await manager.predictSenderAddress(owner);
480
708
  wallets.push({
481
709
  index: i + 1,
482
710
  owner,
483
711
  privateKey,
484
- sender,
712
+ sender: '',
485
713
  mnemonic: i === 0 ? mnemonic : undefined, // 只在第一个记录助记词
486
714
  derivationPath: path,
487
715
  });
488
716
  owners.push(owner);
489
717
  privateKeys.push(privateKey);
490
- senders.push(sender);
491
- console.log(`#${i + 1} (path: ${path})`);
492
- console.log(` Owner: ${owner}`);
493
- console.log(` Sender: ${sender}`);
718
+ senders.push('');
719
+ }
720
+ const sendersPredicted = await manager.predictSendersBatch(owners);
721
+ for (let i = 0; i < wallets.length; i++) {
722
+ wallets[i].sender = sendersPredicted[i] || '';
723
+ senders[i] = sendersPredicted[i] || '';
724
+ console.log(`#${i + 1} (path: ${paths[i]})`);
725
+ console.log(` Owner: ${owners[i]}`);
726
+ console.log(` Sender: ${senders[i]}`);
494
727
  }
495
728
  const formatted = formatWalletOutput(wallets, mnemonic);
496
729
  return {
@@ -516,18 +749,22 @@ export async function predictSendersFromPrivateKeys(privateKeys, config) {
516
749
  for (let i = 0; i < privateKeys.length; i++) {
517
750
  const wallet = new Wallet(privateKeys[i]);
518
751
  const owner = wallet.address;
519
- const sender = await manager.predictSenderAddress(owner);
520
752
  wallets.push({
521
753
  index: i + 1,
522
754
  owner,
523
755
  privateKey: privateKeys[i],
524
- sender,
756
+ sender: '',
525
757
  });
526
758
  owners.push(owner);
527
- senders.push(sender);
759
+ senders.push('');
760
+ }
761
+ const sendersPredicted = await manager.predictSendersBatch(owners);
762
+ for (let i = 0; i < wallets.length; i++) {
763
+ wallets[i].sender = sendersPredicted[i] || '';
764
+ senders[i] = sendersPredicted[i] || '';
528
765
  console.log(`#${i + 1}`);
529
- console.log(` Owner: ${owner}`);
530
- console.log(` Sender: ${sender}`);
766
+ console.log(` Owner: ${owners[i]}`);
767
+ console.log(` Sender: ${senders[i]}`);
531
768
  }
532
769
  const formatted = formatWalletOutput(wallets);
533
770
  return {
@@ -52,6 +52,11 @@ export declare class BundleExecutor {
52
52
  * 构建归集 UserOp(将 OKB 从 sender 转回 owner)
53
53
  */
54
54
  private buildWithdrawUserOp;
55
+ /**
56
+ * 构建归集 UserOp(已知 sender/nonce/balance 的快速版本)
57
+ * - 用于批量流程:避免重复 getAccountInfo / getOkbBalance
58
+ */
59
+ private buildWithdrawUserOpWithState;
55
60
  /**
56
61
  * 构建代币转账 UserOp(将代币从 sender 转回 owner)
57
62
  */