@account-kit/smart-contracts 4.24.0 → 4.25.1

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 (35) hide show
  1. package/dist/esm/src/ma-v2/account/common/modularAccountV2Base.js +4 -8
  2. package/dist/esm/src/ma-v2/account/common/modularAccountV2Base.js.map +1 -1
  3. package/dist/esm/src/ma-v2/actions/deferralActions.d.ts +0 -5
  4. package/dist/esm/src/ma-v2/actions/deferralActions.js +0 -20
  5. package/dist/esm/src/ma-v2/actions/deferralActions.js.map +1 -1
  6. package/dist/esm/src/ma-v2/index.d.ts +2 -1
  7. package/dist/esm/src/ma-v2/index.js +2 -1
  8. package/dist/esm/src/ma-v2/index.js.map +1 -1
  9. package/dist/esm/src/ma-v2/permissionBuilder.d.ts +7 -8
  10. package/dist/esm/src/ma-v2/permissionBuilder.js +43 -34
  11. package/dist/esm/src/ma-v2/permissionBuilder.js.map +1 -1
  12. package/dist/esm/src/ma-v2/permissionBuilderErrors.d.ts +99 -0
  13. package/dist/esm/src/ma-v2/permissionBuilderErrors.js +176 -0
  14. package/dist/esm/src/ma-v2/permissionBuilderErrors.js.map +1 -0
  15. package/dist/esm/src/ma-v2/utils.d.ts +20 -0
  16. package/dist/esm/src/ma-v2/utils.js +28 -1
  17. package/dist/esm/src/ma-v2/utils.js.map +1 -1
  18. package/dist/types/src/ma-v2/account/common/modularAccountV2Base.d.ts.map +1 -1
  19. package/dist/types/src/ma-v2/actions/deferralActions.d.ts +0 -5
  20. package/dist/types/src/ma-v2/actions/deferralActions.d.ts.map +1 -1
  21. package/dist/types/src/ma-v2/index.d.ts +2 -1
  22. package/dist/types/src/ma-v2/index.d.ts.map +1 -1
  23. package/dist/types/src/ma-v2/permissionBuilder.d.ts +7 -8
  24. package/dist/types/src/ma-v2/permissionBuilder.d.ts.map +1 -1
  25. package/dist/types/src/ma-v2/permissionBuilderErrors.d.ts +100 -0
  26. package/dist/types/src/ma-v2/permissionBuilderErrors.d.ts.map +1 -0
  27. package/dist/types/src/ma-v2/utils.d.ts +20 -0
  28. package/dist/types/src/ma-v2/utils.d.ts.map +1 -1
  29. package/package.json +5 -5
  30. package/src/ma-v2/account/common/modularAccountV2Base.ts +4 -8
  31. package/src/ma-v2/actions/deferralActions.ts +0 -31
  32. package/src/ma-v2/index.ts +2 -1
  33. package/src/ma-v2/permissionBuilder.ts +58 -78
  34. package/src/ma-v2/permissionBuilderErrors.ts +153 -0
  35. package/src/ma-v2/utils.ts +45 -0
@@ -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,19 @@ 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
+ UnsupportedPermissionTypeError,
36
+ ValidationConfigUnsetError,
37
+ ZeroAddressError,
38
+ } from "./permissionBuilderErrors.js";
26
39
 
27
40
  // We use this to offset the ERC20 spend limit entityId
28
41
  const HALF_UINT32 = 2147483647;
@@ -209,25 +222,26 @@ export class PermissionBuilder {
209
222
  private hooks: Hook[] = [];
210
223
  private nonce: bigint = 0n;
211
224
  private hasAssociatedExecHooks: boolean = false;
225
+ private deadline: number = 0;
212
226
 
213
- constructor(client: ModularAccountV2Client) {
214
- this.client = client;
215
- }
216
-
217
- // Configures the builder
218
- configure({
227
+ constructor({
228
+ client,
219
229
  key,
220
230
  entityId,
221
231
  nonce,
222
232
  selectors,
223
233
  hooks,
234
+ deadline,
224
235
  }: {
236
+ client: ModularAccountV2Client;
225
237
  key: Key;
226
238
  entityId: number;
227
239
  nonce: bigint;
228
240
  selectors?: Hex[];
229
241
  hooks?: Hook[];
230
- }): this {
242
+ deadline?: number;
243
+ }) {
244
+ this.client = client;
231
245
  this.validationConfig = {
232
246
  moduleAddress: getDefaultSingleSignerValidationModuleAddress(
233
247
  this.client.chain
@@ -241,10 +255,10 @@ export class PermissionBuilder {
241
255
  entityId: entityId,
242
256
  signer: key.publicKey,
243
257
  });
258
+ this.nonce = nonce;
244
259
  if (selectors) this.selectors = selectors;
245
260
  if (hooks) this.hooks = hooks;
246
- this.nonce = nonce;
247
- return this;
261
+ if (deadline) this.deadline = deadline;
248
262
  }
249
263
 
250
264
  addSelector({ selector }: { selector: Hex }): this {
@@ -256,9 +270,7 @@ export class PermissionBuilder {
256
270
  // Check 1: If we're adding root, we can't have any other permissions
257
271
  if (permission.type === PermissionType.ROOT) {
258
272
  if (this.permissions.length !== 0) {
259
- throw new Error(
260
- "PERMISSION: ROOT: Cannot add ROOT permission with other permissions"
261
- );
273
+ throw new RootPermissionOnlyError(permission);
262
274
  }
263
275
  this.permissions.push(permission);
264
276
  // Set isGlobal to true
@@ -271,9 +283,7 @@ export class PermissionBuilder {
271
283
  // NOTE: Technically this could be replaced by checking permissions[0] since it should not be possible
272
284
  // to have >1 permission with root among them
273
285
  if (this.permissions.find((p) => p.type === PermissionType.ROOT)) {
274
- throw new Error(
275
- `PERMISSION: ${permission.type} => Cannot add permissions with ROOT enabled`
276
- );
286
+ throw new RootPermissionOnlyError(permission);
277
287
  }
278
288
 
279
289
  // Check 3: If the permission is either CONTRACT_ACCESS or FUNCTIONS_ON_CONTRACT, ensure it doesn't collide with another like it.
@@ -283,9 +293,7 @@ export class PermissionBuilder {
283
293
  ) {
284
294
  // Check 3.1: address must not be the account address, or the user should use the ACCOUNT_FUNCTIONS permission
285
295
  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
- );
296
+ throw new AccountAddressAsTargetError(permission);
289
297
  }
290
298
 
291
299
  // Check 3.2: there must not be an existing permission with this address as a target
@@ -301,20 +309,19 @@ export class PermissionBuilder {
301
309
  );
302
310
 
303
311
  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
- );
312
+ throw new DuplicateTargetAddressError(permission, targetAddress);
307
313
  }
308
314
  }
309
315
 
310
316
  // Check 4: If the permission is ACCOUNT_FUNCTIONS, add selectors
311
317
  if (permission.type === PermissionType.ACCOUNT_FUNCTIONS) {
318
+ if (permission.data.functions.length === 0) {
319
+ throw new NoFunctionsProvidedError(permission);
320
+ }
312
321
  this.selectors = [...this.selectors, ...permission.data.functions];
313
- return this;
314
322
  }
315
323
 
316
324
  this.permissions.push(permission);
317
-
318
325
  return this;
319
326
  }
320
327
 
@@ -328,33 +335,24 @@ export class PermissionBuilder {
328
335
  }
329
336
 
330
337
  // 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
+ async compileDeferred(): Promise<{
338
339
  typedData: DeferredActionTypedData;
339
340
  fullPreSignatureDeferredActionDigest: Hex;
340
341
  }> {
341
- // Need to remove this because compileRaw may add selectors
342
- // this.validateConfiguration();
343
-
344
342
  // 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
- );
343
+ if (this.deadline !== 0) {
344
+ if (this.deadline < Date.now() / 1000) {
345
+ throw new ExpiredDeadlineError(this.deadline, Date.now() / 1000);
346
+ }
347
+ if (this.deadline > maxUint48) {
348
+ throw new DeadlineOverLimitError(this.deadline);
352
349
  }
350
+
353
351
  this.hooks.push(
354
352
  TimeRangeModule.buildHook(
355
353
  {
356
- entityId: this.validationConfig.entityId, // will be timerange entityId
357
- validUntil: deadline,
354
+ entityId: this.validationConfig.entityId,
355
+ validUntil: this.deadline,
358
356
  validAfter: 0,
359
357
  },
360
358
  getDefaultTimeRangeModuleAddress(this.client.chain)
@@ -368,7 +366,7 @@ export class PermissionBuilder {
368
366
  this.client
369
367
  ).createDeferredActionTypedDataObject({
370
368
  callData: installValidationCall,
371
- deadline: deadline,
369
+ deadline: this.deadline,
372
370
  nonce: this.nonce,
373
371
  });
374
372
 
@@ -377,8 +375,8 @@ export class PermissionBuilder {
377
375
  ).buildPreSignatureDeferredActionDigest({ typedData });
378
376
 
379
377
  // Encode additional information to build the full pre-signature digest
380
- const fullPreSignatureDeferredActionDigest: `0x${string}` = `0x00${
381
- this.hasAssociatedExecHooks ? "01" : "00"
378
+ const fullPreSignatureDeferredActionDigest: `0x${string}` = `0x0${
379
+ this.hasAssociatedExecHooks ? "1" : "0"
382
380
  }${toHex(this.nonce, {
383
381
  size: 32,
384
382
  }).slice(2)}${preSignatureDigest.slice(2)}`;
@@ -428,9 +426,7 @@ export class PermissionBuilder {
428
426
  this.validationConfig.isGlobal === false &&
429
427
  this.selectors.length === 0
430
428
  ) {
431
- throw new Error(
432
- "Validation config unset, use permissionBuilder.configure(...)"
433
- );
429
+ throw new ValidationConfigUnsetError();
434
430
  }
435
431
  }
436
432
 
@@ -449,9 +445,7 @@ export class PermissionBuilder {
449
445
  case PermissionType.NATIVE_TOKEN_TRANSFER:
450
446
  // Should never be added twice, check is on addPermission(s) too
451
447
  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
- );
448
+ throw new MultipleNativeTokenTransferError(permission);
455
449
  }
456
450
  rawHooks[HookIdentifier.NATIVE_TOKEN_TRANSFER] = {
457
451
  hookConfig: {
@@ -472,9 +466,7 @@ export class PermissionBuilder {
472
466
  break;
473
467
  case PermissionType.ERC20_TOKEN_TRANSFER:
474
468
  if (permission.data.address === zeroAddress) {
475
- throw new Error(
476
- "PERMISSION: ERC20_TOKEN_TRANSFER => Zero address provided"
477
- );
469
+ throw new ZeroAddressError(permission);
478
470
  }
479
471
  rawHooks[HookIdentifier.ERC20_TOKEN_TRANSFER] = {
480
472
  hookConfig: {
@@ -530,9 +522,7 @@ export class PermissionBuilder {
530
522
  case PermissionType.GAS_LIMIT:
531
523
  // Should only ever be added once, check is also on addPermission(s)
532
524
  if (rawHooks[HookIdentifier.GAS_LIMIT] !== undefined) {
533
- throw new Error(
534
- "PERMISSION: GAS_LIMIT => Must have at most ONE gas limit permission"
535
- );
525
+ throw new MultipleGasLimitError(permission);
536
526
  }
537
527
  rawHooks[HookIdentifier.GAS_LIMIT] = {
538
528
  hookConfig: {
@@ -552,9 +542,7 @@ export class PermissionBuilder {
552
542
  break;
553
543
  case PermissionType.CONTRACT_ACCESS:
554
544
  if (permission.data.address === zeroAddress) {
555
- throw new Error(
556
- "PERMISSION: CONTRACT_ACCESS => Zero address provided"
557
- );
545
+ throw new ZeroAddressError(permission);
558
546
  }
559
547
  rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
560
548
  hookConfig: {
@@ -582,17 +570,11 @@ export class PermissionBuilder {
582
570
  };
583
571
  break;
584
572
  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
- }
573
+ // This is handled in add permissions
590
574
  break;
591
575
  case PermissionType.FUNCTIONS_ON_ALL_CONTRACTS:
592
576
  if (permission.data.functions.length === 0) {
593
- throw new Error(
594
- "PERMISSION: FUNCTIONS_ON_ALL_CONTRACTS => No functions provided"
595
- );
577
+ throw new NoFunctionsProvidedError(permission);
596
578
  }
597
579
  rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
598
580
  hookConfig: {
@@ -621,14 +603,10 @@ export class PermissionBuilder {
621
603
  break;
622
604
  case PermissionType.FUNCTIONS_ON_CONTRACT:
623
605
  if (permission.data.functions.length === 0) {
624
- throw new Error(
625
- "PERMISSION: FUNCTIONS_ON_CONTRACT => No functions provided"
626
- );
606
+ throw new NoFunctionsProvidedError(permission);
627
607
  }
628
608
  if (permission.data.address === zeroAddress) {
629
- throw new Error(
630
- "PERMISSION: FUNCTIONS_ON_CONTRACT => Zero address provided"
631
- );
609
+ throw new ZeroAddressError(permission);
632
610
  }
633
611
  rawHooks[HookIdentifier.PREVAL_ALLOWLIST] = {
634
612
  hookConfig: {
@@ -659,9 +637,7 @@ export class PermissionBuilder {
659
637
  // Root permission handled in addPermission
660
638
  break;
661
639
  default:
662
- throw new Error(
663
- `Unsupported permission type: ${(permission as any).type}`
664
- );
640
+ assertNever(permission);
665
641
  }
666
642
 
667
643
  // isGlobal guaranteed to be false since it's only set with root permissions,
@@ -722,3 +698,7 @@ export class PermissionBuilder {
722
698
  }
723
699
  }
724
700
  }
701
+
702
+ export function assertNever(_valid: never): never {
703
+ throw new UnsupportedPermissionTypeError();
704
+ }
@@ -0,0 +1,153 @@
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
+ }
@@ -8,6 +8,8 @@ import {
8
8
  type Address,
9
9
  type Transport,
10
10
  parseAbi,
11
+ size,
12
+ concatHex,
11
13
  } from "viem";
12
14
  import {
13
15
  arbitrum,
@@ -263,3 +265,46 @@ export const buildFullNonceKey = ({
263
265
  (isGlobalValidation ? 1n : 0n)
264
266
  );
265
267
  };
268
+
269
+ // Parses out the 3 components from a deferred action
270
+ export const parseDeferredAction = (
271
+ deferredAction: Hex
272
+ ): {
273
+ nonce: bigint;
274
+ deferredActionData: Hex;
275
+ hasAssociatedExecHooks: boolean;
276
+ } => {
277
+ return {
278
+ nonce: BigInt(`0x${deferredAction.slice(4, 68)}`),
279
+ deferredActionData: `0x${deferredAction.slice(68)}`,
280
+ hasAssociatedExecHooks: deferredAction[3] === "1",
281
+ };
282
+ };
283
+ export type BuildDeferredActionDigestParams = {
284
+ fullPreSignatureDeferredActionDigest: Hex;
285
+ sig: Hex;
286
+ };
287
+
288
+ /**
289
+ * Creates the digest which must be prepended to the userOp signature.
290
+ *
291
+ * Assumption: The client this extends is used to sign the typed data.
292
+ *
293
+ * @param {object} args The argument object containing the following:
294
+ * @param {Hex} args.fullPreSignatureDeferredActionDigest The The data to append the signature and length to
295
+ * @param {Hex} args.sig The signature to include in the digest
296
+ * @returns {Hex} The encoded digest to be prepended to the userOp signature
297
+ */
298
+ export const buildDeferredActionDigest = ({
299
+ fullPreSignatureDeferredActionDigest,
300
+ sig,
301
+ }: BuildDeferredActionDigestParams): Hex => {
302
+ const sigLength = size(sig);
303
+
304
+ const encodedData = concatHex([
305
+ fullPreSignatureDeferredActionDigest,
306
+ toHex(sigLength, { size: 4 }),
307
+ sig,
308
+ ]);
309
+ return encodedData;
310
+ };