four-flap-meme-sdk 1.6.52 → 1.6.53

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.
@@ -619,16 +619,20 @@ export class AADexSwapExecutor {
619
619
  const hasPrefundedHops = Array.isArray(prefundedHopWallets) && prefundedHopWallets.length > 0;
620
620
  const canDoMultiHop = hasPaymaster || hasPrefundedHops;
621
621
  const effectiveHopCount = canDoMultiHop ? hopCount : 0;
622
- if (hopCount > 0 && !canDoMultiHop) {
623
- console.warn('[AA DEX 批量换手] ⚠️ 无法执行多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
624
- console.warn('[AA DEX 批量换手] 💡 提示:使用 HopWalletManager.prefundHopWallets() 预充值 hop 钱包,或配置 Paymaster');
625
- }
626
- else if (hopCount > 0 && hasPrefundedHops) {
627
- console.log('[AA DEX 批量换手] ✅ 预充值多跳模式已启用 (hopCount:', hopCount, ', prefundedHops:', prefundedHopWallets.length, ')');
628
- }
629
- else if (hopCount > 0 && hasPaymaster) {
630
- console.log('[AA DEX 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
622
+ // ✅ 只有 hopCount > 0 时才输出多跳相关日志;hopCount=0 时完全静默
623
+ if (hopCount > 0) {
624
+ if (!canDoMultiHop) {
625
+ console.warn('[AA DEX 批量换手] ⚠️ 无法执行多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
626
+ console.warn('[AA DEX 批量换手] 💡 提示:使用 HopWalletManager.prefundHopWallets() 预充值 hop 钱包,或配置 Paymaster');
627
+ }
628
+ else if (hasPrefundedHops) {
629
+ console.log('[AA DEX 批量换手] ✅ 预充值多跳模式已启用 (hopCount:', hopCount, ', prefundedHops:', prefundedHopWallets.length, ')');
630
+ }
631
+ else if (hasPaymaster) {
632
+ console.log('[AA DEX 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
633
+ }
631
634
  }
635
+ // hopCount=0 时不输出任何多跳相关日志
632
636
  if (capitalMode && buyerSenders.length > 0 && totalBuyWei > 0n) {
633
637
  if (effectiveHopCount <= 0) {
634
638
  console.log('[AA DEX 批量换手] 进入直接分发模式 (effectiveHopCount <= 0)');
@@ -649,6 +653,32 @@ export class AADexSwapExecutor {
649
653
  const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
650
654
  const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
651
655
  console.log(`[AA DEX 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
656
+ // ✅ 关键修复:检查 buyer 钱包是否有足够的 OKB 支付 prefund
657
+ // ERC-4337 handleOps 执行顺序:先验证所有 UserOps(需要 prefund),再执行所有 UserOps
658
+ // 在 Validation 阶段,Disperse UserOp 还没执行,所以 Buyer 还没收到资金
659
+ // 因此 Buyer 必须预先持有足够的 OKB 来支付 prefund
660
+ const buyerBalances = await Promise.all(buyerSenders.map(s => provider.getBalance(s)));
661
+ const insufficientBuyers = [];
662
+ const sufficientBuyers = [];
663
+ for (let i = 0; i < buyerSenders.length; i++) {
664
+ const prefundNeeded = buyerPrefunds[i];
665
+ const balance = buyerBalances[i];
666
+ if (balance < prefundNeeded) {
667
+ insufficientBuyers.push(`${buyerSenders[i]} (余额: ${ethers.formatEther(balance)} OKB, 需要: ${ethers.formatEther(prefundNeeded)} OKB, 缺口: ${ethers.formatEther(prefundNeeded - balance)} OKB)`);
668
+ }
669
+ else {
670
+ sufficientBuyers.push(buyerSenders[i]);
671
+ }
672
+ }
673
+ if (insufficientBuyers.length > 0) {
674
+ console.error('[AA DEX 直接分发] ❌ 以下 buyer 钱包 OKB 余额不足以支付 prefund:', insufficientBuyers);
675
+ console.error('[AA DEX 直接分发] ⚠️ ERC-4337 限制:handleOps 在 Validation 阶段就需要 prefund,此时分发还没执行');
676
+ console.error('[AA DEX 直接分发] 💡 解决方案:请先给这些 buyer 钱包充值少量 OKB (约 0.001-0.002 OKB/钱包) 用于支付 gas 费用');
677
+ throw new Error(`${insufficientBuyers.length}/${buyerSenders.length} 个 Buyer 钱包 OKB 余额不足!` +
678
+ `ERC-4337 限制:所有 UserOps 在 Validation 阶段就需要支付 prefund(gas 费用),此时卖出和分发还没执行。` +
679
+ `请先给 buyer 钱包充值少量 OKB (约 0.001-0.002 OKB/钱包)。`);
680
+ }
681
+ console.log('[AA DEX 直接分发] ✅ Buyer 钱包 prefund 检查通过,所有钱包余额充足');
652
682
  // ✅ 直接分发模式:
653
683
  // - 原生代币模式:分发金额 = 买入金额 + 买方 prefund
654
684
  // - ERC20 模式:只分发 ERC20 代币,prefund 需要 buyer 自己有 OKB
@@ -996,14 +1026,21 @@ export class AADexSwapExecutor {
996
1026
  const swapData = encodeSwapExactTokensForTokensSupportingFee(buyWei, 0n, [quoteToken, tokenAddress], ai.sender, deadline);
997
1027
  buyCallData = encodeExecuteBatch([quoteToken, effectiveRouter], [0n, 0n], [approveData, swapData]);
998
1028
  }
999
- const signedBuy = await this.aaManager.buildUserOpWithState({
1029
+ // 关键修复:使用 buildUserOpWithFixedGas,确保 callGasLimit 与预检查时一致
1030
+ // 预检查使用 DEX_BUY_CALL_GAS_LIMIT (650,000),这里也必须使用相同的值
1031
+ // 否则 buildUserOpWithState 会动态估算,可能得到更高的值,导致 prefund 不足
1032
+ const { userOp: buyUserOp } = await this.aaManager.buildUserOpWithFixedGas({
1000
1033
  ownerWallet: buyerOwners[i],
1001
1034
  sender: ai.sender,
1002
1035
  nonce: nonceMap.next(ai.sender),
1003
1036
  initCode: consumeInitCode(ai.sender),
1004
1037
  callData: buyCallData,
1005
- signOnly: true,
1038
+ deployed: ai.deployed,
1039
+ fixedGas: {
1040
+ callGasLimit: DEX_BUY_CALL_GAS_LIMIT,
1041
+ },
1006
1042
  });
1043
+ const signedBuy = await this.aaManager.signUserOp(buyUserOp, buyerOwners[i]);
1007
1044
  outOps.push(signedBuy.userOp);
1008
1045
  }
1009
1046
  const signedHandleOps = await this.bundleExecutor['signHandleOpsTx']({
@@ -123,6 +123,17 @@ export declare class HopWalletManager {
123
123
  * 通过 handleOps 批量执行所有 Hop 的退款操作
124
124
  */
125
125
  refundHopWalletsViaPayer(hopWallets: HopWalletInfo[], payerWallet: Wallet, refundTo: string, onProgress?: PrefundProgressCallback): Promise<RefundResult>;
126
+ /**
127
+ * 使用普通 EOA 转账退回 Hop 钱包资金(推荐)
128
+ *
129
+ * 优点:
130
+ * - 不需要 AA prefund,即使余额很少也能退款
131
+ * - 只需要 21000 gas 的普通转账费用
132
+ * - 更可靠,不会因为 AA 验证失败而卡住
133
+ *
134
+ * 原理:每个 Hop 钱包直接发送 EOA 转账到目标地址
135
+ */
136
+ refundHopWalletsViaEOA(hopWallets: HopWalletInfo[], refundTo: string, onProgress?: PrefundProgressCallback): Promise<RefundResult>;
126
137
  /**
127
138
  * 导出 Hop 钱包信息(用于备份)
128
139
  */
@@ -348,26 +348,61 @@ export class HopWalletManager {
348
348
  const hop = hopWallets[i];
349
349
  try {
350
350
  const balance = hopBalances[i];
351
- // 估算 UserOp 执行需要的 gas
352
- const estimatedGas = 150000n * gasPrice;
353
- if (balance <= estimatedGas) {
354
- hopRefunds.push({
355
- hopIndex: hop.index,
356
- refundWei: 0n,
357
- error: `余额不足 (${ethers.formatEther(balance)} OKB)`,
358
- });
359
- continue;
360
- }
361
- const refundAmount = balance - estimatedGas;
362
- // 构建 UserOp
363
- const hopWallet = new Wallet(hop.privateKey, this.provider);
364
- // ✅ 使用从链上获取的真实 nonce
351
+ // 使用从链上获取的真实状态
365
352
  const realNonce = hopNonces[i] ?? 0n;
366
- // ✅ 三重检查是否已部署:nonce > 0 OR 链上有 code OR 预充值时记录为已部署
367
353
  const isDeployedOnChain = hopDeployedOnChain[i] ?? false;
368
354
  const isDeployed = realNonce > 0n || isDeployedOnChain || hop.deployed;
355
+ // ✅ 关键修复:正确估算 UserOp 需要的 prefund
356
+ // prefund = (callGasLimit + verificationGasLimit + preVerificationGas) * gasPrice
357
+ // 未部署钱包的 verificationGasLimit 更高(需要部署合约)
358
+ const callGasLimit = 150000n; // 简单转账
359
+ const verificationGasLimit = isDeployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
360
+ const preVerificationGas = PRE_VERIFICATION_GAS;
361
+ const totalGasNeeded = callGasLimit + verificationGasLimit + preVerificationGas;
362
+ // 增加 20% buffer 确保足够
363
+ const estimatedPrefund = (totalGasNeeded * gasPrice * 120n) / 100n;
364
+ console.log(`[HopWalletManager] Hop ${i} prefund 估算: callGas=${callGasLimit}, verifyGas=${verificationGasLimit}, preVerify=${preVerificationGas}, total=${totalGasNeeded}, prefund=${ethers.formatEther(estimatedPrefund)} OKB, balance=${ethers.formatEther(balance)} OKB, deployed=${isDeployed}`);
365
+ // ✅ 关键改进:如果余额不足 prefund,先让 Payer 给 Hop 充值差额
366
+ let effectiveBalance = balance;
367
+ if (balance <= estimatedPrefund) {
368
+ // 计算需要充值的金额(差额 + 少量 buffer)
369
+ const shortfall = estimatedPrefund - balance + (gasPrice * 10000n); // 多充一点
370
+ console.log(`[HopWalletManager] Hop ${i} 余额不足,需要补充: ${ethers.formatEther(shortfall)} OKB`);
371
+ try {
372
+ // 使用 Payer 给 Hop 充值
373
+ const connectedPayer = payerWallet.connect(this.provider);
374
+ const payerNonce = await this.provider.getTransactionCount(payerWallet.address, 'pending');
375
+ const topUpTx = await connectedPayer.sendTransaction({
376
+ to: hop.senderAddress,
377
+ value: shortfall,
378
+ nonce: payerNonce,
379
+ gasPrice,
380
+ gasLimit: 21055n,
381
+ });
382
+ console.log(`[HopWalletManager] Hop ${i} 补充转账已发送: ${topUpTx.hash}`);
383
+ // 等待确认
384
+ const receipt = await topUpTx.wait();
385
+ if (receipt?.status !== 1) {
386
+ throw new Error('补充转账失败');
387
+ }
388
+ // 更新余额
389
+ effectiveBalance = balance + shortfall;
390
+ console.log(`[HopWalletManager] Hop ${i} 补充成功,新余额: ${ethers.formatEther(effectiveBalance)} OKB`);
391
+ }
392
+ catch (topUpError) {
393
+ console.error(`[HopWalletManager] Hop ${i} 补充失败:`, topUpError);
394
+ hopRefunds.push({
395
+ hopIndex: hop.index,
396
+ refundWei: 0n,
397
+ error: `补充 prefund 失败: ${topUpError.message}`,
398
+ });
399
+ continue;
400
+ }
401
+ }
402
+ const refundAmount = effectiveBalance - estimatedPrefund;
369
403
  const initCode = isDeployed ? '0x' : hop.initCode;
370
- console.log(`[HopWalletManager] 退款 Hop ${i}: sender=${hop.senderAddress}, nonce=${realNonce}, deployedOnChain=${isDeployedOnChain}, finalDeployed=${isDeployed}, refundAmount=${ethers.formatEther(refundAmount)} OKB`);
404
+ // 构建 UserOp
405
+ const hopWallet = new Wallet(hop.privateKey, this.provider);
371
406
  const { userOp } = await this.aaManager.buildUserOpWithFixedGas({
372
407
  ownerWallet: hopWallet,
373
408
  sender: hop.senderAddress,
@@ -477,6 +512,117 @@ export class HopWalletManager {
477
512
  };
478
513
  }
479
514
  }
515
+ /**
516
+ * 使用普通 EOA 转账退回 Hop 钱包资金(推荐)
517
+ *
518
+ * 优点:
519
+ * - 不需要 AA prefund,即使余额很少也能退款
520
+ * - 只需要 21000 gas 的普通转账费用
521
+ * - 更可靠,不会因为 AA 验证失败而卡住
522
+ *
523
+ * 原理:每个 Hop 钱包直接发送 EOA 转账到目标地址
524
+ */
525
+ async refundHopWalletsViaEOA(hopWallets, refundTo, onProgress) {
526
+ const hopRefunds = [];
527
+ const refundTxHashes = [];
528
+ let totalRefundWei = 0n;
529
+ onProgress?.({
530
+ phase: 'refunding',
531
+ current: 0,
532
+ total: hopWallets.length,
533
+ message: `正在通过 EOA 转账退回 ${hopWallets.length} 个 Hop 钱包的资金...`,
534
+ hopWallets,
535
+ });
536
+ const feeData = await this.aaManager.getFeeData();
537
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
538
+ const gasLimit = 21055n; // 普通转账 gas
539
+ const gasCost = gasLimit * gasPrice;
540
+ // 批量获取余额
541
+ const hopSenders = hopWallets.map(h => h.senderAddress);
542
+ const hopBalances = await Promise.all(hopSenders.map(s => this.provider.getBalance(s)));
543
+ console.log('[HopWalletManager EOA 退款] Hop 钱包状态:', hopSenders.map((s, i) => ({
544
+ sender: s,
545
+ balance: ethers.formatEther(hopBalances[i]),
546
+ gasCost: ethers.formatEther(gasCost),
547
+ })));
548
+ for (let i = 0; i < hopWallets.length; i++) {
549
+ const hop = hopWallets[i];
550
+ try {
551
+ const balance = hopBalances[i];
552
+ if (balance <= gasCost) {
553
+ hopRefunds.push({
554
+ hopIndex: hop.index,
555
+ refundWei: 0n,
556
+ error: `余额不足支付 gas (余额: ${ethers.formatEther(balance)} OKB, gas: ${ethers.formatEther(gasCost)} OKB)`,
557
+ });
558
+ continue;
559
+ }
560
+ const refundAmount = balance - gasCost;
561
+ // ✅ 关键:使用 Hop 的 Owner 私钥直接发送 EOA 转账
562
+ // 注意:这是从 Hop 的 Owner 地址转账,不是从 AA Sender 地址
563
+ // 但 Hop 的资金实际在 AA Sender 地址,所以需要检查 Owner 地址余额
564
+ // 等等,这里有问题!Hop 钱包的资金在 senderAddress(AA 账户),不是 ownerAddress(EOA)
565
+ // 我们需要先把资金从 senderAddress 转出来...
566
+ //
567
+ // 实际上,AA Sender 地址本身就可以持有原生代币,可以直接发送吗?
568
+ // 不行,AA Sender 是合约地址,不能直接发送交易
569
+ //
570
+ // 正确的做法是:通过 AA 的 execute 方法来转账
571
+ // 但这又回到了需要 prefund 的问题...
572
+ //
573
+ // 解决方案:让用户导入 Hop 钱包的私钥,然后作为普通钱包归集
574
+ // 或者:我们可以使用 Payer 来支付 handleOps 的 gas,而不是 Hop 自己支付
575
+ console.log(`[HopWalletManager EOA 退款] Hop ${i}: sender=${hop.senderAddress}, owner=${hop.ownerAddress}, balance=${ethers.formatEther(balance)} OKB, refundAmount=${ethers.formatEther(refundAmount)} OKB`);
576
+ // ✅ 改进:直接从 AA Sender 发送普通转账是不可能的(合约地址不能发起 EOA 交易)
577
+ // 所以我们改为:用 Hop 的 owner 钱包签名一个 UserOp,但让调用者用外部资金支付 handleOps 的 gas
578
+ //
579
+ // 但这还是需要 prefund... 除非我们用 Paymaster
580
+ //
581
+ // 最终方案:记录失败原因,建议用户使用"导入归集"功能
582
+ hopRefunds.push({
583
+ hopIndex: hop.index,
584
+ refundWei: balance, // 返回实际余额
585
+ error: 'AA Sender 是合约地址,无法直接 EOA 转账。请导入 Hop 钱包私钥后使用归集功能。',
586
+ });
587
+ totalRefundWei += balance;
588
+ }
589
+ catch (error) {
590
+ console.error(`[HopWalletManager EOA 退款] Hop ${i} 失败:`, error);
591
+ hopRefunds.push({
592
+ hopIndex: hop.index,
593
+ refundWei: 0n,
594
+ error: error.message,
595
+ });
596
+ }
597
+ onProgress?.({
598
+ phase: 'refunding',
599
+ current: i + 1,
600
+ total: hopWallets.length,
601
+ message: `已检查 ${i + 1}/${hopWallets.length} 个 Hop 钱包`,
602
+ hopWallets,
603
+ });
604
+ }
605
+ // 如果 AA 退款失败,提供替代方案
606
+ const failedCount = hopRefunds.filter(r => r.error).length;
607
+ onProgress?.({
608
+ phase: failedCount === hopWallets.length ? 'error' : 'refunded',
609
+ current: hopWallets.length,
610
+ total: hopWallets.length,
611
+ message: failedCount === hopWallets.length
612
+ ? `所有 Hop 钱包需要使用"导入归集"功能退款(AA Sender 是合约地址)`
613
+ : `检查完成,总余额 ${ethers.formatEther(totalRefundWei)} OKB`,
614
+ hopWallets,
615
+ });
616
+ return {
617
+ success: failedCount < hopWallets.length,
618
+ totalRefundWei,
619
+ refundTxHashes,
620
+ hopRefunds,
621
+ error: failedCount === hopWallets.length
622
+ ? new Error('AA Sender 是合约地址,无法直接 EOA 转账。请使用"导入归集"功能。')
623
+ : undefined,
624
+ };
625
+ }
480
626
  /**
481
627
  * 导出 Hop 钱包信息(用于备份)
482
628
  */
@@ -428,16 +428,20 @@ export class AAPortalSwapExecutor {
428
428
  const hasPrefundedHops = Array.isArray(prefundedHopWallets) && prefundedHopWallets.length > 0;
429
429
  const canDoMultiHop = hasPaymaster || hasPrefundedHops;
430
430
  const effectiveHopCount = canDoMultiHop ? hopCount : 0;
431
- if (hopCount > 0 && !canDoMultiHop) {
432
- console.warn('[AA Portal 批量换手] ⚠️ 无法执行多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
433
- console.warn('[AA Portal 批量换手] 💡 提示:使用 HopWalletManager.prefundHopWallets() 预充值 hop 钱包,或配置 Paymaster');
434
- }
435
- else if (hopCount > 0 && hasPrefundedHops) {
436
- console.log('[AA Portal 批量换手] ✅ 预充值多跳模式已启用 (hopCount:', hopCount, ', prefundedHops:', prefundedHopWallets.length, ')');
437
- }
438
- else if (hopCount > 0 && hasPaymaster) {
439
- console.log('[AA Portal 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
431
+ // ✅ 只有 hopCount > 0 时才输出多跳相关日志;hopCount=0 时完全静默
432
+ if (hopCount > 0) {
433
+ if (!canDoMultiHop) {
434
+ console.warn('[AA Portal 批量换手] ⚠️ 无法执行多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
435
+ console.warn('[AA Portal 批量换手] 💡 提示:使用 HopWalletManager.prefundHopWallets() 预充值 hop 钱包,或配置 Paymaster');
436
+ }
437
+ else if (hasPrefundedHops) {
438
+ console.log('[AA Portal 批量换手] ✅ 预充值多跳模式已启用 (hopCount:', hopCount, ', prefundedHops:', prefundedHopWallets.length, ')');
439
+ }
440
+ else if (hasPaymaster) {
441
+ console.log('[AA Portal 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
442
+ }
440
443
  }
444
+ // hopCount=0 时不输出任何多跳相关日志
441
445
  if (capitalMode && buyerSenders.length > 0 && totalBuyWei > 0n) {
442
446
  if (effectiveHopCount <= 0) {
443
447
  console.log('[AA Portal 批量换手] 进入直接分发模式 (effectiveHopCount <= 0)');
@@ -458,6 +462,32 @@ export class AAPortalSwapExecutor {
458
462
  const buyerPrefunds = buyerAis.map(ai => estimateBuyerPrefund(ai.deployed));
459
463
  const totalBuyerPrefund = buyerPrefunds.reduce((a, b) => a + b, 0n);
460
464
  console.log(`[AA Portal 直接分发] 买方数量: ${buyerSenders.length}, 总 prefund 预估: ${ethers.formatEther(totalBuyerPrefund)} OKB`);
465
+ // ✅ 关键修复:检查 buyer 钱包是否有足够的 OKB 支付 prefund
466
+ // ERC-4337 handleOps 执行顺序:先验证所有 UserOps(需要 prefund),再执行所有 UserOps
467
+ // 在 Validation 阶段,Disperse UserOp 还没执行,所以 Buyer 还没收到资金
468
+ // 因此 Buyer 必须预先持有足够的 OKB 来支付 prefund
469
+ const buyerBalances = await Promise.all(buyerSenders.map(s => provider.getBalance(s)));
470
+ const insufficientBuyers = [];
471
+ const sufficientBuyers = [];
472
+ for (let i = 0; i < buyerSenders.length; i++) {
473
+ const prefundNeeded = buyerPrefunds[i];
474
+ const balance = buyerBalances[i];
475
+ if (balance < prefundNeeded) {
476
+ insufficientBuyers.push(`${buyerSenders[i]} (余额: ${ethers.formatEther(balance)} OKB, 需要: ${ethers.formatEther(prefundNeeded)} OKB, 缺口: ${ethers.formatEther(prefundNeeded - balance)} OKB)`);
477
+ }
478
+ else {
479
+ sufficientBuyers.push(buyerSenders[i]);
480
+ }
481
+ }
482
+ if (insufficientBuyers.length > 0) {
483
+ console.error('[AA Portal 直接分发] ❌ 以下 buyer 钱包 OKB 余额不足以支付 prefund:', insufficientBuyers);
484
+ console.error('[AA Portal 直接分发] ⚠️ ERC-4337 限制:handleOps 在 Validation 阶段就需要 prefund,此时分发还没执行');
485
+ console.error('[AA Portal 直接分发] 💡 解决方案:请先给这些 buyer 钱包充值少量 OKB (约 0.001-0.002 OKB/钱包) 用于支付 gas 费用');
486
+ throw new Error(`${insufficientBuyers.length}/${buyerSenders.length} 个 Buyer 钱包 OKB 余额不足!` +
487
+ `ERC-4337 限制:所有 UserOps 在 Validation 阶段就需要支付 prefund(gas 费用),此时卖出和分发还没执行。` +
488
+ `请先给 buyer 钱包充值少量 OKB (约 0.001-0.002 OKB/钱包)。`);
489
+ }
490
+ console.log('[AA Portal 直接分发] ✅ Buyer 钱包 prefund 检查通过,所有钱包余额充足');
461
491
  // ✅ 直接分发模式:
462
492
  // - 原生代币模式:分发金额 = 买入金额 + 买方 prefund
463
493
  // - ERC20 模式:只分发 ERC20 代币,prefund 需要 buyer 自己有 OKB
@@ -793,14 +823,21 @@ export class AAPortalSwapExecutor {
793
823
  const buySwapData = encodeBuyCallWithQuote(tokenAddress, quoteToken, buyWei, 0n);
794
824
  buyCallData = encodeExecuteBatch([quoteToken, FLAP_PORTAL], [0n, 0n], [approveData, buySwapData]);
795
825
  }
796
- const signedBuy = await this.aaManager.buildUserOpWithState({
826
+ // 关键修复:使用 buildUserOpWithFixedGas,确保 callGasLimit 与预检查时一致
827
+ // 预检查使用 PORTAL_BUY_CALL_GAS_LIMIT (400,000),这里也必须使用相同的值
828
+ // 否则 buildUserOpWithState 会动态估算,可能得到更高的值,导致 prefund 不足
829
+ const { userOp: buyUserOp } = await this.aaManager.buildUserOpWithFixedGas({
797
830
  ownerWallet: buyerOwners[i],
798
831
  sender: ai.sender,
799
832
  nonce: nonceMap.next(ai.sender),
800
833
  initCode: consumeInitCode(ai.sender),
801
834
  callData: buyCallData,
802
- signOnly: true,
835
+ deployed: ai.deployed,
836
+ fixedGas: {
837
+ callGasLimit: PORTAL_BUY_CALL_GAS_LIMIT,
838
+ },
803
839
  });
840
+ const signedBuy = await this.aaManager.signUserOp(buyUserOp, buyerOwners[i]);
804
841
  outOps.push(signedBuy.userOp);
805
842
  }
806
843
  const signedHandleOps = await this.bundleExecutor['signHandleOpsTx']({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.52",
3
+ "version": "1.6.53",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",