Sessions

Get familiar with ZKsync SSO sessions.

A session is a temporary key that grants limited permissions to an account, governed by a configurable policy set by the developer based on the needs of their application. These sessions require explicit approval from the account owners and can be revoked or removed at any time after creation. A session key holder can execute a restricted set of transactions from the account without needing the account owner’s signature for each action.

When an application requests a session, it specifies the desired policies that limit what that session key can do, which the account owner can approve. This ensures that even if the session key is compromised, the potential loss is capped.

Sessions allow only those transactions that are explicitly allowed by the session configuration.

Configuring a Session

Sessions can configured in the zksyncSsoConnector options. Below is an example of a session that has a configured fee limit and transfer limit.

const zksyncConnector = zksyncSsoConnector({
  session: {
    feeLimit: parseEther("0.1"),
    transfers: [
      {
        to: transferRecipient,
        valueLimit: parseEther("0.1"),
      },
    ],
  },
});

SessionPreferences Interface

There are four types of customizations available for sessions:

  • The length of time the session is valid
  • The amount of ETH that can be spent in transaction fees
  • What types of transfers are allows
  • What types of contract calls are allowed

The full interface is shown below:

/**
  * @member expiresAt - Expiry time for the session (e.g. "8 hours"). Defaults are set by the Auth Server (currently 1 day).
  * @member feeLimit - Maximum amount of ETH that can be spent on gas fees
  * @member transfers - ETH transfer transaction policies
  * @member contractCalls - Contract call transaction policies
  */
type SessionPreferences = {
  expiresAt?: string | bigint | Date;
  feeLimit?: Limit,
  transfers?: TransferPolicy[];
  contractCalls?: CallPolicy[];
}

Limits

Limits can be applied in two ways:

  • Value Policies: Limits the amount of ETH that can be transferred.
  • Function Argument Constraints: Limits the amount that can be passed to a contract function (argument is parsed as uint256).

Limit Interface

/**
 * @member limitType - Type of limit to enforce
 * @member limit - The limit is exceeded if the tracked value is greater than set value over the provided period (if applicable)
 * @member period - Resets limit every `period` seconds. The block.timestamp divisor for the limit to be enforced (eg: "60 minutes", "24 hours")
 */
type Limit =
  bigint | // Resolves to "lifetime" limit
  { limit: bigint; period?: string | bigint } | // If period is set resolves to "allowance" limit, otherwise "lifetime"
  { limitType: "lifetime"; limit: bigint } |
  { limitType: "unlimited" } |
  { limitType: "allowance"; limit: bigint; period: string | bigint };

LimitType Interface

There are three types of limits:

  • Unlimited: No limit is enforced.
  • Lifetime: Limit is enforced over the lifetime of the session.
  • Allowance: Limit is enforced over a specific period of time and is reset every period seconds (based on the block.timestamp).
type LimitType =
  "Unlimited" |
  "Lifetime" |
  "Allowance";

Transfer Policy

Transfer policies can be used to specify:

  • What addresses transfers can be sent to
  • How much ETH can be transferred in total
  • How many transfers are allowed in a session

TransferPolicy Interface

/**
 * @member to - Allowed recipient address
 * @member maxValuePerUse - Per transaction ETH limit
 * @member valueLimit - Cumulative ETH limit
 */
type TransferPolicy = {
  to: Address;
  maxValuePerUse?: bigint;
  valueLimit?: Limit;
};

Contract Call Policy

Contract call policies can be used to specify:

  • What contracts users can interact with
  • What functions within a contract can be called
  • What arguments are used in a contract call
  • How much ETH can be sent in total
  • How many calls are allowed in a session

CallPolicy Interface

/**
 * @member address - Contract address
 * @member abi - Contract ABI
 * @member functionName - Function name to call
 * @member maxValuePerUse - Per transaction limit for the ETH value included in the transaction
 * @member valueLimit - Cumulative limit for the ETH value of the transaction
 * @member constraints - List of constraints to apply to the function arguments; unconstrained if not set
 */
type CallPolicy = {
  address: Address;
  abi: Abi;
  functionName: string;
  maxValuePerUse?: bigint;
  valueLimit?: PartialLimit;
  constraints?: Constraint[];
};

Constraints

If constraints are not set, the function allows any argument values.

Constraints enforce limits on the arguments of a contract call. They can be stacked and are checked in order. Any constraint failure will cause the transaction validation to fail.

Constraint Interface

/**
 * @member index - Index of the argument to check
 * @member value - Value of the argument to compare against
 * @member condition - The kind of check to perform (None, =, >, <, >=, <=, !=)
 * @member limit - Limit to enforce on the parsed value
 */
type Constraint = {
  index: number;
  value?: unknown;
  condition?: ConstraintCondition;
  limit?: Limit;
};
type ConstraintCondition =
  "Unconstrained" | // No constraint
  "Equal" |         // =
  "Greater" |       // >
  "Less" |          // <
  "GreaterEqual" |  // >=
  "LessEqual" |     // <=
  "NotEqual";       // !=

Paymasters

General paymasters are supported with sessions. Approval-based paymasters are not yet supported.

const transactionHash = await writeContract(wagmiConfig, {
    address: NFT_CONTRACT_ADDRESS,
    abi: NFT_ABI,
    functionName: "mint",
    args: [mintingForAddress],
    paymaster: GENERAL_PAYMASTER_ADDRESS,
    paymasterInput: getGeneralPaymasterInput({ innerInput: "0x" }),
  });

You can find a full example of a paymaster integrated with SSO in the nft-quest example.


Made with ❤️ by the ZKsync Community