@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('
|
|
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.
|
|
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.
|
|
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
|
-
}
|