swentel/nostr-php
A PHP library for building Nostr clients and tools, with support for keys, events, relays, filters, and NIP implementations. It offers a clean, object-oriented API for publishing notes, subscribing to relays, and handling cryptographic signing in Laravel or plain PHP projects.
Architecture fit
swentel/nostr-php is a framework-agnostic protocol helper library, not a Laravel package: no service provider, no config publishing, no facade, no Eloquent integration. That is the right shape for us — it keeps the dependency in the "vendor plumbing" layer rather than dictating application architecture — but it means the Laravel-facing surface is ours to build. What we get is the undifferentiated heavy lifting of the protocol: event construction and serialization, key handling, filter objects, relay message envelopes, and NIP helpers (NIP-19 bech32 encoding and NIP-05 identifier handling are the ones worth validating first). What we do not get: a connection pool, relay health management, subscription lifecycle, persistence, retries, or backpressure. Those are the actual product decisions, and they stay with us.
The central architectural tension is transport. Nostr is WebSocket-and-long-lived-subscription shaped; PHP-FPM is request-scoped and worker-blocking. The package's client will be usable outbound per-request (publish an event from a queued job), but inbound real-time ingestion requires a long-running process — Laravel Octane/Swoole, a supervised Artisan daemon, or a sidecar. A TPM should treat that as a platform decision, not a library decision, because it constrains hosting (rules out naive shared hosting and complicates Vapor/serverless).
Integration feasibility
High for the outbound path, moderate for inbound. Publishing is a clean fit: Event → sign → EventMessage → dispatch across a relay set in a queued job, with per-relay success tracking. Inbound is where the design work lands: a listener daemon that opens subscriptions, verifies signatures, dedupes on event id, and pushes to the queue. Verification cost is the sleeper issue — Nostr requires BIP-340 Schnorr signatures, and pure-PHP Schnorr verification is expensive relative to a C extension. If the library depends on ext-secp256k1 with the schnorrsig module, that is a hard deployment prerequisite (container base image, CI, local dev). If it has a pure-PHP fallback, throughput becomes the constraint instead. Confirm which before committing.
Also verify: exact NIP coverage versus our roadmap (NIP-01 core, NIP-04/17 encrypted DMs, NIP-09 deletion, NIP-42 relay auth, NIP-50 search), whether the client supports multiple concurrent relay connections or one at a time, PHP version floor, and whether any transitive dependency is unmaintained (older PHP Nostr libraries leaned on simplito/elliptic-php, which is effectively abandoned).
Technical risk
Key questions for the engineering spike
Stack fit
Wrap, don't scatter. Introduce a thin internal package — e.g. app/Domains/Nostr or a private Composer package — that owns the dependency and exposes a narrow, Laravel-idiomatic API: a NostrServiceProvider binding a RelayPool and Signer interface, a config/nostr.php for relay URLs, timeouts, retry policy, and key source, and domain events (NostrEventReceived, NostrPublishFailed) so application code never imports Nostr\* directly. That isolation is what makes the fork-and-own contingency cheap and what lets us swap in a sidecar later without touching feature code.
Migration path Greenfield integration — no legacy migration, so sequence by risk rather than by system:
| Phase | Scope | Risk | Exit criteria |
|---|---|---|---|
| 0 | Spike: signing backend, throughput, relay conformance against 2–3 real relays | Low | Numbers + a NIP coverage matrix |
| 1 | Outbound only: publish events via queued job, per-relay result tracking | Low | Publish success rate > target across relay set |
| 2 | Inbound: daemon ingestion, signature verification, dedupe, persistence | Medium | Sustained ingest with bounded memory over 72h |
| 3 | Identity: NIP-05 verification, NIP-19 handling, key custody hardening | Medium–High | Signer behind KMS; rotation runbook |
| 4 | Encrypted DMs / NIP-42 auth, if the product needs them | High | Threat model signed off |
Compatibility
Laravel 10/11/12 and PHP 8.1+ are plausible but unverified — confirm the floor against our oldest supported app. The absence of framework coupling means no conflict risk with other packages, which is a genuine plus. Container image must carry ext-secp256k1 if required; add a startup assertion so a missing extension fails loudly at boot rather than at first signature.
Sequencing Do not let inbound ingestion onto the roadmap before the long-running-worker hosting decision is made and paid for. Phase 1 delivers user-visible value with none of that commitment and produces the relay-reliability data needed to justify Phase 2.
Maintenance Ongoing cost is concentrated in our wrapper, not the library. Budget for: tracking NIP changes quarterly, maintaining a relay conformance test suite (relay behavior drifts silently), and owning the fork if upstream stalls. Estimate roughly 0.1–0.2 FTE steady state for a Phase 1–2 footprint; the library itself is small enough that a fork is a weekend, not a quarter.
Support
Community-only: GitHub issues, no SLA, no commercial backing. Internal runbooks must cover relay outages, signature verification failures, key access problems, and worker wedges. Pin to an exact version and treat upgrades as reviewed changes with a conformance run, not composer update.
Scaling Outbound scales trivially — it is queue depth and relay rate limits. Watch the fan-out multiplier: N relays × M events means N×M outbound connections and N×M failure surfaces, so cap the relay set and track per-relay health as a first-class metric. Inbound scales with verification CPU, not I/O; on pure-PHP Schnorr, expect verification to be the ceiling and plan horizontal sharding by subscription. Long-running workers need memory ceilings, restart policy, and per-subscription backpressure — an unbounded subscription set is a memory leak with a schedule.
Failure modes to instrument
kind or content, verify every signature before persistence.Ramp-up A mid-level PHP developer is productive on the outbound path in 1–2 days; the library's value objects are self-explanatory. The protocol is the learning curve, not the code: relays, event kinds, NIPs, key handling, and the difference between "published" and "propagated" take 1–2 weeks to internalize. Budget a half-day knowledge transfer on key custody specifically — that is where a well-meaning implementation becomes an incident.
Verdict Adopt for outbound and identity helpers; treat inbound ingestion as a platform project with the library as one input. The 7.7 score and 0.8 opportunity signal read correctly: the gap it fills is real and the code is fit for purpose, but low adoption and single-maintainer concentration mean we isolate it behind our own interface, pin it, and keep the fork option warm.
How can I help you explore Laravel packages today?