Markdown source for agents: https://npub1d70emggs6jzun5lhvqnfqsd9reqmaarn2qjf6q3r02gryyl4v8sqjn44xe.nsite.lol/docs/core/03-how-to-develop.md

Nostr Agent Onboarding · start here · index · source: Nostr-exp/core/03-how-to-develop.md · snapshot 2026-10-10

Paths such as ~/Documents/…, ~/Production Environment/…, repos/… and services on localhost refer to the author's workstation and are not available to you — read them as worked examples of a setup you can recreate.

How to develop for Nostr

Part 3 of the core synthesis: the practitioner's layer — what to build, how to build it, and the responsibilities that come with shipping. Sources: Building Nostr chs. 2–5, nostrdesign.org, the NIPs repo norms, and the working practices proven in the sibling nostr-dev workspace.

Build pieces, not platforms

The single most repeated piece of advice across fiatjaf, Hodlbod, and nostrdesign.org: build a small thing that does one thing well. A client that does everything is fighting three wars at once (routing, UX, moderation) and will lose to the composability of the ecosystem around it. Because every app shares the same identity and data layer, your single-purpose app gets the whole network's users for free — a long-form reader, a group chat, a git forge, a calendar, a marketplace, each speaking events other apps also speak.

Corollaries:

If you're building a business on Nostr

fiatjaf's playbook ("How to do curation and businesses on Nostr"): resist the tempting version — a closed platform that reuses Nostr identities, siphons the open network, imprisons content behind an API, and runs a clever algorithm on top. Even when it succeeds it enshrines you as a platform, with a platform's ads, legal exposure, and capture dynamics. Instead: (1) ship an interoperable, ideally open-source niche client with documented kinds; (2) put the secret sauce — curation, validation, search, AI, moderation — in a relay you run and charge for, and make it your client's default. Your relay is your website; the social layer (follows, comments) still rides the outbox model, so users keep portable identities and graphs that don't depend on you — which is exactly why they can afford to trust you. "You don't own the network, you're just competing against other websites on a leveled playing field."

One sober caveat for web apps: a web client is chained to a domain and its owner's server, so it "can't ever be trusted as an installed client can." Until content-addressed app distribution matures, encourage users to keep a second client configured — interchangeability is the mitigation.

The development loop

  1. Model the events first. Kind, class, tags, content shape — this is your schema and your API, and it outlives your app. Work through 04-how-to-design.md before writing code.
  2. Check the registry and the wild. Read the NIPs kind table; probe live relays for the kinds you're considering (nak req -k <kind> -l 5 ...) — plenty of kinds are occupied but undocumented. Reuse before inventing.
  3. Prototype against a local relay. A throwaway relay (nostr-rs-relay, a Khatru scaffold, or a container) gives you deterministic tests without spamming the public network. Publish to public relays only what is meant to be public — signed events are forever.
  4. Then test against the real network, because the real network has spam, malformed events, dead relays, and 30-second-old data — and your handling of those is the product. Two acceptance checks from the No Solutions school: the flight-mode test (does it work offline against a local relay, syncing later?) and the paradigm test — if you found yourself wrapping Nostr in a REST API with server-held state, stop: "you have to lean into the Nostr paradigm to get all this stuff for free… building REST APIs around Nostr is breaking the coolest part" (Pablo Fernandez). Nostr done right is less to build: authentication, storage, identity, and inter-process communication come with the protocol.
  5. Document your kinds in your repo (kind, class, tag schema, content shape, routing heuristic, why). If you want interop, open a NIP PR once two implementations exist.

Tooling that carries this loop

(Reference environment: the ~/Documents/nostr-dev workspace, which has all of this installed and a live local relay/GRASP/Blossom stack.)

Onboarding: teach the paradigm, don't hide it

The prevailing client instinct — hide keys, relays, and events behind "accounts" and "services" to reduce friction — has a serious dissent, argued hardest by Constant: if Nostr fades into the background, you have rebuilt the platform world. "If you give users in your app an 'account' because that is easier, they will end up making another 'account' in the next app… we hardly differentiated from where we came from." The counter-program:

This doesn't excuse hostile UX — it sets the target: low-friction paths to a correct mental model, not low-friction paths around it.

Keys and signing: never touch the nsec

The private key is the user's whole identity, unrotatable today. The bar:

Fault tolerance: the reading and writing discipline

Reading (from Building Nostr ch. 2 — "a light touch"):

Writing — the prime directive: never destroy data you didn't understand. The classic Nostr bug family is one client clobbering another's data in a shared replaceable event (relay hints in kind 3 content, muted words in kind 10000, encrypted fields dropped on update). The pattern that prevents it: parse the event into your model keeping the original event attached, and on save, mutate the original — preserving unknown tags and content — rather than regenerating from your model:

readProfile  = e => ({...decodeJson(e.content), e})
writeProfile = ({e, ...p}) => ({kind: 0, content: encodeJson(p), tags: e.tags})

Routing is part of correctness, not an optimization: implement the heuristics from 02-how-it-should-work.md for every kind you emit, dedup by id, treat every relay as unreliable, and re-publish user data when their relay selections change.

Ship the ecosystem, not just the app

Anti-patterns (each observed repeatedly in the wild)

Definition of done, Nostr edition

Your thing is done when:

  1. Its event kinds are documented (schema + class + routing) somewhere public.
  2. It signs via NIP-07/46/55 and has never seen an nsec.
  3. It reads via the correct heuristics and survives dead/lying/slow relays.
  4. It writes to the correct heuristics and preserves data it doesn't own.
  5. Another client can consume its events from the spec alone — and at least one does, or could.
  6. It hands unknown kinds off via NIP-89 instead of dead-ending.
  7. Its users can leave it without losing anything. Credible exit from your own app is the final acceptance test.