four-flap-meme-sdk 1.6.44 → 1.6.47

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.
@@ -557,12 +557,42 @@ export class AADexSwapExecutor {
557
557
  return 0n;
558
558
  }
559
559
  })();
560
- if (quotedSellOutWei > 0n && totalBuyWei > quotedSellOutWei) {
561
- throw new Error('AA 批量换手:buyAmountsOkb 总和超过预估卖出输出(quotedSellOut)');
562
- }
560
+ // 关键修复:提前计算 buyerPrefund,用于资金充足性检查
561
+ // capitalMode 下,分发金额 = 买入金额 + buyerPrefund + 利润
562
+ // 必须确保卖出所得 >= 分发金额,否则交易会失败
563
+ const feeDataEarly = await this.aaManager.getFeeData();
564
+ const gasPriceEarly = feeDataEarly.gasPrice ?? feeDataEarly.maxFeePerGas ?? 5000000000n;
565
+ const estimateBuyerPrefundEarly = (deployed) => {
566
+ const callGas = DEX_BUY_CALL_GAS_LIMIT;
567
+ const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
568
+ const preVerifyGas = PRE_VERIFICATION_GAS;
569
+ const totalGas = callGas + verifyGas + preVerifyGas;
570
+ return (totalGas * gasPriceEarly * PREFUND_BUFFER_PERCENT) / 100n;
571
+ };
572
+ // ✅ 计算所有 buyer 需要的 prefund 总和(仅原生代币模式 + capitalMode 需要)
573
+ const totalBuyerPrefundEarly = (useNativeToken && capitalMode)
574
+ ? buyerAis.reduce((sum, ai) => sum + estimateBuyerPrefundEarly(ai.deployed), 0n)
575
+ : 0n;
576
+ // ✅ 计算利润(考虑 buyerPrefund 后的可用利润空间)
563
577
  const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
564
- const profitCap = quotedSellOutWei > totalBuyWei ? (quotedSellOutWei - totalBuyWei) : 0n;
565
- const profitWei = profitWeiRaw > profitCap ? profitCap : profitWeiRaw;
578
+ // 利润上限 = 卖出所得 - 买入金额 - buyerPrefund
579
+ const profitCapWithPrefund = quotedSellOutWei > (totalBuyWei + totalBuyerPrefundEarly)
580
+ ? (quotedSellOutWei - totalBuyWei - totalBuyerPrefundEarly)
581
+ : 0n;
582
+ const profitWei = profitWeiRaw > profitCapWithPrefund ? profitCapWithPrefund : profitWeiRaw;
583
+ // ✅ 资金充足性检查:卖出所得 >= 买入金额 + buyerPrefund + 利润
584
+ const totalDistributeNeeded = totalBuyWei + totalBuyerPrefundEarly + profitWei;
585
+ if (quotedSellOutWei > 0n && totalDistributeNeeded > quotedSellOutWei) {
586
+ console.error('[AA DEX 批量换手] ❌ 资金不足:', {
587
+ quotedSellOutWei: ethers.formatEther(quotedSellOutWei),
588
+ totalBuyWei: ethers.formatEther(totalBuyWei),
589
+ totalBuyerPrefund: ethers.formatEther(totalBuyerPrefundEarly),
590
+ profitWei: ethers.formatEther(profitWei),
591
+ totalNeeded: ethers.formatEther(totalDistributeNeeded),
592
+ shortfall: ethers.formatEther(totalDistributeNeeded - quotedSellOutWei),
593
+ });
594
+ throw new Error(`AA 批量换手:资金不足!卖出预估 ${ethers.formatEther(quotedSellOutWei)} OKB,但分发需要 ${ethers.formatEther(totalDistributeNeeded)} OKB(买入 ${ethers.formatEther(totalBuyWei)} + prefund ${ethers.formatEther(totalBuyerPrefundEarly)} + 利润 ${ethers.formatEther(profitWei)}),缺口 ${ethers.formatEther(totalDistributeNeeded - quotedSellOutWei)} OKB`);
595
+ }
566
596
  // ✅ 利润在分发阶段通过 AA 内部刮取
567
597
  // ✅ 资金分发逻辑(仅 capitalMode=true 时执行,对应 BSC flapQuickBatchSwapMerkle)
568
598
  // capitalMode=false 时跳过分发,买方用自己的 OKB(对应 BSC flapBatchSwapMerkle)
@@ -625,31 +655,44 @@ export class AADexSwapExecutor {
625
655
  const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
626
656
  const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
627
657
  console.log(`[AA DEX 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
628
- // ✅ 直接分发模式:分发金额 = 买入金额 + 买方 prefund
629
- const items = buyerSenders.map((to, i) => ({
630
- to,
631
- value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n) // ✅ 包含 prefund
632
- })).filter(x => x.value > 0n);
633
- // 添加利润接收者到分发列表
634
- if (extractProfit && profitWei > 0n) {
635
- items.push({ to: profitRecipient, value: profitWei });
636
- }
658
+ // ✅ 直接分发模式:
659
+ // - 原生代币模式:分发金额 = 买入金额 + 买方 prefund
660
+ // - ERC20 模式:只分发 ERC20 代币,prefund 需要 buyer 自己有 OKB
661
+ const items = buyerSenders.map((to, i) => {
662
+ const buyAmount = buyAmountsWei[i] ?? 0n;
663
+ const prefund = buyerPrefunds[i] ?? 0n;
664
+ return {
665
+ to,
666
+ buyAmount, // ERC20 代币金额
667
+ prefund, // OKB prefund(仅原生代币模式使用)
668
+ totalValue: buyAmount + prefund // 原生代币模式的总金额
669
+ };
670
+ }).filter(x => x.buyAmount > 0n);
671
+ // 添加利润接收者到分发列表(只用于原生代币模式的金额计算)
672
+ const profitItem = (extractProfit && profitWei > 0n)
673
+ ? { to: profitRecipient, buyAmount: 0n, prefund: 0n, totalValue: profitWei }
674
+ : null;
637
675
  const chunks = chunkArray(items, maxPerOp);
638
676
  for (const ch of chunks) {
639
677
  let callData;
640
678
  if (useNativeToken) {
641
- // ✅ 原生代币模式:使用 Multicall3 批量转账
679
+ // ✅ 原生代币模式:使用 Multicall3 批量转账(包含 prefund)
680
+ const allItems = profitItem && ch === chunks[0] ? [...ch, profitItem] : ch;
642
681
  const { totalValue, data } = encodeNativeDisperseViaMulticall3({
643
- to: ch.map(x => x.to),
644
- values: ch.map(x => x.value),
682
+ to: allItems.map(x => x.to),
683
+ values: allItems.map(x => x.totalValue),
645
684
  });
646
685
  callData = encodeExecute(MULTICALL3, totalValue, data);
647
686
  }
648
687
  else {
649
- // ✅ ERC20 模式:使用 executeBatch 批量调用 ERC20 transfer
650
- const targets = ch.map(() => quoteToken);
651
- const values = ch.map(() => 0n);
652
- const datas = ch.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.value]));
688
+ // ✅ ERC20 模式:只转账 ERC20 代币(不包含 OKB prefund)
689
+ // 注意:buyer 需要自己有足够的 OKB 来支付 prefund,否则交易会失败
690
+ const allItems = profitItem && ch === chunks[0]
691
+ ? [...ch, { ...profitItem, buyAmount: profitWei }]
692
+ : ch;
693
+ const targets = allItems.map(() => quoteToken);
694
+ const values = allItems.map(() => 0n);
695
+ const datas = allItems.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.buyAmount]));
653
696
  callData = encodeExecuteBatch(targets, values, datas);
654
697
  }
655
698
  const signedDisperse = await this.aaManager.buildUserOpWithState({
@@ -744,6 +787,24 @@ export class AADexSwapExecutor {
744
787
  allGeneratedHopWallets.push(chainHops);
745
788
  }
746
789
  console.log(`[AA DEX 多跳] ✅ 使用预充值 hop 钱包: ${totalHopsNeeded} 个 (${buyerSenders.length} 条链 × ${effectiveHopCount} 跳)`);
790
+ // ✅ 关键修复:检查 buyer 钱包是否有足够的 OKB 支付 prefund
791
+ // ERC-4337 的 handleOps 会先验证所有 UserOps(需要 prefund),然后才执行
792
+ // 所以 buyer 需要在交易前就有足够的 OKB,不能依赖 hop 链在 execution 阶段转入的资金
793
+ const buyerBalances = await Promise.all(buyerSenders.map(s => provider.getBalance(s)));
794
+ const insufficientBuyers = [];
795
+ for (let i = 0; i < buyerSenders.length; i++) {
796
+ const buyerAi = buyerAis[i];
797
+ const buyerPrefundNeeded = estimatePrefund(DEX_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
798
+ if (buyerBalances[i] < buyerPrefundNeeded) {
799
+ insufficientBuyers.push(`${buyerSenders[i]} (余额: ${ethers.formatEther(buyerBalances[i])} OKB, 需要: ${ethers.formatEther(buyerPrefundNeeded)} OKB, 缺口: ${ethers.formatEther(buyerPrefundNeeded - buyerBalances[i])} OKB)`);
800
+ }
801
+ }
802
+ if (insufficientBuyers.length > 0) {
803
+ console.error('[AA DEX 多跳] ❌ 以下 buyer 钱包 OKB 余额不足以支付 prefund:', insufficientBuyers);
804
+ throw new Error(`Buyer 钱包 OKB 余额不足!在 ERC-4337 的 handleOps 中,所有 UserOps 在 Validation 阶段就需要支付 prefund,此时 hop 链还没有执行。` +
805
+ `请先给 buyer 钱包充值 OKB。不足的钱包: ${insufficientBuyers.length} 个`);
806
+ }
807
+ console.log('[AA DEX 多跳] ✅ Buyer 钱包 prefund 检查通过');
747
808
  }
748
809
  else {
749
810
  // Paymaster 模式:生成新钱包(不需要预充值,prefund=0)
@@ -368,12 +368,42 @@ export class AAPortalSwapExecutor {
368
368
  catch {
369
369
  quotedSellOutWei = await this.portalQuery.quoteExactInput(tokenAddress, ZERO_ADDRESS, sellAmountWei);
370
370
  }
371
- if (totalBuyWei > quotedSellOutWei) {
372
- throw new Error('AA 批量换手:buyAmountsOkb 总和超过预估卖出输出(quotedSellOut)');
373
- }
371
+ // 关键修复:提前计算 buyerPrefund,用于资金充足性检查
372
+ // capitalMode 下,分发金额 = 买入金额 + buyerPrefund + 利润
373
+ // 必须确保卖出所得 >= 分发金额,否则交易会失败
374
+ const feeDataEarly = await this.aaManager.getFeeData();
375
+ const gasPriceEarly = feeDataEarly.gasPrice ?? feeDataEarly.maxFeePerGas ?? 5000000000n;
376
+ const estimateBuyerPrefundEarly = (deployed) => {
377
+ const callGas = PORTAL_BUY_CALL_GAS_LIMIT;
378
+ const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
379
+ const preVerifyGas = PRE_VERIFICATION_GAS;
380
+ const totalGas = callGas + verifyGas + preVerifyGas;
381
+ return (totalGas * gasPriceEarly * PREFUND_BUFFER_PERCENT) / 100n;
382
+ };
383
+ // ✅ 计算所有 buyer 需要的 prefund 总和(仅原生代币模式 + capitalMode 需要)
384
+ const totalBuyerPrefundEarly = (useNativeToken && capitalMode)
385
+ ? buyerAis.reduce((sum, ai) => sum + estimateBuyerPrefundEarly(ai.deployed), 0n)
386
+ : 0n;
387
+ // ✅ 计算利润(考虑 buyerPrefund 后的可用利润空间)
374
388
  const profitWeiRaw = extractProfit ? calculateProfitWei(quotedSellOutWei, profitBps) : 0n;
375
- const profitCap = quotedSellOutWei - totalBuyWei;
376
- const profitWei = profitWeiRaw > profitCap ? profitCap : profitWeiRaw;
389
+ // 利润上限 = 卖出所得 - 买入金额 - buyerPrefund
390
+ const profitCapWithPrefund = quotedSellOutWei > (totalBuyWei + totalBuyerPrefundEarly)
391
+ ? (quotedSellOutWei - totalBuyWei - totalBuyerPrefundEarly)
392
+ : 0n;
393
+ const profitWei = profitWeiRaw > profitCapWithPrefund ? profitCapWithPrefund : profitWeiRaw;
394
+ // ✅ 资金充足性检查:卖出所得 >= 买入金额 + buyerPrefund + 利润
395
+ const totalDistributeNeeded = totalBuyWei + totalBuyerPrefundEarly + profitWei;
396
+ if (quotedSellOutWei > 0n && totalDistributeNeeded > quotedSellOutWei) {
397
+ console.error('[AA Portal 批量换手] ❌ 资金不足:', {
398
+ quotedSellOutWei: ethers.formatEther(quotedSellOutWei),
399
+ totalBuyWei: ethers.formatEther(totalBuyWei),
400
+ totalBuyerPrefund: ethers.formatEther(totalBuyerPrefundEarly),
401
+ profitWei: ethers.formatEther(profitWei),
402
+ totalNeeded: ethers.formatEther(totalDistributeNeeded),
403
+ shortfall: ethers.formatEther(totalDistributeNeeded - quotedSellOutWei),
404
+ });
405
+ throw new Error(`AA 批量换手:资金不足!卖出预估 ${ethers.formatEther(quotedSellOutWei)} OKB,但分发需要 ${ethers.formatEther(totalDistributeNeeded)} OKB(买入 ${ethers.formatEther(totalBuyWei)} + prefund ${ethers.formatEther(totalBuyerPrefundEarly)} + 利润 ${ethers.formatEther(profitWei)}),缺口 ${ethers.formatEther(totalDistributeNeeded - quotedSellOutWei)} OKB`);
406
+ }
377
407
  // ✅ 利润在分发阶段通过 AA 内部刮取
378
408
  // ✅ 资金分发逻辑(仅 capitalMode=true 时执行,对应 BSC flapQuickBatchSwapMerkle)
379
409
  // capitalMode=false 时跳过分发,买方用自己的 OKB(对应 BSC flapBatchSwapMerkle)
@@ -434,29 +464,44 @@ export class AAPortalSwapExecutor {
434
464
  const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
435
465
  const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
436
466
  console.log(`[AA Portal 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
437
- // ✅ 直接分发模式:分发金额 = 买入金额 + 买方 prefund
438
- const items = buyerSenders.map((to, i) => ({
439
- to,
440
- value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n) // ✅ 包含 prefund
441
- })).filter(x => x.value > 0n);
442
- if (extractProfit && profitWei > 0n) {
443
- items.push({ to: profitRecipient, value: profitWei });
444
- }
467
+ // ✅ 直接分发模式:
468
+ // - 原生代币模式:分发金额 = 买入金额 + 买方 prefund
469
+ // - ERC20 模式:只分发 ERC20 代币,prefund 需要 buyer 自己有 OKB
470
+ const items = buyerSenders.map((to, i) => {
471
+ const buyAmount = buyAmountsWei[i] ?? 0n;
472
+ const prefund = buyerPrefunds[i] ?? 0n;
473
+ return {
474
+ to,
475
+ buyAmount, // ERC20 代币金额
476
+ prefund, // OKB prefund(仅原生代币模式使用)
477
+ totalValue: buyAmount + prefund // 原生代币模式的总金额
478
+ };
479
+ }).filter(x => x.buyAmount > 0n);
480
+ // 添加利润接收者到分发列表(只用于原生代币模式的金额计算)
481
+ const profitItem = (extractProfit && profitWei > 0n)
482
+ ? { to: profitRecipient, buyAmount: 0n, prefund: 0n, totalValue: profitWei }
483
+ : null;
445
484
  const chunks = chunkArray(items, maxPerOp);
446
485
  for (const ch of chunks) {
447
486
  let callData;
448
487
  if (useNativeToken) {
488
+ // ✅ 原生代币模式:使用 Multicall3 批量转账(包含 prefund)
489
+ const allItems = profitItem && ch === chunks[0] ? [...ch, profitItem] : ch;
449
490
  const { totalValue, data } = encodeNativeDisperseViaMulticall3({
450
- to: ch.map(x => x.to),
451
- values: ch.map(x => x.value),
491
+ to: allItems.map(x => x.to),
492
+ values: allItems.map(x => x.totalValue),
452
493
  });
453
494
  callData = encodeExecute(MULTICALL3, totalValue, data);
454
495
  }
455
496
  else {
456
- // ERC20 模式
457
- const targets = ch.map(() => quoteToken);
458
- const values = ch.map(() => 0n);
459
- const datas = ch.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.value]));
497
+ // ERC20 模式:只转账 ERC20 代币(不包含 OKB prefund)
498
+ // 注意:buyer 需要自己有足够的 OKB 来支付 prefund,否则交易会失败
499
+ const allItems = profitItem && ch === chunks[0]
500
+ ? [...ch, { ...profitItem, buyAmount: profitWei }]
501
+ : ch;
502
+ const targets = allItems.map(() => quoteToken);
503
+ const values = allItems.map(() => 0n);
504
+ const datas = allItems.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.buyAmount]));
460
505
  callData = encodeExecuteBatch(targets, values, datas);
461
506
  }
462
507
  const signedDisperse = await this.aaManager.buildUserOpWithState({
@@ -552,6 +597,24 @@ export class AAPortalSwapExecutor {
552
597
  allGeneratedHopWallets.push(chainHops);
553
598
  }
554
599
  console.log(`[AA Portal 多跳] ✅ 使用预充值 hop 钱包: ${totalHopsNeeded} 个`);
600
+ // ✅ 关键修复:检查 buyer 钱包是否有足够的 OKB 支付 prefund
601
+ // ERC-4337 的 handleOps 会先验证所有 UserOps(需要 prefund),然后才执行
602
+ // 所以 buyer 需要在交易前就有足够的 OKB,不能依赖 hop 链在 execution 阶段转入的资金
603
+ const buyerBalances = await Promise.all(buyerSenders.map(s => provider.getBalance(s)));
604
+ const insufficientBuyers = [];
605
+ for (let i = 0; i < buyerSenders.length; i++) {
606
+ const buyerAi = buyerAis[i];
607
+ const buyerPrefundNeeded = estimatePrefund(PORTAL_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
608
+ if (buyerBalances[i] < buyerPrefundNeeded) {
609
+ insufficientBuyers.push(`${buyerSenders[i]} (余额: ${ethers.formatEther(buyerBalances[i])} OKB, 需要: ${ethers.formatEther(buyerPrefundNeeded)} OKB, 缺口: ${ethers.formatEther(buyerPrefundNeeded - buyerBalances[i])} OKB)`);
610
+ }
611
+ }
612
+ if (insufficientBuyers.length > 0) {
613
+ console.error('[AA Portal 多跳] ❌ 以下 buyer 钱包 OKB 余额不足以支付 prefund:', insufficientBuyers);
614
+ throw new Error(`Buyer 钱包 OKB 余额不足!在 ERC-4337 的 handleOps 中,所有 UserOps 在 Validation 阶段就需要支付 prefund,此时 hop 链还没有执行。` +
615
+ `请先给 buyer 钱包充值 OKB。不足的钱包: ${insufficientBuyers.length} 个`);
616
+ }
617
+ console.log('[AA Portal 多跳] ✅ Buyer 钱包 prefund 检查通过');
555
618
  }
556
619
  else {
557
620
  // Paymaster 模式:生成新钱包
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.44",
3
+ "version": "1.6.47",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,4 +36,4 @@
36
36
  "devDependencies": {
37
37
  "typescript": "^5.6.3"
38
38
  }
39
- }
39
+ }