Adding Session Keys to your Modular Account V2
Session Keys are currently an experimental feature in the SDK. We are actively working on simplifying the usage, please note that there could be breaking changes as we improve this feature.
Adding a session key is simple with Account Kit! To add a session key, you need to 1) decide what permissions you want to grant the new key and 2) call the installValidation
method on the account. This method will send a single user operation that will add the session key with scoped permission to your smart contract account on-chain. You can then use that session key to sign transactions for your account within the defined permissions!
Adding scoped permissions to keys will happen via permission modules that you can pass as configuration parameters on the installValidation
method. Permissions can be combined to fit your use case (e.g. you can limit a session key to only be able to spend 10 USDC within the next 24 hours on behalf of your account).
Adding a global session key (i.e. additional owner)
This example shows:
- Adding a global session key to your Modular Account V2. This essentially gives the session key full control of your account. Functionally, this is how you can add another owner on your smart account.
- Adding a session key that can only call ‘execute’ on the account. Functionally, this allows the session key to have full control of the account other than changing the underlying account implementation.
import { function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)createModularAccountV2Client } from "@account-kit/smart-contracts";
import {
const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions,
const getDefaultSingleSignerValidationModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress,
const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule,
const semiModularAccountBytecodeAbi: readonly [{
readonly type: "constructor";
readonly inputs: readonly [{
readonly name: "entryPoint";
readonly type: "address";
readonly internalType: "contract IEntryPoint";
}, {
readonly name: "executionInstallDelegate";
readonly type: "address";
readonly internalType: "contract ExecutionInstallDelegate";
}];
readonly stateMutability: "nonpayable";
}, ... 60 more ..., {
...;
}]semiModularAccountBytecodeAbi,
} from "@account-kit/smart-contracts/experimental";
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
import { const toFunctionSelector: (fn: string | AbiFunction) => `0x${string}`Returns the function selector for a given function definition.
toFunctionSelector, function getAbiItem<const abi extends Abi | readonly unknown[], name extends AbiItemName<abi>, const args extends AbiItemArgs<abi, name> | undefined = undefined>(parameters: GetAbiItemParameters<abi, name, args>): GetAbiItemReturnType<abi, name, args>getAbiItem } from "viem";
import { type interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner } from "@aa-sdk/core";
const const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client = (
await createModularAccountV2Client<Chain, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(args: CreateModularAccountV2AlchemyClientParams<...>): Promise<...> (+1 overload)createModularAccountV2Client({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey: "your-api-key" }),
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
})
).extend: <InstallValidationActions<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>>(fn: (client: Client<...>) => InstallValidationActions<...>) => Client<...>extend(const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions);
let let sessionKeyEntityId: numbersessionKeyEntityId = 1;
const const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress =
function getDefaultSingleSignerValidationModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain);
const const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner: interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner =
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("SESSION_KEY_MNEMONIC");
// 1. Adding a session key with full permissions
await const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.installValidation: (args: InstallValidationParams<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>) => Promise<SendUserOperationResult>installValidation({
validationConfig: ValidationConfigvalidationConfig: {
moduleAddress: `0x${string}`moduleAddress: const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress,
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
isGlobal: booleanisGlobal: true,
isSignatureValidation: booleanisSignatureValidation: true,
isUserOpValidation: booleanisUserOpValidation: true,
},
selectors: `0x${string}`[]selectors: [],
installData: `0x${string}`installData: const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule.encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => HexencodeOnInstallData({
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
signer: `0x${string}`signer: await const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner.SmartAccountSigner<any>.getAddress: () => Promise<Address>getAddress(), // Address of the session key
}),
hooks: {
hookConfig: HookConfig;
initData: Hex;
}[]hooks: [],
});
// 2. Adding a session key that can only call `execute` or `executeBatch` on the account
let sessionKeyEntityId: numbersessionKeyEntityId = 2;
const const executeSelector: `0x${string}`executeSelector = function toFunctionSelector(fn: string | AbiFunction): `0x${string}`Returns the function selector for a given function definition.
toFunctionSelector(
getAbiItem<readonly [{
readonly type: "constructor";
readonly inputs: readonly [{
readonly name: "entryPoint";
readonly type: "address";
readonly internalType: "contract IEntryPoint";
}, {
readonly name: "executionInstallDelegate";
readonly type: "address";
readonly internalType: "contract ExecutionInstallDelegate";
}];
readonly stateMutability: "nonpayable";
}, ... 60 more ..., {
...;
}], "execute", undefined>(parameters: GetAbiItemParameters<...>): {
...;
}getAbiItem({
abi: readonly [{
readonly type: "constructor";
readonly inputs: readonly [{
readonly name: "entryPoint";
readonly type: "address";
readonly internalType: "contract IEntryPoint";
}, {
readonly name: "executionInstallDelegate";
readonly type: "address";
readonly internalType: "contract ExecutionInstallDelegate";
}];
readonly stateMutability: "nonpayable";
}, ... 60 more ..., {
...;
}]abi: const semiModularAccountBytecodeAbi: readonly [{
readonly type: "constructor";
readonly inputs: readonly [{
readonly name: "entryPoint";
readonly type: "address";
readonly internalType: "contract IEntryPoint";
}, {
readonly name: "executionInstallDelegate";
readonly type: "address";
readonly internalType: "contract ExecutionInstallDelegate";
}];
readonly stateMutability: "nonpayable";
}, ... 60 more ..., {
...;
}]semiModularAccountBytecodeAbi,
name: `0x${string}` | "entryPoint" | "installValidation" | "uninstallValidation" | "execute" | "accountId" | "executeBatch" | "executeUserOp" | "executeWithRuntimeValidation" | "getExecutionData" | ... 49 more ... | "ValidationSignatureSegmentMissing"name: "execute",
}),
);
const const executeBatchSelector: `0x${string}`executeBatchSelector = function toFunctionSelector(fn: string | AbiFunction): `0x${string}`Returns the function selector for a given function definition.
toFunctionSelector(
getAbiItem<readonly [{
readonly type: "constructor";
readonly inputs: readonly [{
readonly name: "entryPoint";
readonly type: "address";
readonly internalType: "contract IEntryPoint";
}, {
readonly name: "executionInstallDelegate";
readonly type: "address";
readonly internalType: "contract ExecutionInstallDelegate";
}];
readonly stateMutability: "nonpayable";
}, ... 60 more ..., {
...;
}], "executeBatch", undefined>(parameters: GetAbiItemParameters<...>): {
...;
}getAbiItem({
abi: readonly [{
readonly type: "constructor";
readonly inputs: readonly [{
readonly name: "entryPoint";
readonly type: "address";
readonly internalType: "contract IEntryPoint";
}, {
readonly name: "executionInstallDelegate";
readonly type: "address";
readonly internalType: "contract ExecutionInstallDelegate";
}];
readonly stateMutability: "nonpayable";
}, ... 60 more ..., {
...;
}]abi: const semiModularAccountBytecodeAbi: readonly [{
readonly type: "constructor";
readonly inputs: readonly [{
readonly name: "entryPoint";
readonly type: "address";
readonly internalType: "contract IEntryPoint";
}, {
readonly name: "executionInstallDelegate";
readonly type: "address";
readonly internalType: "contract ExecutionInstallDelegate";
}];
readonly stateMutability: "nonpayable";
}, ... 60 more ..., {
...;
}]semiModularAccountBytecodeAbi,
name: `0x${string}` | "entryPoint" | "installValidation" | "uninstallValidation" | "execute" | "accountId" | "executeBatch" | "executeUserOp" | "executeWithRuntimeValidation" | "getExecutionData" | ... 49 more ... | "ValidationSignatureSegmentMissing"name: "executeBatch",
}),
);
await const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.installValidation: (args: InstallValidationParams<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>) => Promise<SendUserOperationResult>installValidation({
validationConfig: ValidationConfigvalidationConfig: {
moduleAddress: `0x${string}`moduleAddress: const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress,
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
isGlobal: booleanisGlobal: false,
isSignatureValidation: booleanisSignatureValidation: false,
isUserOpValidation: booleanisUserOpValidation: true,
},
selectors: `0x${string}`[]selectors: [const executeSelector: `0x${string}`executeSelector, const executeBatchSelector: `0x${string}`executeBatchSelector],
installData: `0x${string}`installData: const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule.encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => HexencodeOnInstallData({
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
signer: `0x${string}`signer: await const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner.SmartAccountSigner<any>.getAddress: () => Promise<Address>getAddress(), // Address of the session key
}),
hooks: {
hookConfig: HookConfig;
initData: Hex;
}[]hooks: [],
});
Adding a session key with permissions
Time range
Configuring a session key with a time range allows you to limit how long the session key is valid for (e.g. only allow this key to sign on my account for the next day). The Time Range Module is used to enforce time-based validation for User Operations (UOs) in the system. This example will show you how to add a session key that starts in a day and expires in two days.
Additional Notes
- the interval is inclusive i.e.
[beginningOfInterval, endOfInterval]
- the values
beginningOfInterval
andendOfInterval
are unix timestamps with a maximum size of uint32 - the timestamp specifying the end of the interval must be strictly greater than the beginning of the interval
import { function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)createModularAccountV2Client } from "@account-kit/smart-contracts";
import {
enum HookTypeHookType,
const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions,
const getDefaultSingleSignerValidationModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress,
const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule,
const getDefaultTimeRangeModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the time range module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultTimeRangeModuleAddress,
const TimeRangeModule: {
abi: readonly [{
readonly type: "function";
readonly name: "moduleId";
readonly inputs: readonly [];
readonly outputs: readonly [{
readonly name: "";
readonly type: "string";
readonly internalType: "string";
}];
readonly stateMutability: "pure";
}, {
readonly type: "function";
readonly name: "onInstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "onUninstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "preRuntimeValidationHook";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "";
readonly type: "uint256";
readonly internalType: "uint256";
}, {
readonly name: "";
readonly type: "bytes";
readonly ...TimeRangeModule,
} from "@account-kit/smart-contracts/experimental";
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
import { type interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner } from "@aa-sdk/core";
const const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client = (
await createModularAccountV2Client<Chain, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(args: CreateModularAccountV2AlchemyClientParams<...>): Promise<...> (+1 overload)createModularAccountV2Client({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey: "your-api-key" }),
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
})
).extend: <InstallValidationActions<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>>(fn: (client: Client<...>) => InstallValidationActions<...>) => Client<...>extend(const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions);
let let sessionKeyEntityId: numbersessionKeyEntityId = 1;
const const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress =
function getDefaultSingleSignerValidationModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain);
const const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner: interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner =
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("SESSION_KEY_MNEMONIC");
const const hookEntityId: 0hookEntityId = 0; // Make sure that the account does not have a hook with this entity id on the module yet
const const validAfter: 0validAfter = 0; // valid once added
const const validUntil: numbervalidUntil = const validAfter: 0validAfter + 2 * 86400; // validity ends 2 days from now
// Adding a session key that starts in a day and expires in two days
await const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.installValidation: (args: InstallValidationParams<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>) => Promise<SendUserOperationResult>installValidation({
validationConfig: ValidationConfigvalidationConfig: {
moduleAddress: `0x${string}`moduleAddress: const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress,
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
isGlobal: booleanisGlobal: true,
isSignatureValidation: booleanisSignatureValidation: true,
isUserOpValidation: booleanisUserOpValidation: true,
},
selectors: `0x${string}`[]selectors: [],
installData: `0x${string}`installData: const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule.encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => HexencodeOnInstallData({
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
signer: `0x${string}`signer: await const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner.SmartAccountSigner<any>.getAddress: () => Promise<Address>getAddress(), // Address of the session key
}),
hooks: {
hookConfig: HookConfig;
initData: Hex;
}[]hooks: [
{
hookConfig: HookConfighookConfig: {
address: `0x${string}`address: function getDefaultTimeRangeModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the time range module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultTimeRangeModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain),
entityId: numberentityId: const hookEntityId: 0hookEntityId,
hookType: HookTypehookType: enum HookTypeHookType.function (enum member) HookType.VALIDATION = "0x01"VALIDATION, // fixed value
hasPreHooks: booleanhasPreHooks: true, // fixed value
hasPostHooks: booleanhasPostHooks: false, // fixed value
},
initData: `0x${string}`initData: const TimeRangeModule: {
abi: readonly [{
readonly type: "function";
readonly name: "moduleId";
readonly inputs: readonly [];
readonly outputs: readonly [{
readonly name: "";
readonly type: "string";
readonly internalType: "string";
}];
readonly stateMutability: "pure";
}, {
readonly type: "function";
readonly name: "onInstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "onUninstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "preRuntimeValidationHook";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "";
readonly type: "uint256";
readonly internalType: "uint256";
}, {
readonly name: "";
readonly type: "bytes";
readonly ...TimeRangeModule.encodeOnInstallData: (args: {
entityId: number;
validUntil: number;
validAfter: number;
}) => HexencodeOnInstallData({
entityId: numberentityId: const hookEntityId: 0hookEntityId,
validAfter: numbervalidAfter,
validUntil: numbervalidUntil,
}),
},
],
});
Paymaster guard
Purpose
This module provides the ability to limit a session key to only be able to use a specific single paymaster.
Additional Notes
- you MUST specify a paymaster when using this module
- if the paymaster that is registered with this module decides to no longer sponsor your user operations, the entity associated with this hook would no longer be able to send user operations.
import { function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)createModularAccountV2Client } from "@account-kit/smart-contracts";
import {
enum HookTypeHookType,
const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions,
const getDefaultSingleSignerValidationModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress,
const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule,
const getDefaultPaymasterGuardModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the paymaster guard module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultPaymasterGuardModuleAddress,
const PaymasterGuardModule: {
abi: readonly [{
readonly type: "function";
readonly name: "moduleId";
readonly inputs: readonly [];
readonly outputs: readonly [{
readonly name: "";
readonly type: "string";
readonly internalType: "string";
}];
readonly stateMutability: "pure";
}, {
readonly type: "function";
readonly name: "onInstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "onUninstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "paymasters";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "account";
readonly type: "address";
readonly internalType: "address";
}];
readonly outputs: readonly [{
readonly name: "paymaster";
readonly type: "address";
readonly internalType: "address";
}];
readonly stateMutability: "view"; ...PaymasterGuardModule,
} from "@account-kit/smart-contracts/experimental";
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
import { type interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner } from "@aa-sdk/core";
const const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client = (
await createModularAccountV2Client<Chain, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(args: CreateModularAccountV2AlchemyClientParams<...>): Promise<...> (+1 overload)createModularAccountV2Client({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey: "your-api-key" }),
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
})
).extend: <InstallValidationActions<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>>(fn: (client: Client<...>) => InstallValidationActions<...>) => Client<...>extend(const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions);
let let sessionKeyEntityId: numbersessionKeyEntityId = 1;
const const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress =
function getDefaultSingleSignerValidationModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain);
const const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner: interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner =
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("SESSION_KEY_MNEMONIC");
const const hookEntityId: 0hookEntityId = 0; // Make sure that the account does not have a hook with this entity id on the module yet
const const paymasterAddress: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"paymasterAddress = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
// Adding a session key that can only use the above paymaster for user operations
await const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.installValidation: (args: InstallValidationParams<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>) => Promise<SendUserOperationResult>installValidation({
validationConfig: ValidationConfigvalidationConfig: {
moduleAddress: `0x${string}`moduleAddress: const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress,
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
isGlobal: booleanisGlobal: true,
isSignatureValidation: booleanisSignatureValidation: true,
isUserOpValidation: booleanisUserOpValidation: true,
},
selectors: `0x${string}`[]selectors: [],
installData: `0x${string}`installData: const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule.encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => HexencodeOnInstallData({
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
signer: `0x${string}`signer: await const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner.SmartAccountSigner<any>.getAddress: () => Promise<Address>getAddress(), // Address of the session key
}),
hooks: {
hookConfig: HookConfig;
initData: Hex;
}[]hooks: [
{
hookConfig: HookConfighookConfig: {
address: `0x${string}`address: function getDefaultPaymasterGuardModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the paymaster guard module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultPaymasterGuardModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain),
entityId: numberentityId: const hookEntityId: 0hookEntityId,
hookType: HookTypehookType: enum HookTypeHookType.function (enum member) HookType.VALIDATION = "0x01"VALIDATION, // fixed value
hasPreHooks: booleanhasPreHooks: true, // fixed value
hasPostHooks: booleanhasPostHooks: false, // fixed value
},
initData: `0x${string}`initData: const PaymasterGuardModule: {
abi: readonly [{
readonly type: "function";
readonly name: "moduleId";
readonly inputs: readonly [];
readonly outputs: readonly [{
readonly name: "";
readonly type: "string";
readonly internalType: "string";
}];
readonly stateMutability: "pure";
}, {
readonly type: "function";
readonly name: "onInstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "onUninstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "paymasters";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "account";
readonly type: "address";
readonly internalType: "address";
}];
readonly outputs: readonly [{
readonly name: "paymaster";
readonly type: "address";
readonly internalType: "address";
}];
readonly stateMutability: "view"; ...PaymasterGuardModule.encodeOnInstallData: (args: {
entityId: number;
paymaster: Address;
}) => HexencodeOnInstallData({
entityId: numberentityId: const hookEntityId: 0hookEntityId,
paymaster: `0x${string}`paymaster: const paymasterAddress: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"paymasterAddress,
}),
},
],
});
Native token and/or gas limit
This module provides native token spending limits for modular accounts. Below we will show an example of adding a session key that has a 1 eth native token spend limit. Functionally, this module is enabled by:
- Tracking and limiting total native token spending across transactions
- Monitoring both direct transfers and gas costs from UserOperations
- Supporting special paymaster configurations for complex gas payment scenarios
Token Limit Features
- Tracks native token spending across:
- Direct transfers via
execute
- Batch transfers via
executeBatch
- Contract creation via
performCreate
- UserOperation gas costs (when applicable)
- Direct transfers via
- Supports special paymaster configurations for:
- Standard paymasters (gas costs don’t count against limit)
- Special paymasters (gas costs do count against limit)
- Maintains separate limits per entity ID
Gas Cost Tracking
For UserOperations, the module tracks:
- Pre-verification gas
- Verification gas
- Call gas
- Paymaster verification gas (for special paymasters)
- Paymaster post-op gas (for special paymasters)
Additional Notes
- The module must be installed with both validation and execution hooks, the validation hook track gas, whereas the execution hook tracks value
- The module maintains a global singleton state for all accounts
import { function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)createModularAccountV2Client } from "@account-kit/smart-contracts";
import {
enum HookTypeHookType,
const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions,
const getDefaultSingleSignerValidationModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress,
const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule,
const getDefaultNativeTokenLimitModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the native token limit module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultNativeTokenLimitModuleAddress,
const NativeTokenLimitModule: {
abi: readonly [{
readonly type: "function";
readonly name: "limits";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint256";
readonly internalType: "uint256";
}, {
readonly name: "account";
readonly type: "address";
readonly internalType: "address";
}];
readonly outputs: readonly [{
readonly name: "limit";
readonly type: "uint256";
readonly internalType: "uint256";
}];
readonly stateMutability: "view";
}, {
readonly type: "function";
readonly name: "moduleId";
readonly inputs: readonly [];
readonly outputs: readonly [{
readonly name: "";
readonly type: "string";
readonly internalType: "string";
}];
readonly stateMutability: "pure";
}, {
readonly type: "function";
readonly name: "onInstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "onUninstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
} ...NativeTokenLimitModule,
} from "@account-kit/smart-contracts/experimental";
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
import { function parseEther(ether: string, unit?: "wei" | "gwei"): bigintConverts a string representation of ether to numerical wei.
parseEther } from "viem";
import { type interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner } from "@aa-sdk/core";
const const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client = (
await createModularAccountV2Client<Chain, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(args: CreateModularAccountV2AlchemyClientParams<...>): Promise<...> (+1 overload)createModularAccountV2Client({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey: "your-api-key" }),
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
})
).extend: <InstallValidationActions<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>>(fn: (client: Client<...>) => InstallValidationActions<...>) => Client<...>extend(const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions);
let let sessionKeyEntityId: numbersessionKeyEntityId = 1;
const const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress =
function getDefaultSingleSignerValidationModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain);
const const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner: interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner =
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("SESSION_KEY_MNEMONIC");
const const hookEntityId: 0hookEntityId = 0; // Make sure that the account does not have a hook with this entity id on the module yet
// Adding a session key that has a 1 eth native token spend limit
await const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.installValidation: (args: InstallValidationParams<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>) => Promise<SendUserOperationResult>installValidation({
validationConfig: ValidationConfigvalidationConfig: {
moduleAddress: `0x${string}`moduleAddress: const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress,
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
isGlobal: booleanisGlobal: true,
isSignatureValidation: booleanisSignatureValidation: true,
isUserOpValidation: booleanisUserOpValidation: true,
},
selectors: `0x${string}`[]selectors: [],
installData: `0x${string}`installData: const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule.encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => HexencodeOnInstallData({
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
signer: `0x${string}`signer: await const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner.SmartAccountSigner<any>.getAddress: () => Promise<Address>getAddress(), // Address of the session key
}),
hooks: {
hookConfig: HookConfig;
initData: Hex;
}[]hooks: [
{
hookConfig: HookConfighookConfig: {
address: `0x${string}`address: function getDefaultNativeTokenLimitModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the native token limit module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultNativeTokenLimitModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain),
entityId: numberentityId: const hookEntityId: 0hookEntityId,
hookType: HookTypehookType: enum HookTypeHookType.function (enum member) HookType.VALIDATION = "0x01"VALIDATION, // fixed value
hasPreHooks: booleanhasPreHooks: true, // fixed value
hasPostHooks: booleanhasPostHooks: false, // fixed value
},
initData: `0x${string}`initData: const NativeTokenLimitModule: {
abi: readonly [{
readonly type: "function";
readonly name: "limits";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint256";
readonly internalType: "uint256";
}, {
readonly name: "account";
readonly type: "address";
readonly internalType: "address";
}];
readonly outputs: readonly [{
readonly name: "limit";
readonly type: "uint256";
readonly internalType: "uint256";
}];
readonly stateMutability: "view";
}, {
readonly type: "function";
readonly name: "moduleId";
readonly inputs: readonly [];
readonly outputs: readonly [{
readonly name: "";
readonly type: "string";
readonly internalType: "string";
}];
readonly stateMutability: "pure";
}, {
readonly type: "function";
readonly name: "onInstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, {
readonly type: "function";
readonly name: "onUninstall";
readonly inputs: readonly [{
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
} ...NativeTokenLimitModule.encodeOnInstallData: (args: {
entityId: number;
spendLimit: bigint;
}) => HexencodeOnInstallData({
entityId: numberentityId: const hookEntityId: 0hookEntityId,
spendLimit: bigintspendLimit: function parseEther(ether: string, unit?: "wei" | "gwei"): bigintConverts a string representation of ether to numerical wei.
parseEther("1"),
}),
},
{
hookConfig: HookConfighookConfig: {
address: `0x${string}`address: function getDefaultNativeTokenLimitModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the native token limit module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultNativeTokenLimitModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain),
entityId: numberentityId: const hookEntityId: 0hookEntityId,
hookType: HookTypehookType: enum HookTypeHookType.function (enum member) HookType.EXECUTION = "0x00"EXECUTION, // fixed value
hasPreHooks: booleanhasPreHooks: true, // fixed value
hasPostHooks: booleanhasPostHooks: false, // fixed value
},
initData: `0x${string}`initData: "0x", // no initdata required as the limit was set up in the above installation call
},
],
});
Allowlist or an ERC20 token limit
This module provides two key security features for modular accounts:
- Allowlisting - Controls which addresses and functions can be called
- ERC-20 Spend Limits - Manages spending limits for ERC-20 tokens
Allowlist Features
- Can specify permissions for:
- Specific addresses + specific functions
- Specific addresses + all functions (wildcard)
- All addresses + specific functions (wildcard)
- Only applies to execute and executeBatch functions
- Permission checks follow this order:
- If wildcard address → Allow
- If wildcard function → Allow
- If specific address + specific function match → Allow
- Otherwise → Revert
ERC-20 Spend Limit Features
- Only allows transfer and approve functions for tracked tokens
- Works with standard execution functions:
- execute
- executeWithRuntimeValidation
- executeUserOp
- executeBatch
Additional Notes
- Module must be installed/uninstalled on an entity ID basis
- Uninstalling for one entity ID doesn’t affect other entities
- Settings are stored in a global singleton contract
- All permissions and limits can be updated dynamically
- The module is intentionally restrictive about which ERC-20 functions are allowed to prevent edge cases (e.g., DAI’s non-standard functions)
import { function createModularAccountV2Client<TChain extends Chain = Chain, TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, TChain, TSigner>): Promise<ModularAccountV2Client<TSigner, TChain, AlchemyTransport>> (+1 overload)createModularAccountV2Client } from "@account-kit/smart-contracts";
import {
enum HookTypeHookType,
const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions,
const getDefaultSingleSignerValidationModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress,
const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule,
const getDefaultAllowlistModuleAddress: (chain: Chain) => AddressMaps a given chain to a specific address of the allowlist module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultAllowlistModuleAddress,
const AllowlistModule: {
abi: readonly [{
readonly type: "function";
readonly name: "addressAllowlist";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "target";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "account";
readonly type: "address";
readonly internalType: "address";
}];
readonly outputs: readonly [{
readonly name: "allowed";
readonly type: "bool";
readonly internalType: "bool";
}, {
readonly name: "hasSelectorAllowlist";
readonly type: "bool";
readonly internalType: "bool";
}, {
readonly name: "hasERC20SpendLimit";
readonly type: "bool";
readonly internalType: "bool";
}];
readonly stateMutability: "view";
}, {
readonly type: "function";
readonly name: "checkAllowlistCalldata";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "callData";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "view";
}, {
readonly type: "function";
readonly name: "deleteAllowlist";
readonly ...AllowlistModule,
} from "@account-kit/smart-contracts/experimental";
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { const sepolia: Chainsepolia, function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy } from "@account-kit/infra";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
import { function parseEther(ether: string, unit?: "wei" | "gwei"): bigintConverts a string representation of ether to numerical wei.
parseEther } from "viem";
import { type interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner } from "@aa-sdk/core";
const const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client = (
await createModularAccountV2Client<Chain, LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(args: CreateModularAccountV2AlchemyClientParams<...>): Promise<...> (+1 overload)createModularAccountV2Client({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey: "your-api-key" }),
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
})
).extend: <InstallValidationActions<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>>(fn: (client: Client<...>) => InstallValidationActions<...>) => Client<...>extend(const installValidationActions: <TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(client: ModularAccountV2Client<TSigner>) => InstallValidationActions<TSigner>Provides validation installation and uninstallation functionalities for a MA v2 client, ensuring compatibility with SmartAccountClient
.
installValidationActions);
let let sessionKeyEntityId: numbersessionKeyEntityId = 1;
const const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress =
function getDefaultSingleSignerValidationModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the single signer validation module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultSingleSignerValidationModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain);
const const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner: interface SmartAccountSigner<Inner = any>A signer that can sign messages and typed data.
SmartAccountSigner =
class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("SESSION_KEY_MNEMONIC");
const const hookEntityId: 0hookEntityId = 0; // Make sure that the account does not have a hook with this entity id on the module yet
const const allowlistInstallData: `0x${string}`allowlistInstallData = const AllowlistModule: {
abi: readonly [{
readonly type: "function";
readonly name: "addressAllowlist";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "target";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "account";
readonly type: "address";
readonly internalType: "address";
}];
readonly outputs: readonly [{
readonly name: "allowed";
readonly type: "bool";
readonly internalType: "bool";
}, {
readonly name: "hasSelectorAllowlist";
readonly type: "bool";
readonly internalType: "bool";
}, {
readonly name: "hasERC20SpendLimit";
readonly type: "bool";
readonly internalType: "bool";
}];
readonly stateMutability: "view";
}, {
readonly type: "function";
readonly name: "checkAllowlistCalldata";
readonly inputs: readonly [{
readonly name: "entityId";
readonly type: "uint32";
readonly internalType: "uint32";
}, {
readonly name: "callData";
readonly type: "bytes";
readonly internalType: "bytes";
}];
readonly outputs: readonly [];
readonly stateMutability: "view";
}, {
readonly type: "function";
readonly name: "deleteAllowlist";
readonly ...AllowlistModule.encodeOnInstallData: (args: {
entityId: number;
inputs: Array<{
target: Address;
hasSelectorAllowlist: boolean;
hasERC20SpendLimit: boolean;
erc20SpendLimit: bigint;
selectors: Array<Hex>;
}>;
}) => HexencodeOnInstallData({
entityId: numberentityId: const hookEntityId: 0hookEntityId,
inputs: {
target: Address;
hasSelectorAllowlist: boolean;
hasERC20SpendLimit: boolean;
erc20SpendLimit: bigint;
selectors: Array<Hex>;
}[]inputs: [
{
target: `0x${string}`target: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
hasSelectorAllowlist: booleanhasSelectorAllowlist: false, // whether to limit the callable functions on call targets
hasERC20SpendLimit: booleanhasERC20SpendLimit: false, // If "target" is an ERC20 token with a spend limit
erc20SpendLimit: biginterc20SpendLimit: function parseEther(ether: string, unit?: "wei" | "gwei"): bigintConverts a string representation of ether to numerical wei.
parseEther("100"), // The spend limit to set, if relevant
selectors: `0x${string}`[]selectors: [], // The function selectors to allow, if relevant
},
],
});
// Adding a session key that has a 100 ERC token spend limit
await const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.installValidation: (args: InstallValidationParams<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>) => Promise<SendUserOperationResult>installValidation({
validationConfig: ValidationConfigvalidationConfig: {
moduleAddress: `0x${string}`moduleAddress: const ecdsaValidationModuleAddress: `0x${string}`ecdsaValidationModuleAddress,
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
isGlobal: booleanisGlobal: true,
isSignatureValidation: booleanisSignatureValidation: true,
isUserOpValidation: booleanisUserOpValidation: true,
},
selectors: `0x${string}`[]selectors: [],
installData: `0x${string}`installData: const SingleSignerValidationModule: {
encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => Hex;
encodeOnUninstallData: (args: {
entityId: number;
}) => Hex;
}SingleSignerValidationModule.encodeOnInstallData: (args: {
entityId: number;
signer: Address;
}) => HexencodeOnInstallData({
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
signer: `0x${string}`signer: await const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner.SmartAccountSigner<any>.getAddress: () => Promise<Address>getAddress(), // Address of the session key
}),
hooks: {
hookConfig: HookConfig;
initData: Hex;
}[]hooks: [
{
hookConfig: HookConfighookConfig: {
address: `0x${string}`address: function getDefaultAllowlistModuleAddress(chain: Chain): AddressMaps a given chain to a specific address of the allowlist module by its chain ID. If no direct mapping exists, it defaults to returning a specific address.
getDefaultAllowlistModuleAddress(const client: Client<AlchemyTransport, Chain, ModularAccountV2<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>, [...], {
...;
} & ... 4 more ... & PublicActions>client.chain: ChainChain for the client.
chain),
entityId: numberentityId: const hookEntityId: 0hookEntityId,
hookType: HookTypehookType: enum HookTypeHookType.function (enum member) HookType.VALIDATION = "0x01"VALIDATION, // fixed value
hasPreHooks: booleanhasPreHooks: true, // fixed value
hasPostHooks: booleanhasPostHooks: false, // fixed value
},
initData: `0x${string}`initData: const allowlistInstallData: `0x${string}`allowlistInstallData,
},
],
});
Install validation method
This method is used to add session keys to your account, with the following configurable parameters.
validationConfig
: The validation configuration for the session key, containing the following fields:
validationModule
: This is the address of the validation module to use for this key. SingleSignerValidationModule provides ECDSA validation and WebauthnModule provides WebAuthn validation. If you wish to use a custom validation module such as a multisig validation, this would be specified here.entityId
: This is a uint32 identifier for validation chosen by the developer. The only rule here is that you cannot pick an entityId that already is used on the account. Since the owner’s entityId is 0, you can start from 1.isGlobal
: This is a boolean that specifies if the validation can be used to call any function on the account. If this is set to false, the validation can only be used to call functions that are specified in theselectors
array. It’s recommended to leave this asfalse
and use the selector array instead, as a key with global permissions has the authority to upgrade the account to any other implementation, which can change the ownership of the account in the same transaction.isSignatureValidation
: This is a boolean that specifies if the validation can be used for ERC-1271 signature validation, which can be used for signing permit2 token permits. It’s recommended to leave this asfalse
for security reasons.isUserOpValidation
: This is a boolean that specifies if the key can perform user operations on behalf of the account. For most use cases, this should be set totrue
.
selectors
: This is an array of function selectors that the key can call. If isGlobal
is set to true
, the limits in this array will not be applied. If isGlobal
is set to false
, the key can only call functions that are specified in this array. It’s recommended to only have ModularAccount.execute.selector
and ModularAccount.executeBatch.selector
in this array.
installData
: This is the installation data that is passed to the validation module on installation. Each module has their own encoding for this data, so you would need to use the helper functions provided by that module.
hooks
: This is an array of hooks to be installed on the key. Each element in the array contains a hookConfig object as well as initData to pass to the hook module.
hookConfig
: This is the hook configuration to be applied to the session key.
address
: This is the address of the hook module to be installed on the session key.entityId
: This is a hook module entity id that is different from the validation entity id. The decoupling enables multiple hooks provided by the same module to be applied on the same key. The only restriction here is that the hook module entity id should not be an entity id that’s currently in use for the account.hookType
: This specifies which phase should the hook be applied on, either HookType.VALIDATION to be a validation hook, or HookType.EXECUTION to be a pre and/or post-execution hook.hasPreHooks
: This specifies if the hook is supposed to run before validation or execution.hasPostHooks
: This specifies if the hook is supposed to run after validation or execution.