block / block/buzz

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

Open
#265 0 comments 2 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## 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

- Plans: `PLANS/SPROUT_PR4_STANDARD_SOCIAL_KINDS.md`, `PLANS/SPROUT_PR5_REQ_QUERY_PUSHDOWN.md`
- Prerequisite PRs: #245, #246, #249
- NIP specs: [NIP-16](https://github.com/nostr-protocol/nips/blob/master/16.md), [NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md), [NIP-51](https://github.com/nostr-protocol/nips/blob/master/51.md), [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md), [NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.