Implement session keys to enable secure, scoped permissions for dApps and users with Para’s AA infrastructure.

Prerequisites

You need a deployed smart account with session key module support.

Create Smart Account

Create Session Keys

import { toECDSASigner } from "@zerodev/permissions/signers";
import { toPermissionValidator } from "@zerodev/permissions";
import { toCallPolicy, toSudoPolicy } from "@zerodev/permissions/policies";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";

async function createSessionKey(
  kernelClient: any,
  permissions: {
    target?: string;
    functionSelector?: string;
    valueLimit?: bigint;
    validAfter?: number;
    validUntil?: number;
  }
) {
  const sessionPrivateKey = generatePrivateKey();
  const sessionKeySigner = privateKeyToAccount(sessionPrivateKey);
  
  console.log("Session Key Address:", sessionKeySigner.address);
  
  const ecdsaSigner = toECDSASigner({ signer: sessionKeySigner });
  
  const policies = [];
  
  if (permissions.target) {
    policies.push(
      toCallPolicy({
        target: permissions.target,
        selector: permissions.functionSelector,
        valueLimit: permissions.valueLimit || 0n
      })
    );
  }
  
  const permissionPlugin = await toPermissionValidator(kernelClient.publicClient, {
    signer: ecdsaSigner,
    policies,
    validAfter: permissions.validAfter || Math.floor(Date.now() / 1000),
    validUntil: permissions.validUntil || Math.floor(Date.now() / 1000) + 86400
  });
  
  const userOpHash = await kernelClient.installPlugin({
    plugin: permissionPlugin
  });
  
  await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });
  
  console.log("Session key created!");
  
  return {
    sessionKey: sessionPrivateKey,
    sessionKeySigner,
    permissionPlugin
  };
}

Use Session Keys

import { createKernelAccountClient } from "@zerodev/sdk";
import { toECDSASigner } from "@zerodev/permissions/signers";
import { privateKeyToAccount } from "viem/accounts";

async function useSessionKey(
  sessionPrivateKey: string,
  account: any,
  bundlerUrl: string,
  publicClient: any
) {
  const sessionKeySigner = privateKeyToAccount(sessionPrivateKey);
  const ecdsaSigner = toECDSASigner({ signer: sessionKeySigner });
  
  const sessionKeyClient = createKernelAccountClient({
    account,
    chain: account.client.chain,
    bundlerTransport: http(bundlerUrl),
    client: publicClient,
    middleware: {
      sponsorUserOperation: async (args) => {
        console.log("Using session key for:", args);
        return args;
      }
    }
  });
  
  console.log("Session key client ready");
  console.log("Using session key:", sessionKeySigner.address);
  
  return sessionKeyClient;
}

Scoped Permissions

import { toCallPolicy, toGasPolicy, toRateLimitPolicy } from "@zerodev/permissions/policies";
import { encodeFunctionData, parseAbi } from "viem";

async function createScopedSessionKey(
  kernelClient: any,
  config: {
    erc20Token: string;
    spendLimit: bigint;
    recipient: string;
    rateLimit: number;
  }
) {
  const erc20Abi = parseAbi([
    "function transfer(address to, uint256 amount) returns (bool)"
  ]);
  
  const policies = [
    toCallPolicy({
      target: config.erc20Token,
      selector: "0xa9059cbb",
      valueLimit: 0n,
      constraints: [
        {
          condition: "EQUAL",
          offset: 0,
          value: config.recipient
        },
        {
          condition: "LESS_THAN_OR_EQUAL",
          offset: 32,
          value: config.spendLimit
        }
      ]
    }),
    toGasPolicy({
      allowed: parseEther("0.01")
    }),
    toRateLimitPolicy({
      count: config.rateLimit,
      interval: 86400
    })
  ];
  
  const sessionPrivateKey = generatePrivateKey();
  const sessionKeySigner = privateKeyToAccount(sessionPrivateKey);
  const ecdsaSigner = toECDSASigner({ signer: sessionKeySigner });
  
  const permissionPlugin = await toPermissionValidator(kernelClient.publicClient, {
    signer: ecdsaSigner,
    policies,
    validUntil: Math.floor(Date.now() / 1000) + 86400
  });
  
  const userOpHash = await kernelClient.installPlugin({
    plugin: permissionPlugin
  });
  
  await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });
  
  console.log("Scoped session key created");
  console.log("- Token:", config.erc20Token);
  console.log("- Recipient:", config.recipient);
  console.log("- Spend Limit:", formatEther(config.spendLimit));
  console.log("- Rate Limit:", config.rateLimit, "per day");
  
  return {
    sessionKey: sessionPrivateKey,
    sessionKeySigner,
    permissionPlugin
  };
}

Revoke Session Keys

async function revokeSessionKey(
  kernelClient: any,
  permissionPlugin: any
) {
  try {
    const userOpHash = await kernelClient.uninstallPlugin({
      plugin: permissionPlugin
    });
    
    console.log("Revoking session key...");
    
    const receipt = await kernelClient.waitForUserOperationReceipt({
      hash: userOpHash
    });
    
    console.log("Session key revoked:", receipt.receipt.transactionHash);
    
    return receipt;
  } catch (error) {
    console.error("Failed to revoke session key:", error);
    throw error;
  }
}

Next Steps