nostrbasedocs
Start building
Guide
.md

This project is separate from test-environments and test-extended. It has no public service defaults. Missing configuration fails all checks with a clear setup error; no check is silently skipped.

Read-only use

Set both endpoints, then run:

npm run test-live-services
Setting Purpose
NOSTRBASE_LIVE_RELAY Explicit ws:// or wss:// relay URL.
NOSTRBASE_LIVE_BLOSSOM Explicit HTTP(S) Blossom origin, with no path.
NOSTRBASE_LIVE_SEARCH Optional NIP-50 query; default nostr.
NOSTRBASE_LIVE_BLOB_SHA256 Optional existing public blob to download and verify.
NOSTRBASE_LIVE_BLOB_FORMAT Write fixture: text (default), json, or png; MIME type matches the generated bytes.

Endpoint credentials, query strings, and fragments are rejected. Redirects are rejected. The report never contains keys, authorization headers, event bodies, or downloaded content. It contains the configured endpoints, declared software and versions, capability decisions, completion status, and counts.

Read-only mode uses a temporary identity for a signed Blossom list request. It sends no relay publications, uploads, or deletions. It makes bounded relay requests, an optional search, pull-only reconciliation, Blossom GET/HEAD/list requests, and browser CORS OPTIONS preflight requests. No private key is needed.

Explicit write tests

To also run scoped write checks, set all these values:

Setting Required value
NOSTRBASE_LIVE_WRITE Exactly 1.
NOSTRBASE_LIVE_TEST_KEY Private key of a dedicated test account, as 64 lowercase hex characters.
NOSTRBASE_LIVE_TEST_PUBKEY The matching public key, as 64 lowercase hex characters.
NOSTRBASE_LIVE_NAMESPACE Dedicated namespace starting with nostrbase-integration-; remaining characters are letters, digits, _, or -.

Supply the test key through your local environment or secret manager. The suite checks the account match and namespace before contacting the deployment. Read-only mode rejects write identity settings to prevent an ambiguous mode.

Six checks keep read completion, declared capabilities, Blossom routes, seeded NIP-50 search, record writes, and blob writes separate. A failed relay check does not prevent an independent Blossom write check.

The record write check creates one unique record, verifies acknowledgement and readback, and requests signed deletion. Declared reconciliation also uses a fresh SDK instance; successful query fallback can recover the record independently of the strict declared-Negentropy check. The blob write check uses a tiny unique fixture, verifies hash and byte identity, lists it, and requests deletion. PNG mode generates a valid one-pixel image with a unique text chunk (158 bytes in the public run). Declared NIP-50 support gets a separate signed kind-1 note with a unique alphanumeric search token and bounded indexing retries; cleanup requests NIP-09 deletion even when publication fails. A relay acknowledgement or deletion request does not prove permanent storage or removal of every remote copy.

Local validation of the same project

node integration/relay/prepare.mjs
node integration/blossom/setup.mjs
NOSTRBASE_LIVE_LOCAL=1 npm run test-live-services
NOSTRBASE_LIVE_LOCAL=1 NOSTRBASE_LIVE_WRITE=1 npm run test-live-services

Local mode starts digest-pinned strfry 1.1.3 and unmodified hzrd149/blossom-server 6.4.0, commit 32567afb15255c171817a78ed2861cd9e57bf4de. It uses random loopback ports, temporary storage, a generated test account, and a fresh test namespace. It rejects external endpoints and external key settings. Docker and the cached, verified Blossom checkout must be ready. The local run exercises exactly the same compatibility tests as the external run.

What each result proves

  • NIP-01: the relay completes a bounded read and the SDK verifies returned signatures. Empty results are valid.
  • NIP-50: a declared search extension must complete a real search request. Without a known seeded result, this proves protocol completion, not search ranking or indexing quality. If the relay does not declare NIP-50, the report states that the extension was not exercised.
  • NIP-77: supported_nips: [77] or the legacy negentropy: 1 advertisement counts as declared support. It must complete actual Negentropy negotiation with strategy: negentropy; silent query fallback fails. Read-only mode can have an empty matching dataset. Opt-in writes also test signed event transfer. An undeclared extension uses an explicit completed query and is reported as unexercised.
  • Blossom: GET/HEAD routes, SDK error mapping, optional verified blob bytes, authenticated list behavior, and required browser preflight headers. A list route may be absent or deny the temporary identity; the report records that policy result. These HTTP header checks do not replace real browser tests in the separate Blossom service project.

Reports are saved under output/environment/live/compatibility-*.json. The public driver keeps per-run Vitest results, compatibility reports, and logs in a separate folder, so it does not replace the main environment suite result.

NOSTRBASE_LIVE_BLOB_FORMAT=png node integration/live/run-public.mjs wss://relay.damus.io https://blossom.band --write
node integration/live/probe-negentropy.mjs wss://relay.damus.io

The driver requires explicit endpoints. It generates a dedicated identity and namespace, runs read-only checks, then runs writes only with --write. It never prints the key. Before writes, it journals that generated key with mode 0600 under output/integration-cache/live-identities/, outside the CI artifact paths. It removes the key only when every attempted resource has a confirmed cleanup. An absent report or unknown/failed cleanup retains the key for an operator retry. Safe reports record namespace, record/event IDs, blob hashes, and acknowledgements.

The Negentropy probe uses the exported, unmodified Applesauce negotiation helper and a real WebSocket. It sends an empty-vector pull request, never an event write, and records bounded server notices and negotiation completion. It can distinguish a metadata mismatch from an actual wire rejection.

Public results, 2026-10-04

Public reads and writes were authorized for this run. Checks ran from roughly 09:00 to 09:17 UTC. Public compatibility failures remain failed assertions.

Service Observed result
wss://relay.damus.io NIP-11 software strfry 1.1.0-158-gb705403ddf49. Signed reads completed. A record publication, fresh-client query recovery, readback, and signed deletion passed in a complete record check. Some other runs had transient WebSocket/read failures.
Damus Negentropy NIP-11 advertises legacy negentropy: 1, but omits NIP-77 from supported_nips. SDK falls back to a query. Direct modern negotiation receives NOTICE: ERROR: bad msg: negentropy disabled; the strict declared-capability check fails.
wss://search.nos.today NIP-11 software searchnos, version v0.1.0-841b8f6. Actual NIP-50 search completed with verified signed results. General reads fail with error: search filter is required. Synthetic kind-1 publication and deletion both reject with blocked: writes disabled; seeded indexing remains unverified.
https://blossom.band GET/HEAD absence mapping, signed list, and browser preflight passed. Tiny text was rejected with HTTP 400 and a MIME mismatch; JSON was rejected with HTTP 415 and an unsupported-file-type policy. A valid 158-byte PNG upload and signed deletion succeeded.
Blossom CDN bytes The validated PNG descriptor redirects with HTTP 307 from an account subdomain of blossom.band to image.nostr.build. An unauthenticated, bounded redirect diagnostic retrieved exactly the uploaded 158 bytes and verified SHA-256. The SDK download check still fails because the documented SDK policy is redirect: error. No redirect policy was changed.

Blossom did not declare an application version; server: cloudflare identifies its HTTP front end. Descriptor diagnostics omit URL query strings and credentials from their recorded redirect hops. Public tests used only synthetic records, notes, and fixtures.

An initial Damus cleanup failed after an acknowledged synthetic record write. The first driver kept its account key only in memory, so that cleanup cannot be retried and one synthetic record may remain. Later confirmed deletions are recorded individually. The current driver journals generated keys before writes and retains them when cleanup is not confirmed; it makes no claim of global record erasure.

Known scope of the possible remaining synthetic record:

Relay: wss://relay.damus.io
Timestamp: 2026-10-04T09:01:40.825Z
Namespace: nostrbase-integration-d9d6db45-e7bb-4c68-91fb-2a20f10128c0
Author: 803e83bac266d35f106949450f2f2f8aecd6e9a53b5b598a5b99e8c1c9d35594
Row/event IDs: not retained

Evidence is saved in output/environment/live/compatibility-1791104500826-73172.json and output/environment/live/1791104494399-ba41341b-7b6c-43b2-bfee-f6e04f26e76f/summary.json.

Upstream local service logs remain in the usual relay and Blossom output folders.

The local services do not declare NIP-50. Its public request-completion branch was verified against search.nos.today; seeded search was blocked by that relay’s write policy. Hosted routing, certificates, policies, and availability remain specific to the selected deployment. These runs do not prove every public service is compatible.

Search guides, API methods, and protocols.