6 · Session Keys
"Instead of a session, there's a key signing all my transactions — isn't that wrong?" No — that key is the session. This doc explains the sign-once-trade- many model, exactly how it's scoped and bounded, and why it's safe.
6.1 The problem session keys solve
Trading in the ER means signing a lot of transactions — every order, every cancel, a market maker quoting ~once a second. If each one popped a wallet approval dialog, high-frequency trading from a browser would be unusable. And pasting your main wallet's private key into a webpage to auto-sign would be reckless: that key controls all your funds.
Session keys square this circle: you approve once, which authorizes a fresh, disposable key to sign trading actions on your behalf, but only within strict limits. This is the standard "sign-once-trade-many" pattern.
6.2 How it works in Slipstream
The mechanism lives on the TradingCredit account (state/trading_credit.rs).
Two fields implement it:
pub session_authority: [u8; 32], // the authorized session pubkey (all-zero = none)
pub session_expiry: i64, // unix secs; key rejected once now >= this
The lifecycle:
- Authorize (one signature). The owner calls
authorize_session(0x1B), signing once with their real wallet. This stamps asession_authoritypubkey (a fresh keypair generated in the browser) and asession_expirytimestamp onto theirTradingCredit. - Trade (many, no prompts). The browser's session key signs
place_orderandcancel_orderdirectly against the ER — no wallet dialog per action. The program checks authorization viais_authorized_signer. - Expire / revoke. Once
now >= session_expiry, the key is automatically rejected. The owner can also overwrite or clear it.
The authorization check (TradingCredit::is_authorized_signer):
fn is_authorized_signer(&self, signer, now) -> bool {
if signer == &self.owner { return true; } // owner always allowed
let active = self.session_authority != [0u8;32] // a session exists
&& now < self.session_expiry; // and hasn't expired
active && signer == &self.session_authority // and it's THE session key
}
6.3 Why it's safe (the three bounds)
The session key is powerful enough to trade and nothing more. Three independent limits contain it:
Scoped to one TradingCredit. The key is authorized on a specific
TradingCreditfor a specific market. ATradingCreditholds only the margin you allocated to that market — not yourfree_collateral, not your positions, not the vault. Worst case, a leaked session key can only churn orders within that one credit's already-committed margin.Expiring.
session_expiryis a hard wall. After it passes the key is dead; no cleanup required for it to stop working.Signer-only, never owner. This is the subtle, important part, and it directly answers "isn't that key just acting as me?" — No. The session key may sign, but order and position attribution always stays with
owner.place_order,cancel_order, andreconcile_creditall attribute tocredit.owner, never to the signer. The session key cannot create positions owned by itself, cannot redirect funds to itself, cannot withdraw. It is an authorized signer, not a co-owner.
The comments in
trading_credit.rsstate this explicitly: "authorization is about who may SIGN — the order/position is always attributed toowner, never to the session key."
So the answer to the user's question — "there's a key signing all my transactions, isn't that instead of a session?" — is that this is exactly what a session key is: a temporary, scoped, expiring signer that acts for you within walls, so you don't sign every trade by hand and never expose your real wallet.
6.4 What a leaked session key can and can't do
| Action | Possible with a leaked session key? |
|---|---|
| Place/cancel orders within the scoped TradingCredit | Yes (until expiry) |
| Move funds to the attacker | No — actions attribute to owner; no withdraw authority |
Touch your free_collateral |
No — that's L1, never in scope |
Touch your open Positions |
No — positions are owner-attributed L1 state |
| Drain another market's credit | No — scoped to one TradingCredit |
| Act after expiry | No — rejected once now >= session_expiry |
The blast radius is "annoying order churn inside one market's pre-committed margin," bounded in time. Compare that to handing over your main wallet key (total loss). That gap is the whole value of the pattern.
6.5 Frontend integration
The browser generates the session keypair, prompts the wallet once to sign
authorize_session, then keeps the session key in memory to auto-sign ER orders.
Confirmation uses HTTP polling of signature status (lib/confirm.ts,
confirmSignature) rather than a websocket subscription — the order-placement
flow and use-session hook were switched to this because the web3.js
confirmTransaction websocket path hung against the RPC proxy.
6.6 Takeaways
- A session key = approve once, then a disposable browser key auto-signs trades — no per-order wallet prompts, no exposing your real key.
- Implemented as
session_authority+session_expiryonTradingCredit, authorized byauthorize_session(0x1B), checked byis_authorized_signer. - Safe because it's scoped (one credit/market, only committed margin),
expiring, and signer-only (everything attributes to
owner, never the key). - The key is the session — it isn't a replacement for one.
