Skip to content

v0.8.0-rc1

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 18 Jun 22:40
0617c92

GRAIN🌾 v0.8.0

A Nostr client — and the library it's built on
Release candidate · 6.18.2026
Full Changelog: v0.7.1...v0.8.0-rc1


v0.7 turned the relay into something you operate from a browser. v0.8 turns grain into a full Nostr client — an importable Go client library that speaks the outbox model, and a web client built on top of it that manages your profile, relays, media servers, and encryption, all signed with your own key.

The headline is client/core: a standalone, outbox-model client engine you can import into your own Go app. It owns a shared relay pool, resolves each user's relay lists, and routes every read and publish to the right relays under the gossip / outbox model — you read a user's notes from their outbox, and a reply you publish reaches the parent author's inbox. grain's own web frontend is the reference consumer: everything the UI does, it does through this library, so the client is both a usable app and a worked example of building on the engine.

Native NIP-44 encryption (v2 + v3), NIP-42 relay AUTH, NIP-65/17/51/37 relay lists, NIP-89 client tags, and Blossom + NIP-96 media all landed this cycle.


📦 The client library (client/core)

An importable, outbox-model Nostr client engine in pure Go — no cgo, no web/HTTP dependencies. A downstream app builds its own client on it instead of reimplementing relay routing.

  • Outbox routing, automatic. Name the intent and the engine picks the relays: reads come from a user's NIP-65 outbox, a published note reaches the author's outbox plus each mentioned recipient's inbox, and metadata resolves from the indexers. A leased connection pool keeps the routing additive — switching a session's relays adds connections rather than tearing the pool down.
  • A role model for relays. Every relay carries a Role bitmask — outbox / inbox / DM-inbox (per-target, from NIP-65 / NIP-17), search / blocked / favorite / private (the user's own NIP-51/37 lists), and locally-configured indexer / broadcast / trusted roles.
  • Streaming fetches. A multi-relay streaming primitive delivers events on a channel as each relay answers, de-duplicated by id — the basis for lazy-hydrating feeds.
  • Pluggable seams. Supply your own Signer (local key, or bring a NIP-46 / hardware signer), Logger (any *slog.Logger), and RelayListStore (default in-memory; plug a database for persistence). A read-only context needs no signer at all.
  • context.Context throughout. Both the read/fetch and publish paths take a context for caller-set deadlines and cancellation.
  • Fixed-relay opt-out. For a pinned single-/few-relay client, pin a fixed read/write set — which deliberately disables the outbox model. Off by default and discouraged.

The frontend's header shows a live x/y relays pool indicator, and /api/v1/client/status exposes the pool's stats.

For developers — the importable surface
import "github.com/0ceanslim/grain/client/core"

client := core.NewClient(core.DefaultConfig())          // owns the shared pool
signer, _ := core.NewEventSigner(privKeyHex)            // or your own Signer
uc := client.NewUserContext(signer.PublicKey(), core.WithSigner(signer))

notes := uc.FetchNotes(ctx, author, core.WithLimit(20)) // → author's outbox
reply, results, _ := uc.Reply(ctx, parent, "well said!") // → outbox ∪ parent's inbox
  • UserContext facade with intent methods (FetchNotes / StreamNotes / Reply / Publish / SignAndPublish).
  • Inspectable routing — ask the engine which relays it would use without performing the operation.
  • The full surface is documented in docs/client-library-guide.md, with compile-checked examples in client/core/example_test.go that run in CI, so the public API can't drift without breaking the build.
  • Architecture + rationale: the outbox relay-pool design doc. Tracked under #56 and #77.

👤 Your profile, edited in place

The profile page is now a real editor for your own kind-0 metadata.

  • Click into your name, bio, picture, banner, or any field and edit in place — no separate form. An Advanced editor exposes the raw content fields and drag-reorderable tags.
  • Edits are signed and published with your key, routed to the indexers (so anyone can find your metadata) plus your own relays.
  • A live publish toast counts up per-relay acceptances (NIP-20 OK) as each relay answers — and surfaces signer errors instead of failing silently.

📡 Relay management

A new relay-management surface in settings, plus a browser for every relay grain knows about.

  • Per-category relay lists — outbox/inbox (NIP-65), DM (NIP-17), and the NIP-51/37 lists — each editable and published with one signed event. A fixed-relay override is there for pinned clients.
  • Known-relays browser — browse every relay the engine has seen, with live status, NIP-11 info on expand (name, software, supported NIPs, auth/payment flags), and a "sort: fastest" TCP-latency ping. Stage any relay straight into one of your lists.
  • Add-relay autocomplete — every add-relay input completes from the known set as you type.
  • Login hydration warms the relay-list and media caches at sign-in, so settings render instantly instead of resolving cold.

🖼️ Media servers & uploads

Manage your media servers and upload straight from the client.

  • Resolve and edit your Blossom (kind 10063) and legacy NIP-96 (kind 10096) server lists from settings.
  • A reusable upload module with a pre-upload modal: pick your primary server, mirror to others with per-server checkboxes, preview the file, and see an ephemeral-storage warning before you commit. Wired into both the profile editor and the admin image fields.
  • Uploads are signed client-side — sha256 + a Blossom (BUD-01) or NIP-96 authorization, signed with your key — and stream a live per-server progress toast.

🔐 Encryption — NIP-44 (v2 + v3)

Native NIP-44 conversation encryption, validated against the official test vectors.

  • v2 is the deployed standard and the default; v3 (the in-progress draft, opt-in) binds the event kind and a scope into the MAC.
  • This is the primitive behind private relay lists: a NIP-37 (10013) / encrypted NIP-51 list now has a decrypt button that reveals its private entries on demand, with your signer — grain itself never sees the plaintext.

🛡️ Relay AUTH — NIP-42

The client now answers relay AUTH challenges with your signer.

  • Relays that issue a NIP-42 challenge show up in a "relays requesting AUTH" list; one click signs a kind-22242 event and authenticates you for the session. Authenticated relays stay trusted until you remove them, and a fresh challenge re-prompts automatically.

🏷️ Client tags — NIP-89

  • Published events are stamped with a ["client", "grain"] tag, and any foreign client tag is stripped first so a re-published event never leaks another app's attribution. There's a user-facing opt-out slider, plus admin defaults.

🏠 Live homepage feed

  • The homepage streams events hitting this relay live over a direct WebSocket — author avatar + name, readable kind label, relative time, and a snippet for notes. Newest sort to the top; scroll to lazy-load older events. Click through to the event or the author's profile.
  • Built on a new SSE push channel (relay → browser) and a live-sync own-event subscription, so your own actions reflect on the page as they happen.

🆕 New-user onboarding

  • Sign in with a brand-new key and the profile page opens as an empty, editable profile (instead of an error), a permanent "set up relays" banner guides you to configure your relays, and you can publish your first metadata immediately.

📚 Docs & dashboard

  • The API docs at /api/docs now cover the client endpoints, and "Try it out" signs its NIP-98 auth with your connected signer — no pasting tokens.
  • The admin dashboard gained a client-config section (grain_updateclient), and Relay information split into its own section with Operations moved to the top.
For operators — upgrade notes
  • No config migration is required; all client-library seams default to today's behavior (in-memory caches, grain's own logging).
  • The OpenAPI spec is generated from swag annotations at build time and embedded via //go:embed; the build seeds a placeholder so generation is self-contained.
  • This is a release candidate — please file anything you hit before the final v0.8.0.

Issues: #56 · #74 · #77 · #80 · #83 · #87 · #90 · #98 · #99 · #100 · #101