Glasshouse — the public audit log
Every state-changing action on Nextbridge is appended to a public, cryptographically-signed, hash-chained log. Anyone can fetch it, anyone can verify it, no one can edit it without breaking the chain.
Overview
Glasshouse is a forward-secure audit log. Each entry contains the hash of the previous entry, so a single retroactive edit anywhere in the chain breaks every entry that followed. Entries are signed with HMAC-SHA256 by Nextbridge so you can also prove an entry actually came from us.
The live feed and history are at /glasshouse. API surface below.
Entry shape
seq— gap-free auto-increment.actorHash— sha256(userId | deploy-salt); same hash for the same user across the deploy, opaque externally.data— canonical-JSON payload, PII-sanitized.prevHash— entryHash of seq-1, or 64 zeros for the genesis entry.entryHash— sha256 over the canonical preimage{seq, ts, kind, actorHash, data, prevHash}.signature— HMAC-SHA256(GLASSHOUSE_SIGNING_SECRET, entryHash).
Verifying the chain
Two equivalent options. Run the verifier server-side via GET /api/glasshouse/verify, or do it yourself with this ~30 line snippet.
Streaming new entries
Open GET /api/glasshouse/stream as a Server-Sent Events connection. Backfill from a known seq via ?since=N. The server emits event: ready once with the current head, then event: entry per new entry. Heartbeats every 30 seconds as comments to keep proxies alive. Connections self-close after 5 minutes; reconnect.
Privacy model
The point of Glasshouse is transparency without identity. User identifiers are hashed with a per-deploy salt — the same user has the same actorHash across all their entries (so you can prove your own activity if you want), but an observer can't reverse the hash back to a user.
Sensitive payload fields are stripped at the emit point: on-chain tx hashes (deanonymise deposit addresses), destination withdrawal addresses, raw chat message content, and 2FA codes never enter the log. The schema permits PII; the emitting code does not.
Entry kinds
P2P lifecycle kinds (p2p.trade.*, p2p.dispute.opened) are reserved in the schema but not yet emitted. They land alongside the matching webhook fan-out.