Getting started with Session Keys
@account-kit/smart-contracts
exports all of the definitions you need to use session keys with a Modular Account. We provide a simple SessionKeySigner
class that generates session keys on the client and can be used as the signer
for the Multi Owner Modular Account.
We also export the necessary decorators which can be used to extend your modularAccountClient
to make interacting with session keys easy.
Usage
Let’s take a look at a full example that demonstrates how to use session keys with a Modular Account.
/* eslint-disable @typescript-eslint/no-unused-vars */
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import {
enum SessionKeyAccessListTypeSessionKeyAccessListType,
class SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder,
const SessionKeyPlugin: Plugin<readonly [{
readonly type: "function";
readonly name: "addSessionKey";
readonly inputs: readonly [{
readonly name: "sessionKey";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "tag";
readonly type: "bytes32";
readonly internalType: "bytes32";
}, {
readonly name: "permissionUpdates";
readonly type: "bytes[]";
readonly internalType: "bytes[]";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, ... 43 more ..., {
...;
}]>SessionKeyPlugin,
class SessionKeySignerA simple session key signer that uses localStorage or sessionStorage to store a private key. If the key is not found, it will generate a new one and store it in the storage.
SessionKeySigner,
function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient,
const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions,
} from "@account-kit/smart-contracts";
import { const zeroHash: "0x0000000000000000000000000000000000000000000000000000000000000000"zeroHash } from "viem";
const const chain: Chainchain = const sepolia: Chainsepolia;
const const transport: AlchemyTransporttransport = 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" });
// this is the signer to connect with the account, later we will create a new client using a session key signe
const const signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>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>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("MNEMONIC");
const const sessionKeySigner: SessionKeySignersessionKeySigner = new new SessionKeySigner(config_?: SessionKeySignerConfig): SessionKeySignerInitializes a new instance of a session key signer with the provided configuration. This will set the signerType
, storageKey
, and storageType
. It will also manage the session key, either fetching it from storage or generating a new one if it doesn't exist.
SessionKeySigner();
const const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client = (
await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain,
transport: AlchemyTransportThe RPC transport
transport,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>signer,
})
).extend: <SessionKeyPluginActions<MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, keyof EntryPointRegistryBase<...>>>(fn: (client: Client<...>) => SessionKeyPluginActions<...>) => Client<...>extend(const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions);
// 1. check if the plugin is installed
const const isPluginInstalled: booleanisPluginInstalled = await const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client
.function getInstalledPlugins(args: {
account?: SmartContractAccount<string, keyof EntryPointRegistryBase<unknown>> | undefined;
}): Promise<ReadonlyArray<Address>>getInstalledPlugins({})
// This checks using the default address for the chain, but you can always pass in your own plugin address here as an override
.Promise<readonly `0x${string}`[]>.then<boolean, never>(onfulfilled?: ((value: readonly `0x${string}`[]) => boolean | PromiseLike<boolean>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>Attaches callbacks for the resolution and/or rejection of the Promise.
then((x: readonly `0x${string}`[]x) => x: readonly `0x${string}`[]x.ReadonlyArray<`0x${string}`>.includes(searchElement: `0x${string}`, fromIndex?: number): booleanDetermines whether an array includes a certain element, returning true or false as appropriate.
includes(const SessionKeyPlugin: Plugin<readonly [{
readonly type: "function";
readonly name: "addSessionKey";
readonly inputs: readonly [{
readonly name: "sessionKey";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "tag";
readonly type: "bytes32";
readonly internalType: "bytes32";
}, {
readonly name: "permissionUpdates";
readonly type: "bytes[]";
readonly internalType: "bytes[]";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, ... 43 more ..., {
...;
}]>SessionKeyPlugin.meta: {
name: string;
version: string;
addresses: Record<number, Address>;
}meta.addresses: Record<number, `0x${string}`>addresses[const chain: Chainchain.id: numberID in number form
id]));
// 2. if the plugin is not installed, then install it and set up the session key
if (!const isPluginInstalled: booleanisPluginInstalled) {
// lets create an initial permission set for the session key giving it an eth spend limit
const const initialPermissions: SessionKeyPermissionsBuilderinitialPermissions = new new SessionKeyPermissionsBuilder(): SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder()
.SessionKeyPermissionsBuilder.setNativeTokenSpendLimit(limit: NativeTokenLimit): SessionKeyPermissionsBuilderSets the native token spend limit and returns the instance for chaining.
setNativeTokenSpendLimit({
spendLimit: bigintspendLimit: 1000000n,
})
// this will allow the session key plugin to interact with all addresses
.SessionKeyPermissionsBuilder.setContractAccessControlType(aclType: SessionKeyAccessListType): SessionKeyPermissionsBuilderSets the access control type for the contract and returns the current instance for method chaining.
setContractAccessControlType(enum SessionKeyAccessListTypeSessionKeyAccessListType.function (enum member) SessionKeyAccessListType.ALLOW_ALL_ACCESS = 2ALLOW_ALL_ACCESS)
.SessionKeyPermissionsBuilder.setTimeRange(timeRange: TimeRange): SessionKeyPermissionsBuilderSets the time range for an object and returns the object itself for chaining.
setTimeRange({
validFrom: numbervalidFrom: var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.
Math.Math.round(x: number): numberReturns a supplied numeric expression rounded to the nearest integer.
round(var Date: DateConstructorEnables basic storage and retrieval of dates and times.
Date.DateConstructor.now(): numberReturns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now() / 1000),
// valid for 1 hour
validUntil: numbervalidUntil: var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.
Math.Math.round(x: number): numberReturns a supplied numeric expression rounded to the nearest integer.
round(var Date: DateConstructorEnables basic storage and retrieval of dates and times.
Date.DateConstructor.now(): numberReturns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now() / 1000 + 60 * 60),
});
const { const hash: `0x${string}`hash } = await const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client.installSessionKeyPlugin: (args: {
overrides?: UserOperationOverrides<keyof EntryPointRegistryBase<unknown>> | undefined;
} & InstallSessionKeyPluginParams & {
...;
} & {
...;
}) => Promise<...>installSessionKeyPlugin({
// 1st arg is the initial set of session keys
// 2nd arg is the tags for the session keys
// 3rd arg is the initial set of permissions
args: [readonly `0x${string}`[], readonly `0x${string}`[], readonly (readonly `0x${string}`[])[]]args: [
[await const sessionKeySigner: SessionKeySignersessionKeySigner.SessionKeySigner.getAddress: () => Promise<`0x${string}`>An async function that retrieves the address using the inner object's getAddress
method.
getAddress()],
[const zeroHash: "0x0000000000000000000000000000000000000000000000000000000000000000"zeroHash],
[const initialPermissions: SessionKeyPermissionsBuilderinitialPermissions.SessionKeyPermissionsBuilder.encode(): Hex[]Encodes various function calls into an array of hexadecimal strings based on the provided permissions and limits.
encode()],
],
});
await const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client.waitForUserOperationTransaction: (args: WaitForUserOperationTxParameters) => Promise<Hex>waitForUserOperationTransaction({ hash: `0x${string}`hash });
}
// 3. set up a client that's using our session key
const const sessionKeyClient: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<SessionKeySigner>, [{
Method: "eth_sendUserOperation";
Parameters: [UserOperationRequest, Address];
ReturnType: Hash;
}, ... 50 more ..., {
...;
}], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>sessionKeyClient = (
await createModularAccountAlchemyClient<SessionKeySigner>(params: AlchemyModularAccountClientConfig<SessionKeySigner>): Promise<{
account: MultiOwnerModularAccount<SessionKeySigner>;
... 100 more ...;
extend: <client>(fn: (client: Client<...>) => client) => Client<...>;
}>createModularAccountAlchemyClient({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain,
signer: SessionKeySignersigner: const sessionKeySigner: SessionKeySignersessionKeySigner,
transport: AlchemyTransportThe RPC transport
transport,
// this is important because it tells the client to use our previously deployed account
accountAddress?: `0x${string}` | undefinedaccountAddress: const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client.getAddress: () => AddressgetAddress({ account: MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>account: const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client.account: MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>The Account of the Client.
account }),
})
).extend: <SessionKeyPluginActions<MultiOwnerModularAccount<SessionKeySigner>, keyof EntryPointRegistryBase<unknown>>>(fn: (client: Client<...>) => SessionKeyPluginActions<...>) => Client<...>extend(const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions);
// 4. send a user operation using the session key
const const result: SendUserOperationResult<keyof EntryPointRegistryBase<unknown>>result = await const sessionKeyClient: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<SessionKeySigner>, [{
Method: "eth_sendUserOperation";
Parameters: [UserOperationRequest, Address];
ReturnType: Hash;
}, ... 50 more ..., {
...;
}], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>sessionKeyClient.executeWithSessionKey: (args: Pick<EncodeFunctionDataParameters<readonly [{
readonly type: "function";
readonly name: "executeWithSessionKey";
readonly inputs: readonly [{
readonly name: "calls";
readonly type: "tuple[]";
readonly internalType: "struct Call[]";
readonly components: readonly [{
readonly name: "target";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "value";
readonly type: "uint256";
readonly internalType: "uint256";
}, {
readonly name: "data";
readonly type: "bytes";
readonly internalType: "bytes";
}];
}, {
readonly name: "sessionKey";
readonly type: "address";
readonly internalType: "address";
}];
readonly outputs: readonly [{
readonly name: "";
readonly type: "bytes[]";
readonly internalType: "bytes[]";
}];
readonly stateMutability: "nonpayable";
}, {
...;
}, {
...;
}, {
...;
}, {
...;
}], "executeWithSessionKey">, "args"> & {
...;
} & {
...;
} & {
...;
}) => Promise<...>executeWithSessionKey({
args: readonly [readonly {
target: `0x${string}`;
value: bigint;
data: `0x${string}`;
}[], `0x${string}`]args: [
[
{
target: `0x${string}`target: "0x1234123412341234123412341234123412341234",
value: bigintvalue: 1n,
data: `0x${string}`data: "0x",
},
],
await const sessionKeySigner: SessionKeySignersessionKeySigner.SessionKeySigner.getAddress: () => Promise<`0x${string}`>An async function that retrieves the address using the inner object's getAddress
method.
getAddress(),
],
});
Breaking it down
Determine where the session key is stored
Session keys can be held on the client side or on a backend agent. Client side session keys are useful for skipping confirmations, and agent side keys are useful for automations.
In the above example, we use a client-side key using the SessionKeySigner
exported from @account-kit/smart-contracts
.
import { class SessionKeySignerA simple session key signer that uses localStorage or sessionStorage to store a private key. If the key is not found, it will generate a new one and store it in the storage.
SessionKeySigner } from "@account-kit/smart-contracts";
const const sessionKeySigner: SessionKeySignersessionKeySigner = new new SessionKeySigner(config_?: SessionKeySignerConfig): SessionKeySignerInitializes a new instance of a session key signer with the provided configuration. This will set the signerType
, storageKey
, and storageType
. It will also manage the session key, either fetching it from storage or generating a new one if it doesn't exist.
SessionKeySigner();
If you are using backend agent controlled session keys, then the agent should generate the private key and send only the address to the client. This protects the private key by not exposing it to the user.
Extend your client with Modular Account Decorators
The base modularAccountClient
and AlchemymodularAccountClient
, only include base functionality for sending user operations. If you are using a ModularAccount
, then you will want to extend your client with the various decorators exported by @account-kit/smart-contracts
.
import { import modularAccountClientmodularAccountClient } from "./client";
import { const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions } from "@account-kit/smart-contracts";
const const extendedClient: anyextendedClient = import modularAccountClientmodularAccountClient.anyextend(const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions);
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import { function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient } from "@account-kit/smart-contracts";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
export const const chain: Chainchain = const sepolia: Chainsepolia;
export const const modularAccountClient: {
account: MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>;
... 100 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}modularAccountClient = await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
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()),
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain,
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" }),
});
Check if the Session Key Plugin is installed
Before you can start using session keys, you need to check whether the user’s account has the session key plugin installed. You can perform this check using the account loupe decorator, which lets you inspect the state of installed plugins on a Modular Account.
import { const SessionKeyPlugin: Plugin<readonly [{
readonly type: "function";
readonly name: "addSessionKey";
readonly inputs: readonly [{
readonly name: "sessionKey";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "tag";
readonly type: "bytes32";
readonly internalType: "bytes32";
}, {
readonly name: "permissionUpdates";
readonly type: "bytes[]";
readonly internalType: "bytes[]";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, ... 43 more ..., {
...;
}]>SessionKeyPlugin } from "@account-kit/smart-contracts";
// 1. check if the plugin is installed
const const isPluginInstalled: anyisPluginInstalled = await import modularAccountClientmodularAccountClient
.anygetInstalledPlugins({})
// This checks using the default address for the chain, but you can always pass in your own plugin address here as an override
.anythen((x: anyx) => x: anyx.anyincludes(const SessionKeyPlugin: Plugin<readonly [{
readonly type: "function";
readonly name: "addSessionKey";
readonly inputs: readonly [{
readonly name: "sessionKey";
readonly type: "address";
readonly internalType: "address";
}, {
readonly name: "tag";
readonly type: "bytes32";
readonly internalType: "bytes32";
}, {
readonly name: "permissionUpdates";
readonly type: "bytes[]";
readonly internalType: "bytes[]";
}];
readonly outputs: readonly [];
readonly stateMutability: "nonpayable";
}, ... 43 more ..., {
...;
}]>SessionKeyPlugin.meta: {
name: string;
version: string;
addresses: Record<number, Address>;
}meta.addresses: Record<number, `0x${string}`>addresses[anychain.anyid]));
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import { function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient } from "@account-kit/smart-contracts";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
export const const chain: Chainchain = const sepolia: Chainsepolia;
export const const modularAccountClient: {
account: MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>;
... 100 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}modularAccountClient = await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
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()),
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain,
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" }),
});
Install the Session Key Plugin
If the Session Key Plugin is not yet installed, you need to install it before it can be used. To simplify the workflow, it is also possible to batch the plugin installation along with creating session keys and performing other actions, which combines all of these steps into one user operation.
import { class SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
// 2. if the plugin is not installed, then install it and set up the session key
if (!const isPluginInstalled: anyisPluginInstalled) {
// lets create an initial permission set for the session key giving it an eth spend limit
// if we don't set anything here, then the key will have 0 permissions
const const initialPermissions: SessionKeyPermissionsBuilderinitialPermissions =
new new SessionKeyPermissionsBuilder(): SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder().SessionKeyPermissionsBuilder.setNativeTokenSpendLimit(limit: NativeTokenLimit): SessionKeyPermissionsBuilderSets the native token spend limit and returns the instance for chaining.
setNativeTokenSpendLimit({
spendLimit: bigintspendLimit: 1000000n,
});
const { const hash: anyhash } = await const extendedClient: anyextendedClient.anyinstallSessionKeyPlugin({
// 1st arg is the initial set of session keys
// 2nd arg is the tags for the session keys
// 3rd arg is the initial set of permissions
args: any[][]args: [
[await anysessionKeySigner.anygetAddress()],
[anyzeroHash],
[const initialPermissions: SessionKeyPermissionsBuilderinitialPermissions.SessionKeyPermissionsBuilder.encode(): Hex[]Encodes various function calls into an array of hexadecimal strings based on the provided permissions and limits.
encode()],
],
});
await const extendedClient: anyextendedClient.anywaitForUserOperationTransaction({ hash: anyhash });
}
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import { function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient } from "@account-kit/smart-contracts";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
export const const chain: Chainchain = const sepolia: Chainsepolia;
export const const modularAccountClient: {
account: MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>;
... 100 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}modularAccountClient = await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
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()),
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain,
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" }),
});
Construct the initial set of permissions
Session keys are powerful because of permissions that limit what actions they can take. When you add a session key, you should also specify the initial permissions that apply over the key.
See the Supported Permissions page for more information on how to used the permissions builder.
Let’s use the permission builder to build a set of permissions that sets a spend limit:
import { class SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
const const initialPermissions: SessionKeyPermissionsBuilderinitialPermissions =
new new SessionKeyPermissionsBuilder(): SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder().SessionKeyPermissionsBuilder.setNativeTokenSpendLimit(limit: NativeTokenLimit): SessionKeyPermissionsBuilderSets the native token spend limit and returns the instance for chaining.
setNativeTokenSpendLimit({
spendLimit: bigintspendLimit: 1000000n,
});
const const result: anyresult = await const extendedClient: anyextendedClient.anyupdateKeyPermissions({
args: any[]args: [anysessionKeyAddress, const initialPermissions: SessionKeyPermissionsBuilderinitialPermissions.SessionKeyPermissionsBuilder.encode(): Hex[]Encodes various function calls into an array of hexadecimal strings based on the provided permissions and limits.
encode()],
});
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import { function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient } from "@account-kit/smart-contracts";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
export const const chain: Chainchain = const sepolia: Chainsepolia;
export const const modularAccountClient: {
account: MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>;
... 100 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}modularAccountClient = await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
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()),
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain,
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" }),
});
Managing Session Keys
The Session Key Plugin allows you to:
- Add session keys, and set the key’s initial permissions.
- Remove session keys.
- Update key permissions.
- Rotate session keys. This action replaces the previous session key with a new session key, while keeping the existing permissions.
Add a Session Key
Session keys can be added either during installation, or using the addSessionKey
function.
import { class SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
import { function keccak256<to extends To = "hex">(value: Hex | ByteArray, to_?: to | undefined): Keccak256Hash<to>keccak256 } from "viem";
import { import clientclient } from "./base-client";
const const result: anyresult = await import clientclient.anyaddSessionKey({
key: stringkey: "0xSessionKeyAddress",
// tag is an identifier for the emitted SessionKeyAdded event
tag: `0x${string}`tag: keccak256<"hex">(value: Hex | ByteArray, to_?: "hex" | undefined): `0x${string}`keccak256(new var TextEncoder: new () => TextEncoderTextEncoder
class is a global reference for import TextEncoder from 'node:util'
https://nodejs.org/api/globals.html#textencoder
TextEncoder().TextEncoder.encode(input?: string): Uint8ArrayUTF-8 encodes the input
string and returns a Uint8Array
containing the encoded bytes.
encode("session-key-tag")),
permissions: `0x${string}`[]permissions: new new SessionKeyPermissionsBuilder(): SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder().SessionKeyPermissionsBuilder.encode(): Hex[]Encodes various function calls into an array of hexadecimal strings based on the provided permissions and limits.
encode(),
});
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import {
function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient,
const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions,
} from "@account-kit/smart-contracts";
export const const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client = (
await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>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>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("MNEMONIC"),
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: "ALCHEMY_API_KEY" }),
})
).extend: <SessionKeyPluginActions<MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, keyof EntryPointRegistryBase<...>>>(fn: (client: Client<...>) => SessionKeyPluginActions<...>) => Client<...>extend(const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions);
Remove a Session Key
Session keys can be removed using the removeSessionKey
function.
import { import clientclient } from "./base-client";
const const result: anyresult = await import clientclient.anyremoveSessionKey({
key: stringkey: "0xSessionKeyAddress",
});
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import {
function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient,
const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions,
} from "@account-kit/smart-contracts";
export const const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client = (
await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>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>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("MNEMONIC"),
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: "ALCHEMY_API_KEY" }),
})
).extend: <SessionKeyPluginActions<MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, keyof EntryPointRegistryBase<...>>>(fn: (client: Client<...>) => SessionKeyPluginActions<...>) => Client<...>extend(const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions);
Update a Key’s permissions
Session key permissions can be edited after creation using the updateKeyPermissions
function. Note that you should configure initial permissions when the key is added, and not rely on a second user operation to set the permissions.
import { class SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
import { import clientclient } from "./base-client";
const const result: anyresult = await import clientclient.anyupdateSessionKeyPermissions({
key: stringkey: "0xSessionKeyAddress",
// add other permissions to the builder
permissions: `0x${string}`[]permissions: new new SessionKeyPermissionsBuilder(): SessionKeyPermissionsBuilderA builder for creating the hex-encoded data for updating session key permissions.
SessionKeyPermissionsBuilder()
.SessionKeyPermissionsBuilder.setTimeRange(timeRange: TimeRange): SessionKeyPermissionsBuilderSets the time range for an object and returns the object itself for chaining.
setTimeRange({
validFrom: numbervalidFrom: var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.
Math.Math.round(x: number): numberReturns a supplied numeric expression rounded to the nearest integer.
round(var Date: DateConstructorEnables basic storage and retrieval of dates and times.
Date.DateConstructor.now(): numberReturns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now() / 1000),
// valid for 1 hour
validUntil: numbervalidUntil: var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.
Math.Math.round(x: number): numberReturns a supplied numeric expression rounded to the nearest integer.
round(var Date: DateConstructorEnables basic storage and retrieval of dates and times.
Date.DateConstructor.now(): numberReturns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).
now() / 1000 + 60 * 60),
})
.SessionKeyPermissionsBuilder.encode(): Hex[]Encodes various function calls into an array of hexadecimal strings based on the provided permissions and limits.
encode(),
});
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import {
function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient,
const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions,
} from "@account-kit/smart-contracts";
export const const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client = (
await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>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>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("MNEMONIC"),
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: "ALCHEMY_API_KEY" }),
})
).extend: <SessionKeyPluginActions<MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, keyof EntryPointRegistryBase<...>>>(fn: (client: Client<...>) => SessionKeyPluginActions<...>) => Client<...>extend(const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions);
Rotate a Session Key
If the key is no longer available, but there exists a tag identifying a previous session key configured for your application, you may instead choose to rotate the previous key’s permissions. This can be performed using rotateKey
.
import { import clientclient } from "./base-client.js";
const const result: anyresult = await import clientclient.anyrotateSessionKey({
oldKey: stringoldKey: "0xOldKey",
newKey: stringnewKey: "0xNewKey",
});
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 { 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, const sepolia: Chainsepolia } from "@account-kit/infra";
import {
function createModularAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyModularAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, MultiOwnerModularAccount<TSigner>, MultiOwnerPluginActions<MultiOwnerModularAccount<TSigner>> & PluginManagerActions<MultiOwnerModularAccount<TSigner>> & AccountLoupeActions<MultiOwnerModularAccount<TSigner>>>>createModularAccountAlchemyClient,
const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions,
} from "@account-kit/smart-contracts";
export const const client: Client<AlchemyTransport, Chain | undefined, MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, [...], {
...;
} & ... 11 more ... & AlchemySmartAccountClientActions<...>>client = (
await createModularAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>(params: AlchemyModularAccountClientConfig<...>): Promise<...>createModularAccountAlchemyClient({
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>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>.mnemonicToAccountSigner(key: string, opts?: HDOptions): LocalAccountSigner<HDAccount>Creates a LocalAccountSigner using the provided mnemonic key and optional HD options.
mnemonicToAccountSigner("MNEMONIC"),
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: "ALCHEMY_API_KEY" }),
})
).extend: <SessionKeyPluginActions<MultiOwnerModularAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 7 more ...;
getHdKey: () => HDKey;
}>>, keyof EntryPointRegistryBase<...>>>(fn: (client: Client<...>) => SessionKeyPluginActions<...>) => Client<...>extend(const sessionKeyPluginActions: <TTransport extends Transport = Transport, TChain extends Chain | undefined = Chain | undefined, TAccount extends SmartContractAccount | undefined = SmartContractAccount | undefined>(client: Client<TTransport, TChain, TAccount>) => SessionKeyPluginActions<TAccount>Creates actions for managing session keys in a smart contract associated with a client, including adding, removing, rotating, and updating session key permissions.
sessionKeyPluginActions);