Using Session Keys

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.

Once session keys are added, using them is straightforward - just create another client instance with the session key connected along with properties of the session key (session key entityId and global validation is used). These properties were set during the installValidation call.

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 {
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 generatePrivateKey(): Hex
generatePrivateKey
} from "viem/accounts";
import { type
interface SmartAccountSigner<Inner = any>

A signer that can sign messages and typed data.

SmartAccountSigner
} from "@aa-sdk/core";
import {
function parseEther(ether: string, unit?: "wei" | "gwei"): bigint

Converts a string representation of ether to numerical wei.

Docs: https://viem.sh/docs/utilities/parseEther

parseEther
} from "viem";
import {
const sepolia: Chain
sepolia
,
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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";
const
const client: { [x: string]: unknown; account: ModularAccountV2<LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>>; ... 84 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
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: Chain
sepolia
,
transport: AlchemyTransport

The RPC transport

transport
:
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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: string
apiKey
: "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(): Hex
generatePrivateKey
()),
}); let
let sessionKeyEntityId: number
sessionKeyEntityId
= 1;
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>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>

Creates a LocalAccountSigner instance using the provided private key.

privateKeyToAccountSigner
(
function generatePrivateKey(): Hex
generatePrivateKey
());
const
const sessionKeyClient: { [x: string]: unknown; account: ModularAccountV2<SmartAccountSigner<any>>; batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined; ... 83 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
sessionKeyClient
= await
createModularAccountV2Client<Chain, SmartAccountSigner<any>>(args: CreateModularAccountV2AlchemyClientParams<AlchemyTransport, Chain, SmartAccountSigner<any>>): 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: Chain
sepolia
,
transport: AlchemyTransport

The RPC transport

transport
:
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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: string
apiKey
: "your-api-key" }),
signer: SmartAccountSigner<any>
signer
:
const sessionKeySigner: SmartAccountSigner<any>
sessionKeySigner
,
accountAddress?: `0x${string}` | undefined
accountAddress
:
const client: { [x: string]: unknown; account: ModularAccountV2<LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>>; ... 84 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
client
.
getAddress: () => Address
getAddress
(
const client: { [x: string]: unknown; account: ModularAccountV2<LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>>; ... 84 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
client
.
account: ModularAccountV2<LocalAccountSigner<{ address: Address; nonceManager?: NonceManager | undefined; sign: (parameters: { hash: Hash; }) => Promise<Hex>; ... 6 more ...; type: "local"; }>>

The Account of the Client.

account
),
signerEntity?: SignerEntity | undefined
signerEntity
: {
entityId: number
entityId
:
let sessionKeyEntityId: number
sessionKeyEntityId
,
isGlobalValidation: boolean
isGlobalValidation
: true,
}, }); await
const sessionKeyClient: { [x: string]: unknown; account: ModularAccountV2<SmartAccountSigner<any>>; batch?: { multicall?: boolean | Prettify<MulticallBatchOptions> | undefined; } | undefined; ... 83 more ...; extend: <const client extends { ...; } & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>; }
sessionKeyClient
.
sendUserOperation: (args: SendUserOperationParameters<ModularAccountV2<SmartAccountSigner<any>>, UserOperationContext | undefined, keyof EntryPointRegistryBase<unknown>>) => Promise<...>
sendUserOperation
({
uo: UserOperationCallData | BatchUserOperationCallData
uo
: {
target: `0x${string}`
target
: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // The address to call in the UO
data: `0x${string}`
data
: "0x", // The calldata to send in the UO
value?: bigint | undefined
value
:
function parseEther(ether: string, unit?: "wei" | "gwei"): bigint

Converts a string representation of ether to numerical wei.

Docs: https://viem.sh/docs/utilities/parseEther

parseEther
("1"), // The value to send in the UO
}, });

Note that you have to pass in accountAddress to session key clients. By default, the client uses an account address counterfactual that assumes that the connected signer is the owner.