@1llet.xyz/erc4337-gasless-sdk 0.1.2 → 0.1.4

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.2",
6
+ "version": "0.1.4",
7
7
  "description": "SDK for ERC-4337 Gasless Transfers",
8
8
  "main": "./dist/index.js",
9
9
  "module": "./dist/index.mjs",
@@ -23,6 +23,7 @@ import {
23
23
  type ApprovalSupportResult
24
24
  } from "./types";
25
25
  import { DEPLOYMENTS } from "./deployments";
26
+ import { BundlerClient } from "./BundlerClient";
26
27
 
27
28
  /**
28
29
  * ERC-4337 Account Abstraction Client
@@ -32,6 +33,7 @@ export class AccountAbstraction {
32
33
  private smartAccountAddress: Address | null = null;
33
34
  private chainConfig: ChainConfig;
34
35
  private publicClient: PublicClient;
36
+ private bundlerClient: BundlerClient;
35
37
 
36
38
  // Resolved addresses
37
39
  private entryPointAddress: Address;
@@ -66,6 +68,8 @@ export class AccountAbstraction {
66
68
  chain: chainConfig.chain,
67
69
  transport: http(rpcUrl),
68
70
  });
71
+
72
+ this.bundlerClient = new BundlerClient(chainConfig, this.entryPointAddress);
69
73
  }
70
74
 
71
75
  /**
@@ -247,33 +251,7 @@ export class AccountAbstraction {
247
251
  * Estimate gas for a UserOperation
248
252
  */
249
253
  async estimateGas(userOp: Partial<UserOperation>): Promise<GasEstimate> {
250
- const response = await fetch(this.chainConfig.bundlerUrl, {
251
- method: "POST",
252
- headers: { "Content-Type": "application/json" },
253
- body: JSON.stringify({
254
- jsonrpc: "2.0",
255
- id: 1,
256
- method: "eth_estimateUserOperationGas",
257
- params: [
258
- {
259
- sender: userOp.sender,
260
- nonce: userOp.nonce ? "0x" + userOp.nonce.toString(16) : "0x0",
261
- initCode: userOp.initCode || "0x",
262
- callData: userOp.callData || "0x",
263
- paymasterAndData: userOp.paymasterAndData || "0x",
264
- signature: "0x",
265
- },
266
- this.entryPointAddress,
267
- ],
268
- }),
269
- });
270
-
271
- const result = await response.json();
272
- if (result.error) {
273
- throw new Error(result.error.message);
274
- }
275
-
276
- return result.result;
254
+ return this.bundlerClient.estimateGas(userOp);
277
255
  }
278
256
 
279
257
 
@@ -436,38 +414,7 @@ export class AccountAbstraction {
436
414
  * Send a signed UserOperation to the bundler
437
415
  */
438
416
  async sendUserOperation(userOp: UserOperation): Promise<Hash> {
439
- const response = await fetch(this.chainConfig.bundlerUrl, {
440
- method: "POST",
441
- headers: { "Content-Type": "application/json" },
442
- body: JSON.stringify({
443
- jsonrpc: "2.0",
444
- id: 1,
445
- method: "eth_sendUserOperation",
446
- params: [
447
- {
448
- sender: userOp.sender,
449
- nonce: "0x" + userOp.nonce.toString(16),
450
- initCode: userOp.initCode,
451
- callData: userOp.callData,
452
- callGasLimit: "0x" + userOp.callGasLimit.toString(16),
453
- verificationGasLimit: "0x" + userOp.verificationGasLimit.toString(16),
454
- preVerificationGas: "0x" + userOp.preVerificationGas.toString(16),
455
- maxFeePerGas: "0x" + userOp.maxFeePerGas.toString(16),
456
- maxPriorityFeePerGas: "0x" + userOp.maxPriorityFeePerGas.toString(16),
457
- paymasterAndData: userOp.paymasterAndData,
458
- signature: userOp.signature,
459
- },
460
- this.entryPointAddress,
461
- ],
462
- }),
463
- });
464
-
465
- const result = await response.json();
466
- if (result.error) {
467
- throw new Error(result.error.message);
468
- }
469
-
470
- return result.result as Hash;
417
+ return this.bundlerClient.sendUserOperation(userOp);
471
418
  }
472
419
 
473
420
  /**
@@ -477,30 +424,7 @@ export class AccountAbstraction {
477
424
  userOpHash: Hash,
478
425
  timeout = 60000
479
426
  ): Promise<UserOpReceipt> {
480
- const startTime = Date.now();
481
-
482
- while (Date.now() - startTime < timeout) {
483
- const response = await fetch(this.chainConfig.bundlerUrl, {
484
- method: "POST",
485
- headers: { "Content-Type": "application/json" },
486
- body: JSON.stringify({
487
- jsonrpc: "2.0",
488
- id: 1,
489
- method: "eth_getUserOperationReceipt",
490
- params: [userOpHash],
491
- }),
492
- });
493
-
494
- const result = await response.json();
495
- if (result.result) {
496
- return result.result as UserOpReceipt;
497
- }
498
-
499
- // Wait 2 seconds before polling again
500
- await new Promise((resolve) => setTimeout(resolve, 2000));
501
- }
502
-
503
- throw new Error("Timeout waiting for UserOperation");
427
+ return this.bundlerClient.waitForUserOperation(userOpHash, timeout);
504
428
  }
505
429
 
506
430
 
@@ -515,24 +439,60 @@ export class AccountAbstraction {
515
439
  if (!this.owner) {
516
440
  throw new Error("Not connected");
517
441
  }
442
+ return this.bundlerClient.requestApprovalSupport(token, this.owner, spender, amount);
443
+ }
518
444
 
519
- const response = await fetch(this.chainConfig.bundlerUrl, {
520
- method: "POST",
521
- headers: { "Content-Type": "application/json" },
522
- body: JSON.stringify({
523
- jsonrpc: "2.0",
524
- id: 1,
525
- method: "pm_requestApprovalSupport",
526
- params: [token, this.owner, spender, amount.toString()],
527
- }),
528
- });
445
+ /**
446
+ * Deploy the Smart Account
447
+ */
448
+ async deployAccount(): Promise<UserOpReceipt> {
449
+ const userOp = await this.buildDeployUserOperation();
450
+ const signed = await this.signUserOperation(userOp);
451
+ const hash = await this.sendUserOperation(signed);
452
+ return await this.waitForUserOperation(hash);
453
+ }
454
+
455
+ /**
456
+ * Approve a token for the Smart Account (EOA -> Token -> Smart Account)
457
+ * Checks for gas sponsorship (Relayer funding) if needed.
458
+ */
459
+ async approveToken(
460
+ token: Address,
461
+ spender: Address,
462
+ amount: bigint = 115792089237316195423570985008687907853269984665640564039457584007913129639935n // maxUint256
463
+ ): Promise<Hash | "NOT_NEEDED"> {
464
+ if (!this.owner) throw new Error("Not connected");
465
+
466
+ // 1. Check if we need funding
467
+ const support = await this.requestApprovalSupport(token, spender, amount);
468
+
469
+ if (support.type === "approve") {
470
+ // 2. Encode approve data
471
+ const data = encodeFunctionData({
472
+ abi: erc20Abi,
473
+ functionName: "approve",
474
+ args: [spender, amount]
475
+ });
476
+
477
+ // 3. Send transaction via Wallet (MetaMask)
478
+ // If funding was needed, the Relayer has already sent ETH to this.owner
479
+ const txHash = await window.ethereum!.request({
480
+ method: "eth_sendTransaction",
481
+ params: [{
482
+ from: this.owner,
483
+ to: token,
484
+ data,
485
+ }]
486
+ }) as Hash;
487
+
488
+ return txHash;
489
+ }
529
490
 
530
- const result = await response.json();
531
- if (result.error) {
532
- throw new Error(result.error.message);
491
+ if (support.type === "permit") {
492
+ throw new Error("Permit not yet supported in this SDK version");
533
493
  }
534
494
 
535
- return result.result;
495
+ return "NOT_NEEDED";
536
496
  }
537
497
 
538
498
  // Getters
@@ -0,0 +1,93 @@
1
+ import { type Address, type Hash, type Hex } from "viem";
2
+ import { type ChainConfig, type UserOperation, type GasEstimate, type UserOpReceipt, type ApprovalSupportResult } from "./types";
3
+ import { entryPointAbi } from "./constants";
4
+
5
+ export class BundlerClient {
6
+ private bundlerUrl: string;
7
+ private chainId: number;
8
+ private entryPointAddress: Address;
9
+
10
+ constructor(config: ChainConfig, entryPointAddress: Address) {
11
+ this.bundlerUrl = config.bundlerUrl;
12
+ this.chainId = config.chain.id;
13
+ this.entryPointAddress = entryPointAddress;
14
+ }
15
+
16
+ private async call(method: string, params: any[]): Promise<any> {
17
+ const response = await fetch(this.bundlerUrl, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify({
21
+ jsonrpc: "2.0",
22
+ id: 1,
23
+ method,
24
+ params,
25
+ }),
26
+ });
27
+
28
+ const result = await response.json();
29
+ if (result.error) {
30
+ throw new Error(result.error.message);
31
+ }
32
+ return result.result;
33
+ }
34
+
35
+ async estimateGas(userOp: Partial<UserOperation>): Promise<GasEstimate> {
36
+ return await this.call("eth_estimateUserOperationGas", [
37
+ {
38
+ sender: userOp.sender,
39
+ nonce: userOp.nonce ? "0x" + userOp.nonce.toString(16) : "0x0",
40
+ initCode: userOp.initCode || "0x",
41
+ callData: userOp.callData || "0x",
42
+ paymasterAndData: userOp.paymasterAndData || "0x",
43
+ signature: "0x",
44
+ },
45
+ this.entryPointAddress,
46
+ ]);
47
+ }
48
+
49
+ async sendUserOperation(userOp: UserOperation): Promise<Hash> {
50
+ return await this.call("eth_sendUserOperation", [
51
+ {
52
+ sender: userOp.sender,
53
+ nonce: "0x" + userOp.nonce.toString(16),
54
+ initCode: userOp.initCode,
55
+ callData: userOp.callData,
56
+ callGasLimit: "0x" + userOp.callGasLimit.toString(16),
57
+ verificationGasLimit: "0x" + userOp.verificationGasLimit.toString(16),
58
+ preVerificationGas: "0x" + userOp.preVerificationGas.toString(16),
59
+ maxFeePerGas: "0x" + userOp.maxFeePerGas.toString(16),
60
+ maxPriorityFeePerGas: "0x" + userOp.maxPriorityFeePerGas.toString(16),
61
+ paymasterAndData: userOp.paymasterAndData,
62
+ signature: userOp.signature,
63
+ },
64
+ this.entryPointAddress,
65
+ ]);
66
+ }
67
+
68
+ async waitForUserOperation(userOpHash: Hash, timeout = 60000): Promise<UserOpReceipt> {
69
+ const startTime = Date.now();
70
+
71
+ while (Date.now() - startTime < timeout) {
72
+ const result = await this.call("eth_getUserOperationReceipt", [userOpHash]);
73
+
74
+ if (result) {
75
+ return result as UserOpReceipt;
76
+ }
77
+
78
+ // Wait 2 seconds before polling again
79
+ await new Promise((resolve) => setTimeout(resolve, 2000));
80
+ }
81
+
82
+ throw new Error("Timeout waiting for UserOperation");
83
+ }
84
+
85
+ async requestApprovalSupport(token: Address, owner: Address, spender: Address, amount: bigint): Promise<ApprovalSupportResult> {
86
+ return await this.call("pm_requestApprovalSupport", [
87
+ token,
88
+ owner,
89
+ spender,
90
+ amount.toString()
91
+ ]);
92
+ }
93
+ }
package/dist/index.d.mts DELETED
@@ -1,330 +0,0 @@
1
- import { Chain, Address, Hex, Hash } from 'viem';
2
-
3
- interface ChainConfig {
4
- chain: Chain;
5
- rpcUrl?: string;
6
- bundlerUrl: string;
7
- entryPointAddress?: Address;
8
- factoryAddress?: Address;
9
- paymasterAddress?: Address;
10
- usdcAddress?: Address;
11
- }
12
- interface UserOperation {
13
- sender: Address;
14
- nonce: bigint;
15
- initCode: Hex;
16
- callData: Hex;
17
- callGasLimit: bigint;
18
- verificationGasLimit: bigint;
19
- preVerificationGas: bigint;
20
- maxFeePerGas: bigint;
21
- maxPriorityFeePerGas: bigint;
22
- paymasterAndData: Hex;
23
- signature: Hex;
24
- }
25
- interface GasEstimate {
26
- callGasLimit: string;
27
- verificationGasLimit: string;
28
- preVerificationGas: string;
29
- maxFeePerGas: string;
30
- maxPriorityFeePerGas: string;
31
- }
32
- interface UserOpReceipt {
33
- userOpHash: Hash;
34
- sender: Address;
35
- success: boolean;
36
- actualGasCost: string;
37
- receipt: {
38
- transactionHash: Hash;
39
- blockNumber: string;
40
- };
41
- }
42
- interface ApprovalSupportResult {
43
- type: "permit" | "approve" | "none";
44
- gasCost?: string;
45
- fundingNeeded?: string;
46
- fundedAmount?: string;
47
- }
48
-
49
- /**
50
- * ERC-4337 Account Abstraction Client
51
- */
52
- declare class AccountAbstraction {
53
- private owner;
54
- private smartAccountAddress;
55
- private chainConfig;
56
- private publicClient;
57
- private entryPointAddress;
58
- private factoryAddress;
59
- private paymasterAddress?;
60
- private usdcAddress;
61
- constructor(chainConfig: ChainConfig);
62
- /**
63
- * Connect to MetaMask and get the owner address
64
- */
65
- connect(): Promise<{
66
- owner: Address;
67
- smartAccount: Address;
68
- }>;
69
- /**
70
- * Get the Smart Account address for an owner (counterfactual)
71
- */
72
- getSmartAccountAddress(owner: Address): Promise<Address>;
73
- /**
74
- * Check if the Smart Account is deployed
75
- */
76
- isAccountDeployed(): Promise<boolean>;
77
- /**
78
- * Get the USDC balance of the Smart Account
79
- */
80
- getUsdcBalance(): Promise<bigint>;
81
- /**
82
- * Get the EOA's USDC balance
83
- */
84
- getEoaUsdcBalance(): Promise<bigint>;
85
- /**
86
- * Get the allowance of the Smart Account to spend the EOA's USDC
87
- */
88
- getAllowance(): Promise<bigint>;
89
- /**
90
- * Get the nonce for the Smart Account
91
- */
92
- getNonce(): Promise<bigint>;
93
- /**
94
- * Build initCode for account deployment
95
- */
96
- buildInitCode(): Hex;
97
- /**
98
- * Estimate gas for a UserOperation
99
- */
100
- estimateGas(userOp: Partial<UserOperation>): Promise<GasEstimate>;
101
- /**
102
- * Build a UserOperation for Batched Execution (e.g. USDC Transfer + Fee)
103
- */
104
- buildUserOperationBatch(transactions: {
105
- target: Address;
106
- value: bigint;
107
- data: Hex;
108
- }[]): Promise<UserOperation>;
109
- /**
110
- * Build a UserOperation to ONLY deploy the account (empty callData)
111
- */
112
- buildDeployUserOperation(): Promise<UserOperation>;
113
- /**
114
- * Calculate the UserOperation hash
115
- */
116
- getUserOpHash(userOp: UserOperation): Hex;
117
- /**
118
- * Sign a UserOperation with MetaMask
119
- */
120
- signUserOperation(userOp: UserOperation): Promise<UserOperation>;
121
- /**
122
- * Send a signed UserOperation to the bundler
123
- */
124
- sendUserOperation(userOp: UserOperation): Promise<Hash>;
125
- /**
126
- * Wait for a UserOperation to be confirmed
127
- */
128
- waitForUserOperation(userOpHash: Hash, timeout?: number): Promise<UserOpReceipt>;
129
- /**
130
- * Request support for token approval (fund if needed)
131
- */
132
- requestApprovalSupport(token: Address, spender: Address, amount: bigint): Promise<ApprovalSupportResult>;
133
- getOwner(): Address | null;
134
- getSmartAccount(): Address | null;
135
- }
136
- declare global {
137
- interface Window {
138
- ethereum?: {
139
- request: (args: {
140
- method: string;
141
- params?: unknown[];
142
- }) => Promise<unknown>;
143
- on: (event: string, callback: (args: unknown) => void) => void;
144
- removeListener: (event: string, callback: (args: unknown) => void) => void;
145
- };
146
- }
147
- }
148
-
149
- declare const DEFAULT_ENTRYPOINT = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
150
- declare const DEFAULT_FACTORY = "0x9406Cc6185a346906296840746125a0E44976454";
151
- declare const factoryAbi: readonly [{
152
- readonly inputs: readonly [{
153
- readonly name: "owner";
154
- readonly type: "address";
155
- }, {
156
- readonly name: "salt";
157
- readonly type: "uint256";
158
- }];
159
- readonly name: "getAccountAddress";
160
- readonly outputs: readonly [{
161
- readonly name: "";
162
- readonly type: "address";
163
- }];
164
- readonly stateMutability: "view";
165
- readonly type: "function";
166
- }, {
167
- readonly inputs: readonly [{
168
- readonly name: "owner";
169
- readonly type: "address";
170
- }, {
171
- readonly name: "salt";
172
- readonly type: "uint256";
173
- }];
174
- readonly name: "isAccountDeployed";
175
- readonly outputs: readonly [{
176
- readonly name: "";
177
- readonly type: "bool";
178
- }];
179
- readonly stateMutability: "view";
180
- readonly type: "function";
181
- }, {
182
- readonly inputs: readonly [{
183
- readonly name: "owner";
184
- readonly type: "address";
185
- }, {
186
- readonly name: "salt";
187
- readonly type: "uint256";
188
- }];
189
- readonly name: "createAccount";
190
- readonly outputs: readonly [{
191
- readonly name: "account";
192
- readonly type: "address";
193
- }];
194
- readonly stateMutability: "nonpayable";
195
- readonly type: "function";
196
- }];
197
- declare const entryPointAbi: readonly [{
198
- readonly inputs: readonly [{
199
- readonly name: "sender";
200
- readonly type: "address";
201
- }, {
202
- readonly name: "key";
203
- readonly type: "uint192";
204
- }];
205
- readonly name: "getNonce";
206
- readonly outputs: readonly [{
207
- readonly name: "nonce";
208
- readonly type: "uint256";
209
- }];
210
- readonly stateMutability: "view";
211
- readonly type: "function";
212
- }];
213
- declare const smartAccountAbi: readonly [{
214
- readonly inputs: readonly [{
215
- readonly name: "target";
216
- readonly type: "address";
217
- }, {
218
- readonly name: "value";
219
- readonly type: "uint256";
220
- }, {
221
- readonly name: "data";
222
- readonly type: "bytes";
223
- }];
224
- readonly name: "execute";
225
- readonly outputs: readonly [];
226
- readonly stateMutability: "nonpayable";
227
- readonly type: "function";
228
- }, {
229
- readonly inputs: readonly [{
230
- readonly name: "targets";
231
- readonly type: "address[]";
232
- }, {
233
- readonly name: "values";
234
- readonly type: "uint256[]";
235
- }, {
236
- readonly name: "datas";
237
- readonly type: "bytes[]";
238
- }];
239
- readonly name: "executeBatch";
240
- readonly outputs: readonly [];
241
- readonly stateMutability: "nonpayable";
242
- readonly type: "function";
243
- }];
244
- declare const erc20Abi: readonly [{
245
- readonly inputs: readonly [{
246
- readonly name: "account";
247
- readonly type: "address";
248
- }];
249
- readonly name: "balanceOf";
250
- readonly outputs: readonly [{
251
- readonly name: "";
252
- readonly type: "uint256";
253
- }];
254
- readonly stateMutability: "view";
255
- readonly type: "function";
256
- }, {
257
- readonly inputs: readonly [{
258
- readonly name: "to";
259
- readonly type: "address";
260
- }, {
261
- readonly name: "amount";
262
- readonly type: "uint256";
263
- }];
264
- readonly name: "transfer";
265
- readonly outputs: readonly [{
266
- readonly name: "";
267
- readonly type: "bool";
268
- }];
269
- readonly stateMutability: "nonpayable";
270
- readonly type: "function";
271
- }, {
272
- readonly inputs: readonly [{
273
- readonly name: "spender";
274
- readonly type: "address";
275
- }, {
276
- readonly name: "amount";
277
- readonly type: "uint256";
278
- }];
279
- readonly name: "approve";
280
- readonly outputs: readonly [{
281
- readonly name: "";
282
- readonly type: "bool";
283
- }];
284
- readonly stateMutability: "nonpayable";
285
- readonly type: "function";
286
- }, {
287
- readonly inputs: readonly [{
288
- readonly name: "owner";
289
- readonly type: "address";
290
- }, {
291
- readonly name: "spender";
292
- readonly type: "address";
293
- }];
294
- readonly name: "allowance";
295
- readonly outputs: readonly [{
296
- readonly name: "";
297
- readonly type: "uint256";
298
- }];
299
- readonly stateMutability: "view";
300
- readonly type: "function";
301
- }, {
302
- readonly inputs: readonly [{
303
- readonly name: "from";
304
- readonly type: "address";
305
- }, {
306
- readonly name: "to";
307
- readonly type: "address";
308
- }, {
309
- readonly name: "amount";
310
- readonly type: "uint256";
311
- }];
312
- readonly name: "transferFrom";
313
- readonly outputs: readonly [{
314
- readonly name: "";
315
- readonly type: "bool";
316
- }];
317
- readonly stateMutability: "nonpayable";
318
- readonly type: "function";
319
- }, {
320
- readonly inputs: readonly [];
321
- readonly name: "decimals";
322
- readonly outputs: readonly [{
323
- readonly name: "";
324
- readonly type: "uint8";
325
- }];
326
- readonly stateMutability: "view";
327
- readonly type: "function";
328
- }];
329
-
330
- export { AccountAbstraction, type ApprovalSupportResult, type ChainConfig, DEFAULT_ENTRYPOINT, DEFAULT_FACTORY, type GasEstimate, type UserOpReceipt, type UserOperation, entryPointAbi, erc20Abi, factoryAbi, smartAccountAbi };