Appearance
Sessions — sign once, play all night
Wallet popups are where on-chain games go to die. A dice game that interrupts you with a signature request every roll isn't a game — it's a DMV queue. Sessions fix this completely:
ts
import { vrand } from "@vrand.io/web";
const session = await vrand.startSession({ lamports: 0.05e9 }); // ONE signature
// ── from here on: zero popups ──
await session.randomInt(1, 6); // roll
await session.randomInt(1, 6); // roll again
await session.randoms(50); // a whole tournament bracket
await session.draw(); // shuffle + deal, one request
await session.end(); // change swept back to the walletOne signature funds the session. Everything after is instant — no prompts, no interruptions, no friction — until the budget runs out or you end it.
What actually happens
startSession creates a local session key in the page and moves your chosen budget to it with a single wallet signature. That key becomes the payer: it signs every request locally, instantly. Your main wallet is never asked again.
Nothing else changes. Every request still pays its normal fee, still gets its own on-chain proof, and is still verified locally before your code sees the value. Sessions remove the prompting, not the guarantees.
Why it's safe
The session key is a hot key in the page holding only what you funded — that bound is the security model. Compromise of the page risks the session's remaining budget, nothing more: your main wallet delegated nothing and signed nothing beyond one transfer. Fund it like an arcade card, not like a bank account.
end()reclaims the rent from every finished request, sweeps the remaining balance back to your wallet, and forgets the key.- Disconnecting the wallet ends the session automatically — walking away can never strand funds.
- Closed the tab mid-game? Nothing is lost. The key persists in
localStorage; the nextstartSessionfor the same wallet recovers it and its balance — and if it's still funded, resuming costs zero signatures. Resumes report themselves:session.recoveredLamports/session.toppedUpLamports.
Budget math
A request costs the flat network fee plus ~0.004 SOL of reclaimable rent that comes back at end(). So a 0.05 SOL session comfortably runs ~10 requests in flight at a time and many more over its life as rent recycles — and end() returns everything unspent. For a long game night, 0.1e9 is generous.
ts
const session = await vrand.startSession({ lamports: 0.1e9 });
console.log(await session.balance()); // lamports left to play withThe game-loop pattern
ts
let session = await vrand.startSession({ lamports: 0.05e9 });
async function onPlayerRoll() {
const roll = await session.randomInt(1, 6); // no popup, ~2-4s, proven
animateDice(roll);
}
window.addEventListener("beforeunload", () => { /* nothing needed —
the key persists; next visit resumes the session */ });Every roll is a real on-chain request with a real VRF proof your player could audit — at the interaction cost of Math.random() plus a couple of seconds.
API
| Call | What it does |
|---|---|
vrand.startSession({ lamports, persist? }) | one signature; funds (or resumes) the session key |
session.random() / randomInt / pick / shuffle / coinFlip / weighted / draw() / randoms(n) | everything the main client does — prompt-free |
session.balance() | lamports remaining |
session.sessionKey | the session key's address |
session.recoveredLamports / toppedUpLamports | how a resume was funded |
session.end() | reclaim rent, sweep back, forget the key |
Full option details: Web SDK reference.