four-flap-meme-sdk 1.5.21 → 1.5.23
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/contracts/tm-bundle-merkle/index.d.ts +1 -1
- package/dist/contracts/tm-bundle-merkle/index.js +1 -1
- package/dist/contracts/tm-bundle-merkle/types.d.ts +24 -0
- package/dist/contracts/tm-bundle-merkle/utils.d.ts +8 -1
- package/dist/contracts/tm-bundle-merkle/utils.js +287 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/xlayer/aa-account.d.ts +32 -1
- package/dist/xlayer/aa-account.js +184 -46
- package/dist/xlayer/bundle.js +149 -53
- package/dist/xlayer/bundler.js +30 -11
- package/dist/xlayer/portal-ops.d.ts +1 -0
- package/dist/xlayer/portal-ops.js +125 -8
- package/dist/xlayer/types.d.ts +20 -0
- package/package.json +1 -1
- package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
- package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - initCode 生成
|
|
9
9
|
*/
|
|
10
10
|
import { ethers, JsonRpcProvider, Wallet, Contract, Interface } from 'ethers';
|
|
11
|
-
import { XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, DEFAULT_SALT, DEFAULT_GAS_PRICE, GAS_LIMIT_MULTIPLIER, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, FACTORY_ABI, ENTRYPOINT_ABI, SIMPLE_ACCOUNT_ABI, ZERO_ADDRESS, MULTICALL3, } from './constants.js';
|
|
11
|
+
import { XLAYER_CHAIN_ID, DEFAULT_RPC_URL, ENTRYPOINT_V06, SIMPLE_ACCOUNT_FACTORY, DEFAULT_SALT, DEFAULT_GAS_PRICE, DEFAULT_CALL_GAS_LIMIT_SELL, GAS_LIMIT_MULTIPLIER, VERIFICATION_GAS_LIMIT_DEPLOY, VERIFICATION_GAS_LIMIT_NORMAL, PRE_VERIFICATION_GAS, FACTORY_ABI, ENTRYPOINT_ABI, SIMPLE_ACCOUNT_ABI, ZERO_ADDRESS, MULTICALL3, } from './constants.js';
|
|
12
12
|
import { BundlerClient } from './bundler.js';
|
|
13
13
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
14
14
|
// ============================================================================
|
|
@@ -26,12 +26,16 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
|
26
26
|
export class AAAccountManager {
|
|
27
27
|
constructor(config = {}) {
|
|
28
28
|
this.senderCache = new Map(); // key: ownerLower -> sender
|
|
29
|
+
// PERF: 本文件的改造会尽量减少逐地址 RPC 调用
|
|
30
|
+
this.deployedSenderSet = new Set(); // key: senderLower(只缓存 deployed=true)
|
|
31
|
+
this.feeDataCacheTtlMs = 1200;
|
|
29
32
|
this.chainId = config.chainId ?? XLAYER_CHAIN_ID;
|
|
30
33
|
const rpcUrl = config.rpcUrl ?? DEFAULT_RPC_URL;
|
|
31
34
|
this.rpcUrl = rpcUrl;
|
|
32
|
-
this.provider = new JsonRpcProvider(rpcUrl, {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
this.provider = new JsonRpcProvider(rpcUrl, { chainId: this.chainId, name: 'xlayer' }, {
|
|
36
|
+
// 防止 ethers 自动攒太大的 JSON-RPC batch(XLayer 节点对“大 batch”很敏感)
|
|
37
|
+
batchMaxCount: 20,
|
|
38
|
+
batchStallTime: 30,
|
|
35
39
|
});
|
|
36
40
|
this.factoryAddress = config.factory ?? SIMPLE_ACCOUNT_FACTORY;
|
|
37
41
|
this.entryPointAddress = config.entryPoint ?? ENTRYPOINT_V06;
|
|
@@ -39,6 +43,9 @@ export class AAAccountManager {
|
|
|
39
43
|
this.paymaster = config.paymaster;
|
|
40
44
|
this.paymasterData = config.paymasterData;
|
|
41
45
|
this.gasLimitMultiplier = config.gasLimitMultiplier ?? GAS_LIMIT_MULTIPLIER;
|
|
46
|
+
// 默认 fixed:最大化降低 RPC / bundler 请求;如需更稳可显式传 bundlerEstimate
|
|
47
|
+
this.defaultGasPolicy = config.gasPolicy ?? 'fixed';
|
|
48
|
+
this.defaultFixedGas = config.fixedGas;
|
|
42
49
|
this.factory = new Contract(this.factoryAddress, FACTORY_ABI, this.provider);
|
|
43
50
|
this.entryPoint = new Contract(this.entryPointAddress, ENTRYPOINT_ABI, this.provider);
|
|
44
51
|
this.bundler = new BundlerClient({
|
|
@@ -76,7 +83,14 @@ export class AAAccountManager {
|
|
|
76
83
|
* 获取当前 Fee Data
|
|
77
84
|
*/
|
|
78
85
|
async getFeeData() {
|
|
79
|
-
|
|
86
|
+
// 费率在“批量构建 userOp”时会被大量复用;加短 TTL 缓存可显著减少 RPC
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
const hit = this.feeDataCache;
|
|
89
|
+
if (hit && hit.expiresAt > now)
|
|
90
|
+
return hit.value;
|
|
91
|
+
const value = await this.provider.getFeeData();
|
|
92
|
+
this.feeDataCache = { expiresAt: now + this.feeDataCacheTtlMs, value };
|
|
93
|
+
return value;
|
|
80
94
|
}
|
|
81
95
|
/**
|
|
82
96
|
* 预测 AA Sender 地址
|
|
@@ -275,7 +289,7 @@ export class AAAccountManager {
|
|
|
275
289
|
* 构建未签名的 UserOperation(使用 Bundler 估算 Gas)
|
|
276
290
|
*/
|
|
277
291
|
async buildUserOpWithBundlerEstimate(params) {
|
|
278
|
-
const feeData = await this.
|
|
292
|
+
const feeData = await this.getFeeData();
|
|
279
293
|
const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
|
|
280
294
|
const paymasterAndData = this.buildPaymasterAndData();
|
|
281
295
|
// 构建初步 UserOp(Gas 字段为 0,等待估算)
|
|
@@ -324,7 +338,7 @@ export class AAAccountManager {
|
|
|
324
338
|
if (params.ops.length === 0) {
|
|
325
339
|
return { userOps: [], prefundWeis: [] };
|
|
326
340
|
}
|
|
327
|
-
const feeData = await this.
|
|
341
|
+
const feeData = await this.getFeeData();
|
|
328
342
|
const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
|
|
329
343
|
const paymasterAndData = this.buildPaymasterAndData();
|
|
330
344
|
// 先构建 skeleton(Gas=0),然后批量估算
|
|
@@ -375,7 +389,7 @@ export class AAAccountManager {
|
|
|
375
389
|
* 适用于无法通过 Bundler 估算的场景(如尚未授权的 sell 操作)
|
|
376
390
|
*/
|
|
377
391
|
async buildUserOpWithLocalEstimate(params) {
|
|
378
|
-
const feeData = await this.
|
|
392
|
+
const feeData = await this.getFeeData();
|
|
379
393
|
const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
|
|
380
394
|
const paymasterAndData = this.buildPaymasterAndData();
|
|
381
395
|
// 估算 callGasLimit
|
|
@@ -417,11 +431,43 @@ export class AAAccountManager {
|
|
|
417
431
|
const prefundWei = paymasterAndData !== '0x' ? 0n : gasTotal * legacyGasPrice;
|
|
418
432
|
return { userOp, prefundWei };
|
|
419
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* 构建未签名的 UserOperation(固定 Gas,不做任何 estimate)
|
|
436
|
+
*
|
|
437
|
+
* 适用于大规模(1000 地址)场景:尽量减少 RPC 调用量。
|
|
438
|
+
*/
|
|
439
|
+
async buildUserOpWithFixedGas(params) {
|
|
440
|
+
const feeData = await this.getFeeData();
|
|
441
|
+
const legacyGasPrice = feeData.gasPrice ?? feeData.maxFeePerGas ?? DEFAULT_GAS_PRICE;
|
|
442
|
+
const paymasterAndData = this.buildPaymasterAndData();
|
|
443
|
+
const callGasLimit = params.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL;
|
|
444
|
+
const verificationGasLimit = params.deployed
|
|
445
|
+
? (params.fixedGas?.verificationGasLimitDeployed ?? VERIFICATION_GAS_LIMIT_NORMAL)
|
|
446
|
+
: (params.fixedGas?.verificationGasLimitUndeployed ?? VERIFICATION_GAS_LIMIT_DEPLOY);
|
|
447
|
+
const preVerificationGas = params.fixedGas?.preVerificationGas ?? PRE_VERIFICATION_GAS;
|
|
448
|
+
const userOp = {
|
|
449
|
+
sender: params.sender,
|
|
450
|
+
nonce: ethers.toBeHex(params.nonce),
|
|
451
|
+
initCode: params.initCode ?? '0x',
|
|
452
|
+
callData: params.callData,
|
|
453
|
+
callGasLimit: ethers.toBeHex(callGasLimit),
|
|
454
|
+
verificationGasLimit: ethers.toBeHex(verificationGasLimit),
|
|
455
|
+
preVerificationGas: ethers.toBeHex(preVerificationGas),
|
|
456
|
+
// XLayer 更像 legacy gasPrice 语义
|
|
457
|
+
maxFeePerGas: ethers.toBeHex(legacyGasPrice),
|
|
458
|
+
maxPriorityFeePerGas: ethers.toBeHex(legacyGasPrice),
|
|
459
|
+
paymasterAndData,
|
|
460
|
+
signature: '0x',
|
|
461
|
+
};
|
|
462
|
+
const gasTotal = callGasLimit + verificationGasLimit + preVerificationGas;
|
|
463
|
+
const prefundWei = paymasterAndData !== '0x' ? 0n : gasTotal * legacyGasPrice;
|
|
464
|
+
return { userOp, prefundWei };
|
|
465
|
+
}
|
|
420
466
|
/**
|
|
421
467
|
* 签名 UserOperation
|
|
422
468
|
*/
|
|
423
469
|
async signUserOp(userOp, ownerWallet) {
|
|
424
|
-
const userOpHash =
|
|
470
|
+
const userOpHash = computeUserOpHashV06(userOp, this.entryPointAddress, this.chainId);
|
|
425
471
|
const signature = await ownerWallet.signMessage(ethers.getBytes(userOpHash));
|
|
426
472
|
return {
|
|
427
473
|
userOp: { ...userOp, signature },
|
|
@@ -446,7 +492,25 @@ export class AAAccountManager {
|
|
|
446
492
|
};
|
|
447
493
|
let userOp;
|
|
448
494
|
let prefundWei;
|
|
449
|
-
|
|
495
|
+
const policy = params.gasPolicy ??
|
|
496
|
+
this.defaultGasPolicy ??
|
|
497
|
+
(params.useBundlerEstimate === false ? 'localEstimate' : 'bundlerEstimate');
|
|
498
|
+
if (policy === 'fixed') {
|
|
499
|
+
const fixedGasMerged = {
|
|
500
|
+
...(this.defaultFixedGas ?? {}),
|
|
501
|
+
...(params.fixedGas ?? {}),
|
|
502
|
+
...(params.callGasLimit ? { callGasLimit: params.callGasLimit } : {}),
|
|
503
|
+
};
|
|
504
|
+
const fixedGas = Object.keys(fixedGasMerged).length > 0 ? fixedGasMerged : undefined;
|
|
505
|
+
const result = await this.buildUserOpWithFixedGas({
|
|
506
|
+
...buildParams,
|
|
507
|
+
deployed: accountInfo.deployed,
|
|
508
|
+
fixedGas,
|
|
509
|
+
});
|
|
510
|
+
userOp = result.userOp;
|
|
511
|
+
prefundWei = result.prefundWei;
|
|
512
|
+
}
|
|
513
|
+
else if (policy === 'bundlerEstimate') {
|
|
450
514
|
const result = await this.buildUserOpWithBundlerEstimate(buildParams);
|
|
451
515
|
userOp = result.userOp;
|
|
452
516
|
prefundWei = result.prefundWei;
|
|
@@ -520,59 +584,133 @@ export class AAAccountManager {
|
|
|
520
584
|
catch { /* ignore */ }
|
|
521
585
|
}
|
|
522
586
|
}
|
|
523
|
-
// 3)
|
|
524
|
-
const
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
587
|
+
// 3) deployed(getCode):小分片 + 并发上限 + 缓存 deployed=true,避免 -32014 “batch request too many”
|
|
588
|
+
const deployed = new Array(senders.length).fill(false);
|
|
589
|
+
const needCode = [];
|
|
590
|
+
for (let i = 0; i < senders.length; i++) {
|
|
591
|
+
const sender = senders[i];
|
|
592
|
+
const key = sender.toLowerCase();
|
|
593
|
+
if (this.deployedSenderSet.has(key)) {
|
|
594
|
+
deployed[i] = true;
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
needCode.push({ idx: i, sender, key });
|
|
598
|
+
}
|
|
599
|
+
if (needCode.length > 0) {
|
|
600
|
+
const CHUNK = 20;
|
|
601
|
+
const chunks = [];
|
|
602
|
+
for (let c = 0; c < needCode.length; c += CHUNK)
|
|
603
|
+
chunks.push(needCode.slice(c, c + CHUNK));
|
|
604
|
+
const batchResults = await mapWithConcurrency(chunks, 2, async (chunk) => {
|
|
605
|
+
try {
|
|
606
|
+
const reqs = chunk.map((x) => ({ method: 'eth_getCode', params: [x.sender, 'latest'] }));
|
|
607
|
+
const codes = await this.rpcBatch(reqs);
|
|
608
|
+
return { ok: true, chunk, codes };
|
|
533
609
|
}
|
|
534
|
-
|
|
535
|
-
|
|
610
|
+
catch (err) {
|
|
611
|
+
return { ok: false, chunk, err };
|
|
536
612
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
balances[idx] = typeof b === 'string' ? BigInt(b) : 0n;
|
|
548
|
-
}
|
|
549
|
-
catch {
|
|
550
|
-
balances[idx] = 0n;
|
|
613
|
+
});
|
|
614
|
+
for (const r of batchResults) {
|
|
615
|
+
if (r.ok) {
|
|
616
|
+
for (let i = 0; i < r.chunk.length; i++) {
|
|
617
|
+
const it = r.chunk[i];
|
|
618
|
+
const code = r.codes[i];
|
|
619
|
+
const isDeployed = !!code && code !== '0x';
|
|
620
|
+
deployed[it.idx] = isDeployed;
|
|
621
|
+
if (isDeployed)
|
|
622
|
+
this.deployedSenderSet.add(it.key);
|
|
551
623
|
}
|
|
624
|
+
continue;
|
|
552
625
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const [code, bal] = await Promise.all([this.provider.getCode(s), this.provider.getBalance(s)]);
|
|
558
|
-
return { s, code, bal };
|
|
626
|
+
// fallback:单请求但受控并发
|
|
627
|
+
const fetched = await mapWithConcurrency(r.chunk, 4, async (it) => {
|
|
628
|
+
const code = await this.provider.getCode(it.sender);
|
|
629
|
+
return { it, code };
|
|
559
630
|
});
|
|
560
|
-
for (
|
|
631
|
+
for (const f of fetched) {
|
|
632
|
+
const isDeployed = !!f.code && f.code !== '0x';
|
|
633
|
+
deployed[f.it.idx] = isDeployed;
|
|
634
|
+
if (isDeployed)
|
|
635
|
+
this.deployedSenderSet.add(f.it.key);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// 4) balance(getEthBalance):用 Multicall3.getEthBalance 分片批量查询 OKB(减少 N 次 eth_getBalance)
|
|
640
|
+
const balances = new Array(senders.length).fill(0n);
|
|
641
|
+
const ethBalIface = new Interface(['function getEthBalance(address addr) view returns (uint256)']);
|
|
642
|
+
const balCalls = senders.map((sender) => ({
|
|
643
|
+
target: MULTICALL3,
|
|
644
|
+
allowFailure: true,
|
|
645
|
+
callData: ethBalIface.encodeFunctionData('getEthBalance', [sender]),
|
|
646
|
+
}));
|
|
647
|
+
const BAL_BATCH = 300;
|
|
648
|
+
try {
|
|
649
|
+
for (let cursor = 0; cursor < balCalls.length; cursor += BAL_BATCH) {
|
|
650
|
+
const sliceCalls = balCalls.slice(cursor, cursor + BAL_BATCH);
|
|
651
|
+
const res = await this.multicallAggregate3({ calls: sliceCalls });
|
|
652
|
+
for (let i = 0; i < res.length; i++) {
|
|
653
|
+
const r = res[i];
|
|
561
654
|
const idx = cursor + i;
|
|
562
|
-
|
|
563
|
-
|
|
655
|
+
if (!r?.success || !r.returnData || r.returnData === '0x')
|
|
656
|
+
continue;
|
|
657
|
+
try {
|
|
658
|
+
const decoded = ethBalIface.decodeFunctionResult('getEthBalance', r.returnData);
|
|
659
|
+
balances[idx] = BigInt(decoded?.[0] ?? 0n);
|
|
660
|
+
}
|
|
661
|
+
catch { /* ignore */ }
|
|
564
662
|
}
|
|
565
663
|
}
|
|
566
664
|
}
|
|
665
|
+
catch {
|
|
666
|
+
const fetched = await mapWithConcurrency(senders, 6, async (s) => await this.provider.getBalance(s));
|
|
667
|
+
for (let i = 0; i < fetched.length; i++)
|
|
668
|
+
balances[i] = fetched[i] ?? 0n;
|
|
669
|
+
}
|
|
567
670
|
return owners.map((owner, i) => ({
|
|
568
671
|
owner,
|
|
569
672
|
sender: senders[i],
|
|
570
|
-
deployed:
|
|
673
|
+
deployed: deployed[i] ?? false,
|
|
571
674
|
balance: balances[i] ?? 0n,
|
|
572
675
|
nonce: nonces[i] ?? 0n,
|
|
573
676
|
}));
|
|
574
677
|
}
|
|
575
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* ERC-4337 v0.6 userOpHash 本地计算
|
|
681
|
+
*
|
|
682
|
+
* pack 规则(v0.6):对 (sender,nonce,keccak(initCode),keccak(callData),callGasLimit,verificationGasLimit,preVerificationGas,maxFeePerGas,maxPriorityFeePerGas,keccak(paymasterAndData))
|
|
683
|
+
* 做 abi.encode 后 keccak 得到 packHash;再 abi.encode(packHash, entryPoint, chainId) 后 keccak 得到 userOpHash。
|
|
684
|
+
*/
|
|
685
|
+
export function computeUserOpHashV06(userOp, entryPoint, chainId) {
|
|
686
|
+
const coder = ethers.AbiCoder.defaultAbiCoder();
|
|
687
|
+
const packed = coder.encode([
|
|
688
|
+
'address',
|
|
689
|
+
'uint256',
|
|
690
|
+
'bytes32',
|
|
691
|
+
'bytes32',
|
|
692
|
+
'uint256',
|
|
693
|
+
'uint256',
|
|
694
|
+
'uint256',
|
|
695
|
+
'uint256',
|
|
696
|
+
'uint256',
|
|
697
|
+
'bytes32',
|
|
698
|
+
], [
|
|
699
|
+
userOp.sender,
|
|
700
|
+
BigInt(userOp.nonce),
|
|
701
|
+
ethers.keccak256(userOp.initCode ?? '0x'),
|
|
702
|
+
ethers.keccak256(userOp.callData ?? '0x'),
|
|
703
|
+
BigInt(userOp.callGasLimit),
|
|
704
|
+
BigInt(userOp.verificationGasLimit),
|
|
705
|
+
BigInt(userOp.preVerificationGas),
|
|
706
|
+
BigInt(userOp.maxFeePerGas),
|
|
707
|
+
BigInt(userOp.maxPriorityFeePerGas),
|
|
708
|
+
ethers.keccak256(userOp.paymasterAndData ?? '0x'),
|
|
709
|
+
]);
|
|
710
|
+
const packHash = ethers.keccak256(packed);
|
|
711
|
+
const enc = coder.encode(['bytes32', 'address', 'uint256'], [packHash, entryPoint, BigInt(chainId)]);
|
|
712
|
+
return ethers.keccak256(enc);
|
|
713
|
+
}
|
|
576
714
|
// ============================================================================
|
|
577
715
|
// 工具函数
|
|
578
716
|
// ============================================================================
|
package/dist/xlayer/bundle.js
CHANGED
|
@@ -15,6 +15,11 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
|
15
15
|
// ============================================================================
|
|
16
16
|
// 捆绑交易执行器
|
|
17
17
|
// ============================================================================
|
|
18
|
+
// 固定 gas(用于大规模减少 RPC);具体值允许通过 config.fixedGas 覆盖
|
|
19
|
+
const DEFAULT_CALL_GAS_LIMIT_BUY = DEFAULT_CALL_GAS_LIMIT_SELL; // buy 与 sell 共享一个保守值
|
|
20
|
+
const DEFAULT_CALL_GAS_LIMIT_APPROVE = 200000n;
|
|
21
|
+
const DEFAULT_CALL_GAS_LIMIT_TRANSFER = 150000n;
|
|
22
|
+
const DEFAULT_CALL_GAS_LIMIT_WITHDRAW = 120000n;
|
|
18
23
|
/**
|
|
19
24
|
* XLayer 捆绑交易执行器
|
|
20
25
|
*
|
|
@@ -109,14 +114,27 @@ export class BundleExecutor {
|
|
|
109
114
|
const callData = encodeExecute(this.portalAddress, buyAmountWei, swapData);
|
|
110
115
|
// 估算前确保 sender 有足够余额(用于模拟)
|
|
111
116
|
await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, buyAmountWei + parseOkb('0.0003'), `${ownerName ?? 'owner'}/buy-prefund-before-estimate`);
|
|
112
|
-
|
|
113
|
-
const { userOp, prefundWei } =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
|
|
118
|
+
const { userOp, prefundWei } = gasPolicy === 'fixed'
|
|
119
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
120
|
+
ownerWallet,
|
|
121
|
+
sender: accountInfo.sender,
|
|
122
|
+
callData,
|
|
123
|
+
nonce: accountInfo.nonce,
|
|
124
|
+
initCode,
|
|
125
|
+
deployed: accountInfo.deployed,
|
|
126
|
+
fixedGas: {
|
|
127
|
+
...(this.config.fixedGas ?? {}),
|
|
128
|
+
callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_BUY,
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
: await this.aaManager.buildUserOpWithBundlerEstimate({
|
|
132
|
+
ownerWallet,
|
|
133
|
+
sender: accountInfo.sender,
|
|
134
|
+
callData,
|
|
135
|
+
nonce: accountInfo.nonce,
|
|
136
|
+
initCode,
|
|
137
|
+
});
|
|
120
138
|
// 补足 prefund + 买入金额
|
|
121
139
|
await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, buyAmountWei + prefundWei + parseOkb('0.0002'), `${ownerName ?? 'owner'}/buy-fund`);
|
|
122
140
|
// 签名
|
|
@@ -134,13 +152,27 @@ export class BundleExecutor {
|
|
|
134
152
|
const approveData = encodeApproveCall(spender);
|
|
135
153
|
const callData = encodeExecute(tokenAddress, 0n, approveData);
|
|
136
154
|
await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/approve-prefund`);
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
155
|
+
const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
|
|
156
|
+
const { userOp, prefundWei } = gasPolicy === 'fixed'
|
|
157
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
158
|
+
ownerWallet,
|
|
159
|
+
sender,
|
|
160
|
+
callData,
|
|
161
|
+
nonce,
|
|
162
|
+
initCode,
|
|
163
|
+
deployed: initCode === '0x',
|
|
164
|
+
fixedGas: {
|
|
165
|
+
...(this.config.fixedGas ?? {}),
|
|
166
|
+
callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_APPROVE,
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
: await this.aaManager.buildUserOpWithLocalEstimate({
|
|
170
|
+
ownerWallet,
|
|
171
|
+
sender,
|
|
172
|
+
callData,
|
|
173
|
+
nonce,
|
|
174
|
+
initCode,
|
|
175
|
+
});
|
|
144
176
|
await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/approve-fund`);
|
|
145
177
|
const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
|
|
146
178
|
return { ...signed, prefundWei, ownerName };
|
|
@@ -152,23 +184,37 @@ export class BundleExecutor {
|
|
|
152
184
|
const sellData = encodeSellCall(tokenAddress, sellAmount, 0n);
|
|
153
185
|
const callData = encodeExecute(this.portalAddress, 0n, sellData);
|
|
154
186
|
await this.aaManager.ensureSenderBalance(ownerWallet, sender, parseOkb('0.0003'), `${ownerName ?? 'owner'}/sell-prefund`);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
|
|
188
|
+
// 如果需要 approve(还未执行),estimateGas 可能 revert;因此默认就用固定 callGasLimit
|
|
189
|
+
const { userOp, prefundWei } = gasPolicy === 'fixed'
|
|
190
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
158
191
|
ownerWallet,
|
|
159
192
|
sender,
|
|
160
193
|
callData,
|
|
161
194
|
nonce,
|
|
162
195
|
initCode,
|
|
163
|
-
|
|
196
|
+
deployed: initCode === '0x',
|
|
197
|
+
fixedGas: {
|
|
198
|
+
...(this.config.fixedGas ?? {}),
|
|
199
|
+
callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_SELL,
|
|
200
|
+
},
|
|
164
201
|
})
|
|
165
|
-
:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
202
|
+
: needApprove
|
|
203
|
+
? await this.aaManager.buildUserOpWithLocalEstimate({
|
|
204
|
+
ownerWallet,
|
|
205
|
+
sender,
|
|
206
|
+
callData,
|
|
207
|
+
nonce,
|
|
208
|
+
initCode,
|
|
209
|
+
callGasLimit: DEFAULT_CALL_GAS_LIMIT_SELL,
|
|
210
|
+
})
|
|
211
|
+
: await this.aaManager.buildUserOpWithLocalEstimate({
|
|
212
|
+
ownerWallet,
|
|
213
|
+
sender,
|
|
214
|
+
callData,
|
|
215
|
+
nonce,
|
|
216
|
+
initCode,
|
|
217
|
+
});
|
|
172
218
|
await this.aaManager.ensureSenderBalance(ownerWallet, sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/sell-fund`);
|
|
173
219
|
const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
|
|
174
220
|
return { ...signed, prefundWei, ownerName };
|
|
@@ -203,13 +249,27 @@ export class BundleExecutor {
|
|
|
203
249
|
// 先估算 prefund(使用空调用)
|
|
204
250
|
const tempCallData = encodeExecute(params.ownerWallet.address, 0n, '0x');
|
|
205
251
|
await this.aaManager.ensureSenderBalance(params.ownerWallet, params.sender, parseOkb('0.0002'), `${params.ownerName ?? 'owner'}/withdraw-prefund`);
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
252
|
+
const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
|
|
253
|
+
const { prefundWei } = gasPolicy === 'fixed'
|
|
254
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
255
|
+
ownerWallet: params.ownerWallet,
|
|
256
|
+
sender: params.sender,
|
|
257
|
+
callData: tempCallData,
|
|
258
|
+
nonce: params.nonce,
|
|
259
|
+
initCode: params.initCode,
|
|
260
|
+
deployed: params.initCode === '0x',
|
|
261
|
+
fixedGas: {
|
|
262
|
+
...(this.config.fixedGas ?? {}),
|
|
263
|
+
callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
: await this.aaManager.buildUserOpWithLocalEstimate({
|
|
267
|
+
ownerWallet: params.ownerWallet,
|
|
268
|
+
sender: params.sender,
|
|
269
|
+
callData: tempCallData,
|
|
270
|
+
nonce: params.nonce,
|
|
271
|
+
initCode: params.initCode,
|
|
272
|
+
});
|
|
213
273
|
// 计算可归集金额(用已知余额近似;fund 发生时余额会变大,属于可接受的保守近似)
|
|
214
274
|
const withdrawAmount = senderBalance > prefundWei + params.reserveWei
|
|
215
275
|
? senderBalance - prefundWei - params.reserveWei
|
|
@@ -219,13 +279,26 @@ export class BundleExecutor {
|
|
|
219
279
|
return null;
|
|
220
280
|
}
|
|
221
281
|
const callData = encodeExecute(params.ownerWallet.address, withdrawAmount, '0x');
|
|
222
|
-
const { userOp } =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
282
|
+
const { userOp } = gasPolicy === 'fixed'
|
|
283
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
284
|
+
ownerWallet: params.ownerWallet,
|
|
285
|
+
sender: params.sender,
|
|
286
|
+
callData,
|
|
287
|
+
nonce: params.nonce,
|
|
288
|
+
initCode: params.initCode,
|
|
289
|
+
deployed: params.initCode === '0x',
|
|
290
|
+
fixedGas: {
|
|
291
|
+
...(this.config.fixedGas ?? {}),
|
|
292
|
+
callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_WITHDRAW,
|
|
293
|
+
},
|
|
294
|
+
})
|
|
295
|
+
: await this.aaManager.buildUserOpWithLocalEstimate({
|
|
296
|
+
ownerWallet: params.ownerWallet,
|
|
297
|
+
sender: params.sender,
|
|
298
|
+
callData,
|
|
299
|
+
nonce: params.nonce,
|
|
300
|
+
initCode: params.initCode,
|
|
301
|
+
});
|
|
229
302
|
console.log(`\n[${params.ownerName ?? 'owner'}] withdraw: ${formatOkb(withdrawAmount)} OKB`);
|
|
230
303
|
const signed = await this.aaManager.signUserOp(userOp, params.ownerWallet);
|
|
231
304
|
return { ...signed, prefundWei, ownerName: params.ownerName };
|
|
@@ -243,12 +316,26 @@ export class BundleExecutor {
|
|
|
243
316
|
const transferData = encodeTransferCall(ownerWallet.address, tokenBalance);
|
|
244
317
|
const callData = encodeExecute(tokenAddress, 0n, transferData);
|
|
245
318
|
await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/transfer-prefund`);
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
319
|
+
const gasPolicy = this.config.gasPolicy ?? 'bundlerEstimate';
|
|
320
|
+
const { userOp, prefundWei } = gasPolicy === 'fixed'
|
|
321
|
+
? await this.aaManager.buildUserOpWithFixedGas({
|
|
322
|
+
ownerWallet,
|
|
323
|
+
sender: accountInfo.sender,
|
|
324
|
+
callData,
|
|
325
|
+
nonce: accountInfo.nonce,
|
|
326
|
+
initCode: accountInfo.deployed ? '0x' : this.aaManager.generateInitCode(ownerWallet.address),
|
|
327
|
+
deployed: accountInfo.deployed,
|
|
328
|
+
fixedGas: {
|
|
329
|
+
...(this.config.fixedGas ?? {}),
|
|
330
|
+
callGasLimit: this.config.fixedGas?.callGasLimit ?? DEFAULT_CALL_GAS_LIMIT_TRANSFER,
|
|
331
|
+
},
|
|
332
|
+
})
|
|
333
|
+
: await this.aaManager.buildUserOpWithBundlerEstimate({
|
|
334
|
+
ownerWallet,
|
|
335
|
+
sender: accountInfo.sender,
|
|
336
|
+
callData,
|
|
337
|
+
nonce: accountInfo.nonce,
|
|
338
|
+
});
|
|
252
339
|
await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/transfer-fund`);
|
|
253
340
|
console.log(`\n[${ownerName ?? 'owner'}] transfer token: ${tokenBalance.toString()}`);
|
|
254
341
|
const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
|
|
@@ -279,7 +366,10 @@ export class BundleExecutor {
|
|
|
279
366
|
const accountInfos = await this.aaManager.getMultipleAccountInfo(wallets.map((w) => w.address));
|
|
280
367
|
const buyWeis = buyAmounts.map((a) => parseOkb(a));
|
|
281
368
|
// 估算前确保 sender 有足够余额(用于 bundler 模拟;paymaster 场景会自动跳过)
|
|
282
|
-
|
|
369
|
+
// 避免 Promise.all 突发并发(大规模地址会触发 RPC 限流)
|
|
370
|
+
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
371
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, buyWeis[i] + parseOkb('0.0003'), `owner${i + 1}/buy-prefund-before-estimate`);
|
|
372
|
+
});
|
|
283
373
|
const buyCallDatas = buyWeis.map((buyWei) => {
|
|
284
374
|
const swapData = encodeBuyCall(tokenAddress, buyWei, 0n);
|
|
285
375
|
return encodeExecute(this.portalAddress, buyWei, swapData);
|
|
@@ -294,9 +384,11 @@ export class BundleExecutor {
|
|
|
294
384
|
})),
|
|
295
385
|
});
|
|
296
386
|
// 补足 prefund + 买入金额(多数情况下上一步的 0.0003 已足够,这里通常不会再转账)
|
|
297
|
-
await
|
|
298
|
-
|
|
299
|
-
|
|
387
|
+
await mapWithConcurrency(accountInfos, 6, async (ai, i) => {
|
|
388
|
+
await this.aaManager.ensureSenderBalance(wallets[i], ai.sender, buyWeis[i] + prefundWeis[i] + parseOkb('0.0002'), `owner${i + 1}/buy-fund`);
|
|
389
|
+
});
|
|
390
|
+
// 签名(受控并发,避免大规模时阻塞)
|
|
391
|
+
const signedBuy = await mapWithConcurrency(buyUserOps, 10, async (op, i) => this.aaManager.signUserOp(op, wallets[i]));
|
|
300
392
|
const buyOps = signedBuy.map((s) => s.userOp);
|
|
301
393
|
// 2. 执行买入
|
|
302
394
|
const buyResult = await this.runHandleOps('buyBundle', buyOps, bundlerSigner, beneficiary);
|
|
@@ -322,7 +414,9 @@ export class BundleExecutor {
|
|
|
322
414
|
}
|
|
323
415
|
if (idxs.length > 0) {
|
|
324
416
|
// 估算前补一点余额(paymaster 会自动跳过)
|
|
325
|
-
await
|
|
417
|
+
await mapWithConcurrency(idxs, 6, async (i) => {
|
|
418
|
+
await this.aaManager.ensureSenderBalance(wallets[i], senders[i], parseOkb('0.0002'), `owner${i + 1}/transfer-prefund`);
|
|
419
|
+
});
|
|
326
420
|
// buy 已经成功过一次,因此 transfer 的 nonce = 原 nonce + 1,且 initCode = 0x
|
|
327
421
|
const { userOps: transferUserOps, prefundWeis: transferPrefunds } = await this.aaManager.buildUserOpsWithBundlerEstimateBatch({
|
|
328
422
|
ops: idxs.map((i, k) => ({
|
|
@@ -332,8 +426,10 @@ export class BundleExecutor {
|
|
|
332
426
|
initCode: '0x',
|
|
333
427
|
})),
|
|
334
428
|
});
|
|
335
|
-
await
|
|
336
|
-
|
|
429
|
+
await mapWithConcurrency(idxs, 6, async (i, k) => {
|
|
430
|
+
await this.aaManager.ensureSenderBalance(wallets[i], senders[i], transferPrefunds[k] + parseOkb('0.00005'), `owner${i + 1}/transfer-fund`);
|
|
431
|
+
});
|
|
432
|
+
const signedTransfer = await mapWithConcurrency(transferUserOps, 10, async (op, k) => this.aaManager.signUserOp(op, wallets[idxs[k]]));
|
|
337
433
|
const transferOps = signedTransfer.map((s) => s.userOp);
|
|
338
434
|
transferResult =
|
|
339
435
|
(await this.runHandleOps('transferBundle', transferOps, bundlerSigner, beneficiary)) ?? undefined;
|