Files
amethyst/nestsClient/specs/EGG-01.md
T
Claude aadb347b84 feat(nests): adopt deployed nostrnests schema (streaming/auth/live + paired 10112)
The deployed nostrnests reference (NestsUI v2 + moq-auth + moq-rs)
emits a different on-the-wire schema than the previous EGG-01 / EGG-09
drafts and Quartz writers. Verified by reading the production
NestsUI bundle. The deployed schema is now canonical:

kind:30312 (room event)
  - relay URL → ["streaming", url]   (was ["endpoint", url])
  - auth URL  → ["auth",      url]   (was ["service",  url])
  - live status → ["status", "live"]   (was "open")
  - ended status → ["status", "ended"] (was "closed")
  - room name → ["title", name]        (was ["room", name])

kind:10112 (user MoQ-server list)
  - 3-element entries: ["server", relay, auth]
  - first-element name "relay" accepted as a synonym (legacy)
  - 2-element entries: derive auth host by replacing leading "moq."
    with "moq-auth.", or prepending "moq-auth." otherwise

Implementation
- ServiceUrlTag.TAG_NAME = "auth", LEGACY_TAG_NAME = "service"
- EndpointUrlTag.TAG_NAME = "streaming", LEGACY_TAG_NAME = "endpoint"
- StatusTag enum: PLANNED/LIVE/PRIVATE/ENDED with code "live"/"ended";
  legacy "open"/"closed" still parsed on read
- NestsServersEvent: emit/read 3-element [server, relay, auth] tags
  with the legacy-shape tolerances above; expose a NestsServer pair
- Account.nestsServers.flow now produces List<NestsServer> pairs
- NestsServersScreen: rewritten edit-field asks for both URLs,
  recommended row shows the nostrnests pair, list rows show both URLs
- NestsScreen first-time setup writes the nostrnests pair, not a
  single URL; gate the create FAB on both URLs being parseable
- CreateNestViewModel: drop the resolveServerPair hardcoded mapping —
  the saved pair is now authoritative; defaults stay correct for an
  empty kind-10112 list

Specs
- EGG-01 rewritten to canonical streaming/auth/live/ended with a
  legacy-spelling table; example uses the real nostrnests pair
- EGG-02 references "auth" tag throughout; error taxonomy says "ended"
- EGG-09 rewritten to 3-element server tag with derivation fallback
- New nestsClient/specs/nip-53-proposed.md — a single self-contained
  proposed update to upstream NIP-53 covering kind 30312 + kind 10112
  with the deployed schema and the JWT-mint flow

All existing unit tests adjusted; quartz:jvmTest, amethyst:testPlayDebugUnitTest,
and nestsClient:jvmTest pass.
2026-04-29 19:32:53 +00:00

7.5 KiB

EGG-01: Room event (kind:30312)

status: draft requires: NIP-01 category: required

Summary

A nests audio room is a NIP-53 addressable event of kind:30312. The author is the host. Subscribers read this event to learn who runs the room, where the audio plane lives, who else is invited, and whether the room is currently live.

Every other EGG layers on top of this event.

Wire format

{
  "kind": 30312,
  "pubkey": "<host pubkey hex>",
  "tags": [
    ["d", "<room id>"],
    ["title", "<room name>"],
    ["summary", "<one-line description>"],
    ["image", "<optional cover image URL>"],
    ["status", "live" | "private" | "ended" | "planned"],
    ["streaming", "<https URL of moq-relay WebTransport>"],
    ["auth",      "<https URL of moq-auth sidecar>"],
    ["relays", "<wss relay 1>", "<wss relay 2>", ...],   // ONE tag, multiple values
    ["p", "<pubkey>", "<relay hint>", "host" | "admin" | "speaker"],
    ...
  ],
  "content": "",
  ...
}

The d tag is the room id; (pubkey, kind, d) is the addressable identity.

The relays tag is encoded as a SINGLE tag whose first element is "relays" and whose remaining elements are wss URLs. Implementers MUST NOT emit one ["relays", url] tag per relay; receivers MUST treat such input as a malformed event and MAY repair it by concatenating the values, but publishers that emit it are non-conformant.

Tag-name back-compat

Earlier drafts of this EGG used room for the room name, service for the auth URL, endpoint for the relay URL, and open / closed for the live / ended states. Those names are still emitted by some legacy clients. Receivers MUST accept all of:

Canonical (this EGG) Legacy (older drafts)
title room
streaming endpoint
auth service
status: live status: open
status: ended status: closed

When a single event carries both spellings (e.g. streaming AND endpoint), the canonical name wins; the legacy name is treated as a duplicate. Publishers MUST emit only the canonical names.

Behavior

  1. Hosts MUST emit exactly one kind:30312 event per room. Updating the room (rename, status change, role grants) MUST re-publish the same d tag with a higher created_at.
  2. The title, status, streaming, and auth tags MUST be present. A client receiving an event without all four MUST treat the room as un-joinable.
  3. The auth value MUST be the base URL of an EGG-02 auth sidecar (do not include the /auth suffix). Receivers MUST normalize the value by stripping a single trailing / before constructing sub-paths. The auth sidecar speaks HTTP/1.1 or HTTP/2 over TCP/TLS — it MUST be reachable on a port that accepts TCP (typically 443).
  4. The streaming value MUST be the base URL of a WebTransport-capable moq-relay implementing EGG-03. Because WebTransport runs over QUIC, the streaming host's port may listen on UDP only — clients MUST NOT use it for the JWT-mint POST. The two URLs are separate fields precisely so they can live on different hosts/ports (the public nostrnests deployment does: streaming = https://moq.nostrnests.com:4443, auth = https://moq-auth.nostrnests.com).
  5. The p tag MUST list the host as the first participant. Additional p tags grant roles (EGG-07).
  6. Status semantics:
    • live — room is in progress, anyone can join. The auth sidecar MUST mint a listener token to any well-formed NIP-98 request.
    • private — room is live, but the auth sidecar applies an out-of-band allowlist. The allowlist mechanism is implementation-defined; this spec only mandates that a non-allowlisted requester receives 403 (see EGG-02 error taxonomy). Clients without a path to acquire access MUST render private rooms as un-joinable rather than attempt to connect blind.
    • ended — room is over, audio plane SHOULD be torn down server-side.
    • planned — room hasn't started; see EGG-08.
  7. A host MAY treat a room as auto-ended after 8 h of created_at staleness even if the published status is still live/private. Receivers SHOULD do the same to avoid stale rooms hanging at the top of the live UI.
  8. An empty content field is REQUIRED. Future EGGs MAY define structured content; until then, peers MUST ignore non-empty content rather than rejecting the event.
  9. The d tag MUST contain only characters from [A-Za-z0-9._-]. Colons, slashes, whitespace, and percent-encoded sequences are forbidden because the d is interpolated unescaped into the moq-auth namespace nests/<kind>:<host pubkey hex>:<d> (EGG-02). Receivers MUST reject events whose d violates this charset.
  10. Relay discovery. Hosts SHOULD publish the kind:30312 event to (a) every relay listed in the event's own relays tag, AND (b) their NIP-65 (kind:10002) write relays. Receivers SHOULD subscribe to the same union to track room metadata changes (rename, status flip, role grants).
  11. Tie-break on identical created_at. When two kind:30312 events share (pubkey, d) AND created_at, receivers MUST keep the event with the lexicographically SMALLEST id and discard the other (per NIP-01 replaceable-event tie-break).
  12. Publish-before-mint ordering. A peer MUST NOT request a JWT (EGG-02) for a room before that room's kind:30312 event has propagated to relays the auth sidecar can read. The auth sidecar looks up the most-recent kind:30312 by (pubkey, kind, d) to validate the room exists and to enforce status-based gates (EGG-08 rule 5). For hosts: this means publishing the freshly- composed event BEFORE minting their own publish: true token. For listeners: this means waiting until the event is visible on the listener's own relay set (an unknown room cannot be joined). Auth sidecars that cannot find the room MUST return HTTP 410 unknown_room (EGG-02 error taxonomy); peers SHOULD retry with 1 s / 2 s / 4 s exponential backoff before surfacing the error.
  13. Streaming / auth selection (host-side guidance). When composing a new room, hosts SHOULD pre-fill streaming and auth from the FIRST entry of their own kind:10112 user server list (EGG-09 — each entry already carries the pair). When the host has no kind:10112, the client MAY ship a built-in default pair but MUST allow user override. Both streaming and auth MUST be https:// URLs; http:// MUST be rejected at compose time (mirrors EGG-09 rule 1). The two URLs MAY point at the same host:port for community deployments that genuinely co-locate both services, but they are independent fields and clients MUST NOT assume they share an authority.

Example

{
  "kind": 30312,
  "pubkey": "abc...host",
  "created_at": 1714003200,
  "tags": [
    ["d", "office-hours-2026-04"],
    ["title", "Office Hours"],
    ["summary", "Weekly Q&A"],
    ["status", "live"],
    ["streaming", "https://moq.nostrnests.com:4443"],
    ["auth",      "https://moq-auth.nostrnests.com"],
    ["p", "abc...host", "wss://relay.example", "host"],
    ["p", "def...co",   "wss://relay.example", "speaker"]
  ],
  "content": "",
  "id": "...",
  "sig": "..."
}

Compatibility

Peers without EGG-01 cannot interpret any other EGG. EGG-04, EGG-05, EGG-06, EGG-07, and EGG-12 reference rooms by the (kind, pubkey, d) tuple defined here.