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:
Permission | Default Value |
---|---|
Access control list | Type: allowlist The list starts empty. When the allowlist is empty, all calls will be denied. |
Time range | Unlimited |
Native token spend limit | 0 This means all calls spending the native token will be denied, unless the limit is updated or removed. |
ERC-20 spend limit | Unset. 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 limits | Unset. 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
// 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
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
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.
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
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;
};
/**
* A builder for creating the hex-encoded data for updating session key permissions.
*/
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;
/**
* Sets the access control type for the contract and returns the current instance for method chaining.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.setContractAccessControlType(SessionKeyAccessListType.ALLOWLIST);
* ```
*
* @param {SessionKeyAccessListType} aclType The access control type for the session key
* @returns {SessionKeyPermissionsBuilder} The current instance for method chaining
*/
public setContractAccessControlType(aclType: SessionKeyAccessListType) {
this._contractAccessControlType = aclType;
return this;
}
/**
* Adds a contract access entry to the internal list of contract address access entries.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.addContractAddressAccessEntry({
* contractAddress: "0x1234",
* isOnList: true,
* checkSelectors: true,
* });
* ```
*
* @param {ContractAccessEntry} entry the contract access entry to be added
* @returns {SessionKeyPermissionsBuilder} the instance of the current class for chaining
*/
public addContractAddressAccessEntry(entry: ContractAccessEntry) {
this._contractAddressAccessEntrys.push(entry);
return this;
}
/**
* Adds a contract method entry to the `_contractMethodAccessEntrys` array.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.addContractAddressAccessEntry({
* contractAddress: "0x1234",
* methodSelector: "0x45678",
* isOnList: true,
* });
* ```
*
* @param {ContractMethodEntry} entry The contract method entry to be added
* @returns {SessionKeyPermissionsBuilder} The instance of the class for method chaining
*/
public addContractFunctionAccessEntry(entry: ContractMethodEntry) {
this._contractMethodAccessEntrys.push(entry);
return this;
}
/**
* Sets the time range for an object and returns the object itself for chaining.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.setTimeRange({
* validFrom: Date.now(),
* validUntil: Date.now() + (15 * 60 * 1000),
* });
* ```
*
* @param {TimeRange} timeRange The time range to be set
* @returns {SessionKeyPermissionsBuilder} The current object for method chaining
*/
public setTimeRange(timeRange: TimeRange) {
this._timeRange = timeRange;
return this;
}
/**
* Sets the native token spend limit and returns the instance for chaining.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.setNativeTokenSpendLimit({
* spendLimit: 1000000000000000000n,
* refreshInterval: 3600,
* });
* ```
*
* @param {NativeTokenLimit} limit The limit to set for native token spending
* @returns {SessionKeyPermissionsBuilder} The instance for chaining
*/
public setNativeTokenSpendLimit(limit: NativeTokenLimit) {
this._nativeTokenSpendLimit = limit;
return this;
}
/**
* Adds an ERC20 token spend limit to the list of limits and returns the updated object.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.addErc20TokenSpendLimit({
* tokenAddress: "0x1234",
* spendLimit: 1000000000000000000n,
* refreshInterval: 3600,
* });
* ```
*
* @param {Erc20TokenLimit} limit The ERC20 token spend limit to be added
* @returns {object} The updated object with the new ERC20 token spend limit
*/
public addErc20TokenSpendLimit(limit: Erc20TokenLimit) {
this._erc20TokenSpendLimits.push(limit);
return this;
}
/**
* Sets the gas spend limit and returns the current instance for method chaining.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.setGasSpendLimit({
* spendLimit: 1000000000000000000n,
* refreshInterval: 3600,
* });
* ```
*
* @param {GasSpendLimit} limit - The gas spend limit to be set
* @returns {SessionKeyPermissionsBuilder} The current instance for chaining
*/ public setGasSpendLimit(limit: GasSpendLimit) {
this._gasSpendLimit = limit;
return this;
}
/**
* Sets the required paymaster address.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.setRequiredPaymaster("0x1234");
* ```
*
* @param {Address} paymaster the address of the paymaster to be set
* @returns {SessionKeyPermissionsBuilder} the current instance for method chaining
*/
public setRequiredPaymaster(paymaster: Address) {
this._requiredPaymaster = paymaster;
return this;
}
/**
* Encodes various function calls into an array of hexadecimal strings based on the provided permissions and limits.
*
* @example
* ```ts
* import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
*
* const builder = new SessionKeyPermissionsBuilder();
* builder.setRequiredPaymaster("0x1234");
* const encoded = builder.encode();
* ```
*
* @returns {Hex[]} An array of encoded hexadecimal strings representing the function calls for setting access control, permissions, and limits.
*/
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:
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.
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);