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