Skip to content

Supported Permissions

Time range

Supports a start time and an end time for each session key.

Access control lists

Supports either an allowlist or a denylist for addresses. Optionally, access control lists may also specify specific functions on contracts to allow or deny.

ERC-20 spending Limits

Supports limiting how much of a specific ERC-20 token a key may spend. This may be a total for the key, or refreshing on an interval (e.g. 100 USDC per week).

Native token spending limits

Supports limiting how much of the native token, e.g. ETH or MATIC, a key may spend. This may be a total for the key, or refreshing on an interval (e.g. 1 ETH per week).

Gas spending limits

Supports limiting how much of the native token (e.g. ETH or MATIC) a session key can spend on gas. This may be a total for the key, or refreshing on an interval (e.g. 1 ETH per week).

Alternatively, you can also require that a session key uses a specific paymaster address, instead of spending the account’s native token for gas.

Importance of Gas Limits

Gas spend limits are critically important to protecting the account. If you are using a session key, you should configure either a required paymaster rule or a gas spend limit. Failing to do so could allow a compromised session key to drain the account’s native token balance.

Note that the gas limit is tracked in terms of native token units (wei), not in units of gas. The gas usage of a user operation is considered to be the maximum gas a user operation can spend, i.e. total gas limit * maxFeePerGas. This can overestimate when compared to the actual gas cost of each user operation.

Default values

Permissions start with the following default values:

PermissionDefault Value
Access control listType: allowlist
The list starts empty. When the allowlist is empty, all calls will be denied.
Time rangeUnlimited
Native token spend limit0
This means all calls spending the native token will be denied, unless the limit is updated or removed.
ERC-20 spend limitUnset. If you want to enabled an ERC-20 spend limit, add the ERC-20 token contract to the access control list and set the spending limit amount.
Gas spend limitsUnset. When defining the session key’s permissions, you should specify either a gas spending limit or a required paymaster.

Using the PermissionsBuilder

To construct the data to set a key's permissions, you will need to use the SessionKeyPermissionBuilder class. This will allow you to specify a series of updates, and when complete, you may generate the encoded data to perform all updates at once.

The permissions data may be specified in 3 places:

  • In the Session Key Plugin's onInstall data, setting the intial permissions for a session key added at install time.
  • As data for the initial permissions of a session key added via addSessionKey.
  • As a parameter to the updateKeyPermissions function, to change the permissions of an existing key.

Generating the permissions

ts
// Let's create an initial permission set for the session key giving it an eth spend limit
const keyPermissions = new SessionKeyPermissionsBuilder()
  .setNativeTokenSpendLimit({
    spendLimit: 1000000n,
  })
  // this will allow the session key plugin to interact with all addresses
  .setContractAccessControlType(SessionKeyAccessListType.ALLOW_ALL_ACCESS)
  .setTimeRange({
    validFrom: Math.round(Date.now() / 1000),
    // valid for 1 hour
    validUntil: Math.round(Date.now() / 1000 + 60 * 60),
  });

Example: Permissions in plugin install data

ts
const result = await client.installSessionKeyPlugin({
  // 1st arg is the initial set of session keys
  // 2nd arg is the tags for the session keys
  // 3rd arg is the initial set of permissions
  args: [
    [await sessionKeySigner.getAddress()],
    [zeroHash],
    [keyPermissions.encode()],
  ],
});

Example: Initial permissions for a new key

ts
const result = await client.addSessionKey({
  key: "0x1234123412341234123412341234123412341234", // Session key address
  tag: keccak256(new TextEncoder().encode("session-key-tag")), // Session key tag
  permissions: keyPermissions.encode(), // Initial permissions
});

Exmaple: Updating a session key's permissions

This example updates a key's time range, but leaves other permissions to their current values.

ts
const result = await client.updateSessionKeyPermissions({
  key: "0x1234123412341234123412341234123412341234", // Session key address
  // add other permissions to the builder, if needed
  permissions: new SessionKeyPermissionsBuilder()
    .setTimeRange({
      validFrom: Math.round(Date.now() / 1000),
      // valid for 1 hour
      validUntil: Math.round(Date.now() / 1000 + 60 * 60),
    })
    .encode(),
});

Permissions Builder full reference

View the full set of supported permissions here
ts
import { encodeFunctionData, type Address, type Hex } from "viem";
import { SessionKeyPermissionsUpdatesAbi } from "./SessionKeyPermissionsUpdatesAbi.js";

export enum SessionKeyAccessListType {
  ALLOWLIST = 0,
  DENYLIST = 1,
  ALLOW_ALL_ACCESS = 2,
}

export type ContractAccessEntry = {
  // The contract address to add or remove.
  contractAddress: Address;
  // Whether the contract address should be on the list.
  isOnList: boolean;
  // Whether to check selectors for the contract address.
  checkSelectors: boolean;
};

export type ContractMethodEntry = {
  // The contract address to add or remove.
  contractAddress: Address;
  // The function selector to add or remove.
  methodSelector: Hex;
  // Whether the function selector should be on the list.
  isOnList: boolean;
};

export type TimeRange = {
  validFrom: number;
  validUntil: number;
};

export type NativeTokenLimit = {
  spendLimit: bigint;
  // The time interval over which the spend limit is enforced. If unset, there is no time
  /// interval by which the limit is refreshed.
  refreshInterval?: number;
};

export type Erc20TokenLimit = {
  tokenAddress: Address;
  spendLimit: bigint;
  // The time interval over which the spend limit is enforced. If unset, there is no time
  /// interval by which the limit is refreshed.
  refreshInterval?: number;
};

// uint256 spendLimit, uint48 refreshInterval
export type GasSpendLimit = {
  // The amount, in wei, of native tokens that a session key can spend on gas.
  // Note that this is not the same as the gas limit for a user operation, which is measured in units of gas.
  // This tracks gas units * gas price.
  spendLimit: bigint;
  // The time interval over which the spend limit is enforced. If unset, there is no time
  /// interval by which the limit is refreshed.
  refreshInterval?: number;
};

export class SessionKeyPermissionsBuilder {
  private _contractAccessControlType: SessionKeyAccessListType =
    SessionKeyAccessListType.ALLOWLIST;
  private _contractAddressAccessEntrys: ContractAccessEntry[] = [];
  private _contractMethodAccessEntrys: ContractMethodEntry[] = [];
  private _timeRange?: TimeRange;
  private _nativeTokenSpendLimit?: NativeTokenLimit;
  private _erc20TokenSpendLimits: Erc20TokenLimit[] = [];
  private _gasSpendLimit?: GasSpendLimit;
  private _requiredPaymaster?: Address;

  public setContractAccessControlType(aclType: SessionKeyAccessListType) {
    this._contractAccessControlType = aclType;
    return this;
  }

  public addContractAddressAccessEntry(entry: ContractAccessEntry) {
    this._contractAddressAccessEntrys.push(entry);
    return this;
  }

  public addContractFunctionAccessEntry(entry: ContractMethodEntry) {
    this._contractMethodAccessEntrys.push(entry);
    return this;
  }

  public setTimeRange(timeRange: TimeRange) {
    this._timeRange = timeRange;
    return this;
  }

  public setNativeTokenSpendLimit(limit: NativeTokenLimit) {
    this._nativeTokenSpendLimit = limit;
    return this;
  }

  public addErc20TokenSpendLimit(limit: Erc20TokenLimit) {
    this._erc20TokenSpendLimits.push(limit);
    return this;
  }

  public setGasSpendLimit(limit: GasSpendLimit) {
    this._gasSpendLimit = limit;
    return this;
  }

  public setRequiredPaymaster(paymaster: Address) {
    this._requiredPaymaster = paymaster;
    return this;
  }

  public encode(): Hex[] {
    return [
      encodeFunctionData({
        abi: SessionKeyPermissionsUpdatesAbi,
        functionName: "setAccessListType",
        args: [this._contractAccessControlType],
      }),
      ...this._contractAddressAccessEntrys.map((entry) =>
        encodeFunctionData({
          abi: SessionKeyPermissionsUpdatesAbi,
          functionName: "updateAccessListAddressEntry",
          args: [entry.contractAddress, entry.isOnList, entry.checkSelectors],
        })
      ),
      ...this._contractMethodAccessEntrys.map((entry) =>
        encodeFunctionData({
          abi: SessionKeyPermissionsUpdatesAbi,
          functionName: "updateAccessListFunctionEntry",
          args: [entry.contractAddress, entry.methodSelector, entry.isOnList],
        })
      ),
      this.encodeIfDefined(
        (timeRange) =>
          encodeFunctionData({
            abi: SessionKeyPermissionsUpdatesAbi,
            functionName: "updateTimeRange",
            args: [timeRange.validFrom, timeRange.validUntil],
          }),
        this._timeRange
      ),
      this.encodeIfDefined(
        (nativeSpendLimit) =>
          encodeFunctionData({
            abi: SessionKeyPermissionsUpdatesAbi,
            functionName: "setNativeTokenSpendLimit",
            args: [
              nativeSpendLimit.spendLimit,
              nativeSpendLimit.refreshInterval ?? 0,
            ],
          }),
        this._nativeTokenSpendLimit
      ),
      ...this._erc20TokenSpendLimits.map((erc20SpendLimit) =>
        encodeFunctionData({
          abi: SessionKeyPermissionsUpdatesAbi,
          functionName: "setERC20SpendLimit",
          args: [
            erc20SpendLimit.tokenAddress,
            erc20SpendLimit.spendLimit,
            erc20SpendLimit.refreshInterval ?? 0,
          ],
        })
      ),
      this.encodeIfDefined(
        (spendLimit) =>
          encodeFunctionData({
            abi: SessionKeyPermissionsUpdatesAbi,
            functionName: "setGasSpendLimit",
            args: [spendLimit.spendLimit, spendLimit.refreshInterval ?? 0],
          }),
        this._gasSpendLimit
      ),
      this.encodeIfDefined(
        (paymaster) =>
          encodeFunctionData({
            abi: SessionKeyPermissionsUpdatesAbi,
            functionName: "setRequiredPaymaster",
            args: [paymaster],
          }),
        this._requiredPaymaster
      ),
    ].filter((x) => x !== "0x");
  }

  private encodeIfDefined<T>(encode: (param: T) => Hex, param?: T): Hex {
    if (!param) return "0x";

    return encode(param);
  }
}

Reading Permissions

You may wish to view the current permissions of a given session key. This can be done using view functions defined by the Session Key Plugin.

Here's an example of viewing all permissions in TypeScript:

ts
const sessionKeyPluginView = SessionKeyPlugin.getContract(client).read;
const accountAddress = client.getAddress();
const sessionKeyAddress = await sessionKeySigner.getAddress();

const exampleTargetAddress = "0x4567456745674567456745674567456745674567";
const exampleTargetSelector = "0x78907890";
const exampleERC20Address = "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd";

// Using session key permissions view functions

// The key's current access control type. One of:
// - SessionKeyAccessListType.ALLOWLIST
// - SessionKeyAccessListType.DENYLIST
// - SessionKeyAccessListType.ALLOW_ALL_ACCESS
const accessControlType = await sessionKeyPluginView.getAccessControlType([
  accountAddress,
  sessionKeyAddress,
]);

// - Whether or not the address is on the access control list (either allowlist or denylist, depending on setting).
// - Whether or not the function selectors should be checked for the target address (checked according to the access control type).
const [isTargetAddressOnList, checkSelectors] =
  await sessionKeyPluginView.getAccessControlEntry([
    accountAddress,
    sessionKeyAddress,
    exampleTargetAddress,
  ]);

// Whether or not the selector is on the access control list
const isTargetSelectorOnList =
  await sessionKeyPluginView.isSelectorOnAccessControlList([
    accountAddress,
    sessionKeyAddress,
    exampleTargetAddress,
    exampleTargetSelector,
  ]);

// The start and end timestamp of a key.
// If either is zero, that means the value is unset.
const [validAfter, validUntil] = await sessionKeyPluginView.getKeyTimeRange([
  accountAddress,
  sessionKeyAddress,
]);

// The native token spending limit of a key. Details below
const nativeTokenSpendingLimit =
  await sessionKeyPluginView.getNativeTokenSpendLimitInfo([
    accountAddress,
    sessionKeyAddress,
  ]);

const {
  hasLimit: hasNativeTokenSpendLimit, // Whether or not a native token spending limit is enforced on the session key
  limit: nativeTokenSpendLimit, // The limit's maximum value. If a refresh interval is set, this is the max per interval.
  limitUsed: nativeTokenSpendLimitUsed, // How much of the limit is used. If a refresh interval is set, this is the amount used in the current interval.
  refreshInterval: nativeTokenRefreshInterval, // How often to reset the limit and start counting again. If zero, never refresh the limit.
  lastUsedTime: nativeTokenLastUsedTime, // The start of the latest interval, if using the refresh interval.
} = nativeTokenSpendingLimit;

// The spending limit for an ERC-20 token.
const erc20SpendingLimit = await sessionKeyPluginView.getERC20SpendLimitInfo([
  accountAddress,
  sessionKeyAddress,
  exampleERC20Address,
]);

const {
  hasLimit: hasErc20TokenSpendLimit, // Whether or not an ERC-20 token spending limit is enforced on the session key for this token address.
  limit: erc20TokenSpendLimit, // The limit's maximum value. If a refresh interval is set, this is the max per interval.
  limitUsed: erc20TokenSpendLimitUsed, // How much of the limit is used. If a refresh interval is set, this is the amount used in the current interval.
  refreshInterval: erc20TokenRefreshInterval, // How often to reset the limit and start counting again. If zero, never refresh the limit.
  lastUsedTime: erc20TokenLastUsedTime, // The start of the latest interval, if using the refresh interval.
} = erc20SpendingLimit;

// - The spending limit on gas for a given session key, measured in wei.
// - Whether or not the spending limit will reset in the next interval, if a refresh interval is set.
const [gasSpendingLimit, shouldReset] =
  await sessionKeyPluginView.getGasSpendLimit([
    accountAddress,
    sessionKeyAddress,
  ]);

const {
  hasLimit: hasGasSpendLimit, // Whether or not a gas spending limit is enforced on the session key
  limit: gasSpendLimit, // The gas limit's maximum spend amount, in wei. If a refresh interval is set, this is the max per interval.
  limitUsed: gasSpendLimitUsed, // How much of the limit is used. If a refresh interval is set, this is the amount used in the current interval.
  refreshInterval: gasRefreshInterval, // How often to reset the limit and start counting again. If zero, never refresh the limit.
  lastUsedTime: gasLastUsedTime, // The start of the latest interval, if using the refresh interval.
} = gasSpendingLimit;

// The paymaster address required for a given session key.
// If there is no required paymaster, this will return the zero address.
const requiredPaymaster = await sessionKeyPluginView.getRequiredPaymaster([
  accountAddress,
  sessionKeyAddress,
]);

Permission View Functions

The following view functions are declared by the session key plugin and used to read information about permissions. These are the functions used in the example above.

solidity
enum ContractAccessControlType {
    ALLOWLIST, // Allowlist is default
    DENYLIST,
    ALLOW_ALL_ACCESS // Disables contract access control, any address and selector are allowed.
}

// Struct returned by view functions to provide information about a session key's spend limit.
// Used for native token, ERC-20, and gas spend limits.
struct SpendLimitInfo {
    bool hasLimit;
    uint256 limit;
    uint256 limitUsed;
    uint48 refreshInterval;
    uint48 lastUsedTime;
}

/// @notice Get the access control type for a session key on an account.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @return The access control type for the session key on the account.
function getAccessControlType(address account, address sessionKey)
    external
    view
    returns (ContractAccessControlType);

/// @notice Get an access control entry for a session key on an account.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @param targetAddress The target address to check.
/// @return isOnList Whether the target address is on the list (either allowlist or blocklist depending on the
/// access control type).
/// @return checkSelectors Whether the target address should be checked for selectors during permissions
/// enforcement.
function getAccessControlEntry(address account, address sessionKey, address targetAddress)
    external
    view
    returns (bool isOnList, bool checkSelectors);

/// @notice Get whether a selector is on the access control list for a session key on an account.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @param targetAddress The target address to check.
/// @param selector The selector to check.
/// @return isOnList Whether the selector is on the list (either allowlist or blocklist depending on the
/// access control type).
function isSelectorOnAccessControlList(
    address account,
    address sessionKey,
    address targetAddress,
    bytes4 selector
) external view returns (bool isOnList);

/// @notice Get the active time range for a session key on an account.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @return validAfter The time after which the session key is valid.
/// @return validUntil The time until which the session key is valid.
function getKeyTimeRange(address account, address sessionKey)
    external
    view
    returns (uint48 validAfter, uint48 validUntil);

/// @notice Get the native token spend limit for a session key on an account.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @return A struct with fields describing the state of native token spending limits on this session key.
function getNativeTokenSpendLimitInfo(address account, address sessionKey)
    external
    view
    returns (SpendLimitInfo memory);

/// @notice Get the gas spend limit for a session key on an account.
/// Note that this spend limit is measured in wei, not units of gas.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @return info A struct with fields describing the state of gas spending limits on this session key.
/// @return shouldReset Whether this session key must be reset by calling `resetSessionKeyGasLimitTimestamp`
/// before it can be used.
function getGasSpendLimit(address account, address sessionKey)
    external
    view
    returns (SpendLimitInfo memory info, bool shouldReset);

/// @notice Get the ERC20 spend limit for a session key on an account.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @param token The token to check.
/// @return A struct with fields describing the state of ERC20 spending limits on this session key.
function getERC20SpendLimitInfo(address account, address sessionKey, address token)
    external
    view
    returns (SpendLimitInfo memory);

/// @notice Get the required paymaster address for a session key on an account, if any.
/// @param account The account to check.
/// @param sessionKey The session key to check.
/// @return The required paymaster address for this session key on this account, or the zero address if the
/// rule is disabled.
function getRequiredPaymaster(address account, address sessionKey) external view returns (address);