Gasless, delegated transactions on Starknet — Enable users to interact with your dApp without paying gas fees or signing every transaction.
A production-ready Cairo smart contract that combines session keys with SNIP-9 v2 Outside Execution and Paymaster support, enabling seamless Web3 UX on Starknet.
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ User clicks "Play" → Backend sponsors gas → Transaction executes │
│ │
│ • No wallet popup │
│ • No gas fees for user │
│ • Delegated, time-limited access │
│ │
└─────────────────────────────────────────────────────────────────────┘
Key Features:
- 🔑 Session Keys — Temporary, restricted access delegation
- ⛽ Gasless Transactions — Backend sponsors gas via paymasters
- 🔄 Outside Execution — Third parties submit transactions on user's behalf
- 🛡️ Production Security — Battle-tested, audited code with comprehensive tests
| Field | Value |
|---|---|
| Class Hash | 0x0484bbd2404b3c7264bea271f7267d6d4004821ac7787a9eed7f472e79ef40d1 |
| Contract Address | 0x03062f8ec52749beae94daee793871e60a4f71fdee577e9d9fb0c61260024806 |
| Network | Starknet Mainnet |
| Version | v33 (Audit 1 + 2 + 3 + 4 Compliant + Spending Policy) |
| Status | Live on Mainnet |
| Audit | Nethermind AuditAgent — January & February 2026 (4 scans, final: 0 findings) |
| Voyager | View Contract Class |
This contract was audited by Nethermind AuditAgent across 4 scans in January–February 2026. The initial audit identified 10 findings across the session key and outside execution logic. All valid findings have been fixed, and audit 4 returned 0 findings:
| Severity | Findings | Status |
|---|---|---|
| High | 5 | 3 fixed, 1 disputed (sequencer architecture), 1 accepted tradeoff |
| Medium | 2 | 1 invalid (false positive), 1 by design |
| Low | 1 | Fixed |
| Best Practice | 2 | Fixed |
Key fixes applied: session whitelist enforcement across all entry points, call-limit reset protection, session self-revocation guard, safe felt252-to-u64 conversion, and stale entrypoint cleanup.
Full report: audit/nethermind-audit-2026-01.pdf Detailed responses: AUDIT_RESPONSE.md
A second Nethermind AuditAgent scan was performed in February 2026 after audit 1 fixes were applied. It identified 3 additional findings:
| Severity | Findings | Status |
|---|---|---|
| High | 1 | Fixed (nested __execute__ bypass blocked) |
| Medium | 1 | Accepted (session hash scope — see accepted risk) |
| Low | 1 | Fixed (call consumed after validation, not before) |
Full report: audit/audit_agent_report_2_acedcc33-1159-4f2d-939a-cb04b84ff85c.pdf Detailed responses: AUDIT_RESPONSE_2.md
A third Nethermind AuditAgent scan was performed on v31 (commit 6424aa0). It identified 5 findings (2 High — same finding filed as duplicate, 2 Low, 1 Info). All fixed:
| Severity | Findings | Status |
|---|---|---|
| High | 2 | Fixed (1 unique + 1 duplicate: set_public_key/setPublicKey added to blocklist + self-call block) |
| Low | 2 | Fixed (nested execute_from_outside_v2 blocked, SRC-5 interface registered) |
| Info | 1 | Fixed (valid_until binding in SNIP-9 path) |
Systemic fix: v32 adds a self-call block — sessions with empty whitelist cannot target the account contract at all. This eliminates the entire class of privilege escalation via self-calls that audits 1-3 kept finding.
Full report: audit/audit_agent_report_3_4bedc58d-5c45-4607-b61a-d3f040f8a783.pdf Detailed responses: AUDIT_RESPONSE_3.md
A fourth Nethermind AuditAgent scan was performed on v32 (commit 9be9629b). Result: 0 findings. Zero High, Zero Medium, Zero Low, Zero Info, Zero Best Practices.
| Severity | Findings |
|---|---|
| High | 0 |
| Medium | 0 |
| Low | 0 |
| Info | 0 |
| Best Practice | 0 |
The self-call block and expanded blocklist introduced in v32 eliminated the systemic vulnerability class that drove findings in audits 1–3. Findings trajectory: 10 → 3 → 5 → 0.
Full report: audit/audit_agent_report_4_3d9877bd-6f4e-46c3-945d-32e3872e6264.pdf Detailed responses: AUDIT_RESPONSE_4.md
- Security Audit
- Why Custom SNIP-9?
- Paymaster Integration
- Security Architecture
- Test Suite
- Real-World Use Cases
- Quick Start
- API Reference
- Standards Work
OpenZeppelin's SRC9Component uses SNIP-12 Revision 0 for typed data hashing. However:
- Starknet.js uses Revision 1 — The official JavaScript library computes hashes using SNIP-12 Rev 1
- Hash mismatch = failed signatures — Frontend-signed messages don't match what the contract expects
- No session key support — Standard implementations only validate owner signatures
We use OpenZeppelin's SRC9Component but override execute_from_outside_v2 with a custom inline implementation that adds session key support and SNIP-12 Rev 1 hashing:
// SNIP-12 Revision 1 Type Hashes (matching starknet.js)
STARKNET_DOMAIN_TYPE_HASH = 0x1ff2f602e42168014d405a94f75e8a93d640751d71d16311266e140d8b0a210
CALL_TYPE_HASH = 0x3635c7f2a7ba93844c0d064e18e487f35ab90f7c39d00f186a781fc3f0c2ca9
OUTSIDE_EXECUTION_TYPE_HASH = 0x5a4b49e17039355cd95d1f0981d75901191d1319b1f4b05a9a791d218d7e0cKey Differences from OpenZeppelin:
| Feature | OpenZeppelin SRC9 | Our Implementation |
|---|---|---|
| SNIP-12 Version | Revision 0 | Revision 1 ✅ |
| starknet.js Compatible | ❌ Hash mismatch | ✅ Perfect match |
| Session Signatures | ❌ Owner only | ✅ Owner + Sessions |
| Array Hashing | Inline | Pre-hashed (Rev 1 spec) |
| Version Encoding | Shortstring '2' |
Numeric 2 |
SNIP-12 Rev 1 requires pre-hashing arrays:
// Our implementation (correct for Rev 1)
fn _hash_call(call: Call) -> felt252 {
let calldata_hash = poseidon_hash_span(call.calldata); // Pre-hash array
poseidon_hash_span([CALL_TYPE_HASH, call.to, call.selector, calldata_hash])
}
// The OutsideExecution hash also pre-hashes the calls array
let calls_array_hash = poseidon_hash_span(call_hashes.span());This ensures signatures computed by starknet.js match exactly what the contract validates.
┌──────────────┐ 1. Sign ┌──────────────┐ 2. Submit ┌──────────────┐
│ User │ ─────────────→ │ Backend │ ───────────────→ │ Paymaster │
│ (no STRK) │ (session) │ (relayer) │ (sponsored) │ (AVNU) │
└──────────────┘ └──────────────┘ └──────────────┘
│
3. Execute via SNIP-9 │
▼
┌──────────────┐
│ Account │
│ Contract │
└──────────────┘
1. User creates a session (once):
// Owner signs to add session key
await account.execute({
contractAddress: accountAddress,
entrypoint: 'add_or_update_session_key',
calldata: [sessionPubKey, validUntil, maxCalls, ...allowedSelectors]
});2. User signs session messages (gas-free):
// Session key signs the operation (no gas needed)
const signature = signWithSessionKey(messageHash, sessionPrivateKey);3. Backend submits via paymaster:
// Backend calls execute_from_outside_v2 with sponsored gas
const outsideExecution = {
caller: relayerAddress, // or 0 for any caller
nonce: randomNonce,
execute_after: now,
execute_before: now + 3600,
calls: userCalls
};
await paymaster.executeFromOutside(accountAddress, outsideExecution, signature);┌─────────────────────────────────────────────────────────────────┐
│ SECURITY LAYERS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. ACCESS CONTROL │
│ └─ Owner-only: add/revoke sessions, upgrade contract │
│ │
│ 2. SESSION RESTRICTIONS │
│ ├─ Time-limited (valid_until timestamp) │
│ ├─ Call-limited (max_calls counter) │
│ └─ Function-limited (allowed_entrypoints whitelist) │
│ │
│ 3. CRYPTOGRAPHIC SECURITY │
│ ├─ ECDSA signature verification (Stark curve) │
│ ├─ Poseidon hash for message integrity │
│ └─ Nonce-based replay protection │
│ │
│ 4. SNIP-9 PROTECTIONS │
│ ├─ Timestamp bounds (execute_after, execute_before) │
│ ├─ Caller restrictions (specific address or any) │
│ └─ One-time nonce consumption │
│ │
└─────────────────────────────────────────────────────────────────┘
Owner Signatures (2 elements):
[r, s] → Validate via OpenZeppelin → Full account control
Session Signatures (4 elements):
[session_pubkey, r, s, valid_until]
↓
1. Check session exists (valid_until > 0)
2. Check not expired (block_timestamp ≤ valid_until)
3. Check calls remaining (calls_used < max_calls)
4. Block admin selectors (upgrade, add/revoke session, `__execute__`, set_public_key, setPublicKey, execute_from_outside_v2, set_spending_policy, remove_spending_policy)
5. Block self-calls for empty whitelist (call.to == account)
6. Check selector allowed (if explicit whitelist exists)
6. Verify ECDSA signature
7. Increment calls_used counter
↓
Transaction authorized
| Protection | How It Works |
|---|---|
| No Replay Attacks | Nonces are consumed on use; each signature is single-use |
| No Cross-Chain Replay | Chain ID included in message hash |
| No Account Confusion | Account address included in message hash |
| No Privilege Escalation | Sessions can only call whitelisted functions |
| No Admin Access | Sessions blocked from 9 admin selectors (upgrade, add/revoke session, set_public_key, setPublicKey, set_spending_policy, remove_spending_policy) regardless of whitelist |
| No Nested Execution | Sessions blocked from calling __execute__ and execute_from_outside_v2 directly |
| No Self-Call Escalation | Sessions with empty whitelist cannot target the account contract at all (eliminates entire privilege escalation class) |
| No Indefinite Access | Sessions have mandatory expiration |
| No Runaway Usage | Call limits prevent abuse of compromised keys |
The __execute__ function uses best-effort execution: if a subcall fails, it returns an empty span for that call and continues. This is intentional — callers should check the results array for empty spans to detect partial failures. This differs from the atomic all-or-nothing pattern used by some account implementations.
Session signatures bind calls, calldata, nonce, chain_id, and expiration but not fee parameters (resource bounds, tip, paymaster data). In the paymaster flow this is irrelevant — the user does not pay gas, so fee manipulation by a relayer has no economic impact on the signer. This is an accepted tradeoff for paymaster compatibility. See AUDIT_RESPONSE_2.md — Finding #2 for the full analysis.
v32 addition: In the SNIP-9 path, the signature's valid_until is now bound to the stored session's valid_until, preventing a relayer from extending the expiration window (audit 3 finding #5).
- All Nethermind audit fixes applied — Audit 1 (#1-#4, #8-#10), Audit 2 (#1, #3), and Audit 3 (#1-#5)
- Self-call block — Sessions with empty whitelist cannot target account contract (eliminates entire privilege escalation class)
- Expanded blocklist — 7 admin selectors: upgrade, add/revoke session, execute, set_public_key, setPublicKey, execute_from_outside_v2
- SRC-5 registration —
ISessionKeyManagerinterface ID registered for paymaster/dApp discovery - valid_until binding — SNIP-9 path binds signature
valid_untilto stored session value - NatSpec documentation — Comprehensive doc comments on all public and security-critical functions
- Dead code removed, debug events removed, essential events only
- SpendingPolicyComponent — Per-token spending limits with per-call and rolling-window caps, proposed by @OmarEspejel / keep-starknet-strange in Issue #5, informed by starknet-agentic's implementation
- SessionKeyComponent extraction — Reusable component ANY wallet can embed (not just OZ accounts) via a single
HasAccountOwnertrait - 9-selector admin blocklist — Expanded from 7 to 9 with
set_spending_policyandremove_spending_policy - OZ v3.0.0 migration — Starknet 2.14.0, snforge_std 0.54.1
- 65 Cairo tests — 19 new spending policy tests
- 28/28 mainnet integration tests — Including 7 new spending policy tests
$ snforge test
Collected 65 test(s) from sessions_smart_contract package
Running 0 test(s) from src/
Running 65 test(s) from tests/
# Session Validation (21)
[PASS] test_session_validation::test_owner_signature_valid
[PASS] test_session_validation::test_session_signature_valid
[PASS] test_session_validation::test_session_expired
[PASS] test_session_validation::test_session_max_calls
[PASS] test_session_validation::test_session_not_allowed_selector
[PASS] test_session_validation::test_session_invalid_signature
[PASS] test_session_validation::test_session_revoke
[PASS] test_session_validation::test_session_with_no_restrictions
[PASS] test_session_validation::test_session_multiple_calls_in_transaction
[PASS] test_session_validation::test_add_session_unauthorized
[PASS] test_session_validation::test_revoke_session_unauthorized
[PASS] test_session_validation::test_session_events_emitted
[PASS] test_session_validation::test_upgrade_still_owner_only
[PASS] test_session_validation::test_invalid_signature_length_3_elements
[PASS] test_session_validation::test_empty_signature_fails
[PASS] test_session_validation::test_session_max_calls_zero_immediately_exhausted
[PASS] test_session_validation::test_double_revoke_same_session
[PASS] test_session_validation::test_update_session_resets_calls_used
[PASS] test_session_validation::test_multiple_concurrent_sessions_independent
[PASS] test_session_validation::test_signature_length_5_returns_zero
[PASS] test_session_validation::test_session_valid_at_exact_expiration_boundary
# Audit Fix Regressions (22)
[PASS] test_audit_fixes::test_audit1_execute_rejects_unauthorized_caller
[PASS] test_audit_fixes::test_audit1_execute_allows_self_caller
[PASS] test_audit_fixes::test_audit2_validate_blocks_disallowed_selector
[PASS] test_audit_fixes::test_audit3_session_blocked_from_upgrade
[PASS] test_audit_fixes::test_audit3_session_blocked_from_add_session
[PASS] test_audit_fixes::test_audit5_is_valid_signature_session_expired_returns_zero
[PASS] test_audit_fixes::test_audit6_is_valid_signature_does_not_consume_calls
[PASS] test_audit_fixes::test_audit7_execute_continues_after_failed_subcall
[PASS] test_audit_fixes::test_audit8_session_blocked_from_revoke
[PASS] test_audit_fixes::test_audit9_overflow_valid_until_returns_zero
[PASS] test_audit_fixes::test_audit9_is_valid_signature_overflow_returns_zero
[PASS] test_audit_fixes::test_audit10_update_session_clears_old_entrypoints
[PASS] test_audit_fixes::test_audit_new_session_blocked_from_execute
[PASS] test_audit_fixes::test_audit_new_execute_from_outside_invalid_signature_reverts
[PASS] test_audit_fixes::test_audit3_session_blocked_from_set_public_key
[PASS] test_audit_fixes::test_audit3_session_blocked_from_setPublicKey
[PASS] test_audit_fixes::test_audit3_session_blocked_from_execute_from_outside_v2
[PASS] test_audit_fixes::test_audit3_session_blocked_self_call_generic
[PASS] test_audit_fixes::test_audit3_session_allows_external_call_with_empty_whitelist
[PASS] test_audit_fixes::test_audit3_session_explicit_whitelist_allows_external
[PASS] test_audit_fixes::test_audit3_src5_supports_session_interface
[PASS] test_audit_fixes::test_audit3_src5_supports_unknown_returns_false
# SNIP-9 Compatibility (3)
[PASS] test_snip9_compatibility::test_snip9_version_returns_v2
[PASS] test_snip9_compatibility::test_contract_info_shows_snip9_compatible
[PASS] test_snip9_compatibility::test_session_keys_still_work_with_snip9
# Spending Policy (19)
[PASS] test_spending_policy::test_spending_policy_set_and_get
[PASS] test_spending_policy::test_spending_policy_unauthorized_set
[PASS] test_spending_policy::test_spending_policy_remove
[PASS] test_spending_policy::test_spending_policy_remove_unauthorized
[PASS] test_spending_policy::test_spending_policy_multiple_tokens
[PASS] test_spending_policy::test_spending_policy_no_policy_allows_all
[PASS] test_spending_policy::test_enforcement_exceeds_per_call
[PASS] test_spending_policy::test_enforcement_exceeds_window
[PASS] test_spending_policy::test_enforcement_within_limits
[PASS] test_spending_policy::test_enforcement_exactly_at_limit
[PASS] test_spending_policy::test_enforcement_window_auto_reset
[PASS] test_spending_policy::test_enforcement_no_policy_unrestricted
[PASS] test_spending_policy::test_enforcement_non_spending_selector
[PASS] test_spending_policy::test_enforcement_multicall_cumulative
[PASS] test_spending_policy::test_enforcement_multicall_exceeds_window
[PASS] test_spending_policy::test_enforcement_approve_tracked
[PASS] test_spending_policy::test_spending_enforcement_invalid_amount_calldata
[PASS] test_spending_policy::test_blocklist_rejects_set_spending_policy
[PASS] test_spending_policy::test_blocklist_rejects_remove_spending_policy
Tests: 65 passed, 0 failed, 0 ignored, 0 filtered out| Category | Tests | What It Covers |
|---|---|---|
| Session Validation | 21 | Add, revoke, expiry, limits, selectors, signatures, events, edge cases |
| Audit Fix Regressions | 22 | __execute__ caller check, admin blocklist, __execute__ nested bypass, whitelist enforcement, is_valid_signature read-only, non-atomic multicall, safe try_into(), stale entrypoint cleanup, SNIP-9 call ordering, set_public_key/setPublicKey blocklist, execute_from_outside_v2 blocklist, self-call block, SRC-5 interface registration |
| SNIP-9 Compatibility | 3 | Version checks, interface detection, session+SNIP-9 integration |
| Spending Policy | 19 | Set/get/remove policies, auth checks, multi-token, per-call limits, window cumulative limits, window reset, ERC-20 selector tracking (transfer, approve, increase_allowance), admin blocklist enforcement, per-session independence |
Scenario: Players make in-game moves without paying gas or confirming each action.
// Game creates session for player
await gameContract.createPlayerSession(playerAccount, {
validUntil: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
maxCalls: 1000, // Many moves allowed
allowedSelectors: [MAKE_MOVE, CLAIM_REWARD] // Only game functions
});
// Player signs moves with session key (instant, free)
const moveSignature = player.signWithSession(moveData);
// Game backend submits via paymaster (player pays nothing)
await paymaster.submitMove(playerAccount, moveData, moveSignature);Benefits:
- ⚡ Instant gameplay (no wallet popups)
- 💰 Free for players (game sponsors gas)
- 🔒 Limited to game functions only
Scenario: Yield optimizer executes strategies on user's behalf.
// User authorizes yield optimizer
await account.addSessionKey({
sessionKey: optimizerKey,
validUntil: oneWeekFromNow,
maxCalls: 100,
allowedSelectors: [DEPOSIT, WITHDRAW, HARVEST] // Only yield functions
});
// Optimizer executes when conditions are met
await paymaster.executeStrategy(userAccount, {
calls: [harvestCall, compoundCall],
signature: optimizerSignature
});Benefits:
- 🤖 Automated execution without user presence
- ⏰ Time-limited authorization
- 🎯 Function-restricted (can't drain wallet)
Scenario: Users post, like, and interact without wallet friction.
// User logs in and creates session
const session = await socialApp.createSession(userAccount, {
validUntil: Date.now() + 7 * 24 * 60 * 60 * 1000, // 1 week
maxCalls: 500,
allowedSelectors: [POST, LIKE, COMMENT, FOLLOW]
});
// Every interaction is instant and free
await socialApp.post("Hello Starknet!", session.signature);
await socialApp.like(postId, session.signature);Benefits:
- 🚀 Web2-like UX (no interruptions)
- 📊 High-frequency interactions
- 🔐 Can't transfer tokens or NFTs
Scenario: Mass distribution without requiring users to have gas.
// Event organizer sets up sponsored claims
for (const attendee of attendees) {
const claimSession = await createClaimSession(attendee);
// Send claim link (user needs no STRK)
await sendEmail(attendee, {
claimUrl: `https://event.com/claim/${claimSession.token}`
});
}
// User clicks link → backend sponsors claim
await paymaster.claimTicket(attendeeAccount, ticketId, sessionSignature);Benefits:
- 🎁 Zero-friction claiming
- 💵 Organizer pays all gas
- ⏳ Time-limited claim windows
Scenario: Monthly subscriptions without manual approval each time.
// User approves subscription
await account.addSessionKey({
sessionKey: subscriptionServiceKey,
validUntil: oneYearFromNow,
maxCalls: 12, // 12 monthly payments
allowedSelectors: [TRANSFER] // Only payment function
});
// Service charges monthly (user approved once)
await paymaster.chargeSubscription(userAccount, {
to: USDC_ADDRESS,
selector: TRANSFER,
calldata: [serviceAddress, subscriptionAmount]
});Benefits:
- 🔄 Automated recurring payments
- 🎯 Capped at specific amount/frequency
- ❌ Auto-expires after subscription period
# Install Scarb (Cairo compiler)
curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh
# Install Starknet Foundry
curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh
snfoundryup# Clone repository
git clone https://github.com/chipi-pay/sessions-smart-contract.git
cd sessions-smart-contract
# Build
scarb build
# Run tests
snforge test# Declare on mainnet
sncast declare --contract-name Account --network mainnet
# Deploy with your public key
sncast deploy \
--class-hash 0x0484bbd2404b3c7264bea271f7267d6d4004821ac7787a9eed7f472e79ef40d1 \
--constructor-calldata YOUR_PUBLIC_KEY \
--network mainnet// Add or update a session key (owner only)
fn add_or_update_session_key(
session_key: felt252, // Public key of session
valid_until: u64, // Expiration timestamp
max_calls: u32, // Maximum transactions
allowed_entrypoints: Array<felt252> // Whitelisted selectors (empty = all non-admin)
);
// Revoke a session key (owner only)
fn revoke_session_key(session_key: felt252);
// Query session data (public)
fn get_session_data(session_key: felt252) -> SessionData;// Execute calls on behalf of the account
fn execute_from_outside_v2(
outside_execution: OutsideExecution,
signature: Array<felt252>
) -> Array<Span<felt252>>;
// Check if nonce is available
fn is_valid_outside_execution_nonce(nonce: felt252) -> bool;
// Compute message hash for signing
fn get_outside_execution_message_hash_rev_1(
outside_execution: OutsideExecution
) -> felt252;| Type | Format | Use Case |
|---|---|---|
| Owner | [r, s] |
Full account control |
| Session | [session_pubkey, r, s, valid_until] |
Delegated access |
// Set a per-token spending policy for a session key (owner only)
fn set_spending_policy(
ref self: TContractState,
session_key: felt252,
token: ContractAddress,
max_per_call: u256,
max_per_window: u256,
window_seconds: u64,
);
// Query the spending policy for a session key + token pair
fn get_spending_policy(
self: @TContractState,
session_key: felt252,
token: ContractAddress,
) -> SpendingPolicy;
// Remove a spending policy (owner only)
fn remove_spending_policy(
ref self: TContractState,
session_key: felt252,
token: ContractAddress,
);The session key logic is extracted into reusable components that any wallet can embed — not just OpenZeppelin-based accounts.
SessionKeyComponent requires only a single-function HasAccountOwner trait:
pub trait HasAccountOwner<TContractState> {
fn assert_only_self(self: @TContractState);
}This trait has zero OpenZeppelin dependencies. Any wallet framework (Argent, Braavos, Cartridge, or custom) can implement it by delegating to their own owner check.
SpendingPolicyComponent is fully independent with its own storage. It tracks ERC-20 spending (transfer, approve, increase_allowance) with per-call and rolling-window cumulative caps.
Any wallet can embed session keys by following this pattern:
- Add
component!()declaration forSessionKeyComponent(and optionallySpendingPolicyComponent) - Implement
HasAccountOwnertrait — delegateassert_only_self()to your own owner check - Call
session_key.is_session_allowed_for_calls()in__validate__ - Call
session_key.consume_session_call()after signature verification - Optionally call
spending_policy.check_and_update_spending()in__execute__
Session keys are a reusable building block, not a monolithic contract other wallets must fork.
We are drafting a SNIP (Starknet Improvement Proposal) for session key interoperability. Every team building on Starknet — Argent, Braavos, Cartridge, and others — faces the same challenge: session key implementations are mutually incompatible, and paymasters must be forked to support each one.
This is not about imposing our approach. It is about starting a conversation. On-chain validation, off-chain guardians, and library-based proofs are all valid designs serving different priorities. A good standard should accommodate all of them.
- Research & ecosystem analysis: SNIP_RESEARCH.md
- Draft proposal: SNIP_DRAFT.md
We welcome feedback from wallet teams, paymaster operators, and dApp developers. If you have opinions on session key interoperability, please open an issue or reach out.
MIT License — see LICENSE for details.
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Write tests for new functionality
- Ensure all 65 tests pass
- Submit a pull request
- Issues: GitHub Issues
Built with ❤️ for the Starknet ecosystem