Sending Transactions with Smart sessions ⚡️
The Smart Sessions Module enables developers to create session keys with specific permissions and access rights on a user's account. This allows users to control what actions an app can perform on their behalf, enforcing these permissions directly on-chain.
This document provides a step-by-step guide on how to set up and use the Smart Sessions Module with the Nexus client. Below is a breakdown of how to create, install, and use the module's functionality.
Create the Smart Session
Set up an owner account
First, we need to set up an Owner for the Smart Account which will be used to sign User Operations (transactions) for the Smart Account.
import { privateKeyToAccount } from "viem/accounts";
const privateKey = "PRIVATE_KEY";
const account = privateKeyToAccount(`0x${privateKey}`);
Set up Nexus client
A Smart Account needs access to the Network to query for information about its state (e.g., nonce, address, etc.). Let's configure a client for the Smart Account. A bundlerUrl
is required to submit User Operations to the Network, which will initialize the Smart Account.
import { createSmartAccountClient } from "@biconomy/sdk";
import { baseSepolia } from "viem/chains";
import { http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const privateKey = "PRIVATE_KEY";
const account = privateKeyToAccount(`0x${privateKey}`);
const bundlerUrl = "https://bundler.biconomy.io/api/v3/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44";
const nexusClient = await createSmartAccountClient({
signer: account,
chain: baseSepolia,
transport: http(),
bundlerTransport: http(bundlerUrl),
});
Create a smart sessions module for the user's account
const sessionsModule = toSmartSessionsValidator({
account: nexusClient.account,
signer: account
});
Install the smart sessions module
To enable smart session creation, we first need to install the smart sessions module into the Nexus client.
Once the module is installed, we extend the Nexus client with smartSessionCreateActions. This step adds the necessary functionality for managing and creating smart sessions, allowing the Nexus client to interact with the sessions module.
const hash = await nexusClient.installModule({
module: sessionsModule.moduleInitData
})
const { success: installSuccess } = await nexusClient.waitForUserOperationReceipt({ hash });
const nexusSessionClient = nexusClient.extend(smartSessionCreateActions(sessionsModule));
Create a Smart Session
To create a smart session, first define the session's permissions by specifying the session's public key and the actions it can perform (e.g., calling a function like incrementNumber
in a smart contract).
The grantPermission
function submits this information to the blockchain, assigning a unique permissionId
to track and manage the session's permissions.
const sessionOwner = privateKeyToAccount(generatePrivateKey())
const sessionPublicKey = sessionOwner.address;
const sessionRequestedInfo: CreateSessionDataParams[] = [
{
sessionPublicKey,
actionPoliciesInfo: [{
contractAddress: "0xabc", // Replace with your contract address
rules: [],
functionSelector: "0x273ea3e3" as Hex // Function selector for 'incrementNumber'
}]
}
];
const createSessionsResponse = await nexusSessionClient.grantPermission({
sessionRequestedInfo
});
const { success } = await nexusClient.waitForUserOperationReceipt({
hash: createSessionsResponse.userOpHash
});
Create active session data
The sessionData
object serves as the configuration for using a smart session. It includes the session owner’s public key, the granter’s account address, and the specific permissions (via permissionId) for actions the session is allowed to perform.
const sessionData: SessionData = {
granter: nexusClient.account.address,
sessionPublicKey,
description: `Permission to increment number at ${"0xabc"} on behalf of ${nexusClient.account.address.slice(0, 6)} `, // Optional
moduleData: {
...createSessionsResponse,
mode: SmartSessionMode.USE
}
};
const compressedSessionData = stringify(sessionData);
It’s crucial to save the compressedSessionData after the user grants permission. This data can include user-specific preferences, or any other session-related information. There are two main options to store this data:
- Local Storage: Save SessionData as a string on the client side. (Local storage requires data to be stored as a string.)
- Database Storage: Alternatively, you can save this data in your dapp’s database.
Use the stringify()
(exported from @biconomy/sdk
) function to prepare the session data for storage. It does the same as JSON.stringify()
but it accommodates bigints.
Use the Smart Session
Assume that the user has left the dapp and is returning to resume their session. The SessionData from their previous session is necessary to continue seamlessly.
Retrieve the saved compressedSessionData
const sessionData = parse(compressedSessionData) as SessionData
Decompress this data using the parse()
function to restore it to its original structure, making it ready for use. The parse()
function does the same as JSON.parse()
but it accommodates bigints.
Create a Nexus Client for Using the Session
We need a new Nexus client that is associated with the session. This client will use the session owner's private key to sign transactions.
- Account Address: The address of the account that granted the permission.
- Signer: The session owner's private key, which will sign actions during the session.
const smartSessionNexusClient = await createSmartAccountClient({
chain: baseSepolia,
accountAddress: sessionData.granter,
signer: sessionOwner,
transport: http(),
bundlerTransport: http(bundlerUrl)
});
Create a Smart Sessions Module for the Session Key
We now create a smart sessions module using the session key (sessionOwner). This module validates that the session is permitted to perform actions based on the session data we provided.
const usePermissionsModule = toSmartSessionsValidator({
account: smartSessionNexusClient.account,
signer: sessionOwner,
moduleData: sessionData.moduleData
});
const useSmartSessionNexusClient = smartSessionNexusClient.extend(
smartSessionUseActions(usePermissionsModule)
);
Send transactions with sessions
Now that we have everything set up, we can perform an action with the smart session. In this example, we're calling a function on a smart contract (incrementNumber) using the session key. This is done by specifying:
- Target: The contract address.
- Call Data: The encoded data for the function we're calling, using the ABI to define the function.
const userOpHash = await useSmartSessionNexusClient.usePermission({
calls: [
{
to: "0xabc", // Replace with your target contract address
data: encodeFunctionData({
abi: CounterAbi,
functionName: "incrementNumber"
})
}
]
});
console.log(`Transaction hash: ${userOpHash}`);
Full Code
Click here
import {
createSmartAccountClient, toSmartSessionsValidator, smartSessionCreateActions,
smartSessionUseActions, CreateSessionDataParams, SessionData,
} from "@biconomy/sdk";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";
import { Hex, encodeFunctionData, http } from "viem";
import { SmartSessionMode } from "@rhinestone/module-sdk/module"
const CounterAbi = [
{
inputs: [],
name: "incrementNumber",
outputs: [],
stateMutability: "nonpayable",
type: "function"
},
]
const privateKey = "";
const bundlerUrl = "https://bundler.biconomy.io/api/v3/84532/nJPK7B3ru.dd7f7861-190d-41bd-af80-6877f74b8f44";
export const createAccountAndSendTransaction = async () => {
const userAccount = privateKeyToAccount(`0x${privateKey}`)
const sessionOwner = privateKeyToAccount(generatePrivateKey())
const sessionPublicKey = sessionOwner.address;
// 2. Set up Nexus client
const nexusClient = await createSmartAccountClient({
signer: userAccount,
chain: baseSepolia,
transport: http(),
bundlerTransport: http(bundlerUrl),
});
// 3. Create a smart sessions module for the user's account
const sessionsModule = toSmartSessionsValidator({
account: nexusClient.account,
signer: userAccount
});
// 4. Install the smart sessions module
const hash = await nexusClient.installModule({
module: sessionsModule.moduleInitData
})
const { success: installSuccess } = await nexusClient.waitForUserOperationReceipt({ hash })
const nexusSessionClient = nexusClient.extend(
smartSessionCreateActions(sessionsModule)
)
const sessionRequestedInfo: CreateSessionDataParams[] = [
{
sessionPublicKey, // Public key of the session
actionPoliciesInfo: [
{
contractAddress: "0xabc",
rules: [],
functionSelector: "0x273ea3e3" as Hex // Selector for 'incrementNumber'
}
]
}
]
// 5. Create the smart session
const createSessionsResponse = await nexusSessionClient.grantPermission({
sessionRequestedInfo
})
const { success } =
await nexusClient.waitForUserOperationReceipt({
hash: createSessionsResponse.userOpHash
})
// Use the Smart Session
// 1. Create active session data
const sessionData: SessionData = {
granter: nexusClient.account.address,
sessionPublicKey,
moduleData: {
permissionIds: createSessionsResponse.permissionIds,
action: createSessionsResponse.action,
sessions: createSessionsResponse.sessions,
mode: SmartSessionMode.USE
}
}
// 2. Create a Nexus Client for Using the Session
const smartSessionNexusClient = await createSmartAccountClient({
chain: baseSepolia,
accountAddress: sessionData.granter,
signer: sessionOwner,
transport: http(),
bundlerTransport: http(bundlerUrl)
})
// 3. Create a Smart Sessions Module for the Session Key
const usePermissionsModule = toSmartSessionsValidator({
account: smartSessionNexusClient.account,
signer: sessionOwner,
moduleData: sessionData.moduleData
})
const useSmartSessionNexusClient = smartSessionNexusClient.extend(
smartSessionUseActions(usePermissionsModule)
)
// 4. Send transactions with sessions
const userOpHash = await useSmartSessionNexusClient.usePermission({
calls: [
{
to: "0xabc",
data: encodeFunctionData({
abi: CounterAbi,
functionName: "incrementNumber"
})
}
]
})
}
createAccountAndSendTransaction()