Session Keys
What are session keys?
Section titled “What are session keys?”Session keys are ephemeral signing keys that can perform a limited set of operations on behalf of a root wallet. They are registered on-chain via the SessionKeyRegistry contract, which stores permission grants as time-limited authorizations.
This solves a common problem in dApps: without session keys, every storage operation (creating a dataset, uploading pieces, scheduling deletions) requires the user to approve a wallet popup. With session keys, the root wallet authorizes a temporary key once, and that key handles subsequent signing silently until it expires.
Key concepts
Section titled “Key concepts”- Root wallet - The user’s primary wallet (e.g., MetaMask). Owns the identity, funds, and datasets. Used to authorize session keys via
login(). - Session key - An ephemeral key pair that signs operations on behalf of the root wallet. Has no funds or on-chain identity of its own.
- Permissions - Each authorization grants specific operation types until an expiry timestamp. Permissions are identified by
bytes32hashes (by convention, EIP-712 type hashes). - Expiry - Authorizations are time-limited. The SDK defaults to 1 hour; the contract stores whatever expiry is provided.
Permissions
Section titled “Permissions”The SessionKeyRegistry stores arbitrary bytes32 hashes as permissions and is agnostic to what they represent. By convention, the SDK uses EIP-712 type hashes to identify operations:
| Constant | Operation |
|---|---|
CreateDataSetPermission | Create new datasets |
AddPiecesPermission | Upload pieces to datasets |
SchedulePieceRemovalsPermission | Schedule piece deletions |
DeleteDataSetPermission | Delete datasets |
These are convenience constants for FWSS operations. The Permission type also accepts any Hex value, allowing registration of custom permission hashes for non-FWSS operations (e.g., authenticated Curio HTTP endpoints).
Quick start
Section titled “Quick start”A complete session key lifecycle from creation through to use:
import * as SessionKey from '@filoz/synapse-core/session-key'import { calibration } from '@filoz/synapse-core/chains'import { createWalletClient, http, type Hex } from 'viem'import { privateKeyToAccount } from 'viem/accounts'
// The root wallet (the user's primary wallet)const rootAccount = privateKeyToAccount('0x<root-private-key>' as Hex)const rootClient = createWalletClient({ account: rootAccount, chain: calibration, transport: http('https://api.calibration.node.glif.io/rpc/v1'),})
// Create an ephemeral session keyconst sessionKey = SessionKey.fromSecp256k1({ privateKey: '0x<session-private-key>' as Hex, root: rootAccount, // Account or Address chain: calibration,})
// Authorize the session key on-chain (root wallet signs this tx)const { event } = await SessionKey.loginSync(rootClient, { address: sessionKey.address, onHash(hash) { console.log('Tx submitted:', hash) },})
// Sync expirations from the chain so hasPermission() works locallyawait sessionKey.syncExpirations()
// Now use sessionKey.client in place of rootClient for SDK operations.// sessionKey.client signs with the session key; sessionKey.rootAddress// identifies the root wallet as the payer/identity.Detailed usage
Section titled “Detailed usage”Create a session key
Section titled “Create a session key”fromSecp256k1() creates a session key from a secp256k1 private key. It returns a SessionKey<'Secp256k1'> instance.
import * as SessionKey from '@filoz/synapse-core/session-key'import { calibration } from '@filoz/synapse-core/chains'import type { Hex } from 'viem'
const sessionKey = SessionKey.fromSecp256k1({ privateKey: '0x...' as Hex, // secp256k1 private key for the session key root: rootAccount, // Account or Address of the authorizing wallet chain: calibration, // chain definition (calibration or mainnet) // transport: http(customRpc), // optional, defaults to http() // expirations: { ... }, // optional, pre-populate known expirations})The session key is inert until authorized. It holds a viem Client internally (sessionKey.client) that uses the session key for signing and carries sessionKey.rootAddress as the identity.
Authorize the session key (login)
Section titled “Authorize the session key (login)”The root wallet authorizes the session key on-chain. login() and loginSync() both require a viem WalletClient (a Client with an Account):
// Fire-and-forget (returns tx hash: Hex)const hash = await SessionKey.login(rootClient, { address: sessionKey.address,})
// Or wait for confirmation (returns { receipt, event })const { receipt, event } = await SessionKey.loginSync(rootClient, { address: sessionKey.address, expiresAt: BigInt(Math.floor(Date.now() / 1000) + 7200), // 2 hours onHash(hash) { console.log('Tx submitted:', hash) },})// event is the AuthorizationsUpdated log with args: { identity, permissions, expiry }By default, login() grants all four FWSS permissions (DefaultFwssPermissions) with a 1-hour expiry. Both permissions and expiresAt are configurable.
To grant only specific permissions:
await SessionKey.login(rootClient, { address: sessionKey.address, permissions: [ SessionKey.AddPiecesPermission, SessionKey.SchedulePieceRemovalsPermission, ],})To grant a custom (non-FWSS) permission:
await SessionKey.login(rootClient, { address: sessionKey.address, permissions: [ '0xabcdef...' as Hex, // any bytes32 hash ],})Use the session key for operations
Section titled “Use the session key for operations”Pass sessionKey.client to SDK operations. The session key signs the EIP-712 typed data while sessionKey.rootAddress is used as the payer/identity:
import { createDataSet, waitForCreateDataSet } from '@filoz/synapse-core/sp'
const result = await createDataSet(sessionKey.client, { payee: providerAddress, // Address: the SP's address payer: sessionKey.rootAddress, // Address: the root wallet paying for storage serviceURL: 'https://provider.example.com',})
const dataset = await waitForCreateDataSet(result)Use with the Synapse class
Section titled “Use with the Synapse class”The Synapse class accepts a sessionKey option (SessionKey<'Secp256k1'>) and uses it automatically for eligible operations (dataset creation, piece uploads, piece deletions):
import { Synapse } from '@filoz/synapse-sdk'
const synapse = Synapse.create({ account: rootAccount, chain: calibration, transport: http(rpcUrl), sessionKey: sessionKey,})Synapse.create() validates that the session key has all four FWSS permissions (DefaultFwssPermissions) and that none are expired. This means the session key’s expirations must be populated before construction, either by passing expirations to fromSecp256k1(), or by calling sessionKey.syncExpirations() after login.
Revoke the session key
Section titled “Revoke the session key”When done, the root wallet can revoke permissions:
// Fire-and-forget (returns tx hash: Hex)const hash = await SessionKey.revoke(rootClient, { address: sessionKey.address,})
// Or wait for confirmation (returns { receipt, event })await SessionKey.revokeSync(rootClient, { address: sessionKey.address, onHash(hash) { console.log('Revoking:', hash) },})Both default to revoking all FWSS permissions. Pass permissions to revoke selectively.
Expirations and refresh
Section titled “Expirations and refresh”Session key permissions have a fixed expiry set during login(). When a permission expires, any operation signed with that session key will revert on-chain.
The SDK does not automatically track or refresh expirations. For short-lived sessions (login, perform operations, done), this is not a concern. For long-lived sessions, the developer should:
- Check
sessionKey.hasPermission(permission)before operations if expirations are populated - Call
sessionKey.syncExpirations()periodically to refresh cached state from the chain - Call
login()again from the root wallet when permissions are near expiry
Errors from expired session keys will surface as contract reverts. The SDK does not currently distinguish these from other revert causes.
Checking permissions
Section titled “Checking permissions”hasPermission() and hasPermissions() are local checks against cached expiration timestamps. They return true if the permission’s expiry is in the future:
// Check a single permission (returns boolean)if (sessionKey.hasPermission(SessionKey.CreateDataSetPermission)) { // safe to create dataset}
// Check all FWSS permissions at once (returns boolean)if (sessionKey.hasPermissions(SessionKey.DefaultFwssPermissions)) { // all FWSS permissions are valid}These require that expirations have been populated via one of:
fromSecp256k1({ expirations: ... })at creation timesessionKey.syncExpirations()(fetches from chain via multicall)sessionKey.watch()(syncs and subscribes to live updates)
Real-time tracking (optional)
Section titled “Real-time tracking (optional)”For dApps that need live permission state (e.g., to update UI when permissions expire or are revoked):
const unwatch = await sessionKey.watch()
sessionKey.on('expirationsUpdated', (e: CustomEvent<Expirations>) => { console.log('Permissions changed:', e.detail)})
sessionKey.on('error', (e: CustomEvent<Error>) => { console.error('Watch error:', e.detail)})
// When done, clean up the subscriptionunwatch()// or: sessionKey.unwatch()watch() syncs expirations from the chain, starts a watchContractEvent subscription for AuthorizationsUpdated events, and returns a cleanup function. You can also call sessionKey.unwatch() directly. This is primarily useful for dApp UI; server-side code can use syncExpirations() directly.
Custom permissions
Section titled “Custom permissions”The four FWSS constants are SDK conveniences, not an exhaustive set. Any bytes32 hash can be registered as a permission. To work with custom permissions:
import * as SessionKey from '@filoz/synapse-core/session-key'import { createPublicClient, http, type Hex } from 'viem'import { calibration } from '@filoz/synapse-core/chains'
const publicClient = createPublicClient({ chain: calibration, transport: http(),})
const myPermission = '0x...' as Hex
// Grant (requires root wallet client)await SessionKey.login(rootClient, { address: sessionKey.address, permissions: [myPermission],})
// Check single expiry (returns bigint, 0n if no authorization exists)const expiry = await SessionKey.authorizationExpiry(publicClient, { address: rootAddress, // Address: the root wallet sessionKeyAddress: sessionKey.address, permission: myPermission,})
// Batch check (returns Record<Permission, bigint>)const expirations = await SessionKey.getExpirations(publicClient, { address: rootAddress, sessionKeyAddress: sessionKey.address, permissions: [myPermission, SessionKey.AddPiecesPermission],})API reference
Section titled “API reference”Factory functions
Section titled “Factory functions”fromSecp256k1(options) {#fromSecp256k1}
Section titled “fromSecp256k1(options) {#fromSecp256k1}”Create a session key with its own viem client.
- options.privateKey
Hex- secp256k1 private key - options.root
Account | Address- the authorizing wallet - options.chain
Chain- chain definition - options.transport?
Transport- defaults tohttp() - options.expirations?
Expirations- pre-populate known expirations - Returns
SessionKey<'Secp256k1'>
accountFromSecp256k1(options) {#accountFromSecp256k1}
Section titled “accountFromSecp256k1(options) {#accountFromSecp256k1}”Create a session key account without the wrapper (for custom client setup).
- options.privateKey
Hex- secp256k1 private key - options.rootAddress
Address- the authorizing wallet’s address - Returns
SessionKeyAccount<'Secp256k1'>
SessionKey instance
Section titled “SessionKey instance”Properties:
.clientClient<Transport, Chain, SessionKeyAccount<'Secp256k1'>>- viem client that signs with the session key.addressAddress- the session key’s address.rootAddressAddress- the authorizing wallet’s address.expirationsRecord<Permission, bigint>- cached permission expiry timestamps
Methods:
.hasPermission(permission)- returnsboolean, local check against cached expirations.hasPermissions(permissions)- returnsboolean, checks all permissions are valid.syncExpirations()- returnsPromise<void>, fetches expirations from chain via multicall.watch()- returnsPromise<() => void>, syncs + subscribes to liveAuthorizationsUpdatedevents; the returned function stops watching.unwatch()- stops watching for live events
Write operations (called by root wallet)
Section titled “Write operations (called by root wallet)”All write operations require a wallet client (Client<Transport, Chain, Account>).
login(client, options) {#login}
Section titled “login(client, options) {#login}”Authorize a session key on-chain. Returns Hash.
- options.address
Address- session key address - options.permissions?
Permission[]- defaults toDefaultFwssPermissions - options.expiresAt?
bigint- unix timestamp, defaults to now + 1 hour - options.origin?
string- defaults to'synapse'
loginSync(client, options) {#loginSync}
Section titled “loginSync(client, options) {#loginSync}”Authorize and wait for confirmation. Returns { receipt, event }.
Same options as login(), plus:
- options.onHash?
(hash: Hash) => void- called when tx is submitted
revoke(client, options) {#revoke}
Section titled “revoke(client, options) {#revoke}”Revoke session key permissions. Returns Hash.
- options.address
Address- session key address - options.permissions?
Permission[]- defaults toDefaultFwssPermissions - options.origin?
string- defaults to'synapse'
revokeSync(client, options) {#revokeSync}
Section titled “revokeSync(client, options) {#revokeSync}”Revoke and wait for confirmation. Returns { receipt, event }.
Same options as revoke(), plus:
- options.onHash?
(hash: Hash) => void- called when tx is submitted
Read operations
Section titled “Read operations”Read operations require a public client (Client<Transport, Chain>).
authorizationExpiry(client, options) {#authorizationExpiry}
Section titled “authorizationExpiry(client, options) {#authorizationExpiry}”Get the expiry timestamp for a single permission. Returns bigint (0n if no authorization exists).
- options.address
Address- the root wallet - options.sessionKeyAddress
Address- the session key - options.permission
Permission- which permission to check
isExpired(client, options) {#isExpired}
Section titled “isExpired(client, options) {#isExpired}”Check if a permission is expired. Returns boolean.
Same options as authorizationExpiry().
getExpirations(client, options) {#getExpirations}
Section titled “getExpirations(client, options) {#getExpirations}”Batch-fetch expirations for multiple permissions. Returns Record<Permission, bigint>.
- options.address
Address- the root wallet - options.sessionKeyAddress
Address- the session key - options.permissions?
Permission[]- defaults toDefaultFwssPermissions