Introduction

Session keys are cryptographically signed keys generated by a user’s master key (passkey, ECDSA, or multisig). Smart Sessions enables session keys to be created and used with all major smart account implementations (via ERC-7579) and is fully compatible with Rhinestone’s Omni Account transaction infrastructure.

Examples of the onchain permissions that can be tailored with Smart Sessions include:

  • Interacting only with a specific DeFi protocol (Aave or Uniswap)
  • Spending limits on ERC20s or ETH
  • Timeframes for expiry after a pre-determined period
  • Combining permissions (e.g., Uniswap-only, 1000 USDC limit, 3-day expiry)

Key example use cases include:

  • Skipping confirmations: Store a session key locally for “one-click trading,” allowing seamless decentralized application (dapp) interactions without repeated signing prompts.
  • Automating transactions: Users share a scoped key for server-side execution, enabling: Subscription payments
    • Limit orders or stop orders
    • Auto-repaying loans to prevent liquidation
    • This granular control enhances security, streamlines dapp interactions, and makes Web3 more user-friendly.

How it works

Smart Sessions separates owners, policies, and actions to increase composability and component reuse, which improves developer efficiency and costs.

Owners

Smart Sessions support a wide range of signing mechanisms out of the box:

You can also use custom validators to validate sessions, as long as they are ERC-7780 compatible.

Actions

Actions define what transactions (calls) you can make within a session. An action is defined by the target address and the function selector.

An example of an action would be token transfer, where target would be the address of the ERC20 token and selector would be 0xa9059cbb (the transfer function selector).

When defining multiple actions within a session, a transaction that matches any specified action is considered valid. If no actions are specified, any transaction will pass.

When using smart contracts directly, you need to explicitly provide a list of valid actions.

Policies

Policies let you restrict the session to hit specific conditions. You can define policies at the session (affects the entire session) or action (affects a single action within a session) level.

Supported policies include:

  • Sudo: allows any transaction
  • Call: allows transactions with the specified calldata
  • Spending limit: allows a limited value of ERC20 tokens to be transferred and approved
  • Timeframe: allows transactions within the specified time frame
  • Usage limit: allows a limited number of transactions
  • Value limit: allows a limited ETH value transferred

When defining multiple policies within a session or an action, a transaction that passes every specified policy is considered valid. If no policies are specified, any transaction will pass (i.e., the sudo policy is applied).

Usage

Creating sessions

To create an account with a session key:

const session: Session = {
  owners: {
    type: 'ecdsa',
    accounts: [sessionOwnerAccount],
  },
}

const rhinestoneAccount = await createRhinestoneAccount({
  owners: {
    type: 'ecdsa',
    accounts: [ownerAccount],
  },
  sessions: [session],
  rhinestoneApiKey,
})

You can also limit the session to specific allowed actions:

const session: Session = {
  owners: {
    type: 'ecdsa',
    accounts: [sessionOwnerAccount],
  },
  actions: [
    {
      target: usdcAddress,
      selector: toFunctionSelector(
        getAbiItem({
          abi: erc20Abi,
          name: 'transfer',
        }),
      ),
    },
  ],
}

Finally, you can specify policies at both session and action levels:

const session: Session = {
  owners: {
    type: 'ecdsa',
    accounts: [sessionOwnerAccount],
  },
  policies: [
    {
      type: 'sudo',
    },
  ],
  actions: [
    {
      target: usdcAddress,
      selector: toFunctionSelector(
        getAbiItem({
          abi: erc20Abi,
          name: 'transfer',
        }),
      ),
      policies: [
        {
          type: 'universal-action',
          rules: [
            {
              condition: 'equal',
              calldataOffset: 0n,
              referenceValue: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
            },
          ],
        },
      ],
    },
  ],
}

Using sessions

To authorize a transaction with a session key you’ve defined before:

const transactionResult = await rhinestoneAccount.sendTransaction({
  chain,
  calls: [
    {
      to: usdcAddress,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'transfer',
        args: ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045', 1n],
      }),
    },
  ],
  signers: {
    type: 'session',
    session: session,
  },
})

This will prompt the signature request from the session owner(s) and submit the transaction on their behalf.

You don’t have to create a separate transaction to enable the session; this is handled automatically for you. When making your first transaction with the session, the account owner will be prompted to enable it with a signature.

Security

Smart Sessions is a powerful tool that unlocks a bunch of new opportunities and use cases. To keep your users secure when using sessions, follow these guidelines:

  • Store the session key securely. Depending on the use case, you can opt to store it in the browser or on your backend. Consider key management solutions like KMS or Lit Protocol.
  • Stick to the principle of least privilege: do not request more actions than you need.
  • Guard your smart session with granular policies (e.g., restrict the amount of ETH that can be transacted through the session)
  • If possible, timebox your session (e.g., make it valid for only 1 week)
By default, the SDK creates a session that allows any transaction. Make sure you restrict it with relevant actions and policies.

Reach out to us if you need any help!