Skip to content

CEP-17 relay list metadata discoverability (client-side) #80

@harsh04044

Description

@harsh04044

Summary

CEP-17 lets clients dynamically discover which relays a server uses by fetching kind:10002 events (NIP-65). Instead of hardcoding relay URLs, a client only needs the server's pubkey and can resolve operational relays automatically.

Server-side publishing is already done (PRs #77, #78). This issue tracks the remaining client-side discovery work.

Spec: contextvm-docs/src/content/docs/spec/ceps/cep-17.md
TS SDK reference: server-relay-discovery.ts, relay-resolution.ts, server-identity.ts


Server-side publishing (done)

  • RELAY_LIST_METADATA_KIND = 10002 constant
  • DEFAULT_BOOTSTRAP_RELAY_URLS (6 hardcoded bootstrap relays)
  • tags::RELAY = "r" tag constant
  • publish_relay_list() on AnnouncementManager (kind 10002 with ["r", url] tags)
  • get_advertised_relay_urls() / get_discoverability_publish_relay_urls() with dedup
  • is_local_relay_url() helper
  • Smart bootstrap detection: skip defaults when all advertised relays are local
  • Config: relay_list_urls, bootstrap_relay_urls, publish_relay_list, profile_metadata
  • publish_profile_metadata() (kind 0)
  • Relay list + profile published unconditionally (not gated on is_announced_server)
  • 14 tests

PR 1: Identity parsing, relay list fetching, and selection

Standalone building blocks, all independently testable. No transport wiring yet.

RelayPoolTrait extension:

  • Add fetch_events(filters, timeout) to RelayPoolTrait
  • RelayPool impl via nostr-sdk Client::fetch_events()
  • MockRelayPool impl with internal event buffer + inject_event() helper

Server identity parsing (server_identity.rs):

  • parse_server_identity() accepting hex, npub, nprofile
  • Extract relay hints from nprofile
  • Store hinted_relay_urls on NostrClientTransport
  • Replace PublicKey::from_hex() with parse_server_identity() in constructors

Relay list fetching and selection (server_relay_discovery.rs):

  • RelayListEntry type (url + optional marker)
  • select_operational_relay_urls(): prefer unmarked, fall back to read+write union, deduplicate
  • fetch_server_relay_list(): temporary pool, kind 10002 filter, pick latest by created_at, extract r tags

Tests (~16):

  • Identity parsing: hex, npub, nprofile with/without relays, invalid input
  • Selection: unmarked preferred, mixed markers, read+write union, empty, dedup, empty string filtering
  • Fetching: mock returns entries, no events, multiple events picks latest, marker extraction

PR 2: Multi-stage relay resolution + transport integration

Wires PR 1 into a resolution pipeline and integrates into start().

Config additions:

  • discovery_relay_urls and fallback_operational_relay_urls on NostrClientTransportConfig
  • Builder methods for both
  • Default discovery_relay_urls to DEFAULT_BOOTSTRAP_RELAY_URLS
  • Change default relay_urls from vec!["wss://relay.damus.io"] to vec![]

Relay resolution (relay_resolution.rs):

  • RelayResolutionConfig struct
  • connect_fallback_operational_relays(): probe with temporary pool, return URLs or empty
  • resolve_operational_relays() with full priority chain:
    • Configured relays -> return immediately
    • Hinted relays (nprofile) -> return
    • No discovery relays -> return fallback or empty
    • Race discovery vs fallback via tokio::select!
    • Sequential fallback if race winner was empty
    • Last resort: use discovery relays as operational

Transport integration:

  • Call resolve_operational_relays() in start() before connect()
  • Use resolved URLs for connection when non-empty

Tests (~13):

  • Resolution: configured, hinted, discovery fetch, discovery wins, fallback wins, both empty, no discovery + fallback, no discovery + no fallback
  • Integration: builder overrides, publish-then-discover roundtrip, nprofile hint e2e, marker precedence e2e

Docs:

  • Update README.md CEP-17 status

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions