Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sandwichfarm/nostr-watch/llms.txt

Use this file to discover all available pages before exploring further.

@nostrwatch/nocap is the primary relay-probing engine in the nostr-watch monorepo. It checks Nostr relay health across five dimensions — WebSocket open/read/write connectivity, DNS resolution, TLS certificate validity, NIP-11 info document retrieval, and geolocation — using a pluggable adapter architecture that keeps check orchestration separate from check implementation. This means you can swap how DNS is resolved, how SSL is checked, or how WebSocket connections work without changing any application code.

Installation

npm install @nostrwatch/nocap
Node.js >=18 and pnpm >=9 are required. @nostrwatch/websocket must also be available as a peer dependency in your consuming package.

Quick start

import Nocap from '@nostrwatch/nocap'
import WebsocketAdapterDefault from '@nostrwatch/nocap/adapters/default/WebsocketAdapterDefault'
import DnsAdapterDefault from '@nostrwatch/nocap/adapters/default/DnsAdapterDefault'

const nocap = new Nocap('wss://relay.damus.io')
await nocap.useAdapters([WebsocketAdapterDefault, DnsAdapterDefault])

const result = await nocap.check(['open', 'read', 'dns'])
console.log(result)
// {
//   url: 'wss://relay.damus.io',
//   open: { data: true, duration: 120, status: 'success' },
//   read: { data: true, duration: 80, status: 'success' },
//   dns: { data: { ipv4: ['104.21.x.x'] }, duration: 40, status: 'success' }
// }
To run every supported check at once:
const result = await nocap.check('all')

Nocap class

The Nocap class handles check orchestration, result collection, and timeout management. Adapters supply the actual check implementations.
class Nocap {
  constructor(url: string, config?: object)
  useAdapter(Adapter: IAdapterConstructor): Promise<void>
  useAdapters(Adapters: IAdapterConstructor[] | Record<string, IAdapterConstructor>): Promise<void>
  check(keys: CheckKey | CheckKey[] | 'all'): Promise<IResult>
  checkAll(): Promise<IResult>
  static checksSupported(): CheckKey[]
  static translateCheckKeys(checks: string[]): string[]
}

Constructor

url
string
required
A valid wss:// or ws:// WebSocket URL for the relay to check.
config
object
Optional configuration. May include timeout settings and logging configuration.

Methods

useAdapter(Adapter) Registers a single adapter class. The adapter’s static type property determines which check dimension it handles. Throws if an adapter of that type has already been registered. useAdapters(Adapters) Registers multiple adapter classes in one call. Accepts either an array of adapter classes or a plain object mapping type names to adapter classes. Throws if any adapter type is already registered. check(keys) Runs one or more checks against the relay. keys can be a single check key string, an array of check key strings, or 'all' to run every supported check. Returns an IResult object. Rejects if a required adapter is missing for the requested check. Valid check key strings: 'open', 'read', 'write', 'dns', 'ssl', 'info', 'geo' checkAll() Convenience wrapper for check('all'). static checksSupported() Returns the full list of supported check keys: ['open', 'read', 'write', 'ssl', 'dns', 'geo', 'info']. static translateCheckKeys(checks) Translates check key aliases to their canonical form. For example, 'websocket' and 'ws' expand to ['open', 'read', 'write']; 'nip11' expands to ['info']; 'tls' expands to ['ssl'].

IResult interface

The object returned by check() has this shape:
interface IResult {
  url: string
  network?: string        // 'clearnet' | 'tor' | 'i2p'
  hostname?: string
  protocol?: string
  adapters?: string[]     // slugs of adapters used
  checked_at?: number     // unix timestamp ms
  checked_by?: string     // optional monitor identifier
  open?: IResultData
  read?: IResultData
  write?: IResultData
  dns?: IResultData
  ssl?: IResultData
  geo?: IResultData
  info?: IResultData
}

interface IResultData {
  data: boolean | object | null
  duration: number        // milliseconds; -1 if timed out or errored
  status?: string         // 'success' | 'error'
  message?: string        // error message if status is 'error'
}

IResult fields

url
string
required
The relay URL that was checked.
network
string
Network classification: 'clearnet', 'tor', or 'i2p'.
adapters
string[]
Slugs of the adapters used during this check run.
checked_at
number
Unix timestamp in milliseconds when the check was performed.
checked_by
string
Optional identifier for the monitoring node that ran the check.
open
IResultData
read
IResultData
WebSocket read check result. Same shape as open.
write
IResultData
WebSocket write check result. Same shape as open.
dns
IResultData
DNS resolution result. data contains { ipv4: string[] }.
ssl
IResultData
TLS certificate validation result. Node.js only.
geo
IResultData
Geolocation result. Depends on the dns check having run first.
info
IResultData
NIP-11 relay information document result.

AbstractAdapter base class

All custom adapters must extend AbstractAdapter:
abstract class AbstractAdapter implements IAdapter {
  static type: AdapterType             // one of: 'websocket'|'dns'|'geo'|'info'|'ssl'
  readonly slug: string                // human-readable adapter identifier
  protected _base: Base                // the Nocap instance this adapter belongs to
  get base(): Base                     // accessor for _base

  constructor(base: Base)
  abstract initialize(): void          // called automatically on construction
}
The static type property is the dispatch key — nocap reads it to determine which checks the adapter handles. The base getter exposes the parent Nocap instance, including base.finish() which resolves a check result.

Adapter types

Each adapter type maps to a specific set of check methods. Implement the interface that matches your adapter’s static type:
TypeInterfaceRequired methods
websocketIWebsocketAdaptercheck_open(), check_read(), check_write()
dnsIDnsAdaptercheck_dns()
infoIInfoAdaptercheck_info()
sslISslAdaptercheck_ssl()
geoIGeoAdaptercheck_geo()
All check methods have the signature (): Promise<void>. They do not return values directly — instead, they call this.base.finish(key, result) to resolve the check.

Custom adapter example

The following is a complete, minimal DNS adapter:
import {AbstractAdapter, type IAdapter, type AdapterType} from '@nostrwatch/nocap'

export class MyDnsAdapter extends AbstractAdapter implements IAdapter {
  static type: AdapterType = 'dns'
  readonly slug: string = 'MyDnsAdapter'

  constructor(base: any) {
    super(base)
  }

  initialize(): void {}

  async check_dns(): Promise<void> {
    // Perform your DNS lookup here
    const host = new URL(this.base.url).hostname
    const ipv4 = ['1.2.3.4'] // your actual lookup result

    // Call base.finish() to resolve the check.
    // First argument: the check key.
    // Second argument: IResultData with data, duration, and status.
    this.base.finish('dns', {
      data: {ipv4},
      duration: 50,
      status: 'success'
    })
  }
}
Register and use the custom adapter:
import Nocap from '@nostrwatch/nocap'
import {MyDnsAdapter} from './MyDnsAdapter'

const nocap = new Nocap('wss://relay.damus.io')
await nocap.useAdapter(MyDnsAdapter)

const result = await nocap.check('dns')
console.log(result.dns)
// { data: { ipv4: ['1.2.3.4'] }, duration: 50, status: 'success' }
You can also register adapters using an object notation:
await nocap.useAdapters({dns: MyDnsAdapter, websocket: MyWebsocketAdapter})

Default adapters

Five adapter implementations ship with nocap in libraries/nocap/adapters/default/. Use them as-is or as reference implementations for custom adapters:

WebsocketAdapterDefault

WebSocket open/read/write checks. Establishes a connection, sends a REQ and EVENT, and verifies protocol-level responses.

DnsAdapterDefault

DNS lookup via Cloudflare DNS-over-HTTPS (1.1.1.1). Handles both clearnet and IP-addressed relays.

GeoAdapterDefault

Geographic IP lookup. Depends on the DNS check result for the relay’s resolved IP address.

InfoAdapterDefault

Fetches the NIP-11 relay information document from the relay’s HTTP endpoint.

SslAdapterDefault

TLS certificate validation. Retrieves and validates the relay’s SSL certificate chain. Node.js only.

Known limitations

SSL check not available in browser. The ssl check uses Node.js TLS APIs and cannot run in a browser environment. If check('ssl') is called from a browser context, nocap logs a warning and skips the check rather than throwing.
Geo check depends on DNS. The geo check requires a resolved IP address from the dns check. If check('geo') is requested without 'dns' in the check list, nocap automatically prepends 'dns' as a dependency. The DNS result is excluded from the returned result if it was added automatically (controlled by the autoDepsIgnoredInResult config option).

@nostrwatch/auditor

NIP conformance testing; uses nocap WebSocket adapters for relay connectivity.

@nostrwatch/route66

Relay monitoring orchestration; uses nocap as its check engine.

@nostrwatch/nostrings

Relay URL sanitization and normalization used before connecting.

Build docs developers (and LLMs) love