feat: NIP-66 relay monitor integration#136
Merged
dergigi merged 18 commits intodergigi:masterfrom Mar 15, 2026
Merged
Conversation
Replace sequential for...of loop in searchByAnyTerms with Promise.allSettled so that each OR term is searched concurrently. Pre-resolve fallback relay set once before parallel execution to eliminate the memoization race in ensureFallbackRelaySet. Individual term failures are isolated and don't kill the batch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 3 serial for...of author resolution loops with Promise.all so that multiple by: tokens are resolved concurrently. Each token is individually wrapped in try/catch — decode failures return null and are filtered out, matching the previous skip-and-warn behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract measureRelayPing and measureAllRelayPings into a new src/lib/relayPing.ts module with injectable dependencies for testability. The key fix: each ping subscription now receives a relaySet scoped to the single target relay (NDKRelaySet.fromRelayUrls([relayUrl])), so the REQ is sent only to the relay being measured. Previously the subscription went to all connected relays, making ping measurements inaccurate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show first 50 results initially with a "Show more" button that loads 50 more at a time. Pagination resets only when the search query changes, not when results mutate (e.g. NIP-05 verified reordering), avoiding the infinite-reset loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The result deduplication in searchEvents used arr.findIndex(x => x.id === e.id) which is O(n) per element, yielding O(n^2) total. Replace with a Set<string> lookup for O(n). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NIP-51 specifies 'relay' tags for blocked relay lists (kind 10006) and search relay lists (kind 10007). The code was only checking for 'r' tags, which is the NIP-65 convention for kind 10002. Now checks for both 'relay' (per spec) and 'r' (for compatibility with mis-tagged events in the wild). Without this fix, user-configured blocked relays and search relays were silently ignored. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
filterNip50Relays was injecting fallback relays (primal, snort, ditto) without verifying they actually support NIP-50. This caused non-NIP-50 relays to receive search: filters and return extraneous results. Now runs checkNip50Support on each fallback candidate before adding it to the supported list, matching the verification applied to the primary relay set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Queries like 'language:en' were silently dropped because: 1. buildSearchQueryWithExtensions returned early on empty baseQuery 2. search.ts used 'cleanedQuery || undefined' which made '' falsy Now buildSearchQueryWithExtensions produces extension-only strings when extensions are present, and the caller always invokes it so that extension-only queries reach the relay as valid search filters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds mentions:<user> syntax to find events that mention a specific user via NIP-27 p-tags. Supports multiple mentions: tokens and combines with by: and free-text search terms. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Highlights matching search terms in note content with a blue background. Extracts terms from the query (excluding filter tokens like by:, kind:, mentions:) and wraps matches in <mark> elements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace raw nevent identifiers with resolved display names for reply parent authors. Uses e-tag[4] pubkey when available (NIP-10), falls back to fetching the parent event with relay hint from e-tag[2]. Includes module-level profile cache with TTL, in-flight deduplication, and IntersectionObserver for lazy resolution of off-screen notes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract resolveProfileName to shared profileUtils so NoteHeader, InlineNostrToken, and EventCard all use the same cached resolver. When default relays return no profile, retry against profile-specific relays (purplepag.es, search.nos.today, relay.nostr.band). Also fixes double npub:npub1... prefix in fallback display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 6s timeout to parent event fetch to prevent hanging - Try relay hint first, then fall back to default relays - Use relaySets.profileSearch() instead of hardcoded RELAYS.PROFILE_SEARCH to respect user's blocked relay preferences (NIP-51 kind 10006) - EventCard InlineAuthor now uses shared resolveProfileName for consistent caching and profile relay fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Configuration for NIP-66 relay liveness integration: - 30-min cache/refresh interval (monitors publish hourly) - 15s fetch timeout - 80% safety threshold to prevent catastrophic filtering - 24h max age for dead relay entries before degrading to unknown Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New module that fetches kind 30166 events from relay monitors to determine relay liveness, RTT, and NIP support. Key design decisions: - Asymmetric staleness: alive entries trusted indefinitely (false positive = just a timeout), dead entries expire after 24h and degrade to 'unknown' (false negative = missed results) - Safety valve: if filtering would remove >80% of relays, skip entirely to prevent catastrophic failure from bad monitor data - Multiple sources: queries known monitor relays + user's connected NDK pool relays for broader coverage - Persistent cache via localStorage with 30-min background refresh - Concurrent call coalescing to prevent duplicate fetches Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire NIP-66 data into the relay selection pipeline: - filterNip50Relays: pre-filter dead relays before probing, use NIP-66 N tags as fast path to skip HTTP NIP-11 probes - getNip50SearchRelaySet: enrich candidates with NIP-66-discovered NIP-50 relays, remove dead debug loop - getBroadRelaySet: filter dead relays from broad set - getOutboxSearchCapableRelays: pre-filter author's write relays before expensive NIP-11 probing - connect(): fire-and-forget ensureNip66Data() so cache is populated before first search - Relay monitoring lifecycle: start/stop NIP-66 background refresh All integration points use only cached data (synchronous lookups). NIP-66 never blocks search — if cache is empty, all functions fall through to existing behavior unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Display monitor data next to each relay in the UI: - Green [45ms] when monitor reports alive with RTT - Red [dead] when monitor reports dead - Nothing when no monitor data available Visual indicator only — no behavioral change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Self-contained benchmark script measuring NIP-66 relay liveness filtering impact across four phases: - Phase A: HTTP-only NIP-11 probing (3-endpoint, no NIP-66) - Phase B: NIP-66 pre-filtering + NIP-50 fast path - Phase C: actual NIP-50 search comparison (opt-in via --search) - Phase D: WebSocket connection timing with/without filtering Key findings from benchmark runs: - 51 NIP-50 relays discovered via NIP-66 beyond 7 hardcoded - 48% fewer HTTP probes (dead relay filtering + fast path) - +8% search event recall from extra NIP-50 relay discovery - 1 NIP-50 relay rescued per run (HTTP timeout, NIP-66 knows) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
|
@alltheseas is attempting to deploy a commit to the dergigi's projects Team on Vercel. A member of the Team first needs to authorize it. |
Contributor
Author
|
cc @dskvr |
6 tasks
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
dergigi
reviewed
Mar 15, 2026
Owner
dergigi
left a comment
There was a problem hiding this comment.
Reviewed the NIP-66 specific additions — clean integration.
Highlights:
- Asymmetric trust policy (include-by-default, only exclude when confident) is exactly right for relay filtering
- Safety valve at 80% threshold prevents catastrophic filtering on bad monitor data
- Dead entry TTL (24h → degrade to unknown) avoids permanent blacklisting
- Fast-path NIP-50 detection skips unnecessary HTTP probes
- Coalesced inflight fetches prevent duplicate requests
- Going from 7 hardcoded NIP-50 relays to 58 discovered is a big win
Benchmark numbers are compelling: 48% fewer HTTP probes, zero dead relays probed, +8% search events.
Merge order: #135 first, then this one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Ants hardcodes 7 NIP-50 relay URLs. NIP-66 monitor data reveals 51 more — 8x the search surface, found at zero latency cost (sync cache lookup). Dead relays get filtered before probing, cutting HTTP requests in half.
What it does
Fetches kind 30166 events from monitor relays at connect time. Three uses:
Ntags skip HTTP probesBenchmark (
npm run bench:nip66)Files (NIP-66 only)
src/lib/constants.tssrc/lib/nip66.tssrc/lib/ndk.tsensureNip66Data()on connectsrc/lib/relays.tsfilterDeadRelays+ NIP-50 fast pathsrc/lib/search/relayManagement.tsbench/nip66.tspackage.jsontsxdevDep,bench:nip66scriptFuture
When NDK PR #387 lands,
nip66MonitorRelaysin the NDK constructor will add ~45% WebSocket connection speedup on top. See #137.Test plan
npm run bench:nip66and--searchpass🤖 Generated with Claude Code