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.
- package/dist/xlayer/aa-account.d.ts +0 -20
- package/dist/xlayer/aa-account.js +1 -57
- package/dist/xlayer/bundle.js +2 -2
- package/dist/xlayer/bundler.d.ts +0 -21
- package/dist/xlayer/bundler.js +0 -96
- package/dist/xlayer/dex-bundle-swap.js +76 -39
- package/dist/xlayer/hop-wallet-manager.d.ts +143 -0
- package/dist/xlayer/hop-wallet-manager.js +518 -0
- package/dist/xlayer/index.d.ts +1 -0
- package/dist/xlayer/index.js +4 -0
- package/dist/xlayer/portal-bundle-swap.js +64 -27
- package/dist/xlayer/types.d.ts +18 -6
- package/package.json +1 -1
|
@@ -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
|
|
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') {
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -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 ??
|
|
1538
|
-
salt: params.salt ?? ethers.
|
|
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,
|
package/dist/xlayer/bundler.d.ts
CHANGED
|
@@ -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 客户端
|
package/dist/xlayer/bundler.js
CHANGED
|
@@ -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
|
-
// ✅
|
|
585
|
+
// ✅ 多跳模式判断:
|
|
585
586
|
// ERC-4337 的 handleOps 执行流程是"先验证所有 UserOps,再执行所有 UserOps"
|
|
586
587
|
// hop 钱包是临时生成的(余额=0),在 validation 阶段无法支付 prefund,会导致 AA21 错误
|
|
587
588
|
//
|
|
588
|
-
//
|
|
589
|
-
//
|
|
589
|
+
// 多跳可用的条件:
|
|
590
|
+
// 1. 配置了 Paymaster(prefund = 0n,hop 钱包不需要余额)
|
|
591
|
+
// 2. 传入了预充值好的 hop 钱包(hop 钱包已有余额支付 prefund)
|
|
590
592
|
const hasPaymaster = this.aaManager.hasPaymaster();
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
//
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
//
|
|
681
|
+
// ✅ 构建 hop 钱包列表
|
|
682
|
+
// 如果有预充值的 hop 钱包,使用它们;否则生成新钱包(需要 Paymaster)
|
|
672
683
|
const allGeneratedHopWallets = [];
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
chainHops
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
708
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
752
|
-
|
|
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
|
+
}
|
package/dist/xlayer/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/xlayer/index.js
CHANGED
|
@@ -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,
|
|
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
|
-
// ✅
|
|
394
|
+
// ✅ 多跳模式判断:
|
|
394
395
|
// ERC-4337 的 handleOps 执行流程是"先验证所有 UserOps,再执行所有 UserOps"
|
|
395
396
|
// hop 钱包是临时生成的(余额=0),在 validation 阶段无法支付 prefund,会导致 AA21 错误
|
|
396
397
|
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
398
|
+
// 多跳可用的条件:
|
|
399
|
+
// 1. 配置了 Paymaster(prefund = 0n,hop 钱包不需要余额)
|
|
400
|
+
// 2. 传入了预充值好的 hop 钱包(hop 钱包已有余额支付 prefund)
|
|
399
401
|
const hasPaymaster = this.aaManager.hasPaymaster();
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
//
|
|
489
|
+
// ✅ 构建 hop 钱包列表
|
|
490
|
+
// 如果有预充值的 hop 钱包,使用它们;否则生成新钱包(需要 Paymaster)
|
|
480
491
|
const allGeneratedHopWallets = [];
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
package/dist/xlayer/types.d.ts
CHANGED
|
@@ -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[];
|