four-flap-meme-sdk 1.5.23 → 1.5.25
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
|
// 固定 gas(用于大规模减少 RPC);具体值允许通过 config.fixedGas 覆盖
|
|
@@ -365,6 +417,9 @@ export class BundleExecutor {
|
|
|
365
417
|
// 1. 预取账户信息(并行),并批量估算 gas(减少对 bundler 的 N 次请求)
|
|
366
418
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
367
419
|
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
420
|
+
const nonceMap = new AANonceMap();
|
|
421
|
+
for (const ai of accountInfos)
|
|
422
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
368
423
|
// 估算前确保 sender 有足够余额(用于 bundler 模拟;paymaster 场景会自动跳过)
|
|
369
424
|
// 避免 Promise.all 突发并发(大规模地址会触发 RPC 限流)
|
|
370
425
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
@@ -378,7 +433,7 @@ export class BundleExecutor {
|
|
|
378
433
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
379
434
|
ops: accountInfos.map((ai, i) => ({
|
|
380
435
|
sender: ai.sender,
|
|
381
|
-
nonce: ai.
|
|
436
|
+
nonce: nonceMap.next(ai.sender),
|
|
382
437
|
callData: buyCallDatas[i],
|
|
383
438
|
initCode: initCodes[i],
|
|
384
439
|
})),
|
|
@@ -403,6 +458,7 @@ export class BundleExecutor {
|
|
|
403
458
|
if (transferBackToOwner) {
|
|
404
459
|
const idxs = [];
|
|
405
460
|
const transferCallDatas = [];
|
|
461
|
+
const transferNonces = [];
|
|
406
462
|
for (let i = 0; i < wallets.length; i++) {
|
|
407
463
|
const sender = senders[i];
|
|
408
464
|
const bal = tokenBalances.get(sender) ?? 0n;
|
|
@@ -411,6 +467,7 @@ export class BundleExecutor {
|
|
|
411
467
|
idxs.push(i);
|
|
412
468
|
const transferData = encodeTransferCall(wallets[i].address, bal);
|
|
413
469
|
transferCallDatas.push(encodeExecute(tokenAddress, 0n, transferData));
|
|
470
|
+
transferNonces.push(nonceMap.next(sender));
|
|
414
471
|
}
|
|
415
472
|
if (idxs.length > 0) {
|
|
416
473
|
// 估算前补一点余额(paymaster 会自动跳过)
|
|
@@ -421,7 +478,7 @@ export class BundleExecutor {
|
|
|
421
478
|
const { userOps: transferUserOps, prefundWeis: transferPrefunds } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
422
479
|
ops: idxs.map((i, k) => ({
|
|
423
480
|
sender: senders[i],
|
|
424
|
-
nonce:
|
|
481
|
+
nonce: transferNonces[k],
|
|
425
482
|
callData: transferCallDatas[k],
|
|
426
483
|
initCode: '0x',
|
|
427
484
|
})),
|
|
@@ -456,11 +513,15 @@ export class BundleExecutor {
|
|
|
456
513
|
// ✅ 批量获取 accountInfo(含 sender/nonce/deployed),避免循环内重复 getAccountInfo
|
|
457
514
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
458
515
|
const senders = accountInfos.map((ai) => ai.sender);
|
|
516
|
+
const nonceMap = new AANonceMap();
|
|
517
|
+
for (const ai of accountInfos)
|
|
518
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
459
519
|
const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
|
|
460
520
|
const allowances = await this.portalQuery.getMultipleAllowances(tokenAddress, senders);
|
|
461
521
|
// 1. 检查授权,必要时先 approve
|
|
462
522
|
const approveItems = [];
|
|
463
523
|
const didApprove = new Array(wallets.length).fill(false);
|
|
524
|
+
const touched = new Array(wallets.length).fill(false); // 任意阶段使用过 UserOp(用于决定 initCode=0x)
|
|
464
525
|
for (let i = 0; i < wallets.length; i++) {
|
|
465
526
|
const sender = senders[i];
|
|
466
527
|
const balance = tokenBalances.get(sender) ?? 0n;
|
|
@@ -471,8 +532,9 @@ export class BundleExecutor {
|
|
|
471
532
|
continue;
|
|
472
533
|
const ai = accountInfos[i];
|
|
473
534
|
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
474
|
-
approveItems.push({ i, sender, nonce:
|
|
535
|
+
approveItems.push({ i, sender, nonce: nonceMap.next(sender), initCode });
|
|
475
536
|
didApprove[i] = true;
|
|
537
|
+
touched[i] = true;
|
|
476
538
|
}
|
|
477
539
|
const approveOps = [];
|
|
478
540
|
if (approveItems.length > 0) {
|
|
@@ -503,8 +565,9 @@ export class BundleExecutor {
|
|
|
503
565
|
const initCode = ai.deployed ? '0x' : this.aaManager.generateInitCode(wallets[i].address);
|
|
504
566
|
// approve 已单独打包并等待确认,因此这里不需要用“needApprove=真”去走保守 callGasLimit
|
|
505
567
|
const needApprove = false;
|
|
506
|
-
const nonce =
|
|
568
|
+
const nonce = nonceMap.next(sender);
|
|
507
569
|
sellItems.push({ i, sender, nonce, initCode: didApprove[i] ? '0x' : initCode, needApprove, sellAmount });
|
|
570
|
+
touched[i] = true;
|
|
508
571
|
}
|
|
509
572
|
if (sellItems.length > 0) {
|
|
510
573
|
const signedSells = await mapWithConcurrency(sellItems, 4, async (it) => {
|
|
@@ -524,24 +587,15 @@ export class BundleExecutor {
|
|
|
524
587
|
const withdrawOps = [];
|
|
525
588
|
// 批量获取 sender OKB 余额
|
|
526
589
|
const okbBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
527
|
-
// 计算 sell 后的下一 nonce
|
|
528
|
-
const nextNonces = new Array(wallets.length).fill(0n);
|
|
529
|
-
const sold = new Array(wallets.length).fill(false);
|
|
530
|
-
for (const it of sellItems) {
|
|
531
|
-
sold[it.i] = true;
|
|
532
|
-
}
|
|
533
|
-
for (let i = 0; i < wallets.length; i++) {
|
|
534
|
-
const ai = accountInfos[i];
|
|
535
|
-
const sellNonceUsed = ai.nonce + (didApprove[i] ? 1n : 0n);
|
|
536
|
-
nextNonces[i] = sold[i] ? (sellNonceUsed + 1n) : (ai.nonce + (didApprove[i] ? 1n : 0n));
|
|
537
|
-
}
|
|
538
590
|
const withdrawItems = wallets.map((w, i) => ({
|
|
539
591
|
i,
|
|
540
592
|
ownerWallet: w,
|
|
541
593
|
sender: senders[i],
|
|
542
594
|
senderBalance: okbBalances.get(senders[i]) ?? 0n,
|
|
543
|
-
nonce
|
|
544
|
-
|
|
595
|
+
// ⚠️ 这里 nonce 用 peek(不消耗),只有确实生成 withdraw op 才 commit
|
|
596
|
+
nonce: nonceMap.peek(senders[i]),
|
|
597
|
+
// 如果该 sender 在 approve/sell 阶段已经出现过(或原本已部署),则 withdraw 不再需要 initCode
|
|
598
|
+
initCode: (accountInfos[i].deployed || touched[i]) ? '0x' : this.aaManager.generateInitCode(wallets[i].address),
|
|
545
599
|
}));
|
|
546
600
|
const signedWithdraws = await mapWithConcurrency(withdrawItems, 3, async (it) => {
|
|
547
601
|
const signed = await this.buildWithdrawUserOpWithState({
|
|
@@ -553,6 +607,8 @@ export class BundleExecutor {
|
|
|
553
607
|
reserveWei,
|
|
554
608
|
ownerName: `owner${it.i + 1}`,
|
|
555
609
|
});
|
|
610
|
+
if (signed?.userOp)
|
|
611
|
+
nonceMap.commit(it.sender, it.nonce);
|
|
556
612
|
return signed?.userOp ?? null;
|
|
557
613
|
});
|
|
558
614
|
for (const op of signedWithdraws) {
|
|
@@ -587,6 +643,9 @@ export class BundleExecutor {
|
|
|
587
643
|
// ✅ 批量获取 accountInfo(含 sender/nonce/deployed)
|
|
588
644
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
589
645
|
const senders = accountInfos.map((ai) => ai.sender);
|
|
646
|
+
const nonceMap = new AANonceMap();
|
|
647
|
+
for (const ai of accountInfos)
|
|
648
|
+
nonceMap.init(ai.sender, ai.nonce);
|
|
590
649
|
// 1. 买入(批量估算 + 并发补余额 + 并发签名)
|
|
591
650
|
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
592
651
|
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
@@ -603,7 +662,7 @@ export class BundleExecutor {
|
|
|
603
662
|
const { userOps: buyUserOps, prefundWeis } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
604
663
|
ops: accountInfos.map((ai, i) => ({
|
|
605
664
|
sender: ai.sender,
|
|
606
|
-
nonce: ai.
|
|
665
|
+
nonce: nonceMap.next(ai.sender),
|
|
607
666
|
callData: buyCallDatas[i],
|
|
608
667
|
initCode: initCodes[i],
|
|
609
668
|
})),
|
|
@@ -632,19 +691,18 @@ export class BundleExecutor {
|
|
|
632
691
|
}
|
|
633
692
|
const allowance = allowances.get(sender) ?? 0n;
|
|
634
693
|
const needApprove = allowance < balance;
|
|
635
|
-
// buy 已在上一笔 handleOps 执行,因此 nonce = 原 nonce + 1
|
|
636
|
-
let nonce = accountInfos[i].nonce + 1n;
|
|
637
694
|
const initCode = '0x';
|
|
638
695
|
const out = [];
|
|
639
696
|
if (needApprove) {
|
|
697
|
+
const nonce = nonceMap.next(sender);
|
|
640
698
|
const approveOp = await this.buildApproveUserOp(w, tokenAddress, this.portalAddress, sender, nonce, initCode, `owner${i + 1}`);
|
|
641
699
|
out.push(approveOp.userOp);
|
|
642
|
-
nonce = nonce + 1n;
|
|
643
700
|
}
|
|
644
701
|
const sellAmount = (balance * BigInt(sellPercent)) / 100n;
|
|
645
702
|
if (sellAmount === 0n)
|
|
646
703
|
return out;
|
|
647
|
-
const
|
|
704
|
+
const sellNonce = nonceMap.next(sender);
|
|
705
|
+
const sellOp = await this.buildSellUserOp(w, tokenAddress, sellAmount, sender, sellNonce, initCode, needApprove, `owner${i + 1}`);
|
|
648
706
|
out.push(sellOp.userOp);
|
|
649
707
|
return out;
|
|
650
708
|
});
|
|
@@ -660,22 +718,12 @@ export class BundleExecutor {
|
|
|
660
718
|
const withdrawOps = [];
|
|
661
719
|
// 批量获取 OKB 余额(sell 后状态)
|
|
662
720
|
const okbBalances = await this.portalQuery.getMultipleOkbBalances(senders);
|
|
663
|
-
// sell handleOps 里每个 wallet:一定有 sell op(balance>0)且可能还有 approve op
|
|
664
|
-
const nextNonces = wallets.map((_, i) => {
|
|
665
|
-
const sender = senders[i];
|
|
666
|
-
const bal = tokenBalances.get(sender) ?? 0n;
|
|
667
|
-
if (bal === 0n)
|
|
668
|
-
return accountInfos[i].nonce + 1n; // buy 后但未 sell
|
|
669
|
-
const allowance = allowances.get(sender) ?? 0n;
|
|
670
|
-
const needApprove = allowance < bal;
|
|
671
|
-
return accountInfos[i].nonce + 1n + (needApprove ? 2n : 1n);
|
|
672
|
-
});
|
|
673
721
|
const withdrawItems = wallets.map((w, i) => ({
|
|
674
722
|
i,
|
|
675
723
|
ownerWallet: w,
|
|
676
724
|
sender: senders[i],
|
|
677
725
|
senderBalance: okbBalances.get(senders[i]) ?? 0n,
|
|
678
|
-
nonce:
|
|
726
|
+
nonce: nonceMap.peek(senders[i]),
|
|
679
727
|
initCode: '0x',
|
|
680
728
|
}));
|
|
681
729
|
const signedWithdraws = await mapWithConcurrency(withdrawItems, 3, async (it) => {
|
|
@@ -688,6 +736,8 @@ export class BundleExecutor {
|
|
|
688
736
|
reserveWei,
|
|
689
737
|
ownerName: `owner${it.i + 1}`,
|
|
690
738
|
});
|
|
739
|
+
if (signed?.userOp)
|
|
740
|
+
nonceMap.commit(it.sender, it.nonce);
|
|
691
741
|
return signed?.userOp ?? null;
|
|
692
742
|
});
|
|
693
743
|
for (const op of signedWithdraws) {
|