Session-Enabled Account Contract for Starknet
THIS PROJECT DOES NOT COMPETE FOR A PRIZE
A OpenZeppelin-based account contract with native session key support for gasless, time-limited transaction execution on Starknet.
Overview
This Cairo smart contract extends OpenZeppelin's standard account implementation to support temporary session keys - cryptographic keys with built-in expiration, usage limits, and optional function restrictions. Session keys enable users to pre-authorize transactions without requiring wallet signatures for each action, dramatically improving UX for gaming, DeFi, and high-frequency dApps.
Key Features
🔑 Session Key Management
- Time-limited sessions: Each session key has a
valid_untiltimestamp (Unix seconds) - Call limits: Maximum number of transactions per session (
max_calls) - Usage tracking: Automatic counter increment (
calls_used) on each transaction - Entrypoint restrictions: Optional whitelist of allowed function selectors
- Zero-config mode: Empty entrypoint list = allow all functions
🛡️ Dual Signature Validation
The contract supports two signature formats in __validate__:
Owner Path (2 elements):
[r, s]- Standard ECDSA signature from account owner
- Full control over the account
- Uses OpenZeppelin's transaction hash validation
Session Path (4 elements):
[session_pubkey, r, s, valid_until]- ECDSA signature from session key
- Validates timestamp, call limits, and entrypoint restrictions
- Compatible with AVNU paymaster for gasless transactions
🔐 Security Mechanisms
if get_block_timestamp() > valid_until {
return 0; // Expired session
}
if session.calls_used >= session.max_calls {
return 0; // Usage limit exceeded
}
if !self._is_session_allowed_for_calls(session_pubkey, calls.span()) {
return 0; // Unauthorized function call
}
- Temporal safety: Sessions automatically expire after
valid_until - Rate limiting: Prevents session key abuse via
max_callscap - Function whitelisting: Optional entrypoint restrictions per session
- Owner-only management: Only the account owner can add/revoke sessions (
assert_only_self())
Architecture
Storage Structure
#[storage]
struct Storage {
account: AccountComponent::Storage, // OZ account state
src5: SRC5Component::Storage, // ERC-165 introspection
upgradeable: UpgradeableComponent::Storage, // Proxy upgrade logic
session_keys: Map<felt252, SessionData>, // Session metadata
session_entrypoints: Map<(felt252, u32), felt252>, // Allowed functions
}
SessionData struct:
pub struct SessionData {
pub valid_until: u64, // Expiration timestamp
pub max_calls: u32, // Maximum allowed calls
pub calls_used: u32, // Current usage counter
pub allowed_entrypoints_len: u32, // Number of whitelisted functions
}
Session Lifecycle
Creation (via owner transaction):
fn add_or_update_session_key( session_key: felt252, // Public key of session signer valid_until: u64, // Unix timestamp expiration max_calls: u32, // Usage limit allowed_entrypoints: Array<felt252> // Optional function whitelist )Validation (on each transaction):
// Checks performed in __validate__: - Signature length == 4 - Current timestamp <= valid_until - calls_used < max_calls - All call selectors in allowed_entrypoints (if specified) - ECDSA signature verificationRevocation (via owner transaction):
fn revoke_session_key(session_key: felt252) // Clears SessionData and all stored entrypoints
Message Hash Computation
Session signatures use Poseidon hash over the following structure:
poseidon_hash_span([
account_address,
chain_id,
nonce,
valid_until,
// For each call:
call.to,
call.selector,
call.calldata.len(),
...calldata_elements
])
This matches the message construction in the frontend TypeScript code, ensuring signature compatibility.
Usage Example
Step 1: Deploy Account
constructor(public_key: felt252) // Owner's public key
Step 2: Add Session Key (Owner Signs)
add_or_update_session_key(
session_key: 0x123..., // Session public key
valid_until: 1735689600, // 6 hours from now
max_calls: 100, // Allow 100 transactions
allowed_entrypoints: array![] // Empty = allow all functions
)
Step 3: Execute Transaction (Session Signs)
// Submit transaction with 4-element signature:
signature: [
session_pubkey, // 0x123...
r, // Signature r component
s, // Signature s component
valid_until // Must match session data
]
The contract validates the session, verifies the signature, increments calls_used, and executes the transaction.
Advantages Over Standard Accounts
| Feature | Standard Account | Session-Enabled Account |
|---|---|---|
| UX | Sign every transaction | Sign once, execute many |
| Gas Fees | User pays per tx | Paymaster can sponsor |
| Mobile Gaming | Constant popups | Seamless gameplay |
| Security | Full key exposure risk | Time-limited, revocable |
| DeFi Automation | Manual signing | Pre-authorized strategies |
Compatibility
- ✅ OpenZeppelin Account Standard - Inherits battle-tested account logic
- ✅ SRC-6 (Account Interface) - Standard Starknet account validation
- ✅ ERC-1271 - Off-chain signature validation support
- ✅ UORC (Upgradeable) - Proxy pattern for contract upgrades
- ✅ AVNU Paymaster - Compatible with gasless transaction sponsorship
Security Considerations
✅ Audited Components
- Built on OpenZeppelin's audited
AccountComponent - Uses standard Cairo cryptographic primitives (
check_ecdsa_signature,poseidon_hash_span)
✅ Protection Mechanisms
- Replay protection: Nonce included in session message hash
- Temporal bounds: Automatic expiration via
valid_until - Usage caps: Maximum call limit prevents abuse
- Owner privilege: Only owner can manage sessions
- Self-call validation: Prevents unauthorized empty signatures
⚠️ Best Practices
- Set reasonable
valid_untilvalues (hours, not years) - Use
max_callslimits appropriate to use case - Specify
allowed_entrypointsfor sensitive operations - Monitor and revoke compromised session keys immediately
- Always validate session key generation happens in secure environment
Events
#[derive(Drop, starknet::Event)]
struct SessionKeyAdded {
#[key]
session_key: felt252,
valid_until: u64,
max_calls: u32,
}
#[derive(Drop, starknet::Event)]
struct SessionKeyRevoked {
#[key]
session_key: felt252,
}
Events enable off-chain indexing and frontend state synchronization.
Technical Highlights
- Zero-storage wildcard: Empty
allowed_entrypointsenables all functions without storage overhead - Efficient validation: Pure check (
_is_session_allowed_for_calls) before mutation (_consume_session_call) - Flexible whitelisting: Per-session function restrictions via indexed storage map
- Component-based: Modular architecture using OpenZeppelin's component pattern
- Upgrade-safe: Implements
IUpgradeablefor seamless contract migrations
Use Cases
🎮 Gaming
Pre-authorize 1000 game actions for 24 hours. Players execute moves without wallet popups.
💱 DeFi Trading
Session key for automated limit orders. Execute trades within predefined parameters.
🌐 Social dApps
Like, comment, share without signing. Session expires after browsing session.
🤖 Bot Automation
Controlled bot access with time + call limits. Revoke immediately if compromised.
Contract Version: v22
License: MIT
Base: OpenZeppelin Account v0.15+
Built With
- openzeppelin
Log in or sign up for Devpost to join the conversation.