drjones f097721304 feat(lightning): deposits, withdrawals, and solvency behind a node interface
The node is an interface, so the code where money is actually at risk is
tested against a fake that can be made to fail, stall, or lie. Plugging
in Alby Hub is configuration, not new code.

Crediting a deposit is idempotent by payment hash: a node reporting the
same settlement twice must not mint money. Withdrawals debit before they
pay, because a payment that succeeds while the ledger write fails loses
money permanently, whereas the reverse is recoverable. Withdrawals above
a threshold wait for a human, which bounds what a stolen session token
can remove.

17 tests including concurrent settlement, concurrent double-spend,
concurrent processors, failed payment refunds, fee caps, and solvency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:23:51 +00:00

Quantum Arcade

A private physics arcade for one Linux box and the people on your network. Drop-in crash rounds, instant scratch tickets, a real double-entry ledger, and outcomes any player can verify on their own phone.

What it is

Three shared crash games — a rocket fighting gravity, a decaying orbit, and a stacking tower — plus instant scratch tickets to play between rounds. Set an auto cash-out target before the round and it closes at exactly your number, or ride it and tap out by hand. Identity is a keypair your browser generates; there is no account, no email, and no password.

The house edge is 1% on everything. That is better than essentially anything commercial, and it is deliberate: this is a game among friends, not a revenue stream.

Everything is API-first — the browser client uses the same endpoints anyone else can. Write a bot in twenty lines; see docs/API.md.

Everything runs on one machine: one Go binary with the client embedded, PostgreSQL, and Redis.

Running it

docker compose up -d

Then open http://<your-box>:8080 from any phone on the network.

To play with test funds before Lightning is wired up:

ARCADE_DEV_FAUCET=1 docker compose up -d

The faucet mints through the same ledger path a real deposit uses, so the code under test is the production code. Leave it off otherwise.

Development

docker compose up -d postgres
go test ./...
go run ./cmd/arcade

End-to-end tests need a live server:

ARCADE_DEV_FAUCET=1 go run ./cmd/arcade &
ARCADE_E2E=http://localhost:8080 go test ./cmd/arcade/ -v

How fairness works

Before betting opens, the server generates a random seed and publishes SHA-256(seed). It is now committed and cannot change its mind.

The client seed is built from the public keys of everyone who joined the round. The operator does not choose who plays, so it cannot steer the outcome even knowing its own seed.

The crash point is HMAC-SHA256(serverSeed, clientSeed || nonce), run through the simulation. After settlement the seed is published, and the Verify tab recomputes the whole chain in your browser — it asks the server only for the published values, never for a verdict.

Scratch tickets use the identical pipeline, and their odds tables are generated from the same data structure that produces outcomes, so the published odds cannot drift from reality. A test asserts observed frequencies and empirical return against the published figures across two million plays.

Architecture

One binary, with enforced internal boundaries:

Package Responsibility
pkg/fixed Q32.32 fixed-point arithmetic; no floats, so results are identical everywhere
pkg/sim Deterministic RNG and the crash curve
pkg/fair Commit-reveal protocol and verification proofs
pkg/ledger Append-only double-entry accounting
pkg/scratch Scratch tickets and their published odds
pkg/identity Keypair sign-in via signed challenge
pkg/room Round lifecycle, auto cash-out, live broadcast

Nine services on one machine would buy latency and 3am debugging, so this is one process. Modules talk through interfaces only; extracting one into its own service later is a transport change, not a rewrite.

Ledger invariants

Enforced in the application and again by database constraints and triggers:

  • every transaction's postings sum to exactly zero
  • no account may go negative, except the Lightning bridge, whose negative balance is by definition what is owed to players
  • rows are never updated or deleted; corrections are compensating entries

GET /api/health sums every account. It must return zero. Anything else means the books are corrupt.

Round timing

The multiplier follows m(t) = 1/(1 - t/T)², which diverges at exactly 60 seconds. No round can run longer, however extreme the crash point, and the climb visibly accelerates as it goes — which is where the tension comes from.

Testing

make test        # unit and integration
make test-race   # under the race detector
make cover       # coverage per package
make db-reset    # wipe the ledger; it is append-only and accumulates

Measured capacity: 25,000 concurrent connections on one 4-core instance, zero failures, 586MB RSS. Bet throughput is the real ceiling at ~230/sec — see docs/SCALING.md.

Coverage sits around 85%, and the tests found and pinned three real money bugs: a posting set that minted 18 quintillion millisatoshis by wrapping the zero-sum check, a crash point that overflowed negative at the rarest seed, and scratch tables that advertised 98% while paying 56%.

Scaling

The app is stateless: clone the VM and boot it. An instance generates its own identity, finds its peers through Redis, and campaigns for the games it will drive. Exactly one instance runs a given game's rounds; the rest relay its frames and forward bets to it.

An instance dying is not a special case — its lease expires and a survivor takes over. Measured at six seconds, unattended, after a kill -9.

Clone the app VM only. PostgreSQL and Redis stay on one shared machine; cloning those gives every instance its own ledger and they share nothing. Full topology in docs/SCALING.md.

Status

Built and tested:

  • fixed-point deterministic core, ledger, commit-reveal fairness
  • three crash games with live multiplayer rounds
  • two scratch tickets with verified-honest odds
  • keypair identity, peer-to-peer transfers, transaction history
  • auto cash-out targets that pay your exact number
  • in-browser verifier
  • a documented public API, good enough to write bots against

Not yet built:

  • Lightning deposits and withdrawals. The bridge account and ledger paths exist; the node integration does not. The dev faucet stands in for now.
  • Tournaments and scheduled events
  • Operator dashboard
  • In-memory bet reservation, which is what would lift the ~230 bets/sec ceiling

Scope

This is built to run on a private network among people who know each other. It is not hardened for, and should not be exposed to, the public internet. Doing so would make it a public real-money gambling service, which carries licensing, KYC, and AML obligations this codebase does not address.

Description
best
Readme AGPL-3.0 1.8 MiB
Languages
Go 69.6%
JavaScript 17.3%
CSS 4.4%
HTML 4.2%
Shell 3.4%
Other 1%