@smartagentkit/sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.d.mts +743 -0
- package/dist/index.d.ts +743 -0
- package/dist/index.js +1354 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1312 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import {
|
|
3
|
+
createPublicClient,
|
|
4
|
+
createWalletClient,
|
|
5
|
+
http,
|
|
6
|
+
encodeFunctionData
|
|
7
|
+
} from "viem";
|
|
8
|
+
import {
|
|
9
|
+
privateKeyToAccount,
|
|
10
|
+
mnemonicToAccount,
|
|
11
|
+
generatePrivateKey
|
|
12
|
+
} from "viem/accounts";
|
|
13
|
+
import { toSafeSmartAccount } from "permissionless/accounts";
|
|
14
|
+
import {
|
|
15
|
+
createSmartAccountClient
|
|
16
|
+
} from "permissionless/clients";
|
|
17
|
+
import { erc7579Actions } from "permissionless/actions/erc7579";
|
|
18
|
+
import { getHookMultiPlexer } from "@rhinestone/module-sdk";
|
|
19
|
+
|
|
20
|
+
// src/presets.ts
|
|
21
|
+
import { parseEther } from "viem";
|
|
22
|
+
|
|
23
|
+
// src/constants.ts
|
|
24
|
+
var MODULE_TYPE_VALIDATOR = 1;
|
|
25
|
+
var MODULE_TYPE_EXECUTOR = 2;
|
|
26
|
+
var MODULE_TYPE_FALLBACK = 3;
|
|
27
|
+
var MODULE_TYPE_HOOK = 4;
|
|
28
|
+
var NATIVE_TOKEN = "0x0000000000000000000000000000000000000000";
|
|
29
|
+
var ENTRYPOINT_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
|
|
30
|
+
var SAFE_7579_MODULE = "0x7579EE8307284F293B1927136486880611F20002";
|
|
31
|
+
var SAFE_7579_LAUNCHPAD = "0x7579011aB74c46090561ea277Ba79D510c6C00ff";
|
|
32
|
+
var RHINESTONE_ATTESTER = "0x000000333034E9f539ce08819E12c1b8Cb29084d";
|
|
33
|
+
var ATTESTERS_THRESHOLD = 1;
|
|
34
|
+
var WINDOW_1_HOUR = 3600;
|
|
35
|
+
var WINDOW_1_DAY = 86400;
|
|
36
|
+
var WINDOW_1_WEEK = 604800;
|
|
37
|
+
var SPENDING_LIMIT_HOOK_ABI = [
|
|
38
|
+
{
|
|
39
|
+
name: "setSpendingLimit",
|
|
40
|
+
type: "function",
|
|
41
|
+
inputs: [
|
|
42
|
+
{ name: "token", type: "address" },
|
|
43
|
+
{ name: "limit", type: "uint256" },
|
|
44
|
+
{ name: "windowDuration", type: "uint48" }
|
|
45
|
+
],
|
|
46
|
+
outputs: [],
|
|
47
|
+
stateMutability: "nonpayable"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "removeSpendingLimit",
|
|
51
|
+
type: "function",
|
|
52
|
+
inputs: [{ name: "token", type: "address" }],
|
|
53
|
+
outputs: [],
|
|
54
|
+
stateMutability: "nonpayable"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "getRemainingAllowance",
|
|
58
|
+
type: "function",
|
|
59
|
+
inputs: [
|
|
60
|
+
{ name: "account", type: "address" },
|
|
61
|
+
{ name: "token", type: "address" }
|
|
62
|
+
],
|
|
63
|
+
outputs: [{ name: "remaining", type: "uint256" }],
|
|
64
|
+
stateMutability: "view"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "configs",
|
|
68
|
+
type: "function",
|
|
69
|
+
inputs: [
|
|
70
|
+
{ name: "account", type: "address" },
|
|
71
|
+
{ name: "token", type: "address" }
|
|
72
|
+
],
|
|
73
|
+
outputs: [
|
|
74
|
+
{ name: "limit", type: "uint256" },
|
|
75
|
+
{ name: "spent", type: "uint256" },
|
|
76
|
+
{ name: "windowDuration", type: "uint48" },
|
|
77
|
+
{ name: "windowStart", type: "uint48" }
|
|
78
|
+
],
|
|
79
|
+
stateMutability: "view"
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
var ALLOWLIST_HOOK_ABI = [
|
|
83
|
+
{
|
|
84
|
+
name: "addPermission",
|
|
85
|
+
type: "function",
|
|
86
|
+
inputs: [
|
|
87
|
+
{ name: "target", type: "address" },
|
|
88
|
+
{ name: "selector", type: "bytes4" }
|
|
89
|
+
],
|
|
90
|
+
outputs: [],
|
|
91
|
+
stateMutability: "nonpayable"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "removePermission",
|
|
95
|
+
type: "function",
|
|
96
|
+
inputs: [
|
|
97
|
+
{ name: "target", type: "address" },
|
|
98
|
+
{ name: "selector", type: "bytes4" }
|
|
99
|
+
],
|
|
100
|
+
outputs: [],
|
|
101
|
+
stateMutability: "nonpayable"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "setMode",
|
|
105
|
+
type: "function",
|
|
106
|
+
inputs: [{ name: "mode", type: "uint8" }],
|
|
107
|
+
outputs: [],
|
|
108
|
+
stateMutability: "nonpayable"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "isTargetAllowed",
|
|
112
|
+
type: "function",
|
|
113
|
+
inputs: [
|
|
114
|
+
{ name: "account", type: "address" },
|
|
115
|
+
{ name: "target", type: "address" },
|
|
116
|
+
{ name: "selector", type: "bytes4" }
|
|
117
|
+
],
|
|
118
|
+
outputs: [{ name: "", type: "bool" }],
|
|
119
|
+
stateMutability: "view"
|
|
120
|
+
}
|
|
121
|
+
];
|
|
122
|
+
var EMERGENCY_PAUSE_HOOK_ABI = [
|
|
123
|
+
{
|
|
124
|
+
name: "pause",
|
|
125
|
+
type: "function",
|
|
126
|
+
inputs: [{ name: "account", type: "address" }],
|
|
127
|
+
outputs: [],
|
|
128
|
+
stateMutability: "nonpayable"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "unpause",
|
|
132
|
+
type: "function",
|
|
133
|
+
inputs: [{ name: "account", type: "address" }],
|
|
134
|
+
outputs: [],
|
|
135
|
+
stateMutability: "nonpayable"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "isPaused",
|
|
139
|
+
type: "function",
|
|
140
|
+
inputs: [{ name: "account", type: "address" }],
|
|
141
|
+
outputs: [{ name: "", type: "bool" }],
|
|
142
|
+
stateMutability: "view"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "setGuardian",
|
|
146
|
+
type: "function",
|
|
147
|
+
inputs: [{ name: "newGuardian", type: "address" }],
|
|
148
|
+
outputs: [],
|
|
149
|
+
stateMutability: "nonpayable"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "setAutoUnpauseTimeout",
|
|
153
|
+
type: "function",
|
|
154
|
+
inputs: [{ name: "timeout", type: "uint48" }],
|
|
155
|
+
outputs: [],
|
|
156
|
+
stateMutability: "nonpayable"
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
var HOOK_MULTIPLEXER_ADDRESS = "0xF6782ed057F95f334D04F0Af1Af4D14fb84DE549";
|
|
160
|
+
var HOOK_TYPE_GLOBAL = 0;
|
|
161
|
+
var MODULE_ONINSTALL_ABI = [
|
|
162
|
+
{
|
|
163
|
+
name: "onInstall",
|
|
164
|
+
type: "function",
|
|
165
|
+
inputs: [{ name: "data", type: "bytes" }],
|
|
166
|
+
outputs: [],
|
|
167
|
+
stateMutability: "nonpayable"
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
var SET_TRUSTED_FORWARDER_ABI = [
|
|
171
|
+
{
|
|
172
|
+
name: "setTrustedForwarder",
|
|
173
|
+
type: "function",
|
|
174
|
+
inputs: [{ name: "forwarder", type: "address" }],
|
|
175
|
+
outputs: [],
|
|
176
|
+
stateMutability: "nonpayable"
|
|
177
|
+
}
|
|
178
|
+
];
|
|
179
|
+
var HOOK_MULTIPLEXER_ABI = [
|
|
180
|
+
{
|
|
181
|
+
name: "addHook",
|
|
182
|
+
type: "function",
|
|
183
|
+
inputs: [
|
|
184
|
+
{ name: "hook", type: "address" },
|
|
185
|
+
{ name: "hookType", type: "uint8" }
|
|
186
|
+
],
|
|
187
|
+
outputs: [],
|
|
188
|
+
stateMutability: "nonpayable"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "removeHook",
|
|
192
|
+
type: "function",
|
|
193
|
+
inputs: [
|
|
194
|
+
{ name: "hook", type: "address" },
|
|
195
|
+
{ name: "hookType", type: "uint8" }
|
|
196
|
+
],
|
|
197
|
+
outputs: [],
|
|
198
|
+
stateMutability: "nonpayable"
|
|
199
|
+
}
|
|
200
|
+
];
|
|
201
|
+
var MODULE_ADDRESSES = {
|
|
202
|
+
// Will be populated after deployment
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/presets.ts
|
|
206
|
+
var PRESETS = {
|
|
207
|
+
/**
|
|
208
|
+
* DeFi Trader preset:
|
|
209
|
+
* - Daily spending limit on ETH and stablecoins
|
|
210
|
+
* - Allowlist of approved DEX contracts
|
|
211
|
+
* - Emergency pause with owner as guardian
|
|
212
|
+
*/
|
|
213
|
+
"defi-trader": (owner, params = {}) => [
|
|
214
|
+
{
|
|
215
|
+
type: "spending-limit",
|
|
216
|
+
limits: [
|
|
217
|
+
{
|
|
218
|
+
token: NATIVE_TOKEN,
|
|
219
|
+
limit: params.dailyEthLimit ?? parseEther("1"),
|
|
220
|
+
window: WINDOW_1_DAY
|
|
221
|
+
},
|
|
222
|
+
...params.stablecoinLimits ?? []
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
// Only include allowlist if specific DEXes are provided;
|
|
226
|
+
// an empty allowlist in "allow" mode would block all transactions.
|
|
227
|
+
...params.allowedDexes?.length ? [
|
|
228
|
+
{
|
|
229
|
+
type: "allowlist",
|
|
230
|
+
mode: "allow",
|
|
231
|
+
targets: params.allowedDexes.map((addr) => ({
|
|
232
|
+
address: addr,
|
|
233
|
+
selector: "0x00000000"
|
|
234
|
+
}))
|
|
235
|
+
}
|
|
236
|
+
] : [],
|
|
237
|
+
{
|
|
238
|
+
type: "emergency-pause",
|
|
239
|
+
guardian: params.guardian ?? owner,
|
|
240
|
+
autoUnpauseAfter: WINDOW_1_DAY
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
/**
|
|
244
|
+
* Treasury Agent preset:
|
|
245
|
+
* - Lower spending limits with longer windows
|
|
246
|
+
* - Emergency pause (manual only)
|
|
247
|
+
*/
|
|
248
|
+
"treasury-agent": (owner, params = {}) => [
|
|
249
|
+
{
|
|
250
|
+
type: "spending-limit",
|
|
251
|
+
limits: [
|
|
252
|
+
{
|
|
253
|
+
token: NATIVE_TOKEN,
|
|
254
|
+
limit: params.weeklyEthLimit ?? parseEther("5"),
|
|
255
|
+
window: WINDOW_1_WEEK
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
type: "emergency-pause",
|
|
261
|
+
guardian: params.guardian ?? owner,
|
|
262
|
+
autoUnpauseAfter: 0
|
|
263
|
+
}
|
|
264
|
+
],
|
|
265
|
+
/**
|
|
266
|
+
* Payment Agent preset:
|
|
267
|
+
* - Strict spending limits
|
|
268
|
+
* - Allowlist of approved recipients only
|
|
269
|
+
* - Emergency pause
|
|
270
|
+
*/
|
|
271
|
+
"payment-agent": (owner, params = {}) => [
|
|
272
|
+
{
|
|
273
|
+
type: "spending-limit",
|
|
274
|
+
limits: [
|
|
275
|
+
{
|
|
276
|
+
token: NATIVE_TOKEN,
|
|
277
|
+
limit: params.dailyLimit ?? parseEther("0.1"),
|
|
278
|
+
window: WINDOW_1_DAY
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
},
|
|
282
|
+
// Only include allowlist if specific recipients are provided;
|
|
283
|
+
// an empty allowlist in "allow" mode would block all transactions.
|
|
284
|
+
...params.approvedRecipients?.length ? [
|
|
285
|
+
{
|
|
286
|
+
type: "allowlist",
|
|
287
|
+
mode: "allow",
|
|
288
|
+
targets: params.approvedRecipients.map(
|
|
289
|
+
(addr) => ({
|
|
290
|
+
address: addr
|
|
291
|
+
})
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
] : [],
|
|
295
|
+
{
|
|
296
|
+
type: "emergency-pause",
|
|
297
|
+
guardian: params.guardian ?? owner,
|
|
298
|
+
autoUnpauseAfter: 3600
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
/**
|
|
302
|
+
* Minimal preset:
|
|
303
|
+
* - Just emergency pause
|
|
304
|
+
* - For agents that need maximum flexibility with a kill switch
|
|
305
|
+
*/
|
|
306
|
+
minimal: (owner, params = {}) => [
|
|
307
|
+
{
|
|
308
|
+
type: "emergency-pause",
|
|
309
|
+
guardian: params.guardian ?? owner,
|
|
310
|
+
autoUnpauseAfter: 0
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/policies.ts
|
|
316
|
+
import { encodeAbiParameters, parseAbiParameters } from "viem";
|
|
317
|
+
|
|
318
|
+
// src/errors.ts
|
|
319
|
+
var SmartAgentKitError = class extends Error {
|
|
320
|
+
constructor(message, options) {
|
|
321
|
+
super(message, options);
|
|
322
|
+
this.name = "SmartAgentKitError";
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
var WalletCreationError = class extends SmartAgentKitError {
|
|
326
|
+
constructor(message, cause) {
|
|
327
|
+
super(`Wallet creation failed: ${message}`, { cause });
|
|
328
|
+
this.name = "WalletCreationError";
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
var PolicyConfigError = class extends SmartAgentKitError {
|
|
332
|
+
constructor(message, cause) {
|
|
333
|
+
super(`Invalid policy configuration: ${message}`, { cause });
|
|
334
|
+
this.name = "PolicyConfigError";
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
var ExecutionError = class extends SmartAgentKitError {
|
|
338
|
+
constructor(message, cause) {
|
|
339
|
+
super(`Transaction execution failed: ${message}`, { cause });
|
|
340
|
+
this.name = "ExecutionError";
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
var SpendingLimitExceededError = class extends SmartAgentKitError {
|
|
344
|
+
constructor(token, attempted, remaining) {
|
|
345
|
+
super(
|
|
346
|
+
`Spending limit exceeded for ${token}: attempted ${attempted}, remaining ${remaining}`
|
|
347
|
+
);
|
|
348
|
+
this.token = token;
|
|
349
|
+
this.attempted = attempted;
|
|
350
|
+
this.remaining = remaining;
|
|
351
|
+
this.name = "SpendingLimitExceededError";
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
var WalletPausedError = class extends SmartAgentKitError {
|
|
355
|
+
constructor(walletAddress) {
|
|
356
|
+
super(`Wallet ${walletAddress} is currently paused`);
|
|
357
|
+
this.walletAddress = walletAddress;
|
|
358
|
+
this.name = "WalletPausedError";
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
var SessionError = class extends SmartAgentKitError {
|
|
362
|
+
constructor(message, cause) {
|
|
363
|
+
super(`Session error: ${message}`, { cause });
|
|
364
|
+
this.name = "SessionError";
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// src/policies.ts
|
|
369
|
+
function encodePolicyInitData(policy, moduleAddresses, trustedForwarder = "0x0000000000000000000000000000000000000000") {
|
|
370
|
+
const zeroAddress = "0x0000000000000000000000000000000000000000";
|
|
371
|
+
switch (policy.type) {
|
|
372
|
+
case "spending-limit":
|
|
373
|
+
return {
|
|
374
|
+
moduleAddress: moduleAddresses?.spendingLimitHook ?? zeroAddress,
|
|
375
|
+
moduleType: MODULE_TYPE_HOOK,
|
|
376
|
+
initData: encodeSpendingLimitInitData(policy, trustedForwarder)
|
|
377
|
+
};
|
|
378
|
+
case "allowlist":
|
|
379
|
+
return {
|
|
380
|
+
moduleAddress: moduleAddresses?.allowlistHook ?? zeroAddress,
|
|
381
|
+
moduleType: MODULE_TYPE_HOOK,
|
|
382
|
+
initData: encodeAllowlistInitData(policy, trustedForwarder)
|
|
383
|
+
};
|
|
384
|
+
case "emergency-pause":
|
|
385
|
+
return {
|
|
386
|
+
moduleAddress: moduleAddresses?.emergencyPauseHook ?? zeroAddress,
|
|
387
|
+
moduleType: MODULE_TYPE_HOOK,
|
|
388
|
+
initData: encodeEmergencyPauseInitData(policy, trustedForwarder)
|
|
389
|
+
};
|
|
390
|
+
case "automation":
|
|
391
|
+
throw new PolicyConfigError(
|
|
392
|
+
"AutomationExecutor encoding is not yet implemented (Sprint 2)"
|
|
393
|
+
);
|
|
394
|
+
default: {
|
|
395
|
+
const _exhaustive = policy;
|
|
396
|
+
throw new PolicyConfigError(
|
|
397
|
+
`Unknown policy type: ${_exhaustive.type}`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function encodeSpendingLimitInitData(policy, trustedForwarder = "0x0000000000000000000000000000000000000000") {
|
|
403
|
+
if (policy.limits.length === 0) {
|
|
404
|
+
throw new PolicyConfigError("SpendingLimitPolicy must have at least one token limit");
|
|
405
|
+
}
|
|
406
|
+
const seenTokens = /* @__PURE__ */ new Set();
|
|
407
|
+
for (const limit of policy.limits) {
|
|
408
|
+
const tokenKey = limit.token.toLowerCase();
|
|
409
|
+
if (seenTokens.has(tokenKey)) {
|
|
410
|
+
throw new PolicyConfigError(
|
|
411
|
+
`Duplicate token address in limits: ${limit.token}. Only one limit per token is supported.`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
seenTokens.add(tokenKey);
|
|
415
|
+
if (limit.limit <= 0n) {
|
|
416
|
+
throw new PolicyConfigError("Token limit must be greater than zero");
|
|
417
|
+
}
|
|
418
|
+
if (limit.window < 60) {
|
|
419
|
+
throw new PolicyConfigError("Window duration must be at least 60 seconds");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return encodeAbiParameters(
|
|
423
|
+
parseAbiParameters(
|
|
424
|
+
"address trustedForwarder, (address token, uint256 limit, uint48 windowDuration)[]"
|
|
425
|
+
),
|
|
426
|
+
[
|
|
427
|
+
trustedForwarder,
|
|
428
|
+
policy.limits.map((l) => ({
|
|
429
|
+
token: l.token,
|
|
430
|
+
limit: l.limit,
|
|
431
|
+
windowDuration: l.window
|
|
432
|
+
}))
|
|
433
|
+
]
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
function encodeAllowlistInitData(policy, trustedForwarder = "0x0000000000000000000000000000000000000000") {
|
|
437
|
+
if (policy.mode === "allow" && policy.targets.length === 0) {
|
|
438
|
+
throw new PolicyConfigError(
|
|
439
|
+
"AllowlistPolicy in 'allow' mode must have at least one target. An empty allowlist would block all transactions."
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
const mode = policy.mode === "allow" ? 0 : 1;
|
|
443
|
+
const wildcardSelector = "0x431e2cf5";
|
|
444
|
+
const protectedAddresses = policy.protectedAddresses ?? [];
|
|
445
|
+
if (protectedAddresses.length > 20) {
|
|
446
|
+
throw new PolicyConfigError(
|
|
447
|
+
"AllowlistPolicy protectedAddresses cannot exceed 20 entries (on-chain MAX_PROTECTED_ADDRESSES limit)"
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
return encodeAbiParameters(
|
|
451
|
+
parseAbiParameters(
|
|
452
|
+
"address trustedForwarder, uint8 mode, (address target, bytes4 selector)[], address[] protectedAddresses"
|
|
453
|
+
),
|
|
454
|
+
[
|
|
455
|
+
trustedForwarder,
|
|
456
|
+
mode,
|
|
457
|
+
policy.targets.map((t) => ({
|
|
458
|
+
target: t.address,
|
|
459
|
+
selector: t.selector ?? wildcardSelector
|
|
460
|
+
})),
|
|
461
|
+
protectedAddresses
|
|
462
|
+
]
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
function encodeEmergencyPauseInitData(policy, trustedForwarder = "0x0000000000000000000000000000000000000000") {
|
|
466
|
+
if (policy.guardian === "0x0000000000000000000000000000000000000000") {
|
|
467
|
+
throw new PolicyConfigError("Guardian address cannot be the zero address");
|
|
468
|
+
}
|
|
469
|
+
return encodeAbiParameters(
|
|
470
|
+
parseAbiParameters("address trustedForwarder, address guardian, uint48 autoUnpauseAfter"),
|
|
471
|
+
[trustedForwarder, policy.guardian, policy.autoUnpauseAfter ?? 0]
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/deployments/base-sepolia.json
|
|
476
|
+
var base_sepolia_default = {
|
|
477
|
+
chainId: 84532,
|
|
478
|
+
entryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
|
479
|
+
safe7579Module: "0x7579EE8307284F293B1927136486880611F20002",
|
|
480
|
+
safe7579Launchpad: "0x7579011aB74c46090561ea277Ba79D510c6C00ff",
|
|
481
|
+
rhinestoneAttester: "0x000000333034E9f539ce08819E12c1b8Cb29084d",
|
|
482
|
+
hookMultiPlexer: "0xF6782ed057F95f334D04F0Af1Af4D14fb84DE549",
|
|
483
|
+
spendingLimitHook: "0x0ea97ef2fc52700d1628110a8f411fefb0c0aa8b",
|
|
484
|
+
allowlistHook: "0x61a2100072d03f66de6f7dd0dfc2f7aa5c91e777",
|
|
485
|
+
emergencyPauseHook: "0xb8fdc9ee56cfb4077e132eff631b546fe6e79fec",
|
|
486
|
+
automationExecutor: "0x729c29b35c396b907ed118f00fbe4d4bcc3a7f46",
|
|
487
|
+
moduleSetupHelper: "0x4f1555baf4b3221c373094d47419596118828e41"
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/deployments/sepolia.json
|
|
491
|
+
var sepolia_default = {
|
|
492
|
+
chainId: 11155111,
|
|
493
|
+
entryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032",
|
|
494
|
+
safe7579Module: "0x7579EE8307284F293B1927136486880611F20002",
|
|
495
|
+
safe7579Launchpad: "0x7579011aB74c46090561ea277Ba79D510c6C00ff",
|
|
496
|
+
rhinestoneAttester: "0x000000333034E9f539ce08819E12c1b8Cb29084d",
|
|
497
|
+
hookMultiPlexer: "",
|
|
498
|
+
spendingLimitHook: "",
|
|
499
|
+
allowlistHook: "",
|
|
500
|
+
emergencyPauseHook: "",
|
|
501
|
+
automationExecutor: "",
|
|
502
|
+
moduleSetupHelper: ""
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/deployments.ts
|
|
506
|
+
var DEPLOYMENTS = {};
|
|
507
|
+
function loadDeployment(json) {
|
|
508
|
+
if (json.spendingLimitHook && json.spendingLimitHook !== "") {
|
|
509
|
+
DEPLOYMENTS[json.chainId] = {
|
|
510
|
+
spendingLimitHook: json.spendingLimitHook,
|
|
511
|
+
allowlistHook: json.allowlistHook,
|
|
512
|
+
emergencyPauseHook: json.emergencyPauseHook,
|
|
513
|
+
automationExecutor: json.automationExecutor || void 0
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
loadDeployment(base_sepolia_default);
|
|
518
|
+
loadDeployment(sepolia_default);
|
|
519
|
+
|
|
520
|
+
// src/sessions.ts
|
|
521
|
+
import {
|
|
522
|
+
encodeAbiParameters as encodeAbiParameters2,
|
|
523
|
+
parseAbiParameters as parseAbiParameters2,
|
|
524
|
+
keccak256,
|
|
525
|
+
concat,
|
|
526
|
+
toHex
|
|
527
|
+
} from "viem";
|
|
528
|
+
import {
|
|
529
|
+
getSmartSessionsValidator,
|
|
530
|
+
getPermissionId,
|
|
531
|
+
getEnableSessionDetails,
|
|
532
|
+
getRemoveSessionAction,
|
|
533
|
+
encodeSmartSessionSignature,
|
|
534
|
+
getTimeFramePolicy,
|
|
535
|
+
getSpendingLimitsPolicy,
|
|
536
|
+
SmartSessionMode
|
|
537
|
+
} from "@rhinestone/module-sdk";
|
|
538
|
+
import { SmartSessionMode as SmartSessionMode2 } from "@rhinestone/module-sdk";
|
|
539
|
+
var SMART_SESSIONS_ADDRESS = "0x00000000002B0eCfbD0496EE71e01257dA0E37DE";
|
|
540
|
+
var OWNABLE_VALIDATOR_ADDRESS = "0x2483DA3A338895199E5e538530213157e931Bf06";
|
|
541
|
+
function buildSession(sessionKeyAddress, params, chainId, sessionValidatorAddress) {
|
|
542
|
+
const validatorAddress = sessionValidatorAddress ?? OWNABLE_VALIDATOR_ADDRESS;
|
|
543
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
544
|
+
const sessionValidatorInitData = encodeAbiParameters2(
|
|
545
|
+
parseAbiParameters2("address"),
|
|
546
|
+
[sessionKeyAddress]
|
|
547
|
+
);
|
|
548
|
+
const userOpPolicies = [];
|
|
549
|
+
if (!params.expiresAt || params.expiresAt <= now) {
|
|
550
|
+
throw new SessionError("expiresAt must be in the future");
|
|
551
|
+
}
|
|
552
|
+
const timePolicy = getTimeFramePolicy({
|
|
553
|
+
validUntil: params.expiresAt,
|
|
554
|
+
validAfter: now
|
|
555
|
+
});
|
|
556
|
+
userOpPolicies.push({
|
|
557
|
+
policy: timePolicy.address,
|
|
558
|
+
initData: timePolicy.initData
|
|
559
|
+
});
|
|
560
|
+
if (params.spendingLimits && params.spendingLimits.length > 0) {
|
|
561
|
+
const spendingPolicy = getSpendingLimitsPolicy(
|
|
562
|
+
params.spendingLimits.map((l) => ({
|
|
563
|
+
token: l.token,
|
|
564
|
+
limit: l.limit
|
|
565
|
+
}))
|
|
566
|
+
);
|
|
567
|
+
userOpPolicies.push({
|
|
568
|
+
policy: spendingPolicy.address,
|
|
569
|
+
initData: spendingPolicy.initData
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
const actions = params.actions.map(
|
|
573
|
+
(action) => {
|
|
574
|
+
if (action.rules?.length) {
|
|
575
|
+
throw new SessionError(
|
|
576
|
+
"Per-action rules (SessionAction.rules) are not yet supported. Remove rules or wait for a future release."
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
actionTarget: action.target,
|
|
581
|
+
actionTargetSelector: action.selector,
|
|
582
|
+
actionPolicies: []
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
if (actions.length === 0) {
|
|
587
|
+
throw new SessionError("Session must have at least one allowed action");
|
|
588
|
+
}
|
|
589
|
+
const randomBuf = new Uint8Array(32);
|
|
590
|
+
crypto.getRandomValues(randomBuf);
|
|
591
|
+
const salt = keccak256(
|
|
592
|
+
concat([sessionKeyAddress, toHex(randomBuf)])
|
|
593
|
+
);
|
|
594
|
+
return {
|
|
595
|
+
sessionValidator: validatorAddress,
|
|
596
|
+
sessionValidatorInitData,
|
|
597
|
+
salt,
|
|
598
|
+
userOpPolicies,
|
|
599
|
+
erc7739Policies: {
|
|
600
|
+
allowedERC7739Content: [],
|
|
601
|
+
erc1271Policies: []
|
|
602
|
+
},
|
|
603
|
+
actions,
|
|
604
|
+
permitERC4337Paymaster: true,
|
|
605
|
+
chainId
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function getSmartSessionsModule(sessions) {
|
|
609
|
+
return getSmartSessionsValidator({
|
|
610
|
+
sessions,
|
|
611
|
+
useRegistry: true
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
function computePermissionId(session) {
|
|
615
|
+
return getPermissionId({ session });
|
|
616
|
+
}
|
|
617
|
+
async function getEnableDetails(sessions, account, publicClients, enableValidatorAddress) {
|
|
618
|
+
return getEnableSessionDetails({
|
|
619
|
+
sessions,
|
|
620
|
+
account: {
|
|
621
|
+
address: account.address,
|
|
622
|
+
type: account.type,
|
|
623
|
+
deployedOnChains: []
|
|
624
|
+
},
|
|
625
|
+
clients: publicClients,
|
|
626
|
+
enableValidatorAddress
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function encodeUseSessionSignature(permissionId, signature) {
|
|
630
|
+
return encodeSmartSessionSignature({
|
|
631
|
+
mode: SmartSessionMode.USE,
|
|
632
|
+
permissionId,
|
|
633
|
+
signature
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function encodeEnableSessionSignature(permissionId, signature, enableSessionData) {
|
|
637
|
+
return encodeSmartSessionSignature({
|
|
638
|
+
mode: SmartSessionMode.ENABLE,
|
|
639
|
+
permissionId,
|
|
640
|
+
signature,
|
|
641
|
+
enableSessionData
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
function getRemoveAction(permissionId) {
|
|
645
|
+
const action = getRemoveSessionAction({ permissionId });
|
|
646
|
+
const to = typeof action.target === "string" ? action.target : action.to;
|
|
647
|
+
if (!to) throw new SessionError("getRemoveSessionAction returned no target address");
|
|
648
|
+
let value;
|
|
649
|
+
if (typeof action.value === "bigint") {
|
|
650
|
+
value = action.value;
|
|
651
|
+
} else if (action.value != null) {
|
|
652
|
+
value = BigInt(String(action.value));
|
|
653
|
+
} else {
|
|
654
|
+
value = 0n;
|
|
655
|
+
}
|
|
656
|
+
const data = (typeof action.data === "string" ? action.data : action.callData) ?? "0x";
|
|
657
|
+
return { to, value, data };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// src/client.ts
|
|
661
|
+
function resolveAccount(key) {
|
|
662
|
+
if (typeof key === "string") {
|
|
663
|
+
return privateKeyToAccount(key);
|
|
664
|
+
}
|
|
665
|
+
return mnemonicToAccount(key.mnemonic, {
|
|
666
|
+
addressIndex: key.addressIndex ?? 0
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
var SmartAgentKitClient = class {
|
|
670
|
+
config;
|
|
671
|
+
publicClient;
|
|
672
|
+
walletClients;
|
|
673
|
+
sessions;
|
|
674
|
+
constructor(config) {
|
|
675
|
+
if (!config.moduleAddresses) {
|
|
676
|
+
const builtIn = DEPLOYMENTS[config.chain.id];
|
|
677
|
+
if (builtIn) {
|
|
678
|
+
config = { ...config, moduleAddresses: builtIn };
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
this.config = config;
|
|
682
|
+
this.publicClient = createPublicClient({
|
|
683
|
+
chain: config.chain,
|
|
684
|
+
transport: http(config.rpcUrl)
|
|
685
|
+
});
|
|
686
|
+
this.walletClients = /* @__PURE__ */ new Map();
|
|
687
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
688
|
+
}
|
|
689
|
+
// ─── Wallet Creation ──────────────────────────────────────────
|
|
690
|
+
/**
|
|
691
|
+
* Deploy a new policy-governed smart wallet for an AI agent.
|
|
692
|
+
*
|
|
693
|
+
* The wallet is a Safe smart account with ERC-7579 modules. A
|
|
694
|
+
* HookMultiPlexer is installed as the single hook, and sub-hooks
|
|
695
|
+
* (SpendingLimit, Allowlist, EmergencyPause) are routed through it.
|
|
696
|
+
*
|
|
697
|
+
* The deployment and policy initialization happen atomically in
|
|
698
|
+
* the first UserOperation.
|
|
699
|
+
*/
|
|
700
|
+
async createWallet(params) {
|
|
701
|
+
try {
|
|
702
|
+
const policies = this.resolvePolicies(params);
|
|
703
|
+
const moduleAddresses = this.requireModuleAddresses(policies);
|
|
704
|
+
if (!params.ownerPrivateKey && !params.ownerMnemonic) {
|
|
705
|
+
throw new WalletCreationError(
|
|
706
|
+
"Provide either ownerPrivateKey or ownerMnemonic"
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
const ownerKey = params.ownerMnemonic ? { mnemonic: params.ownerMnemonic, addressIndex: params.addressIndex } : params.ownerPrivateKey;
|
|
710
|
+
const ownerAccount = resolveAccount(ownerKey);
|
|
711
|
+
if (ownerAccount.address.toLowerCase() !== params.owner.toLowerCase()) {
|
|
712
|
+
throw new WalletCreationError(
|
|
713
|
+
`Owner address mismatch: key derives ${ownerAccount.address} but params.owner is ${params.owner}`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const hookModule = getHookMultiPlexer({
|
|
717
|
+
globalHooks: [],
|
|
718
|
+
valueHooks: [],
|
|
719
|
+
delegatecallHooks: [],
|
|
720
|
+
sigHooks: [],
|
|
721
|
+
targetHooks: []
|
|
722
|
+
});
|
|
723
|
+
const safeAccount = await toSafeSmartAccount({
|
|
724
|
+
client: this.publicClient,
|
|
725
|
+
owners: [ownerAccount],
|
|
726
|
+
version: "1.4.1",
|
|
727
|
+
entryPoint: {
|
|
728
|
+
address: ENTRYPOINT_V07,
|
|
729
|
+
version: "0.7"
|
|
730
|
+
},
|
|
731
|
+
safe4337ModuleAddress: SAFE_7579_MODULE,
|
|
732
|
+
erc7579LaunchpadAddress: SAFE_7579_LAUNCHPAD,
|
|
733
|
+
hooks: [
|
|
734
|
+
{
|
|
735
|
+
address: hookModule.address,
|
|
736
|
+
context: hookModule.initData
|
|
737
|
+
}
|
|
738
|
+
],
|
|
739
|
+
attesters: [RHINESTONE_ATTESTER],
|
|
740
|
+
attestersThreshold: ATTESTERS_THRESHOLD,
|
|
741
|
+
saltNonce: params.salt ?? 0n
|
|
742
|
+
});
|
|
743
|
+
const smartAccountClient = createSmartAccountClient({
|
|
744
|
+
account: safeAccount,
|
|
745
|
+
chain: this.config.chain,
|
|
746
|
+
bundlerTransport: http(this.config.bundlerUrl),
|
|
747
|
+
client: this.publicClient
|
|
748
|
+
}).extend(erc7579Actions());
|
|
749
|
+
this.walletClients.set(safeAccount.address, smartAccountClient);
|
|
750
|
+
if (policies.length > 0 && moduleAddresses) {
|
|
751
|
+
await this.initializePolicies(
|
|
752
|
+
smartAccountClient,
|
|
753
|
+
policies,
|
|
754
|
+
moduleAddresses,
|
|
755
|
+
hookModule.address
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
address: safeAccount.address,
|
|
760
|
+
owner: params.owner,
|
|
761
|
+
chain: this.config.chain,
|
|
762
|
+
isDeployed: policies.length > 0,
|
|
763
|
+
// Deployed if first UserOp was sent
|
|
764
|
+
policies: this.mapPoliciesToInstalled(policies, moduleAddresses),
|
|
765
|
+
sessions: []
|
|
766
|
+
};
|
|
767
|
+
} catch (error) {
|
|
768
|
+
if (error instanceof WalletCreationError) throw error;
|
|
769
|
+
throw new WalletCreationError(
|
|
770
|
+
error instanceof Error ? error.message : String(error),
|
|
771
|
+
error
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Reconnect to an existing wallet for executing transactions.
|
|
777
|
+
* Accepts a private key or mnemonic credential.
|
|
778
|
+
*/
|
|
779
|
+
async connectWallet(walletAddress, ownerKey) {
|
|
780
|
+
const ownerAccount = resolveAccount(ownerKey);
|
|
781
|
+
const safeAccount = await toSafeSmartAccount({
|
|
782
|
+
client: this.publicClient,
|
|
783
|
+
owners: [ownerAccount],
|
|
784
|
+
version: "1.4.1",
|
|
785
|
+
entryPoint: {
|
|
786
|
+
address: ENTRYPOINT_V07,
|
|
787
|
+
version: "0.7"
|
|
788
|
+
},
|
|
789
|
+
safe4337ModuleAddress: SAFE_7579_MODULE,
|
|
790
|
+
erc7579LaunchpadAddress: SAFE_7579_LAUNCHPAD,
|
|
791
|
+
address: walletAddress,
|
|
792
|
+
attesters: [RHINESTONE_ATTESTER],
|
|
793
|
+
attestersThreshold: ATTESTERS_THRESHOLD
|
|
794
|
+
});
|
|
795
|
+
const smartAccountClient = createSmartAccountClient({
|
|
796
|
+
account: safeAccount,
|
|
797
|
+
chain: this.config.chain,
|
|
798
|
+
bundlerTransport: http(this.config.bundlerUrl),
|
|
799
|
+
client: this.publicClient
|
|
800
|
+
}).extend(erc7579Actions());
|
|
801
|
+
this.walletClients.set(walletAddress, smartAccountClient);
|
|
802
|
+
}
|
|
803
|
+
async predictAddress(_owner, _salt) {
|
|
804
|
+
throw new Error("Not yet implemented \u2014 Sprint 3");
|
|
805
|
+
}
|
|
806
|
+
// ─── Policy Management ────────────────────────────────────────
|
|
807
|
+
async addPolicy(_wallet, _policy, _ownerPrivateKey) {
|
|
808
|
+
throw new Error("Not yet implemented \u2014 Sprint 3");
|
|
809
|
+
}
|
|
810
|
+
async removePolicy(_wallet, _moduleAddress, _ownerPrivateKey) {
|
|
811
|
+
throw new Error("Not yet implemented \u2014 Sprint 3");
|
|
812
|
+
}
|
|
813
|
+
async getPolicies(_walletAddress) {
|
|
814
|
+
throw new Error("Not yet implemented \u2014 Sprint 3");
|
|
815
|
+
}
|
|
816
|
+
// ─── Session Key Management ───────────────────────────────────
|
|
817
|
+
/**
|
|
818
|
+
* Create a session key for an AI agent.
|
|
819
|
+
*
|
|
820
|
+
* Generates a new ECDSA key pair and enables it as a session key on
|
|
821
|
+
* the smart account via Smart Sessions. The session is scoped to
|
|
822
|
+
* specific target contracts, function selectors, and time window.
|
|
823
|
+
*
|
|
824
|
+
* @returns The session key address, private key, and permission ID.
|
|
825
|
+
*/
|
|
826
|
+
async createSession(wallet, params, ownerKey) {
|
|
827
|
+
const client = this.getWalletClient(wallet.address);
|
|
828
|
+
try {
|
|
829
|
+
const sessionPrivateKey = generatePrivateKey();
|
|
830
|
+
const sessionAccount = privateKeyToAccount(sessionPrivateKey);
|
|
831
|
+
const chainId = BigInt(this.config.chain.id);
|
|
832
|
+
const session = buildSession(
|
|
833
|
+
sessionAccount.address,
|
|
834
|
+
params,
|
|
835
|
+
chainId
|
|
836
|
+
);
|
|
837
|
+
const permissionId = computePermissionId(session);
|
|
838
|
+
const smartSessionsModule = getSmartSessionsModule();
|
|
839
|
+
try {
|
|
840
|
+
const isInstalled = await client.isModuleInstalled({
|
|
841
|
+
type: "validator",
|
|
842
|
+
address: smartSessionsModule.address,
|
|
843
|
+
context: "0x"
|
|
844
|
+
});
|
|
845
|
+
if (!isInstalled) {
|
|
846
|
+
await client.installModule({
|
|
847
|
+
type: "validator",
|
|
848
|
+
address: smartSessionsModule.address,
|
|
849
|
+
context: smartSessionsModule.initData
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
} catch {
|
|
853
|
+
await client.installModule({
|
|
854
|
+
type: "validator",
|
|
855
|
+
address: smartSessionsModule.address,
|
|
856
|
+
context: smartSessionsModule.initData
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
const enableDetails = await getEnableDetails(
|
|
860
|
+
[session],
|
|
861
|
+
{ address: wallet.address, type: "safe" },
|
|
862
|
+
[this.publicClient]
|
|
863
|
+
);
|
|
864
|
+
const ownerAccount = resolveAccount(ownerKey);
|
|
865
|
+
const ownerSignature = await ownerAccount.signMessage({
|
|
866
|
+
message: { raw: enableDetails.permissionEnableHash }
|
|
867
|
+
});
|
|
868
|
+
const enableSig = encodeEnableSessionSignature(
|
|
869
|
+
enableDetails.permissionId,
|
|
870
|
+
ownerSignature,
|
|
871
|
+
enableDetails.enableSessionData
|
|
872
|
+
);
|
|
873
|
+
const walletSessions = this.sessions.get(wallet.address) ?? [];
|
|
874
|
+
walletSessions.push({
|
|
875
|
+
permissionId,
|
|
876
|
+
sessionKeyAddress: sessionAccount.address,
|
|
877
|
+
sessionKeyPrivateKey: sessionPrivateKey,
|
|
878
|
+
expiresAt: params.expiresAt,
|
|
879
|
+
actions: params.actions.map((a) => ({
|
|
880
|
+
target: a.target,
|
|
881
|
+
selector: a.selector
|
|
882
|
+
}))
|
|
883
|
+
});
|
|
884
|
+
this.sessions.set(wallet.address, walletSessions);
|
|
885
|
+
return {
|
|
886
|
+
sessionKey: sessionAccount.address,
|
|
887
|
+
privateKey: sessionPrivateKey,
|
|
888
|
+
permissionId
|
|
889
|
+
};
|
|
890
|
+
} catch (error) {
|
|
891
|
+
if (error instanceof SessionError) throw error;
|
|
892
|
+
throw new SessionError(
|
|
893
|
+
error instanceof Error ? error.message : String(error)
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Revoke a session key, permanently disabling it.
|
|
899
|
+
*/
|
|
900
|
+
async revokeSession(wallet, permissionId, _ownerKey) {
|
|
901
|
+
const client = this.getWalletClient(wallet.address);
|
|
902
|
+
try {
|
|
903
|
+
const removeAction = getRemoveAction(permissionId);
|
|
904
|
+
await client.sendTransaction({
|
|
905
|
+
calls: [removeAction]
|
|
906
|
+
});
|
|
907
|
+
const walletSessions = this.sessions.get(wallet.address) ?? [];
|
|
908
|
+
this.sessions.set(
|
|
909
|
+
wallet.address,
|
|
910
|
+
walletSessions.filter((s) => s.permissionId !== permissionId)
|
|
911
|
+
);
|
|
912
|
+
} catch (error) {
|
|
913
|
+
throw new SessionError(
|
|
914
|
+
error instanceof Error ? error.message : String(error)
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Get active sessions for a wallet.
|
|
920
|
+
*/
|
|
921
|
+
getActiveSessions(walletAddress) {
|
|
922
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
923
|
+
const walletSessions = this.sessions.get(walletAddress) ?? [];
|
|
924
|
+
return walletSessions.filter((s) => s.expiresAt > now).map((s) => ({
|
|
925
|
+
sessionKey: s.sessionKeyAddress,
|
|
926
|
+
actions: s.actions.map((a) => ({ target: a.target, selector: a.selector })),
|
|
927
|
+
expiresAt: s.expiresAt,
|
|
928
|
+
isActive: true
|
|
929
|
+
}));
|
|
930
|
+
}
|
|
931
|
+
// ─── Transaction Execution ────────────────────────────────────
|
|
932
|
+
/**
|
|
933
|
+
* Execute a single transaction from the agent wallet.
|
|
934
|
+
* The transaction is submitted as a UserOperation through the bundler.
|
|
935
|
+
* Hooks (spending limits, allowlist, pause) are enforced on-chain.
|
|
936
|
+
*/
|
|
937
|
+
async execute(wallet, params) {
|
|
938
|
+
const client = this.getWalletClient(wallet.address);
|
|
939
|
+
try {
|
|
940
|
+
const hash = await client.sendTransaction({
|
|
941
|
+
calls: [
|
|
942
|
+
{
|
|
943
|
+
to: params.target,
|
|
944
|
+
value: params.value ?? 0n,
|
|
945
|
+
data: params.data ?? "0x"
|
|
946
|
+
}
|
|
947
|
+
]
|
|
948
|
+
});
|
|
949
|
+
return hash;
|
|
950
|
+
} catch (error) {
|
|
951
|
+
throw new ExecutionError(
|
|
952
|
+
error instanceof Error ? error.message : String(error),
|
|
953
|
+
error
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Execute a batch of transactions atomically from the agent wallet.
|
|
959
|
+
* All calls are encoded into a single UserOperation with batch mode.
|
|
960
|
+
*/
|
|
961
|
+
async executeBatch(wallet, params) {
|
|
962
|
+
const client = this.getWalletClient(wallet.address);
|
|
963
|
+
try {
|
|
964
|
+
const calls = params.calls.map((call) => ({
|
|
965
|
+
to: call.target,
|
|
966
|
+
value: call.value ?? 0n,
|
|
967
|
+
data: call.data ?? "0x"
|
|
968
|
+
}));
|
|
969
|
+
const hash = await client.sendTransaction({
|
|
970
|
+
calls
|
|
971
|
+
});
|
|
972
|
+
return hash;
|
|
973
|
+
} catch (error) {
|
|
974
|
+
throw new ExecutionError(
|
|
975
|
+
error instanceof Error ? error.message : String(error),
|
|
976
|
+
error
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// ─── Query Functions ──────────────────────────────────────────
|
|
981
|
+
/**
|
|
982
|
+
* Get the remaining spending allowance for a token on a wallet.
|
|
983
|
+
* Reads directly from the SpendingLimitHook contract.
|
|
984
|
+
*/
|
|
985
|
+
async getRemainingAllowance(walletAddress, token) {
|
|
986
|
+
const moduleAddresses = this.config.moduleAddresses;
|
|
987
|
+
if (!moduleAddresses) {
|
|
988
|
+
throw new PolicyConfigError(
|
|
989
|
+
"moduleAddresses not configured \u2014 cannot query SpendingLimitHook"
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
const result = await this.publicClient.readContract({
|
|
993
|
+
address: moduleAddresses.spendingLimitHook,
|
|
994
|
+
abi: SPENDING_LIMIT_HOOK_ABI,
|
|
995
|
+
functionName: "getRemainingAllowance",
|
|
996
|
+
args: [walletAddress, token]
|
|
997
|
+
});
|
|
998
|
+
return result;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Check if a wallet is currently paused.
|
|
1002
|
+
* Reads directly from the EmergencyPauseHook contract.
|
|
1003
|
+
*/
|
|
1004
|
+
async isPaused(walletAddress) {
|
|
1005
|
+
const moduleAddresses = this.config.moduleAddresses;
|
|
1006
|
+
if (!moduleAddresses) {
|
|
1007
|
+
throw new PolicyConfigError(
|
|
1008
|
+
"moduleAddresses not configured \u2014 cannot query EmergencyPauseHook"
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
const result = await this.publicClient.readContract({
|
|
1012
|
+
address: moduleAddresses.emergencyPauseHook,
|
|
1013
|
+
abi: EMERGENCY_PAUSE_HOOK_ABI,
|
|
1014
|
+
functionName: "isPaused",
|
|
1015
|
+
args: [walletAddress]
|
|
1016
|
+
});
|
|
1017
|
+
return result;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Get the native ETH balance of a wallet.
|
|
1021
|
+
*/
|
|
1022
|
+
async getBalances(walletAddress) {
|
|
1023
|
+
const eth = await this.publicClient.getBalance({
|
|
1024
|
+
address: walletAddress
|
|
1025
|
+
});
|
|
1026
|
+
return { eth, tokens: [] };
|
|
1027
|
+
}
|
|
1028
|
+
// ─── Pause / Unpause (Guardian Actions) ───────────────────────
|
|
1029
|
+
/**
|
|
1030
|
+
* Pause a wallet. Must be called by the configured guardian.
|
|
1031
|
+
* This is NOT a UserOp — it's a direct call to the EmergencyPauseHook.
|
|
1032
|
+
*/
|
|
1033
|
+
async pause(walletAddress, guardianKey) {
|
|
1034
|
+
const moduleAddresses = this.config.moduleAddresses;
|
|
1035
|
+
if (!moduleAddresses) {
|
|
1036
|
+
throw new PolicyConfigError("moduleAddresses not configured");
|
|
1037
|
+
}
|
|
1038
|
+
const guardian = resolveAccount(guardianKey);
|
|
1039
|
+
const { request } = await this.publicClient.simulateContract({
|
|
1040
|
+
account: guardian,
|
|
1041
|
+
address: moduleAddresses.emergencyPauseHook,
|
|
1042
|
+
abi: EMERGENCY_PAUSE_HOOK_ABI,
|
|
1043
|
+
functionName: "pause",
|
|
1044
|
+
args: [walletAddress]
|
|
1045
|
+
});
|
|
1046
|
+
const guardianClient = createWalletClient({
|
|
1047
|
+
account: guardian,
|
|
1048
|
+
chain: this.config.chain,
|
|
1049
|
+
transport: http(this.config.rpcUrl)
|
|
1050
|
+
});
|
|
1051
|
+
return guardianClient.writeContract(request);
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Unpause a wallet. Must be called by the configured guardian.
|
|
1055
|
+
*/
|
|
1056
|
+
async unpause(walletAddress, guardianKey) {
|
|
1057
|
+
const moduleAddresses = this.config.moduleAddresses;
|
|
1058
|
+
if (!moduleAddresses) {
|
|
1059
|
+
throw new PolicyConfigError("moduleAddresses not configured");
|
|
1060
|
+
}
|
|
1061
|
+
const guardian = resolveAccount(guardianKey);
|
|
1062
|
+
const { request } = await this.publicClient.simulateContract({
|
|
1063
|
+
account: guardian,
|
|
1064
|
+
address: moduleAddresses.emergencyPauseHook,
|
|
1065
|
+
abi: EMERGENCY_PAUSE_HOOK_ABI,
|
|
1066
|
+
functionName: "unpause",
|
|
1067
|
+
args: [walletAddress]
|
|
1068
|
+
});
|
|
1069
|
+
const guardianClient = createWalletClient({
|
|
1070
|
+
account: guardian,
|
|
1071
|
+
chain: this.config.chain,
|
|
1072
|
+
transport: http(this.config.rpcUrl)
|
|
1073
|
+
});
|
|
1074
|
+
return guardianClient.writeContract(request);
|
|
1075
|
+
}
|
|
1076
|
+
// ─── Private Helpers ──────────────────────────────────────────
|
|
1077
|
+
resolvePolicies(params) {
|
|
1078
|
+
if (params.preset) {
|
|
1079
|
+
return PRESETS[params.preset](params.owner, params.presetParams);
|
|
1080
|
+
}
|
|
1081
|
+
return params.policies ?? [];
|
|
1082
|
+
}
|
|
1083
|
+
requireModuleAddresses(policies) {
|
|
1084
|
+
if (policies.length === 0) return void 0;
|
|
1085
|
+
const moduleAddresses = this.config.moduleAddresses;
|
|
1086
|
+
if (!moduleAddresses) {
|
|
1087
|
+
throw new WalletCreationError(
|
|
1088
|
+
"moduleAddresses must be configured when policies are specified. Provide the deployed hook contract addresses in SmartAgentKitConfig."
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
for (const policy of policies) {
|
|
1092
|
+
switch (policy.type) {
|
|
1093
|
+
case "spending-limit":
|
|
1094
|
+
if (!moduleAddresses.spendingLimitHook) {
|
|
1095
|
+
throw new WalletCreationError(
|
|
1096
|
+
"spendingLimitHook address required for spending-limit policy"
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
break;
|
|
1100
|
+
case "allowlist":
|
|
1101
|
+
if (!moduleAddresses.allowlistHook) {
|
|
1102
|
+
throw new WalletCreationError(
|
|
1103
|
+
"allowlistHook address required for allowlist policy"
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
break;
|
|
1107
|
+
case "emergency-pause":
|
|
1108
|
+
if (!moduleAddresses.emergencyPauseHook) {
|
|
1109
|
+
throw new WalletCreationError(
|
|
1110
|
+
"emergencyPauseHook address required for emergency-pause policy"
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
break;
|
|
1114
|
+
case "automation":
|
|
1115
|
+
throw new PolicyConfigError(
|
|
1116
|
+
"automation policies are not yet supported"
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return moduleAddresses;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Build and send the first UserOp that initializes all sub-hooks
|
|
1124
|
+
* and adds them to the HookMultiPlexer.
|
|
1125
|
+
*
|
|
1126
|
+
* This batch includes for each sub-hook:
|
|
1127
|
+
* 1. onInstall(initData) — initialize the sub-hook for this account
|
|
1128
|
+
* 2. setTrustedForwarder(hookMultiPlexer) — so sub-hooks resolve the
|
|
1129
|
+
* correct account when called through the multiplexer
|
|
1130
|
+
* 3. addHook(hookAddress, GLOBAL) on HookMultiPlexer — register the
|
|
1131
|
+
* sub-hook for all transactions
|
|
1132
|
+
*/
|
|
1133
|
+
async initializePolicies(client, policies, moduleAddresses, hookMultiPlexerAddress) {
|
|
1134
|
+
const calls = [];
|
|
1135
|
+
for (const policy of policies) {
|
|
1136
|
+
switch (policy.type) {
|
|
1137
|
+
case "spending-limit": {
|
|
1138
|
+
const hookAddress = moduleAddresses.spendingLimitHook;
|
|
1139
|
+
const initData = encodeSpendingLimitInitData(policy);
|
|
1140
|
+
this.pushSubHookInitCalls(
|
|
1141
|
+
calls,
|
|
1142
|
+
hookAddress,
|
|
1143
|
+
initData,
|
|
1144
|
+
hookMultiPlexerAddress
|
|
1145
|
+
);
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
case "allowlist": {
|
|
1149
|
+
const hookAddress = moduleAddresses.allowlistHook;
|
|
1150
|
+
const initData = encodeAllowlistInitData(policy);
|
|
1151
|
+
this.pushSubHookInitCalls(
|
|
1152
|
+
calls,
|
|
1153
|
+
hookAddress,
|
|
1154
|
+
initData,
|
|
1155
|
+
hookMultiPlexerAddress
|
|
1156
|
+
);
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
case "emergency-pause": {
|
|
1160
|
+
const hookAddress = moduleAddresses.emergencyPauseHook;
|
|
1161
|
+
const initData = encodeEmergencyPauseInitData(policy);
|
|
1162
|
+
this.pushSubHookInitCalls(
|
|
1163
|
+
calls,
|
|
1164
|
+
hookAddress,
|
|
1165
|
+
initData,
|
|
1166
|
+
hookMultiPlexerAddress
|
|
1167
|
+
);
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
case "automation":
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (calls.length === 0) return;
|
|
1175
|
+
await client.sendTransaction({
|
|
1176
|
+
calls
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Push the 3 calls needed to initialize a sub-hook:
|
|
1181
|
+
* 1. onInstall(initData) on the sub-hook
|
|
1182
|
+
* 2. setTrustedForwarder(multiplexer) on the sub-hook
|
|
1183
|
+
* 3. addHook(hookAddr, GLOBAL) on the HookMultiPlexer
|
|
1184
|
+
*/
|
|
1185
|
+
pushSubHookInitCalls(calls, hookAddress, initData, hookMultiPlexerAddress) {
|
|
1186
|
+
calls.push({
|
|
1187
|
+
to: hookAddress,
|
|
1188
|
+
value: 0n,
|
|
1189
|
+
data: encodeFunctionData({
|
|
1190
|
+
abi: MODULE_ONINSTALL_ABI,
|
|
1191
|
+
functionName: "onInstall",
|
|
1192
|
+
args: [initData]
|
|
1193
|
+
})
|
|
1194
|
+
});
|
|
1195
|
+
calls.push({
|
|
1196
|
+
to: hookAddress,
|
|
1197
|
+
value: 0n,
|
|
1198
|
+
data: encodeFunctionData({
|
|
1199
|
+
abi: SET_TRUSTED_FORWARDER_ABI,
|
|
1200
|
+
functionName: "setTrustedForwarder",
|
|
1201
|
+
args: [hookMultiPlexerAddress]
|
|
1202
|
+
})
|
|
1203
|
+
});
|
|
1204
|
+
calls.push({
|
|
1205
|
+
to: hookMultiPlexerAddress,
|
|
1206
|
+
value: 0n,
|
|
1207
|
+
data: encodeFunctionData({
|
|
1208
|
+
abi: HOOK_MULTIPLEXER_ABI,
|
|
1209
|
+
functionName: "addHook",
|
|
1210
|
+
args: [hookAddress, HOOK_TYPE_GLOBAL]
|
|
1211
|
+
})
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
mapPoliciesToInstalled(policies, moduleAddresses) {
|
|
1215
|
+
if (!moduleAddresses) return [];
|
|
1216
|
+
return policies.map((policy) => {
|
|
1217
|
+
switch (policy.type) {
|
|
1218
|
+
case "spending-limit":
|
|
1219
|
+
return {
|
|
1220
|
+
moduleAddress: moduleAddresses.spendingLimitHook,
|
|
1221
|
+
moduleType: 4,
|
|
1222
|
+
name: "SpendingLimitHook",
|
|
1223
|
+
config: policy
|
|
1224
|
+
};
|
|
1225
|
+
case "allowlist":
|
|
1226
|
+
return {
|
|
1227
|
+
moduleAddress: moduleAddresses.allowlistHook,
|
|
1228
|
+
moduleType: 4,
|
|
1229
|
+
name: "AllowlistHook",
|
|
1230
|
+
config: policy
|
|
1231
|
+
};
|
|
1232
|
+
case "emergency-pause":
|
|
1233
|
+
return {
|
|
1234
|
+
moduleAddress: moduleAddresses.emergencyPauseHook,
|
|
1235
|
+
moduleType: 4,
|
|
1236
|
+
name: "EmergencyPauseHook",
|
|
1237
|
+
config: policy
|
|
1238
|
+
};
|
|
1239
|
+
case "automation":
|
|
1240
|
+
return {
|
|
1241
|
+
moduleAddress: moduleAddresses.automationExecutor ?? "0x0000000000000000000000000000000000000000",
|
|
1242
|
+
moduleType: 2,
|
|
1243
|
+
name: "AutomationExecutor",
|
|
1244
|
+
config: policy
|
|
1245
|
+
};
|
|
1246
|
+
default: {
|
|
1247
|
+
const _exhaustive = policy;
|
|
1248
|
+
return {
|
|
1249
|
+
moduleAddress: "0x0000000000000000000000000000000000000000",
|
|
1250
|
+
moduleType: 0,
|
|
1251
|
+
name: "Unknown",
|
|
1252
|
+
config: _exhaustive
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
getWalletClient(walletAddress) {
|
|
1259
|
+
const client = this.walletClients.get(walletAddress);
|
|
1260
|
+
if (!client) {
|
|
1261
|
+
throw new ExecutionError(
|
|
1262
|
+
`No client found for wallet ${walletAddress}. Call createWallet() or connectWallet() first.`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
return client;
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
export {
|
|
1269
|
+
ALLOWLIST_HOOK_ABI,
|
|
1270
|
+
DEPLOYMENTS,
|
|
1271
|
+
EMERGENCY_PAUSE_HOOK_ABI,
|
|
1272
|
+
ENTRYPOINT_V07,
|
|
1273
|
+
ExecutionError,
|
|
1274
|
+
HOOK_MULTIPLEXER_ABI,
|
|
1275
|
+
HOOK_MULTIPLEXER_ADDRESS,
|
|
1276
|
+
HOOK_TYPE_GLOBAL,
|
|
1277
|
+
MODULE_ADDRESSES,
|
|
1278
|
+
MODULE_TYPE_EXECUTOR,
|
|
1279
|
+
MODULE_TYPE_FALLBACK,
|
|
1280
|
+
MODULE_TYPE_HOOK,
|
|
1281
|
+
MODULE_TYPE_VALIDATOR,
|
|
1282
|
+
NATIVE_TOKEN,
|
|
1283
|
+
OWNABLE_VALIDATOR_ADDRESS,
|
|
1284
|
+
PRESETS,
|
|
1285
|
+
PolicyConfigError,
|
|
1286
|
+
RHINESTONE_ATTESTER,
|
|
1287
|
+
SAFE_7579_LAUNCHPAD,
|
|
1288
|
+
SAFE_7579_MODULE,
|
|
1289
|
+
SMART_SESSIONS_ADDRESS,
|
|
1290
|
+
SPENDING_LIMIT_HOOK_ABI,
|
|
1291
|
+
SessionError,
|
|
1292
|
+
SmartAgentKitClient,
|
|
1293
|
+
SmartAgentKitError,
|
|
1294
|
+
SmartSessionMode2 as SmartSessionMode,
|
|
1295
|
+
SpendingLimitExceededError,
|
|
1296
|
+
WINDOW_1_DAY,
|
|
1297
|
+
WINDOW_1_HOUR,
|
|
1298
|
+
WINDOW_1_WEEK,
|
|
1299
|
+
WalletCreationError,
|
|
1300
|
+
WalletPausedError,
|
|
1301
|
+
buildSession,
|
|
1302
|
+
computePermissionId,
|
|
1303
|
+
encodeAllowlistInitData,
|
|
1304
|
+
encodeEmergencyPauseInitData,
|
|
1305
|
+
encodeEnableSessionSignature,
|
|
1306
|
+
encodePolicyInitData,
|
|
1307
|
+
encodeSpendingLimitInitData,
|
|
1308
|
+
encodeUseSessionSignature,
|
|
1309
|
+
getRemoveAction as getRemoveSessionAction,
|
|
1310
|
+
getSmartSessionsModule
|
|
1311
|
+
};
|
|
1312
|
+
//# sourceMappingURL=index.mjs.map
|