Skip to content

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.

  • 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 bytes32 hashes (by convention, EIP-712 type hashes).
  • Expiry - Authorizations are time-limited. The SDK defaults to 1 hour; the contract stores whatever expiry is provided.

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:

ConstantOperation
CreateDataSetPermissionCreate new datasets
AddPiecesPermissionAdd pieces to datasets
SchedulePieceRemovalsPermissionSchedule piece removals
DeleteDataSetPermissionDelete datasets

These are the constants currently supported by FWSS. The Permission type also accepts any Hex value, allowing registration of custom permission hashes for non-FWSS operations (e.g., authenticated Curio HTTP endpoints).

A complete session key lifecycle from creation through to use:

import * as
import SessionKey
SessionKey
from '@filoz/synapse-core/session-key'
import {
const calibration: Chain
calibration
} from '@filoz/synapse-core/chains'
import {
function createWalletClient<transport extends Transport, chain extends Chain | undefined = undefined, accountOrAddress extends Account | Address | undefined = undefined, rpcSchema extends RpcSchema | undefined = undefined>(parameters: WalletClientConfig<transport, chain, accountOrAddress, rpcSchema>): WalletClient<transport, chain, ParseAccount<accountOrAddress>, rpcSchema>

Creates a Wallet Client with a given Transport configured for a Chain.

A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.

The Wallet Client supports signing over:

@paramconfig - WalletClientConfig

@returnsA Wallet Client. WalletClient

@example

// JSON-RPC Account import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), })

@example

// Local Account import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…') chain: mainnet, transport: http(), })

createWalletClient
,
function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>

@description Creates a HTTP transport that connects to a JSON-RPC API.

http
, type
type Hex = `0x${string}`
Hex
} from 'viem'
import {
function privateKeyToAccount(privateKey: Hex, options?: PrivateKeyToAccountOptions): PrivateKeyAccount

@description Creates an Account from a private key.

@returnsA Private Key Account.

privateKeyToAccount
,
function generatePrivateKey(): Hex

@description Generates a random private key.

@returnsA randomly generated private key.

generatePrivateKey
} from 'viem/accounts'
// The root wallet (the user's primary wallet)
const
const rootAccount: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
}
rootAccount
=
function privateKeyToAccount(privateKey: Hex, options?: PrivateKeyToAccountOptions): PrivateKeyAccount

@description Creates an Account from a private key.

@returnsA Private Key Account.

privateKeyToAccount
('0x<root-private-key>' as
type Hex = `0x${string}`
Hex
)
const
const rootClient: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
};
... 41 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
rootClient
=
createWalletClient<HttpTransport<undefined, false>, Chain, {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
}, undefined>(parameters: {
...;
}): {
...;
}

Creates a Wallet Client with a given Transport configured for a Chain.

A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.

The Wallet Client supports signing over:

@paramconfig - WalletClientConfig

@returnsA Wallet Client. WalletClient

@example

// JSON-RPC Account import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), })

@example

// Local Account import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…') chain: mainnet, transport: http(), })

createWalletClient
({
account?: `0x${string}` | {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
} | Account | undefined

The Account to use for the Client. This will be used for Actions that require an account as an argument.

account
:
const rootAccount: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
}
rootAccount
,
chain?: Chain | Chain | undefined

Chain for the client.

chain
:
const calibration: Chain
calibration
,
transport: HttpTransport<undefined, false>

The RPC transport

transport
:
http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>

@description Creates a HTTP transport that connects to a JSON-RPC API.

http
(),
})
// Create an ephemeral session key
const
const privateKey: `0x${string}`
privateKey
=
function generatePrivateKey(): Hex

@description Generates a random private key.

@returnsA randomly generated private key.

generatePrivateKey
()
const
const sessionKey: Secp256k1SessionKey
sessionKey
=
import SessionKey
SessionKey
.
function fromSecp256k1(options: SessionKey.FromSecp256k1Options): Secp256k1SessionKey
export fromSecp256k1
fromSecp256k1
({
FromSecp256k1Options.privateKey: `0x${string}`
privateKey
,
FromSecp256k1Options.root: `0x${string}` | Account
root
:
const rootAccount: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
}
rootAccount
, // Account or Address
FromSecp256k1Options.chain: Chain
chain
:
const calibration: Chain
calibration
,
})
// Authorize the session key on-chain (root wallet signs this tx)
const {
const event: Log<bigint, number, false, undefined, true, readonly [{
readonly type: "function";
readonly inputs: readonly [{
readonly name: "user";
readonly internalType: "address";
readonly type: "address";
}, {
readonly name: "signer";
readonly internalType: "address";
readonly type: "address";
}, {
readonly name: "permission";
readonly internalType: "bytes32";
readonly type: "bytes32";
}];
readonly name: "authorizationExpiry";
readonly outputs: readonly [{
readonly name: "";
readonly internalType: "uint256";
readonly type: "uint256";
}];
readonly stateMutability: "view";
}, {
...;
}, {
...;
}, {
...;
}, {
...;
}], "AuthorizationsUpdated">
event
} = await
import SessionKey
SessionKey
.
function loginSync(client: Client<Transport, Chain, Account>, options: SessionKey.loginSync.OptionsType): Promise<SessionKey.loginSync.OutputType>
loginSync
(
const rootClient: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
};
... 41 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
rootClient
, {
address: `0x${string}`
address
:
const sessionKey: Secp256k1SessionKey
sessionKey
.
Secp256k1SessionKey.address: `0x${string}`
address
,
onHash?: ((hash: Hash) => void) | undefined
onHash
(
hash: `0x${string}`
hash
) {
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
('Tx submitted:',
hash: `0x${string}`
hash
)
},
})
// Sync expirations from the chain so hasPermission() works locally
await
const sessionKey: Secp256k1SessionKey
sessionKey
.
Secp256k1SessionKey.syncExpirations(permissions?: SessionKey.Permission[]): Promise<void>
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.

fromSecp256k1() creates a session key from a secp256k1 private key. It returns a SessionKey<'Secp256k1'> instance.

import * as
import SessionKey
SessionKey
from '@filoz/synapse-core/session-key'
import {
const calibration: Chain
calibration
} from '@filoz/synapse-core/chains'
import type {
type Hex = `0x${string}`
Hex
} from 'viem'
import {
function privateKeyToAccount(privateKey: Hex, options?: PrivateKeyToAccountOptions): PrivateKeyAccount

@description Creates an Account from a private key.

@returnsA Private Key Account.

privateKeyToAccount
} from 'viem/accounts'
const
const rootAccount: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
}
rootAccount
=
function privateKeyToAccount(privateKey: Hex, options?: PrivateKeyToAccountOptions): PrivateKeyAccount

@description Creates an Account from a private key.

@returnsA Private Key Account.

privateKeyToAccount
('0x<root-private-key>' as
type Hex = `0x${string}`
Hex
)
const
const sessionKey: Secp256k1SessionKey
sessionKey
=
import SessionKey
SessionKey
.
function fromSecp256k1(options: SessionKey.FromSecp256k1Options): Secp256k1SessionKey
export fromSecp256k1
fromSecp256k1
({
FromSecp256k1Options.privateKey: `0x${string}`
privateKey
: '0x...' as
type Hex = `0x${string}`
Hex
, // secp256k1 private key for the session key
FromSecp256k1Options.root: `0x${string}` | Account
root
:
const rootAccount: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
}
rootAccount
, // Account or Address of the authorizing wallet
FromSecp256k1Options.chain: Chain
chain
:
const calibration: 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.

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
const hash: `0x${string}`
hash
= await
import SessionKey
SessionKey
.
function login(client: Client<Transport, Chain, Account>, options: SessionKey.login.OptionsType): Promise<SessionKey.login.OutputType>
login
(
const rootClient: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
};
... 41 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
rootClient
, {
address: `0x${string}`
address
:
const sessionKey: Secp256k1SessionKey
sessionKey
.
Secp256k1SessionKey.address: `0x${string}`
address
,
})
// Or wait for confirmation (returns { receipt, event })
const {
const receipt: TransactionReceipt
receipt
,
const event: Log<bigint, number, false, undefined, true, readonly [{
readonly type: "function";
readonly inputs: readonly [{
readonly name: "user";
readonly internalType: "address";
readonly type: "address";
}, {
readonly name: "signer";
readonly internalType: "address";
readonly type: "address";
}, {
readonly name: "permission";
readonly internalType: "bytes32";
readonly type: "bytes32";
}];
readonly name: "authorizationExpiry";
readonly outputs: readonly [{
readonly name: "";
readonly internalType: "uint256";
readonly type: "uint256";
}];
readonly stateMutability: "view";
}, {
...;
}, {
...;
}, {
...;
}, {
...;
}], "AuthorizationsUpdated">
event
} = await
import SessionKey
SessionKey
.
function loginSync(client: Client<Transport, Chain, Account>, options: SessionKey.loginSync.OptionsType): Promise<SessionKey.loginSync.OutputType>
loginSync
(
const rootClient: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
serializer?: serializer | undefined;
} | undefined) => Promise<Hex>;
signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
publicKey: Hex;
source: "privateKey";
type: "local";
};
... 41 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
rootClient
, {
address: `0x${string}`
address
:
const sessionKey: Secp256k1SessionKey
sessionKey
.
Secp256k1SessionKey.address: `0x${string}`
address
,
expiresAt?: bigint | undefined
expiresAt
:
var BigInt: BigIntConstructor
(value: bigint | boolean | number | string) => bigint
BigInt
(
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.floor(x: number): number

Returns the greatest integer less than or equal to its numeric argument.

@paramx A numeric expression.

floor
(
var Date: DateConstructor

Enables basic storage and retrieval of dates and times.

Date
.
DateConstructor.now(): number

Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).

now
() / 1000) + 7200), // 2 hours
onHash?: ((hash: Hash) => void) | undefined
onHash
(
hash: `0x${string}`
hash
) {
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
('Tx submitted:',
hash: `0x${string}`
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
],
})

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)

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.

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.

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.

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 time
  • sessionKey.syncExpirations() (fetches from chain via multicall)
  • sessionKey.watch() (syncs and subscribes to live updates)

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 subscription
unwatch()
// 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.

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],
})