@1llet.xyz/erc4337-gasless-sdk 0.4.0 → 0.4.2
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/index.d.mts +275 -0
- package/dist/index.d.ts +275 -0
- package/dist/index.js +740 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +714 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +4 -1
- package/src/AccountAbstraction.ts +0 -356
- package/src/BundlerClient.ts +0 -93
- package/src/TokenService.ts +0 -92
- package/src/UserOpBuilder.ts +0 -173
- package/src/chains.ts +0 -54
- package/src/constants.ts +0 -131
- package/src/index.ts +0 -12
- package/src/types.ts +0 -57
- package/tsconfig.json +0 -19
- package/tsup.config.ts +0 -10
package/dist/index.js
ADDED
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AccountAbstraction: () => AccountAbstraction,
|
|
24
|
+
BASE_MAINNET: () => BASE_MAINNET,
|
|
25
|
+
BASE_SEPOLIA: () => BASE_SEPOLIA,
|
|
26
|
+
BundlerClient: () => BundlerClient,
|
|
27
|
+
CHAIN_CONFIGS: () => CHAIN_CONFIGS,
|
|
28
|
+
entryPointAbi: () => entryPointAbi,
|
|
29
|
+
erc20Abi: () => erc20Abi,
|
|
30
|
+
smartAccountAbi: () => smartAccountAbi
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/AccountAbstraction.ts
|
|
35
|
+
var import_viem3 = require("viem");
|
|
36
|
+
|
|
37
|
+
// src/constants.ts
|
|
38
|
+
var factoryAbi = [
|
|
39
|
+
{
|
|
40
|
+
inputs: [
|
|
41
|
+
{ name: "owner", type: "address" },
|
|
42
|
+
{ name: "salt", type: "uint256" }
|
|
43
|
+
],
|
|
44
|
+
name: "getAccountAddress",
|
|
45
|
+
outputs: [{ name: "", type: "address" }],
|
|
46
|
+
stateMutability: "view",
|
|
47
|
+
type: "function"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
inputs: [
|
|
51
|
+
{ name: "owner", type: "address" },
|
|
52
|
+
{ name: "salt", type: "uint256" }
|
|
53
|
+
],
|
|
54
|
+
name: "isAccountDeployed",
|
|
55
|
+
outputs: [{ name: "", type: "bool" }],
|
|
56
|
+
stateMutability: "view",
|
|
57
|
+
type: "function"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
inputs: [
|
|
61
|
+
{ name: "owner", type: "address" },
|
|
62
|
+
{ name: "salt", type: "uint256" }
|
|
63
|
+
],
|
|
64
|
+
name: "createAccount",
|
|
65
|
+
outputs: [{ name: "account", type: "address" }],
|
|
66
|
+
stateMutability: "nonpayable",
|
|
67
|
+
type: "function"
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
var entryPointAbi = [
|
|
71
|
+
{
|
|
72
|
+
inputs: [
|
|
73
|
+
{ name: "sender", type: "address" },
|
|
74
|
+
{ name: "key", type: "uint192" }
|
|
75
|
+
],
|
|
76
|
+
name: "getNonce",
|
|
77
|
+
outputs: [{ name: "nonce", type: "uint256" }],
|
|
78
|
+
stateMutability: "view",
|
|
79
|
+
type: "function"
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
var smartAccountAbi = [
|
|
83
|
+
{
|
|
84
|
+
inputs: [
|
|
85
|
+
{ name: "target", type: "address" },
|
|
86
|
+
{ name: "value", type: "uint256" },
|
|
87
|
+
{ name: "data", type: "bytes" }
|
|
88
|
+
],
|
|
89
|
+
name: "execute",
|
|
90
|
+
outputs: [],
|
|
91
|
+
stateMutability: "nonpayable",
|
|
92
|
+
type: "function"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
inputs: [
|
|
96
|
+
{ name: "targets", type: "address[]" },
|
|
97
|
+
{ name: "values", type: "uint256[]" },
|
|
98
|
+
{ name: "datas", type: "bytes[]" }
|
|
99
|
+
],
|
|
100
|
+
name: "executeBatch",
|
|
101
|
+
outputs: [],
|
|
102
|
+
stateMutability: "nonpayable",
|
|
103
|
+
type: "function"
|
|
104
|
+
}
|
|
105
|
+
];
|
|
106
|
+
var erc20Abi = [
|
|
107
|
+
{
|
|
108
|
+
inputs: [{ name: "account", type: "address" }],
|
|
109
|
+
name: "balanceOf",
|
|
110
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
111
|
+
stateMutability: "view",
|
|
112
|
+
type: "function"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
inputs: [
|
|
116
|
+
{ name: "to", type: "address" },
|
|
117
|
+
{ name: "amount", type: "uint256" }
|
|
118
|
+
],
|
|
119
|
+
name: "transfer",
|
|
120
|
+
outputs: [{ name: "", type: "bool" }],
|
|
121
|
+
stateMutability: "nonpayable",
|
|
122
|
+
type: "function"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
inputs: [
|
|
126
|
+
{ name: "spender", type: "address" },
|
|
127
|
+
{ name: "amount", type: "uint256" }
|
|
128
|
+
],
|
|
129
|
+
name: "approve",
|
|
130
|
+
outputs: [{ name: "", type: "bool" }],
|
|
131
|
+
stateMutability: "nonpayable",
|
|
132
|
+
type: "function"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
inputs: [
|
|
136
|
+
{ name: "owner", type: "address" },
|
|
137
|
+
{ name: "spender", type: "address" }
|
|
138
|
+
],
|
|
139
|
+
name: "allowance",
|
|
140
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
141
|
+
stateMutability: "view",
|
|
142
|
+
type: "function"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
inputs: [
|
|
146
|
+
{ name: "from", type: "address" },
|
|
147
|
+
{ name: "to", type: "address" },
|
|
148
|
+
{ name: "amount", type: "uint256" }
|
|
149
|
+
],
|
|
150
|
+
name: "transferFrom",
|
|
151
|
+
outputs: [{ name: "", type: "bool" }],
|
|
152
|
+
stateMutability: "nonpayable",
|
|
153
|
+
type: "function"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
inputs: [],
|
|
157
|
+
name: "decimals",
|
|
158
|
+
outputs: [{ name: "", type: "uint8" }],
|
|
159
|
+
stateMutability: "view",
|
|
160
|
+
type: "function"
|
|
161
|
+
}
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
// src/BundlerClient.ts
|
|
165
|
+
var BundlerClient = class {
|
|
166
|
+
constructor(config, entryPointAddress) {
|
|
167
|
+
this.bundlerUrl = config.bundlerUrl;
|
|
168
|
+
this.chainId = config.chain.id;
|
|
169
|
+
this.entryPointAddress = entryPointAddress;
|
|
170
|
+
}
|
|
171
|
+
async call(method, params) {
|
|
172
|
+
const response = await fetch(this.bundlerUrl, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "Content-Type": "application/json" },
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
jsonrpc: "2.0",
|
|
177
|
+
id: 1,
|
|
178
|
+
method,
|
|
179
|
+
params
|
|
180
|
+
})
|
|
181
|
+
});
|
|
182
|
+
const result = await response.json();
|
|
183
|
+
if (result.error) {
|
|
184
|
+
throw new Error(result.error.message);
|
|
185
|
+
}
|
|
186
|
+
return result.result;
|
|
187
|
+
}
|
|
188
|
+
async estimateGas(userOp) {
|
|
189
|
+
return await this.call("eth_estimateUserOperationGas", [
|
|
190
|
+
{
|
|
191
|
+
sender: userOp.sender,
|
|
192
|
+
nonce: userOp.nonce ? "0x" + userOp.nonce.toString(16) : "0x0",
|
|
193
|
+
initCode: userOp.initCode || "0x",
|
|
194
|
+
callData: userOp.callData || "0x",
|
|
195
|
+
paymasterAndData: userOp.paymasterAndData || "0x",
|
|
196
|
+
signature: "0x"
|
|
197
|
+
},
|
|
198
|
+
this.entryPointAddress
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
async sendUserOperation(userOp) {
|
|
202
|
+
return await this.call("eth_sendUserOperation", [
|
|
203
|
+
{
|
|
204
|
+
sender: userOp.sender,
|
|
205
|
+
nonce: "0x" + userOp.nonce.toString(16),
|
|
206
|
+
initCode: userOp.initCode,
|
|
207
|
+
callData: userOp.callData,
|
|
208
|
+
callGasLimit: "0x" + userOp.callGasLimit.toString(16),
|
|
209
|
+
verificationGasLimit: "0x" + userOp.verificationGasLimit.toString(16),
|
|
210
|
+
preVerificationGas: "0x" + userOp.preVerificationGas.toString(16),
|
|
211
|
+
maxFeePerGas: "0x" + userOp.maxFeePerGas.toString(16),
|
|
212
|
+
maxPriorityFeePerGas: "0x" + userOp.maxPriorityFeePerGas.toString(16),
|
|
213
|
+
paymasterAndData: userOp.paymasterAndData,
|
|
214
|
+
signature: userOp.signature
|
|
215
|
+
},
|
|
216
|
+
this.entryPointAddress
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
async waitForUserOperation(userOpHash, timeout = 6e4) {
|
|
220
|
+
const startTime = Date.now();
|
|
221
|
+
while (Date.now() - startTime < timeout) {
|
|
222
|
+
const result = await this.call("eth_getUserOperationReceipt", [userOpHash]);
|
|
223
|
+
if (result) {
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
227
|
+
}
|
|
228
|
+
throw new Error("Timeout waiting for UserOperation");
|
|
229
|
+
}
|
|
230
|
+
async requestApprovalSupport(token, owner, spender, amount) {
|
|
231
|
+
return await this.call("pm_requestApprovalSupport", [
|
|
232
|
+
token,
|
|
233
|
+
owner,
|
|
234
|
+
spender,
|
|
235
|
+
amount.toString()
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/TokenService.ts
|
|
241
|
+
var import_viem = require("viem");
|
|
242
|
+
var TokenService = class {
|
|
243
|
+
constructor(chainConfig, publicClient) {
|
|
244
|
+
this.tokens = /* @__PURE__ */ new Map();
|
|
245
|
+
this.publicClient = publicClient;
|
|
246
|
+
chainConfig.tokens.forEach((token) => {
|
|
247
|
+
this.tokens.set(token.symbol.toUpperCase(), token);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Resolve token address from symbol or return address if provided
|
|
252
|
+
*/
|
|
253
|
+
getTokenAddress(token) {
|
|
254
|
+
if (token === "ETH") {
|
|
255
|
+
return "0x0000000000000000000000000000000000000000";
|
|
256
|
+
}
|
|
257
|
+
if (token.startsWith("0x")) return token;
|
|
258
|
+
const info = this.tokens.get(token.toUpperCase());
|
|
259
|
+
if (!info) throw new Error(`Token ${token} not found in chain config`);
|
|
260
|
+
return info.address;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get balance of a token for an account
|
|
264
|
+
*/
|
|
265
|
+
async getBalance(token, account) {
|
|
266
|
+
const address = this.getTokenAddress(token);
|
|
267
|
+
if (address === "0x0000000000000000000000000000000000000000") {
|
|
268
|
+
return await this.publicClient.getBalance({ address: account });
|
|
269
|
+
}
|
|
270
|
+
return await this.publicClient.readContract({
|
|
271
|
+
address,
|
|
272
|
+
abi: erc20Abi,
|
|
273
|
+
functionName: "balanceOf",
|
|
274
|
+
args: [account]
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get allowance (ERC-20 only)
|
|
279
|
+
*/
|
|
280
|
+
async getAllowance(token, owner, spender) {
|
|
281
|
+
const address = this.getTokenAddress(token);
|
|
282
|
+
if (address === "0x0000000000000000000000000000000000000000") {
|
|
283
|
+
return 0n;
|
|
284
|
+
}
|
|
285
|
+
return await this.publicClient.readContract({
|
|
286
|
+
address,
|
|
287
|
+
abi: erc20Abi,
|
|
288
|
+
functionName: "allowance",
|
|
289
|
+
args: [owner, spender]
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Encode transfer data
|
|
294
|
+
*/
|
|
295
|
+
encodeTransfer(recipient, amount) {
|
|
296
|
+
return (0, import_viem.encodeFunctionData)({
|
|
297
|
+
abi: erc20Abi,
|
|
298
|
+
functionName: "transfer",
|
|
299
|
+
args: [recipient, amount]
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Encode approve data
|
|
304
|
+
*/
|
|
305
|
+
encodeApprove(spender, amount) {
|
|
306
|
+
return (0, import_viem.encodeFunctionData)({
|
|
307
|
+
abi: erc20Abi,
|
|
308
|
+
functionName: "approve",
|
|
309
|
+
args: [spender, amount]
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/UserOpBuilder.ts
|
|
315
|
+
var import_viem2 = require("viem");
|
|
316
|
+
var UserOpBuilder = class {
|
|
317
|
+
constructor(chainConfig, bundlerClient, publicClient) {
|
|
318
|
+
this.chainConfig = chainConfig;
|
|
319
|
+
this.bundlerClient = bundlerClient;
|
|
320
|
+
this.publicClient = publicClient;
|
|
321
|
+
this.entryPointAddress = chainConfig.entryPointAddress;
|
|
322
|
+
this.factoryAddress = chainConfig.factoryAddress;
|
|
323
|
+
}
|
|
324
|
+
async getNonce(smartAccountAddress) {
|
|
325
|
+
return await this.publicClient.readContract({
|
|
326
|
+
address: this.entryPointAddress,
|
|
327
|
+
abi: entryPointAbi,
|
|
328
|
+
functionName: "getNonce",
|
|
329
|
+
args: [smartAccountAddress, 0n]
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
buildInitCode(owner) {
|
|
333
|
+
const createAccountData = (0, import_viem2.encodeFunctionData)({
|
|
334
|
+
abi: factoryAbi,
|
|
335
|
+
functionName: "createAccount",
|
|
336
|
+
args: [owner, 0n]
|
|
337
|
+
});
|
|
338
|
+
return `${this.factoryAddress}${createAccountData.slice(2)}`;
|
|
339
|
+
}
|
|
340
|
+
async isAccountDeployed(smartAccountAddress) {
|
|
341
|
+
const code = await this.publicClient.getCode({
|
|
342
|
+
address: smartAccountAddress
|
|
343
|
+
});
|
|
344
|
+
return code !== void 0 && code !== "0x";
|
|
345
|
+
}
|
|
346
|
+
async buildUserOperationBatch(owner, smartAccountAddress, transactions) {
|
|
347
|
+
const isDeployed = await this.isAccountDeployed(smartAccountAddress);
|
|
348
|
+
const initCode = isDeployed ? "0x" : this.buildInitCode(owner);
|
|
349
|
+
const targets = transactions.map((tx) => tx.target);
|
|
350
|
+
const values = transactions.map((tx) => tx.value);
|
|
351
|
+
const datas = transactions.map((tx) => tx.data);
|
|
352
|
+
const callData = (0, import_viem2.encodeFunctionData)({
|
|
353
|
+
abi: smartAccountAbi,
|
|
354
|
+
functionName: "executeBatch",
|
|
355
|
+
args: [targets, values, datas]
|
|
356
|
+
});
|
|
357
|
+
const nonce = await this.getNonce(smartAccountAddress);
|
|
358
|
+
const partialOp = {
|
|
359
|
+
sender: smartAccountAddress,
|
|
360
|
+
nonce,
|
|
361
|
+
initCode,
|
|
362
|
+
callData,
|
|
363
|
+
paymasterAndData: this.chainConfig.paymasterAddress || "0x"
|
|
364
|
+
};
|
|
365
|
+
const gasEstimate = await this.bundlerClient.estimateGas(partialOp);
|
|
366
|
+
return {
|
|
367
|
+
...partialOp,
|
|
368
|
+
callGasLimit: BigInt(gasEstimate.callGasLimit),
|
|
369
|
+
verificationGasLimit: BigInt(gasEstimate.verificationGasLimit),
|
|
370
|
+
preVerificationGas: BigInt(gasEstimate.preVerificationGas),
|
|
371
|
+
maxFeePerGas: BigInt(gasEstimate.maxFeePerGas),
|
|
372
|
+
maxPriorityFeePerGas: BigInt(gasEstimate.maxPriorityFeePerGas),
|
|
373
|
+
signature: "0x"
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
async buildDeployUserOp(owner, smartAccountAddress) {
|
|
377
|
+
const isDeployed = await this.isAccountDeployed(smartAccountAddress);
|
|
378
|
+
if (isDeployed) throw new Error("Account already deployed");
|
|
379
|
+
const initCode = this.buildInitCode(owner);
|
|
380
|
+
const callData = "0x";
|
|
381
|
+
const nonce = await this.getNonce(smartAccountAddress);
|
|
382
|
+
const partialOp = {
|
|
383
|
+
sender: smartAccountAddress,
|
|
384
|
+
nonce,
|
|
385
|
+
initCode,
|
|
386
|
+
callData,
|
|
387
|
+
paymasterAndData: this.chainConfig.paymasterAddress || "0x"
|
|
388
|
+
};
|
|
389
|
+
const gasEstimate = await this.bundlerClient.estimateGas(partialOp);
|
|
390
|
+
return {
|
|
391
|
+
...partialOp,
|
|
392
|
+
callGasLimit: BigInt(gasEstimate.callGasLimit),
|
|
393
|
+
verificationGasLimit: BigInt(gasEstimate.verificationGasLimit),
|
|
394
|
+
preVerificationGas: BigInt(gasEstimate.preVerificationGas),
|
|
395
|
+
maxFeePerGas: BigInt(gasEstimate.maxFeePerGas),
|
|
396
|
+
maxPriorityFeePerGas: BigInt(gasEstimate.maxPriorityFeePerGas),
|
|
397
|
+
signature: "0x"
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
getUserOpHash(userOp) {
|
|
401
|
+
const packed = (0, import_viem2.encodeAbiParameters)(
|
|
402
|
+
[
|
|
403
|
+
{ type: "address" },
|
|
404
|
+
{ type: "uint256" },
|
|
405
|
+
{ type: "bytes32" },
|
|
406
|
+
{ type: "bytes32" },
|
|
407
|
+
{ type: "uint256" },
|
|
408
|
+
{ type: "uint256" },
|
|
409
|
+
{ type: "uint256" },
|
|
410
|
+
{ type: "uint256" },
|
|
411
|
+
{ type: "uint256" },
|
|
412
|
+
{ type: "bytes32" }
|
|
413
|
+
],
|
|
414
|
+
[
|
|
415
|
+
userOp.sender,
|
|
416
|
+
userOp.nonce,
|
|
417
|
+
(0, import_viem2.keccak256)(userOp.initCode),
|
|
418
|
+
(0, import_viem2.keccak256)(userOp.callData),
|
|
419
|
+
userOp.callGasLimit,
|
|
420
|
+
userOp.verificationGasLimit,
|
|
421
|
+
userOp.preVerificationGas,
|
|
422
|
+
userOp.maxFeePerGas,
|
|
423
|
+
userOp.maxPriorityFeePerGas,
|
|
424
|
+
(0, import_viem2.keccak256)(userOp.paymasterAndData)
|
|
425
|
+
]
|
|
426
|
+
);
|
|
427
|
+
const packedHash = (0, import_viem2.keccak256)(packed);
|
|
428
|
+
return (0, import_viem2.keccak256)(
|
|
429
|
+
(0, import_viem2.encodeAbiParameters)(
|
|
430
|
+
[{ type: "bytes32" }, { type: "address" }, { type: "uint256" }],
|
|
431
|
+
[packedHash, this.entryPointAddress, BigInt(this.chainConfig.chain.id)]
|
|
432
|
+
)
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/AccountAbstraction.ts
|
|
438
|
+
var AccountAbstraction = class {
|
|
439
|
+
constructor(chainConfig) {
|
|
440
|
+
this.owner = null;
|
|
441
|
+
this.smartAccountAddress = null;
|
|
442
|
+
this.chainConfig = chainConfig;
|
|
443
|
+
if (!chainConfig.entryPointAddress) throw new Error("EntryPoint address required");
|
|
444
|
+
this.entryPointAddress = chainConfig.entryPointAddress;
|
|
445
|
+
if (!chainConfig.factoryAddress) throw new Error("Factory address required");
|
|
446
|
+
this.factoryAddress = chainConfig.factoryAddress;
|
|
447
|
+
const rpcUrl = chainConfig.rpcUrl || chainConfig.chain.rpcUrls.default.http[0];
|
|
448
|
+
this.publicClient = (0, import_viem3.createPublicClient)({
|
|
449
|
+
chain: chainConfig.chain,
|
|
450
|
+
transport: (0, import_viem3.http)(rpcUrl)
|
|
451
|
+
});
|
|
452
|
+
this.bundlerClient = new BundlerClient(chainConfig, this.entryPointAddress);
|
|
453
|
+
this.tokenService = new TokenService(chainConfig, this.publicClient);
|
|
454
|
+
this.userOpBuilder = new UserOpBuilder(chainConfig, this.bundlerClient, this.publicClient);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Connect to MetaMask and get the owner address
|
|
458
|
+
*/
|
|
459
|
+
async connect() {
|
|
460
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
461
|
+
throw new Error("MetaMask is not installed");
|
|
462
|
+
}
|
|
463
|
+
const accounts = await window.ethereum.request({
|
|
464
|
+
method: "eth_requestAccounts"
|
|
465
|
+
});
|
|
466
|
+
if (!accounts || accounts.length === 0) throw new Error("No accounts found");
|
|
467
|
+
const chainId = await window.ethereum.request({
|
|
468
|
+
method: "eth_chainId"
|
|
469
|
+
});
|
|
470
|
+
const targetChainId = this.chainConfig.chain.id;
|
|
471
|
+
if (parseInt(chainId, 16) !== targetChainId) {
|
|
472
|
+
try {
|
|
473
|
+
await window.ethereum.request({
|
|
474
|
+
method: "wallet_switchEthereumChain",
|
|
475
|
+
params: [{ chainId: "0x" + targetChainId.toString(16) }]
|
|
476
|
+
});
|
|
477
|
+
} catch (switchError) {
|
|
478
|
+
const error = switchError;
|
|
479
|
+
if (error.code === 4902) {
|
|
480
|
+
await window.ethereum.request({
|
|
481
|
+
method: "wallet_addEthereumChain",
|
|
482
|
+
params: [
|
|
483
|
+
{
|
|
484
|
+
chainId: "0x" + targetChainId.toString(16),
|
|
485
|
+
chainName: this.chainConfig.chain.name,
|
|
486
|
+
nativeCurrency: this.chainConfig.chain.nativeCurrency,
|
|
487
|
+
rpcUrls: [this.chainConfig.rpcUrl || this.chainConfig.chain.rpcUrls.default.http[0]],
|
|
488
|
+
blockExplorerUrls: this.chainConfig.chain.blockExplorers?.default?.url ? [this.chainConfig.chain.blockExplorers.default.url] : []
|
|
489
|
+
}
|
|
490
|
+
]
|
|
491
|
+
});
|
|
492
|
+
} else {
|
|
493
|
+
throw switchError;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
this.owner = accounts[0];
|
|
498
|
+
this.smartAccountAddress = await this.getSmartAccountAddress(this.owner);
|
|
499
|
+
return {
|
|
500
|
+
owner: this.owner,
|
|
501
|
+
smartAccount: this.smartAccountAddress
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Get the Smart Account address for an owner
|
|
506
|
+
*/
|
|
507
|
+
async getSmartAccountAddress(owner) {
|
|
508
|
+
const address = await this.publicClient.readContract({
|
|
509
|
+
address: this.factoryAddress,
|
|
510
|
+
abi: factoryAbi,
|
|
511
|
+
functionName: "getAccountAddress",
|
|
512
|
+
args: [owner, 0n]
|
|
513
|
+
});
|
|
514
|
+
return address;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Check if the Smart Account is deployed
|
|
518
|
+
*/
|
|
519
|
+
async isAccountDeployed() {
|
|
520
|
+
if (!this.smartAccountAddress) throw new Error("Not connected");
|
|
521
|
+
return this.userOpBuilder.isAccountDeployed(this.smartAccountAddress);
|
|
522
|
+
}
|
|
523
|
+
// --- Token Methods (Delegated) ---
|
|
524
|
+
getTokenAddress(token) {
|
|
525
|
+
return this.tokenService.getTokenAddress(token);
|
|
526
|
+
}
|
|
527
|
+
async getUsdcBalance() {
|
|
528
|
+
if (!this.smartAccountAddress) throw new Error("Not connected");
|
|
529
|
+
return this.tokenService.getBalance("USDC", this.smartAccountAddress);
|
|
530
|
+
}
|
|
531
|
+
async getEoaUsdcBalance() {
|
|
532
|
+
if (!this.owner) throw new Error("Not connected");
|
|
533
|
+
return this.tokenService.getBalance("USDC", this.owner);
|
|
534
|
+
}
|
|
535
|
+
async getAllowance() {
|
|
536
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
537
|
+
return this.tokenService.getAllowance("USDC", this.owner, this.smartAccountAddress);
|
|
538
|
+
}
|
|
539
|
+
// --- Transactions ---
|
|
540
|
+
async deployAccount() {
|
|
541
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
542
|
+
try {
|
|
543
|
+
const userOp = await this.userOpBuilder.buildDeployUserOp(this.owner, this.smartAccountAddress);
|
|
544
|
+
const signed = await this.signUserOperation(userOp);
|
|
545
|
+
const hash = await this.sendUserOperation(signed);
|
|
546
|
+
return await this.waitForUserOperation(hash);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
throw this.decodeError(error);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async sendTransaction(tx) {
|
|
552
|
+
return this.sendBatchTransaction([tx]);
|
|
553
|
+
}
|
|
554
|
+
async sendBatchTransaction(txs) {
|
|
555
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
556
|
+
const transactions = txs.map((tx) => ({
|
|
557
|
+
target: tx.target,
|
|
558
|
+
value: tx.value ?? 0n,
|
|
559
|
+
data: tx.data ?? "0x"
|
|
560
|
+
}));
|
|
561
|
+
try {
|
|
562
|
+
const userOp = await this.userOpBuilder.buildUserOperationBatch(
|
|
563
|
+
this.owner,
|
|
564
|
+
this.smartAccountAddress,
|
|
565
|
+
transactions
|
|
566
|
+
);
|
|
567
|
+
const signed = await this.signUserOperation(userOp);
|
|
568
|
+
const hash = await this.sendUserOperation(signed);
|
|
569
|
+
return await this.waitForUserOperation(hash);
|
|
570
|
+
} catch (error) {
|
|
571
|
+
throw this.decodeError(error);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async deposit(amount) {
|
|
575
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
576
|
+
const txHash = await window.ethereum.request({
|
|
577
|
+
method: "eth_sendTransaction",
|
|
578
|
+
params: [{
|
|
579
|
+
from: this.owner,
|
|
580
|
+
to: this.smartAccountAddress,
|
|
581
|
+
value: "0x" + amount.toString(16)
|
|
582
|
+
}]
|
|
583
|
+
});
|
|
584
|
+
return txHash;
|
|
585
|
+
}
|
|
586
|
+
async transfer(token, recipient, amount) {
|
|
587
|
+
const tokenAddress = this.getTokenAddress(token);
|
|
588
|
+
if (tokenAddress === "0x0000000000000000000000000000000000000000") {
|
|
589
|
+
return this.sendTransaction({
|
|
590
|
+
target: recipient,
|
|
591
|
+
value: amount,
|
|
592
|
+
data: "0x"
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
const data = this.tokenService.encodeTransfer(recipient, amount);
|
|
596
|
+
return this.sendTransaction({
|
|
597
|
+
target: tokenAddress,
|
|
598
|
+
value: 0n,
|
|
599
|
+
data
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Approve a token for the Smart Account
|
|
604
|
+
*/
|
|
605
|
+
async approveToken(token, spender, amount = 115792089237316195423570985008687907853269984665640564039457584007913129639935n) {
|
|
606
|
+
if (!this.owner) throw new Error("Not connected");
|
|
607
|
+
const support = await this.requestApprovalSupport(token, spender, amount);
|
|
608
|
+
if (support.type === "approve") {
|
|
609
|
+
const data = this.tokenService.encodeApprove(spender, amount);
|
|
610
|
+
const txHash = await window.ethereum.request({
|
|
611
|
+
method: "eth_sendTransaction",
|
|
612
|
+
params: [{
|
|
613
|
+
from: this.owner,
|
|
614
|
+
to: token,
|
|
615
|
+
data
|
|
616
|
+
}]
|
|
617
|
+
});
|
|
618
|
+
return txHash;
|
|
619
|
+
}
|
|
620
|
+
if (support.type === "permit") throw new Error("Permit not yet supported");
|
|
621
|
+
return "NOT_NEEDED";
|
|
622
|
+
}
|
|
623
|
+
// --- Core Bridge to Bundler/UserOp ---
|
|
624
|
+
// Deprecated/Legacy but kept for compatibility or advanced usage?
|
|
625
|
+
// buildUserOperationBatch moved to internal usage mostly, but maybe exposed?
|
|
626
|
+
// If I remove them from public API, that is a BREAKING change if user used them.
|
|
627
|
+
// User requested "modularize", but usually expects same public API.
|
|
628
|
+
// I will expose them as simple delegates if needed, or assume they primarily use sendBatchTransaction.
|
|
629
|
+
// The previous implementation exposed `buildUserOperationBatch`.
|
|
630
|
+
async buildUserOperationBatch(transactions) {
|
|
631
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
632
|
+
return this.userOpBuilder.buildUserOperationBatch(this.owner, this.smartAccountAddress, transactions);
|
|
633
|
+
}
|
|
634
|
+
async signUserOperation(userOp) {
|
|
635
|
+
if (!this.owner) throw new Error("Not connected");
|
|
636
|
+
const userOpHash = this.userOpBuilder.getUserOpHash(userOp);
|
|
637
|
+
const signature = await window.ethereum.request({
|
|
638
|
+
method: "personal_sign",
|
|
639
|
+
params: [userOpHash, this.owner]
|
|
640
|
+
});
|
|
641
|
+
return { ...userOp, signature };
|
|
642
|
+
}
|
|
643
|
+
async sendUserOperation(userOp) {
|
|
644
|
+
return this.bundlerClient.sendUserOperation(userOp);
|
|
645
|
+
}
|
|
646
|
+
async waitForUserOperation(hash, timeout = 6e4) {
|
|
647
|
+
return this.bundlerClient.waitForUserOperation(hash, timeout);
|
|
648
|
+
}
|
|
649
|
+
// Internal but exposed via BundlerClient originally
|
|
650
|
+
async requestApprovalSupport(token, spender, amount) {
|
|
651
|
+
if (!this.owner) throw new Error("Not connected");
|
|
652
|
+
return this.bundlerClient.requestApprovalSupport(token, this.owner, spender, amount);
|
|
653
|
+
}
|
|
654
|
+
// Error Decoding (Private)
|
|
655
|
+
decodeError(error) {
|
|
656
|
+
const msg = error?.message || "";
|
|
657
|
+
const hexMatch = msg.match(/(0x[0-9a-fA-F]+)/);
|
|
658
|
+
if (hexMatch) {
|
|
659
|
+
try {
|
|
660
|
+
const decoded = (0, import_viem3.decodeErrorResult)({
|
|
661
|
+
abi: [{ inputs: [{ name: "message", type: "string" }], name: "Error", type: "error" }],
|
|
662
|
+
data: hexMatch[0]
|
|
663
|
+
});
|
|
664
|
+
if (decoded.errorName === "Error") return new Error(`Smart Account Error: ${decoded.args[0]}`);
|
|
665
|
+
} catch (e) {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (msg.includes("AA21")) return new Error("Smart Account: Native transfer failed (ETH missing?)");
|
|
669
|
+
if (msg.includes("AA25")) return new Error("Smart Account: Invalid account nonce");
|
|
670
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
671
|
+
}
|
|
672
|
+
// Getters
|
|
673
|
+
getOwner() {
|
|
674
|
+
return this.owner;
|
|
675
|
+
}
|
|
676
|
+
getSmartAccount() {
|
|
677
|
+
return this.smartAccountAddress;
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/chains.ts
|
|
682
|
+
var import_chains = require("viem/chains");
|
|
683
|
+
var BASE_MAINNET = {
|
|
684
|
+
chain: import_chains.base,
|
|
685
|
+
bundlerUrl: "http://localhost:3000/rpc?chain=base",
|
|
686
|
+
// Default to local bundler pattern
|
|
687
|
+
// Addresses
|
|
688
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
689
|
+
factoryAddress: "0xe2584152891E4769025807DEa0cD611F135aDC68",
|
|
690
|
+
paymasterAddress: "0x1e13Eb16C565E3f3FDe49A011755e50410bb1F95",
|
|
691
|
+
tokens: [
|
|
692
|
+
{
|
|
693
|
+
symbol: "USDC",
|
|
694
|
+
decimals: 6,
|
|
695
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
symbol: "ETH",
|
|
699
|
+
decimals: 18,
|
|
700
|
+
address: "0x0000000000000000000000000000000000000000"
|
|
701
|
+
}
|
|
702
|
+
]
|
|
703
|
+
};
|
|
704
|
+
var BASE_SEPOLIA = {
|
|
705
|
+
chain: import_chains.baseSepolia,
|
|
706
|
+
bundlerUrl: "http://localhost:3000/rpc?chain=baseSepolia",
|
|
707
|
+
// Default to local bundler pattern
|
|
708
|
+
// Addresses
|
|
709
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
710
|
+
factoryAddress: "0x9406Cc6185a346906296840746125a0E44976454",
|
|
711
|
+
// Paymaster not configured in deployments.ts for Sepolia?
|
|
712
|
+
tokens: [
|
|
713
|
+
{
|
|
714
|
+
symbol: "USDC",
|
|
715
|
+
decimals: 6,
|
|
716
|
+
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
symbol: "ETH",
|
|
720
|
+
decimals: 18,
|
|
721
|
+
address: "0x0000000000000000000000000000000000000000"
|
|
722
|
+
}
|
|
723
|
+
]
|
|
724
|
+
};
|
|
725
|
+
var CHAIN_CONFIGS = {
|
|
726
|
+
[import_chains.base.id]: BASE_MAINNET,
|
|
727
|
+
[import_chains.baseSepolia.id]: BASE_SEPOLIA
|
|
728
|
+
};
|
|
729
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
730
|
+
0 && (module.exports = {
|
|
731
|
+
AccountAbstraction,
|
|
732
|
+
BASE_MAINNET,
|
|
733
|
+
BASE_SEPOLIA,
|
|
734
|
+
BundlerClient,
|
|
735
|
+
CHAIN_CONFIGS,
|
|
736
|
+
entryPointAbi,
|
|
737
|
+
erc20Abi,
|
|
738
|
+
smartAccountAbi
|
|
739
|
+
});
|
|
740
|
+
//# sourceMappingURL=index.js.map
|