Overview
Multi-chain session keys allow you to create and manage session keys that work across multiple blockchains with a single signature.
- Sign once, deploy everywhere: Create multiple sessions for different chains and sign them all at once
- Flexible installation: Install sessions in any order across different chains as needed
- Reduce user friction: Users only need to sign once instead of multiple times for each chain
- Cross-chain automation: Enable seamless automation across multiple chains with the same session key
The key innovation is the “enable mode” — where you prepare all your sessions upfront, get a single signature from the user, and enable specific sessions on specific chains as needed.
While this guide focuses on multi-chain usage, you can also use this pattern on a single chain to pre-sign multiple different sessions and enable them over time as your application needs to evolve.
Creating the Sessions
First, define your sessions for different chains. Each session can have different owners, policies, and actions depending on your use case:
import { baseSepolia, optimismSepolia } from 'viem/chains'
import { Session } from '@rhinestone/sdk'
const sessions: Session[] = [
{
chain: baseSepolia,
owners: {
type: 'ecdsa',
accounts: [sessionOwnerAccountA],
},
// Add specific policies and actions as needed
},
{
chain: baseSepolia,
owners: {
type: 'ecdsa',
accounts: [sessionOwnerAccountB],
},
},
{
chain: optimismSepolia,
owners: {
type: 'ecdsa',
accounts: [sessionOwnerAccountB],
},
},
]
Enabling the Sessions
To enable a session, you need to get the session details and enable data. This step prepares the session for installation on the target chain:
const sessionIndex = 0 // Which session to enable
const sessionDetails = await rhinestoneAccount.getSessionDetails(
sessions,
sessionIndex,
)
The method returns the data needed to install the session on-chain, including the signature required to enable sessions.
The account owner will be prompted to sign the session installation request.
Reusing the Signature
One of the most powerful features is the ability to reuse an existing signature across multiple session installations. Once you have the signature to enable a session, you can use it for future installations:
// First time: Get session details and signature
const sessionDetails = await rhinestoneAccount.getSessionDetails(
sessions,
sessionIndex,
)
// Store the `sessionDetails` for later use
// Later: Reuse the existing signature
const sessionDetailsReused = await rhinestoneAccount.getSessionDetails(
sessions,
sessionIndex,
existingSessionDetails.nonces,
existingSessionDetails.signature
)
You also need to supply the original session nonces, otherwise the session digest will not match the signed digest.
This pattern allows you to:
- Pre-sign all your sessions during user onboarding
- Enable specific sessions on-demand without additional signatures
- Batch multiple session installations with the same signature
Using the Sessions
Once your sessions are enabled, you can use them to sign transactions on any supported chain:
const data = await rhinestoneAccount.prepareUserOperation({
chain,
calls: [
{
to: targetAddress,
data: transactionData,
},
],
signers: {
type: 'session',
session: sessions[sessionIndex],
enableData: sessionDetails.enableSessionData,
},
})
// Sign with the session key
const signedData = await rhinestoneAccount.signUserOperation(data)
await rhinestoneAccount.submitUserOperation(signedData)
Complete Example
Here’s a complete working example that demonstrates the full multi-chain session workflow:
import {
RhinestoneSDK,
type Session
} from '@rhinestone/sdk'
import { zeroAddress } from 'viem'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
import { baseSepolia, optimismSepolia } from 'viem/chains'
const ownerAccount = privateKeyToAccount('0x…')
const sessionOwnerAccount = privateKeyToAccount(generatePrivateKey())
const rhinestone = new RhinestoneSDK({
apiKey: rhinestoneApiKey,
})
const rhinestoneAccount = await rhinestone.createAccount({
owners: {
type: 'ecdsa',
accounts: [ownerAccount],
},
sessions: [],
})
// Define the sessions
const sessions: Session[] = [
{
chain: baseSepolia,
owners: {
type: 'ecdsa',
accounts: [sessionOwnerAccount],
},
},
{
chain: optimismSepolia,
owners: {
type: 'ecdsa',
accounts: [sessionOwnerAccount],
},
},
]
// Get the session installation details on Base
const sessionIndexBase = 0
const sessionDetailsBase = await rhinestoneAccount.getSessionDetails(
sessions,
sessionIndexBase,
)
// Enable and use the session on Base
const dataBase = await rhinestoneAccount.prepareUserOperation({
chain: baseSepolia,
calls: [
{
to: zeroAddress,
data: '0xdeadbeef',
},
],
tokenRequests: [],
signers: {
type: 'session',
session: sessions[sessionIndexBase],
enableData: sessionDetailsBase.enableSessionData,
},
})
const signedDataBase = await rhinestoneAccount.signUserOperation(dataBase)
await rhinestoneAccount.submitUserOperation(signedDataBase)
// Get the session installation details on Optimism
const sessionIndexOptimism = 1
const sessionDetailsOptimism = await rhinestoneAccount.getSessionDetails(
sessions,
sessionIndexOptimism,
// Reusing the existing nonces and the signature
sessionDetails.nonces,
sessionDetails.signature
)
// Enable and use the session on Optimism
const dataOptimism = await rhinestoneAccount.prepareUserOperation({
chain: optimismSepolia,
calls: [
{
to: zeroAddress,
data: '0xdeadbeef',
},
],
tokenRequests: [],
signers: {
type: 'session',
session: sessions[sessionIndexOptimism],
enableData: sessionDetailsOptimism.enableSessionData,
},
})
const signedDataOptimism = await rhinestoneAccount.signUserOperation(dataOptimism)
await rhinestoneAccount.submitUserOperation(signedDataOptimism)