four-flap-meme-sdk 1.6.34 → 1.6.36

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.
@@ -31,7 +31,6 @@ export declare class AAAccountManager {
31
31
  private bundler;
32
32
  private paymaster?;
33
33
  private paymasterData?;
34
- private useParticlePaymaster;
35
34
  private gasLimitMultiplier;
36
35
  private senderCache;
37
36
  private deployedSenderSet;
@@ -64,27 +63,8 @@ export declare class AAAccountManager {
64
63
  * 而临时生成的 hop 钱包余额为 0,会导致 AA21 验证失败。
65
64
  *
66
65
  * 如果配置了 Paymaster,prefund = 0,hop 钱包不需要余额即可通过验证。
67
- *
68
- * 支持两种模式:
69
- * 1. 静态配置:config.paymaster 指定 Paymaster 合约地址
70
- * 2. Particle Paymaster:config.useParticlePaymaster = true,自动请求 pm_sponsorUserOperation
71
66
  */
72
67
  hasPaymaster(): boolean;
73
- /**
74
- * 是否使用 Particle Paymaster(动态请求 paymasterAndData)
75
- */
76
- usesParticlePaymaster(): boolean;
77
- /**
78
- * 请求 Particle Paymaster 赞助 UserOp
79
- *
80
- * @param userOp 未签名的 UserOperation
81
- * @returns paymasterAndData,如果失败则返回 '0x'
82
- */
83
- requestParticlePaymasterSponsor(userOp: UserOperation): Promise<string>;
84
- /**
85
- * 批量请求 Particle Paymaster 赞助
86
- */
87
- requestParticlePaymasterSponsorBatch(userOps: UserOperation[]): Promise<string[]>;
88
68
  /**
89
69
  * 获取当前 Fee Data
90
70
  */
@@ -42,7 +42,6 @@ export class AAAccountManager {
42
42
  this.salt = config.salt ?? DEFAULT_SALT;
43
43
  this.paymaster = config.paymaster;
44
44
  this.paymasterData = config.paymasterData;
45
- this.useParticlePaymaster = config.useParticlePaymaster ?? false;
46
45
  this.gasLimitMultiplier = config.gasLimitMultiplier ?? GAS_LIMIT_MULTIPLIER;
47
46
  // 默认 fixed:最大化降低 RPC / bundler 请求;如需更稳可显式传 bundlerEstimate
48
47
  this.defaultGasPolicy = config.gasPolicy ?? 'fixed';
@@ -101,40 +100,9 @@ export class AAAccountManager {
101
100
  * 而临时生成的 hop 钱包余额为 0,会导致 AA21 验证失败。
102
101
  *
103
102
  * 如果配置了 Paymaster,prefund = 0,hop 钱包不需要余额即可通过验证。
104
- *
105
- * 支持两种模式:
106
- * 1. 静态配置:config.paymaster 指定 Paymaster 合约地址
107
- * 2. Particle Paymaster:config.useParticlePaymaster = true,自动请求 pm_sponsorUserOperation
108
103
  */
109
104
  hasPaymaster() {
110
- return !!this.paymaster || this.useParticlePaymaster;
111
- }
112
- /**
113
- * 是否使用 Particle Paymaster(动态请求 paymasterAndData)
114
- */
115
- usesParticlePaymaster() {
116
- return this.useParticlePaymaster;
117
- }
118
- /**
119
- * 请求 Particle Paymaster 赞助 UserOp
120
- *
121
- * @param userOp 未签名的 UserOperation
122
- * @returns paymasterAndData,如果失败则返回 '0x'
123
- */
124
- async requestParticlePaymasterSponsor(userOp) {
125
- if (!this.useParticlePaymaster)
126
- return '0x';
127
- const result = await this.bundler.sponsorUserOperation(userOp);
128
- return result ?? '0x';
129
- }
130
- /**
131
- * 批量请求 Particle Paymaster 赞助
132
- */
133
- async requestParticlePaymasterSponsorBatch(userOps) {
134
- if (!this.useParticlePaymaster)
135
- return userOps.map(() => '0x');
136
- const results = await this.bundler.sponsorUserOperationBatch(userOps);
137
- return results.map(r => r ?? '0x');
105
+ return !!this.paymaster;
138
106
  }
139
107
  /**
140
108
  * 获取当前 Fee Data
@@ -588,18 +556,6 @@ export class AAAccountManager {
588
556
  userOp = result.userOp;
589
557
  prefundWei = result.prefundWei;
590
558
  }
591
- // ✅ Particle Paymaster:请求赞助并填入 paymasterAndData
592
- if (this.useParticlePaymaster && userOp.paymasterAndData === '0x') {
593
- const sponsorData = await this.requestParticlePaymasterSponsor(userOp);
594
- if (sponsorData && sponsorData !== '0x') {
595
- userOp = { ...userOp, paymasterAndData: sponsorData };
596
- prefundWei = 0n; // Paymaster 代付,无需 prefund
597
- console.log('[AAAccountManager] ✅ Particle Paymaster 赞助成功');
598
- }
599
- else {
600
- console.warn('[AAAccountManager] ⚠️ Particle Paymaster 赞助失败,回退为自付模式');
601
- }
602
- }
603
559
  const signed = await this.signUserOp(userOp, params.ownerWallet);
604
560
  return { ...signed, prefundWei };
605
561
  }
@@ -649,14 +605,6 @@ export class AAAccountManager {
649
605
  userOp = result.userOp;
650
606
  prefundWei = result.prefundWei;
651
607
  }
652
- // ✅ Particle Paymaster:请求赞助并填入 paymasterAndData
653
- if (this.useParticlePaymaster && userOp.paymasterAndData === '0x') {
654
- const sponsorData = await this.requestParticlePaymasterSponsor(userOp);
655
- if (sponsorData && sponsorData !== '0x') {
656
- userOp = { ...userOp, paymasterAndData: sponsorData };
657
- prefundWei = 0n; // Paymaster 代付,无需 prefund
658
- }
659
- }
660
608
  // 只有非 signOnly 模式才执行 ensureSenderBalance
661
609
  if (!signOnly) {
662
610
  await this.ensureSenderBalance(ownerWallet, sender, prefundWei, 'buildUserOpWithState');
@@ -674,10 +622,6 @@ export class AAAccountManager {
674
622
  * @returns 是否进行了转账
675
623
  */
676
624
  async ensureSenderBalance(ownerWallet, sender, requiredWei, tag) {
677
- // 如果有 Paymaster(静态配置或 Particle),无需检查余额
678
- if (this.hasPaymaster()) {
679
- return { funded: false };
680
- }
681
625
  if (this.paymaster) {
682
626
  const code = await this.provider.getCode(this.paymaster);
683
627
  if (code && code !== '0x') {
@@ -1534,8 +1534,8 @@ export class BundleExecutor {
1534
1534
  name: tokenInfo.name,
1535
1535
  symbol: tokenInfo.symbol,
1536
1536
  meta: tokenInfo.meta,
1537
- dexThresh: params.dexThresh ?? 0,
1538
- salt: params.salt ?? ethers.hexlify(ethers.randomBytes(32)),
1537
+ dexThresh: (params.dexThresh ?? 1) & 0xff, // ✅ 与 BSC 保持一致,默认 1
1538
+ salt: params.salt ?? ethers.ZeroHash, // ✅ 修复:与 bundleCreateBuySign 保持一致,使用 ZeroHash 确保代币地址预计算一致
1539
1539
  taxRate: params.taxRate ?? 0,
1540
1540
  migratorType: params.migratorType ?? 0,
1541
1541
  quoteToken: params.quoteToken ?? ZERO_ADDRESS,
@@ -116,27 +116,6 @@ export declare class BundlerClient {
116
116
  * 获取当前链 ID
117
117
  */
118
118
  getChainId(): number;
119
- /**
120
- * 请求 Particle Paymaster 赞助 UserOperation
121
- *
122
- * Particle 的 Paymaster 服务会返回 paymasterAndData,
123
- * 将其填入 UserOp 后,用户无需支付 prefund(gas 由 Paymaster 代付)
124
- *
125
- * @param userOp 未签名的 UserOperation(paymasterAndData 为空)
126
- * @returns paymasterAndData 字符串,如果不支持则返回 null
127
- */
128
- sponsorUserOperation(userOp: UserOperation): Promise<string | null>;
129
- /**
130
- * 批量请求 Paymaster 赞助
131
- *
132
- * @param userOps 未签名的 UserOperations
133
- * @returns paymasterAndData 数组,不支持的返回 null
134
- */
135
- sponsorUserOperationBatch(userOps: UserOperation[]): Promise<(string | null)[]>;
136
- /**
137
- * 检查 Paymaster 是否可用
138
- */
139
- isPaymasterAvailable(): Promise<boolean>;
140
119
  }
141
120
  /**
142
121
  * 创建默认的 Bundler 客户端
@@ -245,102 +245,6 @@ export class BundlerClient {
245
245
  getChainId() {
246
246
  return this.chainId;
247
247
  }
248
- // ============================================================================
249
- // Paymaster API(Particle Network 特有)
250
- // ============================================================================
251
- /**
252
- * 请求 Particle Paymaster 赞助 UserOperation
253
- *
254
- * Particle 的 Paymaster 服务会返回 paymasterAndData,
255
- * 将其填入 UserOp 后,用户无需支付 prefund(gas 由 Paymaster 代付)
256
- *
257
- * @param userOp 未签名的 UserOperation(paymasterAndData 为空)
258
- * @returns paymasterAndData 字符串,如果不支持则返回 null
259
- */
260
- async sponsorUserOperation(userOp) {
261
- try {
262
- // Particle 使用 pm_sponsorUserOperation 方法
263
- const result = await this.rpc('pm_sponsorUserOperation', [userOp, this.entryPoint]);
264
- if (typeof result === 'string') {
265
- return result;
266
- }
267
- if (result && typeof result === 'object' && result.paymasterAndData) {
268
- return result.paymasterAndData;
269
- }
270
- return null;
271
- }
272
- catch (err) {
273
- // Paymaster 不可用或不愿意赞助,返回 null
274
- console.warn('[Bundler] Paymaster 请求失败(可能不支持或拒绝赞助):', err);
275
- return null;
276
- }
277
- }
278
- /**
279
- * 批量请求 Paymaster 赞助
280
- *
281
- * @param userOps 未签名的 UserOperations
282
- * @returns paymasterAndData 数组,不支持的返回 null
283
- */
284
- async sponsorUserOperationBatch(userOps) {
285
- if (userOps.length === 0)
286
- return [];
287
- try {
288
- const results = await this.rpcBatch(userOps.map(op => ({
289
- method: 'pm_sponsorUserOperation',
290
- params: [op, this.entryPoint],
291
- })));
292
- return results.map(r => {
293
- if (typeof r === 'string')
294
- return r;
295
- if (r && typeof r === 'object' && r.paymasterAndData) {
296
- return r.paymasterAndData;
297
- }
298
- return null;
299
- });
300
- }
301
- catch (err) {
302
- console.warn('[Bundler] 批量 Paymaster 请求失败,尝试单个请求:', err);
303
- // 降级为单个请求
304
- const out = [];
305
- for (const op of userOps) {
306
- out.push(await this.sponsorUserOperation(op));
307
- }
308
- return out;
309
- }
310
- }
311
- /**
312
- * 检查 Paymaster 是否可用
313
- */
314
- async isPaymasterAvailable() {
315
- try {
316
- // 尝试调用一个空的 sponsor 请求,看是否返回错误
317
- // 如果返回 "method not found" 则不支持
318
- const testOp = {
319
- sender: '0x0000000000000000000000000000000000000001',
320
- nonce: '0x0',
321
- initCode: '0x',
322
- callData: '0x',
323
- callGasLimit: '0x5208',
324
- verificationGasLimit: '0x5208',
325
- preVerificationGas: '0x5208',
326
- maxFeePerGas: '0x1',
327
- maxPriorityFeePerGas: '0x1',
328
- paymasterAndData: '0x',
329
- signature: '0x',
330
- };
331
- await this.rpc('pm_sponsorUserOperation', [testOp, this.entryPoint]);
332
- return true;
333
- }
334
- catch (err) {
335
- const msg = String(err?.message || err || '').toLowerCase();
336
- // 如果是 "method not found" 则不支持
337
- if (msg.includes('method not found') || msg.includes('not supported') || msg.includes('unknown method')) {
338
- return false;
339
- }
340
- // 其他错误(如拒绝赞助)说明方法存在,只是不愿意赞助这个特定请求
341
- return true;
342
- }
343
- }
344
248
  }
345
249
  // ============================================================================
346
250
  // 工具函数
@@ -426,6 +426,7 @@ export class AADexSwapExecutor {
426
426
  async bundleBatchSwapSign(params) {
427
427
  const { dexKey, routerAddress: routerAddressIn, tokenAddress, sellerPrivateKey, buyerPrivateKeys, buyAmountsOkb, sellAmount, sellPercent = 100, capitalMode = true, // ✅ 默认资金利用率模式(卖出→分发→买入)
428
428
  disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, tradeType, lpFeeProfile = 0, quoteToken, quoteTokenDecimals = 6, // XLayer USDT/USDC/USDT0 都是 6 位精度
429
+ prefundedHopWallets, // ✅ 预充值多跳:已预充值的 hop 钱包列表
429
430
  } = params;
430
431
  const effectiveConfig = { ...(this.config ?? {}), ...(params.config ?? {}) };
431
432
  const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effectiveConfig);
@@ -581,17 +582,23 @@ export class AADexSwapExecutor {
581
582
  extractProfit,
582
583
  profitWei: ethers.formatEther(profitWei),
583
584
  });
584
- // ✅ 多跳模式与 Paymaster 的关系:
585
+ // ✅ 多跳模式判断:
585
586
  // ERC-4337 的 handleOps 执行流程是"先验证所有 UserOps,再执行所有 UserOps"
586
587
  // hop 钱包是临时生成的(余额=0),在 validation 阶段无法支付 prefund,会导致 AA21 错误
587
588
  //
588
- // 但是!如果配置了 Paymaster,prefund = 0n,hop 钱包不需要余额即可通过验证
589
- // 因此:只有在 **没有 Paymaster** 时才需要强制禁用多跳
589
+ // 多跳可用的条件:
590
+ // 1. 配置了 Paymaster(prefund = 0n,hop 钱包不需要余额)
591
+ // 2. 传入了预充值好的 hop 钱包(hop 钱包已有余额支付 prefund)
590
592
  const hasPaymaster = this.aaManager.hasPaymaster();
591
- const effectiveHopCount = hasPaymaster ? hopCount : 0;
592
- if (hopCount > 0 && !hasPaymaster) {
593
- console.warn('[AA DEX 批量换手] ⚠️ Paymaster 模式不支持多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
594
- console.warn('[AA DEX 批量换手] 💡 提示:配置 Paymaster 可启用多跳功能');
593
+ const hasPrefundedHops = Array.isArray(prefundedHopWallets) && prefundedHopWallets.length > 0;
594
+ const canDoMultiHop = hasPaymaster || hasPrefundedHops;
595
+ const effectiveHopCount = canDoMultiHop ? hopCount : 0;
596
+ if (hopCount > 0 && !canDoMultiHop) {
597
+ console.warn('[AA DEX 批量换手] ⚠️ 无法执行多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
598
+ console.warn('[AA DEX 批量换手] 💡 提示:使用 HopWalletManager.prefundHopWallets() 预充值 hop 钱包,或配置 Paymaster');
599
+ }
600
+ else if (hopCount > 0 && hasPrefundedHops) {
601
+ console.log('[AA DEX 批量换手] ✅ 预充值多跳模式已启用 (hopCount:', hopCount, ', prefundedHops:', prefundedHopWallets.length, ')');
595
602
  }
596
603
  else if (hopCount > 0 && hasPaymaster) {
597
604
  console.log('[AA DEX 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
@@ -655,9 +662,13 @@ export class AADexSwapExecutor {
655
662
  }
656
663
  }
657
664
  else {
658
- // ⚠️ 多跳模式在 XLayer AA 下不可行(effectiveHopCount 已强制为 0)
659
- // 以下代码保留但不会执行,仅供参考
660
- console.log('[AA DEX 批量换手] 进入多跳模式 (effectiveHopCount > 0):', { effectiveHopCount, buyerCount: buyerSenders.length });
665
+ // 多跳模式:使用预充值的 hop 钱包或 Paymaster 模式
666
+ console.log('[AA DEX 批量换手] 进入多跳模式 (effectiveHopCount > 0):', {
667
+ effectiveHopCount,
668
+ buyerCount: buyerSenders.length,
669
+ usePrefundedHops: hasPrefundedHops,
670
+ usePaymaster: hasPaymaster,
671
+ });
661
672
  const feeData = await this.aaManager.getFeeData();
662
673
  const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
663
674
  // 预估 prefund 的函数(使用全局常量)
@@ -667,25 +678,54 @@ export class AADexSwapExecutor {
667
678
  const totalGas = callGas + verifyGas + preVerifyGas;
668
679
  return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
669
680
  };
670
- // ✅ 为每个买方生成独立的多跳链
671
- // hopChains[buyerIdx] = [hop0, hop1, ..., hop(H-1)] 每条链有 hopCount 个 hop 钱包
681
+ // ✅ 构建 hop 钱包列表
682
+ // 如果有预充值的 hop 钱包,使用它们;否则生成新钱包(需要 Paymaster)
672
683
  const allGeneratedHopWallets = [];
673
- for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
674
- const chainHops = [];
675
- for (let h = 0; h < hopCount; h++) {
676
- const randomWallet = ethers.Wallet.createRandom();
677
- const wallet = new ethers.Wallet(randomWallet.privateKey);
678
- const sender = await this.aaManager.predictSenderAddress(wallet.address);
679
- const code = await provider.getCode(sender);
680
- const deployed = code !== null && code !== '0x';
681
- const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
682
- chainHops.push({ wallet, sender, deployed, initCode });
683
- // 初始化 nonce
684
- nonceMap.init(sender, 0n);
684
+ if (hasPrefundedHops) {
685
+ // 预充值模式:使用传入的 hop 钱包
686
+ // 每个买方分配 effectiveHopCount hop 钱包
687
+ const totalHopsNeeded = buyerSenders.length * effectiveHopCount;
688
+ if (prefundedHopWallets.length < totalHopsNeeded) {
689
+ throw new Error(`预充值 hop 钱包数量不足: 需要 ${totalHopsNeeded} 个,实际 ${prefundedHopWallets.length} 个`);
690
+ }
691
+ let hopIdx = 0;
692
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
693
+ const chainHops = [];
694
+ for (let h = 0; h < effectiveHopCount; h++) {
695
+ const prefundedHop = prefundedHopWallets[hopIdx++];
696
+ const wallet = new ethers.Wallet(prefundedHop.privateKey, provider);
697
+ chainHops.push({
698
+ wallet,
699
+ sender: prefundedHop.senderAddress,
700
+ deployed: prefundedHop.deployed,
701
+ initCode: prefundedHop.initCode,
702
+ });
703
+ // 初始化 nonce(预充值的钱包 nonce 应该是 0)
704
+ nonceMap.init(prefundedHop.senderAddress, 0n);
705
+ }
706
+ allGeneratedHopWallets.push(chainHops);
685
707
  }
686
- allGeneratedHopWallets.push(chainHops);
708
+ console.log(`[AA DEX 多跳] ✅ 使用预充值 hop 钱包: ${totalHopsNeeded} 个 (${buyerSenders.length} 条链 × ${effectiveHopCount} 跳)`);
709
+ }
710
+ else {
711
+ // Paymaster 模式:生成新钱包(不需要预充值,prefund=0)
712
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
713
+ const chainHops = [];
714
+ for (let h = 0; h < effectiveHopCount; h++) {
715
+ const randomWallet = ethers.Wallet.createRandom();
716
+ const wallet = new ethers.Wallet(randomWallet.privateKey, provider);
717
+ const sender = await this.aaManager.predictSenderAddress(wallet.address);
718
+ const code = await provider.getCode(sender);
719
+ const deployed = code !== null && code !== '0x';
720
+ const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
721
+ chainHops.push({ wallet, sender, deployed, initCode });
722
+ // 初始化 nonce
723
+ nonceMap.init(sender, 0n);
724
+ }
725
+ allGeneratedHopWallets.push(chainHops);
726
+ }
727
+ console.log(`[AA DEX 多跳] 使用 Paymaster 模式生成 hop 钱包: ${buyerSenders.length * effectiveHopCount} 个`);
687
728
  }
688
- console.log(`[AA DEX 多跳] 总 hop 钱包数: ${buyerSenders.length * hopCount} (${buyerSenders.length} 条链 × ${hopCount} 跳)`);
689
729
  // ✅ 利润刮取(只在第一笔 seller UserOp 中执行)
690
730
  let profitHandled = false;
691
731
  // ✅ 构建每条多跳链的 UserOps
@@ -696,16 +736,14 @@ export class AADexSwapExecutor {
696
736
  const buyAmount = buyAmountsWei[buyerIdx] ?? 0n;
697
737
  if (buyAmount <= 0n)
698
738
  continue;
699
- // 计算这条链上所有 hop 的 prefund(使用常量)
700
- const hopPrefunds = chainHops.map((hop, idx) => {
701
- // 最后一个 hop 转账给 buyer,中间 hop 转账给下一个 hop
702
- return estimatePrefund(HOP_CALL_GAS_LIMIT, hop.deployed);
703
- });
704
- const totalHopPrefund = hopPrefunds.reduce((a, b) => a + b, 0n);
705
- // 计算 buyer 的 prefund(用于买入,使用常量)
739
+ // 计算 buyer 的 prefund(用于买入,使用常量)
706
740
  const buyerPrefund = estimatePrefund(DEX_BUY_CALL_GAS_LIMIT, buyerAi.deployed);
707
- // 这条链的总金额 = 买入金额 + 所有 hop prefund + buyer prefund
708
- const chainTotalAmount = buyAmount + totalHopPrefund + buyerPrefund;
741
+ // 计算分发金额:
742
+ // - 预充值模式:hop 已有 prefund,分发金额 = 买入金额 + buyer prefund
743
+ // - Paymaster 模式:prefund = 0,分发金额 = 买入金额 + buyer prefund
744
+ // 注意:无论哪种模式,都不需要在分发中包含 hop 的 prefund
745
+ //(预充值模式下 hop 已有余额,Paymaster 模式下 prefund=0)
746
+ const chainTotalAmount = buyAmount + buyerPrefund;
709
747
  // 1) seller → hop0
710
748
  const hop0 = chainHops[0];
711
749
  let callData0;
@@ -744,13 +782,12 @@ export class AADexSwapExecutor {
744
782
  });
745
783
  outOps.push(signedSellerToHop0.userOp);
746
784
  // 2) hop[j] → hop[j+1] (中间跳)
747
- let remainingPrefund = totalHopPrefund;
785
+ // 预充值模式下,hop 钱包自己有 prefund,只需要传递买入金额 + buyer prefund
748
786
  for (let j = 0; j < chainHops.length - 1; j++) {
749
787
  const currentHop = chainHops[j];
750
788
  const nextHop = chainHops[j + 1];
751
- // 当前 hop 扣除自己的 prefund 后转给下一个
752
- remainingPrefund -= hopPrefunds[j];
753
- const amountToTransfer = buyAmount + remainingPrefund + buyerPrefund;
789
+ // 传递金额 = 买入金额 + buyer prefund(hop 自己的 gas 从预充值中支付)
790
+ const amountToTransfer = buyAmount + buyerPrefund;
754
791
  let callData;
755
792
  if (useNativeToken) {
756
793
  callData = encodeExecute(nextHop.sender, amountToTransfer, '0x');
@@ -0,0 +1,143 @@
1
+ /**
2
+ * XLayer AA 多跳钱包管理器
3
+ *
4
+ * 功能:
5
+ * 1. 生成临时 Hop 钱包(可导出私钥)
6
+ * 2. 预充值 Hop 钱包(支持取消)
7
+ * 3. 退回预充值资金(原路返回)
8
+ * 4. 状态回调(进度通知)
9
+ */
10
+ import { Wallet } from 'ethers';
11
+ import { AAAccountManager } from './aa-account.js';
12
+ import { XLayerConfig } from './types.js';
13
+ /** Hop 钱包信息 */
14
+ export interface HopWalletInfo {
15
+ /** 钱包索引 */
16
+ index: number;
17
+ /** Owner 私钥(可导出) */
18
+ privateKey: string;
19
+ /** Owner 地址 */
20
+ ownerAddress: string;
21
+ /** AA Sender 地址 */
22
+ senderAddress: string;
23
+ /** 是否已部署 */
24
+ deployed: boolean;
25
+ /** initCode(未部署时需要) */
26
+ initCode: string;
27
+ /** 预充值金额(wei) */
28
+ prefundWei: bigint;
29
+ /** 预充值状态 */
30
+ prefundStatus: 'pending' | 'funded' | 'refunded' | 'failed';
31
+ /** 预充值交易哈希 */
32
+ prefundTxHash?: string;
33
+ /** 退款交易哈希 */
34
+ refundTxHash?: string;
35
+ }
36
+ /** 预充值进度回调 */
37
+ export interface PrefundProgressCallback {
38
+ (progress: {
39
+ phase: 'generating' | 'prefunding' | 'confirming' | 'ready' | 'cancelled' | 'refunding' | 'refunded' | 'error';
40
+ current: number;
41
+ total: number;
42
+ message: string;
43
+ hopWallets?: HopWalletInfo[];
44
+ error?: Error;
45
+ }): void;
46
+ }
47
+ /** 预充值配置 */
48
+ export interface PrefundConfig {
49
+ /** Hop 数量 */
50
+ hopCount: number;
51
+ /** 每个 Hop 的预估 gas 消耗 */
52
+ hopCallGasLimit?: bigint;
53
+ /** 预留缓冲百分比(默认 150%) */
54
+ bufferPercent?: bigint;
55
+ /** Payer 钱包 */
56
+ payerWallet: Wallet;
57
+ /** 进度回调 */
58
+ onProgress?: PrefundProgressCallback;
59
+ /** 取消信号 */
60
+ abortSignal?: AbortSignal;
61
+ }
62
+ /** 预充值结果 */
63
+ export interface PrefundResult {
64
+ /** 是否成功 */
65
+ success: boolean;
66
+ /** Hop 钱包列表 */
67
+ hopWallets: HopWalletInfo[];
68
+ /** 总预充值金额(wei) */
69
+ totalPrefundWei: bigint;
70
+ /** 预充值交易哈希 */
71
+ prefundTxHash?: string;
72
+ /** 错误信息 */
73
+ error?: Error;
74
+ /** 是否被取消 */
75
+ cancelled?: boolean;
76
+ }
77
+ /** 退款结果 */
78
+ export interface RefundResult {
79
+ /** 是否成功 */
80
+ success: boolean;
81
+ /** 退款总金额(wei) */
82
+ totalRefundWei: bigint;
83
+ /** 退款交易哈希列表 */
84
+ refundTxHashes: string[];
85
+ /** 各 Hop 退款详情 */
86
+ hopRefunds: {
87
+ hopIndex: number;
88
+ refundWei: bigint;
89
+ txHash?: string;
90
+ error?: string;
91
+ }[];
92
+ /** 错误信息 */
93
+ error?: Error;
94
+ }
95
+ export declare class HopWalletManager {
96
+ private aaManager;
97
+ private provider;
98
+ private config;
99
+ constructor(config?: XLayerConfig);
100
+ /**
101
+ * 生成 Hop 钱包列表
102
+ */
103
+ generateHopWallets(count: number): Promise<HopWalletInfo[]>;
104
+ /**
105
+ * 计算单个 Hop 需要的 prefund
106
+ */
107
+ calculateHopPrefund(deployed: boolean, callGasLimit?: bigint): Promise<bigint>;
108
+ /**
109
+ * 预充值 Hop 钱包
110
+ *
111
+ * 使用 Payer 发送一笔批量转账交易,给所有 Hop 钱包充值 prefund
112
+ */
113
+ prefundHopWallets(config: PrefundConfig): Promise<PrefundResult>;
114
+ /**
115
+ * 退回 Hop 钱包的预充值资金
116
+ *
117
+ * 将所有 Hop 钱包的余额转回给指定地址(通常是 Payer)
118
+ */
119
+ refundHopWallets(hopWallets: HopWalletInfo[], refundTo: string, onProgress?: PrefundProgressCallback): Promise<RefundResult>;
120
+ /**
121
+ * 使用 Payer 批量退回 Hop 钱包资金
122
+ *
123
+ * 通过 handleOps 批量执行所有 Hop 的退款操作
124
+ */
125
+ refundHopWalletsViaPayer(hopWallets: HopWalletInfo[], payerWallet: Wallet, refundTo: string, onProgress?: PrefundProgressCallback): Promise<RefundResult>;
126
+ /**
127
+ * 导出 Hop 钱包信息(用于备份)
128
+ */
129
+ exportHopWallets(hopWallets: HopWalletInfo[]): string;
130
+ /**
131
+ * 导入 Hop 钱包信息
132
+ */
133
+ importHopWallets(jsonStr: string): Promise<HopWalletInfo[]>;
134
+ /**
135
+ * 编码 execute 调用
136
+ */
137
+ private encodeExecute;
138
+ /**
139
+ * 获取 AAAccountManager
140
+ */
141
+ getAAManager(): AAAccountManager;
142
+ }
143
+ export declare function createHopWalletManager(config?: XLayerConfig): HopWalletManager;
@@ -0,0 +1,518 @@
1
+ /**
2
+ * XLayer AA 多跳钱包管理器
3
+ *
4
+ * 功能:
5
+ * 1. 生成临时 Hop 钱包(可导出私钥)
6
+ * 2. 预充值 Hop 钱包(支持取消)
7
+ * 3. 退回预充值资金(原路返回)
8
+ * 4. 状态回调(进度通知)
9
+ */
10
+ import { ethers, Wallet } from 'ethers';
11
+ import { AAAccountManager } from './aa-account.js';
12
+ import { VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, } from './constants.js';
13
+ // ============================================================================
14
+ // HopWalletManager
15
+ // ============================================================================
16
+ const DEFAULT_HOP_CALL_GAS_LIMIT = 150000n;
17
+ const DEFAULT_BUFFER_PERCENT = 150n; // 1.5x
18
+ export class HopWalletManager {
19
+ constructor(config = {}) {
20
+ this.config = config;
21
+ this.aaManager = new AAAccountManager(config);
22
+ this.provider = this.aaManager.getProvider();
23
+ }
24
+ /**
25
+ * 生成 Hop 钱包列表
26
+ */
27
+ async generateHopWallets(count) {
28
+ const hopWallets = [];
29
+ for (let i = 0; i < count; i++) {
30
+ const randomWallet = ethers.Wallet.createRandom();
31
+ const wallet = new Wallet(randomWallet.privateKey);
32
+ const ownerAddress = wallet.address;
33
+ const senderAddress = await this.aaManager.predictSenderAddress(ownerAddress);
34
+ const code = await this.provider.getCode(senderAddress);
35
+ const deployed = code !== null && code !== '0x';
36
+ const initCode = deployed ? '0x' : this.aaManager.generateInitCode(ownerAddress);
37
+ hopWallets.push({
38
+ index: i,
39
+ privateKey: randomWallet.privateKey,
40
+ ownerAddress,
41
+ senderAddress,
42
+ deployed,
43
+ initCode,
44
+ prefundWei: 0n,
45
+ prefundStatus: 'pending',
46
+ });
47
+ }
48
+ return hopWallets;
49
+ }
50
+ /**
51
+ * 计算单个 Hop 需要的 prefund
52
+ */
53
+ async calculateHopPrefund(deployed, callGasLimit) {
54
+ const feeData = await this.aaManager.getFeeData();
55
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
56
+ const callGas = callGasLimit ?? DEFAULT_HOP_CALL_GAS_LIMIT;
57
+ const verifyGas = deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
58
+ const preVerifyGas = PRE_VERIFICATION_GAS;
59
+ const totalGas = callGas + verifyGas + preVerifyGas;
60
+ return (totalGas * gasPrice * DEFAULT_BUFFER_PERCENT) / 100n;
61
+ }
62
+ /**
63
+ * 预充值 Hop 钱包
64
+ *
65
+ * 使用 Payer 发送一笔批量转账交易,给所有 Hop 钱包充值 prefund
66
+ */
67
+ async prefundHopWallets(config) {
68
+ const { hopCount, hopCallGasLimit, bufferPercent, payerWallet, onProgress, abortSignal } = config;
69
+ // 检查取消信号
70
+ const checkAbort = () => {
71
+ if (abortSignal?.aborted) {
72
+ throw new Error('PREFUND_CANCELLED');
73
+ }
74
+ };
75
+ try {
76
+ // 1. 生成 Hop 钱包
77
+ onProgress?.({
78
+ phase: 'generating',
79
+ current: 0,
80
+ total: hopCount,
81
+ message: `正在生成 ${hopCount} 个 Hop 钱包...`,
82
+ });
83
+ checkAbort();
84
+ const hopWallets = await this.generateHopWallets(hopCount);
85
+ onProgress?.({
86
+ phase: 'generating',
87
+ current: hopCount,
88
+ total: hopCount,
89
+ message: `已生成 ${hopCount} 个 Hop 钱包`,
90
+ hopWallets,
91
+ });
92
+ // 2. 计算每个 Hop 需要的 prefund
93
+ checkAbort();
94
+ const feeData = await this.aaManager.getFeeData();
95
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
96
+ const effectiveBufferPercent = bufferPercent ?? DEFAULT_BUFFER_PERCENT;
97
+ for (const hop of hopWallets) {
98
+ const callGas = hopCallGasLimit ?? DEFAULT_HOP_CALL_GAS_LIMIT;
99
+ const verifyGas = hop.deployed ? VERIFICATION_GAS_LIMIT_NORMAL : VERIFICATION_GAS_LIMIT_DEPLOY;
100
+ const preVerifyGas = PRE_VERIFICATION_GAS;
101
+ const totalGas = callGas + verifyGas + preVerifyGas;
102
+ hop.prefundWei = (totalGas * gasPrice * effectiveBufferPercent) / 100n;
103
+ }
104
+ const totalPrefundWei = hopWallets.reduce((sum, h) => sum + h.prefundWei, 0n);
105
+ // 3. 检查 Payer 余额
106
+ const payerAddress = payerWallet.address;
107
+ const payerBalance = await this.provider.getBalance(payerAddress);
108
+ const estimatedGas = 21000n * BigInt(hopCount) + 50000n; // 预估 gas
109
+ const totalNeeded = totalPrefundWei + estimatedGas * gasPrice;
110
+ if (payerBalance < totalNeeded) {
111
+ throw new Error(`Payer 余额不足: 需要 ${ethers.formatEther(totalNeeded)} OKB, 当前 ${ethers.formatEther(payerBalance)} OKB`);
112
+ }
113
+ // 4. 发送预充值交易
114
+ onProgress?.({
115
+ phase: 'prefunding',
116
+ current: 0,
117
+ total: hopCount,
118
+ message: `正在发送预充值交易...`,
119
+ hopWallets,
120
+ });
121
+ checkAbort();
122
+ // 使用批量转账(逐笔发送,XLayer 不支持 Bundle)
123
+ const connectedPayer = payerWallet.connect(this.provider);
124
+ const nonce = await this.provider.getTransactionCount(payerAddress, 'pending');
125
+ const txHashes = [];
126
+ for (let i = 0; i < hopWallets.length; i++) {
127
+ checkAbort();
128
+ const hop = hopWallets[i];
129
+ const tx = await connectedPayer.sendTransaction({
130
+ to: hop.senderAddress,
131
+ value: hop.prefundWei,
132
+ nonce: nonce + i,
133
+ gasPrice,
134
+ gasLimit: 21055n,
135
+ });
136
+ hop.prefundTxHash = tx.hash;
137
+ txHashes.push(tx.hash);
138
+ onProgress?.({
139
+ phase: 'prefunding',
140
+ current: i + 1,
141
+ total: hopCount,
142
+ message: `已发送 ${i + 1}/${hopCount} 笔预充值交易`,
143
+ hopWallets,
144
+ });
145
+ }
146
+ // 5. 等待所有交易确认
147
+ onProgress?.({
148
+ phase: 'confirming',
149
+ current: 0,
150
+ total: hopCount,
151
+ message: `正在等待交易确认...`,
152
+ hopWallets,
153
+ });
154
+ for (let i = 0; i < txHashes.length; i++) {
155
+ checkAbort();
156
+ const receipt = await this.provider.waitForTransaction(txHashes[i], 1, 60000);
157
+ if (receipt?.status === 1) {
158
+ hopWallets[i].prefundStatus = 'funded';
159
+ }
160
+ else {
161
+ hopWallets[i].prefundStatus = 'failed';
162
+ }
163
+ onProgress?.({
164
+ phase: 'confirming',
165
+ current: i + 1,
166
+ total: hopCount,
167
+ message: `已确认 ${i + 1}/${hopCount} 笔交易`,
168
+ hopWallets,
169
+ });
170
+ }
171
+ // 检查是否全部成功
172
+ const allFunded = hopWallets.every(h => h.prefundStatus === 'funded');
173
+ if (!allFunded) {
174
+ const failedCount = hopWallets.filter(h => h.prefundStatus === 'failed').length;
175
+ throw new Error(`${failedCount} 笔预充值交易失败`);
176
+ }
177
+ onProgress?.({
178
+ phase: 'ready',
179
+ current: hopCount,
180
+ total: hopCount,
181
+ message: `✅ 预充值完成,共 ${ethers.formatEther(totalPrefundWei)} OKB`,
182
+ hopWallets,
183
+ });
184
+ return {
185
+ success: true,
186
+ hopWallets,
187
+ totalPrefundWei,
188
+ prefundTxHash: txHashes[0],
189
+ };
190
+ }
191
+ catch (error) {
192
+ const err = error;
193
+ if (err.message === 'PREFUND_CANCELLED') {
194
+ onProgress?.({
195
+ phase: 'cancelled',
196
+ current: 0,
197
+ total: hopCount,
198
+ message: '预充值已取消',
199
+ });
200
+ return {
201
+ success: false,
202
+ hopWallets: [],
203
+ totalPrefundWei: 0n,
204
+ cancelled: true,
205
+ };
206
+ }
207
+ onProgress?.({
208
+ phase: 'error',
209
+ current: 0,
210
+ total: hopCount,
211
+ message: `预充值失败: ${err.message}`,
212
+ error: err,
213
+ });
214
+ return {
215
+ success: false,
216
+ hopWallets: [],
217
+ totalPrefundWei: 0n,
218
+ error: err,
219
+ };
220
+ }
221
+ }
222
+ /**
223
+ * 退回 Hop 钱包的预充值资金
224
+ *
225
+ * 将所有 Hop 钱包的余额转回给指定地址(通常是 Payer)
226
+ */
227
+ async refundHopWallets(hopWallets, refundTo, onProgress) {
228
+ const hopRefunds = [];
229
+ const refundTxHashes = [];
230
+ let totalRefundWei = 0n;
231
+ onProgress?.({
232
+ phase: 'refunding',
233
+ current: 0,
234
+ total: hopWallets.length,
235
+ message: `正在退回 ${hopWallets.length} 个 Hop 钱包的资金...`,
236
+ hopWallets,
237
+ });
238
+ const feeData = await this.aaManager.getFeeData();
239
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
240
+ const gasLimit = 21055n;
241
+ const gasCost = gasLimit * gasPrice;
242
+ for (let i = 0; i < hopWallets.length; i++) {
243
+ const hop = hopWallets[i];
244
+ try {
245
+ // 检查 Hop sender 余额
246
+ const balance = await this.provider.getBalance(hop.senderAddress);
247
+ if (balance <= gasCost) {
248
+ // 余额不足以支付 gas,跳过
249
+ hopRefunds.push({
250
+ hopIndex: hop.index,
251
+ refundWei: 0n,
252
+ error: '余额不足以支付 gas',
253
+ });
254
+ continue;
255
+ }
256
+ const refundAmount = balance - gasCost;
257
+ // 使用 AA 账户转账(通过 UserOp)
258
+ const hopWallet = new Wallet(hop.privateKey, this.provider);
259
+ // 构建简单的 native 转账 UserOp
260
+ const { userOp } = await this.aaManager.buildUserOpWithFixedGas({
261
+ ownerWallet: hopWallet,
262
+ sender: hop.senderAddress,
263
+ nonce: 0n,
264
+ initCode: hop.initCode,
265
+ callData: this.encodeExecute(refundTo, refundAmount, '0x'),
266
+ deployed: hop.deployed,
267
+ });
268
+ // 签名(buildUserOpWithFixedGas 不签名,需要手动签名)
269
+ const signedOp = await this.aaManager.signUserOp(userOp, hopWallet);
270
+ // 发送 handleOps
271
+ const entryPoint = this.aaManager.getEntryPoint();
272
+ const connectedEntryPoint = entryPoint.connect(new Wallet(hop.privateKey, this.provider));
273
+ // 由于 Hop 没有足够的 gas 发送 handleOps,需要用 Payer 来发送
274
+ // 这里我们返回 UserOp,让调用者处理
275
+ hopRefunds.push({
276
+ hopIndex: hop.index,
277
+ refundWei: refundAmount,
278
+ error: '需要外部 Payer 发送 handleOps(Hop 余额仅够 prefund)',
279
+ });
280
+ totalRefundWei += refundAmount;
281
+ }
282
+ catch (error) {
283
+ hopRefunds.push({
284
+ hopIndex: hop.index,
285
+ refundWei: 0n,
286
+ error: error.message,
287
+ });
288
+ }
289
+ onProgress?.({
290
+ phase: 'refunding',
291
+ current: i + 1,
292
+ total: hopWallets.length,
293
+ message: `已处理 ${i + 1}/${hopWallets.length} 个 Hop 钱包`,
294
+ hopWallets,
295
+ });
296
+ }
297
+ onProgress?.({
298
+ phase: 'refunded',
299
+ current: hopWallets.length,
300
+ total: hopWallets.length,
301
+ message: `退款完成,共 ${ethers.formatEther(totalRefundWei)} OKB`,
302
+ hopWallets,
303
+ });
304
+ return {
305
+ success: true,
306
+ totalRefundWei,
307
+ refundTxHashes,
308
+ hopRefunds,
309
+ };
310
+ }
311
+ /**
312
+ * 使用 Payer 批量退回 Hop 钱包资金
313
+ *
314
+ * 通过 handleOps 批量执行所有 Hop 的退款操作
315
+ */
316
+ async refundHopWalletsViaPayer(hopWallets, payerWallet, refundTo, onProgress) {
317
+ const hopRefunds = [];
318
+ const refundTxHashes = [];
319
+ let totalRefundWei = 0n;
320
+ onProgress?.({
321
+ phase: 'refunding',
322
+ current: 0,
323
+ total: hopWallets.length,
324
+ message: `正在构建退款 UserOps...`,
325
+ hopWallets,
326
+ });
327
+ const feeData = await this.aaManager.getFeeData();
328
+ const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
329
+ // 构建所有退款 UserOps
330
+ const userOps = [];
331
+ const hopWalletsToRefund = [];
332
+ for (let i = 0; i < hopWallets.length; i++) {
333
+ const hop = hopWallets[i];
334
+ try {
335
+ const balance = await this.provider.getBalance(hop.senderAddress);
336
+ // 估算 UserOp 执行需要的 gas
337
+ const estimatedGas = 150000n * gasPrice;
338
+ if (balance <= estimatedGas) {
339
+ hopRefunds.push({
340
+ hopIndex: hop.index,
341
+ refundWei: 0n,
342
+ error: '余额不足',
343
+ });
344
+ continue;
345
+ }
346
+ const refundAmount = balance - estimatedGas;
347
+ // 构建 UserOp
348
+ const hopWallet = new Wallet(hop.privateKey, this.provider);
349
+ const { userOp } = await this.aaManager.buildUserOpWithFixedGas({
350
+ ownerWallet: hopWallet,
351
+ sender: hop.senderAddress,
352
+ nonce: 0n,
353
+ initCode: hop.initCode,
354
+ callData: this.encodeExecute(refundTo, refundAmount, '0x'),
355
+ deployed: hop.deployed,
356
+ });
357
+ // 签名
358
+ const signedOp = await this.aaManager.signUserOp(userOp, hopWallet);
359
+ userOps.push(signedOp.userOp);
360
+ hopWalletsToRefund.push(hop);
361
+ totalRefundWei += refundAmount;
362
+ hopRefunds.push({
363
+ hopIndex: hop.index,
364
+ refundWei: refundAmount,
365
+ });
366
+ }
367
+ catch (error) {
368
+ hopRefunds.push({
369
+ hopIndex: hop.index,
370
+ refundWei: 0n,
371
+ error: error.message,
372
+ });
373
+ }
374
+ }
375
+ if (userOps.length === 0) {
376
+ onProgress?.({
377
+ phase: 'refunded',
378
+ current: hopWallets.length,
379
+ total: hopWallets.length,
380
+ message: '没有可退款的 Hop 钱包',
381
+ hopWallets,
382
+ });
383
+ return {
384
+ success: true,
385
+ totalRefundWei: 0n,
386
+ refundTxHashes: [],
387
+ hopRefunds,
388
+ };
389
+ }
390
+ onProgress?.({
391
+ phase: 'refunding',
392
+ current: 0,
393
+ total: userOps.length,
394
+ message: `正在发送 handleOps 退款交易 (${userOps.length} 个 UserOps)...`,
395
+ hopWallets,
396
+ });
397
+ // 发送 handleOps
398
+ try {
399
+ const connectedPayer = payerWallet.connect(this.provider);
400
+ const entryPoint = this.aaManager.getEntryPoint();
401
+ const entryPointWithSigner = entryPoint.connect(connectedPayer);
402
+ const tx = await entryPointWithSigner.handleOps(userOps, payerWallet.address, {
403
+ gasPrice,
404
+ gasLimit: 500000n * BigInt(userOps.length),
405
+ });
406
+ refundTxHashes.push(tx.hash);
407
+ onProgress?.({
408
+ phase: 'confirming',
409
+ current: 0,
410
+ total: 1,
411
+ message: `等待 handleOps 交易确认...`,
412
+ hopWallets,
413
+ });
414
+ const receipt = await tx.wait();
415
+ if (receipt?.status === 1) {
416
+ for (const hop of hopWalletsToRefund) {
417
+ hop.prefundStatus = 'refunded';
418
+ hop.refundTxHash = tx.hash;
419
+ }
420
+ onProgress?.({
421
+ phase: 'refunded',
422
+ current: hopWallets.length,
423
+ total: hopWallets.length,
424
+ message: `✅ 退款完成,共 ${ethers.formatEther(totalRefundWei)} OKB`,
425
+ hopWallets,
426
+ });
427
+ return {
428
+ success: true,
429
+ totalRefundWei,
430
+ refundTxHashes,
431
+ hopRefunds,
432
+ };
433
+ }
434
+ else {
435
+ throw new Error('handleOps 交易失败');
436
+ }
437
+ }
438
+ catch (error) {
439
+ onProgress?.({
440
+ phase: 'error',
441
+ current: 0,
442
+ total: hopWallets.length,
443
+ message: `退款失败: ${error.message}`,
444
+ error: error,
445
+ hopWallets,
446
+ });
447
+ return {
448
+ success: false,
449
+ totalRefundWei: 0n,
450
+ refundTxHashes,
451
+ hopRefunds,
452
+ error: error,
453
+ };
454
+ }
455
+ }
456
+ /**
457
+ * 导出 Hop 钱包信息(用于备份)
458
+ */
459
+ exportHopWallets(hopWallets) {
460
+ const exportData = hopWallets.map(h => ({
461
+ index: h.index,
462
+ privateKey: h.privateKey,
463
+ ownerAddress: h.ownerAddress,
464
+ senderAddress: h.senderAddress,
465
+ prefundWei: h.prefundWei.toString(),
466
+ prefundStatus: h.prefundStatus,
467
+ prefundTxHash: h.prefundTxHash,
468
+ }));
469
+ return JSON.stringify(exportData, null, 2);
470
+ }
471
+ /**
472
+ * 导入 Hop 钱包信息
473
+ */
474
+ async importHopWallets(jsonStr) {
475
+ const data = JSON.parse(jsonStr);
476
+ const hopWallets = [];
477
+ for (const item of data) {
478
+ const wallet = new Wallet(item.privateKey);
479
+ const senderAddress = await this.aaManager.predictSenderAddress(wallet.address);
480
+ const code = await this.provider.getCode(senderAddress);
481
+ const deployed = code !== null && code !== '0x';
482
+ const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
483
+ hopWallets.push({
484
+ index: item.index,
485
+ privateKey: item.privateKey,
486
+ ownerAddress: item.ownerAddress,
487
+ senderAddress,
488
+ deployed,
489
+ initCode,
490
+ prefundWei: BigInt(item.prefundWei || '0'),
491
+ prefundStatus: item.prefundStatus || 'pending',
492
+ prefundTxHash: item.prefundTxHash,
493
+ });
494
+ }
495
+ return hopWallets;
496
+ }
497
+ /**
498
+ * 编码 execute 调用
499
+ */
500
+ encodeExecute(to, value, data) {
501
+ const iface = new ethers.Interface([
502
+ 'function execute(address dest, uint256 value, bytes func) external',
503
+ ]);
504
+ return iface.encodeFunctionData('execute', [to, value, data]);
505
+ }
506
+ /**
507
+ * 获取 AAAccountManager
508
+ */
509
+ getAAManager() {
510
+ return this.aaManager;
511
+ }
512
+ }
513
+ // ============================================================================
514
+ // 工厂函数
515
+ // ============================================================================
516
+ export function createHopWalletManager(config = {}) {
517
+ return new HopWalletManager(config);
518
+ }
@@ -66,6 +66,7 @@ export { BundleExecutor, createBundleExecutor, bundleBuy, bundleSell, bundleBuyS
66
66
  export { DexBundleExecutor, createDexBundleExecutor, dexBundleBuySell, type DexBundleConfig, type DexBundleBuySellParams, } from './dex-bundle.js';
67
67
  export { AAPortalSwapExecutor, } from './portal-bundle-swap.js';
68
68
  export { AADexSwapExecutor, } from './dex-bundle-swap.js';
69
+ export { HopWalletManager, createHopWalletManager, type HopWalletInfo, type PrefundProgressCallback, type PrefundConfig, type PrefundResult, type RefundResult, } from './hop-wallet-manager.js';
69
70
  export { AAPortalBuyFirstExecutor, createAAPortalBuyFirstExecutor, } from './portal-buy-first.js';
70
71
  export { AADexBuyFirstExecutor, createAADexBuyFirstExecutor, } from './dex-buy-first.js';
71
72
  export { BuyFirstVolumeExecutor, createBuyFirstVolumeExecutor, makeBuyFirstVolume, } from './buy-first-volume.js';
@@ -94,6 +94,10 @@ export { DexBundleExecutor, createDexBundleExecutor, dexBundleBuySell, } from '.
94
94
  export { AAPortalSwapExecutor, } from './portal-bundle-swap.js';
95
95
  export { AADexSwapExecutor, } from './dex-bundle-swap.js';
96
96
  // ============================================================================
97
+ // Hop 钱包管理器(多跳预充值)
98
+ // ============================================================================
99
+ export { HopWalletManager, createHopWalletManager, } from './hop-wallet-manager.js';
100
+ // ============================================================================
97
101
  // AA Buy-First(刷量专用)
98
102
  // ============================================================================
99
103
  export { AAPortalBuyFirstExecutor, createAAPortalBuyFirstExecutor, } from './portal-buy-first.js';
@@ -277,7 +277,8 @@ export class AAPortalSwapExecutor {
277
277
  */
278
278
  async bundleBatchSwapSign(params) {
279
279
  const { tokenAddress, sellerPrivateKey, buyerPrivateKeys, buyAmountsOkb, sellAmount, sellPercent = 100, capitalMode = true, // ✅ 默认资金利用率模式(卖出→分发→买入)
280
- disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, quoteToken, quoteTokenDecimals = 6, } = params;
280
+ disperseHopCount: disperseHopCountIn, payerPrivateKey, beneficiary: beneficiaryIn, payerStartNonce, routeAddress, skipApprovalCheck = false, quoteToken, quoteTokenDecimals = 6, prefundedHopWallets, // ✅ 预充值多跳:已预充值的 hop 钱包列表
281
+ } = params;
281
282
  const effectiveConfig = { ...(this.config ?? {}), ...(params.config ?? {}) };
282
283
  const { extractProfit, profitBps, profitRecipient } = resolveProfitSettings(effectiveConfig);
283
284
  // ✅ ERC20 稳定币支持
@@ -390,17 +391,23 @@ export class AAPortalSwapExecutor {
390
391
  extractProfit,
391
392
  profitWei: ethers.formatEther(profitWei),
392
393
  });
393
- // ✅ 多跳模式与 Paymaster 的关系:
394
+ // ✅ 多跳模式判断:
394
395
  // ERC-4337 的 handleOps 执行流程是"先验证所有 UserOps,再执行所有 UserOps"
395
396
  // hop 钱包是临时生成的(余额=0),在 validation 阶段无法支付 prefund,会导致 AA21 错误
396
397
  //
397
- // 但是!如果配置了 Paymaster,prefund = 0n,hop 钱包不需要余额即可通过验证
398
- // 因此:只有在 **没有 Paymaster** 时才需要强制禁用多跳
398
+ // 多跳可用的条件:
399
+ // 1. 配置了 Paymaster(prefund = 0n,hop 钱包不需要余额)
400
+ // 2. 传入了预充值好的 hop 钱包(hop 钱包已有余额支付 prefund)
399
401
  const hasPaymaster = this.aaManager.hasPaymaster();
400
- const effectiveHopCount = hasPaymaster ? hopCount : 0;
401
- if (hopCount > 0 && !hasPaymaster) {
402
- console.warn('[AA Portal 批量换手] ⚠️ Paymaster 模式不支持多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
403
- console.warn('[AA Portal 批量换手] 💡 提示:配置 Paymaster 可启用多跳功能');
402
+ const hasPrefundedHops = Array.isArray(prefundedHopWallets) && prefundedHopWallets.length > 0;
403
+ const canDoMultiHop = hasPaymaster || hasPrefundedHops;
404
+ const effectiveHopCount = canDoMultiHop ? hopCount : 0;
405
+ if (hopCount > 0 && !canDoMultiHop) {
406
+ console.warn('[AA Portal 批量换手] ⚠️ 无法执行多跳(hop 钱包无法支付 prefund),已自动降级为直接分发模式');
407
+ console.warn('[AA Portal 批量换手] 💡 提示:使用 HopWalletManager.prefundHopWallets() 预充值 hop 钱包,或配置 Paymaster');
408
+ }
409
+ else if (hopCount > 0 && hasPrefundedHops) {
410
+ console.log('[AA Portal 批量换手] ✅ 预充值多跳模式已启用 (hopCount:', hopCount, ', prefundedHops:', prefundedHopWallets.length, ')');
404
411
  }
405
412
  else if (hopCount > 0 && hasPaymaster) {
406
413
  console.log('[AA Portal 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
@@ -462,9 +469,13 @@ export class AAPortalSwapExecutor {
462
469
  }
463
470
  }
464
471
  else {
465
- // ⚠️ 多跳模式在 XLayer AA 下不可行(effectiveHopCount 已强制为 0)
466
- // 以下代码保留但不会执行,仅供参考
467
- console.log('[AA Portal 批量换手] 进入多跳模式 (effectiveHopCount > 0):', { effectiveHopCount, buyerCount: buyerSenders.length });
472
+ // 多跳模式:使用预充值的 hop 钱包或 Paymaster 模式
473
+ console.log('[AA Portal 批量换手] 进入多跳模式 (effectiveHopCount > 0):', {
474
+ effectiveHopCount,
475
+ buyerCount: buyerSenders.length,
476
+ usePrefundedHops: hasPrefundedHops,
477
+ usePaymaster: hasPaymaster,
478
+ });
468
479
  const feeData = await this.aaManager.getFeeData();
469
480
  const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
470
481
  const provider = this.aaManager.getProvider();
@@ -475,25 +486,51 @@ export class AAPortalSwapExecutor {
475
486
  const totalGas = callGas + verifyGas + preVerifyGas;
476
487
  return (totalGas * gasPrice * PREFUND_BUFFER_PERCENT) / 100n;
477
488
  };
478
- // ✅ 为每个买方生成独立的多跳链
479
- // hopChains[buyerIdx] = [hop0, hop1, ..., hop(H-1)] 每条链有 hopCount 个 hop 钱包
489
+ // ✅ 构建 hop 钱包列表
490
+ // 如果有预充值的 hop 钱包,使用它们;否则生成新钱包(需要 Paymaster)
480
491
  const allGeneratedHopWallets = [];
481
- for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
482
- const chainHops = [];
483
- for (let h = 0; h < hopCount; h++) {
484
- const randomWallet = ethers.Wallet.createRandom();
485
- const wallet = new ethers.Wallet(randomWallet.privateKey);
486
- const sender = await this.aaManager.predictSenderAddress(wallet.address);
487
- const code = await provider.getCode(sender);
488
- const deployed = code !== null && code !== '0x';
489
- const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
490
- chainHops.push({ wallet, sender, deployed, initCode });
491
- // 初始化 nonce
492
- nonceMap.init(sender, 0n);
492
+ if (hasPrefundedHops) {
493
+ // 预充值模式:使用传入的 hop 钱包
494
+ const totalHopsNeeded = buyerSenders.length * effectiveHopCount;
495
+ if (prefundedHopWallets.length < totalHopsNeeded) {
496
+ throw new Error(`预充值 hop 钱包数量不足: 需要 ${totalHopsNeeded} 个,实际 ${prefundedHopWallets.length} 个`);
497
+ }
498
+ let hopIdx = 0;
499
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
500
+ const chainHops = [];
501
+ for (let h = 0; h < effectiveHopCount; h++) {
502
+ const prefundedHop = prefundedHopWallets[hopIdx++];
503
+ const wallet = new ethers.Wallet(prefundedHop.privateKey, provider);
504
+ chainHops.push({
505
+ wallet,
506
+ sender: prefundedHop.senderAddress,
507
+ deployed: prefundedHop.deployed,
508
+ initCode: prefundedHop.initCode,
509
+ });
510
+ nonceMap.init(prefundedHop.senderAddress, 0n);
511
+ }
512
+ allGeneratedHopWallets.push(chainHops);
513
+ }
514
+ console.log(`[AA Portal 多跳] ✅ 使用预充值 hop 钱包: ${totalHopsNeeded} 个`);
515
+ }
516
+ else {
517
+ // Paymaster 模式:生成新钱包
518
+ for (let buyerIdx = 0; buyerIdx < buyerSenders.length; buyerIdx++) {
519
+ const chainHops = [];
520
+ for (let h = 0; h < effectiveHopCount; h++) {
521
+ const randomWallet = ethers.Wallet.createRandom();
522
+ const wallet = new ethers.Wallet(randomWallet.privateKey, provider);
523
+ const sender = await this.aaManager.predictSenderAddress(wallet.address);
524
+ const code = await provider.getCode(sender);
525
+ const deployed = code !== null && code !== '0x';
526
+ const initCode = deployed ? '0x' : this.aaManager.generateInitCode(wallet.address);
527
+ chainHops.push({ wallet, sender, deployed, initCode });
528
+ nonceMap.init(sender, 0n);
529
+ }
530
+ allGeneratedHopWallets.push(chainHops);
493
531
  }
494
- allGeneratedHopWallets.push(chainHops);
532
+ console.log(`[AA Portal 多跳] 使用 Paymaster 模式生成 hop 钱包`);
495
533
  }
496
- console.log(`[AA 多跳] 总 hop 钱包数: ${buyerSenders.length * hopCount} (${buyerSenders.length} 条链 × ${hopCount} 跳)`);
497
534
  // ✅ 利润刮取(只在第一笔 seller UserOp 中执行)
498
535
  let profitHandled = false;
499
536
  // ✅ 构建每条多跳链的 UserOps
@@ -72,12 +72,6 @@ export interface XLayerConfig {
72
72
  paymaster?: string;
73
73
  /** Paymaster Data(与 paymaster 一起使用) */
74
74
  paymasterData?: string;
75
- /**
76
- * 使用 Particle Network 的 Paymaster 服务(自动请求赞助)
77
- * 如果启用,SDK 会调用 pm_sponsorUserOperation 获取 paymasterAndData
78
- * 这样 hop 钱包无需 prefund,可以支持多跳
79
- */
80
- useParticlePaymaster?: boolean;
81
75
  /** 默认超时时间(毫秒) */
82
76
  timeoutMs?: number;
83
77
  /** Gas 估算安全余量倍数 */
@@ -558,6 +552,24 @@ export interface BundleBatchSwapSignParams extends BundleBatchSwapParams {
558
552
  payerStartNonce?: number;
559
553
  handleOpsGasLimit?: bigint;
560
554
  routeAddress?: string;
555
+ /**
556
+ * ✅ 预充值多跳:传入已预充值好的 Hop 钱包列表
557
+ *
558
+ * 如果提供此参数,将使用这些 hop 钱包进行多跳转账,
559
+ * 而不是自动生成新钱包(需要 Paymaster 才能工作的模式)
560
+ *
561
+ * 使用流程:
562
+ * 1. 调用 HopWalletManager.prefundHopWallets() 预充值
563
+ * 2. 将返回的 hopWallets 传入此参数
564
+ * 3. 多跳将使用这些已有余额的 hop 钱包
565
+ */
566
+ prefundedHopWallets?: Array<{
567
+ privateKey: string;
568
+ ownerAddress: string;
569
+ senderAddress: string;
570
+ deployed: boolean;
571
+ initCode: string;
572
+ }>;
561
573
  }
562
574
  export interface BundleBatchSwapSignResult {
563
575
  signedTransactions: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.34",
3
+ "version": "1.6.36",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",