@account-kit/smart-contracts 4.25.0 → 4.26.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 (43) hide show
  1. package/dist/esm/src/ma-v2/account/common/modularAccountV2Base.js +29 -17
  2. package/dist/esm/src/ma-v2/account/common/modularAccountV2Base.js.map +1 -1
  3. package/dist/esm/src/ma-v2/account/nativeSMASigner.js +6 -2
  4. package/dist/esm/src/ma-v2/account/nativeSMASigner.js.map +1 -1
  5. package/dist/esm/src/ma-v2/actions/deferralActions.d.ts +0 -5
  6. package/dist/esm/src/ma-v2/actions/deferralActions.js +0 -20
  7. package/dist/esm/src/ma-v2/actions/deferralActions.js.map +1 -1
  8. package/dist/esm/src/ma-v2/index.d.ts +2 -1
  9. package/dist/esm/src/ma-v2/index.js +2 -1
  10. package/dist/esm/src/ma-v2/index.js.map +1 -1
  11. package/dist/esm/src/ma-v2/modules/single-signer-validation/signer.js +6 -2
  12. package/dist/esm/src/ma-v2/modules/single-signer-validation/signer.js.map +1 -1
  13. package/dist/esm/src/ma-v2/permissionBuilder.d.ts +1 -0
  14. package/dist/esm/src/ma-v2/permissionBuilder.js +34 -20
  15. package/dist/esm/src/ma-v2/permissionBuilder.js.map +1 -1
  16. package/dist/esm/src/ma-v2/permissionBuilderErrors.d.ts +108 -0
  17. package/dist/esm/src/ma-v2/permissionBuilderErrors.js +192 -0
  18. package/dist/esm/src/ma-v2/permissionBuilderErrors.js.map +1 -0
  19. package/dist/esm/src/ma-v2/utils.d.ts +17 -0
  20. package/dist/esm/src/ma-v2/utils.js +23 -1
  21. package/dist/esm/src/ma-v2/utils.js.map +1 -1
  22. package/dist/types/src/ma-v2/account/common/modularAccountV2Base.d.ts.map +1 -1
  23. package/dist/types/src/ma-v2/account/nativeSMASigner.d.ts.map +1 -1
  24. package/dist/types/src/ma-v2/actions/deferralActions.d.ts +0 -5
  25. package/dist/types/src/ma-v2/actions/deferralActions.d.ts.map +1 -1
  26. package/dist/types/src/ma-v2/index.d.ts +2 -1
  27. package/dist/types/src/ma-v2/index.d.ts.map +1 -1
  28. package/dist/types/src/ma-v2/modules/single-signer-validation/signer.d.ts.map +1 -1
  29. package/dist/types/src/ma-v2/permissionBuilder.d.ts +1 -0
  30. package/dist/types/src/ma-v2/permissionBuilder.d.ts.map +1 -1
  31. package/dist/types/src/ma-v2/permissionBuilderErrors.d.ts +109 -0
  32. package/dist/types/src/ma-v2/permissionBuilderErrors.d.ts.map +1 -0
  33. package/dist/types/src/ma-v2/utils.d.ts +17 -0
  34. package/dist/types/src/ma-v2/utils.d.ts.map +1 -1
  35. package/package.json +5 -5
  36. package/src/ma-v2/account/common/modularAccountV2Base.ts +29 -18
  37. package/src/ma-v2/account/nativeSMASigner.ts +7 -2
  38. package/src/ma-v2/actions/deferralActions.ts +0 -31
  39. package/src/ma-v2/index.ts +2 -1
  40. package/src/ma-v2/modules/single-signer-validation/signer.ts +7 -2
  41. package/src/ma-v2/permissionBuilder.ts +50 -53
  42. package/src/ma-v2/permissionBuilderErrors.ts +166 -0
  43. package/src/ma-v2/utils.ts +38 -1
@@ -1,4 +1,4 @@
1
- import { toHex, zeroAddress, type Address, type Hex } from "viem";
1
+ import { maxUint48, toHex, zeroAddress, type Address, type Hex } from "viem";
2
2
  import {
3
3
  HookType,
4
4
  type HookConfig,
@@ -23,6 +23,20 @@ import {
23
23
  import { SingleSignerValidationModule } from "./modules/single-signer-validation/module.js";
24
24
  import { AllowlistModule } from "./modules/allowlist-module/module.js";
25
25
  import { TimeRangeModule } from "./modules/time-range-module/module.js";
26
+ import {
27
+ AccountAddressAsTargetError,
28
+ DeadlineOverLimitError,
29
+ DuplicateTargetAddressError,
30
+ ExpiredDeadlineError,
31
+ MultipleGasLimitError,
32
+ MultipleNativeTokenTransferError,
33
+ NoFunctionsProvidedError,
34
+ RootPermissionOnlyError,
35
+ SelectorNotAllowed,
36
+ UnsupportedPermissionTypeError,
37
+ ValidationConfigUnsetError,
38
+ ZeroAddressError,
39
+ } from "./permissionBuilderErrors.js";
26
40
 
27
41
  // We use this to offset the ERC20 spend limit entityId
28
42
  const HALF_UINT32 = 2147483647;
@@ -257,9 +271,7 @@ export class PermissionBuilder {
257
271
  // Check 1: If we're adding root, we can't have any other permissions
258
272
  if (permission.type === PermissionType.ROOT) {
259
273
  if (this.permissions.length !== 0) {
260
- throw new Error(
261
- "PERMISSION: ROOT: Cannot add ROOT permission with other permissions"
262
- );
274
+ throw new RootPermissionOnlyError(permission);
263
275
  }
264
276
  this.permissions.push(permission);
265
277
  // Set isGlobal to true
@@ -272,9 +284,7 @@ export class PermissionBuilder {
272
284
  // NOTE: Technically this could be replaced by checking permissions[0] since it should not be possible
273
285
  // to have >1 permission with root among them
274
286
  if (this.permissions.find((p) => p.type === PermissionType.ROOT)) {
275
- throw new Error(
276
- `PERMISSION: ${permission.type} => Cannot add permissions with ROOT enabled`
277
- );
287
+ throw new RootPermissionOnlyError(permission);
278
288
  }
279
289
 
280
290
  // Check 3: If the permission is either CONTRACT_ACCESS or FUNCTIONS_ON_CONTRACT, ensure it doesn't collide with another like it.
@@ -284,9 +294,7 @@ export class PermissionBuilder {
284
294
  ) {
285
295
  // Check 3.1: address must not be the account address, or the user should use the ACCOUNT_FUNCTIONS permission
286
296
  if (permission.data.address === this.client.account.address) {
287
- throw new Error(
288
- `PERMISSION: ${permission.type} => Account address as target, use ACCOUNT_FUNCTIONS for account address`
289
- );
297
+ throw new AccountAddressAsTargetError(permission);
290
298
  }
291
299
 
292
300
  // Check 3.2: there must not be an existing permission with this address as a target
@@ -302,20 +310,27 @@ export class PermissionBuilder {
302
310
  );
303
311
 
304
312
  if (existingPermissionWithSameAddress) {
305
- throw new Error(
306
- `PERMISSION: ${permission.type} => Address ${targetAddress} already has a permission. Cannot add multiple CONTRACT_ACCESS or FUNCTIONS_ON_CONTRACT permissions for the same target address.`
307
- );
313
+ throw new DuplicateTargetAddressError(permission, targetAddress);
308
314
  }
309
315
  }
310
316
 
311
317
  // Check 4: If the permission is ACCOUNT_FUNCTIONS, add selectors
312
318
  if (permission.type === PermissionType.ACCOUNT_FUNCTIONS) {
319
+ if (permission.data.functions.length === 0) {
320
+ throw new NoFunctionsProvidedError(permission);
321
+ }
322
+ // Explicitly disallow adding execute & executeBatch
323
+ if (permission.data.functions.includes(ACCOUNT_EXECUTE_SELECTOR)) {
324
+ throw new SelectorNotAllowed("execute");
325
+ } else if (
326
+ permission.data.functions.includes(ACCOUNT_EXECUTEBATCH_SELECTOR)
327
+ ) {
328
+ throw new SelectorNotAllowed("executeBatch");
329
+ }
313
330
  this.selectors = [...this.selectors, ...permission.data.functions];
314
- return this;
315
331
  }
316
332
 
317
333
  this.permissions.push(permission);
318
-
319
334
  return this;
320
335
  }
321
336
 
@@ -336,16 +351,16 @@ export class PermissionBuilder {
336
351
  // Add time range module hook via expiry
337
352
  if (this.deadline !== 0) {
338
353
  if (this.deadline < Date.now() / 1000) {
339
- throw new Error(
340
- `PERMISSION: compileDeferred(): deadline ${
341
- this.deadline
342
- } cannot be before now (${Date.now() / 1000})`
343
- );
354
+ throw new ExpiredDeadlineError(this.deadline, Date.now() / 1000);
355
+ }
356
+ if (this.deadline > maxUint48) {
357
+ throw new DeadlineOverLimitError(this.deadline);
344
358
  }
359
+
345
360
  this.hooks.push(
346
361
  TimeRangeModule.buildHook(
347
362
  {
348
- entityId: this.validationConfig.entityId, // will be timerange entityId
363
+ entityId: this.validationConfig.entityId,
349
364
  validUntil: this.deadline,
350
365
  validAfter: 0,
351
366
  },
@@ -420,9 +435,7 @@ export class PermissionBuilder {
420
435
  this.validationConfig.isGlobal === false &&
421
436
  this.selectors.length === 0
422
437
  ) {
423
- throw new Error(
424
- "Validation config unset, use permissionBuilder.configure(...)"
425
- );
438
+ throw new ValidationConfigUnsetError();
426
439
  }
427
440
  }
428
441
 
@@ -441,9 +454,7 @@ export class PermissionBuilder {
441
454
  case PermissionType.NATIVE_TOKEN_TRANSFER:
442
455
  // Should never be added twice, check is on addPermission(s) too
443
456
  if (rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER] !== undefined) {
444
- throw new Error(
445
- "PERMISSION: NATIVE_TOKEN_TRANSFER => Must have at most ONE native token transfer permission"
446
- );
457
+ throw new MultipleNativeTokenTransferError(permission);
447
458
  }
448
459
  rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER] = {
449
460
  hookConfig: {
@@ -464,9 +475,7 @@ export class PermissionBuilder {
464
475
  break;
465
476
  case PermissionType.ERC20_TOKEN_TRANSFER:
466
477
  if (permission.data.address === zeroAddress) {
467
- throw new Error(
468
- "PERMISSION: ERC20_TOKEN_TRANSFER => Zero address provided"
469
- );
478
+ throw new ZeroAddressError(permission);
470
479
  }
471
480
  rawHooks[HookIdentifier.ERC20_TOKEN_TRANSFER] = {
472
481
  hookConfig: {
@@ -522,9 +531,7 @@ export class PermissionBuilder {
522
531
  case PermissionType.GAS_LIMIT:
523
532
  // Should only ever be added once, check is also on addPermission(s)
524
533
  if (rawHooks[HookIdentifier.GAS_LIMIT] !== undefined) {
525
- throw new Error(
526
- "PERMISSION: GAS_LIMIT => Must have at most ONE gas limit permission"
527
- );
534
+ throw new MultipleGasLimitError(permission);
528
535
  }
529
536
  rawHooks[HookIdentifier.GAS_LIMIT] = {
530
537
  hookConfig: {
@@ -544,9 +551,7 @@ export class PermissionBuilder {
544
551
  break;
545
552
  case PermissionType.CONTRACT_ACCESS:
546
553
  if (permission.data.address === zeroAddress) {
547
- throw new Error(
548
- "PERMISSION: CONTRACT_ACCESS => Zero address provided"
549
- );
554
+ throw new ZeroAddressError(permission);
550
555
  }
551
556
  rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
552
557
  hookConfig: {
@@ -574,17 +579,11 @@ export class PermissionBuilder {
574
579
  };
575
580
  break;
576
581
  case PermissionType.ACCOUNT_FUNCTIONS:
577
- if (permission.data.functions.length === 0) {
578
- throw new Error(
579
- "PERMISSION: ACCOUNT_FUNCTION => No functions provided"
580
- ); // should be in add perm
581
- }
582
+ // This is handled in add permissions
582
583
  break;
583
584
  case PermissionType.FUNCTIONS_ON_ALL_CONTRACTS:
584
585
  if (permission.data.functions.length === 0) {
585
- throw new Error(
586
- "PERMISSION: FUNCTIONS_ON_ALL_CONTRACTS => No functions provided"
587
- );
586
+ throw new NoFunctionsProvidedError(permission);
588
587
  }
589
588
  rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
590
589
  hookConfig: {
@@ -613,14 +612,10 @@ export class PermissionBuilder {
613
612
  break;
614
613
  case PermissionType.FUNCTIONS_ON_CONTRACT:
615
614
  if (permission.data.functions.length === 0) {
616
- throw new Error(
617
- "PERMISSION: FUNCTIONS_ON_CONTRACT => No functions provided"
618
- );
615
+ throw new NoFunctionsProvidedError(permission);
619
616
  }
620
617
  if (permission.data.address === zeroAddress) {
621
- throw new Error(
622
- "PERMISSION: FUNCTIONS_ON_CONTRACT => Zero address provided"
623
- );
618
+ throw new ZeroAddressError(permission);
624
619
  }
625
620
  rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
626
621
  hookConfig: {
@@ -651,9 +646,7 @@ export class PermissionBuilder {
651
646
  // Root permission handled in addPermission
652
647
  break;
653
648
  default:
654
- throw new Error(
655
- `Unsupported permission type: ${(permission as any).type}`
656
- );
649
+ assertNever(permission);
657
650
  }
658
651
 
659
652
  // isGlobal guaranteed to be false since it's only set with root permissions,
@@ -714,3 +707,7 @@ export class PermissionBuilder {
714
707
  }
715
708
  }
716
709
  }
710
+
711
+ export function assertNever(_valid: never): never {
712
+ throw new UnsupportedPermissionTypeError();
713
+ }
@@ -0,0 +1,166 @@
1
+ import { BaseError, type Address } from "@aa-sdk/core";
2
+ import type { Permission } from "./permissionBuilder";
3
+
4
+ export class RootPermissionOnlyError extends BaseError {
5
+ override name = "PermissionBuilder: RootPermissionOnlyError";
6
+
7
+ /**
8
+ * Constructor for initializing an error message indicating that an account could not be found to execute the specified action.
9
+ *
10
+ * @param {Permission} permission The permission trying to be added atop the root permission
11
+ */
12
+ constructor(permission: Permission) {
13
+ super(`Adding ${permission}: Cannot add permissions with ROOT permission`);
14
+ }
15
+ }
16
+
17
+ export class AccountAddressAsTargetError extends BaseError {
18
+ override name = "PermissionBuilder: AccountAddressAsTargetError";
19
+
20
+ /**
21
+ * Constructor for initializing an error message indicating that account address is used as target.
22
+ *
23
+ * @param {Permission} permission The permission with account address as target
24
+ */
25
+ constructor(permission: Permission) {
26
+ super(
27
+ `${permission.type}: Account address as target, use ACCOUNT_FUNCTIONS for account address`
28
+ );
29
+ }
30
+ }
31
+
32
+ export class DuplicateTargetAddressError extends BaseError {
33
+ override name = "PermissionBuilder: DuplicateTargetAddressError";
34
+
35
+ /**
36
+ * Constructor for initializing an error message indicating duplicate target address in permissions.
37
+ *
38
+ * @param {Permission} permission The permission with duplicate target address
39
+ * @param {Address} targetAddress The duplicate target address
40
+ */
41
+ constructor(permission: Permission, targetAddress: Address) {
42
+ super(
43
+ `${permission.type}: Address ${targetAddress} already has a permission. Cannot add multiple CONTRACT_ACCESS or FUNCTIONS_ON_CONTRACT permissions for the same target address.`
44
+ );
45
+ }
46
+ }
47
+
48
+ export class NoFunctionsProvidedError extends BaseError {
49
+ override name = "PermissionBuilder: NoFunctionsProvidedError";
50
+
51
+ /**
52
+ * Constructor for initializing an error message indicating no functions were provided.
53
+ *
54
+ * @param {Permission} permission The permission missing functions
55
+ */
56
+ constructor(permission: Permission) {
57
+ super(`${permission.type}: No functions provided`);
58
+ }
59
+ }
60
+
61
+ export class ExpiredDeadlineError extends BaseError {
62
+ override name = "PermissionBuilder: ExpiredDeadlineError";
63
+
64
+ /**
65
+ * Constructor for initializing an error message indicating the deadline has expired.
66
+ *
67
+ * @param {number} deadline The expired deadline timestamp
68
+ * @param {number} currentTime The current timestamp
69
+ */
70
+ constructor(deadline: number, currentTime: number) {
71
+ super(
72
+ `compileDeferred(): deadline ${deadline} cannot be before now (${currentTime})`
73
+ );
74
+ }
75
+ }
76
+
77
+ export class DeadlineOverLimitError extends BaseError {
78
+ override name = "PermissionBuilder: DeadlineOverLimitError";
79
+
80
+ /**
81
+ * Constructor for initializing an error message indicating the deadline has expired.
82
+ *
83
+ * @param {number} deadline The expired deadline timestamp
84
+ */
85
+ constructor(deadline: number) {
86
+ super(
87
+ `compileDeferred(): deadline ${deadline} cannot be > max uint48 (2^48 - 1)`
88
+ );
89
+ }
90
+ }
91
+
92
+ export class ValidationConfigUnsetError extends BaseError {
93
+ override name = "PermissionBuilder: ValidationConfigUnsetError";
94
+
95
+ /**
96
+ * Constructor for initializing an error message indicating the validation config is unset.
97
+ */
98
+ constructor() {
99
+ super("Validation config unset, use permissionBuilder.configure(...)");
100
+ }
101
+ }
102
+
103
+ export class MultipleNativeTokenTransferError extends BaseError {
104
+ override name = "PermissionBuilder: MultipleNativeTokenTransferError";
105
+
106
+ /**
107
+ * Constructor for initializing an error message indicating multiple native token transfer permissions.
108
+ *
109
+ * @param {Permission} permission The duplicate native token transfer permission
110
+ */
111
+ constructor(permission: Permission) {
112
+ super(
113
+ `${permission.type}: Must have at most ONE native token transfer permission`
114
+ );
115
+ }
116
+ }
117
+
118
+ export class ZeroAddressError extends BaseError {
119
+ override name = "PermissionBuilder: ZeroAddressError";
120
+
121
+ /**
122
+ * Constructor for initializing an error message indicating zero address was provided.
123
+ *
124
+ * @param {Permission} permission The permission with zero address
125
+ */
126
+ constructor(permission: Permission) {
127
+ super(`${permission.type}: Zero address provided`);
128
+ }
129
+ }
130
+
131
+ export class MultipleGasLimitError extends BaseError {
132
+ override name = "PermissionBuilder: MultipleGasLimitError";
133
+
134
+ /**
135
+ * Constructor for initializing an error message indicating multiple gas limit permissions.
136
+ *
137
+ * @param {Permission} permission The duplicate gas limit permission
138
+ */
139
+ constructor(permission: Permission) {
140
+ super(`${permission.type}: Must have at most ONE gas limit permission`);
141
+ }
142
+ }
143
+
144
+ export class UnsupportedPermissionTypeError extends BaseError {
145
+ override name = "PermissionBuilder: UnsupportedPermissionTypeError";
146
+
147
+ /**
148
+ * Constructor for initializing an error message indicating an unsupported permission type.
149
+ */
150
+ constructor() {
151
+ super(`Unsupported permission type`);
152
+ }
153
+ }
154
+
155
+ export class SelectorNotAllowed extends BaseError {
156
+ override name = "SelectorNotAllowed";
157
+
158
+ /**
159
+ * Constructor for initializing an error message indicating that the selector being added is not allowed.
160
+ *
161
+ * @param {string} functionName The function name of the selector that is being added.
162
+ */
163
+ constructor(functionName: string) {
164
+ super(`Cannot add ${functionName} on the account`);
165
+ }
166
+ }
@@ -8,6 +8,9 @@ import {
8
8
  type Address,
9
9
  type Transport,
10
10
  parseAbi,
11
+ size,
12
+ concatHex,
13
+ hexToNumber,
11
14
  } from "viem";
12
15
  import {
13
16
  arbitrum,
@@ -268,13 +271,47 @@ export const buildFullNonceKey = ({
268
271
  export const parseDeferredAction = (
269
272
  deferredAction: Hex
270
273
  ): {
274
+ entityId: number;
275
+ isGlobalValidation: boolean;
271
276
  nonce: bigint;
272
277
  deferredActionData: Hex;
273
278
  hasAssociatedExecHooks: boolean;
274
279
  } => {
280
+ // 2 for 0x, 2 for 00/01, 38 for parallel nonce, 8 for entity id, 2 for options byte, 16 for parallel nonce
275
281
  return {
282
+ entityId: hexToNumber(`0x${deferredAction.slice(42, 50)}`),
283
+ isGlobalValidation:
284
+ hexToNumber(`0x${deferredAction.slice(50, 52)}`) % 2 === 1,
276
285
  nonce: BigInt(`0x${deferredAction.slice(4, 68)}`),
277
- deferredActionData: `0x${deferredAction.slice(68)}`,
286
+ deferredActionData: `0x${deferredAction.slice(68)}` as `0x${string}`,
278
287
  hasAssociatedExecHooks: deferredAction[3] === "1",
279
288
  };
280
289
  };
290
+ export type BuildDeferredActionDigestParams = {
291
+ fullPreSignatureDeferredActionDigest: Hex;
292
+ sig: Hex;
293
+ };
294
+
295
+ /**
296
+ * Creates the digest which must be prepended to the userOp signature.
297
+ *
298
+ * Assumption: The client this extends is used to sign the typed data.
299
+ *
300
+ * @param {object} args The argument object containing the following:
301
+ * @param {Hex} args.fullPreSignatureDeferredActionDigest The The data to append the signature and length to
302
+ * @param {Hex} args.sig The signature to include in the digest
303
+ * @returns {Hex} The encoded digest to be prepended to the userOp signature
304
+ */
305
+ export const buildDeferredActionDigest = ({
306
+ fullPreSignatureDeferredActionDigest,
307
+ sig,
308
+ }: BuildDeferredActionDigestParams): Hex => {
309
+ const sigLength = size(sig);
310
+
311
+ const encodedData = concatHex([
312
+ fullPreSignatureDeferredActionDigest,
313
+ toHex(sigLength, { size: 4 }),
314
+ sig,
315
+ ]);
316
+ return encodedData;
317
+ };