@1llet.xyz/erc4337-gasless-sdk 0.4.72 → 0.4.73
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 +8 -13
- package/dist/index.d.ts +8 -13
- package/dist/index.js +1130 -1213
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1121 -1212
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { parseAbi, createPublicClient, http, encodeFunctionData, createWalletClient, decodeErrorResult, getAddress, parseUnits, formatUnits, erc20Abi as erc20Abi$1, encodeAbiParameters, keccak256, maxUint256, padHex, toHex } from 'viem';
|
|
2
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
3
1
|
import { monad, unichain, arbitrum, polygon, bsc, avalanche, optimism, gnosis, baseSepolia, base, optimismSepolia, mainnet, worldchain } from 'viem/chains';
|
|
4
2
|
import * as StellarSdk from 'stellar-sdk';
|
|
5
3
|
import { Networks } from 'stellar-sdk';
|
|
6
4
|
import axios from 'axios';
|
|
5
|
+
import { parseAbi, createPublicClient, http, encodeFunctionData, parseUnits, formatUnits, createWalletClient, decodeErrorResult, encodeAbiParameters, keccak256, maxUint256, padHex, getAddress, erc20Abi as erc20Abi$1 } from 'viem';
|
|
6
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
7
7
|
import { OpenAPI, QuoteRequest, OneClickService } from '@defuse-protocol/one-click-sdk-typescript';
|
|
8
|
+
import { c32addressDecode } from 'c32check';
|
|
8
9
|
|
|
9
10
|
var __defProp = Object.defineProperty;
|
|
10
11
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -16,858 +17,6 @@ var __export = (target, all) => {
|
|
|
16
17
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
18
|
};
|
|
18
19
|
|
|
19
|
-
// src/constants.ts
|
|
20
|
-
var factoryAbi, entryPointAbi, smartAccountAbi, erc20Abi;
|
|
21
|
-
var init_constants = __esm({
|
|
22
|
-
"src/constants.ts"() {
|
|
23
|
-
factoryAbi = [
|
|
24
|
-
{
|
|
25
|
-
inputs: [
|
|
26
|
-
{ name: "owner", type: "address" },
|
|
27
|
-
{ name: "salt", type: "uint256" }
|
|
28
|
-
],
|
|
29
|
-
name: "getAccountAddress",
|
|
30
|
-
outputs: [{ name: "", type: "address" }],
|
|
31
|
-
stateMutability: "view",
|
|
32
|
-
type: "function"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
inputs: [
|
|
36
|
-
{ name: "owner", type: "address" },
|
|
37
|
-
{ name: "salt", type: "uint256" }
|
|
38
|
-
],
|
|
39
|
-
name: "isAccountDeployed",
|
|
40
|
-
outputs: [{ name: "", type: "bool" }],
|
|
41
|
-
stateMutability: "view",
|
|
42
|
-
type: "function"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
inputs: [
|
|
46
|
-
{ name: "owner", type: "address" },
|
|
47
|
-
{ name: "salt", type: "uint256" }
|
|
48
|
-
],
|
|
49
|
-
name: "createAccount",
|
|
50
|
-
outputs: [{ name: "account", type: "address" }],
|
|
51
|
-
stateMutability: "nonpayable",
|
|
52
|
-
type: "function"
|
|
53
|
-
}
|
|
54
|
-
];
|
|
55
|
-
entryPointAbi = [
|
|
56
|
-
{
|
|
57
|
-
inputs: [
|
|
58
|
-
{ name: "sender", type: "address" },
|
|
59
|
-
{ name: "key", type: "uint192" }
|
|
60
|
-
],
|
|
61
|
-
name: "getNonce",
|
|
62
|
-
outputs: [{ name: "nonce", type: "uint256" }],
|
|
63
|
-
stateMutability: "view",
|
|
64
|
-
type: "function"
|
|
65
|
-
}
|
|
66
|
-
];
|
|
67
|
-
smartAccountAbi = [
|
|
68
|
-
{
|
|
69
|
-
inputs: [
|
|
70
|
-
{ name: "target", type: "address" },
|
|
71
|
-
{ name: "value", type: "uint256" },
|
|
72
|
-
{ name: "data", type: "bytes" }
|
|
73
|
-
],
|
|
74
|
-
name: "execute",
|
|
75
|
-
outputs: [],
|
|
76
|
-
stateMutability: "nonpayable",
|
|
77
|
-
type: "function"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
inputs: [
|
|
81
|
-
{ name: "targets", type: "address[]" },
|
|
82
|
-
{ name: "values", type: "uint256[]" },
|
|
83
|
-
{ name: "datas", type: "bytes[]" }
|
|
84
|
-
],
|
|
85
|
-
name: "executeBatch",
|
|
86
|
-
outputs: [],
|
|
87
|
-
stateMutability: "nonpayable",
|
|
88
|
-
type: "function"
|
|
89
|
-
}
|
|
90
|
-
];
|
|
91
|
-
erc20Abi = [
|
|
92
|
-
{
|
|
93
|
-
inputs: [{ name: "account", type: "address" }],
|
|
94
|
-
name: "balanceOf",
|
|
95
|
-
outputs: [{ name: "", type: "uint256" }],
|
|
96
|
-
stateMutability: "view",
|
|
97
|
-
type: "function"
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
inputs: [
|
|
101
|
-
{ name: "to", type: "address" },
|
|
102
|
-
{ name: "amount", type: "uint256" }
|
|
103
|
-
],
|
|
104
|
-
name: "transfer",
|
|
105
|
-
outputs: [{ name: "", type: "bool" }],
|
|
106
|
-
stateMutability: "nonpayable",
|
|
107
|
-
type: "function"
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
inputs: [
|
|
111
|
-
{ name: "spender", type: "address" },
|
|
112
|
-
{ name: "amount", type: "uint256" }
|
|
113
|
-
],
|
|
114
|
-
name: "approve",
|
|
115
|
-
outputs: [{ name: "", type: "bool" }],
|
|
116
|
-
stateMutability: "nonpayable",
|
|
117
|
-
type: "function"
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
inputs: [
|
|
121
|
-
{ name: "owner", type: "address" },
|
|
122
|
-
{ name: "spender", type: "address" }
|
|
123
|
-
],
|
|
124
|
-
name: "allowance",
|
|
125
|
-
outputs: [{ name: "", type: "uint256" }],
|
|
126
|
-
stateMutability: "view",
|
|
127
|
-
type: "function"
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
inputs: [
|
|
131
|
-
{ name: "from", type: "address" },
|
|
132
|
-
{ name: "to", type: "address" },
|
|
133
|
-
{ name: "amount", type: "uint256" }
|
|
134
|
-
],
|
|
135
|
-
name: "transferFrom",
|
|
136
|
-
outputs: [{ name: "", type: "bool" }],
|
|
137
|
-
stateMutability: "nonpayable",
|
|
138
|
-
type: "function"
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
inputs: [],
|
|
142
|
-
name: "decimals",
|
|
143
|
-
outputs: [{ name: "", type: "uint8" }],
|
|
144
|
-
stateMutability: "view",
|
|
145
|
-
type: "function"
|
|
146
|
-
}
|
|
147
|
-
];
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// src/BundlerClient.ts
|
|
152
|
-
var BundlerClient;
|
|
153
|
-
var init_BundlerClient = __esm({
|
|
154
|
-
"src/BundlerClient.ts"() {
|
|
155
|
-
BundlerClient = class {
|
|
156
|
-
constructor(config, entryPointAddress) {
|
|
157
|
-
this.bundlerUrl = config.bundlerUrl;
|
|
158
|
-
this.entryPointAddress = entryPointAddress;
|
|
159
|
-
}
|
|
160
|
-
async call(method, params) {
|
|
161
|
-
const response = await fetch(this.bundlerUrl, {
|
|
162
|
-
method: "POST",
|
|
163
|
-
headers: { "Content-Type": "application/json" },
|
|
164
|
-
body: JSON.stringify({
|
|
165
|
-
jsonrpc: "2.0",
|
|
166
|
-
id: 1,
|
|
167
|
-
method,
|
|
168
|
-
params
|
|
169
|
-
})
|
|
170
|
-
});
|
|
171
|
-
const result = await response.json();
|
|
172
|
-
if (result.error) {
|
|
173
|
-
throw new Error(result.error.message);
|
|
174
|
-
}
|
|
175
|
-
return result.result;
|
|
176
|
-
}
|
|
177
|
-
async estimateGas(userOp) {
|
|
178
|
-
const result = await this.call("eth_estimateUserOperationGas", [
|
|
179
|
-
{
|
|
180
|
-
sender: userOp.sender,
|
|
181
|
-
nonce: userOp.nonce ? "0x" + userOp.nonce.toString(16) : "0x0",
|
|
182
|
-
initCode: userOp.initCode || "0x",
|
|
183
|
-
callData: userOp.callData || "0x",
|
|
184
|
-
paymasterAndData: userOp.paymasterAndData || "0x",
|
|
185
|
-
signature: "0x"
|
|
186
|
-
},
|
|
187
|
-
this.entryPointAddress
|
|
188
|
-
]);
|
|
189
|
-
console.log("DEBUG: estimateGas result:", result);
|
|
190
|
-
return {
|
|
191
|
-
callGasLimit: result.callGasLimit,
|
|
192
|
-
verificationGasLimit: result.verificationGasLimit,
|
|
193
|
-
preVerificationGas: result.preVerificationGas,
|
|
194
|
-
maxFeePerGas: result.maxFeePerGas,
|
|
195
|
-
maxPriorityFeePerGas: result.maxPriorityFeePerGas,
|
|
196
|
-
paymasterAndData: result.paymasterAndData
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
async sendUserOperation(userOp) {
|
|
200
|
-
return await this.call("eth_sendUserOperation", [
|
|
201
|
-
{
|
|
202
|
-
sender: userOp.sender,
|
|
203
|
-
nonce: "0x" + userOp.nonce.toString(16),
|
|
204
|
-
initCode: userOp.initCode,
|
|
205
|
-
callData: userOp.callData,
|
|
206
|
-
callGasLimit: "0x" + userOp.callGasLimit.toString(16),
|
|
207
|
-
verificationGasLimit: "0x" + userOp.verificationGasLimit.toString(16),
|
|
208
|
-
preVerificationGas: "0x" + userOp.preVerificationGas.toString(16),
|
|
209
|
-
maxFeePerGas: "0x" + userOp.maxFeePerGas.toString(16),
|
|
210
|
-
maxPriorityFeePerGas: "0x" + userOp.maxPriorityFeePerGas.toString(16),
|
|
211
|
-
paymasterAndData: userOp.paymasterAndData,
|
|
212
|
-
signature: userOp.signature
|
|
213
|
-
},
|
|
214
|
-
this.entryPointAddress
|
|
215
|
-
]);
|
|
216
|
-
}
|
|
217
|
-
async waitForUserOperation(userOpHash, timeout = 6e4) {
|
|
218
|
-
const startTime = Date.now();
|
|
219
|
-
while (Date.now() - startTime < timeout) {
|
|
220
|
-
const result = await this.call("eth_getUserOperationReceipt", [userOpHash]);
|
|
221
|
-
if (result) {
|
|
222
|
-
return result;
|
|
223
|
-
}
|
|
224
|
-
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
225
|
-
}
|
|
226
|
-
throw new Error("Timeout waiting for UserOperation");
|
|
227
|
-
}
|
|
228
|
-
async requestApprovalSupport(token, owner, spender, amount) {
|
|
229
|
-
return await this.call("pm_requestApprovalSupport", [
|
|
230
|
-
token,
|
|
231
|
-
owner,
|
|
232
|
-
spender,
|
|
233
|
-
amount.toString()
|
|
234
|
-
]);
|
|
235
|
-
}
|
|
236
|
-
async scheduleUserOp(userOp, condition) {
|
|
237
|
-
return await this.call("bundler_scheduleUserOp", [
|
|
238
|
-
{
|
|
239
|
-
sender: userOp.sender,
|
|
240
|
-
nonce: "0x" + userOp.nonce.toString(16),
|
|
241
|
-
initCode: userOp.initCode,
|
|
242
|
-
callData: userOp.callData,
|
|
243
|
-
callGasLimit: "0x" + userOp.callGasLimit.toString(16),
|
|
244
|
-
verificationGasLimit: "0x" + userOp.verificationGasLimit.toString(16),
|
|
245
|
-
preVerificationGas: "0x" + userOp.preVerificationGas.toString(16),
|
|
246
|
-
maxFeePerGas: "0x" + userOp.maxFeePerGas.toString(16),
|
|
247
|
-
maxPriorityFeePerGas: "0x" + userOp.maxPriorityFeePerGas.toString(16),
|
|
248
|
-
paymasterAndData: userOp.paymasterAndData,
|
|
249
|
-
signature: userOp.signature
|
|
250
|
-
},
|
|
251
|
-
condition
|
|
252
|
-
]);
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
var TokenService;
|
|
258
|
-
var init_TokenService = __esm({
|
|
259
|
-
"src/TokenService.ts"() {
|
|
260
|
-
init_constants();
|
|
261
|
-
TokenService = class {
|
|
262
|
-
constructor(chainConfig, publicClient) {
|
|
263
|
-
this.tokens = /* @__PURE__ */ new Map();
|
|
264
|
-
this.publicClient = publicClient;
|
|
265
|
-
chainConfig.tokens.forEach((token) => {
|
|
266
|
-
this.tokens.set(token.symbol.toUpperCase(), token);
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Resolve token address from symbol or return address if provided
|
|
271
|
-
*/
|
|
272
|
-
getTokenAddress(token) {
|
|
273
|
-
if (token.startsWith("0x")) return token;
|
|
274
|
-
const info = this.tokens.get(token.toUpperCase());
|
|
275
|
-
if (!info) throw new Error(`Token ${token} not found in chain config`);
|
|
276
|
-
return info.address;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Get balance of a token for an account
|
|
280
|
-
*/
|
|
281
|
-
async getBalance(token, account) {
|
|
282
|
-
const address = this.getTokenAddress(token);
|
|
283
|
-
if (address === "0x0000000000000000000000000000000000000000") {
|
|
284
|
-
return await this.publicClient.getBalance({ address: account });
|
|
285
|
-
}
|
|
286
|
-
return await this.publicClient.readContract({
|
|
287
|
-
address,
|
|
288
|
-
abi: erc20Abi,
|
|
289
|
-
functionName: "balanceOf",
|
|
290
|
-
args: [account]
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Get allowance (ERC-20 only)
|
|
295
|
-
*/
|
|
296
|
-
async getAllowance(token, owner, spender) {
|
|
297
|
-
const address = this.getTokenAddress(token);
|
|
298
|
-
if (address === "0x0000000000000000000000000000000000000000") {
|
|
299
|
-
return 0n;
|
|
300
|
-
}
|
|
301
|
-
return await this.publicClient.readContract({
|
|
302
|
-
address,
|
|
303
|
-
abi: erc20Abi,
|
|
304
|
-
functionName: "allowance",
|
|
305
|
-
args: [owner, spender]
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Encode transfer data
|
|
310
|
-
*/
|
|
311
|
-
encodeTransfer(recipient, amount) {
|
|
312
|
-
return encodeFunctionData({
|
|
313
|
-
abi: erc20Abi,
|
|
314
|
-
functionName: "transfer",
|
|
315
|
-
args: [recipient, amount]
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Encode transferFrom data
|
|
320
|
-
*/
|
|
321
|
-
encodeTransferFrom(sender, recipient, amount) {
|
|
322
|
-
return encodeFunctionData({
|
|
323
|
-
abi: erc20Abi,
|
|
324
|
-
functionName: "transferFrom",
|
|
325
|
-
args: [sender, recipient, amount]
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Encode approve data
|
|
330
|
-
*/
|
|
331
|
-
encodeApprove(spender, amount) {
|
|
332
|
-
return encodeFunctionData({
|
|
333
|
-
abi: erc20Abi,
|
|
334
|
-
functionName: "approve",
|
|
335
|
-
args: [spender, amount]
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
var UserOpBuilder;
|
|
342
|
-
var init_UserOpBuilder = __esm({
|
|
343
|
-
"src/UserOpBuilder.ts"() {
|
|
344
|
-
init_constants();
|
|
345
|
-
UserOpBuilder = class {
|
|
346
|
-
constructor(chainConfig, bundlerClient, publicClient) {
|
|
347
|
-
this.chainConfig = chainConfig;
|
|
348
|
-
this.bundlerClient = bundlerClient;
|
|
349
|
-
this.publicClient = publicClient;
|
|
350
|
-
this.entryPointAddress = chainConfig.entryPointAddress;
|
|
351
|
-
this.factoryAddress = chainConfig.factoryAddress;
|
|
352
|
-
}
|
|
353
|
-
async getNonce(smartAccountAddress) {
|
|
354
|
-
return await this.publicClient.readContract({
|
|
355
|
-
address: this.entryPointAddress,
|
|
356
|
-
abi: entryPointAbi,
|
|
357
|
-
functionName: "getNonce",
|
|
358
|
-
args: [smartAccountAddress, 0n]
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
buildInitCode(owner) {
|
|
362
|
-
const createAccountData = encodeFunctionData({
|
|
363
|
-
abi: factoryAbi,
|
|
364
|
-
functionName: "createAccount",
|
|
365
|
-
args: [owner, 0n]
|
|
366
|
-
});
|
|
367
|
-
return `${this.factoryAddress}${createAccountData.slice(2)}`;
|
|
368
|
-
}
|
|
369
|
-
async isAccountDeployed(smartAccountAddress) {
|
|
370
|
-
const code = await this.publicClient.getCode({
|
|
371
|
-
address: smartAccountAddress
|
|
372
|
-
});
|
|
373
|
-
return code !== void 0 && code !== "0x";
|
|
374
|
-
}
|
|
375
|
-
async buildUserOperationBatch(owner, smartAccountAddress, transactions) {
|
|
376
|
-
const isDeployed = await this.isAccountDeployed(smartAccountAddress);
|
|
377
|
-
const initCode = isDeployed ? "0x" : this.buildInitCode(owner);
|
|
378
|
-
const targets = transactions.map((tx) => tx.target);
|
|
379
|
-
const values = transactions.map((tx) => tx.value);
|
|
380
|
-
const datas = transactions.map((tx) => tx.data);
|
|
381
|
-
const callData = encodeFunctionData({
|
|
382
|
-
abi: smartAccountAbi,
|
|
383
|
-
functionName: "executeBatch",
|
|
384
|
-
args: [targets, values, datas]
|
|
385
|
-
});
|
|
386
|
-
const nonce = await this.getNonce(smartAccountAddress);
|
|
387
|
-
const partialOp = {
|
|
388
|
-
sender: smartAccountAddress,
|
|
389
|
-
nonce,
|
|
390
|
-
initCode,
|
|
391
|
-
callData,
|
|
392
|
-
paymasterAndData: this.chainConfig.paymasterAddress || "0x"
|
|
393
|
-
};
|
|
394
|
-
const gasEstimate = await this.bundlerClient.estimateGas(partialOp);
|
|
395
|
-
return {
|
|
396
|
-
...partialOp,
|
|
397
|
-
callGasLimit: BigInt(gasEstimate.callGasLimit),
|
|
398
|
-
verificationGasLimit: BigInt(gasEstimate.verificationGasLimit),
|
|
399
|
-
preVerificationGas: BigInt(gasEstimate.preVerificationGas),
|
|
400
|
-
maxFeePerGas: BigInt(gasEstimate.maxFeePerGas),
|
|
401
|
-
maxPriorityFeePerGas: BigInt(gasEstimate.maxPriorityFeePerGas),
|
|
402
|
-
paymasterAndData: gasEstimate.paymasterAndData || partialOp.paymasterAndData,
|
|
403
|
-
signature: "0x"
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
async buildDeployUserOp(owner, smartAccountAddress) {
|
|
407
|
-
const isDeployed = await this.isAccountDeployed(smartAccountAddress);
|
|
408
|
-
if (isDeployed) throw new Error("Account already deployed");
|
|
409
|
-
const initCode = this.buildInitCode(owner);
|
|
410
|
-
const callData = "0x";
|
|
411
|
-
const nonce = await this.getNonce(smartAccountAddress);
|
|
412
|
-
const partialOp = {
|
|
413
|
-
sender: smartAccountAddress,
|
|
414
|
-
nonce,
|
|
415
|
-
initCode,
|
|
416
|
-
callData,
|
|
417
|
-
paymasterAndData: this.chainConfig.paymasterAddress || "0x"
|
|
418
|
-
};
|
|
419
|
-
const gasEstimate = await this.bundlerClient.estimateGas(partialOp);
|
|
420
|
-
return {
|
|
421
|
-
...partialOp,
|
|
422
|
-
callGasLimit: BigInt(gasEstimate.callGasLimit),
|
|
423
|
-
verificationGasLimit: BigInt(gasEstimate.verificationGasLimit),
|
|
424
|
-
preVerificationGas: BigInt(gasEstimate.preVerificationGas),
|
|
425
|
-
maxFeePerGas: BigInt(gasEstimate.maxFeePerGas),
|
|
426
|
-
maxPriorityFeePerGas: BigInt(gasEstimate.maxPriorityFeePerGas),
|
|
427
|
-
paymasterAndData: gasEstimate.paymasterAndData || partialOp.paymasterAndData,
|
|
428
|
-
signature: "0x"
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
getUserOpHash(userOp) {
|
|
432
|
-
const packed = encodeAbiParameters(
|
|
433
|
-
[
|
|
434
|
-
{ type: "address" },
|
|
435
|
-
{ type: "uint256" },
|
|
436
|
-
{ type: "bytes32" },
|
|
437
|
-
{ type: "bytes32" },
|
|
438
|
-
{ type: "uint256" },
|
|
439
|
-
{ type: "uint256" },
|
|
440
|
-
{ type: "uint256" },
|
|
441
|
-
{ type: "uint256" },
|
|
442
|
-
{ type: "uint256" },
|
|
443
|
-
{ type: "bytes32" }
|
|
444
|
-
],
|
|
445
|
-
[
|
|
446
|
-
userOp.sender,
|
|
447
|
-
userOp.nonce,
|
|
448
|
-
keccak256(userOp.initCode),
|
|
449
|
-
keccak256(userOp.callData),
|
|
450
|
-
userOp.callGasLimit,
|
|
451
|
-
userOp.verificationGasLimit,
|
|
452
|
-
userOp.preVerificationGas,
|
|
453
|
-
userOp.maxFeePerGas,
|
|
454
|
-
userOp.maxPriorityFeePerGas,
|
|
455
|
-
keccak256(userOp.paymasterAndData)
|
|
456
|
-
]
|
|
457
|
-
);
|
|
458
|
-
const packedHash = keccak256(packed);
|
|
459
|
-
return keccak256(
|
|
460
|
-
encodeAbiParameters(
|
|
461
|
-
[{ type: "bytes32" }, { type: "address" }, { type: "uint256" }],
|
|
462
|
-
[packedHash, this.entryPointAddress, BigInt(this.chainConfig.chain.id)]
|
|
463
|
-
)
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
var AccountAbstraction;
|
|
470
|
-
var init_AccountAbstraction = __esm({
|
|
471
|
-
"src/AccountAbstraction.ts"() {
|
|
472
|
-
init_constants();
|
|
473
|
-
init_BundlerClient();
|
|
474
|
-
init_TokenService();
|
|
475
|
-
init_UserOpBuilder();
|
|
476
|
-
AccountAbstraction = class {
|
|
477
|
-
constructor(chainConfig) {
|
|
478
|
-
this.owner = null;
|
|
479
|
-
this.smartAccountAddress = null;
|
|
480
|
-
this.walletClient = null;
|
|
481
|
-
this.chainConfig = chainConfig;
|
|
482
|
-
if (!chainConfig.entryPointAddress) throw new Error("EntryPoint address required");
|
|
483
|
-
this.entryPointAddress = chainConfig.entryPointAddress;
|
|
484
|
-
if (!chainConfig.factoryAddress) throw new Error("Factory address required");
|
|
485
|
-
this.factoryAddress = chainConfig.factoryAddress;
|
|
486
|
-
const rpcUrl = chainConfig.rpcUrl || chainConfig.chain.rpcUrls.default.http[0];
|
|
487
|
-
this.publicClient = createPublicClient({
|
|
488
|
-
chain: chainConfig.chain,
|
|
489
|
-
transport: http(rpcUrl)
|
|
490
|
-
});
|
|
491
|
-
this.bundlerClient = new BundlerClient(chainConfig, this.entryPointAddress);
|
|
492
|
-
this.tokenService = new TokenService(chainConfig, this.publicClient);
|
|
493
|
-
this.userOpBuilder = new UserOpBuilder(chainConfig, this.bundlerClient, this.publicClient);
|
|
494
|
-
}
|
|
495
|
-
getBundlerClient() {
|
|
496
|
-
return this.bundlerClient;
|
|
497
|
-
}
|
|
498
|
-
getChainId() {
|
|
499
|
-
return this.chainConfig.chain.id;
|
|
500
|
-
}
|
|
501
|
-
getPublicClient() {
|
|
502
|
-
return this.publicClient;
|
|
503
|
-
}
|
|
504
|
-
async buildUserOperation(transaction) {
|
|
505
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
506
|
-
return this.userOpBuilder.buildUserOperationBatch(this.owner, this.smartAccountAddress, [transaction]);
|
|
507
|
-
}
|
|
508
|
-
async buildBatchUserOperation(transactions) {
|
|
509
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
510
|
-
return this.userOpBuilder.buildUserOperationBatch(this.owner, this.smartAccountAddress, transactions);
|
|
511
|
-
}
|
|
512
|
-
async connect(signer) {
|
|
513
|
-
if (typeof signer === "string") {
|
|
514
|
-
const account = privateKeyToAccount(signer);
|
|
515
|
-
this.owner = account.address;
|
|
516
|
-
const rpcUrl = this.chainConfig.rpcUrl || this.chainConfig.chain.rpcUrls.default.http[0];
|
|
517
|
-
this.walletClient = createWalletClient({
|
|
518
|
-
account,
|
|
519
|
-
chain: this.chainConfig.chain,
|
|
520
|
-
transport: http(rpcUrl)
|
|
521
|
-
});
|
|
522
|
-
} else if (signer && typeof signer === "object") {
|
|
523
|
-
this.walletClient = signer;
|
|
524
|
-
if (!this.walletClient.account) throw new Error("WalletClient must have an account");
|
|
525
|
-
this.owner = this.walletClient.account.address;
|
|
526
|
-
} else {
|
|
527
|
-
if (typeof window === "undefined" || !window.ethereum) {
|
|
528
|
-
throw new Error("MetaMask is not installed and no private key provided");
|
|
529
|
-
}
|
|
530
|
-
const accounts = await window.ethereum.request({
|
|
531
|
-
method: "eth_requestAccounts"
|
|
532
|
-
});
|
|
533
|
-
if (!accounts || accounts.length === 0) throw new Error("No accounts found");
|
|
534
|
-
const chainId = await window.ethereum.request({
|
|
535
|
-
method: "eth_chainId"
|
|
536
|
-
});
|
|
537
|
-
const targetChainId = this.chainConfig.chain.id;
|
|
538
|
-
if (parseInt(chainId, 16) !== targetChainId) {
|
|
539
|
-
try {
|
|
540
|
-
await window.ethereum.request({
|
|
541
|
-
method: "wallet_switchEthereumChain",
|
|
542
|
-
params: [{ chainId: "0x" + targetChainId.toString(16) }]
|
|
543
|
-
});
|
|
544
|
-
} catch (switchError) {
|
|
545
|
-
const error = switchError;
|
|
546
|
-
if (error.code === 4902) {
|
|
547
|
-
await window.ethereum.request({
|
|
548
|
-
method: "wallet_addEthereumChain",
|
|
549
|
-
params: [
|
|
550
|
-
{
|
|
551
|
-
chainId: "0x" + targetChainId.toString(16),
|
|
552
|
-
chainName: this.chainConfig.chain.name,
|
|
553
|
-
nativeCurrency: this.chainConfig.chain.nativeCurrency,
|
|
554
|
-
rpcUrls: [this.chainConfig.rpcUrl || this.chainConfig.chain.rpcUrls.default.http[0]],
|
|
555
|
-
blockExplorerUrls: this.chainConfig.chain.blockExplorers?.default?.url ? [this.chainConfig.chain.blockExplorers.default.url] : []
|
|
556
|
-
}
|
|
557
|
-
]
|
|
558
|
-
});
|
|
559
|
-
} else {
|
|
560
|
-
throw switchError;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
this.owner = accounts[0];
|
|
565
|
-
}
|
|
566
|
-
this.smartAccountAddress = await this.getSmartAccountAddress(this.owner);
|
|
567
|
-
return {
|
|
568
|
-
owner: this.owner,
|
|
569
|
-
smartAccount: this.smartAccountAddress
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
// --- Account Management ---
|
|
573
|
-
async isAccountDeployed() {
|
|
574
|
-
if (!this.smartAccountAddress) return false;
|
|
575
|
-
const code = await this.publicClient.getBytecode({ address: this.smartAccountAddress });
|
|
576
|
-
return code !== void 0;
|
|
577
|
-
}
|
|
578
|
-
async deployAccount() {
|
|
579
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
580
|
-
return this.sendTransaction({
|
|
581
|
-
target: this.smartAccountAddress,
|
|
582
|
-
value: 0n,
|
|
583
|
-
data: "0x"
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Get the Smart Account address for an owner
|
|
588
|
-
*/
|
|
589
|
-
async getSmartAccountAddress(owner) {
|
|
590
|
-
try {
|
|
591
|
-
const address = await this.publicClient.readContract({
|
|
592
|
-
address: this.factoryAddress,
|
|
593
|
-
abi: factoryAbi,
|
|
594
|
-
functionName: "getAccountAddress",
|
|
595
|
-
args: [owner, 0n]
|
|
596
|
-
});
|
|
597
|
-
return address;
|
|
598
|
-
} catch (e) {
|
|
599
|
-
try {
|
|
600
|
-
const { result } = await this.publicClient.simulateContract({
|
|
601
|
-
address: this.factoryAddress,
|
|
602
|
-
abi: factoryAbi,
|
|
603
|
-
functionName: "createAccount",
|
|
604
|
-
args: [owner, 0n],
|
|
605
|
-
account: "0x0000000000000000000000000000000000000000"
|
|
606
|
-
// Zero address or random for simulation
|
|
607
|
-
});
|
|
608
|
-
return result;
|
|
609
|
-
} catch (inner) {
|
|
610
|
-
throw e;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
// --- Token Methods (Delegated) ---
|
|
615
|
-
getTokenAddress(token) {
|
|
616
|
-
return this.tokenService.getTokenAddress(token);
|
|
617
|
-
}
|
|
618
|
-
async getBalance(token) {
|
|
619
|
-
if (!this.smartAccountAddress) throw new Error("Not connected");
|
|
620
|
-
return this.tokenService.getBalance(token, this.smartAccountAddress);
|
|
621
|
-
}
|
|
622
|
-
async getEoaBalance(token) {
|
|
623
|
-
if (!this.owner) throw new Error("Not connected");
|
|
624
|
-
return this.tokenService.getBalance(token, this.owner);
|
|
625
|
-
}
|
|
626
|
-
async getAllowance(token = "USDC") {
|
|
627
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
628
|
-
return this.tokenService.getAllowance(token, this.owner, this.smartAccountAddress);
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Get comprehensive state of the account for a specific token
|
|
632
|
-
* Useful for UI initialization
|
|
633
|
-
*/
|
|
634
|
-
async getAccountState(token) {
|
|
635
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
636
|
-
const tokenAddress = this.getTokenAddress(token);
|
|
637
|
-
const isNative = tokenAddress === "0x0000000000000000000000000000000000000000";
|
|
638
|
-
const [balance, eoaBalance, allowance, isDeployed] = await Promise.all([
|
|
639
|
-
this.getBalance(token),
|
|
640
|
-
this.getEoaBalance(token),
|
|
641
|
-
isNative ? 0n : this.getAllowance(token).catch(() => 0n),
|
|
642
|
-
// Handle native/error gracefully
|
|
643
|
-
this.isAccountDeployed()
|
|
644
|
-
]);
|
|
645
|
-
return {
|
|
646
|
-
owner: this.owner,
|
|
647
|
-
smartAccount: this.smartAccountAddress,
|
|
648
|
-
balance,
|
|
649
|
-
eoaBalance,
|
|
650
|
-
allowance,
|
|
651
|
-
isDeployed
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
// --- Transactions ---
|
|
655
|
-
async sendTransaction(tx) {
|
|
656
|
-
return this.sendBatchTransaction([tx]);
|
|
657
|
-
}
|
|
658
|
-
async sendBatchTransaction(txs) {
|
|
659
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
660
|
-
const transactions = txs.map((tx) => ({
|
|
661
|
-
target: tx.target,
|
|
662
|
-
value: tx.value ?? 0n,
|
|
663
|
-
data: tx.data ?? "0x"
|
|
664
|
-
}));
|
|
665
|
-
try {
|
|
666
|
-
const userOp = await this.userOpBuilder.buildUserOperationBatch(
|
|
667
|
-
this.owner,
|
|
668
|
-
this.smartAccountAddress,
|
|
669
|
-
transactions
|
|
670
|
-
);
|
|
671
|
-
const signed = await this.signUserOperation(userOp);
|
|
672
|
-
const hash = await this.sendUserOperation(signed);
|
|
673
|
-
return await this.waitForUserOperation(hash);
|
|
674
|
-
} catch (error) {
|
|
675
|
-
throw this.decodeError(error);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
async deposit(amount) {
|
|
679
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
680
|
-
if (this.walletClient) {
|
|
681
|
-
return await this.walletClient.sendTransaction({
|
|
682
|
-
account: this.walletClient.account,
|
|
683
|
-
to: this.smartAccountAddress,
|
|
684
|
-
value: amount,
|
|
685
|
-
chain: this.chainConfig.chain
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
return await window.ethereum.request({
|
|
689
|
-
method: "eth_sendTransaction",
|
|
690
|
-
params: [{
|
|
691
|
-
from: this.owner,
|
|
692
|
-
to: this.smartAccountAddress,
|
|
693
|
-
value: "0x" + amount.toString(16)
|
|
694
|
-
}]
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Smart Transfer: Automatically chooses best method (SA vs EOA) based on balances.
|
|
699
|
-
* Supports strict fee handling.
|
|
700
|
-
*/
|
|
701
|
-
async smartTransfer(token, recipient, amount, fee) {
|
|
702
|
-
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
703
|
-
const tokenAddress = this.getTokenAddress(token);
|
|
704
|
-
const isNative = tokenAddress === "0x0000000000000000000000000000000000000000";
|
|
705
|
-
const [saBal, eoaBal, allowance] = await Promise.all([
|
|
706
|
-
this.getBalance(token),
|
|
707
|
-
this.getEoaBalance(token),
|
|
708
|
-
isNative ? 0n : this.getAllowance(token).catch(() => 0n)
|
|
709
|
-
]);
|
|
710
|
-
const totalNeeded = amount + (fee?.amount || 0n);
|
|
711
|
-
if (saBal >= totalNeeded) {
|
|
712
|
-
const txs = [];
|
|
713
|
-
if (isNative) {
|
|
714
|
-
txs.push({ target: recipient, value: amount, data: "0x" });
|
|
715
|
-
} else {
|
|
716
|
-
txs.push({ target: tokenAddress, value: 0n, data: this.tokenService.encodeTransfer(recipient, amount) });
|
|
717
|
-
}
|
|
718
|
-
if (fee && fee.amount > 0n) {
|
|
719
|
-
if (isNative) {
|
|
720
|
-
txs.push({ target: fee.recipient, value: fee.amount, data: "0x" });
|
|
721
|
-
} else {
|
|
722
|
-
txs.push({ target: tokenAddress, value: 0n, data: this.tokenService.encodeTransfer(fee.recipient, fee.amount) });
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
console.log("SmartTransfer: Using Smart Account");
|
|
726
|
-
return this.sendBatchTransaction(txs);
|
|
727
|
-
}
|
|
728
|
-
if (eoaBal >= totalNeeded) {
|
|
729
|
-
if (isNative) {
|
|
730
|
-
console.log("SmartTransfer: Using EOA (Native)");
|
|
731
|
-
if (this.walletClient) {
|
|
732
|
-
const hash2 = await this.walletClient.sendTransaction({
|
|
733
|
-
account: this.walletClient.account,
|
|
734
|
-
to: recipient,
|
|
735
|
-
value: amount,
|
|
736
|
-
chain: this.chainConfig.chain
|
|
737
|
-
});
|
|
738
|
-
return { receipt: { transactionHash: hash2 } };
|
|
739
|
-
}
|
|
740
|
-
const hash = await window.ethereum.request({
|
|
741
|
-
method: "eth_sendTransaction",
|
|
742
|
-
params: [{ from: this.owner, to: recipient, value: "0x" + amount.toString(16) }]
|
|
743
|
-
});
|
|
744
|
-
return { receipt: { transactionHash: hash } };
|
|
745
|
-
} else {
|
|
746
|
-
console.log("SmartTransfer: Using EOA (Pull)");
|
|
747
|
-
if (allowance < totalNeeded) {
|
|
748
|
-
throw new Error(`Approval required. Please approve ${token} usage.`);
|
|
749
|
-
}
|
|
750
|
-
const txs = [];
|
|
751
|
-
txs.push({
|
|
752
|
-
target: tokenAddress,
|
|
753
|
-
value: 0n,
|
|
754
|
-
data: this.tokenService.encodeTransferFrom(this.owner, recipient, amount)
|
|
755
|
-
});
|
|
756
|
-
if (fee && fee.amount > 0n) {
|
|
757
|
-
txs.push({
|
|
758
|
-
target: tokenAddress,
|
|
759
|
-
value: 0n,
|
|
760
|
-
data: this.tokenService.encodeTransferFrom(this.owner, fee.recipient, fee.amount)
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
return this.sendBatchTransaction(txs);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
throw new Error(`Insufficient funds.`);
|
|
767
|
-
}
|
|
768
|
-
async transfer(token, recipient, amount) {
|
|
769
|
-
const tokenAddress = this.getTokenAddress(token);
|
|
770
|
-
if (tokenAddress === "0x0000000000000000000000000000000000000000") {
|
|
771
|
-
return this.sendTransaction({
|
|
772
|
-
target: recipient,
|
|
773
|
-
value: amount,
|
|
774
|
-
data: "0x"
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
const data = this.tokenService.encodeTransfer(recipient, amount);
|
|
778
|
-
return this.sendTransaction({
|
|
779
|
-
target: tokenAddress,
|
|
780
|
-
value: 0n,
|
|
781
|
-
data
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Approve a token for the Smart Account
|
|
786
|
-
*/
|
|
787
|
-
async approveToken(token, spender, amount = 115792089237316195423570985008687907853269984665640564039457584007913129639935n) {
|
|
788
|
-
if (!this.owner) throw new Error("Not connected");
|
|
789
|
-
const support = await this.requestApprovalSupport(token, spender, amount);
|
|
790
|
-
if (support.type === "approve") {
|
|
791
|
-
const data = this.tokenService.encodeApprove(spender, amount);
|
|
792
|
-
if (this.walletClient) {
|
|
793
|
-
return await this.walletClient.sendTransaction({
|
|
794
|
-
account: this.walletClient.account,
|
|
795
|
-
to: token,
|
|
796
|
-
data,
|
|
797
|
-
chain: this.chainConfig.chain
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
return await window.ethereum.request({
|
|
801
|
-
method: "eth_sendTransaction",
|
|
802
|
-
params: [{
|
|
803
|
-
from: this.owner,
|
|
804
|
-
to: token,
|
|
805
|
-
data
|
|
806
|
-
}]
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
if (support.type === "permit") throw new Error("Permit not yet supported");
|
|
810
|
-
return "NOT_NEEDED";
|
|
811
|
-
}
|
|
812
|
-
// --- Core Bridge to Bundler/UserOp ---
|
|
813
|
-
async signUserOperation(userOp) {
|
|
814
|
-
if (!this.owner) throw new Error("Not connected");
|
|
815
|
-
const userOpHash = this.userOpBuilder.getUserOpHash(userOp);
|
|
816
|
-
let signature;
|
|
817
|
-
if (this.walletClient) {
|
|
818
|
-
signature = await this.walletClient.signMessage({
|
|
819
|
-
account: this.walletClient.account,
|
|
820
|
-
message: { raw: userOpHash }
|
|
821
|
-
// Sign hash directly
|
|
822
|
-
});
|
|
823
|
-
} else {
|
|
824
|
-
signature = await window.ethereum.request({
|
|
825
|
-
method: "personal_sign",
|
|
826
|
-
params: [userOpHash, this.owner]
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
return { ...userOp, signature };
|
|
830
|
-
}
|
|
831
|
-
async sendUserOperation(userOp) {
|
|
832
|
-
return this.bundlerClient.sendUserOperation(userOp);
|
|
833
|
-
}
|
|
834
|
-
async waitForUserOperation(hash, timeout = 6e4) {
|
|
835
|
-
return this.bundlerClient.waitForUserOperation(hash, timeout);
|
|
836
|
-
}
|
|
837
|
-
// Internal but exposed via BundlerClient originally
|
|
838
|
-
async requestApprovalSupport(token, spender, amount) {
|
|
839
|
-
if (!this.owner) throw new Error("Not connected");
|
|
840
|
-
return this.bundlerClient.requestApprovalSupport(token, this.owner, spender, amount);
|
|
841
|
-
}
|
|
842
|
-
// Error Decoding (Private)
|
|
843
|
-
decodeError(error) {
|
|
844
|
-
const msg = error?.message || "";
|
|
845
|
-
const hexMatch = msg.match(/(0x[0-9a-fA-F]+)/);
|
|
846
|
-
if (hexMatch) {
|
|
847
|
-
try {
|
|
848
|
-
const decoded = decodeErrorResult({
|
|
849
|
-
abi: [{ inputs: [{ name: "message", type: "string" }], name: "Error", type: "error" }],
|
|
850
|
-
data: hexMatch[0]
|
|
851
|
-
});
|
|
852
|
-
if (decoded.errorName === "Error") return new Error(`Smart Account Error: ${decoded.args[0]}`);
|
|
853
|
-
} catch (e) {
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
if (msg.includes("AA21")) return new Error("Smart Account: Native transfer failed (ETH missing?)");
|
|
857
|
-
if (msg.includes("AA25")) return new Error("Smart Account: Invalid account nonce");
|
|
858
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
859
|
-
}
|
|
860
|
-
// Getters
|
|
861
|
-
getOwner() {
|
|
862
|
-
return this.owner;
|
|
863
|
-
}
|
|
864
|
-
getSmartAccount() {
|
|
865
|
-
return this.smartAccountAddress;
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
|
|
871
20
|
// src/constants/bundler.ts
|
|
872
21
|
var DEFAULT_BUNDLER_URL, BUNDLER_URL;
|
|
873
22
|
var init_bundler = __esm({
|
|
@@ -1091,6 +240,43 @@ var init_Optimism = __esm({
|
|
|
1091
240
|
};
|
|
1092
241
|
}
|
|
1093
242
|
});
|
|
243
|
+
var UNICHAIN;
|
|
244
|
+
var init_Unichain = __esm({
|
|
245
|
+
"src/chains/Evm/Unichain.ts"() {
|
|
246
|
+
init_bundler();
|
|
247
|
+
UNICHAIN = {
|
|
248
|
+
assets: [
|
|
249
|
+
{
|
|
250
|
+
name: "USDC",
|
|
251
|
+
decimals: 6,
|
|
252
|
+
address: "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
|
|
253
|
+
coingeckoId: "usd-coin"
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
evm: {
|
|
257
|
+
chain: unichain,
|
|
258
|
+
rpcUrl: "https://unichain-mainnet.g.alchemy.com/v2/49fUGmuW05ynCui0VEvDN",
|
|
259
|
+
bundlerUrl: `${BUNDLER_URL}/rpc?chain=unichain`,
|
|
260
|
+
supports7702: true,
|
|
261
|
+
erc4337: false,
|
|
262
|
+
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
263
|
+
factoryAddress: "0xB2E45aCbB68f3e98C87B6df16625f22e11728556",
|
|
264
|
+
paymasterAddress: "0x7A92b3Fee017E3E181a51D9045AACE30eC2B387D"
|
|
265
|
+
},
|
|
266
|
+
crossChainInformation: {
|
|
267
|
+
circleInformation: {
|
|
268
|
+
supportCirclePaymaster: true,
|
|
269
|
+
cCTPInformation: {
|
|
270
|
+
supportCCTP: true,
|
|
271
|
+
domain: 10
|
|
272
|
+
},
|
|
273
|
+
aproxFromFee: 0
|
|
274
|
+
},
|
|
275
|
+
nearIntentInformation: null
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
});
|
|
1094
280
|
var GNOSIS;
|
|
1095
281
|
var init_Gnosis = __esm({
|
|
1096
282
|
"src/chains/Evm/Gnosis.ts"() {
|
|
@@ -1414,43 +600,6 @@ var init_Arbitrum = __esm({
|
|
|
1414
600
|
};
|
|
1415
601
|
}
|
|
1416
602
|
});
|
|
1417
|
-
var UNICHAIN;
|
|
1418
|
-
var init_Unichain = __esm({
|
|
1419
|
-
"src/chains/Evm/Unichain.ts"() {
|
|
1420
|
-
init_bundler();
|
|
1421
|
-
UNICHAIN = {
|
|
1422
|
-
assets: [
|
|
1423
|
-
{
|
|
1424
|
-
name: "USDC",
|
|
1425
|
-
decimals: 6,
|
|
1426
|
-
address: "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
|
|
1427
|
-
coingeckoId: "usd-coin"
|
|
1428
|
-
}
|
|
1429
|
-
],
|
|
1430
|
-
evm: {
|
|
1431
|
-
chain: unichain,
|
|
1432
|
-
rpcUrl: "https://unichain-mainnet.g.alchemy.com/v2/49fUGmuW05ynCui0VEvDN",
|
|
1433
|
-
bundlerUrl: `${BUNDLER_URL}/rpc?chain=unichain`,
|
|
1434
|
-
supports7702: true,
|
|
1435
|
-
erc4337: false,
|
|
1436
|
-
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
1437
|
-
factoryAddress: "0xB2E45aCbB68f3e98C87B6df16625f22e11728556",
|
|
1438
|
-
paymasterAddress: "0x7A92b3Fee017E3E181a51D9045AACE30eC2B387D"
|
|
1439
|
-
},
|
|
1440
|
-
crossChainInformation: {
|
|
1441
|
-
circleInformation: {
|
|
1442
|
-
supportCirclePaymaster: true,
|
|
1443
|
-
cCTPInformation: {
|
|
1444
|
-
supportCCTP: true,
|
|
1445
|
-
domain: 10
|
|
1446
|
-
},
|
|
1447
|
-
aproxFromFee: 0
|
|
1448
|
-
},
|
|
1449
|
-
nearIntentInformation: null
|
|
1450
|
-
}
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
});
|
|
1454
603
|
var Monad;
|
|
1455
604
|
var init_Monad = __esm({
|
|
1456
605
|
"src/chains/Evm/Monad.ts"() {
|
|
@@ -1568,6 +717,34 @@ var init_Stellar = __esm({
|
|
|
1568
717
|
}
|
|
1569
718
|
});
|
|
1570
719
|
|
|
720
|
+
// src/chains/NoEvm/Stacks.ts
|
|
721
|
+
var STACKS;
|
|
722
|
+
var init_Stacks = __esm({
|
|
723
|
+
"src/chains/NoEvm/Stacks.ts"() {
|
|
724
|
+
STACKS = {
|
|
725
|
+
assets: [
|
|
726
|
+
{
|
|
727
|
+
name: "USDC",
|
|
728
|
+
decimals: 6,
|
|
729
|
+
address: "SP2ZNGJ15SD91GWW54B2Q1V31749666c2Q01",
|
|
730
|
+
// Placeholder or real Stacks token ID
|
|
731
|
+
coingeckoId: "usd-coin"
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
nonEvm: {
|
|
735
|
+
serverURL: "https://api.mainnet.hiro.so"
|
|
736
|
+
},
|
|
737
|
+
crossChainInformation: {
|
|
738
|
+
circleInformation: {
|
|
739
|
+
supportCirclePaymaster: false,
|
|
740
|
+
aproxFromFee: 0
|
|
741
|
+
},
|
|
742
|
+
nearIntentInformation: null
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
1571
748
|
// src/services/StellarService.ts
|
|
1572
749
|
var StellarService_exports = {};
|
|
1573
750
|
__export(StellarService_exports, {
|
|
@@ -2044,6 +1221,7 @@ var init_chains = __esm({
|
|
|
2044
1221
|
init_Binance();
|
|
2045
1222
|
init_Gnosis();
|
|
2046
1223
|
init_Ethereum();
|
|
1224
|
+
init_Stacks();
|
|
2047
1225
|
}
|
|
2048
1226
|
});
|
|
2049
1227
|
|
|
@@ -2311,7 +1489,7 @@ async function executeCCTPBridge(sourceChain, amount, crossChainConfig, facilita
|
|
|
2311
1489
|
mintTransactionHash: mintHash,
|
|
2312
1490
|
payer: payerAddress,
|
|
2313
1491
|
fee: fee.toString(),
|
|
2314
|
-
netAmount: amountBigInt.toString(),
|
|
1492
|
+
netAmount: (amountBigInt - fee).toString(),
|
|
2315
1493
|
attestation: {
|
|
2316
1494
|
message: attestationResponse.message,
|
|
2317
1495
|
attestation: attestationResponse.attestation
|
|
@@ -2374,14 +1552,6 @@ var init_cctp2 = __esm({
|
|
|
2374
1552
|
};
|
|
2375
1553
|
}
|
|
2376
1554
|
});
|
|
2377
|
-
|
|
2378
|
-
// src/services/near.ts
|
|
2379
|
-
var near_exports = {};
|
|
2380
|
-
__export(near_exports, {
|
|
2381
|
-
NearStrategy: () => NearStrategy,
|
|
2382
|
-
getNearQuote: () => getNearQuote,
|
|
2383
|
-
getNearSimulation: () => getNearSimulation
|
|
2384
|
-
});
|
|
2385
1555
|
async function getNearQuote(sourceChain, destChain, amount, destToken, sourceToken, recipient, senderAddress, options) {
|
|
2386
1556
|
const sourceConfig = NETWORKS[sourceChain];
|
|
2387
1557
|
const destConfig = NETWORKS[destChain];
|
|
@@ -2830,7 +2000,8 @@ var init_stacks = __esm({
|
|
|
2830
2000
|
{ "internalType": "uint32", "name": "remoteDomain", "type": "uint32" },
|
|
2831
2001
|
{ "internalType": "bytes32", "name": "remoteRecipient", "type": "bytes32" },
|
|
2832
2002
|
{ "internalType": "address", "name": "localToken", "type": "address" },
|
|
2833
|
-
{ "internalType": "uint256", "name": "maxFee", "type": "uint256" }
|
|
2003
|
+
{ "internalType": "uint256", "name": "maxFee", "type": "uint256" },
|
|
2004
|
+
{ "internalType": "bytes", "name": "hookData", "type": "bytes" }
|
|
2834
2005
|
],
|
|
2835
2006
|
"name": "depositToRemote",
|
|
2836
2007
|
"outputs": [],
|
|
@@ -2868,8 +2039,20 @@ var init_stacks = __esm({
|
|
|
2868
2039
|
chain: networkConfig.evm.chain,
|
|
2869
2040
|
transport: http(networkConfig.evm.rpcUrl)
|
|
2870
2041
|
});
|
|
2871
|
-
const
|
|
2042
|
+
const ethAssets = NETWORKS["Ethereum"].assets;
|
|
2043
|
+
const usdcToken = ethAssets.find((a) => a.name === "USDC");
|
|
2044
|
+
if (!usdcToken || !usdcToken.address) throw new Error("USDC config/address not found for Ethereum");
|
|
2045
|
+
const usdcAddress = getAddress(usdcToken.address);
|
|
2872
2046
|
const amountBigInt = parseUnits(amount, 6);
|
|
2047
|
+
const balance = await publicClient.readContract({
|
|
2048
|
+
address: usdcAddress,
|
|
2049
|
+
abi: erc20Abi$1,
|
|
2050
|
+
functionName: "balanceOf",
|
|
2051
|
+
args: [account.address]
|
|
2052
|
+
});
|
|
2053
|
+
if (balance < amountBigInt) {
|
|
2054
|
+
throw new Error(`Insufficient USDC balance on Ethereum (Facilitator). Has: ${balance}, Needed: ${amountBigInt}`);
|
|
2055
|
+
}
|
|
2873
2056
|
const allowance = await publicClient.readContract({
|
|
2874
2057
|
address: usdcAddress,
|
|
2875
2058
|
abi: erc20Abi$1,
|
|
@@ -2894,9 +2077,10 @@ var init_stacks = __esm({
|
|
|
2894
2077
|
recipientBytes32 = padHex(recipient, { size: 32 });
|
|
2895
2078
|
} else {
|
|
2896
2079
|
try {
|
|
2897
|
-
|
|
2080
|
+
const [version, hash2] = c32addressDecode(recipient);
|
|
2081
|
+
recipientBytes32 = padHex(`0x${hash2}`, { size: 32 });
|
|
2898
2082
|
} catch (e) {
|
|
2899
|
-
throw new Error(
|
|
2083
|
+
throw new Error(`Invalid Stacks Recipient: ${e.message}`);
|
|
2900
2084
|
}
|
|
2901
2085
|
}
|
|
2902
2086
|
console.log(`[Stacks] Depositing ${amount} USDC to ${recipient} (Domain ${REMOTE_DOMAIN_STACKS})...`);
|
|
@@ -2909,8 +2093,10 @@ var init_stacks = __esm({
|
|
|
2909
2093
|
REMOTE_DOMAIN_STACKS,
|
|
2910
2094
|
recipientBytes32,
|
|
2911
2095
|
usdcAddress,
|
|
2912
|
-
0n
|
|
2913
|
-
// maxFee
|
|
2096
|
+
0n,
|
|
2097
|
+
// maxFee
|
|
2098
|
+
"0x"
|
|
2099
|
+
// hookData
|
|
2914
2100
|
],
|
|
2915
2101
|
chain: networkConfig.evm.chain
|
|
2916
2102
|
});
|
|
@@ -3008,109 +2194,13 @@ var init_Router = __esm({
|
|
|
3008
2194
|
} catch (e) {
|
|
3009
2195
|
onLog(`[Router] Polling check failed (retrying): ${e.message}`);
|
|
3010
2196
|
}
|
|
3011
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
3012
|
-
elapsed += interval;
|
|
3013
|
-
if (elapsed % 2e4 === 0) onLog(`[Router] Still waiting... (${Math.floor(elapsed / 1e3)}s)`);
|
|
3014
|
-
}
|
|
3015
|
-
return false;
|
|
3016
|
-
}
|
|
3017
|
-
};
|
|
3018
|
-
}
|
|
3019
|
-
});
|
|
3020
|
-
|
|
3021
|
-
// src/services/uniswap.ts
|
|
3022
|
-
var uniswap_exports = {};
|
|
3023
|
-
__export(uniswap_exports, {
|
|
3024
|
-
UniswapService: () => UniswapService,
|
|
3025
|
-
uniswapService: () => uniswapService
|
|
3026
|
-
});
|
|
3027
|
-
var UNISWAP_V3_ROUTER, UNISWAP_V3_QUOTER, USDC_ADDRESS, WETH_ADDRESS, QUOTER_ABI, ROUTER_ABI, UniswapService, uniswapService;
|
|
3028
|
-
var init_uniswap = __esm({
|
|
3029
|
-
"src/services/uniswap.ts"() {
|
|
3030
|
-
UNISWAP_V3_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481";
|
|
3031
|
-
UNISWAP_V3_QUOTER = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";
|
|
3032
|
-
USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
3033
|
-
WETH_ADDRESS = "0x4200000000000000000000000000000000000006";
|
|
3034
|
-
QUOTER_ABI = parseAbi([
|
|
3035
|
-
"function quoteExactOutputSingle((address tokenIn, address tokenOut, uint256 amount, uint24 fee, uint160 sqrtPriceLimitX96) params) external returns (uint256 amountIn, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)"
|
|
3036
|
-
]);
|
|
3037
|
-
ROUTER_ABI = parseAbi([
|
|
3038
|
-
"function exactOutputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountIn)"
|
|
3039
|
-
]);
|
|
3040
|
-
UniswapService = class {
|
|
3041
|
-
constructor() {
|
|
3042
|
-
this.publicClient = createPublicClient({
|
|
3043
|
-
chain: base,
|
|
3044
|
-
transport: http("https://mainnet.base.org")
|
|
3045
|
-
// Default public RPC
|
|
3046
|
-
});
|
|
3047
|
-
}
|
|
3048
|
-
/**
|
|
3049
|
-
* Get amount of USDC needed to buy exact amount of ETH
|
|
3050
|
-
* @param amountETHWei Amount of ETH (Wei) needed
|
|
3051
|
-
*/
|
|
3052
|
-
async quoteUSDCForETH(amountETHWei) {
|
|
3053
|
-
try {
|
|
3054
|
-
const params = {
|
|
3055
|
-
tokenIn: USDC_ADDRESS,
|
|
3056
|
-
tokenOut: WETH_ADDRESS,
|
|
3057
|
-
amount: amountETHWei,
|
|
3058
|
-
fee: 500,
|
|
3059
|
-
sqrtPriceLimitX96: 0n
|
|
3060
|
-
};
|
|
3061
|
-
const result = await this.publicClient.readContract({
|
|
3062
|
-
address: UNISWAP_V3_QUOTER,
|
|
3063
|
-
abi: QUOTER_ABI,
|
|
3064
|
-
functionName: "quoteExactOutputSingle",
|
|
3065
|
-
args: [params]
|
|
3066
|
-
});
|
|
3067
|
-
const amountIn = result[0];
|
|
3068
|
-
return amountIn * 105n / 100n;
|
|
3069
|
-
} catch (e) {
|
|
3070
|
-
console.error("Quote failed", e);
|
|
3071
|
-
throw new Error("Failed to quote USDC for ETH swap");
|
|
2197
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
2198
|
+
elapsed += interval;
|
|
2199
|
+
if (elapsed % 2e4 === 0) onLog(`[Router] Still waiting... (${Math.floor(elapsed / 1e3)}s)`);
|
|
3072
2200
|
}
|
|
3073
|
-
|
|
3074
|
-
/**
|
|
3075
|
-
* Build tx data for swapping USDC -> ETH
|
|
3076
|
-
* Uses SwapRouter02.exactOutputSingle + unwrapWETH9
|
|
3077
|
-
*/
|
|
3078
|
-
buildSwapData(recipient, amountOutETH, maxAmountInUSDC) {
|
|
3079
|
-
const swapParams = {
|
|
3080
|
-
tokenIn: USDC_ADDRESS,
|
|
3081
|
-
tokenOut: WETH_ADDRESS,
|
|
3082
|
-
fee: 500,
|
|
3083
|
-
recipient: UNISWAP_V3_ROUTER,
|
|
3084
|
-
// Router must hold WETH to unwrap it
|
|
3085
|
-
amountOut: amountOutETH,
|
|
3086
|
-
amountInMaximum: maxAmountInUSDC,
|
|
3087
|
-
sqrtPriceLimitX96: 0n
|
|
3088
|
-
};
|
|
3089
|
-
const swapCalldata = encodeFunctionData({
|
|
3090
|
-
abi: ROUTER_ABI,
|
|
3091
|
-
functionName: "exactOutputSingle",
|
|
3092
|
-
args: [swapParams]
|
|
3093
|
-
});
|
|
3094
|
-
const unwrapCalldata = encodeFunctionData({
|
|
3095
|
-
abi: parseAbi(["function unwrapWETH9(uint256 amountMinimum, address recipient) payable"]),
|
|
3096
|
-
functionName: "unwrapWETH9",
|
|
3097
|
-
args: [amountOutETH, recipient]
|
|
3098
|
-
});
|
|
3099
|
-
const multicallCalldata = encodeFunctionData({
|
|
3100
|
-
abi: parseAbi(["function multicall(bytes[] data) payable returns (bytes[])"]),
|
|
3101
|
-
functionName: "multicall",
|
|
3102
|
-
args: [[swapCalldata, unwrapCalldata]]
|
|
3103
|
-
});
|
|
3104
|
-
return multicallCalldata;
|
|
3105
|
-
}
|
|
3106
|
-
getRouterAddress() {
|
|
3107
|
-
return UNISWAP_V3_ROUTER;
|
|
3108
|
-
}
|
|
3109
|
-
getUSDCAddress() {
|
|
3110
|
-
return USDC_ADDRESS;
|
|
2201
|
+
return false;
|
|
3111
2202
|
}
|
|
3112
2203
|
};
|
|
3113
|
-
uniswapService = new UniswapService();
|
|
3114
2204
|
}
|
|
3115
2205
|
});
|
|
3116
2206
|
|
|
@@ -3119,7 +2209,7 @@ var TransferManager_exports = {};
|
|
|
3119
2209
|
__export(TransferManager_exports, {
|
|
3120
2210
|
TransferManager: () => TransferManager
|
|
3121
2211
|
});
|
|
3122
|
-
var
|
|
2212
|
+
var TransferManager;
|
|
3123
2213
|
var init_TransferManager = __esm({
|
|
3124
2214
|
"src/services/TransferManager.ts"() {
|
|
3125
2215
|
init_cctp2();
|
|
@@ -3127,23 +2217,7 @@ var init_TransferManager = __esm({
|
|
|
3127
2217
|
init_stargate();
|
|
3128
2218
|
init_stacks();
|
|
3129
2219
|
init_Router();
|
|
3130
|
-
|
|
3131
|
-
init_Optimism();
|
|
3132
|
-
OP_SEPOLIA_CONFIG = {
|
|
3133
|
-
chain: {
|
|
3134
|
-
id: 11155420,
|
|
3135
|
-
name: "Optimism Sepolia",
|
|
3136
|
-
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
3137
|
-
rpcUrls: { default: { http: ["https://sepolia.optimism.io"] } },
|
|
3138
|
-
testnet: true
|
|
3139
|
-
},
|
|
3140
|
-
rpcUrl: "https://sepolia.optimism.io",
|
|
3141
|
-
// bundlerUrl will be constructed dynamically
|
|
3142
|
-
entryPointAddress: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
|
|
3143
|
-
factoryAddress: "0xe2584152891E4769025807DEa0cD611F135aDC68",
|
|
3144
|
-
paymasterAddress: OPTIMISM.evm?.paymasterAddress,
|
|
3145
|
-
tokens: []
|
|
3146
|
-
};
|
|
2220
|
+
init_chainsInformation();
|
|
3147
2221
|
TransferManager = class {
|
|
3148
2222
|
constructor() {
|
|
3149
2223
|
this.strategies = [
|
|
@@ -3170,6 +2244,24 @@ var init_TransferManager = __esm({
|
|
|
3170
2244
|
}
|
|
3171
2245
|
};
|
|
3172
2246
|
}
|
|
2247
|
+
if (context.destChain === "Stacks" && context.sourceChain !== "Ethereum") {
|
|
2248
|
+
const networkConfig = NETWORKS[context.sourceChain];
|
|
2249
|
+
if (networkConfig?.crossChainInformation?.circleInformation?.cCTPInformation?.supportCCTP) {
|
|
2250
|
+
log(`[TransferManager] Auto-Routing to Stacks via CCTP (${context.sourceChain} -> Ethereum -> Stacks)`);
|
|
2251
|
+
if (!context.sourceAA || !context.facilitatorPrivateKey) {
|
|
2252
|
+
return {
|
|
2253
|
+
success: false,
|
|
2254
|
+
errorReason: "Source AA and Facilitator Private Key required for Stacks Multi-Hop"
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
return this.executeEVMToStacks(
|
|
2258
|
+
context,
|
|
2259
|
+
context.sourceAA,
|
|
2260
|
+
context.facilitatorPrivateKey,
|
|
2261
|
+
logCallback
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
3173
2265
|
const strategies = this.strategies;
|
|
3174
2266
|
const stargateStrategy = strategies.find((s) => s instanceof StargateStrategy);
|
|
3175
2267
|
if (stargateStrategy && stargateStrategy.canHandle(context)) {
|
|
@@ -3192,144 +2284,13 @@ var init_TransferManager = __esm({
|
|
|
3192
2284
|
};
|
|
3193
2285
|
}
|
|
3194
2286
|
/**
|
|
3195
|
-
*
|
|
3196
|
-
*
|
|
3197
|
-
*/
|
|
3198
|
-
async executeMultiHopDemo(context, sourceAA, logCallback, overrides) {
|
|
3199
|
-
const log = logCallback || console.log;
|
|
3200
|
-
log("[TransferManager] Calculating Multi-Hop Route...");
|
|
3201
|
-
context.sourceChain === "Base" && (sourceAA.getChainId() === 84532 || context.sourceChainId === 84532);
|
|
3202
|
-
const sourceChainId = await sourceAA.getPublicClient().getChainId();
|
|
3203
|
-
const isSepolia = sourceChainId === 84532;
|
|
3204
|
-
const baseUrl = overrides?.bundlerUrl || "https://bundler.1llet.xyz";
|
|
3205
|
-
let intermediateConfig;
|
|
3206
|
-
if (isSepolia) {
|
|
3207
|
-
log(`[TransferManager] Detected Testnet (Base Sepolia). Using Optimism Sepolia.`);
|
|
3208
|
-
intermediateConfig = {
|
|
3209
|
-
tokens: [],
|
|
3210
|
-
...OP_SEPOLIA_CONFIG,
|
|
3211
|
-
bundlerUrl: `${baseUrl}/rpc?chain=optimismSepolia`
|
|
3212
|
-
};
|
|
3213
|
-
getAddress("0x5fd84259d66Cd46123540766Be93DFE6D43130D7");
|
|
3214
|
-
} else {
|
|
3215
|
-
log(`[TransferManager] Detected Mainnet (Base). Using Optimism Mainnet.`);
|
|
3216
|
-
intermediateConfig = {
|
|
3217
|
-
tokens: [],
|
|
3218
|
-
// Default empty
|
|
3219
|
-
...OPTIMISM.evm,
|
|
3220
|
-
bundlerUrl: `${baseUrl}/rpc?chain=optimism`
|
|
3221
|
-
};
|
|
3222
|
-
if (!intermediateConfig.tokens) intermediateConfig.tokens = [];
|
|
3223
|
-
getAddress("0x0b2C639c533813f4Aa9D7837CAf992c92bdE5162");
|
|
3224
|
-
}
|
|
3225
|
-
const intermediateAA = new AccountAbstraction(intermediateConfig);
|
|
3226
|
-
await intermediateAA.connect();
|
|
3227
|
-
const interAddress = await intermediateAA.getSmartAccount();
|
|
3228
|
-
log(`[TransferManager] Refund Address (Intermediate SA): ${interAddress}`);
|
|
3229
|
-
const stargate = new StargateStrategy();
|
|
3230
|
-
const { getNearQuote: getNearQuote2 } = await Promise.resolve().then(() => (init_near(), near_exports));
|
|
3231
|
-
const { uniswapService: uniswapService2 } = await Promise.resolve().then(() => (init_uniswap(), uniswap_exports));
|
|
3232
|
-
const parsedInputAmount = parseUnits(context.amount, 6);
|
|
3233
|
-
const estimatedOpAmount = parsedInputAmount * 99n / 100n;
|
|
3234
|
-
const humanEstimatedOpAmount = formatUnits(estimatedOpAmount, 6);
|
|
3235
|
-
log(`[TransferManager] Planning Optimism -> Base (Near) for ~${humanEstimatedOpAmount} USDC...`);
|
|
3236
|
-
const nearQuote = await getNearQuote2(
|
|
3237
|
-
"Optimism",
|
|
3238
|
-
"Base",
|
|
3239
|
-
humanEstimatedOpAmount,
|
|
3240
|
-
"USDC",
|
|
3241
|
-
"USDC",
|
|
3242
|
-
context.recipient || context.senderAddress || await sourceAA.getSmartAccount(),
|
|
3243
|
-
// Final Recipient on Base
|
|
3244
|
-
interAddress
|
|
3245
|
-
// Refund Address on Optimism (Intermediate SA)
|
|
3246
|
-
);
|
|
3247
|
-
if (!nearQuote?.depositAddress) throw new Error("Planning Failed (No Near Deposit Address)");
|
|
3248
|
-
log(`[TransferManager] Near Deposit Address: ${nearQuote.depositAddress}`);
|
|
3249
|
-
log("[TransferManager] Planning Base -> Optimism (Stargate Direct)...");
|
|
3250
|
-
const sgResult = await stargate.execute({
|
|
3251
|
-
...context,
|
|
3252
|
-
destChain: "Optimism",
|
|
3253
|
-
recipient: nearQuote.depositAddress
|
|
3254
|
-
// DIRECTLY to Near
|
|
3255
|
-
});
|
|
3256
|
-
if (!sgResult.success) throw new Error(`Step 1 Planning Failed: ${sgResult.errorReason}`);
|
|
3257
|
-
const { txTarget, txData, txValue, approvalRequired } = sgResult.data;
|
|
3258
|
-
const buildStep1 = async () => {
|
|
3259
|
-
const requiredValue = BigInt(txValue || 0);
|
|
3260
|
-
let autoSwapTxs = [];
|
|
3261
|
-
if (requiredValue > 0n) {
|
|
3262
|
-
const nativeBalance = await sourceAA.getBalance("0x0000000000000000000000000000000000000000");
|
|
3263
|
-
if (nativeBalance < requiredValue) {
|
|
3264
|
-
log(`[TransferManager] Auto-Swap: Need ETH for fee.`);
|
|
3265
|
-
const missingETH = requiredValue * 110n / 100n - nativeBalance;
|
|
3266
|
-
const usdcNeeded = await uniswapService2.quoteUSDCForETH(missingETH);
|
|
3267
|
-
const routerAddress = uniswapService2.getRouterAddress();
|
|
3268
|
-
if (!routerAddress) throw new Error("Uniswap Router not found");
|
|
3269
|
-
const tokenAddr = getAddress(context.sourceToken === "USDC" ? "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" : "0x0");
|
|
3270
|
-
const parsedTransferAmount = parseUnits(context.amount, 6);
|
|
3271
|
-
const tokenBalance = await sourceAA.getBalance(tokenAddr);
|
|
3272
|
-
const totalRequiredUSDC = parsedTransferAmount + usdcNeeded;
|
|
3273
|
-
const amountToPull = totalRequiredUSDC > tokenBalance ? totalRequiredUSDC - tokenBalance : 0n;
|
|
3274
|
-
if (amountToPull > 0n) {
|
|
3275
|
-
const owner = sourceAA.getOwner();
|
|
3276
|
-
const smartAccount = await sourceAA.getSmartAccount();
|
|
3277
|
-
if (!owner || !smartAccount) throw new Error("Owner/SA not found for Pull");
|
|
3278
|
-
const allowance = await sourceAA.getAllowance(tokenAddr);
|
|
3279
|
-
if (allowance < amountToPull) throw new Error(`Auto-Swap: Approval needed (${formatUnits(amountToPull, 6)} USDC).`);
|
|
3280
|
-
log(`[TransferManager] Auto-Swap: Pulling ${formatUnits(amountToPull, 6)} USDC...`);
|
|
3281
|
-
autoSwapTxs.push({
|
|
3282
|
-
target: tokenAddr,
|
|
3283
|
-
data: encodeFunctionData({
|
|
3284
|
-
abi: erc20Abi$1,
|
|
3285
|
-
functionName: "transferFrom",
|
|
3286
|
-
args: [owner, smartAccount, amountToPull]
|
|
3287
|
-
}),
|
|
3288
|
-
value: 0n
|
|
3289
|
-
});
|
|
3290
|
-
}
|
|
3291
|
-
const swapData = uniswapService2.buildSwapData(await sourceAA.getSmartAccount(), missingETH, usdcNeeded);
|
|
3292
|
-
const approveSwapData = encodeFunctionData({
|
|
3293
|
-
abi: erc20Abi$1,
|
|
3294
|
-
functionName: "approve",
|
|
3295
|
-
args: [routerAddress, usdcNeeded]
|
|
3296
|
-
});
|
|
3297
|
-
autoSwapTxs.push({ target: tokenAddr, data: approveSwapData, value: 0n });
|
|
3298
|
-
autoSwapTxs.push({ target: routerAddress, data: swapData, value: 0n });
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
|
-
const batch = [...autoSwapTxs];
|
|
3302
|
-
if (approvalRequired) {
|
|
3303
|
-
batch.push({
|
|
3304
|
-
target: approvalRequired.target,
|
|
3305
|
-
data: approvalRequired.data,
|
|
3306
|
-
value: BigInt(approvalRequired.value || 0)
|
|
3307
|
-
});
|
|
3308
|
-
}
|
|
3309
|
-
batch.push({
|
|
3310
|
-
target: txTarget,
|
|
3311
|
-
data: txData,
|
|
3312
|
-
value: BigInt(txValue || 0)
|
|
3313
|
-
});
|
|
3314
|
-
return sourceAA.buildBatchUserOperation(batch);
|
|
3315
|
-
};
|
|
3316
|
-
const steps = [
|
|
3317
|
-
{
|
|
3318
|
-
aa: sourceAA,
|
|
3319
|
-
buildUserOp: buildStep1,
|
|
3320
|
-
description: "Base -> Optimism (Stargate -> Near Direct)"
|
|
3321
|
-
}
|
|
3322
|
-
];
|
|
3323
|
-
return this.router.executeMultiHop(steps, log);
|
|
3324
|
-
}
|
|
3325
|
-
/**
|
|
3326
|
-
* Specialized method for Unichain -> Stacks flow.
|
|
3327
|
-
* 1. Unichain -> Ethereum (CCTP)
|
|
2287
|
+
* Specialized method for Base/Unichain -> Stacks flow.
|
|
2288
|
+
* 1. EVM (Base/Unichain) -> Ethereum (CCTP)
|
|
3328
2289
|
* 2. Ethereum -> Stacks (Stacks Bridge)
|
|
3329
2290
|
*/
|
|
3330
|
-
async
|
|
2291
|
+
async executeEVMToStacks(context, sourceAA, ethPrivateKey, logCallback) {
|
|
3331
2292
|
const log = logCallback || console.log;
|
|
3332
|
-
log(
|
|
2293
|
+
log(`[TransferManager] Starting ${context.sourceChain} -> Stacks Flow...`);
|
|
3333
2294
|
const cctpStrategy = this.strategies.find((s) => s instanceof CCTPStrategy);
|
|
3334
2295
|
const stacksStrategy = this.strategies.find((s) => s instanceof StacksStrategy);
|
|
3335
2296
|
if (context.sourceChain === "Ethereum") {
|
|
@@ -3339,70 +2300,930 @@ var init_TransferManager = __esm({
|
|
|
3339
2300
|
facilitatorPrivateKey: ethPrivateKey
|
|
3340
2301
|
});
|
|
3341
2302
|
}
|
|
3342
|
-
|
|
3343
|
-
|
|
2303
|
+
const sourceConfig = NETWORKS[context.sourceChain];
|
|
2304
|
+
const supportsCCTP = sourceConfig?.crossChainInformation?.circleInformation?.cCTPInformation?.supportCCTP;
|
|
2305
|
+
if (supportsCCTP) {
|
|
2306
|
+
log(`[TransferManager] Step 1: ${context.sourceChain} -> Ethereum (via CCTP)`);
|
|
3344
2307
|
const ethAccount = privateKeyToAccount(ethPrivateKey);
|
|
3345
2308
|
const ethAddress = ethAccount.address;
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
2309
|
+
log(`[TransferManager] Funding Facilitator (${ethAddress})...`);
|
|
2310
|
+
try {
|
|
2311
|
+
const amountBigInt = parseUnits(context.amount, 6);
|
|
2312
|
+
const executeFunding = async () => {
|
|
2313
|
+
try {
|
|
2314
|
+
return await sourceAA.smartTransfer(
|
|
2315
|
+
"USDC",
|
|
2316
|
+
ethAddress,
|
|
2317
|
+
amountBigInt
|
|
2318
|
+
);
|
|
2319
|
+
} catch (e) {
|
|
2320
|
+
const msg = e.message || String(e);
|
|
2321
|
+
if (msg.includes("Approval required")) {
|
|
2322
|
+
log("[TransferManager] Approval Required. Approving USDC...");
|
|
2323
|
+
const saAddress = await sourceAA.getSmartAccount();
|
|
2324
|
+
if (!saAddress) throw new Error("SA Address missing for approval");
|
|
2325
|
+
await sourceAA.approveToken(sourceAA.getTokenAddress("USDC"), saAddress, amountBigInt);
|
|
2326
|
+
log("[TransferManager] Approved. Retrying Transfer...");
|
|
2327
|
+
return await sourceAA.smartTransfer(
|
|
2328
|
+
"USDC",
|
|
2329
|
+
ethAddress,
|
|
2330
|
+
amountBigInt
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
throw e;
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
const transferResult = await executeFunding();
|
|
2337
|
+
if ("success" in transferResult && !transferResult.success) {
|
|
2338
|
+
throw new Error("Funding Transfer Failed");
|
|
2339
|
+
}
|
|
2340
|
+
const fundingHash = transferResult.receipt.transactionHash;
|
|
2341
|
+
log(`[TransferManager] Funds Sent. Hash: ${fundingHash}`);
|
|
2342
|
+
const cctpResult = await cctpStrategy.execute({
|
|
3362
2343
|
...context,
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
2344
|
+
destChain: "Ethereum",
|
|
2345
|
+
destToken: "USDC",
|
|
2346
|
+
recipient: ethAddress,
|
|
2347
|
+
facilitatorPrivateKey: ethPrivateKey,
|
|
2348
|
+
depositTxHash: fundingHash
|
|
3368
2349
|
});
|
|
2350
|
+
if (!cctpResult.success) {
|
|
2351
|
+
return cctpResult;
|
|
2352
|
+
}
|
|
2353
|
+
log("[TransferManager] CCTP Step Initiated.");
|
|
2354
|
+
if (cctpResult.mintTransactionHash) {
|
|
2355
|
+
log("[TransferManager] CCTP Complete (Funds on Ethereum). Proceeding to Stacks Step...");
|
|
2356
|
+
const stacksResult = await stacksStrategy.execute({
|
|
2357
|
+
...context,
|
|
2358
|
+
sourceChain: "Ethereum",
|
|
2359
|
+
amount: cctpResult.netAmount ? formatUnits(BigInt(cctpResult.netAmount), 6) : context.amount,
|
|
2360
|
+
facilitatorPrivateKey: ethPrivateKey
|
|
2361
|
+
});
|
|
2362
|
+
return {
|
|
2363
|
+
success: stacksResult.success,
|
|
2364
|
+
transactionHash: stacksResult.transactionHash,
|
|
2365
|
+
data: {
|
|
2366
|
+
fundingStep: { transactionHash: fundingHash },
|
|
2367
|
+
cctpStep: cctpResult,
|
|
2368
|
+
stacksStep: stacksResult
|
|
2369
|
+
},
|
|
2370
|
+
netAmount: stacksResult.netAmount,
|
|
2371
|
+
errorReason: stacksResult.errorReason
|
|
2372
|
+
};
|
|
2373
|
+
} else {
|
|
2374
|
+
log("[TransferManager] CCTP Pending. Please resume later.");
|
|
2375
|
+
return {
|
|
2376
|
+
...cctpResult,
|
|
2377
|
+
attestation: {
|
|
2378
|
+
message: "CCTP Initiated but not finalized.",
|
|
2379
|
+
attestation: cctpResult.attestation?.attestation || ""
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
} catch (err) {
|
|
3369
2384
|
return {
|
|
3370
|
-
success:
|
|
3371
|
-
|
|
3372
|
-
// The final Stacks Tx
|
|
3373
|
-
data: {
|
|
3374
|
-
cctpStep: cctpResult,
|
|
3375
|
-
stacksStep: stacksResult
|
|
3376
|
-
},
|
|
3377
|
-
netAmount: stacksResult.netAmount,
|
|
3378
|
-
errorReason: stacksResult.errorReason
|
|
3379
|
-
};
|
|
3380
|
-
} else {
|
|
3381
|
-
log("[TransferManager] CCTP Pending (Attestation/Mint not final yet). Please resume later.");
|
|
3382
|
-
return {
|
|
3383
|
-
...cctpResult,
|
|
3384
|
-
attestation: {
|
|
3385
|
-
message: "CCTP Initiated but not finalized (Timeout or Pending). Resume with sourceChain='Ethereum' once funds arrive.",
|
|
3386
|
-
attestation: cctpResult.attestation?.attestation || ""
|
|
3387
|
-
}
|
|
2385
|
+
success: false,
|
|
2386
|
+
errorReason: `Funding/CCTP Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3388
2387
|
};
|
|
3389
2388
|
}
|
|
3390
2389
|
}
|
|
3391
2390
|
return {
|
|
3392
2391
|
success: false,
|
|
3393
|
-
errorReason:
|
|
2392
|
+
errorReason: `Unsupported Route for ${context.sourceChain}->Stacks Helper`
|
|
3394
2393
|
};
|
|
3395
2394
|
}
|
|
3396
2395
|
};
|
|
3397
2396
|
}
|
|
3398
2397
|
});
|
|
3399
2398
|
|
|
2399
|
+
// src/constants.ts
|
|
2400
|
+
var factoryAbi = [
|
|
2401
|
+
{
|
|
2402
|
+
inputs: [
|
|
2403
|
+
{ name: "owner", type: "address" },
|
|
2404
|
+
{ name: "salt", type: "uint256" }
|
|
2405
|
+
],
|
|
2406
|
+
name: "getAccountAddress",
|
|
2407
|
+
outputs: [{ name: "", type: "address" }],
|
|
2408
|
+
stateMutability: "view",
|
|
2409
|
+
type: "function"
|
|
2410
|
+
},
|
|
2411
|
+
{
|
|
2412
|
+
inputs: [
|
|
2413
|
+
{ name: "owner", type: "address" },
|
|
2414
|
+
{ name: "salt", type: "uint256" }
|
|
2415
|
+
],
|
|
2416
|
+
name: "isAccountDeployed",
|
|
2417
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2418
|
+
stateMutability: "view",
|
|
2419
|
+
type: "function"
|
|
2420
|
+
},
|
|
2421
|
+
{
|
|
2422
|
+
inputs: [
|
|
2423
|
+
{ name: "owner", type: "address" },
|
|
2424
|
+
{ name: "salt", type: "uint256" }
|
|
2425
|
+
],
|
|
2426
|
+
name: "createAccount",
|
|
2427
|
+
outputs: [{ name: "account", type: "address" }],
|
|
2428
|
+
stateMutability: "nonpayable",
|
|
2429
|
+
type: "function"
|
|
2430
|
+
}
|
|
2431
|
+
];
|
|
2432
|
+
var entryPointAbi = [
|
|
2433
|
+
{
|
|
2434
|
+
inputs: [
|
|
2435
|
+
{ name: "sender", type: "address" },
|
|
2436
|
+
{ name: "key", type: "uint192" }
|
|
2437
|
+
],
|
|
2438
|
+
name: "getNonce",
|
|
2439
|
+
outputs: [{ name: "nonce", type: "uint256" }],
|
|
2440
|
+
stateMutability: "view",
|
|
2441
|
+
type: "function"
|
|
2442
|
+
}
|
|
2443
|
+
];
|
|
2444
|
+
var smartAccountAbi = [
|
|
2445
|
+
{
|
|
2446
|
+
inputs: [
|
|
2447
|
+
{ name: "target", type: "address" },
|
|
2448
|
+
{ name: "value", type: "uint256" },
|
|
2449
|
+
{ name: "data", type: "bytes" }
|
|
2450
|
+
],
|
|
2451
|
+
name: "execute",
|
|
2452
|
+
outputs: [],
|
|
2453
|
+
stateMutability: "nonpayable",
|
|
2454
|
+
type: "function"
|
|
2455
|
+
},
|
|
2456
|
+
{
|
|
2457
|
+
inputs: [
|
|
2458
|
+
{ name: "targets", type: "address[]" },
|
|
2459
|
+
{ name: "values", type: "uint256[]" },
|
|
2460
|
+
{ name: "datas", type: "bytes[]" }
|
|
2461
|
+
],
|
|
2462
|
+
name: "executeBatch",
|
|
2463
|
+
outputs: [],
|
|
2464
|
+
stateMutability: "nonpayable",
|
|
2465
|
+
type: "function"
|
|
2466
|
+
}
|
|
2467
|
+
];
|
|
2468
|
+
var erc20Abi = [
|
|
2469
|
+
{
|
|
2470
|
+
inputs: [{ name: "account", type: "address" }],
|
|
2471
|
+
name: "balanceOf",
|
|
2472
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
2473
|
+
stateMutability: "view",
|
|
2474
|
+
type: "function"
|
|
2475
|
+
},
|
|
2476
|
+
{
|
|
2477
|
+
inputs: [
|
|
2478
|
+
{ name: "to", type: "address" },
|
|
2479
|
+
{ name: "amount", type: "uint256" }
|
|
2480
|
+
],
|
|
2481
|
+
name: "transfer",
|
|
2482
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2483
|
+
stateMutability: "nonpayable",
|
|
2484
|
+
type: "function"
|
|
2485
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
inputs: [
|
|
2488
|
+
{ name: "spender", type: "address" },
|
|
2489
|
+
{ name: "amount", type: "uint256" }
|
|
2490
|
+
],
|
|
2491
|
+
name: "approve",
|
|
2492
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2493
|
+
stateMutability: "nonpayable",
|
|
2494
|
+
type: "function"
|
|
2495
|
+
},
|
|
2496
|
+
{
|
|
2497
|
+
inputs: [
|
|
2498
|
+
{ name: "owner", type: "address" },
|
|
2499
|
+
{ name: "spender", type: "address" }
|
|
2500
|
+
],
|
|
2501
|
+
name: "allowance",
|
|
2502
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
2503
|
+
stateMutability: "view",
|
|
2504
|
+
type: "function"
|
|
2505
|
+
},
|
|
2506
|
+
{
|
|
2507
|
+
inputs: [
|
|
2508
|
+
{ name: "from", type: "address" },
|
|
2509
|
+
{ name: "to", type: "address" },
|
|
2510
|
+
{ name: "amount", type: "uint256" }
|
|
2511
|
+
],
|
|
2512
|
+
name: "transferFrom",
|
|
2513
|
+
outputs: [{ name: "", type: "bool" }],
|
|
2514
|
+
stateMutability: "nonpayable",
|
|
2515
|
+
type: "function"
|
|
2516
|
+
},
|
|
2517
|
+
{
|
|
2518
|
+
inputs: [],
|
|
2519
|
+
name: "decimals",
|
|
2520
|
+
outputs: [{ name: "", type: "uint8" }],
|
|
2521
|
+
stateMutability: "view",
|
|
2522
|
+
type: "function"
|
|
2523
|
+
}
|
|
2524
|
+
];
|
|
2525
|
+
|
|
2526
|
+
// src/BundlerClient.ts
|
|
2527
|
+
var BundlerClient = class {
|
|
2528
|
+
constructor(config, entryPointAddress) {
|
|
2529
|
+
this.bundlerUrl = config.bundlerUrl;
|
|
2530
|
+
this.entryPointAddress = entryPointAddress;
|
|
2531
|
+
}
|
|
2532
|
+
async call(method, params) {
|
|
2533
|
+
const response = await fetch(this.bundlerUrl, {
|
|
2534
|
+
method: "POST",
|
|
2535
|
+
headers: { "Content-Type": "application/json" },
|
|
2536
|
+
body: JSON.stringify({
|
|
2537
|
+
jsonrpc: "2.0",
|
|
2538
|
+
id: 1,
|
|
2539
|
+
method,
|
|
2540
|
+
params
|
|
2541
|
+
})
|
|
2542
|
+
});
|
|
2543
|
+
const result = await response.json();
|
|
2544
|
+
if (result.error) {
|
|
2545
|
+
throw new Error(result.error.message);
|
|
2546
|
+
}
|
|
2547
|
+
return result.result;
|
|
2548
|
+
}
|
|
2549
|
+
async estimateGas(userOp) {
|
|
2550
|
+
const result = await this.call("eth_estimateUserOperationGas", [
|
|
2551
|
+
{
|
|
2552
|
+
sender: userOp.sender,
|
|
2553
|
+
nonce: userOp.nonce ? "0x" + userOp.nonce.toString(16) : "0x0",
|
|
2554
|
+
initCode: userOp.initCode || "0x",
|
|
2555
|
+
callData: userOp.callData || "0x",
|
|
2556
|
+
paymasterAndData: userOp.paymasterAndData || "0x",
|
|
2557
|
+
signature: "0x"
|
|
2558
|
+
},
|
|
2559
|
+
this.entryPointAddress
|
|
2560
|
+
]);
|
|
2561
|
+
console.log("DEBUG: estimateGas result:", result);
|
|
2562
|
+
return {
|
|
2563
|
+
callGasLimit: result.callGasLimit,
|
|
2564
|
+
verificationGasLimit: result.verificationGasLimit,
|
|
2565
|
+
preVerificationGas: result.preVerificationGas,
|
|
2566
|
+
maxFeePerGas: result.maxFeePerGas,
|
|
2567
|
+
maxPriorityFeePerGas: result.maxPriorityFeePerGas,
|
|
2568
|
+
paymasterAndData: result.paymasterAndData
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
async sendUserOperation(userOp) {
|
|
2572
|
+
return await this.call("eth_sendUserOperation", [
|
|
2573
|
+
{
|
|
2574
|
+
sender: userOp.sender,
|
|
2575
|
+
nonce: "0x" + userOp.nonce.toString(16),
|
|
2576
|
+
initCode: userOp.initCode,
|
|
2577
|
+
callData: userOp.callData,
|
|
2578
|
+
callGasLimit: "0x" + userOp.callGasLimit.toString(16),
|
|
2579
|
+
verificationGasLimit: "0x" + userOp.verificationGasLimit.toString(16),
|
|
2580
|
+
preVerificationGas: "0x" + userOp.preVerificationGas.toString(16),
|
|
2581
|
+
maxFeePerGas: "0x" + userOp.maxFeePerGas.toString(16),
|
|
2582
|
+
maxPriorityFeePerGas: "0x" + userOp.maxPriorityFeePerGas.toString(16),
|
|
2583
|
+
paymasterAndData: userOp.paymasterAndData,
|
|
2584
|
+
signature: userOp.signature
|
|
2585
|
+
},
|
|
2586
|
+
this.entryPointAddress
|
|
2587
|
+
]);
|
|
2588
|
+
}
|
|
2589
|
+
async waitForUserOperation(userOpHash, timeout = 6e4) {
|
|
2590
|
+
const startTime = Date.now();
|
|
2591
|
+
while (Date.now() - startTime < timeout) {
|
|
2592
|
+
const result = await this.call("eth_getUserOperationReceipt", [userOpHash]);
|
|
2593
|
+
if (result) {
|
|
2594
|
+
return result;
|
|
2595
|
+
}
|
|
2596
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
2597
|
+
}
|
|
2598
|
+
throw new Error("Timeout waiting for UserOperation");
|
|
2599
|
+
}
|
|
2600
|
+
async requestApprovalSupport(token, owner, spender, amount) {
|
|
2601
|
+
return await this.call("pm_requestApprovalSupport", [
|
|
2602
|
+
token,
|
|
2603
|
+
owner,
|
|
2604
|
+
spender,
|
|
2605
|
+
amount.toString()
|
|
2606
|
+
]);
|
|
2607
|
+
}
|
|
2608
|
+
async scheduleUserOp(userOp, condition) {
|
|
2609
|
+
return await this.call("bundler_scheduleUserOp", [
|
|
2610
|
+
{
|
|
2611
|
+
sender: userOp.sender,
|
|
2612
|
+
nonce: "0x" + userOp.nonce.toString(16),
|
|
2613
|
+
initCode: userOp.initCode,
|
|
2614
|
+
callData: userOp.callData,
|
|
2615
|
+
callGasLimit: "0x" + userOp.callGasLimit.toString(16),
|
|
2616
|
+
verificationGasLimit: "0x" + userOp.verificationGasLimit.toString(16),
|
|
2617
|
+
preVerificationGas: "0x" + userOp.preVerificationGas.toString(16),
|
|
2618
|
+
maxFeePerGas: "0x" + userOp.maxFeePerGas.toString(16),
|
|
2619
|
+
maxPriorityFeePerGas: "0x" + userOp.maxPriorityFeePerGas.toString(16),
|
|
2620
|
+
paymasterAndData: userOp.paymasterAndData,
|
|
2621
|
+
signature: userOp.signature
|
|
2622
|
+
},
|
|
2623
|
+
condition
|
|
2624
|
+
]);
|
|
2625
|
+
}
|
|
2626
|
+
};
|
|
2627
|
+
var TokenService = class {
|
|
2628
|
+
constructor(chainConfig, publicClient) {
|
|
2629
|
+
this.tokens = /* @__PURE__ */ new Map();
|
|
2630
|
+
this.publicClient = publicClient;
|
|
2631
|
+
chainConfig.tokens.forEach((token) => {
|
|
2632
|
+
this.tokens.set(token.symbol.toUpperCase(), token);
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Resolve token address from symbol or return address if provided
|
|
2637
|
+
*/
|
|
2638
|
+
getTokenAddress(token) {
|
|
2639
|
+
if (token.startsWith("0x")) return token;
|
|
2640
|
+
const info = this.tokens.get(token.toUpperCase());
|
|
2641
|
+
if (!info) throw new Error(`Token ${token} not found in chain config`);
|
|
2642
|
+
return info.address;
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Get balance of a token for an account
|
|
2646
|
+
*/
|
|
2647
|
+
async getBalance(token, account) {
|
|
2648
|
+
const address = this.getTokenAddress(token);
|
|
2649
|
+
if (address === "0x0000000000000000000000000000000000000000") {
|
|
2650
|
+
return await this.publicClient.getBalance({ address: account });
|
|
2651
|
+
}
|
|
2652
|
+
return await this.publicClient.readContract({
|
|
2653
|
+
address,
|
|
2654
|
+
abi: erc20Abi,
|
|
2655
|
+
functionName: "balanceOf",
|
|
2656
|
+
args: [account]
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Get allowance (ERC-20 only)
|
|
2661
|
+
*/
|
|
2662
|
+
async getAllowance(token, owner, spender) {
|
|
2663
|
+
const address = this.getTokenAddress(token);
|
|
2664
|
+
if (address === "0x0000000000000000000000000000000000000000") {
|
|
2665
|
+
return 0n;
|
|
2666
|
+
}
|
|
2667
|
+
return await this.publicClient.readContract({
|
|
2668
|
+
address,
|
|
2669
|
+
abi: erc20Abi,
|
|
2670
|
+
functionName: "allowance",
|
|
2671
|
+
args: [owner, spender]
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
/**
|
|
2675
|
+
* Encode transfer data
|
|
2676
|
+
*/
|
|
2677
|
+
encodeTransfer(recipient, amount) {
|
|
2678
|
+
return encodeFunctionData({
|
|
2679
|
+
abi: erc20Abi,
|
|
2680
|
+
functionName: "transfer",
|
|
2681
|
+
args: [recipient, amount]
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
/**
|
|
2685
|
+
* Encode transferFrom data
|
|
2686
|
+
*/
|
|
2687
|
+
encodeTransferFrom(sender, recipient, amount) {
|
|
2688
|
+
return encodeFunctionData({
|
|
2689
|
+
abi: erc20Abi,
|
|
2690
|
+
functionName: "transferFrom",
|
|
2691
|
+
args: [sender, recipient, amount]
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Encode approve data
|
|
2696
|
+
*/
|
|
2697
|
+
encodeApprove(spender, amount) {
|
|
2698
|
+
return encodeFunctionData({
|
|
2699
|
+
abi: erc20Abi,
|
|
2700
|
+
functionName: "approve",
|
|
2701
|
+
args: [spender, amount]
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
var UserOpBuilder = class {
|
|
2706
|
+
constructor(chainConfig, bundlerClient, publicClient) {
|
|
2707
|
+
this.chainConfig = chainConfig;
|
|
2708
|
+
this.bundlerClient = bundlerClient;
|
|
2709
|
+
this.publicClient = publicClient;
|
|
2710
|
+
this.entryPointAddress = chainConfig.entryPointAddress;
|
|
2711
|
+
this.factoryAddress = chainConfig.factoryAddress;
|
|
2712
|
+
}
|
|
2713
|
+
async getNonce(smartAccountAddress) {
|
|
2714
|
+
return await this.publicClient.readContract({
|
|
2715
|
+
address: this.entryPointAddress,
|
|
2716
|
+
abi: entryPointAbi,
|
|
2717
|
+
functionName: "getNonce",
|
|
2718
|
+
args: [smartAccountAddress, 0n]
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
buildInitCode(owner) {
|
|
2722
|
+
const createAccountData = encodeFunctionData({
|
|
2723
|
+
abi: factoryAbi,
|
|
2724
|
+
functionName: "createAccount",
|
|
2725
|
+
args: [owner, 0n]
|
|
2726
|
+
});
|
|
2727
|
+
return `${this.factoryAddress}${createAccountData.slice(2)}`;
|
|
2728
|
+
}
|
|
2729
|
+
async isAccountDeployed(smartAccountAddress) {
|
|
2730
|
+
const code = await this.publicClient.getCode({
|
|
2731
|
+
address: smartAccountAddress
|
|
2732
|
+
});
|
|
2733
|
+
return code !== void 0 && code !== "0x";
|
|
2734
|
+
}
|
|
2735
|
+
async buildUserOperationBatch(owner, smartAccountAddress, transactions) {
|
|
2736
|
+
const isDeployed = await this.isAccountDeployed(smartAccountAddress);
|
|
2737
|
+
const initCode = isDeployed ? "0x" : this.buildInitCode(owner);
|
|
2738
|
+
const targets = transactions.map((tx) => tx.target);
|
|
2739
|
+
const values = transactions.map((tx) => tx.value);
|
|
2740
|
+
const datas = transactions.map((tx) => tx.data);
|
|
2741
|
+
const callData = encodeFunctionData({
|
|
2742
|
+
abi: smartAccountAbi,
|
|
2743
|
+
functionName: "executeBatch",
|
|
2744
|
+
args: [targets, values, datas]
|
|
2745
|
+
});
|
|
2746
|
+
const nonce = await this.getNonce(smartAccountAddress);
|
|
2747
|
+
const partialOp = {
|
|
2748
|
+
sender: smartAccountAddress,
|
|
2749
|
+
nonce,
|
|
2750
|
+
initCode,
|
|
2751
|
+
callData,
|
|
2752
|
+
paymasterAndData: this.chainConfig.paymasterAddress || "0x"
|
|
2753
|
+
};
|
|
2754
|
+
const gasEstimate = await this.bundlerClient.estimateGas(partialOp);
|
|
2755
|
+
return {
|
|
2756
|
+
...partialOp,
|
|
2757
|
+
callGasLimit: BigInt(gasEstimate.callGasLimit),
|
|
2758
|
+
verificationGasLimit: BigInt(gasEstimate.verificationGasLimit),
|
|
2759
|
+
preVerificationGas: BigInt(gasEstimate.preVerificationGas),
|
|
2760
|
+
maxFeePerGas: BigInt(gasEstimate.maxFeePerGas),
|
|
2761
|
+
maxPriorityFeePerGas: BigInt(gasEstimate.maxPriorityFeePerGas),
|
|
2762
|
+
paymasterAndData: gasEstimate.paymasterAndData || partialOp.paymasterAndData,
|
|
2763
|
+
signature: "0x"
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
async buildDeployUserOp(owner, smartAccountAddress) {
|
|
2767
|
+
const isDeployed = await this.isAccountDeployed(smartAccountAddress);
|
|
2768
|
+
if (isDeployed) throw new Error("Account already deployed");
|
|
2769
|
+
const initCode = this.buildInitCode(owner);
|
|
2770
|
+
const callData = "0x";
|
|
2771
|
+
const nonce = await this.getNonce(smartAccountAddress);
|
|
2772
|
+
const partialOp = {
|
|
2773
|
+
sender: smartAccountAddress,
|
|
2774
|
+
nonce,
|
|
2775
|
+
initCode,
|
|
2776
|
+
callData,
|
|
2777
|
+
paymasterAndData: this.chainConfig.paymasterAddress || "0x"
|
|
2778
|
+
};
|
|
2779
|
+
const gasEstimate = await this.bundlerClient.estimateGas(partialOp);
|
|
2780
|
+
return {
|
|
2781
|
+
...partialOp,
|
|
2782
|
+
callGasLimit: BigInt(gasEstimate.callGasLimit),
|
|
2783
|
+
verificationGasLimit: BigInt(gasEstimate.verificationGasLimit),
|
|
2784
|
+
preVerificationGas: BigInt(gasEstimate.preVerificationGas),
|
|
2785
|
+
maxFeePerGas: BigInt(gasEstimate.maxFeePerGas),
|
|
2786
|
+
maxPriorityFeePerGas: BigInt(gasEstimate.maxPriorityFeePerGas),
|
|
2787
|
+
paymasterAndData: gasEstimate.paymasterAndData || partialOp.paymasterAndData,
|
|
2788
|
+
signature: "0x"
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
getUserOpHash(userOp) {
|
|
2792
|
+
const packed = encodeAbiParameters(
|
|
2793
|
+
[
|
|
2794
|
+
{ type: "address" },
|
|
2795
|
+
{ type: "uint256" },
|
|
2796
|
+
{ type: "bytes32" },
|
|
2797
|
+
{ type: "bytes32" },
|
|
2798
|
+
{ type: "uint256" },
|
|
2799
|
+
{ type: "uint256" },
|
|
2800
|
+
{ type: "uint256" },
|
|
2801
|
+
{ type: "uint256" },
|
|
2802
|
+
{ type: "uint256" },
|
|
2803
|
+
{ type: "bytes32" }
|
|
2804
|
+
],
|
|
2805
|
+
[
|
|
2806
|
+
userOp.sender,
|
|
2807
|
+
userOp.nonce,
|
|
2808
|
+
keccak256(userOp.initCode),
|
|
2809
|
+
keccak256(userOp.callData),
|
|
2810
|
+
userOp.callGasLimit,
|
|
2811
|
+
userOp.verificationGasLimit,
|
|
2812
|
+
userOp.preVerificationGas,
|
|
2813
|
+
userOp.maxFeePerGas,
|
|
2814
|
+
userOp.maxPriorityFeePerGas,
|
|
2815
|
+
keccak256(userOp.paymasterAndData)
|
|
2816
|
+
]
|
|
2817
|
+
);
|
|
2818
|
+
const packedHash = keccak256(packed);
|
|
2819
|
+
return keccak256(
|
|
2820
|
+
encodeAbiParameters(
|
|
2821
|
+
[{ type: "bytes32" }, { type: "address" }, { type: "uint256" }],
|
|
2822
|
+
[packedHash, this.entryPointAddress, BigInt(this.chainConfig.chain.id)]
|
|
2823
|
+
)
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2828
|
+
// src/AccountAbstraction.ts
|
|
2829
|
+
var AccountAbstraction = class {
|
|
2830
|
+
constructor(chainConfig) {
|
|
2831
|
+
this.owner = null;
|
|
2832
|
+
this.smartAccountAddress = null;
|
|
2833
|
+
this.walletClient = null;
|
|
2834
|
+
this.chainConfig = chainConfig;
|
|
2835
|
+
if (!chainConfig.entryPointAddress) throw new Error("EntryPoint address required");
|
|
2836
|
+
this.entryPointAddress = chainConfig.entryPointAddress;
|
|
2837
|
+
if (!chainConfig.factoryAddress) throw new Error("Factory address required");
|
|
2838
|
+
this.factoryAddress = chainConfig.factoryAddress;
|
|
2839
|
+
const rpcUrl = chainConfig.rpcUrl || chainConfig.chain.rpcUrls.default.http[0];
|
|
2840
|
+
this.publicClient = createPublicClient({
|
|
2841
|
+
chain: chainConfig.chain,
|
|
2842
|
+
transport: http(rpcUrl)
|
|
2843
|
+
});
|
|
2844
|
+
this.bundlerClient = new BundlerClient(chainConfig, this.entryPointAddress);
|
|
2845
|
+
this.tokenService = new TokenService(chainConfig, this.publicClient);
|
|
2846
|
+
this.userOpBuilder = new UserOpBuilder(chainConfig, this.bundlerClient, this.publicClient);
|
|
2847
|
+
}
|
|
2848
|
+
getBundlerClient() {
|
|
2849
|
+
return this.bundlerClient;
|
|
2850
|
+
}
|
|
2851
|
+
getChainId() {
|
|
2852
|
+
return this.chainConfig.chain.id;
|
|
2853
|
+
}
|
|
2854
|
+
getPublicClient() {
|
|
2855
|
+
return this.publicClient;
|
|
2856
|
+
}
|
|
2857
|
+
async buildUserOperation(transaction) {
|
|
2858
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
2859
|
+
return this.userOpBuilder.buildUserOperationBatch(this.owner, this.smartAccountAddress, [transaction]);
|
|
2860
|
+
}
|
|
2861
|
+
async buildBatchUserOperation(transactions) {
|
|
2862
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
2863
|
+
return this.userOpBuilder.buildUserOperationBatch(this.owner, this.smartAccountAddress, transactions);
|
|
2864
|
+
}
|
|
2865
|
+
async connect(signer) {
|
|
2866
|
+
if (typeof signer === "string") {
|
|
2867
|
+
const account = privateKeyToAccount(signer);
|
|
2868
|
+
this.owner = account.address;
|
|
2869
|
+
const rpcUrl = this.chainConfig.rpcUrl || this.chainConfig.chain.rpcUrls.default.http[0];
|
|
2870
|
+
this.walletClient = createWalletClient({
|
|
2871
|
+
account,
|
|
2872
|
+
chain: this.chainConfig.chain,
|
|
2873
|
+
transport: http(rpcUrl)
|
|
2874
|
+
});
|
|
2875
|
+
} else if (signer && typeof signer === "object") {
|
|
2876
|
+
this.walletClient = signer;
|
|
2877
|
+
if (!this.walletClient.account) throw new Error("WalletClient must have an account");
|
|
2878
|
+
this.owner = this.walletClient.account.address;
|
|
2879
|
+
} else {
|
|
2880
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
2881
|
+
throw new Error("MetaMask is not installed and no private key provided");
|
|
2882
|
+
}
|
|
2883
|
+
const accounts = await window.ethereum.request({
|
|
2884
|
+
method: "eth_requestAccounts"
|
|
2885
|
+
});
|
|
2886
|
+
if (!accounts || accounts.length === 0) throw new Error("No accounts found");
|
|
2887
|
+
const chainId = await window.ethereum.request({
|
|
2888
|
+
method: "eth_chainId"
|
|
2889
|
+
});
|
|
2890
|
+
const targetChainId = this.chainConfig.chain.id;
|
|
2891
|
+
if (parseInt(chainId, 16) !== targetChainId) {
|
|
2892
|
+
try {
|
|
2893
|
+
await window.ethereum.request({
|
|
2894
|
+
method: "wallet_switchEthereumChain",
|
|
2895
|
+
params: [{ chainId: "0x" + targetChainId.toString(16) }]
|
|
2896
|
+
});
|
|
2897
|
+
} catch (switchError) {
|
|
2898
|
+
const error = switchError;
|
|
2899
|
+
if (error.code === 4902) {
|
|
2900
|
+
await window.ethereum.request({
|
|
2901
|
+
method: "wallet_addEthereumChain",
|
|
2902
|
+
params: [
|
|
2903
|
+
{
|
|
2904
|
+
chainId: "0x" + targetChainId.toString(16),
|
|
2905
|
+
chainName: this.chainConfig.chain.name,
|
|
2906
|
+
nativeCurrency: this.chainConfig.chain.nativeCurrency,
|
|
2907
|
+
rpcUrls: [this.chainConfig.rpcUrl || this.chainConfig.chain.rpcUrls.default.http[0]],
|
|
2908
|
+
blockExplorerUrls: this.chainConfig.chain.blockExplorers?.default?.url ? [this.chainConfig.chain.blockExplorers.default.url] : []
|
|
2909
|
+
}
|
|
2910
|
+
]
|
|
2911
|
+
});
|
|
2912
|
+
} else {
|
|
2913
|
+
throw switchError;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
this.owner = accounts[0];
|
|
2918
|
+
}
|
|
2919
|
+
this.smartAccountAddress = await this.getSmartAccountAddress(this.owner);
|
|
2920
|
+
return {
|
|
2921
|
+
owner: this.owner,
|
|
2922
|
+
smartAccount: this.smartAccountAddress
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
// --- Account Management ---
|
|
2926
|
+
async isAccountDeployed() {
|
|
2927
|
+
if (!this.smartAccountAddress) return false;
|
|
2928
|
+
const code = await this.publicClient.getBytecode({ address: this.smartAccountAddress });
|
|
2929
|
+
return code !== void 0;
|
|
2930
|
+
}
|
|
2931
|
+
async deployAccount() {
|
|
2932
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
2933
|
+
return this.sendTransaction({
|
|
2934
|
+
target: this.smartAccountAddress,
|
|
2935
|
+
value: 0n,
|
|
2936
|
+
data: "0x"
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Get the Smart Account address for an owner
|
|
2941
|
+
*/
|
|
2942
|
+
async getSmartAccountAddress(owner) {
|
|
2943
|
+
try {
|
|
2944
|
+
const address = await this.publicClient.readContract({
|
|
2945
|
+
address: this.factoryAddress,
|
|
2946
|
+
abi: factoryAbi,
|
|
2947
|
+
functionName: "getAccountAddress",
|
|
2948
|
+
args: [owner, 0n]
|
|
2949
|
+
});
|
|
2950
|
+
return address;
|
|
2951
|
+
} catch (e) {
|
|
2952
|
+
try {
|
|
2953
|
+
const { result } = await this.publicClient.simulateContract({
|
|
2954
|
+
address: this.factoryAddress,
|
|
2955
|
+
abi: factoryAbi,
|
|
2956
|
+
functionName: "createAccount",
|
|
2957
|
+
args: [owner, 0n],
|
|
2958
|
+
account: "0x0000000000000000000000000000000000000000"
|
|
2959
|
+
// Zero address or random for simulation
|
|
2960
|
+
});
|
|
2961
|
+
return result;
|
|
2962
|
+
} catch (inner) {
|
|
2963
|
+
throw e;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
// --- Token Methods (Delegated) ---
|
|
2968
|
+
getTokenAddress(token) {
|
|
2969
|
+
return this.tokenService.getTokenAddress(token);
|
|
2970
|
+
}
|
|
2971
|
+
async getBalance(token) {
|
|
2972
|
+
if (!this.smartAccountAddress) throw new Error("Not connected");
|
|
2973
|
+
return this.tokenService.getBalance(token, this.smartAccountAddress);
|
|
2974
|
+
}
|
|
2975
|
+
async getEoaBalance(token) {
|
|
2976
|
+
if (!this.owner) throw new Error("Not connected");
|
|
2977
|
+
return this.tokenService.getBalance(token, this.owner);
|
|
2978
|
+
}
|
|
2979
|
+
async getAllowance(token = "USDC") {
|
|
2980
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
2981
|
+
return this.tokenService.getAllowance(token, this.owner, this.smartAccountAddress);
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Get comprehensive state of the account for a specific token
|
|
2985
|
+
* Useful for UI initialization
|
|
2986
|
+
*/
|
|
2987
|
+
async getAccountState(token) {
|
|
2988
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
2989
|
+
const tokenAddress = this.getTokenAddress(token);
|
|
2990
|
+
const isNative = tokenAddress === "0x0000000000000000000000000000000000000000";
|
|
2991
|
+
const [balance, eoaBalance, allowance, isDeployed] = await Promise.all([
|
|
2992
|
+
this.getBalance(token),
|
|
2993
|
+
this.getEoaBalance(token),
|
|
2994
|
+
isNative ? 0n : this.getAllowance(token).catch(() => 0n),
|
|
2995
|
+
// Handle native/error gracefully
|
|
2996
|
+
this.isAccountDeployed()
|
|
2997
|
+
]);
|
|
2998
|
+
return {
|
|
2999
|
+
owner: this.owner,
|
|
3000
|
+
smartAccount: this.smartAccountAddress,
|
|
3001
|
+
balance,
|
|
3002
|
+
eoaBalance,
|
|
3003
|
+
allowance,
|
|
3004
|
+
isDeployed
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
// --- Transactions ---
|
|
3008
|
+
async sendTransaction(tx) {
|
|
3009
|
+
return this.sendBatchTransaction([tx]);
|
|
3010
|
+
}
|
|
3011
|
+
async sendBatchTransaction(txs) {
|
|
3012
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
3013
|
+
const transactions = txs.map((tx) => ({
|
|
3014
|
+
target: tx.target,
|
|
3015
|
+
value: tx.value ?? 0n,
|
|
3016
|
+
data: tx.data ?? "0x"
|
|
3017
|
+
}));
|
|
3018
|
+
try {
|
|
3019
|
+
const userOp = await this.userOpBuilder.buildUserOperationBatch(
|
|
3020
|
+
this.owner,
|
|
3021
|
+
this.smartAccountAddress,
|
|
3022
|
+
transactions
|
|
3023
|
+
);
|
|
3024
|
+
const signed = await this.signUserOperation(userOp);
|
|
3025
|
+
const hash = await this.sendUserOperation(signed);
|
|
3026
|
+
return await this.waitForUserOperation(hash);
|
|
3027
|
+
} catch (error) {
|
|
3028
|
+
throw this.decodeError(error);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
async deposit(amount) {
|
|
3032
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
3033
|
+
if (this.walletClient) {
|
|
3034
|
+
return await this.walletClient.sendTransaction({
|
|
3035
|
+
account: this.walletClient.account,
|
|
3036
|
+
to: this.smartAccountAddress,
|
|
3037
|
+
value: amount,
|
|
3038
|
+
chain: this.chainConfig.chain
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
return await window.ethereum.request({
|
|
3042
|
+
method: "eth_sendTransaction",
|
|
3043
|
+
params: [{
|
|
3044
|
+
from: this.owner,
|
|
3045
|
+
to: this.smartAccountAddress,
|
|
3046
|
+
value: "0x" + amount.toString(16)
|
|
3047
|
+
}]
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
/**
|
|
3051
|
+
* Smart Transfer: Automatically chooses best method (SA vs EOA) based on balances.
|
|
3052
|
+
* Supports strict fee handling.
|
|
3053
|
+
*/
|
|
3054
|
+
async smartTransfer(token, recipient, amount, fee) {
|
|
3055
|
+
if (!this.owner || !this.smartAccountAddress) throw new Error("Not connected");
|
|
3056
|
+
const tokenAddress = this.getTokenAddress(token);
|
|
3057
|
+
const isNative = tokenAddress === "0x0000000000000000000000000000000000000000";
|
|
3058
|
+
const [saBal, eoaBal, allowance] = await Promise.all([
|
|
3059
|
+
this.getBalance(token),
|
|
3060
|
+
this.getEoaBalance(token),
|
|
3061
|
+
isNative ? 0n : this.getAllowance(token).catch(() => 0n)
|
|
3062
|
+
]);
|
|
3063
|
+
const totalNeeded = amount + (fee?.amount || 0n);
|
|
3064
|
+
if (saBal >= totalNeeded) {
|
|
3065
|
+
const txs = [];
|
|
3066
|
+
if (isNative) {
|
|
3067
|
+
txs.push({ target: recipient, value: amount, data: "0x" });
|
|
3068
|
+
} else {
|
|
3069
|
+
txs.push({ target: tokenAddress, value: 0n, data: this.tokenService.encodeTransfer(recipient, amount) });
|
|
3070
|
+
}
|
|
3071
|
+
if (fee && fee.amount > 0n) {
|
|
3072
|
+
if (isNative) {
|
|
3073
|
+
txs.push({ target: fee.recipient, value: fee.amount, data: "0x" });
|
|
3074
|
+
} else {
|
|
3075
|
+
txs.push({ target: tokenAddress, value: 0n, data: this.tokenService.encodeTransfer(fee.recipient, fee.amount) });
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
console.log("SmartTransfer: Using Smart Account");
|
|
3079
|
+
return this.sendBatchTransaction(txs);
|
|
3080
|
+
}
|
|
3081
|
+
if (eoaBal >= totalNeeded) {
|
|
3082
|
+
if (isNative) {
|
|
3083
|
+
console.log("SmartTransfer: Using EOA (Native)");
|
|
3084
|
+
if (this.walletClient) {
|
|
3085
|
+
const hash2 = await this.walletClient.sendTransaction({
|
|
3086
|
+
account: this.walletClient.account,
|
|
3087
|
+
to: recipient,
|
|
3088
|
+
value: amount,
|
|
3089
|
+
chain: this.chainConfig.chain
|
|
3090
|
+
});
|
|
3091
|
+
return { receipt: { transactionHash: hash2 } };
|
|
3092
|
+
}
|
|
3093
|
+
const hash = await window.ethereum.request({
|
|
3094
|
+
method: "eth_sendTransaction",
|
|
3095
|
+
params: [{ from: this.owner, to: recipient, value: "0x" + amount.toString(16) }]
|
|
3096
|
+
});
|
|
3097
|
+
return { receipt: { transactionHash: hash } };
|
|
3098
|
+
} else {
|
|
3099
|
+
console.log("SmartTransfer: Using EOA (Pull)");
|
|
3100
|
+
if (allowance < totalNeeded) {
|
|
3101
|
+
throw new Error(`Approval required. Please approve ${token} usage.`);
|
|
3102
|
+
}
|
|
3103
|
+
const txs = [];
|
|
3104
|
+
txs.push({
|
|
3105
|
+
target: tokenAddress,
|
|
3106
|
+
value: 0n,
|
|
3107
|
+
data: this.tokenService.encodeTransferFrom(this.owner, recipient, amount)
|
|
3108
|
+
});
|
|
3109
|
+
if (fee && fee.amount > 0n) {
|
|
3110
|
+
txs.push({
|
|
3111
|
+
target: tokenAddress,
|
|
3112
|
+
value: 0n,
|
|
3113
|
+
data: this.tokenService.encodeTransferFrom(this.owner, fee.recipient, fee.amount)
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
return this.sendBatchTransaction(txs);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
throw new Error(`Insufficient funds.`);
|
|
3120
|
+
}
|
|
3121
|
+
async transfer(token, recipient, amount) {
|
|
3122
|
+
const tokenAddress = this.getTokenAddress(token);
|
|
3123
|
+
if (tokenAddress === "0x0000000000000000000000000000000000000000") {
|
|
3124
|
+
return this.sendTransaction({
|
|
3125
|
+
target: recipient,
|
|
3126
|
+
value: amount,
|
|
3127
|
+
data: "0x"
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
const data = this.tokenService.encodeTransfer(recipient, amount);
|
|
3131
|
+
return this.sendTransaction({
|
|
3132
|
+
target: tokenAddress,
|
|
3133
|
+
value: 0n,
|
|
3134
|
+
data
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
/**
|
|
3138
|
+
* Approve a token for the Smart Account
|
|
3139
|
+
*/
|
|
3140
|
+
async approveToken(token, spender, amount = 115792089237316195423570985008687907853269984665640564039457584007913129639935n) {
|
|
3141
|
+
if (!this.owner) throw new Error("Not connected");
|
|
3142
|
+
const support = await this.requestApprovalSupport(token, spender, amount);
|
|
3143
|
+
if (support.type === "approve") {
|
|
3144
|
+
const data = this.tokenService.encodeApprove(spender, amount);
|
|
3145
|
+
if (this.walletClient) {
|
|
3146
|
+
return await this.walletClient.sendTransaction({
|
|
3147
|
+
account: this.walletClient.account,
|
|
3148
|
+
to: token,
|
|
3149
|
+
data,
|
|
3150
|
+
chain: this.chainConfig.chain
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
return await window.ethereum.request({
|
|
3154
|
+
method: "eth_sendTransaction",
|
|
3155
|
+
params: [{
|
|
3156
|
+
from: this.owner,
|
|
3157
|
+
to: token,
|
|
3158
|
+
data
|
|
3159
|
+
}]
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
if (support.type === "permit") throw new Error("Permit not yet supported");
|
|
3163
|
+
return "NOT_NEEDED";
|
|
3164
|
+
}
|
|
3165
|
+
// --- Core Bridge to Bundler/UserOp ---
|
|
3166
|
+
async signUserOperation(userOp) {
|
|
3167
|
+
if (!this.owner) throw new Error("Not connected");
|
|
3168
|
+
const userOpHash = this.userOpBuilder.getUserOpHash(userOp);
|
|
3169
|
+
let signature;
|
|
3170
|
+
if (this.walletClient) {
|
|
3171
|
+
signature = await this.walletClient.signMessage({
|
|
3172
|
+
account: this.walletClient.account,
|
|
3173
|
+
message: { raw: userOpHash }
|
|
3174
|
+
// Sign hash directly
|
|
3175
|
+
});
|
|
3176
|
+
} else {
|
|
3177
|
+
signature = await window.ethereum.request({
|
|
3178
|
+
method: "personal_sign",
|
|
3179
|
+
params: [userOpHash, this.owner]
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
return { ...userOp, signature };
|
|
3183
|
+
}
|
|
3184
|
+
async sendUserOperation(userOp) {
|
|
3185
|
+
return this.bundlerClient.sendUserOperation(userOp);
|
|
3186
|
+
}
|
|
3187
|
+
async waitForUserOperation(hash, timeout = 6e4) {
|
|
3188
|
+
return this.bundlerClient.waitForUserOperation(hash, timeout);
|
|
3189
|
+
}
|
|
3190
|
+
// Internal but exposed via BundlerClient originally
|
|
3191
|
+
async requestApprovalSupport(token, spender, amount) {
|
|
3192
|
+
if (!this.owner) throw new Error("Not connected");
|
|
3193
|
+
return this.bundlerClient.requestApprovalSupport(token, this.owner, spender, amount);
|
|
3194
|
+
}
|
|
3195
|
+
// Error Decoding (Private)
|
|
3196
|
+
decodeError(error) {
|
|
3197
|
+
const msg = error?.message || "";
|
|
3198
|
+
const hexMatch = msg.match(/(0x[0-9a-fA-F]+)/);
|
|
3199
|
+
if (hexMatch) {
|
|
3200
|
+
try {
|
|
3201
|
+
const decoded = decodeErrorResult({
|
|
3202
|
+
abi: [{ inputs: [{ name: "message", type: "string" }], name: "Error", type: "error" }],
|
|
3203
|
+
data: hexMatch[0]
|
|
3204
|
+
});
|
|
3205
|
+
if (decoded.errorName === "Error") return new Error(`Smart Account Error: ${decoded.args[0]}`);
|
|
3206
|
+
} catch (e) {
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
if (msg.includes("AA21")) return new Error("Smart Account: Native transfer failed (ETH missing?)");
|
|
3210
|
+
if (msg.includes("AA25")) return new Error("Smart Account: Invalid account nonce");
|
|
3211
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
3212
|
+
}
|
|
3213
|
+
// Getters
|
|
3214
|
+
getOwner() {
|
|
3215
|
+
return this.owner;
|
|
3216
|
+
}
|
|
3217
|
+
getSmartAccount() {
|
|
3218
|
+
return this.smartAccountAddress;
|
|
3219
|
+
}
|
|
3220
|
+
};
|
|
3221
|
+
|
|
3400
3222
|
// src/index.ts
|
|
3401
|
-
init_AccountAbstraction();
|
|
3402
|
-
init_BundlerClient();
|
|
3403
3223
|
init_Base();
|
|
3404
3224
|
init_Avalanche();
|
|
3405
3225
|
init_Optimism();
|
|
3226
|
+
init_Unichain();
|
|
3406
3227
|
|
|
3407
3228
|
// src/chains.ts
|
|
3408
3229
|
init_Base();
|
|
@@ -3445,11 +3266,14 @@ init_Arbitrum();
|
|
|
3445
3266
|
init_Unichain();
|
|
3446
3267
|
init_Monad();
|
|
3447
3268
|
init_Stellar();
|
|
3269
|
+
init_Stacks();
|
|
3448
3270
|
function mapToSDKConfig(data) {
|
|
3449
3271
|
if (data.nonEvm) {
|
|
3272
|
+
const isStacks = data.assets[0].address?.toString().startsWith("SP") || data.nonEvm.serverURL?.includes("hiro");
|
|
3273
|
+
const chainId = isStacks ? 5e3 : 9e3;
|
|
3274
|
+
const chainName = isStacks ? "Stacks" : "Stellar";
|
|
3450
3275
|
return {
|
|
3451
|
-
chain: { id:
|
|
3452
|
-
// Custom ID for Stellar
|
|
3276
|
+
chain: { id: chainId, name: chainName },
|
|
3453
3277
|
tokens: data.assets.map((a) => ({
|
|
3454
3278
|
symbol: a.name,
|
|
3455
3279
|
decimals: a.decimals,
|
|
@@ -3483,6 +3307,7 @@ var ARBITRUM_MAINNET = mapToSDKConfig(ARBITRUM);
|
|
|
3483
3307
|
var UNICHAIN_MAINNET = mapToSDKConfig(UNICHAIN);
|
|
3484
3308
|
var MONAD_MAINNET = mapToSDKConfig(Monad);
|
|
3485
3309
|
var STELLAR_MAINNET = mapToSDKConfig(STELLAR);
|
|
3310
|
+
var STACKS_MAINNET = mapToSDKConfig(STACKS);
|
|
3486
3311
|
var CHAIN_CONFIGS = {
|
|
3487
3312
|
[base.id]: BASE_MAINNET,
|
|
3488
3313
|
[baseSepolia.id]: BASE_SEPOLIA2,
|
|
@@ -3494,11 +3319,9 @@ var CHAIN_CONFIGS = {
|
|
|
3494
3319
|
[arbitrum.id]: ARBITRUM_MAINNET,
|
|
3495
3320
|
[unichain.id]: UNICHAIN_MAINNET,
|
|
3496
3321
|
[monad.id]: MONAD_MAINNET,
|
|
3497
|
-
9e3: STELLAR_MAINNET
|
|
3322
|
+
9e3: STELLAR_MAINNET,
|
|
3323
|
+
5e3: STACKS_MAINNET
|
|
3498
3324
|
};
|
|
3499
|
-
|
|
3500
|
-
// src/index.ts
|
|
3501
|
-
init_constants();
|
|
3502
3325
|
var CHAIN_ID_TO_KEY = {
|
|
3503
3326
|
[mainnet.id]: "Ethereum",
|
|
3504
3327
|
[base.id]: "Base",
|
|
@@ -3512,7 +3335,8 @@ var CHAIN_ID_TO_KEY = {
|
|
|
3512
3335
|
[avalanche.id]: "Avalanche",
|
|
3513
3336
|
[unichain.id]: "Unichain",
|
|
3514
3337
|
[monad.id]: "Monad",
|
|
3515
|
-
"9000": "Stellar"
|
|
3338
|
+
"9000": "Stellar",
|
|
3339
|
+
"5000": "Stacks"
|
|
3516
3340
|
};
|
|
3517
3341
|
|
|
3518
3342
|
// src/index.ts
|
|
@@ -3522,9 +3346,94 @@ init_TransferManager();
|
|
|
3522
3346
|
init_near();
|
|
3523
3347
|
init_cctp2();
|
|
3524
3348
|
init_stargate();
|
|
3525
|
-
|
|
3349
|
+
var UNISWAP_V3_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481";
|
|
3350
|
+
var UNISWAP_V3_QUOTER = "0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a";
|
|
3351
|
+
var USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
3352
|
+
var WETH_ADDRESS = "0x4200000000000000000000000000000000000006";
|
|
3353
|
+
var QUOTER_ABI = parseAbi([
|
|
3354
|
+
"function quoteExactOutputSingle((address tokenIn, address tokenOut, uint256 amount, uint24 fee, uint160 sqrtPriceLimitX96) params) external returns (uint256 amountIn, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)"
|
|
3355
|
+
]);
|
|
3356
|
+
var ROUTER_ABI = parseAbi([
|
|
3357
|
+
"function exactOutputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountOut, uint256 amountInMaximum, uint160 sqrtPriceLimitX96) params) external payable returns (uint256 amountIn)"
|
|
3358
|
+
]);
|
|
3359
|
+
var UniswapService = class {
|
|
3360
|
+
constructor() {
|
|
3361
|
+
this.publicClient = createPublicClient({
|
|
3362
|
+
chain: base,
|
|
3363
|
+
transport: http("https://mainnet.base.org")
|
|
3364
|
+
// Default public RPC
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
/**
|
|
3368
|
+
* Get amount of USDC needed to buy exact amount of ETH
|
|
3369
|
+
* @param amountETHWei Amount of ETH (Wei) needed
|
|
3370
|
+
*/
|
|
3371
|
+
async quoteUSDCForETH(amountETHWei) {
|
|
3372
|
+
try {
|
|
3373
|
+
const params = {
|
|
3374
|
+
tokenIn: USDC_ADDRESS,
|
|
3375
|
+
tokenOut: WETH_ADDRESS,
|
|
3376
|
+
amount: amountETHWei,
|
|
3377
|
+
fee: 500,
|
|
3378
|
+
sqrtPriceLimitX96: 0n
|
|
3379
|
+
};
|
|
3380
|
+
const result = await this.publicClient.readContract({
|
|
3381
|
+
address: UNISWAP_V3_QUOTER,
|
|
3382
|
+
abi: QUOTER_ABI,
|
|
3383
|
+
functionName: "quoteExactOutputSingle",
|
|
3384
|
+
args: [params]
|
|
3385
|
+
});
|
|
3386
|
+
const amountIn = result[0];
|
|
3387
|
+
return amountIn * 105n / 100n;
|
|
3388
|
+
} catch (e) {
|
|
3389
|
+
console.error("Quote failed", e);
|
|
3390
|
+
throw new Error("Failed to quote USDC for ETH swap");
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
/**
|
|
3394
|
+
* Build tx data for swapping USDC -> ETH
|
|
3395
|
+
* Uses SwapRouter02.exactOutputSingle + unwrapWETH9
|
|
3396
|
+
*/
|
|
3397
|
+
buildSwapData(recipient, amountOutETH, maxAmountInUSDC) {
|
|
3398
|
+
const swapParams = {
|
|
3399
|
+
tokenIn: USDC_ADDRESS,
|
|
3400
|
+
tokenOut: WETH_ADDRESS,
|
|
3401
|
+
fee: 500,
|
|
3402
|
+
recipient: UNISWAP_V3_ROUTER,
|
|
3403
|
+
// Router must hold WETH to unwrap it
|
|
3404
|
+
amountOut: amountOutETH,
|
|
3405
|
+
amountInMaximum: maxAmountInUSDC,
|
|
3406
|
+
sqrtPriceLimitX96: 0n
|
|
3407
|
+
};
|
|
3408
|
+
const swapCalldata = encodeFunctionData({
|
|
3409
|
+
abi: ROUTER_ABI,
|
|
3410
|
+
functionName: "exactOutputSingle",
|
|
3411
|
+
args: [swapParams]
|
|
3412
|
+
});
|
|
3413
|
+
const unwrapCalldata = encodeFunctionData({
|
|
3414
|
+
abi: parseAbi(["function unwrapWETH9(uint256 amountMinimum, address recipient) payable"]),
|
|
3415
|
+
functionName: "unwrapWETH9",
|
|
3416
|
+
args: [amountOutETH, recipient]
|
|
3417
|
+
});
|
|
3418
|
+
const multicallCalldata = encodeFunctionData({
|
|
3419
|
+
abi: parseAbi(["function multicall(bytes[] data) payable returns (bytes[])"]),
|
|
3420
|
+
functionName: "multicall",
|
|
3421
|
+
args: [[swapCalldata, unwrapCalldata]]
|
|
3422
|
+
});
|
|
3423
|
+
return multicallCalldata;
|
|
3424
|
+
}
|
|
3425
|
+
getRouterAddress() {
|
|
3426
|
+
return UNISWAP_V3_ROUTER;
|
|
3427
|
+
}
|
|
3428
|
+
getUSDCAddress() {
|
|
3429
|
+
return USDC_ADDRESS;
|
|
3430
|
+
}
|
|
3431
|
+
};
|
|
3432
|
+
var uniswapService = new UniswapService();
|
|
3433
|
+
|
|
3434
|
+
// src/index.ts
|
|
3526
3435
|
init_Router();
|
|
3527
3436
|
|
|
3528
|
-
export { ARBITRUM_MAINNET, AVALANCHE, AVALANCHE_MAINNET, AccountAbstraction, BASE, BASE_MAINNET, BASE_SEPOLIA2 as BASE_SEPOLIA, BSC_MAINNET, BundlerClient, CCTPStrategy, CHAIN_CONFIGS, CHAIN_ID_TO_KEY, GNOSIS_MAINNET, MONAD_MAINNET, NearStrategy, OPTIMISM, OPTIMISM_MAINNET, POLYGON_MAINNET, RouterService, STELLAR, STELLAR_MAINNET, StargateStrategy, StellarService, TransferManager, UNICHAIN_MAINNET, UniswapService, entryPointAbi, erc20Abi, getNearQuote, getNearSimulation, getStargateSimulation, smartAccountAbi, uniswapService };
|
|
3437
|
+
export { ARBITRUM_MAINNET, AVALANCHE, AVALANCHE_MAINNET, AccountAbstraction, BASE, BASE_MAINNET, BASE_SEPOLIA2 as BASE_SEPOLIA, BSC_MAINNET, BundlerClient, CCTPStrategy, CHAIN_CONFIGS, CHAIN_ID_TO_KEY, GNOSIS_MAINNET, MONAD_MAINNET, NearStrategy, OPTIMISM, OPTIMISM_MAINNET, POLYGON_MAINNET, RouterService, STACKS_MAINNET, STELLAR, STELLAR_MAINNET, StargateStrategy, StellarService, TransferManager, UNICHAIN, UNICHAIN_MAINNET, UniswapService, entryPointAbi, erc20Abi, getNearQuote, getNearSimulation, getStargateSimulation, smartAccountAbi, uniswapService };
|
|
3529
3438
|
//# sourceMappingURL=index.mjs.map
|
|
3530
3439
|
//# sourceMappingURL=index.mjs.map
|