@account-kit/smart-contracts 4.23.0 → 4.24.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.
Files changed (49) hide show
  1. package/dist/esm/src/ma-v2/account/common/modularAccountV2Base.d.ts +1 -0
  2. package/dist/esm/src/ma-v2/account/common/modularAccountV2Base.js +38 -11
  3. package/dist/esm/src/ma-v2/account/common/modularAccountV2Base.js.map +1 -1
  4. package/dist/esm/src/ma-v2/account/modularAccountV2.d.ts +1 -0
  5. package/dist/esm/src/ma-v2/account/modularAccountV2.js +2 -1
  6. package/dist/esm/src/ma-v2/account/modularAccountV2.js.map +1 -1
  7. package/dist/esm/src/ma-v2/account/nativeSMASigner.d.ts +2 -2
  8. package/dist/esm/src/ma-v2/account/nativeSMASigner.js +11 -8
  9. package/dist/esm/src/ma-v2/account/nativeSMASigner.js.map +1 -1
  10. package/dist/esm/src/ma-v2/actions/deferralActions.d.ts +9 -7
  11. package/dist/esm/src/ma-v2/actions/deferralActions.js +21 -35
  12. package/dist/esm/src/ma-v2/actions/deferralActions.js.map +1 -1
  13. package/dist/esm/src/ma-v2/index.d.ts +4 -0
  14. package/dist/esm/src/ma-v2/index.js +2 -0
  15. package/dist/esm/src/ma-v2/index.js.map +1 -1
  16. package/dist/esm/src/ma-v2/modules/single-signer-validation/signer.d.ts +2 -1
  17. package/dist/esm/src/ma-v2/modules/single-signer-validation/signer.js +10 -6
  18. package/dist/esm/src/ma-v2/modules/single-signer-validation/signer.js.map +1 -1
  19. package/dist/esm/src/ma-v2/permissionBuilder.d.ts +115 -0
  20. package/dist/esm/src/ma-v2/permissionBuilder.js +485 -0
  21. package/dist/esm/src/ma-v2/permissionBuilder.js.map +1 -0
  22. package/dist/esm/src/ma-v2/utils.d.ts +37 -0
  23. package/dist/esm/src/ma-v2/utils.js +7 -1
  24. package/dist/esm/src/ma-v2/utils.js.map +1 -1
  25. package/dist/types/src/ma-v2/account/common/modularAccountV2Base.d.ts +1 -0
  26. package/dist/types/src/ma-v2/account/common/modularAccountV2Base.d.ts.map +1 -1
  27. package/dist/types/src/ma-v2/account/modularAccountV2.d.ts +1 -0
  28. package/dist/types/src/ma-v2/account/modularAccountV2.d.ts.map +1 -1
  29. package/dist/types/src/ma-v2/account/nativeSMASigner.d.ts +2 -2
  30. package/dist/types/src/ma-v2/account/nativeSMASigner.d.ts.map +1 -1
  31. package/dist/types/src/ma-v2/actions/deferralActions.d.ts +9 -7
  32. package/dist/types/src/ma-v2/actions/deferralActions.d.ts.map +1 -1
  33. package/dist/types/src/ma-v2/index.d.ts +4 -0
  34. package/dist/types/src/ma-v2/index.d.ts.map +1 -1
  35. package/dist/types/src/ma-v2/modules/single-signer-validation/signer.d.ts +2 -1
  36. package/dist/types/src/ma-v2/modules/single-signer-validation/signer.d.ts.map +1 -1
  37. package/dist/types/src/ma-v2/permissionBuilder.d.ts +116 -0
  38. package/dist/types/src/ma-v2/permissionBuilder.d.ts.map +1 -0
  39. package/dist/types/src/ma-v2/utils.d.ts +37 -0
  40. package/dist/types/src/ma-v2/utils.d.ts.map +1 -1
  41. package/package.json +6 -5
  42. package/src/ma-v2/account/common/modularAccountV2Base.ts +52 -10
  43. package/src/ma-v2/account/modularAccountV2.ts +3 -0
  44. package/src/ma-v2/account/nativeSMASigner.ts +20 -14
  45. package/src/ma-v2/actions/deferralActions.ts +38 -51
  46. package/src/ma-v2/index.ts +4 -0
  47. package/src/ma-v2/modules/single-signer-validation/signer.ts +19 -13
  48. package/src/ma-v2/permissionBuilder.ts +724 -0
  49. package/src/ma-v2/utils.ts +10 -0
@@ -0,0 +1,724 @@
1
+ import { toHex, zeroAddress, type Address, type Hex } from "viem";
2
+ import {
3
+ HookType,
4
+ type HookConfig,
5
+ type ValidationConfig,
6
+ } from "./actions/common/types.js";
7
+ import {
8
+ installValidationActions,
9
+ type InstallValidationParams,
10
+ } from "./actions/install-validation/installValidation.js";
11
+ import type { ModularAccountV2Client } from "./client/client.js";
12
+ import {
13
+ deferralActions,
14
+ type DeferredActionTypedData,
15
+ } from "./actions/deferralActions.js";
16
+ import { NativeTokenLimitModule } from "./modules/native-token-limit-module/module.js";
17
+ import {
18
+ getDefaultAllowlistModuleAddress,
19
+ getDefaultNativeTokenLimitModuleAddress,
20
+ getDefaultSingleSignerValidationModuleAddress,
21
+ getDefaultTimeRangeModuleAddress,
22
+ } from "./modules/utils.js";
23
+ import { SingleSignerValidationModule } from "./modules/single-signer-validation/module.js";
24
+ import { AllowlistModule } from "./modules/allowlist-module/module.js";
25
+ import { TimeRangeModule } from "./modules/time-range-module/module.js";
26
+
27
+ // We use this to offset the ERC20 spend limit entityId
28
+ const HALF_UINT32 = 2147483647;
29
+ const ERC20_APPROVE_SELECTOR = "0x095ea7b3";
30
+ const ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
31
+ const ACCOUNT_EXECUTE_SELECTOR = "0xb61d27f6";
32
+ const ACCOUNT_EXECUTEBATCH_SELECTOR = "0x34fcd5be";
33
+
34
+ export enum PermissionType {
35
+ NATIVE_TOKEN_TRANSFER = "native-token-transfer",
36
+ ERC20_TOKEN_TRANSFER = "erc20-token-transfer",
37
+ // ERC721_TOKEN_TRANSFER = "erc721-token-transfer", //Unimplemented
38
+ // ERC1155_TOKEN_TRANSFER = "erc1155-token-transfer", //Unimplemented
39
+ GAS_LIMIT = "gas-limit",
40
+ // CALL_LIMIT = "call-limit", //Unimplemented
41
+ // RATE_LIMIT = "rate-limit", //Unimplemented
42
+ CONTRACT_ACCESS = "contract-access",
43
+ ACCOUNT_FUNCTIONS = "account-functions",
44
+ FUNCTIONS_ON_ALL_CONTRACTS = "functions-on-all-contracts",
45
+ FUNCTIONS_ON_CONTRACT = "functions-on-contract",
46
+ ROOT = "root",
47
+ }
48
+
49
+ enum HookIdentifier {
50
+ NATIVE_TOKEN_TRANSFER,
51
+ ERC20_TOKEN_TRANSFER,
52
+ GAS_LIMIT,
53
+ PREVAL_ALLOWLIST, // aggregate of CONTRACT_ACCESS, ACCOUNT_FUNCTIONS, FUNCTIONS_ON_ALL_CONTRACTS, FUNCTIONS_ON_CONTRACT
54
+ }
55
+
56
+ type PreExecutionHookConfig = {
57
+ address: Address;
58
+ entityId: number;
59
+ hookType: HookType.EXECUTION;
60
+ hasPreHooks: true;
61
+ hasPostHooks: false;
62
+ };
63
+
64
+ type PreValidationHookConfig = {
65
+ address: Address;
66
+ entityId: number;
67
+ hookType: HookType.VALIDATION;
68
+ hasPreHooks: true;
69
+ hasPostHooks: false;
70
+ };
71
+
72
+ type RawHooks = {
73
+ [HookIdentifier.NATIVE_TOKEN_TRANSFER]:
74
+ | {
75
+ hookConfig: PreExecutionHookConfig;
76
+ initData: {
77
+ entityId: number;
78
+ spendLimit: bigint;
79
+ };
80
+ }
81
+ | undefined;
82
+ [HookIdentifier.ERC20_TOKEN_TRANSFER]:
83
+ | {
84
+ hookConfig: PreExecutionHookConfig;
85
+ initData: {
86
+ entityId: number;
87
+ inputs: Array<{
88
+ target: Address;
89
+ hasSelectorAllowlist: boolean;
90
+ hasERC20SpendLimit: boolean;
91
+ erc20SpendLimit: bigint;
92
+ selectors: Array<Hex>;
93
+ }>;
94
+ };
95
+ }
96
+ | undefined;
97
+ [HookIdentifier.GAS_LIMIT]:
98
+ | {
99
+ hookConfig: PreValidationHookConfig;
100
+ initData: {
101
+ entityId: number;
102
+ spendLimit: bigint;
103
+ };
104
+ }
105
+ | undefined;
106
+ [HookIdentifier.PREVAL_ALLOWLIST]:
107
+ | {
108
+ hookConfig: PreValidationHookConfig;
109
+
110
+ initData: {
111
+ entityId: number;
112
+ inputs: Array<{
113
+ target: Address;
114
+ hasSelectorAllowlist: boolean;
115
+ hasERC20SpendLimit: boolean;
116
+ erc20SpendLimit: bigint;
117
+ selectors: Array<Hex>;
118
+ }>;
119
+ };
120
+ }
121
+ | undefined;
122
+ };
123
+
124
+ type OneOf<T extends {}[]> = T[number];
125
+
126
+ type Key = {
127
+ publicKey: Hex;
128
+ type: "secp256k1" | "contract";
129
+ };
130
+
131
+ export type Permission = OneOf<
132
+ [
133
+ {
134
+ // this permission allows transfer of native tokens from the account
135
+ type: PermissionType.NATIVE_TOKEN_TRANSFER;
136
+ data: {
137
+ allowance: Hex;
138
+ };
139
+ },
140
+ {
141
+ // this permission allows transfer or approval of erc20 tokens from the account
142
+ type: PermissionType.ERC20_TOKEN_TRANSFER;
143
+ data: {
144
+ address: Address; // erc20 token contract address
145
+ allowance: Hex;
146
+ };
147
+ },
148
+ {
149
+ // this permissions allows the key to spend gas for UOs
150
+ type: PermissionType.GAS_LIMIT;
151
+ data: {
152
+ limit: Hex;
153
+ };
154
+ },
155
+ {
156
+ // this permission grants access to all functions in a contract
157
+ type: PermissionType.CONTRACT_ACCESS;
158
+ data: {
159
+ address: Address;
160
+ };
161
+ },
162
+ {
163
+ // this permission grants access to functions in the account
164
+ type: PermissionType.ACCOUNT_FUNCTIONS;
165
+ data: {
166
+ functions: Hex[]; // function signatures
167
+ };
168
+ },
169
+ {
170
+ // this permission grants access to a function selector in any address or contract
171
+ type: PermissionType.FUNCTIONS_ON_ALL_CONTRACTS;
172
+ data: {
173
+ functions: Hex[]; // function signatures
174
+ };
175
+ },
176
+ {
177
+ // this permission grants access to specified functions on a specific contract
178
+ type: PermissionType.FUNCTIONS_ON_CONTRACT;
179
+ data: {
180
+ address: Address;
181
+ functions: Hex[];
182
+ };
183
+ },
184
+ {
185
+ // this permission grants full access to everything
186
+ type: PermissionType.ROOT;
187
+ data?: never;
188
+ }
189
+ ]
190
+ >;
191
+
192
+ type Hook = {
193
+ hookConfig: HookConfig;
194
+ initData: Hex;
195
+ };
196
+
197
+ export class PermissionBuilder {
198
+ private client: ModularAccountV2Client;
199
+ private validationConfig: ValidationConfig = {
200
+ moduleAddress: zeroAddress,
201
+ entityId: 0, // uint32
202
+ isGlobal: false,
203
+ isSignatureValidation: false,
204
+ isUserOpValidation: false,
205
+ };
206
+ private selectors: Hex[] = [];
207
+ private installData: Hex = "0x";
208
+ private permissions: Permission[] = [];
209
+ private hooks: Hook[] = [];
210
+ private nonce: bigint = 0n;
211
+ private hasAssociatedExecHooks: boolean = false;
212
+
213
+ constructor(client: ModularAccountV2Client) {
214
+ this.client = client;
215
+ }
216
+
217
+ // Configures the builder
218
+ configure({
219
+ key,
220
+ entityId,
221
+ nonce,
222
+ selectors,
223
+ hooks,
224
+ }: {
225
+ key: Key;
226
+ entityId: number;
227
+ nonce: bigint;
228
+ selectors?: Hex[];
229
+ hooks?: Hook[];
230
+ }): this {
231
+ this.validationConfig = {
232
+ moduleAddress: getDefaultSingleSignerValidationModuleAddress(
233
+ this.client.chain
234
+ ),
235
+ entityId,
236
+ isUserOpValidation: true,
237
+ isGlobal: false,
238
+ isSignatureValidation: false,
239
+ };
240
+ this.installData = SingleSignerValidationModule.encodeOnInstallData({
241
+ entityId: entityId,
242
+ signer: key.publicKey,
243
+ });
244
+ if (selectors) this.selectors = selectors;
245
+ if (hooks) this.hooks = hooks;
246
+ this.nonce = nonce;
247
+ return this;
248
+ }
249
+
250
+ addSelector({ selector }: { selector: Hex }): this {
251
+ this.selectors.push(selector);
252
+ return this;
253
+ }
254
+
255
+ addPermission({ permission }: { permission: Permission }): this {
256
+ // Check 1: If we're adding root, we can't have any other permissions
257
+ if (permission.type === PermissionType.ROOT) {
258
+ if (this.permissions.length !== 0) {
259
+ throw new Error(
260
+ "PERMISSION: ROOT: Cannot add ROOT permission with other permissions"
261
+ );
262
+ }
263
+ this.permissions.push(permission);
264
+ // Set isGlobal to true
265
+ this.validationConfig.isGlobal = true;
266
+ return this;
267
+ }
268
+
269
+ // Check 2: If the permission is NOT ROOT (guaranteed), ensure there is no ROOT permission set
270
+ // Will resolve to undefined if ROOT is not found
271
+ // NOTE: Technically this could be replaced by checking permissions[0] since it should not be possible
272
+ // to have >1 permission with root among them
273
+ if (this.permissions.find((p) => p.type === PermissionType.ROOT)) {
274
+ throw new Error(
275
+ `PERMISSION: ${permission.type} => Cannot add permissions with ROOT enabled`
276
+ );
277
+ }
278
+
279
+ // Check 3: If the permission is either CONTRACT_ACCESS or FUNCTIONS_ON_CONTRACT, ensure it doesn't collide with another like it.
280
+ if (
281
+ permission.type === PermissionType.CONTRACT_ACCESS ||
282
+ permission.type === PermissionType.FUNCTIONS_ON_CONTRACT
283
+ ) {
284
+ // Check 3.1: address must not be the account address, or the user should use the ACCOUNT_FUNCTIONS permission
285
+ if (permission.data.address === this.client.account.address) {
286
+ throw new Error(
287
+ `PERMISSION: ${permission.type} => Account address as target, use ACCOUNT_FUNCTIONS for account address`
288
+ );
289
+ }
290
+
291
+ // Check 3.2: there must not be an existing permission with this address as a target
292
+ const targetAddress = permission.data.address;
293
+ const existingPermissionWithSameAddress = this.permissions.find(
294
+ (p) =>
295
+ (p.type === PermissionType.CONTRACT_ACCESS &&
296
+ "address" in p.data &&
297
+ p.data.address === targetAddress) ||
298
+ (p.type === PermissionType.FUNCTIONS_ON_CONTRACT &&
299
+ "address" in p.data &&
300
+ p.data.address === targetAddress)
301
+ );
302
+
303
+ if (existingPermissionWithSameAddress) {
304
+ throw new Error(
305
+ `PERMISSION: ${permission.type} => Address ${targetAddress} already has a permission. Cannot add multiple CONTRACT_ACCESS or FUNCTIONS_ON_CONTRACT permissions for the same target address.`
306
+ );
307
+ }
308
+ }
309
+
310
+ // Check 4: If the permission is ACCOUNT_FUNCTIONS, add selectors
311
+ if (permission.type === PermissionType.ACCOUNT_FUNCTIONS) {
312
+ this.selectors = [...this.selectors, ...permission.data.functions];
313
+ return this;
314
+ }
315
+
316
+ this.permissions.push(permission);
317
+
318
+ return this;
319
+ }
320
+
321
+ addPermissions({ permissions }: { permissions: Permission[] }): this {
322
+ // We could validate each permission here, but for simplicity we'll just add them
323
+ // A better approach would be to call addPermission for each one
324
+ permissions.forEach((permission) => {
325
+ this.addPermission({ permission });
326
+ });
327
+ return this;
328
+ }
329
+
330
+ // Use for building deferred action typed data to sign
331
+ async compileDeferred({
332
+ deadline,
333
+ }: {
334
+ deadline: number;
335
+ uoValidationEntityId: number;
336
+ uoIsGlobalValidation: boolean;
337
+ }): Promise<{
338
+ typedData: DeferredActionTypedData;
339
+ fullPreSignatureDeferredActionDigest: Hex;
340
+ }> {
341
+ // Need to remove this because compileRaw may add selectors
342
+ // this.validateConfiguration();
343
+
344
+ // Add time range module hook via expiry
345
+ if (deadline !== 0) {
346
+ if (deadline < Date.now() / 1000) {
347
+ throw new Error(
348
+ `PERMISSION: compileDeferred(): Deadline ${deadline} cannot be before now (${
349
+ Date.now() / 1000
350
+ })`
351
+ );
352
+ }
353
+ this.hooks.push(
354
+ TimeRangeModule.buildHook(
355
+ {
356
+ entityId: this.validationConfig.entityId, // will be timerange entityId
357
+ validUntil: deadline,
358
+ validAfter: 0,
359
+ },
360
+ getDefaultTimeRangeModuleAddress(this.client.chain)
361
+ )
362
+ );
363
+ }
364
+
365
+ const installValidationCall = await this.compileRaw();
366
+
367
+ const { typedData } = await deferralActions(
368
+ this.client
369
+ ).createDeferredActionTypedDataObject({
370
+ callData: installValidationCall,
371
+ deadline: deadline,
372
+ nonce: this.nonce,
373
+ });
374
+
375
+ const preSignatureDigest = deferralActions(
376
+ this.client
377
+ ).buildPreSignatureDeferredActionDigest({ typedData });
378
+
379
+ // Encode additional information to build the full pre-signature digest
380
+ const fullPreSignatureDeferredActionDigest: `0x${string}` = `0x00${
381
+ this.hasAssociatedExecHooks ? "01" : "00"
382
+ }${toHex(this.nonce, {
383
+ size: 32,
384
+ }).slice(2)}${preSignatureDigest.slice(2)}`;
385
+
386
+ return {
387
+ typedData,
388
+ fullPreSignatureDeferredActionDigest,
389
+ };
390
+ }
391
+
392
+ // Use for direct `installValidation()` low-level calls (maybe useless)
393
+ async compileRaw(): Promise<Hex> {
394
+ // Translate all permissions into raw hooks if >0
395
+ if (this.permissions.length > 0) {
396
+ const rawHooks = this.translatePermissions(
397
+ this.validationConfig.entityId
398
+ );
399
+ // Add the translated permissions as hooks
400
+ this.addHooks(rawHooks);
401
+ }
402
+ this.validateConfiguration();
403
+
404
+ return await installValidationActions(this.client).encodeInstallValidation({
405
+ validationConfig: this.validationConfig,
406
+ selectors: this.selectors,
407
+ installData: this.installData,
408
+ hooks: this.hooks,
409
+ account: this.client.account,
410
+ });
411
+ }
412
+
413
+ // Use for compiling args to installValidation
414
+ async compileInstallArgs(): Promise<InstallValidationParams> {
415
+ this.validateConfiguration();
416
+
417
+ return {
418
+ validationConfig: this.validationConfig,
419
+ selectors: this.selectors,
420
+ installData: this.installData,
421
+ hooks: this.hooks,
422
+ account: this.client.account,
423
+ };
424
+ }
425
+
426
+ private validateConfiguration(): void {
427
+ if (
428
+ this.validationConfig.isGlobal === false &&
429
+ this.selectors.length === 0
430
+ ) {
431
+ throw new Error(
432
+ "Validation config unset, use permissionBuilder.configure(...)"
433
+ );
434
+ }
435
+ }
436
+
437
+ // Used to translate consolidated permissions into raw unencoded hooks
438
+ // Note entityId will be a member object later
439
+ private translatePermissions(entityId: number): RawHooks {
440
+ const rawHooks: RawHooks = {
441
+ [HookIdentifier.NATIVE_TOKEN_TRANSFER]: undefined,
442
+ [HookIdentifier.ERC20_TOKEN_TRANSFER]: undefined,
443
+ [HookIdentifier.GAS_LIMIT]: undefined,
444
+ [HookIdentifier.PREVAL_ALLOWLIST]: undefined,
445
+ };
446
+
447
+ this.permissions.forEach((permission) => {
448
+ switch (permission.type) {
449
+ case PermissionType.NATIVE_TOKEN_TRANSFER:
450
+ // Should never be added twice, check is on addPermission(s) too
451
+ if (rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER] !== undefined) {
452
+ throw new Error(
453
+ "PERMISSION: NATIVE_TOKEN_TRANSFER => Must have at most ONE native token transfer permission"
454
+ );
455
+ }
456
+ rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER] = {
457
+ hookConfig: {
458
+ address: getDefaultNativeTokenLimitModuleAddress(
459
+ this.client.chain
460
+ ),
461
+ entityId,
462
+ hookType: HookType.EXECUTION,
463
+ hasPreHooks: true,
464
+ hasPostHooks: false,
465
+ },
466
+ initData: {
467
+ entityId,
468
+ spendLimit: BigInt(permission.data.allowance),
469
+ },
470
+ };
471
+ this.hasAssociatedExecHooks = true;
472
+ break;
473
+ case PermissionType.ERC20_TOKEN_TRANSFER:
474
+ if (permission.data.address === zeroAddress) {
475
+ throw new Error(
476
+ "PERMISSION: ERC20_TOKEN_TRANSFER => Zero address provided"
477
+ );
478
+ }
479
+ rawHooks[HookIdentifier.ERC20_TOKEN_TRANSFER] = {
480
+ hookConfig: {
481
+ address: getDefaultAllowlistModuleAddress(this.client.chain),
482
+ entityId: entityId + HALF_UINT32,
483
+ hookType: HookType.EXECUTION,
484
+ hasPreHooks: true,
485
+ hasPostHooks: false,
486
+ },
487
+ initData: {
488
+ entityId: entityId + HALF_UINT32,
489
+ inputs: [
490
+ // Add previous inputs if they exist
491
+ ...(rawHooks[HookIdentifier.ERC20_TOKEN_TRANSFER]?.initData
492
+ .inputs || []),
493
+ {
494
+ target: permission.data.address,
495
+ hasSelectorAllowlist: false,
496
+ hasERC20SpendLimit: true,
497
+ erc20SpendLimit: BigInt(permission.data.allowance),
498
+ selectors: [],
499
+ },
500
+ ],
501
+ },
502
+ };
503
+ this.hasAssociatedExecHooks = true;
504
+ // Also allow `approve` and `transfer` for the erc20
505
+ rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
506
+ hookConfig: {
507
+ address: getDefaultAllowlistModuleAddress(this.client.chain),
508
+ entityId,
509
+ hookType: HookType.VALIDATION,
510
+ hasPreHooks: true,
511
+ hasPostHooks: false,
512
+ },
513
+ initData: {
514
+ entityId,
515
+ inputs: [
516
+ // Add previous inputs if they exist
517
+ ...(rawHooks[HookIdentifier.PREVAL_ALLOWLIST]?.initData
518
+ .inputs || []),
519
+ {
520
+ target: permission.data.address,
521
+ hasSelectorAllowlist: true,
522
+ hasERC20SpendLimit: false,
523
+ erc20SpendLimit: 0n,
524
+ selectors: [ERC20_APPROVE_SELECTOR, ERC20_TRANSFER_SELECTOR], // approve, transfer
525
+ },
526
+ ],
527
+ },
528
+ };
529
+ break;
530
+ case PermissionType.GAS_LIMIT:
531
+ // Should only ever be added once, check is also on addPermission(s)
532
+ if (rawHooks[HookIdentifier.GAS_LIMIT] !== undefined) {
533
+ throw new Error(
534
+ "PERMISSION: GAS_LIMIT => Must have at most ONE gas limit permission"
535
+ );
536
+ }
537
+ rawHooks[HookIdentifier.GAS_LIMIT] = {
538
+ hookConfig: {
539
+ address: getDefaultNativeTokenLimitModuleAddress(
540
+ this.client.chain
541
+ ),
542
+ entityId,
543
+ hookType: HookType.VALIDATION,
544
+ hasPreHooks: true,
545
+ hasPostHooks: false,
546
+ },
547
+ initData: {
548
+ entityId,
549
+ spendLimit: BigInt(permission.data.limit),
550
+ },
551
+ };
552
+ break;
553
+ case PermissionType.CONTRACT_ACCESS:
554
+ if (permission.data.address === zeroAddress) {
555
+ throw new Error(
556
+ "PERMISSION: CONTRACT_ACCESS => Zero address provided"
557
+ );
558
+ }
559
+ rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
560
+ hookConfig: {
561
+ address: getDefaultAllowlistModuleAddress(this.client.chain),
562
+ entityId,
563
+ hookType: HookType.VALIDATION,
564
+ hasPreHooks: true,
565
+ hasPostHooks: false,
566
+ },
567
+ initData: {
568
+ entityId,
569
+ inputs: [
570
+ // Add previous inputs if they exist
571
+ ...(rawHooks[HookIdentifier.PREVAL_ALLOWLIST]?.initData
572
+ .inputs || []),
573
+ {
574
+ target: permission.data.address,
575
+ hasSelectorAllowlist: false,
576
+ hasERC20SpendLimit: false,
577
+ erc20SpendLimit: 0n,
578
+ selectors: [],
579
+ },
580
+ ],
581
+ },
582
+ };
583
+ break;
584
+ case PermissionType.ACCOUNT_FUNCTIONS:
585
+ if (permission.data.functions.length === 0) {
586
+ throw new Error(
587
+ "PERMISSION: ACCOUNT_FUNCTION => No functions provided"
588
+ ); // should be in add perm
589
+ }
590
+ break;
591
+ case PermissionType.FUNCTIONS_ON_ALL_CONTRACTS:
592
+ if (permission.data.functions.length === 0) {
593
+ throw new Error(
594
+ "PERMISSION: FUNCTIONS_ON_ALL_CONTRACTS => No functions provided"
595
+ );
596
+ }
597
+ rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
598
+ hookConfig: {
599
+ address: getDefaultAllowlistModuleAddress(this.client.chain),
600
+ entityId,
601
+ hookType: HookType.VALIDATION,
602
+ hasPreHooks: true,
603
+ hasPostHooks: false,
604
+ },
605
+ initData: {
606
+ entityId,
607
+ inputs: [
608
+ // Add previous inputs if they exist
609
+ ...(rawHooks[HookIdentifier.PREVAL_ALLOWLIST]?.initData
610
+ .inputs || []),
611
+ {
612
+ target: zeroAddress,
613
+ hasSelectorAllowlist: false,
614
+ hasERC20SpendLimit: false,
615
+ erc20SpendLimit: 0n,
616
+ selectors: permission.data.functions,
617
+ },
618
+ ],
619
+ },
620
+ };
621
+ break;
622
+ case PermissionType.FUNCTIONS_ON_CONTRACT:
623
+ if (permission.data.functions.length === 0) {
624
+ throw new Error(
625
+ "PERMISSION: FUNCTIONS_ON_CONTRACT => No functions provided"
626
+ );
627
+ }
628
+ if (permission.data.address === zeroAddress) {
629
+ throw new Error(
630
+ "PERMISSION: FUNCTIONS_ON_CONTRACT => Zero address provided"
631
+ );
632
+ }
633
+ rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
634
+ hookConfig: {
635
+ address: getDefaultAllowlistModuleAddress(this.client.chain),
636
+ entityId,
637
+ hookType: HookType.VALIDATION,
638
+ hasPreHooks: true,
639
+ hasPostHooks: false,
640
+ },
641
+ initData: {
642
+ entityId,
643
+ inputs: [
644
+ // Add previous inputs if they exist
645
+ ...(rawHooks[HookIdentifier.PREVAL_ALLOWLIST]?.initData
646
+ .inputs || []),
647
+ {
648
+ target: permission.data.address,
649
+ hasSelectorAllowlist: true,
650
+ hasERC20SpendLimit: false,
651
+ erc20SpendLimit: 0n,
652
+ selectors: permission.data.functions,
653
+ },
654
+ ],
655
+ },
656
+ };
657
+ break;
658
+ case PermissionType.ROOT:
659
+ // Root permission handled in addPermission
660
+ break;
661
+ default:
662
+ throw new Error(
663
+ `Unsupported permission type: ${(permission as any).type}`
664
+ );
665
+ }
666
+
667
+ // isGlobal guaranteed to be false since it's only set with root permissions,
668
+ // we must add access to execute & executeBatch if there's a preVal allowlist hook set.
669
+ if (rawHooks[HookIdentifier.PREVAL_ALLOWLIST] !== undefined) {
670
+ const selectorsToAdd: `0x${string}`[] = [
671
+ ACCOUNT_EXECUTE_SELECTOR,
672
+ ACCOUNT_EXECUTEBATCH_SELECTOR,
673
+ ]; // execute, executeBatch
674
+
675
+ // Only add the selectors if they aren't already in this.selectors
676
+ const newSelectors = selectorsToAdd.filter(
677
+ (selector) => !this.selectors.includes(selector)
678
+ );
679
+
680
+ this.selectors = [...this.selectors, ...newSelectors];
681
+ }
682
+ });
683
+
684
+ return rawHooks;
685
+ }
686
+
687
+ private addHooks(rawHooks: RawHooks) {
688
+ if (rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER]) {
689
+ this.hooks.push({
690
+ hookConfig: rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER].hookConfig,
691
+ initData: NativeTokenLimitModule.encodeOnInstallData(
692
+ rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER].initData
693
+ ),
694
+ });
695
+ }
696
+
697
+ if (rawHooks[HookIdentifier.ERC20_TOKEN_TRANSFER]) {
698
+ this.hooks.push({
699
+ hookConfig: rawHooks[HookIdentifier.ERC20_TOKEN_TRANSFER].hookConfig,
700
+ initData: AllowlistModule.encodeOnInstallData(
701
+ rawHooks[HookIdentifier.ERC20_TOKEN_TRANSFER].initData
702
+ ),
703
+ });
704
+ }
705
+
706
+ if (rawHooks[HookIdentifier.GAS_LIMIT]) {
707
+ this.hooks.push({
708
+ hookConfig: rawHooks[HookIdentifier.GAS_LIMIT].hookConfig,
709
+ initData: NativeTokenLimitModule.encodeOnInstallData(
710
+ rawHooks[HookIdentifier.GAS_LIMIT].initData
711
+ ),
712
+ });
713
+ }
714
+
715
+ if (rawHooks[HookIdentifier.PREVAL_ALLOWLIST]) {
716
+ this.hooks.push({
717
+ hookConfig: rawHooks[HookIdentifier.PREVAL_ALLOWLIST].hookConfig,
718
+ initData: AllowlistModule.encodeOnInstallData(
719
+ rawHooks[HookIdentifier.PREVAL_ALLOWLIST].initData
720
+ ),
721
+ });
722
+ }
723
+ }
724
+ }