@aastar/enduser 0.16.18 → 0.16.19

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.
@@ -369,8 +369,9 @@ export class UserClient extends BaseClient {
369
369
  args: [params.target, params.value, params.data]
370
370
  });
371
371
  // 3. Delegate to PaymasterClient for v0.7 Gasless Submission
372
+ // This ensures we follow the exact same logic as successful demo scripts
372
373
  // We dynamic import to avoid circular dependencies if any
373
- const { PaymasterClient: SDKPaymasterClient } = await import('../../paymaster/src/V4/PaymasterClient.js');
374
+ const { PaymasterClient: SDKPaymasterClient } = await import('@aastar/paymaster');
374
375
  let verificationGasLimit;
375
376
  let paymasterVerificationGasLimit;
376
377
  let paymasterPostOpGasLimit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aastar/enduser",
3
- "version": "0.16.18",
3
+ "version": "0.16.19",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -20,7 +20,8 @@
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "viem": "2.43.3",
23
- "@aastar/core": "0.16.18"
23
+ "@aastar/core": "0.16.19",
24
+ "@aastar/paymaster": "0.16.19"
24
25
  },
25
26
  "devDependencies": {
26
27
  "typescript": "5.7.2",
@@ -1,28 +0,0 @@
1
- import { type Address } from 'viem';
2
- /**
3
- * Bundler types we support
4
- */
5
- export declare enum BundlerType {
6
- ALCHEMY = "alchemy",
7
- PIMLICO = "pimlico",
8
- STACKUP = "stackup",
9
- CANDIDE = "candide",
10
- UNKNOWN = "unknown"
11
- }
12
- /**
13
- * Detect bundler type from URL
14
- */
15
- export declare function detectBundlerType(bundlerUrl: string): BundlerType;
16
- /**
17
- * Minimal interface to satisfy basic Pimlico/Bundler needs
18
- * without bringing in heavy permissionless types that might conflict
19
- */
20
- export interface BundlerConfig {
21
- type: BundlerType;
22
- url: string;
23
- entryPoint: Address;
24
- }
25
- /**
26
- * Create a bundler client config
27
- */
28
- export declare function createBundlerClient(bundlerUrl: string, entryPoint: Address): BundlerConfig;
@@ -1,37 +0,0 @@
1
- /**
2
- * Bundler types we support
3
- */
4
- export var BundlerType;
5
- (function (BundlerType) {
6
- BundlerType["ALCHEMY"] = "alchemy";
7
- BundlerType["PIMLICO"] = "pimlico";
8
- BundlerType["STACKUP"] = "stackup";
9
- BundlerType["CANDIDE"] = "candide";
10
- BundlerType["UNKNOWN"] = "unknown";
11
- })(BundlerType || (BundlerType = {}));
12
- /**
13
- * Detect bundler type from URL
14
- */
15
- export function detectBundlerType(bundlerUrl) {
16
- const url = bundlerUrl.toLowerCase();
17
- if (url.includes('alchemy.com'))
18
- return BundlerType.ALCHEMY;
19
- if (url.includes('pimlico.io'))
20
- return BundlerType.PIMLICO;
21
- if (url.includes('stackup'))
22
- return BundlerType.STACKUP;
23
- if (url.includes('candide.dev'))
24
- return BundlerType.CANDIDE;
25
- return BundlerType.UNKNOWN;
26
- }
27
- /**
28
- * Create a bundler client config
29
- */
30
- export function createBundlerClient(bundlerUrl, entryPoint) {
31
- const bundlerType = detectBundlerType(bundlerUrl);
32
- return {
33
- type: bundlerType,
34
- url: bundlerUrl,
35
- entryPoint
36
- };
37
- }
@@ -1,89 +0,0 @@
1
- import { type Address, type Hex } from 'viem';
2
- /**
3
- * PaymasterClient
4
- * Focus: Integration, Funding, Interaction.
5
- */
6
- export declare class PaymasterClient {
7
- /**
8
- * @private
9
- * Static utility class, should not be instantiated.
10
- */
11
- private constructor();
12
- /**
13
- * Get user's deposited balance on the Paymaster.
14
- */
15
- static getDepositedBalance(publicClient: any, address: Address, user: Address, token: Address): Promise<bigint>;
16
- /**
17
- * Deposit tokens to Paymaster for a user (enables gasless transactions).
18
- */
19
- static depositFor(wallet: any, address: Address, user: Address, token: Address, amount: bigint): Promise<any>;
20
- /**
21
- * Approve the Paymaster (or any spender) to spend gas tokens.
22
- */
23
- static approveGasToken(wallet: any, token: Address, spender: Address, amount: bigint): Promise<any>;
24
- /**
25
- * Estimate Gas for a UserOperation.
26
- */
27
- static estimateUserOperationGas(client: any, wallet: any, aaAddress: Address, entryPoint: Address, paymasterAddress: Address, token: Address, bundlerUrl: string, callData: `0x${string}`, options?: {
28
- validityWindow?: number;
29
- operator?: Address;
30
- factory?: Address;
31
- factoryData?: Hex;
32
- }): Promise<{
33
- preVerificationGas: bigint;
34
- verificationGasLimit: bigint;
35
- callGasLimit: bigint;
36
- paymasterVerificationGasLimit: bigint | undefined;
37
- paymasterPostOpGasLimit: bigint;
38
- }>;
39
- /**
40
- * High-level API to submit a gasless UserOperation.
41
- * Automatically handles nonce fetching, gas estimation (if not provided), signing, and submission.
42
- */
43
- static submitGaslessUserOperation(client: any, wallet: any, aaAddress: Address, entryPoint: Address, paymasterAddress: Address, token: Address, bundlerUrl: string, callData: `0x${string}`, options?: {
44
- validityWindow?: number;
45
- verificationGasLimit?: bigint;
46
- callGasLimit?: bigint;
47
- preVerificationGas?: bigint;
48
- maxFeePerGas?: bigint;
49
- maxPriorityFeePerGas?: bigint;
50
- autoEstimate?: boolean;
51
- operator?: Address;
52
- paymasterVerificationGasLimit?: bigint;
53
- paymasterPostOpGasLimit?: bigint;
54
- factory?: Address;
55
- factoryData?: Hex;
56
- }): Promise<`0x${string}`>;
57
- /**
58
- * Helper to extract the actual Gas Token fee from a UserOperation receipt.
59
- * Looks for the 'PostOpProcessed' event emitted by the Paymaster.
60
- */
61
- static getFeeFromReceipt(receipt: any, paymasterAddress: Address): {
62
- tokenCost: bigint;
63
- actualGasCostWei: bigint;
64
- } | null;
65
- /**
66
- * Get the fee for a specific transaction hash.
67
- * Fetches the receipt (no scanning required) and decodes the log.
68
- */
69
- static getTransactionFee(publicClient: any, txHash: `0x${string}`, paymasterAddress: Address): Promise<{
70
- tokenCost: bigint;
71
- actualGasCostWei: bigint;
72
- } | null>;
73
- /**
74
- * Helper: Encode a standardized ERC-20 Transfer.
75
- * Returns the raw function data: `transfer(to, amount)`
76
- */
77
- static encodeTokenTransfer(recipient: Address, amount: bigint): `0x${string}`;
78
- /**
79
- * Helper: Encode a SimpleAccount execution.
80
- * Wraps the inner call into: `execute(target, value, data)`
81
- * This is the payload signed by the user.
82
- */
83
- static encodeExecution(target: Address, value: bigint, data: `0x${string}`): `0x${string}`;
84
- /**
85
- * More robust version of waitForUserOperationReceipt.
86
- * Catches timeouts and returns a cleaner result.
87
- */
88
- static waitForUserOperation(bundlerClient: any, hash: `0x${string}`, timeout?: number): Promise<any>;
89
- }
@@ -1,413 +0,0 @@
1
- import { parseAbi, concat, pad, toHex, encodeFunctionData } from 'viem';
2
- import { buildPaymasterData, buildSuperPaymasterData, formatUserOpV07, getUserOpHashV07 } from './PaymasterUtils.js';
3
- import { detectBundlerType } from './BundlerCompat.js';
4
- /**
5
- * PaymasterClient
6
- * Focus: Integration, Funding, Interaction.
7
- */
8
- export class PaymasterClient {
9
- /**
10
- * @private
11
- * Static utility class, should not be instantiated.
12
- */
13
- constructor() { }
14
- /**
15
- * Get user's deposited balance on the Paymaster.
16
- */
17
- static async getDepositedBalance(publicClient, address, user, token) {
18
- return publicClient.readContract({
19
- address,
20
- abi: [{
21
- name: 'balances',
22
- type: 'function',
23
- inputs: [
24
- { name: 'user', type: 'address' },
25
- { name: 'token', type: 'address' }
26
- ],
27
- outputs: [{ name: '', type: 'uint256' }],
28
- stateMutability: 'view'
29
- }],
30
- functionName: 'balances',
31
- args: [user, token]
32
- });
33
- }
34
- /**
35
- * Deposit tokens to Paymaster for a user (enables gasless transactions).
36
- */
37
- static async depositFor(wallet, address, user, token, amount) {
38
- return wallet.writeContract({
39
- address,
40
- abi: parseAbi(['function depositFor(address user, address token, uint256 amount) external']),
41
- functionName: 'depositFor',
42
- args: [user, token, amount],
43
- chain: wallet.chain
44
- });
45
- }
46
- /**
47
- * Approve the Paymaster (or any spender) to spend gas tokens.
48
- */
49
- static async approveGasToken(wallet, token, spender, amount) {
50
- return wallet.writeContract({
51
- address: token,
52
- abi: parseAbi(['function approve(address spender, uint256 amount) external returns (bool)']),
53
- functionName: 'approve',
54
- args: [spender, amount],
55
- chain: wallet.chain
56
- });
57
- }
58
- /**
59
- * Estimate Gas for a UserOperation.
60
- */
61
- static async estimateUserOperationGas(client, wallet, aaAddress, entryPoint, paymasterAddress, token, bundlerUrl, callData, options) {
62
- // 0. Check cachedPrice (Critical for Paymaster V4)
63
- if (!options?.operator) { // Only for Paymaster V4, not SuperPaymaster
64
- try {
65
- const cache = await client.readContract({
66
- address: paymasterAddress,
67
- abi: parseAbi(['function cachedPrice() view returns (uint208 price, uint48 updatedAt)']),
68
- functionName: 'cachedPrice'
69
- });
70
- if (!cache || cache.price === 0n || cache[0] === 0n) {
71
- console.log('[PaymasterClient] ⚠️ cachedPrice is 0! Auto-initializing...');
72
- // Check if we're on testnet (chainId 11155111 = Sepolia, 11155420 = OP Sepolia)
73
- const chainId = client.chain?.id || await client.getChainId();
74
- const isTestnet = [11155111, 11155420, 31337].includes(chainId);
75
- if (isTestnet) {
76
- // Auto-call updatePrice on testnet
77
- const updateHash = await wallet.writeContract({
78
- address: paymasterAddress,
79
- abi: parseAbi(['function updatePrice() external']),
80
- functionName: 'updatePrice'
81
- });
82
- await client.waitForTransactionReceipt({ hash: updateHash });
83
- console.log('[PaymasterClient] ✅ cachedPrice initialized via updatePrice()');
84
- }
85
- else {
86
- // Mainnet: throw error, require Keeper
87
- throw new Error(`Paymaster cachedPrice is 0 on Mainnet (chainId: ${chainId}). ` +
88
- `This requires Keeper to call updatePrice(). Please ensure Keeper is running.`);
89
- }
90
- }
91
- }
92
- catch (e) {
93
- // If error is our mainnet check, re-throw
94
- if (e.message?.includes('requires Keeper'))
95
- throw e;
96
- // Otherwise log and continue (might be old Paymaster without cachedPrice)
97
- console.log('[PaymasterClient] ⚠️ Failed to check cachedPrice:', e.message?.slice(0, 50));
98
- }
99
- }
100
- // 1. Construct a dummy UserOp for estimation
101
- let paymasterAndData;
102
- if (options?.operator) {
103
- paymasterAndData = buildSuperPaymasterData(paymasterAddress, options.operator, {
104
- verificationGasLimit: 300000n,
105
- postOpGasLimit: 300000n
106
- });
107
- }
108
- else {
109
- paymasterAndData = buildPaymasterData(paymasterAddress, token, {
110
- validityWindow: options?.validityWindow,
111
- verificationGasLimit: 250000n,
112
- postOpGasLimit: 150000n
113
- });
114
- }
115
- // 1.5. Get dynamic gas prices from network
116
- let maxFeePerGas = 30000000000n; // 30 Gwei default
117
- let maxPriorityFeePerGas = 1000000000n; // 1 Gwei default
118
- try {
119
- const feeData = await client.estimateFeesPerGas();
120
- maxFeePerGas = (feeData.maxFeePerGas ?? 30000000000n) * 150n / 100n; // 1.5x buffer
121
- maxPriorityFeePerGas = (feeData.maxPriorityFeePerGas ?? 1000000000n) * 150n / 100n;
122
- if (maxPriorityFeePerGas < 500000000n)
123
- maxPriorityFeePerGas = 500000000n; // Min 0.5 Gwei
124
- }
125
- catch (e) {
126
- // Use defaults if estimation fails
127
- }
128
- const partialUserOp = {
129
- sender: aaAddress,
130
- nonce: 0n,
131
- initCode: (options?.factory && options?.factoryData)
132
- ? concat([options.factory, options.factoryData])
133
- : '0x',
134
- callData,
135
- accountGasLimits: concat([pad(toHex(250000n), { size: 16 }), pad(toHex(500000n), { size: 16 })]),
136
- preVerificationGas: 100000n,
137
- gasFees: concat([pad(toHex(maxPriorityFeePerGas), { size: 16 }), pad(toHex(maxFeePerGas), { size: 16 })]),
138
- paymasterAndData,
139
- signature: '0x'
140
- };
141
- // Get actual nonce
142
- try {
143
- const nonce = await client.readContract({
144
- address: aaAddress,
145
- abi: parseAbi(['function getNonce() view returns (uint256)']),
146
- functionName: 'getNonce'
147
- });
148
- partialUserOp.nonce = BigInt(nonce);
149
- }
150
- catch (e) { }
151
- const userOpHash = getUserOpHashV07(partialUserOp, entryPoint, BigInt(client.chain.id));
152
- partialUserOp.signature = (await wallet.account.signMessage({ message: { raw: userOpHash } }));
153
- const payload = {
154
- jsonrpc: '2.0',
155
- id: 1,
156
- method: 'eth_estimateUserOperationGas',
157
- params: [formatUserOpV07(partialUserOp), entryPoint]
158
- };
159
- const response = await fetch(bundlerUrl, {
160
- method: 'POST',
161
- headers: { 'Content-Type': 'application/json' },
162
- body: JSON.stringify(payload, (_, v) => typeof v === 'bigint' ? '0x' + v.toString(16) : v)
163
- });
164
- const result = await response.json();
165
- // Debug logging for Candide
166
- if (bundlerUrl.includes('candide')) {
167
- console.log('[PaymasterClient] Candide Request:', JSON.stringify(payload.params[0], null, 2));
168
- console.log('[PaymasterClient] Candide Response:', JSON.stringify(result, null, 2));
169
- }
170
- const data = result.result;
171
- // Debug logging for estimation
172
- console.log('[PaymasterClient] Gas Estimation Result:', JSON.stringify(data, null, 2));
173
- // Anvil Fallback for Estimation
174
- if (result.error && (result.error.code === -32601 || result.error.message?.includes('Method not found'))) {
175
- console.log('[PaymasterClient] EstimateUserOp failed (Method not found). Using Anvil defaults.');
176
- return {
177
- preVerificationGas: 100000n,
178
- verificationGasLimit: 1000000n,
179
- callGasLimit: 2000000n,
180
- paymasterVerificationGasLimit: 100000n,
181
- paymasterPostOpGasLimit: 100000n
182
- };
183
- }
184
- if (result.error)
185
- throw new Error(`Estimation Error: ${JSON.stringify(result.error)}`);
186
- // Dynamic tuning: use estimated values directly to maintain efficiency
187
- // Bundler efficiency check: actual_used / limit >= 0.4
188
- return {
189
- preVerificationGas: BigInt(data.preVerificationGas),
190
- verificationGasLimit: BigInt(data.verificationGasLimit), // Use estimate as-is for efficiency
191
- callGasLimit: (BigInt(data.callGasLimit) * 110n) / 100n, // Small 1.1x buffer
192
- paymasterVerificationGasLimit: data.paymasterVerificationGasLimit ? BigInt(data.paymasterVerificationGasLimit) : undefined,
193
- paymasterPostOpGasLimit: data.paymasterPostOpGasLimit ? BigInt(data.paymasterPostOpGasLimit) : 100000n
194
- };
195
- }
196
- /**
197
- * High-level API to submit a gasless UserOperation.
198
- * Automatically handles nonce fetching, gas estimation (if not provided), signing, and submission.
199
- */
200
- static async submitGaslessUserOperation(client, wallet, aaAddress, entryPoint, paymasterAddress, token, bundlerUrl, callData, options) {
201
- // 0. Auto-Estimate if requested or if limits missing
202
- let gasLimits = {
203
- preVerificationGas: options?.preVerificationGas,
204
- verificationGasLimit: options?.verificationGasLimit,
205
- callGasLimit: options?.callGasLimit,
206
- paymasterVerificationGasLimit: options?.paymasterVerificationGasLimit,
207
- paymasterPostOpGasLimit: options?.paymasterPostOpGasLimit ?? 150000n
208
- };
209
- if (options?.autoEstimate !== false && (!gasLimits.verificationGasLimit || !gasLimits.callGasLimit)) {
210
- const est = await this.estimateUserOperationGas(client, wallet, aaAddress, entryPoint, paymasterAddress, token, bundlerUrl, callData, {
211
- validityWindow: options?.validityWindow,
212
- operator: options?.operator,
213
- factory: options?.factory,
214
- factoryData: options?.factoryData
215
- });
216
- gasLimits.preVerificationGas = options?.preVerificationGas ?? est.preVerificationGas;
217
- gasLimits.verificationGasLimit = options?.verificationGasLimit ?? est.verificationGasLimit;
218
- gasLimits.callGasLimit = options?.callGasLimit ?? est.callGasLimit;
219
- gasLimits.paymasterVerificationGasLimit = options?.paymasterVerificationGasLimit ?? est.paymasterVerificationGasLimit;
220
- gasLimits.paymasterPostOpGasLimit = options?.paymasterPostOpGasLimit ?? est.paymasterPostOpGasLimit;
221
- }
222
- // 1. Get Nonce
223
- const nonce = await client.readContract({
224
- address: aaAddress,
225
- abi: parseAbi(['function getNonce() view returns (uint256)']),
226
- functionName: 'getNonce'
227
- });
228
- // 1.5 Get Gas Prices from Network if not provided
229
- let maxFeePerGas = options?.maxFeePerGas;
230
- let maxPriorityFeePerGas = options?.maxPriorityFeePerGas;
231
- if (!maxFeePerGas || !maxPriorityFeePerGas) {
232
- try {
233
- const feeData = await client.estimateFeesPerGas();
234
- // Apply 1.5x buffer for network volatility
235
- maxFeePerGas = maxFeePerGas ?? ((feeData.maxFeePerGas ?? 30000000000n) * 150n) / 100n;
236
- maxPriorityFeePerGas = maxPriorityFeePerGas ?? ((feeData.maxPriorityFeePerGas ?? 1000000000n) * 150n) / 100n;
237
- if (maxPriorityFeePerGas < 500000000n) {
238
- console.log(`[PaymasterClient] Priority Fee ${maxPriorityFeePerGas} too low, clamping to 0.5 Gwei`);
239
- maxPriorityFeePerGas = 500000000n; // Min 0.5 Gwei
240
- }
241
- // Ensure MaxFee >= Priority
242
- if (maxFeePerGas < maxPriorityFeePerGas) {
243
- maxFeePerGas = maxPriorityFeePerGas + 1000000n; // Add small buffer
244
- console.log(`[PaymasterClient] MaxFee bumped to accommodate Priority Fee`);
245
- }
246
- console.log(`[PaymasterClient] Fees Set: MaxFee=${maxFeePerGas}, Priority=${maxPriorityFeePerGas}`);
247
- }
248
- catch (e) {
249
- console.log('[PaymasterClient] Fee Estimation Failed:', e);
250
- // Fallback to safer defaults if estimation fails
251
- maxFeePerGas = maxFeePerGas ?? 50000000000n; // 50 Gwei
252
- maxPriorityFeePerGas = maxPriorityFeePerGas ?? 2000000000n; // 2 Gwei
253
- }
254
- }
255
- // 2. Build paymasterAndData
256
- let paymasterAndData;
257
- if (options?.operator) {
258
- paymasterAndData = buildSuperPaymasterData(paymasterAddress, options.operator, {
259
- verificationGasLimit: gasLimits.paymasterVerificationGasLimit ?? gasLimits.verificationGasLimit ?? 150000n,
260
- postOpGasLimit: gasLimits.paymasterPostOpGasLimit ?? 100000n
261
- });
262
- }
263
- else {
264
- // MATH: Target Efficiency = PVG / (PVG + VGL + PMVGL) >= 0.4
265
- // Since PVG is ~100k, (VGL + PMVGL) must be <= 150k.
266
- // We set each to 75k to safely pass the 0.4 efficiency guard.
267
- const pmVerGas = 75000n;
268
- paymasterAndData = buildPaymasterData(paymasterAddress, token, {
269
- validityWindow: options?.validityWindow,
270
- verificationGasLimit: pmVerGas,
271
- postOpGasLimit: gasLimits.paymasterPostOpGasLimit ?? 100000n
272
- });
273
- }
274
- // 3. Construct UserOp
275
- const userOp = {
276
- sender: aaAddress,
277
- nonce: BigInt(nonce),
278
- initCode: (options?.factory && options?.factoryData)
279
- ? concat([options.factory, options.factoryData])
280
- : '0x',
281
- callData,
282
- accountGasLimits: concat([
283
- pad(toHex(75000n), { size: 16 }), // Verification (Tuned for 0.4 efficiency)
284
- pad(toHex(gasLimits.callGasLimit ?? 500000n), { size: 16 }) // Call
285
- ]),
286
- preVerificationGas: gasLimits.preVerificationGas ?? 50000n,
287
- gasFees: concat([
288
- pad(toHex(maxPriorityFeePerGas), { size: 16 }),
289
- pad(toHex(maxFeePerGas), { size: 16 })
290
- ]),
291
- paymasterAndData,
292
- signature: '0x'
293
- };
294
- // Debug logs (Commented out for production)
295
- /*
296
- console.log("DEBUG: UserOp Gas Limits:", {
297
- accountGasLimits: userOp.accountGasLimits,
298
- preVerificationGas: userOp.preVerificationGas,
299
- gasFees: userOp.gasFees,
300
- paymasterAndData: userOp.paymasterAndData
301
- });
302
- */
303
- // 4. Final Hashing and Signing
304
- const userOpHash = getUserOpHashV07(userOp, entryPoint, BigInt(client.chain.id));
305
- const signature = (await wallet.account.signMessage({ message: { raw: userOpHash } }));
306
- userOp.signature = signature;
307
- // 6. Submit to Bundler (Unified JSON-RPC)
308
- const bundlerType = detectBundlerType(bundlerUrl);
309
- console.log(`[PaymasterClient] Using ${bundlerType} Bundler`);
310
- // Use standard JSON-RPC for all bundlers (Pimlico/Alchemy/Stackup/etc)
311
- const response = await fetch(bundlerUrl, {
312
- method: 'POST',
313
- headers: { 'Content-Type': 'application/json' },
314
- body: JSON.stringify({
315
- jsonrpc: '2.0',
316
- id: 1,
317
- method: 'eth_sendUserOperation',
318
- params: [formatUserOpV07(userOp), entryPoint]
319
- }, (_, v) => typeof v === 'bigint' ? '0x' + v.toString(16) : v)
320
- });
321
- const result = await response.json();
322
- if (result.error && (result.error.code === -32601 || result.error.message?.includes('Method not found'))) {
323
- console.log('[PaymasterClient] SendUserOp failed (Method not found). Falling back to direct handleOps...');
324
- const caller = wallet.account?.address ? wallet.account.address : wallet.account;
325
- return await wallet.writeContract({
326
- address: entryPoint,
327
- abi: parseAbi(['function handleOps((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes)[], address) external']),
328
- functionName: 'handleOps',
329
- args: [[userOp], caller],
330
- chain: wallet.chain,
331
- account: wallet.account
332
- });
333
- }
334
- if (result.error)
335
- throw new Error(`Bundler Error: ${JSON.stringify(result.error)}`);
336
- console.log('[PaymasterClient] ✅ Submitted via', bundlerType, 'hash:', result.result);
337
- return result.result;
338
- }
339
- /**
340
- * Helper to extract the actual Gas Token fee from a UserOperation receipt.
341
- * Looks for the 'PostOpProcessed' event emitted by the Paymaster.
342
- */
343
- static getFeeFromReceipt(receipt, paymasterAddress) {
344
- // Event Signature: PostOpProcessed(address indexed user, address indexed token, uint256 actualGasCostWei, uint256 tokenCost, uint256 protocolRevenue)
345
- // Topic0: 0x62544d7f48b11c32334310ebd306b47224fca220163218d4a7264322c52ae073
346
- const TOPIC_POST_OP = '0x62544d7f48b11c32334310ebd306b47224fca220163218d4a7264322c52ae073';
347
- for (const log of receipt.logs) {
348
- if (log.address.toLowerCase() === paymasterAddress.toLowerCase() && log.topics[0] === TOPIC_POST_OP) {
349
- // Decode Data: actualGasCostWei, tokenCost, protocolRevenue (3x uint256)
350
- // We manually decode or use viem's decodeEventLog if available.
351
- // Here we use a lightweight manual decode for the data part (non-indexed).
352
- // Data is 3 * 32 bytes.
353
- const data = log.data.replace('0x', '');
354
- if (data.length >= 192) { // 3 * 64 hex chars = 192
355
- const actualGasCostWei = BigInt('0x' + data.slice(0, 64));
356
- const tokenCost = BigInt('0x' + data.slice(64, 128));
357
- // const protocolRevenue = BigInt('0x' + data.slice(128, 192));
358
- return { tokenCost, actualGasCostWei };
359
- }
360
- }
361
- }
362
- return null;
363
- }
364
- /**
365
- * Get the fee for a specific transaction hash.
366
- * Fetches the receipt (no scanning required) and decodes the log.
367
- */
368
- static async getTransactionFee(publicClient, txHash, paymasterAddress) {
369
- const receipt = await publicClient.getTransactionReceipt({ hash: txHash });
370
- return this.getFeeFromReceipt(receipt, paymasterAddress);
371
- }
372
- // ===========================================
373
- // 🛠️ Semantic CallData Builders (For DX)
374
- // ===========================================
375
- /**
376
- * Helper: Encode a standardized ERC-20 Transfer.
377
- * Returns the raw function data: `transfer(to, amount)`
378
- */
379
- static encodeTokenTransfer(recipient, amount) {
380
- return encodeFunctionData({
381
- abi: parseAbi(['function transfer(address to, uint256 amount) external returns (bool)']),
382
- functionName: 'transfer',
383
- args: [recipient, amount]
384
- });
385
- }
386
- /**
387
- * Helper: Encode a SimpleAccount execution.
388
- * Wraps the inner call into: `execute(target, value, data)`
389
- * This is the payload signed by the user.
390
- */
391
- static encodeExecution(target, value, data) {
392
- return encodeFunctionData({
393
- abi: parseAbi(['function execute(address dest, uint256 value, bytes func) external']),
394
- functionName: 'execute',
395
- args: [target, value, data]
396
- });
397
- }
398
- /**
399
- * More robust version of waitForUserOperationReceipt.
400
- * Catches timeouts and returns a cleaner result.
401
- */
402
- static async waitForUserOperation(bundlerClient, hash, timeout = 60000) {
403
- try {
404
- return await bundlerClient.waitForUserOperationReceipt({ hash, timeout });
405
- }
406
- catch (error) {
407
- if (error.name === 'TimeoutError' || error.message?.includes('timed out')) {
408
- return { timeout: true, hash };
409
- }
410
- throw error;
411
- }
412
- }
413
- }