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.
@@ -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.nonce,
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: accountInfos[i].nonce + 1n,
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: ai.nonce, initCode });
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 = ai.nonce + (didApprove[i] ? 1n : 0n);
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: nextNonces[i],
544
- initCode: (accountInfos[i].deployed || didApprove[i] || sold[i]) ? '0x' : this.aaManager.generateInitCode(wallets[i].address),
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.nonce,
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 sellOp = await this.buildSellUserOp(w, tokenAddress, sellAmount, sender, nonce, initCode, needApprove, `owner${i + 1}`);
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: nextNonces[i],
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.5.23",
3
+ "version": "1.5.25",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",