four-flap-meme-sdk 1.6.30 → 1.6.32

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.
@@ -218,8 +218,28 @@ export declare function computeUserOpHashV06(userOp: UserOperation, entryPoint:
218
218
  export declare function encodeExecute(dest: string, value: bigint, data: string): string;
219
219
  /**
220
220
  * 编码 SimpleAccount.executeBatch 调用
221
+ *
222
+ * ✅ 兼容性修复:
223
+ * - 如果所有 values 都是 0,使用 executeBatch(address[],bytes[])(选择器 0x18dfb3c7)
224
+ * - 如果有非零 value,使用 executeBatch(address[],uint256[],bytes[])(选择器 0x47e1da2a)
225
+ *
226
+ * 注意:某些 AA 账户实现只支持无 values 的版本,此时需要用 Multicall3 或多次 execute
221
227
  */
222
228
  export declare function encodeExecuteBatch(dests: string[], values: bigint[], datas: string[]): string;
229
+ /**
230
+ * ✅ 使用 Multicall3 编码批量转账(兼容所有 AA 实现)
231
+ *
232
+ * 当 AA 账户不支持 executeBatch(address[],uint256[],bytes[]) 时,
233
+ * 可以用 Multicall3 打包多个调用,再通过单个 execute 执行
234
+ *
235
+ * @param calls 调用列表 [{target, value, data}, ...]
236
+ * @returns execute(multicall3, totalValue, aggregate3ValueData)
237
+ */
238
+ export declare function encodeExecuteViaMulticall3(calls: Array<{
239
+ target: string;
240
+ value: bigint;
241
+ data: string;
242
+ }>): string;
223
243
  /**
224
244
  * 创建 AA 账户管理器
225
245
  */
@@ -800,12 +800,49 @@ export function encodeExecute(dest, value, data) {
800
800
  }
801
801
  /**
802
802
  * 编码 SimpleAccount.executeBatch 调用
803
+ *
804
+ * ✅ 兼容性修复:
805
+ * - 如果所有 values 都是 0,使用 executeBatch(address[],bytes[])(选择器 0x18dfb3c7)
806
+ * - 如果有非零 value,使用 executeBatch(address[],uint256[],bytes[])(选择器 0x47e1da2a)
807
+ *
808
+ * 注意:某些 AA 账户实现只支持无 values 的版本,此时需要用 Multicall3 或多次 execute
803
809
  */
804
810
  export function encodeExecuteBatch(dests, values, datas) {
805
811
  const iface = new Interface(SIMPLE_ACCOUNT_ABI);
806
- // 使用完整签名消除 ethers v6 对重载函数的歧义
812
+ // 检查是否所有 values 都是 0
813
+ const allZeroValues = values.every(v => v === 0n);
814
+ if (allZeroValues) {
815
+ // 使用无 values 的签名(更多 AA 实现支持)
816
+ return iface.encodeFunctionData('executeBatch(address[],bytes[])', [dests, datas]);
817
+ }
818
+ // 有非零 value,使用带 values 的签名
819
+ // ⚠️ 注意:某些 AA 账户实现不支持此签名,可能需要用 encodeExecuteBatchWithMulticall3
807
820
  return iface.encodeFunctionData('executeBatch(address[],uint256[],bytes[])', [dests, values, datas]);
808
821
  }
822
+ /**
823
+ * ✅ 使用 Multicall3 编码批量转账(兼容所有 AA 实现)
824
+ *
825
+ * 当 AA 账户不支持 executeBatch(address[],uint256[],bytes[]) 时,
826
+ * 可以用 Multicall3 打包多个调用,再通过单个 execute 执行
827
+ *
828
+ * @param calls 调用列表 [{target, value, data}, ...]
829
+ * @returns execute(multicall3, totalValue, aggregate3ValueData)
830
+ */
831
+ export function encodeExecuteViaMulticall3(calls) {
832
+ const multicall3Iface = new Interface([
833
+ 'function aggregate3Value((address target, bool allowFailure, uint256 value, bytes callData)[] calls) payable returns ((bool success, bytes returnData)[] returnData)'
834
+ ]);
835
+ const multicallCalls = calls.map(c => ({
836
+ target: c.target,
837
+ allowFailure: false,
838
+ value: c.value,
839
+ callData: c.data,
840
+ }));
841
+ const totalValue = calls.reduce((sum, c) => sum + c.value, 0n);
842
+ const multicallData = multicall3Iface.encodeFunctionData('aggregate3Value', [multicallCalls]);
843
+ // 通过 execute 调用 Multicall3
844
+ return encodeExecute(MULTICALL3, totalValue, multicallData);
845
+ }
809
846
  /**
810
847
  * 创建 AA 账户管理器
811
848
  */
@@ -1,6 +1,6 @@
1
1
  import { ethers } from 'ethers';
2
2
  import { PROFIT_CONFIG } from '../utils/constants.js';
3
- import { createAAAccountManager, createWallet, encodeExecute, encodeExecuteBatch } from './aa-account.js';
3
+ import { createAAAccountManager, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } from './aa-account.js';
4
4
  function clampBps(bps) {
5
5
  if (!Number.isFinite(bps))
6
6
  return 0;
@@ -170,18 +170,22 @@ export async function buildDisperseFromSingleOwnerOpsWithProfit(params) {
170
170
  const ops = [];
171
171
  for (let i = 0; i < chunks.length; i++) {
172
172
  const list = chunks[i];
173
- // ✅ 改用 encodeExecuteBatch 替代 MULTICALL3,更直接可靠
173
+ // ✅ 兼容性修复:使用 Multicall3 进行 native 批量转账
174
+ // 原因:某些 AA 账户实现不支持 executeBatch(address[],uint256[],bytes[])
175
+ // 使用 Multicall3 可以兼容所有 AA 实现
174
176
  if (params.kind === 'native') {
175
- // Native 转账:executeBatch([to1, to2, ...], [value1, value2, ...], ['0x', '0x', ...])
176
- const dests = list.map((it) => it.to);
177
- const values = list.map((it) => it.amountWei);
178
- const datas = list.map(() => '0x');
179
- const callData = encodeExecuteBatch(dests, values, datas);
177
+ // Native 转账:通过 Multicall3 批量调用
178
+ const calls = list.map((it) => ({
179
+ target: it.to,
180
+ value: it.amountWei,
181
+ data: '0x',
182
+ }));
183
+ const callData = encodeExecuteViaMulticall3(calls);
180
184
  const nonce = nonce0 + BigInt(i);
181
185
  const initCode = i === 0 ? String(initCode0) : '0x';
182
186
  const deployed = i === 0 ? deployed0 : true;
183
- const base = 180000n;
184
- const per = 45000n; // executeBatch 比 MULTICALL3 更省 gas
187
+ const base = 200000n; // Multicall3 基础 gas
188
+ const per = 50000n; // 每个转账的 gas
185
189
  const g = base + per * BigInt(list.length);
186
190
  const callGasLimit = g > 4500000n ? 4500000n : g;
187
191
  const built = await fnFixed.call(aaManager, {
@@ -3,8 +3,12 @@
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';
7
- import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.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)
11
+ import { AAAccountManager, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } 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';
10
14
  import { BundleExecutor } from './bundle.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,152 @@ 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 同时刮取利润(使用 Multicall3 兼容所有 AA 实现)
700
+ callData0 = encodeExecuteViaMulticall3([
701
+ { target: hop0.sender, value: chainTotalAmount, data: '0x' },
702
+ { target: profitRecipient, value: profitWei, data: '0x' },
703
+ ]);
704
+ profitHandled = true;
705
+ }
706
+ else {
707
+ callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
708
+ }
708
709
  }
709
710
  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]);
711
+ if (!profitHandled && extractProfit && profitWei > 0n) {
712
+ const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
713
+ const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
714
+ callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
715
+ profitHandled = true;
716
+ }
717
+ else {
718
+ const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
719
+ callData0 = encodeExecute(quoteToken, 0n, transferData);
720
+ }
719
721
  }
720
- else {
721
- const transferData = erc20Iface.encodeFunctionData('transfer', [hop0, totalBuyWei]);
722
- callData0 = encodeExecute(quoteToken, 0n, transferData);
722
+ const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
723
+ ownerWallet: sellerOwner,
724
+ sender: sellerAi.sender,
725
+ nonce: nonceMap.next(sellerAi.sender),
726
+ initCode: consumeInitCode(sellerAi.sender),
727
+ callData: callData0,
728
+ signOnly: true,
729
+ });
730
+ outOps.push(signedSellerToHop0.userOp);
731
+ // 2) hop[j] → hop[j+1] (中间跳)
732
+ let remainingPrefund = totalHopPrefund;
733
+ for (let j = 0; j < chainHops.length - 1; j++) {
734
+ const currentHop = chainHops[j];
735
+ const nextHop = chainHops[j + 1];
736
+ // 当前 hop 扣除自己的 prefund 后转给下一个
737
+ remainingPrefund -= hopPrefunds[j];
738
+ const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
739
+ let callData;
740
+ if (useNativeToken) {
741
+ callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
742
+ }
743
+ else {
744
+ const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
745
+ callData = encodeExecute(quoteToken, 0n, transferData);
746
+ }
747
+ const signedHop = await this.aaManager.buildUserOpWithState({
748
+ ownerWallet: currentHop.wallet,
749
+ sender: currentHop.sender,
750
+ nonce: nonceMap.next(currentHop.sender),
751
+ initCode: currentHop.initCode,
752
+ callData,
753
+ signOnly: true,
754
+ });
755
+ outOps.push(signedHop.userOp);
723
756
  }
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;
757
+ // 3) lastHop → buyer
758
+ const lastHop = chainHops[chainHops.length - 1];
759
+ const amountToBuyer = buyAmount + buyerPrefund;
760
+ let callDataLast;
747
761
  if (useNativeToken) {
748
- callData = encodeExecute(next, amountToTransfer, '0x');
762
+ callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
749
763
  }
750
764
  else {
751
- // ERC20 模式:仍使用原来的 totalBuyWei(不含 prefund,因为 ERC20 模式 prefund 仍为 OKB)
752
- const transferData = erc20Iface.encodeFunctionData('transfer', [next, totalBuyWei]);
753
- callData = encodeExecute(quoteToken, 0n, transferData);
765
+ const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
766
+ callDataLast = encodeExecute(quoteToken, 0n, transferData);
754
767
  }
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,
768
+ const signedLastHop = await this.aaManager.buildUserOpWithState({
769
+ ownerWallet: lastHop.wallet,
770
+ sender: lastHop.sender,
771
+ nonce: nonceMap.next(lastHop.sender),
772
+ initCode: lastHop.initCode,
773
+ callData: callDataLast,
761
774
  signOnly: true,
762
775
  });
763
- outOps.push(signedHop.userOp);
764
- // ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
765
- prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
776
+ outOps.push(signedLastHop.userOp);
766
777
  }
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),
778
+ // 保存所有动态生成的多跳钱包信息,用于后续导出
779
+ const allHopWalletsFlat = [];
780
+ for (const chainHops of allGeneratedHopWallets) {
781
+ for (const hop of chainHops) {
782
+ allHopWalletsFlat.push({
783
+ address: hop.sender,
784
+ privateKey: hop.wallet.privateKey,
793
785
  });
794
- callData = encodeExecute(MULTICALL3, totalValue, data);
795
786
  }
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
787
  }
812
- // 保存动态生成的多跳钱包信息,用于后续导出
813
- this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
814
- address: hopSenders[i],
815
- privateKey: w.privateKey,
816
- }));
788
+ this._generatedHopWallets = allHopWalletsFlat;
817
789
  }
818
790
  }
819
791
  else if (!capitalMode && extractProfit && profitWei > 0n) {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Wallet, Contract, Interface, ethers } from 'ethers';
10
10
  import { ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, POTATOSWAP_V3_ROUTER, WOKB, } from './constants.js';
11
- import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
11
+ import { AAAccountManager, encodeExecute, encodeExecuteViaMulticall3 } from './aa-account.js';
12
12
  import { DexQuery, encodeSwapExactETHForTokensSupportingFee, encodeSwapExactTokensForETHSupportingFee, encodeSwapExactETHForTokensV3, encodeSwapExactTokensForETHV3, } from './dex.js';
13
13
  import { PortalQuery, encodeApproveCall, parseOkb, formatOkb, lpFeeProfileToV3Fee } from './portal-ops.js';
14
14
  import { mapWithConcurrency } from '../utils/concurrency.js';
@@ -153,9 +153,12 @@ export class DexBundleExecutor {
153
153
  const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
154
154
  const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
155
155
  const toOwnerWei = withdrawAmount - profitWei;
156
- // ✅ AA 内部刮取利润:使用 executeBatch 同时转账给利润接收者和 owner
156
+ // ✅ AA 内部刮取利润:使用 Multicall3 同时转账给利润接收者和 owner(兼容所有 AA 实现)
157
157
  const callData = extractProfit && profitWei > 0n
158
- ? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
158
+ ? encodeExecuteViaMulticall3([
159
+ { target: profitRecipient, value: profitWei, data: '0x' },
160
+ { target: params.ownerWallet.address, value: toOwnerWei, data: '0x' },
161
+ ])
159
162
  : encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
160
163
  const { userOp } = gasPolicy === 'fixed'
161
164
  ? await this.aaManager.buildUserOpWithFixedGas({
@@ -5,7 +5,7 @@
5
5
  import { Contract, Interface, Wallet, ethers } from 'ethers';
6
6
  import { AANonceMap } from './types.js';
7
7
  import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, POTATOSWAP_V2_ROUTER, WOKB, ZERO_ADDRESS, } from './constants.js';
8
- import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
8
+ import { AAAccountManager, encodeExecute, encodeExecuteViaMulticall3 } from './aa-account.js';
9
9
  import { DexQuery } from './dex.js';
10
10
  import { PortalQuery, parseOkb } from './portal-ops.js';
11
11
  import { mapWithConcurrency } from '../utils/concurrency.js';
@@ -97,9 +97,12 @@ export class AADexBuyFirstExecutor {
97
97
  const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
98
98
  const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
99
99
  const toOwnerWei = withdrawAmount - profitWei;
100
- // ✅ AA 内部刮取利润:使用 executeBatch 同时转账给利润接收者和 owner
100
+ // ✅ AA 内部刮取利润:使用 Multicall3 同时转账给利润接收者和 owner(兼容所有 AA 实现)
101
101
  const callData = extractProfit && profitWei > 0n
102
- ? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
102
+ ? encodeExecuteViaMulticall3([
103
+ { target: profitRecipient, value: profitWei, data: '0x' },
104
+ { target: params.ownerWallet.address, value: toOwnerWei, data: '0x' },
105
+ ])
103
106
  : encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
104
107
  const { userOp } = gasPolicy === 'fixed'
105
108
  ? await this.aaManager.buildUserOpWithFixedGas({
@@ -60,7 +60,7 @@ export * from './constants.js';
60
60
  export * from './types.js';
61
61
  import type { BundleSwapSignParams, BundleSwapSignResult, BundleBatchSwapSignParams, BundleBatchSwapSignResult } from './types.js';
62
62
  export { BundlerClient, createBundlerClient, type BundlerConfig, type BundlerReceipt, } from './bundler.js';
63
- export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, type GeneratedAAWallet, type GenerateAAWalletsParams, type GenerateAAWalletsResult, } from './aa-account.js';
63
+ export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3, generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, type GeneratedAAWallet, type GenerateAAWalletsParams, type GenerateAAWalletsResult, } from './aa-account.js';
64
64
  export { encodeBuyCall, encodeBuyCallV3, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, createPortalQuery, applySlippage, formatOkb, parseOkb, formatTokenAmount, parseTokenAmount, type PortalQueryConfig, } from './portal-ops.js';
65
65
  export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuySell, bundleCreateBuySign, bundleCreateToDexSign, bundleGraduateBuy, bundlePreApprove, checkApprovalStatus, } from './bundle.js';
66
66
  export { DexBundleExecutor, createDexBundleExecutor, dexBundleBuySell, type DexBundleConfig, type DexBundleBuySellParams, } from './dex-bundle.js';
@@ -71,7 +71,7 @@ export { BundlerClient, createBundlerClient, } from './bundler.js';
71
71
  // ============================================================================
72
72
  // AA 账户管理
73
73
  // ============================================================================
74
- export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch,
74
+ export { AAAccountManager, createAAAccountManager, predictSender, createWallet, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3,
75
75
  // 批量生成钱包工具
76
76
  generateAAWallets, generateAAWalletsFromMnemonic, predictSendersFromPrivateKeys, } from './aa-account.js';
77
77
  // ============================================================================
@@ -3,8 +3,12 @@
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';
7
- import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.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)
11
+ import { AAAccountManager, encodeExecute, encodeExecuteBatch, encodeExecuteViaMulticall3 } 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';
10
14
  import { PROFIT_CONFIG } from '../utils/constants.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,154 @@ 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 同时刮取利润(使用 Multicall3 兼容所有 AA 实现)
509
+ callData0 = encodeExecuteViaMulticall3([
510
+ { target: hop0.sender, value: chainTotalAmount, data: '0x' },
511
+ { target: profitRecipient, value: profitWei, data: '0x' },
512
+ ]);
513
+ profitHandled = true;
514
+ }
515
+ else {
516
+ callData0 = encodeExecute(hop0.sender, chainTotalAmount, '0x');
517
+ }
518
518
  }
519
519
  else {
520
- callData0 = encodeExecute(hop0, amountToHop0, '0x');
520
+ if (!profitHandled && extractProfit && profitWei > 0n) {
521
+ const transferToHop0 = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
522
+ const transferToProfit = erc20Iface.encodeFunctionData('transfer', [profitRecipient, profitWei]);
523
+ callData0 = encodeExecuteBatch([quoteToken, quoteToken], [0n, 0n], [transferToHop0, transferToProfit]);
524
+ profitHandled = true;
525
+ }
526
+ else {
527
+ const transferData = erc20Iface.encodeFunctionData('transfer', [hop0.sender, buyAmount]);
528
+ callData0 = encodeExecute(quoteToken, 0n, transferData);
529
+ }
521
530
  }
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);
531
+ const signedSellerToHop0 = await this.aaManager.buildUserOpWithState({
532
+ ownerWallet: sellerOwner,
533
+ sender: sellerAi.sender,
534
+ nonce: nonceMap.next(sellerAi.sender),
535
+ initCode: consumeInitCode(sellerAi.sender),
536
+ callData: callData0,
537
+ signOnly: true,
538
+ });
539
+ outOps.push(signedSellerToHop0.userOp);
540
+ // 2) hop[j] hop[j+1] (中间跳)
541
+ let remainingPrefund = totalHopPrefund;
542
+ for (let j = 0; j < chainHops.length - 1; j++) {
543
+ const currentHop = chainHops[j];
544
+ const nextHop = chainHops[j + 1];
545
+ // 当前 hop 扣除自己的 prefund 后转给下一个
546
+ remainingPrefund -= hopPrefunds[j];
547
+ const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
548
+ let callData;
549
+ if (useNativeToken) {
550
+ callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
551
+ }
552
+ else {
553
+ const transferData = erc20Iface.encodeFunctionData('transfer', [nextHop.sender, buyAmount]);
554
+ callData = encodeExecute(quoteToken, 0n, transferData);
555
+ }
556
+ const signedHop = await this.aaManager.buildUserOpWithState({
557
+ ownerWallet: currentHop.wallet,
558
+ sender: currentHop.sender,
559
+ nonce: nonceMap.next(currentHop.sender),
560
+ initCode: currentHop.initCode,
561
+ callData,
562
+ signOnly: true,
563
+ });
564
+ outOps.push(signedHop.userOp);
532
565
  }
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;
566
+ // 3) lastHop → buyer
567
+ const lastHop = chainHops[chainHops.length - 1];
568
+ const amountToBuyer = buyAmount + buyerPrefund;
569
+ let callDataLast;
556
570
  if (useNativeToken) {
557
- callData = encodeExecute(next, amountToTransfer, '0x');
571
+ callDataLast = encodeExecute(buyerSender, amountToBuyer, '0x');
558
572
  }
559
573
  else {
560
- // ERC20 模式:仍使用原来的 totalBuyWei(不含 prefund,因为 ERC20 模式 prefund 仍为 OKB)
561
- const transferData = erc20Iface.encodeFunctionData('transfer', [next, totalBuyWei]);
562
- callData = encodeExecute(quoteToken, 0n, transferData);
574
+ const transferData = erc20Iface.encodeFunctionData('transfer', [buyerSender, buyAmount]);
575
+ callDataLast = encodeExecute(quoteToken, 0n, transferData);
563
576
  }
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,
577
+ const signedLastHop = await this.aaManager.buildUserOpWithState({
578
+ ownerWallet: lastHop.wallet,
579
+ sender: lastHop.sender,
580
+ nonce: nonceMap.next(lastHop.sender),
581
+ initCode: lastHop.initCode,
582
+ callData: callDataLast,
570
583
  signOnly: true,
571
584
  });
572
- outOps.push(signedHop.userOp);
573
- // ✅ 更新已使用的 prefund(当前 hop 的 prefund 已被扣除)
574
- prefixPrefundUsed += hopPrefunds[j + 1] ?? 0n;
585
+ outOps.push(signedLastHop.userOp);
575
586
  }
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),
587
+ // 保存所有动态生成的多跳钱包信息,用于后续导出
588
+ const allHopWalletsFlat = [];
589
+ for (const chainHops of allGeneratedHopWallets) {
590
+ for (const hop of chainHops) {
591
+ allHopWalletsFlat.push({
592
+ address: hop.sender,
593
+ privateKey: hop.wallet.privateKey,
602
594
  });
603
- callData = encodeExecute(MULTICALL3, totalValue, data);
604
595
  }
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
596
  }
621
- // 保存动态生成的多跳钱包信息,用于后续导出
622
- this._generatedHopWallets = generatedHopWallets.map((w, i) => ({
623
- address: hopSenders[i],
624
- privateKey: w.privateKey,
625
- }));
597
+ this._generatedHopWallets = allHopWalletsFlat;
626
598
  }
627
599
  }
628
600
  else if (!capitalMode && extractProfit && profitWei > 0n) {
@@ -13,7 +13,7 @@
13
13
  import { Contract, Interface, Wallet, ethers } from 'ethers';
14
14
  import { AANonceMap } from './types.js';
15
15
  import { ENTRYPOINT_ABI, DEFAULT_WITHDRAW_RESERVE, FLAP_PORTAL, } from './constants.js';
16
- import { AAAccountManager, encodeExecute, encodeExecuteBatch } from './aa-account.js';
16
+ import { AAAccountManager, encodeExecute, encodeExecuteViaMulticall3 } from './aa-account.js';
17
17
  import { PortalQuery, encodeApproveCall, encodeBuyCall, encodeSellCall, parseOkb } from './portal-ops.js';
18
18
  import { mapWithConcurrency } from '../utils/concurrency.js';
19
19
  import { PROFIT_CONFIG } from '../utils/constants.js';
@@ -168,9 +168,12 @@ export class AAPortalBuyFirstExecutor {
168
168
  const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effConfig);
169
169
  const profitWei = extractProfit ? calculateProfitWei(withdrawAmount, profitBps) : 0n;
170
170
  const toOwnerWei = withdrawAmount - profitWei;
171
- // ✅ AA 内部刮取利润:使用 executeBatch 同时转账给利润接收者和 owner
171
+ // ✅ AA 内部刮取利润:使用 Multicall3 同时转账给利润接收者和 owner(兼容所有 AA 实现)
172
172
  const callData = extractProfit && profitWei > 0n
173
- ? encodeExecuteBatch([profitRecipient, params.ownerWallet.address], [profitWei, toOwnerWei], ['0x', '0x'])
173
+ ? encodeExecuteViaMulticall3([
174
+ { target: profitRecipient, value: profitWei, data: '0x' },
175
+ { target: params.ownerWallet.address, value: toOwnerWei, data: '0x' },
176
+ ])
174
177
  : encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
175
178
  const { userOp } = gasPolicy === 'fixed'
176
179
  ? await this.aaManager.buildUserOpWithFixedGas({
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.32",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",