nostr-client

A nostr client built as independent, swappable parts. Vanilla JavaScript, no build step, no bundler, no framework. One repo, one thing — eleven clients, a built-in bitcoin wallet, and a theme system, all from the same components. Everything served from GitHub Pages. AGPL.

Eleven clients, zero builds

Composed clients are just pages that arrange parts — so they come in grades (new here? join nostr in 60 seconds):

gradeclientcharacter
minimalzen read only — serif, moss, world & circle, conversations. Nothing else.
long-formreader a quiet magazine for NIP-23 articles
dailymicro post, reply, react, follow, mentions, search, profile, relays
fullmonty the full monty — relay explorer, in-app profiles, hashtags, on-chain tips
replicaskyline bluesky-flavored — same parts wearing app.bsky's outfit 🦋
replicabirdsite twitter/X-flavored — For you, trends, the blue 𝕏
replicadittly mastodon/ditto-flavored — columns and the elephant 🐘
replicabookface facebook-flavored — friends, cards, what's on your mind
replicajumbly jumble-flavored — browse relays like channels 🪩
walletpurse the little bitcoin wallet that lives in your nostr key 👛
replicabtcnostr bchnostr-flavored, for bitcoin — built-in testnet wallet, on-chain tips ₿

Same components, different HTML files. zen's entire "fork" is two CSS custom properties.

It already works — this is live

independent components from separate repos, one shared relay pool

Principles

The parts

repoone thingdemo
pool relay pool — subscribe/list/get/publish, dedup, reconnect live feed
nip19 display encoding — npub/note at the UI edge only converter
login <nostr-login> — NIP-07 / guest keys, NIP-98 fetch login + NIP-98
did did:nostr identity documents, 3-tier resolution resolver
did-nostr whois for nostr — DID + profile + bitcoin address + walkable graph explorer
verify event id + schnorr verification; wraps any pool forgery test
theme design tokens + ten named themes — reskin every client theme gallery
feed <nostr-feed> — live notes, batched profile resolution global feed
note <nostr-note> — one note card + shared safe rendering note
thread <nostr-thread> — NIP-10 conversations, live reply tree thread
composer <nostr-composer> — publish kind 1 with per-relay acks composer
reactions <nostr-reactions> — likes & reposts, lazy counts reactions
contacts follow button + kind-3 list editor, preserves metadata contacts
notifications <nostr-notifications> — mentions/replies/reactions to you notifications
profile <nostr-profile> — profile card for any pubkey profile page
profile-editor <nostr-profile-editor> — edit kind 0, preserves unknown fields editor
relay-manager <nostr-relay-manager> — page relays + NIP-65 lists relays
search <nostr-search> — NIP-50 full-text search search
nip46 remote signing via nsec bunker (experimental) bunker
cache IndexedDB event cache — wraps any pool instant reload
guestbook comments for any website, stored on nostr (NIP-22) comment
groups mailing lists on nostr — NIP-72 communities, gmail-style reader groups
chat IRC-style rooms on nostr (NIP-28), live chat
chess real-time chess — the moves are signed events play
ameba digital organisms — typed bot messaging, encrypted channels, host-independent memory 🦠 petri dish
wallet in-browser bitcoin wallet + instant tip button (testnet) wallet
tip <nostr-tip> — on-chain bitcoin tips, testnet-first ₿ tip
article <nostr-article> — NIP-23 long-form, safe markdown article
embed put nostr on any website — copy-paste snippets generator
pulse the live heartbeat of nostr — no backend pulse
reader composed client, long-form grade — a quiet magazine reader
zen composed client, minimal grade — read only zen
micro composed client, daily grade — the essentials micro
monty composed client, full grade — everything monty
skyline composed client, replica grade — bluesky-flavored skyline
starter join nostr in 60 seconds — key, profile, follows, hello start

Quick start

A working nostr page, from nothing, no toolchain:

<script type="module" src="https://nostr-client.github.io/login/login.js"></script>
<script type="module" src="https://nostr-client.github.io/feed/feed.js"></script>
<script type="module" src="https://nostr-client.github.io/composer/composer.js"></script>

<nostr-login></nostr-login>
<nostr-composer></nostr-composer>
<nostr-feed limit="20"></nostr-feed>

The contract

Parts interoperate through five small conventions — see CONTRACT.md:

1. hex is the primitive          64-char hex pubkeys/ids everywhere; NIP-19 is display-only
2. the signer                    window.nostrSigner = { type, getPublicKey(), signEvent(evt) }
                                 announced via 'nostr:login' / 'nostr:logout' on window
3. the pool                      defaultPool() — one websocket per relay per page,
                                 however many components you compose
4. events                        components emit bubbling 'nostr:*' CustomEvents
5. swapping                      absolute-URL ES module imports — override any of them
                                 with an import map, or inject .pool / .signer properties

Identity

This org itself has a nostr identity: agent.did.json — a did:nostr document, because a nostr public key is already a W3C-resolvable identifier.