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.
Create 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],
},
},
]
Enable the Session
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 sessionDetails =
await rhinestoneAccount.experimental_getSessionDetails(sessions)
const enableSignature =
await rhinestoneAccount.experimental_signEnableSession(sessionDetails)
const sessionIndex = 0 // Which session to enable
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. For this, store the sessionDetails and enableSignature for later use.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
Use the Sessions
Once your sessions are enabled, you can use them to sign transactions on any supported chain:const data = await rhinestoneAccount.prepareTransaction({
chain,
calls: [
{
to: targetAddress,
data: transactionData,
},
],
signers: {
type: 'experimental_session',
session: sessions[sessionIndex],
enableData: {
userSignature: enableSignature,
hashesAndChainIds: sessionDetails.hashesAndChainIds,
sessionIndex,
},
},
})
// Sign with the session key
const signedData = await rhinestoneAccount.signTransaction(data)
await rhinestoneAccount.submitTransaction(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 details and sign once
const sessionDetails =
await rhinestoneAccount.experimental_getSessionDetails(sessions)
const enableSignature =
await rhinestoneAccount.experimental_signEnableSession(sessionDetails)
// Enable and use the session on Base
const sessionIndexBase = 0
const dataBase = await rhinestoneAccount.prepareTransaction({
chain: baseSepolia,
calls: [
{
to: zeroAddress,
data: '0xdeadbeef',
},
],
tokenRequests: [],
signers: {
type: 'experimental_session',
session: sessions[sessionIndexBase],
enableData: {
userSignature: enableSignature,
hashesAndChainIds: sessionDetails.hashesAndChainIds,
sessionIndex: sessionIndexBase,
},
},
})
const signedDataBase = await rhinestoneAccount.signTransaction(dataBase)
await rhinestoneAccount.submitTransaction(signedDataBase)
// Enable and use the session on Optimism (reusing the same signature)
const sessionIndexOptimism = 1
const dataOptimism = await rhinestoneAccount.prepareTransaction({
chain: optimismSepolia,
calls: [
{
to: zeroAddress,
data: '0xdeadbeef',
},
],
tokenRequests: [],
signers: {
type: 'experimental_session',
session: sessions[sessionIndexOptimism],
enableData: {
userSignature: enableSignature,
hashesAndChainIds: sessionDetails.hashesAndChainIds,
sessionIndex: sessionIndexOptimism,
},
},
})
const signedDataOptimism = await rhinestoneAccount.signTransaction(dataOptimism)
await rhinestoneAccount.submitTransaction(signedDataOptimism)