Skip to content

Concepts ​

The lifecycle: base layer → ephemeral rollup → base layer ​

Every solsocket room moves through three phases:

  1. Create + delegate (base layer, one wallet signature). createRoom / joinOrCreate builds the room and presence accounts and delegates them to a MagicBlock Ephemeral Rollup — all in a single transaction.
  2. Play (ephemeral rollup). While delegated, all writes — presence broadcasts, events, shared state — execute on the ER: ~50ms slots, zero fees, signed by your session key. Subscriptions are processed-commitment websockets straight from the ER, so delivery is push, not confirm-polling.
  3. Commit + undelegate (base layer). leave() commits your presence slot back; closeToBase() (creator only) commits the room. The session was ephemeral — the outcome is ordinary Solana state anyone can read or compose with.

Rooms and presence are just accounts ​

The on-chain program (solsocket-engine) owns two account types, both PDAs:

  • Room — creator, id, max players, a size-capped state blob, and a write sequence number.
  • Presence — one per player per room: your session authority and your size-capped presence blob.

Because they're ordinary accounts, listRooms() is just getProgramAccounts with discriminator filters, and any explorer can show your room's final state.

Session keys ​

At connect, solsocket loads or creates a keypair in localStorage (loadOrCreateSession). Joining a room registers it as your presence slot's authority; from then on it signs every realtime write — so there are no wallet popups after join.

Session keys never hold or move funds — ER transactions are fee-free, so the key needs no SOL, ever. Your real wallet signs only the base-layer create/join/close transactions. If localStorage is wiped, joinRoom detects the lost session and rotates in a fresh key with one wallet-signed recovery transaction.

The three channels, and codecs ​

State, presence, and messages are independent channels with independent codecs (Room<TState, TPresence, TMessage>):

ChannelWriteReadOn chain as
Shared statesetStategetState / onStateChangeRoom account data
PresencebroadcastonPresencePresence account data
MessagesemitonMessageTransaction logs (no state write)

Codecs are pluggable: JSON by default, structCodec for compact binary structs (a full game avatar fits in ~20 bytes), rawCodec for apps that manage their own bytes.

Presence helpers ​

Raw broadcasts arrive at whatever rate peers send them (10Hz is typical). Two helpers turn that into something render-ready:

  • trackPresence(room, { onJoin, onUpdate, onLeave }) — a roster with staleness sweeping. Presence slots stay onchain when a tab closes without leave(); this keeps ghost avatars out of your world.
  • smoothPresence(room, render) — entity interpolation: remote players render ~120ms in the past and lerp between samples, so 10Hz broadcasts look like 60fps movement.

Regions ​

Devnet ERs run in three regions — asia (default), eu, us. Pick the one closest to your players. Delegation pins a room to the region it was created on, so every client joining that room must connect with the same region; encode it in your invite links.

Reconnection ​

  • Same session (page refresh): rejoin resumes in ~1ms with zero base-layer transactions — the presence slot and session key are still valid.
  • Lost session (cleared storage, new device): joinRoom recovers with one wallet-signed transaction that rotates the session authority, then waits for the ER to pick up the re-delegation before letting writes through.

MIT Licensed