Smart Sessions is an implementation of session keys that works across all ERC-7579-compatible accounts.

It 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 transaction within the specified time frame
  • Usage limit: allows a limited number of transaction
  • Value limit: allows a limited ETH value transfered

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.