Relay
The Relay module provides utilities for working with Nostr relay URLs — normalization, validation, and classification.
NIP-11 relay information documents are modeled by the Relay class in @welshman/domain, which owns the profile display (relay.display) and capability checks (relay.hasNegentropy, relay.hasNip). relay.displayUrl() delegates to displayRelayUrl below.
API
Types and Enums
// Relay operation modes
export enum RelayMode {
Read = "read",
Write = "write",
Search = "search",
Blocked = "blocked",
Messaging = "messaging"
}URL Validation
// Check if URL is a valid relay URL
export declare const isRelayUrl: (url: string) => boolean;
// Check if URL is an onion (Tor) address
export declare const isOnionUrl: (url: string) => boolean;
// Check if URL is a local address
export declare const isLocalUrl: (url: string) => boolean;
// Check if URL contains an IP address
export declare const isIPAddress: (url: string) => boolean;
// Check if URL is safe to share publicly
export declare const isShareableRelayUrl: (url: string) => boolean;URL Normalization
// Normalize relay URL to standard format
export declare const normalizeRelayUrl: (url: string) => string;
// Format URL for display (strip protocol + trailing slash)
export declare const displayRelayUrl: (url: string) => string;Relay Selection DSL
A RelaySelection is a declarative description of which relays an operation wants — not a list of urls. It names sources ("the author's outbox", "this pubkey's inbox", "the relays this event was seen on") that can only be turned into concrete urls in a context where the necessary data is available (relay lists, the tracker, the network to load a referenced event). Domain code produces selections from an event; a Resolver turns them into urls.
Route types
// A reference to an event we route relative to (e.g. to reach its author). Every
// field is optional and additive: a known `pubkey` lets us route directly; `id`
// or `kind`+`pubkey`+`identifier` let the resolver look the event up; `relays`
// are hints for that lookup and a last-resort routing fallback.
export type EventRef = {
id?: string;
pubkey?: string;
kind?: number;
identifier?: string;
relays?: string[];
};
export type RelayRoute =
// The current user's inbox (read), outbox (write), or messaging relays.
| {type: "userInbox"}
| {type: "userOutbox"}
| {type: "userMessaging"}
// A specific pubkey's inbox (read), outbox (write), or messaging relays.
| {type: "pubkeyInbox"; pubkey: string}
| {type: "pubkeyOutbox"; pubkey: string}
| {type: "pubkeyMessaging"; pubkey: string}
// The author of a referenced event — the resolver finds the event first.
| {type: "eventInbox"; ref: EventRef}
| {type: "eventOutbox"; ref: EventRef}
// The relays a given event was found on.
| {type: "seen"; ref: EventRef}
// A literal relay url (e.g. a hint embedded in a tag, or a group relay).
| {type: "relay"; url: string}
// Relays that index profiles/relay-lists.
| {type: "index"}
// Relays configured for full-text search.
| {type: "search"};
export type RelaySelection = {
route: RelayRoute;
weight: number;
};DSL constructors
Each constructor returns a RelaySelection (default weight = 1), except relays and inboxes, which return RelaySelection[] (spread them with ... into a route list).
export declare const inbox: (pubkey: string, weight?: number) => RelaySelection;
export declare const outbox: (pubkey: string, weight?: number) => RelaySelection;
export declare const messaging: (pubkey: string, weight?: number) => RelaySelection;
export declare const userInbox: (weight?: number) => RelaySelection;
export declare const userOutbox: (weight?: number) => RelaySelection;
export declare const userMessaging: (weight?: number) => RelaySelection;
export declare const eventInbox: (ref: EventRef, weight?: number) => RelaySelection;
export declare const eventOutbox: (ref: EventRef, weight?: number) => RelaySelection;
// Relays the given event was found on (its tracker relays plus any ref hints).
export declare const seen: (ref: EventRef, weight?: number) => RelaySelection;
// A literal relay url (renamed from the old `relayHint`).
export declare const relay: (url: string, weight?: number) => RelaySelection;
// One `relay` selection per url (renamed from the old `relayHints`).
export declare const relays: (urls: string[], weight?: number) => RelaySelection[];
// Inbox selections for a set of pubkeys (mentions/recipients); dedupes pubkeys.
export declare const inboxes: (pubkeys: string[], weight?: number) => RelaySelection[];
export declare const indexers: (weight?: number) => RelaySelection;
export declare const searchRelays: (weight?: number) => RelaySelection;Example — deliver the user's note to their own outbox plus every mentioned pubkey's inbox at half weight:
const selections = [userOutbox(), ...inboxes(mentionedPubkeys, 0.5)];RelayScenario
A RelayScenario scores a set of resolved, weighted relay sets and picks the best concrete urls, optionally topping up with fallback (default) relays.
// A concrete, resolved weighted relay set.
export type Selection = {
weight: number;
relays: string[];
};
// Filters to valid relay urls and normalizes them.
export declare const makeSelection: (relays: string[], weight?: number) => Selection;
// Fallback policies decide how many default relays to add.
export type FallbackPolicy = (count: number, limit: number) => number;
export declare const addNoFallbacks: FallbackPolicy; // never add fallbacks
export declare const addMinimalFallbacks: FallbackPolicy; // add 1 only if none found
export declare const addMaximalFallbacks: FallbackPolicy; // fill up to the limit
export type RelayScenarioOptions = {
policy?: FallbackPolicy;
limit?: number;
allowLocal?: boolean;
allowOnion?: boolean;
allowInsecure?: boolean;
getRelayQuality?: (url: string) => number;
getDefaultRelays?: () => string[];
};
export declare class RelayScenario {
constructor(selections: Selection[], options?: RelayScenarioOptions);
// Chainable — each returns a new scenario with merged options.
clone(options: RelayScenarioOptions): RelayScenario;
limit(limit: number): RelayScenario;
policy(policy: FallbackPolicy): RelayScenario;
allowLocal(allowLocal: boolean): RelayScenario;
allowOnion(allowOnion: boolean): RelayScenario;
allowInsecure(allowInsecure: boolean): RelayScenario;
getLimit(): number; // options.limit || 3
getPolicy(): FallbackPolicy; // options.policy || addNoFallbacks
// Accumulates weight per relay across selections (filtering onion/local/insecure
// ws:// unless allowed), scores by quality and weight with some randomness, takes
// the best `limit`, then tops up with shuffled default relays per the policy.
getUrls(): string[];
getUrl(): string | undefined; // first of getUrls()
}Resolver
A Resolver combines a single route-resolver function with bound scenario options. It dereferences each route to urls and builds scenarios from selections. This replaces the old standalone resolve() function.
export type ResolveRoute = (route: RelayRoute) => MaybeAsync<string[]>;
export declare class Resolver {
constructor(routeResolver: ResolveRoute, options?: RelayScenarioOptions);
// Resolve each route and build a scenario bound to this resolver's options.
scenario(selections: RelaySelection[]): Promise<RelayScenario>;
// Convenience wrappers.
relays(selections: RelaySelection[]): Promise<string[]>; // scenario(...).getUrls()
relay(selections: RelaySelection[]): Promise<string | undefined>; // scenario(...).getUrl()
}Example — build a resolver and route to a set of selections:
const resolver = new Resolver(resolveRoute, {
getRelayQuality,
getDefaultRelays,
});
const scenario = await resolver.scenario([userOutbox(), ...inboxes(pubkeys, 0.5)]);
const urls = scenario.limit(5).getUrls();
// Or skip the scenario for the common cases:
const urls2 = await resolver.relays([outbox(pubkey)]);
const url = await resolver.relay([relay(hintUrl), outbox(pubkey)]);In @welshman/app, the Router plugin exposes a Resolver (app.use(Router).resolver) built with getRelayQuality/getDefaultRelays from app config, and this is the resolver injected into every domain kind via Domain.configure.