Own Your Keys, Own Your Voice, Own Your Money — Built in Three Days
The bet
Most “build an app with AI” stories end at a demo. This one ends with a stranger’s sats landing in a wallet, a relay surviving a real production incident, and a security bug found by reading a Go library’s source code at 2am and
forking it on the spot.
Three days. A working Nostr client, a self-hosted relay, and a Data Vending Machine selling Claude API access for Lightning payments — all live, all real, none of it staged.
Day one: the boring part, on purpose
The plan was deliberately unglamorous: a PWA client and a khatru-based relay, nothing else. No zaps, no DVMs, no design system — just kind:0 profiles, a kind:1 feed, and enough plumbing to prove the loop worked end to end.
The one real decision that mattered: non-custodial, permanently. No hosted signer, no bundled wallet, ever. Not because it was easier — bring-your-own-signer is harder to build than “just store the key” — but because the whole
point of Nostr is that nobody gets to hold your identity hostage, including us.
By the end of day one: a real feed, rendering real public content, publishing scoped to the local relay only so nothing typed during development could leak to the public network by accident. That single guardrail —
devPublishRelaySet, local-only, checked and re-checked before every feature that touched publishing — held for the entire build.
Day two: real signers, real money, real bugs
This is where it stopped being a toy.
NIP-46 and NIP-07 landed — a real Alby extension, a real bunker session, sign/publish/reload all verified against actual signer implementations, not mocks. Then NIP-57 zaps and NIP-47 wallet connect, hand-rolled because core
NDK doesn’t ship a wallet client — and the standing rule that’s held since the first line of that code: the AI builds and displays the invoice, the human pays it. Every time. No exceptions, enforced by what the code physically does,
not just a comment.
Then the DVM. A NIP-90 Data Vending Machine, selling real Claude API calls for real sats, priced per job from actual token cost rather than a flat fee — because a flat fee for a two-word question and a full paragraph felt like the
wrong kind of dishonest. Payment-gated with a hand-rolled NIP-47 client polling a Lightning invoice for settlement.
The first time real money moved through it — a real wallet, a real ~21-sat invoice, paid by a human’s own hand — the DVM logged processing 118 seconds after the invoice went out, then published a real answer. A hundred and eighteen
seconds is a person actually finding their phone and paying, not a demo timer. That gap in the log is the whole story in one line.
Day three: the internet finds you, and you have to be ready
The moment nostras.app went live and got linked from a landing page, the character of every problem changed. This is the day the project stopped debugging itself and started debugging the real world hitting it.
A relay-broadcast bug, found by reading khatru’s actual source rather than guessing: notifying subscribers of a new event was fully sequential and had a write-timeout field that was declared but never once applied. One slow connection
could stall delivery to every other listener on the relay. Fixed by forking the library — it’s public domain — patching the broadcast path to fan out concurrently under a bounded semaphore, and shipping it behind a proper rollback
plan.
Write a comment