four-flap-meme-sdk 1.6.32 → 1.6.34

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,6 +31,7 @@ export declare class AAAccountManager {
31
31
  private bundler;
32
32
  private paymaster?;
33
33
  private paymasterData?;
34
+ private useParticlePaymaster;
34
35
  private gasLimitMultiplier;
35
36
  private senderCache;
36
37
  private deployedSenderSet;
@@ -56,6 +57,34 @@ export declare class AAAccountManager {
56
57
  * 获取 Bundler 客户端
57
58
  */
58
59
  getBundler(): BundlerClient;
60
+ /**
61
+ * 判断是否配置了 Paymaster(用于决定多跳可行性)
62
+ *
63
+ * ERC-4337 多跳场景下,如果没有 Paymaster,hop 钱包需要自己支付 prefund,
64
+ * 而临时生成的 hop 钱包余额为 0,会导致 AA21 验证失败。
65
+ *
66
+ * 如果配置了 Paymaster,prefund = 0,hop 钱包不需要余额即可通过验证。
67
+ *
68
+ * 支持两种模式:
69
+ * 1. 静态配置:config.paymaster 指定 Paymaster 合约地址
70
+ * 2. Particle Paymaster:config.useParticlePaymaster = true,自动请求 pm_sponsorUserOperation
71
+ */
72
+ 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[]>;
59
88
  /**
60
89
  * 获取当前 Fee Data
61
90
  */
@@ -42,6 +42,7 @@ 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;
45
46
  this.gasLimitMultiplier = config.gasLimitMultiplier ?? GAS_LIMIT_MULTIPLIER;
46
47
  // 默认 fixed:最大化降低 RPC / bundler 请求;如需更稳可显式传 bundlerEstimate
47
48
  this.defaultGasPolicy = config.gasPolicy ?? 'fixed';
@@ -93,6 +94,48 @@ export class AAAccountManager {
93
94
  getBundler() {
94
95
  return this.bundler;
95
96
  }
97
+ /**
98
+ * 判断是否配置了 Paymaster(用于决定多跳可行性)
99
+ *
100
+ * ERC-4337 多跳场景下,如果没有 Paymaster,hop 钱包需要自己支付 prefund,
101
+ * 而临时生成的 hop 钱包余额为 0,会导致 AA21 验证失败。
102
+ *
103
+ * 如果配置了 Paymaster,prefund = 0,hop 钱包不需要余额即可通过验证。
104
+ *
105
+ * 支持两种模式:
106
+ * 1. 静态配置:config.paymaster 指定 Paymaster 合约地址
107
+ * 2. Particle Paymaster:config.useParticlePaymaster = true,自动请求 pm_sponsorUserOperation
108
+ */
109
+ 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');
138
+ }
96
139
  /**
97
140
  * 获取当前 Fee Data
98
141
  */
@@ -545,6 +588,18 @@ export class AAAccountManager {
545
588
  userOp = result.userOp;
546
589
  prefundWei = result.prefundWei;
547
590
  }
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
+ }
548
603
  const signed = await this.signUserOp(userOp, params.ownerWallet);
549
604
  return { ...signed, prefundWei };
550
605
  }
@@ -594,6 +649,14 @@ export class AAAccountManager {
594
649
  userOp = result.userOp;
595
650
  prefundWei = result.prefundWei;
596
651
  }
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
+ }
597
660
  // 只有非 signOnly 模式才执行 ensureSenderBalance
598
661
  if (!signOnly) {
599
662
  await this.ensureSenderBalance(ownerWallet, sender, prefundWei, 'buildUserOpWithState');
@@ -611,6 +674,10 @@ export class AAAccountManager {
611
674
  * @returns 是否进行了转账
612
675
  */
613
676
  async ensureSenderBalance(ownerWallet, sender, requiredWei, tag) {
677
+ // 如果有 Paymaster(静态配置或 Particle),无需检查余额
678
+ if (this.hasPaymaster()) {
679
+ return { funded: false };
680
+ }
614
681
  if (this.paymaster) {
615
682
  const code = await this.provider.getCode(this.paymaster);
616
683
  if (code && code !== '0x') {
@@ -116,6 +116,27 @@ 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>;
119
140
  }
120
141
  /**
121
142
  * 创建默认的 Bundler 客户端
@@ -245,6 +245,102 @@ 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
+ }
248
344
  }
249
345
  // ============================================================================
250
346
  // 工具函数
@@ -581,9 +581,24 @@ export class AADexSwapExecutor {
581
581
  extractProfit,
582
582
  profitWei: ethers.formatEther(profitWei),
583
583
  });
584
+ // ✅ 多跳模式与 Paymaster 的关系:
585
+ // ERC-4337 的 handleOps 执行流程是"先验证所有 UserOps,再执行所有 UserOps"
586
+ // hop 钱包是临时生成的(余额=0),在 validation 阶段无法支付 prefund,会导致 AA21 错误
587
+ //
588
+ // 但是!如果配置了 Paymaster,prefund = 0n,hop 钱包不需要余额即可通过验证
589
+ // 因此:只有在 **没有 Paymaster** 时才需要强制禁用多跳
590
+ 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 可启用多跳功能');
595
+ }
596
+ else if (hopCount > 0 && hasPaymaster) {
597
+ console.log('[AA DEX 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
598
+ }
584
599
  if (capitalMode && buyerSenders.length > 0 && totalBuyWei > 0n) {
585
- if (hopCount <= 0) {
586
- console.log('[AA DEX 批量换手] 进入直接分发模式 (hopCount <= 0)');
600
+ if (effectiveHopCount <= 0) {
601
+ console.log('[AA DEX 批量换手] 进入直接分发模式 (effectiveHopCount <= 0)');
587
602
  // ✅ 关键修复:计算每个买方执行买入 UserOp 需要的 prefund(gas 费用)
588
603
  // EntryPoint 在 validation 阶段会扣除 prefund,此时分发还没执行
589
604
  // 因此需要为每个买方额外预留 prefund
@@ -640,9 +655,9 @@ export class AADexSwapExecutor {
640
655
  }
641
656
  }
642
657
  else {
643
- // 多跳模式(BSC 模式):每个买方有独立的多跳链
644
- // 填写 3 跳 + 2 个买方 = 2 条独立的多跳链,每条 3 个 hop 钱包 = 6 个动态生成的 hop 钱包
645
- console.log('[AA DEX 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount, buyerCount: buyerSenders.length });
658
+ // ⚠️ 多跳模式在 XLayer AA 下不可行(effectiveHopCount 已强制为 0)
659
+ // 以下代码保留但不会执行,仅供参考
660
+ console.log('[AA DEX 批量换手] 进入多跳模式 (effectiveHopCount > 0):', { effectiveHopCount, buyerCount: buyerSenders.length });
646
661
  const feeData = await this.aaManager.getFeeData();
647
662
  const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
648
663
  // 预估 prefund 的函数(使用全局常量)
@@ -880,7 +895,7 @@ export class AADexSwapExecutor {
880
895
  profitRecipient,
881
896
  profitWei: profitWei.toString(),
882
897
  quotedSellOutWei: quotedSellOutWei.toString(),
883
- disperseHopCount: capitalMode ? String(hopCount) : '0', // ✅ capitalMode=false 时无分发
898
+ disperseHopCount: capitalMode ? String(effectiveHopCount) : '0', // ✅ XLayer AA 模式强制使用直接分发
884
899
  },
885
900
  };
886
901
  }
@@ -390,9 +390,24 @@ export class AAPortalSwapExecutor {
390
390
  extractProfit,
391
391
  profitWei: ethers.formatEther(profitWei),
392
392
  });
393
+ // ✅ 多跳模式与 Paymaster 的关系:
394
+ // ERC-4337 的 handleOps 执行流程是"先验证所有 UserOps,再执行所有 UserOps"
395
+ // hop 钱包是临时生成的(余额=0),在 validation 阶段无法支付 prefund,会导致 AA21 错误
396
+ //
397
+ // 但是!如果配置了 Paymaster,prefund = 0n,hop 钱包不需要余额即可通过验证
398
+ // 因此:只有在 **没有 Paymaster** 时才需要强制禁用多跳
399
+ 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 可启用多跳功能');
404
+ }
405
+ else if (hopCount > 0 && hasPaymaster) {
406
+ console.log('[AA Portal 批量换手] ✅ Paymaster 模式已启用,支持多跳换手 (hopCount:', hopCount, ')');
407
+ }
393
408
  if (capitalMode && buyerSenders.length > 0 && totalBuyWei > 0n) {
394
- if (hopCount <= 0) {
395
- console.log('[AA Portal 批量换手] 进入直接分发模式 (hopCount <= 0)');
409
+ if (effectiveHopCount <= 0) {
410
+ console.log('[AA Portal 批量换手] 进入直接分发模式 (effectiveHopCount <= 0)');
396
411
  // ✅ 关键修复:计算每个买方执行买入 UserOp 需要的 prefund(gas 费用)
397
412
  // EntryPoint 在 validation 阶段会扣除 prefund,此时分发还没执行
398
413
  // 因此需要为每个买方额外预留 prefund
@@ -447,9 +462,9 @@ export class AAPortalSwapExecutor {
447
462
  }
448
463
  }
449
464
  else {
450
- // 多跳模式(BSC 模式):每个买方有独立的多跳链
451
- // 填写 3 跳 + 2 个买方 = 2 条独立的多跳链,每条 3 个 hop 钱包 = 6 个动态生成的 hop 钱包
452
- console.log('[AA Portal 批量换手] 进入多跳模式 (hopCount > 0):', { hopCount, buyerCount: buyerSenders.length });
465
+ // ⚠️ 多跳模式在 XLayer AA 下不可行(effectiveHopCount 已强制为 0)
466
+ // 以下代码保留但不会执行,仅供参考
467
+ console.log('[AA Portal 批量换手] 进入多跳模式 (effectiveHopCount > 0):', { effectiveHopCount, buyerCount: buyerSenders.length });
453
468
  const feeData = await this.aaManager.getFeeData();
454
469
  const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? 5000000000n;
455
470
  const provider = this.aaManager.getProvider();
@@ -673,7 +688,7 @@ export class AAPortalSwapExecutor {
673
688
  profitRecipient,
674
689
  profitWei: profitWei.toString(),
675
690
  quotedSellOutWei: quotedSellOutWei.toString(),
676
- disperseHopCount: capitalMode ? String(hopCount) : '0', // ✅ capitalMode=false 时无分发
691
+ disperseHopCount: capitalMode ? String(effectiveHopCount) : '0', // ✅ XLayer AA 模式强制使用直接分发
677
692
  },
678
693
  };
679
694
  }
@@ -72,6 +72,12 @@ 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;
75
81
  /** 默认超时时间(毫秒) */
76
82
  timeoutMs?: number;
77
83
  /** Gas 估算安全余量倍数 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-flap-meme-sdk",
3
- "version": "1.6.32",
3
+ "version": "1.6.34",
4
4
  "description": "SDK for Flap bonding curve and four.meme TokenManager",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",