Smart ContractsOther AccountsModular Account V1Session keys

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.

1/* eslint-disable @typescript-eslint/no-unused-vars */
2import { LocalAccountSigner } from "@aa-sdk/core";
3import { alchemy, sepolia } from "@account-kit/infra";
4import {
5 SessionKeyAccessListType,
6 SessionKeyPermissionsBuilder,
7 SessionKeyPlugin,
8 SessionKeySigner,
9 createModularAccountAlchemyClient,
10 sessionKeyPluginActions,
11} from "@account-kit/smart-contracts";
12import { zeroHash } from "viem";
13
14const chain = sepolia;
15const transport = alchemy({ apiKey: "YOUR_API_KEY" });
16
17// this is the signer to connect with the account, later we will create a new client using a session key signe
18const signer = LocalAccountSigner.mnemonicToAccountSigner("MNEMONIC");
19const sessionKeySigner = new SessionKeySigner();
20const client = (
21 await createModularAccountAlchemyClient({
22 chain,
23 transport,
24 signer,
25 })
26).extend(sessionKeyPluginActions);
27
28// 1. check if the plugin is installed
29const isPluginInstalled = await client
30 .getInstalledPlugins({})
31 // This checks using the default address for the chain, but you can always pass in your own plugin address here as an override
32 .then((x) => x.includes(SessionKeyPlugin.meta.addresses[chain.id]));
33
34// 2. if the plugin is not installed, then install it and set up the session key
35if (!isPluginInstalled) {
36 // lets create an initial permission set for the session key giving it an eth spend limit
37 const initialPermissions = new SessionKeyPermissionsBuilder()
38 .setNativeTokenSpendLimit({
39 spendLimit: 1000000n,
40 })
41 // this will allow the session key plugin to interact with all addresses
42 .setContractAccessControlType(SessionKeyAccessListType.ALLOW_ALL_ACCESS)
43 .setTimeRange({
44 validFrom: Math.round(Date.now() / 1000),
45 // valid for 1 hour
46 validUntil: Math.round(Date.now() / 1000 + 60 * 60),
47 });
48
49 const { hash } = await client.installSessionKeyPlugin({
50 // 1st arg is the initial set of session keys
51 // 2nd arg is the tags for the session keys
52 // 3rd arg is the initial set of permissions
53 args: [
54 [await sessionKeySigner.getAddress()],
55 [zeroHash],
56 [initialPermissions.encode()],
57 ],
58 });
59
60 await client.waitForUserOperationTransaction({ hash });
61}
62
63// 3. set up a client that's using our session key
64const sessionKeyClient = (
65 await createModularAccountAlchemyClient({
66 chain,
67 signer: sessionKeySigner,
68 transport,
69 // this is important because it tells the client to use our previously deployed account
70 accountAddress: client.getAddress({ account: client.account }),
71 })
72).extend(sessionKeyPluginActions);
73
74// 4. send a user operation using the session key
75const result = await sessionKeyClient.executeWithSessionKey({
76 args: [
77 [
78 {
79 target: "0x1234123412341234123412341234123412341234",
80 value: 1n,
81 data: "0x",
82 },
83 ],
84 await sessionKeySigner.getAddress(),
85 ],
86});

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.

1import { SessionKeySigner } from "@account-kit/smart-contracts";
2
3const sessionKeySigner = new 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.

1import { modularAccountClient } from "./client";
2import { sessionKeyPluginActions } from "@account-kit/smart-contracts";
3
4const extendedClient = modularAccountClient.extend(sessionKeyPluginActions);

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.

1import { modularAccountClient } from "./client.js";
2import {
3 accountLoupeActions,
4 multiOwnerPluginActions,
5 sessionKeyPluginActions,
6 pluginManagerActions,
7} from "@account-kit/smart-contracts";
8
9const extendedClient = modularAccountClient.extend(sessionKeyPluginActions);
10
11//---cut---
12
13import { SessionKeyPlugin } from "@account-kit/smart-contracts";
14
15// 1. check if the plugin is installed
16const isPluginInstalled = await modularAccountClient
17 .getInstalledPlugins({})
18 // This checks using the default address for the chain, but you can always pass in your own plugin address here as an override
19 .then((x) => x.includes(SessionKeyPlugin.meta.addresses[chain.id]));

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.

1import { modularAccountClient } from "./client";
2import {
3 accountLoupeActions,
4 multiOwnerPluginActions,
5 sessionKeyPluginActions,
6 pluginManagerActions,
7} from "@account-kit/smart-contracts";
8
9const extendedClient = modularAccountClient.extend(sessionKeyPluginActions);
10
11import { SessionKeyPlugin } from "@account-kit/smart-contracts";
12
13// 1. check if the plugin is installed
14const isPluginInstalled = await extendedClient
15 .getInstalledPlugins({})
16 // This checks using the default address for the chain, but you can always pass in your own plugin address here as an override
17 .then((x) => x.includes(SessionKeyPlugin.meta.addresses[chain.id]));
18
19//---cut---
20import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
21
22// 2. if the plugin is not installed, then install it and set up the session key
23if (!isPluginInstalled) {
24 // lets create an initial permission set for the session key giving it an eth spend limit
25 // if we don't set anything here, then the key will have 0 permissions
26 const initialPermissions =
27 new SessionKeyPermissionsBuilder().setNativeTokenSpendLimit({
28 spendLimit: 1000000n,
29 });
30
31 const { hash } = await extendedClient.installSessionKeyPlugin({
32 // 1st arg is the initial set of session keys
33 // 2nd arg is the tags for the session keys
34 // 3rd arg is the initial set of permissions
35 args: [
36 [await sessionKeySigner.getAddress()],
37 [zeroHash],
38 [initialPermissions.encode()],
39 ],
40 });
41
42 await extendedClient.waitForUserOperationTransaction({ hash });
43}

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:

1import { modularAccountClient } from "./client";
2import {
3 accountLoupeActions,
4 multiOwnerPluginActions,
5 sessionKeyPluginActions,
6 pluginManagerActions,
7} from "@account-kit/smart-contracts";
8
9const extendedClient = modularAccountClient.extend(sessionKeyPluginActions);
10
11// ---cut---
12import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
13
14const initialPermissions =
15 new SessionKeyPermissionsBuilder().setNativeTokenSpendLimit({
16 spendLimit: 1000000n,
17 });
18
19const result = await extendedClient.updateKeyPermissions({
20 args: [sessionKeyAddress, initialPermissions.encode()],
21});

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.

1import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
2import { keccak256 } from "viem";
3import { client } from "./base-client";
4
5const result = await client.addSessionKey({
6 key: "0xSessionKeyAddress",
7 // tag is an identifier for the emitted SessionKeyAdded event
8 tag: keccak256(new TextEncoder().encode("session-key-tag")),
9 permissions: new SessionKeyPermissionsBuilder().encode(),
10});

Remove a Session Key

Session keys can be removed using the removeSessionKey function.

1import { client } from "./base-client";
2
3const result = await client.removeSessionKey({
4 key: "0xSessionKeyAddress",
5});

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.

1import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
2import { client } from "./base-client";
3
4const result = await client.updateSessionKeyPermissions({
5 key: "0xSessionKeyAddress",
6 // add other permissions to the builder
7 permissions: new SessionKeyPermissionsBuilder()
8 .setTimeRange({
9 validFrom: Math.round(Date.now() / 1000),
10 // valid for 1 hour
11 validUntil: Math.round(Date.now() / 1000 + 60 * 60),
12 })
13 .encode(),
14});

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 .

1import { client } from "./base-client.js";
2
3const result = await client.rotateSessionKey({
4 oldKey: "0xOldKey",
5 newKey: "0xNewKey",
6});