four-flap-meme-sdk 1.5.22 → 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.
- package/dist/xlayer/bundle.js +83 -33
- package/package.json +1 -1
package/dist/xlayer/bundle.js
CHANGED
|
@@ -13,6 +13,58 @@ import { AAAccountManager, encodeExecute } from './aa-account.js';
|
|
|
13
13
|
import { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, parseOkb, formatOkb, } from './portal-ops.js';
|
|
14
14
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
15
15
|
// ============================================================================
|
|
16
|
+
// AA Nonce(EntryPoint nonce)本地分配器
|
|
17
|
+
// ============================================================================
|
|
18
|
+
/**
|
|
19
|
+
* ✅ 仅用于 ERC-4337 AA(EntryPoint.getNonce(sender, 0))
|
|
20
|
+
*
|
|
21
|
+
* 目标:不要在业务代码里手写 `+1/+2` 推导,而是像 BSC bundle 一样用本地 Map
|
|
22
|
+
* 对“同一 sender 多步流程”连续分配 nonce。
|
|
23
|
+
*
|
|
24
|
+
* 注意:
|
|
25
|
+
* - 这是**同一次 SDK 调用/同一条流程**内的 nonce 分配;不解决多进程/多并发任务同时使用同一 sender 的问题。
|
|
26
|
+
* - 对“可能不生成 op”的分支(例如 withdraw 返回 null),使用 peek + commit,避免提前消耗 nonce。
|
|
27
|
+
*/
|
|
28
|
+
class AANonceMap {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.nextBySenderLower = new Map();
|
|
31
|
+
}
|
|
32
|
+
init(sender, startNonce) {
|
|
33
|
+
const k = sender.toLowerCase();
|
|
34
|
+
const cur = this.nextBySenderLower.get(k);
|
|
35
|
+
if (cur === undefined || startNonce > cur) {
|
|
36
|
+
this.nextBySenderLower.set(k, startNonce);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
peek(sender) {
|
|
40
|
+
const k = sender.toLowerCase();
|
|
41
|
+
const cur = this.nextBySenderLower.get(k);
|
|
42
|
+
if (cur === undefined) {
|
|
43
|
+
throw new Error(`AANonceMap: sender not initialized: ${sender}`);
|
|
44
|
+
}
|
|
45
|
+
return cur;
|
|
46
|
+
}
|
|
47
|
+
commit(sender, usedNonce) {
|
|
48
|
+
const k = sender.toLowerCase();
|
|
49
|
+
const cur = this.nextBySenderLower.get(k);
|
|
50
|
+
if (cur === undefined) {
|
|
51
|
+
throw new Error(`AANonceMap: sender not initialized: ${sender}`);
|
|
52
|
+
}
|
|
53
|
+
// 只允许“使用当前 nonce”或“重复 commit”(幂等)
|
|
54
|
+
if (usedNonce !== cur && usedNonce !== cur - 1n) {
|
|
55
|
+
throw new Error(`AANonceMap: nonce mismatch for ${sender}: used=${usedNonce.toString()} cur=${cur.toString()}`);
|
|
56
|
+
}
|
|
57
|
+
if (usedNonce === cur) {
|
|
58
|
+
this.nextBySenderLower.set(k, cur + 1n);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
next(sender) {
|
|
62
|
+
const n = this.peek(sender);
|
|
63
|
+
this.commit(sender, n);
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ============================================================================
|
|
16
68
|
// 捆绑交易执行器
|
|
17
69
|
// ============================================================================
|
|
18
70
|
/**
|
|
@@ -278,6 +330,9 @@ export class BundleExecutor {
|
|
|
278
330
|
// 1. 预取账户信息(并行),并批量估算 gas(减少对 bundler 的 N 次请求)
|
|
279
331
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
280
332
|
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
333
|
+
const nonceMap = new AANonceMap();
|
|
334
|
+
for (const ai of accountInfos)
|
|
335
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
281
336
|
// 估算前确保 sender 有足够余额(用于 bundler 模拟;paymaster 场景会自动跳过)
|
|
282
337
|
await Promise.all(accountInfos.map((ai, i) => this.aaManager.ensureSenderBalance(wallets[i], ai.sender, buyWeis[i] + parseOkb('0.0003'), `owner${i + 1}/buy-prefund-before-estimate`)));
|
|
283
338
|
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
@@ -288,7 +343,7 @@ export class BundleExecutor {
|
|
|
288
343
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
289
344
|
ops: accountInfos.map((ai, i) => ({
|
|
290
345
|
sender: ai.sender,
|
|
291
|
-
nonce: ai.
|
|
346
|
+
nonce: nonceMap.next(ai.sender),
|
|
292
347
|
callData: buyCallDatas[i],
|
|
293
348
|
initCode: initCodes[i],
|
|
294
349
|
})),
|
|
@@ -311,6 +366,7 @@ export class BundleExecutor {
|
|
|
311
366
|
if (transferBackToOwner) {
|
|
312
367
|
const idxs = [];
|
|
313
368
|
const transferCallDatas = [];
|
|
369
|
+
const transferNonces = [];
|
|
314
370
|
for (let i = 0; i < wallets.length; i++) {
|
|
315
371
|
const sender = senders[i];
|
|
316
372
|
const bal = tokenBalances.get(sender) ?? 0n;
|
|
@@ -319,6 +375,7 @@ export class BundleExecutor {
|
|
|
319
375
|
idxs.push(i);
|
|
320
376
|
const transferData = encodeTransferCall(wallets[i].address, bal);
|
|
321
377
|
transferCallDatas.push(encodeExecute(tokenAddress, 0n, transferData));
|
|
378
|
+
transferNonces.push(nonceMap.next(sender));
|
|
322
379
|
}
|
|
323
380
|
if (idxs.length > 0) {
|
|
324
381
|
// 估算前补一点余额(paymaster 会自动跳过)
|
|
@@ -327,7 +384,7 @@ export class BundleExecutor {
|
|
|
327
384
|
const { userOps: transferUserOps, prefundWeis: transferPrefunds } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
328
385
|
ops: idxs.map((i, k) => ({
|
|
329
386
|
sender: senders[i],
|
|
330
|
-
nonce:
|
|
387
|
+
nonce: transferNonces[k],
|
|
331
388
|
callData: transferCallDatas[k],
|
|
332
389
|
initCode: '0x',
|
|
333
390
|
})),
|
|
@@ -360,11 +417,15 @@ export class BundleExecutor {
|
|
|
360
417
|
// ✅ 批量获取 accountInfo(含 sender/nonce/deployed),避免循环内重复 getAccountInfo
|
|
361
418
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
362
419
|
const senders = accountInfos.map((ai) => ai.sender);
|
|
420
|
+
const nonceMap = new AANonceMap();
|
|
421
|
+
for (const ai of accountInfos)
|
|
422
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
363
423
|
const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
|
|
364
424
|
const allowances = await this.portalQuery.getMultipleAllowances(tokenAddress, senders);
|
|
365
425
|
// 1. 检查授权,必要时先 approve
|
|
366
426
|
const approveItems = [];
|
|
367
427
|
const didApprove = new Array(wallets.length).fill(false);
|
|
428
|
+
const touched = new Array(wallets.length).fill(false); // 任意阶段使用过 UserOp(用于决定 initCode=0x)
|
|
368
429
|
for (let i = 0; i < wallets.length; i++) {
|
|
369
430
|
const sender = senders[i];
|
|
370
431
|
const balance = tokenBalances.get(sender) ?? 0n;
|
|
@@ -375,8 +436,9 @@ export class BundleExecutor {
|
|
|
375
436
|
continue;
|
|
376
437
|
const ai = accountInfos[i];
|
|
377
438
|
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
378
|
-
approveItems.push({ i, sender, nonce:
|
|
439
|
+
approveItems.push({ i, sender, nonce: nonceMap.next(sender), initCode });
|
|
379
440
|
didApprove[i] = true;
|
|
441
|
+
touched[i] = true;
|
|
380
442
|
}
|
|
381
443
|
const approveOps = [];
|
|
382
444
|
if (approveItems.length > 0) {
|
|
@@ -407,8 +469,9 @@ export class BundleExecutor {
|
|
|
407
469
|
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
408
470
|
// approve 已单独打包并等待确认,因此这里不需要用“needApprove=真”去走保守 callGasLimit
|
|
409
471
|
const needApprove = false;
|
|
410
|
-
const nonce =
|
|
472
|
+
const nonce = nonceMap.next(sender);
|
|
411
473
|
sellItems.push({ i, sender, nonce, initCode: didApprove[i] ? '0x' : initCode, needApprove, sellAmount });
|
|
474
|
+
touched[i] = true;
|
|
412
475
|
}
|
|
413
476
|
if (sellItems.length > 0) {
|
|
414
477
|
const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
|
|
@@ -428,24 +491,15 @@ export class BundleExecutor {
|
|
|
428
491
|
const withdrawOps = [];
|
|
429
492
|
// 批量获取 sender OKB 余额
|
|
430
493
|
const okbBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
431
|
-
// 计算 sell 后的下一 nonce
|
|
432
|
-
const nextNonces = new Array(wallets.length).fill(0n);
|
|
433
|
-
const sold = new Array(wallets.length).fill(false);
|
|
434
|
-
for (const it of sellItems) {
|
|
435
|
-
sold[it.i] = true;
|
|
436
|
-
}
|
|
437
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
438
|
-
const ai = accountInfos[i];
|
|
439
|
-
const sellNonceUsed = ai.nonce + (didApprove[i] ? 1n : 0n);
|
|
440
|
-
nextNonces[i] = sold[i] ? (sellNonceUsed + 1n) : (ai.nonce + (didApprove[i] ? 1n : 0n));
|
|
441
|
-
}
|
|
442
494
|
const withdrawItems = wallets.map((w, i) => ({
|
|
443
495
|
i,
|
|
444
496
|
ownerWallet: w,
|
|
445
497
|
sender: senders[i],
|
|
446
498
|
senderBalance: okbBalances.get(senders[i]) ?? 0n,
|
|
447
|
-
nonce
|
|
448
|
-
|
|
499
|
+
// ⚠️ 这里 nonce 用 peek(不消耗),只有确实生成 withdraw op 才 commit
|
|
500
|
+
nonce: nonceMap.peek(senders[i]),
|
|
501
|
+
// 如果该 sender 在 approve/sell 阶段已经出现过(或原本已部署),则 withdraw 不再需要 initCode
|
|
502
|
+
initCode: (accountInfos[i].deployed || touched[i]) ? '0x' : this.aaManager.generateInitCode(wallets[i].address),
|
|
449
503
|
}));
|
|
450
504
|
const signedWithdraws = await mapWithConcurrency(withdrawItems, 3, async (it) => {
|
|
451
505
|
const signed = await this.buildWithdrawUserOpWithState({
|
|
@@ -457,6 +511,8 @@ export class BundleExecutor {
|
|
|
457
511
|
reserveWei,
|
|
458
512
|
ownerName: `owner${it.i + 1}`,
|
|
459
513
|
});
|
|
514
|
+
if (signed?.userOp)
|
|
515
|
+
nonceMap.commit(it.sender, it.nonce);
|
|
460
516
|
return signed?.userOp ?? null;
|
|
461
517
|
});
|
|
462
518
|
for (const op of signedWithdraws) {
|
|
@@ -491,6 +547,9 @@ export class BundleExecutor {
|
|
|
491
547
|
// ✅ 批量获取 accountInfo(含 sender/nonce/deployed)
|
|
492
548
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
493
549
|
const senders = accountInfos.map((ai) => ai.sender);
|
|
550
|
+
const nonceMap = new AANonceMap();
|
|
551
|
+
for (const ai of accountInfos)
|
|
552
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
494
553
|
// 1. 买入(批量估算 + 并发补余额 + 并发签名)
|
|
495
554
|
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
496
555
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
@@ -507,7 +566,7 @@ export class BundleExecutor {
|
|
|
507
566
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
508
567
|
ops: accountInfos.map((ai, i) => ({
|
|
509
568
|
sender: ai.sender,
|
|
510
|
-
nonce: ai.
|
|
569
|
+
nonce: nonceMap.next(ai.sender),
|
|
511
570
|
callData: buyCallDatas[i],
|
|
512
571
|
initCode: initCodes[i],
|
|
513
572
|
})),
|
|
@@ -536,19 +595,18 @@ export class BundleExecutor {
|
|
|
536
595
|
}
|
|
537
596
|
const allowance = allowances.get(sender) ?? 0n;
|
|
538
597
|
const needApprove = allowance < balance;
|
|
539
|
-
// buy 已在上一笔 handleOps 执行,因此 nonce = 原 nonce + 1
|
|
540
|
-
let nonce = accountInfos[i].nonce + 1n;
|
|
541
598
|
const initCode = '0x';
|
|
542
599
|
const out = [];
|
|
543
600
|
if (needApprove) {
|
|
601
|
+
const nonce = nonceMap.next(sender);
|
|
544
602
|
const approveOp = await this.buildApproveUserOp(w, tokenAddress, this.portalAddress, sender, nonce, initCode, `owner${i + 1}`);
|
|
545
603
|
out.push(approveOp.userOp);
|
|
546
|
-
nonce = nonce + 1n;
|
|
547
604
|
}
|
|
548
605
|
const sellAmount = (balance * BigInt(sellPercent)) / 100n;
|
|
549
606
|
if (sellAmount === 0n)
|
|
550
607
|
return out;
|
|
551
|
-
const
|
|
608
|
+
const sellNonce = nonceMap.next(sender);
|
|
609
|
+
const sellOp = await this.buildSellUserOp(w, tokenAddress, sellAmount, sender, sellNonce, initCode, needApprove, `owner${i + 1}`);
|
|
552
610
|
out.push(sellOp.userOp);
|
|
553
611
|
return out;
|
|
554
612
|
});
|
|
@@ -564,22 +622,12 @@ export class BundleExecutor {
|
|
|
564
622
|
const withdrawOps = [];
|
|
565
623
|
// 批量获取 OKB 余额(sell 后状态)
|
|
566
624
|
const okbBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
567
|
-
// sell handleOps 里每个 wallet:一定有 sell op(balance>0)且可能还有 approve op
|
|
568
|
-
const nextNonces = wallets.map((_, i) => {
|
|
569
|
-
const sender = senders[i];
|
|
570
|
-
const bal = tokenBalances.get(sender) ?? 0n;
|
|
571
|
-
if (bal === 0n)
|
|
572
|
-
return accountInfos[i].nonce + 1n; // buy 后但未 sell
|
|
573
|
-
const allowance = allowances.get(sender) ?? 0n;
|
|
574
|
-
const needApprove = allowance < bal;
|
|
575
|
-
return accountInfos[i].nonce + 1n + (needApprove ? 2n : 1n);
|
|
576
|
-
});
|
|
577
625
|
const withdrawItems = wallets.map((w, i) => ({
|
|
578
626
|
i,
|
|
579
627
|
ownerWallet: w,
|
|
580
628
|
sender: senders[i],
|
|
581
629
|
senderBalance: okbBalances.get(senders[i]) ?? 0n,
|
|
582
|
-
nonce:
|
|
630
|
+
nonce: nonceMap.peek(senders[i]),
|
|
583
631
|
initCode: '0x',
|
|
584
632
|
}));
|
|
585
633
|
const signedWithdraws = await mapWithConcurrency(withdrawItems, 3, async (it) => {
|
|
@@ -592,6 +640,8 @@ export class BundleExecutor {
|
|
|
592
640
|
reserveWei,
|
|
593
641
|
ownerName: `owner${it.i + 1}`,
|
|
594
642
|
});
|
|
643
|
+
if (signed?.userOp)
|
|
644
|
+
nonceMap.commit(it.sender, it.nonce);
|
|
595
645
|
return signed?.userOp ?? null;
|
|
596
646
|
});
|
|
597
647
|
for (const op of signedWithdraws) {
|