承接 innis/nostr-relay 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

innis/nostr-relay

Composer 安装命令:

composer require innis/nostr-relay

包简介

AMPHP-based async WebSocket relay server for Nostr protocol

README 文档

README

CI

AMPHP-based async WebSocket relay server for Nostr protocol

A private, high-performance Nostr relay implementation designed to be embedded in PHP applications. Built with AMPHP for concurrent connection handling and clean architecture principles.

Features

  • Interface-driven design - Storage and policies provided by host application
  • AMPHP async - Non-blocking concurrent connection handling
  • Private relay focus - Built for single-user/controlled access scenarios
  • NIP-01 compliant - EVENT, REQ, CLOSE message handling
  • NIP-09 deletion - Kind 5 event processing
  • NIP-11 support - Relay information document
  • NIP-42 AUTH - Challenge/response authentication; a challenge is issued only when a subscription exceeds guest scope (never on connect), and the client's live subscriptions are re-evaluated once it authenticates
  • NIP-45 COUNT - COUNT message support
  • Ephemeral events - Kinds 20000-29999 skip storage
  • Custom HTTP handlers - Inject handlers for additional HTTP endpoints (e.g. management APIs, landing pages)
  • NIP-11 metadata - Served from a single Nip11InfoProviderInterface: the built-in StaticNip11InfoProvider for a fixed document, or a custom implementation to compute it at runtime
  • Built-in RelayPolicy - Configurable tenant/guest permissions
  • Real-time distribution - Events broadcast to matching subscriptions
  • Metrics - RelayInstance::getMetrics() returns a RelayMetrics snapshot (active connections, events received/sent, subscriptions, start time) via the MetricsCollectorInterface port
  • Rate limiting - DDoS protection with configurable limits; tenants (and trusted clients via isRateLimitExempt()) bypass
  • Idle timeout - Connections with no inbound message for 5 minutes are closed, freeing the slot (see ADR-0005)
  • CORS support - OPTIONS preflight handling and uniform CORS headers on every HTTP response for browser-based clients
  • Trusted proxies - X-Forwarded-For honoured when the client IP matches a trusted proxy
  • PSR-3 logging - Standard logging interface

Requirements

  • PHP 8.4 or higher
  • innis/nostr-core - Core Nostr protocol entities
  • amphp/amp ^3.0 - Async runtime
  • amphp/http-server ^3.0 - HTTP server
  • amphp/websocket-server ^4.0 - WebSocket server
  • psr/log ^3.0 - Logging interface

Installation

composer require innis/nostr-relay

Quick Start

1. Implement Required Interfaces

The relay requires these interfaces from your host application:

  • RelayEventStoreInterface - Event persistence and queries
  • RelayConfigInterface - Server configuration (host, port, max connections, relay URL, trusted proxies)
  • RateLimitPolicyInterface - Per-minute rate-limit budgets keyed by RateLimitMetric (events, subscriptions). Use the built-in StaticRateLimitPolicy for fixed limits, or implement the interface to vary limits at runtime.
  • Nip11InfoProviderInterface - The single source of the relay's NIP-11 document. Wrap a fixed document in the built-in StaticNip11InfoProvider, or implement the interface to project metadata at runtime (e.g. reflecting live policy).

Access control can use the built-in RelayPolicy or a custom implementation of RelayPolicyInterface.

Optional interfaces extend the relay's behaviour:

  • HttpRequestHandlerInterface - Handle additional HTTP requests (e.g. management API, landing page). Return a response or null to fall through to WebSocket.
  • ConnectionGateInterface - Decide whether an IP may connect, before the WebSocket session is established. Defaults to allowing every IP; implement it to enforce an allow-list or deny-list.
  • MetricsCollectorInterface - Collect relay metrics (connections, events, subscriptions). Defaults to the in-memory InMemoryMetricsCollector exposed via RelayInstance::getMetrics(); implement it to export to an external monitoring system.

2. Create and Start the Relay

use Innis\Nostr\Core\Domain\ValueObject\Protocol\Nip11Info;
use Innis\Nostr\Core\Infrastructure\Crypto\NativeRandomBytesGenerator;
use Innis\Nostr\Relay\Application\Service\InMemoryAuthenticationRegistry;
use Innis\Nostr\Relay\Application\Service\RelayPolicy;
use Innis\Nostr\Relay\Domain\ValueObject\RateLimitConfig;
use Innis\Nostr\Relay\Domain\ValueObject\RelayPolicyConfig;
use Innis\Nostr\Relay\Infrastructure\Http\StaticNip11InfoProvider;
use Innis\Nostr\Relay\Infrastructure\RateLimiting\StaticRateLimitPolicy;
use Innis\Nostr\Relay\Infrastructure\Server\RelayServerFactory;

use function Amp\trapSignal;

$authenticationRegistry = new InMemoryAuthenticationRegistry(new NativeRandomBytesGenerator());
$logger = new \Psr\Log\NullLogger();

$policyConfig = RelayPolicyConfig::tryFromArray([
    'tenants' => ['your-hex-pubkey'],
    'guest' => [
        'read' => [
            ['kinds' => [0, 1, 6, 7, 30023], 'from' => 'tenants'],
        ],
        'write' => [
            ['kinds' => [7, 9735]],
        ],
    ],
]) ?? throw new RuntimeException('Invalid relay policy configuration');

$policy = new RelayPolicy($authenticationRegistry, $logger, $policyConfig);

$rateLimitPolicy = new StaticRateLimitPolicy(new RateLimitConfig(
    eventsPerMinute: 60,
    subscriptionsPerMinute: 20,
));

$config = new MyRelayConfig();

$nip11InfoProvider = new StaticNip11InfoProvider(Nip11Info::fromArray($config->getRelayUrl(), [
    'name' => 'My Nostr Relay',
    'pubkey' => 'your-hex-pubkey',
    'supported_nips' => [1, 9, 11, 42, 45],
]));

$factory = new RelayServerFactory(
    eventStore: new MyEventStore(),
    policy: $policy,
    config: $config,
    rateLimitPolicy: $rateLimitPolicy,
    authenticationRegistry: $authenticationRegistry,
    logger: $logger,
    nip11InfoProvider: $nip11InfoProvider,
    // Optional: custom HTTP handler for additional endpoints
    // httpHandler: new MyHttpHandler(),
    // Optional: implement Nip11InfoProviderInterface instead for runtime-computed metadata
);

$relay = $factory->create();
$relay->start();
trapSignal([SIGINT, SIGTERM]); // start() is non-blocking; keep the event loop alive until interrupted
$relay->stop();

See examples/relay.example.php for a complete working example with all interface implementations.

3. Configure Nginx

The relay does not handle TLS. Use a reverse proxy for SSL. If the proxy sets X-Forwarded-For, return its address from RelayConfigInterface::getTrustedProxies() as an IPv4/IPv6 string with an optional CIDR mask (e.g. '10.0.0.1', '172.18.0.0/24', '2001:db8::/32'). Invalid entries cause the relay to refuse to start. If no proxy sits in front of the relay, return an empty array; honouring forwarded headers from an untrusted source lets any client spoof their IP.

upstream nostr_relay {
    server 127.0.0.1:8080;
}

server {
    listen 443 ssl;
    server_name relay.example.com;

    location / {
        proxy_pass http://nostr_relay;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

Policy Configuration

The built-in RelayPolicy accepts a configuration array that controls access for tenants and guests.

Tenants

tenants: array of hex pubkeys or npub strings identifying relay owners. Tenants authenticate via NIP-42 and bypass all guest restrictions. If the array is empty or omitted, the relay operates as an open relay (all writes and reads allowed).

Limits

Optional keys with sensible defaults:

  • max_subscriptions - Maximum concurrent subscriptions per client. Also gates COUNT requests: a COUNT from a client already at the cap is rejected with blocked: too many subscriptions (see ADR-0006).
  • max_filters - Maximum filters per subscription
  • max_event_size - Maximum event payload size in bytes
  • max_query_limit - Maximum limit value in REQ filters

Rate-Limit Exemption

RelayPolicyInterface::isRateLimitExempt() lets the policy opt specific clients out of rate limits and subscription caps. The built-in RelayPolicy exempts authenticated tenants (and everyone on an open relay). Implement RelayPolicyInterface directly to exempt other trusted clients — for example, internal services or IPs behind a trusted proxy.

Guest Rules

Unauthenticated clients are treated as guests. Guest permissions are defined under the guest key:

guest.read: array of rules controlling what events guests can query. Each rule has:

  • kinds (int array) - Event kinds the guest may read
  • from (optional, 'tenants') - Restrict results to events authored by tenants

guest.write: array of rules controlling what events guests can publish. Each rule has:

  • kinds (int array) - Event kinds the guest may publish
  • tagged_to_tenant (optional, true) - Require the event to tag a tenant pubkey

If no config is passed, the relay is fully open with no restrictions.

Authentication (NIP-42)

The relay does not challenge on connect. It issues an AUTH challenge only when a subscription requests something outside the guest's scope — when the requested kinds aren't guest-readable, when the requested authors aren't tenants (under from = 'tenants'), or when the filter reads a tenant's mailbox (a #p tag referencing a tenant). The challenge is an offer: a client that authenticates gains full scope, while a client that ignores it still receives the guest-scoped results. The connection is never blocked for not authenticating. (Why a scope-exceeding request rather than every connection: see ADR-0004.)

When a client authenticates, its already-open subscriptions are re-evaluated against its new scope: each is re-admitted with its original filters and the now-visible stored events are streamed, so a subscription opened as a guest widens automatically without the client having to re-subscribe (see ADR-0007).

Architecture

┌─────────────────────────────────────────────────────┐
│ Host Application                                    │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ MyEventStore │ │ MyPolicy     │ │ MyConfig     │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└───────┬──────────────────┬───────────────┬──────────┘
        │                  │               │
┌───────▼──────────────────▼───────────────▼──────────┐
│ innis/nostr-relay                                   │
│                                                     │
│  WebSocket Server → Message Router → Use Cases      │
│                                                     │
│  SubscriptionRegistry → EventDistributor            │
└─────────────────────────────────────────────────────┘

Relay Handles:

  • WebSocket server lifecycle
  • Connection management
  • Message parsing (EVENT, REQ, CLOSE, AUTH, COUNT)
  • NIP-42 authentication (challenge/response)
  • NIP-09 deletion (kind 5 event processing)
  • Ephemeral event handling (kinds 20000-29999)
  • Subscription management and limits
  • Filter matching and event distribution
  • Rate limiting

Host Application Handles:

  • Event storage and queries
  • Access control policies (use built-in RelayPolicy or implement RelayPolicyInterface directly)
  • Server configuration (RelayConfigInterface)
  • NIP-11 metadata (Nip11InfoProviderInterface — built-in StaticNip11InfoProvider, or implement for runtime-computed metadata)
  • Custom HTTP endpoints (optional HttpRequestHandlerInterface)

Testing

composer test

Runs the full test suite and PHPStan level 9 static analysis.

Manual testing with websocat:

websocat ws://localhost:8080

["REQ","test",{"kinds":[1],"limit":10}]

Performance

The relay is designed for concurrent connection handling. Concrete throughput, latency, and memory figures depend on the host's event store and hardware and are not benchmarked here.

Design choices that bear on performance:

  • AMPHP fibres for concurrent clients
  • Subscriptions pre-indexed by event kind, so event distribution looks up only the subscriptions whose filters declare a matching kind (plus kind-agnostic filters) rather than scanning every subscription
  • Per-event filter matching delegated to nostr-core's Filter
  • Non-blocking I/O throughout

License

MIT License. See LICENSE file for details.

innis/nostr-relay 适用场景与选型建议

innis/nostr-relay 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 55 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 03 月 25 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「websocket」 「async」 「protocol」 「amphp」 「Relay」 「nostr」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 innis/nostr-relay 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 innis/nostr-relay 我们能提供哪些服务?
定制开发 / 二次开发

基于 innis/nostr-relay 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 55
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 28
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-03-25