@alchemy/smart-accounts 5.0.0-beta.8 → 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.
- package/README.md +45 -0
- package/dist/esm/errors/InvalidEntityIdError.d.ts +2 -1
- package/dist/esm/errors/InvalidEntityIdError.js +4 -2
- package/dist/esm/errors/InvalidEntityIdError.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/ma-v2/accounts/account.d.ts +39 -0
- package/dist/esm/ma-v2/accounts/account.js +39 -0
- package/dist/esm/ma-v2/accounts/account.js.map +1 -1
- package/dist/esm/ma-v2/decorators/deferralActions.d.ts +3 -3
- package/dist/esm/ma-v2/decorators/deferralActions.js +3 -3
- package/dist/esm/ma-v2/decorators/deferralActions.js.map +1 -1
- package/dist/esm/ma-v2/permissionBuilder.d.ts +2 -2
- package/dist/esm/ma-v2/permissionBuilder.js +61 -17
- package/dist/esm/ma-v2/permissionBuilder.js.map +1 -1
- package/dist/esm/ma-v2/utils/deferredActions.d.ts +6 -6
- package/dist/esm/ma-v2/utils/deferredActions.js +5 -5
- package/dist/esm/ma-v2/utils/deferredActions.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/types/errors/InvalidEntityIdError.d.ts +2 -1
- package/dist/types/errors/InvalidEntityIdError.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/ma-v2/accounts/account.d.ts +39 -0
- package/dist/types/ma-v2/accounts/account.d.ts.map +1 -1
- package/dist/types/ma-v2/decorators/deferralActions.d.ts +3 -3
- package/dist/types/ma-v2/decorators/deferralActions.d.ts.map +1 -1
- package/dist/types/ma-v2/permissionBuilder.d.ts +2 -2
- package/dist/types/ma-v2/permissionBuilder.d.ts.map +1 -1
- package/dist/types/ma-v2/utils/deferredActions.d.ts +6 -6
- package/dist/types/ma-v2/utils/deferredActions.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/version.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/errors/InvalidEntityIdError.ts +7 -2
- package/src/index.ts +1 -1
- package/src/ma-v2/accounts/account.ts +39 -0
- package/src/ma-v2/decorators/deferralActions.ts +7 -7
- package/src/ma-v2/permissionBuilder.ts +70 -20
- package/src/ma-v2/utils/deferredActions.ts +9 -9
- 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
|
|
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
|
-
|
|
64
|
-
args:
|
|
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`, `
|
|
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
|
|
117
|
+
const buildPreSignatureDeferredActionPayload = ({
|
|
118
118
|
typedData,
|
|
119
|
-
}:
|
|
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
|
-
|
|
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
|
-
//
|
|
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)
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
472
|
+
const preSignaturePayload = deferralActions(
|
|
425
473
|
this.client,
|
|
426
|
-
).
|
|
474
|
+
).buildPreSignatureDeferredActionPayload({ typedData });
|
|
427
475
|
|
|
428
|
-
// Encode additional information to build the full pre-signature
|
|
429
|
-
const
|
|
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)}${
|
|
481
|
+
}).slice(2)}${preSignaturePayload.slice(2)}`;
|
|
434
482
|
|
|
435
483
|
return {
|
|
436
484
|
typedData,
|
|
437
|
-
|
|
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
|
|
38
|
-
|
|
37
|
+
export type EncodeDeferredActionWithSignatureParams = {
|
|
38
|
+
fullPreSignatureDeferredActionPayload: Hex;
|
|
39
39
|
sig: Hex;
|
|
40
40
|
signaturePrefix?: SignaturePrefix;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
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 {
|
|
49
|
-
* @returns {Hex} The encoded
|
|
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
|
|
52
|
-
|
|
51
|
+
export const encodeDeferredActionWithSignature = ({
|
|
52
|
+
fullPreSignatureDeferredActionPayload,
|
|
53
53
|
sig,
|
|
54
|
-
}:
|
|
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
|
-
|
|
61
|
+
fullPreSignatureDeferredActionPayload,
|
|
62
62
|
toHex(sigLength, { size: 4 }),
|
|
63
63
|
_sig,
|
|
64
64
|
]);
|
package/src/version.ts
CHANGED