Session Keys
Session keys allow you to delegate signing authority to a temporary key without exposing your main wallet’s private key. This is essential for building secure dApps and automated systems.
Why Session Keys?
Security Never expose your main wallet’s private key in application code. Session keys can be revoked instantly.
User Experience Users sign once to create a session, then operations proceed without repeated wallet prompts.
Automation Backend services can perform operations on behalf of users without accessing their main keys.
Scoped Permissions Session keys are limited to specific operations and can have expiration times.
How Session Keys Work
Main Wallet (Root)
│
│ Signs login transaction
│ Sets permissions & expiry
│
↓
Session Key Registry (on-chain)
│
│ Records: Root → Session mapping
│ Stores: Permissions & expiration
│
↓
Session Key (Temporary)
│
│ Signs operations on behalf of root
│ Validated by contracts
│
↓
Smart Contracts (FWSS, etc.)
- Verify session key is authorized
- Check permissions match operation
- Ensure not expired
- Bill the root wallet (payer)
The payer address always remains the root wallet, even when signing with a session key. Payments come from the main wallet.
Creating a Session Key
Using synapse-core
Low-level session key creation:
import * as SessionKey from '@filoz/synapse-core/session-key'
import { calibration } from '@filoz/synapse-core/chains'
import { generatePrivateKey } from 'viem/accounts'
import { http } from 'viem'
// Generate a new temporary key
const sessionPrivateKey = generatePrivateKey ()
// Create session key linked to your main wallet
const sessionKey = SessionKey . fromSecp256k1 ({
chain: calibration ,
transport: http (),
privateKey: sessionPrivateKey ,
root: mainAccount . address , // Your main wallet address
})
console . log ( 'Session key address:' , sessionKey . address )
console . log ( 'Root address:' , sessionKey . rootAddress )
Login: Set Permissions
Register the session key on-chain with permissions:
import * as SessionKey from '@filoz/synapse-core/session-key'
import { login } from '@filoz/synapse-core/session-key'
import { createWalletClient , http } from 'viem'
// Your main wallet client
const rootClient = createWalletClient ({
chain: calibration ,
transport: http (),
account: mainAccount , // Has private key or wallet provider
})
// Login: authorize session key with FWSS permissions
const hash = await login ( rootClient , {
sessionKeyAddress: sessionKey . address ,
permissions: SessionKey . DefaultFwssPermissions ,
expiry: BigInt ( Math . floor ( Date . now () / 1000 ) + 86400 ), // 24 hours
})
await rootClient . waitForTransactionReceipt ({ hash })
console . log ( 'Session key registered' )
// Sync expirations
await sessionKey . syncExpirations ()
Default FWSS Permissions :
export const DefaultFwssPermissions = [
Permission . CreateDataSetAndAddPieces ,
Permission . AddPieces ,
Permission . SchedulePieceRemoval ,
Permission . TerminateDataSet ,
]
These cover all standard storage operations.
Use with Synapse
Pass the session key to Synapse:
import { Synapse } from '@filoz/synapse-sdk'
const synapse = Synapse . create ({
chain: calibration ,
transport: http (),
account: mainAccount ,
sessionKey , // Session key for signing
})
// All operations use the session key for signing
// but payments come from mainAccount
const result = await synapse . storage . upload ( data )
console . log ( 'Uploaded with session key' )
console . log ( 'Payer:' , mainAccount . address ) // Root wallet pays
The session key must have permissions synced before passing to Synapse, or initialization will fail.
Full Example: Main Wallet + Session Key
import { Synapse } from '@filoz/synapse-sdk'
import * as SessionKey from '@filoz/synapse-core/session-key'
import { login } from '@filoz/synapse-core/session-key'
import { calibration } from '@filoz/synapse-core/chains'
import { http } from 'viem'
import { privateKeyToAccount , generatePrivateKey } from 'viem/accounts'
import { createWalletClient } from 'viem'
async function setupSession () {
// 1. Main wallet (has funds, approvals, etc.)
const mainAccount = privateKeyToAccount ( '0x...' as `0x ${ string } ` )
const rootClient = createWalletClient ({
chain: calibration ,
transport: http (),
account: mainAccount ,
})
// 2. Generate temporary session key
const sessionPrivateKey = generatePrivateKey ()
console . log ( 'Generated session private key:' , sessionPrivateKey )
// 3. Create session key object
const sessionKey = SessionKey . fromSecp256k1 ({
chain: calibration ,
transport: http (),
privateKey: sessionPrivateKey ,
root: mainAccount . address ,
})
console . log ( 'Session address:' , sessionKey . address )
// 4. Login: register on-chain
console . log ( 'Logging in session key...' )
const hash = await login ( rootClient , {
sessionKeyAddress: sessionKey . address ,
permissions: SessionKey . DefaultFwssPermissions ,
expiry: BigInt ( Math . floor ( Date . now () / 1000 ) + 3600 ), // 1 hour
})
await rootClient . waitForTransactionReceipt ({ hash })
console . log ( 'Login confirmed' )
// 5. Sync permissions
await sessionKey . syncExpirations ()
// 6. Use with Synapse
const synapse = Synapse . create ({
chain: calibration ,
transport: http (),
account: mainAccount ,
sessionKey ,
})
console . log ( 'Synapse created with session key' )
// 7. Upload with session key signing
const data = new TextEncoder (). encode ( 'Session key test' )
const result = await synapse . storage . upload ( data )
console . log ( 'Upload successful' )
console . log ( 'PieceCID:' , result . pieceCid )
console . log ( 'Signed by:' , sessionKey . address )
console . log ( 'Paid by:' , mainAccount . address )
return { synapse , sessionKey , sessionPrivateKey }
}
setupSession (). catch ( console . error )
Store the session private key securely :
In environment variables for backend services
In secure storage for mobile apps
In localStorage for web apps (with user awareness)
Anyone with the session private key can perform authorized operations until expiry. Treat session keys as sensitive credentials.
Checking Permissions
Verify session key has required permissions:
const hasPermission = sessionKey . hasPermission (
SessionKey . Permission . CreateDataSetAndAddPieces
)
if ( ! hasPermission ) {
console . error ( 'Session key lacks required permission' )
// Re-login or request new permissions
}
// Check multiple permissions
const hasAll = sessionKey . hasPermissions ([
SessionKey . Permission . CreateDataSetAndAddPieces ,
SessionKey . Permission . AddPieces ,
])
Watching for Updates
Listen for permission changes (e.g., renewal, revocation):
// Start watching
const unwatch = await sessionKey . watch ()
// Listen for events
sessionKey . addEventListener ( 'connected' , ( expirations ) => {
console . log ( 'Session connected, expirations:' , expirations )
})
sessionKey . addEventListener ( 'expirationsUpdated' , ( expirations ) => {
console . log ( 'Permissions updated:' , expirations )
})
sessionKey . addEventListener ( 'disconnected' , () => {
console . log ( 'Session disconnected' )
})
sessionKey . addEventListener ( 'error' , ( error ) => {
console . error ( 'Session error:' , error )
})
// Stop watching
unwatch ()
Revoking Session Keys
Revoke a session key before expiry:
import { revoke } from '@filoz/synapse-core/session-key'
// Sign with main wallet to revoke
const hash = await revoke ( rootClient , {
sessionKeyAddress: sessionKey . address ,
permissions: SessionKey . DefaultFwssPermissions ,
})
await rootClient . waitForTransactionReceipt ({ hash })
console . log ( 'Session key revoked' )
// Session key can no longer sign operations
Revocation is instant. The session key cannot be used for new operations after the transaction confirms.
Renewing Session Keys
Extend expiration by calling login() again:
// Re-login with new expiry
const hash = await login ( rootClient , {
sessionKeyAddress: sessionKey . address ,
permissions: SessionKey . DefaultFwssPermissions ,
expiry: BigInt ( Math . floor ( Date . now () / 1000 ) + 7200 ), // 2 more hours
})
await rootClient . waitForTransactionReceipt ({ hash })
// Sync the updated expirations
await sessionKey . syncExpirations ()
console . log ( 'Session renewed' )
Use Cases
Backend Service
A backend service that uploads on behalf of users:
// One-time setup (user signs)
async function initUserSession ( userId : string , userAccount : Account ) {
const sessionPrivateKey = generatePrivateKey ()
const sessionKey = SessionKey . fromSecp256k1 ({
chain: calibration ,
privateKey: sessionPrivateKey ,
root: userAccount . address ,
})
// User signs login
const hash = await login ( userClient , {
sessionKeyAddress: sessionKey . address ,
permissions: SessionKey . DefaultFwssPermissions ,
expiry: BigInt ( Math . floor ( Date . now () / 1000 ) + 86400 * 7 ), // 7 days
})
await userClient . waitForTransactionReceipt ({ hash })
// Store session private key
await db . saveSessionKey ( userId , sessionPrivateKey , sessionKey . address )
return sessionKey
}
// Automated operations (no user interaction)
async function uploadForUser ( userId : string , data : Uint8Array ) {
const { sessionPrivateKey , rootAddress } = await db . getSessionKey ( userId )
const sessionKey = SessionKey . fromSecp256k1 ({
chain: calibration ,
privateKey: sessionPrivateKey as `0x ${ string } ` ,
root: rootAddress ,
})
const synapse = Synapse . create ({
chain: calibration ,
account: rootAddress , // Root pays
sessionKey , // Session signs
})
return await synapse . storage . upload ( data )
}
Web App
A web app that reduces wallet prompts:
// User clicks "Connect Wallet"
async function connectWallet () {
const provider = window . ethereum
const [ address ] = await provider . request ({ method: 'eth_requestAccounts' })
// Generate session key
const sessionPrivateKey = generatePrivateKey ()
localStorage . setItem ( 'session_key' , sessionPrivateKey )
const sessionKey = SessionKey . fromSecp256k1 ({
chain: calibration ,
privateKey: sessionPrivateKey ,
root: address ,
})
// User signs login (one wallet prompt)
const walletClient = createWalletClient ({
chain: calibration ,
transport: custom ( provider ),
account: address ,
})
const hash = await login ( walletClient , {
sessionKeyAddress: sessionKey . address ,
permissions: SessionKey . DefaultFwssPermissions ,
expiry: BigInt ( Math . floor ( Date . now () / 1000 ) + 3600 ), // 1 hour
})
await walletClient . waitForTransactionReceipt ({ hash })
// Now user can upload without prompts
return sessionKey
}
// Upload without wallet prompt
async function upload ( data : Uint8Array ) {
const sessionPrivateKey = localStorage . getItem ( 'session_key' ) as `0x ${ string } `
const [ address ] = await window . ethereum . request ({ method: 'eth_accounts' })
const sessionKey = SessionKey . fromSecp256k1 ({
chain: calibration ,
privateKey: sessionPrivateKey ,
root: address ,
})
const synapse = Synapse . create ({
chain: calibration ,
transport: custom ( window . ethereum ),
account: address ,
sessionKey ,
})
return await synapse . storage . upload ( data )
}
Best Practices
Session keys should expire quickly:
Web apps: 1-2 hours
Mobile apps: 1 day
Backend services: 7 days max
Renew as needed rather than using long expirations.
Store session keys securely
Backend : Environment variables, secret managers
Web : localStorage (inform users)
Mobile : Secure enclave, keychain
Never commit session keys to version control.
Sync expirations after login
Always call syncExpirations() after login(): await login ( client , { ... })
await sessionKey . syncExpirations ()
Otherwise, permission checks may fail.
When a user logs out, revoke the session key: await revoke ( client , { sessionKeyAddress: sessionKey . address , ... })
localStorage . removeItem ( 'session_key' )
Next Steps
Architecture Understand how session keys fit into the SDK
Session Keys Guide Practical examples of session key usage
Browser Integration Use session keys in web apps
Storage Operations Upload and download with session keys