Biconomy Account

Use Biconomy smart account's session key implementation

Only BICONOMY smart account 2.0.0 supports this feature

Try now

  1. Enable BICONOMY 2.0.0 & switch to Polygon Mumbai

  2. Login and use the session key feature

Execution Process

The basic process is as follows:

  1. Authorize Session

    1. Create a Session containing the following information:

      1. sessionKeyData: Metadata for the validation contract, which includes a temporary public key address and some other parameters. This needs to be registered in the SmartAccount. During the verification process, SmartAccount will pass the specified SessionKeyData to the validation contract for reading and usage.

      2. sessionValidationModule: A custom validation contract that replaces the validateUserOp functionality. Its responsibility is to verify whether UserOp is legitimate, such as being signed by an authorized Session Signer and for a specified purpose.

    2. Register the Session on the blockchain.

  2. Subsequent UserOps for specific purposes can then be directly signed using the Session Signer.

Custom Validation Contract

The interface is implemented as follows

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {UserOperation} from "@account-abstraction/contracts/interfaces/UserOperation.sol";

abstract contract ISessionValidationModule {
    // execute(address,uint256,bytes)
    bytes4 public constant EXECUTE_SELECTOR = 0xb61d27f6;
    // execute_ncC(address,uint256,bytes)
    bytes4 public constant EXECUTE_OPTIMIZED_SELECTOR = 0x0000189a;

     * @dev validates if the _op (UserOperation) matches the SessionKey permissions
     * and that _op has been signed by this SessionKey
     * @param _op User Operation to be validated.
     * @param _userOpHash Hash of the User Operation to be validated.
     * @param _sessionKeyData SessionKey data, that describes sessionKey permissions
     * @param _sessionKeySignature Signature over the the _userOpHash.
     * @return true if the _op is valid, false otherwise.
    function validateSessionUserOp(
        UserOperation calldata _op,
        bytes32 _userOpHash,
        bytes calldata _sessionKeyData,
        bytes calldata _sessionKeySignature
    ) external virtual returns (bool);

Example: mint one NFT for a specific address every time

  1. Prepare an ERC 1155 NFT contract


  2. Define the sessionKeyData data structure

        "address", // session signer address
        "address", // recipient, which can receive the nft
        "uint256"  // specified nft token id
  1. Prepare a custom validation contract


  2. Register the session

import Axios from "axios";
import { Wallet, utils } from "ethers";

const chainId = 80001;
const PARTICLE_RPC = `${chainId}`;
const scanUrl = '';
const nft1155ContractAddress = "0x909E30bdBCb728131E3F8d17150eaE740C904649";
const customValidationContractAddress = "0x8E09744b738e9Fec4A4df7Ab5621f1857F6Fa175";

const mainSigner = Wallet.createRandom();
const sessionSigner = Wallet.createRandom();
const smartAccount = { name: "BICONOMY", version: "2.0.0", ownerAddress: mainSigner.address };

(async () => {
    let sessions: any[] = [
            validUntil: 0, // 0 means no expiry
            validAfter: 0, // 0 means no start time
            sessionValidationModule: customValidationContractAddress,
            // you can use `sessionKeyData` to replace `sessionKeyDataInAbi`
            // sessionKeyData: abi.encode(...)
            sessionKeyDataInAbi: [
                ["address", "address", "uint256"],
                    sessionSigner.address, // session signer address
                    sessionSigner.address, // receiver address
                    1, // nft token id

    const resCreateSessions = await, {
        method: "particle_aa_createSessions",
        params: [smartAccount, sessions],

    // we use gasless mode
    const userOp =;
    const userOpHash =;
    sessions =; // the sessions you need to store locally
    userOp.signature = await mainSigner.signMessage(utils.arrayify(userOpHash));

    const resSendUserOp = await, {
        method: "particle_aa_sendUserOp",
        params: [smartAccount, userOp],

  1. Use session signer to sign the userOps follows

const resValidateSession = await, {
    method: "particle_aa_validateSession",
    params: [
            sessions, // all sessions to generate proof
            targetSession: sessions[0], // which session to check if valid

const isValidSession =;
if (!isValidSession) {
    throw new Error("Session is not valid");

const erc1155Contract = new Contract(nft1155ContractAddress, ["function mintTo(address, uint256, uint256) public"], provider);
const tx = await erc1155Contract.populateTransaction.mintTo(sessionSigner.address, 1, 1);

const resGetFeeQuotes = await, {
    method: "particle_aa_getFeeQuotes",
    params: [smartAccount, [tx]],

const userOp =;
const userOpHash =;
userOp.signature = await sessionSigner.signMessage(utils.arrayify(userOpHash));

const resSendUserOp = await, {
    method: "particle_aa_sendUserOp",
    params: [
            sessions, // all sessions to generate proof
            targetSession: sessions[0], // which session to use in this userOp


Management and Use of Session

  1. Since only the hash value of the session collection is stored on the chain, the specific data of sessions needs to be saved off-chain, usually by the client itself. And because each update chain of sessions is in the form of overwriting the original hash, each time a session is registered, all sessions need to be passed in.

  2. Generally speaking, for the client, every time a session is updated, only all the sessions that are within the validity period need to be passed.

Last updated