Heads-up limit hold'em against a bot, for sats, where every hand settles the moment it ends and nobody takes a cut. Test coins only for now (txbt4, the BLAKE2b testnet4): worth nothing, which is the point while the rails are new.
flowchart LR
subgraph you["Your browser"]
K[Nostr key = your account]
P[The table page]
end
subgraph relays["Nostr relays"]
L[(Ledger · kind 30333<br/>signed by the operator)]
R[(Requests · kind 3700<br/>join · transfer · withdraw)]
end
subgraph op["The operator (anyone can run one)"]
O[Operator key]
D[Deposit addresses<br/>one per account, derived<br/>from operator key + your did]
end
C[(txbt4 chain)]
P -- reads --> L
P -- signs & publishes --> R
R -- applied every minute --> O
O -- republishes --> L
C -- deposits seen, credited --> O
O -- withdrawals paid --> C
K -. signs .-> P
D --- C
Three small things, each of which you could run yourself:
- Your account is a Nostr key. Sign in with a NIP-07 extension or a key the page makes for you. Your
did:nostr:<pubkey>is the account; there is no signup. - The money is a Web Ledger run by a teller. An operator publishes a small signed JSON document (a Nostr event, kind 30333) listing every account's balance. Each account has its own deposit address, derived from the operator's key and your did, so the page shows you yours without asking anyone. Pay it from any wallet; the operator credits it after one confirmation.
- A hand is a signed transfer. When a hand ends, the loser signs a Nostr event (kind 3700: "transfer N sats to
<winner>") and publishes it. The operator applies it and republishes the ledger. That's the whole settlement: a signature, no fee, no block, no rake. Your page applies it locally the instant it's signed, so the next hand deals at once.
sequenceDiagram
participant You
participant Page as Table page
participant Relays
participant Op as Operator (every minute)
You->>Page: play the hand
Page->>Page: hand ends: loser owes winner N sats
Page->>Page: loser's key signs "transfer N to winner" (kind 3700)
Page->>Page: applied to the local copy at once · next hand deals
Page--)Relays: publish (retried until a relay has it)
Relays->>Op: the transfer
Op->>Op: apply, republish the ledger (kind 30333)
Relays->>Page: newest ledger · the hand is checked off
Withdrawals are the same shape: you sign "withdraw N to <address>", the operator pays it from the deposits in one real transaction, and the ledger shows the debit. The operator for this table pays automatically, within a minute, up to 100,000 sats a go.
- Not trustless. The operator holds the deposits and writes the ledger. What's provable is that it can't invent a hand you didn't sign, and that the book must equal deposits minus payouts; what's trusted is that it pays out. Same trust as a poker club's cashier, with a public ledger.
- Not private. Anyone with the operator's key and the ledger hash can compute every deposit address.
- Not real money yet. txbt4 sats. Mainnet or Lightning on the same rails is a decision, not a technical gap.
- This demo table's bot key is in the link. The bot is an account like any other, and the page needs its key to sign the bot's losses, so it travels in the URL fragment. Anyone with the link could play the bot's money or withdraw it. That's fine for a demo and will move to a house-run bot before it's anything else.
Open the link, press bank, make a key (or use your extension), copy your deposit address, send it some txbt4 from Reef (a node and wallet in a tab; the page links straight into its Send form) and press Join. When the block lands you're credited and can sit. Stakes 1/2 to 100/200, buy-in 100 big blinds. The bank panel lists your withdrawals with their state until they're paid.
Everything is a static page or a script, pinned by commit, AGPL:
- The teller — https://github.com/solidpayorg/teller:
lib/teller.mjs(the rules),lib/client.mjs(how a page uses one: balances, deposit addresses, requests signed and applied at once, published apart with retry),bin/operator.mjs(the automatic operator: applies hands, credits deposits, pays withdrawals under caps, every minute). Make a ledger on https://solidpayorg.github.io/teller/, run the operator with your key, done. - The table — https://github.com/libre-poker/sats: imports the main table's engine and the teller's client, adds no protocol of its own. Give it
#ledger=<your hash>&bot=<a key you funded>. - The spec — https://webledgers.org/ (the ledger document) and solidpayorg/webledgers#7 (the teller proposal). Keys follow https://did-nostr.com/ ; the deposit tweak is a plain addition on the full point (sidestr/spec
keys.mjs), x-only only at the taproot output.
Anything that can hold a Nostr key can add deposits, withdrawals and transfers with the client and a ledger hash — a tip jar, a paywall, another game. Poker is the first flower.