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(): HexgeneratePrivateKey } 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"): bigintConverts a string representation of ether to numerical wei.
parseEther } from "viem";
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";
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: 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()),
});
let let sessionKeyEntityId: numbersessionKeyEntityId = 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(): HexgeneratePrivateKey());
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: 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: SmartAccountSigner<any>signer: const sessionKeySigner: SmartAccountSigner<any>sessionKeySigner,
accountAddress?: `0x${string}` | undefinedaccountAddress: 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: () => AddressgetAddress(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),
initCode: `0x${string}`initCode: await 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.getInitCode: () => Promise<Hex>getInitCode(),
signerEntity?: SignerEntity | undefinedsignerEntity: {
entityId: numberentityId: let sessionKeyEntityId: numbersessionKeyEntityId,
isGlobalValidation: booleanisGlobalValidation: 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 | BatchUserOperationCallDatauo: {
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 | undefinedvalue: function parseEther(ether: string, unit?: "wei" | "gwei"): bigintConverts a string representation of ether to numerical wei.
parseEther("1"), // The value to send in the UO
},
});
Note that you have to pass in accountAddress
and initCode
to session key clients. By default, the client uses an account address counterfactual that assumes that the connected signer is the owner.