Changelog

What's new.

Latest upgrades, newest first. Generated from changesets on every release; currently at v3.5.0.

nostream

3.5.0

Minor Changes

  • #823 82287d4 Thanks @Ferryx349! - feat(website): add Astro marketing site with landing film, docs, changelog and community pages

    Adds website/site (nostream.dev) and updates the landing hero animation to sync NIP count and branding from the repo. Docs, changelog, version label and NIP list are read from the repository at build time.

  • #830 f0bcdcc Thanks @Priyanshubhartistm! - feat(nip77): advertise NIP-77 in NIP-11 supported_nips while it is active and expose its settings in the admin console

  • #818 7409006 Thanks @Priyanshubhartistm! - feat(nip77): serve NEG-OPEN, NEG-MSG and NEG-CLOSE negentropy sync for the PostgreSQL event store

3.4.1

Patch Changes

  • #825 48fe9c4 Thanks @chappie-daemon! - fix(settings): validate info.relay_url is a usable absolute URL

    validateSettings() now rejects a relay_url that is not a valid absolute URL, parses with an empty host (scheme-only strings like localhost:8008), or uses a protocol other than ws/wss/http/https. Previously an invalid value started cleanly and then broke every HTTP request with 500 TypeError: Invalid URL, including the Admin Console, leaving the bad setting fixable only by editing the file by hand. Found in #824.

3.4.0

Minor Changes

  • #821 36d2a30 Thanks @Ferryx349! - feat(nip-01): send the rate-limited backoff hint on CLOSED

    When the per-IP message rate limit trips on a REQ or COUNT, the relay now replies ["CLOSED", <id>, "rate-limited: slow down", "<ms>"] with the tripped window’s period as the backoff, instead of a NOTICE. Other message types keep the existing NOTICE.

  • #768 8a5600b Thanks @Ferryx349! - docs(deploy): add deploy runbook and single-relay recreate script

  • #809 4cf419f Thanks @Priyanshubhartistm! - feat(nip77): add negentropy set-reconciliation core and a sorted event id query

Patch Changes

3.3.0

Minor Changes

  • #806 0304b8a Thanks @Anshumancanrock! - feat(storage): add an eventStore.backend setting and build every event repository through one factory

    Defaults to postgres, the only backend for now. The backend is read once at startup and passed to every worker.

  • #799 75d2a91 Thanks @Ferryx349! - feat(admin): add NIP-11 vs probe mismatch warnings on Network Health

    Closes #798

  • #807 f7d4b76 Thanks @Ferryx349! - NIP-01: include an optional backoff (ms) as the fifth element of rate-limited OK messages

  • #796 e861beb Thanks @Ferryx349! - feat(nip66): add read/write RTT probes, NIP-42 detection, mirror targets, and enriched kind 30166 tags

    Extends the relay monitor with WebSocket read/write probes, optional NIP-42 auth detection, probing of mirroring.static[] peers, and richer NIP-66 discovery and monitor announcement events.

    Closes #794

3.2.0

Minor Changes

  • #777 3ea816c Thanks @Ferryx349! - feat(deploy): add HAProxy blue/green compose stack

    Two relays behind HAProxy with /readyz health checks and option redispatch, plus a rolling recreate script that replaces one relay at a time for zero-downtime image updates. Optional Redis stream fan-out (RELAY_BROADCAST_FANOUT) lets both relays share live WebSocket broadcasts while workers keep using cluster process.send.

  • #788 ce368e8 Thanks @Priyanshubhartistm! - feat: execute hide action for actionable NIP-56 reports

    Adds nip56.hideActionableReports (default false): when true, events matching an actionable report (a trusted-moderator report against a valid target) are excluded from REQ/COUNT results. A pubkey-targeted report hides every event from that pubkey; an event-targeted report hides just that event. Requires nip56.enabled to have any effect. Previously an actionable report was only ever recorded, never acted on.

Patch Changes

3.1.0

Minor Changes

  • #756 570a7cc Thanks @Priyanshubhartistm! - feat: add relay-load-aware adaptive PoW difficulty (NIP-13)

    Adds limits.event.pow settings that scale the required proof-of-work difficulty between a configured floor and ceiling based on the observed event rate, in place of the existing static eventId.minLeadingZeroBits value (the static pubkey.minLeadingZeroBits check is unaffected and still enforced independently). The event rate is tracked per worker process with the same EWMA shape already used by the relay’s rate limiter. Disabled by default (limits.event.pow.enabled: false), so existing static PoW configuration is unaffected unless explicitly opted in.

  • #641 837540b Thanks @Ferryx349! - feat: add disabled-by-default admin API with password auth, session, and health endpoints

  • #666 44f3bb4 Thanks @Ferryx349! - feat: add admin observability dashboard with Grafana embed and provisioned metrics panels

  • #653 8ab4825 Thanks @Ferryx349! - feat: add OpenTelemetry metrics bootstrap with OTLP export for Prometheus

  • #661 237b1a4 Thanks @Ferryx349! - feat: instrument event and websocket handlers with OpenTelemetry metrics

  • #662 36d95cc Thanks @Ferryx349! - feat: add Prometheus-backed admin metrics SSE endpoint

  • #753 7c2875e Thanks @Ferryx349! - feat(admin): add GET /admin/network-health endpoint

  • #764 ff43cd3 Thanks @Ferryx349! - feat(admin): add Network Health panel to observability dashboard

    Adds a dashboard section that renders the latest NIP-66 probe snapshot with per-target DNS, TLS, WebSocket RTT, and NIP-11 status.

  • #784 c6c4c4f Thanks @Ferryx349! - feat(admin): add GET/PATCH /admin/notifications API for operator alert config

    Closes #760.

  • #787 de7c9df Thanks @Ferryx349! - feat(admin): add notifications console page for operator alerts

    Adds a Notifications view to the admin dashboard for configuring targets and event toggles, testing delivery, and browsing the delivery log via the admin API.

    Closes #761

  • #690 f70adf2 Thanks @Ferryx349! - feat: add authenticated admin settings API endpoints

  • #706 841833f Thanks @Ferryx349! - feat: add settings editor tab to admin dashboard UI

  • #770 07524eb Thanks @Priyanshubhartistm! - feat: add WoT-weighted NIP-56 content reporting

    Accepts and stores kind-1984 report events, weighting each report by the reporter’s WoT distance from wot.seedPubkey (full weight for a direct follow, halving each additional hop, zero for a pubkey outside the trust graph). Reports from a nip56.trustedModerators pubkey always get maximum weight and are flagged actionable, ready for a future management-API surface to act on; every other report is stored for manual review only. Disabled by default (nip56.enabled: false).

  • #734 fd7f56a Thanks @Priyanshubhartistm! - feat(dvm): dispatch pending DVM jobs to worker processes and publish kind 6000-6999 results back

  • #729 275c30c Thanks @Priyanshubhartistm! - feat(dvm): trap NIP-90 job request events (kind 5000-5999) and record them via the job repository

  • #727 d00eb42 Thanks @Priyanshubhartistm! - feat(dvm): add job persistence migration and repository for DVM job state

  • #737 9f36191 Thanks @Priyanshubhartistm! - feat(dvm): add NIP-89 handler recommendation/information kinds and verify passive accept-and-serve

  • #721 11ec673 Thanks @Priyanshubhartistm! - feat(dvm): add worker registry settings and dvm-orchestrator process topology

  • #587 30fa252 Thanks @Anshumancanrock! - Add NIP-50 full-text search support with PostgreSQL tsvector/GIN indexing.

    Clients can now include a search field in REQ filter objects to perform full-text queries against event content. Results are ranked by relevance (ts_rank) instead of the usual created_at ordering, per the NIP-50 specification.

    Features:

    • New search filter field accepted in REQ messages
    • PostgreSQL GIN index on to_tsvector('simple', event_content) for fast full-text lookups
    • Configurable text-search language (defaults to simple, supports english, spanish, etc.)
    • Configurable max search query length for abuse prevention
    • NIP-50 listed in NIP-11 relay information document
    • Search can be combined with all existing filter fields (kinds, authors, tags, etc.)
  • #702 e172cce Thanks @Anshumancanrock! - feat(nip42): enforce authentication on reads for restricted event kinds (encrypted DMs, gift wraps) across REQ, live broadcasts and COUNT

  • #716 2f5a1c0 Thanks @Anshumancanrock! - feat(nip42): add session tracking with optional TTL and publish-time authRequired (NIP-11 restricted_writes)

  • #732 d413bd6 Thanks @Anshumancanrock! - Add a CLI to mint NIP-43 invite codes (nostream invite create) so operators can issue a claim without SQL. New codes honor nip43.defaultMaxUses and nip43.inviteCodeExpirySeconds.

  • #650 3461dfe Thanks @Anshumancanrock! - Add NIP-43 invite code foundation: InviteCodeRepository with atomic claimCode, invite_codes migration, and event kind/tag constants.

  • #738 a0853db Thanks @Anshumancanrock! - feat(nip43): issue kind 28935 invite codes on request

    NIP-43 kind 28935 is not an event clients publish — it is a REQ the relay answers by minting an invite code on the fly and returning a relay-signed ephemeral event. Nostream now serves those subscriptions, completing the membership flow: request a claim, join with kind 28934, publish.

    Off by default. It requires nip43.enabled and the new nip43.allowInviteRequests, a NIP-42 authenticated requester, an info.self consistent with the relay signing key, and a per-pubkey budget under the new limits.invite.rateLimits (5/hour by default). This also makes the previously inert nip43.inviteRequestWhitelist setting take effect. The minted event is never persisted and never broadcast: the claim tag is a bearer secret and is sent only to the socket that asked for it.

    Two fixes the flow depended on. The relay signs its own events with a key derived from SECRET, but info.self was a hand-edited string that nothing validated — by default it was a placeholder that is not a pubkey at all, so any NIP-43 client verifying a relay-signed event against self would reject it. info.self is now optional: when unset or unparseable, NIP-11 advertises the derived signing pubkey instead, and nostream info prints that pubkey so operators can pin it.

    Kind 28935 also sits in the ephemeral range, so a client-published one fell through to EphemeralEventStrategy and was broadcast to every subscriber — including everyone subscribed to kind 28935 waiting for a real invite. Anyone could inject a forged claim tag into that subscription. It is now rejected with an OK false and never broadcast, and bypasses the NIP-43 admission gate so that rejection actually reaches non-members, who are the ones most likely to publish it by mistake while trying to obtain a code.

    CLI.md and README.md now describe the request flow. CLI.md previously claimed the relay “does not yet generate kind 28935 on REQ”, and never mentioned that nostream info prints the signing pubkey that CONFIGURATION.md tells operators to pin.

  • #676 0bfa0b5 Thanks @Anshumancanrock! - Add NIP-43 join/leave request event strategies (kinds 28934/28936) with NIP-42 auth enforcement, created_at freshness validation, invite code claiming, and admission management. When nip43.enabled is set, publishing is restricted to admitted members even without payments enabled, and NIP-43 is advertised in the NIP-11 document (hidden when disabled). Join/leave update the admission cache so membership changes take effect immediately.

  • #675 5a70839 Thanks @Ferryx349! - feat(nip66): add shared relay probe engine for DNS, TLS, WebSocket RTT, and NIP-11 checks

  • #741 afa8999 Thanks @Ferryx349! - feat(nip66): publish kind 30166 and 10166 relay health events after probe runs

    After each relay monitor probe run, sign and store NIP-66 relay discovery and monitor announcement events using the configured monitor identity, bootstrap kind 0/10002 on first run, and persist via the existing parameterized replaceable event path.

    Fixes #696

  • #724 d14b1e9 Thanks @Ferryx349! - feat(nip66): add RelayMonitorWorker cluster worker and probe scheduler

  • #689 e294a71 Thanks @Ferryx349! - Add NIP-66 relay monitor settings foundation with defaults for probe interval, timeouts, targets, monitor identity, and DNS cache TTL.

  • #644 2f6d773 Thanks @Anshumancanrock! - feat: reject NIP-70 protected events and reposts embedding them

  • #730 ef4123e Thanks @Anshumancanrock! - feat(admin): accept NIP-98 Authorization on protected admin API routes

  • #722 cf5ea4f Thanks @Anshumancanrock! - feat(nip98): add Authorization header event verifier for HTTP auth (kind 27235)

  • #781 8d29f15 Thanks @Ferryx349! - feat(admin): operator notification backend with Postgres outbox

    Adds transactional outbox dispatch for operator alerts (HTTP, Discord, Slack, Telegram), delivery log, admin.notifications settings, event hooks for admission invoices and settings changes, and admin test/history endpoints. Closes #759.

  • #763 358763a Thanks @Ferryx349! - feat(ops): add /readyz readiness probe for Postgres and Redis

    Adds a public readiness endpoint for zero-downtime deploy workflows. HAProxy (or similar) can use /readyz to confirm an instance can serve traffic before cutover, while /healthz remains a lightweight liveness check.

  • #672 595c0a6 Thanks @Ferryx349! - refactor: extract shared settings-config module and guided schema for admin settings editor foundation

  • #779 f6ee3e3 Thanks @Priyanshubhartistm! - feat: wire the WoT graph into adaptive PoW difficulty

    Adds limits.event.pow.wotThresholds, letting operators reduce (or bypass) the eventId PoW requirement for pubkeys within their configured WoT distance. A direct follow can post instantly under load while an unknown pubkey pays the full adaptive difficulty. Disabled by default (no thresholds configured); requires wot.enabled to have any effect, since a pubkey’s distance is otherwise always unknown.

  • #745 276fe16 Thanks @Priyanshubhartistm! - feat: add a Web of Trust graph service that tracks NIP-02 follow distance from an operator-configured seed pubkey

    Adds a WotGraphService that builds a trust graph rooted at wot.seedPubkey, updated in real time as kind-3 contact list events are ingested, with configurable depth (wot.maxDepth) and a minimum-followers threshold for 2+ hop trust (wot.minimumFollowers). Exposes getDistance() and isTrusted() for other parts of the relay to query. Disabled by default (wot.enabled: false).

  • #773 5577070 Thanks @Ferryx349! - feat(shutdown): drain WebSocket clients on SIGTERM

    On SIGTERM, /readyz returns 503, new WebSocket connections are rejected, and existing clients receive Nostr CLOSED messages before the socket closes. Drain is bounded by WS_DRAIN_TIMEOUT_MS (default 30s).

Patch Changes

  • #714 df1ed5d Thanks @Ferryx349! - fix(admin): update aria-expanded and label when mobile menu is toggled

  • #680 f92eabe Thanks @Priyanshubhartistm! - fix: advertise NIP-13 (Proof of Work) support in supportedNips

  • #750 f5c8dc8 Thanks @Anshumancanrock! - deploy: run production migrations from the relay image

    Bake migrations/ and knexfile.js into the runtime image and point the prod compose migrate service at that image so schema cannot drift from the code that ships.

  • #743 f36410a Thanks @Ferryx349! - ci: build and push container image to GHCR after CI passes on main

    Adds a GitHub Actions workflow that publishes ghcr.io/cameri/nostream:main and a per-commit sha-* tag once the CI Checks workflow succeeds for pushes to main.

  • #775 59b1e51 Thanks @Anshumancanrock! - fix: return COUNT results for filters with generic tag queries (#e, #p, #g, #h), which projected event_id twice and failed with “error: unable to count events”

  • #646 eb64d8a Thanks @dependabot! - chore(deps): bump js-yaml from 4.1.1 to 4.2.0

  • #647 68da3d4 Thanks @dependabot! - chore(deps): bump ws from 8.20.1 to 8.21.0

  • #705 95a672e Thanks @dependabot! - chore(deps): bump axios from 1.16.0 to 1.18.0

  • #751 8509aea Thanks @Ferryx349! - deploy: minimal server bootstrap with optional settings overrides

    Add deploy/bootstrap.sh, document what operators must keep locally vs what ships in the image, and stop seeding a full settings.yaml on first boot so release defaults merge with optional overrides only.

  • #744 8fa72d6 Thanks @Ferryx349! - deploy: add production Docker Compose stack for relay servers

    Adds a minimal prod compose file, migrate image Dockerfile, and server layout docs for deployments that pull ghcr.io/cameri/nostream:main instead of building on the host.

  • #765 3690bd8 Thanks @Ferryx349! - ci: disable CodeQL workflow

    Removes the CodeQL Advanced GitHub Actions workflow, custom query pack, config, and route suppression comments to stop false-positive security alerts on admin routes that already use custom auth and rate limiting.

  • #694 7c4b728 Thanks @Priyanshubhartistm! - fix: de-duplicate events returned by generic tag-filter subscriptions

    EventRepository.findByFilters() left-joins event_tags for generic tag filters (#e, #p, etc.) without deduplicating the result. An event matching more than one tag row for the same filter (e.g. {"#p": ["a", "b"]} matching an event tagged with both) was returned once per matching event_tags row, so subscribers received the same EVENT message multiple times. The query now selects DISTINCT events.* for tag-filtered queries so each stored event is returned at most once. This also covers generic tag filters combined with a NIP-50 search term (e.g. {"search": "...", "#p": ["a", "b"]}), which take the search branch and are now de-duplicated as well.

  • #771 b07f9c3 Thanks @chappie-daemon! - fix: enforce limits.client.subscription.maxFilterValues on REQ and COUNT filters

    Filters whose array criteria (ids, authors, kinds, #<tag>) hold more than maxFilterValues values in total are now rejected with Too many filter values instead of being handed to PostgreSQL as an unbounded WHERE IN (...). The limit was previously defined in settings and surfaced in the admin settings editor while being read by nothing at all.

    The enforced limit is also advertised in the NIP-11 limitation object as max_filter_values, a non-standard extension since NIP-11 has no field for per-filter value counts.

  • #703 7af0387 Thanks @Priyanshubhartistm! - Fix the Content-Security-Policy connect-src directive for relays served over plain ws://.

    The web app factory derived an HTTP(S) origin from the relay’s WebSocket URL but mapped ws: to the invalid scheme ':', which the WHATWG URL API silently ignores. As a result the connect-src directive kept a ws://… entry instead of the intended http://… origin for local/dev, Tor, or reverse-proxied setups. The ws: protocol now correctly maps to http:.

    Adds regression test coverage for the protocol mapping (getWebProtocolForRelay, extracted from createWebApp so it can be unit tested directly), since this file previously had no test coverage at all.

  • #708 a76d0b5 Thanks @Priyanshubhartistm! - fix: reject expiration timestamp 0 and millisecond-scale values, and accept safe-integer second-based timestamps up to the Postgres int4 max (2038-01-19T03:14:07Z) in getEventExpiration()

  • #747 14cca60 Thanks @Ferryx349! - fix(ci): publish container image after CI passes on main

    Removes the standalone publish workflow and its unsafe workflow_run trigger. Image publish now runs as the final job in checks.yml on pushes to main, after lint, build, and tests succeed.

  • #735 f0aab15 Thanks @Ferryx349! - fix: include event id in expired and rate-limited rejection logs

    Expired and rate-limited event rejections logged event %s rejected: ... without passing event.id, so operators saw a literal %s instead of the event id.

  • #715 9361601 Thanks @Priyanshubhartistm! - fix: abort in-flight streaming queries when a subscription is cancelled

  • #746 7297b96 Thanks @Anshumancanrock! - fix(payments): stop stale invoices from wedging payment polling

    A relay could stop clearing payments entirely, needing delete from invoices to recover. Two things combined to cause it.

    Invoices created without an expiry could never be retired, because the expiry check treats a missing date as “not expired”, so the maintenance worker left them pending forever. The LNURL processor set no expiry on any invoice, making this certain there rather than incidental. Invoices now fall back to payments.invoiceExpirySeconds when the processor reports no expiry of its own, and existing pending rows without one are backfilled.

    Separately, each maintenance pass re-read the same oldest page of pending invoices, so one page of invoices that never resolve starved every newer one indefinitely. The worker now advances through the queue and wraps at the end, keeping the same per-pass cost while guaranteeing every pending invoice is eventually polled.

  • #786 e87e1af Thanks @Priyanshubhartistm! - fix(nip56): skip targetless report rows, record every p/e target, batch report inserts in one transaction, and warm the WoT graph at boot instead of blocking the first report on a cold-start rebuild

  • #733 9fb1c10 Thanks @Ferryx349! - test(nip66): add integration tests for RelayMonitorWorker snapshot storage

  • #725 849c3f7 Thanks @Anshumancanrock! - feat(http): build absolute request URL from relay_url

  • #726 3d3848e Thanks @Anshumancanrock! - feat(redis): add setKeyIfNotExists for one-time claims

  • #640 ca23be1 Thanks @Anshumancanrock! - test: optimize nip05.spec.ts & nip03.spec.ts resource management

    • Lift sinon stub to before/after in verifyNip05Identifier tests (create once, reset between tests)
    • Extract SSRF guard callback once in before instead of per-test beforeEach
    • Pre-build shared OTS buffers and attestations at module scope to eliminate redundant Buffer.concat calls
    • Add shared event factory for extractNip05FromEvent tests
  • #686 cb7daf6 Thanks @Priyanshubhartistm! - fix: stop checking additional rate limit windows once a client is already rate-limited

    isRateLimited() in EventMessageHandler and WebSocketAdapter looped through every configured rate limit window even after one had already tripped, calling rateLimiter.hit() (a Redis write) for each remaining window. Both now return as soon as the first exceeded window is found, avoiding redundant Redis writes for clients that are already being limited.

  • #684 3648954 Thanks @Priyanshubhartistm! - fix: await Redis EXISTS call in RedisAdapter.hasKey() so it reflects actual key presence instead of always returning true

  • #711 220949d Thanks @Priyanshubhartistm! - fix: include the actual error message in replaceable event rejection responses

    ReplaceableEventStrategy.execute() sent clients a bare error: command result (with no message body) whenever eventRepository.upsert() failed for a reason other than a duplicate event id. The underlying error.message was caught but never included in the response, leaving clients with no actionable information about why the event was rejected. The command result now includes error.message.

  • #682 dc78df5 Thanks @Priyanshubhartistm! - fix: prevent crash in NIP-11 relay information document when payments settings are absent

3.0.0

Major Changes

  • #524 b3effd1 Thanks @vikashsiwach! - Use exact pubkey matching for fee-schedule whitelists and event pubkey whitelist/blacklist checks.

  • #574 f1c1118 Thanks @Mahmoud-s-Khedr! - Add a brand-new unified nostream CLI/TUI that replaces the legacy scripts/* shell wrappers for lifecycle, setup, info, config, data, and development workflows.

    Fixes - fixed some consistnacy issues after the migration from npm to pnpm

Minor Changes

  • #522 7edd6c3 Thanks @a-khushal! - added NIP-45 COUNT support with end-to-end handling (validation, handler routing, DB counting, and tests).

  • #629 36f8bad Thanks @Anshumancanrock! - feat: NIP-42 AUTH handler and WebSocket session wiring

  • #534 a07b0f6 Thanks @archief2910! - Add hot-path PostgreSQL indexes for subscription, vanish, retention, and invoice queries; add db:benchmark and db:verify-index-impact tooling; document index rationale and benchmarking. Closes #68.

  • #556 d8f62b4 Thanks @saniddhyaDubey! - perf: added k6 performance tests for connection and message rate limiting

  • #623 54139ed Thanks @saniddhyaDubey! - new user-facing config field

  • #476 49322a9 Thanks @saniddhyaDubey! - Add EWMA rate limiter with configurable strategy support

  • #602 d3ba328 Thanks @CKodidela! - Add relay support for the Marmot Protocol (E2EE group messaging over Nostr).

    Supported MIPs: 00 (KeyPackages), 01 (Group Construction), 02 (Welcome Events), 03 (Group Messages).

    • kind 443 (legacy KeyPackage): stored as a regular event
    • kind 10051 (KeyPackage relay list): stored as a replaceable event
    • kind 30443 (KeyPackage): stored as a parameterized-replaceable event with d-tag deduplication
    • kind 444 (Welcome rumor): blocked from direct publishing; must travel inside a kind 1059 gift wrap
    • kind 445 (Group Event): dedicated strategy validates the required h tag (nostr_group_id) before storing; #h tag subscriptions work via the existing generic tag index
    • NIP-11 relay info now advertises supported_mips: [0, 1, 2, 3]
  • #515 5c12f36 Thanks @archief2910! - Add NIP-03 OpenTimestamps support for kind 1040 events: structural .ots validation, Bitcoin attestation requirement, digest match to the referenced e tag, and relay metadata updates (#105).

  • #589 25d5405 Thanks @CKodidela! - Add NIP-25 Reactions support for kind 7 and kind 17 events: reaction utility helpers (isReactionEvent, isExternalContentReactionEvent, isLikeReaction, isDislikeReaction, parseReaction), schema validation enforcing required e tag on kind 7 and required k/i tags on kind 17, unit tests, and integration tests.

  • #585 ce59383 Thanks @CKodidela! - Add NIP-65 Relay List Metadata support for kind 10002 events: relay list utility with isRelayListEvent and parseRelayList helpers, unit tests, and relay information document updated to advertise NIP-65 (#577).

  • #514 214bef5 Thanks @Anshumancanrock! - Add gzip and xz compression support to event import/export flows.

    • Export supports --compress/-z with --format gzip|gz|xz.
    • Import auto-detects compressed input by extension and magic bytes and decompresses in a stream pipeline.
    • Includes docs updates and unit/integration test coverage for compression paths.
  • #628 ce7b838 Thanks @Ferryx349! - refactor(http): remove deprecated network.remote_ip_header fallback and rely on network.remoteIpHeader

  • #539 bdd4f6b Thanks @Justxd22! - Add NWC (NIP-47) as a payments processor for admission invoices, including configurable invoice expiry and reply timeout handling, compatibility for legacy NWC URI schemes, and docs/env updates.

  • #497 e1a7bfb Thanks @phoenix-server! - Release highlights:

    Features

    • NIP-05 verification support (#463)
    • NIP-17 & NIP-44 v2 Modern Direct Messages (#458)
    • NIP-62 vanish event support (#418)
    • Vanish optimization (#446)
    • Export events to JSON Lines format (#451)
    • Import .jsonl events into events table (#414)
    • Opt-in event retention purge (#359, #412)
    • Wipe events table script (#450)
    • Nginx reverse proxy in docker-compose (#423)
    • Docker DNS pre-flight check for connectivity verification (#398)
    • Strict validation for payment callbacks (#426)
    • Real home page with templated pages (#409)

    Bug Fixes

    • NIP-01 compliance: deterministic event ordering by event_id
    • NIP-01 compliance: correct dedup keys for parametrized replaceable events (#480)
    • NIP-01 replaceable event tiebreaker (#416)
    • NIP-11 served only on root path instead of relay path (#399)
    • Dockerfile: run database migrations in CMD (#422)
    • Added expired_at filter to message pipeline (#403)
    • Removed unsafe-inline and implemented script nonces for CSP hardening (#394)
    • Axios upgraded to fix CVE-2025-62718 (#466)

    Refactors & Chores

    • Migrated validation from Joi to Zod (#484)
    • Migrated linting and formatting to Biome (#452)
    • Converted user admission to PostgreSQL stored function (#428)
    • Upgraded to Node.js 24 LTS (#419)
    • Updated dependencies (express, body-parser, js-yaml, axios)
  • #622 6a0a5fa Thanks @Anshumancanrock! - feat: add NIP-42 types, schemas and constants

Patch Changes

  • #555 ddc811d Thanks @Anshumancanrock! - Migrate project tooling from npm to pnpm across CI workflows, Docker setup, hooks, and contributor commands.

  • #600 dfa2838 Thanks @saniddhyaDubey! - fix: maxLimit checks added to subscription message handler

  • #572 b718036 Thanks @dependabot! - Bump dev dependency uuid from 8.3.2 to 14.0.0

  • #616 f9f6d64 Thanks @Anshumancanrock! - fix: check payments.enabled in callback route middleware

  • #597 7da1b9a Thanks @Anshumancanrock! - refactor: only register OpenNode, LNbits, and Zebedee callback routes when their processor is active

  • #438 f5ba023 Thanks @tharu-jwd! - fix: close dead connections even if they have active subscriptions

  • #546 faa7ed2 Thanks @Justxd22! - Fix root HTML negotiation and subpath-aware template links behind trusted proxies.

  • #613 36e5af8 Thanks @dependabot! - chore(deps): bump axios from 1.15.1 to 1.15.2

  • #617 50822b9 Thanks @dependabot! - chore(deps): bump fast-uri from 3.1.0 to 3.1.2

  • #618 c6368db Thanks @dependabot! - chore(deps): bump ws from 8.20.0 to 8.20.1

  • #620 dac92b4 Thanks @dependabot! - chore(deps): bump uuid from 8.3.2 to 14.0.0

  • #630 1295272 Thanks @dependabot! - chore(deps): bump axios from 1.15.2 to 1.16.0

  • #575 b7324a6 Thanks @kanishka0411! - Expire stale pending invoices when LNbits no longer has the invoice or reports it as unpaid past its expiry time.

  • #592 0119c74 Thanks @Anshumancanrock! - Security: override serialize-javascript to >=7.0.3 (CVE RCE, GHSA-5c6j-r48x-rmvq)

  • #553 3c78e61 Thanks @Anshumancanrock! - Fix replaceable batch upserts to apply NIP-01 tie-breaker semantics when timestamps are equal by comparing event IDs.

  • #583 321a9cc Thanks @kanishka0411! - Allow generic tag filters to match empty string tag values.

  • #586 2418209 Thanks @kanishka0411! - Implement geohash wildcard/prefix behavior for #g filters (closes #265): a criterion ending in * matches any event g tag whose value starts with the prefix before *; exact matching (no *) is unchanged. Only normal geohash prefixes are intended as input. This is a Nostream extension, not part of NIP-12.

  • #584 a6d32b1 Thanks @Anshumancanrock! - Use timingSafeEqual for Nodeless webhook HMAC verification and guard against missing NODELESS_WEBHOOK_SECRET

  • #591 f31be1c Thanks @Priyanshubhartistm! - fix: add husky install fallback for non-dev environments

  • #551 7fc0552 Thanks @CKodidela! - Add unit tests for InvoiceRepository and UserRepository with sinon-stubbed DB client

  • #538 9496685 Thanks @saniddhyaDubey! - Fix: Restore CONFIGURATION.md with proper settings and remove duplicate changesets created during recovery

  • #557 32a1ec5 Thanks @a-khushal! - update NIP-11 relay info fields and CORS, with test and docs updates

  • #593 f599b1d Thanks @YashIIT0909! - fix: resolve TOCTOU race condition and key collisions in SlidingWindowRateLimiter

  • #511 a38d402 Thanks @Priyanshubhartistm! - Migrate runtime logging to pino across adapters, services, workers, and controllers, and stabilize CI-related fixes for coverage and integration workflows after rebasing.

  • #552 25f9637 Thanks @vikashsiwach! - Add integration tests for NIP-02 contact lists (Kind 3)

  • #527 4d030c7 Thanks @kanishka0411! - Add NIP-11 integration tests and fix max_filters mapping in relay information document.

  • #547 664168a Thanks @Priyanshubhartistm! - Improve NIP-22 created_at limit handling coverage and boundary reliability.

    This adds integration coverage for accepted and rejected events across configured positive and negative created_at deltas, and keeps rejection semantics consistent (rejected) for out-of-range timestamps.

  • #537 a89a95e Thanks @vikashsiwach! - Add NIP-62 integration tests for Request to Vanish

  • #643 faf55f1 Thanks @Anshumancanrock! - feat: add NIP-70 protected event detection utility

  • #596 250c767 Thanks @CKodidela! - Normalize runCommandWithOutput to return a CommandResult discriminated union instead of rejecting on spawn errors, fixing a crash in info --json when Docker is not installed.

  • #625 69d6187 Thanks @Ferryx349! - Refactor EventRepository query construction to reduce method complexity.

  • #497 e1a7bfb Thanks @phoenix-server! - Replace semantic-release with changesets for explicit PR-level version management. Contributors now add a changeset file per PR; the Changesets Release workflow handles version bumps and GitHub releases.

  • #627 c87dd03 Thanks @Ferryx349! - test(integration): verify response content-type across core HTTP paths

  • #562 de14f3c Thanks @Priyanshubhartistm! - Add integration test coverage for NIP-04 encrypted direct messages (kind 4).

  • #525 b09e23a Thanks @kushagra0902! - Dedup keys were taking multiple tags, that was not according to NIP-01 behaviour.

  • #568 c0c1c35 Thanks @tharu-jwd! - fix: static mirroring silently drops events when mirror has no limits configured

  • #604 40abf66 Thanks @phoenix-server! - Add unit tests for maintenance service factory instantiation and dependency wiring.

  • #493 5bf1a58 Thanks @kanishka0411! - Fix IP spoofing via unconditional trust of x-forwarded-for header

  • #606 47a9f4e Thanks @phoenix-server! - Add unit tests for maintenance-worker-factory with 100% code coverage

  • #545 a9ae0cd Thanks @Justxd22! - Fix Redis cache connection config to skip AUTH when REDIS_PASSWORD is unset

  • #548 00240a9 Thanks @CKodidela! - Support uppercase tag filters (#A-Z) in filter schema validation

  • #566 8eee70f Thanks @a-khushal! - add support for NIP-14 subject tags in text notes, with units tests to validate parsing and repository behavior