four-flap-meme-sdk 1.6.30 → 1.6.31

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.
@@ -3,7 +3,11 @@
3
3
  */
4
4
  import { Wallet, ethers } from 'ethers';
5
5
  import { AANonceMap, } from './types.js';
6
- import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_V3_FACTORY, WOKB, MULTICALL3, } from './constants.js';
6
+ import { POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, POTATOSWAP_V3_FACTORY, WOKB, MULTICALL3, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, } from './constants.js';
7
+ // 多跳 prefund 估算用的 gas 常量(可配置)
8
+ const HOP_CALL_GAS_LIMIT = 150000n; // hop 转账操作
9
+ const DEX_BUY_CALL_GAS_LIMIT = 650000n; // DEX V3 买入操作
10
+ const PREFUND_BUFFER_PERCENT = 120n; // prefund buffer 百分比 (120 = 1.2x)
7
11
  import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
8
12
  import { encodeApproveCall, lpFeeProfileToV3Fee, } from './portal-ops.js';
9
13
  import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, encodeSwapExactTokensForTokensSupportingFee, } from './dex.js';
@@ -586,13 +590,12 @@ export class AADexSwapExecutor {
586
590
  const feeData = await this.aaManager.getFeeData();
587
591
  const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
588
592
  const estimateBuyerPrefund = (deployed) => {
589
- // 买入操作的 gas:V3 swap 约 650,000,部署需要额外 verificationGas
590
- const callGas = 650000n;
591
- const verifyGas = deployed ? 250000n : 800000n;
592
- const preVerifyGas = 60000n;
593
+ // 买入操作的 gas(使用常量)
594
+ const callGas = DEX_BUY_CALL_GAS_LIMIT;
595
+ const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
596
+ const preVerifyGas = PRE_VERIFICATION_GAS;
593
597
  const totalGas = callGas + verifyGas + preVerifyGas;
594
- // 20% buffer 以确保足够
595
- return (totalGas * gasPrice * 120n) / 100n;
598
+ return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
596
599
  };
597
600
  // 计算每个买方需要的 prefund
598
601
  const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
@@ -637,183 +640,149 @@ export class AADexSwapExecutor {
637
640
  }
638
641
  }
639
642
  else {
640
- // ✅ 多跳模式:动态生成独立的多跳钱包(不再使用买方钱包)
641
- console.log('[AA DEX 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount });
642
- // 动态生成 hopCount 个独立的多跳钱包
643
- const generatedHopWallets = [];
644
- for (let i = 0; i < hopCount; i++) {
645
- const randomWallet = ethers.Wallet.createRandom();
646
- generatedHopWallets.push(new ethers.Wallet(randomWallet.privateKey));
647
- }
648
- // 预测这些 hop 钱包的 AA sender 地址
649
- const hopAiPromises = generatedHopWallets.map(async (w) => {
650
- const sender = await this.aaManager.predictSenderAddress(w.address);
651
- const code = await provider.getCode(sender);
652
- const deployed = code && code !== '0x';
653
- const initCode = deployed ? '0x' : this.aaManager.generateInitCode(w.address);
654
- return { sender, deployed, initCode, ownerAddress: w.address };
655
- });
656
- const hopAis = await Promise.all(hopAiPromises);
657
- const hopSenders = hopAis.map(ai => ai.sender);
658
- const hopOwners = generatedHopWallets;
659
- // ✅ 关键:为动态生成的 hop 钱包初始化 nonce(新账户从 0 开始)
660
- for (const hopAi of hopAis) {
661
- nonceMap.init(hopAi.sender, 0n);
662
- }
663
- // ✅ 关键修复:计算每个 hop 钱包执行 UserOp 需要的 prefund(gas 费用)
664
- // EntryPoint 在 validation 阶段会扣除 prefund,此时 hop 钱包还没收到 seller 的转账
665
- // 因此需要在 seller 转账时额外包含 hop 的 gas 费用
643
+ // ✅ 多跳模式(BSC 模式):每个买方有独立的多跳链
644
+ // 填写 3 + 2 个买方 = 2 条独立的多跳链,每条 3 个 hop 钱包 = 6 个动态生成的 hop 钱包
645
+ console.log('[AA DEX 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount, buyerCount: buyerSenders.length });
666
646
  const feeData = await this.aaManager.getFeeData();
667
- const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
668
- // 每个 hop 钱包的预估 prefund:(callGasLimit + verificationGasLimit + preVerificationGas) * gasPrice
669
- // 未部署账户需要更高的 verificationGasLimit(约 800,000),已部署约 250,000
670
- // callGasLimit 150,000(native 转账),preVerificationGas 21,000
671
- const estimateHopPrefund = (deployed) => {
672
- const callGas = 150000n;
673
- const verifyGas = deployed ? 250000n : 800000n;
674
- const preVerifyGas = 21000n;
647
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
648
+ // 预估 prefund 的函数(使用全局常量)
649
+ const estimatePrefund = (callGas, deployed) => {
650
+ const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
651
+ const preVerifyGas = PRE_VERIFICATION_GAS;
675
652
  const totalGas = callGas + verifyGas + preVerifyGas;
676
- // 20% buffer 以确保足够
677
- return (totalGas * gasPrice * 120n) / 100n;
653
+ return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
678
654
  };
679
- // 计算所有 hop 钱包需要的总 prefund
680
- let totalHopPrefund = 0n;
681
- const hopPrefunds = [];
682
- for (let j = 0; j < hopSenders.length; j++) {
683
- const isLastHop = j === hopSenders.length - 1;
684
- const hopAi = hopAis[j];
685
- const hopDeployed = hopAi.deployed;
686
- if (isLastHop) {
687
- // 最后一个 hop 需要分发给所有买方
688
- const disperseOpCount = Math.ceil(buyerSenders.length / maxPerOp) || 1;
689
- const prefund = estimateHopPrefund(hopDeployed) * BigInt(disperseOpCount);
690
- hopPrefunds.push(prefund);
691
- totalHopPrefund += prefund;
692
- }
693
- else {
694
- const prefund = estimateHopPrefund(hopDeployed);
695
- hopPrefunds.push(prefund);
696
- totalHopPrefund += prefund;
655
+ // 为每个买方生成独立的多跳链
656
+ // hopChains[buyerIdx] = [hop0, hop1, ..., hop(H-1)] 每条链有 hopCount 个 hop 钱包
657
+ const allGeneratedHopWallets = [];
658
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
659
+ const chainHops = [];
660
+ for (let h = 0; h < hopCount; h++) {
661
+ const randomWallet = ethers.Wallet.createRandom();
662
+ const wallet = new ethers.Wallet(randomWallet.privateKey);
663
+ const sender = await this.aaManager.predictSenderAddress(wallet.address);
664
+ const code = await provider.getCode(sender);
665
+ const deployed = code !== null && code !== '0x';
666
+ const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
667
+ chainHops.push({ wallet, sender, deployed, initCode });
668
+ // 初始化 nonce
669
+ nonceMap.init(sender, 0n);
697
670
  }
671
+ allGeneratedHopWallets.push(chainHops);
698
672
  }
699
- console.log(`[AA DEX 多跳] hop 数量: ${hopSenders.length}, prefund 预估: ${ethers.formatEther(totalHopPrefund)} OKB`);
700
- const hop0 = hopSenders[0];
701
- let callData0;
702
- // ✅ 关键修复:seller 转给 hop0 时,额外包含所有 hop 的 gas 费用
703
- const amountToHop0 = totalBuyWei + totalHopPrefund;
704
- if (useNativeToken) {
705
- // 原生代币模式
706
- if (extractProfit && profitWei > 0n) {
707
- callData0 = encodeExecuteBatch([hop0, profitRecipient], [amountToHop0, profitWei], ['0x', '0x']);
673
+ console.log(`[AA DEX 多跳] hop 钱包数: ${buyerSenders.length * hopCount} (${buyerSenders.length} 条链 × ${hopCount} 跳)`);
674
+ // 利润刮取(只在第一笔 seller UserOp 中执行)
675
+ let profitHandled = false;
676
+ // ✅ 构建每条多跳链的 UserOps
677
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
678
+ const chainHops = allGeneratedHopWallets[buyerIdx];
679
+ const buyerSender = buyerSenders[buyerIdx];
680
+ const buyerAi = buyerAis[buyerIdx];
681
+ const buyAmount = buyAmountsWei[buyerIdx] ?? 0n;
682
+ if (buyAmount <= 0n)
683
+ continue;
684
+ // 计算这条链上所有 hop 的 prefund(使用常量)
685
+ const hopPrefunds = chainHops.map((hop, idx) => {
686
+ // 最后一个 hop 转账给 buyer,中间 hop 转账给下一个 hop
687
+ return estimatePrefund(HOP_CALL_GAS_LIMIT, hop.deployed);
688
+ });
689
+ const totalHopPrefund = hopPrefunds.reduce((a, b) => a + b, 0n);
690
+ // 计算 buyer 的 prefund(用于买入,使用常量)
691
+ const buyerPrefund = estimatePrefund(DEX_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
692
+ // 这条链的总金额 = 买入金额 + 所有 hop prefund + buyer prefund
693
+ const chainTotalAmount = buyAmount + totalHopPrefund + buyerPrefund;
694
+ // 1) seller → hop0
695
+ const hop0 = chainHops[0];
696
+ let callData0;
697
+ if (useNativeToken) {
698
+ if (!profitHandled && extractProfit && profitWei > 0n) {
699
+ // 第一笔 seller UserOp 同时刮取利润
700
+ callData0 = encodeExecuteBatch([hop0.sender, profitRecipient], [chainTotalAmount, profitWei], ['0x', '0x']);
701
+ profitHandled = true;
702
+ }
703
+ else {
704
+ callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
705
+ }
708
706
  }
709
707
  else {
710
- callData0 = encodeExecute(hop0, amountToHop0, '0x');
711
- }
712
- }
713
- else {
714
- // ERC20 模式
715
- if (extractProfit && profitWei > 0n) {
716
- const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0, totalBuyWei]);
717
- const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
718
- callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
708
+ if (!profitHandled && extractProfit && profitWei > 0n) {
709
+ const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
710
+ const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
711
+ callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
712
+ profitHandled = true;
713
+ }
714
+ else {
715
+ const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
716
+ callData0 = encodeExecute(quoteToken, 0n, transferData);
717
+ }
719
718
  }
720
- else {
721
- const transferData = erc20Iface.encodeFunctionData('transfer', [hop0, totalBuyWei]);
722
- callData0 = encodeExecute(quoteToken, 0n, transferData);
719
+ const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
720
+ ownerWallet: sellerOwner,
721
+ sender: sellerAi.sender,
722
+ nonce: nonceMap.next(sellerAi.sender),
723
+ initCode: consumeInitCode(sellerAi.sender),
724
+ callData: callData0,
725
+ signOnly: true,
726
+ });
727
+ outOps.push(signedSellerToHop0.userOp);
728
+ // 2) hop[j] → hop[j+1] (中间跳)
729
+ let remainingPrefund = totalHopPrefund;
730
+ for (let j = 0; j < chainHops.length - 1; j++) {
731
+ const currentHop = chainHops[j];
732
+ const nextHop = chainHops[j + 1];
733
+ // 当前 hop 扣除自己的 prefund 后转给下一个
734
+ remainingPrefund -= hopPrefunds[j];
735
+ const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
736
+ let callData;
737
+ if (useNativeToken) {
738
+ callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
739
+ }
740
+ else {
741
+ const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
742
+ callData = encodeExecute(quoteToken, 0n, transferData);
743
+ }
744
+ const signedHop = await this.aaManager.buildUserOpWithState({
745
+ ownerWallet: currentHop.wallet,
746
+ sender: currentHop.sender,
747
+ nonce: nonceMap.next(currentHop.sender),
748
+ initCode: currentHop.initCode,
749
+ callData,
750
+ signOnly: true,
751
+ });
752
+ outOps.push(signedHop.userOp);
723
753
  }
724
- }
725
- const signedToHop0 = await this.aaManager.buildUserOpWithState({
726
- ownerWallet: sellerOwner,
727
- sender: sellerAi.sender,
728
- nonce: nonceMap.next(sellerAi.sender),
729
- initCode: consumeInitCode(sellerAi.sender),
730
- callData: callData0,
731
- signOnly: true,
732
- });
733
- outOps.push(signedToHop0.userOp);
734
- // 2) hop j -> hop j+1
735
- // ✅ 修复:动态生成的 hop 钱包只做资金中转,不保留任何金额
736
- let prefixPrefundUsed = hopPrefunds[0] ?? 0n; // hop0 自己用掉的 prefund
737
- for (let j = 0; j < hopSenders.length - 1; j++) {
738
- const sender = hopSenders[j];
739
- const next = hopSenders[j + 1];
740
- const hopAi = hopAis[j];
741
- // 剩余需要转给后续 hop 的金额 = totalBuyWei + (剩余 hop 的 prefund)
742
- const remainingPrefund = totalHopPrefund - prefixPrefundUsed;
743
- const amountToTransfer = totalBuyWei + remainingPrefund;
744
- if (amountToTransfer <= 0n)
745
- break;
746
- let callData;
754
+ // 3) lastHop → buyer
755
+ const lastHop = chainHops[chainHops.length - 1];
756
+ const amountToBuyer = buyAmount + buyerPrefund;
757
+ let callDataLast;
747
758
  if (useNativeToken) {
748
- callData = encodeExecute(next, amountToTransfer, '0x');
759
+ callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
749
760
  }
750
761
  else {
751
- // ERC20 模式:仍使用原来的 totalBuyWei(不含 prefund,因为 ERC20 模式 prefund 仍为 OKB)
752
- const transferData = erc20Iface.encodeFunctionData('transfer', [next, totalBuyWei]);
753
- callData = encodeExecute(quoteToken, 0n, transferData);
762
+ const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
763
+ callDataLast = encodeExecute(quoteToken, 0n, transferData);
754
764
  }
755
- const signedHop = await this.aaManager.buildUserOpWithState({
756
- ownerWallet: hopOwners[j],
757
- sender,
758
- nonce: nonceMap.next(sender),
759
- initCode: hopAi.initCode || '0x', // ✅ 使用动态生成的 hop 钱包的 initCode
760
- callData,
765
+ const signedLastHop = await this.aaManager.buildUserOpWithState({
766
+ ownerWallet: lastHop.wallet,
767
+ sender: lastHop.sender,
768
+ nonce: nonceMap.next(lastHop.sender),
769
+ initCode: lastHop.initCode,
770
+ callData: callDataLast,
761
771
  signOnly: true,
762
772
  });
763
- outOps.push(signedHop.userOp);
764
- // ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
765
- prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
773
+ outOps.push(signedLastHop.userOp);
766
774
  }
767
- // 3) lastHop 分发给所有买方(动态生成 hop 后,所有买方都是真正买方)
768
- const lastHopIdx = hopSenders.length - 1;
769
- const lastHopSender = hopSenders[lastHopIdx];
770
- const lastHopOwner = hopOwners[lastHopIdx];
771
- const lastHopAi = hopAis[lastHopIdx];
772
- // ✅ 计算每个买方需要的 prefund(用于执行买入 UserOp)
773
- const estimateBuyerPrefund = (deployed) => {
774
- const callGas = 650000n; // DEX V3 买入
775
- const verifyGas = deployed ? 250000n : 800000n;
776
- const preVerifyGas = 60000n;
777
- const totalGas = callGas + verifyGas + preVerifyGas;
778
- return (totalGas * gasPrice * 120n) / 100n;
779
- };
780
- const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
781
- // ✅ 分发金额 = 买入金额 + 买方 prefund
782
- const allItems = buyerSenders.map((to, i) => ({
783
- to,
784
- value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n)
785
- })).filter(x => x.value > 0n);
786
- const restChunks = chunkArray(allItems, maxPerOp);
787
- for (const ch of restChunks) {
788
- let callData;
789
- if (useNativeToken) {
790
- const { totalValue, data } = encodeNativeDisperseViaMulticall3({
791
- to: ch.map(x => x.to),
792
- values: ch.map(x => x.value),
775
+ // 保存所有动态生成的多跳钱包信息,用于后续导出
776
+ const allHopWalletsFlat = [];
777
+ for (const chainHops of allGeneratedHopWallets) {
778
+ for (const hop of chainHops) {
779
+ allHopWalletsFlat.push({
780
+ address: hop.sender,
781
+ privateKey: hop.wallet.privateKey,
793
782
  });
794
- callData = encodeExecute(MULTICALL3, totalValue, data);
795
783
  }
796
- else {
797
- const targets = ch.map(() => quoteToken);
798
- const values = ch.map(() => 0n);
799
- const datas = ch.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.value]));
800
- callData = encodeExecuteBatch(targets, values, datas);
801
- }
802
- const signedDisperse = await this.aaManager.buildUserOpWithState({
803
- ownerWallet: lastHopOwner,
804
- sender: lastHopSender,
805
- nonce: nonceMap.next(lastHopSender),
806
- initCode: lastHopAi.initCode || '0x', // ✅ 使用动态生成的 hop 钱包的 initCode
807
- callData,
808
- signOnly: true,
809
- });
810
- outOps.push(signedDisperse.userOp);
811
784
  }
812
- // 保存动态生成的多跳钱包信息,用于后续导出
813
- this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
814
- address: hopSenders[i],
815
- privateKey: w.privateKey,
816
- }));
785
+ this._generatedHopWallets = allHopWalletsFlat;
817
786
  }
818
787
  }
819
788
  else if (!capitalMode && extractProfit && profitWei > 0n) {
@@ -3,7 +3,11 @@
3
3
  */
4
4
  import { Wallet, ethers } from 'ethers';
5
5
  import { AANonceMap, } from './types.js';
6
- import { FLAP_PORTAL, ZERO_ADDRESS, MULTICALL3, } from './constants.js';
6
+ import { FLAP_PORTAL, ZERO_ADDRESS, MULTICALL3, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, } from './constants.js';
7
+ // 多跳 prefund 估算用的 gas 常量(可配置)
8
+ const HOP_CALL_GAS_LIMIT = 150000n; // hop 转账操作
9
+ const PORTAL_BUY_CALL_GAS_LIMIT = 450000n; // Portal 买入操作
10
+ const PREFUND_BUFFER_PERCENT = 120n; // prefund buffer 百分比 (120 = 1.2x)
7
11
  import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
8
12
  import { encodeBuyCall, encodeSellCall, encodeBuyCallWithQuote, encodeSellCallWithQuote, encodeApproveCall, PortalQuery, parseOkb, } from './portal-ops.js';
9
13
  import { BundleExecutor } from './bundle.js';
@@ -395,13 +399,12 @@ export class AAPortalSwapExecutor {
395
399
  const feeData = await this.aaManager.getFeeData();
396
400
  const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
397
401
  const estimateBuyerPrefund = (deployed) => {
398
- // 买入操作的 gas:Portal 买入约 450,000,部署需要额外 verificationGas
399
- const callGas = 450000n;
400
- const verifyGas = deployed ? 250000n : 800000n;
401
- const preVerifyGas = 60000n;
402
+ // 买入操作的 gas(使用常量)
403
+ const callGas = PORTAL_BUY_CALL_GAS_LIMIT;
404
+ const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
405
+ const preVerifyGas = PRE_VERIFICATION_GAS;
402
406
  const totalGas = callGas + verifyGas + preVerifyGas;
403
- // 20% buffer 以确保足够
404
- return (totalGas * gasPrice * 120n) / 100n;
407
+ return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
405
408
  };
406
409
  // 计算每个买方需要的 prefund
407
410
  const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
@@ -444,185 +447,151 @@ export class AAPortalSwapExecutor {
444
447
  }
445
448
  }
446
449
  else {
447
- // ✅ 多跳模式:动态生成独立的多跳钱包(不再使用买方钱包)
448
- console.log('[AA Portal 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount });
449
- // 动态生成 hopCount 个独立的多跳钱包
450
- const generatedHopWallets = [];
451
- for (let i = 0; i < hopCount; i++) {
452
- const randomWallet = ethers.Wallet.createRandom();
453
- generatedHopWallets.push(new ethers.Wallet(randomWallet.privateKey));
454
- }
455
- // 预测这些 hop 钱包的 AA sender 地址
456
- const provider = this.aaManager.getProvider();
457
- const hopAiPromises = generatedHopWallets.map(async (w) => {
458
- const sender = await this.aaManager.predictSenderAddress(w.address);
459
- const code = await provider.getCode(sender);
460
- const deployed = code && code !== '0x';
461
- const initCode = deployed ? '0x' : this.aaManager.generateInitCode(w.address);
462
- return { sender, deployed, initCode, ownerAddress: w.address };
463
- });
464
- const hopAis = await Promise.all(hopAiPromises);
465
- const hopSenders = hopAis.map(ai => ai.sender);
466
- const hopOwners = generatedHopWallets;
467
- // ✅ 关键:为动态生成的 hop 钱包初始化 nonce(新账户从 0 开始)
468
- for (const hopAi of hopAis) {
469
- nonceMap.init(hopAi.sender, 0n);
470
- }
471
- // ✅ 关键修复:计算每个 hop 钱包执行 UserOp 需要的 prefund(gas 费用)
472
- // EntryPoint 在 validation 阶段会扣除 prefund,此时 hop 钱包还没收到 seller 的转账
473
- // 因此需要在 seller 转账时额外包含 hop 的 gas 费用
450
+ // ✅ 多跳模式(BSC 模式):每个买方有独立的多跳链
451
+ // 填写 3 + 2 个买方 = 2 条独立的多跳链,每条 3 个 hop 钱包 = 6 个动态生成的 hop 钱包
452
+ console.log('[AA Portal 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount, buyerCount: buyerSenders.length });
474
453
  const feeData = await this.aaManager.getFeeData();
475
- const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n; // 5 gwei fallback
476
- // 每个 hop 钱包的预估 prefund:(callGasLimit + verificationGasLimit + preVerificationGas) * gasPrice
477
- // 未部署账户需要更高的 verificationGasLimit(约 800,000),已部署约 250,000
478
- // callGasLimit 150,000(native 转账),preVerificationGas 21,000
479
- const estimateHopPrefund = (deployed) => {
480
- const callGas = 150000n;
481
- const verifyGas = deployed ? 250000n : 800000n;
482
- const preVerifyGas = 21000n;
454
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
455
+ const provider = this.aaManager.getProvider();
456
+ // 预估 prefund 的函数(使用全局常量)
457
+ const estimatePrefund = (callGas, deployed) => {
458
+ const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
459
+ const preVerifyGas = PRE_VERIFICATION_GAS;
483
460
  const totalGas = callGas + verifyGas + preVerifyGas;
484
- // 20% buffer 以确保足够
485
- return (totalGas * gasPrice * 120n) / 100n;
461
+ return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
486
462
  };
487
- // 计算所有 hop 钱包需要的总 prefund
488
- let totalHopPrefund = 0n;
489
- const hopPrefunds = [];
490
- for (let j = 0; j < hopSenders.length; j++) {
491
- // hop[j] 需要执行的 UserOp 数量:
492
- // - 中间 hop (j < length-1):1 个转账 UserOp
493
- // - 最后一个 hop:需要分发给所有买方,可能需要多个 UserOp
494
- const isLastHop = j === hopSenders.length - 1;
495
- const hopAi = hopAis[j];
496
- const hopDeployed = hopAi.deployed;
497
- if (isLastHop) {
498
- // 最后一个 hop 需要分发给所有买方(动态生成 hop 钱包后,所有买方都是真正买方)
499
- const disperseOpCount = Math.ceil(buyerSenders.length / maxPerOp) || 1;
500
- const prefund = estimateHopPrefund(hopDeployed) * BigInt(disperseOpCount);
501
- hopPrefunds.push(prefund);
502
- totalHopPrefund += prefund;
503
- }
504
- else {
505
- const prefund = estimateHopPrefund(hopDeployed);
506
- hopPrefunds.push(prefund);
507
- totalHopPrefund += prefund;
463
+ // 为每个买方生成独立的多跳链
464
+ // hopChains[buyerIdx] = [hop0, hop1, ..., hop(H-1)] 每条链有 hopCount 个 hop 钱包
465
+ const allGeneratedHopWallets = [];
466
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
467
+ const chainHops = [];
468
+ for (let h = 0; h < hopCount; h++) {
469
+ const randomWallet = ethers.Wallet.createRandom();
470
+ const wallet = new ethers.Wallet(randomWallet.privateKey);
471
+ const sender = await this.aaManager.predictSenderAddress(wallet.address);
472
+ const code = await provider.getCode(sender);
473
+ const deployed = code !== null && code !== '0x';
474
+ const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
475
+ chainHops.push({ wallet, sender, deployed, initCode });
476
+ // 初始化 nonce
477
+ nonceMap.init(sender, 0n);
508
478
  }
479
+ allGeneratedHopWallets.push(chainHops);
509
480
  }
510
- console.log(`[AA 多跳] hop 数量: ${hopSenders.length}, prefund 预估: ${ethers.formatEther(totalHopPrefund)} OKB`);
511
- const hop0 = hopSenders[0];
512
- let callData0;
513
- // ✅ 关键修复:seller 转给 hop0 时,额外包含所有 hop 的 gas 费用
514
- const amountToHop0 = totalBuyWei + totalHopPrefund;
515
- if (useNativeToken) {
516
- if (extractProfit && profitWei > 0n) {
517
- callData0 = encodeExecuteBatch([hop0, profitRecipient], [amountToHop0, profitWei], ['0x', '0x']);
481
+ console.log(`[AA 多跳] hop 钱包数: ${buyerSenders.length * hopCount} (${buyerSenders.length} 条链 × ${hopCount} 跳)`);
482
+ // 利润刮取(只在第一笔 seller UserOp 中执行)
483
+ let profitHandled = false;
484
+ // ✅ 构建每条多跳链的 UserOps
485
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
486
+ const chainHops = allGeneratedHopWallets[buyerIdx];
487
+ const buyerSender = buyerSenders[buyerIdx];
488
+ const buyerAi = buyerAis[buyerIdx];
489
+ const buyAmount = buyAmountsWei[buyerIdx] ?? 0n;
490
+ if (buyAmount <= 0n)
491
+ continue;
492
+ // 计算这条链上所有 hop 的 prefund(使用常量)
493
+ const hopPrefunds = chainHops.map((hop, idx) => {
494
+ const isLastHop = idx === chainHops.length - 1;
495
+ // 最后一个 hop 转账给 buyer,中间 hop 转账给下一个 hop
496
+ return estimatePrefund(HOP_CALL_GAS_LIMIT, hop.deployed);
497
+ });
498
+ const totalHopPrefund = hopPrefunds.reduce((a, b) => a + b, 0n);
499
+ // 计算 buyer 的 prefund(用于买入,使用常量)
500
+ const buyerPrefund = estimatePrefund(PORTAL_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
501
+ // 这条链的总金额 = 买入金额 + 所有 hop prefund + buyer prefund
502
+ const chainTotalAmount = buyAmount + totalHopPrefund + buyerPrefund;
503
+ // 1) seller → hop0
504
+ const hop0 = chainHops[0];
505
+ let callData0;
506
+ if (useNativeToken) {
507
+ if (!profitHandled && extractProfit && profitWei > 0n) {
508
+ // 第一笔 seller UserOp 同时刮取利润
509
+ callData0 = encodeExecuteBatch([hop0.sender, profitRecipient], [chainTotalAmount, profitWei], ['0x', '0x']);
510
+ profitHandled = true;
511
+ }
512
+ else {
513
+ callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
514
+ }
518
515
  }
519
516
  else {
520
- callData0 = encodeExecute(hop0, amountToHop0, '0x');
517
+ if (!profitHandled && extractProfit && profitWei > 0n) {
518
+ const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
519
+ const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
520
+ callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
521
+ profitHandled = true;
522
+ }
523
+ else {
524
+ const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
525
+ callData0 = encodeExecute(quoteToken, 0n, transferData);
526
+ }
521
527
  }
522
- }
523
- else {
524
- if (extractProfit && profitWei > 0n) {
525
- const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0, totalBuyWei]);
526
- const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
527
- callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
528
- }
529
- else {
530
- const transferData = erc20Iface.encodeFunctionData('transfer', [hop0, totalBuyWei]);
531
- callData0 = encodeExecute(quoteToken, 0n, transferData);
528
+ const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
529
+ ownerWallet: sellerOwner,
530
+ sender: sellerAi.sender,
531
+ nonce: nonceMap.next(sellerAi.sender),
532
+ initCode: consumeInitCode(sellerAi.sender),
533
+ callData: callData0,
534
+ signOnly: true,
535
+ });
536
+ outOps.push(signedSellerToHop0.userOp);
537
+ // 2) hop[j] hop[j+1] (中间跳)
538
+ let remainingPrefund = totalHopPrefund;
539
+ for (let j = 0; j < chainHops.length - 1; j++) {
540
+ const currentHop = chainHops[j];
541
+ const nextHop = chainHops[j + 1];
542
+ // 当前 hop 扣除自己的 prefund 后转给下一个
543
+ remainingPrefund -= hopPrefunds[j];
544
+ const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
545
+ let callData;
546
+ if (useNativeToken) {
547
+ callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
548
+ }
549
+ else {
550
+ const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
551
+ callData = encodeExecute(quoteToken, 0n, transferData);
552
+ }
553
+ const signedHop = await this.aaManager.buildUserOpWithState({
554
+ ownerWallet: currentHop.wallet,
555
+ sender: currentHop.sender,
556
+ nonce: nonceMap.next(currentHop.sender),
557
+ initCode: currentHop.initCode,
558
+ callData,
559
+ signOnly: true,
560
+ });
561
+ outOps.push(signedHop.userOp);
532
562
  }
533
- }
534
- const signedToHop0 = await this.aaManager.buildUserOpWithState({
535
- ownerWallet: sellerOwner,
536
- sender: sellerAi.sender,
537
- nonce: nonceMap.next(sellerAi.sender),
538
- initCode: consumeInitCode(sellerAi.sender),
539
- callData: callData0,
540
- signOnly: true,
541
- });
542
- outOps.push(signedToHop0.userOp);
543
- // 2) hop j -> hop j+1
544
- // ✅ 修复:动态生成的 hop 钱包只做资金中转,不保留任何金额
545
- let prefixPrefundUsed = hopPrefunds[0] ?? 0n; // hop0 自己用掉的 prefund
546
- for (let j = 0; j < hopSenders.length - 1; j++) {
547
- const sender = hopSenders[j];
548
- const next = hopSenders[j + 1];
549
- const hopAi = hopAis[j];
550
- // 剩余需要转给后续 hop 的金额 = totalBuyWei + (剩余 hop 的 prefund)
551
- const remainingPrefund = totalHopPrefund - prefixPrefundUsed;
552
- const amountToTransfer = totalBuyWei + remainingPrefund;
553
- if (amountToTransfer <= 0n)
554
- break;
555
- let callData;
563
+ // 3) lastHop → buyer
564
+ const lastHop = chainHops[chainHops.length - 1];
565
+ const amountToBuyer = buyAmount + buyerPrefund;
566
+ let callDataLast;
556
567
  if (useNativeToken) {
557
- callData = encodeExecute(next, amountToTransfer, '0x');
568
+ callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
558
569
  }
559
570
  else {
560
- // ERC20 模式:仍使用原来的 totalBuyWei(不含 prefund,因为 ERC20 模式 prefund 仍为 OKB)
561
- const transferData = erc20Iface.encodeFunctionData('transfer', [next, totalBuyWei]);
562
- callData = encodeExecute(quoteToken, 0n, transferData);
571
+ const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
572
+ callDataLast = encodeExecute(quoteToken, 0n, transferData);
563
573
  }
564
- const signedHop = await this.aaManager.buildUserOpWithState({
565
- ownerWallet: hopOwners[j],
566
- sender,
567
- nonce: nonceMap.next(sender),
568
- initCode: hopAi.initCode || '0x',
569
- callData,
574
+ const signedLastHop = await this.aaManager.buildUserOpWithState({
575
+ ownerWallet: lastHop.wallet,
576
+ sender: lastHop.sender,
577
+ nonce: nonceMap.next(lastHop.sender),
578
+ initCode: lastHop.initCode,
579
+ callData: callDataLast,
570
580
  signOnly: true,
571
581
  });
572
- outOps.push(signedHop.userOp);
573
- // ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
574
- prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
582
+ outOps.push(signedLastHop.userOp);
575
583
  }
576
- // 3) lastHop 分发给所有买方(动态生成 hop 后,所有买方都是真正买方)
577
- const lastHopIdx = hopSenders.length - 1;
578
- const lastHopSender = hopSenders[lastHopIdx];
579
- const lastHopOwner = hopOwners[lastHopIdx];
580
- const lastHopAi = hopAis[lastHopIdx];
581
- // ✅ 计算每个买方需要的 prefund(用于执行买入 UserOp)
582
- const estimateBuyerPrefund = (deployed) => {
583
- const callGas = 450000n; // Portal 买入
584
- const verifyGas = deployed ? 250000n : 800000n;
585
- const preVerifyGas = 60000n;
586
- const totalGas = callGas + verifyGas + preVerifyGas;
587
- return (totalGas * gasPrice * 120n) / 100n;
588
- };
589
- const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
590
- // ✅ 分发金额 = 买入金额 + 买方 prefund
591
- const allItems = buyerSenders.map((to, i) => ({
592
- to,
593
- value: (buyAmountsWei[i] ?? 0n) + (buyerPrefunds[i] ?? 0n)
594
- })).filter(x => x.value > 0n);
595
- const restChunks = chunkArray(allItems, maxPerOp);
596
- for (const ch of restChunks) {
597
- let callData;
598
- if (useNativeToken) {
599
- const { totalValue, data } = encodeNativeDisperseViaMulticall3({
600
- to: ch.map(x => x.to),
601
- values: ch.map(x => x.value),
584
+ // 保存所有动态生成的多跳钱包信息,用于后续导出
585
+ const allHopWalletsFlat = [];
586
+ for (const chainHops of allGeneratedHopWallets) {
587
+ for (const hop of chainHops) {
588
+ allHopWalletsFlat.push({
589
+ address: hop.sender,
590
+ privateKey: hop.wallet.privateKey,
602
591
  });
603
- callData = encodeExecute(MULTICALL3, totalValue, data);
604
592
  }
605
- else {
606
- const targets = ch.map(() => quoteToken);
607
- const values = ch.map(() => 0n);
608
- const datas = ch.map(x => erc20Iface.encodeFunctionData('transfer', [x.to, x.value]));
609
- callData = encodeExecuteBatch(targets, values, datas);
610
- }
611
- const signedDisperse = await this.aaManager.buildUserOpWithState({
612
- ownerWallet: lastHopOwner,
613
- sender: lastHopSender,
614
- nonce: nonceMap.next(lastHopSender),
615
- initCode: lastHopAi.initCode || '0x', // ✅ 使用动态生成的 hop 钱包的 initCode
616
- callData,
617
- signOnly: true,
618
- });
619
- outOps.push(signedDisperse.userOp);
620
593
  }
621
- // 保存动态生成的多跳钱包信息,用于后续导出
622
- this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
623
- address: hopSenders[i],
624
- privateKey: w.privateKey,
625
- }));
594
+ this._generatedHopWallets = allHopWalletsFlat;
626
595
  }
627
596
  }
628
597
  else if (!capitalMode && extractProfit && profitWei > 0n) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.30",
3
+ "version": "1.6.31",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",