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.
- package/dist/contracts/tm-bundle-merkle/index.d.ts +1 -1
- package/dist/contracts/tm-bundle-merkle/index.js +1 -1
- package/dist/contracts/tm-bundle-merkle/types.d.ts +24 -0
- package/dist/contracts/tm-bundle-merkle/utils.d.ts +8 -1
- package/dist/contracts/tm-bundle-merkle/utils.js +287 -0
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +16 -0
- package/dist/flap/portal-bundle-merkle/encryption.js +146 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/concurrency.d.ts +5 -0
- package/dist/utils/concurrency.js +19 -0
- package/dist/xlayer/aa-account.d.ts +10 -0
- package/dist/xlayer/aa-account.js +257 -20
- package/dist/xlayer/bundle.d.ts +5 -0
- package/dist/xlayer/bundle.js +202 -72
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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(
|
|
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: ${
|
|
444
|
-
console.log(` 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(
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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(
|
|
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: ${
|
|
530
|
-
console.log(` Sender: ${
|
|
766
|
+
console.log(` Owner: ${owners[i]}`);
|
|
767
|
+
console.log(` Sender: ${senders[i]}`);
|
|
531
768
|
}
|
|
532
769
|
const formatted = formatWalletOutput(wallets);
|
|
533
770
|
return {
|
package/dist/xlayer/bundle.d.ts
CHANGED
|
@@ -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
|
*/
|