@alchemy/smart-accounts 5.0.0-beta.9 → 5.0.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 (44) hide show
  1. package/README.md +45 -0
  2. package/dist/esm/errors/InvalidEntityIdError.d.ts +2 -1
  3. package/dist/esm/errors/InvalidEntityIdError.js +4 -2
  4. package/dist/esm/errors/InvalidEntityIdError.js.map +1 -1
  5. package/dist/esm/index.d.ts +1 -1
  6. package/dist/esm/index.js +1 -1
  7. package/dist/esm/index.js.map +1 -1
  8. package/dist/esm/ma-v2/accounts/account.d.ts +39 -0
  9. package/dist/esm/ma-v2/accounts/account.js +39 -0
  10. package/dist/esm/ma-v2/accounts/account.js.map +1 -1
  11. package/dist/esm/ma-v2/decorators/deferralActions.d.ts +3 -3
  12. package/dist/esm/ma-v2/decorators/deferralActions.js +3 -3
  13. package/dist/esm/ma-v2/decorators/deferralActions.js.map +1 -1
  14. package/dist/esm/ma-v2/permissionBuilder.d.ts +2 -2
  15. package/dist/esm/ma-v2/permissionBuilder.js +61 -17
  16. package/dist/esm/ma-v2/permissionBuilder.js.map +1 -1
  17. package/dist/esm/ma-v2/utils/deferredActions.d.ts +6 -6
  18. package/dist/esm/ma-v2/utils/deferredActions.js +5 -5
  19. package/dist/esm/ma-v2/utils/deferredActions.js.map +1 -1
  20. package/dist/esm/version.d.ts +1 -1
  21. package/dist/esm/version.js +1 -1
  22. package/dist/esm/version.js.map +1 -1
  23. package/dist/types/errors/InvalidEntityIdError.d.ts +2 -1
  24. package/dist/types/errors/InvalidEntityIdError.d.ts.map +1 -1
  25. package/dist/types/index.d.ts +1 -1
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/types/ma-v2/accounts/account.d.ts +39 -0
  28. package/dist/types/ma-v2/accounts/account.d.ts.map +1 -1
  29. package/dist/types/ma-v2/decorators/deferralActions.d.ts +3 -3
  30. package/dist/types/ma-v2/decorators/deferralActions.d.ts.map +1 -1
  31. package/dist/types/ma-v2/permissionBuilder.d.ts +2 -2
  32. package/dist/types/ma-v2/permissionBuilder.d.ts.map +1 -1
  33. package/dist/types/ma-v2/utils/deferredActions.d.ts +6 -6
  34. package/dist/types/ma-v2/utils/deferredActions.d.ts.map +1 -1
  35. package/dist/types/version.d.ts +1 -1
  36. package/dist/types/version.d.ts.map +1 -1
  37. package/package.json +4 -4
  38. package/src/errors/InvalidEntityIdError.ts +7 -2
  39. package/src/index.ts +1 -1
  40. package/src/ma-v2/accounts/account.ts +39 -0
  41. package/src/ma-v2/decorators/deferralActions.ts +7 -7
  42. package/src/ma-v2/permissionBuilder.ts +70 -20
  43. package/src/ma-v2/utils/deferredActions.ts +9 -9
  44. package/src/version.ts +1 -1
@@ -65,6 +65,45 @@ export type ToModularAccountV2Params<
65
65
  *
66
66
  * @param {ToModularAccountV2Params} param0 - The parameters for creating a MAv2 account.
67
67
  * @returns {Promise<ModularAccountV2>} A MAv2 account.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { createPublicClient } from "viem";
72
+ * import { createBundlerClient, createPaymasterClient } from "viem/account-abstraction";
73
+ * import { sepolia } from "viem/chains";
74
+ * import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
75
+ * import { alchemyTransport } from "@alchemy/common";
76
+ * import { estimateFeesPerGas } from "@alchemy/aa-infra";
77
+ * import { toModularAccountV2 } from "@alchemy/smart-accounts";
78
+ *
79
+ * const transport = alchemyTransport({ apiKey: "YOUR_API_KEY" });
80
+ *
81
+ * // 1. Create a MAv2 smart account
82
+ * const account = await toModularAccountV2({
83
+ * client: createPublicClient({ chain: sepolia, transport }),
84
+ * owner: privateKeyToAccount(generatePrivateKey()),
85
+ * });
86
+ *
87
+ * // 2. Create a bundler client with the account
88
+ * const bundlerClient = createBundlerClient({
89
+ * account,
90
+ * chain: sepolia,
91
+ * transport,
92
+ * userOperation: {
93
+ * estimateFeesPerGas,
94
+ * },
95
+ * // Optional: sponsor gas with a paymaster
96
+ * paymaster: createPaymasterClient({ transport }),
97
+ * paymasterContext: { policyId: "YOUR_POLICY_ID" },
98
+ * });
99
+ *
100
+ * // 3. Send a user operation
101
+ * const hash = await bundlerClient.sendUserOperation({
102
+ * calls: [{ to: "0x...", value: 0n, data: "0x" }],
103
+ * });
104
+ *
105
+ * const receipt = await bundlerClient.waitForUserOperationReceipt({ hash });
106
+ * ```
68
107
  */
69
108
  export async function toModularAccountV2<TMode extends Mode = Mode>({
70
109
  client,
@@ -45,7 +45,7 @@ export type CreateDeferredActionTypedDataParams = {
45
45
  nonce: bigint;
46
46
  };
47
47
 
48
- export type BuildPreSignatureDeferredActionDigestParams = {
48
+ export type BuildPreSignatureDeferredActionPayloadParams = {
49
49
  typedData: DeferredActionTypedData;
50
50
  };
51
51
 
@@ -60,8 +60,8 @@ export type DeferralActions = {
60
60
  createDeferredActionTypedDataObject: (
61
61
  args: CreateDeferredActionTypedDataParams,
62
62
  ) => Promise<DeferredActionReturnData>;
63
- buildPreSignatureDeferredActionDigest: (
64
- args: BuildPreSignatureDeferredActionDigestParams,
63
+ buildPreSignatureDeferredActionPayload: (
64
+ args: BuildPreSignatureDeferredActionPayloadParams,
65
65
  ) => Hex;
66
66
  getEntityIdAndNonce: (
67
67
  args: EntityIdAndNonceParams,
@@ -72,7 +72,7 @@ export type DeferralActions = {
72
72
  * Provides deferred action functionalities for a MA v2 client, ensuring compatibility with `SmartAccountClient`.
73
73
  *
74
74
  * @param {ModularAccountV2Client} client - The client instance which provides account and sendUserOperation functionality.
75
- * @returns {object} - An object containing three methods: `createDeferredActionTypedDataObject`, `buildDeferredActionDigest`, and `buildUserOperationWithDeferredAction`.
75
+ * @returns {object} - An object containing three methods: `createDeferredActionTypedDataObject`, `buildPreSignatureDeferredActionPayload`, and `buildUserOperationWithDeferredAction`.
76
76
  */
77
77
  export const deferralActions = <
78
78
  TTransport extends Transport = Transport,
@@ -114,9 +114,9 @@ export const deferralActions = <
114
114
  };
115
115
  };
116
116
 
117
- const buildPreSignatureDeferredActionDigest = ({
117
+ const buildPreSignatureDeferredActionPayload = ({
118
118
  typedData,
119
- }: BuildPreSignatureDeferredActionDigestParams): Hex => {
119
+ }: BuildPreSignatureDeferredActionPayloadParams): Hex => {
120
120
  const account = client.account;
121
121
  if (!account || !isModularAccountV2(account)) {
122
122
  throw new AccountNotFoundError();
@@ -187,7 +187,7 @@ export const deferralActions = <
187
187
 
188
188
  return {
189
189
  createDeferredActionTypedDataObject,
190
- buildPreSignatureDeferredActionDigest,
190
+ buildPreSignatureDeferredActionPayload,
191
191
  getEntityIdAndNonce,
192
192
  };
193
193
  };
@@ -35,15 +35,59 @@ import {
35
35
  ValidationConfigUnsetError,
36
36
  ZeroAddressError,
37
37
  } from "../errors/permissionBuilderErrors.js";
38
+ import { InvalidEntityIdError } from "../errors/InvalidEntityIdError.js";
38
39
  import type { SmartAccount } from "viem/account-abstraction";
39
40
  import { DefaultModuleAddress, isModularAccountV2 } from "./utils/account.js";
40
41
 
41
- // We use this to offset the ERC20 spend limit entityId
42
+ // Reserved offset for hooks that would otherwise collide on shared module storage
43
+ // (ERC20 spend limit vs PREVAL_ALLOWLIST on AllowlistModule; GAS_LIMIT vs
44
+ // NATIVE_TOKEN_TRANSFER on NativeTokenLimitModule). Any user-supplied entityId
45
+ // must be strictly less than this so the offset namespace stays disjoint.
42
46
  const HALF_UINT32 = 2147483647;
43
47
  const ERC20_APPROVE_SELECTOR = "0x095ea7b3";
44
48
  const ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
45
49
  const ACCOUNT_EXECUTE_SELECTOR = "0xb61d27f6";
46
50
  const ACCOUNT_EXECUTEBATCH_SELECTOR = "0x34fcd5be";
51
+ const ACCOUNT_PERFORM_CREATE_SELECTOR = "0x5998db5c";
52
+ const ACCOUNT_EXECUTE_WITH_RUNTIME_VALIDATION_SELECTOR = "0xf2680c0f";
53
+ const ACCOUNT_INSTALL_VALIDATION_SELECTOR = "0x1bbf564c";
54
+ const ACCOUNT_UNINSTALL_VALIDATION_SELECTOR = "0xb6b1ccfe";
55
+ const ACCOUNT_INSTALL_EXECUTION_SELECTOR = "0x1d37e7d6";
56
+ const ACCOUNT_UNINSTALL_EXECUTION_SELECTOR = "0x0b7cad71";
57
+ const ACCOUNT_UPGRADE_TO_AND_CALL_SELECTOR = "0x4f1ef286";
58
+ // Wrapped native functions that must not be added to a session key's selector allowlist.
59
+ const PRIVILEGED_SELECTORS: Record<string, string> = {
60
+ [ACCOUNT_PERFORM_CREATE_SELECTOR]: "performCreate",
61
+ [ACCOUNT_EXECUTE_WITH_RUNTIME_VALIDATION_SELECTOR]:
62
+ "executeWithRuntimeValidation",
63
+ [ACCOUNT_INSTALL_VALIDATION_SELECTOR]: "installValidation",
64
+ [ACCOUNT_UNINSTALL_VALIDATION_SELECTOR]: "uninstallValidation",
65
+ [ACCOUNT_INSTALL_EXECUTION_SELECTOR]: "installExecution",
66
+ [ACCOUNT_UNINSTALL_EXECUTION_SELECTOR]: "uninstallExecution",
67
+ [ACCOUNT_UPGRADE_TO_AND_CALL_SELECTOR]: "upgradeToAndCall",
68
+ };
69
+
70
+ // Auto-added by translatePermissions when a PREVAL_ALLOWLIST hook exists.
71
+ // Blocked from manual addition to ensure they're only added with proper hook context.
72
+ const SYSTEM_MANAGED_SELECTORS: Record<string, string> = {
73
+ [ACCOUNT_EXECUTE_SELECTOR]: "execute",
74
+ [ACCOUNT_EXECUTEBATCH_SELECTOR]: "executeBatch",
75
+ };
76
+
77
+ function assertNotForbiddenSelector(selector: Hex): void {
78
+ const normalized = selector.toLowerCase();
79
+ const match =
80
+ PRIVILEGED_SELECTORS[normalized] ?? SYSTEM_MANAGED_SELECTORS[normalized];
81
+ if (match != null) {
82
+ throw new SelectorNotAllowed(match);
83
+ }
84
+ }
85
+
86
+ function assertNoForbiddenSelectors(selectors: Hex[]): void {
87
+ for (const selector of selectors) {
88
+ assertNotForbiddenSelector(selector);
89
+ }
90
+ }
47
91
 
48
92
  /**
49
93
  * A pseudo-enum for permission types.
@@ -264,6 +308,13 @@ export class PermissionBuilder {
264
308
  throw new AccountNotFoundError();
265
309
  }
266
310
 
311
+ // EntityIds in [HALF_UINT32, uint32.max] overlap the offset namespace used by
312
+ // ERC20 spend-limit and GAS_LIMIT hooks, which would silently corrupt the
313
+ // shared module storage. Reject early.
314
+ if (entityId >= HALF_UINT32) {
315
+ throw new InvalidEntityIdError(entityId, HALF_UINT32 - 1);
316
+ }
317
+
267
318
  this.client = client;
268
319
  this.validationConfig = {
269
320
  moduleAddress: DefaultModuleAddress.SINGLE_SIGNER_VALIDATION,
@@ -277,7 +328,10 @@ export class PermissionBuilder {
277
328
  signer: key.publicKey,
278
329
  });
279
330
  this.nonce = nonce;
280
- if (selectors) this.selectors = selectors;
331
+ if (selectors) {
332
+ assertNoForbiddenSelectors(selectors);
333
+ this.selectors = selectors;
334
+ }
281
335
  if (hooks) this.hooks = hooks;
282
336
  if (deadline) this.deadline = deadline;
283
337
  }
@@ -289,6 +343,7 @@ export class PermissionBuilder {
289
343
  * @returns {this} The permission builder instance.
290
344
  */
291
345
  addSelector({ selector }: { selector: Hex }): this {
346
+ assertNotForbiddenSelector(selector);
292
347
  this.selectors.push(selector);
293
348
  return this;
294
349
  }
@@ -351,14 +406,7 @@ export class PermissionBuilder {
351
406
  if (permission.data.functions.length === 0) {
352
407
  throw new NoFunctionsProvidedError(permission);
353
408
  }
354
- // Explicitly disallow adding execute & executeBatch
355
- if (permission.data.functions.includes(ACCOUNT_EXECUTE_SELECTOR)) {
356
- throw new SelectorNotAllowed("execute");
357
- } else if (
358
- permission.data.functions.includes(ACCOUNT_EXECUTEBATCH_SELECTOR)
359
- ) {
360
- throw new SelectorNotAllowed("executeBatch");
361
- }
409
+ assertNoForbiddenSelectors(permission.data.functions);
362
410
  this.selectors = [...this.selectors, ...permission.data.functions];
363
411
  }
364
412
 
@@ -384,11 +432,11 @@ export class PermissionBuilder {
384
432
  /**
385
433
  * Compiles the deferred action typed data to sign.
386
434
  *
387
- * @returns {Promise<{typedData: DeferredActionTypedData, fullPreSignatureDeferredActionDigest: Hex}>} The deferred action typed data and the full pre-signature deferred action digest.
435
+ * @returns {Promise<{typedData: DeferredActionTypedData, fullPreSignatureDeferredActionPayload: Hex}>} The deferred action typed data and the full pre-signature deferred action payload.
388
436
  */
389
437
  async compileDeferred(): Promise<{
390
438
  typedData: DeferredActionTypedData;
391
- fullPreSignatureDeferredActionDigest: Hex;
439
+ fullPreSignatureDeferredActionPayload: Hex;
392
440
  }> {
393
441
  // Add time range module hook via expiry
394
442
  if (this.deadline !== 0) {
@@ -421,20 +469,20 @@ export class PermissionBuilder {
421
469
  nonce: this.nonce,
422
470
  });
423
471
 
424
- const preSignatureDigest = deferralActions(
472
+ const preSignaturePayload = deferralActions(
425
473
  this.client,
426
- ).buildPreSignatureDeferredActionDigest({ typedData });
474
+ ).buildPreSignatureDeferredActionPayload({ typedData });
427
475
 
428
- // Encode additional information to build the full pre-signature digest
429
- const fullPreSignatureDeferredActionDigest: `0x${string}` = `0x0${
476
+ // Encode additional information to build the full pre-signature payload
477
+ const fullPreSignatureDeferredActionPayload: `0x${string}` = `0x0${
430
478
  this.hasAssociatedExecHooks ? "1" : "0"
431
479
  }${toHex(this.nonce, {
432
480
  size: 32,
433
- }).slice(2)}${preSignatureDigest.slice(2)}`;
481
+ }).slice(2)}${preSignaturePayload.slice(2)}`;
434
482
 
435
483
  return {
436
484
  typedData,
437
- fullPreSignatureDeferredActionDigest,
485
+ fullPreSignatureDeferredActionPayload,
438
486
  };
439
487
  }
440
488
 
@@ -591,16 +639,18 @@ export class PermissionBuilder {
591
639
  if (rawHooks[HookIdentifier.GAS_LIMIT] !== undefined) {
592
640
  throw new MultipleGasLimitError(permission);
593
641
  }
642
+ // Offset the entityId so GAS_LIMIT writes to a different slot than
643
+ // NATIVE_TOKEN_TRANSFER on the shared NativeTokenLimitModule.
594
644
  rawHooks[HookIdentifier.GAS_LIMIT] = {
595
645
  hookConfig: {
596
646
  address: DefaultModuleAddress.NATIVE_TOKEN_LIMIT,
597
- entityId,
647
+ entityId: entityId + HALF_UINT32,
598
648
  hookType: HookType.VALIDATION,
599
649
  hasPreHooks: true,
600
650
  hasPostHooks: false,
601
651
  },
602
652
  initData: {
603
- entityId,
653
+ entityId: entityId + HALF_UINT32,
604
654
  spendLimit: BigInt(permission.data.limit),
605
655
  },
606
656
  };
@@ -34,31 +34,31 @@ export const parseDeferredAction = (
34
34
  };
35
35
  };
36
36
 
37
- export type BuildDeferredActionDigestParams = {
38
- fullPreSignatureDeferredActionDigest: Hex;
37
+ export type EncodeDeferredActionWithSignatureParams = {
38
+ fullPreSignatureDeferredActionPayload: Hex;
39
39
  sig: Hex;
40
40
  signaturePrefix?: SignaturePrefix;
41
41
  };
42
42
 
43
43
  /**
44
- * Creates the digest which must be prepended to the userOp signature.
44
+ * Encodes the deferred action with its signature, producing the payload to prepend to the userOp signature.
45
45
  *
46
46
  * Assumption: The client this extends is used to sign the typed data.
47
47
  *
48
- * @param {BuildDeferredActionDigestParams} params - The parameters for building the deferred action digest.
49
- * @returns {Hex} The encoded digest to be prepended to the userOp signature.
48
+ * @param {EncodeDeferredActionWithSignatureParams} params - The parameters for encoding the deferred action with its signature.
49
+ * @returns {Hex} The encoded payload to be prepended to the userOp signature.
50
50
  */
51
- export const buildDeferredActionDigest = ({
52
- fullPreSignatureDeferredActionDigest,
51
+ export const encodeDeferredActionWithSignature = ({
52
+ fullPreSignatureDeferredActionPayload,
53
53
  sig,
54
- }: BuildDeferredActionDigestParams): Hex => {
54
+ }: EncodeDeferredActionWithSignatureParams): Hex => {
55
55
  // 6492 sigs don't work here.
56
56
  const _sig = parseErc6492Signature(sig).signature;
57
57
 
58
58
  const sigLength = size(_sig);
59
59
 
60
60
  const encodedData = concatHex([
61
- fullPreSignatureDeferredActionDigest,
61
+ fullPreSignatureDeferredActionPayload,
62
62
  toHex(sigLength, { size: 4 }),
63
63
  _sig,
64
64
  ]);
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is autogenerated by inject-version.ts. Any changes will be
2
2
  // overwritten on commit!
3
- export const VERSION = "5.0.0-beta.9";
3
+ export const VERSION = "5.0.1";