Smart ContractsOther AccountsModular Account V1Session keys

Session Key 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

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

Example: Permissions in plugin install data

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

Example: Initial permissions for a new key

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

Exmaple: Updating a session key’s permissions

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

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

Permissions Builder full reference

1import { encodeFunctionData, type Address, type Hex } from "viem";
2import { SessionKeyPermissionsUpdatesAbi } from "./SessionKeyPermissionsUpdatesAbi.js";
3
4export enum SessionKeyAccessListType {
5 ALLOWLIST = 0,
6 DENYLIST = 1,
7 ALLOW_ALL_ACCESS = 2,
8}
9
10export type ContractAccessEntry = {
11 // The contract address to add or remove.
12 contractAddress: Address;
13 // Whether the contract address should be on the list.
14 isOnList: boolean;
15 // Whether to check selectors for the contract address.
16 checkSelectors: boolean;
17};
18
19export type ContractMethodEntry = {
20 // The contract address to add or remove.
21 contractAddress: Address;
22 // The function selector to add or remove.
23 methodSelector: Hex;
24 // Whether the function selector should be on the list.
25 isOnList: boolean;
26};
27
28export type TimeRange = {
29 validFrom: number;
30 validUntil: number;
31};
32
33export type NativeTokenLimit = {
34 spendLimit: bigint;
35 // The time interval over which the spend limit is enforced. If unset, there is no time
36 /// interval by which the limit is refreshed.
37 refreshInterval?: number;
38};
39
40export type Erc20TokenLimit = {
41 tokenAddress: Address;
42 spendLimit: bigint;
43 // The time interval over which the spend limit is enforced. If unset, there is no time
44 /// interval by which the limit is refreshed.
45 refreshInterval?: number;
46};
47
48// uint256 spendLimit, uint48 refreshInterval
49export type GasSpendLimit = {
50 // The amount, in wei, of native tokens that a session key can spend on gas.
51 // Note that this is not the same as the gas limit for a user operation, which is measured in units of gas.
52 // This tracks gas units * gas price.
53 spendLimit: bigint;
54 // The time interval over which the spend limit is enforced. If unset, there is no time
55 /// interval by which the limit is refreshed.
56 refreshInterval?: number;
57};
58
59/**
60 * A builder for creating the hex-encoded data for updating session key permissions.
61 */
62export class SessionKeyPermissionsBuilder {
63 private _contractAccessControlType: SessionKeyAccessListType =
64 SessionKeyAccessListType.ALLOWLIST;
65 private _contractAddressAccessEntrys: ContractAccessEntry[] = [];
66 private _contractMethodAccessEntrys: ContractMethodEntry[] = [];
67 private _timeRange?: TimeRange;
68 private _nativeTokenSpendLimit?: NativeTokenLimit;
69 private _erc20TokenSpendLimits: Erc20TokenLimit[] = [];
70 private _gasSpendLimit?: GasSpendLimit;
71 private _requiredPaymaster?: Address;
72
73 /**
74 * Sets the access control type for the contract and returns the current instance for method chaining.
75 *
76 * @example
77 * ```ts
78 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
79 *
80 * const builder = new SessionKeyPermissionsBuilder();
81 * builder.setContractAccessControlType(SessionKeyAccessListType.ALLOWLIST);
82 * ```
83 *
84 * @param {SessionKeyAccessListType} aclType The access control type for the session key
85 * @returns {SessionKeyPermissionsBuilder} The current instance for method chaining
86 */
87 public setContractAccessControlType(aclType: SessionKeyAccessListType) {
88 this._contractAccessControlType = aclType;
89 return this;
90 }
91
92 /**
93 * Adds a contract access entry to the internal list of contract address access entries.
94 *
95 * @example
96 * ```ts
97 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
98 *
99 * const builder = new SessionKeyPermissionsBuilder();
100 * builder.addContractAddressAccessEntry({
101 * contractAddress: "0x1234",
102 * isOnList: true,
103 * checkSelectors: true,
104 * });
105 * ```
106 *
107 * @param {ContractAccessEntry} entry the contract access entry to be added
108 * @returns {SessionKeyPermissionsBuilder} the instance of the current class for chaining
109 */
110 public addContractAddressAccessEntry(entry: ContractAccessEntry) {
111 this._contractAddressAccessEntrys.push(entry);
112 return this;
113 }
114
115 /**
116 * Adds a contract method entry to the `_contractMethodAccessEntrys` array.
117 *
118 * @example
119 * ```ts
120 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
121 *
122 * const builder = new SessionKeyPermissionsBuilder();
123 * builder.addContractAddressAccessEntry({
124 * contractAddress: "0x1234",
125 * methodSelector: "0x45678",
126 * isOnList: true,
127 * });
128 * ```
129 *
130 * @param {ContractMethodEntry} entry The contract method entry to be added
131 * @returns {SessionKeyPermissionsBuilder} The instance of the class for method chaining
132 */
133 public addContractFunctionAccessEntry(entry: ContractMethodEntry) {
134 this._contractMethodAccessEntrys.push(entry);
135 return this;
136 }
137
138 /**
139 * Sets the time range for an object and returns the object itself for chaining.
140 *
141 * @example
142 * ```ts
143 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
144 *
145 * const builder = new SessionKeyPermissionsBuilder();
146 * builder.setTimeRange({
147 * validFrom: Date.now(),
148 * validUntil: Date.now() + (15 * 60 * 1000),
149 * });
150 * ```
151 *
152 * @param {TimeRange} timeRange The time range to be set
153 * @returns {SessionKeyPermissionsBuilder} The current object for method chaining
154 */
155 public setTimeRange(timeRange: TimeRange) {
156 this._timeRange = timeRange;
157 return this;
158 }
159
160 /**
161 * Sets the native token spend limit and returns the instance for chaining.
162 *
163 * @example
164 * ```ts
165 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
166 *
167 * const builder = new SessionKeyPermissionsBuilder();
168 * builder.setNativeTokenSpendLimit({
169 * spendLimit: 1000000000000000000n,
170 * refreshInterval: 3600,
171 * });
172 * ```
173 *
174 * @param {NativeTokenLimit} limit The limit to set for native token spending
175 * @returns {SessionKeyPermissionsBuilder} The instance for chaining
176 */
177 public setNativeTokenSpendLimit(limit: NativeTokenLimit) {
178 this._nativeTokenSpendLimit = limit;
179 return this;
180 }
181
182 /**
183 * Adds an ERC20 token spend limit to the list of limits and returns the updated object.
184 *
185 * @example
186 * ```ts
187 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
188 *
189 * const builder = new SessionKeyPermissionsBuilder();
190 * builder.addErc20TokenSpendLimit({
191 * tokenAddress: "0x1234",
192 * spendLimit: 1000000000000000000n,
193 * refreshInterval: 3600,
194 * });
195 * ```
196 *
197 * @param {Erc20TokenLimit} limit The ERC20 token spend limit to be added
198 * @returns {object} The updated object with the new ERC20 token spend limit
199 */
200 public addErc20TokenSpendLimit(limit: Erc20TokenLimit) {
201 this._erc20TokenSpendLimits.push(limit);
202 return this;
203 }
204
205 /**
206 * Sets the gas spend limit and returns the current instance for method chaining.
207 *
208 * @example
209 * ```ts
210 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
211 *
212 * const builder = new SessionKeyPermissionsBuilder();
213 * builder.setGasSpendLimit({
214 * spendLimit: 1000000000000000000n,
215 * refreshInterval: 3600,
216 * });
217 * ```
218 *
219 * @param {GasSpendLimit} limit - The gas spend limit to be set
220 * @returns {SessionKeyPermissionsBuilder} The current instance for chaining
221 */ public setGasSpendLimit(limit: GasSpendLimit) {
222 this._gasSpendLimit = limit;
223 return this;
224 }
225
226 /**
227 * Sets the required paymaster address.
228 *
229 * @example
230 * ```ts
231 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
232 *
233 * const builder = new SessionKeyPermissionsBuilder();
234 * builder.setRequiredPaymaster("0x1234");
235 * ```
236 *
237 * @param {Address} paymaster the address of the paymaster to be set
238 * @returns {SessionKeyPermissionsBuilder} the current instance for method chaining
239 */
240 public setRequiredPaymaster(paymaster: Address) {
241 this._requiredPaymaster = paymaster;
242 return this;
243 }
244
245 /**
246 * Encodes various function calls into an array of hexadecimal strings based on the provided permissions and limits.
247 *
248 * @example
249 * ```ts
250 * import { SessionKeyPermissionsBuilder } from "@account-kit/smart-contracts";
251 *
252 * const builder = new SessionKeyPermissionsBuilder();
253 * builder.setRequiredPaymaster("0x1234");
254 * const encoded = builder.encode();
255 * ```
256 *
257 * @returns {Hex[]} An array of encoded hexadecimal strings representing the function calls for setting access control, permissions, and limits.
258 */
259 public encode(): Hex[] {
260 return [
261 encodeFunctionData({
262 abi: SessionKeyPermissionsUpdatesAbi,
263 functionName: "setAccessListType",
264 args: [this._contractAccessControlType],
265 }),
266 ...this._contractAddressAccessEntrys.map((entry) =>
267 encodeFunctionData({
268 abi: SessionKeyPermissionsUpdatesAbi,
269 functionName: "updateAccessListAddressEntry",
270 args: [entry.contractAddress, entry.isOnList, entry.checkSelectors],
271 })
272 ),
273 ...this._contractMethodAccessEntrys.map((entry) =>
274 encodeFunctionData({
275 abi: SessionKeyPermissionsUpdatesAbi,
276 functionName: "updateAccessListFunctionEntry",
277 args: [entry.contractAddress, entry.methodSelector, entry.isOnList],
278 })
279 ),
280 this.encodeIfDefined(
281 (timeRange) =>
282 encodeFunctionData({
283 abi: SessionKeyPermissionsUpdatesAbi,
284 functionName: "updateTimeRange",
285 args: [timeRange.validFrom, timeRange.validUntil],
286 }),
287 this._timeRange
288 ),
289 this.encodeIfDefined(
290 (nativeSpendLimit) =>
291 encodeFunctionData({
292 abi: SessionKeyPermissionsUpdatesAbi,
293 functionName: "setNativeTokenSpendLimit",
294 args: [
295 nativeSpendLimit.spendLimit,
296 nativeSpendLimit.refreshInterval ?? 0,
297 ],
298 }),
299 this._nativeTokenSpendLimit
300 ),
301 ...this._erc20TokenSpendLimits.map((erc20SpendLimit) =>
302 encodeFunctionData({
303 abi: SessionKeyPermissionsUpdatesAbi,
304 functionName: "setERC20SpendLimit",
305 args: [
306 erc20SpendLimit.tokenAddress,
307 erc20SpendLimit.spendLimit,
308 erc20SpendLimit.refreshInterval ?? 0,
309 ],
310 })
311 ),
312 this.encodeIfDefined(
313 (spendLimit) =>
314 encodeFunctionData({
315 abi: SessionKeyPermissionsUpdatesAbi,
316 functionName: "setGasSpendLimit",
317 args: [spendLimit.spendLimit, spendLimit.refreshInterval ?? 0],
318 }),
319 this._gasSpendLimit
320 ),
321 this.encodeIfDefined(
322 (paymaster) =>
323 encodeFunctionData({
324 abi: SessionKeyPermissionsUpdatesAbi,
325 functionName: "setRequiredPaymaster",
326 args: [paymaster],
327 }),
328 this._requiredPaymaster
329 ),
330 ].filter((x) => x !== "0x");
331 }
332
333 private encodeIfDefined<T>(encode: (param: T) => Hex, param?: T): Hex {
334 if (!param) return "0x";
335
336 return encode(param);
337 }
338}

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:

1const sessionKeyPluginView = SessionKeyPlugin.getContract(client).read;
2const accountAddress = client.getAddress();
3const sessionKeyAddress = await sessionKeySigner.getAddress();
4
5const exampleTargetAddress = "0x4567456745674567456745674567456745674567";
6const exampleTargetSelector = "0x78907890";
7const exampleERC20Address = "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd";
8
9// Using session key permissions view functions
10
11// The key's current access control type. One of:
12// - SessionKeyAccessListType.ALLOWLIST
13// - SessionKeyAccessListType.DENYLIST
14// - SessionKeyAccessListType.ALLOW_ALL_ACCESS
15const accessControlType = await sessionKeyPluginView.getAccessControlType([
16 accountAddress,
17 sessionKeyAddress,
18]);
19
20// - Whether or not the address is on the access control list (either allowlist or denylist, depending on setting).
21// - Whether or not the function selectors should be checked for the target address (checked according to the access control type).
22const [isTargetAddressOnList, checkSelectors] =
23 await sessionKeyPluginView.getAccessControlEntry([
24 accountAddress,
25 sessionKeyAddress,
26 exampleTargetAddress,
27 ]);
28
29// Whether or not the selector is on the access control list
30const isTargetSelectorOnList =
31 await sessionKeyPluginView.isSelectorOnAccessControlList([
32 accountAddress,
33 sessionKeyAddress,
34 exampleTargetAddress,
35 exampleTargetSelector,
36 ]);
37
38// The start and end timestamp of a key.
39// If either is zero, that means the value is unset.
40const [validAfter, validUntil] = await sessionKeyPluginView.getKeyTimeRange([
41 accountAddress,
42 sessionKeyAddress,
43]);
44
45// The native token spending limit of a key. Details below
46const nativeTokenSpendingLimit =
47 await sessionKeyPluginView.getNativeTokenSpendLimitInfo([
48 accountAddress,
49 sessionKeyAddress,
50 ]);
51
52const {
53 hasLimit: hasNativeTokenSpendLimit, // Whether or not a native token spending limit is enforced on the session key
54 limit: nativeTokenSpendLimit, // The limit's maximum value. If a refresh interval is set, this is the max per interval.
55 limitUsed: nativeTokenSpendLimitUsed, // How much of the limit is used. If a refresh interval is set, this is the amount used in the current interval.
56 refreshInterval: nativeTokenRefreshInterval, // How often to reset the limit and start counting again. If zero, never refresh the limit.
57 lastUsedTime: nativeTokenLastUsedTime, // The start of the latest interval, if using the refresh interval.
58} = nativeTokenSpendingLimit;
59
60// The spending limit for an ERC-20 token.
61const erc20SpendingLimit = await sessionKeyPluginView.getERC20SpendLimitInfo([
62 accountAddress,
63 sessionKeyAddress,
64 exampleERC20Address,
65]);
66
67const {
68 hasLimit: hasErc20TokenSpendLimit, // Whether or not an ERC-20 token spending limit is enforced on the session key for this token address.
69 limit: erc20TokenSpendLimit, // The limit's maximum value. If a refresh interval is set, this is the max per interval.
70 limitUsed: erc20TokenSpendLimitUsed, // How much of the limit is used. If a refresh interval is set, this is the amount used in the current interval.
71 refreshInterval: erc20TokenRefreshInterval, // How often to reset the limit and start counting again. If zero, never refresh the limit.
72 lastUsedTime: erc20TokenLastUsedTime, // The start of the latest interval, if using the refresh interval.
73} = erc20SpendingLimit;
74
75// - The spending limit on gas for a given session key, measured in wei.
76// - Whether or not the spending limit will reset in the next interval, if a refresh interval is set.
77const [gasSpendingLimit, shouldReset] =
78 await sessionKeyPluginView.getGasSpendLimit([
79 accountAddress,
80 sessionKeyAddress,
81 ]);
82
83const {
84 hasLimit: hasGasSpendLimit, // Whether or not a gas spending limit is enforced on the session key
85 limit: gasSpendLimit, // The gas limit's maximum spend amount, in wei. If a refresh interval is set, this is the max per interval.
86 limitUsed: gasSpendLimitUsed, // How much of the limit is used. If a refresh interval is set, this is the amount used in the current interval.
87 refreshInterval: gasRefreshInterval, // How often to reset the limit and start counting again. If zero, never refresh the limit.
88 lastUsedTime: gasLastUsedTime, // The start of the latest interval, if using the refresh interval.
89} = gasSpendingLimit;
90
91// The paymaster address required for a given session key.
92// If there is no required paymaster, this will return the zero address.
93const requiredPaymaster = await sessionKeyPluginView.getRequiredPaymaster([
94 accountAddress,
95 sessionKeyAddress,
96]);

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.

1enum ContractAccessControlType {
2 ALLOWLIST, // Allowlist is default
3 DENYLIST,
4 ALLOW_ALL_ACCESS // Disables contract access control, any address and selector are allowed.
5}
6
7// Struct returned by view functions to provide information about a session key's spend limit.
8// Used for native token, ERC-20, and gas spend limits.
9struct SpendLimitInfo {
10 bool hasLimit;
11 uint256 limit;
12 uint256 limitUsed;
13 uint48 refreshInterval;
14 uint48 lastUsedTime;
15}
16
17/// @notice Get the access control type for a session key on an account.
18/// @param account The account to check.
19/// @param sessionKey The session key to check.
20/// @return The access control type for the session key on the account.
21function getAccessControlType(address account, address sessionKey)
22 external
23 view
24 returns (ContractAccessControlType);
25
26/// @notice Get an access control entry for a session key on an account.
27/// @param account The account to check.
28/// @param sessionKey The session key to check.
29/// @param targetAddress The target address to check.
30/// @return isOnList Whether the target address is on the list (either allowlist or blocklist depending on the
31/// access control type).
32/// @return checkSelectors Whether the target address should be checked for selectors during permissions
33/// enforcement.
34function getAccessControlEntry(address account, address sessionKey, address targetAddress)
35 external
36 view
37 returns (bool isOnList, bool checkSelectors);
38
39/// @notice Get whether a selector is on the access control list for a session key on an account.
40/// @param account The account to check.
41/// @param sessionKey The session key to check.
42/// @param targetAddress The target address to check.
43/// @param selector The selector to check.
44/// @return isOnList Whether the selector is on the list (either allowlist or blocklist depending on the
45/// access control type).
46function isSelectorOnAccessControlList(
47 address account,
48 address sessionKey,
49 address targetAddress,
50 bytes4 selector
51) external view returns (bool isOnList);
52
53/// @notice Get the active time range for a session key on an account.
54/// @param account The account to check.
55/// @param sessionKey The session key to check.
56/// @return validAfter The time after which the session key is valid.
57/// @return validUntil The time until which the session key is valid.
58function getKeyTimeRange(address account, address sessionKey)
59 external
60 view
61 returns (uint48 validAfter, uint48 validUntil);
62
63/// @notice Get the native token spend limit for a session key on an account.
64/// @param account The account to check.
65/// @param sessionKey The session key to check.
66/// @return A struct with fields describing the state of native token spending limits on this session key.
67function getNativeTokenSpendLimitInfo(address account, address sessionKey)
68 external
69 view
70 returns (SpendLimitInfo memory);
71
72/// @notice Get the gas spend limit for a session key on an account.
73/// Note that this spend limit is measured in wei, not units of gas.
74/// @param account The account to check.
75/// @param sessionKey The session key to check.
76/// @return info A struct with fields describing the state of gas spending limits on this session key.
77/// @return shouldReset Whether this session key must be reset by calling `resetSessionKeyGasLimitTimestamp`
78/// before it can be used.
79function getGasSpendLimit(address account, address sessionKey)
80 external
81 view
82 returns (SpendLimitInfo memory info, bool shouldReset);
83
84/// @notice Get the ERC20 spend limit for a session key on an account.
85/// @param account The account to check.
86/// @param sessionKey The session key to check.
87/// @param token The token to check.
88/// @return A struct with fields describing the state of ERC20 spending limits on this session key.
89function getERC20SpendLimitInfo(address account, address sessionKey, address token)
90 external
91 view
92 returns (SpendLimitInfo memory);
93
94/// @notice Get the required paymaster address for a session key on an account, if any.
95/// @param account The account to check.
96/// @param sessionKey The session key to check.
97/// @return The required paymaster address for this session key on this account, or the zero address if the
98/// rule is disabled.
99function getRequiredPaymaster(address account, address sessionKey) external view returns (address);