Skip to content

Make Sprout a standard-compliant Nostr relay alongside its enterprise extensions #265

@tlongwell-block

Description

@tlongwell-block

Summary

Sprout uses the Nostr wire protocol (NIP-01) and extends it with enterprise features: NIP-29 relay-based groups, workflows, canvases, forums, agent harness, and audit logging. However, standard Nostr clients (Damus, Amethyst, Coracle) cannot function normally because Sprout rejects most standard event kinds via a hard allowlist in required_scope_for_kind().

PRs #245, #246, and #249 begin addressing this by adding kind:1 (text notes), kind:3 (follow lists), kind:30023 (long-form articles), NIP-16 replaceable event routing, and NIP-33 parameterized replaceable event support. This issue tracks the remaining work to make Sprout a standard-compliant Nostr relay that also has its enterprise/agent collaboration extensions.

The goal: Sprout should be a superset — a good Nostr relay that standard clients can connect to, plus all the Slack-replacement and agent features that make it unique.


What standard clients need

Research into Damus, Amethyst, and Coracle source code shows these clients:

  • On login, fetch kinds 0, 3, 10002, 10050, 10000, 10006, 10009, 10013, 30315 (profiles, follows, relay lists, DM relay lists, mute lists, blocked relays, group lists, interests, user status)
  • For home feeds, query with multi-author filters (up to 500 authors per batch) for kinds 1, 6, 7, 30023
  • For threads/replies, query with #e tag filters referencing parent events
  • For zap counts, query kind:9735 with #e filters referencing the zapped event
  • For DMs, use NIP-17 gift wraps (kind:1059, already supported) plus kind:13 seals
  • All three clients fully support NIP-42 authentication

Remaining work (two PRs)

PR 4: Accept standard social kinds

Open the kind allowlist for the standard kinds that clients publish and query.

Regular kinds:

  • kind:6 (NIP-18 reposts) — in every client's home feed filter
  • kind:16 (NIP-18 generic reposts)
  • kind:9734 / kind:9735 (NIP-57 zap request / zap receipt) — relay role is passive (just store events; the LNURL server handles Lightning)
  • kind:13 (NIP-59 seal) — completes the NIP-17 DM flow alongside kind:1059 gift wraps. Must be #p-gated on REQ to prevent metadata leakage (same treatment as kind:1059).

NIP-16 replaceable range (10000–19999) — open the entire range:
PR #245 already routes is_replaceable() kinds through atomic replace_addressable_event() with stale-write protection. The allowlist is the only remaining gate. Key kinds unlocked:

  • kind:10000 (NIP-51 mute list)
  • kind:10002 (NIP-65 relay list metadata — critical for Amethyst's outbox model)
  • kind:10050 (NIP-17 DM relay list)
  • kind:10009 (NIP-51 group list)
  • All other NIP-51 list kinds

NIP-33 parameterized replaceable range (30000–39999) — open the range with NIP-29 carve-out:
PR #246 already routes is_parameterized_replaceable() kinds through atomic replace_parameterized_event(). Open the range with an explicit carve-out for NIP-29 group-state kinds (39000–39003) which remain channel-scoped. Key kinds unlocked:

  • kind:30315 (NIP-38 user status — Amethyst fetches on login)
  • kind:30078 (NIP-78 app-specific data)

Estimated effort: Small (~50 LOC, 1–2 days). The storage infrastructure is already built by PRs #245 and #246.

PR 5: REQ query pushdown (ids, multi-author, #e)

Fix silent data loss in standard client fetch patterns. The current REQ handler pushes only kinds, single author, since/until, #p, and #d into SQL. Everything else is post-filtered after a capped LIMIT query, which means events that exist in the database are silently not returned.

ids pushdown:
Clients fetch specific events by ID for thread root resolution, quote resolution, and profile lookups. The idx_events_id index exists but is never used by the query builder. Push ids into SQL as AND id IN (...).

Multi-author pushdown:
Clients send follow-feed queries with up to 500 authors. Currently only single-author is pushed to SQL — multi-author falls through to post-filter, consuming the entire LIMIT window with events from non-followed authors. Push multi-author into SQL as AND pubkey IN (...). For >500 authors, chunk into multiple SQL queries and merge results (no lossy fallback).

#e tag pushdown:
Clients fetch replies and zap counts via #e tag filters. Currently post-filtered — under volume, replies to older events and zap receipts are invisible. Add an event_references denormalized table (same pattern as existing event_mentions for #p tags) with a join-based query path.

Estimated effort: Medium (schema change + ~330 LOC, 1–2 weeks).


After these two PRs

Sprout becomes a standard-compliant authenticated Nostr relay with meaningful client interoperability: core social kinds, NIP-16 replaceables, NIP-33 parameterized replaceables, NIP-17 private DMs, NIP-29 relay-based groups, NIP-57 zap event storage, and correct REQ behavior for ids, multi-author feeds, and #e thread/zap lookups — alongside all existing enterprise features (workflows, canvases, forums, agent harness, audit logging, full-text search).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions