Compare commits

...
174 Commits
Author SHA1 Message Date
Laan Tungir 8453d82ee4 v2.1.31 - Fix make_and_restart_relay.sh: load test keys by default, add --no-test-keys flag 2026-08-02 17:12:52 -04:00
Laan Tungir 8c6770c7e2 v2.1.30 - Add Event Cleanup admin page with saved queries, preview, and delete execution 2026-08-02 17:08:03 -04:00
Laan Tungir 7cd33aa6ea v2.1.29 - Remove SQLite support, hardcode PostgreSQL backend, fix admin API to use first_seen, add pgweb systemd service 2026-08-02 08:20:11 -04:00
Laan Tungir 81c5339063 v2.1.28 - Add kind-3 fallback for relay discovery (NIP-65 pre-standard relay lists in contact list r tags) 2026-07-31 13:58:24 -04:00
Laan Tungir 5ec5ed9bd4 v2.1.27 - Caching page: active backfill row highlight (red border), increase follows limit to 1000, accept npub or hex for caching_root_npubs, fix static caching binary build with PostgreSQL 2026-07-31 11:42:27 -04:00
Laan Tungir bd5150cd09 v2.1.26 - Accept both npub and hex format for caching_root_npubs, fix static caching binary build with PostgreSQL support 2026-07-31 11:23:09 -04:00
Laan Tungir 2e1d731de4 v2.1.25 - Fix: insert missing config keys on existing-relay startup, preserve config.php in deploy_lt.sh 2026-07-31 11:12:21 -04:00
Laan Tungir 75d983b2df v2.1.24 - Reactive caching service start/stop via LISTEN/NOTIFY, per-relay progress display, re-run/refresh caching controls 2026-07-31 10:08:45 -04:00
Laan Tungir a766f08738 v2.1.23 - Caching service: descriptive error reporting, retry limit, inbox poller fix, config reload fix, PHP admin page 2026-07-28 16:37:08 -04:00
Laan Tungir 23c37abe3a Update nostr_core_lib: increase receive timeout to 1000ms 2026-07-28 10:14:47 -04:00
Laan Tungir 5b3ccfd1f4 Update nostr_core_lib: handle NOTICE/CLOSED in synchronous_query 2026-07-28 09:39:22 -04:00
Laan Tungir 38a027e626 Update nostr_core_lib submodule: increase receive buffer to 256KB 2026-07-28 07:52:08 -04:00
Laan Tungir a042aca524 v2.1.22 - Added --start-caching and --reset-backfill CLI options, redirected caching_relay output to log file, increased backfill page size to 200 and max cap to 1000 for better completeness 2026-07-26 18:35:35 -04:00
Laan Tungir 20902e1c5d v2.1.21 - Fixed caching_service_binary_path to ./caching_relay (relative to relay CWD), added caching_relay build step to make_and_restart_relay.sh, added default caching config values (admin npub, 4 bootstrap relays, kinds 0 and 10002) 2026-07-26 14:56:16 -04:00
Laan Tungir 7233818da9 v2.1.20 - Added default caching config values: admin npub as root, 4 bootstrap relays, kinds 0 and 10002 added to default kinds list 2026-07-25 18:21:28 -04:00
Laan Tungir 86cbf7ff16 v2.1.19 - Consolidated caching_relay code into caching/ directory, switched launcher to PostgreSQL mode (-p pg_conn) so all config comes from the config table instead of a JSON config file 2026-07-25 16:27:56 -04:00
Laan Tungir 3035e71ca5 v2.1.18 - Fixed caching service integration: aligned caching_status response schema, fixed launcher to use -c config_path instead of --pg-conn, added caching service config fields to UI, replaced misleading not-implemented placeholder with accurate auth-failure messaging 2026-07-25 11:19:37 -04:00
Laan Tungir 179b160cf3 v2.1.17 - Verified api-worker LISTEN/NOTIFY monitoring on v2.1.16 relay (port 7777): subscriber-cadence, NOTIFY-reactive, and zero-overhead idle tests all pass 2026-07-25 10:17:30 -04:00
Laan Tungir aedd22436e v2.1.16 - Implemented api-worker timer + LISTEN/NOTIFY monitoring (Phases 1,2,5) and fixed thread-connection binding bug that stopped dashboard updates 2026-07-25 10:07:08 -04:00
Laan Tungir 8fdc362a8b v2.1.15 - Fix PG addressable d-tag derivation and replaceable/addressable transactional replace semantics 2026-05-01 19:01:06 -04:00
Laan Tungir 67cf3973c5 v2.1.14 - Fix PostgreSQL replaceable/addressable upsert semantics with NIP-01 conflict ordering 2026-05-01 14:38:57 -04:00
Laan Tungir c02ae2be5b v2.1.13 - Fix PostgreSQL startup detection for existing relays and make admin_pubkey parsing whitespace-tolerant 2026-04-03 16:36:56 -04:00
Laan Tungir fd3efdd01f v2.1.12 - Implement multi-admin auth cache and checks, add admin management commands, and add multi-admin integration test 2026-04-03 15:46:33 -04:00
Laan Tungir 232f93c16f v2.1.11 - Fix api-worker DB lifecycle and NIP test reliability: add dedicated worker DB connection, guard PG expiration cast in REQ path, correct NIP-45 baseline-aware expectation, and harden run_all_tests skip handling 2026-04-03 07:32:14 -04:00
Laan Tungir 9a9365dcfa v2.1.10 - Fix NIP-50 PG search semantics and NIP-17 test compatibility 2026-04-02 13:00:34 -04:00
Laan Tungir 5577b57149 v2.1.9 - Rebrand repository, docs, scripts, and tooling from c-relay to c-relay-pg 2026-04-02 07:06:41 -04:00
Laan Tungir 22c943d495 v2.1.8 - Implement explicit WAL checkpoint scheduling and disable SQLite auto-checkpoint 2026-04-01 21:03:24 -04:00
Laan Tungir 7e3d9b6825 v2.1.7 - Refactor SQLite connection ownership into db_ops, remove direct sqlite usage from runtime modules, and document agent-browser admin testing flow 2026-04-01 19:48:29 -04:00
Laan Tungir 547d22d09f v2.1.6 - Fix strict-mode admin API key access by prewarming config and relay private key caches 2026-04-01 19:08:42 -04:00
Laan Tungir ce9ae5a488 v2.1.5 - Offload duplicate and COUNT paths, add config/NIP-11 caching, and reduce DB reader threads 2026-04-01 16:15:46 -04:00
Laan Tungir 9b248b740f v2.1.4 - Harden async EVENT fallback handling and verify relay/nginx 503 behavior 2026-04-01 15:19:28 -04:00
Laan Tungir 0f77aeb72b v2.1.3 - Fix async EVENT thread safety by splitting store core/post-actions and naming lws main thread 2026-04-01 11:29:14 -04:00
Laan Tungir bb64651a0c v2.1.2 - Fix async REQ callback leak and validate Phase 2+3 subscription offload path 2026-04-01 11:01:38 -04:00
Laan Tungir 17be9c2b03 v2.1.1 - Wire REQ/COUNT/EVENT paths through thread pool sync helpers and stabilize filter limit tests 2026-04-01 09:38:25 -04:00
Laan Tungir f396c622b6 v2.1.0 - Bump to v2.1.0 as SQLite baseline for PostgreSQL fork handoff 2026-04-01 09:14:23 -04:00
Laan Tungir cbd260c1ae v2.0.4 - Complete Phase 1 DB abstraction: opaque db_stmt API, ip_ban migration, and SQLite leakage cleanup 2026-04-01 09:10:42 -04:00
Laan Tungir bc9ac290ab v2.0.3 - Complete Phase 1 SQLite abstraction cleanup across config/api/websockets and add db path accessor 2026-04-01 07:47:21 -04:00
Laan Tungir 192eeb248d v2.0.2 - Stabilize websocket queue draining, add db config count helper, and align NIP-45/50 test expectations 2026-04-01 07:04:58 -04:00
Laan Tungir da6c505420 v2.0.1 - Fix test-mode build/restart pipeline and NIP-11 root path handling; validate core NIP tests 2026-04-01 05:43:57 -04:00
Laan Tungir c077c209e5 v2.0.0 - Major architecture refactor: db_ops abstraction, low-risk SQLite refactor, and thread-pool scaffolding 2026-04-01 05:25:03 -04:00
Laan Tungir 9bf02702c2 v1.2.56 - Update codebase and documentation 2026-04-01 04:51:44 -04:00
Your Name 0920cc092d v1.2.55 - Preserve relay version metadata for API header/title and accept JSON NIP-11 content type 2026-03-23 09:09:50 -04:00
Your Name a89460be5d v1.2.54 - Sync relay_version to compiled CRELAY_VERSION on startup and show version in API header/title 2026-03-23 09:04:49 -04:00
Your Name ff2a3aa335 v1.2.53 - Fix P0 config-value memory leaks in hot paths and add investigation plan 2026-03-19 07:59:40 -04:00
Your Name 94b61e8a7c v1.2.52 - Update nostr_core_lib to v0.4.13 and route nostr logs through relay callback logger 2026-03-13 14:33:08 -04:00
Your Name b0c0754e83 v1.2.51 - Fix NIP-01 non-compliance: limit:0 now correctly returns zero stored events instead of falling through to default_limit 2026-03-04 11:23:58 -04:00
Your Name 3265e3d114 v1.2.50 - Fix NIP-42 relay URL verification and add onauth to all publish flows 2026-03-01 12:20:44 -04:00
Your Name 927659ece1 v1.2.49 - Fix: MEM% bar format (bar then MB), move CPU Core row after CPU Usage 2026-02-25 07:37:50 -04:00
Your Name b6ff4150b4 v1.2.48 - Add CPU% and MEM% ASCII bar graphs to API page stats 2026-02-25 07:33:06 -04:00
Your Name 63bc526163 v1.2.47 - Fix build: use getter function for g_connection_count (was static, can't extern) 2026-02-25 07:24:17 -04:00
Your Name a1f712236a v1.2.46 - Fix: move extern g_connection_count to file scope in api.c 2026-02-25 07:22:14 -04:00
Your Name 06e6c17b7b v1.2.45 - Add WebSocket Connections to system status: api.c sends active_connections, HTML+JS display it 2026-02-25 07:18:42 -04:00
Your Name 0751a7c55c v1.2.44 - IP Bans: show idle+auth bans in stats/status, fix filter buttons, add idle ban columns to query 2026-02-25 07:12:07 -04:00
Your Name f1728932a9 v1.2.43 - IP Bans page: fix filter buttons, add whitelist management UI, compact table rows 2026-02-25 07:08:20 -04:00
Your Name ef8bdef2a8 v1.2.42 - IP Bans page: fix filter buttons, add whitelist management UI, compact table rows 2026-02-25 07:08:12 -04:00
Your Name c11a8ba292 v1.2.41 - Fix IP Bans page: route ip_bans SQL responses to handleIpBansResponse 2026-02-25 06:59:22 -04:00
Your Name 0f124fe575 v1.2.40 - IP Bans page: show whitelisted IPs with star icon, hide Unban button for whitelisted IPs 2026-02-25 06:54:35 -04:00
Your Name 6b20452fab v1.2.39 - Fix IP Bans page: convert rows+columns SQL response format to objects for display 2026-02-25 06:53:50 -04:00
Your Name 10c19bc243 v1.2.38 - Add idle_ban_whitelist config: comma-separated IPs that are never idle-banned 2026-02-25 06:48:41 -04:00
Your Name 3d7aa2196f v1.2.37 - Fix: executeSqlQueryRaw uses relayPool/sendAdminCommand instead of undefined pool variable 2026-02-25 06:43:29 -04:00
Your Name a416c3f275 v1.2.36 - Fix: don't reset relay connection state when external relays time out in subscription onclose 2026-02-25 06:33:25 -04:00
Your Name bba9baabc3 v1.2.35 - Fix: only record idle ban failures for WebSocket connections, not HTTP requests (NIP-11/embedded files) 2026-02-24 17:41:59 -04:00
Your Name 31187c4c4f v1.2.34 - Fix: mark HTTP requests (NIP-11, embedded files) as session_active to prevent idle ban on legitimate HTTP clients 2026-02-24 17:07:33 -04:00
Your Name 55f862b879 v1.2.33 - Set default debug_level to 3 (INFO) 2026-02-24 16:06:19 -04:00
Your Name 76c9b3fcf0 v1.2.32 - Set idle_ban_threshold default to 1 and idle_ban_window_sec default to 30 2026-02-24 15:45:40 -04:00
Your Name 929bd09164 v1.2.31 - Fix idle connection timeout: use global connection list + periodic timer instead of lws_set_timeout which was not firing 2026-02-24 15:44:11 -04:00
Your Name d17f1dd8d5 v1.2.30 - Temporarily bypass admin verification on API page (idle timeout fix pending) 2026-02-24 14:55:39 -04:00
Your Name 207a949835 v1.2.29 - Added idle connection ban system - bans IPs that connect but never send REQ/EVENT (idle timeout or early disconnect) with separate rate limiting from auth failures 2026-02-24 14:18:27 -04:00
Your Name d08f4e4221 v1.2.28 - Add debug_level config setting: live update every 60s without restart, default 0 2026-02-24 08:56:32 -04:00
Your Name c96736fa6a v1.2.27 - Permanent ban escalation: ban_count never resets, IPs with history always get 24h ban on next failure 2026-02-23 18:18:55 -04:00
Your Name f8ec4ae924 v1.2.26 - Persistent IP ban table: save/load to ip_bans DB, auth success tracking, lifetime stats per IP 2026-02-23 18:16:14 -04:00
Your Name fe7304ac7f v1.2.25 - Add NIP-42 AUTH support to web UI: onauth callback in subscribeMany and publish calls 2026-02-23 18:03:29 -04:00
Your Name e5d39c984b v1.2.24 - Fix: run ip_ban maintenance unconditionally every 60s regardless of max_connection_seconds setting 2026-02-23 17:57:20 -04:00
Your Name 5e45f21e35 v1.2.23 - ip_ban: retain ban_count for 24 hours after last ban expiry, then fully clean entry 2026-02-23 17:16:02 -04:00
Your Name 5321a238b8 v1.2.22 - Fix ip_ban cleanup: preserve ban_count on cleanup so exponential backoff persists across ban expiry 2026-02-23 17:12:15 -04:00
Your Name 083bc14972 v1.2.21 - Fix IP ban check: use pss->client_ip (proxy-aware) instead of raw socket IP to match failure recording 2026-02-23 16:26:23 -04:00
Your Name 11aaccba9b v1.2.20 - Add IP auth failure ban system: track failures per IP, ban after threshold with exponential backoff, periodic log stats 2026-02-23 16:02:07 -04:00
Your Name bd1bbd763d v1.2.19 - Add IP auth failure ban system: track failures per IP, ban after threshold with exponential backoff, periodic log stats 2026-02-23 16:00:53 -04:00
Your Name 2bd7aa5a10 v1.2.18 - Proactive auth timeout via lws_set_timeout: close idle unauthenticated connections after nip42_auth_timeout_sec even without REQ 2026-02-23 15:37:45 -04:00
Your Name 361912ec85 v1.2.17 - Add nip42_auth_timeout_sec (default 10s): close unauthenticated connections after timeout to prevent connection accumulation 2026-02-23 15:22:18 -04:00
Your Name 0de491382e v1.2.16 - Fix auth rules UI: change label/placeholder/errors from nsec to npub (public key, not private key) 2026-02-23 14:36:19 -04:00
Your Name 3148bbbee7 v1.2.15 - Admin verification: show 'Waiting for response' with status updates, 60s timeout, no premature access denied 2026-02-23 14:26:02 -04:00
Your Name 3965ba04d8 v1.2.14 - Handle late admin verification response: grant access even if timeout already fired and access-denied overlay was shown 2026-02-23 14:24:55 -04:00
Your Name b96af938bd v1.2.13 - Add WoT status to NIP-11 response: restricted_writes, auth_required, and web_of_trust object when WoT enabled 2026-02-23 14:22:08 -04:00
Your Name 89c8248013 v1.2.12 - Increase admin verification timeout from 5s to 30s; continue waiting after publish timeout under heavy load 2026-02-23 14:16:56 -04:00
Your Name d8f477c6cf v1.2.11 - Early duplicate check before secp256k1 signature verification — skip crypto for events already in DB 2026-02-23 14:01:23 -04:00
Your Name 040eeadb13 v1.2.10 - Zero-copy message queue: eliminate memcpy in broadcast and REQ paths via queue_message_take_ownership() 2026-02-23 13:45:56 -04:00
Your Name 83f8b0ab88 v1.2.9 - Replace cJSON_GetObjectItem with CaseSensitive variant (178 calls) — eliminates 14% CPU from tolower+linear scan in hot path 2026-02-23 13:25:50 -04:00
Your Name b82f7eaaf3 v1.2.8 - Add debug build support: _debug suffix in build_static.sh, deploy_lt_debug.sh with perf profiling workflow 2026-02-23 13:07:53 -04:00
Your Name 0dc5b75d7c v1.2.7 - Add configurable SQLite mmap_size (256MB) and cache_size (64MB) PRAGMAs for ~20% CPU reduction from DB read overhead 2026-02-23 09:48:01 -04:00
Your Name e94b1a81e3 v1.2.6 - Add MAX_MESSAGE_QUEUE_SIZE=500 limit to prevent OOM from unbounded write queue; fix wasted buf allocation in broadcast 2026-02-23 09:12:09 -04:00
Your Name 1adabdbc4e v1.2.5 - Add nip17_admin_enabled config toggle (default off) to prevent OOM from NIP-17 gift wrap decryption flood 2026-02-23 08:58:10 -04:00
Your Name 81d44c3d8c v1.2.4 - Add more characters to valid subscription characters 2026-02-11 05:56:09 -04:00
Your Name 7acc0bdd90 v1.2.3 - Handle rate limiting properly on kind 99999 request 2026-02-09 07:49:43 -04:00
Your Name c4ef71d673 v1.2.2 - Add + to allowed subscription characters 2026-02-07 13:32:00 -04:00
Your Name 086d2af56c v1.2.1 - Handle NDKs pings of kind 99999 2026-02-03 13:19:33 -04:00
Your Name 18a7deec54 v1.2.0 - Schema v11: Added event_json storage for 2500x performance improvement 2026-02-02 20:27:50 -04:00
Your Name c794370a3f v1.1.9 - Add composite index for active_subscriptions_log view optimization (schema v10) - reduces monitoring queries from 540ms to <10ms 2026-02-02 11:38:57 -04:00
Your Name 2ed4b96058 v1.1.8 - Add comprehensive database query logging with timing at debug level 3, fix schema version compatibility (v6-v9), add version logging at startup, allow monitoring throttle=0 to disable monitoring 2026-02-02 11:20:11 -04:00
Your Name c0051b22be v1.1.7 - Add per-connection database query tracking for abuse detection
Implemented comprehensive database query tracking to identify clients causing
high CPU usage through excessive database queries. The relay now tracks and
displays query statistics per WebSocket connection in the admin UI.

Features Added:
- Track db_queries_executed and db_rows_returned per connection
- Calculate query rate (queries/minute) and row rate (rows/minute)
- Display stats in admin UI grouped by IP address and WebSocket
- Show: IP, Subscriptions, Queries, Rows, Query Rate, Duration

Implementation:
- Added tracking fields to per_session_data structure
- Increment counters in handle_req_message() and handle_count_message()
- Extract stats from pss in query_subscription_details()
- Updated admin UI to display IP address and query metrics

Use Case:
Admins can now identify abusive clients by monitoring:
- High query rates (>50 queries/min indicates polling abuse)
- High row counts (>10K rows/min indicates broad filter abuse)
- Query patterns (high queries + low rows = targeted, high both = crawler)

This enables informed decisions about which IPs to blacklist based on
actual resource consumption rather than just connection count.
2026-02-01 16:26:37 -04:00
Your Name 4cc2d2376e v1.1.6 - Optimize: Deduplicate kinds in subscription index to prevent redundant operations
The kind index was adding subscriptions multiple times when filters contained
duplicate kinds (e.g., 'kinds': [1, 1, 1] or multiple filters with same kind).
This caused:
- Redundant malloc/free operations during add/remove
- Multiple index entries for same subscription+kind pair
- Excessive TRACE logging (7+ removals for single subscription)
- Wasted CPU cycles on duplicate operations

Fix:
- Added bitmap-based deduplication in add_subscription_to_kind_index()
- Uses 8KB bitmap (65536 bits) to track which kinds already added
- Prevents adding same subscription to same kind index multiple times
- Reduces index operations by 3-10x for subscriptions with duplicate kinds

Performance Impact:
- Eliminates redundant malloc/free cycles
- Reduces lock contention on kind index operations
- Decreases log volume significantly
- Should reduce CPU usage by 20-40% under production load
2026-02-01 15:59:54 -04:00
Your Name 30dc4bf67d v1.1.5 - Fix CRITICAL segfault: Use wrapper nodes for no-kind-filter subscriptions
The kind index optimization in v1.1.4 introduced a critical bug that caused
segmentation faults in production. The bug was in add_subscription_to_kind_index()
which directly assigned sub->next for no-kind-filter subscriptions, corrupting
the main active_subscriptions linked list.

Root Cause:
- subscription_t has only ONE 'next' pointer used by active_subscriptions list
- Code tried to reuse 'next' for no_kind_filter_subs list
- This overwrote the active_subscriptions linkage, breaking list traversal
- Result: segfaults when iterating subscriptions

Fix:
- Added no_kind_filter_node_t wrapper structure (like kind_subscription_node_t)
- Changed no_kind_filter_subs from subscription_t* to no_kind_filter_node_t*
- Updated add/remove functions to use wrapper nodes
- Updated broadcast function to iterate through wrapper nodes

This follows the same pattern already used for kind_index entries and
prevents any corruption of the subscription structure's next pointer.
2026-02-01 12:37:07 -04:00
Your Name a1928cc5d7 v1.1.4 - Add kind-based index for 10x subscription matching performance improvement 2026-02-01 11:15:26 -04:00
Your Name 7bf0757b1f v1.1.3 - Rename VERSION to CRELAY_VERSION to avoid conflict with nostr_core_lib 2026-02-01 10:02:19 -04:00
Your Name 11b0a88cdd v1.1.2 - Fix hardcoded version string in print_version() - use VERSION define from main.h
The print_version() function was displaying a hardcoded 'v1.0.0' string instead
of using the VERSION define from main.h. This caused version mismatches where
the git tag and main.h showed v1.1.1 but the binary reported v1.0.0.

Now print_version() uses the VERSION macro, ensuring all version displays are
consistent and automatically updated when increment_and_push.sh updates main.h.
2026-01-31 16:48:11 -04:00
Your Name e8f8e3b0cf v1.1.1 - Fix NOTICE message silent failure - add pss parameter to send_notice_message()
Previously, send_notice_message() called queue_message() with NULL pss, causing
all NOTICE messages to fail silently. This affected filter validation errors
(e.g., invalid kinds > 65535 per NIP-01) where clients received no response.

Changes:
- Updated send_notice_message() signature to accept struct per_session_data* pss
- Updated 37 call sites across websockets.c (31) and nip042.c (6)
- Updated forward declarations in main.c, websockets.c, and nip042.c
- Added tests/invalid_kind_test.sh to verify NOTICE responses for invalid filters

Fixes issue where REQ with kinds:[99999] received no response instead of NOTICE.
2026-01-31 15:48:29 -04:00
Your Name 35b1461ff6 v1.1.0 - Documentation restructure: New README.md and comprehensive API.md 2026-01-23 06:58:14 -04:00
Your Name 87c6aa5e16 . 2025-12-21 09:30:48 -04:00
Your Name f0462929ea v1.0.9 - Fix active_subscriptions_log view to check ended_at column instead of looking for separate close events (schema v9) 2025-12-09 06:29:36 -04:00
Your Name 0ccf1959c2 v1.0.8 - Remove limit from subscription count 2025-12-07 07:40:17 -04:00
Your Name ef172c8356 v1.0.7 - Added a clean startup to erase subscriptions. 2025-12-06 18:26:22 -04:00
Your Name ae0370b47f v1.0.6 - Working on cleaning up subscriptions which were piling up. Set a startup cleanup, and a connection age limit. 2025-12-05 07:37:57 -04:00
Your Name 137df6253d v1.0.5 - Just catching up 2025-11-11 17:01:39 -04:00
Your Name 79f06bf1ed v1.0.4 - Fixed web socket limitation with the number of npubs in a subscription 2025-11-07 19:59:34 -05:00
laan tungir 9e5a42fd3c v1.0.3 - From remote 2025-11-07 14:07:46 -05:00
laan tungir 553d398ef5 removed some files 2025-11-03 07:33:12 -05:00
Your Name ac0bf17c4f v1.0.0 - First major release 2025-11-01 07:04:56 -04:00
Your Name 4efd7140d7 Reset version back to v0.8.6 2025-11-01 07:03:39 -04:00
Your Name 43b9027d4a v7.0.0 - Version 1.0.0 2025-11-01 06:56:02 -04:00
Your Name 9a0b2714f5 v7.0.0 - Version 1.0.0 2025-10-31 11:17:34 -04:00
Your Name 10fc80aa07 v6.0.0 - Test fixed output redirection 2025-10-31 11:17:19 -04:00
Your Name 9fa4e1dcf1 v6.0.0 - Version 1.0.0 2025-10-31 11:15:32 -04:00
Your Name 11c9f8c2a4 v5.0.0 - Test fixed release ID extraction 2025-10-31 11:15:16 -04:00
Your Name 17ddf3f2b8 v5.0.0 - Version 1.0.0 2025-10-31 11:13:41 -04:00
Your Name f70d5077e8 v4.0.0 - Debug upload with existing binary 2025-10-31 11:13:21 -04:00
Your Name 19cc1fa804 v4.0.0 - Debug upload issue 2025-10-31 11:12:58 -04:00
Your Name ed1de7d32d v4.0.0 - Version 1.0.0 2025-10-31 11:11:31 -04:00
Your Name 7b02c6a2cb v3.0.0 - Test release upload fix 2025-10-31 11:10:53 -04:00
Your Name dba229040d v3.0.0 - Version 1.0.0 2025-10-31 11:08:18 -04:00
Your Name d8b09e0dbb v2.0.0 - Version 1.0.0 2025-10-31 11:06:27 -04:00
Your Name 73fb681118 v1.0.0 - Version 1.0.0) 2025-10-31 10:39:06 -04:00
Your Name cf0f606dd9 v0.8.5 - cleanup 2025-10-30 07:06:48 -04:00
Your Name cb165ddab1 v0.8.4 - Updated increment_and_push.sh 2025-10-30 07:03:22 -04:00
Your Name 58eb19e4e7 v0.8.3 - --dry-run 2025-10-30 06:52:05 -04:00
Your Name aeaecddb5a v0.8.2 - markdown intro 2025-10-29 07:53:56 -04:00
Your Name 09dff8e91f v0.8.1 - added screenshots 2025-10-29 07:39:08 -04:00
Your Name f49aae8ab0 v0.7.44 - Release v0.8.0 with NIP-59 timestamp randomization and status command fixes 2025-10-27 13:21:47 -04:00
Your Name f6debcf799 v0.7.44 - Release v0.8.0 with NIP-59 timestamp randomization and status command fixes 2025-10-27 13:19:58 -04:00
Your Name edbc4f1359 v0.7.43 - Add plain text 'status' command handler for NIP-17 DMs 2025-10-27 13:19:10 -04:00
Your Name 5242f066e7 Update nostr_core_lib with timestamp randomization feature 2025-10-27 12:59:19 -04:00
Your Name af186800fa v0.7.42 - Fix ephemeral event storage and document monitoring system 2025-10-26 15:02:00 -04:00
Your Name 2bff4a5f44 v0.7.41 - Fix SQL query routing in admin API - add missing sql_query case to handle_kind_23456_unified 2025-10-26 13:34:16 -04:00
Your Name edb73d50cf v0.7.40 - Removed event_broadcasts table and related code to fix FOREIGN KEY constraint failures preventing event insertion 2025-10-25 15:26:31 -04:00
Your Name 3dc09d55fd v0.7.39 - Set dm's back 2 days to adjust for timestamp ramdomization of giftwraps. 2025-10-23 18:43:45 -03:00
Your Name 079fb1b0f5 v0.7.38 - Fixed error upon startup with existing db 2025-10-23 11:17:16 -04:00
Your Name 17b2aa8111 v0.7.37 - Enhanced admin interface with sliding sidebar navigation, moved dark mode and logout to sidebar footer, improved button styling consistency 2025-10-22 12:43:09 -04:00
Your Name 78d484cfe0 v0.7.36 - Implement sliding side navigation menu with page switching for admin sections 2025-10-22 11:01:30 -04:00
Your Name 182e12817d v0.7.35 - Implement event-driven monitoring system with dual triggers for events and subscriptions 2025-10-22 10:48:57 -04:00
Your Name 9179d57cc9 v0.7.34 - We seemed to maybe finally fixed the monitoring error? 2025-10-22 10:19:43 -04:00
Your Name 9cb9b746d8 v0.7.33 - Refactor monitoring system to use subscription-based activation with ephemeral events - fixes recursive crash bug 2025-10-19 10:26:09 -04:00
Your Name 57a0089664 v0.7.32 - Implement ephemeral event bypass (NIP-01) - events with kinds 20000-29999 are now broadcast to subscriptions but never stored in database, preventing recursive monitoring event loops 2025-10-19 09:38:02 -04:00
Your Name 53f7608872 v0.7.31 - Fixed production crash by replacing in-memory subscription iteration with database queries in monitoring system 2025-10-18 18:09:13 -04:00
Your Name 838ce5b45a v0.7.30 - Update increment and push script 2025-10-18 15:04:45 -04:00
Your Name e878b9557e v0.7.29 - Update increment and push script 2025-10-18 14:57:34 -04:00
Your Name 6638d37d6f v0.7.28 - Update increment and push script 2025-10-18 14:55:51 -04:00
Your Name 4c29e15329 v0.7.27 - Update increment and push script 2025-10-18 14:53:37 -04:00
Your Name 48890a2121 v0.7.26 - Tidy up api 2025-10-18 14:48:16 -04:00
Your Name e312d7e18c v0.7.25 - Implement SQL Query Admin API
- Move non-NIP-17 admin functions from dm_admin.c to api.c for better architecture
- Add NIP-44 encryption to send_admin_response() for secure admin responses
- Implement SQL query validation and execution with safety limits
- Add unified SQL query handler for admin API
- Fix buffer size for encrypted content to handle larger responses
- Update function declarations and includes across files
- Successfully test frontend query execution through web interface
2025-10-16 15:41:21 -04:00
Your Name 6c38aaebf3 v0.7.24 - Fix admin API subscription issues: NIP-17 historical events and relay pubkey timing 2025-10-16 06:27:01 -04:00
Your Name 18b0ac44bf v0.7.23 - Remove sticky positioning from main header to prevent floating behavior 2025-10-15 19:22:42 -04:00
Your Name b6749eff2f v0.7.22 - Fix compiler warnings in c_utils_lib version.c by adding proper includes for popen/pclose functions 2025-10-15 19:20:14 -04:00
Your Name c73a103280 v0.7.21 - Remove manual relay connection UI and implement automatic background connection with seamless data loading 2025-10-15 16:50:22 -04:00
Your Name a5d194f730 v0.7.20 - Fix automatic relay connection for restored authentication state 2025-10-15 15:47:02 -04:00
Your Name 6320436b88 v0.7.19 - Implement automatic relay connection after login with authentication error handling 2025-10-15 15:41:18 -04:00
Your Name 87325927ed v0.7.18 - Fixed duplicate login modal bug and improved header layout 2025-10-15 15:31:44 -04:00
Your Name 4435cdf5b6 Add c_utils_lib as submodule with debug and version utilities 2025-10-15 10:29:35 -04:00
Your Name b041654611 v0.7.17 - Fixed critical race condition in CLOSE message handler causing segfault during subscription storms 2025-10-15 09:10:18 -04:00
Your Name e833dcefd4 v0.7.16 - Fixed blacklist authentication system - removed redundant action/parameters columns, added active=1 filtering, added comprehensive debug tracing, and identified that auth must be enabled for blacklist to work 2025-10-14 13:07:19 -04:00
Your Name 29680f0ee8 v0.7.15 - Fixed race condition in subscription management causing intermittent core dumps and format truncation warning 2025-10-14 11:34:55 -04:00
Your Name 670329700c v0.7.14 - Remove unified config cache system and fix first-time startup - All config values now queried directly from database, eliminating cache inconsistency bugs. Fixed startup sequence to use output parameters for pubkey passing. 2025-10-13 19:06:27 -04:00
Your Name 62e17af311 v0.7.13 - -t 2025-10-13 16:35:26 -04:00
Your Name e3938a2c85 v0.7.12 - Implemented comprehensive debug system with 6 levels (0-5), file:line tracking at TRACE level, deployment script integration, and default level 5 for development 2025-10-13 12:44:18 -04:00
Your Name 49ffc3d99e v0.7.11 - Got api back working after switching to static build 2025-10-12 14:54:02 -04:00
Your Name 34bb1c34a2 v0.7.10 - Fixed api errors in accepting : in subscriptions 2025-10-12 10:31:03 -04:00
323 changed files with 118975 additions and 10739 deletions
+1 -1
View File
@@ -11,4 +11,4 @@ copy_executable_local.sh
nostr_login_lite/
style_guide/
nostr-tools
.test_keysadmin/cache/
+6 -3
View File
@@ -1,3 +1,6 @@
[submodule "nostr_core_lib"]
path = nostr_core_lib
url = https://git.laantungir.net/laantungir/nostr_core_lib.git
[submodule "c_utils_lib"]
path = c_utils_lib
url = ssh://git@git.laantungir.net:2222/laantungir/c_utils_lib.git
[submodule "text_graph"]
path = text_graph
url = ssh://git@git.laantungir.net:2222/laantungir/text_graph.git
+2
View File
@@ -0,0 +1,2 @@
ADMIN_PUBKEY='6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3'
SERVER_PRIVKEY='1111111111111111111111111111111111111111111111111111111111111111'
+11 -11
View File
@@ -1,12 +1,12 @@
# AGENTS.md - AI Agent Integration Guide for Architect Mode
**Project-Specific Information for AI Agents Working with C-Relay in Architect Mode**
**Project-Specific Information for AI Agents Working with C-Relay-PG in Architect Mode**
## Critical Architecture Understanding
### System Architecture Overview
C-Relay implements a **unique event-based configuration architecture** that fundamentally differs from traditional Nostr relays:
C-Relay-PG implements a **unique event-based configuration architecture** that fundamentally differs from traditional Nostr relays:
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
@@ -51,9 +51,9 @@ C-Relay implements a **unique event-based configuration architecture** that fund
## Architectural Decision Analysis
### Configuration System Design
**Traditional Approach vs C-Relay:**
**Traditional Approach vs C-Relay-PG:**
```
Traditional: C-Relay:
Traditional: C-Relay-PG:
config.json → kind 33334 events
ENV variables → cryptographically signed tags
File watching → database polling/restart
@@ -119,18 +119,18 @@ File watching → database polling/restart
```
Developer Machine:
├── ./make_and_restart_relay.sh
├── build/c_relay_x86
├── build/c_relay_pg_x86
├── build/<relay_pubkey>.db
└── relay.log
```
### Production SystemD Deployment
```
/opt/c-relay/:
├── c_relay_x86
/opt/c-relay-pg/:
├── c_relay_pg_x86
├── <relay_pubkey>.db
├── systemd service (c-relay.service)
└── c-relay user isolation
├── systemd service (c-relay-pg.service)
└── c-relay-pg user isolation
```
### Container Deployment Architecture
@@ -144,7 +144,7 @@ Container:
### Reverse Proxy Architecture
```
Internet → Nginx/HAProxy → C-Relay
Internet → Nginx/HAProxy → C-Relay-PG
├── WebSocket upgrade handling
├── SSL termination
└── Rate limiting
@@ -292,7 +292,7 @@ Admin Signs Event → WebSocket Submit → Validate → Store → Restart Requir
**Decision**: Same port serves both protocols
**Consequences**: Simplified deployment, protocol detection overhead, libwebsockets dependency
These architectural decisions form the foundation of C-Relay's unique approach to Nostr relay implementation and should be carefully considered when planning extensions or modifications.
These architectural decisions form the foundation of C-Relay-PG's unique approach to Nostr relay implementation and should be carefully considered when planning extensions or modifications.
**
[Response interrupted by a tool use result. Only one tool may be used at a time and should be placed at the end of the message.]
+2 -2
View File
@@ -2,6 +2,6 @@
description: "Brief description of what this command does"
---
Run build_and_push.sh, and supply a good git commit message. For example:
Run increment_and_push.sh, and supply a good git commit message. For example:
./build_and_push.sh "Fixed the bug with nip05 implementation"
./increment_and_push.sh "Fixed the bug with nip05 implementation"
+1 -1
View File
@@ -1 +1 @@
src/embedded_web_content.c
src/embedded_web_content.c
+2
View File
@@ -0,0 +1,2 @@
ADMIN_PUBKEY='8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e'
SERVER_PRIVKEY='1111111111111111111111111111111111111111111111111111111111111111'
Executable
BIN
View File
Binary file not shown.
+63 -10
View File
@@ -1,6 +1,59 @@
# AGENTS.md - AI Agent Integration Guide
**Project-Specific Information for AI Agents Working with C-Relay**
**Project-Specific Information for AI Agents Working with C-Relay-PG**
## ⚠️ CRITICAL: Never Run `make` Directly
**NEVER run `make` to build the relay binary.** The Makefile will refuse and
print an error. Running `make` directly produces a dynamically-linked binary
that will silently fall back to SQLite storage while the admin UI connects to
PostgreSQL — causing the admin page to show stale data.
Always use one of these instead:
- **`./build_static.sh`** — Build the static MUSL binary with PostgreSQL backend
- **`./make_and_restart_relay.sh`** — Build, kill old relay, and start new one
The Makefile exists only for submodule compilation (nostr_core_lib, c_utils_lib)
and utility targets (clean, install-deps). It will refuse to build the relay
binary directly.
## Critical Build Commands
### Primary Build Command
```bash
./make_and_restart_relay.sh
```
**Never use `make` directly.** The project requires the custom restart script which:
- Handles database preservation/cleanup based on flags
- Manages architecture-specific binary detection (x86/ARM64)
- Performs automatic process cleanup and port management
- Starts relay in background with proper logging
**Note:** `--test-keys` / `-t` is now the **default**. Use `--no-test-keys` or `--production` for random key generation.
### Architecture-Specific Binary Outputs
- **x86_64**: `./build/c_relay_pg_x86`
- **ARM64**: `./build/c_relay_pg_arm64`
- **Other**: `./build/c_relay_pg_$(ARCH)`
**Project-Specific Information for AI Agents Working with C-Relay-PG**
## ⚠️ CRITICAL: Never Run `make` Directly
**NEVER run `make` to build the relay binary.** The Makefile will refuse and
print an error. Running `make` directly produces a dynamically-linked binary
that will silently fall back to SQLite storage while the admin UI connects to
PostgreSQL — causing the admin page to show stale data.
Always use one of these instead:
- **`./build_static.sh`** — Build the static MUSL binary with PostgreSQL backend
- **`./make_and_restart_relay.sh`** — Build, kill old relay, and start new one
The Makefile exists only for submodule compilation (nostr_core_lib, c_utils_lib)
and utility targets (clean, install-deps). It will refuse to build the relay
binary directly.
## Critical Build Commands
@@ -15,9 +68,9 @@
- Starts relay in background with proper logging
### Architecture-Specific Binary Outputs
- **x86_64**: `./build/c_relay_x86`
- **ARM64**: `./build/c_relay_arm64`
- **Other**: `./build/c_relay_$(ARCH)`
- **x86_64**: `./build/c_relay_pg_x86`
- **ARM64**: `./build/c_relay_pg_arm64`
- **Other**: `./build/c_relay_pg_$(ARCH)`
### Database File Naming Convention
- **Format**: `<relay_pubkey>.db` (NOT `.nrdb` as shown in docs)
@@ -75,10 +128,10 @@
### Process Management
```bash
# Kill existing relay processes
pkill -f "c_relay_"
pkill -f "c_relay_pg_"
# Check running processes
ps aux | grep c_relay_
ps aux | grep c_relay_pg_
# Force kill port binding
fuser -k 8888/tcp
@@ -95,7 +148,7 @@ fuser -k 8888/tcp
- Event configuration tests: `tests/event_config_tests.sh`
### SystemD Integration Considerations
- Service runs as `c-relay` user in `/opt/c-relay`
- Service runs as `c-relay-pg` user in `/opt/c-relay-pg`
- Database files created in WorkingDirectory automatically
- No environment variables needed (event-based config)
- Resource limits: 65536 file descriptors, 4096 processes
@@ -121,8 +174,8 @@ fuser -k 8888/tcp
- Event filtering done at C level, not SQL level for NIP-40 expiration
### Configuration Override Behavior
- CLI port override only affects first-time startup
- After database creation, all config comes from events
- CLI port override applies during first-time startup and existing relay restarts
- After database creation, all config comes from events (but CLI overrides can still be applied)
- Database path cannot be changed after initialization
## Non-Obvious Pitfalls
@@ -137,7 +190,7 @@ fuser -k 8888/tcp
## Quick Debugging Commands
```bash
# Check relay status
ps aux | grep c_relay_ && netstat -tln | grep 8888
ps aux | grep c_relay_pg_ && netstat -tln | grep 8888
# View logs
tail -f relay.log
+995
View File
@@ -0,0 +1,995 @@
# C-Relay-PG API Documentation
Complete API reference for the C-Relay-PG event-based administration system and advanced features.
## Table of Contents
- [Overview](#overview)
- [Authentication](#authentication)
- [Admin API](#admin-api)
- [Event Structure](#event-structure)
- [Configuration Management](#configuration-management)
- [Auth Rules Management](#auth-rules-management)
- [System Commands](#system-commands)
- [Database Queries](#database-queries)
- [Configuration Reference](#configuration-reference)
- [Real-time Monitoring](#real-time-monitoring)
- [Direct Message Admin](#direct-message-admin)
- [Response Formats](#response-formats)
- [Error Handling](#error-handling)
- [Examples](#examples)
## Overview
C-Relay-PG uses an innovative **event-based administration system** where all configuration and management commands are sent as cryptographically signed Nostr events. This provides:
- **Cryptographic security**: All commands must be signed with the admin private key
- **Audit trail**: Complete history of all administrative actions
- **Real-time updates**: Configuration changes applied instantly
- **Standard protocol**: Uses Nostr events, no custom protocols
- **NIP-44 encryption**: All admin commands and responses are encrypted
### Key Concepts
1. **Admin Keypair**: Generated on first startup, used to sign all admin commands
2. **Relay Keypair**: The relay's identity on the Nostr network
3. **Admin Events**: Kind 23456 events with NIP-44 encrypted commands
4. **Response Events**: Kind 23457 events with NIP-44 encrypted responses
5. **Event-Based Config**: All settings stored as events in the database
## Authentication
### Admin Private Key
The admin private key is displayed **only once** during first startup:
```
========================================
IMPORTANT: SAVE THIS ADMIN PRIVATE KEY
========================================
Admin Private Key: nsec1abc123...
Admin Public Key: npub1def456...
========================================
```
**Critical**: Save this key immediately. It cannot be recovered and is required for all administrative operations.
### Secure Storage
Store the admin private key securely:
```bash
# Environment variable
export C_RELAY_ADMIN_KEY="nsec1abc123..."
# Secure file
echo "nsec1abc123..." > ~/.c-relay-pg-admin
chmod 600 ~/.c-relay-pg-admin
# Password manager (recommended)
# Store in 1Password, Bitwarden, etc.
```
### Key Loss Recovery
If you lose the admin private key:
1. Stop the relay
2. Delete the database file (`*.db`)
3. Restart the relay (generates new keys)
4. **Note**: This deletes all events and configuration
## Admin API
### Event Structure
All admin commands use the same event structure with NIP-44 encrypted content.
#### Admin Command Event (Kind 23456)
```json
{
"id": "computed_event_id",
"pubkey": "admin_public_key_hex",
"created_at": 1234567890,
"kind": 23456,
"content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...",
"tags": [
["p", "relay_public_key_hex"]
],
"sig": "event_signature"
}
```
**Fields**:
- `kind`: Must be `23456` for admin commands
- `pubkey`: Admin public key (hex format)
- `content`: NIP-44 encrypted JSON array containing the command
- `tags`: Must include `["p", "relay_pubkey"]` tag
- `sig`: Valid signature from admin private key
#### Encrypted Content Format
The `content` field contains a NIP-44 encrypted JSON array:
```json
["command_name", "param1", "param2", ...]
```
Examples:
```json
["config_query", "all"]
["config_update", [{"key": "relay_name", "value": "My Relay", ...}]]
["blacklist", "pubkey", "abc123..."]
["auth_query", "all"]
["sql_query", "SELECT * FROM events LIMIT 10"]
```
#### Admin Response Event (Kind 23457)
```json
{
"id": "response_event_id",
"pubkey": "relay_public_key_hex",
"created_at": 1234567890,
"kind": 23457,
"content": "BpKCVhfN8eYtRmPqSvWxZnMkL2gHjUiOp3rTyEwQaS5dFg...",
"tags": [
["p", "admin_public_key_hex"],
["e", "request_event_id"]
],
"sig": "response_signature"
}
```
**Fields**:
- `kind`: Always `23457` for admin responses
- `pubkey`: Relay public key (hex format)
- `content`: NIP-44 encrypted JSON response object
- `tags`: Includes `["p", "admin_pubkey"]` and optionally `["e", "request_id"]`
- `sig`: Valid signature from relay private key
### Configuration Management
#### Query All Configuration
**Command**:
```json
["config_query", "all"]
```
**Response** (decrypted):
```json
{
"query_type": "config_all",
"total_results": 27,
"timestamp": 1234567890,
"data": [
{
"key": "relay_name",
"value": "C-Relay-PG",
"data_type": "string",
"category": "relay",
"description": "Relay name displayed in NIP-11"
},
{
"key": "auth_enabled",
"value": "false",
"data_type": "boolean",
"category": "auth",
"description": "Enable whitelist/blacklist authentication"
}
]
}
```
#### Update Configuration
**Command**:
```json
["config_update", [
{
"key": "relay_name",
"value": "My Awesome Relay",
"data_type": "string",
"category": "relay"
},
{
"key": "max_subscriptions_per_client",
"value": "50",
"data_type": "integer",
"category": "limits"
}
]]
```
**Response** (decrypted):
```json
{
"query_type": "config_update",
"status": "success",
"total_results": 2,
"timestamp": 1234567890,
"data": [
{
"key": "relay_name",
"value": "My Awesome Relay",
"status": "updated"
},
{
"key": "max_subscriptions_per_client",
"value": "50",
"status": "updated"
}
]
}
```
### Auth Rules Management
#### Add Blacklist Rule
**Command**:
```json
["blacklist", "pubkey", "abc123def456..."]
```
**Response** (decrypted):
```json
{
"query_type": "auth_add",
"status": "success",
"message": "Blacklist rule added successfully",
"timestamp": 1234567890
}
```
#### Add Whitelist Rule
**Command**:
```json
["whitelist", "pubkey", "def456abc123..."]
```
#### Delete Auth Rule
**Command**:
```json
["delete_auth_rule", "blacklist", "pubkey", "abc123def456..."]
```
**Response** (decrypted):
```json
{
"query_type": "auth_delete",
"status": "success",
"message": "Auth rule deleted successfully",
"timestamp": 1234567890
}
```
#### Query All Auth Rules
**Command**:
```json
["auth_query", "all"]
```
**Response** (decrypted):
```json
{
"query_type": "auth_rules_all",
"total_results": 5,
"timestamp": 1234567890,
"data": [
{
"rule_type": "blacklist",
"pattern_type": "pubkey",
"pattern_value": "abc123...",
"action": "deny"
},
{
"rule_type": "whitelist",
"pattern_type": "pubkey",
"pattern_value": "def456...",
"action": "allow"
}
]
}
```
#### Query Specific Rule Type
**Command**:
```json
["auth_query", "whitelist"]
```
#### Query Specific Pattern
**Command**:
```json
["auth_query", "pattern", "abc123..."]
```
### System Commands
#### System Status
**Command**:
```json
["system_command", "system_status"]
```
**Response** (decrypted):
```json
{
"query_type": "system_status",
"timestamp": 1234567890,
"status": "running",
"uptime_seconds": 86400,
"version": "0.6.0",
"relay_pubkey": "relay_public_key_hex",
"database_size_bytes": 10485760,
"total_events": 15432,
"active_connections": 42,
"active_subscriptions": 156
}
```
#### Clear All Auth Rules
**Command**:
```json
["system_command", "clear_all_auth_rules"]
```
**Response** (decrypted):
```json
{
"query_type": "system_command",
"status": "success",
"message": "All auth rules cleared",
"timestamp": 1234567890
}
```
#### Database Statistics
**Command**:
```json
["stats_query"]
```
**Response** (decrypted):
```json
{
"query_type": "stats_query",
"timestamp": 1234567890,
"database_size_bytes": 10485760,
"total_events": 15432,
"database_created_at": 1234567800,
"latest_event_at": 1234567890,
"event_kinds": [
{
"kind": 1,
"count": 12000,
"percentage": 77.8
},
{
"kind": 0,
"count": 2500,
"percentage": 16.2
}
],
"time_stats": {
"total": 15432,
"last_24h": 234,
"last_7d": 1456,
"last_30d": 5432
},
"top_pubkeys": [
{
"pubkey": "abc123...",
"event_count": 1234,
"percentage": 8.0
}
]
}
```
### Database Queries
#### SQL Query Command
Execute read-only SQL queries against the relay database.
**Command**:
```json
["sql_query", "SELECT * FROM events ORDER BY created_at DESC LIMIT 10"]
```
**Response** (decrypted):
```json
{
"query_type": "sql_query",
"request_id": "request_event_id",
"timestamp": 1234567890,
"query": "SELECT * FROM events ORDER BY created_at DESC LIMIT 10",
"execution_time_ms": 45,
"row_count": 10,
"columns": ["id", "pubkey", "created_at", "kind", "content", "tags", "sig"],
"rows": [
["abc123...", "def456...", 1234567890, 1, "Hello world", "[]", "sig123..."],
["ghi789...", "jkl012...", 1234567880, 0, "{\"name\":\"Alice\"}", "[]", "sig456..."]
]
}
```
#### Security Features
- **Read-only**: Only SELECT statements allowed
- **Query timeout**: 5 seconds maximum
- **Result limit**: 1000 rows maximum
- **Logging**: All queries logged with execution time
- **Validation**: SQL injection protection
#### Available Tables and Views
**Core Tables**:
- `events` - All Nostr events
- `config` - Configuration parameters
- `auth_rules` - Authentication rules
- `subscription_events` - Subscription lifecycle log
- `event_broadcasts` - Event broadcast log
**Views**:
- `recent_events` - Last 1000 events
- `event_stats` - Event statistics by type
- `subscription_analytics` - Subscription metrics
- `active_subscriptions_log` - Currently active subscriptions
- `event_kinds_view` - Event distribution by kind
- `top_pubkeys_view` - Top 10 pubkeys by event count
- `time_stats_view` - Time-based statistics
#### Example Queries
**Recent events**:
```sql
SELECT id, pubkey, created_at, kind
FROM events
ORDER BY created_at DESC
LIMIT 20
```
**Event distribution**:
```sql
SELECT * FROM event_kinds_view
ORDER BY count DESC
```
**Active subscriptions**:
```sql
SELECT * FROM active_subscriptions_log
ORDER BY created_at DESC
```
**Database statistics**:
```sql
SELECT
(SELECT COUNT(*) FROM events) as total_events,
(SELECT COUNT(*) FROM subscription_events) as total_subscriptions,
(SELECT COUNT(DISTINCT pubkey) FROM events) as unique_pubkeys
```
**Events by specific pubkey**:
```sql
SELECT id, created_at, kind, content
FROM events
WHERE pubkey = 'abc123...'
ORDER BY created_at DESC
LIMIT 50
```
**Events in time range**:
```sql
SELECT COUNT(*) as count, kind
FROM events
WHERE created_at BETWEEN 1234567000 AND 1234567890
GROUP BY kind
ORDER BY count DESC
```
## Configuration Reference
### Basic Relay Settings
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `relay_name` | string | "C-Relay-PG" | Relay name (NIP-11) |
| `relay_description` | string | "C Nostr Relay" | Relay description |
| `relay_contact` | string | "" | Admin contact info |
| `relay_software` | string | "c-relay-pg" | Software identifier |
| `relay_version` | string | auto | Software version |
| `supported_nips` | string | "1,9,11,13,15,20,33,40,42,45,50,70" | Supported NIPs |
| `language_tags` | string | "*" | Supported languages |
| `relay_countries` | string | "*" | Supported countries |
| `posting_policy` | string | "" | Posting policy URL |
| `payments_url` | string | "" | Payment URL |
### Connection & Limits
| Key | Type | Default | Range | Restart Required |
|-----|------|---------|-------|------------------|
| `max_connections` | integer | 1000 | 1-10000 | Yes |
| `max_subscriptions_per_client` | integer | 25 | 1-100 | No |
| `max_total_subscriptions` | integer | 5000 | 100-50000 | No |
| `max_message_length` | integer | 65536 | 1024-1048576 | No |
| `max_event_tags` | integer | 2000 | 10-10000 | No |
| `max_content_length` | integer | 65536 | 1-1048576 | No |
### Authentication & Access Control
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `auth_enabled` | boolean | false | Enable whitelist/blacklist |
| `nip42_auth_required` | boolean | false | Require NIP-42 auth |
| `nip42_auth_required_kinds` | string | "" | Kinds requiring NIP-42 |
| `nip42_challenge_timeout` | integer | 300 | Challenge timeout (seconds) |
### Proof of Work (NIP-13)
| Key | Type | Default | Values | Description |
|-----|------|---------|--------|-------------|
| `pow_min_difficulty` | integer | 0 | 0-40 | Minimum PoW difficulty |
| `pow_mode` | string | "optional" | disabled/optional/required | PoW enforcement mode |
### Event Expiration (NIP-40)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `nip40_expiration_enabled` | boolean | true | Enable expiration support |
| `nip40_expiration_strict` | boolean | false | Reject expired events |
| `nip40_expiration_filter` | boolean | true | Filter expired from results |
| `nip40_expiration_grace_period` | integer | 300 | Grace period (seconds) |
### Monitoring
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `kind_24567_reporting_throttle_sec` | integer | 5 | Monitoring event throttle |
### Dynamic vs Restart-Required
**Dynamic (No Restart)**:
- All NIP-11 relay information
- Authentication settings
- Subscription limits
- Event validation limits
- Proof of Work settings
- Expiration settings
**Restart Required**:
- `max_connections`
- `relay_port`
- Database settings
## Real-time Monitoring
C-Relay-PG provides subscription-based real-time monitoring using ephemeral events (kind 24567).
### Activation
Subscribe to kind 24567 events to activate monitoring:
```json
["REQ", "monitoring-sub", {"kinds": [24567]}]
```
### Monitoring Event Types
Subscribe to specific monitoring types using d-tag filters:
```json
["REQ", "event-kinds", {"kinds": [24567], "#d": ["event_kinds"]}]
["REQ", "time-stats", {"kinds": [24567], "#d": ["time_stats"]}]
["REQ", "top-pubkeys", {"kinds": [24567], "#d": ["top_pubkeys"]}]
["REQ", "cpu-metrics", {"kinds": [24567], "#d": ["cpu_metrics"]}]
```
### Event Structure
```json
{
"kind": 24567,
"pubkey": "relay_pubkey",
"created_at": 1234567890,
"content": "{\"data_type\":\"event_kinds\",\"timestamp\":1234567890,...}",
"tags": [
["d", "event_kinds"]
]
}
```
### Monitoring Types
#### Event Distribution (`event_kinds`)
```json
{
"data_type": "event_kinds",
"timestamp": 1234567890,
"total_events": 15432,
"kinds": [
{"kind": 1, "count": 12000, "percentage": 77.8},
{"kind": 0, "count": 2500, "percentage": 16.2}
]
}
```
#### Time Statistics (`time_stats`)
```json
{
"data_type": "time_stats",
"timestamp": 1234567890,
"total_events": 15432,
"last_24h": 234,
"last_7d": 1456,
"last_30d": 5432
}
```
#### Top Publishers (`top_pubkeys`)
```json
{
"data_type": "top_pubkeys",
"timestamp": 1234567890,
"top_pubkeys": [
{"pubkey": "abc123...", "count": 1234, "percentage": 8.0},
{"pubkey": "def456...", "count": 987, "percentage": 6.4}
]
}
```
#### CPU Metrics (`cpu_metrics`)
```json
{
"data_type": "cpu_metrics",
"timestamp": 1234567890,
"cpu_percent": 12.5,
"memory_mb": 45.2,
"uptime_seconds": 86400
}
```
#### Active Subscriptions (`active_subscriptions`) - Admin Only
```json
{
"data_type": "active_subscriptions",
"timestamp": 1234567890,
"total_subscriptions": 156,
"subscriptions_by_client": [
{"client_id": "client1", "count": 12},
{"client_id": "client2", "count": 8}
]
}
```
### Configuration
Control monitoring frequency:
```json
["config_update", [{
"key": "kind_24567_reporting_throttle_sec",
"value": "10",
"data_type": "integer",
"category": "monitoring"
}]]
```
### Performance
- Events are ephemeral (not stored)
- Automatic activation/deactivation based on subscriptions
- Throttling prevents excessive event generation
- Minimal overhead when no clients monitoring
## Direct Message Admin
Control your relay by sending direct messages from any Nostr client.
### Setup
1. The relay has its own keypair (shown on startup)
2. The relay knows the admin public key
3. Send NIP-17 direct messages to the relay
### Available Commands
Send a DM containing any of these keywords:
| Command | Aliases | Response |
|---------|---------|----------|
| Statistics | stats, statistics | Database statistics |
| Configuration | config, configuration | Current configuration |
### Example
Using any Nostr client that supports NIP-17:
1. Find the relay's public key (shown on startup)
2. Send a DM: "stats"
3. Receive a DM with current relay statistics
### Response Format
The relay responds with a NIP-17 DM containing:
**Stats Response**:
```
Relay Statistics
================
Total Events: 15,432
Database Size: 10.5 MB
Active Connections: 42
Active Subscriptions: 156
Uptime: 1 day, 2 hours
```
**Config Response**:
```
Relay Configuration
===================
Name: My Awesome Relay
Description: Community relay
Max Subscriptions: 25
Auth Enabled: false
PoW Difficulty: 0
```
## Response Formats
### Success Response
```json
{
"query_type": "command_name",
"status": "success",
"message": "Operation completed successfully",
"timestamp": 1234567890,
"data": [...]
}
```
### Error Response
```json
{
"query_type": "command_name",
"status": "error",
"error": "Error description",
"timestamp": 1234567890
}
```
### Query Response
```json
{
"query_type": "query_name",
"total_results": 10,
"timestamp": 1234567890,
"data": [...]
}
```
## Error Handling
### Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| `invalid_signature` | Event signature invalid | Check admin private key |
| `unauthorized` | Wrong admin pubkey | Use correct admin key |
| `invalid_command` | Unknown command | Check command format |
| `validation_failed` | Invalid parameter value | Check parameter ranges |
| `database_error` | Database operation failed | Check database integrity |
| `timeout` | Query took too long | Simplify query or increase timeout |
### Error Response Example
```json
{
"query_type": "config_update",
"status": "error",
"error": "field validation failed: invalid port number '99999' (must be 1-65535)",
"timestamp": 1234567890
}
```
## Examples
### JavaScript/TypeScript Example
```javascript
import { SimplePool, nip44, getPublicKey, finalizeEvent } from 'nostr-tools';
const adminPrivkey = 'your_admin_privkey_hex';
const adminPubkey = getPublicKey(adminPrivkey);
const relayPubkey = 'relay_pubkey_hex';
const relayUrl = 'ws://localhost:8888';
// Create admin command
async function sendAdminCommand(command) {
const pool = new SimplePool();
// Encrypt command with NIP-44
const encryptedContent = await nip44.encrypt(
adminPrivkey,
relayPubkey,
JSON.stringify(command)
);
// Create event
const event = finalizeEvent({
kind: 23456,
created_at: Math.floor(Date.now() / 1000),
tags: [['p', relayPubkey]],
content: encryptedContent
}, adminPrivkey);
// Publish event
await pool.publish([relayUrl], event);
// Subscribe to response
const sub = pool.sub([relayUrl], [{
kinds: [23457],
'#p': [adminPubkey],
since: Math.floor(Date.now() / 1000)
}]);
return new Promise((resolve) => {
sub.on('event', async (event) => {
// Decrypt response
const decrypted = await nip44.decrypt(
adminPrivkey,
relayPubkey,
event.content
);
resolve(JSON.parse(decrypted));
sub.unsub();
});
});
}
// Query configuration
const config = await sendAdminCommand(['config_query', 'all']);
console.log(config);
// Update configuration
const result = await sendAdminCommand(['config_update', [
{
key: 'relay_name',
value: 'My Relay',
data_type: 'string',
category: 'relay'
}
]]);
console.log(result);
// Add blacklist rule
const blacklist = await sendAdminCommand([
'blacklist',
'pubkey',
'abc123...'
]);
console.log(blacklist);
// Execute SQL query
const query = await sendAdminCommand([
'sql_query',
'SELECT * FROM events ORDER BY created_at DESC LIMIT 10'
]);
console.log(query);
```
### Python Example
```python
from nostr_sdk import Keys, Client, EventBuilder, Filter, nip44
admin_privkey = "your_admin_privkey_hex"
relay_pubkey = "relay_pubkey_hex"
relay_url = "ws://localhost:8888"
# Initialize
keys = Keys.parse(admin_privkey)
client = Client(keys)
client.add_relay(relay_url)
client.connect()
# Send admin command
async def send_admin_command(command):
# Encrypt command
encrypted = nip44.encrypt(
keys.secret_key(),
relay_pubkey,
json.dumps(command)
)
# Create event
event = EventBuilder.new(
kind=23456,
content=encrypted,
tags=[["p", relay_pubkey]]
).to_event(keys)
# Publish
await client.send_event(event)
# Wait for response
filter = Filter().kind(23457).pubkey(relay_pubkey).since(int(time.time()))
events = await client.get_events_of([filter], timeout=5)
if events:
# Decrypt response
decrypted = nip44.decrypt(
keys.secret_key(),
relay_pubkey,
events[0].content()
)
return json.loads(decrypted)
# Query configuration
config = await send_admin_command(["config_query", "all"])
print(config)
```
### Bash/curl Example
```bash
#!/bin/bash
# Note: This is a simplified example. Real implementation requires:
# - NIP-44 encryption
# - Event signing
# - WebSocket connection
RELAY_URL="ws://localhost:8888"
ADMIN_PRIVKEY="your_admin_privkey"
RELAY_PUBKEY="relay_pubkey"
# Use nostrtool or similar for proper event creation
nostrtool event \
--kind 23456 \
--content "$(echo '["config_query","all"]' | nip44-encrypt)" \
--tag p "$RELAY_PUBKEY" \
--private-key "$ADMIN_PRIVKEY" \
| nostrtool send "$RELAY_URL"
```
---
## Additional Resources
- **[Configuration Guide](docs/configuration_guide.md)** - Detailed configuration options
- **[Deployment Guide](docs/deployment_guide.md)** - Production deployment
- **[NIP-42 Authentication](docs/NIP-42_Authentication.md)** - Authentication setup
- **[User Guide](docs/user_guide.md)** - End-user documentation
## Support
For API questions or issues:
- Open an issue on GitHub
- Check existing documentation
- Join the Nostr community
---
**API Version**: 0.6.0
**Last Updated**: 2026-01-23
+57 -29
View File
@@ -1,8 +1,14 @@
# Alpine-based MUSL static binary builder for C-Relay
# Alpine-based MUSL static binary builder for C-Relay-PG
# Produces truly portable binaries with zero runtime dependencies
# PostgreSQL backend only.
ARG DEBUG_BUILD=false
FROM alpine:3.19 AS builder
# Re-declare build arguments in this stage
ARG DEBUG_BUILD=false
# Install build dependencies
RUN apk add --no-cache \
build-base \
@@ -21,9 +27,11 @@ RUN apk add --no-cache \
curl-static \
sqlite-dev \
sqlite-static \
postgresql-dev \
linux-headers \
wget \
bash
bash \
openssh-client
# Set working directory
WORKDIR /build
@@ -63,57 +71,77 @@ RUN cd /tmp && \
make install && \
rm -rf /tmp/libwebsockets
# Copy only submodule configuration and git directory
COPY .gitmodules /build/.gitmodules
COPY .git /build/.git
# Clean up any stale submodule references (nips directory is not a submodule)
RUN git rm --cached nips 2>/dev/null || true
# Initialize submodules (cached unless .gitmodules changes)
RUN git submodule update --init --recursive
# Submodules are provided in the local build context and copied explicitly below.
# Avoid network/SSH dependency inside Docker image build.
# Copy nostr_core_lib source files (cached unless nostr_core_lib changes)
COPY nostr_core_lib /build/nostr_core_lib/
# Copy c_utils_lib source files (cached unless c_utils_lib changes)
COPY c_utils_lib /build/c_utils_lib/
# Build c_utils_lib static library for relay logging utilities
# (build only debug.c to avoid broken/missing version header surface in submodule revision)
RUN cd c_utils_lib && \
mkdir -p build && \
gcc -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -O2 -g \
-Isrc -Iinclude -c src/debug.c -o build/debug.o && \
ar rcs libc_utils.a build/debug.o
# Build nostr_core_lib with required NIPs (cached unless nostr_core_lib changes)
# Disable fortification in build.sh to prevent __*_chk symbol issues
# NIPs: 001(Basic), 006(Keys), 013(PoW), 017(DMs), 019(Bech32), 044(Encryption), 059(Gift Wrap - required by NIP-17)
# NIPs: 001(Basic), 004(Encryption), 006(Keys), 013(PoW), 017(DMs), 019(Bech32), 044(Encryption), 059(Gift Wrap - required by NIP-17)
# NIP-04 is required by nostr_signer.c (NIP-46 bunker signer uses NIP-04 encryption)
RUN cd nostr_core_lib && \
chmod +x build.sh && \
sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \
rm -f *.o *.a 2>/dev/null || true && \
./build.sh --nips=1,6,13,17,19,44,59
./build.sh --nips=1,4,6,13,17,19,44,59
# Copy c-relay source files LAST (only this layer rebuilds on source changes)
# Copy c-relay-pg source files LAST (only this layer rebuilds on source changes)
COPY src/ /build/src/
COPY Makefile /build/Makefile
# Build c-relay with full static linking (only rebuilds when src/ changes)
# Build c-relay-pg with full static linking (only rebuilds when src/ changes)
# Disable fortification to avoid __*_chk symbols that don't exist in MUSL
RUN gcc -static -O2 -Wall -Wextra -std=c99 \
# Use conditional compilation flags based on DEBUG_BUILD build arg.
# PostgreSQL backend is always used.
RUN if [ "$DEBUG_BUILD" = "true" ]; then \
CFLAGS="-g -O2 -DDEBUG"; \
STRIP_CMD="echo 'Keeping debug symbols'"; \
echo "Building with DEBUG symbols enabled (optimized with -O2)"; \
else \
CFLAGS="-O2"; \
STRIP_CMD="strip /build/c_relay_pg_static"; \
echo "Building optimized production binary (symbols stripped)"; \
fi && \
DB_FLAGS="-DDB_BACKEND_POSTGRES -DHAVE_LIBPQ" && \
DB_LIBS="-lpq -lpgcommon -lpgport" && \
echo "Compiling with PostgreSQL backend" && \
gcc -static $CFLAGS $DB_FLAGS -Wall -Wextra -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
-I. -Ic_utils_lib/src -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket -I/usr/include/postgresql \
src/main.c src/config.c src/dm_admin.c src/request_validator.c \
src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c \
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \
-o /build/c_relay_static \
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c \
src/caching_inbox_poller.c src/caching_service_launcher.c \
src/db_ops.c src/db_ops_postgres.c src/thread_pool.c \
-o /build/c_relay_pg_static \
c_utils_lib/libc_utils.a \
nostr_core_lib/libnostr_core_x64.a \
-lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \
-lcurl -lz -lpthread -lm -ldl
# Strip binary to reduce size
RUN strip /build/c_relay_static
-lwebsockets -lssl -lcrypto -lsecp256k1 \
-lcurl -lz -lpthread -lm -ldl $DB_LIBS && \
eval "$STRIP_CMD"
# Verify it's truly static
RUN echo "=== Binary Information ===" && \
file /build/c_relay_static && \
ls -lh /build/c_relay_static && \
file /build/c_relay_pg_static && \
ls -lh /build/c_relay_pg_static && \
echo "=== Checking for dynamic dependencies ===" && \
(ldd /build/c_relay_static 2>&1 || echo "Binary is static") && \
(ldd /build/c_relay_pg_static 2>&1 || echo "Binary is static") && \
echo "=== Build complete ==="
# Output stage - just the binary
FROM scratch AS output
COPY --from=builder /build/c_relay_static /c_relay_static
COPY --from=builder /build/c_relay_pg_static /c_relay_pg_static
+93 -132
View File
@@ -1,40 +1,72 @@
# C-Relay Makefile
# C-Relay-PG Makefile
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -g -O2
INCLUDES = -I. -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket
LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -L/usr/local/lib -lcurl
CFLAGS = -Wall -Wextra -std=c99 -g -O2 -DDB_BACKEND_POSTGRES -DHAVE_LIBPQ
INCLUDES = -I. -Ic_utils_lib/src -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket -I/usr/include/postgresql
LIBS = -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -L/usr/local/lib -lcurl -Lc_utils_lib -lc_utils -lpq
# Build directory
BUILD_DIR = build
# Source files
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c src/ip_ban.c src/thread_pool.c src/caching_inbox_poller.c src/caching_service_launcher.c
DB_OPS_SRC = src/db_ops.c src/db_ops_postgres.c
NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a
C_UTILS_LIB = c_utils_lib/libc_utils.a
# Architecture detection
ARCH = $(shell uname -m)
ifeq ($(ARCH),x86_64)
TARGET = $(BUILD_DIR)/c_relay_x86
TARGET = $(BUILD_DIR)/c_relay_pg_x86
else ifeq ($(ARCH),aarch64)
TARGET = $(BUILD_DIR)/c_relay_arm64
TARGET = $(BUILD_DIR)/c_relay_pg_arm64
else ifeq ($(ARCH),arm64)
TARGET = $(BUILD_DIR)/c_relay_arm64
TARGET = $(BUILD_DIR)/c_relay_pg_arm64
else
TARGET = $(BUILD_DIR)/c_relay_$(ARCH)
TARGET = $(BUILD_DIR)/c_relay_pg_$(ARCH)
endif
# Default target
all: $(TARGET)
# Default target — refuse direct build, instruct to use build_static.sh
# The Makefile is only for submodule builds (nostr_core_lib, c_utils_lib)
# and utility targets (clean, install-deps, etc.).
# Running 'make' directly produces a dynamically-linked binary that will
# silently fall back to SQLite storage while the admin UI connects to
# PostgreSQL — causing the admin page to show stale data.
all:
@echo "============================================"
@echo " ERROR: Do not run 'make' directly!"
@echo ""
@echo " This project requires a static MUSL build"
@echo " with PostgreSQL backend. Run:"
@echo ""
@echo " ./build_static.sh"
@echo ""
@echo " Or use the full build+restart script:"
@echo ""
@echo " ./make_and_restart_relay.sh"
@echo ""
@echo " The Makefile is only for submodule builds"
@echo " (nostr_core_lib, c_utils_lib) and utility"
@echo " targets (clean, install-deps, etc.)."
@echo "============================================"
@exit 1
# Create build directory
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
# Check if nostr_core_lib is built
# Explicitly specify NIPs to ensure NIP-44 (encryption) is included
# NIPs: 1 (basic), 6 (keys), 13 (PoW), 17 (DMs), 19 (bech32), 44 (encryption), 59 (gift wrap)
$(NOSTR_CORE_LIB):
@echo "Building nostr_core_lib..."
cd nostr_core_lib && ./build.sh
@echo "Building nostr_core_lib with required NIPs (including NIP-44 for encryption)..."
cd nostr_core_lib && ./build.sh --nips=1,6,13,17,19,44,59
# Check if c_utils_lib is built
$(C_UTILS_LIB):
@echo "Building c_utils_lib..."
cd c_utils_lib && ./build.sh lib
# Update main.h version information (requires main.h to exist)
src/main.h:
@@ -72,146 +104,75 @@ force-version:
@echo "Force updating main.h version information..."
@$(MAKE) src/main.h
# Build the relay
$(TARGET): $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
@echo "Compiling C-Relay for architecture: $(ARCH)"
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(TARGET) $(NOSTR_CORE_LIB) $(LIBS)
@echo "Build complete: $(TARGET)"
# Build the relay — guarded to prevent accidental use.
# Use ./build_static.sh instead.
$(TARGET):
@echo "ERROR: Do not run 'make $(TARGET)' directly."
@echo "Use ./build_static.sh to build the static MUSL binary."
@exit 1
# Build for specific architectures
x86: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
@echo "Building C-Relay for x86_64..."
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(BUILD_DIR)/c_relay_x86 $(NOSTR_CORE_LIB) $(LIBS)
@echo "Build complete: $(BUILD_DIR)/c_relay_x86"
x86:
@echo "ERROR: Do not run 'make x86' directly."
@echo "Use ./build_static.sh to build the static MUSL binary."
@exit 1
arm64: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
@echo "Cross-compiling C-Relay for ARM64..."
arm64:
@echo "ERROR: Do not run 'make arm64' directly."
@echo "Use ./build_static.sh to build the static MUSL binary."
@exit 1
@if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
echo "ERROR: ARM64 cross-compiler not found."; \
echo "Install with: make install-cross-tools"; \
echo "Or install manually: sudo apt install gcc-aarch64-linux-gnu"; \
echo "ERROR: aarch64-linux-gnu-gcc not found."; \
echo "Install the ARM64 cross-compiler:"; \
echo " sudo apt install gcc-aarch64-linux-gnu"; \
exit 1; \
fi
@echo "Checking for ARM64 development libraries..."
@if ! dpkg -l | grep -q "libssl-dev:arm64\|libsqlite3-dev:arm64"; then \
@if ! dpkg -l | grep -q "libssl-dev:arm64\|libpq-dev:arm64"; then \
echo "ERROR: ARM64 libraries not found. Cross-compilation requires ARM64 versions of:"; \
echo " - libssl-dev:arm64"; \
echo " - libsqlite3-dev:arm64"; \
echo " - libpq-dev:arm64"; \
echo " - libwebsockets-dev:arm64"; \
echo " - libsecp256k1-dev:arm64"; \
echo " - zlib1g-dev:arm64"; \
echo " - libcurl4-openssl-dev:arm64"; \
echo " - libcurl-dev:arm64"; \
echo ""; \
echo "Install ARM64 libraries with: make install-arm64-deps"; \
echo "Or use Docker for cross-platform builds."; \
echo "Install them with:"; \
echo " sudo dpkg --add-architecture arm64"; \
echo " sudo apt update"; \
echo " sudo apt install gcc-aarch64-linux-gnu \\"; \
echo " libssl-dev:arm64 libpq-dev:arm64 \\"; \
echo " libwebsockets-dev:arm64 zlib1g-dev:arm64 \\"; \
echo " libcurl4-openssl-dev:arm64"; \
exit 1; \
fi
@echo "Using aarch64-linux-gnu-gcc with ARM64 libraries..."
PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/share/pkgconfig \
aarch64-linux-gnu-gcc $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(BUILD_DIR)/c_relay_arm64 $(NOSTR_CORE_LIB) \
-L/usr/lib/aarch64-linux-gnu $(LIBS)
@echo "Build complete: $(BUILD_DIR)/c_relay_arm64"
fi; \
PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig \
PKG_CONFIG_SYSROOT_DIR=/ \
aarch64-linux-gnu-gcc -Wall -Wextra -std=c99 -g -O2 \
-DDB_BACKEND_POSTGRES -DHAVE_LIBPQ \
-I. -Ic_utils_lib/src -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
$(MAIN_SRC) $(DB_OPS_SRC) \
-o $(BUILD_DIR)/c_relay_pg_arm64 \
$(NOSTR_CORE_LIB) $(C_UTILS_LIB) \
-lwebsockets -lssl -lcrypto -lz -ldl -lpthread -lm \
-lsecp256k1 -lcurl -lpq
@echo "Cross-compilation complete: $(BUILD_DIR)/c_relay_pg_arm64"
# Install ARM64 cross-compilation dependencies
# Install dependencies for ARM64 cross-compilation
install-arm64-deps:
@echo "Installing ARM64 cross-compilation dependencies..."
@echo "This requires adding ARM64 architecture and installing cross-libraries..."
sudo dpkg --add-architecture arm64
sudo apt update
sudo apt install -y \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
libssl-dev:arm64 \
libsqlite3-dev:arm64 \
zlib1g-dev:arm64 \
sudo apt install -y gcc-aarch64-linux-gnu \
libssl-dev:arm64 libpq-dev:arm64 \
libwebsockets-dev:arm64 zlib1g-dev:arm64 \
libcurl4-openssl-dev:arm64
@echo "Note: libwebsockets-dev:arm64 and libsecp256k1-dev:arm64 may need manual building"
# Install cross-compilation tools
install-cross-tools:
@echo "Installing cross-compilation tools..."
# Install dependencies for native build
install-deps:
sudo apt update
sudo apt install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
# Check what architectures we can actually build
check-toolchain:
@echo "Checking available toolchains:"
@echo "Native compiler: $(shell $(CC) --version | head -1)"
@if command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
echo "ARM64 cross-compiler: $(shell aarch64-linux-gnu-gcc --version | head -1)"; \
else \
echo "ARM64 cross-compiler: NOT INSTALLED (install with 'make install-cross-tools')"; \
fi
# Run tests
test: $(TARGET)
@echo "Running tests..."
./tests/1_nip_test.sh
# Initialize database (now handled automatically when server starts)
init-db:
@echo "Database initialization is now handled automatically when the server starts."
@echo "The schema is embedded in the binary - no external files needed."
@echo "To manually recreate database: rm -f db/c_nostr_relay.db && ./build/c_relay_x86"
sudo apt install -y build-essential libpq-dev libssl-dev libcurl4-openssl-dev libsecp256k1-dev zlib1g-dev jq curl
# Clean build artifacts
clean:
rm -rf $(BUILD_DIR)
@echo "Clean complete"
# Clean everything including nostr_core_lib
clean-all: clean
cd nostr_core_lib && make clean 2>/dev/null || true
# Install dependencies (Ubuntu/Debian)
install-deps:
@echo "Installing dependencies..."
sudo apt update
sudo apt install -y build-essential libsqlite3-dev libssl-dev libcurl4-openssl-dev libsecp256k1-dev zlib1g-dev jq curl
# Help
help:
@echo "C-Relay Build System"
@echo ""
@echo "Targets:"
@echo " all Build the relay for current architecture (default)"
@echo " x86 Build specifically for x86_64"
@echo " arm64 Build for ARM64 (requires cross-compilation setup)"
@echo " test Build and run tests"
@echo " init-db Initialize the database"
@echo " clean Clean build artifacts"
@echo " clean-all Clean everything including dependencies"
@echo " install-deps Install system dependencies"
@echo " install-cross-tools Install basic ARM64 cross-compiler"
@echo " install-arm64-deps Install ARM64 cross-compilation libraries"
@echo " check-toolchain Check available compilers"
@echo " help Show this help"
@echo ""
@echo "Usage:"
@echo " make # Build the relay for current arch"
@echo " make x86 # Build for x86_64"
@echo " make arm64 # Build for ARM64 (fails if cross-compilation not set up)"
@echo " make install-arm64-deps # Install full ARM64 cross-compilation setup"
@echo " make check-toolchain # Check what compilers are available"
@echo " make test # Run tests"
@echo " make init-db # Set up database"
@echo " make force-version # Force regenerate main.h from git"
# Build fully static MUSL binaries using Docker
static-musl-x86_64:
@echo "Building fully static MUSL binary for x86_64..."
docker buildx build --platform linux/amd64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-x86_64 --load .
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-x86_64 sh -c "cp /c_relay_static_musl_x86_64 /output/"
@echo "Static binary created: build/c_relay_static_musl_x86_64"
static-musl-arm64:
@echo "Building fully static MUSL binary for ARM64..."
docker buildx build --platform linux/arm64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-arm64 --load .
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-arm64 sh -c "cp /c_relay_static_musl_x86_64 /output/c_relay_static_musl_arm64"
@echo "Static binary created: build/c_relay_static_musl_arm64"
static-musl: static-musl-x86_64 static-musl-arm64
@echo "Built static MUSL binaries for both architectures"
.PHONY: static-musl-x86_64 static-musl-arm64 static-musl
.PHONY: all x86 arm64 test init-db clean clean-all install-deps install-cross-tools install-arm64-deps check-toolchain help force-version
.PHONY: all x86 arm64 install-arm64-deps install-deps clean force-version
+65
View File
@@ -0,0 +1,65 @@
# Relay
I am releasing the code for the nostr relay that I wrote use myself. The code is free for anyone to use in any way that they wish.
Some of the features of this relay are conventional, and some are unconventional.
## The conventional
This relay is written in C99 with a sqlite database.
It implements the following NIPs.
- [x] NIP-01: Basic protocol flow implementation
- [x] NIP-09: Event deletion
- [x] NIP-11: Relay information document
- [x] NIP-13: Proof of Work
- [x] NIP-15: End of Stored Events Notice
- [x] NIP-20: Command Results
- [x] NIP-33: Parameterized Replaceable Events
- [x] NIP-40: Expiration Timestamp
- [x] NIP-42: Authentication of clients to relays
- [x] NIP-45: Counting results
- [x] NIP-50: Keywords filter
- [x] NIP-70: Protected Events
## The unconventional
### The binaries are fully self contained.
It should just run in linux without having to worry about what you have on your system. I want to download and run. No docker. No dependency hell.
I'm not bothering with other operating systems.
### The relay is a full nostr citizen with it's own public and private keys.
For example, you can see my relay's profile (wss://relay.laantungir.net) running here:
[Primal link](https://primal.net/p/nprofile1qqswn2jsmm8lq8evas0v9vhqkdpn9nuujt90mtz60nqgsxndy66es4qjjnhr7)
What this means in practice is that when you start the relay, it generates keys for itself, and for it's administrator (You can specify these if you wish)
Now the program and the administrator can have verifed communication between the two, simply by using nostr events. For example, the administrator can send DMs to the relay, asking it's status, and changing it's configuration through any client that can handle nip17 DMs. The relay can also send notifications to the administrator about it's current status, or it can publish it's status on a regular schedule directly to NOSTR as kind-1 notes.
Also included is a more standard administrative web front end. This front end communicates to the relay using an extensive api, which again is nostr events signed by the administrator. You can completely control the relay through signed nostr events.
## Screenshots
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/main.png)
Main page with real time updates.
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/config.png)
Set your configuration preferences.
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/subscriptions.png)
View current subscriptions
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/white-blacklists.png)
Add npubs to white or black lists.
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/sqlQuery.png)
Run sql queries on the database.
![](https://git.laantungir.net/laantungir/c-relay-pg/raw/branch/master/screenshots/main-light.png)
Light mode.
+215 -277
View File
@@ -1,347 +1,285 @@
# C-Nostr Relay
# C-Relay-PG: High-Performance Nostr Relay
A high-performance Nostr relay implemented in C with SQLite backend, featuring nostr event-based management.
A blazingly fast, production-ready Nostr relay implemented in C with an innovative event-based configuration system. Built for performance, security, and ease of deployment.
## Supported NIPs
## 🚀 Why C-Relay-PG?
<!--
NOTE FOR ASSISTANTS: When updating the NIPs checklist below, ONLY change [ ] to [x] to mark as complete.
Do NOT modify the formatting, add emojis, or change the text. Keep the simple format consistent.
-->
### Event-Based Configuration
Unlike traditional relays that require config files, C-Relay-PG uses **cryptographically signed Nostr events** for all configuration. This means:
- **Zero config files** - Everything stored in the database
- **Real-time updates** - Changes applied instantly without restart
- **Cryptographic security** - All changes must be signed by admin
- **Complete audit trail** - Every configuration change is timestamped and signed
- **Version control built-in** - Configuration history is part of the event stream
- [x] NIP-01: Basic protocol flow implementation
- [x] NIP-09: Event deletion
- [x] NIP-11: Relay information document
- [x] NIP-13: Proof of Work
- [x] NIP-15: End of Stored Events Notice
- [x] NIP-20: Command Results
- [x] NIP-33: Parameterized Replaceable Events
- [x] NIP-40: Expiration Timestamp
- [x] NIP-42: Authentication of clients to relays
- [x] NIP-45: Counting results
- [x] NIP-50: Keywords filter
- [x] NIP-70: Protected Events
### Built-in Web Admin Interface
Access a full-featured web dashboard at `http://localhost:8888/api/` with:
- Real-time configuration management
- Database statistics and analytics
- Auth rules management (whitelist/blacklist)
- NIP-42 authentication for secure access
- No external dependencies - all files embedded in the binary
## Quick Start
### Direct Message Admin System
Control your relay by sending direct messages from any Nostr client:
- Send "stats" to get relay statistics
- Send "config" to view current configuration
- Full Nostr citizen with its own keypair
- Works with any NIP-17 compatible client
Get your C-Relay up and running in minutes with a static binary (no dependencies required):
### Performance & Efficiency
- **Written in C** - Maximum performance and minimal resource usage
- **SQLite backend** - Reliable, fast, and self-contained
- **Static binary available** - Single file deployment with zero dependencies
- **Efficient memory management** - Optimized for long-running operation
- **WebSocket native** - Built on libwebsockets for optimal protocol support
### 1. Download Static Binary
## 📋 Supported NIPs
Download the latest static release from the [releases page](https://git.laantungir.net/laantungir/c-relay/releases):
C-Relay-PG implements a comprehensive set of Nostr Improvement Proposals:
-**NIP-01**: Basic protocol flow implementation
-**NIP-09**: Event deletion
-**NIP-11**: Relay information document
-**NIP-13**: Proof of Work
-**NIP-15**: End of Stored Events Notice
-**NIP-20**: Command Results
-**NIP-33**: Parameterized Replaceable Events
-**NIP-40**: Expiration Timestamp
-**NIP-42**: Authentication of clients to relays
-**NIP-45**: Counting results
-**NIP-50**: Keywords filter
-**NIP-70**: Protected Events
## 🎯 Key Features
### Security
- **NIP-42 Authentication** - Cryptographic client authentication
- **Proof of Work** - Configurable PoW requirements (NIP-13)
- **Protected Events** - Support for encrypted/protected content (NIP-70)
- **Whitelist/Blacklist** - Flexible access control by pubkey
- **Admin key security** - Private key shown only once, never stored
### Flexibility
- **Dynamic configuration** - Most settings update without restart
- **Subscription management** - Configurable limits per client and globally
- **Event expiration** - Automatic cleanup of expired events (NIP-40)
- **Parameterized events** - Full support for replaceable events (NIP-33)
- **Keyword search** - Built-in full-text search (NIP-50)
### Monitoring
- **Real-time statistics** - Live event distribution and metrics
- **Subscription-based monitoring** - Ephemeral events (kind 24567) for dashboards
- **SQL query API** - Direct database queries for advanced analytics
- **Resource tracking** - CPU, memory, and database size monitoring
- **Event broadcast logging** - Complete audit trail of all operations
### Developer-Friendly
- **Comprehensive test suite** - Automated tests for all NIPs
- **Clear documentation** - Detailed guides for deployment and configuration
- **SystemD integration** - Production-ready service files included
- **Docker support** - Container deployment with Alpine Linux
- **Cross-platform** - Builds on Linux, macOS, and Windows (WSL)
## 🚀 Quick Start
### Option 1: Static Binary (Recommended)
Download and run - no dependencies required:
```bash
# Static binary - works on all Linux distributions (no dependencies)
wget https://git.laantungir.net/laantungir/c-relay/releases/download/v0.6.0/c-relay-v0.6.0-linux-x86_64-static
chmod +x c-relay-v0.6.0-linux-x86_64-static
mv c-relay-v0.6.0-linux-x86_64-static c-relay
# Download the latest static release
wget https://git.laantungir.net/laantungir/c-relay-pg/releases/download/v0.6.0/c-relay-pg-v0.6.0-linux-x86_64-static
chmod +x c-relay-pg-v0.6.0-linux-x86_64-static
mv c-relay-pg-v0.6.0-linux-x86_64-static c-relay-pg
# Run the relay
./c-relay-pg
```
### 2. Start the Relay
**Important**: On first startup, save the **Admin Private Key** displayed in the console. You'll need it for all administrative operations.
Simply run the binary - no configuration files needed:
### Option 2: Build from Source
```bash
./c-relay
# Install dependencies (Ubuntu/Debian)
sudo apt install -y build-essential git sqlite3 libsqlite3-dev \
libwebsockets-dev libssl-dev libsecp256k1-dev libcurl4-openssl-dev zlib1g-dev
# Clone and build
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
git submodule update --init --recursive
./make_and_restart_relay.sh
```
On first startup, you'll see:
- **Admin Private Key**: Save this securely! You'll need it for administration
- **Relay Public Key**: Your relay's identity on the Nostr network
- **Port Information**: Default is 8888, or the next available port
The relay will start on port 8888 (or the next available port).
### 3. Access the Web Interface
## 🌐 Access the Web Interface
Once running, open your browser to:
Open your browser and navigate to:
```
http://localhost:8888/api/
```
The web interface provides:
- Real-time configuration management
- Database statistics dashboard
- Auth rules management
- Secure admin authentication with your Nostr identity
- Configuration management with live updates
- Database statistics and event distribution charts
- Auth rules management (whitelist/blacklist)
- SQL query interface for advanced analytics
- Real-time monitoring dashboard
### 4. Test Your Relay
## 📦 Installation Options
### Production Deployment (SystemD)
Test basic connectivity:
```bash
# Test WebSocket connection
curl -H "Accept: application/nostr+json" http://localhost:8888
# Clone repository
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
git submodule update --init --recursive
# Test with a Nostr client
# Add ws://localhost:8888 to your client's relay list
# Build
make clean && make
# Install as system service
sudo systemd/install-service.sh
# Start and enable
sudo systemctl start c-relay-pg
sudo systemctl enable c-relay-pg
# Capture admin keys from logs
sudo journalctl -u c-relay-pg | grep "Admin Private Key"
```
### 5. Configure Your Relay (Optional)
### Docker Deployment
Use the web interface or send admin commands to customize:
- Relay name and description
- Authentication rules (whitelist/blacklist)
- Connection limits
- Proof-of-work requirements
```bash
# Build Docker image
docker build -f Dockerfile.alpine-musl -t c-relay-pg .
**That's it!** Your relay is now running with zero configuration required. The event-based configuration system means you can adjust all settings through the web interface or admin API without editing config files.
## Web Admin Interface
C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides:
- **Real-time Configuration Management**: View and edit all relay settings through a web UI
- **Database Statistics Dashboard**: Monitor event counts, storage usage, and performance metrics
- **Auth Rules Management**: Configure whitelist/blacklist rules for pubkeys
- **NIP-42 Authentication**: Secure access using your Nostr identity
- **Event-Based Updates**: All changes are applied as cryptographically signed Nostr events
The web interface serves embedded static files with no external dependencies and includes proper CORS headers for browser compatibility.
## Administrator API
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. All admin commands use **NIP-44 encrypted command arrays** for security and compatibility.
### Authentication
All admin commands require signing with the admin private key displayed during first-time startup. **Save this key securely** - it cannot be recovered and is needed for all administrative operations.
### Event Structure
All admin commands use the same unified event structure with NIP-44 encrypted content:
**Admin Command Event:**
```json
{
"id": "event_id",
"pubkey": "admin_public_key",
"created_at": 1234567890,
"kind": 23456,
"content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...",
"tags": [
["p", "relay_public_key"]
],
"sig": "event_signature"
}
# Run container
docker run -d \
--name c-relay-pg \
-p 8888:8888 \
-v /path/to/data:/data \
c-relay-pg
```
The `content` field contains a NIP-44 encrypted JSON array representing the command.
### Cloud Deployment
**Admin Response Event:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "BpKCVhfN8eYtRmPqSvWxZnMkL2gHjUiOp3rTyEwQaS5dFg...",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
Quick deployment scripts for popular cloud providers:
```bash
# AWS, GCP, DigitalOcean, etc.
sudo examples/deployment/simple-vps/deploy.sh
```
The `content` field contains a NIP-44 encrypted JSON response object.
See [`docs/deployment_guide.md`](docs/deployment_guide.md) for detailed deployment instructions.
### Admin Commands
## 🔧 Configuration
All commands are sent as NIP-44 encrypted JSON arrays in the event content. The following table lists all available commands:
C-Relay-PG uses an innovative event-based configuration system. All settings are managed through signed Nostr events.
| Command Type | Command Format | Description |
|--------------|----------------|-------------|
| **Configuration Management** |
| `config_update` | `["config_update", [{"key": "auth_enabled", "value": "true", "data_type": "boolean", "category": "auth"}, {"key": "relay_description", "value": "My Relay", "data_type": "string", "category": "relay"}, ...]]` | Update relay configuration parameters (supports multiple updates) |
| `config_query` | `["config_query", "all"]` | Query all configuration parameters |
| **Auth Rules Management** |
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
| `auth_add_whitelist` | `["whitelist", "pubkey", "def456..."]` | Add pubkey to whitelist |
| `auth_delete_rule` | `["delete_auth_rule", "blacklist", "pubkey", "abc123..."]` | Delete specific auth rule |
| `auth_query_all` | `["auth_query", "all"]` | Query all auth rules |
| `auth_query_type` | `["auth_query", "whitelist"]` | Query specific rule type |
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
| **System Commands** |
| `system_clear_auth` | `["system_command", "clear_all_auth_rules"]` | Clear all auth rules |
| `system_status` | `["system_command", "system_status"]` | Get system status |
| `stats_query` | `["stats_query"]` | Get comprehensive database statistics |
### Basic Configuration
### Available Configuration Keys
Use the web interface at `http://localhost:8888/api/` or send admin commands via the API.
**Basic Relay Settings:**
- `relay_name`: Relay name (displayed in NIP-11)
- `relay_description`: Relay description text
- `relay_contact`: Contact information
- `relay_software`: Software URL
- `relay_version`: Software version
- `supported_nips`: Comma-separated list of supported NIP numbers (e.g., "1,2,4,9,11,12,13,15,16,20,22,33,40,42")
- `language_tags`: Comma-separated list of supported language tags (e.g., "en,es,fr" or "*" for all)
- `relay_countries`: Comma-separated list of supported country codes (e.g., "US,CA,MX" or "*" for all)
- `posting_policy`: Posting policy URL or text
- `payments_url`: Payment URL for premium features
- `max_connections`: Maximum concurrent connections
- `max_subscriptions_per_client`: Max subscriptions per client
- `max_event_tags`: Maximum tags per event
- `max_content_length`: Maximum event content length
### Common Settings
**Authentication & Access Control:**
- `auth_enabled`: Enable whitelist/blacklist auth rules (`true`/`false`)
- `nip42_auth_required`: Enable NIP-42 cryptographic authentication (`true`/`false`)
- `nip42_auth_required_kinds`: Event kinds requiring NIP-42 auth (comma-separated)
- `nip42_challenge_timeout`: NIP-42 challenge expiration seconds
- **Relay Information**: Name, description, contact info
- **Connection Limits**: Max subscriptions per client, total subscriptions
- **Authentication**: Enable/disable NIP-42, whitelist/blacklist rules
- **Proof of Work**: Minimum difficulty, enforcement mode
- **Event Validation**: Max tags, content length, message size
- **Expiration**: Enable/disable NIP-40 event expiration
**Proof of Work & Validation:**
- `pow_min_difficulty`: Minimum proof-of-work difficulty
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
### Dynamic Updates
### Dynamic Configuration Updates
Most configuration changes take effect immediately without restart:
- Relay information (NIP-11)
- Authentication settings
- Subscription limits
- Event validation rules
- Proof of Work settings
C-Relay supports **dynamic configuration updates** without requiring a restart for most settings. Configuration parameters are categorized as either **dynamic** (can be updated immediately) or **restart-required** (require relay restart to take effect).
See [`docs/configuration_guide.md`](docs/configuration_guide.md) for complete configuration reference.
**Dynamic Configuration Parameters (No Restart Required):**
- All relay information (NIP-11) settings: `relay_name`, `relay_description`, `relay_contact`, `relay_software`, `relay_version`, `supported_nips`, `language_tags`, `relay_countries`, `posting_policy`, `payments_url`
- Authentication settings: `auth_enabled`, `nip42_auth_required`, `nip42_auth_required_kinds`, `nip42_challenge_timeout`
- Subscription limits: `max_subscriptions_per_client`, `max_total_subscriptions`
- Event validation limits: `max_event_tags`, `max_content_length`, `max_message_length`
- Proof of Work settings: `pow_min_difficulty`, `pow_mode`
- Event expiration settings: `nip40_expiration_enabled`, `nip40_expiration_strict`, `nip40_expiration_filter`, `nip40_expiration_grace_period`
## 📚 Documentation
**Restart-Required Configuration Parameters:**
- Connection settings: `max_connections`, `relay_port`
- Database and core system settings
- **[API Documentation](API.md)** - Complete API reference and advanced features
- **[Configuration Guide](docs/configuration_guide.md)** - Detailed configuration options
- **[Deployment Guide](docs/deployment_guide.md)** - Production deployment instructions
- **[User Guide](docs/user_guide.md)** - End-user documentation
- **[NIP-42 Authentication](docs/NIP-42_Authentication.md)** - Authentication setup guide
When updating configuration, the admin API response will indicate whether a restart is required for each parameter. Dynamic updates take effect immediately and are reflected in NIP-11 relay information documents without restart.
## 🧪 Testing
### Response Format
Run the comprehensive test suite:
All admin commands return **signed EVENT responses** via WebSocket following standard Nostr protocol. Responses use JSON content with structured data.
```bash
# Run all tests
./tests/run_all_tests.sh
#### Response Examples
# Run NIP-specific tests
./tests/run_nip_tests.sh
**Success Response:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"success\", \"message\": \"Operation completed successfully\", \"timestamp\": 1234567890}",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
# Test specific NIPs
./tests/42_nip_test.sh # NIP-42 authentication
./tests/13_nip_test.sh # NIP-13 proof of work
```
**Error Response:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"error\", \"error\": \"invalid configuration value\", \"timestamp\": 1234567890}",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
## 🔒 Security
### Admin Key Management
The admin private key is displayed **only once** during first startup. Store it securely:
```bash
# Save to secure location
echo "ADMIN_PRIVKEY=your_admin_private_key" > ~/.c-relay-pg-admin
chmod 600 ~/.c-relay-pg-admin
```
**Auth Rules Query Response:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"auth_rules_all\", \"total_results\": 2, \"timestamp\": 1234567890, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"allow\"}]}",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
```
### Production Security
**Configuration Query Response:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"config_all\", \"total_results\": 27, \"timestamp\": 1234567890, \"data\": [{\"key\": \"auth_enabled\", \"value\": \"false\", \"data_type\": \"boolean\", \"category\": \"auth\", \"description\": \"Enable NIP-42 authentication\"}, {\"key\": \"relay_description\", \"value\": \"My Relay\", \"data_type\": \"string\", \"category\": \"relay\", \"description\": \"Relay description text\"}]}",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
```
- Use HTTPS/WSS with reverse proxy (nginx/Apache)
- Enable NIP-42 authentication for client verification
- Configure whitelist/blacklist for access control
- Set up firewall rules to restrict access
- Enable Proof of Work to prevent spam
- Regular database backups
**Configuration Update Success Response:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"total_results\": 2, \"timestamp\": 1234567890, \"status\": \"success\", \"data\": [{\"key\": \"auth_enabled\", \"value\": \"true\", \"status\": \"updated\"}, {\"key\": \"relay_description\", \"value\": \"My Updated Relay\", \"status\": \"updated\"}]}",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
```
See [`docs/deployment_guide.md`](docs/deployment_guide.md#security-hardening) for security hardening guide.
**Configuration Update Error Response:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"error\", \"error\": \"field validation failed: invalid port number '99999' (must be 1-65535)\", \"timestamp\": 1234567890}",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
```
## 🤝 Contributing
**Database Statistics Query Response:**
```json
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"stats_query\", \"timestamp\": 1234567890, \"database_size_bytes\": 1048576, \"total_events\": 15432, \"database_created_at\": 1234567800, \"latest_event_at\": 1234567890, \"event_kinds\": [{\"kind\": 1, \"count\": 12000, \"percentage\": 77.8}, {\"kind\": 0, \"count\": 2500, \"percentage\": 16.2}], \"time_stats\": {\"total\": 15432, \"last_24h\": 234, \"last_7d\": 1456, \"last_30d\": 5432}, \"top_pubkeys\": [{\"pubkey\": \"abc123...\", \"event_count\": 1234, \"percentage\": 8.0}, {\"pubkey\": \"def456...\", \"event_count\": 987, \"percentage\": 6.4}]}",
"tags": [
["p", "admin_public_key"]
],
"sig": "response_event_signature"
}]
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes with tests
4. Submit a pull request
```
## 📄 License
[Add your license here]
## 🔗 Links
- **Repository**: [https://github.com/your-org/c-relay-pg](https://github.com/your-org/c-relay-pg)
- **Releases**: [https://git.laantungir.net/laantungir/c-relay-pg/releases](https://git.laantungir.net/laantungir/c-relay-pg/releases)
- **Issues**: [https://github.com/your-org/c-relay-pg/issues](https://github.com/your-org/c-relay-pg/issues)
- **Nostr Protocol**: [https://github.com/nostr-protocol/nostr](https://github.com/nostr-protocol/nostr)
## 💬 Support
- Open an issue on GitHub
- Join the Nostr community
- Contact via Nostr DM (relay pubkey shown on startup)
## Direct Messaging Admin System
In addition to the above admin API, c-relay allows the administrator to direct message the relay to get information or control some settings. As long as the administrator is signed in with any nostr client that allows sending nip-17 direct messages (DMs), they can control the relay.
The is possible because the relay is a full nostr citizen with it's own private and public key, and it knows the administrator's public key.
**Available DM commands**
The intent is not to be strict in the formatting of the DM. So for example if the relay receives any DM from the administrator with the words "stats" or "statistics" in it, it will respond to the administrator with a reply DM with the current relay statistics.
- `stats`|`statistics`: Relay statistics
- `config`|`configuration`: Relay configuration
---
**Built with ❤️ for the Nostr protocol**
+174
View File
@@ -0,0 +1,174 @@
# Real-Time Traffic Monitoring Commands (Direct Server Use)
Copy and paste these commands directly on your server.
## Quick Status Checks
### See IPs visiting in the last few minutes:
```bash
sudo tail -500 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
```
### See what status codes they're getting:
```bash
sudo tail -500 /var/log/nginx/access.log | awk '{print $1, $9}' | grep '216.73.216.38'
```
### Count status codes (200 vs 403):
```bash
sudo tail -500 /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c
```
## Real-Time Monitoring
### Watch live traffic (updates every 2 seconds):
```bash
watch -n 2 'sudo tail -200 /var/log/nginx/access.log | awk "{print \$1}" | sort | uniq -c | sort -rn | head -15'
```
### See live log entries as they happen:
```bash
sudo tail -f /var/log/nginx/access.log
```
### Live GoAccess dashboard:
```bash
sudo tail -f /var/log/nginx/access.log | goaccess -
```
## Active Connections
### See who's connected RIGHT NOW:
```bash
sudo netstat -tn | grep ':443' | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn
```
### Alternative (using ss command):
```bash
sudo ss -tn | grep ':443' | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn
```
## Detailed Analysis
### Last 100 requests with timestamps:
```bash
sudo tail -100 /var/log/nginx/access.log | awk '{print $4, $1}' | sed 's/\[//'
```
### See what blocked IPs are trying to access:
```bash
sudo tail -500 /var/log/nginx/access.log | grep '216.73.216.38' | awk '{print $7}' | head -10
```
### Show all 403 (blocked) requests:
```bash
sudo tail -500 /var/log/nginx/access.log | awk '$9==403 {print $1}' | sort | uniq -c | sort -rn
```
### Show all successful (200) requests:
```bash
sudo tail -500 /var/log/nginx/access.log | awk '$9==200 {print $1}' | sort | uniq -c | sort -rn | head -10
```
## Comprehensive Monitoring Script
### Create a monitoring script:
```bash
cat > /tmp/monitor-traffic.sh << 'EOF'
#!/bin/bash
echo "=== Traffic in last 5 minutes ==="
echo "Time: $(date)"
echo ""
echo "Top IPs:"
sudo tail -1000 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "Blocked IPs (403 errors):"
sudo tail -1000 /var/log/nginx/access.log | awk '$9==403 {print $1}' | sort | uniq -c | sort -rn
echo ""
echo "Successful requests (200):"
sudo tail -1000 /var/log/nginx/access.log | awk '$9==200 {print $1}' | sort | uniq -c | sort -rn | head -5
echo ""
echo "Status Code Summary:"
sudo tail -1000 /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c | sort -rn
EOF
chmod +x /tmp/monitor-traffic.sh
```
### Run the monitoring script:
```bash
/tmp/monitor-traffic.sh
```
## Auto-Refreshing Dashboard
### Live dashboard (refreshes every 5 seconds):
```bash
watch -n 5 'echo "=== Last 5 minutes ==="
date
echo ""
echo "Top IPs:"
sudo tail -1000 /var/log/nginx/access.log | awk "{print \$1}" | sort | uniq -c | sort -rn | head -10
echo ""
echo "Status Codes:"
sudo tail -1000 /var/log/nginx/access.log | awk "{print \$9}" | sort | uniq -c | sort -rn'
```
Press `Ctrl+C` to exit.
## GoAccess HTML Report (Live Updating)
### Generate live HTML report:
```bash
sudo goaccess /var/log/nginx/access.log -o /var/www/html/live-stats.html --real-time-html --daemonize
```
Then visit: https://git.laantungir.net/live-stats.html
### Stop the live report:
```bash
sudo pkill -f "goaccess.*live-stats"
```
## Filter by Time
### Get timestamp from 5 minutes ago:
```bash
date -d '5 minutes ago' '+%d/%b/%Y:%H:%M'
```
### Analyze only recent logs (replace timestamp):
```bash
sudo awk '/01\/Feb\/2026:19:09/,0' /var/log/nginx/access.log | goaccess -
```
## Check Gitea CPU
### Current CPU usage:
```bash
ps aux | grep gitea | grep -v grep
```
### Watch CPU in real-time:
```bash
watch -n 2 'ps aux | grep gitea | grep -v grep'
```
## Most Useful Command for Quick Check
This one-liner shows everything you need:
```bash
echo "=== Quick Status ===" && \
echo "Time: $(date)" && \
echo "" && \
echo "Top 10 IPs (last 1000 requests):" && \
sudo tail -1000 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 && \
echo "" && \
echo "Status Codes:" && \
sudo tail -1000 /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c && \
echo "" && \
echo "Gitea CPU:" && \
ps aux | grep gitea | grep -v grep
```
Copy any of these commands and run them directly on your server!
+612
View File
@@ -0,0 +1,612 @@
# Static MUSL Build Guide for C Programs
## Overview
This guide explains how to build truly portable static binaries using Alpine Linux and MUSL libc. These binaries have **zero runtime dependencies** and work on any Linux distribution without modification.
This guide is specifically tailored for C programs that use:
- **nostr_core_lib** - Nostr protocol implementation
- **nostr_login_lite** - Nostr authentication library
- Common dependencies: libwebsockets, OpenSSL, SQLite, curl, secp256k1
## Why MUSL Static Binaries?
### Advantages Over glibc
| Feature | MUSL Static | glibc Static | glibc Dynamic |
|---------|-------------|--------------|---------------|
| **Portability** | ✓ Any Linux | ⚠ glibc only | ✗ Requires matching libs |
| **Binary Size** | ~7-10 MB | ~12-15 MB | ~2-3 MB |
| **Dependencies** | None | NSS libs | Many system libs |
| **Deployment** | Single file | Single file + NSS | Binary + libraries |
| **Compatibility** | Universal | glibc version issues | Library version hell |
### Key Benefits
1. **True Portability**: Works on Alpine, Ubuntu, Debian, CentOS, Arch, etc.
2. **No Library Hell**: No `GLIBC_2.XX not found` errors
3. **Simple Deployment**: Just copy one file
4. **Reproducible Builds**: Same Docker image = same binary
5. **Security**: No dependency on system libraries with vulnerabilities
## Quick Start
### Prerequisites
- Docker installed and running
- Your C project with source code
- Internet connection for downloading dependencies
### Basic Build Process
```bash
# 1. Copy the Dockerfile template (see below)
cp /path/to/c-relay-pg/Dockerfile.alpine-musl ./Dockerfile.static
# 2. Customize for your project (see Customization section)
vim Dockerfile.static
# 3. Build the static binary
docker build --platform linux/amd64 -f Dockerfile.static -t my-app-builder .
# 4. Extract the binary
docker create --name temp-container my-app-builder
docker cp temp-container:/build/my_app_static ./my_app_static
docker rm temp-container
# 5. Verify it's static
ldd ./my_app_static # Should show "not a dynamic executable"
```
## Dockerfile Template
Here's a complete Dockerfile template you can customize for your project:
```dockerfile
# Alpine-based MUSL static binary builder
# Produces truly portable binaries with zero runtime dependencies
FROM alpine:3.19 AS builder
# Install build dependencies
RUN apk add --no-cache \
build-base \
musl-dev \
git \
cmake \
pkgconfig \
autoconf \
automake \
libtool \
openssl-dev \
openssl-libs-static \
zlib-dev \
zlib-static \
curl-dev \
curl-static \
sqlite-dev \
sqlite-static \
linux-headers \
wget \
bash
WORKDIR /build
# Build libsecp256k1 static (required for Nostr)
RUN cd /tmp && \
git clone https://github.com/bitcoin-core/secp256k1.git && \
cd secp256k1 && \
./autogen.sh && \
./configure --enable-static --disable-shared --prefix=/usr \
CFLAGS="-fPIC" && \
make -j$(nproc) && \
make install && \
rm -rf /tmp/secp256k1
# Build libwebsockets static (if needed for WebSocket support)
RUN cd /tmp && \
git clone --depth 1 --branch v4.3.3 https://github.com/warmcat/libwebsockets.git && \
cd libwebsockets && \
mkdir build && cd build && \
cmake .. \
-DLWS_WITH_STATIC=ON \
-DLWS_WITH_SHARED=OFF \
-DLWS_WITH_SSL=ON \
-DLWS_WITHOUT_TESTAPPS=ON \
-DLWS_WITHOUT_TEST_SERVER=ON \
-DLWS_WITHOUT_TEST_CLIENT=ON \
-DLWS_WITHOUT_TEST_PING=ON \
-DLWS_WITH_HTTP2=OFF \
-DLWS_WITH_LIBUV=OFF \
-DLWS_WITH_LIBEVENT=OFF \
-DLWS_IPV6=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_C_FLAGS="-fPIC" && \
make -j$(nproc) && \
make install && \
rm -rf /tmp/libwebsockets
# Copy git configuration for submodules
COPY .gitmodules /build/.gitmodules
COPY .git /build/.git
# Initialize submodules
RUN git submodule update --init --recursive
# Copy and build nostr_core_lib
COPY nostr_core_lib /build/nostr_core_lib/
RUN cd nostr_core_lib && \
chmod +x build.sh && \
sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \
rm -f *.o *.a 2>/dev/null || true && \
./build.sh --nips=1,6,13,17,19,44,59
# Copy and build nostr_login_lite (if used)
# COPY nostr_login_lite /build/nostr_login_lite/
# RUN cd nostr_login_lite && make static
# Copy your application source
COPY src/ /build/src/
COPY Makefile /build/Makefile
# Build your application with full static linking
RUN gcc -static -O2 -Wall -Wextra -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
src/*.c \
-o /build/my_app_static \
nostr_core_lib/libnostr_core_x64.a \
-lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \
-lcurl -lz -lpthread -lm -ldl && \
strip /build/my_app_static
# Verify it's truly static
RUN echo "=== Binary Information ===" && \
file /build/my_app_static && \
ls -lh /build/my_app_static && \
echo "=== Checking for dynamic dependencies ===" && \
(ldd /build/my_app_static 2>&1 || echo "Binary is static")
# Output stage - just the binary
FROM scratch AS output
COPY --from=builder /build/my_app_static /my_app_static
```
## Customization Guide
### 1. Adjust Dependencies
**Add dependencies** by modifying the `apk add` section:
```dockerfile
RUN apk add --no-cache \
build-base \
musl-dev \
# Add your dependencies here:
libpng-dev \
libpng-static \
libjpeg-turbo-dev \
libjpeg-turbo-static
```
**Remove unused dependencies** to speed up builds:
- Remove `libwebsockets` section if you don't need WebSocket support
- Remove `sqlite` if you don't use databases
- Remove `curl` if you don't make HTTP requests
### 2. Configure nostr_core_lib NIPs
Specify which NIPs your application needs:
```bash
./build.sh --nips=1,6,19 # Minimal: Basic protocol, keys, bech32
./build.sh --nips=1,6,13,17,19,44,59 # Full: All common NIPs
./build.sh --nips=all # Everything available
```
**Common NIP combinations:**
- **Basic client**: `1,6,19` (events, keys, bech32)
- **With encryption**: `1,6,19,44` (add modern encryption)
- **With DMs**: `1,6,17,19,44,59` (add private messages)
- **Relay/server**: `1,6,13,17,19,42,44,59` (add PoW, auth)
### 3. Modify Compilation Flags
**For your application:**
```dockerfile
RUN gcc -static -O2 -Wall -Wextra -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ # REQUIRED for MUSL
-I. -Inostr_core_lib \ # Include paths
src/*.c \ # Your source files
-o /build/my_app_static \ # Output binary
nostr_core_lib/libnostr_core_x64.a \ # Nostr library
-lwebsockets -lssl -lcrypto \ # Link libraries
-lsqlite3 -lsecp256k1 -lcurl \
-lz -lpthread -lm -ldl
```
**Debug build** (with symbols, no optimization):
```dockerfile
RUN gcc -static -g -O0 -DDEBUG \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
# ... rest of flags
```
### 4. Multi-Architecture Support
Build for different architectures:
```bash
# x86_64 (Intel/AMD)
docker build --platform linux/amd64 -f Dockerfile.static -t my-app-x86 .
# ARM64 (Apple Silicon, Raspberry Pi 4+)
docker build --platform linux/arm64 -f Dockerfile.static -t my-app-arm64 .
```
## Build Script Template
Create a `build_static.sh` script for convenience:
```bash
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="$SCRIPT_DIR/build"
DOCKERFILE="$SCRIPT_DIR/Dockerfile.static"
# Detect architecture
ARCH=$(uname -m)
case "$ARCH" in
x86_64)
PLATFORM="linux/amd64"
OUTPUT_NAME="my_app_static_x86_64"
;;
aarch64|arm64)
PLATFORM="linux/arm64"
OUTPUT_NAME="my_app_static_arm64"
;;
*)
echo "Unknown architecture: $ARCH"
exit 1
;;
esac
echo "Building for platform: $PLATFORM"
mkdir -p "$BUILD_DIR"
# Build Docker image
docker build \
--platform "$PLATFORM" \
-f "$DOCKERFILE" \
-t my-app-builder:latest \
--progress=plain \
.
# Extract binary
CONTAINER_ID=$(docker create my-app-builder:latest)
docker cp "$CONTAINER_ID:/build/my_app_static" "$BUILD_DIR/$OUTPUT_NAME"
docker rm "$CONTAINER_ID"
chmod +x "$BUILD_DIR/$OUTPUT_NAME"
echo "✓ Build complete: $BUILD_DIR/$OUTPUT_NAME"
echo "✓ Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)"
# Verify
if ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 | grep -q "not a dynamic executable"; then
echo "✓ Binary is fully static"
else
echo "⚠ Warning: Binary may have dynamic dependencies"
fi
```
Make it executable:
```bash
chmod +x build_static.sh
./build_static.sh
```
## Common Issues and Solutions
### Issue 1: Fortification Errors
**Error:**
```
undefined reference to '__snprintf_chk'
undefined reference to '__fprintf_chk'
```
**Cause**: GCC's `-O2` enables fortification by default, which uses glibc-specific functions.
**Solution**: Add these flags to **all** compilation commands:
```bash
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0
```
This must be applied to:
1. nostr_core_lib build.sh
2. Your application compilation
3. Any other libraries you build
### Issue 2: Missing Symbols from nostr_core_lib
**Error:**
```
undefined reference to 'nostr_create_event'
undefined reference to 'nostr_sign_event'
```
**Cause**: Required NIPs not included in nostr_core_lib build.
**Solution**: Add missing NIPs:
```bash
./build.sh --nips=1,6,19 # Add the NIPs you need
```
### Issue 3: Docker Permission Denied
**Error:**
```
permission denied while trying to connect to the Docker daemon socket
```
**Solution**:
```bash
sudo usermod -aG docker $USER
newgrp docker # Or logout and login
```
### Issue 4: Binary Won't Run on Target System
**Checks**:
```bash
# 1. Verify it's static
ldd my_app_static # Should show "not a dynamic executable"
# 2. Check architecture
file my_app_static # Should match target system
# 3. Test on different distributions
docker run --rm -v $(pwd):/app alpine:latest /app/my_app_static --version
docker run --rm -v $(pwd):/app ubuntu:latest /app/my_app_static --version
```
## Project Structure Example
Organize your project for easy static builds:
```
my-nostr-app/
├── src/
│ ├── main.c
│ ├── handlers.c
│ └── utils.c
├── nostr_core_lib/ # Git submodule
├── nostr_login_lite/ # Git submodule (if used)
├── Dockerfile.static # Static build Dockerfile
├── build_static.sh # Build script
├── Makefile # Regular build
└── README.md
```
### Makefile Integration
Add static build targets to your Makefile:
```makefile
# Regular dynamic build
all: my_app
my_app: src/*.c
gcc -O2 src/*.c -o my_app \
nostr_core_lib/libnostr_core_x64.a \
-lssl -lcrypto -lsecp256k1 -lz -lpthread -lm
# Static MUSL build via Docker
static:
./build_static.sh
# Clean
clean:
rm -f my_app build/my_app_static_*
.PHONY: all static clean
```
## Deployment
### Single Binary Deployment
```bash
# Copy to server
scp build/my_app_static_x86_64 user@server:/opt/my-app/
# Run (no dependencies needed!)
ssh user@server
/opt/my-app/my_app_static_x86_64
```
### SystemD Service
```ini
[Unit]
Description=My Nostr Application
After=network.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/my-app
ExecStart=/opt/my-app/my_app_static_x86_64
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
### Docker Container (Minimal)
```dockerfile
FROM scratch
COPY my_app_static_x86_64 /app
ENTRYPOINT ["/app"]
```
Build and run:
```bash
docker build -t my-app:latest .
docker run --rm my-app:latest --help
```
## Reusing c-relay-pg Files
You can directly copy these files from c-relay-pg:
### 1. Dockerfile.alpine-musl
```bash
cp /path/to/c-relay-pg/Dockerfile.alpine-musl ./Dockerfile.static
```
Then customize:
- Change binary name (line 125)
- Adjust source files (line 122-124)
- Modify include paths (line 120-121)
### 2. build_static.sh
```bash
cp /path/to/c-relay-pg/build_static.sh ./
```
Then customize:
- Change `OUTPUT_NAME` variable (lines 66, 70)
- Update Docker image name (line 98)
- Modify verification commands (lines 180-184)
### 3. .dockerignore (Optional)
```bash
cp /path/to/c-relay-pg/.dockerignore ./
```
Helps speed up Docker builds by excluding unnecessary files.
## Best Practices
1. **Version Control**: Commit your Dockerfile and build script
2. **Tag Builds**: Include git commit hash in binary version
3. **Test Thoroughly**: Verify on multiple distributions
4. **Document Dependencies**: List required NIPs and libraries
5. **Automate**: Use CI/CD to build on every commit
6. **Archive Binaries**: Keep old versions for rollback
## Performance Comparison
| Metric | MUSL Static | glibc Dynamic |
|--------|-------------|---------------|
| Binary Size | 7-10 MB | 2-3 MB + libs |
| Startup Time | ~50ms | ~40ms |
| Memory Usage | Similar | Similar |
| Portability | ✓ Universal | ✗ System-dependent |
| Deployment | Single file | Binary + libraries |
## References
- [MUSL libc](https://musl.libc.org/)
- [Alpine Linux](https://alpinelinux.org/)
- [nostr_core_lib](https://github.com/chebizarro/nostr_core_lib)
- [Static Linking Best Practices](https://www.musl-libc.org/faq.html)
- [c-relay-pg Implementation](./docs/musl_static_build.md)
## Example: Minimal Nostr Client
Here's a complete example of building a minimal Nostr client:
```c
// minimal_client.c
#include "nostr_core/nostr_core.h"
#include <stdio.h>
int main() {
// Generate keypair
char nsec[64], npub[64];
nostr_generate_keypair(nsec, npub);
printf("Generated keypair:\n");
printf("Private key (nsec): %s\n", nsec);
printf("Public key (npub): %s\n", npub);
// Create event
cJSON *event = nostr_create_event(1, "Hello, Nostr!", NULL);
nostr_sign_event(event, nsec);
char *json = cJSON_Print(event);
printf("\nSigned event:\n%s\n", json);
free(json);
cJSON_Delete(event);
return 0;
}
```
**Dockerfile.static:**
```dockerfile
FROM alpine:3.19 AS builder
RUN apk add --no-cache build-base musl-dev git autoconf automake libtool \
openssl-dev openssl-libs-static zlib-dev zlib-static
WORKDIR /build
# Build secp256k1
RUN cd /tmp && git clone https://github.com/bitcoin-core/secp256k1.git && \
cd secp256k1 && ./autogen.sh && \
./configure --enable-static --disable-shared --prefix=/usr CFLAGS="-fPIC" && \
make -j$(nproc) && make install
# Copy and build nostr_core_lib
COPY nostr_core_lib /build/nostr_core_lib/
RUN cd nostr_core_lib && \
sed -i 's/CFLAGS="-Wall/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall/' build.sh && \
./build.sh --nips=1,6,19
# Build application
COPY minimal_client.c /build/
RUN gcc -static -O2 -Wall -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson \
minimal_client.c -o /build/minimal_client_static \
nostr_core_lib/libnostr_core_x64.a \
-lssl -lcrypto -lsecp256k1 -lz -lpthread -lm -ldl && \
strip /build/minimal_client_static
FROM scratch
COPY --from=builder /build/minimal_client_static /minimal_client_static
```
**Build and run:**
```bash
docker build -f Dockerfile.static -t minimal-client .
docker create --name temp minimal-client
docker cp temp:/minimal_client_static ./
docker rm temp
./minimal_client_static
```
## Conclusion
Static MUSL binaries provide the best portability for C applications. While they're slightly larger than dynamic binaries, the benefits of zero dependencies and universal compatibility make them ideal for:
- Server deployments across different Linux distributions
- Embedded systems and IoT devices
- Docker containers (FROM scratch)
- Distribution to users without dependency management
- Long-term archival and reproducibility
Follow this guide to create portable, self-contained binaries for your Nostr applications!
+37
View File
@@ -0,0 +1,37 @@
<?php
/** admin2/api/auth.php — Auth rules + WoT status. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
try {
if ($action === 'add') {
$pdo->prepare("INSERT INTO auth_rules (rule_type, pattern_type, pattern_value) VALUES (?, 'pubkey', ?)")->execute([$input['rule_type'], $input['pattern_value']]);
json_response(['message' => 'Rule added']);
} elseif ($action === 'remove') {
$pdo->prepare("DELETE FROM auth_rules WHERE id = ?")->execute([intval($input['id'])]);
json_response(['message' => 'Rule removed']);
} elseif ($action === 'set_wot_level') {
$pdo->prepare("UPDATE config SET value = ? WHERE key = 'wot_level'")->execute([strval($input['level'])]);
json_response(['message' => 'WoT level set']);
} elseif ($action === 'sync_wot') {
json_response(['message' => 'WoT sync not available via PHP (requires relay)']);
}
} catch (PDOException $e) { json_response(['error' => $e->getMessage()]); }
}
// GET: WoT status
if (isset($_GET['action']) && $_GET['action'] === 'wot') {
$level = $pdo->query("SELECT value FROM config WHERE key = 'wot_level'")->fetchColumn() ?: '0';
$wl_count = 0;
try { $wl_count = intval($pdo->query("SELECT count(*) FROM auth_rules WHERE rule_type = 'whitelist'")->fetchColumn()); } catch (PDOException $e) {}
$descriptions = ['0' => 'Level 0: Open relay — anyone can read and write', '1' => 'Level 1: Write only — WoT users can write', '2' => 'Level 2: Full — WoT users only'];
json_response(['kind3_status' => 'N/A (PHP admin)', 'whitelist_count' => $wl_count, 'level_description' => $descriptions[$level] ?? $descriptions['0']]);
}
// GET: auth rules
$rules = [];
try { $rules = $pdo->query("SELECT id, rule_type, pattern_type, pattern_value FROM auth_rules ORDER BY id")->fetchAll(); } catch (PDOException $e) {}
json_response(['rules' => $rules]);
+153
View File
@@ -0,0 +1,153 @@
<?php
/** admin2/api/caching.php — Caching service status + follows + reset actions. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
// --- POST actions: reset backfill (all or per-user) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true) ?: [];
$action = $input['action'] ?? '';
$pubkey = $input['pubkey'] ?? '';
if ($action === 'reset_all') {
// Reset backfill progress for ALL followed authors, then bump config
// generation so the running caching service hot-reloads and re-drains.
try {
$pdo->beginTransaction();
$pdo->exec("UPDATE caching_followed_pubkeys
SET until_cursor = 0, backfill_complete = FALSE,
events_fetched = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT");
// Also reset per-relay progress rows
$pdo->exec("UPDATE caching_backfill_relay_progress
SET complete = FALSE, events_fetched = 0,
until_cursor = 0, last_status = 'reset',
consecutive_errors = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT");
// Bump config generation to trigger hot-reload
$pdo->exec("UPDATE config SET value = (COALESCE(value::int, 0) + 1)::text
WHERE key = 'caching_config_generation'");
$pdo->commit();
json_response(['ok' => true, 'message' => 'All backfill progress reset. Caching service will re-drain.']);
} catch (PDOException $e) {
$pdo->rollBack();
json_response(['ok' => false, 'error' => $e->getMessage()], 500);
}
} elseif ($action === 'reset_user' && $pubkey) {
// Reset backfill progress for a single followed author.
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare("UPDATE caching_followed_pubkeys
SET until_cursor = 0, backfill_complete = FALSE,
events_fetched = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT
WHERE pubkey = ?");
$stmt->execute([$pubkey]);
// Also reset per-relay progress for this author
$stmt2 = $pdo->prepare("UPDATE caching_backfill_relay_progress
SET complete = FALSE, events_fetched = 0,
until_cursor = 0, last_status = 'reset',
consecutive_errors = 0,
updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT
WHERE author_pubkey = ?");
$stmt2->execute([$pubkey]);
// Bump config generation to trigger hot-reload
$pdo->exec("UPDATE config SET value = (COALESCE(value::int, 0) + 1)::text
WHERE key = 'caching_config_generation'");
$pdo->commit();
json_response(['ok' => true, 'message' => "Backfill reset for pubkey. Caching service will re-fetch."]);
} catch (PDOException $e) {
$pdo->rollBack();
json_response(['ok' => false, 'error' => $e->getMessage()], 500);
}
} else {
json_response(['ok' => false, 'error' => 'Unknown action'], 400);
}
exit;
}
// Caching config values (for toggle controls)
$config = [];
try {
$cfg_rows = $pdo->query("SELECT key, value FROM config WHERE key IN ('caching_enabled','caching_inbox_enabled','caching_live_enabled','caching_backfill_enabled')")->fetchAll();
foreach ($cfg_rows as $r) { $config[$r['key']] = $r['value']; }
} catch (PDOException $e) {}
// Service state
$state = [];
try { $state = $pdo->query("SELECT * FROM caching_service_state WHERE id = 1")->fetch() ?: []; } catch (PDOException $e) {}
// Active target
$active = ['pubkey' => '', 'relay' => ''];
try { $active = $pdo->query("SELECT active_pubkey AS pubkey, active_relay AS relay FROM caching_backfill_active WHERE id = 1")->fetch() ?: $active; } catch (PDOException $e) {}
// Inbox
$inbox = ['pending' => 0, 'live' => 0, 'backfill' => 0];
try {
$inbox = $pdo->query("SELECT COUNT(*) AS pending, COUNT(*) FILTER (WHERE source_class='live') AS live, COUNT(*) FILTER (WHERE source_class='backfill') AS backfill FROM caching_event_inbox")->fetch() ?: $inbox;
} catch (PDOException $e) {}
// Upstream relay status (per-relay connection state)
$upstreamRelays = [];
try {
$upstreamRelays = $pdo->query("
SELECT relay_url, status_code, status_text, updated_at
FROM caching_upstream_relays
ORDER BY relay_url
")->fetchAll();
} catch (PDOException $e) {}
// Follows (paginated, with names from profiles cache + per-relay progress)
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 50;
$offset = ($page - 1) * $perPage;
$follows = [];
$totalFollows = 0;
try {
$totalFollows = (int)$pdo->query("SELECT COUNT(*) FROM caching_followed_pubkeys")->fetchColumn();
} catch (PDOException $e) {}
try {
$follows = $pdo->query("
SELECT fp.pubkey, fp.is_root, fp.backfill_complete, fp.events_fetched,
fp.last_event_at,
p.name, p.display_name,
(SELECT count(*) FROM events WHERE pubkey = fp.pubkey) AS total_events,
(SELECT count(*) FROM caching_backfill_relay_progress WHERE author_pubkey = fp.pubkey) AS relay_count,
(SELECT count(*) FROM caching_backfill_relay_progress WHERE author_pubkey = fp.pubkey AND complete = false) AS relay_incomplete
FROM caching_followed_pubkeys fp
LEFT JOIN profiles p ON p.pubkey = fp.pubkey
ORDER BY fp.is_root DESC, fp.events_fetched DESC
LIMIT $perPage OFFSET $offset
")->fetchAll();
// Fetch per-relay progress for all followed pubkeys in one query
$relayProgress = [];
try {
$rpRows = $pdo->query("
SELECT author_pubkey, relay_url, complete, events_fetched, until_cursor, last_status, updated_at
FROM caching_backfill_relay_progress
ORDER BY author_pubkey, events_fetched DESC
")->fetchAll();
foreach ($rpRows as $rp) {
$relayProgress[$rp['author_pubkey']][] = $rp;
}
} catch (PDOException $e) {}
foreach ($follows as &$f) {
$f['name'] = profile_display_name($f);
$f['npub'] = function_exists('hex_to_npub') ? hex_to_npub($f['pubkey']) : substr($f['pubkey'], 0, 20);
$f['relays'] = $relayProgress[$f['pubkey']] ?? [];
}
} catch (PDOException $e) {}
json_response([
'state' => $state,
'active' => $active,
'inbox' => $inbox,
'follows' => $follows,
'config' => $config,
'upstreamRelays' => $upstreamRelays,
'totalFollows' => $totalFollows,
'page' => $page,
'perPage' => $perPage,
]);
+137
View File
@@ -0,0 +1,137 @@
<?php
/**
* admin/api/chart.php — Standalone ASCII chart endpoint.
*
* Returns a plain-text ASCII X-bar chart of event counts over time.
* Works in both the browser (injected into a <div>) and the terminal:
*
* curl http://localhost:8088/api/chart.php?range=hour
* curl http://localhost:8088/api/chart.php?range=day
* curl http://localhost:8088/api/chart.php?range=month
* curl http://localhost:8088/api/chart.php?range=year
*
* This endpoint is read-only (a fixed aggregate COUNT query with the only
* user input being the `range` selector, which is validated against a
* whitelist). It is safe to expose publicly and is served without auth at:
*
* https://<domain>/relay/api/chart.php?range=day
*
* It is also reachable (behind Basic Auth) from the admin UI at:
*
* https://<domain>/relay/admin/api/chart.php?range=day
*
* Caching: the hour chart is never cached (live). Day/month/year are
* cached to file with TTLs to avoid expensive queries on every request.
*
* NOTE: Uses first_seen (not created_at) for binning, because some events
* have corrupted created_at timestamps (far-future values) that would
* place them outside the visible chart range.
*/
require_once __DIR__ . '/../lib/helpers.php';
require_once __DIR__ . '/../lib/ascii_chart.php';
// --- Range configuration ---
$ranges = [
'hour' => ['span' => 3600, 'bin' => 45, 'bins' => 80, 'ttl' => 0, 'title' => 'New Events — Last Hour'],
'day' => ['span' => 86400, 'bin' => 1080, 'bins' => 80, 'ttl' => 3600, 'title' => 'New Events — Last 24 Hours'],
'month' => ['span' => 2592000, 'bin' => 32400, 'bins' => 80, 'ttl' => 86400, 'title' => 'New Events — Last 30 Days'],
'year' => ['span' => 31536000, 'bin' => 394200, 'bins' => 80, 'ttl' => 2592000, 'title' => 'New Events — Last Year'],
];
$range = $_GET['range'] ?? 'hour';
if (!isset($ranges[$range])) {
http_response_code(400);
header('Content-Type: text/plain; charset=utf-8');
echo "Invalid range. Use: hour, day, month, or year.\n";
exit;
}
$cfg = $ranges[$range];
// --- Set content type ---
header('Content-Type: text/plain; charset=utf-8');
// --- Check cache ---
if ($cfg['ttl'] > 0) {
$cached = get_cached_chart($range, $cfg['ttl']);
if ($cached !== null) {
echo $cached;
exit;
}
}
// --- Run binning query ---
$pdo = db();
$now = time();
$epoch = $now - $cfg['span'];
$bin_size = $cfg['bin'];
$num_bins = $cfg['bins'];
try {
// Calculate the bin offset so the rightmost bin aligns with "now"
// bin index 0 = oldest, bin index (num_bins-1) = newest
$base_bin = (int)floor($epoch / $bin_size);
// Use first_seen (not created_at) for binning, because some events
// have corrupted created_at timestamps (far-future values) that
// would place them outside the visible chart range.
$sql = "SELECT FLOOR(first_seen / {$bin_size})::BIGINT - {$base_bin} AS bin, COUNT(*) AS cnt
FROM events
WHERE first_seen >= {$epoch}
GROUP BY bin
ORDER BY bin";
$rows = $pdo->query($sql)->fetchAll();
} catch (PDOException $e) {
echo "Chart query failed: " . $e->getMessage() . "\n";
exit;
}
// --- Build bin array (zero-filled, fixed length) ---
// Index 0 = oldest, index N-1 = newest (natural time order from SQL)
$bins = build_bin_array($rows, $num_bins);
// Reverse so newest bin is at index 0 (leftmost) and oldest at right.
// This matches the original text_graph.js display: new data enters
// from the left and proceeds rightward as it ages.
$bins = array_reverse($bins);
// --- Render ASCII chart ---
$ascii = render_ascii_chart($bins, [
'title' => $cfg['title'],
'max_height' => 11,
'bin_duration' => $bin_size,
'label_interval' => max(1, (int)floor($num_bins / 15)), // ~15 labels across
]);
// --- Write to cache (if TTL > 0) ---
if ($cfg['ttl'] > 0) {
set_cached_chart($range, $ascii);
}
echo $ascii;
// ================================
// CACHE HELPERS
// ================================
function get_cache_dir(): string {
return __DIR__ . '/../cache';
}
function get_cached_chart(string $range, int $ttl): ?string {
$file = get_cache_dir() . "/chart_{$range}.txt";
if (!file_exists($file)) return null;
if (filemtime($file) < (time() - $ttl)) return null;
$content = @file_get_contents($file);
return ($content !== false) ? $content : null;
}
function set_cached_chart(string $range, string $ascii): void {
$dir = get_cache_dir();
if (!is_dir($dir)) {
@mkdir($dir, 0775, true);
}
@file_put_contents($dir . "/chart_{$range}.txt", $ascii);
}
+286
View File
@@ -0,0 +1,286 @@
<?php
/**
* admin2/api/cleanup.php — Event Cleanup: Preview & Execute.
*
* GET — Preview a cleanup query (count + size estimate, no delete).
* POST — Execute a cleanup query (dry_run=true for preview, false for delete).
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
// ── Shared filter builder ──────────────────────────────────────────────
// Returns [sql_conditions[], params[]] for the WHERE clauses.
function build_filters(array $opts): array {
$conds = [];
$params = [];
// Kinds filter
if (!empty($opts['kinds'])) {
$kinds = is_array($opts['kinds']) ? $opts['kinds'] : explode(',', $opts['kinds']);
$kinds = array_map('intval', $kinds);
$kinds = array_filter($kinds, fn($v) => $v > 0);
if (!empty($kinds)) {
$placeholders = [];
foreach ($kinds as $i => $k) {
$key = ':kind_' . $i;
$placeholders[] = $key;
$params[$key] = $k;
}
$conds[] = 'e.kind IN (' . implode(',', $placeholders) . ')';
}
}
// Max age filter
$max_age_days = intval($opts['max_age_days'] ?? 0);
if ($max_age_days > 0) {
$conds[] = 'e.created_at < EXTRACT(EPOCH FROM NOW())::BIGINT - :max_age_seconds';
$params[':max_age_seconds'] = $max_age_days * 86400;
}
// Follows filter
$follows_filter = $opts['follows_filter'] ?? 'all';
if ($follows_filter === 'follows') {
$conds[] = 'e.pubkey IN (SELECT pubkey FROM caching_followed_pubkeys)';
} elseif ($follows_filter === 'non_follows') {
$conds[] = 'e.pubkey NOT IN (SELECT pubkey FROM caching_followed_pubkeys)';
}
return [$conds, $params];
}
// ── Build a WHERE clause string from conditions ────────────────────────
function where_clause(array $conds): string {
if (empty($conds)) return '';
return 'AND ' . implode("\n AND ", $conds);
}
// ── Format bytes as human-readable ─────────────────────────────────────
function format_bytes(int $bytes): string {
if ($bytes < 1024) return $bytes . ' B';
if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB';
if ($bytes < 1073741824) return round($bytes / 1048576, 1) . ' MB';
return round($bytes / 1073741824, 2) . ' GB';
}
// ── GET: Preview ───────────────────────────────────────────────────────
if ($method === 'GET') {
$opts = [
'follows_filter' => $_GET['follows_filter'] ?? 'all',
'kinds' => $_GET['kinds'] ?? '',
'max_age_days' => intval($_GET['max_age_days'] ?? 0),
'max_events' => intval($_GET['max_events'] ?? 0),
];
list($conds, $params) = build_filters($opts);
$where = where_clause($conds);
// Preview SQL (for display)
$sql_preview = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1\n"
. ($where ? " $where\n" : '');
// Count + size query
$count_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($count_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$match_count = intval($row['match_count'] ?? 0);
$total_size_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Preview query failed: ' . $e->getMessage()]);
exit;
}
// Kind breakdown
$breakdown = [];
if ($match_count > 0) {
$breakdown_sql = "SELECT e.kind,\n"
. " COUNT(*) AS count,\n"
. " COALESCE(SUM(pg_column_size(e.event_json)), 0) AS size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where\n"
. "GROUP BY e.kind\n"
. "ORDER BY count DESC\n"
. "LIMIT 50";
try {
$stmt = $pdo->prepare($breakdown_sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
foreach ($rows as $r) {
$breakdown[] = [
'kind' => intval($r['kind']),
'count' => intval($r['count']),
'size_bytes' => intval($r['size_bytes']),
];
}
} catch (PDOException $e) {
// Breakdown is non-critical
}
}
// If a query_id was provided, update last_preview_count and last_preview_size_bytes
$query_id = intval($_GET['query_id'] ?? 0);
if ($query_id > 0) {
try {
$pdo->prepare(
"UPDATE cleanup_saved_queries\n"
. " SET last_preview_count = :count,\n"
. " last_preview_size_bytes = :size\n"
. " WHERE id = :id"
)->execute([
':id' => $query_id,
':count' => $match_count,
':size' => $total_size_bytes,
]);
} catch (PDOException $e) {}
}
json_response([
'match_count' => $match_count,
'total_size_bytes' => $total_size_bytes,
'total_size_human' => format_bytes($total_size_bytes),
'avg_size_per_event' => $match_count > 0 ? intval($total_size_bytes / $match_count) : 0,
'kinds_breakdown' => $breakdown,
'sql_preview' => $sql_preview,
]);
exit;
}
// ── POST: Execute (or dry-run) ─────────────────────────────────────────
if ($method === 'POST') {
$body = json_decode(file_get_contents('php://input'), true);
if (!$body) {
json_response(['error' => 'Invalid JSON body']);
exit;
}
$dry_run = !empty($body['dry_run']);
$opts = [
'follows_filter' => $body['follows_filter'] ?? 'all',
'kinds' => $body['kinds'] ?? [],
'max_age_days' => intval($body['max_age_days'] ?? 0),
'max_events' => intval($body['max_events'] ?? 0),
];
list($conds, $params) = build_filters($opts);
$where = where_clause($conds);
// If dry_run, return preview (same as GET)
if ($dry_run) {
$count_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($count_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$match_count = intval($row['match_count'] ?? 0);
$total_size_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Preview query failed: ' . $e->getMessage()]);
exit;
}
// Kind breakdown
$breakdown = [];
if ($match_count > 0) {
$breakdown_sql = "SELECT e.kind, COUNT(*) AS count,\n"
. " COALESCE(SUM(pg_column_size(e.event_json)), 0) AS size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where\n"
. "GROUP BY e.kind\n"
. "ORDER BY count DESC\n"
. "LIMIT 50";
try {
$stmt = $pdo->prepare($breakdown_sql);
$stmt->execute($params);
foreach ($stmt->fetchAll() as $r) {
$breakdown[] = [
'kind' => intval($r['kind']),
'count' => intval($r['count']),
'size_bytes' => intval($r['size_bytes']),
];
}
} catch (PDOException $e) {}
}
json_response([
'match_count' => $match_count,
'total_size_bytes' => $total_size_bytes,
'total_size_human' => format_bytes($total_size_bytes),
'avg_size_per_event' => $match_count > 0 ? intval($total_size_bytes / $match_count) : 0,
'kinds_breakdown' => $breakdown,
'dry_run' => true,
]);
exit;
}
// ── Actual DELETE ──────────────────────────────────────────────────
$max_events = intval($body['max_events'] ?? 0);
$limit_clause = $max_events > 0 ? 'LIMIT :max_events' : '';
// First, get the count and size of what will be deleted
$preview_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($preview_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$expected_count = intval($row['match_count'] ?? 0);
$expected_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Pre-delete count failed: ' . $e->getMessage()]);
exit;
}
// Build the DELETE using id IN (subquery) for safe LIMIT + ORDER BY
$delete_sql = "DELETE FROM events e\n"
. "WHERE e.id IN (\n"
. " SELECT e2.id FROM events e2\n"
. " WHERE 1=1 $where\n"
. " ORDER BY e2.created_at ASC\n"
. " $limit_clause\n"
. ")";
$start_time = microtime(true);
try {
$stmt = $pdo->prepare($delete_sql);
if ($max_events > 0) {
$params[':max_events'] = $max_events;
}
$stmt->execute($params);
$deleted_count = $stmt->rowCount();
} catch (PDOException $e) {
json_response(['error' => 'Delete query failed: ' . $e->getMessage()]);
exit;
}
$duration_ms = round((microtime(true) - $start_time) * 1000);
// If we deleted fewer than expected, the actual freed bytes are proportional
$freed_bytes = $expected_count > 0
? intval($expected_bytes * ($deleted_count / $expected_count))
: 0;
json_response([
'deleted_count' => $deleted_count,
'freed_bytes' => $freed_bytes,
'freed_human' => format_bytes($freed_bytes),
'duration_ms' => $duration_ms,
]);
exit;
}
// Unsupported method
json_response(['error' => 'Method not allowed']);
+317
View File
@@ -0,0 +1,317 @@
<?php
/**
* admin2/api/cleanup_queries.php — Saved Cleanup Query CRUD.
*
* GET — List all saved queries.
* POST — Create/update/delete/execute saved queries.
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$method = $_SERVER['REQUEST_METHOD'];
/** Format bytes as human-readable. */
function fmt_bytes(int $bytes): string {
if ($bytes < 1024) return $bytes . ' B';
if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB';
if ($bytes < 1073741824) return round($bytes / 1048576, 1) . ' MB';
return round($bytes / 1073741824, 2) . ' GB';
}
// ── GET: List all saved queries ────────────────────────────────────────
if ($method === 'GET') {
try {
$rows = $pdo->query(
"SELECT id, name, follows_filter, kinds, max_age_days, max_events,\n"
. " last_preview_count, last_preview_size_bytes,\n"
. " last_executed_at, created_at, updated_at\n"
. " FROM cleanup_saved_queries\n"
. " ORDER BY updated_at DESC"
)->fetchAll();
} catch (PDOException $e) {
// Table may not exist yet
json_response(['queries' => []]);
exit;
}
$queries = [];
foreach ($rows as $r) {
$kinds = $r['kinds'];
if (is_string($kinds)) {
// PostgreSQL returns {1,7} format — parse it
$kinds = trim($kinds, '{}');
$kinds = $kinds !== '' ? array_map('intval', explode(',', $kinds)) : [];
} elseif (is_resource($kinds)) {
$kinds = [];
}
$queries[] = [
'id' => intval($r['id']),
'name' => $r['name'],
'follows_filter' => $r['follows_filter'],
'kinds' => $kinds,
'max_age_days' => intval($r['max_age_days']),
'max_events' => intval($r['max_events']),
'last_preview_count' => intval($r['last_preview_count']),
'last_preview_size_human' => fmt_bytes(intval($r['last_preview_size_bytes'])),
'last_executed_at' => intval($r['last_executed_at']) > 0
? date('Y-m-d H:i:s', intval($r['last_executed_at']))
: null,
'created_at' => date('Y-m-d H:i:s', intval($r['created_at'])),
'updated_at' => date('Y-m-d H:i:s', intval($r['updated_at'])),
];
}
json_response(['queries' => $queries]);
exit;
}
// ── POST: Actions ──────────────────────────────────────────────────────
if ($method === 'POST') {
$body = json_decode(file_get_contents('php://input'), true);
if (!$body || empty($body['action'])) {
json_response(['error' => 'Missing action']);
exit;
}
$action = $body['action'];
// ── Save (create or update) ────────────────────────────────────────
if ($action === 'save') {
$id = $body['id'] ?? null;
$name = trim($body['name'] ?? '');
if ($name === '') {
json_response(['error' => 'Name is required']);
exit;
}
$follows_filter = $body['follows_filter'] ?? 'all';
if (!in_array($follows_filter, ['all', 'follows', 'non_follows'])) {
$follows_filter = 'all';
}
$kinds = $body['kinds'] ?? [];
if (is_array($kinds)) {
$kinds = array_map('intval', $kinds);
$kinds = array_filter($kinds, fn($v) => $v > 0);
$kinds = array_values($kinds);
} else {
$kinds = [];
}
// PostgreSQL array literal
$kinds_pg = '{' . implode(',', $kinds) . '}';
$max_age_days = intval($body['max_age_days'] ?? 0);
$max_events = intval($body['max_events'] ?? 0);
try {
if ($id) {
// Update existing
$stmt = $pdo->prepare(
"UPDATE cleanup_saved_queries\n"
. " SET name = :name,\n"
. " follows_filter = :follows_filter,\n"
. " kinds = :kinds::integer[],\n"
. " max_age_days = :max_age_days,\n"
. " max_events = :max_events,\n"
. " updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT\n"
. " WHERE id = :id"
);
$stmt->execute([
':id' => $id,
':name' => $name,
':follows_filter' => $follows_filter,
':kinds' => $kinds_pg,
':max_age_days' => $max_age_days,
':max_events' => $max_events,
]);
} else {
// Insert new
$stmt = $pdo->prepare(
"INSERT INTO cleanup_saved_queries\n"
. " (name, follows_filter, kinds, max_age_days, max_events)\n"
. "VALUES (:name, :follows_filter, :kinds::integer[],\n"
. " :max_age_days, :max_events)\n"
. "ON CONFLICT (name) DO UPDATE SET\n"
. " follows_filter = EXCLUDED.follows_filter,\n"
. " kinds = EXCLUDED.kinds,\n"
. " max_age_days = EXCLUDED.max_age_days,\n"
. " max_events = EXCLUDED.max_events,\n"
. " updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT"
);
$stmt->execute([
':name' => $name,
':follows_filter' => $follows_filter,
':kinds' => $kinds_pg,
':max_age_days' => $max_age_days,
':max_events' => $max_events,
]);
$id = $pdo->lastInsertId();
}
} catch (PDOException $e) {
json_response(['error' => 'Save failed: ' . $e->getMessage()]);
exit;
}
json_response(['ok' => true, 'id' => intval($id)]);
exit;
}
// ── Delete ─────────────────────────────────────────────────────────
if ($action === 'delete') {
$id = intval($body['id'] ?? 0);
if ($id <= 0) {
json_response(['error' => 'Invalid id']);
exit;
}
try {
$stmt = $pdo->prepare("DELETE FROM cleanup_saved_queries WHERE id = :id");
$stmt->execute([':id' => $id]);
} catch (PDOException $e) {
json_response(['error' => 'Delete failed: ' . $e->getMessage()]);
exit;
}
json_response(['ok' => true]);
exit;
}
// ── Execute (run a saved query as non-dry-run DELETE) ──────────────
if ($action === 'execute') {
$id = intval($body['id'] ?? 0);
if ($id <= 0) {
json_response(['error' => 'Invalid id']);
exit;
}
// Load the saved query
try {
$stmt = $pdo->prepare(
"SELECT id, name, follows_filter, kinds, max_age_days, max_events\n"
. " FROM cleanup_saved_queries\n"
. " WHERE id = :id"
);
$stmt->execute([':id' => $id]);
$query = $stmt->fetch();
} catch (PDOException $e) {
json_response(['error' => 'Query load failed: ' . $e->getMessage()]);
exit;
}
if (!$query) {
json_response(['error' => 'Saved query not found']);
exit;
}
// Parse kinds from PG array
$kinds_raw = $query['kinds'];
if (is_string($kinds_raw)) {
$kinds_raw = trim($kinds_raw, '{}');
$kinds = $kinds_raw !== '' ? array_map('intval', explode(',', $kinds_raw)) : [];
} else {
$kinds = [];
}
// Build filters (same logic as cleanup.php)
$conds = [];
$params = [];
if (!empty($kinds)) {
$kind_placeholders = [];
foreach ($kinds as $i => $k) {
$key = ':kind_' . $i;
$kind_placeholders[] = $key;
$params[$key] = $k;
}
$conds[] = 'e.kind IN (' . implode(',', $kind_placeholders) . ')';
}
$max_age_days = intval($query['max_age_days']);
if ($max_age_days > 0) {
$conds[] = 'e.created_at < EXTRACT(EPOCH FROM NOW())::BIGINT - :max_age_seconds';
$params[':max_age_seconds'] = $max_age_days * 86400;
}
$follows_filter = $query['follows_filter'];
if ($follows_filter === 'follows') {
$conds[] = 'e.pubkey IN (SELECT pubkey FROM caching_followed_pubkeys)';
} elseif ($follows_filter === 'non_follows') {
$conds[] = 'e.pubkey NOT IN (SELECT pubkey FROM caching_followed_pubkeys)';
}
$where = !empty($conds) ? 'AND ' . implode("\n AND ", $conds) : '';
$max_events = intval($query['max_events']);
$limit_clause = $max_events > 0 ? 'LIMIT :max_events' : '';
// Pre-delete count
$preview_sql = "SELECT COUNT(*) AS match_count,\n"
. " COALESCE(SUM(pg_column_size(event_json)), 0) AS total_size_bytes\n"
. "FROM events e\n"
. "WHERE 1=1 $where";
try {
$stmt = $pdo->prepare($preview_sql);
$stmt->execute($params);
$row = $stmt->fetch();
$expected_count = intval($row['match_count'] ?? 0);
$expected_bytes = intval($row['total_size_bytes'] ?? 0);
} catch (PDOException $e) {
json_response(['error' => 'Pre-delete count failed: ' . $e->getMessage()]);
exit;
}
// Execute DELETE
$delete_sql = "DELETE FROM events e\n"
. "WHERE e.id IN (\n"
. " SELECT e2.id FROM events e2\n"
. " WHERE 1=1 $where\n"
. " ORDER BY e2.created_at ASC\n"
. " $limit_clause\n"
. ")";
$start_time = microtime(true);
try {
$stmt = $pdo->prepare($delete_sql);
if ($max_events > 0) {
$params[':max_events'] = $max_events;
}
$stmt->execute($params);
$deleted_count = $stmt->rowCount();
} catch (PDOException $e) {
json_response(['error' => 'Delete failed: ' . $e->getMessage()]);
exit;
}
$duration_ms = round((microtime(true) - $start_time) * 1000);
$freed_bytes = $expected_count > 0
? intval($expected_bytes * ($deleted_count / $expected_count))
: 0;
// Update last_preview_count, last_preview_size_bytes, last_executed_at
try {
$pdo->prepare(
"UPDATE cleanup_saved_queries\n"
. " SET last_preview_count = :count,\n"
. " last_preview_size_bytes = :size,\n"
. " last_executed_at = EXTRACT(EPOCH FROM NOW())::BIGINT\n"
. " WHERE id = :id"
)->execute([
':id' => $id,
':count' => $expected_count,
':size' => $expected_bytes,
]);
} catch (PDOException $e) {}
json_response([
'deleted_count' => $deleted_count,
'freed_bytes' => $freed_bytes,
'freed_human' => fmt_bytes($freed_bytes),
'duration_ms' => $duration_ms,
]);
exit;
}
json_response(['error' => 'Unknown action: ' . $action]);
exit;
}
json_response(['error' => 'Method not allowed']);
+20
View File
@@ -0,0 +1,20 @@
<?php
/** admin2/api/config.php — Config table read/edit (all keys). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$key = $input['key'] ?? '';
$value = $input['value'] ?? '';
if (!$key) json_response(['error' => 'Missing key']);
try {
$pdo->prepare("UPDATE config SET value = ? WHERE key = ?")->execute([$value, $key]);
json_response(['message' => "Updated $key"]);
} catch (PDOException $e) {
json_response(['error' => $e->getMessage()]);
}
}
$rows = $pdo->query("SELECT key, value FROM config ORDER BY key")->fetchAll();
json_response(['config' => $rows]);
+16
View File
@@ -0,0 +1,16 @@
<?php
/** admin2/api/dm.php — Direct messages (kind 4/14/15). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$limit = min(200, intval($_GET['limit'] ?? 50));
$rows = $pdo->prepare("SELECT id, pubkey, kind, created_at, content FROM events WHERE kind IN (4, 14, 15) ORDER BY created_at DESC LIMIT $limit");
$rows->execute();
$messages = $rows->fetchAll();
foreach ($messages as &$m) {
$m['created_at'] = date('Y-m-d H:i:s', intval($m['created_at']));
$m['content'] = substr($m['content'] ?? '', 0, 300);
}
json_response(['messages' => $messages]);
+36
View File
@@ -0,0 +1,36 @@
<?php
/** admin2/api/events.php — Recent relay events (paginated). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$limit = min(200, intval($_GET['limit'] ?? 50));
$offset = intval($_GET['offset'] ?? 0);
$kind = $_GET['kind'] ?? '';
$where = [];
$params = [];
if ($kind !== '') { $where[] = 'kind = ?'; $params[] = intval($kind); }
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
// Order by first_seen DESC to show most recently received events first,
// avoiding corrupted created_at timestamps (e.g. far-future values) that
// would otherwise dominate the ordering.
$rows = $pdo->prepare("SELECT e.id, e.pubkey, e.kind, e.created_at, e.first_seen, e.content,
p.name, p.display_name
FROM events e
LEFT JOIN profiles p ON p.pubkey = e.pubkey
$where_sql
ORDER BY e.first_seen DESC LIMIT $limit OFFSET $offset");
$rows->execute($params);
$events = $rows->fetchAll();
foreach ($events as &$e) {
$e['created_at'] = date('Y-m-d H:i:s', intval($e['created_at']));
$e['first_seen'] = date('Y-m-d H:i:s', intval($e['first_seen']));
$e['content'] = substr($e['content'] ?? '', 0, 200);
// Resolve display name from profiles cache; fall back to truncated pubkey.
$best = profile_display_name($e);
$e['display_name'] = $best !== '' ? $best : substr($e['pubkey'] ?? '', 0, 16) . '…';
}
json_response(['events' => $events]);
+35
View File
@@ -0,0 +1,35 @@
<?php
/** admin2/api/ipbans.php — IP ban management. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
try {
if ($action === 'add') {
$until = time() + intval($input['duration'] ?? 86400);
$pdo->prepare("INSERT INTO ip_bans (ip, banned_until, ban_count) VALUES (?, ?, 1) ON CONFLICT (ip) DO UPDATE SET banned_until = ?, ban_count = ip_bans.ban_count + 1")->execute([$input['ip'], $until, $until]);
json_response(['message' => 'IP banned']);
} elseif ($action === 'remove') {
$pdo->prepare("DELETE FROM ip_bans WHERE ip = ?")->execute([$input['ip']]);
json_response(['message' => 'Ban removed']);
}
} catch (PDOException $e) { json_response(['error' => $e->getMessage()]); }
}
$now = time();
$total = 0; $active = 0; $issued = 0;
$bans = [];
try {
$total = intval($pdo->query("SELECT count(*) FROM ip_bans")->fetchColumn());
$active = intval($pdo->query("SELECT count(*) FROM ip_bans WHERE banned_until > $now")->fetchColumn());
$issued = intval($pdo->query("SELECT COALESCE(sum(ban_count),0) FROM ip_bans")->fetchColumn());
$bans = $pdo->query("SELECT ip, banned_until, ban_count, failures, authed_successfully, connection_attempts FROM ip_bans ORDER BY banned_until DESC LIMIT 100")->fetchAll();
foreach ($bans as &$b) {
$b['status'] = intval($b['banned_until']) > $now ? 'banned' : 'expired';
$b['banned_until'] = date('Y-m-d H:i:s', intval($b['banned_until']));
}
} catch (PDOException $e) {}
json_response(['total' => $total, 'active' => $active, 'issued' => $issued, 'bans' => $bans]);
+238
View File
@@ -0,0 +1,238 @@
<?php
/**
* admin/api/kind_1_report.php — Markdown relay status report.
*
* Returns a markdown-formatted relay status report suitable for use as
* a kind 1 event content. Includes:
* - Relay name header
* - 1H ASCII chart (event rate)
* - Database overview
* - Top 10 event kinds
* - Time-based statistics
* - Top pubkeys
*
* Accessible at: /relay/admin/api/kind_1_report.php
*/
require_once __DIR__ . '/../lib/helpers.php';
require_once __DIR__ . '/../lib/ascii_chart.php';
header('Content-Type: text/plain; charset=utf-8');
$pdo = db();
// --- Relay info ---
$relay_name = 'C-Relay-PG';
$relay_version = '';
try {
$relay_name = $pdo->query("SELECT value FROM config WHERE key = 'relay_name'")->fetchColumn() ?: 'C-Relay-PG';
$relay_version = $pdo->query("SELECT value FROM config WHERE key = 'relay_version'")->fetchColumn() ?: '';
} catch (PDOException $e) {}
$display_name = $relay_version ? "$relay_name v$relay_version" : $relay_name;
// --- Total events ---
$total_events = 0;
try {
$total_events = intval($pdo->query("SELECT COUNT(*) FROM events")->fetchColumn());
} catch (PDOException $e) {}
// --- Database size ---
$db_size = '-';
try {
$bytes = intval($pdo->query("SELECT pg_database_size(current_database())")->fetchColumn());
if ($bytes < 1073741824) {
$db_size = round($bytes / 1048576, 1) . ' MB';
} else {
$db_size = round($bytes / 1073741824, 2) . ' GB';
}
} catch (PDOException $e) {}
// --- Process ID ---
$process_id = '-';
try {
$pid = $pdo->query("SELECT pg_backend_pid()")->fetchColumn();
$process_id = strval($pid);
} catch (PDOException $e) {}
// --- WebSocket connections (estimate from pg_class) ---
$ws_connections = 0;
try {
$est = intval($pdo->query("SELECT reltuples FROM pg_class WHERE relname = 'subscriptions'")->fetchColumn());
$ws_connections = max(0, intval($est * 0.2));
} catch (PDOException $e) {}
// --- Active subscriptions (same estimate) ---
$active_subscriptions = $ws_connections;
// --- Memory usage ---
$memory_usage = '-';
$mem_total = 0;
$mem_avail = 0;
$meminfo = @file_get_contents('/proc/meminfo');
if ($meminfo !== false) {
// /proc/meminfo uses "Key: Value kB" format (colon, not equals sign)
foreach (explode("\n", $meminfo) as $line) {
if (preg_match('/^MemTotal:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_total = intval($m[1]) * 1024;
} elseif (preg_match('/^MemAvailable:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_avail = intval($m[1]) * 1024;
}
}
}
if ($mem_total > 0) {
$used = $mem_total - $mem_avail;
if ($used < 1073741824) {
$memory_usage = round($used / 1048576, 1) . ' MB / ' . round($mem_total / 1073741824, 2) . ' GB';
} else {
$memory_usage = round($used / 1073741824, 2) . ' GB / ' . round($mem_total / 1073741824, 2) . ' GB';
}
}
// --- CPU ---
$cpu_usage = '-';
$cpu_core = '-';
if (is_readable('/proc/cpuinfo')) {
$cores = intval(@shell_exec('nproc 2>/dev/null') ?: 1);
$cpu_core = strval($cores) . ' cores';
}
$load = @file_get_contents('/proc/loadavg');
if ($load !== false) {
$cpu_usage = trim(explode(' ', $load)[0] ?? '-');
}
// --- Oldest / newest event ---
$oldest_event = '-';
$newest_event = '-';
try {
$row = $pdo->query("SELECT MIN(created_at) AS oldest, MAX(created_at) AS newest FROM events")->fetch();
if ($row && $row['oldest']) $oldest_event = date('Y-m-d H:i:s', intval($row['oldest']));
if ($row && $row['newest']) $newest_event = date('Y-m-d H:i:s', intval($row['newest']));
} catch (PDOException $e) {}
// --- Time-based stats ---
$now = time();
$events_24h = 0; $events_7d = 0; $events_30d = 0;
try {
$events_24h = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 86400")->fetchColumn());
$events_7d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 604800")->fetchColumn());
$events_30d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 2592000")->fetchColumn());
} catch (PDOException $e) {}
// --- Kind distribution (top 10) ---
$kinds = [];
try {
$rows = $pdo->query("SELECT kind, COUNT(*) AS cnt FROM events GROUP BY kind ORDER BY cnt DESC LIMIT 10")->fetchAll();
foreach ($rows as $r) {
$kinds[] = ['kind' => intval($r['kind']), 'count' => intval($r['cnt']), 'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0];
}
} catch (PDOException $e) {}
// --- Top pubkeys (top 20) ---
$top_pubkeys = [];
try {
$rows = $pdo->query("
SELECT pubkey, COUNT(*) AS cnt
FROM events
GROUP BY pubkey
ORDER BY cnt DESC LIMIT 20
")->fetchAll();
$pubkeys = array_column($rows, 'pubkey');
$pmap = profile_map($pubkeys);
foreach ($rows as $r) {
$pk = $r['pubkey'];
$prof = $pmap[$pk] ?? null;
$top_pubkeys[] = [
'pubkey' => $pk,
'name' => $prof ? $prof['best_name'] : '',
'count' => intval($r['cnt']),
'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0,
];
}
} catch (PDOException $e) {}
// --- 1H ASCII chart (30 bins of 2 minutes each) ---
$chart = '';
try {
$span = 3600;
$bin_size = 120; // 2 minutes
$num_bins = 30;
$epoch = $now - $span;
$base_bin = (int)floor($epoch / $bin_size);
$sql = "SELECT FLOOR(created_at / {$bin_size})::BIGINT - {$base_bin} AS bin, COUNT(*) AS cnt
FROM events
WHERE created_at >= {$epoch}
GROUP BY bin
ORDER BY bin";
$rows = $pdo->query($sql)->fetchAll();
$bins = build_bin_array($rows, $num_bins);
$bins = array_reverse($bins);
$chart = render_ascii_chart($bins, [
'title' => 'Event Rate (Last Hour)',
'max_height' => 11,
'bin_duration' => $bin_size,
'label_interval' => 5, // label every 5 bins (10 min)
]);
} catch (PDOException $e) {
$chart = "Chart unavailable\n";
}
// ================================
// BUILD MARKDOWN OUTPUT
// ================================
$output = '';
// Chart
$output .= "## laantungir.net/relay\n\n";
$output .= "```\n$chart```\n";
// Database Overview
$output .= "## Database Overview\n\n";
$output .= "| Metric | Value |\n";
$output .= "|--------|-------|\n";
$output .= "| Database Size | $db_size |\n";
$output .= "| Total Events | " . number_format($total_events) . " |\n";
$output .= "| Process ID | $process_id |\n";
$output .= "| WebSocket Connections | " . number_format($ws_connections) . " |\n";
$output .= "| Active Subscriptions | " . number_format($active_subscriptions) . " |\n";
$output .= "| Memory Usage | $memory_usage |\n";
$output .= "| CPU Usage | $cpu_usage |\n";
$output .= "| CPU Core | $cpu_core |\n";
$output .= "| Oldest Event | $oldest_event |\n";
$output .= "| Newest Event | $newest_event |\n\n";
// Event Kinds (Top 10)
$output .= "## Event Kinds (Top 10)\n\n";
$output .= "| Kind | Count | % |\n";
$output .= "|------|-------|---|\n";
foreach ($kinds as $k) {
$output .= "| {$k['kind']} | " . number_format($k['count']) . " | {$k['pct']}% |\n";
}
$output .= "\n";
// Time-Based Statistics
$output .= "## Time-Based Statistics\n\n";
$output .= "| Period | Events |\n";
$output .= "|--------|-------|\n";
$output .= "| 24 Hours | " . number_format($events_24h) . " |\n";
$output .= "| 7 Days | " . number_format($events_7d) . " |\n";
$output .= "| 30 Days | " . number_format($events_30d) . " |\n\n";
// Top Pubkeys
$output .= "## Top Pubkeys\n\n";
$output .= "| Name | Events | % |\n";
$output .= "|------|-------|---|\n";
foreach ($top_pubkeys as $p) {
if ($p['name']) {
$name = $p['name'];
} else {
$name = substr($p['pubkey'], 0, 4) . '..' . substr($p['pubkey'], -4);
}
$output .= "| $name | " . number_format($p['count']) . " | {$p['pct']}% |\n";
}
$output .= "\n";
// Footer
$output .= "---\n";
$output .= "_Report generated: " . date('Y-m-d H:i:s T') . "_\n";
echo $output;
+38
View File
@@ -0,0 +1,38 @@
<?php
/**
* admin/api/profile.php — Single-profile lookup from the profiles cache.
* Returns cached kind-0 metadata for a given pubkey.
*
* Usage: profile.php?pubkey=<64-char-hex>
* Returns: { "pubkey": "...", "name": "...", "display_name": "...",
* "best_name": "...", "picture": "...", "nip05": "...",
* "about": "...", "website": "...", "lud16": "..." }
* or { "error": "not found" } with HTTP 404 if no profile is cached.
*/
require_once __DIR__ . '/../lib/helpers.php';
$pubkey = trim($_GET['pubkey'] ?? '');
if (!preg_match('/^[0-9a-fA-F]{64}$/', $pubkey)) {
http_response_code(400);
json_response(['error' => 'Invalid pubkey']);
}
$pdo = db();
try {
$stmt = $pdo->prepare(
"SELECT pubkey, name, display_name, about, picture, banner,
nip05, website, lud16, lud06
FROM profiles WHERE pubkey = ?"
);
$stmt->execute([$pubkey]);
$row = $stmt->fetch();
if (!$row) {
http_response_code(404);
json_response(['error' => 'not found']);
}
$row['best_name'] = profile_display_name($row);
json_response($row);
} catch (PDOException $e) {
http_response_code(500);
json_response(['error' => 'Database error']);
}
+24
View File
@@ -0,0 +1,24 @@
<?php
/** admin2/api/query.php — Direct SQL query (SELECT only, admin only). */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
json_response(['error' => 'POST only']);
}
$input = json_decode(file_get_contents('php://input'), true);
$sql = trim($input['sql'] ?? '');
if (!$sql) json_response(['error' => 'No query provided']);
if (!preg_match('/^\s*SELECT/i', $sql)) json_response(['error' => 'Only SELECT queries are allowed']);
$start = microtime(true);
try {
$stmt = $pdo->query($sql);
$rows = $stmt->fetchAll();
$time_ms = round((microtime(true) - $start) * 1000, 1);
json_response(['rows' => $rows, 'row_count' => count($rows), 'time_ms' => $time_ms]);
} catch (PDOException $e) {
json_response(['error' => $e->getMessage()]);
}
+188
View File
@@ -0,0 +1,188 @@
<?php
/**
* admin2/api/stats.php — Statistics page data.
* Returns DB size, event counts, kind distribution, top pubkeys,
* time-based stats, and event-rate delta (for the chart).
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
// Database size
$db_size = '-';
try {
$bytes = $pdo->query("SELECT pg_database_size(current_database())")->fetchColumn();
$db_size = format_bytes(intval($bytes));
} catch (PDOException $e) {}
// Total events
$total_events = intval($pdo->query("SELECT COUNT(*) FROM events")->fetchColumn());
// Event rate: events with first_seen in last 10 seconds
$events_delta = 0;
try {
$events_delta = intval($pdo->query("SELECT COUNT(*) FROM events WHERE first_seen >= EXTRACT(EPOCH FROM NOW())::BIGINT - 10")->fetchColumn());
} catch (PDOException $e) {}
// Process info
$process_id = '-';
try {
$pid = $pdo->query("SELECT pg_backend_pid()")->fetchColumn();
$process_id = strval($pid);
} catch (PDOException $e) {}
// WebSocket connections: tracked in-memory by the relay (g_connection_count).
// The subscriptions table is a 32M-row historical log, not a live state table.
// Use pg_class for an instant estimate (updated by autovacuum).
$ws_connections = 0;
try {
$est = intval($pdo->query("SELECT reltuples FROM pg_class WHERE relname = 'subscriptions'")->fetchColumn());
// ~60% are 'created', ~40% are 'closed' — active ≈ created - closed
$ws_connections = max(0, intval($est * 0.2));
} catch (PDOException $e) {}
// Active subscriptions: same estimate approach.
$active_subscriptions = $ws_connections;
// Memory/CPU (from /proc on Linux)
$memory_usage = '-';
$cpu_usage = '-';
$cpu_core = '-';
$mem_total = 0;
$mem_avail = 0;
$meminfo = @file_get_contents('/proc/meminfo');
if ($meminfo !== false) {
// /proc/meminfo uses "Key: Value kB" format (colon, not equals sign)
foreach (explode("\n", $meminfo) as $line) {
if (preg_match('/^MemTotal:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_total = intval($m[1]) * 1024;
} elseif (preg_match('/^MemAvailable:\s+(\d+)\s+kB/i', $line, $m)) {
$mem_avail = intval($m[1]) * 1024;
}
}
}
if ($mem_total > 0 && $mem_avail > 0) {
$memory_usage = format_bytes($mem_total - $mem_avail) . ' / ' . format_bytes($mem_total);
} elseif ($mem_total > 0) {
$memory_usage = format_bytes($mem_total);
}
if (is_readable('/proc/cpuinfo')) {
$cores = intval(@shell_exec('nproc 2>/dev/null') ?: 1);
$cpu_core = strval($cores) . ' cores';
}
$cpu_usage = @file_get_contents('/proc/loadavg');
if ($cpu_usage !== false) $cpu_usage = trim(explode(' ', $cpu_usage)[0] ?? '-');
// Oldest / newest event (single query)
$oldest_event = '-';
$newest_event = '-';
try {
$row = $pdo->query("SELECT MIN(created_at) AS oldest, MAX(created_at) AS newest FROM events")->fetch();
if ($row && $row['oldest']) {
$oldest_event = date('Y-m-d H:i:s', intval($row['oldest']));
}
if ($row && $row['newest']) {
$newest_event = date('Y-m-d H:i:s', intval($row['newest']));
}
} catch (PDOException $e) {}
// Time-based stats
$now = time();
$events_24h = 0; $events_7d = 0; $events_30d = 0;
try {
$events_24h = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 86400")->fetchColumn());
$events_7d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 604800")->fetchColumn());
$events_30d = intval($pdo->query("SELECT COUNT(*) FROM events WHERE created_at >= $now - 2592000")->fetchColumn());
} catch (PDOException $e) {}
// Kind distribution — cached to file, refreshed every 5 minutes
$kinds = [];
$cache_dir = __DIR__ . '/../cache';
if (!is_dir($cache_dir)) @mkdir($cache_dir, 0755, true);
$kinds_cache = $cache_dir . '/stats_kinds.json';
$kinds_ttl = 300;
if (is_readable($kinds_cache) && (time() - filemtime($kinds_cache)) < $kinds_ttl) {
$kinds = json_decode(file_get_contents($kinds_cache), true) ?: [];
} else {
try {
$rows = $pdo->query("SELECT kind, COUNT(*) AS cnt FROM events GROUP BY kind ORDER BY cnt DESC LIMIT 20")->fetchAll();
foreach ($rows as $r) {
$kinds[] = ['kind' => intval($r['kind']), 'count' => intval($r['cnt']), 'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0];
}
@file_put_contents($kinds_cache, json_encode($kinds));
} catch (PDOException $e) {}
}
// Top pubkeys — cached to file, refreshed every 5 minutes
$top_pubkeys = [];
$pubkeys_cache = $cache_dir . '/stats_pubkeys.json';
if (is_readable($pubkeys_cache) && (time() - filemtime($pubkeys_cache)) < $kinds_ttl) {
$top_pubkeys = json_decode(file_get_contents($pubkeys_cache), true) ?: [];
} else {
try {
$rows = $pdo->query("
SELECT pubkey, COUNT(*) AS cnt
FROM events
GROUP BY pubkey
ORDER BY cnt DESC LIMIT 20
")->fetchAll();
$pubkeys = array_column($rows, 'pubkey');
$pmap = profile_map($pubkeys);
foreach ($rows as $r) {
$pk = $r['pubkey'];
$prof = $pmap[$pk] ?? null;
$top_pubkeys[] = [
'pubkey' => $pk,
'name' => $prof ? $prof['best_name'] : '',
'count' => intval($r['cnt']),
'pct' => $total_events > 0 ? round(intval($r['cnt']) / $total_events * 100, 1) : 0,
];
}
@file_put_contents($pubkeys_cache, json_encode($top_pubkeys));
} catch (PDOException $e) {}
}
// Name-field usage stats (from profiles cache)
$name_field_usage = ['both' => 0, 'name_only' => 0, 'display_only' => 0, 'neither' => 0, 'both_differ' => 0, 'total' => 0];
try {
$row = $pdo->query("
SELECT count(*) FILTER (WHERE name <> '' AND display_name <> '') AS both,
count(*) FILTER (WHERE name <> '' AND display_name = '') AS name_only,
count(*) FILTER (WHERE name = '' AND display_name <> '') AS display_only,
count(*) FILTER (WHERE name = '' AND display_name = '') AS neither,
count(*) FILTER (WHERE name <> '' AND display_name <> '' AND name <> display_name) AS both_differ,
count(*) AS total
FROM profiles
")->fetch();
if ($row) {
$name_field_usage = array_map('intval', $row);
}
} catch (PDOException $e) {}
json_response([
'db_size' => $db_size,
'total_events' => $total_events,
'events_delta' => $events_delta,
'process_id' => $process_id,
'ws_connections' => $ws_connections,
'active_subscriptions' => $active_subscriptions,
'memory_usage' => $memory_usage,
'cpu_usage' => $cpu_usage,
'cpu_core' => $cpu_core,
'oldest_event' => $oldest_event,
'newest_event' => $newest_event,
'events_24h' => $events_24h,
'events_7d' => $events_7d,
'events_30d' => $events_30d,
'kinds' => $kinds,
'top_pubkeys' => $top_pubkeys,
'name_field_usage' => $name_field_usage,
]);
/** Format bytes as human-readable. */
function format_bytes(int $bytes): string {
if ($bytes < 1024) return $bytes . ' B';
if ($bytes < 1048576) return round($bytes / 1024, 1) . ' KB';
if ($bytes < 1073741824) return round($bytes / 1048576, 1) . ' MB';
return round($bytes / 1073741824, 2) . ' GB';
}
+11
View File
@@ -0,0 +1,11 @@
<?php
/** admin2/api/subscriptions.php — Active subscription details. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
$subs = [];
try {
$subs = $pdo->query("SELECT id, sub_id, pubkey, filters, created_at, events_sent FROM subscriptions ORDER BY created_at DESC LIMIT 100")->fetchAll();
} catch (PDOException $e) {}
json_response(['subscriptions' => $subs]);
+1328
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
New Events — Last 24 Hours
21 |
19 |
17 |
15 |
13 |
11 | X
9 | X
7 | X
5 | X
3 | X X X
1 | X X X X X XX X X XX X X X X X X X X XX X X X XX X X
+--------------------------------------------------------------------------------
0s 1h 3h 4h 6h 7h 9h 10h 12h 13h 15h 16h 18h 19h 21h 22h
+15
View File
@@ -0,0 +1,15 @@
New Events — Last 30 Days
81 | X
73 | XX X
65 | XX X
57 | XX X X X
49 | X X XX XX X X X
41 | X X X XX X X X XXX XXX XXX X X X X
33 | X X X XXXX XX X X X XX XXX XXXXXXXX XX X X X X X XX
25 |X XX XX XX XXXX XX XXX X XX XXXXXXXXXXXXXXXXXXXX XX XXX X XX XX XXXXX
17 |X XXXXX XX XXXXXXXXXXXXXXX X XX XXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXX XX XXXXXXXX
9 |XXX XXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXX
1 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+--------------------------------------------------------------------------------
0s 1d 3d 5d 7d 9d 11d 13d 15d 16d 18d 20d 22d 24d 26d 28d
+15
View File
@@ -0,0 +1,15 @@
New Events — Last Year
531 | X XX
478 | X X X XXX X
425 | X X XX X XX XXX X
372 | XXX XX X XX XXX X
319 |XXXXXXXXXXX X X X XXXXXXX X
266 |XXXXXXXXXXXX X XX XXX XXXXXXXXX X
213 |XXXXXXXXXXXXXX XXX XXXXXXXXXXXXX XX X
160 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X X XXXX X
107 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XX XX XX X X X XX XXX X XXXXXXX X
54 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXX
1 |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+--------------------------------------------------------------------------------
0s 22d 45d 68d 91d 114d 136d 159d 182d 205d 228d 250d 273d 296d 319d 342d
+1
View File
@@ -0,0 +1 @@
[]
+1
View File
@@ -0,0 +1 @@
[]
+523
View File
@@ -0,0 +1,523 @@
<?php
/**
* admin2 — Full PHP admin for C-Relay-PG.
* Recreates the original api/ admin interface with a PHP/PDO backend
* instead of Nostr WebSocket admin commands.
*/
require_once __DIR__ . '/lib/helpers.php';
$pdo = db();
// Fetch relay info for the header (from config table)
$relay_name = '';
$relay_desc = '';
$relay_pubkey = '';
$relay_version = '';
try {
$relay_name = $pdo->query("SELECT value FROM config WHERE key = 'relay_name'")->fetchColumn() ?: 'C-Relay-PG';
$relay_desc = $pdo->query("SELECT value FROM config WHERE key = 'relay_description'")->fetchColumn() ?: '';
$relay_pubkey = $pdo->query("SELECT value FROM config WHERE key = 'relay_pubkey'")->fetchColumn() ?: '';
$relay_version = $pdo->query("SELECT value FROM config WHERE key = 'relay_version'")->fetchColumn() ?: '';
} catch (PDOException $e) {}
// Convert relay pubkey (hex) to npub and format into 3 lines of 3 groups of 7 chars
$relay_npub = $relay_pubkey;
if ($relay_pubkey && preg_match('/^[0-9a-fA-F]{64}$/', $relay_pubkey)) {
$relay_npub = hex_to_npub($relay_pubkey);
}
// Format npub (63 chars) into 3 lines of "xxxxxxx xxxxxxx xxxxxxx"
$formatted_npub = $relay_npub;
if (strlen($relay_npub) === 63) {
$line1 = substr($relay_npub, 0, 7) . ' ' . substr($relay_npub, 7, 7) . ' ' . substr($relay_npub, 14, 7);
$line2 = substr($relay_npub, 21, 7) . ' ' . substr($relay_npub, 28, 7) . ' ' . substr($relay_npub, 35, 7);
$line3 = substr($relay_npub, 42, 7) . ' ' . substr($relay_npub, 49, 7) . ' ' . substr($relay_npub, 56, 7);
$formatted_npub = $line1 . "\n" . $line2 . "\n" . $line3;
}
$display_name = $relay_version ? ($relay_name . ' ' . $relay_version) : $relay_name;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>C-Relay-PG Admin</title>
<link rel="stylesheet" href="assets/index.css">
</head>
<body>
<!-- Side Navigation Menu -->
<nav class="side-nav" id="side-nav">
<ul class="nav-menu">
<li><button class="nav-item" data-page="statistics">Statistics</button></li>
<li><button class="nav-item" data-page="subscriptions">Subscriptions</button></li>
<li><button class="nav-item" data-page="configuration">Configuration</button></li>
<li><button class="nav-item" data-page="authorization">Authorization</button></li>
<li><button class="nav-item" data-page="ip-bans">IP BAN</button></li>
<li><button class="nav-item" data-page="relay-events">Relay Events</button></li>
<li><button class="nav-item" data-page="caching">Caching</button></li>
<li><button class="nav-item" data-page="dm">DM</button></li>
<li><button class="nav-item" data-page="cleanup">Cleanup</button></li>
<li><button class="nav-item" data-page="database">Database Query</button></li>
</ul>
<div class="nav-footer">
<button class="nav-footer-btn" id="nav-dark-mode-btn">DARK MODE</button>
</div>
</nav>
<!-- Side Navigation Overlay -->
<div class="side-nav-overlay" id="side-nav-overlay"></div>
<!-- Header with title and profile display -->
<div class="section">
<div class="header-content">
<div class="header-title clickable" id="header-title" onclick="toggleNav()">
<span class="relay-letter" data-letter="R">R</span>
<span class="relay-letter" data-letter="E">E</span>
<span class="relay-letter" data-letter="L">L</span>
<span class="relay-letter" data-letter="A">A</span>
<span class="relay-letter" data-letter="Y">Y</span>
</div>
<div class="relay-info">
<div id="relay-name" class="relay-name"><?= e($display_name) ?></div>
<div id="relay-description" class="relay-description"><?= e($relay_desc) ?></div>
<div id="relay-pubkey-container" class="relay-pubkey-container" title="Click to copy npub">
<div id="relay-pubkey" class="relay-pubkey"><?= e($formatted_npub) ?></div>
</div>
</div>
<div class="profile-area" id="profile-area" style="display: none;">
<div class="admin-label">admin</div>
<div class="profile-container">
<img id="header-user-image" class="header-user-image" alt="Profile" style="display: none;">
<span id="header-user-name" class="header-user-name">Loading...</span>
</div>
<!-- Logout dropdown (under admin picture) -->
<div class="logout-dropdown" id="logout-dropdown" style="display: none;">
<button type="button" class="logout-btn" onclick="logout()">LOGOUT</button>
</div>
</div>
</div>
</div>
<!-- Login Modal Overlay (nostr_login_lite) -->
<div id="login-modal" class="login-modal-overlay" style="display: none;">
<div class="login-modal-content">
<div id="login-modal-container"></div>
</div>
</div>
<!-- Access Denied Overlay (shown when non-admin user logs in) -->
<div id="access-denied-overlay" class="access-denied-overlay" style="display: none;">
<div class="access-denied-content">
<div class="access-denied-icon">⛔</div>
<h2>ACCESS DENIED</h2>
<p class="access-denied-message">This interface is restricted to the relay administrator.</p>
<p class="access-denied-submessage">The logged-in account does not have admin privileges.</p>
<button type="button" class="access-denied-logout-btn" onclick="logout()">LOGOUT</button>
</div>
</div>
<!-- DATABASE STATISTICS Section -->
<div class="section flex-section" id="databaseStatisticsSection">
<div class="section-header">DATABASE STATISTICS</div>
<!-- Event Rate Graph — range selector tabs + ASCII chart (server-rendered) -->
<div class="chart-range-tabs">
<button class="chart-tab active" data-range="hour">1H</button>
<button class="chart-tab" data-range="day">1D</button>
<button class="chart-tab" data-range="month">1M</button>
<button class="chart-tab" data-range="year">1Y</button>
</div>
<div id="event-rate-chart" class="event-rate-chart-container">Loading chart...</div>
<!-- Database Overview Table -->
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="stats-overview-table">
<thead><tr><th>Metric</th><th>Value</th></tr></thead>
<tbody id="stats-overview-table-body">
<tr><td>Database Size</td><td id="db-size">-</td></tr>
<tr><td>Total Events</td><td id="total-events">-</td></tr>
<tr><td>Process ID</td><td id="process-id">-</td></tr>
<tr><td>WebSocket Connections</td><td id="websocket-connections">-</td></tr>
<tr><td>Active Subscriptions</td><td id="active-subscriptions">-</td></tr>
<tr><td>Memory Usage</td><td id="memory-usage">-</td></tr>
<tr><td>CPU Usage</td><td id="cpu-usage">-</td></tr>
<tr><td>CPU Core</td><td id="cpu-core">-</td></tr>
<tr><td>Oldest Event</td><td id="oldest-event">-</td></tr>
<tr><td>Newest Event</td><td id="newest-event">-</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Event Kind Distribution Table -->
<div class="input-group">
<label>Event Kind Distribution:</label>
<div class="config-table-container">
<table class="config-table" id="stats-kinds-table">
<thead><tr><th>Event Kind</th><th>Count</th><th>Percentage</th></tr></thead>
<tbody id="stats-kinds-table-body">
<tr><td colspan="3" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Time-based Statistics Table -->
<div class="input-group">
<label>Time-based Statistics:</label>
<div class="config-table-container">
<table class="config-table" id="stats-time-table">
<thead><tr><th>Period</th><th>Events</th></tr></thead>
<tbody id="stats-time-table-body">
<tr><td>Last 24 Hours</td><td id="events-24h">-</td></tr>
<tr><td>Last 7 Days</td><td id="events-7d">-</td></tr>
<tr><td>Last 30 Days</td><td id="events-30d">-</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Top Pubkeys Table -->
<div class="input-group">
<label>Top Pubkeys by Event Count:</label>
<div class="config-table-container">
<table class="config-table" id="stats-pubkeys-table">
<thead><tr><th>Rank</th><th>Name</th><th>Pubkey</th><th>Event Count</th><th>Percentage</th></tr></thead>
<tbody id="stats-pubkeys-table-body">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Name-field usage stats (from profiles cache) -->
<div class="input-group" id="name-field-usage" style="display:none;"></div>
</div>
<!-- SUBSCRIPTION DETAILS Section -->
<div class="section flex-section" id="subscriptionDetailsSection" style="display: none;">
<div class="section-header">ACTIVE SUBSCRIPTION DETAILS</div>
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="subscription-details-table">
<thead><tr><th>Sub ID</th><th>Pubkey</th><th>Filters</th><th>Created</th><th>Events Sent</th></tr></thead>
<tbody id="subscription-details-table-body">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- CONFIGURATION Section -->
<div id="div_config" class="section flex-section" style="display: none;">
<div class="section-header">RELAY CONFIGURATION</div>
<div id="config-display">
<div class="config-table-container">
<table class="config-table" id="config-table">
<thead><tr><th>Parameter</th><th>Value</th><th>Actions</th></tr></thead>
<tbody id="config-table-body">
<tr><td colspan="3" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
<div class="inline-buttons">
<button type="button" id="fetch-config-btn" onclick="loadConfig()">REFRESH</button>
</div>
</div>
</div>
<!-- AUTH RULES Section -->
<div class="section flex-section" id="authRulesSection" style="display: none;">
<div class="section-header">AUTH RULES MANAGEMENT</div>
<div id="authRulesTableContainer">
<table class="config-table" id="authRulesTable">
<thead><tr><th>Rule Type</th><th>Pattern Type</th><th>Pattern Value</th><th>Status</th><th>Actions</th></tr></thead>
<tbody id="authRulesTableBody">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
<div id="authRuleInputSections" style="display: block;">
<div class="input-group">
<label for="authRulePubkey">Public Key (npub or hex):</label>
<input type="text" id="authRulePubkey" placeholder="npub1... or 64-character hex pubkey">
</div>
<div class="inline-buttons">
<button type="button" onclick="addAuthRule('whitelist')">ADD TO WHITELIST</button>
<button type="button" onclick="addAuthRule('blacklist')">ADD TO BLACKLIST</button>
<button type="button" onclick="loadAuthRules()">REFRESH</button>
</div>
</div>
</div>
<!-- WEB OF TRUST Section -->
<div class="section flex-section" id="wotSection" style="display: none;">
<div class="section-header">WEB OF TRUST</div>
<div id="wotKind3Status" class="wot-status-row">
<span>Admin Contact List (kind 3):</span>
<span id="wotKind3Indicator" class="wot-indicator wot-unknown">Loading...</span>
</div>
<div class="wot-level-selector">
<label>WoT Level:</label>
<div class="inline-buttons">
<button type="button" class="wot-level-btn" onclick="setWotLevel(0)">OFF</button>
<button type="button" class="wot-level-btn" onclick="setWotLevel(1)">WRITE ONLY</button>
<button type="button" class="wot-level-btn" onclick="setWotLevel(2)">FULL</button>
</div>
<div class="wot-level-description" id="wotLevelDescription">Loading...</div>
</div>
<div class="wot-stats-row">
<span>Whitelisted Pubkeys:</span>
<span id="wotWhitelistCount">—</span>
</div>
<div class="inline-buttons">
<button type="button" onclick="syncWot()">SYNC FROM KIND 3</button>
<button type="button" onclick="loadWotStatus()">REFRESH STATUS</button>
</div>
</div>
<!-- NIP-17 DIRECT MESSAGES Section -->
<div class="section" id="nip17DMSection" style="display: none;">
<div class="section-header"><h2>NIP-17 DIRECT MESSAGES</h2></div>
<div class="input-group">
<label>Received Messages (kind 4/14/15):</label>
<div id="dm-inbox" class="log-panel" style="height: 300px; overflow-y: auto;">
<div class="log-entry">Loading...</div>
</div>
</div>
</div>
<!-- IP BANS Section -->
<div class="section" id="ipBansSection" style="display: none;">
<div class="section-header">IP BAN MANAGEMENT</div>
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="ip-bans-stats-table">
<thead><tr><th>Total IPs Tracked</th><th>Currently Banned</th><th>Total Bans Issued</th></tr></thead>
<tbody>
<tr><td id="ip-bans-total">-</td><td id="ip-bans-active">-</td><td id="ip-bans-issued">-</td></tr>
</tbody>
</table>
</div>
</div>
<div class="input-group">
<h3>Manually Ban IP Address</h3>
<div class="form-group">
<label for="ban-ip-input">IP Address:</label>
<input type="text" id="ban-ip-input" placeholder="192.168.1.100">
</div>
<div class="form-group">
<label for="ban-duration-select">Ban Duration:</label>
<select id="ban-duration-select">
<option value="3600">1 Hour</option>
<option value="86400" selected>24 Hours</option>
<option value="604800">7 Days</option>
<option value="2592000">30 Days</option>
<option value="31536000">1 Year</option>
<option value="999999999">Permanent</option>
</select>
</div>
<div class="inline-buttons">
<button type="button" onclick="addBan()">BAN IP</button>
<button type="button" onclick="loadIpBans()">REFRESH</button>
</div>
<div id="add-ban-status" class="status-message"></div>
</div>
<div class="input-group">
<label>Banned IP Addresses:</label>
<div class="config-table-container">
<table class="config-table" id="ip-bans-table">
<thead><tr><th>IP Address</th><th>Status</th><th>Banned Until</th><th>Failures</th><th>Actions</th></tr></thead>
<tbody id="ip-bans-tbody">
<tr><td colspan="5" style="text-align: center;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- RELAY EVENTS Section -->
<div class="section" id="relayEventsSection" style="display: none;">
<div class="section-header">RELAY EVENTS MANAGEMENT</div>
<div class="input-group">
<h3>Recent Events</h3>
<div class="inline-buttons">
<button type="button" onclick="loadEvents()">REFRESH</button>
</div>
<div class="config-table-container">
<table class="config-table" id="live-relay-events-table">
<thead><tr><th>Time</th><th>Kind</th><th>Name</th><th>ID</th><th>Content Preview</th></tr></thead>
<tbody id="live-relay-events-table-body">
<tr><td colspan="5" style="text-align: center;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- CACHING Section -->
<div class="section" id="cachingSection" style="display: none;">
<div class="section-header">CACHING</div>
<div class="input-group">
<h3>Caching Controls</h3>
<div id="caching-controls" class="inline-buttons">
<span id="caching-enabled-label">Caching: ?</span>
<button type="button" id="caching-toggle-btn" onclick="toggleCachingEnabled()">Toggle Caching</button>
<span id="caching-inbox-enabled-label">Inbox: ?</span>
<button type="button" id="caching-inbox-toggle-btn" onclick="toggleCachingInboxEnabled()">Toggle Inbox</button>
</div>
</div>
<div class="input-group">
<h3>Caching Service Status</h3>
<div id="caching-service-status" class="status-display">
<p>Loading...</p>
</div>
<div class="inline-buttons" style="margin-top:8px">
<button type="button" id="caching-rerun-all-btn" onclick="rerunAllCaching()">Re-run All Caching</button>
</div>
</div>
<div class="input-group">
<h3>Relay Inbox Status</h3>
<div id="caching-inbox-status" class="status-display">
<p>Loading...</p>
</div>
</div>
<div class="input-group">
<h3>Upstream Relay Status <span style="font-size:11px;font-weight:normal;color:var(--text-muted,#888)">(live connection state, updated every 15s)</span></h3>
<div id="caching-relay-status" class="status-display">
<p>Loading...</p>
</div>
</div>
<div class="input-group">
<h3>Followed Pubkeys <span style="font-size:11px;font-weight:normal;color:var(--text-muted,#888)">(click a row for per-relay details)</span></h3>
<div id="caching-follows-status" class="status-message"></div>
<div id="caching-follows-pagination" class="pagination-bar"></div>
<div class="config-table-container">
<table class="config-table" id="caching-follows-table">
<thead><tr><th>Name</th><th>npub</th><th>Root?</th><th>Events in DB</th><th>Backfill</th><th>Relays</th></tr></thead>
<tbody id="caching-follows-table-body">
<tr><td colspan="6" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<div class="inline-buttons">
<button type="button" onclick="loadCaching()">REFRESH</button>
</div>
</div>
<!-- EVENT CLEANUP Section -->
<div class="section" id="cleanupSection" style="display: none;">
<div class="section-header"><h2>EVENT CLEANUP</h2></div>
<!-- Saved Queries Table -->
<div class="input-group" id="saved-queries-group">
<label>Saved Queries</label>
<div class="config-table-container">
<table class="config-table" id="cleanup-saved-queries-table">
<thead><tr><th>Name</th><th>Filters</th><th>Last Preview</th><th>Last Executed</th><th>Actions</th></tr></thead>
<tbody id="cleanup-saved-queries-body">
<tr><td colspan="5" style="text-align: center; font-style: italic;">Loading...</td></tr>
</tbody>
</table>
</div>
<div class="inline-buttons" style="margin-top:8px">
<button type="button" onclick="newCleanupQuery()">New Query</button>
<button type="button" onclick="loadCleanupQueries()">Refresh</button>
</div>
</div>
<!-- Query Builder -->
<div class="input-group" id="cleanup-builder-group">
<label>Query Builder</label>
<div class="form-group">
<label for="cleanup-name">Query Name:</label>
<input type="text" id="cleanup-name" placeholder="My Cleanup Query" style="width:100%;max-width:400px">
</div>
<div class="form-group">
<label>Follows Filter:</label>
<div class="radio-group">
<label><input type="radio" name="cleanup-follows" value="all" checked> All</label>
<label><input type="radio" name="cleanup-follows" value="follows"> Follows only</label>
<label><input type="radio" name="cleanup-follows" value="non_follows"> Non-follows only</label>
</div>
</div>
<div class="form-group">
<label for="cleanup-kinds">Event Kinds (comma-separated, empty = all):</label>
<input type="text" id="cleanup-kinds" placeholder="e.g. 1, 7, 9734" style="width:100%;max-width:400px">
<div style="font-size:11px;color:var(--text-muted,#888);margin-top:2px">
Common kinds: 0 (profiles), 1 (text), 3 (contacts), 4 (DM), 5 (delete),
6 (repost), 7 (reaction), 9734 (zap request), 9735 (zap receipt), 10002 (relay list)
</div>
</div>
<div class="form-group">
<label for="cleanup-max-age">Max Age (days, 0 = no limit):</label>
<input type="number" id="cleanup-max-age" value="0" min="0" style="width:120px">
</div>
<div class="form-group">
<label for="cleanup-max-events">Max Events (0 = no limit):</label>
<input type="number" id="cleanup-max-events" value="0" min="0" style="width:120px">
</div>
<div class="inline-buttons" style="margin-top:8px">
<button type="button" onclick="previewCleanup()">Preview</button>
<button type="button" onclick="saveCleanupQuery()">Save Query</button>
<button type="button" onclick="executeCleanup()" style="background:#c0392b;color:#fff">Execute Delete</button>
</div>
</div>
<!-- Results Area -->
<div class="input-group" id="cleanup-results-group" style="display:none;">
<label>Results</label>
<div id="cleanup-results-summary" class="info-box"></div>
<div id="cleanup-results-breakdown" class="config-table-container" style="margin-top:8px"></div>
<div id="cleanup-results-sql" style="margin-top:8px">
<details>
<summary style="cursor:pointer;font-size:12px;color:var(--text-muted,#888)">SQL Preview</summary>
<pre id="cleanup-sql-preview" style="background:var(--bg-code,#f5f5f5);padding:8px;border-radius:4px;overflow-x:auto;font-size:11px;margin-top:4px;white-space:pre-wrap"></pre>
</details>
</div>
</div>
<!-- Confirmation Dialog -->
<div id="cleanup-confirm-dialog" class="modal-overlay" style="display:none;">
<div class="modal-content" style="background:var(--bg-card,#fff);border:1px solid var(--border,#ccc);border-radius:8px;padding:20px;max-width:480px;margin:10% auto;box-shadow:0 4px 20px rgba(0,0,0,0.3)">
<h3 style="margin-top:0">Confirm Delete</h3>
<p id="cleanup-confirm-msg">Are you sure you want to delete these events?</p>
<p style="color:#c0392b;font-weight:bold">This action cannot be undone.</p>
<div class="inline-buttons" style="margin-top:12px">
<button type="button" id="cleanup-confirm-btn" onclick="executeCleanupConfirmed()" style="background:#c0392b;color:#fff">Delete</button>
<button type="button" onclick="document.getElementById('cleanup-confirm-dialog').style.display='none'">Cancel</button>
</div>
</div>
</div>
</div>
<!-- SQL QUERY Section -->
<div class="section" id="sqlQuerySection" style="display: none;">
<div class="section-header"><h2>SQL QUERY CONSOLE</h2></div>
<div class="input-group">
<label for="sql-input">SQL Query (SELECT only):</label>
<textarea id="sql-input" rows="5" placeholder="SELECT * FROM events LIMIT 10"></textarea>
</div>
<div class="input-group">
<div class="inline-buttons">
<button type="button" onclick="executeQuery()">EXECUTE QUERY</button>
<button type="button" onclick="document.getElementById('sql-input').value=''">CLEAR</button>
</div>
</div>
<div class="input-group">
<label>Query Results:</label>
<div id="query-info" class="info-box"></div>
<div id="query-table" class="config-table-container"></div>
</div>
</div>
<!-- Nostr libraries for nostr_login_lite auth modal -->
<script src="assets/nostr.bundle.js"></script>
<script src="assets/nostr-lite.js"></script>
<script src="assets/app.js"></script>
</body>
</html>
+147
View File
@@ -0,0 +1,147 @@
<?php
/**
* admin2/lib/ascii_chart.php — Server-side ASCII X-bar chart renderer.
*
* Produces a monospaced ASCII bar chart (using 'X' characters) from an
* array of bin counts. Mirrors the layout of the original text_graph.js
* ASCIIBarChart: Y-axis count labels, X-bar columns, X-axis with
* elapsed-time labels.
*
* The output is a plain text string suitable for both browser <div>
* injection (white-space: pre) and terminal display via curl.
*/
/**
* Render an ASCII bar chart from an array of bin counts.
*
* @param array $bins Array of integer counts (one per time bin, left=oldest)
* @param array $options {
* @var string $title Chart title (centered at top)
* @var int $max_height Chart height in rows (default 11)
* @var string $x_axis_label Label below the X-axis (default '')
* @var int $bin_duration Seconds per bin — for X-axis elapsed labels
* @var int $label_interval Label every N bins (default 5)
* }
* @return string The ASCII chart as a multi-line string.
*/
function render_ascii_chart(array $bins, array $options = []): string {
$title = $options['title'] ?? 'New Events';
$max_height = $options['max_height'] ?? 11;
$x_axis_label = $options['x_axis_label'] ?? '';
$bin_duration = $options['bin_duration'] ?? 10;
$label_interval = $options['label_interval'] ?? 5;
$num_bins = count($bins);
if ($num_bins === 0) {
return "No data available.\n";
}
$max_count = max($bins);
if ($max_count < 1) $max_count = 1; // Avoid division by zero for all-empty bins
// Scaling: each X represents scale_factor counts
$scale_factor = max(1, (int)ceil($max_count / $max_height));
$scaled_max = (int)ceil($max_count / $scale_factor) * $scale_factor;
$output = '';
// --- Title (centered) ---
$chart_width = 4 + $num_bins; // 4 = Y-axis number width (3) + separator (1)
if ($title !== '') {
$title_padding = (int)floor(($chart_width - strlen($title)) / 2);
if ($title_padding < 0) $title_padding = 0;
$output .= str_repeat(' ', $title_padding) . $title . "\n\n";
}
// --- Bar rows (top to bottom) ---
for ($row = $max_height; $row > 0; $row--) {
$row_count = ($row - 1) * $scale_factor + 1;
$line = str_pad((string)$row_count, 3, ' ', STR_PAD_LEFT) . ' |';
for ($i = 0; $i < $num_bins; $i++) {
$count = $bins[$i];
$scaled_height = ($count > 0) ? (int)ceil($count / $scale_factor) : 0;
$line .= ($scaled_height >= $row) ? 'X' : ' ';
}
$output .= $line . "\n";
}
// --- X-axis line ---
$output .= ' +' . str_repeat('-', $num_bins) . "\n";
// --- X-axis labels (elapsed time every label_interval bins) ---
$label_line = ' ';
$labels = [];
for ($i = 0; $i < $num_bins; $i++) {
if ($i % $label_interval === 0) {
$elapsed_sec = $i * $bin_duration;
$labels[] = format_elapsed_time($elapsed_sec);
}
}
// Build label line with spacing
for ($i = 0; $i < count($labels); $i++) {
$label_line .= $labels[$i];
if ($i < count($labels) - 1) {
$spacing = $label_interval - strlen($labels[$i]);
if ($spacing < 1) $spacing = 1;
$label_line .= str_repeat(' ', $spacing);
}
}
// Pad to match X-axis dash line length
$min_label_len = 4 + $num_bins;
if (strlen($label_line) < $min_label_len) {
$label_line .= str_repeat(' ', $min_label_len - strlen($label_line));
}
$output .= $label_line . "\n";
// --- X-axis label (if provided) ---
if ($x_axis_label !== '') {
$label_pad = (int)floor(($num_bins - strlen($x_axis_label)) / 2);
if ($label_pad < 0) $label_pad = 0;
$output .= "\n" . ' ' . str_repeat(' ', $label_pad) . $x_axis_label . "\n";
}
return $output;
}
/**
* Format an elapsed time in seconds as a compact label.
* < 60s → "Ns"
* < 1h → "Nm"
* < 1d → "Nh"
* else → "Nd"
*/
function format_elapsed_time(int $seconds): string {
if ($seconds < 60) {
return $seconds . 's';
} elseif ($seconds < 3600) {
return (int)floor($seconds / 60) . 'm';
} elseif ($seconds < 86400) {
return (int)floor($seconds / 3600) . 'h';
} else {
return (int)floor($seconds / 86400) . 'd';
}
}
/**
* Build a fixed-length bin array from SQL query rows.
*
* The query returns only non-empty bins. This function creates a
* zero-filled array of $num_bins length and overlays the counts
* at the correct positions, so empty time slots show as blank
* columns — the chart always advances in time.
*
* @param array $rows Query rows with 'bin' (int) and 'cnt' (int)
* @param int $num_bins Total number of bins (fixed length)
* @return array Zero-filled array of counts
*/
function build_bin_array(array $rows, int $num_bins): array {
$bins = array_fill(0, $num_bins, 0);
foreach ($rows as $r) {
$idx = (int)$r['bin'];
if ($idx >= 0 && $idx < $num_bins) {
$bins[$idx] = (int)$r['cnt'];
}
}
return $bins;
}
+27
View File
@@ -0,0 +1,27 @@
<?php
/**
* C-Relay-PG Admin — Database connection configuration.
*
* This file is NOT web-accessible (denied by nginx location block).
* It contains the PostgreSQL connection string used by all admin pages.
*
* Edit these values to match your relay's PostgreSQL credentials.
*/
// PostgreSQL connection parameters.
// These should match the --db-* flags passed to the c-relay-pg binary
// or the connection string passed to caching_relay -p "...".
return [
'db_host' => 'localhost',
'db_port' => '5432',
'db_name' => 'crelay',
'db_user' => 'crelay',
'db_password' => 'crelay',
// Page sizes for paginated tables.
'follows_per_page' => 50,
'relays_per_page' => 50,
// Auto-refresh interval for dashboard polling (milliseconds).
'refresh_ms' => 10000,
];
+48
View File
@@ -0,0 +1,48 @@
<?php
/**
* C-Relay-PG Admin — PDO database connection helper.
*
* Provides a singleton PDO connection to the relay's PostgreSQL database.
* All admin pages use db() to get the connection.
*/
require_once __DIR__ . '/config.php';
function db(): PDO {
static $pdo = null;
if ($pdo === null) {
$cfg = require __DIR__ . '/config.php';
// Build DSN — supports both TCP (host=localhost) and Unix socket
// (host=/var/run/postgresql). Omit password if empty (peer auth).
$dsn = sprintf(
'pgsql:host=%s;port=%d;dbname=%s;user=%s',
$cfg['db_host'],
$cfg['db_port'],
$cfg['db_name'],
$cfg['db_user']
);
if (!empty($cfg['db_password'])) {
$dsn .= ';password=' . $cfg['db_password'];
}
try {
$pdo = new PDO($dsn, null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $e) {
http_response_code(500);
die('Database connection failed: ' . htmlspecialchars($e->getMessage()));
}
}
return $pdo;
}
/** Get a config value from the admin config file. */
function cfg(string $key, $default = null) {
static $cfg = null;
if ($cfg === null) {
$cfg = require __DIR__ . '/config.php';
}
return $cfg[$key] ?? $default;
}
+247
View File
@@ -0,0 +1,247 @@
<?php
/**
* C-Relay-PG Admin — Shared helper functions.
*/
require_once __DIR__ . '/db.php';
/** HTML-escape a string. */
function e(?string $s): string {
return htmlspecialchars($s ?? '', ENT_QUOTES, 'UTF-8');
}
/** Format a Unix timestamp as a human-readable relative time. */
function time_ago(int $ts): string {
if ($ts <= 0) return 'never';
$diff = time() - $ts;
if ($diff < 60) return $diff . 's ago';
if ($diff < 3600) return floor($diff / 60) . 'm ago';
if ($diff < 86400) return floor($diff / 3600) . 'h ago';
return floor($diff / 86400) . 'd ago';
}
/** Format a Unix timestamp as a date/time string. */
function fmt_date(int $ts): string {
if ($ts <= 0) return '-';
return date('Y-m-d H:i:s', $ts);
}
/**
* Convert a 64-char hex pubkey to npub (bech32).
* Uses the standard bech32 encoding (NIP-19).
*/
function hex_to_npub(string $hex): string {
if (!preg_match('/^[0-9a-fA-F]{64}$/', $hex)) return $hex;
$hex = strtolower($hex);
// Bech32 constants
$charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
$data = [];
for ($i = 0; $i < strlen($hex); $i += 2) {
$data[] = intval(substr($hex, $i, 2), 16);
}
// Convert 8-bit groups to 5-bit groups
$conv = [];
$buffer = 0;
$bits = 0;
foreach ($data as $byte) {
$buffer = ($buffer << 8) | $byte;
$bits += 8;
while ($bits >= 5) {
$conv[] = ($buffer >> ($bits - 5)) & 31;
$bits -= 5;
}
}
if ($bits > 0) {
$conv[] = ($buffer << (5 - $bits)) & 31;
}
// Compute checksum — HRP values are positions of each char in the charset.
$values = array_merge(
array_map(function($c) {
return strpos('qpzry9x8gf2tvdw0s3jn54khce6mua7l', $c);
}, str_split('npub')),
$conv
);
$gen = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
$chk = 1;
foreach ($values as $v) {
$top = $chk >> 25;
$chk = (($chk & 0x1ffffff) << 5) ^ $v;
for ($i = 0; $i < 5; $i++) {
if (($top >> $i) & 1) $chk ^= $gen[$i];
}
}
$chk ^= 1;
$checksum = [];
for ($i = 0; $i < 6; $i++) {
$checksum[] = ($chk >> (5 * (5 - $i))) & 31;
}
$result = 'npub1';
foreach (array_merge($conv, $checksum) as $v) {
$result .= $charset[$v];
}
return $result;
}
/** Truncate a string to $len chars with ellipsis. */
function trunc(string $s, int $len = 16): string {
if (strlen($s) <= $len) return $s;
return substr($s, 0, $len) . '…';
}
/** Build pagination links HTML. */
function pagination(int $page, int $per_page, int $total, string $base_url): string {
$total_pages = max(1, ceil($total / $per_page));
if ($total_pages <= 1) return '';
$html = '<div class="pagination">';
if ($page > 1) {
$html .= '<a href="' . e($base_url) . '?page=' . ($page - 1) . '">← Prev</a>';
}
$html .= '<span>Page ' . $page . ' of ' . $total_pages . ' (' . $total . ' total)</span>';
if ($page < $total_pages) {
$html .= '<a href="' . e($base_url) . '?page=' . ($page + 1) . '">Next →</a>';
}
$html .= '</div>';
return $html;
}
/** Get the current page number from the query string. */
function current_page(): int {
$p = intval($_GET['page'] ?? 1);
return max(1, $p);
}
/** JSON response helper for AJAX endpoints. */
function json_response($data): void {
header('Content-Type: application/json');
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
/** Get a query parameter with a default. */
function query_param(string $key, $default = null) {
return $_GET[$key] ?? $default;
}
/**
* Batch-resolve profiles from the profiles cache table.
* Returns [pubkey_hex => ['name'=>..., 'display_name'=>...,
* 'best_name'=>..., 'picture'=>..., 'nip05'=>...]].
* Pubkeys with no cached profile are absent from the result.
*/
function profile_map(array $pubkeys): array {
if (empty($pubkeys)) return [];
$pdo = db();
$map = [];
try {
// Build parameterized IN clause: WHERE pubkey = ANY(?)
// PDO PostgreSQL supports passing an array as a string literal.
$placeholders = implode(',', array_fill(0, count($pubkeys), '?'));
$stmt = $pdo->prepare(
"SELECT pubkey, name, display_name, picture, nip05 FROM profiles "
. "WHERE pubkey IN ($placeholders)"
);
$stmt->execute(array_values($pubkeys));
$rows = $stmt->fetchAll();
foreach ($rows as $r) {
$map[$r['pubkey']] = [
'name' => $r['name'] ?? '',
'display_name' => $r['display_name'] ?? '',
'best_name' => profile_display_name($r),
'picture' => $r['picture'] ?? '',
'nip05' => $r['nip05'] ?? '',
];
}
} catch (PDOException $e) {}
return $map;
}
/**
* Apply the profile_name_preference config key to resolve the display name.
* Never returns null. Returns '' if neither field is set.
*/
function profile_display_name(array $profile): string {
$name = $profile['name'] ?? '';
$display_name = $profile['display_name'] ?? '';
static $pref = null;
if ($pref === null) {
try {
$pdo = db();
$pref = $pdo->query("SELECT value FROM config WHERE key = 'profile_name_preference'")->fetchColumn() ?: 'display_name';
} catch (PDOException $e) {
$pref = 'display_name';
}
}
if ($pref === 'name') {
return $name !== '' ? $name : $display_name;
}
// Default: prefer display_name, fall back to name.
return $display_name !== '' ? $display_name : $name;
}
/**
* Render the shared HTML header with side navigation.
* Pass the active page name to highlight the current nav item.
*/
function admin_header(string $active = '', string $title = 'C-Relay-PG Admin'): void {
$pages = [
'dashboard' => ['index.php', 'Dashboard'],
'follows' => ['follows.php', 'Follows'],
'relays' => ['relays.php', 'Relays'],
'config' => ['config-edit.php', 'Config'],
'inbox' => ['inbox.php', 'Inbox'],
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($title) ?></title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<!-- Side Navigation -->
<nav class="side-nav" id="side-nav">
<ul class="nav-menu">
<?php foreach ($pages as $key => [$url, $label]): ?>
<li><a class="nav-item<?= $active === $key ? ' active' : '' ?>" href="<?= e($url) ?>"><?= e($label) ?></a></li>
<?php endforeach; ?>
</ul>
</nav>
<div class="side-nav-overlay" id="side-nav-overlay" onclick="closeNav()"></div>
<!-- Header -->
<div class="main-header">
<div class="header-content">
<button class="menu-btn" onclick="openNav()">☰</button>
<div class="header-title" onclick="location.href='index.php'">
<span class="relay-letter">R</span><span class="relay-letter">E</span><span class="relay-letter">L</span><span class="relay-letter">A</span><span class="relay-letter">Y</span>
</div>
<div style="width:40px"></div>
</div>
</div>
<script>
function openNav() {
document.getElementById('side-nav').classList.add('open');
document.getElementById('side-nav-overlay').classList.add('show');
}
function closeNav() {
document.getElementById('side-nav').classList.remove('open');
document.getElementById('side-nav-overlay').classList.remove('show');
}
</script>
<?php
}
/** Render the shared HTML footer. */
function admin_footer(): void {
?>
</body>
</html>
<?php
}
+3239
View File
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
<?php
/**
* admin/public/index.php — Public relay stats page.
*
* Served at https://<domain>/relay/stats/ (no authentication required).
*
* Shows the relay name/description/pubkey (read-only, from the config table)
* and the server-rendered ASCII event-rate chart. The chart is fetched from
* the public endpoint at /relay/api/chart.php?range=...
*
* This page exposes ONLY read-only, non-sensitive information:
* - relay_name, relay_description, relay_pubkey, relay_version (from config)
* - the event-rate chart (aggregate COUNT query, no user data)
*
* All sensitive admin endpoints (config edits, auth rules, IP bans, DMs,
* raw SQL queries) remain behind Basic Auth at /relay/admin/.
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
// Fetch relay info for the header (from config table).
$relay_name = '';
$relay_desc = '';
$relay_pubkey = '';
$relay_version = '';
try {
$relay_name = $pdo->query("SELECT value FROM config WHERE key = 'relay_name'")->fetchColumn() ?: 'C-Relay-PG';
$relay_desc = $pdo->query("SELECT value FROM config WHERE key = 'relay_description'")->fetchColumn() ?: '';
$relay_pubkey = $pdo->query("SELECT value FROM config WHERE key = 'relay_pubkey'")->fetchColumn() ?: '';
$relay_version = $pdo->query("SELECT value FROM config WHERE key = 'relay_version'")->fetchColumn() ?: '';
} catch (PDOException $e) {}
// Convert relay pubkey (hex) to npub and format into 3 lines of 3 groups of 7 chars.
$relay_npub = $relay_pubkey;
if ($relay_pubkey && preg_match('/^[0-9a-fA-F]{64}$/', $relay_pubkey)) {
$relay_npub = hex_to_npub($relay_pubkey);
}
$formatted_npub = $relay_npub;
if (strlen($relay_npub) === 63) {
$line1 = substr($relay_npub, 0, 7) . ' ' . substr($relay_npub, 7, 7) . ' ' . substr($relay_npub, 14, 7);
$line2 = substr($relay_npub, 21, 7) . ' ' . substr($relay_npub, 28, 7) . ' ' . substr($relay_npub, 35, 7);
$line3 = substr($relay_npub, 42, 7) . ' ' . substr($relay_npub, 49, 7) . ' ' . substr($relay_npub, 56, 7);
$formatted_npub = $line1 . "\n" . $line2 . "\n" . $line3;
}
$display_name = $relay_version ? ($relay_name . ' ' . $relay_version) : $relay_name;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($display_name) ?></title>
<link rel="stylesheet" href="/relay/assets/index.css">
<style>
/* Public page uses the admin theme but hides the nav and centers content. */
.side-nav, .side-nav-overlay { display: none !important; }
.public-wrap { max-width: 1100px; margin: 0 auto; padding: 20px; }
.public-chart-box {
background: var(--secondary-color);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius);
padding: 12px;
margin-top: 16px;
}
.public-footer {
margin-top: 24px;
padding-top: 12px;
border-top: var(--border-width) solid var(--border-color);
font-size: 12px;
color: var(--muted-color);
text-align: center;
}
.public-footer a { color: var(--accent-color); text-decoration: none; }
.event-rate-chart-container {
white-space: pre;
font-family: var(--font-family);
font-size: 12px;
line-height: 1.2;
overflow-x: auto;
}
</style>
</head>
<body>
<!-- Header with title and relay info (read-only) -->
<div class="section">
<div class="header-content">
<div class="header-title">
<span class="relay-letter" data-letter="R">R</span>
<span class="relay-letter" data-letter="E">E</span>
<span class="relay-letter" data-letter="L">L</span>
<span class="relay-letter" data-letter="A">A</span>
<span class="relay-letter" data-letter="Y">Y</span>
</div>
<div class="relay-info">
<div id="relay-name" class="relay-name"><?= e($display_name) ?></div>
<div id="relay-description" class="relay-description"><?= e($relay_desc) ?></div>
<div id="relay-pubkey-container" class="relay-pubkey-container" title="Click to copy npub">
<div id="relay-pubkey" class="relay-pubkey"><?= e($formatted_npub) ?></div>
</div>
</div>
</div>
</div>
<div class="public-wrap">
<div class="section-header">EVENT RATE</div>
<!-- Range selector tabs -->
<div class="chart-range-tabs">
<button class="chart-tab active" data-range="hour">1H</button>
<button class="chart-tab" data-range="day">1D</button>
<button class="chart-tab" data-range="month">1M</button>
<button class="chart-tab" data-range="year">1Y</button>
</div>
<!-- Server-rendered ASCII chart (fetched from the public chart endpoint) -->
<div class="public-chart-box">
<div id="event-rate-chart" class="event-rate-chart-container">Loading chart...</div>
</div>
<div class="public-footer">
Public relay statistics &middot;
<a href="wss://<?= e($_SERVER['HTTP_HOST'] ?? '') ?>">wss://<?= e($_SERVER['HTTP_HOST'] ?? '') ?></a>
&middot; Admin? visit <a href="/relay/admin/">/relay/admin/</a>
</div>
</div>
<script>
// Minimal chart loader — fetches the public ASCII chart endpoint and
// injects the plain-text response into the chart container. No auth.
const chartEl = document.getElementById('event-rate-chart');
let currentRange = 'hour';
let chartTimer = null;
async function loadChart(range) {
currentRange = range;
if (!chartEl) return;
chartEl.textContent = 'Loading chart...';
try {
const res = await fetch('/relay/api/chart.php?range=' + encodeURIComponent(range));
if (!res.ok) { chartEl.textContent = 'Chart load failed (HTTP ' + res.status + ')'; return; }
const text = await res.text();
chartEl.textContent = text || '(no data)';
} catch (e) {
chartEl.textContent = 'Chart load failed: ' + e.message;
}
}
document.querySelectorAll('.chart-tab').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.chart-tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const range = btn.getAttribute('data-range');
loadChart(range);
// Hour chart is live — auto-refresh every 30s. Others are cached server-side.
if (chartTimer) { clearInterval(chartTimer); chartTimer = null; }
if (range === 'hour') {
chartTimer = setInterval(() => loadChart(currentRange), 30000);
}
});
});
// Initial load + live refresh for the default (hour) range.
loadChart('hour');
chartTimer = setInterval(() => loadChart(currentRange), 30000);
// Click-to-copy the relay npub.
const pkContainer = document.getElementById('relay-pubkey-container');
if (pkContainer) {
pkContainer.style.cursor = 'pointer';
pkContainer.addEventListener('click', () => {
const txt = (document.getElementById('relay-pubkey').textContent || '').replace(/\s+/g, '');
if (txt && navigator.clipboard) navigator.clipboard.writeText(txt);
});
}
</script>
</body>
</html>
Executable
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# Launch the admin2 PHP built-in server, fully detached from the calling shell.
# Usage: ./admin2/serve.sh [port] (default port 8088)
PORT="${1:-8088}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# Kill any existing instance on this port.
pkill -f "php -S 127.0.0.1:$PORT" 2>/dev/null
sleep 1
# Start fully detached in its own session so SIGTERM to the launcher
# does not propagate to the PHP server.
setsid php -S 127.0.0.1:$PORT -t "$SCRIPT_DIR" > "$SCRIPT_DIR/php_server.log" 2>&1 < /dev/null &
PHP_PID=$!
disown $PHP_PID 2>/dev/null
# Give it a moment to bind, then report.
sleep 1
if kill -0 "$PHP_PID" 2>/dev/null; then
echo "admin PHP server started on http://127.0.0.1:$PORT (PID $PHP_PID)"
echo "Serving from: $SCRIPT_DIR"
echo "Log: $SCRIPT_DIR/php_server.log"
else
echo "ERROR: PHP server failed to start. Log:"
cat "$SCRIPT_DIR/php_server.log" 2>/dev/null
exit 1
fi
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Embedded NOSTR_LOGIN_LITE</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 40px;
background: white;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
max-width: 400px;
width: 100%;
}
#login-container {
/* No styling - let embedded modal blend seamlessly */
}
</style>
</head>
<body>
<div class="container">
<div id="login-container"></div>
</div>
<script src="../lite/nostr.bundle.js"></script>
<script src="../lite/nostr-lite.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
await window.NOSTR_LOGIN_LITE.init({
theme:'default',
methods: {
extension: true,
local: true,
seedphrase: true,
readonly: true,
connect: true,
remote: true,
otp: true
}
});
window.NOSTR_LOGIN_LITE.embed('#login-container', {
seamless: true
});
});
</script>
</body>
</html>
+1062 -51
View File
File diff suppressed because it is too large Load Diff
+638 -171
View File
@@ -4,214 +4,142 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>C-Relay Admin API</title>
<title>C-Relay-PG Admin</title>
<link rel="stylesheet" href="/api/index.css">
</head>
<body>
<h1>C-RELAY ADMIN API</h1>
<!-- Side Navigation Menu -->
<nav class="side-nav" id="side-nav">
<ul class="nav-menu">
<li><button class="nav-item" data-page="statistics">Statistics</button></li>
<li><button class="nav-item" data-page="subscriptions">Subscriptions</button></li>
<li><button class="nav-item" data-page="configuration">Configuration</button></li>
<li><button class="nav-item" data-page="authorization">Authorization</button></li>
<li><button class="nav-item" data-page="ip-bans">IP BANS</button></li>
<li><button class="nav-item" data-page="relay-events">Relay Events</button></li>
<li><button class="nav-item" data-page="caching">Caching</button></li>
<li><button class="nav-item" data-page="dm">DM</button></li>
<li><button class="nav-item" data-page="database">Database Query</button></li>
</ul>
<div class="nav-footer">
<button class="nav-footer-btn" id="nav-dark-mode-btn">DARK MODE</button>
<button class="nav-footer-btn" id="nav-logout-btn">LOGOUT</button>
</div>
</nav>
<!-- Main Sections Wrapper -->
<div class="main-sections-wrapper">
<!-- Side Navigation Overlay -->
<div class="side-nav-overlay" id="side-nav-overlay"></div>
<!-- Persistent Authentication Header - Always Visible -->
<div id="persistent-auth-container" class="section flex-section">
<div class="user-info-container">
<button type="button" id="login-logout-btn" class="login-logout-btn">LOGIN</button>
<div class="user-details" id="persistent-user-details" style="display: none;">
<div><strong>Name:</strong> <span id="persistent-user-name">Loading...</span></div>
<div><strong>Public Key:</strong>
<div class="user-pubkey" id="persistent-user-pubkey">Loading...</div>
</div>
<div><strong>About:</strong> <span id="persistent-user-about">Loading...</span></div>
<!-- Header with title and profile display -->
<div class="section">
<div class="header-content">
<div class="header-title clickable" id="header-title">
<span class="relay-letter" data-letter="R">R</span>
<span class="relay-letter" data-letter="E">E</span>
<span class="relay-letter" data-letter="L">L</span>
<span class="relay-letter" data-letter="A">A</span>
<span class="relay-letter" data-letter="Y">Y</span>
</div>
<div class="relay-info">
<div id="relay-name" class="relay-name">C-Relay-PG</div>
<div id="relay-description" class="relay-description">Loading...</div>
<div id="relay-pubkey-container" class="relay-pubkey-container">
<div id="relay-pubkey" class="relay-pubkey">Loading...</div>
</div>
</div>
</div>
<!-- Login Section -->
<div id="login-section" class="flex-section">
<div class="section">
<h2>NOSTR AUTHENTICATION</h2>
<p id="login-instructions">Please login with your Nostr identity to access the admin interface.</p>
<!-- nostr-lite login UI will be injected here -->
<div class="profile-area" id="profile-area" style="display: none;">
<div class="admin-label">admin</div>
<div class="profile-container">
<img id="header-user-image" class="header-user-image" alt="Profile" style="display: none;">
<span id="header-user-name" class="header-user-name">Loading...</span>
</div>
<!-- Logout dropdown -->
<!-- Dropdown menu removed - buttons moved to sidebar -->
</div>
</div>
<!-- Relay Connection Section -->
<div id="relay-connection-section" class="flex-section">
<div class="section">
<h2>RELAY CONNECTION</h2>
<div class="input-group">
<label for="relay-connection-url">Relay URL:</label>
<input type="text" id="relay-connection-url" value="ws://localhost:8888"
placeholder="ws://localhost:8888 or wss://relay.example.com">
</div>
<div class="input-group">
<label for="relay-pubkey-manual">Relay Pubkey (if not available via NIP-11):</label>
<input type="text" id="relay-pubkey-manual" placeholder="64-character hex pubkey"
pattern="[0-9a-fA-F]{64}" title="64-character hexadecimal public key">
</div>
<div class="inline-buttons">
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
<button type="button" id="disconnect-relay-btn" disabled>DISCONNECT</button>
<button type="button" id="restart-relay-btn" disabled>RESTART RELAY</button>
</div>
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
<!-- Relay Information Display -->
<div id="relay-info-display" class="hidden">
<h3>Relay Information (NIP-11)</h3>
<table class="config-table" id="relay-info-table">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tbody id="relay-info-table-body">
</tbody>
</table>
</div>
</div>
</div>
</div> <!-- End Main Sections Wrapper -->
<!-- Testing Section -->
<div id="div_config" class="section flex-section" style="display: none;">
<h2>RELAY CONFIGURATION</h2>
<div id="config-display" class="hidden">
<div class="config-table-container">
<table class="config-table" id="config-table">
<thead>
<tr>
<th>Parameter</th>
<th>Value</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="config-table-body">
</tbody>
</table>
</div>
<div class="inline-buttons">
<button type="button" id="fetch-config-btn">REFRESH</button>
</div>
</div>
</div>
<!-- Auth Rules Management - Moved after configuration -->
<div class="section flex-section" id="authRulesSection" style="display: none;">
<div class="section-header">
<h2>AUTH RULES MANAGEMENT</h2>
</div>
<!-- Auth Rules Table -->
<div id="authRulesTableContainer" style="display: none;">
<table class="config-table" id="authRulesTable">
<thead>
<tr>
<th>Rule Type</th>
<th>Pattern Type</th>
<th>Pattern Value</th>
<th>Action</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="authRulesTableBody">
</tbody>
</table>
</div>
<!-- Simplified Auth Rule Input Section -->
<div id="authRuleInputSections" style="display: block;">
<!-- Combined Pubkey Auth Rule Section -->
<div class="input-group">
<label for="authRulePubkey">Pubkey (nsec or hex):</label>
<input type="text" id="authRulePubkey" placeholder="nsec1... or 64-character hex pubkey">
</div>
<div id="whitelistWarning" class="warning-box" style="display: none;">
<strong>⚠️ WARNING:</strong> Adding whitelist rules changes relay behavior to whitelist-only
mode.
Only whitelisted users will be able to interact with the relay.
</div>
<div class="inline-buttons">
<button type="button" id="addWhitelistBtn" onclick="addWhitelistRule()">ADD TO
WHITELIST</button>
<button type="button" id="addBlacklistBtn" onclick="addBlacklistRule()">ADD TO
BLACKLIST</button>
<button type="button" id="refreshAuthRulesBtn">REFRESH</button>
</div>
</div>
</div>
<!-- Login Modal Overlay -->
<div id="login-modal" class="login-modal-overlay" style="display: none;">
<div class="login-modal-content">
<div id="login-modal-container"></div>
</div>
</div>
<!-- Access Denied Overlay (shown when non-admin user logs in) -->
<div id="access-denied-overlay" class="access-denied-overlay" style="display: none;">
<div class="access-denied-content">
<div class="access-denied-icon"></div>
<h2>ACCESS DENIED</h2>
<p class="access-denied-message">This interface is restricted to the relay administrator.</p>
<p class="access-denied-submessage">The logged-in account does not have admin privileges.</p>
<button type="button" class="access-denied-logout-btn" onclick="logout()">LOGOUT</button>
</div>
</div>
<!-- DATABASE STATISTICS Section -->
<div class="section">
<!-- Subscribe to kind 24567 events to receive real-time monitoring data -->
<div class="section flex-section" id="databaseStatisticsSection" style="display: none;">
<div class="section-header">
<h2>DATABASE STATISTICS</h2>
DATABASE STATISTICS
</div>
<!-- Event Rate Graph Container -->
<div id="event-rate-chart"></div>
<!-- Database Overview Table -->
<div class="input-group">
<label>Database Overview:</label>
<div class="config-table-container">
<table class="config-table" id="stats-overview-table">
<thead>
<tr>
<th>Metric</th>
<th>Value</th>
<th>Description</th>
</tr>
</thead>
<tbody id="stats-overview-table-body">
<tr>
<td>Database Size</td>
<td id="db-size">-</td>
<td>Current database file size</td>
</tr>
<tr>
<td>Total Events</td>
<td id="total-events">-</td>
<td>Total number of events stored</td>
</tr>
<tr>
<td>Process ID</td>
<td id="process-id">-</td>
</tr>
<tr>
<td>WebSocket Connections</td>
<td id="websocket-connections">-</td>
</tr>
<tr>
<td>Active Subscriptions</td>
<td id="active-subscriptions">-</td>
</tr>
<tr>
<td>Memory Usage</td>
<td id="memory-usage">-</td>
</tr>
<tr>
<td>CPU Usage</td>
<td id="cpu-usage">-</td>
</tr>
<tr>
<td>CPU Core</td>
<td id="cpu-core">-</td>
</tr>
<tr>
<td>Oldest Event</td>
<td id="oldest-event">-</td>
<td>Timestamp of oldest event</td>
</tr>
<tr>
<td>Newest Event</td>
<td id="newest-event">-</td>
<td>Timestamp of newest event</td>
</tr>
</tbody>
</table>
@@ -248,24 +176,20 @@
<tr>
<th>Period</th>
<th>Events</th>
<th>Description</th>
</tr>
</thead>
<tbody id="stats-time-table-body">
<tr>
<td>Last 24 Hours</td>
<td id="events-24h">-</td>
<td>Events in the last day</td>
</tr>
<tr>
<td>Last 7 Days</td>
<td id="events-7d">-</td>
<td>Events in the last week</td>
</tr>
<tr>
<td>Last 30 Days</td>
<td id="events-30d">-</td>
<td>Events in the last month</td>
</tr>
</tbody>
</table>
@@ -280,6 +204,7 @@
<thead>
<tr>
<th>Rank</th>
<th>Name</th>
<th>Pubkey</th>
<th>Event Count</th>
<th>Percentage</th>
@@ -287,19 +212,157 @@
</thead>
<tbody id="stats-pubkeys-table-body">
<tr>
<td colspan="4" style="text-align: center; font-style: italic;">No data loaded</td>
<td colspan="5" style="text-align: center; font-style: italic;">No data loaded</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Refresh Button -->
</div>
<!-- SUBSCRIPTION DETAILS Section (Admin Only) -->
<div class="section flex-section" id="subscriptionDetailsSection" style="display: none;">
<div class="section-header">
ACTIVE SUBSCRIPTION DETAILS
</div>
<div class="input-group">
<button type="button" id="refresh-stats-btn">REFRESH STATISTICS</button>
<div class="config-table-container">
<table class="config-table" id="subscription-details-table">
<tbody id="subscription-details-table-body">
<tr>
<td colspan="4" style="text-align: center; font-style: italic;">No subscriptions active</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Testing Section -->
<div id="div_config" class="section flex-section" style="display: none;">
<div class="section-header">
RELAY CONFIGURATION
</div>
<div id="config-display" class="hidden">
<div class="config-table-container">
<table class="config-table" id="config-table">
<thead>
<tr>
<th>Parameter</th>
<th>Value</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="config-table-body">
</tbody>
</table>
</div>
<div class="inline-buttons">
<button type="button" id="fetch-config-btn">REFRESH</button>
</div>
</div>
</div>
<!-- Auth Rules Management - Moved after configuration -->
<!-- AUTH RULES MANAGEMENT SECTION -->
<div class="section flex-section" id="authRulesSection" style="display: none;">
<div class="section-header">
AUTH RULES MANAGEMENT
</div>
<!-- Auth Rules Table -->
<div id="authRulesTableContainer" style="display: none;">
<table class="config-table" id="authRulesTable">
<thead>
<tr>
<th>Rule Type</th>
<th>Pattern Type</th>
<th>Pattern Value</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="authRulesTableBody">
</tbody>
</table>
</div>
<!-- Auth Rule Input Section -->
<div id="authRuleInputSections" style="display: block;">
<div class="input-group">
<label for="authRulePubkey">Public Key (npub or hex):</label>
<input type="text" id="authRulePubkey" placeholder="npub1... or 64-character hex pubkey">
</div>
<div id="whitelistWarning" class="warning-box" style="display: none;">
<strong>⚠️ WARNING:</strong> Adding whitelist rules changes relay behavior to whitelist-only
mode.
Only whitelisted users will be able to interact with the relay.
</div>
<div class="inline-buttons">
<button type="button" id="addWhitelistBtn" onclick="addWhitelistRule()">ADD TO
WHITELIST</button>
<button type="button" id="addBlacklistBtn" onclick="addBlacklistRule()">ADD TO
BLACKLIST</button>
<button type="button" id="refreshAuthRulesBtn">REFRESH</button>
</div>
</div>
</div>
<!-- WEB OF TRUST SECTION -->
<div class="section flex-section" id="wotSection" style="display: none;">
<div class="section-header">
WEB OF TRUST
</div>
<!-- Kind 3 Status Indicator -->
<div id="wotKind3Status" class="wot-status-row">
<span>Admin Contact List (kind 3):</span>
<span id="wotKind3Indicator" class="wot-indicator wot-unknown">Checking...</span>
</div>
<!-- WoT Level Selector -->
<div class="wot-level-selector">
<label>WoT Level:</label>
<div class="inline-buttons">
<button type="button" id="wotLevel0Btn" class="wot-level-btn" onclick="setWotLevel(0)">
OFF
</button>
<button type="button" id="wotLevel1Btn" class="wot-level-btn" onclick="setWotLevel(1)">
WRITE ONLY
</button>
<button type="button" id="wotLevel2Btn" class="wot-level-btn" onclick="setWotLevel(2)">
FULL
</button>
</div>
<div class="wot-level-description" id="wotLevelDescription">
Level 0: Open relay — anyone can read and write
</div>
</div>
<!-- WoT Stats -->
<div class="wot-stats-row">
<span>Whitelisted Pubkeys:</span>
<span id="wotWhitelistCount"></span>
</div>
<!-- Sync Button -->
<div class="inline-buttons">
<button type="button" id="wotSyncBtn" onclick="syncWot()">SYNC FROM KIND 3</button>
<button type="button" id="wotRefreshBtn" onclick="loadWotStatus()">REFRESH STATUS</button>
</div>
</div>
<!-- NIP-17 DIRECT MESSAGES Section -->
<div class="section" id="nip17DMSection" style="display: none;">
<div class="section-header">
@@ -307,7 +370,7 @@
</div>
<!-- Outbox -->
<div class="input-group">
<div>
<label for="dm-outbox">Send Message to Relay:</label>
<textarea id="dm-outbox" rows="4" placeholder="Enter your message to send to the relay..."></textarea>
</div>
@@ -326,6 +389,408 @@
</div>
</div>
<!-- IP BANS Section -->
<div class="section" id="ipBansSection" style="display: none;">
<div class="section-header">
IP BAN MANAGEMENT
</div>
<!-- Statistics Cards -->
<div class="input-group">
<div class="config-table-container">
<table class="config-table" id="ip-bans-stats-table">
<thead>
<tr>
<th>Total IPs Tracked</th>
<th>Currently Banned</th>
<th>Total Bans Issued</th>
</tr>
</thead>
<tbody>
<tr>
<td id="ip-bans-total">-</td>
<td id="ip-bans-active">-</td>
<td id="ip-bans-issued">-</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Add Ban Form -->
<div class="input-group">
<h3>Manually Ban IP Address</h3>
<div class="form-group">
<label for="ban-ip-input">IP Address:</label>
<input type="text" id="ban-ip-input" placeholder="192.168.1.100">
</div>
<div class="form-group">
<label for="ban-duration-select">Ban Duration:</label>
<select id="ban-duration-select">
<option value="3600">1 Hour</option>
<option value="86400" selected>24 Hours</option>
<option value="604800">7 Days</option>
<option value="2592000">30 Days</option>
<option value="31536000">1 Year</option>
<option value="999999999">Permanent</option>
</select>
</div>
<div class="inline-buttons">
<button type="button" id="add-ban-btn">BAN IP</button>
</div>
<div id="add-ban-status" class="status-message"></div>
</div>
<!-- Whitelist Management -->
<div class="input-group">
<h3>IP Whitelist (Never Banned)</h3>
<p style="font-size:13px;opacity:0.8;">IPs in this list are never idle-banned. Comma-separated.</p>
<div class="form-group">
<label for="whitelist-ip-input">Add IP to Whitelist:</label>
<input type="text" id="whitelist-ip-input" placeholder="103.81.231.220">
</div>
<div class="inline-buttons">
<button type="button" id="add-whitelist-btn">ADD TO WHITELIST</button>
</div>
<div id="whitelist-status" class="status-message"></div>
<div id="whitelist-current" style="margin-top:8px;font-size:13px;"></div>
</div>
<!-- Filter Controls -->
<div class="input-group">
<div class="inline-buttons">
<button type="button" id="ip-ban-filter-all" class="active">All IPs</button>
<button type="button" id="ip-ban-filter-banned">Currently Banned</button>
<button type="button" id="ip-ban-filter-expired">Expired</button>
<button type="button" id="refresh-ip-bans-btn">REFRESH</button>
</div>
</div>
<!-- IP Bans List -->
<div class="input-group">
<label>Banned IP Addresses:</label>
<div class="config-table-container">
<table class="config-table" id="ip-bans-table">
<thead>
<tr>
<th>IP Address</th>
<th>Status</th>
<th>Banned Until</th>
<th>Failures</th>
<th>Authed Successfully</th>
<th>Connection Attempts</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="ip-bans-tbody">
<tr>
<td colspan="7" style="text-align: center;">Click REFRESH to load IP bans</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- RELAY EVENTS Section -->
<div class="section" id="relayEventsSection" style="display: none;">
<div class="section-header">
RELAY EVENTS MANAGEMENT
</div>
<!-- Live Relay Event Feed -->
<div class="input-group">
<h3>Live Relay Event Feed (Normal Nostr Subscription)</h3>
<div class="config-table-container">
<table class="config-table" id="live-relay-events-table">
<thead>
<tr>
<th>Time</th>
<th>Kind</th>
<th>Pubkey</th>
<th>ID</th>
<th>Content Preview</th>
</tr>
</thead>
<tbody id="live-relay-events-table-body">
<tr>
<td colspan="5" style="text-align: center;">Waiting for live events...</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Kind 0: User Metadata -->
<div class="input-group">
<h3>Kind 0: User Metadata</h3>
<div class="form-group">
<label for="kind0-name">Name:</label>
<input type="text" id="kind0-name" placeholder="Relay Name">
</div>
<div class="form-group">
<label for="kind0-about">About:</label>
<textarea id="kind0-about" rows="3" placeholder="Relay Description"></textarea>
</div>
<div class="form-group">
<label for="kind0-picture">Picture URL:</label>
<input type="url" id="kind0-picture" placeholder="https://example.com/logo.png">
</div>
<div class="form-group">
<label for="kind0-banner">Banner URL:</label>
<input type="url" id="kind0-banner" placeholder="https://example.com/banner.png">
</div>
<div class="form-group">
<label for="kind0-nip05">NIP-05:</label>
<input type="text" id="kind0-nip05" placeholder="relay@example.com">
</div>
<div class="form-group">
<label for="kind0-website">Website:</label>
<input type="url" id="kind0-website" placeholder="https://example.com">
</div>
<div class="inline-buttons">
<button type="button" id="submit-kind0-btn">UPDATE METADATA</button>
</div>
<div id="kind0-status" class="status-message"></div>
</div>
<!-- Kind 10050: DM Relay List -->
<div class="input-group">
<h3>Kind 10050: DM Relay List</h3>
<div class="form-group">
<label for="kind10050-relays">Relay URLs (one per line):</label>
<textarea id="kind10050-relays" rows="4" placeholder="wss://relay1.com&#10;wss://relay2.com"></textarea>
</div>
<div class="inline-buttons">
<button type="button" id="submit-kind10050-btn">UPDATE DM RELAYS</button>
</div>
<div id="kind10050-status" class="status-message"></div>
</div>
<!-- Kind 10002: Relay List -->
<div class="input-group">
<h3>Kind 10002: Relay List</h3>
<div id="kind10002-relay-entries">
<!-- Dynamic relay entries will be added here -->
</div>
<div class="inline-buttons">
<button type="button" id="add-relay-entry-btn">ADD RELAY</button>
<button type="button" id="submit-kind10002-btn">UPDATE RELAYS</button>
</div>
<div id="kind10002-status" class="status-message"></div>
</div>
</div>
<!-- CACHING Section -->
<div class="section" id="cachingSection" style="display: none;">
<div class="section-header">CACHING</div>
<!-- Caching Service Status (read-only) -->
<div class="input-group">
<h3>Caching Service Status</h3>
<div id="caching-service-status" class="status-display">
<p>Service status will appear here after connecting.</p>
</div>
</div>
<!-- Relay Inbox Status (read-only) -->
<div class="input-group">
<h3>Relay Inbox Status</h3>
<div id="caching-inbox-status" class="status-display">
<p>Inbox status will appear here after connecting.</p>
</div>
</div>
<!-- Caching Configuration (editable) -->
<div class="input-group">
<h3>Caching Configuration</h3>
<div class="form-group">
<label for="caching-enabled">Enable Caching:</label>
<select id="caching-enabled">
<option value="false">Disabled</option>
<option value="true">Enabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-inbox-enabled">Enable Inbox Consumer:</label>
<select id="caching-inbox-enabled">
<option value="false">Disabled</option>
<option value="true">Enabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-root-npubs">Root Npubs (one per line):</label>
<textarea id="caching-root-npubs" rows="4" placeholder="npub1...">npub13lm5wf8dvsdnc2894pkhch9uf8phvw9varrv8zf4sc885hhdmc8q6lx7ks</textarea>
</div>
<div class="form-group">
<label for="caching-bootstrap-relays">Bootstrap Relays (one per line):</label>
<textarea id="caching-bootstrap-relays" rows="4" placeholder="wss://relay.damus.io">wss://relay.damus.io
wss://nos.lol
wss://relay.primal.net
wss://laantungir.net/relay</textarea>
</div>
<div class="form-group">
<label for="caching-kinds">Kinds (comma-separated):</label>
<input type="text" id="caching-kinds" placeholder="1,3,6,10000,30023" value="0,1,3,6,10000,10002,30023">
</div>
<div class="form-group">
<label for="caching-admin-kinds">Admin Kinds (comma-separated, * for all):</label>
<input type="text" id="caching-admin-kinds" placeholder="*">
</div>
<div class="form-group">
<label for="caching-live-enabled">Live Subscriptions:</label>
<select id="caching-live-enabled">
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-backfill-enabled">Backfill:</label>
<select id="caching-backfill-enabled">
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
</div>
<div class="form-group">
<label for="caching-backfill-windows">Backfill Windows (seconds, comma-separated):</label>
<input type="text" id="caching-backfill-windows" placeholder="86400,604800,2592000,7776000,31536000">
</div>
<div class="form-group">
<label for="caching-backfill-page-size">Backfill Page Size:</label>
<input type="number" id="caching-backfill-page-size" placeholder="50">
</div>
<div class="form-group">
<label for="caching-backfill-tick-interval">Backfill Tick Interval (ms):</label>
<input type="number" id="caching-backfill-tick-interval" placeholder="5000">
</div>
<div class="form-group">
<label for="caching-inbox-batch-size">Inbox Batch Size:</label>
<input type="number" id="caching-inbox-batch-size" placeholder="150">
</div>
<div class="form-group">
<label for="caching-inbox-active-poll">Inbox Active Poll (ms):</label>
<input type="number" id="caching-inbox-active-poll" placeholder="200">
</div>
<div class="form-group">
<label for="caching-inbox-idle-poll">Inbox Idle Poll (ms):</label>
<input type="number" id="caching-inbox-idle-poll" placeholder="5000">
</div>
<div class="form-group">
<label for="caching-service-binary-path">Caching Service Binary Path:</label>
<input type="text" id="caching-service-binary-path" placeholder="./caching_relay" value="./caching_relay">
</div>
<div class="form-group">
<label for="caching-service-pg-conn">Caching Service PG Connection:</label>
<input type="text" id="caching-service-pg-conn" placeholder="host=localhost port=5432 dbname=crelay user=crelay password=crelay" value="host=localhost port=5432 dbname=crelay user=crelay password=crelay">
</div>
<div class="inline-buttons">
<button type="button" id="caching-apply-btn">APPLY CONFIGURATION</button>
<button type="button" id="caching-reset-progress-btn">RESET BACKFILL PROGRESS</button>
</div>
<div id="caching-config-status" class="status-message"></div>
</div>
<div class="input-group">
<h3>Caching Service Control</h3>
<p>Start or stop the external caching service process. Set <code>caching_service_binary_path</code> and <code>caching_service_pg_conn</code> above and click APPLY CONFIGURATION before starting.</p>
<div class="inline-buttons">
<button type="button" id="caching-start-service-btn">START CACHING SERVICE</button>
<button type="button" id="caching-stop-service-btn">STOP CACHING SERVICE</button>
</div>
<div id="caching-service-control-status" class="status-message"></div>
</div>
<div class="input-group">
<h3>Followed Pubkeys</h3>
<p>Pubkeys being cached with their profile names, event counts, outbox relays, and backfill status.</p>
<div id="caching-follows-status" class="status-message"></div>
<table id="caching-follows-table" style="width: 100%; border-collapse: collapse; font-size: 13px;">
<thead>
<tr style="border-bottom: 2px solid var(--border-color, #444); text-align: left;">
<th style="padding: 6px 8px;">Name</th>
<th style="padding: 6px 8px;">npub</th>
<th style="padding: 6px 8px;">Root?</th>
<th style="padding: 6px 8px;">Events in DB</th>
<th style="padding: 6px 8px;">Backfill</th>
<th style="padding: 6px 8px; width: 40%;">Relays (status, events fetched)</th>
</tr>
</thead>
<tbody id="caching-follows-table-body">
<tr><td colspan="6" style="text-align: center; font-style: italic; padding: 12px;">Loading follows status...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- SQL QUERY Section -->
<div class="section" id="sqlQuerySection" style="display: none;">
<div class="section-header">
<h2>SQL QUERY CONSOLE</h2>
</div>
<!-- Query Selector -->
<div class="input-group">
<label for="query-dropdown">Quick Queries & History:</label>
<select id="query-dropdown" onchange="loadSelectedQuery()">
<option value="">-- Select a query --</option>
<optgroup label="Common Queries">
<option value="recent_events">Recent Events</option>
<option value="event_stats">Event Statistics</option>
<option value="subscriptions">Active Subscriptions</option>
<option value="top_pubkeys">Top Pubkeys</option>
<option value="event_kinds">Event Kinds Distribution</option>
<option value="time_stats">Time-based Statistics</option>
</optgroup>
<optgroup label="IP Ban Queries">
<option value="banned_ips_summary">Banned IPs Summary</option>
<option value="banned_ips_list">All Banned IP Addresses</option>
</optgroup>
<optgroup label="Query History" id="history-group">
<!-- Dynamically populated from localStorage -->
</optgroup>
</select>
</div>
<!-- Query Editor -->
<div class="input-group">
<label for="sql-input">SQL Query:</label>
<textarea id="sql-input" rows="5" placeholder="SELECT * FROM events LIMIT 10"></textarea>
</div>
<!-- Query Actions -->
<div class="input-group">
<div class="inline-buttons">
<button type="button" id="execute-sql-btn">EXECUTE QUERY</button>
<button type="button" id="clear-sql-btn">CLEAR</button>
<button type="button" id="clear-history-btn">CLEAR HISTORY</button>
</div>
</div>
<!-- Query Results -->
<div class="input-group">
<label>Query Results:</label>
<div id="query-info" class="info-box"></div>
<div id="query-table" class="config-table-container"></div>
</div>
</div>
<!-- Load the official nostr-tools bundle first -->
<!-- <script src="https://laantungir.net/nostr-login-lite/nostr.bundle.js"></script> -->
<script src="/api/nostr.bundle.js"></script>
@@ -333,6 +798,8 @@
<!-- Load NOSTR_LOGIN_LITE main library -->
<!-- <script src="https://laantungir.net/nostr-login-lite/nostr-lite.js"></script> -->
<script src="/api/nostr-lite.js"></script>
<!-- Load text_graph library -->
<script src="/api/text_graph.js"></script>
+7067 -3120
View File
File diff suppressed because it is too large Load Diff
+95 -1
View File
@@ -3297,8 +3297,18 @@ var NostrTools = (() => {
this.enablePing = opts.enablePing;
}
async ensureRelay(url, params) {
const rawUrl = url;
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
url = normalizeURL(url);
let relay = this.relays.get(url);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay START", {
rawUrl,
normalizedUrl: url,
hadRelay: !!relay,
relayMapKeysBefore: Array.from(this.relays.keys())
});
}
if (!relay) {
relay = new AbstractRelay(url, {
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
@@ -3306,13 +3316,38 @@ var NostrTools = (() => {
enablePing: this.enablePing
});
relay.onclose = () => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay onclose", {
normalizedUrl: url,
relayMapKeysBeforeDelete: Array.from(this.relays.keys())
});
}
this.relays.delete(url);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay onclose complete", {
normalizedUrl: url,
relayMapKeysAfterDelete: Array.from(this.relays.keys())
});
}
};
if (params?.connectionTimeout)
relay.connectionTimeout = params.connectionTimeout;
this.relays.set(url, relay);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay created relay", {
normalizedUrl: url,
relayMapKeysAfterCreate: Array.from(this.relays.keys())
});
}
}
await relay.connect();
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL ensureRelay connected", {
normalizedUrl: url,
relayConnected: relay.connected,
relayMapKeysAfterConnect: Array.from(this.relays.keys())
});
}
return relay;
}
close(relays) {
@@ -3334,16 +3369,26 @@ var NostrTools = (() => {
}
subscribeMany(relays, filters, params) {
params.onauth = params.onauth || params.doauth;
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
const request = [];
const uniqUrls = [];
for (let i2 = 0; i2 < relays.length; i2++) {
const url = normalizeURL(relays[i2]);
if (uniqUrls.indexOf(url) === -1) {
uniqUrls.push(url);
for (let f2 = 0; f2 < filters.length; f2++) {
request.push({ url, filter: filters[f2] });
}
}
}
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL subscribeMany", {
relaysInput: relays,
uniqUrls,
filtersCount: filters.length,
requestsCount: request.length
});
}
return this.subscribeMap(request, params);
}
subscribeMap(requests, params) {
@@ -3391,14 +3436,31 @@ var NostrTools = (() => {
_knownIds.add(id);
return have;
};
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
const allOpened = Promise.all(
requests.map(async ({ url, filter }, i2) => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL subscribeMap request", {
index: i2,
url,
filterKinds: filter?.kinds,
hasAuthorFilter: !!filter?.authors,
hasPTagFilter: !!filter?.["#p"]
});
}
let relay;
try {
relay = await this.ensureRelay(url, {
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
});
} catch (err) {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL subscribeMap ensureRelay FAILED", {
index: i2,
url,
error: err?.message || String(err)
});
}
handleClose(i2, err?.message || String(err));
return;
}
@@ -3480,18 +3542,50 @@ var NostrTools = (() => {
return events[0] || null;
}
publish(relays, event, options) {
return relays.map(normalizeURL).map(async (url, i2, arr) => {
const debugEnabled = typeof window !== "undefined" && !!window.__SIMPLE_POOL_DEBUG__;
const normalizedRelays = relays.map(normalizeURL);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish START", {
relaysInput: relays,
normalizedRelays,
eventKind: event?.kind,
eventId: event?.id
});
}
return normalizedRelays.map(async (url, i2, arr) => {
if (arr.indexOf(url) !== i2) {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish duplicate URL", { url, index: i2 });
}
return Promise.reject("duplicate url");
}
let r = await this.ensureRelay(url);
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish ensured relay", {
url,
relayConnected: r?.connected,
relayMapKeys: Array.from(this.relays.keys())
});
}
return r.publish(event).catch(async (err) => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish ERROR", {
url,
error: err?.message || String(err)
});
}
if (err instanceof Error && err.message.startsWith("auth-required: ") && options?.onauth) {
await r.auth(options.onauth);
return r.publish(event);
}
throw err;
}).then((reason) => {
if (debugEnabled) {
console.log("🔎 SIMPLE_POOL publish SUCCESS", {
url,
reason
});
}
if (this.trackRelays) {
let set = this.seenOn.get(event.id);
if (!set) {
+813
View File
@@ -0,0 +1,813 @@
<!DOCTYPE html>
<?xml version="1.0" encoding="UTF-8"?>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title>TEMPLATE</title>
<link rel="stylesheet" href="./css/client.css" />
<!-- Initialize theme BEFORE any components load -->
<script>
(function () {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark-mode');
if (document.body) {
document.body.classList.add('dark-mode');
}
}
})();
</script>
<link rel="shortcut icon" type="image/x-icon" href="./favicon/favicon-dots2.ico" />
<!-- SVG.js library (required by HamburgerMorphing) -->
<script src="https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js@3.0/dist/svg.min.js"></script>
</head>
<body>
<!-- ================================================================
HAMBURGER BUTTON (Fixed, separate from header)
================================================================
The hamburger button is a fixed element outside the header
to ensure it stays visible above the sidenav (z-index: 10 > 3).
================================================================ -->
<div id="divSvgHam" class="divHeaderButtons">
<!-- HamburgerMorphing will be injected here -->
</div>
<!-- ================================================================
HEADER
================================================================
Standard header with title (center).
================================================================ -->
<div id="divHeader">
<div id="divHeaderFlexLeft">
<!-- Hamburger is now separate fixed element -->
</div>
<div id="divHeaderFlexCenter">
<div class="divHeaderText"></div>
</div>
<div id="divHeaderFlexRight">
<!-- No button in header right - logout is in sidenav footer -->
</div>
</div>
<!-- ================================================================
BODY
================================================================
Main content area. Add your page-specific content here.
================================================================ -->
<div id="divBody">
</div>
<!-- ================================================================
FOOTER
================================================================
Three-section footer layout:
- Left: Relay status animations (HamburgerMorphing instances)
- Center: General status information
- Right: Additional information
================================================================ -->
<div id="divFooter">
<div id="divFooterLeft" class="divFooterBox"></div>
<div id="divFooterCenter" class="divFooterBox"></div>
<div id="divFooterRight" class="divFooterBox"></div>
<div id="divFooterBalance" class="divFooterBox">0 sats</div>
</div>
<!-- ================================================================
SIDENAV
================================================================
Slide-out navigation panel. Opens from left when hamburger clicked.
Uses flexbox layout to pin version bar to bottom.
Includes a version bar footer with theme toggle and logout buttons.
================================================================ -->
<div id="divSideNav">
<div id="divSideNavHeader">
<!-- No close button - use main hamburger to close -->
</div>
<div id="divSideNavBody">
<div id="divFiles"></div>
</div>
<div id="divAiSection" class="sidenavSection">
<div id="divAiSectionTitle" class="sidenavSectionTitle">AI</div>
<div id="divAiList" class="sidenavSectionList">
<div id="divAiProvidersList">No saved providers yet.</div>
</div>
</div>
<div id="divRelaySection">
<div id="divRelaySectionTitle">
リレー
</div>
<div id="divRelayList">
Loading relays...
</div>
</div>
<div id="divBlossomSection">
<div id="divBlossomSectionTitle">ブロッサム</div>
<div id="divBlossomList">Loading blossom servers...</div>
</div>
<div id="divVersionBar">
<span id="versionDisplay">v0.0.1</span>
<div id="divVersionBarButtons">
<button id="themeToggleButton" title="Toggle Dark/Light Mode">
<div id="themeToggleHamburgerContainer"></div>
</button>
<button id="logoutButton" title="Logout">
<div id="logoutHamburgerContainer"></div>
</button>
</div>
</div>
</div>
<!-- ================================================================
REQUIRED SCRIPTS
================================================================
These scripts must be loaded in this order:
1. nostr.bundle.js - Nostr tools library
2. nostr-lite.js - Authentication modal (nostr-login-lite)
================================================================ -->
<script src="./nostr.bundle.js"></script>
<script src="./nostr-lite.js"></script>
<script type="module">
/* ================================================================
IMPORTS
================================================================
Import shared NDK functionality from init-ndk.mjs:
- initNDKPage() - Initialize authentication and worker
- getPubkey() - Get current user's pubkey
- subscribe() - Create NDK subscriptions
- publishEvent() - Publish events via NDK
- disconnect() - Disconnect from worker
- getRelayData() - Get relay connection data
- getRelayStats() - Get relay activity statistics
Import HamburgerMorphing for animated icons
================================================================ */
import {
initNDKPage,
getPubkey, injectHeaderAvatar,
subscribe,
publishEvent,
disconnect,
getVersion,
updateVersionDisplay,
getUserSettings,
patchUserSettings,
onUserSettings
} from './js/init-ndk.mjs';
import { HamburgerMorphing } from "./hamburger_morphing/hamburger.mjs";
import { initFooterRelayStatus, updateFooterRelayStatus, initSidenavRelaySection, updateSidenavRelaySection, setRelayActivityState } from './js/relay-ui.mjs';
import { initBlossomSection, updateBlossomSection } from './js/blossom-ui.mjs';
import { initAiSectionWithLocalConfig } from './js/ai-ui.mjs';
// Version will be loaded asynchronously
const versionInfo = await getVersion();
const VERSION = versionInfo.VERSION;
console.log(`[template.html ${VERSION}] Loading...`);
/* ================================================================
GLOBAL VARIABLES
================================================================
Track state for hamburger menu, relay status, and theme.
================================================================ */
let updateIntervalId = null;
let currentPubkey = null;
/*
AUTH STATE MODEL (Template reference)
------------------------------------------------------------------
This template now demonstrates three auth modes for standalone pages:
- required (default):
Behaves like existing pages: login is required immediately.
- optional:
Page can render public/read-only data without login, but can still
prompt login later for user actions (publish, settings, etc).
- none:
Never auto-login on load (pure public page).
URL behavior in this template:
- If ?auth=required|optional|none is present, it wins.
- Otherwise, if URL includes ?npub=... or ?pubkey=..., mode defaults
to optional because pages with explicit profile targets are commonly
public-readable.
- Otherwise, mode defaults to required.
*/
let isAuthenticated = false;
let authMode = 'required';
let authedPageInitialized = false;
let relayActivityListenersBound = false;
// Hamburger menu
let hamburgerInstance = null;
let isNavOpen = false;
// Version bar buttons
let logoutHamburger = null;
let themeToggleHamburger = null;
let isDarkMode = false;
// App-wide user settings (NIP-78 kind 30078, d:user-settings)
let pageSettings = {};
let unsubscribeUserSettings = null;
/* ================================================================
DOM VARIABLES
================================================================
Cache DOM element references for better performance.
================================================================ */
const divBody = document.getElementById("divBody");
const divSideNav = document.getElementById("divSideNav");
const divSideNavBody = document.getElementById("divSideNavBody");
const divFooterCenter = document.getElementById("divFooterCenter");
const divFooterRight = document.getElementById("divFooterRight");
/* ================================================================
HAMBURGER MENU
================================================================
Initialize and control the animated hamburger menu.
================================================================ */
function initHamburgerMenu() {
hamburgerInstance = new HamburgerMorphing('#divSvgHam', {
foreground: 'var(--primary-color)',
background: 'var(--secondary-color)',
hover: 'var(--accent-color)'
});
hamburgerInstance.animateTo('burger');
}
/* ================================================================
SIDENAV FUNCTIONS
================================================================
Open/close sidenav with hamburger morphing animation.
================================================================ */
function openNav() {
divSideNav.style.zIndex = 3;
divSideNav.style.width = "clamp(400px, 50vw, 600px)";
isNavOpen = true;
if (hamburgerInstance) {
hamburgerInstance.animateTo('arrow_left');
}
// Initialize version bar buttons when sidenav opens (lazy load)
if (!logoutHamburger) {
logoutHamburger = new HamburgerMorphing('#logoutHamburgerContainer', {
size: 24,
foreground: 'var(--primary-color)',
background: 'var(--secondary-color)',
hover: 'var(--accent-color)'
});
logoutHamburger.animateTo('x');
}
if (!themeToggleHamburger) {
themeToggleHamburger = new HamburgerMorphing('#themeToggleHamburgerContainer', {
size: 24,
foreground: 'var(--primary-color)',
background: 'var(--secondary-color)',
hover: 'var(--accent-color)'
});
// Determine current theme
const savedTheme = localStorage.getItem('theme');
isDarkMode = savedTheme === 'dark' || document.body.classList.contains('dark-mode');
const initialShape = isDarkMode ? 'moon' : 'circle';
themeToggleHamburger.animateTo(initialShape);
}
}
function closeNav() {
divSideNav.style.width = "0vw";
divSideNav.style.zIndex = -1;
isNavOpen = false;
if (hamburgerInstance) {
hamburgerInstance.animateTo('burger');
}
}
function toggleNav() {
if (isNavOpen) {
closeNav();
} else {
openNav();
}
}
/* ================================================================
AUTH MODE HELPERS
================================================================
These functions are meant as reusable guidance for future pages.
================================================================ */
function hasTargetPubkeyInUrl() {
const params = new URLSearchParams(window.location.search || '');
const npub = String(params.get('npub') || '').trim();
const pubkey = String(params.get('pubkey') || '').trim();
return Boolean(npub || pubkey);
}
function resolveAuthModeFromUrl() {
const params = new URLSearchParams(window.location.search || '');
const explicitAuth = String(params.get('auth') || '').trim().toLowerCase();
if (explicitAuth === 'required' || explicitAuth === 'optional' || explicitAuth === 'none') {
return explicitAuth;
}
// Convention: explicit target profiles are public-readable by default.
if (hasTargetPubkeyInUrl()) {
return 'optional';
}
return 'required';
}
function isAuthRequiredError(error) {
const message = String(error?.message || error || '').toLowerCase();
return message.includes('authentication required');
}
async function initializeAuthentication(mode) {
// required: existing behavior, throw if auth fails.
if (mode === 'required') {
await initNDKPage();
currentPubkey = await getPubkey();
isAuthenticated = true;
return;
}
// none: public page, no login attempt on load.
if (mode === 'none') {
isAuthenticated = false;
currentPubkey = null;
return;
}
// optional: try silent/normal init; if auth required, continue public.
try {
await initNDKPage();
currentPubkey = await getPubkey();
isAuthenticated = true;
} catch (error) {
if (isAuthRequiredError(error)) {
console.log('[template.html] Optional auth mode: continuing unauthenticated');
isAuthenticated = false;
currentPubkey = null;
return;
}
throw error;
}
}
async function initializeAuthenticatedPageFeatures() {
if (!isAuthenticated || authedPageInitialized) return;
await injectHeaderAvatar(currentPubkey);
console.log('[template.html] Authenticated as:', currentPubkey);
// Hydrate app-wide user settings for this page
try {
pageSettings = await getUserSettings();
} catch (error) {
console.warn('[template.html] getUserSettings failed:', error);
pageSettings = {};
}
// Subscribe to live user settings updates (cross-tab + publish echoes)
if (!unsubscribeUserSettings) {
unsubscribeUserSettings = onUserSettings((settings) => {
pageSettings = settings || {};
// TODO: Re-render page-specific UI from pageSettings here.
});
}
// Initialize relay-dependent UI only once authenticated.
initFooterRelayStatus();
initSidenavRelaySection();
await initBlossomSection();
initAiSectionWithLocalConfig();
await UpdateFooter();
if (!updateIntervalId) {
updateIntervalId = setInterval(UpdateFooter, 1000);
}
// Relay activity listeners only matter after worker init/auth.
if (!relayActivityListenersBound) {
window.addEventListener('ndkRelayActivity', (event) => {
const { relayUrl, activity, stats } = event.detail;
console.log(`[template.html] Relay activity: ${relayUrl} - ${activity}`, stats);
setRelayActivityState(relayUrl, activity);
});
window.addEventListener('message', (event) => {
if (event.data && event.data.type === 'relayActivity') {
const { relayUrl, activity } = event.data;
console.log(`[template.html] Relay activity: ${relayUrl} - ${activity}`);
setRelayActivityState(relayUrl, activity);
}
});
relayActivityListenersBound = true;
}
authedPageInitialized = true;
}
async function promptLoginIfNeeded() {
if (isAuthenticated) return true;
await initNDKPage();
currentPubkey = await getPubkey();
isAuthenticated = true;
await initializeAuthenticatedPageFeatures();
return true;
}
/* ================================================================
UPDATE FOOTER
================================================================
Update footer sections with relay status, pubkey, and other info.
Called periodically by update loop.
================================================================ */
const UpdateFooter = async () => {
try {
// Update relay status visuals in footer and sidenav
await updateFooterRelayStatus();
await updateSidenavRelaySection();
await updateBlossomSection();
// Clear center and right sections
divFooterCenter.innerHTML = '';
divFooterRight.innerHTML = '';
} catch (error) {
console.error('[template.html] Error updating footer:', error);
}
};
/* ================================================================
LOGOUT
================================================================
Complete logout process:
1. Stop update loop
2. Disconnect from NDK worker
3. Logout from nostr-login-lite
4. Clear all storage (localStorage, sessionStorage, IndexedDB)
5. Reload page
================================================================ */
const Logout = async () => {
console.log("[template.html] Starting logout process...");
// Stop the update loop
if (updateIntervalId) {
clearInterval(updateIntervalId);
updateIntervalId = null;
}
// Disconnect from worker
disconnect();
// Logout from nostr-login-lite
if (window.NOSTR_LOGIN_LITE && window.NOSTR_LOGIN_LITE.logout) {
await window.NOSTR_LOGIN_LITE.logout();
}
// Clear all storage
localStorage.clear();
sessionStorage.clear();
// Clear IndexedDB
if (window.indexedDB) {
const databases = await window.indexedDB.databases();
for (const db of databases) {
if (db.name) {
window.indexedDB.deleteDatabase(db.name);
}
}
}
console.log("[template.html] Logged out, reloading page");
location.reload(true);
};
/* ================================================================
EVENT LISTENERS
================================================================
Wire up UI interactions.
Main hamburger button click handler is set up in main() after initialization.
================================================================ */
/* ================================================================
SUBSCRIPTION EXAMPLE
================================================================
Example of how to subscribe to Nostr events:
const sub = subscribe(
{ kinds: [1], authors: [pubkey], limit: 10 },
{ closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
);
Cache usage options:
- 'CACHE_FIRST' - Check cache first, then relays
- 'ONLY_RELAY' - Only query relays
- 'ONLY_CACHE' - Only query cache
- 'PARALLEL' - Query cache and relays simultaneously
Listen for events via window events:
window.addEventListener('ndkEvent', (event) => {
const evt = event.detail;
console.log('Received event:', evt);
});
================================================================ */
/* ================================================================
PUBLISH EXAMPLE
================================================================
Example of how to publish a Nostr event:
const event = {
created_at: Math.floor(Date.now() / 1000),
kind: 1,
tags: [],
content: "Hello, Nostr!"
};
try {
const result = await publishEvent(event);
console.log("✅ Published to:", result.relayResults.successful);
console.log("❌ Failed:", result.relayResults.failed);
console.log("Total relays:", result.totalRelays);
} catch (error) {
console.error("Publish error:", error);
}
Note: Events are automatically signed by the NDK worker using
the message-based signer (which calls window.nostr.signEvent).
================================================================ */
/* ================================================================
USER SETTINGS EXAMPLE (NIP-78)
================================================================
Read/subscribe/write helper pattern for all pages:
// Read latest merged settings (cache + relay hydrated by worker)
const settings = await getUserSettings();
// Subscribe to cross-tab updates
const unsubscribe = onUserSettings((nextSettings) => {
// Re-render page from nextSettings
});
// Patch only your feature namespace
await patchUserSettings({
myFeature: {
someFlag: true
}
});
// On page teardown (if applicable)
// unsubscribe();
================================================================ */
/* ================================================================
INITIALIZATION
================================================================
Main initialization sequence:
1. Initialize hamburger menu
2. Set up hamburger click handler
3. Resolve auth mode from URL/query policy
4. Initialize authentication based on mode
5. Initialize authenticated-only features (if signed in)
6. Set up version bar button listeners
7. Restore sidenav state
8. Update version display
Notes:
- required mode = existing behavior (prompt login on load)
- optional mode = allow public load, login later on demand
- none mode = no auto-login on load
================================================================ */
(async function main() {
console.log("[template.html] Starting initialization...");
try {
// Initialize hamburger menu first
initHamburgerMenu();
// Add click handler to hamburger
const divSvgHam = document.getElementById('divSvgHam');
if (divSvgHam) {
divSvgHam.addEventListener('click', toggleNav);
}
// Initialize version bar buttons
const themeToggleButton = document.getElementById('themeToggleButton');
const logoutButton = document.getElementById('logoutButton');
if (themeToggleButton) {
themeToggleButton.addEventListener('click', () => {
isDarkMode = !isDarkMode;
if (isDarkMode) {
localStorage.setItem('theme', 'dark');
} else {
localStorage.setItem('theme', 'light');
}
// Save sidenav state before reload
localStorage.setItem('sidenavWasOpen', isNavOpen ? 'true' : 'false');
window.location.reload();
});
}
if (logoutButton) {
logoutButton.addEventListener('click', async () => {
try {
// In optional/none modes this doubles as a "Sign in" entry point.
if (!isAuthenticated) {
await promptLoginIfNeeded();
return;
}
await Logout();
} catch (error) {
console.error('Logout/login action failed:', error);
}
});
}
// Resolve and initialize page auth policy.
authMode = resolveAuthModeFromUrl();
console.log('[template.html] Resolved auth mode:', authMode);
await initializeAuthentication(authMode);
// Initialize authenticated features only when signed in.
await initializeAuthenticatedPageFeatures();
/* ============================================================
EXAMPLE: Subscribe to events
============================================================
Uncomment to subscribe to user's notes:
const notesSub = subscribe(
{ kinds: [1], authors: [currentPubkey], limit: 10 },
{ closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
);
console.log("[template.html] Subscribed to kind 1");
============================================================ */
/* ============================================================
EXAMPLE: Listen for events from worker
============================================================
Uncomment to handle incoming events:
window.addEventListener('ndkEvent', (event) => {
const evt = event.detail;
console.log("[template.html] Received event:", evt.kind, evt.pubkey);
if (evt.pubkey === currentPubkey) {
if (evt.kind === 1) {
// Handle note event
console.log("Note:", evt.content);
}
}
});
============================================================ */
/* ============================================================
EXAMPLE: Listen for cached profile
============================================================
Uncomment to handle cached profile data:
window.addEventListener('ndkProfile', (event) => {
console.log("[template.html] Cached profile:", event.detail);
// event.detail contains profile object (name, about, etc.)
});
============================================================ */
// Restore sidenav state if it was open before theme toggle
const sidenavWasOpen = localStorage.getItem('sidenavWasOpen');
if (sidenavWasOpen === 'true') {
localStorage.removeItem('sidenavWasOpen');
openNav();
}
// Optional UX note for public mode pages.
if (!isAuthenticated && (authMode === 'optional' || authMode === 'none')) {
divFooterCenter.textContent = 'Public mode';
divFooterRight.textContent = 'Sign in from side menu for private features';
}
// Update version display
await updateVersionDisplay();
console.log('[template.html] Initialization complete');
} catch (error) {
console.error('[template.html] Initialization failed:', error);
divBody.innerHTML = `<div style="text-align: center; padding: 50px;">
<div style="font-size: 24px; margin-bottom: 20px; color: red;">❌ Authentication Error</div>
<div style="font-size: 16px; color: #666;">${error.message}</div>
<div style="margin-top: 20px;">
<button onclick="location.reload()" style="padding: 10px 20px; font-size: 16px;">Retry</button>
</div>
</div>`;
}
})();
/* ================================================================
WORKER MESSAGE TYPES
================================================================
The NDK worker can send these message types:
1. 'response' - Response to init/subscribe/publish requests
- data.profile - User profile (from init)
- data.relays - User relays (from init)
- data.success - Publish success status
- data.relayResults - Relay publish results
2. 'event' - Nostr event from subscription
- Dispatched as 'ndkEvent' window event
- event.detail contains the Nostr event
3. 'eose' - End of stored events for subscription
- Dispatched as 'ndkEose' window event
- event.detail.subId contains subscription ID
4. 'signRequest' - Request to sign event/encrypt/decrypt
- Handled automatically by init-ndk.mjs
- Calls window.nostr methods and sends response
5. 'error' - Error from worker
- Logged to console automatically
6. 'relayActivity' - Relay read/write activity notification
- Dispatched as 'ndkRelayActivity' window event
- Used to animate relay status icons in footer
================================================================ */
/* ================================================================
DISTRIBUTED ARCHITECTURE NOTES
================================================================
Each page is independently accessible and self-contained:
1. Authentication persists via nostr-login-lite localStorage
- Login once on any page
- All other pages automatically authenticated
2. NDK SharedWorker is shared across all tabs/pages
- Single NDK instance manages all connections
- Subscriptions from all pages handled by one worker
- Events broadcast to all connected pages
3. Dexie cache is shared across all pages
- IndexedDB persists across sessions
- Cache-first queries are fast
- Reduces relay load
4. User settings are centralized and shared
- Worker hydrates kind 30078 (`d:user-settings`) on init
- Pages read via getUserSettings()
- Pages patch via patchUserSettings({ featureNamespace: ... })
- Pages subscribe via onUserSettings() for live updates
5. Each page can be distributed independently
- Copy template.html and customize
- No dependencies on other pages
- Works standalone or as part of suite
6. Message-based signer bridges worker and page
- Worker's NDK uses MessageBasedSigner
- Signer sends sign requests to page
- Page calls window.nostr.signEvent()
- Response sent back to worker
- NDK completes signing and publishing
7. Relay status visualization
- Footer left section shows connected relays
- Each relay has animated icon (HamburgerMorphing)
- Icons morph based on activity (read/write)
- Temporary animations show real-time activity
================================================================ */
</script>
</body>
</html>
+463
View File
@@ -0,0 +1,463 @@
/**
* ASCIIBarChart - A dynamic ASCII-based vertical bar chart renderer
*
* Creates real-time animated bar charts using monospaced characters (X)
* with automatic scaling, labels, and responsive font sizing.
*/
class ASCIIBarChart {
/**
* Create a new ASCII bar chart
* @param {string} containerId - The ID of the HTML element to render the chart in
* @param {Object} options - Configuration options
* @param {number} [options.maxHeight=20] - Maximum height of the chart in rows
* @param {number} [options.maxDataPoints=30] - Maximum number of data columns before scrolling
* @param {string} [options.title=''] - Chart title (displayed centered at top)
* @param {string} [options.xAxisLabel=''] - X-axis label (displayed centered at bottom)
* @param {string} [options.yAxisLabel=''] - Y-axis label (displayed vertically on left)
* @param {boolean} [options.autoFitWidth=true] - Automatically adjust font size to fit container width
* @param {boolean} [options.useBinMode=false] - Enable time bin mode for data aggregation
* @param {number} [options.binDuration=10000] - Duration of each time bin in milliseconds (10 seconds default)
* @param {string} [options.xAxisLabelFormat='elapsed'] - X-axis label format: 'elapsed', 'bins', 'timestamps', 'ranges'
* @param {boolean} [options.debug=false] - Enable debug logging
*/
constructor(containerId, options = {}) {
this.container = document.getElementById(containerId);
this.data = [];
this.maxHeight = options.maxHeight || 20;
this.maxDataPoints = options.maxDataPoints || 30;
this.totalDataPoints = 0; // Track total number of data points added
this.title = options.title || '';
this.xAxisLabel = options.xAxisLabel || '';
this.yAxisLabel = options.yAxisLabel || '';
this.autoFitWidth = options.autoFitWidth !== false; // Default to true
this.debug = options.debug || false; // Debug logging option
// Time bin configuration
this.useBinMode = options.useBinMode !== false; // Default to true
this.binDuration = options.binDuration || 4000; // 4 seconds default
this.xAxisLabelFormat = options.xAxisLabelFormat || 'elapsed';
// Time bin data structures
this.bins = [];
this.currentBinIndex = -1;
this.binStartTime = null;
this.binCheckInterval = null;
this.chartStartTime = Date.now();
// Set up resize observer if auto-fit is enabled
if (this.autoFitWidth) {
this.resizeObserver = new ResizeObserver(() => {
this.adjustFontSize();
});
this.resizeObserver.observe(this.container);
}
// Initialize first bin if bin mode is enabled
if (this.useBinMode) {
this.initializeBins();
}
}
/**
* Add a new data point to the chart
* @param {number} value - The numeric value to add
*/
addValue(value) {
// Time bin mode: add value to current active bin count
this.checkBinRotation(); // Ensure we have an active bin
this.bins[this.currentBinIndex].count += value; // Changed from ++ to += value
this.totalDataPoints++;
this.render();
this.updateInfo();
}
/**
* Clear all data from the chart
*/
clear() {
this.data = [];
this.totalDataPoints = 0;
if (this.useBinMode) {
this.bins = [];
this.currentBinIndex = -1;
this.binStartTime = null;
this.initializeBins();
}
this.render();
this.updateInfo();
}
/**
* Calculate the width of the chart in characters
* @returns {number} The chart width in characters
* @private
*/
getChartWidth() {
let dataLength = this.maxDataPoints; // Always use maxDataPoints for consistent width
if (dataLength === 0) return 50; // Default width for empty chart
const yAxisPadding = this.yAxisLabel ? 2 : 0;
const yAxisNumbers = 3; // Width of Y-axis numbers
const separator = 1; // The '|' character
// const dataWidth = dataLength * 2; // Each column is 2 characters wide // TEMP: commented for no-space test
const dataWidth = dataLength; // Each column is 1 character wide // TEMP: adjusted for no-space columns
const padding = 1; // Extra padding
const totalWidth = yAxisPadding + yAxisNumbers + separator + dataWidth + padding;
// Only log when width changes
if (this.debug && this.lastChartWidth !== totalWidth) {
console.log('getChartWidth changed:', { dataLength, totalWidth, previous: this.lastChartWidth });
this.lastChartWidth = totalWidth;
}
return totalWidth;
}
/**
* Adjust font size to fit container width
* @private
*/
adjustFontSize() {
if (!this.autoFitWidth) return;
const containerWidth = this.container.clientWidth;
const chartWidth = this.getChartWidth();
if (chartWidth === 0) return;
// Calculate optimal font size
// For monospace fonts, character width is approximately 0.6 * font size
// Use a slightly smaller ratio to fit more content
const charWidthRatio = 0.7;
const padding = 30; // Reduce padding to fit more content
const availableWidth = containerWidth - padding;
const optimalFontSize = Math.floor((availableWidth / chartWidth) / charWidthRatio);
// Set reasonable bounds (min 4px, max 20px)
const fontSize = Math.max(4, Math.min(20, optimalFontSize));
// Only log when font size changes
if (this.debug && this.lastFontSize !== fontSize) {
console.log('fontSize changed:', { containerWidth, chartWidth, fontSize, previous: this.lastFontSize });
this.lastFontSize = fontSize;
}
this.container.style.fontSize = fontSize + 'px';
this.container.style.lineHeight = '1.0';
}
/**
* Render the chart to the container
* @private
*/
render() {
let dataToRender = [];
let maxValue = 0;
let minValue = 0;
let valueRange = 0;
if (this.useBinMode) {
// Bin mode: render bin counts
if (this.bins.length === 0) {
this.container.textContent = 'No data yet. Click Start to begin.';
return;
}
// Always create a fixed-length array filled with 0s, then overlay actual bin data
dataToRender = new Array(this.maxDataPoints).fill(0);
// Overlay actual bin data (most recent bins, reversed for left-to-right display)
const startIndex = Math.max(0, this.bins.length - this.maxDataPoints);
const recentBins = this.bins.slice(startIndex);
// Reverse the bins so most recent is on the left, and overlay onto the fixed array
recentBins.reverse().forEach((bin, index) => {
if (index < this.maxDataPoints) {
dataToRender[index] = bin.count;
}
});
if (this.debug) {
console.log('render() dataToRender:', dataToRender, 'bins length:', this.bins.length);
}
maxValue = Math.max(...dataToRender);
minValue = Math.min(...dataToRender);
valueRange = maxValue - minValue;
} else {
// Legacy mode: render individual values
if (this.data.length === 0) {
this.container.textContent = 'No data yet. Click Start to begin.';
return;
}
dataToRender = this.data;
maxValue = Math.max(...this.data);
minValue = Math.min(...this.data);
valueRange = maxValue - minValue;
}
let output = '';
const scale = this.maxHeight;
// Calculate scaling factor: each X represents at least 1 count
const maxCount = Math.max(...dataToRender);
const scaleFactor = Math.max(1, Math.ceil(maxCount / scale)); // 1 X = scaleFactor counts
const scaledMax = Math.ceil(maxCount / scaleFactor) * scaleFactor;
// Calculate Y-axis label width (for vertical text)
const yLabelWidth = this.yAxisLabel ? 2 : 0;
const yAxisPadding = this.yAxisLabel ? ' ' : '';
// Add title if provided (centered)
if (this.title) {
// const chartWidth = 4 + this.maxDataPoints * 2; // Y-axis numbers + data columns // TEMP: commented for no-space test
const chartWidth = 4 + this.maxDataPoints; // Y-axis numbers + data columns // TEMP: adjusted for no-space columns
const titlePadding = Math.floor((chartWidth - this.title.length) / 2);
output += yAxisPadding + ' '.repeat(Math.max(0, titlePadding)) + this.title + '\n\n';
}
// Draw from top to bottom
for (let row = scale; row > 0; row--) {
let line = '';
// Add vertical Y-axis label character
if (this.yAxisLabel) {
const L = this.yAxisLabel.length;
const startRow = Math.floor((scale - L) / 2) + 1;
const relativeRow = scale - row + 1; // 1 at top, scale at bottom
if (relativeRow >= startRow && relativeRow < startRow + L) {
const labelIndex = relativeRow - startRow;
line += this.yAxisLabel[labelIndex] + ' ';
} else {
line += ' ';
}
}
// Calculate the actual count value this row represents (1 at bottom, increasing upward)
const rowCount = (row - 1) * scaleFactor + 1;
// Add Y-axis label (show actual count values)
line += String(rowCount).padStart(3, ' ') + ' |';
// Draw each column
for (let i = 0; i < dataToRender.length; i++) {
const count = dataToRender[i];
const scaledHeight = Math.ceil(count / scaleFactor);
if (scaledHeight >= row) {
// line += ' X'; // TEMP: commented out space between columns
line += 'X'; // TEMP: no space between columns
} else {
// line += ' '; // TEMP: commented out space between columns
line += ' '; // TEMP: single space for empty columns
}
}
output += line + '\n';
}
// Draw X-axis
// output += yAxisPadding + ' +' + '-'.repeat(this.maxDataPoints * 2) + '\n'; // TEMP: commented out for no-space test
output += yAxisPadding + ' +' + '-'.repeat(this.maxDataPoints) + '\n'; // TEMP: back to original length
// Draw X-axis labels based on mode and format
let xAxisLabels = yAxisPadding + ' '; // Initial padding to align with X-axis
// Determine label interval (every 5 columns)
const labelInterval = 5;
// Generate all labels first and store in array
let labels = [];
for (let i = 0; i < this.maxDataPoints; i++) {
if (i % labelInterval === 0) {
let label = '';
if (this.useBinMode) {
// For bin mode, show labels for all possible positions
// i=0 is leftmost (most recent), i=maxDataPoints-1 is rightmost (oldest)
const elapsedSec = (i * this.binDuration) / 1000;
// Format with appropriate precision for sub-second bins
if (this.binDuration < 1000) {
// Show decimal seconds for sub-second bins
label = elapsedSec.toFixed(1) + 's';
} else {
// Show whole seconds for 1+ second bins
label = String(Math.round(elapsedSec)) + 's';
}
} else {
// For legacy mode, show data point numbers
const startIndex = Math.max(1, this.totalDataPoints - this.maxDataPoints + 1);
label = String(startIndex + i);
}
labels.push(label);
}
}
// Build the label string with calculated spacing
for (let i = 0; i < labels.length; i++) {
const label = labels[i];
xAxisLabels += label;
// Add spacing: labelInterval - label.length (except for last label)
if (i < labels.length - 1) {
const spacing = labelInterval - label.length;
xAxisLabels += ' '.repeat(spacing);
}
}
// Ensure the label line extends to match the X-axis dash line length
// The dash line is this.maxDataPoints characters long, starting after " +"
const dashLineLength = this.maxDataPoints;
const minLabelLineLength = yAxisPadding.length + 4 + dashLineLength; // 4 for " "
if (xAxisLabels.length < minLabelLineLength) {
xAxisLabels += ' '.repeat(minLabelLineLength - xAxisLabels.length);
}
output += xAxisLabels + '\n';
// Add X-axis label if provided
if (this.xAxisLabel) {
// const labelPadding = Math.floor((this.maxDataPoints * 2 - this.xAxisLabel.length) / 2); // TEMP: commented for no-space test
const labelPadding = Math.floor((this.maxDataPoints - this.xAxisLabel.length) / 2); // TEMP: adjusted for no-space columns
output += '\n' + yAxisPadding + ' ' + ' '.repeat(Math.max(0, labelPadding)) + this.xAxisLabel + '\n';
}
this.container.textContent = output;
// Adjust font size to fit width (only once at initialization)
if (this.autoFitWidth) {
this.adjustFontSize();
}
// Update the external info display
if (this.useBinMode) {
const binCounts = this.bins.map(bin => bin.count);
const scaleFactor = Math.max(1, Math.ceil(maxValue / scale));
document.getElementById('values').textContent = `[${dataToRender.join(', ')}]`;
document.getElementById('max-value').textContent = maxValue;
document.getElementById('scale').textContent = `Min: ${minValue}, Max: ${maxValue}, 1X=${scaleFactor} counts`;
} else {
document.getElementById('values').textContent = `[${this.data.join(', ')}]`;
document.getElementById('max-value').textContent = maxValue;
document.getElementById('scale').textContent = `Min: ${minValue}, Max: ${maxValue}, Height: ${scale}`;
}
}
/**
* Update the info display
* @private
*/
updateInfo() {
if (this.useBinMode) {
const totalCount = this.bins.reduce((sum, bin) => sum + bin.count, 0);
document.getElementById('count').textContent = totalCount;
} else {
document.getElementById('count').textContent = this.data.length;
}
}
/**
* Initialize the bin system
* @private
*/
initializeBins() {
this.bins = [];
this.currentBinIndex = -1;
this.binStartTime = null;
this.chartStartTime = Date.now();
// Create first bin
this.rotateBin();
// Set up automatic bin rotation check
this.binCheckInterval = setInterval(() => {
this.checkBinRotation();
}, 100); // Check every 100ms for responsiveness
}
/**
* Check if current bin should rotate and create new bin if needed
* @private
*/
checkBinRotation() {
if (!this.useBinMode || !this.binStartTime) return;
const now = Date.now();
if ((now - this.binStartTime) >= this.binDuration) {
this.rotateBin();
}
}
/**
* Rotate to a new bin, finalizing the current one
*/
rotateBin() {
// Finalize current bin if it exists
if (this.currentBinIndex >= 0) {
this.bins[this.currentBinIndex].isActive = false;
}
// Create new bin
const newBin = {
startTime: Date.now(),
count: 0,
isActive: true
};
this.bins.push(newBin);
this.currentBinIndex = this.bins.length - 1;
this.binStartTime = newBin.startTime;
// Keep only the most recent bins
if (this.bins.length > this.maxDataPoints) {
this.bins.shift();
this.currentBinIndex--;
}
// Ensure currentBinIndex points to the last bin (the active one)
this.currentBinIndex = this.bins.length - 1;
// Force a render to update the display immediately
this.render();
this.updateInfo();
}
/**
* Format X-axis label for a bin based on the configured format
* @param {number} binIndex - Index of the bin
* @returns {string} Formatted label
* @private
*/
formatBinLabel(binIndex) {
const bin = this.bins[binIndex];
if (!bin) return ' ';
switch (this.xAxisLabelFormat) {
case 'bins':
return String(binIndex + 1).padStart(2, ' ');
case 'timestamps':
const time = new Date(bin.startTime);
return time.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).replace(/:/g, '');
case 'ranges':
const startSec = Math.floor((bin.startTime - this.chartStartTime) / 1000);
const endSec = startSec + Math.floor(this.binDuration / 1000);
return `${startSec}-${endSec}`;
case 'elapsed':
default:
// For elapsed time, always show time relative to the first bin (index 0)
// This keeps the leftmost label as 0s and increases to the right
const firstBinTime = this.bins[0] ? this.bins[0].startTime : this.chartStartTime;
const elapsedSec = Math.floor((bin.startTime - firstBinTime) / 1000);
return String(elapsedSec).padStart(2, ' ') + 's';
}
}
}
-616
View File
@@ -1,616 +0,0 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_status() { echo -e "${BLUE}[INFO]${NC} $1"; }
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Global variables
COMMIT_MESSAGE=""
RELEASE_MODE=false
show_usage() {
echo "C-Relay Build and Push Script"
echo ""
echo "Usage:"
echo " $0 \"commit message\" - Default: compile, increment patch, commit & push"
echo " $0 -r \"commit message\" - Release: compile x86+arm64, increment minor, create release"
echo ""
echo "Examples:"
echo " $0 \"Fixed event validation bug\""
echo " $0 --release \"Major release with new features\""
echo ""
echo "Default Mode (patch increment):"
echo " - Compile C-Relay"
echo " - Increment patch version (v1.2.3 → v1.2.4)"
echo " - Git add, commit with message, and push"
echo ""
echo "Release Mode (-r flag):"
echo " - Compile C-Relay for x86_64 and arm64 (dynamic and static versions)"
echo " - Increment minor version, zero patch (v1.2.3 → v1.3.0)"
echo " - Git add, commit, push, and create Gitea release"
echo ""
echo "Requirements for Release Mode:"
echo " - For ARM64 builds: make install-arm64-deps (optional - will build x86_64 only if missing)"
echo " - For static builds: sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev"
echo " - Gitea token in ~/.gitea_token for release uploads"
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-r|--release)
RELEASE_MODE=true
shift
;;
-h|--help)
show_usage
exit 0
;;
*)
# First non-flag argument is the commit message
if [[ -z "$COMMIT_MESSAGE" ]]; then
COMMIT_MESSAGE="$1"
fi
shift
;;
esac
done
# Validate inputs
if [[ -z "$COMMIT_MESSAGE" ]]; then
print_error "Commit message is required"
echo ""
show_usage
exit 1
fi
# Check if we're in a git repository
check_git_repo() {
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_error "Not in a git repository"
exit 1
fi
}
# Function to get current version and increment appropriately
increment_version() {
local increment_type="$1" # "patch" or "minor"
print_status "Getting current version..."
# Get the highest version tag (not chronologically latest)
LATEST_TAG=$(git tag -l 'v*.*.*' | sort -V | tail -n 1 || echo "")
if [[ -z "$LATEST_TAG" ]]; then
LATEST_TAG="v0.0.0"
print_warning "No version tags found, starting from $LATEST_TAG"
fi
# Extract version components (remove 'v' prefix)
VERSION=${LATEST_TAG#v}
# Parse major.minor.patch using regex
if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
MAJOR=${BASH_REMATCH[1]}
MINOR=${BASH_REMATCH[2]}
PATCH=${BASH_REMATCH[3]}
else
print_error "Invalid version format in tag: $LATEST_TAG"
print_error "Expected format: v0.1.0"
exit 1
fi
# Increment version based on type
if [[ "$increment_type" == "minor" ]]; then
# Minor release: increment minor, zero patch
NEW_MINOR=$((MINOR + 1))
NEW_PATCH=0
NEW_VERSION="v${MAJOR}.${NEW_MINOR}.${NEW_PATCH}"
print_status "Release mode: incrementing minor version"
else
# Default: increment patch
NEW_PATCH=$((PATCH + 1))
NEW_VERSION="v${MAJOR}.${MINOR}.${NEW_PATCH}"
print_status "Default mode: incrementing patch version"
fi
print_status "Current version: $LATEST_TAG"
print_status "New version: $NEW_VERSION"
# Export for use in other functions
export NEW_VERSION
}
# Function to compile the C-Relay project
compile_project() {
print_status "Compiling C-Relay..."
# Clean previous build
if make clean > /dev/null 2>&1; then
print_success "Cleaned previous build"
else
print_warning "Clean failed or no Makefile found"
fi
# Force regenerate main.h to pick up new tags
if make force-version > /dev/null 2>&1; then
print_success "Regenerated main.h"
else
print_warning "Failed to regenerate main.h"
fi
# Compile the project
if make > /dev/null 2>&1; then
print_success "C-Relay compiled successfully"
else
print_error "Compilation failed"
exit 1
fi
}
# Function to build release binaries
build_release_binaries() {
print_status "Building release binaries..."
# Build x86_64 version
print_status "Building x86_64 version..."
make clean > /dev/null 2>&1
if make x86 > /dev/null 2>&1; then
if [[ -f "build/c_relay_x86" ]]; then
cp build/c_relay_x86 c-relay-x86_64
print_success "x86_64 binary created: c-relay-x86_64"
else
print_error "x86_64 binary not found after compilation"
exit 1
fi
else
print_error "x86_64 build failed"
exit 1
fi
# Try to build ARM64 version
print_status "Attempting ARM64 build..."
make clean > /dev/null 2>&1
if make arm64 > /dev/null 2>&1; then
if [[ -f "build/c_relay_arm64" ]]; then
cp build/c_relay_arm64 c-relay-arm64
print_success "ARM64 binary created: c-relay-arm64"
else
print_warning "ARM64 binary not found after compilation"
fi
else
print_warning "ARM64 build failed - ARM64 cross-compilation not properly set up"
print_status "Only x86_64 binary will be included in release"
fi
# Build static x86_64 version
print_status "Building static x86_64 version..."
make clean > /dev/null 2>&1
if make static-musl-x86_64 > /dev/null 2>&1; then
if [[ -f "build/c_relay_static_musl_x86_64" ]]; then
cp build/c_relay_static_musl_x86_64 c-relay-static-x86_64
print_success "Static x86_64 binary created: c-relay-static-x86_64"
else
print_warning "Static x86_64 binary not found after compilation"
fi
else
print_warning "Static x86_64 build failed - MUSL development packages may not be installed"
print_status "Run 'sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev' to enable static builds"
fi
# Try to build static ARM64 version
print_status "Attempting static ARM64 build..."
make clean > /dev/null 2>&1
if make static-musl-arm64 > /dev/null 2>&1; then
if [[ -f "build/c_relay_static_musl_arm64" ]]; then
cp build/c_relay_static_musl_arm64 c-relay-static-arm64
print_success "Static ARM64 binary created: c-relay-static-arm64"
else
print_warning "Static ARM64 binary not found after compilation"
fi
else
print_warning "Static ARM64 build failed - ARM64 cross-compilation or MUSL ARM64 packages not set up"
fi
# Restore normal build
make clean > /dev/null 2>&1
make > /dev/null 2>&1
}
# Function to commit and push changes
git_commit_and_push() {
print_status "Preparing git commit..."
# Stage all changes
if git add . > /dev/null 2>&1; then
print_success "Staged all changes"
else
print_error "Failed to stage changes"
exit 1
fi
# Check if there are changes to commit
if git diff --staged --quiet; then
print_warning "No changes to commit"
else
# Commit changes
if git commit -m "$NEW_VERSION - $COMMIT_MESSAGE" > /dev/null 2>&1; then
print_success "Committed changes"
else
print_error "Failed to commit changes"
exit 1
fi
fi
# Create new git tag
if git tag "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Created tag: $NEW_VERSION"
else
print_warning "Tag $NEW_VERSION already exists"
fi
# Push changes and tags
print_status "Pushing to remote repository..."
if git push > /dev/null 2>&1; then
print_success "Pushed changes"
else
print_error "Failed to push changes"
exit 1
fi
# Push only the new tag to avoid conflicts with existing tags
if git push origin "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Pushed tag: $NEW_VERSION"
else
print_warning "Tag push failed, trying force push..."
if git push --force origin "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Force-pushed updated tag: $NEW_VERSION"
else
print_error "Failed to push tag: $NEW_VERSION"
exit 1
fi
fi
}
# Function to commit and push changes without creating a tag (tag already created)
git_commit_and_push_no_tag() {
print_status "Preparing git commit..."
# Stage all changes
if git add . > /dev/null 2>&1; then
print_success "Staged all changes"
else
print_error "Failed to stage changes"
exit 1
fi
# Check if there are changes to commit
if git diff --staged --quiet; then
print_warning "No changes to commit"
else
# Commit changes
if git commit -m "$NEW_VERSION - $COMMIT_MESSAGE" > /dev/null 2>&1; then
print_success "Committed changes"
else
print_error "Failed to commit changes"
exit 1
fi
fi
# Push changes and tags
print_status "Pushing to remote repository..."
if git push > /dev/null 2>&1; then
print_success "Pushed changes"
else
print_error "Failed to push changes"
exit 1
fi
# Push only the new tag to avoid conflicts with existing tags
if git push origin "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Pushed tag: $NEW_VERSION"
else
print_warning "Tag push failed, trying force push..."
if git push --force origin "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Force-pushed updated tag: $NEW_VERSION"
else
print_error "Failed to push tag: $NEW_VERSION"
exit 1
fi
fi
}
# Function to create Gitea release
create_gitea_release() {
print_status "Creating Gitea release..."
# Check for Gitea token
if [[ ! -f "$HOME/.gitea_token" ]]; then
print_warning "No ~/.gitea_token found. Skipping release creation."
print_warning "Create ~/.gitea_token with your Gitea access token to enable releases."
return 0
fi
local token=$(cat "$HOME/.gitea_token" | tr -d '\n\r')
local api_url="https://git.laantungir.net/api/v1/repos/laantungir/c-relay"
# Create release
print_status "Creating release $NEW_VERSION..."
local response=$(curl -s -X POST "$api_url/releases" \
-H "Authorization: token $token" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
local upload_result=false
if echo "$response" | grep -q '"id"'; then
print_success "Created release $NEW_VERSION"
if upload_release_binaries "$api_url" "$token"; then
upload_result=true
fi
elif echo "$response" | grep -q "already exists"; then
print_warning "Release $NEW_VERSION already exists"
if upload_release_binaries "$api_url" "$token"; then
upload_result=true
fi
else
print_error "Failed to create release $NEW_VERSION"
print_error "Response: $response"
# Try to check if the release exists anyway
print_status "Checking if release exists..."
local check_response=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
if echo "$check_response" | grep -q '"id"'; then
print_warning "Release exists but creation response was unexpected"
if upload_release_binaries "$api_url" "$token"; then
upload_result=true
fi
else
print_error "Release does not exist and creation failed"
return 1
fi
fi
# Return based on upload success
if [[ "$upload_result" == true ]]; then
return 0
else
print_error "Binary upload failed"
return 1
fi
}
# Function to upload release binaries
upload_release_binaries() {
local api_url="$1"
local token="$2"
local upload_success=true
# Get release ID with more robust parsing
print_status "Getting release ID for $NEW_VERSION..."
local response=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
local release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2)
if [[ -z "$release_id" ]]; then
print_error "Could not get release ID for $NEW_VERSION"
print_error "API Response: $response"
# Try to list all releases to debug
print_status "Available releases:"
curl -s -H "Authorization: token $token" "$api_url/releases" | grep -o '"tag_name":"[^"]*"' | head -5
return 1
fi
print_success "Found release ID: $release_id"
# Upload x86_64 binary
if [[ -f "c-relay-x86_64" ]]; then
print_status "Uploading x86_64 binary..."
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
-H "Authorization: token $token" \
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64")
local http_code=$(echo "$upload_response" | tail -n1)
local response_body=$(echo "$upload_response" | head -n -1)
if [[ "$http_code" == "201" ]]; then
print_success "Uploaded x86_64 binary successfully"
else
print_error "Failed to upload x86_64 binary (HTTP $http_code)"
print_error "Response: $response_body"
upload_success=false
fi
else
print_warning "x86_64 binary not found: c-relay-x86_64"
fi
# Upload ARM64 binary
if [[ -f "c-relay-arm64" ]]; then
print_status "Uploading ARM64 binary..."
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
-H "Authorization: token $token" \
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64")
local http_code=$(echo "$upload_response" | tail -n1)
local response_body=$(echo "$upload_response" | head -n -1)
if [[ "$http_code" == "201" ]]; then
print_success "Uploaded ARM64 binary successfully"
else
print_error "Failed to upload ARM64 binary (HTTP $http_code)"
print_error "Response: $response_body"
upload_success=false
fi
else
print_warning "ARM64 binary not found: c-relay-arm64"
fi
# Upload static x86_64 binary
if [[ -f "c-relay-static-x86_64" ]]; then
print_status "Uploading static x86_64 binary..."
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
-H "Authorization: token $token" \
-F "attachment=@c-relay-static-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64-static")
local http_code=$(echo "$upload_response" | tail -n1)
local response_body=$(echo "$upload_response" | head -n -1)
if [[ "$http_code" == "201" ]]; then
print_success "Uploaded static x86_64 binary successfully"
else
print_error "Failed to upload static x86_64 binary (HTTP $http_code)"
print_error "Response: $response_body"
upload_success=false
fi
else
print_warning "Static x86_64 binary not found: c-relay-static-x86_64"
fi
# Upload static ARM64 binary
if [[ -f "c-relay-static-arm64" ]]; then
print_status "Uploading static ARM64 binary..."
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
-H "Authorization: token $token" \
-F "attachment=@c-relay-static-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64-static")
local http_code=$(echo "$upload_response" | tail -n1)
local response_body=$(echo "$upload_response" | head -n -1)
if [[ "$http_code" == "201" ]]; then
print_success "Uploaded static ARM64 binary successfully"
else
print_error "Failed to upload static ARM64 binary (HTTP $http_code)"
print_error "Response: $response_body"
upload_success=false
fi
else
print_warning "Static ARM64 binary not found: c-relay-static-arm64"
fi
# Return success/failure status
if [[ "$upload_success" == true ]]; then
return 0
else
return 1
fi
}
# Function to clean up release binaries
cleanup_release_binaries() {
local force_cleanup="$1" # Optional parameter to force cleanup even on failure
if [[ "$force_cleanup" == "force" ]] || [[ "$upload_success" == true ]]; then
if [[ -f "c-relay-x86_64" ]]; then
rm -f c-relay-x86_64
print_status "Cleaned up x86_64 binary"
fi
if [[ -f "c-relay-arm64" ]]; then
rm -f c-relay-arm64
print_status "Cleaned up ARM64 binary"
fi
if [[ -f "c-relay-static-x86_64" ]]; then
rm -f c-relay-static-x86_64
print_status "Cleaned up static x86_64 binary"
fi
if [[ -f "c-relay-static-arm64" ]]; then
rm -f c-relay-static-arm64
print_status "Cleaned up static ARM64 binary"
fi
else
print_warning "Keeping binary files due to upload failures"
print_status "Files available for manual upload:"
if [[ -f "c-relay-x86_64" ]]; then
print_status " - c-relay-x86_64"
fi
if [[ -f "c-relay-arm64" ]]; then
print_status " - c-relay-arm64"
fi
if [[ -f "c-relay-static-x86_64" ]]; then
print_status " - c-relay-static-x86_64"
fi
if [[ -f "c-relay-static-arm64" ]]; then
print_status " - c-relay-static-arm64"
fi
fi
}
# Main execution
main() {
print_status "C-Relay Build and Push Script"
# Check prerequisites
check_git_repo
if [[ "$RELEASE_MODE" == true ]]; then
print_status "=== RELEASE MODE ==="
# Increment minor version for releases
increment_version "minor"
# Create new git tag BEFORE compilation so version.h picks it up
if git tag "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Created tag: $NEW_VERSION"
else
print_warning "Tag $NEW_VERSION already exists, removing and recreating..."
git tag -d "$NEW_VERSION" > /dev/null 2>&1
git tag "$NEW_VERSION" > /dev/null 2>&1
fi
# Compile project first (will now pick up the new tag)
compile_project
# Build release binaries
build_release_binaries
# Commit and push (but skip tag creation since we already did it)
git_commit_and_push_no_tag
# Create Gitea release with binaries
if create_gitea_release; then
print_success "Release $NEW_VERSION completed successfully!"
print_status "Binaries uploaded to Gitea release"
upload_success=true
else
print_error "Release creation or binary upload failed"
upload_success=false
fi
# Cleanup (only if upload was successful)
cleanup_release_binaries
else
print_status "=== DEFAULT MODE ==="
# Increment patch version for regular commits
increment_version "patch"
# Create new git tag BEFORE compilation so version.h picks it up
if git tag "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Created tag: $NEW_VERSION"
else
print_warning "Tag $NEW_VERSION already exists, removing and recreating..."
git tag -d "$NEW_VERSION" > /dev/null 2>&1
git tag "$NEW_VERSION" > /dev/null 2>&1
fi
# Compile project (will now pick up the new tag)
compile_project
# Commit and push (but skip tag creation since we already did it)
git_commit_and_push_no_tag
print_success "Build and push completed successfully!"
print_status "Version $NEW_VERSION pushed to repository"
fi
}
# Execute main function
main
+81 -62
View File
@@ -1,7 +1,8 @@
#!/bin/bash
# Build fully static MUSL binaries for C-Relay using Alpine Docker
# Build fully static MUSL binaries for C-Relay-PG using Alpine Docker
# Produces truly portable binaries with zero runtime dependencies
# PostgreSQL backend only.
set -e
@@ -9,11 +10,36 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="$SCRIPT_DIR/build"
DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl"
echo "=========================================="
echo "C-Relay MUSL Static Binary Builder"
echo "=========================================="
# Parse command line arguments
DEBUG_BUILD=false
while [[ $# -gt 0 ]]; do
case "$1" in
--debug)
DEBUG_BUILD=true
shift
;;
*)
echo "ERROR: Unknown argument: $1"
echo "Usage: $0 [--debug]"
exit 1
;;
esac
done
if [[ "$DEBUG_BUILD" == "true" ]]; then
echo "=========================================="
echo "C-Relay-PG MUSL Static Binary Builder (DEBUG MODE)"
echo "=========================================="
else
echo "=========================================="
echo "C-Relay-PG MUSL Static Binary Builder (PRODUCTION MODE)"
echo "=========================================="
fi
echo "Project directory: $SCRIPT_DIR"
echo "Build directory: $BUILD_DIR"
echo "Debug build: $DEBUG_BUILD"
echo "DB backend: postgres"
echo ""
# Create build directory
@@ -53,24 +79,52 @@ ARCH=$(uname -m)
case "$ARCH" in
x86_64)
PLATFORM="linux/amd64"
OUTPUT_NAME="c_relay_static_x86_64"
OUTPUT_NAME="c_relay_pg_static_x86_64"
;;
aarch64|arm64)
PLATFORM="linux/arm64"
OUTPUT_NAME="c_relay_static_arm64"
OUTPUT_NAME="c_relay_pg_static_arm64"
;;
*)
echo "WARNING: Unknown architecture: $ARCH"
echo "Defaulting to linux/amd64"
PLATFORM="linux/amd64"
OUTPUT_NAME="c_relay_static_${ARCH}"
OUTPUT_NAME="c_relay_pg_static_${ARCH}"
;;
esac
# Append _debug suffix to output name for debug builds so production binary is never overwritten
if [ "$DEBUG_BUILD" = true ]; then
OUTPUT_NAME="${OUTPUT_NAME}_debug"
fi
echo "Building for platform: $PLATFORM"
echo "Output binary: $OUTPUT_NAME"
echo ""
# Check if Alpine base image is cached
echo "Checking for cached Alpine Docker image..."
if ! docker images alpine:3.19 --format "{{.Repository}}:{{.Tag}}" | grep -q "alpine:3.19"; then
echo "⚠ Alpine 3.19 image not found in cache"
echo "Attempting to pull Alpine 3.19 image..."
if ! docker pull alpine:3.19; then
echo ""
echo "ERROR: Failed to pull Alpine 3.19 image"
echo "This is required for the static build."
echo ""
echo "Possible solutions:"
echo " 1. Check your internet connection"
echo " 2. Try again later (Docker Hub may be temporarily unavailable)"
echo " 3. If you have IPv6 issues, disable IPv6 for Docker"
echo ""
exit 1
fi
echo "✓ Alpine 3.19 image pulled successfully"
else
echo "✓ Alpine 3.19 image found in cache"
fi
echo ""
# Build the Docker image
echo "=========================================="
echo "Step 1: Building Alpine Docker image"
@@ -78,13 +132,14 @@ echo "=========================================="
echo "This will:"
echo " - Use Alpine Linux (native MUSL)"
echo " - Build all dependencies statically"
echo " - Compile c-relay with full static linking"
echo " - Compile c-relay-pg with full static linking"
echo ""
$DOCKER_CMD build \
--platform "$PLATFORM" \
--build-arg DEBUG_BUILD=$DEBUG_BUILD \
-f "$DOCKERFILE" \
-t c-relay-musl-builder:latest \
-t c-relay-pg-musl-builder:latest \
--progress=plain \
. || {
echo ""
@@ -105,16 +160,17 @@ echo "=========================================="
# Build the builder stage to extract the binary
$DOCKER_CMD build \
--platform "$PLATFORM" \
--build-arg DEBUG_BUILD=$DEBUG_BUILD \
--target builder \
-f "$DOCKERFILE" \
-t c-relay-static-builder-stage:latest \
-t c-relay-pg-static-builder-stage:latest \
. > /dev/null 2>&1
# Create a temporary container to copy the binary
CONTAINER_ID=$($DOCKER_CMD create c-relay-static-builder-stage:latest)
CONTAINER_ID=$($DOCKER_CMD create c-relay-pg-static-builder-stage:latest)
# Copy binary from container
$DOCKER_CMD cp "$CONTAINER_ID:/build/c_relay_static" "$BUILD_DIR/$OUTPUT_NAME" || {
$DOCKER_CMD cp "$CONTAINER_ID:/build/c_relay_pg_static" "$BUILD_DIR/$OUTPUT_NAME" || {
echo "ERROR: Failed to extract binary from container"
$DOCKER_CMD rm "$CONTAINER_ID" 2>/dev/null
exit 1
@@ -123,68 +179,31 @@ $DOCKER_CMD cp "$CONTAINER_ID:/build/c_relay_static" "$BUILD_DIR/$OUTPUT_NAME" |
# Clean up container
$DOCKER_CMD rm "$CONTAINER_ID" > /dev/null
echo "✓ Binary extracted to: $BUILD_DIR/$OUTPUT_NAME"
echo "✓ Binary extracted: $BUILD_DIR/$OUTPUT_NAME"
echo ""
# Make binary executable
chmod +x "$BUILD_DIR/$OUTPUT_NAME"
# Verify the binary
echo "=========================================="
echo "Step 3: Verifying static binary"
echo "=========================================="
echo ""
file "$BUILD_DIR/$OUTPUT_NAME"
ls -lh "$BUILD_DIR/$OUTPUT_NAME"
echo "Checking for dynamic dependencies:"
if LDD_OUTPUT=$(timeout 5 ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1); then
if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable"; then
echo "✓ Binary is fully static (no dynamic dependencies)"
TRULY_STATIC=true
elif echo "$LDD_OUTPUT" | grep -q "statically linked"; then
echo "✓ Binary is statically linked"
TRULY_STATIC=true
else
echo "⚠ WARNING: Binary may have dynamic dependencies:"
echo "$LDD_OUTPUT"
TRULY_STATIC=false
fi
# Check for dynamic dependencies
if ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 | grep -q "not a dynamic executable"; then
echo "✓ Binary is truly static (no dynamic dependencies)"
else
# ldd failed or timed out - check with file command instead
if file "$BUILD_DIR/$OUTPUT_NAME" | grep -q "statically linked"; then
echo "✓ Binary is statically linked (verified with file command)"
TRULY_STATIC=true
else
echo "⚠ Could not verify static linking (ldd check failed)"
TRULY_STATIC=false
fi
echo "⚠ Binary has dynamic dependencies:"
ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1
fi
echo ""
echo "File size: $(ls -lh "$BUILD_DIR/$OUTPUT_NAME" | awk '{print $5}')"
echo ""
# Test if binary runs
echo "Testing binary execution:"
if "$BUILD_DIR/$OUTPUT_NAME" --version 2>&1 | head -5; then
echo "✓ Binary executes successfully"
else
echo "⚠ Binary execution test failed (this may be normal if --version is not supported)"
fi
echo ""
# Summary
echo "=========================================="
echo "Build Summary"
echo "Build complete!"
echo "=========================================="
echo "Binary: $BUILD_DIR/$OUTPUT_NAME"
echo "Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)"
echo "Platform: $PLATFORM"
if [ "$TRULY_STATIC" = true ]; then
echo "Type: Fully static binary (Alpine MUSL-based)"
echo "Portability: Works on ANY Linux distribution"
else
echo "Type: Static binary (may have minimal dependencies)"
fi
echo ""
echo "✓ Build complete!"
echo "Size: $(ls -lh "$BUILD_DIR/$OUTPUT_NAME" | awk '{print $5}')"
echo ""
# Clean up Docker builder stage image
$DOCKER_CMD rmi c-relay-pg-static-builder-stage:latest > /dev/null 2>&1 || true
Submodule
+1
Submodule c_utils_lib added at f46747b243
+31
View File
@@ -0,0 +1,31 @@
# Database files (from c-relay running in this directory)
*.db
*.db-shm
*.db-wal
# Build artifacts
*.o
*.a
# Binary (built into root, not version-controlled)
caching_relay
caching_relay_static_*
# Logs
*.log
crelay.log
# Test configs (not for version control)
test_config*.jsonc
# Note: caching_relay_config.jsonc IS version-controlled (it's the default
# config + state store). Don't ignore it.
# Docker build context temp (copied nostr_core_lib during static build)
nostr_core_lib/
# Editor
*.swp
*.swo
*~
.vscode/
+128
View File
@@ -0,0 +1,128 @@
# Alpine-based MUSL static binary builder for caching_relay
# Produces a truly portable binary with zero runtime dependencies.
# Adapted from c-relay's Dockerfile.alpine-musl.
ARG DEBUG_BUILD=false
FROM alpine:3.19 AS builder
ARG DEBUG_BUILD=false
# Install build dependencies
RUN apk add --no-cache \
build-base \
musl-dev \
git \
cmake \
pkgconfig \
autoconf \
automake \
libtool \
openssl-dev \
openssl-libs-static \
zlib-dev \
zlib-static \
curl-dev \
curl-static \
sqlite-dev \
postgresql-dev \
linux-headers \
wget \
bash
WORKDIR /build
# Build libsecp256k1 static
RUN cd /tmp && \
git clone https://github.com/bitcoin-core/secp256k1.git && \
cd secp256k1 && \
./autogen.sh && \
./configure --enable-static --disable-shared --prefix=/usr \
CFLAGS="-fPIC" && \
make -j$(nproc) && \
make install && \
rm -rf /tmp/secp256k1
# Build libwebsockets static with minimal features
RUN cd /tmp && \
git clone --depth 1 --branch v4.3.3 https://github.com/warmcat/libwebsockets.git && \
cd libwebsockets && \
mkdir build && cd build && \
cmake .. \
-DLWS_WITH_STATIC=ON \
-DLWS_WITH_SHARED=OFF \
-DLWS_WITH_SSL=ON \
-DLWS_WITHOUT_TESTAPPS=ON \
-DLWS_WITHOUT_TEST_SERVER=ON \
-DLWS_WITHOUT_TEST_CLIENT=ON \
-DLWS_WITHOUT_TEST_PING=ON \
-DLWS_WITH_HTTP2=OFF \
-DLWS_WITH_LIBUV=OFF \
-DLWS_WITH_LIBEVENT=OFF \
-DLWS_IPV6=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_C_FLAGS="-fPIC" && \
make -j$(nproc) && \
make install && \
rm -rf /tmp/libwebsockets
# Copy nostr_core_lib source (sibling project)
COPY nostr_core_lib /build/nostr_core_lib/
# Build nostr_core_lib with the NIPs needed by the relay pool + signer:
# 1 (basic), 6 (keys), 19 (npub bech32), 4 (legacy encryption, signer dep),
# 42 (auth, relay pool dep), 44 (modern encryption, signer dep)
RUN cd nostr_core_lib && \
chmod +x build.sh && \
sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \
rm -f *.o *.a 2>/dev/null || true && \
./build.sh --nips=1,4,6,19,42,44 && \
if [ -f libnostr_core_arm64.a ]; then \
cp libnostr_core_arm64.a libnostr_core.a; \
elif [ -f libnostr_core_x64.a ]; then \
cp libnostr_core_x64.a libnostr_core.a; \
else \
echo "ERROR: No supported nostr_core static library produced"; \
ls -la *.a 2>/dev/null || true; \
exit 1; \
fi
# Copy caching_relay source LAST (only this layer rebuilds on source changes)
COPY src/ /build/src/
# Build caching_relay with full static linking.
# No sqlite (the daemon doesn't use it), no c_utils (not needed).
RUN if [ "$DEBUG_BUILD" = "true" ]; then \
CFLAGS="-g -O2 -DDEBUG -fno-omit-frame-pointer"; \
STRIP_CMD="echo 'Keeping debug symbols'"; \
else \
CFLAGS="-O2"; \
STRIP_CMD="strip /build/caching_relay_static"; \
fi && \
gcc -static $CFLAGS -Wall -Wextra -std=c99 \
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
-I. -Isrc -Inostr_core_lib -Inostr_core_lib/nostr_core \
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
-I/usr/include/postgresql \
src/main.c src/debug.c src/jsonc_strip.c src/config.c src/state.c \
src/follow_graph.c src/relay_sink.c src/live_subscriber.c \
src/backfill.c src/relay_discovery.c \
src/pg_inbox.c src/pg_config.c src/forward_catchup.c \
-o /build/caching_relay_static \
nostr_core_lib/libnostr_core.a \
-lwebsockets -lssl -lcrypto -lsecp256k1 \
-lcurl -lz -lpthread -lm -ldl -lpq -lpgcommon -lpgport && \
eval "$STRIP_CMD"
# Verify it's truly static
RUN echo "=== Binary Information ===" && \
file /build/caching_relay_static && \
ls -lh /build/caching_relay_static && \
echo "=== Checking for dynamic dependencies ===" && \
(ldd /build/caching_relay_static 2>&1 || echo "Binary is static") && \
echo "=== Build complete ==="
# Output stage - just the binary
FROM scratch AS output
COPY --from=builder /build/caching_relay_static /caching_relay_static
+85
View File
@@ -0,0 +1,85 @@
# caching_relay Makefile - statically linked C99 binary, c-relay style
# Use bash for reliable glob expansion in recipes (dash handles globs differently).
SHELL := /bin/bash
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -g -O2
# nostr_core_lib is inside the c-relay-pg project root (one dir up from caching/).
NOSTR_CORE_DIR = ../nostr_core_lib
INCLUDES = -I. -Isrc -I$(NOSTR_CORE_DIR) -I$(NOSTR_CORE_DIR)/nostr_core \
-I$(NOSTR_CORE_DIR)/cjson -I$(NOSTR_CORE_DIR)/nostr_websocket \
-I/usr/include/postgresql
# -lsqlite3: nostr_core_lib's request_validator.x64.o references SQLite symbols;
# we use --whole-archive so the linker pulls in all objects (needed for NIP-42
# cross-references within the archive), which means SQLite must be satisfied.
# No c_utils_lib: nostr_core_lib does not depend on it for the NIPs we use.
# -lpq: PostgreSQL client library for the caching_event_inbox integration.
LIBS = -lwebsockets -lssl -lcrypto -lsecp256k1 -lcurl -lz -ldl -lpthread -lm -lpq -lsqlite3
MAIN_SRC = src/main.c src/debug.c src/jsonc_strip.c src/config.c src/state.c \
src/follow_graph.c src/relay_sink.c src/live_subscriber.c \
src/backfill.c src/forward_catchup.c src/relay_discovery.c \
src/pg_inbox.c src/pg_config.c
# Architecture detection
ARCH = $(shell uname -m)
ifeq ($(ARCH),x86_64)
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_x64.a
else ifeq ($(ARCH),aarch64)
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_arm64.a
else ifeq ($(ARCH),arm64)
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_arm64.a
else
NOSTR_CORE_LIB = $(NOSTR_CORE_DIR)/libnostr_core_x64.a
endif
# Binary goes in the c-relay-pg build/ directory so the launcher can find it.
TARGET = ../build/caching_relay
all: $(TARGET)
# Build nostr_core_lib with the NIPs needed by the relay pool + signer:
# 1 (basic), 6 (keys), 19 (npub bech32), 4 (legacy encryption, signer dep),
# 42 (auth, relay pool dep), 44 (modern encryption, signer dep)
$(NOSTR_CORE_LIB):
@echo "Building nostr_core_lib with required NIPs..."
cd $(NOSTR_CORE_DIR) && ./build.sh --nips=1,4,6,19,42,44
$(TARGET): $(MAIN_SRC) $(NOSTR_CORE_LIB)
@echo "Compiling caching_relay for architecture: $(ARCH)"
@# Extract all objects from the static library and link them directly.
@# This avoids archive symbol resolution ordering issues (NIP-42 cross-refs).
@# Use a fixed temp dir and chain all commands with && so failures stop the build.
@rm -rf /tmp/cr_lib && mkdir -p /tmp/cr_lib && \
ar x $(NOSTR_CORE_LIB) --output=/tmp/cr_lib && \
echo "Extracted $$(ls /tmp/cr_lib/*.o | wc -l) objects" && \
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) /tmp/cr_lib/*.o -o $(TARGET) $(LIBS) && \
rm -rf /tmp/cr_lib && \
echo "Build complete: $(TARGET)"
# ------------------------------------------------------------------ #
# cache_all_test - feasibility test for "cache everything" approach #
# ------------------------------------------------------------------ #
TEST_TARGET = ../build/cache_all_test
TEST_SRC = src/cache_all_test.c src/debug.c src/jsonc_strip.c src/config.c \
src/state.c
$(TEST_TARGET): $(TEST_SRC) $(NOSTR_CORE_LIB)
@echo "Compiling cache_all_test for architecture: $(ARCH)"
@rm -rf /tmp/cr_lib && mkdir -p /tmp/cr_lib && \
ar x $(NOSTR_CORE_LIB) --output=/tmp/cr_lib && \
echo "Extracted $$(ls /tmp/cr_lib/*.o | wc -l) objects" && \
$(CC) $(CFLAGS) $(INCLUDES) $(TEST_SRC) /tmp/cr_lib/*.o -o $(TEST_TARGET) $(LIBS) && \
rm -rf /tmp/cr_lib && \
echo "Build complete: $(TEST_TARGET)"
cache_all_test: $(TEST_TARGET)
clean:
rm -f $(TARGET) $(TEST_TARGET)
.PHONY: all clean cache_all_test
+297
View File
@@ -0,0 +1,297 @@
# caching_relay
A C99 daemon that caches Nostr events from people you follow into a local relay,
so your Nostr client can point at a single fast local relay instead of fanning
out to dozens of upstream relays.
## What it does
1. Reads a `.jsonc` config file listing your **root npub(s)**, upstream relays,
a local relay URL, and the event kinds to cache.
2. For each root npub, fetches its kind-3 contact list to discover **followed
pubkeys** (your follows + their follows).
3. **Live-subscribes** to new events of the configured kinds from the union of
followed pubkeys.
4. **Backfills** historical events using a progressive window-expansion strategy
(24h → 7d → 30d → 90d → 365d), round-robin per pubkey, throttled to be polite
to upstream relays.
5. **Re-publishes** every fetched event to the local relay via a plain WebSocket
`EVENT` client connection (relay-agnostic - works with c-relay, c-relay-pg,
or any Nostr relay).
6. Persists its backfill progress **in the config file itself** so a restart
resumes where it left off.
## Build
### Static binary (recommended, c-relay style)
Produces a truly portable statically-linked MUSL binary with zero runtime
dependencies:
```bash
./build_static.sh
# or with debug symbols:
./build_static.sh --debug
# or cross-compile for arm64:
./build_static.sh --arch arm64
```
Output: `caching_relay` (in the project root).
Requires Docker. The build runs in an Alpine container that compiles
libsecp256k1, libwebsockets, and nostr_core_lib from source, then statically
links everything.
### Local build (if you have the shared libs installed)
```bash
make
```
Output: `caching_relay` (in the project root).
Requires: `libwebsockets`, `openssl`, `libsecp256k1`, `libcurl`, `zlib` shared
libraries, and a pre-built `../nostr_core_lib/libnostr_core_x64.a`.
## Usage
```bash
./caching_relay -d 3
```
The daemon looks for `caching_relay_config.jsonc` in the current directory by default.
You can override with `-c`:
```bash
./caching_relay -c /path/to/my_config.jsonc -d 3
```
Options:
- `-c, --config <file>` - Path to `.jsonc` config file (default: `./caching_relay_config.jsonc`)
- `-d, --debug <level>` - Log level 1-4 (1=error, 2=warn, 3=info, 4=debug). Default 3.
- `-h, --help` - Show help
Signals:
- `SIGINT` / `SIGTERM` - Graceful shutdown (saves state, closes connections)
- `SIGHUP` - Reload config (preserves backfill state)
## Config file
The config file [`caching_relay_config.jsonc`](caching_relay_config.jsonc) lives in the
project root. It is JSONC (JSON with `//` and `/* */` comments). The daemon
rewrites it as plain JSON when saving state (comments are not preserved on
rewrite).
Key fields:
| Field | Description |
|-------|-------------|
| `root_npubs` | npubs whose kind-3 follows list we crawl |
| `upstream_relays` | bootstrap relays for initial discovery (kind-3, kind-10002). Outbox relays are discovered dynamically via NIP-65. |
| `local_relay` | relay to publish cached events into (never queried) |
| `kinds` | event kinds to cache for followed people (e.g. `[1, 3, 6, 10000, 30023]`) |
| `admin_kinds` | kinds to follow specifically for root (admin) npubs. Use `["*"]` for all kinds. If omitted, admin uses same `kinds` as everyone else. |
| `backfill.window_schedule_seconds` | progressive window sizes in seconds |
| `backfill.events_per_tick` | max events per pubkey per backfill tick |
| `backfill.tick_interval_seconds` | delay between pubkey backfills (throttle) |
| `live.enabled` | enable live subscription |
| `follow_graph_refresh_seconds` | how often to re-resolve the follow graph |
| `state.*` | managed by the daemon - do not hand-edit |
## Architecture
See [`plans/plan.md`](plans/plan.md) for the full architecture document with
Mermaid diagrams.
The daemon uses a **NIP-65 outbox model**: instead of querying a fixed set of
bootstrap relays for everything, it discovers which relays each followed pubkey
actually posts to (via kind 10002 relay lists) and connects to those relays
dynamically. The `upstream_relays` in the config serve only as **bootstrap
relays** -- the initial set used to discover kind-3 and kind-10002 events before
the outbox relay map is built.
### Relay pool selection: minimum covering set
After discovering each followed pubkey's outbox relays (from their kind 10002),
the daemon computes the **minimum set of relays that covers all followed
pubkeys**. This is the classic set cover problem, solved with a greedy
approximation:
1. Build a map: `{relay_url -> set of pubkeys that list it in their 10002}`
2. Greedily pick the relay that covers the most uncovered pubkeys
3. Repeat until all pubkeys are covered (or no more relays to pick)
4. Always include the bootstrap relays in the final set (they may have events
from pubkeys that don't publish a kind 10002)
This minimizes the number of WebSocket connections while ensuring every
followed pubkey is reachable. The daemon logs the selected relay set and which
pubkeys each relay covers.
Pubkeys that have no kind 10002 (or whose 10002 lists no relays) are covered by
the bootstrap relays as a fallback.
```
┌─────────────────────────────────────────┐
│ caching_relay │
│ │
upstream relays │ upstream_pool sink_pool │ local relay
(damus, nos.lol) │ (query + subscribe) (publish only) │ (c-relay)
│ │ │ │ │ │
│ │ follow_graph relay_sink │ │
└──────────►│ live_subscriber ──────►│ ├─────►│
│ backfill ──────────────►│ │ │
│ │ │ │ │
│ state + seen ring │ │ │
└──────────────────────────────────────────┘ │
```
Two `nostr_relay_pool_t` instances:
- **upstream_pool** - holds bootstrap relays initially, then dynamically adds
outbox relays discovered from kind 10002. Used for `query_sync` (kind-3 fetch,
kind-10002 fetch, backfill) and the long-lived live subscription.
- **sink_pool** - holds only the local relay; used exclusively for
`publish_async`. Never queried.
## Startup Flow
### First-time startup (empty local relay)
The daemon has never run before. The local relay has no cached events. The
daemon must bootstrap from the config's `upstream_relays` to discover the
outbox relays for each followed pubkey.
```
START
|
v
Load config (caching_relay_config.jsonc)
| - root_npubs, upstream_relays (bootstrap), kinds, admin_kinds
| - state.backfilled_until == 0 => first-time startup
|
v
Create upstream_pool with bootstrap relays only
Create sink_pool with local_relay
|
v
Phase 1: Resolve follow graph (from bootstrap relays)
| - For each root npub: query_sync kind=3 from bootstrap relays
| - Parse "p" tags => followed pubkey set
| - Log: "follow: resolved N followed pubkeys"
|
v
Phase 2: Discover outbox relays (NIP-65, kind 10002) from bootstrap relays
| - For each followed pubkey: query_sync kind=10002 from bootstrap relays
| - Parse "r" tags => per-pubkey relay list
| - Publish all kind-10002 events to local relay (cache them)
| - Dynamically add discovered relays to upstream_pool
| - Log: "relay_discovery: found N relays from M pubkeys"
| - Log: "upstream_pool: now connected to N relays" + list them
|
v
Phase 3: Open live subscriptions on upstream_pool
| - follows_sub: non-admin pubkeys + regular kinds
| - admin_sub: admin npubs + admin_kinds (or all kinds)
|
v
Phase 4: Begin progressive backfill
| - Window 0: 24h -> query each pubkey from their outbox relays
| - Window 1: 7d -> ...
| - Window 2: 30d -> ...
| - Publish all fetched events to local relay
| - Save state to config file as each window completes
|
v
Steady-state: live sub + periodic follow-graph refresh + periodic
kind-10002 refresh + backfill re-cycle
|
v
SHUTDOWN (SIGINT/SIGTERM) -> save state -> clean exit
```
### Subsequent startup (local relay already has cached events)
The daemon has run before. The local relay already has kind-3 and kind-10002
events cached from the previous run. The daemon can read these from the local
relay directly (fast, no network round-trip to bootstrap relays) and only falls
back to bootstrap relays for pubkeys it cannot find locally.
```
START
|
v
Load config (caching_relay_config.jsonc)
| - state.backfilled_until > 0 => subsequent startup
| - state.current_window_index, backfill_cursor preserved
|
v
Create upstream_pool with bootstrap relays
Create sink_pool with local_relay
|
v
Phase 1: Resolve follow graph (from LOCAL relay first, bootstrap fallback)
| - Query local relay for kind=3 per root npub
| - If found locally: use it (fast, no upstream query)
| - If not found: fall back to bootstrap relays
| - Parse "p" tags => followed pubkey set
| - Log: "follow: resolved N followed pubkeys (L local, B bootstrap)"
|
v
Phase 2: Discover outbox relays (from LOCAL relay first, bootstrap fallback)
| - Query local relay for kind=10002 per followed pubkey
| - If found locally: use it (fast)
| - If not found: fall back to bootstrap relays, cache result to local
| - Parse "r" tags => per-pubkey relay list
| - Dynamically add discovered relays to upstream_pool
| - Log: "relay_discovery: found N relays (L local, B bootstrap)"
| - Log: "upstream_pool: now connected to N relays" + list them
|
v
Phase 3: Open live subscriptions on upstream_pool
| - (same as first-time)
|
v
Phase 4: Resume backfill from saved state
| - Resume at state.current_window_index, state.backfill_cursor
| - No re-pull of already-backfilled windows
| - Continue progressive window expansion from where it left off
|
v
Steady-state (same as first-time)
|
v
SHUTDOWN -> save state -> clean exit
```
### Relay logging
The daemon logs all relay activity so you can see exactly which relays are
being used:
- `upstream: added wss://relay.damus.io` -- each relay added to the pool
- `relay_discovery: found 47 relays from 195 pubkeys` -- outbox discovery summary
- `upstream_pool: 50 relays connected:` -- full relay list at startup
- `relay_discovery: pubkey X -> wss://relay.example.com (local)` -- per-pubkey
relay source (local cache vs bootstrap)
- `backfill: pubkey[3/195] abc123 -> wss://relay.example.com (12 events)` --
which relay served each backfill query
## Dependencies
- [nostr_core_lib](../nostr_core_lib) - built with NIPs 1, 4, 6, 19, 42, 44
- libwebsockets, openssl, libsecp256k1, libcurl, zlib (all statically linked
in the Docker build)
## Testing
Start a local c-relay, then run the daemon:
```bash
# Start local relay (from c-relay project)
cd ../c-relay/build && ./c_relay_static_x86_64 -p 8888 &
# Run the caching daemon (uses ./caching_relay_config.jsonc by default)
./caching_relay -d 3
# Verify events are cached (from another terminal)
nak req -k 1 -l 10 ws://127.0.0.1:8888
```
+1
View File
@@ -0,0 +1 @@
0.0.2
+147
View File
@@ -0,0 +1,147 @@
#!/bin/bash
# Build fully static MUSL binary for caching_relay using Alpine Docker.
# Produces a truly portable binary with zero runtime dependencies.
# Adapted from c-relay's build_static.sh.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl"
# Parse command line arguments
DEBUG_BUILD=false
TARGET_ARCH=""
while [[ $# -gt 0 ]]; do
case "$1" in
--debug)
DEBUG_BUILD=true
shift
;;
--arch)
if [[ -z "$2" ]]; then
echo "ERROR: --arch requires a value"
echo "Usage: $0 [--debug] [--arch <arm64|x86_64>]"
exit 1
fi
case "$2" in
arm64|x86_64)
TARGET_ARCH="$2"
;;
*)
echo "ERROR: Unsupported architecture '$2'"
echo "Supported values: arm64, x86_64"
exit 1
;;
esac
shift 2
;;
*)
echo "ERROR: Unknown argument '$1'"
echo "Usage: $0 [--debug] [--arch <arm64|x86_64>]"
exit 1
;;
esac
done
if [ "$DEBUG_BUILD" = true ]; then
echo "=========================================="
echo "caching_relay MUSL Static Binary Builder (DEBUG MODE)"
echo "=========================================="
else
echo "=========================================="
echo "caching_relay MUSL Static Binary Builder (PRODUCTION MODE)"
echo "=========================================="
fi
echo "Project directory: $SCRIPT_DIR"
echo ""
if ! command -v docker &> /dev/null; then
echo "ERROR: Docker is not installed or not in PATH"
exit 1
fi
if ! docker info &> /dev/null; then
echo "ERROR: Docker daemon is not running or user not in docker group"
exit 1
fi
echo "Docker is available and running"
echo ""
# Detect host architecture
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH="x86_64";;
aarch64) ARCH="arm64";;
arm64) ARCH="arm64";;
esac
if [[ -n "$TARGET_ARCH" ]]; then
ARCH="$TARGET_ARCH"
echo "Using target architecture: $ARCH"
else
echo "Detected host architecture: $ARCH"
fi
echo ""
# nostr_core_lib is inside the c-relay-pg project root (one dir up from caching/).
# Copy it into the Docker build context (excluding .git and build artifacts).
NOSTR_CORE_DIR="$SCRIPT_DIR/../nostr_core_lib"
if [ ! -d "$NOSTR_CORE_DIR" ]; then
echo "ERROR: nostr_core_lib not found at $NOSTR_CORE_DIR"
exit 1
fi
NEEDS_COPY=1
if [ -d "$SCRIPT_DIR/nostr_core_lib" ] && [ ! -L "$SCRIPT_DIR/nostr_core_lib" ]; then
# Already a real directory (e.g. from a previous run); assume it's good.
NEEDS_COPY=0
fi
if [ "$NEEDS_COPY" = "1" ]; then
echo "Copying nostr_core_lib into build context..."
rm -rf "$SCRIPT_DIR/nostr_core_lib"
mkdir -p "$SCRIPT_DIR/nostr_core_lib"
rsync -a --exclude='.git' --exclude='*.a' --exclude='*.o' \
--exclude='.venv*' --exclude='backups' --exclude='verify_*' \
--exclude='rewrite_mirror' --exclude='websocket_debug' \
"$NOSTR_CORE_DIR/" "$SCRIPT_DIR/nostr_core_lib/"
COPIED_NOSTR_CORE=1
fi
# Build args
BUILD_ARGS="--build-arg DEBUG_BUILD=$DEBUG_BUILD"
if [ "$ARCH" = "arm64" ] && [ "$(uname -m)" != "aarch64" ] && [ "$(uname -m)" != "arm64" ]; then
BUILD_ARGS="$BUILD_ARGS --platform linux/arm64"
fi
IMAGE_TAG="caching_relay_builder:latest"
echo "Building Docker image..."
docker build $BUILD_ARGS \
-f "$DOCKERFILE" \
-t "$IMAGE_TAG" \
"$SCRIPT_DIR" 2>&1
BUILD_RC=$?
# Clean up the copied nostr_core_lib to keep the project dir tidy.
if [ "${COPIED_NOSTR_CORE:-0}" = "1" ]; then
echo "Cleaning up copied nostr_core_lib from build context..."
rm -rf "$SCRIPT_DIR/nostr_core_lib"
fi
if [ $BUILD_RC -ne 0 ]; then
exit $BUILD_RC
fi
echo ""
echo "Extracting binary from image..."
# The output stage is FROM scratch with no CMD, so pass an empty command.
CONTAINER_ID=$(docker create "$IMAGE_TAG" "")
docker cp "$CONTAINER_ID:/caching_relay_static" "$SCRIPT_DIR/caching_relay"
docker rm "$CONTAINER_ID" >/dev/null
chmod +x "$SCRIPT_DIR/caching_relay"
echo ""
echo "=== Build complete ==="
ls -lh "$SCRIPT_DIR/caching_relay"
file "$SCRIPT_DIR/caching_relay"
+56
View File
@@ -0,0 +1,56 @@
{
// Cache-All Feasibility Test Config
// Root npubs to resolve follow graph from
"root_npubs": [
"npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06"
],
// Relays to test (from server relay list, connected ones only)
"upstream_relays": [
"wss://laantungir.net",
"wss://nos.lol",
"wss://nostr-pub.wellorder.net",
"wss://nostr.mom",
"wss://nostr.oxtr.dev",
"wss://offchain.pub",
"wss://premium.primal.net",
"wss://relay.damus.io",
"wss://relay.ditto.pub",
"wss://relay.divine.video",
"wss://relay.momostr.pink",
"wss://relay.mostr.pub",
"wss://relay.nostrplebs.com",
"wss://relay.primal.net",
"wss://theforest.nostr1.com"
],
// Local relay (not used by test, but required by config parser)
"local_relay": "ws://localhost:8888",
// Kinds to follow (not used by test, but required by config parser)
"kinds": [0, 1, 3, 5, 6, 7, 9735, 10002, 30023],
// Admin kinds
"admin_kinds": ["*"],
// Backfill config (not used by test)
"backfill": {
"enabled": false,
"events_per_tick": 500,
"tick_interval_seconds": 5
},
// Live config (not used by test)
"live": {
"enabled": false,
"resubscribe_interval_seconds": 300
},
// Follow graph refresh (not used by test)
"follow_graph_refresh_seconds": 600,
// State (not used by test)
"state": {
"backfilled_until": 0
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"root_npubs": [
"npub1rmz9gu6de0m0u4ysrn39crrud099ahvfgs6pvasl4hpjr5ud7yus54xv06"
],
"upstream_relays": [
"wss://relay.primal.net"
],
"local_relay": "ws://localhost:8888",
"kinds": [0, 1, 3, 5, 6, 7, 9735, 10002, 30023],
"admin_kinds": ["*"],
"backfill": { "enabled": false, "events_per_tick": 500, "tick_interval_seconds": 5 },
"live": { "enabled": false, "resubscribe_interval_seconds": 300 },
"follow_graph_refresh_seconds": 600,
"state": { "backfilled_until": 0 }
}
@@ -0,0 +1,253 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 5m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 11200
Total bytes: 17099684 (16.31 MB)
Avg event size: 1527 bytes
Max event: 45848 bytes (kind 3)
Unique pubkeys: 4292
Relays contacted: 3
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 3492 1 0 0 0 0 DOWN
wss://nos.lol 5613 0 0 0 0 0 DOWN
wss://relay.damus.io 2095 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 3060 27.3% 8988132 2937 19622
5 Deletion 1124 10.0% 498378 443 664
24747 Ephemeral 1020 9.1% 602037 590 671
22668 Ephemeral 547 4.9% 234663 429 429
30078 App data 439 3.9% 1714659 3906 37853
20001 Ephemeral (key xchg) 395 3.5% 266277 674 1719
22734 Ephemeral 265 2.4% 113155 427 427
4515 Unknown 246 2.2% 390676 1588 8760
1 Text note 205 1.8% 164127 801 2024
1059 Gift wrap seal 188 1.7% 301940 1606 5285
23334 Ephemeral 179 1.6% 77621 434 438
38501 App data (btc) 157 1.4% 116283 741 744
30382 Addressable 138 1.2% 66879 485 498
21050 Ephemeral 132 1.2% 128700 975 2558
10002 Relay list 127 1.1% 178988 1409 42385
22850 Ephemeral 124 1.1% 53320 430 430
31991 Addressable 122 1.1% 56242 461 461
0 Profile metadata 120 1.1% 92899 774 7180
37195 Addressable 102 0.9% 77276 758 832
24242 Ephemeral 93 0.8% 56549 608 3281
25050 Ephemeral 90 0.8% 39930 444 465
7 Reaction 79 0.7% 42454 537 738
23337 Ephemeral 73 0.7% 37477 513 519
24133 Ephemeral 71 0.6% 146874 2069 7334
445 Encrypted group msg 68 0.6% 386304 5681 38736
22601 Ephemeral 62 0.6% 26536 428 428
22613 Ephemeral 58 0.5% 40166 693 695
22749 Ephemeral 56 0.5% 24024 429 429
22699 Ephemeral 56 0.5% 23968 428 428
22676 Ephemeral 56 0.5% 23912 427 427
23003 Ephemeral 50 0.4% 21400 428 428
13194 NWC info 50 0.4% 25490 510 514
29333 Ephemeral 50 0.4% 24600 492 492
22795 Ephemeral 50 0.4% 22860 457 1839
22774 Ephemeral 50 0.4% 21401 428 429
20000 Ephemeral 50 0.4% 23047 461 536
22655 Ephemeral 49 0.4% 20972 428 428
31990 App handler info 46 0.4% 43151 938 1465
5300 Unknown 44 0.4% 27099 616 701
30383 Addressable 44 0.4% 23495 534 542
22456 Ephemeral 43 0.4% 21378 497 790
25555 Ephemeral 42 0.4% 175994 4190 16987
20004 Ephemeral 39 0.3% 20619 529 549
3927 Unknown 39 0.3% 20809 534 544
22484 Ephemeral 36 0.3% 32790 911 1165
11998 Unknown 33 0.3% 15297 464 465
7368 Unknown 32 0.3% 33232 1038 1041
20387 Ephemeral 31 0.3% 15934 514 514
22507 Ephemeral 28 0.2% 25861 924 1163
20787 Ephemeral 26 0.2% 34212 1316 2282
10100 Unknown 25 0.2% 48552 1942 3825
30091 Addressable 23 0.2% 14133 614 629
30088 Addressable 23 0.2% 42847 1863 1871
38502 Addressable 22 0.2% 8470 385 385
4 Encrypted DM 22 0.2% 25752 1171 6250
1985 Label 22 0.2% 10436 474 483
10050 DM relays 21 0.2% 9383 447 465
30315 User status 20 0.2% 12697 635 926
22816 Ephemeral 20 0.2% 8580 429 429
4004 Unknown 19 0.2% 22211 1169 1169
22522 Ephemeral 18 0.2% 7632 424 424
22914 Ephemeral 17 0.2% 15202 894 1819
3 Follow list 15 0.1% 210884 14059 45848
22591 Ephemeral 15 0.1% 6390 426 426
30089 Chunked data 15 0.1% 221528 14769 44372
6300 Unknown 15 0.1% 83624 5575 17374
30023 Long-form article 15 0.1% 83310 5554 8177
20333 Ephemeral 15 0.1% 11950 797 798
22832 Ephemeral 14 0.1% 6006 429 429
1077 Unknown 14 0.1% 8498 607 607
22651 Ephemeral 14 0.1% 5992 428 428
30515 Addressable 14 0.1% 55160 3940 3940
23333 Ephemeral 11 0.1% 6455 587 601
2333 Unknown 11 0.1% 7423 675 689
30311 Addressable 10 0.1% 10569 1057 1214
21617 Ephemeral 10 0.1% 4950 495 495
30443 Addressable 10 0.1% 11954 1195 1292
9735 Zap receipt 10 0.1% 20601 2060 2282
1337 Unknown 10 0.1% 5696 570 811
25055 Ephemeral 10 0.1% 14724 1472 1764
21060 Ephemeral 9 0.1% 5130 570 570
38225 Addressable 9 0.1% 9197 1022 1047
22949 Ephemeral 9 0.1% 3879 431 431
4244 Unknown 9 0.1% 5076 564 564
7375 Unknown 9 0.1% 72189 8021 14089
7376 Unknown 9 0.1% 4689 521 521
38385 Addressable 9 0.1% 12280 1364 1496
31234 Addressable 9 0.1% 12744 1416 1416
22712 Ephemeral 8 0.1% 3424 428 428
38503 Addressable 8 0.1% 4424 553 553
30113 Addressable 8 0.1% 4688 586 701
6 Repost 8 0.1% 11116 1390 2553
31510 Addressable 7 0.1% 6853 979 982
22785 Ephemeral 7 0.1% 2996 428 428
22680 Ephemeral 7 0.1% 6191 884 1160
22793 Ephemeral 7 0.1% 2996 428 428
22684 Ephemeral 6 0.1% 2574 429 429
22225 Ephemeral 6 0.1% 2661 444 444
1002 Unknown 6 0.1% 5458 910 971
22739 Ephemeral 6 0.1% 2568 428 428
22658 Ephemeral 6 0.1% 5377 896 1164
30080 Addressable 5 0.0% 4658 932 985
33334 Addressable 5 0.0% 4155 831 831
10011 Unknown 5 0.0% 2155 431 431
25195 Ephemeral 5 0.0% 3390 678 678
22508 Ephemeral 5 0.0% 2130 426 426
22789 Ephemeral 5 0.0% 2140 428 428
30380 Addressable 5 0.0% 2270 454 454
38384 Addressable 5 0.0% 3715 743 743
20384 Ephemeral 5 0.0% 3195 639 639
22688 Ephemeral 5 0.0% 2150 430 430
22649 Ephemeral 5 0.0% 2135 427 427
39101 Addressable 5 0.0% 2665 533 533
22611 Ephemeral 5 0.0% 2130 426 426
22526 Ephemeral 5 0.0% 2150 430 430
22514 Ephemeral 4 0.0% 1700 425 425
22775 Ephemeral 4 0.0% 1716 429 429
443 Unknown 4 0.0% 4336 1084 1084
25196 Ephemeral 4 0.0% 7152 1788 2214
30618 Addressable 4 0.0% 6570 1642 4371
23194 NWC request 4 0.0% 2106 526 576
1040 Unknown 4 0.0% 4192 1048 1048
38500 Addressable 4 0.0% 2848 712 713
22236 Ephemeral 4 0.0% 3109 777 801
1111 Comment 4 0.0% 5593 1398 1988
3434 Unknown 3 0.0% 2035 678 705
1050 Unknown 3 0.0% 1311 437 437
33051 Addressable 3 0.0% 1559 520 522
7000 Unknown 3 0.0% 2329 776 779
22309 Ephemeral 3 0.0% 1331 444 444
30617 Addressable 3 0.0% 3267 1089 1089
10030 Emoji list 3 0.0% 1215 405 405
22253 Ephemeral 3 0.0% 1331 444 444
22345 Ephemeral 3 0.0% 1331 444 444
4245 Unknown 3 0.0% 1332 444 444
22423 Ephemeral 3 0.0% 1331 444 444
4243 Unknown 3 0.0% 1692 564 564
10000 Mute list 3 0.0% 4678 1559 3850
22352 Ephemeral 3 0.0% 1330 443 444
38383 Addressable 3 0.0% 2159 720 743
22327 Ephemeral 3 0.0% 1330 443 444
30213 Addressable 3 0.0% 1880 627 637
20 Picture 2 0.0% 1272 636 827
10312 Unknown 2 0.0% 973 486 491
10007 Search relays 2 0.0% 1272 636 754
10086 Unknown 2 0.0% 1451 726 757
22891 Ephemeral 2 0.0% 2272 1136 1843
22495 Ephemeral 2 0.0% 854 427 427
4242 Unknown 2 0.0% 12868 6434 6443
30000 Follow sets 2 0.0% 1773 886 950
22777 Ephemeral 2 0.0% 856 428 428
22391 Ephemeral 2 0.0% 887 444 444
6472 Unknown 2 0.0% 1106 553 553
22239 Ephemeral 2 0.0% 887 444 444
22146 Ephemeral 2 0.0% 886 443 443
22420 Ephemeral 2 0.0% 887 444 444
10063 Unknown 2 0.0% 1012 506 627
22193 Ephemeral 2 0.0% 886 443 443
22275 Ephemeral 2 0.0% 886 443 443
31000 Addressable 2 0.0% 1204 602 616
22738 Ephemeral 2 0.0% 858 429 429
31340 Addressable 2 0.0% 854 427 430
22218 Ephemeral 2 0.0% 887 444 444
22285 Ephemeral 2 0.0% 887 444 444
7403 Unknown 2 0.0% 982 491 491
22302 Ephemeral 2 0.0% 886 443 443
22241 Ephemeral 2 0.0% 887 444 444
22288 Ephemeral 2 0.0% 887 444 444
22120 Ephemeral 2 0.0% 2170 1085 1118
22119 Ephemeral 2 0.0% 1691 846 846
15750 Unknown 2 0.0% 1184 592 620
10443 Unknown 2 0.0% 2578 1289 1289
22736 Ephemeral 1 0.0% 429 429 429
22653 Ephemeral 1 0.0% 1840 1840 1840
22842 Ephemeral 1 0.0% 1847 1847 1847
22190 Ephemeral 1 0.0% 443 443 443
37779 Addressable 1 0.0% 523 523 523
22312 Ephemeral 1 0.0% 443 443 443
1717 Unknown 1 0.0% 1776 1776 1776
22407 Ephemeral 1 0.0% 425 425 425
1340 Unknown 1 0.0% 435 435 435
10051 Unknown 1 0.0% 443 443 443
10012 Unknown 1 0.0% 522 522 522
22368 Ephemeral 1 0.0% 443 443 443
10003 Bookmarks 1 0.0% 1974 1974 1974
39015 Addressable 1 0.0% 696 696 696
1311 Unknown 1 0.0% 534 534 534
22820 Ephemeral 1 0.0% 430 430 430
22928 Ephemeral 1 0.0% 429 429 429
22713 Ephemeral 1 0.0% 429 429 429
22543 Ephemeral 1 0.0% 1843 1843 1843
22369 Ephemeral 1 0.0% 443 443 443
22314 Ephemeral 1 0.0% 443 443 443
10001 Pinned notes 1 0.0% 710 710 710
30815 Addressable 1 0.0% 37534 37534 37534
31339 Addressable 1 0.0% 1470 1470 1470
38422 Addressable 1 0.0% 20019 20019 20019
30087 Addressable 1 0.0% 7325 7325 7325
30090 Addressable 1 0.0% 1515 1515 1515
10089 Unknown 1 0.0% 1097 1097 1097
13196 Unknown 1 0.0% 656 656 656
39050 Addressable 1 0.0% 1843 1843 1843
10005 Unknown 1 0.0% 694 694 694
30554 Addressable 1 0.0% 609 609 609
22765 Ephemeral 1 0.0% 431 431 431
1078 Unknown 1 0.0% 13949 13949 13949
22740 Ephemeral 1 0.0% 3194 3194 3194
38111 Addressable 1 0.0% 747 747 747
4747 Unknown 1 0.0% 721 721 721
10019 Unknown 1 0.0% 536 536 536
22333 Ephemeral 1 0.0% 443 443 443
22318 Ephemeral 1 0.0% 443 443 443
23010 Ephemeral 1 0.0% 429 429 429
30079 Addressable 1 0.0% 875 875 875
22455 Ephemeral 1 0.0% 1840 1840 1840
22219 Ephemeral 1 0.0% 443 443 443
30787 Addressable 1 0.0% 1370 1370 1370
22624 Ephemeral 1 0.0% 3198 3198 3198
22750 Ephemeral 1 0.0% 553 553 553
22313 Ephemeral 1 0.0% 443 443 443
1080 Unknown 1 0.0% 1033 1033 1033
11316 Unknown 1 0.0% 725 725 725
11317 Unknown 1 0.0% 12051 12051 12051
12222 Unknown 1 0.0% 579 579 579
22823 Ephemeral 1 0.0% 429 429 429
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 09:32:17 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
[2026-08-02 09:32:24] [RELAY] wss://relay.primal.net EOSE
@@ -0,0 +1,207 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 2m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 4267
Total bytes: 6726611 (6.41 MB)
Avg event size: 1576 bytes
Max event: 42385 bytes (kind 10002)
Unique pubkeys: 1860
Relays contacted: 3
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 1670 1 0 0 0 0 OK
wss://nos.lol 1792 0 0 0 0 0 OK
wss://relay.damus.io 805 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 1199 28.1% 3547818 2959 5314
5 Deletion 466 10.9% 204587 439 1064
30078 App data 222 5.2% 948782 4274 40337
22668 Ephemeral 221 5.2% 94809 429 429
20001 Ephemeral (key xchg) 152 3.6% 106536 701 1755
4515 Unknown 152 3.6% 260732 1715 8759
22734 Ephemeral 108 2.5% 46116 427 427
1 Text note 105 2.5% 83026 791 3482
21050 Ephemeral 91 2.1% 89206 980 2558
24747 Ephemeral 85 2.0% 42775 503 671
22850 Ephemeral 77 1.8% 33110 430 430
22591 Ephemeral 66 1.5% 53607 812 1222
38501 App data (btc) 61 1.4% 45175 741 744
10002 Relay list 51 1.2% 63749 1250 42385
0 Profile metadata 47 1.1% 33672 716 1611
37195 Addressable 44 1.0% 32855 747 771
24242 Ephemeral 37 0.9% 21423 579 579
25050 Ephemeral 36 0.8% 15972 444 465
7 Reaction 36 0.8% 18965 527 646
22613 Ephemeral 35 0.8% 26154 747 1134
22484 Ephemeral 32 0.7% 29125 910 1163
22749 Ephemeral 29 0.7% 12441 429 429
24133 Ephemeral 25 0.6% 37446 1498 7334
22601 Ephemeral 25 0.6% 10700 428 428
22507 Ephemeral 24 0.6% 22582 941 1165
1059 Gift wrap seal 23 0.5% 41391 1800 2897
22699 Ephemeral 23 0.5% 9844 428 428
22676 Ephemeral 23 0.5% 9821 427 427
22774 Ephemeral 20 0.5% 8560 428 428
22795 Ephemeral 20 0.5% 8580 429 429
22655 Ephemeral 20 0.5% 8560 428 428
23003 Ephemeral 20 0.5% 8560 428 428
30382 Addressable 20 0.5% 9960 498 498
29333 Ephemeral 20 0.5% 9840 492 492
25555 Ephemeral 18 0.4% 62434 3469 17003
11998 Unknown 18 0.4% 8322 462 465
31990 App handler info 18 0.4% 16845 936 1465
4 Encrypted DM 18 0.4% 28064 1559 17018
13194 NWC info 18 0.4% 9252 514 514
22689 Ephemeral 17 0.4% 7259 427 427
5300 Unknown 17 0.4% 10495 617 701
445 Encrypted group msg 17 0.4% 81892 4817 19620
20000 Ephemeral 15 0.4% 7045 470 546
20387 Ephemeral 14 0.3% 7196 514 514
20004 Ephemeral 14 0.3% 7488 535 549
7368 Unknown 13 0.3% 13476 1037 1040
22456 Ephemeral 13 0.3% 6110 470 470
9735 Zap receipt 13 0.3% 26072 2006 2331
22551 Ephemeral 13 0.3% 11042 849 1161
22760 Ephemeral 13 0.3% 11014 847 1161
22658 Ephemeral 13 0.3% 11050 850 1164
10100 Unknown 11 0.3% 20684 1880 3825
30315 User status 11 0.3% 8899 809 1038
20787 Ephemeral 11 0.3% 15458 1405 2474
22236 Ephemeral 10 0.2% 7818 782 814
22832 Ephemeral 10 0.2% 4286 429 429
38502 Addressable 8 0.2% 3080 385 385
4004 Unknown 8 0.2% 9352 1169 1169
22785 Ephemeral 8 0.2% 3432 429 429
3 Follow list 8 0.2% 35850 4481 9845
1985 Label 8 0.2% 3792 474 474
22584 Ephemeral 8 0.2% 3416 427 427
22816 Ephemeral 8 0.2% 3432 429 429
22752 Ephemeral 8 0.2% 6212 776 3199
6 Repost 7 0.2% 10112 1445 1872
443 Unknown 7 0.2% 7780 1111 1116
22651 Ephemeral 6 0.1% 2568 428 428
1077 Unknown 6 0.1% 3642 607 607
26428 Ephemeral 6 0.1% 5254 876 876
22616 Ephemeral 6 0.1% 2574 429 429
22626 Ephemeral 6 0.1% 2568 428 428
22914 Ephemeral 6 0.1% 2580 430 430
22614 Ephemeral 6 0.1% 2568 428 428
22419 Ephemeral 5 0.1% 2218 444 444
22847 Ephemeral 5 0.1% 2155 431 431
30088 Addressable 5 0.1% 9317 1863 1871
10050 DM relays 5 0.1% 2250 450 450
30091 Addressable 5 0.1% 3085 617 629
30311 Addressable 5 0.1% 5153 1031 1197
23080 Ephemeral 5 0.1% 2155 431 431
38225 Addressable 4 0.1% 4077 1019 1036
4244 Unknown 4 0.1% 2256 564 564
26429 Ephemeral 4 0.1% 1964 491 491
3927 Unknown 4 0.1% 2132 533 533
22806 Ephemeral 4 0.1% 1712 428 428
30000 Follow sets 4 0.1% 9358 2340 2845
21617 Ephemeral 4 0.1% 1980 495 495
20333 Ephemeral 4 0.1% 3188 797 798
22548 Ephemeral 4 0.1% 1720 430 430
38503 Addressable 4 0.1% 2212 553 553
30515 Addressable 4 0.1% 18496 4624 4624
22740 Ephemeral 4 0.1% 1708 427 427
22226 Ephemeral 3 0.1% 1331 444 444
10011 Unknown 3 0.1% 1375 458 513
30089 Chunked data 3 0.1% 6944 2315 3412
30023 Long-form article 3 0.1% 19822 6607 7860
21060 Ephemeral 3 0.1% 1710 570 570
22526 Ephemeral 3 0.1% 1290 430 430
22511 Ephemeral 3 0.1% 2383 794 1163
22167 Ephemeral 3 0.1% 1331 444 444
30213 Addressable 3 0.1% 1866 622 632
22748 Ephemeral 3 0.1% 1284 428 428
31234 Addressable 3 0.1% 4248 1416 1416
22838 Ephemeral 2 0.0% 860 430 430
38500 Addressable 2 0.0% 1424 712 713
38384 Addressable 2 0.0% 1486 743 743
30443 Addressable 2 0.0% 2379 1190 1220
20384 Ephemeral 2 0.0% 1278 639 639
34237 Addressable 2 0.0% 836 418 418
22280 Ephemeral 2 0.0% 887 444 444
23194 NWC request 2 0.0% 1020 510 510
22320 Ephemeral 2 0.0% 887 444 444
30080 Addressable 2 0.0% 1971 986 986
22205 Ephemeral 2 0.0% 887 444 444
22770 Ephemeral 2 0.0% 862 431 431
22305 Ephemeral 2 0.0% 887 444 444
22356 Ephemeral 2 0.0% 886 443 443
22611 Ephemeral 2 0.0% 852 426 426
22217 Ephemeral 2 0.0% 887 444 444
22884 Ephemeral 2 0.0% 860 430 430
33334 Addressable 2 0.0% 1662 831 831
6300 Unknown 2 0.0% 8371 4186 5058
22169 Ephemeral 2 0.0% 887 444 444
22190 Ephemeral 2 0.0% 887 444 444
30079 Addressable 2 0.0% 1750 875 875
39101 Addressable 2 0.0% 1066 533 533
22345 Ephemeral 2 0.0% 887 444 444
22310 Ephemeral 2 0.0% 887 444 444
1111 Comment 2 0.0% 2557 1278 1564
22235 Ephemeral 2 0.0% 887 444 444
38385 Addressable 2 0.0% 2552 1276 1319
4747 Unknown 1 0.0% 721 721 721
22844 Ephemeral 1 0.0% 428 428 428
22807 Ephemeral 1 0.0% 3205 3205 3205
30066 Addressable 1 0.0% 675 675 675
4245 Unknown 1 0.0% 444 444 444
7376 Unknown 1 0.0% 521 521 521
22450 Ephemeral 1 0.0% 443 443 443
22261 Ephemeral 1 0.0% 443 443 443
4243 Unknown 1 0.0% 564 564 564
22242 Ephemeral 1 0.0% 443 443 443
10063 Unknown 1 0.0% 415 415 415
22306 Ephemeral 1 0.0% 443 443 443
22174 Ephemeral 1 0.0% 443 443 443
22831 Ephemeral 1 0.0% 3182 3182 3182
22227 Ephemeral 1 0.0% 443 443 443
7375 Unknown 1 0.0% 8629 8629 8629
22685 Ephemeral 1 0.0% 553 553 553
22299 Ephemeral 1 0.0% 443 443 443
22193 Ephemeral 1 0.0% 443 443 443
22975 Ephemeral 1 0.0% 3195 3195 3195
30380 Addressable 1 0.0% 454 454 454
20 Picture 1 0.0% 860 860 860
10030 Emoji list 1 0.0% 405 405 405
30087 Addressable 1 0.0% 7325 7325 7325
30090 Addressable 1 0.0% 1515 1515 1515
22220 Ephemeral 1 0.0% 443 443 443
39019 Addressable 1 0.0% 961 961 961
39015 Addressable 1 0.0% 696 696 696
30617 Addressable 1 0.0% 1089 1089 1089
30618 Addressable 1 0.0% 733 733 733
31991 Addressable 1 0.0% 461 461 461
1080 Unknown 1 0.0% 949 949 949
3434 Unknown 1 0.0% 705 705 705
33333 Addressable 1 0.0% 2346 2346 2346
22735 Ephemeral 1 0.0% 430 430 430
22415 Ephemeral 1 0.0% 443 443 443
21966 Ephemeral 1 0.0% 443 443 443
11316 Unknown 1 0.0% 725 725 725
11317 Unknown 1 0.0% 12051 12051 12051
25195 Ephemeral 1 0.0% 678 678 678
25196 Ephemeral 1 0.0% 1362 1362 1362
22402 Ephemeral 1 0.0% 443 443 443
31510 Addressable 1 0.0% 979 979 979
1078 Unknown 1 0.0% 14200 14200 14200
38383 Addressable 1 0.0% 685 685 685
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 09:38:02 2026
# Config: cache_all_test_config.jsonc
# Duration: 120 seconds
[2026-08-02 09:38:07] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 09:38:07] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 09:38:07] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 09:38:08] [RELAY] wss://relay.primal.net EOSE
@@ -0,0 +1,206 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 2m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 5012
Total bytes: 8091410 (7.72 MB)
Avg event size: 1614 bytes
Max event: 52672 bytes (kind 3)
Unique pubkeys: 2034
Relays contacted: 3
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 1599 1 0 0 0 0 OK
wss://nos.lol 2389 0 0 0 0 0 OK
wss://relay.damus.io 1024 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 1361 27.2% 4189338 3078 5314
5 Deletion 579 11.6% 254363 439 624
22668 Ephemeral 265 5.3% 113685 429 429
30078 App data 190 3.8% 702178 3696 14343
20001 Ephemeral (key xchg) 181 3.6% 108257 598 1489
22639 Ephemeral 176 3.5% 136058 773 1228
4515 Unknown 168 3.4% 319242 1900 14220
24133 Ephemeral 152 3.0% 464764 3058 14162
22734 Ephemeral 135 2.7% 57645 427 427
31991 Addressable 116 2.3% 53476 461 461
21050 Ephemeral 84 1.7% 81900 975 2558
1 Text note 80 1.6% 58412 730 1443
22795 Ephemeral 76 1.5% 104000 1368 1848
22850 Ephemeral 75 1.5% 33353 445 1169
38501 App data (btc) 69 1.4% 51105 741 744
22842 Ephemeral 55 1.1% 92475 1681 1849
10002 Relay list 48 1.0% 19784 412 537
0 Profile metadata 47 0.9% 34961 744 1356
37195 Addressable 38 0.8% 28214 742 771
7 Reaction 38 0.8% 21600 568 779
25050 Ephemeral 36 0.7% 15972 444 465
24242 Ephemeral 34 0.7% 19686 579 579
1059 Gift wrap seal 33 0.7% 58147 1762 3298
22484 Ephemeral 32 0.6% 29156 911 1164
22507 Ephemeral 28 0.6% 24308 868 1165
22613 Ephemeral 25 0.5% 17238 690 691
29333 Ephemeral 25 0.5% 12300 492 492
22601 Ephemeral 24 0.5% 10272 428 428
5300 Unknown 23 0.5% 14096 613 701
22751 Ephemeral 23 0.5% 9798 426 426
22699 Ephemeral 22 0.4% 9416 428 428
11998 Unknown 22 0.4% 10182 463 465
22676 Ephemeral 22 0.4% 9394 427 427
13194 NWC info 21 0.4% 10617 506 514
22774 Ephemeral 20 0.4% 8560 428 428
23003 Ephemeral 20 0.4% 8560 428 428
22655 Ephemeral 19 0.4% 8132 428 428
22543 Ephemeral 19 0.4% 34993 1842 1848
22455 Ephemeral 19 0.4% 34943 1839 1844
25555 Ephemeral 19 0.4% 94673 4983 17111
30443 Addressable 19 0.4% 22379 1178 1260
22777 Ephemeral 19 0.4% 16685 878 1651
22891 Ephemeral 18 0.4% 33189 1844 1848
31990 App handler info 17 0.3% 15825 931 1465
20000 Ephemeral 16 0.3% 7206 450 522
32123 Addressable 14 0.3% 10528 752 900
22456 Ephemeral 13 0.3% 6110 470 470
20004 Ephemeral 13 0.3% 6741 519 549
22591 Ephemeral 13 0.3% 6348 488 697
20787 Ephemeral 12 0.2% 16864 1405 2478
4 Encrypted DM 12 0.2% 7972 664 786
20387 Ephemeral 12 0.2% 6168 514 514
38502 Addressable 12 0.2% 4620 385 385
10100 Unknown 11 0.2% 20938 1903 3825
20050 Ephemeral 11 0.2% 9711 883 891
3 Follow list 10 0.2% 72137 7214 52672
9735 Zap receipt 10 0.2% 20833 2083 2143
445 Encrypted group msg 10 0.2% 17866 1787 4126
22626 Ephemeral 10 0.2% 4280 428 428
7368 Unknown 9 0.2% 9343 1038 1041
22736 Ephemeral 9 0.2% 3816 424 424
1985 Label 8 0.2% 3810 476 483
30091 Addressable 8 0.2% 4927 616 629
30315 User status 8 0.2% 4522 565 872
25055 Ephemeral 8 0.2% 10605 1326 1764
22791 Ephemeral 8 0.2% 3416 427 427
22816 Ephemeral 8 0.2% 3432 429 429
4004 Unknown 8 0.2% 9352 1169 1169
30088 Addressable 8 0.2% 14929 1866 1871
31234 Addressable 8 0.2% 14048 1756 1756
22813 Ephemeral 7 0.1% 2996 428 428
22957 Ephemeral 7 0.1% 3010 430 430
30089 Chunked data 7 0.1% 45972 6567 27988
32678 Addressable 7 0.1% 5704 815 900
22832 Ephemeral 6 0.1% 2574 429 429
22897 Ephemeral 6 0.1% 2586 431 431
22549 Ephemeral 6 0.1% 2574 429 429
22702 Ephemeral 6 0.1% 4753 792 2618
20333 Ephemeral 6 0.1% 4780 797 798
30023 Long-form article 6 0.1% 21101 3517 5056
22568 Ephemeral 5 0.1% 2135 427 427
38385 Addressable 5 0.1% 6849 1370 1496
4244 Unknown 5 0.1% 2820 564 564
22717 Ephemeral 5 0.1% 2135 427 427
21617 Ephemeral 4 0.1% 1980 495 495
38225 Addressable 4 0.1% 4061 1015 1039
6 Repost 4 0.1% 8823 2206 3647
22333 Ephemeral 4 0.1% 1773 443 444
22962 Ephemeral 3 0.1% 1290 430 430
33334 Addressable 3 0.1% 2229 743 831
1002 Unknown 3 0.1% 2793 931 979
22244 Ephemeral 3 0.1% 1330 443 444
22907 Ephemeral 3 0.1% 1287 429 429
10011 Unknown 3 0.1% 1229 410 431
22258 Ephemeral 3 0.1% 1330 443 444
22245 Ephemeral 3 0.1% 1331 444 444
30618 Addressable 3 0.1% 1755 585 733
22296 Ephemeral 3 0.1% 1331 444 444
38503 Addressable 3 0.1% 1659 553 553
22338 Ephemeral 3 0.1% 1330 443 444
22195 Ephemeral 3 0.1% 1331 444 444
22342 Ephemeral 3 0.1% 1331 444 444
10050 DM relays 3 0.1% 1350 450 450
30311 Addressable 3 0.1% 3414 1138 1197
32680 Addressable 2 0.0% 1376 688 688
30380 Addressable 2 0.0% 908 454 454
22611 Ephemeral 2 0.0% 852 426 426
4243 Unknown 2 0.0% 1128 564 564
22526 Ephemeral 2 0.0% 860 430 430
22320 Ephemeral 2 0.0% 887 444 444
4245 Unknown 2 0.0% 888 444 444
1984 Report 2 0.0% 873 436 446
1111 Comment 2 0.0% 3115 1558 1558
22311 Ephemeral 2 0.0% 886 443 443
22662 Ephemeral 2 0.0% 858 429 429
22089 Ephemeral 2 0.0% 886 443 443
22221 Ephemeral 2 0.0% 887 444 444
22157 Ephemeral 2 0.0% 887 444 444
38384 Addressable 2 0.0% 1486 743 743
20384 Ephemeral 2 0.0% 1278 639 639
30080 Addressable 2 0.0% 1679 840 985
39101 Addressable 2 0.0% 1066 533 533
6300 Unknown 2 0.0% 11904 5952 6898
10003 Bookmarks 2 0.0% 14466 7233 7233
30515 Addressable 2 0.0% 9248 4624 4624
22829 Ephemeral 2 0.0% 6387 3194 3194
22106 Ephemeral 2 0.0% 887 444 444
31927 Addressable 2 0.0% 3000 1500 1500
31510 Addressable 2 0.0% 1959 980 980
3927 Unknown 2 0.0% 1066 533 533
1040 Unknown 2 0.0% 2096 1048 1048
31000 Addressable 2 0.0% 1204 602 616
22201 Ephemeral 2 0.0% 886 443 443
22269 Ephemeral 2 0.0% 887 444 444
22818 Ephemeral 2 0.0% 852 426 426
22364 Ephemeral 2 0.0% 887 444 444
25195 Ephemeral 1 0.0% 678 678 678
25196 Ephemeral 1 0.0% 1362 1362 1362
22283 Ephemeral 1 0.0% 443 443 443
10030 Emoji list 1 0.0% 405 405 405
22706 Ephemeral 1 0.0% 3192 3192 3192
22236 Ephemeral 1 0.0% 737 737 737
31339 Addressable 1 0.0% 1470 1470 1470
22651 Ephemeral 1 0.0% 428 428 428
38500 Addressable 1 0.0% 713 713 713
10000 Mute list 1 0.0% 1311 1311 1311
31926 Addressable 1 0.0% 1038 1038 1038
22757 Ephemeral 1 0.0% 428 428 428
39015 Addressable 1 0.0% 696 696 696
30617 Addressable 1 0.0% 1089 1089 1089
22234 Ephemeral 1 0.0% 443 443 443
30815 Addressable 1 0.0% 37534 37534 37534
1078 Unknown 1 0.0% 13939 13939 13939
39050 Addressable 1 0.0% 1843 1843 1843
22281 Ephemeral 1 0.0% 443 443 443
30166 Addressable 1 0.0% 808 808 808
22772 Ephemeral 1 0.0% 429 429 429
22356 Ephemeral 1 0.0% 443 443 443
30554 Addressable 1 0.0% 609 609 609
38422 Addressable 1 0.0% 20025 20025 20025
38000 Addressable 1 0.0% 562 562 562
22069 Ephemeral 1 0.0% 443 443 443
1050 Unknown 1 0.0% 437 437 437
33051 Addressable 1 0.0% 522 522 522
39036 Addressable 1 0.0% 1138 1138 1138
23194 NWC request 1 0.0% 510 510 510
30081 Addressable 1 0.0% 1370 1370 1370
22299 Ephemeral 1 0.0% 443 443 443
22556 Ephemeral 1 0.0% 427 427 427
22302 Ephemeral 1 0.0% 443 443 443
39019 Addressable 1 0.0% 961 961 961
30000 Follow sets 1 0.0% 823 823 823
38383 Addressable 1 0.0% 657 657 657
30087 Addressable 1 0.0% 7325 7325 7325
30090 Addressable 1 0.0% 1515 1515 1515
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 09:48:55 2026
# Config: cache_all_test_config.jsonc
# Duration: 120 seconds
[2026-08-02 09:49:03] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 09:49:03] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 09:49:03] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 09:49:03] [RELAY] wss://relay.primal.net EOSE
@@ -0,0 +1,51 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 0
Total bytes: 0 (0.00 MB)
Avg event size: 0 bytes
Max event: 0 bytes (kind 0)
Unique pubkeys: 0
Relays contacted: 27
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 0 0 0 0 0 0 DOWN
wss://nos.lol 0 0 0 0 0 0 DOWN
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 0 0 0 0 0 0 DOWN
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://nostr.wine 0 0 0 0 0 0 DOWN
wss://nostr.oxtr.dev 0 0 0 0 0 0 DOWN
wss://spatia-arcana.com/inbox 0 0 0 0 0 0 DOWN
wss://eden.nostr.land 0 0 0 0 0 0 DOWN
wss://relay.nostr.band 0 0 0 0 0 0 DOWN
wss://theforest.nostr1.com 0 0 0 0 0 0 DOWN
wss://wot.utxo.one 0 0 0 0 0 0 DOWN
wss://premium.primal.net 0 0 0 0 0 0 DOWN
wss://nostr.mom 0 0 0 0 0 0 DOWN
wss://relay.mostr.pub 0 0 0 0 0 0 DOWN
wss://offchain.pub 0 0 0 0 0 0 DOWN
wss://relay.divine.video 0 0 0 0 0 0 DOWN
wss://relay.nostrplebs.com 0 0 0 0 0 0 DOWN
wss://nostr-pub.wellorder.net 0 0 0 0 0 0 DOWN
wss://devrelay.azzamo.net 0 0 0 0 0 0 DOWN
wss://relay.noderunners.network 0 0 0 0 0 0 DOWN
wss://nostr-pub.semisol.dev 0 0 0 0 0 0 DOWN
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
wss://relay.nostrcheck.me 0 0 0 0 0 0 DOWN
wss://relay.nostr.info 0 0 0 0 0 0 DOWN
wss://relay.momostr.pink 0 0 0 0 0 0 DOWN
wss://bitsat.molonlabe.holdings 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:01:51 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
@@ -0,0 +1,39 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 0
Total bytes: 0 (0.00 MB)
Avg event size: 0 bytes
Max event: 0 bytes (kind 0)
Unique pubkeys: 0
Relays contacted: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 0 0 0 0 0 0 DOWN
wss://nos.lol 0 0 0 0 0 0 DOWN
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 0 0 0 0 0 0 DOWN
wss://nostr.oxtr.dev 0 0 0 0 0 0 DOWN
wss://pyramid.fiatjaf.com/inbox 0 0 0 0 0 0 DOWN
wss://relay.mostr.pub 0 0 0 0 0 0 DOWN
wss://relay.divine.video 0 0 0 0 0 0 DOWN
wss://nostr.wine 0 0 0 0 0 0 DOWN
wss://theforest.nostr1.com 0 0 0 0 0 0 DOWN
wss://relay.nostrplebs.com 0 0 0 0 0 0 DOWN
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
wss://relay.nostr.band 0 0 0 0 0 0 DOWN
wss://relay.nostr.info 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:10:55 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
@@ -0,0 +1,35 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 0
Total bytes: 0 (0.00 MB)
Avg event size: 0 bytes
Max event: 0 bytes (kind 0)
Unique pubkeys: 0
Relays contacted: 11
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 0 0 0 0 0 0 DOWN
wss://nos.lol 0 0 0 0 0 0 DOWN
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 0 0 0 0 0 0 DOWN
wss://nostr.bitcoiner.social 0 0 0 0 0 0 DOWN
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://pyramid.fiatjaf.com/inbox 0 0 0 0 0 0 DOWN
wss://relay.divine.video 0 0 0 0 0 0 DOWN
wss://relay.nostrplebs.com 0 0 0 0 0 0 DOWN
wss://eden.nostr.land 0 0 0 0 0 0 DOWN
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:17:20 2026
# Config: cache_all_test_config.jsonc
# Duration: 30 seconds
@@ -0,0 +1,197 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 1m 54s
=== Global Summary ===
Total events: 4719
Total bytes: 6608416 (6.30 MB)
Avg event size: 1400 bytes
Max event: 44372 bytes (kind 30089)
Unique pubkeys: 1333
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 804 0 0 0 0 3 OK
wss://nostr-pub.wellorder.net 83 0 0 0 0 0 OK
wss://nostr.mom 175 0 0 0 0 0 OK
wss://nostr.oxtr.dev 108 0 0 0 0 0 OK
wss://offchain.pub 627 0 0 0 0 0 OK
wss://premium.primal.net 1 0 0 0 0 0 OK
wss://relay.damus.io 0 0 0 0 0 0 DOWN
wss://relay.ditto.pub 721 0 0 0 0 0 OK
wss://relay.divine.video 3 0 0 0 0 0 OK
wss://relay.momostr.pink 86 0 0 0 0 0 OK
wss://relay.mostr.pub 836 0 0 0 0 0 OK
wss://relay.nostrplebs.com 4 0 0 0 0 0 OK
wss://relay.primal.net 1239 0 0 0 0 0 OK
wss://theforest.nostr1.com 32 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 675 14.3% 2192938 3249 5314
30382 Addressable 563 11.9% 255591 454 498
5 Deletion 341 7.2% 153667 451 1404
22639 Ephemeral 231 4.9% 178011 771 1230
22551 Ephemeral 182 3.9% 78260 430 430
20001 Ephemeral 143 3.0% 70427 492 1206
30078 App data 141 3.0% 1361122 9653 44169
0 Profile metadata 135 2.9% 119793 887 3483
22734 Ephemeral 122 2.6% 52094 427 427
24747 Ephemeral 101 2.1% 59352 588 594
1 Text note 90 1.9% 92140 1024 4709
22668 Ephemeral 88 1.9% 37752 429 429
22850 Ephemeral 83 1.8% 35690 430 430
22870 Ephemeral 76 1.6% 77061 1014 2202
22951 Ephemeral 75 1.6% 77282 1030 2204
10002 Relay list 75 1.6% 202196 2696 42385
22912 Ephemeral 74 1.6% 31672 428 428
38501 Addressable 65 1.4% 48152 741 744
24133 Ephemeral 63 1.3% 72262 1147 2898
6 Repost 50 1.1% 40985 820 3346
37195 Addressable 48 1.0% 36225 755 771
21050 Ephemeral 48 1.0% 46800 975 2558
4515 Unknown 46 1.0% 81638 1775 6028
25555 Ephemeral 45 1.0% 209551 4657 16603
7 Reaction 40 0.8% 22486 562 805
22917 Ephemeral 39 0.8% 35435 909 1166
22699 Ephemeral 39 0.8% 26723 685 1166
22690 Ephemeral 39 0.8% 16770 430 430
22918 Ephemeral 38 0.8% 36470 960 1507
22832 Ephemeral 37 0.8% 16128 436 446
25050 Ephemeral 35 0.7% 15507 443 465
30383 Addressable 35 0.7% 18766 536 541
22713 Ephemeral 34 0.7% 14570 429 430
22698 Ephemeral 34 0.7% 31371 923 1674
22601 Ephemeral 28 0.6% 11984 428 428
22655 Ephemeral 28 0.6% 11984 428 428
1059 Gift wrap seal 23 0.5% 46912 2040 5285
22751 Ephemeral 21 0.4% 8946 426 426
22676 Ephemeral 21 0.4% 8967 427 427
29333 Ephemeral 20 0.4% 9840 492 492
5300 Unknown 20 0.4% 11959 598 630
22743 Ephemeral 19 0.4% 8132 428 428
22603 Ephemeral 19 0.4% 8094 426 426
22693 Ephemeral 19 0.4% 8113 427 427
22774 Ephemeral 19 0.4% 8132 428 428
22808 Ephemeral 19 0.4% 8151 429 429
22575 Ephemeral 19 0.4% 8075 425 425
23003 Ephemeral 19 0.4% 8132 428 428
22949 Ephemeral 19 0.4% 8151 429 429
23334 Ephemeral 19 0.4% 8189 431 431
24242 Ephemeral 17 0.4% 9843 579 579
22816 Ephemeral 16 0.3% 6864 429 429
20787 Ephemeral 16 0.3% 25100 1569 2478
13194 NWC info 15 0.3% 7574 505 514
38502 Addressable 14 0.3% 5390 385 385
22236 Ephemeral 14 0.3% 11214 801 814
22613 Ephemeral 13 0.3% 8977 691 694
30088 Addressable 12 0.3% 22340 1862 1871
22484 Ephemeral 12 0.3% 10948 912 1165
30091 Addressable 12 0.3% 7385 615 629
20004 Ephemeral 11 0.2% 5643 513 549
31990 App handler info 11 0.2% 8711 792 1465
445 Encrypted group msg 10 0.2% 17972 1797 4126
22795 Ephemeral 9 0.2% 3861 429 429
22507 Ephemeral 9 0.2% 8480 942 1165
22922 Ephemeral 9 0.2% 3870 430 430
24421 Ephemeral 9 0.2% 4644 516 516
22830 Ephemeral 9 0.2% 7317 813 1168
20000 Ephemeral 9 0.2% 3854 428 484
22955 Ephemeral 9 0.2% 3852 428 428
22793 Ephemeral 9 0.2% 3843 427 427
30315 User status 8 0.2% 4604 576 926
20387 Ephemeral 8 0.2% 4112 514 514
7368 Unknown 8 0.2% 8309 1039 1041
4004 Unknown 8 0.2% 9352 1169 1169
443 Unknown 8 0.2% 8928 1116 1116
11998 Unknown 7 0.1% 3255 465 465
10100 Unknown 7 0.1% 11851 1693 3825
22456 Ephemeral 7 0.1% 3290 470 470
30089 Chunked data 7 0.1% 157322 22475 44372
22737 Ephemeral 7 0.1% 4669 667 707
23337 Ephemeral 7 0.1% 3557 508 509
30515 Addressable 7 0.1% 18032 2576 2576
30311 Addressable 6 0.1% 5818 970 1197
1985 Unknown 6 0.1% 2860 477 482
22859 Ephemeral 5 0.1% 8606 1721 2195
4 Encrypted DM 5 0.1% 3222 644 722
10011 Unknown 5 0.1% 2027 405 431
22764 Ephemeral 4 0.1% 1712 428 428
22777 Ephemeral 4 0.1% 2790 698 698
4244 Unknown 4 0.1% 2256 564 564
30023 Long-form article 4 0.1% 22789 5697 6817
9735 Zap receipt 4 0.1% 7033 1758 1805
20333 Ephemeral 4 0.1% 3188 797 798
3 Follow list 3 0.1% 26232 8744 14166
38503 Addressable 3 0.1% 1655 552 552
10050 DM relays 3 0.1% 1411 470 511
21617 Ephemeral 3 0.1% 1485 495 495
1984 Report 3 0.1% 1986 662 662
3927 Unknown 3 0.1% 1599 533 533
3434 Unknown 3 0.1% 1907 636 665
22514 Ephemeral 2 0.0% 856 428 428
33334 Addressable 2 0.0% 1662 831 831
38500 Addressable 2 0.0% 1424 712 713
22526 Ephemeral 2 0.0% 860 430 430
30380 Addressable 2 0.0% 908 454 454
23194 NWC request 2 0.0% 1020 510 510
23042 Ephemeral 2 0.0% 858 429 429
38385 Addressable 2 0.0% 2692 1346 1478
30443 Addressable 2 0.0% 2369 1184 1219
22747 Ephemeral 2 0.0% 2064 1032 1288
4245 Unknown 2 0.0% 888 444 444
1111 Comment 2 0.0% 1936 968 981
38383 Addressable 2 0.0% 1439 720 720
4243 Unknown 2 0.0% 1128 564 564
38225 Addressable 1 0.0% 978 978 978
30618 Addressable 1 0.0% 733 733 733
31510 Addressable 1 0.0% 981 981 981
30617 Addressable 1 0.0% 1089 1089 1089
1077 Unknown 1 0.0% 607 607 607
20201 Ephemeral 1 0.0% 491 491 491
20200 Ephemeral 1 0.0% 491 491 491
1078 Unknown 1 0.0% 14173 14173 14173
6300 Unknown 1 0.0% 3698 3698 3698
11316 Unknown 1 0.0% 791 791 791
11317 Unknown 1 0.0% 2873 2873 2873
17 Unknown 1 0.0% 610 610 610
23059 Ephemeral 1 0.0% 429 429 429
22806 Ephemeral 1 0.0% 1520 1520 1520
22120 Ephemeral 1 0.0% 1127 1127 1127
13196 Unknown 1 0.0% 656 656 656
24913 Ephemeral 1 0.0% 431 431 431
12222 Unknown 1 0.0% 579 579 579
1040 Unknown 1 0.0% 1048 1048 1048
22119 Ephemeral 1 0.0% 845 845 845
10312 Unknown 1 0.0% 472 472 472
3079 Unknown 1 0.0% 909 909 909
30000 Addressable 1 0.0% 594 594 594
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:25:47 2026
# Config: cache_all_test_config.jsonc
# Duration: 120 seconds
[2026-08-02 10:26:23] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 10:26:23] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.damus.io STATUS: ?
[2026-08-02 10:26:23] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 10:26:23] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 10:26:59] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:26:59] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:27:59] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:27:59] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:28:09] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:28:10] [RELAY] wss://nos.lol CONNECTED
@@ -0,0 +1,257 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 5m 0s
Config: cache_all_test_config.jsonc
=== Global Summary ===
Total events: 14714
Total bytes: 20163747 (19.23 MB)
Avg event size: 1370 bytes
Max event: 51791 bytes (kind 30078)
Unique pubkeys: 3435
Relays contacted: 24
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 2820 0 0 0 0 2 OK
wss://nos.lol 2964 0 0 0 0 3 OK
wss://relay.damus.io 1999 0 0 0 0 0 OK
wss://relay.ditto.pub 2049 0 0 0 0 0 OK
wss://nostr.land 0 0 0 0 0 0 DOWN
wss://nostr.wine 0 0 0 0 0 0 DOWN
wss://nostr.oxtr.dev 599 0 0 0 0 1 OK
wss://spatia-arcana.com/inbox 0 0 0 0 0 0 DOWN
wss://nostrelites.org 0 0 0 0 0 0 DOWN
wss://offchain.pub 1431 0 0 0 0 0 OK
wss://relay.nostr.band 0 0 0 0 0 0 DOWN
wss://premium.primal.net 7 0 0 0 0 0 OK
wss://nostr.mom 412 0 0 0 0 0 OK
wss://relay.mostr.pub 2134 0 0 0 0 1 OK
wss://relay.divine.video 14 0 0 0 0 0 OK
wss://relay.nostrplebs.com 4 0 0 0 0 0 OK
wss://eden.nostr.land 0 0 0 0 0 0 DOWN
wss://theforest.nostr1.com 67 0 0 0 0 0 OK
wss://nostr-pub.wellorder.net 214 0 0 0 0 0 OK
wss://devrelay.azzamo.net 0 0 0 0 0 0 DOWN
wss://relay.noderunners.network 0 0 0 0 0 0 OK
wss://relay.shosho.live 0 0 0 0 0 0 DOWN
wss://relay.nostrcheck.me 0 0 0 0 0 0 DOWN
wss://basspistol.org 0 0 0 0 0 0 DOWN
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 2477 16.8% 8220486 3319 19622
30382 Addressable 1558 10.6% 708564 455 498
5 Deletion 1219 8.3% 542263 445 1404
22639 Ephemeral 607 4.1% 467729 771 1230
20001 Ephemeral (key xchg) 519 3.5% 251716 485 1431
22551 Ephemeral 479 3.3% 205970 430 430
30078 App data 432 2.9% 3024405 7001 51791
24747 Ephemeral 317 2.2% 188378 594 650
22734 Ephemeral 315 2.1% 134505 427 427
22668 Ephemeral 307 2.1% 131703 429 429
1 Text note 307 2.1% 270378 881 3665
0 Profile metadata 300 2.0% 260832 869 9187
22850 Ephemeral 242 1.6% 104060 430 430
38501 App data (btc) 241 1.6% 178603 741 744
22912 Ephemeral 238 1.6% 101864 428 428
4515 Unknown 225 1.5% 376537 1673 6028
10002 Relay list 211 1.4% 309808 1468 42385
24133 Ephemeral 193 1.3% 237962 1233 14162
22951 Ephemeral 179 1.2% 170939 955 2204
22870 Ephemeral 172 1.2% 165571 963 2202
25555 Ephemeral 157 1.1% 776467 4946 16603
37195 Addressable 142 1.0% 107593 758 869
21050 Ephemeral 141 1.0% 136906 971 2558
6 Repost 139 0.9% 113883 819 4053
7 Reaction 116 0.8% 65547 565 786
22832 Ephemeral 112 0.8% 48745 435 446
22690 Ephemeral 109 0.7% 46870 430 430
30383 Addressable 100 0.7% 53571 536 543
22713 Ephemeral 98 0.7% 41994 429 430
22601 Ephemeral 93 0.6% 39804 428 428
25050 Ephemeral 87 0.6% 38343 441 465
1059 Gift wrap seal 84 0.6% 158090 1882 5285
22699 Ephemeral 83 0.6% 55625 670 1872
22655 Ephemeral 80 0.5% 34240 428 428
22917 Ephemeral 75 0.5% 67279 897 1166
22676 Ephemeral 72 0.5% 30744 427 427
29333 Ephemeral 70 0.5% 34440 492 492
22918 Ephemeral 67 0.5% 65746 981 1876
20787 Ephemeral 62 0.4% 84460 1362 2478
22456 Ephemeral 61 0.4% 29310 480 790
22808 Ephemeral 59 0.4% 25296 429 429
24242 Ephemeral 57 0.4% 33003 579 579
22816 Ephemeral 56 0.4% 24024 429 429
22603 Ephemeral 55 0.4% 23430 426 426
22743 Ephemeral 55 0.4% 23540 428 428
22693 Ephemeral 54 0.4% 23058 427 427
22575 Ephemeral 54 0.4% 22950 425 425
23334 Ephemeral 53 0.4% 22843 431 431
13194 NWC info 52 0.4% 26592 511 514
22751 Ephemeral 51 0.3% 21726 426 426
22949 Ephemeral 50 0.3% 21450 429 429
11998 Unknown 50 0.3% 23202 464 465
24421 Ephemeral 48 0.3% 24795 517 525
22236 Ephemeral 47 0.3% 37103 789 814
22484 Ephemeral 47 0.3% 42919 913 1165
3927 Unknown 45 0.3% 23985 533 533
23003 Ephemeral 45 0.3% 19260 428 428
22774 Ephemeral 45 0.3% 19260 428 428
31990 App handler info 44 0.3% 40657 924 1465
5300 Unknown 43 0.3% 25958 604 701
20387 Ephemeral 40 0.3% 20560 514 514
20000 Ephemeral 38 0.3% 15672 412 487
38502 Addressable 38 0.3% 14630 385 385
22507 Ephemeral 38 0.3% 34749 914 1167
445 Encrypted group msg 37 0.3% 62402 1687 4130
22698 Ephemeral 34 0.2% 31371 923 1674
4 Encrypted DM 33 0.2% 44743 1356 17018
30315 User status 31 0.2% 20374 657 1166
1985 Label 31 0.2% 28905 932 4831
22859 Ephemeral 31 0.2% 30700 990 2195
22613 Ephemeral 31 0.2% 21415 691 694
20004 Ephemeral 29 0.2% 15525 535 549
7368 Unknown 27 0.2% 28042 1039 1041
30091 Addressable 27 0.2% 16620 616 629
30088 Addressable 27 0.2% 50292 1863 1871
22598 Ephemeral 27 0.2% 23198 859 1162
22955 Ephemeral 25 0.2% 10700 428 428
4004 Unknown 25 0.2% 29225 1169 1169
22793 Ephemeral 25 0.2% 10675 427 427
22922 Ephemeral 25 0.2% 10750 430 430
22795 Ephemeral 25 0.2% 10725 429 429
10100 Unknown 24 0.2% 39530 1647 3825
22764 Ephemeral 22 0.1% 9416 428 428
30089 Chunked data 18 0.1% 260436 14469 44372
30311 Addressable 18 0.1% 18058 1003 1197
20333 Ephemeral 18 0.1% 14340 797 798
443 Unknown 18 0.1% 20088 1116 1116
38383 Addressable 18 0.1% 13557 753 972
22777 Ephemeral 16 0.1% 12435 777 1159
30023 Long-form article 16 0.1% 92998 5812 8050
4244 Unknown 15 0.1% 8460 564 564
30515 Addressable 14 0.1% 36064 2576 2576
22967 Ephemeral 13 0.1% 5603 431 431
10011 Unknown 13 0.1% 5283 406 431
3 Follow list 12 0.1% 97388 8116 24970
38503 Addressable 12 0.1% 6624 552 553
1077 Unknown 12 0.1% 7284 607 607
22550 Ephemeral 12 0.1% 5112 426 426
9735 Zap receipt 12 0.1% 21551 1796 1963
22591 Ephemeral 11 0.1% 4686 426 426
22830 Ephemeral 11 0.1% 8707 792 1168
23337 Ephemeral 11 0.1% 5589 508 509
38385 Addressable 9 0.1% 12290 1366 1478
10050 DM relays 9 0.1% 4160 462 511
39101 Addressable 8 0.1% 4264 533 533
31510 Addressable 8 0.1% 7834 979 982
1111 Comment 8 0.1% 9909 1239 1706
1002 Unknown 7 0.0% 6647 950 1009
22737 Ephemeral 7 0.0% 4669 667 707
33334 Addressable 6 0.0% 4986 831 831
38384 Addressable 6 0.0% 4458 743 743
20384 Ephemeral 6 0.0% 3834 639 639
22526 Ephemeral 6 0.0% 2580 430 430
30443 Addressable 6 0.0% 7288 1215 1260
3434 Unknown 6 0.0% 3902 650 665
21617 Ephemeral 6 0.0% 2970 495 495
38500 Addressable 6 0.0% 4272 712 713
30380 Addressable 6 0.0% 2724 454 454
22514 Ephemeral 5 0.0% 2140 428 428
4245 Unknown 5 0.0% 2220 444 444
4243 Unknown 5 0.0% 2820 564 564
6300 Unknown 5 0.0% 19347 3869 4596
30000 Follow sets 5 0.0% 3005 601 823
22611 Ephemeral 5 0.0% 2130 426 426
38225 Addressable 5 0.0% 4929 986 993
23042 Ephemeral 5 0.0% 2145 429 429
14 Unknown 4 0.0% 5446 1362 1550
23059 Ephemeral 4 0.0% 2853 713 1162
3079 Unknown 4 0.0% 3636 909 909
1984 Report 4 0.0% 2648 662 662
23194 NWC request 4 0.0% 2040 510 510
21060 Ephemeral 4 0.0% 2280 570 570
30080 Addressable 4 0.0% 3763 941 986
31234 Addressable 4 0.0% 4512 1128 1856
10003 Bookmarks 3 0.0% 10518 3506 3506
1632 Unknown 3 0.0% 2187 729 729
22120 Ephemeral 3 0.0% 3229 1076 1127
1000 Unknown 3 0.0% 5178 1726 1734
22119 Ephemeral 3 0.0% 2537 846 846
11317 Unknown 2 0.0% 5746 2873 2873
11316 Unknown 2 0.0% 1582 791 791
30617 Addressable 2 0.0% 2178 1089 1089
30618 Addressable 2 0.0% 1466 733 733
17 External reaction 2 0.0% 1220 610 610
31991 Addressable 2 0.0% 922 461 461
24913 Ephemeral 2 0.0% 862 431 431
31985 Addressable 2 0.0% 1267 634 667
1060 Gift wrap (alt) 2 0.0% 2268 1134 1134
22806 Ephemeral 2 0.0% 3040 1520 1520
31339 Addressable 2 0.0% 2940 1470 1470
30090 Addressable 2 0.0% 3030 1515 1515
10000 Mute list 2 0.0% 1963 982 1034
30087 Addressable 2 0.0% 14650 7325 7325
10312 Unknown 2 0.0% 963 482 491
30815 Addressable 2 0.0% 75068 37534 37534
20200 Ephemeral 2 0.0% 982 491 491
20201 Ephemeral 2 0.0% 982 491 491
22747 Ephemeral 2 0.0% 2064 1032 1288
30787 Addressable 1 0.0% 1370 1370 1370
1078 Unknown 1 0.0% 14173 14173 14173
13196 Unknown 1 0.0% 656 656 656
7403 Unknown 1 0.0% 491 491 491
15129 Unknown 1 0.0% 455 455 455
12222 Unknown 1 0.0% 579 579 579
10030 Emoji list 1 0.0% 405 405 405
38111 Addressable 1 0.0% 748 748 748
15750 Unknown 1 0.0% 616 616 616
30421 Addressable 1 0.0% 995 995 995
33333 Addressable 1 0.0% 2346 2346 2346
30079 Addressable 1 0.0% 875 875 875
39019 Addressable 1 0.0% 961 961 961
39015 Addressable 1 0.0% 696 696 696
5555 Unknown 1 0.0% 732 732 732
34235 Addressable 1 0.0% 1071 1071 1071
1040 Unknown 1 0.0% 1048 1048 1048
31000 Addressable 1 0.0% 616 616 616
10063 Unknown 1 0.0% 627 627 627
30554 Addressable 1 0.0% 609 609 609
25195 Ephemeral 1 0.0% 678 678 678
25196 Ephemeral 1 0.0% 1362 1362 1362
30213 Addressable 1 0.0% 632 632 632
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 10:18:44 2026
# Config: cache_all_test_config.jsonc
# Duration: 300 seconds
[2026-08-02 10:25:43] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://offchain.pub CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nostr.mom CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.divine.video CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.nostrplebs.com CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://theforest.nostr1.com CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://nostr-pub.wellorder.net CONNECTED
[2026-08-02 10:25:43] [RELAY] wss://relay.noderunners.network CONNECTED
[2026-08-02 10:26:30] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:26:30] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 10:27:00] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:27:00] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:28:30] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:28:30] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 10:28:55] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 10:28:55] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 10:29:49] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:29:50] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 10:29:50] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 10:29:51] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 10:30:00] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 10:30:00] [RELAY] wss://nos.lol CONNECTED
@@ -0,0 +1,167 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 0m 32s
=== Global Summary ===
Total events: 1485
Total bytes: 1812166 (1.73 MB)
Avg event size: 1220 bytes
Max event: 44546 bytes (kind 31234)
Unique pubkeys: 511
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 247 0 0 0 0 0 OK
wss://nostr-pub.wellorder.net 37 0 0 0 0 0 OK
wss://nostr.mom 38 0 0 0 0 0 OK
wss://nostr.oxtr.dev 36 0 0 0 0 0 OK
wss://offchain.pub 113 0 0 0 0 0 OK
wss://premium.primal.net 1 0 0 0 0 0 OK
wss://relay.damus.io 194 0 0 0 0 0 OK
wss://relay.ditto.pub 277 0 0 0 0 1 OK
wss://relay.divine.video 2 0 0 0 0 0 OK
wss://relay.momostr.pink 27 0 0 0 0 0 OK
wss://relay.mostr.pub 148 0 0 0 0 0 OK
wss://relay.nostrplebs.com 0 0 0 0 0 0 OK
wss://relay.primal.net 360 0 0 0 0 0 OK
wss://theforest.nostr1.com 5 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 184 12.4% 623532 3389 5314
22591 Ephemeral 139 9.4% 107560 774 1223
30382 Addressable 119 8.0% 53663 451 458
30383 Addressable 119 8.0% 53605 450 536
5 Deletion 91 6.1% 39950 439 624
30078 App data 54 3.6% 179695 3328 14343
20001 Ephemeral 49 3.3% 35635 727 1871
0 Profile metadata 48 3.2% 39906 831 1468
22668 Ephemeral 48 3.2% 20592 429 429
1 Text note 45 3.0% 38547 857 2413
22734 Ephemeral 26 1.8% 11102 427 427
4515 Unknown 26 1.8% 38218 1470 2956
10002 Relay list 25 1.7% 11707 468 565
22850 Ephemeral 24 1.6% 10320 430 430
7 Reaction 24 1.6% 13528 564 780
22551 Ephemeral 24 1.6% 10320 430 430
31991 Addressable 20 1.3% 9220 461 461
38501 Addressable 18 1.2% 13341 741 744
22663 Ephemeral 17 1.1% 7276 428 428
25555 Ephemeral 15 1.0% 90493 6033 16887
22912 Ephemeral 13 0.9% 5564 428 428
1059 Gift wrap seal 13 0.9% 23775 1829 1883
22832 Ephemeral 12 0.8% 5233 436 446
22690 Ephemeral 11 0.7% 4730 430 430
22545 Ephemeral 11 0.7% 4697 427 427
25050 Ephemeral 10 0.7% 4394 439 465
37195 Addressable 10 0.7% 7494 749 771
22601 Ephemeral 10 0.7% 4280 428 428
22795 Ephemeral 10 0.7% 11639 1164 1487
22891 Ephemeral 9 0.6% 13331 1481 1490
22655 Ephemeral 9 0.6% 3852 428 428
22911 Ephemeral 9 0.6% 13328 1481 1487
24133 Ephemeral 9 0.6% 8250 917 1534
13194 NWC info 8 0.5% 4112 514 514
22647 Ephemeral 8 0.5% 3416 427 427
22695 Ephemeral 7 0.5% 10339 1477 1481
22653 Ephemeral 7 0.5% 10368 1481 1487
22455 Ephemeral 7 0.5% 10339 1477 1485
22543 Ephemeral 7 0.5% 10354 1479 1487
30315 User status 7 0.5% 2714 388 398
22676 Ephemeral 6 0.4% 2562 427 427
22575 Ephemeral 6 0.4% 2550 425 425
22689 Ephemeral 6 0.4% 8851 1475 1484
22913 Ephemeral 6 0.4% 2586 431 431
22774 Ephemeral 6 0.4% 2568 428 428
24242 Ephemeral 6 0.4% 3474 579 579
22808 Ephemeral 6 0.4% 2574 429 429
22842 Ephemeral 6 0.4% 8891 1482 1488
22603 Ephemeral 5 0.3% 2130 426 426
6 Repost 5 0.3% 10814 2163 7298
31990 App handler info 5 0.3% 4305 861 1321
22384 Ephemeral 5 0.3% 2130 426 426
22699 Ephemeral 5 0.3% 2140 428 428
22613 Ephemeral 5 0.3% 3443 689 691
38502 Addressable 5 0.3% 1925 385 385
23003 Ephemeral 5 0.3% 2140 428 428
22743 Ephemeral 5 0.3% 2140 428 428
29333 Ephemeral 5 0.3% 2460 492 492
20004 Ephemeral 4 0.3% 1800 450 549
4 Encrypted DM 4 0.3% 3072 768 826
20387 Ephemeral 4 0.3% 2056 514 514
22816 Ephemeral 4 0.3% 1716 429 429
20000 Ephemeral 4 0.3% 1872 468 628
22934 Ephemeral 3 0.2% 5535 1845 1846
1985 Unknown 2 0.1% 948 474 474
31234 Addressable 2 0.1% 89092 44546 44546
10777 Unknown 2 0.1% 976 488 488
4004 Unknown 2 0.1% 2338 1169 1169
9735 Zap receipt 2 0.1% 3926 1963 2135
1000 Unknown 2 0.1% 3415 1708 1712
25195 Ephemeral 2 0.1% 1356 678 678
30311 Addressable 2 0.1% 2210 1105 1198
25196 Ephemeral 2 0.1% 3576 1788 2214
1040 Unknown 2 0.1% 1920 960 960
38383 Addressable 2 0.1% 1532 766 815
30515 Addressable 2 0.1% 5152 2576 2576
30091 Addressable 2 0.1% 1221 610 611
30088 Addressable 2 0.1% 3717 1858 1859
21617 Ephemeral 1 0.1% 494 494 494
30380 Addressable 1 0.1% 454 454 454
22611 Ephemeral 1 0.1% 426 426 426
30079 Addressable 1 0.1% 875 875 875
22767 Ephemeral 1 0.1% 429 429 429
38500 Addressable 1 0.1% 711 711 711
30312 Addressable 1 0.1% 461 461 461
7368 Unknown 1 0.1% 1039 1039 1039
22967 Ephemeral 1 0.1% 431 431 431
39101 Addressable 1 0.1% 533 533 533
445 Encrypted group msg 1 0.1% 4130 4130 4130
10000 Mute list 1 0.1% 1722 1722 1722
30617 Addressable 1 0.1% 1089 1089 1089
22526 Ephemeral 1 0.1% 430 430 430
20787 Ephemeral 1 0.1% 926 926 926
443 Unknown 1 0.1% 800 800 800
16 Generic repost 1 0.1% 780 780 780
10011 Unknown 1 0.1% 397 397 397
22679 Ephemeral 1 0.1% 693 693 693
23042 Ephemeral 1 0.1% 429 429 429
22514 Ephemeral 1 0.1% 428 428 428
30023 Long-form article 1 0.1% 8164 8164 8164
5300 Unknown 1 0.1% 630 630 630
22605 Ephemeral 1 0.1% 692 692 692
34237 Addressable 1 0.1% 418 418 418
30618 Addressable 1 0.1% 733 733 733
38421 Addressable 1 0.1% 1069 1069 1069
10100 Unknown 1 0.1% 1036 1036 1036
30080 Addressable 1 0.1% 925 925 925
38385 Addressable 1 0.1% 1214 1214 1214
38225 Addressable 1 0.1% 1012 1012 1012
11998 Unknown 1 0.1% 465 465 465
22236 Ephemeral 1 0.1% 747 747 747
22823 Ephemeral 1 0.1% 1852 1852 1852
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 11:20:28 2026
# Config: cache_all_test_config.jsonc
# Duration: 86400 seconds
[2026-08-02 11:21:06] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 11:21:06] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.damus.io STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 11:21:06] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 11:21:26] [RELAY] wss://relay.ditto.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:21:26] [RELAY] wss://relay.ditto.pub CONNECTED
@@ -0,0 +1,423 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 40m 5s
=== Global Summary ===
Total events: 40082
Total bytes: 52985157 (50.53 MB)
Avg event size: 1322 bytes
Max event: 58662 bytes (kind 3)
Unique pubkeys: 7202
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 7469 0 0 0 0 11 OK
wss://nostr-pub.wellorder.net 697 0 0 0 0 0 OK
wss://nostr.mom 1389 0 0 0 0 0 OK
wss://nostr.oxtr.dev 1009 0 0 0 0 0 OK
wss://offchain.pub 4338 0 0 0 0 0 OK
wss://premium.primal.net 226 0 0 0 0 36 OK
wss://relay.damus.io 4929 0 0 0 0 6 OK
wss://relay.ditto.pub 5951 0 0 0 0 0 OK
wss://relay.divine.video 127 0 0 0 0 0 OK
wss://relay.momostr.pink 857 0 0 0 0 0 OK
wss://relay.mostr.pub 4182 0 0 0 0 1 OK
wss://relay.nostrplebs.com 23 0 0 0 0 1 OK
wss://relay.primal.net 8596 0 0 0 0 10 OK
wss://theforest.nostr1.com 289 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 5631 14.0% 18980562 3371 5314
30382 Addressable 4179 10.4% 1900908 455 483
22591 Ephemeral 3320 8.3% 2581724 778 1814
5 Deletion 2848 7.1% 1264487 444 1659
20001 Ephemeral 1602 4.0% 1333680 833 5870
1 Text note 1556 3.9% 1359052 873 2843
30078 App data 1118 2.8% 4202383 3759 52067
22668 Ephemeral 1014 2.5% 435006 429 429
24133 Ephemeral 965 2.4% 2468722 2558 19622
0 Profile metadata 959 2.4% 865512 903 6506
7 Reaction 766 1.9% 465618 608 1403
22551 Ephemeral 703 1.8% 305982 435 1819
22734 Ephemeral 684 1.7% 292068 427 427
38501 Addressable 583 1.5% 432057 741 744
22912 Ephemeral 582 1.5% 249096 428 428
10002 Relay list 579 1.4% 898767 1552 42385
22850 Ephemeral 543 1.4% 233490 430 430
37195 Addressable 436 1.1% 329238 755 934
25555 Ephemeral 407 1.0% 2047337 5030 16903
30383 Addressable 379 0.9% 197360 521 544
22832 Ephemeral 342 0.9% 149404 437 446
22690 Ephemeral 309 0.8% 132870 430 430
22651 Ephemeral 305 0.8% 130235 427 427
22236 Ephemeral 301 0.8% 226221 752 815
6 Repost 299 0.7% 301866 1010 9866
4515 Unknown 291 0.7% 533897 1835 16952
22601 Ephemeral 252 0.6% 107856 428 428
25050 Ephemeral 233 0.6% 102329 439 465
31991 Addressable 229 0.6% 105569 461 461
22655 Ephemeral 218 0.5% 93304 428 428
29333 Ephemeral 215 0.5% 105780 492 492
22795 Ephemeral 188 0.5% 214828 1143 1847
22676 Ephemeral 188 0.5% 80276 427 427
22808 Ephemeral 178 0.4% 84072 472 1253
22384 Ephemeral 175 0.4% 74550 426 426
31990 App handler info 167 0.4% 183247 1097 6861
22913 Ephemeral 160 0.4% 68960 431 431
13194 NWC info 156 0.4% 80184 514 514
22603 Ephemeral 155 0.4% 66030 426 426
22743 Ephemeral 155 0.4% 66340 428 428
22575 Ephemeral 154 0.4% 65450 425 425
20000 Ephemeral 151 0.4% 67354 446 1198
7368 Unknown 149 0.4% 154743 1039 1059
23049 Ephemeral 147 0.4% 63210 430 430
24242 Ephemeral 144 0.4% 83376 579 579
11998 Unknown 142 0.4% 65790 463 465
445 Encrypted group msg 140 0.3% 293740 2098 5194
22456 Ephemeral 131 0.3% 63614 486 790
20787 Ephemeral 131 0.3% 179206 1368 2478
20387 Ephemeral 128 0.3% 65792 514 514
22911 Ephemeral 125 0.3% 190126 1521 1849
22653 Ephemeral 124 0.3% 188585 1521 1848
22455 Ephemeral 124 0.3% 188540 1520 1844
22695 Ephemeral 124 0.3% 188335 1519 1850
22891 Ephemeral 122 0.3% 185731 1522 1850
1059 Gift wrap seal 122 0.3% 235480 1930 5285
22543 Ephemeral 122 0.3% 185791 1523 1846
22689 Ephemeral 119 0.3% 181177 1522 1845
22816 Ephemeral 118 0.3% 50622 429 429
22774 Ephemeral 115 0.3% 49220 428 428
23003 Ephemeral 113 0.3% 48364 428 428
22842 Ephemeral 111 0.3% 169484 1527 1848
443 Unknown 111 0.3% 163453 1473 22480
23337 Ephemeral 108 0.3% 57242 530 531
30315 User status 107 0.3% 66269 619 1166
22699 Ephemeral 106 0.3% 48256 455 1505
1985 Unknown 104 0.3% 63816 614 4833
22663 Ephemeral 101 0.3% 43228 428 428
38502 Addressable 101 0.3% 38885 385 385
23004 Ephemeral 100 0.2% 43100 431 431
4004 Unknown 100 0.2% 116900 1169 1169
3 Follow list 96 0.2% 1944563 20256 58662
4 Encrypted DM 95 0.2% 95818 1009 17018
9735 Zap receipt 94 0.2% 183437 1951 2436
22647 Ephemeral 83 0.2% 35441 427 427
31234 Addressable 81 0.2% 177493 2191 44546
5300 Unknown 80 0.2% 48788 610 744
23334 Ephemeral 75 0.2% 33681 449 454
10100 Unknown 74 0.2% 110084 1488 3826
22613 Ephemeral 74 0.2% 53289 720 1814
20004 Ephemeral 71 0.2% 37593 529 549
22631 Ephemeral 67 0.2% 61813 923 1822
20333 Ephemeral 67 0.2% 53378 797 798
22657 Ephemeral 67 0.2% 53820 803 1279
10050 DM relays 64 0.2% 27983 437 536
22545 Ephemeral 61 0.2% 26047 427 427
30023 Long-form article 56 0.1% 369281 6594 16772
30311 Addressable 55 0.1% 56530 1028 1198
1111 Comment 51 0.1% 72307 1418 2352
30166 Addressable 49 0.1% 26188 534 808
4244 Unknown 41 0.1% 23124 564 564
30088 Addressable 41 0.1% 76377 1863 1871
30091 Addressable 40 0.1% 24624 616 629
38503 Addressable 40 0.1% 22081 552 553
22782 Ephemeral 40 0.1% 36524 913 1167
22611 Ephemeral 38 0.1% 16263 428 429
22605 Ephemeral 36 0.1% 42806 1189 1849
22767 Ephemeral 35 0.1% 15015 429 429
22967 Ephemeral 34 0.1% 14654 431 431
38383 Addressable 33 0.1% 28002 849 3207
10011 Unknown 32 0.1% 13152 411 431
8108 Unknown 30 0.1% 39052 1302 1574
22623 Ephemeral 30 0.1% 24536 818 1283
30089 Chunked data 29 0.1% 370012 12759 44372
30515 Addressable 29 0.1% 74704 2576 2576
10777 Unknown 26 0.1% 12688 488 488
38385 Addressable 26 0.1% 34815 1339 1496
22679 Ephemeral 24 0.1% 28787 1199 1852
25195 Ephemeral 24 0.1% 14928 622 678
21060 Ephemeral 23 0.1% 13110 570 570
22738 Ephemeral 23 0.1% 18515 805 1252
30380 Addressable 23 0.1% 10442 454 454
33334 Addressable 22 0.1% 18282 831 831
22547 Ephemeral 22 0.1% 20278 922 1164
21617 Ephemeral 21 0.1% 10544 502 517
33301 Addressable 20 0.0% 35736 1787 2162
38500 Addressable 19 0.0% 13531 712 713
38384 Addressable 19 0.0% 14115 743 743
22526 Ephemeral 19 0.0% 8170 430 430
6300 Unknown 19 0.0% 64041 3371 6898
30443 Addressable 19 0.0% 22604 1190 1260
22919 Ephemeral 18 0.0% 32466 1804 1849
22724 Ephemeral 18 0.0% 13222 735 1253
22789 Ephemeral 17 0.0% 28521 1678 3267
1002 Unknown 17 0.0% 16010 942 981
3434 Unknown 17 0.0% 11585 681 705
22507 Ephemeral 16 0.0% 9927 620 1163
20384 Ephemeral 16 0.0% 10223 639 639
30080 Addressable 16 0.0% 15048 940 987
22672 Ephemeral 16 0.0% 24987 1562 1847
22844 Ephemeral 15 0.0% 6435 429 429
4245 Unknown 15 0.0% 6660 444 444
4243 Unknown 15 0.0% 8460 564 564
30009 Addressable 15 0.0% 6949 463 479
22514 Ephemeral 14 0.0% 5992 428 428
14 Unknown 14 0.0% 15322 1094 1551
3927 Unknown 14 0.0% 7484 535 544
23042 Ephemeral 14 0.0% 6006 429 429
10000 Mute list 14 0.0% 32097 2293 8671
30618 Addressable 13 0.0% 25947 1996 8739
1040 Unknown 13 0.0% 13272 1021 1048
31510 Addressable 13 0.0% 12731 979 982
30617 Addressable 12 0.0% 13112 1093 1146
25196 Ephemeral 12 0.0% 19580 1632 2214
38225 Addressable 12 0.0% 11742 978 1006
39101 Addressable 11 0.0% 5863 533 533
26428 Ephemeral 10 0.0% 8765 876 877
30213 Addressable 10 0.0% 6224 622 632
26429 Ephemeral 10 0.0% 4910 491 491
30000 Addressable 9 0.0% 17509 1945 5660
22639 Ephemeral 9 0.0% 3852 428 428
31339 Addressable 9 0.0% 13230 1470 1470
30312 Addressable 8 0.0% 3555 444 461
9321 Unknown 7 0.0% 16541 2363 2363
7403 Unknown 7 0.0% 3435 491 491
1010 Unknown 7 0.0% 5098 728 748
16 Generic repost 7 0.0% 5460 780 780
7375 Unknown 7 0.0% 8120 1160 1160
22839 Ephemeral 7 0.0% 2996 428 428
7376 Unknown 7 0.0% 5817 831 831
3079 Unknown 6 0.0% 5454 909 909
22658 Ephemeral 6 0.0% 6269 1045 1813
30090 Addressable 5 0.0% 7575 1515 1515
34236 Addressable 5 0.0% 61577 12315 21112
22120 Ephemeral 5 0.0% 5493 1099 1133
22119 Ephemeral 5 0.0% 4227 845 846
23194 NWC request 5 0.0% 2554 511 534
1984 Report 5 0.0% 2714 543 662
31000 Addressable 5 0.0% 3024 605 616
1078 Unknown 5 0.0% 70534 14107 14226
10003 Bookmarks 5 0.0% 15698 3140 4954
1000 Unknown 4 0.0% 6474 1618 1782
30087 Addressable 4 0.0% 29300 7325 7325
38111 Addressable 4 0.0% 2996 749 752
25055 Ephemeral 4 0.0% 6286 1572 1788
30421 Addressable 4 0.0% 4079 1020 1028
30079 Addressable 4 0.0% 3500 875 875
30815 Addressable 4 0.0% 150136 37534 37534
13302 Unknown 4 0.0% 30097 7524 22303
22865 Ephemeral 4 0.0% 2785 696 699
10012 Unknown 3 0.0% 1388 463 622
39015 Addressable 3 0.0% 2088 696 696
30787 Addressable 3 0.0% 4118 1373 1374
10001 Pinned notes 3 0.0% 1758 586 586
15129 Unknown 3 0.0% 1365 455 455
37779 Addressable 3 0.0% 1569 523 523
8 Unknown 3 0.0% 1527 509 509
30003 Addressable 3 0.0% 1167 389 389
31512 Addressable 3 0.0% 10234 3411 3414
39050 Addressable 3 0.0% 5529 1843 1843
31237 Addressable 3 0.0% 2439 813 821
30554 Addressable 3 0.0% 1827 609 609
22778 Ephemeral 3 0.0% 4059 1353 1847
30402 Addressable 3 0.0% 2036 679 680
10063 Unknown 3 0.0% 1639 546 627
1631 Unknown 2 0.0% 2620 1310 1310
1315 Unknown 2 0.0% 990 495 495
22760 Ephemeral 2 0.0% 3624 1812 1814
30495 Addressable 2 0.0% 4074 2037 2037
10051 Unknown 2 0.0% 878 439 439
13196 Unknown 2 0.0% 1312 656 656
38421 Addressable 2 0.0% 2140 1070 1070
23333 Ephemeral 2 0.0% 1636 818 818
2333 Unknown 2 0.0% 1812 906 906
24913 Ephemeral 2 0.0% 1164 582 582
22882 Ephemeral 2 0.0% 2000 1000 1164
22617 Ephemeral 2 0.0% 852 426 426
22729 Ephemeral 2 0.0% 1860 930 930
22733 Ephemeral 2 0.0% 3330 1665 1848
23013 Ephemeral 2 0.0% 2965 1482 1485
9 Unknown 2 0.0% 4085 2042 2157
31986 Addressable 2 0.0% 1182 591 591
1063 File metadata 1 0.0% 909 909 909
1060 Unknown 1 0.0% 1207 1207 1207
10312 Unknown 1 0.0% 472 472 472
37368 Addressable 1 0.0% 1236 1236 1236
15750 Unknown 1 0.0% 626 626 626
22598 Ephemeral 1 0.0% 1666 1666 1666
17375 Unknown 1 0.0% 606 606 606
22496 Ephemeral 1 0.0% 1518 1518 1518
20200 Ephemeral 1 0.0% 491 491 491
39019 Addressable 1 0.0% 961 961 961
34238 Addressable 1 0.0% 920 920 920
22732 Ephemeral 1 0.0% 1334 1334 1334
20201 Ephemeral 1 0.0% 491 491 491
12222 Unknown 1 0.0% 579 579 579
31337 Addressable 1 0.0% 818 818 818
23071 Ephemeral 1 0.0% 1910 1910 1910
33333 Addressable 1 0.0% 2346 2346 2346
20 Picture 1 0.0% 445 445 445
1808 Unknown 1 0.0% 683 683 683
30113 Addressable 1 0.0% 770 770 770
34235 Addressable 1 0.0% 1501 1501 1501
10377 Unknown 1 0.0% 664 664 664
11111 Unknown 1 0.0% 382 382 382
10133 Unknown 1 0.0% 499 499 499
30081 Addressable 1 0.0% 1370 1370 1370
10009 Unknown 1 0.0% 422 422 422
30005 Addressable 1 0.0% 9671 9671 9671
10443 Unknown 1 0.0% 676 676 676
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 11:21:56 2026
# Config: cache_all_test_config.jsonc
# Duration: 86400 seconds
[2026-08-02 11:22:33] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 11:22:33] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.damus.io STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 11:22:33] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 11:23:06] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:23:06] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:23:07] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:23:08] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:23:08] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:23:09] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:23:38] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:23:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:23:45] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:23:45] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:24:08] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:24:08] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:26:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:10] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:14] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:15] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:15] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:26:15] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:26:28] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:29] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:38] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:26:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:26:43] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:43] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:44] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:45] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:47] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:48] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:49] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:50] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:26:53] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:26:53] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:27:17] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:27:17] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:27:31] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:27:32] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 11:27:58] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:27:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:28:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:11] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:12] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:12] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:15] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:28:15] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:28:15] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:16] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:17] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:18] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:20] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:20] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:22] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:22] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:24] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:25] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:44] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:45] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:45] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:28:45] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:28:46] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:47] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:51] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:51] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:28:56] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:28:56] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:29:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:29:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:29:59] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:00] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:01] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:01] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:02] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:03] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:03] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:04] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:06] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:07] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:07] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:08] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:11] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:14] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:15] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:17] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:18] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:18] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:19] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:30:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:30:20] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:20] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:22] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:23] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:25] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:30:26] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 11:30:58] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:30:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:31:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:31:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:31:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:31:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:32:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:32:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:32:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:32:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:32:46] [RELAY] wss://relay.damus.io DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:32:52] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:33:03] [RELAY] wss://relay.damus.io DISCONNECTED: Failed to create WebSocket client for relay wss://relay.damus.io
[2026-08-02 11:33:13] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:33:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:33:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:33:54] [RELAY] wss://relay.damus.io DISCONNECTED: Failed to create WebSocket client for relay wss://relay.damus.io
[2026-08-02 11:33:54] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:34:12] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 11:34:12] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:34:24] [RELAY] wss://relay.damus.io DISCONNECTED:
[2026-08-02 11:34:24] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:35:13] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 11:35:13] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 11:35:24] [RELAY] wss://relay.damus.io DISCONNECTED:
[2026-08-02 11:35:24] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:36:18] [RELAY] wss://relay.damus.io DISCONNECTED: Failed to create WebSocket client for relay wss://relay.damus.io
[2026-08-02 11:36:19] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:36:19] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:36:28] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 11:36:49] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 11:36:49] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 11:37:14] [RELAY] wss://relay.nostrplebs.com DISCONNECTED:
[2026-08-02 11:37:14] [RELAY] wss://relay.nostrplebs.com CONNECTED
[2026-08-02 11:37:19] [RELAY] wss://premium.primal.net DISCONNECTED:
[2026-08-02 11:37:19] [RELAY] wss://premium.primal.net CONNECTED
@@ -0,0 +1,646 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 58m 5s
=== Global Summary ===
Total events: 140345
Total bytes: 182488517 (174.03 MB)
Avg event size: 1300 bytes
Max event: 62825 bytes (kind 1)
Unique pubkeys: 26918
Relays: 15
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://laantungir.net 0 0 0 0 0 0 DOWN
wss://nos.lol 25038 0 0 0 0 54 OK
wss://nostr-pub.wellorder.net 3608 0 0 0 0 0 OK
wss://nostr.mom 8692 0 0 0 0 0 OK
wss://nostr.oxtr.dev 4360 0 0 0 0 7 OK
wss://offchain.pub 17857 0 0 0 0 0 OK
wss://premium.primal.net 171 0 0 0 0 26 OK
wss://relay.damus.io 21539 0 0 0 0 0 OK
wss://relay.ditto.pub 22251 0 0 0 0 7 OK
wss://relay.divine.video 281 0 0 0 0 0 OK
wss://relay.momostr.pink 1812 0 0 0 0 7 OK
wss://relay.mostr.pub 20917 0 0 0 0 10 OK
wss://relay.nostrplebs.com 23 0 0 0 0 0 OK
wss://relay.primal.net 12945 0 0 0 0 57 OK
wss://theforest.nostr1.com 851 0 0 0 0 0 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
21059 Gift wrap (NIP-59) 22589 16.1% 76886982 3404 22354
30382 Addressable 15147 10.8% 6854204 453 484
5 Deletion 10634 7.6% 4652532 438 955
20001 Ephemeral 4553 3.2% 2990229 657 26578
22591 Ephemeral 4464 3.2% 3384073 758 1230
1 Text note 4307 3.1% 4175360 969 62825
22668 Ephemeral 4214 3.0% 1807801 429 429
22466 Ephemeral 3541 2.5% 1519089 429 429
30383 Addressable 3125 2.2% 1507650 482 550
0 Profile metadata 2878 2.1% 2533064 880 9187
22551 Ephemeral 2857 2.0% 1233111 432 1160
30078 App data 2566 1.8% 8335901 3249 55090
38501 Addressable 2349 1.7% 1740867 741 744
22912 Ephemeral 1869 1.3% 802701 429 3197
22684 Ephemeral 1859 1.3% 803991 432 3193
22734 Ephemeral 1629 1.2% 695583 427 427
10002 Relay list 1604 1.1% 4950855 3087 42385
7 Reaction 1435 1.0% 847949 591 1287
29333 Ephemeral 1408 1.0% 690797 491 492
24133 Ephemeral 1375 1.0% 1890802 1375 38738
37195 Addressable 1338 1.0% 1004351 751 934
25555 Ephemeral 1181 0.8% 5929927 5021 17075
22690 Ephemeral 1081 0.8% 467584 433 3184
22601 Ephemeral 990 0.7% 423742 428 429
22850 Ephemeral 947 0.7% 407208 430 430
4515 Unknown 868 0.6% 1923082 2216 55180
23334 Ephemeral 859 0.6% 374353 436 439
22655 Ephemeral 821 0.6% 351388 428 428
6 Repost 811 0.6% 771135 951 10128
22795 Ephemeral 778 0.6% 1060284 1363 3201
4 Encrypted DM 767 0.5% 725743 946 19618
22720 Ephemeral 749 0.5% 1079163 1441 3249
22876 Ephemeral 724 0.5% 308478 426 431
22676 Ephemeral 696 0.5% 297193 427 428
22913 Ephemeral 678 0.5% 294988 435 3201
22689 Ephemeral 663 0.5% 1094513 1651 5926
22927 Ephemeral 607 0.4% 1017238 1676 1876
22236 Ephemeral 595 0.4% 446548 751 814
22911 Ephemeral 569 0.4% 951997 1673 1880
22943 Ephemeral 563 0.4% 249401 443 1658
22603 Ephemeral 543 0.4% 231317 426 427
22743 Ephemeral 542 0.4% 231978 428 430
22695 Ephemeral 538 0.4% 871272 1619 1850
1059 Gift wrap seal 535 0.4% 2557163 4780 55121
22842 Ephemeral 528 0.4% 882417 1671 1853
24242 Ephemeral 527 0.4% 305133 579 579
13194 NWC info 505 0.4% 259258 513 514
22730 Ephemeral 474 0.3% 418919 884 1517
11998 Unknown 449 0.3% 208257 464 465
23337 Ephemeral 446 0.3% 252660 567 586
7368 Unknown 443 0.3% 459822 1038 1059
22816 Ephemeral 410 0.3% 183597 448 3196
30315 User status 406 0.3% 280885 692 1166
22456 Ephemeral 403 0.3% 196962 489 790
22815 Ephemeral 386 0.3% 620350 1607 3199
20387 Ephemeral 368 0.3% 189152 514 514
20000 Ephemeral 351 0.3% 237048 675 17065
5300 Unknown 350 0.2% 213715 611 701
22774 Ephemeral 349 0.2% 149372 428 428
31990 App handler info 343 0.2% 389821 1137 6861
3 Follow list 325 0.2% 4492770 13824 58982
23003 Ephemeral 317 0.2% 135676 428 428
38502 Addressable 295 0.2% 113575 385 385
1985 Unknown 287 0.2% 189598 661 4834
22807 Ephemeral 279 0.2% 119596 429 430
25050 Ephemeral 272 0.2% 113448 417 1362
4004 Unknown 270 0.2% 315630 1169 1169
20004 Ephemeral 233 0.2% 124155 533 549
35000 Addressable 208 0.1% 160992 774 774
22613 Ephemeral 208 0.1% 143395 689 1134
22592 Ephemeral 201 0.1% 93351 464 3193
20333 Ephemeral 197 0.1% 156942 797 798
31950 Addressable 196 0.1% 170818 872 937
22632 Ephemeral 195 0.1% 85266 437 3204
9735 Zap receipt 193 0.1% 370520 1920 4167
10100 Unknown 188 0.1% 279911 1489 3826
30311 Addressable 187 0.1% 202343 1082 1197
24913 Ephemeral 179 0.1% 79157 442 582
23004 Ephemeral 179 0.1% 77149 431 431
22854 Ephemeral 174 0.1% 137235 789 1660
30384 Addressable 166 0.1% 85437 515 556
22891 Ephemeral 164 0.1% 235679 1437 3243
10050 DM relays 160 0.1% 72634 454 592
30023 Long-form article 144 0.1% 954406 6628 13594
6300 Unknown 144 0.1% 880738 6116 17462
445 Encrypted group msg 140 0.1% 350204 2501 4134
10000 Mute list 136 0.1% 274284 2017 27742
22693 Ephemeral 130 0.1% 101515 781 2619
30166 Addressable 128 0.1% 66754 522 808
4244 Unknown 127 0.1% 71628 564 564
38503 Addressable 125 0.1% 68916 551 552
20787 Ephemeral 124 0.1% 181428 1463 2478
22455 Ephemeral 112 0.1% 183137 1635 1847
22616 Ephemeral 111 0.1% 98460 887 3453
22543 Ephemeral 107 0.1% 172800 1615 1849
22885 Ephemeral 106 0.1% 102539 967 3524
22672 Ephemeral 104 0.1% 174090 1674 1849
22717 Ephemeral 99 0.1% 120874 1221 1849
22929 Ephemeral 90 0.1% 88203 980 1517
10777 Unknown 89 0.1% 43507 489 489
22829 Ephemeral 89 0.1% 43243 486 3200
30088 Addressable 87 0.1% 162094 1863 1871
30091 Addressable 85 0.1% 52311 615 629
21584 Ephemeral 83 0.1% 36413 439 439
38385 Addressable 82 0.1% 111350 1358 1496
22864 Ephemeral 82 0.1% 56300 687 1223
39101 Addressable 81 0.1% 43173 533 533
22751 Ephemeral 81 0.1% 73608 909 4083
1111 Comment 79 0.1% 93490 1183 2457
3927 Unknown 78 0.1% 41585 533 544
10011 Unknown 77 0.1% 33123 430 431
21060 Ephemeral 74 0.1% 42180 570 570
21617 Ephemeral 73 0.1% 36119 495 495
33334 Addressable 70 0.0% 58170 831 831
30380 Addressable 66 0.0% 29964 454 454
38383 Addressable 66 0.0% 59966 909 3211
38384 Addressable 62 0.0% 46066 743 743
38500 Addressable 60 0.0% 42716 712 713
20384 Ephemeral 59 0.0% 37701 639 639
30089 Chunked data 53 0.0% 614992 11604 44372
1002 Unknown 53 0.0% 48203 909 1011
22840 Ephemeral 52 0.0% 22285 429 430
23066 Ephemeral 50 0.0% 43751 875 1165
22526 Ephemeral 50 0.0% 21469 429 430
22405 Ephemeral 47 0.0% 20886 444 445
23042 Ephemeral 44 0.0% 18876 429 429
22514 Ephemeral 44 0.0% 18832 428 428
22748 Ephemeral 44 0.0% 18874 429 429
31510 Addressable 43 0.0% 42075 978 982
3079 Unknown 42 0.0% 38178 909 909
4243 Unknown 42 0.0% 23688 564 564
22862 Ephemeral 41 0.0% 17578 429 429
22475 Ephemeral 40 0.0% 43786 1095 3923
22721 Ephemeral 40 0.0% 23480 587 1842
22674 Ephemeral 39 0.0% 17992 461 1661
4245 Unknown 39 0.0% 17316 444 444
30080 Addressable 39 0.0% 37674 966 1011
30443 Addressable 38 0.0% 46231 1217 1547
30515 Addressable 38 0.0% 201552 5304 5304
22212 Ephemeral 37 0.0% 16440 444 445
10003 Bookmarks 37 0.0% 420277 11359 56461
38225 Addressable 35 0.0% 33592 960 1021
30815 Addressable 34 0.0% 1276156 37534 37534
1984 Report 34 0.0% 22174 652 667
22753 Ephemeral 33 0.0% 40204 1218 3766
30618 Addressable 32 0.0% 45089 1409 8804
1040 Unknown 32 0.0% 36596 1144 5164
3434 Unknown 30 0.0% 20382 679 705
7000 Unknown 28 0.0% 21752 777 822
30385 Addressable 28 0.0% 22344 798 981
33301 Addressable 27 0.0% 45586 1688 2162
16 Generic repost 27 0.0% 21778 807 1604
22119 Ephemeral 25 0.0% 21137 845 846
22120 Ephemeral 25 0.0% 27207 1088 1128
24421 Ephemeral 24 0.0% 12384 516 516
22525 Ephemeral 24 0.0% 42802 1783 4259
22756 Ephemeral 24 0.0% 32806 1367 3198
31124 Addressable 23 0.0% 28396 1235 1445
22733 Ephemeral 23 0.0% 15376 669 1224
30617 Addressable 23 0.0% 25047 1089 1089
22649 Ephemeral 22 0.0% 9459 430 430
22680 Ephemeral 21 0.0% 14310 681 3193
22874 Ephemeral 21 0.0% 24675 1175 1850
31339 Addressable 21 0.0% 30870 1470 1470
30000 Addressable 19 0.0% 16519 869 3185
22688 Ephemeral 19 0.0% 18248 960 3195
31991 Addressable 19 0.0% 8759 461 461
39842 Addressable 18 0.0% 19919 1107 1385
10312 Unknown 18 0.0% 9219 512 559
7403 Unknown 17 0.0% 8336 490 491
30280 Addressable 17 0.0% 8721 513 513
14 Unknown 16 0.0% 17438 1090 1531
31000 Addressable 16 0.0% 9716 607 616
22645 Ephemeral 16 0.0% 9285 580 1838
2333 Unknown 15 0.0% 10074 672 722
34236 Addressable 15 0.0% 211139 14076 26491
30079 Addressable 15 0.0% 47271 3151 4574
23333 Ephemeral 15 0.0% 8754 584 634
23194 NWC request 15 0.0% 8952 597 1086
22778 Ephemeral 15 0.0% 6421 428 429
38111 Addressable 14 0.0% 10495 750 752
22747 Ephemeral 13 0.0% 8895 684 1657
25000 Ephemeral 13 0.0% 23498 1808 2214
22506 Ephemeral 13 0.0% 23855 1835 1841
1000 Unknown 13 0.0% 21880 1683 1790
26428 Ephemeral 12 0.0% 10516 876 877
23195 NWC response 12 0.0% 9184 765 1183
1078 Unknown 12 0.0% 169653 14138 14241
30554 Addressable 12 0.0% 7292 608 608
22361 Ephemeral 11 0.0% 4708 428 428
30421 Addressable 11 0.0% 11427 1039 1066
30085 Addressable 11 0.0% 7227 657 659
10063 Unknown 11 0.0% 5982 544 627
22402 Ephemeral 11 0.0% 4683 426 443
39015 Addressable 11 0.0% 7656 696 696
11125 Unknown 10 0.0% 10262 1026 1916
30113 Addressable 10 0.0% 6987 699 770
33051 Addressable 10 0.0% 5206 521 522
30087 Addressable 9 0.0% 65925 7325 7325
30213 Addressable 9 0.0% 5662 629 637
1018 Unknown 8 0.0% 4500 562 677
30090 Addressable 8 0.0% 12120 1515 1515
1050 Unknown 8 0.0% 3496 437 437
22619 Ephemeral 8 0.0% 11165 1396 1842
10051 Unknown 7 0.0% 3205 458 489
26429 Ephemeral 7 0.0% 3437 491 491
38421 Addressable 7 0.0% 7485 1069 1070
39019 Addressable 6 0.0% 5766 961 961
9842 Unknown 6 0.0% 6742 1124 1260
20200 Ephemeral 6 0.0% 2946 491 491
25742 Ephemeral 6 0.0% 11756 1959 7334
30081 Addressable 6 0.0% 8220 1370 1370
20 Picture 6 0.0% 4558 760 852
20201 Ephemeral 6 0.0% 2946 491 491
22722 Ephemeral 5 0.0% 7756 1551 1660
7336 Unknown 5 0.0% 3280 656 656
22825 Ephemeral 5 0.0% 5660 1132 1848
13196 Unknown 5 0.0% 3280 656 656
9841 Unknown 5 0.0% 14029 2806 4468
23336 Ephemeral 5 0.0% 3285 657 657
1063 File metadata 5 0.0% 4593 919 941
31344 Addressable 4 0.0% 2500 625 625
31986 Addressable 4 0.0% 2372 593 593
22432 Ephemeral 4 0.0% 5412 1353 1843
31985 Addressable 4 0.0% 2534 634 667
10015 Unknown 3 0.0% 11266 3755 4578
1010 Unknown 3 0.0% 2505 835 1152
37779 Addressable 3 0.0% 1569 523 523
21734 Ephemeral 3 0.0% 1131 377 377
22671 Ephemeral 3 0.0% 1281 427 427
31343 Addressable 2 0.0% 53115 26558 45456
16767 Unknown 2 0.0% 1974 987 987
1058 Unknown 2 0.0% 1186 593 593
22724 Ephemeral 2 0.0% 2103 1052 1674
30005 Addressable 2 0.0% 11351 5676 9817
21724 Ephemeral 2 0.0% 754 377 377
20177 Ephemeral 2 0.0% 851 426 426
2003 Unknown 2 0.0% 8711 4356 7015
30003 Addressable 2 0.0% 778 389 389
25582 Ephemeral 2 0.0% 4500 2250 3690
38422 Addressable 2 0.0% 60198 30099 30212
22387 Ephemeral 2 0.0% 887 444 444
1618 Unknown 1 0.0% 2168 2168 2168
10001 Pinned notes 1 0.0% 710 710 710
30758 Addressable 1 0.0% 436 436 436
32267 Addressable 1 0.0% 1693 1693 1693
1069 Unknown 1 0.0% 757 757 757
31345 Addressable 1 0.0% 8666 8666 8666
21840 Ephemeral 1 0.0% 377 377 377
21764 Ephemeral 1 0.0% 377 377 377
31353 Addressable 1 0.0% 737 737 737
1808 Unknown 1 0.0% 1696 1696 1696
31633 Addressable 1 0.0% 592 592 592
30066 Addressable 1 0.0% 675 675 675
30063 Addressable 1 0.0% 2891 2891 2891
21711 Ephemeral 1 0.0% 377 377 377
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 15:24:51 2026
# Config: cache_all_test_config.jsonc
# Duration: 86400 seconds
[2026-08-02 15:25:27] [RELAY] wss://laantungir.net STATUS: ?
[2026-08-02 15:25:27] [RELAY] wss://nos.lol STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://nostr-pub.wellorder.net STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://nostr.mom STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://nostr.oxtr.dev STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://offchain.pub STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://premium.primal.net STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.damus.io STATUS: ?
[2026-08-02 15:25:27] [RELAY] wss://relay.ditto.pub STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.divine.video STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.momostr.pink STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.mostr.pub STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.nostrplebs.com STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 15:25:27] [RELAY] wss://theforest.nostr1.com STATUS: connected
[2026-08-02 15:25:39] [RELAY] wss://relay.damus.io CONNECTED
[2026-08-02 15:26:24] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:26:24] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:26:46] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:26:46] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:27:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:27:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:27:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:27:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:28:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:28:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:28:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:28:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:28:46] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:28:46] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:29:16] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:29:16] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:29:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:29:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:30:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:30:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:30:16] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:30:16] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:31:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:31:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:31:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:31:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:32:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:32:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:32:37] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:32:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:33:19] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:33:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:33:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:33:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:33:49] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:33:49] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:34:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:34:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:34:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:34:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:35:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:35:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:35:11] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:35:11] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:35:12] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:35:13] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:35:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:35:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:35:41] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:35:41] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:35:54] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:35:55] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:37:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:37:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:37:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:37:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:38:11] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:38:11] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:38:34] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:38:35] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:39:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:39:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:39:17] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:18] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:39:32] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:32] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:39:34] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:34] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:39:59] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:39:59] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:40:07] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:40:07] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:40:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:40:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:40:40] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:40:41] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:41:29] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:41:29] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:41:39] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:41:40] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:41:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:41:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:42:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:42:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:42:51] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:42:51] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:44:41] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:44:41] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:45:00] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:45:01] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:45:07] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:45:07] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:45:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:45:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:45:26] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:45:27] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:45:37] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:45:37] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:46:07] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:46:07] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:46:08] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:46:08] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:46:10] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:46:10] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:46:37] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:46:37] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:46:38] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:46:38] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:46:40] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:46:40] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:46:51] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:46:52] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:47:00] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:01] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:01] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:02] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:07] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:47:07] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:47:07] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:08] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:08] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:09] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:47:17] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:18] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:47:18] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:47:33] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:47:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:48:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:48:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:48:41] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:48:42] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 15:49:03] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:49:03] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:49:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:49:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:49:37] [RELAY] wss://relay.ditto.pub DISCONNECTED:
[2026-08-02 15:49:37] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:50:02] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:50:02] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:50:05] [RELAY] wss://relay.ditto.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:50:05] [RELAY] wss://relay.ditto.pub CONNECTED
[2026-08-02 15:50:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:50:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:51:18] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:51:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:51:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:51:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:52:00] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:52:00] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:52:30] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:52:30] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:52:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:52:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:52:33] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:52:34] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:52:34] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:52:35] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:53:00] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:53:00] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:53:30] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 15:53:30] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:53:30] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:53:30] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:53:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:53:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:54:19] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:19] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:54:54] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:55] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:54:56] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:56] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:54:57] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:58] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:54:58] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:54:59] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:01] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:02] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:02] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:04] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:04] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:55:04] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:55:04] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:05] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:06] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:06] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:07] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:07] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:08] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:09] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:55:09] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:55:11] [RELAY] wss://nostr.oxtr.dev DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:12] [RELAY] wss://nostr.oxtr.dev CONNECTED
[2026-08-02 15:55:33] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 15:55:33] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:55:36] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:37] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 15:55:50] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:55:50] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:56:09] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:56:10] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 15:56:12] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:56:13] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 15:57:22] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:57:23] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 15:58:04] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 15:58:04] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:00:01] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:00:01] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:00:31] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:00:31] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:00:54] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:00:54] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:01:31] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:01:31] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:01:48] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:01:48] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:01:55] [RELAY] wss://relay.mostr.pub DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:01:55] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:02:29] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:02:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:02:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:02:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:03:01] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:03:01] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:03:29] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:03:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:03:31] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:03:31] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:03:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:03:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:04:29] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:04:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:04:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:04:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:05:10] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:11] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:12] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:13] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:15] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:16] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:17] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:18] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:19] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:20] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:21] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:21] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:24] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:05:25] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:05:59] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:05:59] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:06:09] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:06:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:08:00] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:08:01] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:08:09] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:08:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:08:42] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:08:42] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:10:20] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:10:21] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:11:09] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:11:09] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:11:26] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:27] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:11:30] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:30] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:11:32] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:32] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:11:39] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:11:39] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:11:46] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:11:47] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:12:58] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:12:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:13:25] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:13:25] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:13:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:13:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:13:58] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:13:58] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:14:28] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:14:28] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:14:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:14:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:15:16] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:15:17] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:15:23] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:15:24] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:15:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:15:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:15:41] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:15:42] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:16:25] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:16:26] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:16:28] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:16:28] [RELAY] wss://premium.primal.net CONNECTED
[2026-08-02 16:16:32] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:16:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:16:36] [RELAY] wss://relay.momostr.pink DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:16:36] [RELAY] wss://relay.momostr.pink CONNECTED
[2026-08-02 16:17:02] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:17:02] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:17:25] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:17:25] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:18:02] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:18:02] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:18:08] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:18:08] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:18:25] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:18:25] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:18:49] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:18:49] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:18:55] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:18:56] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:19:37] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:19:37] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:19:40] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:19:40] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:20:37] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:20:37] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:21:07] [RELAY] wss://relay.primal.net DISCONNECTED:
[2026-08-02 16:21:07] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:21:10] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:21:10] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:21:14] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:21:15] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:21:56] [RELAY] wss://nos.lol DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:21:56] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:22:26] [RELAY] wss://nos.lol DISCONNECTED:
[2026-08-02 16:22:26] [RELAY] wss://nos.lol CONNECTED
[2026-08-02 16:22:55] [RELAY] wss://relay.mostr.pub DISCONNECTED:
[2026-08-02 16:22:55] [RELAY] wss://relay.mostr.pub CONNECTED
[2026-08-02 16:23:32] [RELAY] wss://premium.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:23:33] [RELAY] wss://premium.primal.net CONNECTED
@@ -0,0 +1,243 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 8m 23s
=== Global Summary ===
Total events: 7884
Total bytes: 12205231 (11.64 MB)
Avg event size: 1548 bytes
Max event: 56750 bytes (kind 3)
Unique pubkeys: 2301
Relays: 1
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 7884 0 0 0 0 10 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
1059 Gift wrap seal 871 11.0% 1657832 1903 55121
21059 Gift wrap (NIP-59) 740 9.4% 2308644 3120 22354
30078 App data 622 7.9% 2651879 4263 55090
30383 Addressable 520 6.6% 278610 536 548
20001 Ephemeral 386 4.9% 403111 1044 2026
22734 Ephemeral 357 4.5% 152451 427 428
1 Text note 303 3.8% 303990 1003 4648
30382 Addressable 288 3.7% 137207 476 484
0 Profile metadata 189 2.4% 140594 744 2012
29333 Ephemeral 165 2.1% 81180 492 492
10002 Relay list 158 2.0% 571188 3615 42385
22236 Ephemeral 158 2.0% 120802 765 801
5 Deletion 154 2.0% 73847 480 664
4515 Unknown 154 2.0% 463562 3010 38797
22743 Ephemeral 134 1.7% 57352 428 428
7 Reaction 113 1.4% 62452 553 727
24133 Ephemeral 102 1.3% 132440 1298 7334
22684 Ephemeral 99 1.3% 42669 431 431
22850 Ephemeral 94 1.2% 40420 430 430
22838 Ephemeral 88 1.1% 37752 429 429
22774 Ephemeral 84 1.1% 35952 428 428
23003 Ephemeral 84 1.1% 35952 428 428
22876 Ephemeral 83 1.1% 35358 426 426
22611 Ephemeral 75 1.0% 32175 429 429
25555 Ephemeral 69 0.9% 281319 4077 16987
4 Encrypted DM 66 0.8% 72028 1091 17018
22613 Ephemeral 63 0.8% 43489 690 692
25050 Ephemeral 61 0.8% 24461 401 401
11998 Unknown 61 0.8% 28269 463 465
443 Unknown 49 0.6% 68056 1389 22480
30301 Addressable 47 0.6% 53284 1134 1912
3927 Unknown 47 0.6% 25051 533 533
20387 Ephemeral 44 0.6% 22616 514 514
22761 Ephemeral 44 0.6% 18832 428 428
13194 NWC info 43 0.5% 22043 513 514
20004 Ephemeral 42 0.5% 22266 530 549
5300 Unknown 42 0.5% 25437 606 637
22546 Ephemeral 39 0.5% 16614 426 426
22714 Ephemeral 37 0.5% 15836 428 428
30166 Addressable 35 0.4% 17904 512 589
9735 Zap receipt 35 0.4% 70344 2010 3538
23334 Ephemeral 35 0.4% 15084 431 432
1985 Unknown 34 0.4% 16232 477 483
22816 Ephemeral 33 0.4% 14157 429 429
21050 Ephemeral 33 0.4% 30862 935 1874
30315 User status 32 0.4% 21166 661 925
445 Encrypted group msg 32 0.4% 120956 3780 9346
22698 Ephemeral 32 0.4% 13664 427 427
4004 Unknown 32 0.4% 37408 1169 1169
38501 Addressable 31 0.4% 23034 743 744
31990 App handler info 25 0.3% 9675 387 387
10100 Unknown 24 0.3% 27687 1154 1589
20333 Ephemeral 24 0.3% 19120 797 798
3 Follow list 23 0.3% 328284 14273 56750
24913 Ephemeral 22 0.3% 9700 441 493
30311 Addressable 21 0.3% 22061 1051 1197
6 Repost 21 0.3% 49793 2371 12925
10050 DM relays 19 0.2% 8172 430 450
22712 Ephemeral 18 0.2% 7704 428 428
22808 Ephemeral 17 0.2% 7293 429 429
10000 Mute list 16 0.2% 26659 1666 12630
4244 Unknown 15 0.2% 8460 564 564
23025 Ephemeral 15 0.2% 6465 431 431
31234 Addressable 14 0.2% 24051 1718 2268
38503 Addressable 13 0.2% 7174 552 552
22630 Ephemeral 12 0.2% 11762 980 1654
37195 Addressable 12 0.2% 8752 729 934
6300 Unknown 12 0.2% 77247 6437 17248
10777 Unknown 12 0.2% 5861 488 489
30091 Addressable 11 0.1% 6763 615 629
20000 Ephemeral 11 0.1% 3806 346 346
30515 Addressable 11 0.1% 58344 5304 5304
30088 Addressable 11 0.1% 20483 1862 1871
22361 Ephemeral 10 0.1% 4439 444 445
30280 Addressable 9 0.1% 4617 513 513
22902 Ephemeral 9 0.1% 3861 429 429
22506 Ephemeral 8 0.1% 3416 427 427
7368 Unknown 8 0.1% 8304 1038 1040
22645 Ephemeral 8 0.1% 3432 429 429
38500 Addressable 8 0.1% 5696 712 713
33334 Addressable 8 0.1% 6648 831 831
30380 Addressable 8 0.1% 3632 454 454
22688 Ephemeral 7 0.1% 2989 427 427
22656 Ephemeral 7 0.1% 3017 431 431
22316 Ephemeral 7 0.1% 3106 444 444
30089 Chunked data 6 0.1% 123676 20613 44372
22456 Ephemeral 6 0.1% 3140 523 790
22739 Ephemeral 6 0.1% 2580 430 430
22882 Ephemeral 6 0.1% 5292 882 1449
22862 Ephemeral 6 0.1% 2574 429 429
1111 Comment 6 0.1% 7227 1204 1698
4245 Unknown 5 0.1% 2220 444 444
30512 Addressable 5 0.1% 7504 1501 1506
22641 Ephemeral 5 0.1% 2145 429 429
22586 Ephemeral 5 0.1% 2135 427 427
4243 Unknown 5 0.1% 2820 564 564
22835 Ephemeral 5 0.1% 2145 429 429
22620 Ephemeral 5 0.1% 2145 429 429
22298 Ephemeral 5 0.1% 2218 444 444
22852 Ephemeral 5 0.1% 2140 428 428
22726 Ephemeral 5 0.1% 2150 430 430
22362 Ephemeral 5 0.1% 2218 444 444
22772 Ephemeral 5 0.1% 4051 810 1132
22805 Ephemeral 4 0.1% 1712 428 428
9734 Zap request 4 0.1% 3756 939 1033
22200 Ephemeral 4 0.1% 1774 444 444
31510 Addressable 4 0.1% 3922 980 982
22570 Ephemeral 4 0.1% 1716 429 429
25195 Ephemeral 4 0.1% 2712 678 678
22752 Ephemeral 3 0.0% 4047 1349 3191
31339 Addressable 3 0.0% 4410 1470 1470
22949 Ephemeral 3 0.0% 1287 429 429
22358 Ephemeral 3 0.0% 1331 444 444
22474 Ephemeral 3 0.0% 1330 443 444
22185 Ephemeral 3 0.0% 1330 443 444
22936 Ephemeral 3 0.0% 1290 430 430
25196 Ephemeral 3 0.0% 4938 1646 2214
22439 Ephemeral 3 0.0% 1330 443 444
22824 Ephemeral 3 0.0% 1281 427 427
22270 Ephemeral 3 0.0% 1330 443 444
22263 Ephemeral 3 0.0% 1330 443 444
22755 Ephemeral 3 0.0% 1284 428 428
10051 Unknown 3 0.0% 1317 439 439
34775 Addressable 3 0.0% 7599 2533 2561
30300 Addressable 3 0.0% 5502 1834 1834
22234 Ephemeral 3 0.0% 1330 443 444
7403 Unknown 2 0.0% 978 489 489
22626 Ephemeral 2 0.0% 1916 958 1486
30079 Addressable 2 0.0% 1750 875 875
15750 Unknown 2 0.0% 1242 621 626
23337 Ephemeral 2 0.0% 1027 514 514
30815 Addressable 2 0.0% 75068 37534 37534
30213 Addressable 2 0.0% 1264 632 632
22716 Ephemeral 2 0.0% 854 427 427
22492 Ephemeral 2 0.0% 887 444 444
30000 Addressable 2 0.0% 1646 823 823
22463 Ephemeral 2 0.0% 887 444 444
22304 Ephemeral 2 0.0% 887 444 444
10003 Bookmarks 2 0.0% 20016 10008 15003
30443 Addressable 2 0.0% 2572 1286 1312
22233 Ephemeral 2 0.0% 886 443 443
22275 Ephemeral 2 0.0% 887 444 444
30087 Addressable 2 0.0% 11238 5619 7325
20078 Ephemeral 2 0.0% 1458 729 729
22057 Ephemeral 2 0.0% 887 444 444
22251 Ephemeral 2 0.0% 887 444 444
22181 Ephemeral 2 0.0% 887 444 444
22678 Ephemeral 2 0.0% 1388 694 694
22747 Ephemeral 2 0.0% 858 429 429
22410 Ephemeral 2 0.0% 887 444 444
22334 Ephemeral 2 0.0% 887 444 444
22249 Ephemeral 2 0.0% 887 444 444
22258 Ephemeral 2 0.0% 887 444 444
30090 Addressable 2 0.0% 2588 1294 1515
10312 Unknown 1 0.0% 472 472 472
22853 Ephemeral 1 0.0% 1673 1673 1673
22303 Ephemeral 1 0.0% 443 443 443
22306 Ephemeral 1 0.0% 443 443 443
20200 Ephemeral 1 0.0% 491 491 491
22132 Ephemeral 1 0.0% 443 443 443
20201 Ephemeral 1 0.0% 491 491 491
22245 Ephemeral 1 0.0% 443 443 443
22352 Ephemeral 1 0.0% 443 443 443
22240 Ephemeral 1 0.0% 443 443 443
34236 Addressable 1 0.0% 931 931 931
17 Unknown 1 0.0% 610 610 610
22286 Ephemeral 1 0.0% 443 443 443
13196 Unknown 1 0.0% 656 656 656
20242 Ephemeral 1 0.0% 346 346 346
7376 Unknown 1 0.0% 670 670 670
38385 Addressable 1 0.0% 1350 1350 1350
22355 Ephemeral 1 0.0% 443 443 443
23333 Ephemeral 1 0.0% 571 571 571
22737 Ephemeral 1 0.0% 556 556 556
22291 Ephemeral 1 0.0% 443 443 443
30421 Addressable 1 0.0% 995 995 995
22860 Ephemeral 1 0.0% 553 553 553
2333 Unknown 1 0.0% 659 659 659
22219 Ephemeral 1 0.0% 443 443 443
22382 Ephemeral 1 0.0% 443 443 443
23032 Ephemeral 1 0.0% 430 430 430
7375 Unknown 1 0.0% 1822 1822 1822
22579 Ephemeral 1 0.0% 1670 1670 1670
10007 Unknown 1 0.0% 715 715 715
10086 Unknown 1 0.0% 715 715 715
22729 Ephemeral 1 0.0% 3030 3030 3030
22540 Ephemeral 1 0.0% 426 426 426
22671 Ephemeral 1 0.0% 3194 3194 3194
30618 Addressable 1 0.0% 460 460 460
23118 Ephemeral 1 0.0% 430 430 430
38421 Addressable 1 0.0% 1069 1069 1069
10377 Unknown 1 0.0% 664 664 664
11111 Unknown 1 0.0% 382 382 382
34550 Community def 1 0.0% 867 867 867
22307 Ephemeral 1 0.0% 443 443 443
1078 Unknown 1 0.0% 13969 13969 13969
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 16:24:15 2026
# Config: cache_all_test_primal.jsonc
# Duration: 3600 seconds
[2026-08-02 16:24:32] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 16:24:33] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:24:34] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:24:37] [RELAY] ? EOSE
[2026-08-02 16:28:28] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:28:29] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:29:27] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:29:28] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:29:32] [RELAY] ? EOSE
[2026-08-02 16:29:45] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:29:46] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:30:42] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:30:43] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:30:54] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:30:55] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:29] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:30] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:30] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:31] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:31] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:32] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:32:48] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:32:49] [RELAY] wss://relay.primal.net CONNECTED
@@ -0,0 +1,217 @@
=== Cache-All Feasibility Test Report ===
Duration: 0h 6m 16s
=== Global Summary ===
Total events: 5992
Total bytes: 8743262 (8.34 MB)
Avg event size: 1459 bytes
Max event: 61894 bytes (kind 3)
Unique pubkeys: 1142
Relays: 1
=== Relay Summary ===
Relay Events EOSE CLOSED NOTICE Err Disc Status
wss://relay.primal.net 5992 0 0 0 0 3 OK
=== Kind Distribution ===
Kind Name Count % Total Bytes Avg Max
30078 App data 495 8.3% 2024957 4091 54029
21059 Gift wrap (NIP-59) 386 6.4% 1099128 2847 5314
4515 Unknown 374 6.2% 781718 2090 14221
22591 Ephemeral 371 6.2% 286839 773 1228
20001 Ephemeral 271 4.5% 315643 1165 2029
22734 Ephemeral 264 4.4% 112728 427 427
30382 Addressable 255 4.3% 121375 476 483
30383 Addressable 237 4.0% 126938 536 547
1 Text note 212 3.5% 211399 997 2011
5 Deletion 183 3.1% 91915 502 664
22236 Ephemeral 146 2.4% 104252 714 810
0 Profile metadata 145 2.4% 103418 713 2012
7 Reaction 138 2.3% 79421 576 1036
22743 Ephemeral 134 2.2% 57352 428 428
1059 Gift wrap seal 132 2.2% 430222 3259 5285
29333 Ephemeral 130 2.2% 63960 492 492
22611 Ephemeral 117 2.0% 50147 429 429
10002 Relay list 114 1.9% 510211 4476 42449
23456 Ephemeral 72 1.2% 121616 1689 1874
22850 Ephemeral 71 1.2% 30530 430 430
22684 Ephemeral 70 1.2% 30170 431 431
22838 Ephemeral 63 1.1% 27027 429 429
23003 Ephemeral 63 1.1% 26964 428 428
22774 Ephemeral 62 1.0% 26536 428 428
22876 Ephemeral 61 1.0% 28880 473 3193
22583 Ephemeral 61 1.0% 26047 427 427
22613 Ephemeral 60 1.0% 43172 720 1133
4 Encrypted DM 58 1.0% 61778 1065 17018
25555 Ephemeral 56 0.9% 249772 4460 16895
25050 Ephemeral 51 0.9% 20451 401 401
11998 Unknown 48 0.8% 22272 464 465
22728 Ephemeral 48 0.8% 20592 429 429
31234 Addressable 33 0.6% 77859 2359 2612
13194 NWC info 32 0.5% 16448 514 514
20387 Ephemeral 32 0.5% 16448 514 514
24133 Ephemeral 32 0.5% 23680 740 2214
3 Follow list 31 0.5% 167289 5396 61894
4004 Unknown 26 0.4% 30394 1169 1169
22816 Ephemeral 26 0.4% 11154 429 429
20004 Ephemeral 26 0.4% 13878 534 549
30315 User status 26 0.4% 17657 679 872
443 Unknown 25 0.4% 43504 1740 22480
38501 Addressable 24 0.4% 17819 742 744
1985 Unknown 24 0.4% 11435 476 483
22882 Ephemeral 23 0.4% 20949 911 1455
5300 Unknown 22 0.4% 13434 611 701
31990 App handler info 22 0.4% 21777 990 6861
22790 Ephemeral 21 0.4% 8988 428 428
21050 Ephemeral 19 0.3% 17510 922 1874
10100 Unknown 19 0.3% 22108 1164 1589
20333 Ephemeral 18 0.3% 14340 797 798
30088 Addressable 16 0.3% 29798 1862 1871
6 Repost 16 0.3% 28131 1758 3369
30091 Addressable 16 0.3% 9842 615 629
6300 Unknown 15 0.3% 45643 3043 8157
22658 Ephemeral 15 0.3% 11909 794 1161
30311 Addressable 15 0.3% 16556 1104 1197
445 Encrypted group msg 15 0.3% 45070 3005 4130
10777 Unknown 14 0.2% 6840 489 489
9735 Zap receipt 14 0.2% 27231 1945 2777
4244 Unknown 12 0.2% 6768 564 564
22456 Ephemeral 12 0.2% 6808 567 790
38503 Addressable 11 0.2% 6070 552 552
10050 DM relays 11 0.2% 4882 444 592
30515 Addressable 10 0.2% 53040 5304 5304
30512 Addressable 10 0.2% 15210 1521 1596
30089 Chunked data 10 0.2% 164434 16443 44372
10000 Mute list 10 0.2% 16448 1645 4287
22809 Ephemeral 9 0.2% 3852 428 428
22486 Ephemeral 9 0.2% 3834 426 426
22376 Ephemeral 8 0.1% 3550 444 444
7368 Unknown 8 0.1% 8294 1037 1040
38500 Addressable 8 0.1% 5696 712 713
30380 Addressable 7 0.1% 3178 454 454
20000 Ephemeral 7 0.1% 2422 346 346
22104 Ephemeral 7 0.1% 3105 444 444
23025 Ephemeral 7 0.1% 3017 431 431
22896 Ephemeral 7 0.1% 3003 429 429
33334 Addressable 7 0.1% 5817 831 831
22835 Ephemeral 6 0.1% 2574 429 429
30443 Addressable 6 0.1% 7297 1216 1260
22633 Ephemeral 6 0.1% 6097 1016 1668
22392 Ephemeral 6 0.1% 2662 444 444
1984 Report 6 0.1% 3180 530 530
30064 Addressable 6 0.1% 4438 740 991
22715 Ephemeral 6 0.1% 2562 427 427
30280 Addressable 6 0.1% 3078 513 513
22559 Ephemeral 6 0.1% 2574 429 429
22813 Ephemeral 6 0.1% 2562 427 427
22635 Ephemeral 6 0.1% 2556 426 426
22746 Ephemeral 5 0.1% 2155 431 431
22364 Ephemeral 5 0.1% 2135 427 427
37195 Addressable 5 0.1% 3664 733 786
22571 Ephemeral 5 0.1% 4055 811 1127
23004 Ephemeral 5 0.1% 2145 429 429
22834 Ephemeral 5 0.1% 4050 810 1126
22636 Ephemeral 5 0.1% 3280 656 1164
22786 Ephemeral 4 0.1% 1720 430 430
25195 Ephemeral 4 0.1% 2712 678 678
4243 Unknown 4 0.1% 2256 564 564
22168 Ephemeral 4 0.1% 1774 444 444
22760 Ephemeral 4 0.1% 2239 560 695
22733 Ephemeral 4 0.1% 1696 424 424
22878 Ephemeral 3 0.1% 4059 1353 3203
22902 Ephemeral 3 0.1% 5023 1674 1675
22428 Ephemeral 3 0.1% 1331 444 444
25196 Ephemeral 3 0.1% 4938 1646 2214
22325 Ephemeral 3 0.1% 1331 444 444
4245 Unknown 3 0.1% 1332 444 444
22258 Ephemeral 3 0.1% 1331 444 444
22625 Ephemeral 3 0.1% 1284 428 428
22631 Ephemeral 3 0.1% 1281 427 427
22551 Ephemeral 3 0.1% 1816 605 695
22763 Ephemeral 3 0.1% 2988 996 1331
3927 Unknown 2 0.0% 1066 533 533
22186 Ephemeral 2 0.0% 887 444 444
30213 Addressable 2 0.0% 1264 632 632
22367 Ephemeral 2 0.0% 887 444 444
22783 Ephemeral 2 0.0% 1391 696 697
22616 Ephemeral 2 0.0% 858 429 429
22268 Ephemeral 2 0.0% 887 444 444
22279 Ephemeral 2 0.0% 887 444 444
22474 Ephemeral 2 0.0% 887 444 444
22407 Ephemeral 2 0.0% 887 444 444
22729 Ephemeral 2 0.0% 854 427 427
22234 Ephemeral 2 0.0% 887 444 444
10003 Bookmarks 2 0.0% 18782 9391 15076
30815 Addressable 2 0.0% 75068 37534 37534
1111 Comment 2 0.0% 1775 888 922
30087 Addressable 2 0.0% 13286 6643 7325
22256 Ephemeral 2 0.0% 886 443 443
22299 Ephemeral 2 0.0% 887 444 444
30090 Addressable 2 0.0% 2911 1456 1515
22260 Ephemeral 2 0.0% 887 444 444
7376 Unknown 2 0.0% 1340 670 670
22012 Ephemeral 2 0.0% 887 444 444
30618 Addressable 2 0.0% 920 460 460
1078 Unknown 2 0.0% 28397 14198 14219
22313 Ephemeral 2 0.0% 887 444 444
31339 Addressable 2 0.0% 2940 1470 1470
15750 Unknown 2 0.0% 1184 592 620
20 Picture 2 0.0% 1664 832 834
22326 Ephemeral 2 0.0% 887 444 444
22841 Ephemeral 1 0.0% 428 428 428
22738 Ephemeral 1 0.0% 1668 1668 1668
22295 Ephemeral 1 0.0% 443 443 443
22296 Ephemeral 1 0.0% 443 443 443
31510 Addressable 1 0.0% 979 979 979
22691 Ephemeral 1 0.0% 431 431 431
22340 Ephemeral 1 0.0% 443 443 443
30003 Addressable 1 0.0% 387 387 387
22197 Ephemeral 1 0.0% 443 443 443
22257 Ephemeral 1 0.0% 443 443 443
30079 Addressable 1 0.0% 875 875 875
22811 Ephemeral 1 0.0% 3211 3211 3211
38385 Addressable 1 0.0% 1350 1350 1350
22339 Ephemeral 1 0.0% 443 443 443
13196 Unknown 1 0.0% 656 656 656
20200 Ephemeral 1 0.0% 491 491 491
20201 Ephemeral 1 0.0% 491 491 491
22395 Ephemeral 1 0.0% 443 443 443
30023 Long-form article 1 0.0% 27062 27062 27062
7403 Unknown 1 0.0% 490 490 490
22618 Ephemeral 1 0.0% 1668 1668 1668
17 Unknown 1 0.0% 593 593 593
30000 Addressable 1 0.0% 823 823 823
22558 Ephemeral 1 0.0% 428 428 428
22143 Ephemeral 1 0.0% 443 443 443
22198 Ephemeral 1 0.0% 443 443 443
7375 Unknown 1 0.0% 2846 2846 2846
22506 Ephemeral 1 0.0% 426 426 426
22243 Ephemeral 1 0.0% 443 443 443
23086 Ephemeral 1 0.0% 431 431 431
22688 Ephemeral 1 0.0% 3201 3201 3201
22975 Ephemeral 1 0.0% 429 429 429
22270 Ephemeral 1 0.0% 443 443 443
22584 Ephemeral 1 0.0% 1675 1675 1675
22764 Ephemeral 1 0.0% 551 551 551
23071 Ephemeral 1 0.0% 1675 1675 1675
22817 Ephemeral 1 0.0% 559 559 559
22614 Ephemeral 1 0.0% 3200 3200 3200
22330 Ephemeral 1 0.0% 443 443 443
22229 Ephemeral 1 0.0% 443 443 443
24421 Ephemeral 1 0.0% 525 525 525
=== Raw Relay Response Log ===
# Cache-All Feasibility Test - Raw Relay Log
# Started: Sun Aug 2 16:34:39 2026
# Config: cache_all_test_primal.jsonc
# Duration: 3600 seconds
[2026-08-02 16:34:56] [RELAY] wss://relay.primal.net STATUS: connected
[2026-08-02 16:34:57] [RELAY] ? EOSE
[2026-08-02 16:38:58] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:38:58] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:39:56] [RELAY] ? EOSE
[2026-08-02 16:40:16] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:40:17] [RELAY] wss://relay.primal.net CONNECTED
[2026-08-02 16:40:17] [RELAY] wss://relay.primal.net DISCONNECTED: Connection state transitioned to 2 during poll
[2026-08-02 16:40:18] [RELAY] wss://relay.primal.net CONNECTED
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,921 @@
{
"duration_seconds": 120,
"total_events": 4267,
"total_bytes": 6726611,
"max_event_bytes": 42385,
"max_event_kind": 10002,
"unique_pubkeys": 1860,
"relay_count": 3,
"relays": [{
"url": "wss://relay.primal.net",
"events": 1670,
"eose": 1,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 1792,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 805,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}],
"kinds": [{
"kind": 21059,
"count": 1199,
"total_bytes": 3547818,
"max_bytes": 5314
}, {
"kind": 5,
"count": 466,
"total_bytes": 204587,
"max_bytes": 1064
}, {
"kind": 30078,
"count": 222,
"total_bytes": 948782,
"max_bytes": 40337
}, {
"kind": 22668,
"count": 221,
"total_bytes": 94809,
"max_bytes": 429
}, {
"kind": 20001,
"count": 152,
"total_bytes": 106536,
"max_bytes": 1755
}, {
"kind": 4515,
"count": 152,
"total_bytes": 260732,
"max_bytes": 8759
}, {
"kind": 22734,
"count": 108,
"total_bytes": 46116,
"max_bytes": 427
}, {
"kind": 1,
"count": 105,
"total_bytes": 83026,
"max_bytes": 3482
}, {
"kind": 21050,
"count": 91,
"total_bytes": 89206,
"max_bytes": 2558
}, {
"kind": 24747,
"count": 85,
"total_bytes": 42775,
"max_bytes": 671
}, {
"kind": 22850,
"count": 77,
"total_bytes": 33110,
"max_bytes": 430
}, {
"kind": 22591,
"count": 66,
"total_bytes": 53607,
"max_bytes": 1222
}, {
"kind": 38501,
"count": 61,
"total_bytes": 45175,
"max_bytes": 744
}, {
"kind": 10002,
"count": 51,
"total_bytes": 63749,
"max_bytes": 42385
}, {
"kind": 0,
"count": 47,
"total_bytes": 33672,
"max_bytes": 1611
}, {
"kind": 37195,
"count": 44,
"total_bytes": 32855,
"max_bytes": 771
}, {
"kind": 24242,
"count": 37,
"total_bytes": 21423,
"max_bytes": 579
}, {
"kind": 25050,
"count": 36,
"total_bytes": 15972,
"max_bytes": 465
}, {
"kind": 7,
"count": 36,
"total_bytes": 18965,
"max_bytes": 646
}, {
"kind": 22613,
"count": 35,
"total_bytes": 26154,
"max_bytes": 1134
}, {
"kind": 22484,
"count": 32,
"total_bytes": 29125,
"max_bytes": 1163
}, {
"kind": 22749,
"count": 29,
"total_bytes": 12441,
"max_bytes": 429
}, {
"kind": 24133,
"count": 25,
"total_bytes": 37446,
"max_bytes": 7334
}, {
"kind": 22601,
"count": 25,
"total_bytes": 10700,
"max_bytes": 428
}, {
"kind": 22507,
"count": 24,
"total_bytes": 22582,
"max_bytes": 1165
}, {
"kind": 1059,
"count": 23,
"total_bytes": 41391,
"max_bytes": 2897
}, {
"kind": 22699,
"count": 23,
"total_bytes": 9844,
"max_bytes": 428
}, {
"kind": 22676,
"count": 23,
"total_bytes": 9821,
"max_bytes": 427
}, {
"kind": 22774,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 22795,
"count": 20,
"total_bytes": 8580,
"max_bytes": 429
}, {
"kind": 22655,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 23003,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 30382,
"count": 20,
"total_bytes": 9960,
"max_bytes": 498
}, {
"kind": 29333,
"count": 20,
"total_bytes": 9840,
"max_bytes": 492
}, {
"kind": 25555,
"count": 18,
"total_bytes": 62434,
"max_bytes": 17003
}, {
"kind": 11998,
"count": 18,
"total_bytes": 8322,
"max_bytes": 465
}, {
"kind": 31990,
"count": 18,
"total_bytes": 16845,
"max_bytes": 1465
}, {
"kind": 4,
"count": 18,
"total_bytes": 28064,
"max_bytes": 17018
}, {
"kind": 13194,
"count": 18,
"total_bytes": 9252,
"max_bytes": 514
}, {
"kind": 22689,
"count": 17,
"total_bytes": 7259,
"max_bytes": 427
}, {
"kind": 5300,
"count": 17,
"total_bytes": 10495,
"max_bytes": 701
}, {
"kind": 445,
"count": 17,
"total_bytes": 81892,
"max_bytes": 19620
}, {
"kind": 20000,
"count": 15,
"total_bytes": 7045,
"max_bytes": 546
}, {
"kind": 20387,
"count": 14,
"total_bytes": 7196,
"max_bytes": 514
}, {
"kind": 20004,
"count": 14,
"total_bytes": 7488,
"max_bytes": 549
}, {
"kind": 7368,
"count": 13,
"total_bytes": 13476,
"max_bytes": 1040
}, {
"kind": 22456,
"count": 13,
"total_bytes": 6110,
"max_bytes": 470
}, {
"kind": 9735,
"count": 13,
"total_bytes": 26072,
"max_bytes": 2331
}, {
"kind": 22551,
"count": 13,
"total_bytes": 11042,
"max_bytes": 1161
}, {
"kind": 22760,
"count": 13,
"total_bytes": 11014,
"max_bytes": 1161
}, {
"kind": 22658,
"count": 13,
"total_bytes": 11050,
"max_bytes": 1164
}, {
"kind": 10100,
"count": 11,
"total_bytes": 20684,
"max_bytes": 3825
}, {
"kind": 30315,
"count": 11,
"total_bytes": 8899,
"max_bytes": 1038
}, {
"kind": 20787,
"count": 11,
"total_bytes": 15458,
"max_bytes": 2474
}, {
"kind": 22236,
"count": 10,
"total_bytes": 7818,
"max_bytes": 814
}, {
"kind": 22832,
"count": 10,
"total_bytes": 4286,
"max_bytes": 429
}, {
"kind": 38502,
"count": 8,
"total_bytes": 3080,
"max_bytes": 385
}, {
"kind": 4004,
"count": 8,
"total_bytes": 9352,
"max_bytes": 1169
}, {
"kind": 22785,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 3,
"count": 8,
"total_bytes": 35850,
"max_bytes": 9845
}, {
"kind": 1985,
"count": 8,
"total_bytes": 3792,
"max_bytes": 474
}, {
"kind": 22584,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 22816,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 22752,
"count": 8,
"total_bytes": 6212,
"max_bytes": 3199
}, {
"kind": 6,
"count": 7,
"total_bytes": 10112,
"max_bytes": 1872
}, {
"kind": 443,
"count": 7,
"total_bytes": 7780,
"max_bytes": 1116
}, {
"kind": 22651,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 1077,
"count": 6,
"total_bytes": 3642,
"max_bytes": 607
}, {
"kind": 26428,
"count": 6,
"total_bytes": 5254,
"max_bytes": 876
}, {
"kind": 22616,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22626,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 22914,
"count": 6,
"total_bytes": 2580,
"max_bytes": 430
}, {
"kind": 22614,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 22419,
"count": 5,
"total_bytes": 2218,
"max_bytes": 444
}, {
"kind": 22847,
"count": 5,
"total_bytes": 2155,
"max_bytes": 431
}, {
"kind": 30088,
"count": 5,
"total_bytes": 9317,
"max_bytes": 1871
}, {
"kind": 10050,
"count": 5,
"total_bytes": 2250,
"max_bytes": 450
}, {
"kind": 30091,
"count": 5,
"total_bytes": 3085,
"max_bytes": 629
}, {
"kind": 30311,
"count": 5,
"total_bytes": 5153,
"max_bytes": 1197
}, {
"kind": 23080,
"count": 5,
"total_bytes": 2155,
"max_bytes": 431
}, {
"kind": 38225,
"count": 4,
"total_bytes": 4077,
"max_bytes": 1036
}, {
"kind": 4244,
"count": 4,
"total_bytes": 2256,
"max_bytes": 564
}, {
"kind": 26429,
"count": 4,
"total_bytes": 1964,
"max_bytes": 491
}, {
"kind": 3927,
"count": 4,
"total_bytes": 2132,
"max_bytes": 533
}, {
"kind": 22806,
"count": 4,
"total_bytes": 1712,
"max_bytes": 428
}, {
"kind": 30000,
"count": 4,
"total_bytes": 9358,
"max_bytes": 2845
}, {
"kind": 21617,
"count": 4,
"total_bytes": 1980,
"max_bytes": 495
}, {
"kind": 20333,
"count": 4,
"total_bytes": 3188,
"max_bytes": 798
}, {
"kind": 22548,
"count": 4,
"total_bytes": 1720,
"max_bytes": 430
}, {
"kind": 38503,
"count": 4,
"total_bytes": 2212,
"max_bytes": 553
}, {
"kind": 30515,
"count": 4,
"total_bytes": 18496,
"max_bytes": 4624
}, {
"kind": 22740,
"count": 4,
"total_bytes": 1708,
"max_bytes": 427
}, {
"kind": 22226,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 10011,
"count": 3,
"total_bytes": 1375,
"max_bytes": 513
}, {
"kind": 30089,
"count": 3,
"total_bytes": 6944,
"max_bytes": 3412
}, {
"kind": 30023,
"count": 3,
"total_bytes": 19822,
"max_bytes": 7860
}, {
"kind": 21060,
"count": 3,
"total_bytes": 1710,
"max_bytes": 570
}, {
"kind": 22526,
"count": 3,
"total_bytes": 1290,
"max_bytes": 430
}, {
"kind": 22511,
"count": 3,
"total_bytes": 2383,
"max_bytes": 1163
}, {
"kind": 22167,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 30213,
"count": 3,
"total_bytes": 1866,
"max_bytes": 632
}, {
"kind": 22748,
"count": 3,
"total_bytes": 1284,
"max_bytes": 428
}, {
"kind": 31234,
"count": 3,
"total_bytes": 4248,
"max_bytes": 1416
}, {
"kind": 22838,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 38500,
"count": 2,
"total_bytes": 1424,
"max_bytes": 713
}, {
"kind": 38384,
"count": 2,
"total_bytes": 1486,
"max_bytes": 743
}, {
"kind": 30443,
"count": 2,
"total_bytes": 2379,
"max_bytes": 1220
}, {
"kind": 20384,
"count": 2,
"total_bytes": 1278,
"max_bytes": 639
}, {
"kind": 34237,
"count": 2,
"total_bytes": 836,
"max_bytes": 418
}, {
"kind": 22280,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 23194,
"count": 2,
"total_bytes": 1020,
"max_bytes": 510
}, {
"kind": 22320,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30080,
"count": 2,
"total_bytes": 1971,
"max_bytes": 986
}, {
"kind": 22205,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22770,
"count": 2,
"total_bytes": 862,
"max_bytes": 431
}, {
"kind": 22305,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22356,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22611,
"count": 2,
"total_bytes": 852,
"max_bytes": 426
}, {
"kind": 22217,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22884,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 33334,
"count": 2,
"total_bytes": 1662,
"max_bytes": 831
}, {
"kind": 6300,
"count": 2,
"total_bytes": 8371,
"max_bytes": 5058
}, {
"kind": 22169,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22190,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30079,
"count": 2,
"total_bytes": 1750,
"max_bytes": 875
}, {
"kind": 39101,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 22345,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22310,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 1111,
"count": 2,
"total_bytes": 2557,
"max_bytes": 1564
}, {
"kind": 22235,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 38385,
"count": 2,
"total_bytes": 2552,
"max_bytes": 1319
}, {
"kind": 4747,
"count": 1,
"total_bytes": 721,
"max_bytes": 721
}, {
"kind": 22844,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 22807,
"count": 1,
"total_bytes": 3205,
"max_bytes": 3205
}, {
"kind": 30066,
"count": 1,
"total_bytes": 675,
"max_bytes": 675
}, {
"kind": 4245,
"count": 1,
"total_bytes": 444,
"max_bytes": 444
}, {
"kind": 7376,
"count": 1,
"total_bytes": 521,
"max_bytes": 521
}, {
"kind": 22450,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22261,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 4243,
"count": 1,
"total_bytes": 564,
"max_bytes": 564
}, {
"kind": 22242,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 10063,
"count": 1,
"total_bytes": 415,
"max_bytes": 415
}, {
"kind": 22306,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22174,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22831,
"count": 1,
"total_bytes": 3182,
"max_bytes": 3182
}, {
"kind": 22227,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 7375,
"count": 1,
"total_bytes": 8629,
"max_bytes": 8629
}, {
"kind": 22685,
"count": 1,
"total_bytes": 553,
"max_bytes": 553
}, {
"kind": 22299,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22193,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22975,
"count": 1,
"total_bytes": 3195,
"max_bytes": 3195
}, {
"kind": 30380,
"count": 1,
"total_bytes": 454,
"max_bytes": 454
}, {
"kind": 20,
"count": 1,
"total_bytes": 860,
"max_bytes": 860
}, {
"kind": 10030,
"count": 1,
"total_bytes": 405,
"max_bytes": 405
}, {
"kind": 30087,
"count": 1,
"total_bytes": 7325,
"max_bytes": 7325
}, {
"kind": 30090,
"count": 1,
"total_bytes": 1515,
"max_bytes": 1515
}, {
"kind": 22220,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 39019,
"count": 1,
"total_bytes": 961,
"max_bytes": 961
}, {
"kind": 39015,
"count": 1,
"total_bytes": 696,
"max_bytes": 696
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 30618,
"count": 1,
"total_bytes": 733,
"max_bytes": 733
}, {
"kind": 31991,
"count": 1,
"total_bytes": 461,
"max_bytes": 461
}, {
"kind": 1080,
"count": 1,
"total_bytes": 949,
"max_bytes": 949
}, {
"kind": 3434,
"count": 1,
"total_bytes": 705,
"max_bytes": 705
}, {
"kind": 33333,
"count": 1,
"total_bytes": 2346,
"max_bytes": 2346
}, {
"kind": 22735,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 22415,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 21966,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 11316,
"count": 1,
"total_bytes": 725,
"max_bytes": 725
}, {
"kind": 11317,
"count": 1,
"total_bytes": 12051,
"max_bytes": 12051
}, {
"kind": 25195,
"count": 1,
"total_bytes": 678,
"max_bytes": 678
}, {
"kind": 25196,
"count": 1,
"total_bytes": 1362,
"max_bytes": 1362
}, {
"kind": 22402,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 31510,
"count": 1,
"total_bytes": 979,
"max_bytes": 979
}, {
"kind": 1078,
"count": 1,
"total_bytes": 14200,
"max_bytes": 14200
}, {
"kind": 38383,
"count": 1,
"total_bytes": 685,
"max_bytes": 685
}]
}
@@ -0,0 +1,916 @@
{
"duration_seconds": 120,
"total_events": 5012,
"total_bytes": 8091410,
"max_event_bytes": 52672,
"max_event_kind": 3,
"unique_pubkeys": 2034,
"relay_count": 3,
"relays": [{
"url": "wss://relay.primal.net",
"events": 1599,
"eose": 1,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 2389,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 1024,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true,
"raw_responses": []
}],
"kinds": [{
"kind": 21059,
"count": 1361,
"total_bytes": 4189338,
"max_bytes": 5314
}, {
"kind": 5,
"count": 579,
"total_bytes": 254363,
"max_bytes": 624
}, {
"kind": 22668,
"count": 265,
"total_bytes": 113685,
"max_bytes": 429
}, {
"kind": 30078,
"count": 190,
"total_bytes": 702178,
"max_bytes": 14343
}, {
"kind": 20001,
"count": 181,
"total_bytes": 108257,
"max_bytes": 1489
}, {
"kind": 22639,
"count": 176,
"total_bytes": 136058,
"max_bytes": 1228
}, {
"kind": 4515,
"count": 168,
"total_bytes": 319242,
"max_bytes": 14220
}, {
"kind": 24133,
"count": 152,
"total_bytes": 464764,
"max_bytes": 14162
}, {
"kind": 22734,
"count": 135,
"total_bytes": 57645,
"max_bytes": 427
}, {
"kind": 31991,
"count": 116,
"total_bytes": 53476,
"max_bytes": 461
}, {
"kind": 21050,
"count": 84,
"total_bytes": 81900,
"max_bytes": 2558
}, {
"kind": 1,
"count": 80,
"total_bytes": 58412,
"max_bytes": 1443
}, {
"kind": 22795,
"count": 76,
"total_bytes": 104000,
"max_bytes": 1848
}, {
"kind": 22850,
"count": 75,
"total_bytes": 33353,
"max_bytes": 1169
}, {
"kind": 38501,
"count": 69,
"total_bytes": 51105,
"max_bytes": 744
}, {
"kind": 22842,
"count": 55,
"total_bytes": 92475,
"max_bytes": 1849
}, {
"kind": 10002,
"count": 48,
"total_bytes": 19784,
"max_bytes": 537
}, {
"kind": 0,
"count": 47,
"total_bytes": 34961,
"max_bytes": 1356
}, {
"kind": 37195,
"count": 38,
"total_bytes": 28214,
"max_bytes": 771
}, {
"kind": 7,
"count": 38,
"total_bytes": 21600,
"max_bytes": 779
}, {
"kind": 25050,
"count": 36,
"total_bytes": 15972,
"max_bytes": 465
}, {
"kind": 24242,
"count": 34,
"total_bytes": 19686,
"max_bytes": 579
}, {
"kind": 1059,
"count": 33,
"total_bytes": 58147,
"max_bytes": 3298
}, {
"kind": 22484,
"count": 32,
"total_bytes": 29156,
"max_bytes": 1164
}, {
"kind": 22507,
"count": 28,
"total_bytes": 24308,
"max_bytes": 1165
}, {
"kind": 22613,
"count": 25,
"total_bytes": 17238,
"max_bytes": 691
}, {
"kind": 29333,
"count": 25,
"total_bytes": 12300,
"max_bytes": 492
}, {
"kind": 22601,
"count": 24,
"total_bytes": 10272,
"max_bytes": 428
}, {
"kind": 5300,
"count": 23,
"total_bytes": 14096,
"max_bytes": 701
}, {
"kind": 22751,
"count": 23,
"total_bytes": 9798,
"max_bytes": 426
}, {
"kind": 22699,
"count": 22,
"total_bytes": 9416,
"max_bytes": 428
}, {
"kind": 11998,
"count": 22,
"total_bytes": 10182,
"max_bytes": 465
}, {
"kind": 22676,
"count": 22,
"total_bytes": 9394,
"max_bytes": 427
}, {
"kind": 13194,
"count": 21,
"total_bytes": 10617,
"max_bytes": 514
}, {
"kind": 22774,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 23003,
"count": 20,
"total_bytes": 8560,
"max_bytes": 428
}, {
"kind": 22655,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22543,
"count": 19,
"total_bytes": 34993,
"max_bytes": 1848
}, {
"kind": 22455,
"count": 19,
"total_bytes": 34943,
"max_bytes": 1844
}, {
"kind": 25555,
"count": 19,
"total_bytes": 94673,
"max_bytes": 17111
}, {
"kind": 30443,
"count": 19,
"total_bytes": 22379,
"max_bytes": 1260
}, {
"kind": 22777,
"count": 19,
"total_bytes": 16685,
"max_bytes": 1651
}, {
"kind": 22891,
"count": 18,
"total_bytes": 33189,
"max_bytes": 1848
}, {
"kind": 31990,
"count": 17,
"total_bytes": 15825,
"max_bytes": 1465
}, {
"kind": 20000,
"count": 16,
"total_bytes": 7206,
"max_bytes": 522
}, {
"kind": 32123,
"count": 14,
"total_bytes": 10528,
"max_bytes": 900
}, {
"kind": 22456,
"count": 13,
"total_bytes": 6110,
"max_bytes": 470
}, {
"kind": 20004,
"count": 13,
"total_bytes": 6741,
"max_bytes": 549
}, {
"kind": 22591,
"count": 13,
"total_bytes": 6348,
"max_bytes": 697
}, {
"kind": 20787,
"count": 12,
"total_bytes": 16864,
"max_bytes": 2478
}, {
"kind": 4,
"count": 12,
"total_bytes": 7972,
"max_bytes": 786
}, {
"kind": 20387,
"count": 12,
"total_bytes": 6168,
"max_bytes": 514
}, {
"kind": 38502,
"count": 12,
"total_bytes": 4620,
"max_bytes": 385
}, {
"kind": 10100,
"count": 11,
"total_bytes": 20938,
"max_bytes": 3825
}, {
"kind": 20050,
"count": 11,
"total_bytes": 9711,
"max_bytes": 891
}, {
"kind": 3,
"count": 10,
"total_bytes": 72137,
"max_bytes": 52672
}, {
"kind": 9735,
"count": 10,
"total_bytes": 20833,
"max_bytes": 2143
}, {
"kind": 445,
"count": 10,
"total_bytes": 17866,
"max_bytes": 4126
}, {
"kind": 22626,
"count": 10,
"total_bytes": 4280,
"max_bytes": 428
}, {
"kind": 7368,
"count": 9,
"total_bytes": 9343,
"max_bytes": 1041
}, {
"kind": 22736,
"count": 9,
"total_bytes": 3816,
"max_bytes": 424
}, {
"kind": 1985,
"count": 8,
"total_bytes": 3810,
"max_bytes": 483
}, {
"kind": 30091,
"count": 8,
"total_bytes": 4927,
"max_bytes": 629
}, {
"kind": 30315,
"count": 8,
"total_bytes": 4522,
"max_bytes": 872
}, {
"kind": 25055,
"count": 8,
"total_bytes": 10605,
"max_bytes": 1764
}, {
"kind": 22791,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 22816,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 4004,
"count": 8,
"total_bytes": 9352,
"max_bytes": 1169
}, {
"kind": 30088,
"count": 8,
"total_bytes": 14929,
"max_bytes": 1871
}, {
"kind": 31234,
"count": 8,
"total_bytes": 14048,
"max_bytes": 1756
}, {
"kind": 22813,
"count": 7,
"total_bytes": 2996,
"max_bytes": 428
}, {
"kind": 22957,
"count": 7,
"total_bytes": 3010,
"max_bytes": 430
}, {
"kind": 30089,
"count": 7,
"total_bytes": 45972,
"max_bytes": 27988
}, {
"kind": 32678,
"count": 7,
"total_bytes": 5704,
"max_bytes": 900
}, {
"kind": 22832,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22897,
"count": 6,
"total_bytes": 2586,
"max_bytes": 431
}, {
"kind": 22549,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22702,
"count": 6,
"total_bytes": 4753,
"max_bytes": 2618
}, {
"kind": 20333,
"count": 6,
"total_bytes": 4780,
"max_bytes": 798
}, {
"kind": 30023,
"count": 6,
"total_bytes": 21101,
"max_bytes": 5056
}, {
"kind": 22568,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 38385,
"count": 5,
"total_bytes": 6849,
"max_bytes": 1496
}, {
"kind": 4244,
"count": 5,
"total_bytes": 2820,
"max_bytes": 564
}, {
"kind": 22717,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 21617,
"count": 4,
"total_bytes": 1980,
"max_bytes": 495
}, {
"kind": 38225,
"count": 4,
"total_bytes": 4061,
"max_bytes": 1039
}, {
"kind": 6,
"count": 4,
"total_bytes": 8823,
"max_bytes": 3647
}, {
"kind": 22333,
"count": 4,
"total_bytes": 1773,
"max_bytes": 444
}, {
"kind": 22962,
"count": 3,
"total_bytes": 1290,
"max_bytes": 430
}, {
"kind": 33334,
"count": 3,
"total_bytes": 2229,
"max_bytes": 831
}, {
"kind": 1002,
"count": 3,
"total_bytes": 2793,
"max_bytes": 979
}, {
"kind": 22244,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22907,
"count": 3,
"total_bytes": 1287,
"max_bytes": 429
}, {
"kind": 10011,
"count": 3,
"total_bytes": 1229,
"max_bytes": 431
}, {
"kind": 22258,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22245,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 30618,
"count": 3,
"total_bytes": 1755,
"max_bytes": 733
}, {
"kind": 22296,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 38503,
"count": 3,
"total_bytes": 1659,
"max_bytes": 553
}, {
"kind": 22338,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22195,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 22342,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 10050,
"count": 3,
"total_bytes": 1350,
"max_bytes": 450
}, {
"kind": 30311,
"count": 3,
"total_bytes": 3414,
"max_bytes": 1197
}, {
"kind": 32680,
"count": 2,
"total_bytes": 1376,
"max_bytes": 688
}, {
"kind": 30380,
"count": 2,
"total_bytes": 908,
"max_bytes": 454
}, {
"kind": 22611,
"count": 2,
"total_bytes": 852,
"max_bytes": 426
}, {
"kind": 4243,
"count": 2,
"total_bytes": 1128,
"max_bytes": 564
}, {
"kind": 22526,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 22320,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 4245,
"count": 2,
"total_bytes": 888,
"max_bytes": 444
}, {
"kind": 1984,
"count": 2,
"total_bytes": 873,
"max_bytes": 446
}, {
"kind": 1111,
"count": 2,
"total_bytes": 3115,
"max_bytes": 1558
}, {
"kind": 22311,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22662,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 22089,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22221,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22157,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 38384,
"count": 2,
"total_bytes": 1486,
"max_bytes": 743
}, {
"kind": 20384,
"count": 2,
"total_bytes": 1278,
"max_bytes": 639
}, {
"kind": 30080,
"count": 2,
"total_bytes": 1679,
"max_bytes": 985
}, {
"kind": 39101,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 6300,
"count": 2,
"total_bytes": 11904,
"max_bytes": 6898
}, {
"kind": 10003,
"count": 2,
"total_bytes": 14466,
"max_bytes": 7233
}, {
"kind": 30515,
"count": 2,
"total_bytes": 9248,
"max_bytes": 4624
}, {
"kind": 22829,
"count": 2,
"total_bytes": 6387,
"max_bytes": 3194
}, {
"kind": 22106,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 31927,
"count": 2,
"total_bytes": 3000,
"max_bytes": 1500
}, {
"kind": 31510,
"count": 2,
"total_bytes": 1959,
"max_bytes": 980
}, {
"kind": 3927,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 1040,
"count": 2,
"total_bytes": 2096,
"max_bytes": 1048
}, {
"kind": 31000,
"count": 2,
"total_bytes": 1204,
"max_bytes": 616
}, {
"kind": 22201,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22269,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22818,
"count": 2,
"total_bytes": 852,
"max_bytes": 426
}, {
"kind": 22364,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 25195,
"count": 1,
"total_bytes": 678,
"max_bytes": 678
}, {
"kind": 25196,
"count": 1,
"total_bytes": 1362,
"max_bytes": 1362
}, {
"kind": 22283,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 10030,
"count": 1,
"total_bytes": 405,
"max_bytes": 405
}, {
"kind": 22706,
"count": 1,
"total_bytes": 3192,
"max_bytes": 3192
}, {
"kind": 22236,
"count": 1,
"total_bytes": 737,
"max_bytes": 737
}, {
"kind": 31339,
"count": 1,
"total_bytes": 1470,
"max_bytes": 1470
}, {
"kind": 22651,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 38500,
"count": 1,
"total_bytes": 713,
"max_bytes": 713
}, {
"kind": 10000,
"count": 1,
"total_bytes": 1311,
"max_bytes": 1311
}, {
"kind": 31926,
"count": 1,
"total_bytes": 1038,
"max_bytes": 1038
}, {
"kind": 22757,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 39015,
"count": 1,
"total_bytes": 696,
"max_bytes": 696
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 22234,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30815,
"count": 1,
"total_bytes": 37534,
"max_bytes": 37534
}, {
"kind": 1078,
"count": 1,
"total_bytes": 13939,
"max_bytes": 13939
}, {
"kind": 39050,
"count": 1,
"total_bytes": 1843,
"max_bytes": 1843
}, {
"kind": 22281,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30166,
"count": 1,
"total_bytes": 808,
"max_bytes": 808
}, {
"kind": 22772,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22356,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30554,
"count": 1,
"total_bytes": 609,
"max_bytes": 609
}, {
"kind": 38422,
"count": 1,
"total_bytes": 20025,
"max_bytes": 20025
}, {
"kind": 38000,
"count": 1,
"total_bytes": 562,
"max_bytes": 562
}, {
"kind": 22069,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 1050,
"count": 1,
"total_bytes": 437,
"max_bytes": 437
}, {
"kind": 33051,
"count": 1,
"total_bytes": 522,
"max_bytes": 522
}, {
"kind": 39036,
"count": 1,
"total_bytes": 1138,
"max_bytes": 1138
}, {
"kind": 23194,
"count": 1,
"total_bytes": 510,
"max_bytes": 510
}, {
"kind": 30081,
"count": 1,
"total_bytes": 1370,
"max_bytes": 1370
}, {
"kind": 22299,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22556,
"count": 1,
"total_bytes": 427,
"max_bytes": 427
}, {
"kind": 22302,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 39019,
"count": 1,
"total_bytes": 961,
"max_bytes": 961
}, {
"kind": 30000,
"count": 1,
"total_bytes": 823,
"max_bytes": 823
}, {
"kind": 38383,
"count": 1,
"total_bytes": 657,
"max_bytes": 657
}, {
"kind": 30087,
"count": 1,
"total_bytes": 7325,
"max_bytes": 7325
}, {
"kind": 30090,
"count": 1,
"total_bytes": 1515,
"max_bytes": 1515
}]
}
@@ -0,0 +1,281 @@
{
"duration_seconds": 0,
"total_events": 0,
"total_bytes": 0,
"max_event_bytes": 0,
"max_event_kind": 0,
"unique_pubkeys": 0,
"relay_count": 27,
"relays": [{
"url": "wss://relay.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.ditto.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.wine",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.oxtr.dev",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://spatia-arcana.com/inbox",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://eden.nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.band",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://theforest.nostr1.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://wot.utxo.one",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://premium.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.mom",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.mostr.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://offchain.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.divine.video",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr-pub.wellorder.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://devrelay.azzamo.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.noderunners.network",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr-pub.semisol.dev",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.shosho.live",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrcheck.me",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.info",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.momostr.pink",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://bitsat.molonlabe.holdings",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}],
"kinds": []
}
@@ -0,0 +1,161 @@
{
"duration_seconds": 0,
"total_events": 0,
"total_bytes": 0,
"max_event_bytes": 0,
"max_event_kind": 0,
"unique_pubkeys": 0,
"relay_count": 15,
"relays": [{
"url": "wss://relay.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.ditto.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.oxtr.dev",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://pyramid.fiatjaf.com/inbox",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.mostr.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.divine.video",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.wine",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://theforest.nostr1.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.shosho.live",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.band",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostr.info",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}],
"kinds": []
}
@@ -0,0 +1,121 @@
{
"duration_seconds": 0,
"total_events": 0,
"total_bytes": 0,
"max_event_bytes": 0,
"max_event_kind": 0,
"unique_pubkeys": 0,
"relay_count": 11,
"relays": [{
"url": "wss://relay.primal.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nos.lol",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.ditto.pub",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.bitcoiner.social",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://pyramid.fiatjaf.com/inbox",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.divine.video",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://eden.nostr.land",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}, {
"url": "wss://relay.shosho.live",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false,
"raw_responses": []
}],
"kinds": []
}
@@ -0,0 +1,834 @@
{
"duration_seconds": 114,
"total_events": 4719,
"total_bytes": 6608416,
"unique_pubkeys": 1333,
"relay_count": 15,
"relays": [{
"url": "wss://laantungir.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false
}, {
"url": "wss://nos.lol",
"events": 804,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 3,
"connected": true
}, {
"url": "wss://nostr-pub.wellorder.net",
"events": 83,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.mom",
"events": 175,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.oxtr.dev",
"events": 108,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://offchain.pub",
"events": 627,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://premium.primal.net",
"events": 1,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.damus.io",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false
}, {
"url": "wss://relay.ditto.pub",
"events": 721,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.divine.video",
"events": 3,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.momostr.pink",
"events": 86,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.mostr.pub",
"events": 836,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.nostrplebs.com",
"events": 4,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.primal.net",
"events": 1239,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://theforest.nostr1.com",
"events": 32,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}],
"kinds": [{
"kind": 21059,
"count": 675,
"total_bytes": 2192938,
"max_bytes": 5314
}, {
"kind": 30382,
"count": 563,
"total_bytes": 255591,
"max_bytes": 498
}, {
"kind": 5,
"count": 341,
"total_bytes": 153667,
"max_bytes": 1404
}, {
"kind": 22639,
"count": 231,
"total_bytes": 178011,
"max_bytes": 1230
}, {
"kind": 22551,
"count": 182,
"total_bytes": 78260,
"max_bytes": 430
}, {
"kind": 20001,
"count": 143,
"total_bytes": 70427,
"max_bytes": 1206
}, {
"kind": 30078,
"count": 141,
"total_bytes": 1361122,
"max_bytes": 44169
}, {
"kind": 0,
"count": 135,
"total_bytes": 119793,
"max_bytes": 3483
}, {
"kind": 22734,
"count": 122,
"total_bytes": 52094,
"max_bytes": 427
}, {
"kind": 24747,
"count": 101,
"total_bytes": 59352,
"max_bytes": 594
}, {
"kind": 1,
"count": 90,
"total_bytes": 92140,
"max_bytes": 4709
}, {
"kind": 22668,
"count": 88,
"total_bytes": 37752,
"max_bytes": 429
}, {
"kind": 22850,
"count": 83,
"total_bytes": 35690,
"max_bytes": 430
}, {
"kind": 22870,
"count": 76,
"total_bytes": 77061,
"max_bytes": 2202
}, {
"kind": 22951,
"count": 75,
"total_bytes": 77282,
"max_bytes": 2204
}, {
"kind": 10002,
"count": 75,
"total_bytes": 202196,
"max_bytes": 42385
}, {
"kind": 22912,
"count": 74,
"total_bytes": 31672,
"max_bytes": 428
}, {
"kind": 38501,
"count": 65,
"total_bytes": 48152,
"max_bytes": 744
}, {
"kind": 24133,
"count": 63,
"total_bytes": 72262,
"max_bytes": 2898
}, {
"kind": 6,
"count": 50,
"total_bytes": 40985,
"max_bytes": 3346
}, {
"kind": 37195,
"count": 48,
"total_bytes": 36225,
"max_bytes": 771
}, {
"kind": 21050,
"count": 48,
"total_bytes": 46800,
"max_bytes": 2558
}, {
"kind": 4515,
"count": 46,
"total_bytes": 81638,
"max_bytes": 6028
}, {
"kind": 25555,
"count": 45,
"total_bytes": 209551,
"max_bytes": 16603
}, {
"kind": 7,
"count": 40,
"total_bytes": 22486,
"max_bytes": 805
}, {
"kind": 22917,
"count": 39,
"total_bytes": 35435,
"max_bytes": 1166
}, {
"kind": 22699,
"count": 39,
"total_bytes": 26723,
"max_bytes": 1166
}, {
"kind": 22690,
"count": 39,
"total_bytes": 16770,
"max_bytes": 430
}, {
"kind": 22918,
"count": 38,
"total_bytes": 36470,
"max_bytes": 1507
}, {
"kind": 22832,
"count": 37,
"total_bytes": 16128,
"max_bytes": 446
}, {
"kind": 25050,
"count": 35,
"total_bytes": 15507,
"max_bytes": 465
}, {
"kind": 30383,
"count": 35,
"total_bytes": 18766,
"max_bytes": 541
}, {
"kind": 22713,
"count": 34,
"total_bytes": 14570,
"max_bytes": 430
}, {
"kind": 22698,
"count": 34,
"total_bytes": 31371,
"max_bytes": 1674
}, {
"kind": 22601,
"count": 28,
"total_bytes": 11984,
"max_bytes": 428
}, {
"kind": 22655,
"count": 28,
"total_bytes": 11984,
"max_bytes": 428
}, {
"kind": 1059,
"count": 23,
"total_bytes": 46912,
"max_bytes": 5285
}, {
"kind": 22751,
"count": 21,
"total_bytes": 8946,
"max_bytes": 426
}, {
"kind": 22676,
"count": 21,
"total_bytes": 8967,
"max_bytes": 427
}, {
"kind": 29333,
"count": 20,
"total_bytes": 9840,
"max_bytes": 492
}, {
"kind": 5300,
"count": 20,
"total_bytes": 11959,
"max_bytes": 630
}, {
"kind": 22743,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22603,
"count": 19,
"total_bytes": 8094,
"max_bytes": 426
}, {
"kind": 22693,
"count": 19,
"total_bytes": 8113,
"max_bytes": 427
}, {
"kind": 22774,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22808,
"count": 19,
"total_bytes": 8151,
"max_bytes": 429
}, {
"kind": 22575,
"count": 19,
"total_bytes": 8075,
"max_bytes": 425
}, {
"kind": 23003,
"count": 19,
"total_bytes": 8132,
"max_bytes": 428
}, {
"kind": 22949,
"count": 19,
"total_bytes": 8151,
"max_bytes": 429
}, {
"kind": 23334,
"count": 19,
"total_bytes": 8189,
"max_bytes": 431
}, {
"kind": 24242,
"count": 17,
"total_bytes": 9843,
"max_bytes": 579
}, {
"kind": 22816,
"count": 16,
"total_bytes": 6864,
"max_bytes": 429
}, {
"kind": 20787,
"count": 16,
"total_bytes": 25100,
"max_bytes": 2478
}, {
"kind": 13194,
"count": 15,
"total_bytes": 7574,
"max_bytes": 514
}, {
"kind": 38502,
"count": 14,
"total_bytes": 5390,
"max_bytes": 385
}, {
"kind": 22236,
"count": 14,
"total_bytes": 11214,
"max_bytes": 814
}, {
"kind": 22613,
"count": 13,
"total_bytes": 8977,
"max_bytes": 694
}, {
"kind": 30088,
"count": 12,
"total_bytes": 22340,
"max_bytes": 1871
}, {
"kind": 22484,
"count": 12,
"total_bytes": 10948,
"max_bytes": 1165
}, {
"kind": 30091,
"count": 12,
"total_bytes": 7385,
"max_bytes": 629
}, {
"kind": 20004,
"count": 11,
"total_bytes": 5643,
"max_bytes": 549
}, {
"kind": 31990,
"count": 11,
"total_bytes": 8711,
"max_bytes": 1465
}, {
"kind": 445,
"count": 10,
"total_bytes": 17972,
"max_bytes": 4126
}, {
"kind": 22795,
"count": 9,
"total_bytes": 3861,
"max_bytes": 429
}, {
"kind": 22507,
"count": 9,
"total_bytes": 8480,
"max_bytes": 1165
}, {
"kind": 22922,
"count": 9,
"total_bytes": 3870,
"max_bytes": 430
}, {
"kind": 24421,
"count": 9,
"total_bytes": 4644,
"max_bytes": 516
}, {
"kind": 22830,
"count": 9,
"total_bytes": 7317,
"max_bytes": 1168
}, {
"kind": 20000,
"count": 9,
"total_bytes": 3854,
"max_bytes": 484
}, {
"kind": 22955,
"count": 9,
"total_bytes": 3852,
"max_bytes": 428
}, {
"kind": 22793,
"count": 9,
"total_bytes": 3843,
"max_bytes": 427
}, {
"kind": 30315,
"count": 8,
"total_bytes": 4604,
"max_bytes": 926
}, {
"kind": 20387,
"count": 8,
"total_bytes": 4112,
"max_bytes": 514
}, {
"kind": 7368,
"count": 8,
"total_bytes": 8309,
"max_bytes": 1041
}, {
"kind": 4004,
"count": 8,
"total_bytes": 9352,
"max_bytes": 1169
}, {
"kind": 443,
"count": 8,
"total_bytes": 8928,
"max_bytes": 1116
}, {
"kind": 11998,
"count": 7,
"total_bytes": 3255,
"max_bytes": 465
}, {
"kind": 10100,
"count": 7,
"total_bytes": 11851,
"max_bytes": 3825
}, {
"kind": 22456,
"count": 7,
"total_bytes": 3290,
"max_bytes": 470
}, {
"kind": 30089,
"count": 7,
"total_bytes": 157322,
"max_bytes": 44372
}, {
"kind": 22737,
"count": 7,
"total_bytes": 4669,
"max_bytes": 707
}, {
"kind": 23337,
"count": 7,
"total_bytes": 3557,
"max_bytes": 509
}, {
"kind": 30515,
"count": 7,
"total_bytes": 18032,
"max_bytes": 2576
}, {
"kind": 30311,
"count": 6,
"total_bytes": 5818,
"max_bytes": 1197
}, {
"kind": 1985,
"count": 6,
"total_bytes": 2860,
"max_bytes": 482
}, {
"kind": 22859,
"count": 5,
"total_bytes": 8606,
"max_bytes": 2195
}, {
"kind": 4,
"count": 5,
"total_bytes": 3222,
"max_bytes": 722
}, {
"kind": 10011,
"count": 5,
"total_bytes": 2027,
"max_bytes": 431
}, {
"kind": 22764,
"count": 4,
"total_bytes": 1712,
"max_bytes": 428
}, {
"kind": 22777,
"count": 4,
"total_bytes": 2790,
"max_bytes": 698
}, {
"kind": 4244,
"count": 4,
"total_bytes": 2256,
"max_bytes": 564
}, {
"kind": 30023,
"count": 4,
"total_bytes": 22789,
"max_bytes": 6817
}, {
"kind": 9735,
"count": 4,
"total_bytes": 7033,
"max_bytes": 1805
}, {
"kind": 20333,
"count": 4,
"total_bytes": 3188,
"max_bytes": 798
}, {
"kind": 3,
"count": 3,
"total_bytes": 26232,
"max_bytes": 14166
}, {
"kind": 38503,
"count": 3,
"total_bytes": 1655,
"max_bytes": 552
}, {
"kind": 10050,
"count": 3,
"total_bytes": 1411,
"max_bytes": 511
}, {
"kind": 21617,
"count": 3,
"total_bytes": 1485,
"max_bytes": 495
}, {
"kind": 1984,
"count": 3,
"total_bytes": 1986,
"max_bytes": 662
}, {
"kind": 3927,
"count": 3,
"total_bytes": 1599,
"max_bytes": 533
}, {
"kind": 3434,
"count": 3,
"total_bytes": 1907,
"max_bytes": 665
}, {
"kind": 22514,
"count": 2,
"total_bytes": 856,
"max_bytes": 428
}, {
"kind": 33334,
"count": 2,
"total_bytes": 1662,
"max_bytes": 831
}, {
"kind": 38500,
"count": 2,
"total_bytes": 1424,
"max_bytes": 713
}, {
"kind": 22526,
"count": 2,
"total_bytes": 860,
"max_bytes": 430
}, {
"kind": 30380,
"count": 2,
"total_bytes": 908,
"max_bytes": 454
}, {
"kind": 23194,
"count": 2,
"total_bytes": 1020,
"max_bytes": 510
}, {
"kind": 23042,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 38385,
"count": 2,
"total_bytes": 2692,
"max_bytes": 1478
}, {
"kind": 30443,
"count": 2,
"total_bytes": 2369,
"max_bytes": 1219
}, {
"kind": 22747,
"count": 2,
"total_bytes": 2064,
"max_bytes": 1288
}, {
"kind": 4245,
"count": 2,
"total_bytes": 888,
"max_bytes": 444
}, {
"kind": 1111,
"count": 2,
"total_bytes": 1936,
"max_bytes": 981
}, {
"kind": 38383,
"count": 2,
"total_bytes": 1439,
"max_bytes": 720
}, {
"kind": 4243,
"count": 2,
"total_bytes": 1128,
"max_bytes": 564
}, {
"kind": 38225,
"count": 1,
"total_bytes": 978,
"max_bytes": 978
}, {
"kind": 30618,
"count": 1,
"total_bytes": 733,
"max_bytes": 733
}, {
"kind": 31510,
"count": 1,
"total_bytes": 981,
"max_bytes": 981
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 1077,
"count": 1,
"total_bytes": 607,
"max_bytes": 607
}, {
"kind": 20201,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 20200,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 1078,
"count": 1,
"total_bytes": 14173,
"max_bytes": 14173
}, {
"kind": 6300,
"count": 1,
"total_bytes": 3698,
"max_bytes": 3698
}, {
"kind": 11316,
"count": 1,
"total_bytes": 791,
"max_bytes": 791
}, {
"kind": 11317,
"count": 1,
"total_bytes": 2873,
"max_bytes": 2873
}, {
"kind": 17,
"count": 1,
"total_bytes": 610,
"max_bytes": 610
}, {
"kind": 23059,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22806,
"count": 1,
"total_bytes": 1520,
"max_bytes": 1520
}, {
"kind": 22120,
"count": 1,
"total_bytes": 1127,
"max_bytes": 1127
}, {
"kind": 13196,
"count": 1,
"total_bytes": 656,
"max_bytes": 656
}, {
"kind": 24913,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 12222,
"count": 1,
"total_bytes": 579,
"max_bytes": 579
}, {
"kind": 1040,
"count": 1,
"total_bytes": 1048,
"max_bytes": 1048
}, {
"kind": 22119,
"count": 1,
"total_bytes": 845,
"max_bytes": 845
}, {
"kind": 10312,
"count": 1,
"total_bytes": 472,
"max_bytes": 472
}, {
"kind": 3079,
"count": 1,
"total_bytes": 909,
"max_bytes": 909
}, {
"kind": 30000,
"count": 1,
"total_bytes": 594,
"max_bytes": 594
}]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,704 @@
{
"duration_seconds": 32,
"total_events": 1485,
"total_bytes": 1812166,
"unique_pubkeys": 511,
"relay_count": 15,
"relays": [{
"url": "wss://laantungir.net",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": false
}, {
"url": "wss://nos.lol",
"events": 247,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr-pub.wellorder.net",
"events": 37,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.mom",
"events": 38,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://nostr.oxtr.dev",
"events": 36,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://offchain.pub",
"events": 113,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://premium.primal.net",
"events": 1,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.damus.io",
"events": 194,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.ditto.pub",
"events": 277,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 1,
"connected": true
}, {
"url": "wss://relay.divine.video",
"events": 2,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.momostr.pink",
"events": 27,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.mostr.pub",
"events": 148,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.nostrplebs.com",
"events": 0,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://relay.primal.net",
"events": 360,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}, {
"url": "wss://theforest.nostr1.com",
"events": 5,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 0,
"connected": true
}],
"kinds": [{
"kind": 21059,
"count": 184,
"total_bytes": 623532,
"max_bytes": 5314
}, {
"kind": 22591,
"count": 139,
"total_bytes": 107560,
"max_bytes": 1223
}, {
"kind": 30382,
"count": 119,
"total_bytes": 53663,
"max_bytes": 458
}, {
"kind": 30383,
"count": 119,
"total_bytes": 53605,
"max_bytes": 536
}, {
"kind": 5,
"count": 91,
"total_bytes": 39950,
"max_bytes": 624
}, {
"kind": 30078,
"count": 54,
"total_bytes": 179695,
"max_bytes": 14343
}, {
"kind": 20001,
"count": 49,
"total_bytes": 35635,
"max_bytes": 1871
}, {
"kind": 0,
"count": 48,
"total_bytes": 39906,
"max_bytes": 1468
}, {
"kind": 22668,
"count": 48,
"total_bytes": 20592,
"max_bytes": 429
}, {
"kind": 1,
"count": 45,
"total_bytes": 38547,
"max_bytes": 2413
}, {
"kind": 22734,
"count": 26,
"total_bytes": 11102,
"max_bytes": 427
}, {
"kind": 4515,
"count": 26,
"total_bytes": 38218,
"max_bytes": 2956
}, {
"kind": 10002,
"count": 25,
"total_bytes": 11707,
"max_bytes": 565
}, {
"kind": 22850,
"count": 24,
"total_bytes": 10320,
"max_bytes": 430
}, {
"kind": 7,
"count": 24,
"total_bytes": 13528,
"max_bytes": 780
}, {
"kind": 22551,
"count": 24,
"total_bytes": 10320,
"max_bytes": 430
}, {
"kind": 31991,
"count": 20,
"total_bytes": 9220,
"max_bytes": 461
}, {
"kind": 38501,
"count": 18,
"total_bytes": 13341,
"max_bytes": 744
}, {
"kind": 22663,
"count": 17,
"total_bytes": 7276,
"max_bytes": 428
}, {
"kind": 25555,
"count": 15,
"total_bytes": 90493,
"max_bytes": 16887
}, {
"kind": 22912,
"count": 13,
"total_bytes": 5564,
"max_bytes": 428
}, {
"kind": 1059,
"count": 13,
"total_bytes": 23775,
"max_bytes": 1883
}, {
"kind": 22832,
"count": 12,
"total_bytes": 5233,
"max_bytes": 446
}, {
"kind": 22690,
"count": 11,
"total_bytes": 4730,
"max_bytes": 430
}, {
"kind": 22545,
"count": 11,
"total_bytes": 4697,
"max_bytes": 427
}, {
"kind": 25050,
"count": 10,
"total_bytes": 4394,
"max_bytes": 465
}, {
"kind": 37195,
"count": 10,
"total_bytes": 7494,
"max_bytes": 771
}, {
"kind": 22601,
"count": 10,
"total_bytes": 4280,
"max_bytes": 428
}, {
"kind": 22795,
"count": 10,
"total_bytes": 11639,
"max_bytes": 1487
}, {
"kind": 22891,
"count": 9,
"total_bytes": 13331,
"max_bytes": 1490
}, {
"kind": 22655,
"count": 9,
"total_bytes": 3852,
"max_bytes": 428
}, {
"kind": 22911,
"count": 9,
"total_bytes": 13328,
"max_bytes": 1487
}, {
"kind": 24133,
"count": 9,
"total_bytes": 8250,
"max_bytes": 1534
}, {
"kind": 13194,
"count": 8,
"total_bytes": 4112,
"max_bytes": 514
}, {
"kind": 22647,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 22695,
"count": 7,
"total_bytes": 10339,
"max_bytes": 1481
}, {
"kind": 22653,
"count": 7,
"total_bytes": 10368,
"max_bytes": 1487
}, {
"kind": 22455,
"count": 7,
"total_bytes": 10339,
"max_bytes": 1485
}, {
"kind": 22543,
"count": 7,
"total_bytes": 10354,
"max_bytes": 1487
}, {
"kind": 30315,
"count": 7,
"total_bytes": 2714,
"max_bytes": 398
}, {
"kind": 22676,
"count": 6,
"total_bytes": 2562,
"max_bytes": 427
}, {
"kind": 22575,
"count": 6,
"total_bytes": 2550,
"max_bytes": 425
}, {
"kind": 22689,
"count": 6,
"total_bytes": 8851,
"max_bytes": 1484
}, {
"kind": 22913,
"count": 6,
"total_bytes": 2586,
"max_bytes": 431
}, {
"kind": 22774,
"count": 6,
"total_bytes": 2568,
"max_bytes": 428
}, {
"kind": 24242,
"count": 6,
"total_bytes": 3474,
"max_bytes": 579
}, {
"kind": 22808,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22842,
"count": 6,
"total_bytes": 8891,
"max_bytes": 1488
}, {
"kind": 22603,
"count": 5,
"total_bytes": 2130,
"max_bytes": 426
}, {
"kind": 6,
"count": 5,
"total_bytes": 10814,
"max_bytes": 7298
}, {
"kind": 31990,
"count": 5,
"total_bytes": 4305,
"max_bytes": 1321
}, {
"kind": 22384,
"count": 5,
"total_bytes": 2130,
"max_bytes": 426
}, {
"kind": 22699,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 22613,
"count": 5,
"total_bytes": 3443,
"max_bytes": 691
}, {
"kind": 38502,
"count": 5,
"total_bytes": 1925,
"max_bytes": 385
}, {
"kind": 23003,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 22743,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 29333,
"count": 5,
"total_bytes": 2460,
"max_bytes": 492
}, {
"kind": 20004,
"count": 4,
"total_bytes": 1800,
"max_bytes": 549
}, {
"kind": 4,
"count": 4,
"total_bytes": 3072,
"max_bytes": 826
}, {
"kind": 20387,
"count": 4,
"total_bytes": 2056,
"max_bytes": 514
}, {
"kind": 22816,
"count": 4,
"total_bytes": 1716,
"max_bytes": 429
}, {
"kind": 20000,
"count": 4,
"total_bytes": 1872,
"max_bytes": 628
}, {
"kind": 22934,
"count": 3,
"total_bytes": 5535,
"max_bytes": 1846
}, {
"kind": 1985,
"count": 2,
"total_bytes": 948,
"max_bytes": 474
}, {
"kind": 31234,
"count": 2,
"total_bytes": 89092,
"max_bytes": 44546
}, {
"kind": 10777,
"count": 2,
"total_bytes": 976,
"max_bytes": 488
}, {
"kind": 4004,
"count": 2,
"total_bytes": 2338,
"max_bytes": 1169
}, {
"kind": 9735,
"count": 2,
"total_bytes": 3926,
"max_bytes": 2135
}, {
"kind": 1000,
"count": 2,
"total_bytes": 3415,
"max_bytes": 1712
}, {
"kind": 25195,
"count": 2,
"total_bytes": 1356,
"max_bytes": 678
}, {
"kind": 30311,
"count": 2,
"total_bytes": 2210,
"max_bytes": 1198
}, {
"kind": 25196,
"count": 2,
"total_bytes": 3576,
"max_bytes": 2214
}, {
"kind": 1040,
"count": 2,
"total_bytes": 1920,
"max_bytes": 960
}, {
"kind": 38383,
"count": 2,
"total_bytes": 1532,
"max_bytes": 815
}, {
"kind": 30515,
"count": 2,
"total_bytes": 5152,
"max_bytes": 2576
}, {
"kind": 30091,
"count": 2,
"total_bytes": 1221,
"max_bytes": 611
}, {
"kind": 30088,
"count": 2,
"total_bytes": 3717,
"max_bytes": 1859
}, {
"kind": 21617,
"count": 1,
"total_bytes": 494,
"max_bytes": 494
}, {
"kind": 30380,
"count": 1,
"total_bytes": 454,
"max_bytes": 454
}, {
"kind": 22611,
"count": 1,
"total_bytes": 426,
"max_bytes": 426
}, {
"kind": 30079,
"count": 1,
"total_bytes": 875,
"max_bytes": 875
}, {
"kind": 22767,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 38500,
"count": 1,
"total_bytes": 711,
"max_bytes": 711
}, {
"kind": 30312,
"count": 1,
"total_bytes": 461,
"max_bytes": 461
}, {
"kind": 7368,
"count": 1,
"total_bytes": 1039,
"max_bytes": 1039
}, {
"kind": 22967,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 39101,
"count": 1,
"total_bytes": 533,
"max_bytes": 533
}, {
"kind": 445,
"count": 1,
"total_bytes": 4130,
"max_bytes": 4130
}, {
"kind": 10000,
"count": 1,
"total_bytes": 1722,
"max_bytes": 1722
}, {
"kind": 30617,
"count": 1,
"total_bytes": 1089,
"max_bytes": 1089
}, {
"kind": 22526,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 20787,
"count": 1,
"total_bytes": 926,
"max_bytes": 926
}, {
"kind": 443,
"count": 1,
"total_bytes": 800,
"max_bytes": 800
}, {
"kind": 16,
"count": 1,
"total_bytes": 780,
"max_bytes": 780
}, {
"kind": 10011,
"count": 1,
"total_bytes": 397,
"max_bytes": 397
}, {
"kind": 22679,
"count": 1,
"total_bytes": 693,
"max_bytes": 693
}, {
"kind": 23042,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22514,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 30023,
"count": 1,
"total_bytes": 8164,
"max_bytes": 8164
}, {
"kind": 5300,
"count": 1,
"total_bytes": 630,
"max_bytes": 630
}, {
"kind": 22605,
"count": 1,
"total_bytes": 692,
"max_bytes": 692
}, {
"kind": 34237,
"count": 1,
"total_bytes": 418,
"max_bytes": 418
}, {
"kind": 30618,
"count": 1,
"total_bytes": 733,
"max_bytes": 733
}, {
"kind": 38421,
"count": 1,
"total_bytes": 1069,
"max_bytes": 1069
}, {
"kind": 10100,
"count": 1,
"total_bytes": 1036,
"max_bytes": 1036
}, {
"kind": 30080,
"count": 1,
"total_bytes": 925,
"max_bytes": 925
}, {
"kind": 38385,
"count": 1,
"total_bytes": 1214,
"max_bytes": 1214
}, {
"kind": 38225,
"count": 1,
"total_bytes": 1012,
"max_bytes": 1012
}, {
"kind": 11998,
"count": 1,
"total_bytes": 465,
"max_bytes": 465
}, {
"kind": 22236,
"count": 1,
"total_bytes": 747,
"max_bytes": 747
}, {
"kind": 22823,
"count": 1,
"total_bytes": 1852,
"max_bytes": 1852
}]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,998 @@
{
"duration_seconds": 503,
"total_events": 7884,
"total_bytes": 12205231,
"unique_pubkeys": 2301,
"relay_count": 1,
"relays": [{
"url": "wss://relay.primal.net",
"events": 7884,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 10,
"connected": true
}],
"kinds": [{
"kind": 1059,
"count": 871,
"total_bytes": 1657832,
"max_bytes": 55121
}, {
"kind": 21059,
"count": 740,
"total_bytes": 2308644,
"max_bytes": 22354
}, {
"kind": 30078,
"count": 622,
"total_bytes": 2651879,
"max_bytes": 55090
}, {
"kind": 30383,
"count": 520,
"total_bytes": 278610,
"max_bytes": 548
}, {
"kind": 20001,
"count": 386,
"total_bytes": 403111,
"max_bytes": 2026
}, {
"kind": 22734,
"count": 357,
"total_bytes": 152451,
"max_bytes": 428
}, {
"kind": 1,
"count": 303,
"total_bytes": 303990,
"max_bytes": 4648
}, {
"kind": 30382,
"count": 288,
"total_bytes": 137207,
"max_bytes": 484
}, {
"kind": 0,
"count": 189,
"total_bytes": 140594,
"max_bytes": 2012
}, {
"kind": 29333,
"count": 165,
"total_bytes": 81180,
"max_bytes": 492
}, {
"kind": 10002,
"count": 158,
"total_bytes": 571188,
"max_bytes": 42385
}, {
"kind": 22236,
"count": 158,
"total_bytes": 120802,
"max_bytes": 801
}, {
"kind": 5,
"count": 154,
"total_bytes": 73847,
"max_bytes": 664
}, {
"kind": 4515,
"count": 154,
"total_bytes": 463562,
"max_bytes": 38797
}, {
"kind": 22743,
"count": 134,
"total_bytes": 57352,
"max_bytes": 428
}, {
"kind": 7,
"count": 113,
"total_bytes": 62452,
"max_bytes": 727
}, {
"kind": 24133,
"count": 102,
"total_bytes": 132440,
"max_bytes": 7334
}, {
"kind": 22684,
"count": 99,
"total_bytes": 42669,
"max_bytes": 431
}, {
"kind": 22850,
"count": 94,
"total_bytes": 40420,
"max_bytes": 430
}, {
"kind": 22838,
"count": 88,
"total_bytes": 37752,
"max_bytes": 429
}, {
"kind": 22774,
"count": 84,
"total_bytes": 35952,
"max_bytes": 428
}, {
"kind": 23003,
"count": 84,
"total_bytes": 35952,
"max_bytes": 428
}, {
"kind": 22876,
"count": 83,
"total_bytes": 35358,
"max_bytes": 426
}, {
"kind": 22611,
"count": 75,
"total_bytes": 32175,
"max_bytes": 429
}, {
"kind": 25555,
"count": 69,
"total_bytes": 281319,
"max_bytes": 16987
}, {
"kind": 4,
"count": 66,
"total_bytes": 72028,
"max_bytes": 17018
}, {
"kind": 22613,
"count": 63,
"total_bytes": 43489,
"max_bytes": 692
}, {
"kind": 25050,
"count": 61,
"total_bytes": 24461,
"max_bytes": 401
}, {
"kind": 11998,
"count": 61,
"total_bytes": 28269,
"max_bytes": 465
}, {
"kind": 443,
"count": 49,
"total_bytes": 68056,
"max_bytes": 22480
}, {
"kind": 30301,
"count": 47,
"total_bytes": 53284,
"max_bytes": 1912
}, {
"kind": 3927,
"count": 47,
"total_bytes": 25051,
"max_bytes": 533
}, {
"kind": 20387,
"count": 44,
"total_bytes": 22616,
"max_bytes": 514
}, {
"kind": 22761,
"count": 44,
"total_bytes": 18832,
"max_bytes": 428
}, {
"kind": 13194,
"count": 43,
"total_bytes": 22043,
"max_bytes": 514
}, {
"kind": 20004,
"count": 42,
"total_bytes": 22266,
"max_bytes": 549
}, {
"kind": 5300,
"count": 42,
"total_bytes": 25437,
"max_bytes": 637
}, {
"kind": 22546,
"count": 39,
"total_bytes": 16614,
"max_bytes": 426
}, {
"kind": 22714,
"count": 37,
"total_bytes": 15836,
"max_bytes": 428
}, {
"kind": 30166,
"count": 35,
"total_bytes": 17904,
"max_bytes": 589
}, {
"kind": 9735,
"count": 35,
"total_bytes": 70344,
"max_bytes": 3538
}, {
"kind": 23334,
"count": 35,
"total_bytes": 15084,
"max_bytes": 432
}, {
"kind": 1985,
"count": 34,
"total_bytes": 16232,
"max_bytes": 483
}, {
"kind": 22816,
"count": 33,
"total_bytes": 14157,
"max_bytes": 429
}, {
"kind": 21050,
"count": 33,
"total_bytes": 30862,
"max_bytes": 1874
}, {
"kind": 30315,
"count": 32,
"total_bytes": 21166,
"max_bytes": 925
}, {
"kind": 445,
"count": 32,
"total_bytes": 120956,
"max_bytes": 9346
}, {
"kind": 22698,
"count": 32,
"total_bytes": 13664,
"max_bytes": 427
}, {
"kind": 4004,
"count": 32,
"total_bytes": 37408,
"max_bytes": 1169
}, {
"kind": 38501,
"count": 31,
"total_bytes": 23034,
"max_bytes": 744
}, {
"kind": 31990,
"count": 25,
"total_bytes": 9675,
"max_bytes": 387
}, {
"kind": 10100,
"count": 24,
"total_bytes": 27687,
"max_bytes": 1589
}, {
"kind": 20333,
"count": 24,
"total_bytes": 19120,
"max_bytes": 798
}, {
"kind": 3,
"count": 23,
"total_bytes": 328284,
"max_bytes": 56750
}, {
"kind": 24913,
"count": 22,
"total_bytes": 9700,
"max_bytes": 493
}, {
"kind": 30311,
"count": 21,
"total_bytes": 22061,
"max_bytes": 1197
}, {
"kind": 6,
"count": 21,
"total_bytes": 49793,
"max_bytes": 12925
}, {
"kind": 10050,
"count": 19,
"total_bytes": 8172,
"max_bytes": 450
}, {
"kind": 22712,
"count": 18,
"total_bytes": 7704,
"max_bytes": 428
}, {
"kind": 22808,
"count": 17,
"total_bytes": 7293,
"max_bytes": 429
}, {
"kind": 10000,
"count": 16,
"total_bytes": 26659,
"max_bytes": 12630
}, {
"kind": 4244,
"count": 15,
"total_bytes": 8460,
"max_bytes": 564
}, {
"kind": 23025,
"count": 15,
"total_bytes": 6465,
"max_bytes": 431
}, {
"kind": 31234,
"count": 14,
"total_bytes": 24051,
"max_bytes": 2268
}, {
"kind": 38503,
"count": 13,
"total_bytes": 7174,
"max_bytes": 552
}, {
"kind": 22630,
"count": 12,
"total_bytes": 11762,
"max_bytes": 1654
}, {
"kind": 37195,
"count": 12,
"total_bytes": 8752,
"max_bytes": 934
}, {
"kind": 6300,
"count": 12,
"total_bytes": 77247,
"max_bytes": 17248
}, {
"kind": 10777,
"count": 12,
"total_bytes": 5861,
"max_bytes": 489
}, {
"kind": 30091,
"count": 11,
"total_bytes": 6763,
"max_bytes": 629
}, {
"kind": 20000,
"count": 11,
"total_bytes": 3806,
"max_bytes": 346
}, {
"kind": 30515,
"count": 11,
"total_bytes": 58344,
"max_bytes": 5304
}, {
"kind": 30088,
"count": 11,
"total_bytes": 20483,
"max_bytes": 1871
}, {
"kind": 22361,
"count": 10,
"total_bytes": 4439,
"max_bytes": 445
}, {
"kind": 30280,
"count": 9,
"total_bytes": 4617,
"max_bytes": 513
}, {
"kind": 22902,
"count": 9,
"total_bytes": 3861,
"max_bytes": 429
}, {
"kind": 22506,
"count": 8,
"total_bytes": 3416,
"max_bytes": 427
}, {
"kind": 7368,
"count": 8,
"total_bytes": 8304,
"max_bytes": 1040
}, {
"kind": 22645,
"count": 8,
"total_bytes": 3432,
"max_bytes": 429
}, {
"kind": 38500,
"count": 8,
"total_bytes": 5696,
"max_bytes": 713
}, {
"kind": 33334,
"count": 8,
"total_bytes": 6648,
"max_bytes": 831
}, {
"kind": 30380,
"count": 8,
"total_bytes": 3632,
"max_bytes": 454
}, {
"kind": 22688,
"count": 7,
"total_bytes": 2989,
"max_bytes": 427
}, {
"kind": 22656,
"count": 7,
"total_bytes": 3017,
"max_bytes": 431
}, {
"kind": 22316,
"count": 7,
"total_bytes": 3106,
"max_bytes": 444
}, {
"kind": 30089,
"count": 6,
"total_bytes": 123676,
"max_bytes": 44372
}, {
"kind": 22456,
"count": 6,
"total_bytes": 3140,
"max_bytes": 790
}, {
"kind": 22739,
"count": 6,
"total_bytes": 2580,
"max_bytes": 430
}, {
"kind": 22882,
"count": 6,
"total_bytes": 5292,
"max_bytes": 1449
}, {
"kind": 22862,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 1111,
"count": 6,
"total_bytes": 7227,
"max_bytes": 1698
}, {
"kind": 4245,
"count": 5,
"total_bytes": 2220,
"max_bytes": 444
}, {
"kind": 30512,
"count": 5,
"total_bytes": 7504,
"max_bytes": 1506
}, {
"kind": 22641,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22586,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 4243,
"count": 5,
"total_bytes": 2820,
"max_bytes": 564
}, {
"kind": 22835,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22620,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22298,
"count": 5,
"total_bytes": 2218,
"max_bytes": 444
}, {
"kind": 22852,
"count": 5,
"total_bytes": 2140,
"max_bytes": 428
}, {
"kind": 22726,
"count": 5,
"total_bytes": 2150,
"max_bytes": 430
}, {
"kind": 22362,
"count": 5,
"total_bytes": 2218,
"max_bytes": 444
}, {
"kind": 22772,
"count": 5,
"total_bytes": 4051,
"max_bytes": 1132
}, {
"kind": 22805,
"count": 4,
"total_bytes": 1712,
"max_bytes": 428
}, {
"kind": 9734,
"count": 4,
"total_bytes": 3756,
"max_bytes": 1033
}, {
"kind": 22200,
"count": 4,
"total_bytes": 1774,
"max_bytes": 444
}, {
"kind": 31510,
"count": 4,
"total_bytes": 3922,
"max_bytes": 982
}, {
"kind": 22570,
"count": 4,
"total_bytes": 1716,
"max_bytes": 429
}, {
"kind": 25195,
"count": 4,
"total_bytes": 2712,
"max_bytes": 678
}, {
"kind": 22752,
"count": 3,
"total_bytes": 4047,
"max_bytes": 3191
}, {
"kind": 31339,
"count": 3,
"total_bytes": 4410,
"max_bytes": 1470
}, {
"kind": 22949,
"count": 3,
"total_bytes": 1287,
"max_bytes": 429
}, {
"kind": 22358,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 22474,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22185,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22936,
"count": 3,
"total_bytes": 1290,
"max_bytes": 430
}, {
"kind": 25196,
"count": 3,
"total_bytes": 4938,
"max_bytes": 2214
}, {
"kind": 22439,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22824,
"count": 3,
"total_bytes": 1281,
"max_bytes": 427
}, {
"kind": 22270,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22263,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 22755,
"count": 3,
"total_bytes": 1284,
"max_bytes": 428
}, {
"kind": 10051,
"count": 3,
"total_bytes": 1317,
"max_bytes": 439
}, {
"kind": 34775,
"count": 3,
"total_bytes": 7599,
"max_bytes": 2561
}, {
"kind": 30300,
"count": 3,
"total_bytes": 5502,
"max_bytes": 1834
}, {
"kind": 22234,
"count": 3,
"total_bytes": 1330,
"max_bytes": 444
}, {
"kind": 7403,
"count": 2,
"total_bytes": 978,
"max_bytes": 489
}, {
"kind": 22626,
"count": 2,
"total_bytes": 1916,
"max_bytes": 1486
}, {
"kind": 30079,
"count": 2,
"total_bytes": 1750,
"max_bytes": 875
}, {
"kind": 15750,
"count": 2,
"total_bytes": 1242,
"max_bytes": 626
}, {
"kind": 23337,
"count": 2,
"total_bytes": 1027,
"max_bytes": 514
}, {
"kind": 30815,
"count": 2,
"total_bytes": 75068,
"max_bytes": 37534
}, {
"kind": 30213,
"count": 2,
"total_bytes": 1264,
"max_bytes": 632
}, {
"kind": 22716,
"count": 2,
"total_bytes": 854,
"max_bytes": 427
}, {
"kind": 22492,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30000,
"count": 2,
"total_bytes": 1646,
"max_bytes": 823
}, {
"kind": 22463,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22304,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 10003,
"count": 2,
"total_bytes": 20016,
"max_bytes": 15003
}, {
"kind": 30443,
"count": 2,
"total_bytes": 2572,
"max_bytes": 1312
}, {
"kind": 22233,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22275,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30087,
"count": 2,
"total_bytes": 11238,
"max_bytes": 7325
}, {
"kind": 20078,
"count": 2,
"total_bytes": 1458,
"max_bytes": 729
}, {
"kind": 22057,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22251,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22181,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22678,
"count": 2,
"total_bytes": 1388,
"max_bytes": 694
}, {
"kind": 22747,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 22410,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22334,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22249,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22258,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30090,
"count": 2,
"total_bytes": 2588,
"max_bytes": 1515
}, {
"kind": 10312,
"count": 1,
"total_bytes": 472,
"max_bytes": 472
}, {
"kind": 22853,
"count": 1,
"total_bytes": 1673,
"max_bytes": 1673
}, {
"kind": 22303,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22306,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 20200,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 22132,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 20201,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 22245,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22352,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22240,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 34236,
"count": 1,
"total_bytes": 931,
"max_bytes": 931
}, {
"kind": 17,
"count": 1,
"total_bytes": 610,
"max_bytes": 610
}, {
"kind": 22286,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 13196,
"count": 1,
"total_bytes": 656,
"max_bytes": 656
}, {
"kind": 20242,
"count": 1,
"total_bytes": 346,
"max_bytes": 346
}, {
"kind": 7376,
"count": 1,
"total_bytes": 670,
"max_bytes": 670
}, {
"kind": 38385,
"count": 1,
"total_bytes": 1350,
"max_bytes": 1350
}, {
"kind": 22355,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 23333,
"count": 1,
"total_bytes": 571,
"max_bytes": 571
}, {
"kind": 22737,
"count": 1,
"total_bytes": 556,
"max_bytes": 556
}, {
"kind": 22291,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30421,
"count": 1,
"total_bytes": 995,
"max_bytes": 995
}, {
"kind": 22860,
"count": 1,
"total_bytes": 553,
"max_bytes": 553
}, {
"kind": 2333,
"count": 1,
"total_bytes": 659,
"max_bytes": 659
}, {
"kind": 22219,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22382,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 23032,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 7375,
"count": 1,
"total_bytes": 1822,
"max_bytes": 1822
}, {
"kind": 22579,
"count": 1,
"total_bytes": 1670,
"max_bytes": 1670
}, {
"kind": 10007,
"count": 1,
"total_bytes": 715,
"max_bytes": 715
}, {
"kind": 10086,
"count": 1,
"total_bytes": 715,
"max_bytes": 715
}, {
"kind": 22729,
"count": 1,
"total_bytes": 3030,
"max_bytes": 3030
}, {
"kind": 22540,
"count": 1,
"total_bytes": 426,
"max_bytes": 426
}, {
"kind": 22671,
"count": 1,
"total_bytes": 3194,
"max_bytes": 3194
}, {
"kind": 30618,
"count": 1,
"total_bytes": 460,
"max_bytes": 460
}, {
"kind": 23118,
"count": 1,
"total_bytes": 430,
"max_bytes": 430
}, {
"kind": 38421,
"count": 1,
"total_bytes": 1069,
"max_bytes": 1069
}, {
"kind": 10377,
"count": 1,
"total_bytes": 664,
"max_bytes": 664
}, {
"kind": 11111,
"count": 1,
"total_bytes": 382,
"max_bytes": 382
}, {
"kind": 34550,
"count": 1,
"total_bytes": 867,
"max_bytes": 867
}, {
"kind": 22307,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 1078,
"count": 1,
"total_bytes": 13969,
"max_bytes": 13969
}]
}
@@ -0,0 +1,938 @@
{
"duration_seconds": 376,
"total_events": 5992,
"total_bytes": 8743262,
"unique_pubkeys": 1142,
"relay_count": 1,
"relays": [{
"url": "wss://relay.primal.net",
"events": 5992,
"eose": 0,
"closed": 0,
"notice": 0,
"errors": 0,
"disconnects": 3,
"connected": true
}],
"kinds": [{
"kind": 30078,
"count": 495,
"total_bytes": 2024957,
"max_bytes": 54029
}, {
"kind": 21059,
"count": 386,
"total_bytes": 1099128,
"max_bytes": 5314
}, {
"kind": 4515,
"count": 374,
"total_bytes": 781718,
"max_bytes": 14221
}, {
"kind": 22591,
"count": 371,
"total_bytes": 286839,
"max_bytes": 1228
}, {
"kind": 20001,
"count": 271,
"total_bytes": 315643,
"max_bytes": 2029
}, {
"kind": 22734,
"count": 264,
"total_bytes": 112728,
"max_bytes": 427
}, {
"kind": 30382,
"count": 255,
"total_bytes": 121375,
"max_bytes": 483
}, {
"kind": 30383,
"count": 237,
"total_bytes": 126938,
"max_bytes": 547
}, {
"kind": 1,
"count": 212,
"total_bytes": 211399,
"max_bytes": 2011
}, {
"kind": 5,
"count": 183,
"total_bytes": 91915,
"max_bytes": 664
}, {
"kind": 22236,
"count": 146,
"total_bytes": 104252,
"max_bytes": 810
}, {
"kind": 0,
"count": 145,
"total_bytes": 103418,
"max_bytes": 2012
}, {
"kind": 7,
"count": 138,
"total_bytes": 79421,
"max_bytes": 1036
}, {
"kind": 22743,
"count": 134,
"total_bytes": 57352,
"max_bytes": 428
}, {
"kind": 1059,
"count": 132,
"total_bytes": 430222,
"max_bytes": 5285
}, {
"kind": 29333,
"count": 130,
"total_bytes": 63960,
"max_bytes": 492
}, {
"kind": 22611,
"count": 117,
"total_bytes": 50147,
"max_bytes": 429
}, {
"kind": 10002,
"count": 114,
"total_bytes": 510211,
"max_bytes": 42449
}, {
"kind": 23456,
"count": 72,
"total_bytes": 121616,
"max_bytes": 1874
}, {
"kind": 22850,
"count": 71,
"total_bytes": 30530,
"max_bytes": 430
}, {
"kind": 22684,
"count": 70,
"total_bytes": 30170,
"max_bytes": 431
}, {
"kind": 22838,
"count": 63,
"total_bytes": 27027,
"max_bytes": 429
}, {
"kind": 23003,
"count": 63,
"total_bytes": 26964,
"max_bytes": 428
}, {
"kind": 22774,
"count": 62,
"total_bytes": 26536,
"max_bytes": 428
}, {
"kind": 22876,
"count": 61,
"total_bytes": 28880,
"max_bytes": 3193
}, {
"kind": 22583,
"count": 61,
"total_bytes": 26047,
"max_bytes": 427
}, {
"kind": 22613,
"count": 60,
"total_bytes": 43172,
"max_bytes": 1133
}, {
"kind": 4,
"count": 58,
"total_bytes": 61778,
"max_bytes": 17018
}, {
"kind": 25555,
"count": 56,
"total_bytes": 249772,
"max_bytes": 16895
}, {
"kind": 25050,
"count": 51,
"total_bytes": 20451,
"max_bytes": 401
}, {
"kind": 11998,
"count": 48,
"total_bytes": 22272,
"max_bytes": 465
}, {
"kind": 22728,
"count": 48,
"total_bytes": 20592,
"max_bytes": 429
}, {
"kind": 31234,
"count": 33,
"total_bytes": 77859,
"max_bytes": 2612
}, {
"kind": 13194,
"count": 32,
"total_bytes": 16448,
"max_bytes": 514
}, {
"kind": 20387,
"count": 32,
"total_bytes": 16448,
"max_bytes": 514
}, {
"kind": 24133,
"count": 32,
"total_bytes": 23680,
"max_bytes": 2214
}, {
"kind": 3,
"count": 31,
"total_bytes": 167289,
"max_bytes": 61894
}, {
"kind": 4004,
"count": 26,
"total_bytes": 30394,
"max_bytes": 1169
}, {
"kind": 22816,
"count": 26,
"total_bytes": 11154,
"max_bytes": 429
}, {
"kind": 20004,
"count": 26,
"total_bytes": 13878,
"max_bytes": 549
}, {
"kind": 30315,
"count": 26,
"total_bytes": 17657,
"max_bytes": 872
}, {
"kind": 443,
"count": 25,
"total_bytes": 43504,
"max_bytes": 22480
}, {
"kind": 38501,
"count": 24,
"total_bytes": 17819,
"max_bytes": 744
}, {
"kind": 1985,
"count": 24,
"total_bytes": 11435,
"max_bytes": 483
}, {
"kind": 22882,
"count": 23,
"total_bytes": 20949,
"max_bytes": 1455
}, {
"kind": 5300,
"count": 22,
"total_bytes": 13434,
"max_bytes": 701
}, {
"kind": 31990,
"count": 22,
"total_bytes": 21777,
"max_bytes": 6861
}, {
"kind": 22790,
"count": 21,
"total_bytes": 8988,
"max_bytes": 428
}, {
"kind": 21050,
"count": 19,
"total_bytes": 17510,
"max_bytes": 1874
}, {
"kind": 10100,
"count": 19,
"total_bytes": 22108,
"max_bytes": 1589
}, {
"kind": 20333,
"count": 18,
"total_bytes": 14340,
"max_bytes": 798
}, {
"kind": 30088,
"count": 16,
"total_bytes": 29798,
"max_bytes": 1871
}, {
"kind": 6,
"count": 16,
"total_bytes": 28131,
"max_bytes": 3369
}, {
"kind": 30091,
"count": 16,
"total_bytes": 9842,
"max_bytes": 629
}, {
"kind": 6300,
"count": 15,
"total_bytes": 45643,
"max_bytes": 8157
}, {
"kind": 22658,
"count": 15,
"total_bytes": 11909,
"max_bytes": 1161
}, {
"kind": 30311,
"count": 15,
"total_bytes": 16556,
"max_bytes": 1197
}, {
"kind": 445,
"count": 15,
"total_bytes": 45070,
"max_bytes": 4130
}, {
"kind": 10777,
"count": 14,
"total_bytes": 6840,
"max_bytes": 489
}, {
"kind": 9735,
"count": 14,
"total_bytes": 27231,
"max_bytes": 2777
}, {
"kind": 4244,
"count": 12,
"total_bytes": 6768,
"max_bytes": 564
}, {
"kind": 22456,
"count": 12,
"total_bytes": 6808,
"max_bytes": 790
}, {
"kind": 38503,
"count": 11,
"total_bytes": 6070,
"max_bytes": 552
}, {
"kind": 10050,
"count": 11,
"total_bytes": 4882,
"max_bytes": 592
}, {
"kind": 30515,
"count": 10,
"total_bytes": 53040,
"max_bytes": 5304
}, {
"kind": 30512,
"count": 10,
"total_bytes": 15210,
"max_bytes": 1596
}, {
"kind": 30089,
"count": 10,
"total_bytes": 164434,
"max_bytes": 44372
}, {
"kind": 10000,
"count": 10,
"total_bytes": 16448,
"max_bytes": 4287
}, {
"kind": 22809,
"count": 9,
"total_bytes": 3852,
"max_bytes": 428
}, {
"kind": 22486,
"count": 9,
"total_bytes": 3834,
"max_bytes": 426
}, {
"kind": 22376,
"count": 8,
"total_bytes": 3550,
"max_bytes": 444
}, {
"kind": 7368,
"count": 8,
"total_bytes": 8294,
"max_bytes": 1040
}, {
"kind": 38500,
"count": 8,
"total_bytes": 5696,
"max_bytes": 713
}, {
"kind": 30380,
"count": 7,
"total_bytes": 3178,
"max_bytes": 454
}, {
"kind": 20000,
"count": 7,
"total_bytes": 2422,
"max_bytes": 346
}, {
"kind": 22104,
"count": 7,
"total_bytes": 3105,
"max_bytes": 444
}, {
"kind": 23025,
"count": 7,
"total_bytes": 3017,
"max_bytes": 431
}, {
"kind": 22896,
"count": 7,
"total_bytes": 3003,
"max_bytes": 429
}, {
"kind": 33334,
"count": 7,
"total_bytes": 5817,
"max_bytes": 831
}, {
"kind": 22835,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 30443,
"count": 6,
"total_bytes": 7297,
"max_bytes": 1260
}, {
"kind": 22633,
"count": 6,
"total_bytes": 6097,
"max_bytes": 1668
}, {
"kind": 22392,
"count": 6,
"total_bytes": 2662,
"max_bytes": 444
}, {
"kind": 1984,
"count": 6,
"total_bytes": 3180,
"max_bytes": 530
}, {
"kind": 30064,
"count": 6,
"total_bytes": 4438,
"max_bytes": 991
}, {
"kind": 22715,
"count": 6,
"total_bytes": 2562,
"max_bytes": 427
}, {
"kind": 30280,
"count": 6,
"total_bytes": 3078,
"max_bytes": 513
}, {
"kind": 22559,
"count": 6,
"total_bytes": 2574,
"max_bytes": 429
}, {
"kind": 22813,
"count": 6,
"total_bytes": 2562,
"max_bytes": 427
}, {
"kind": 22635,
"count": 6,
"total_bytes": 2556,
"max_bytes": 426
}, {
"kind": 22746,
"count": 5,
"total_bytes": 2155,
"max_bytes": 431
}, {
"kind": 22364,
"count": 5,
"total_bytes": 2135,
"max_bytes": 427
}, {
"kind": 37195,
"count": 5,
"total_bytes": 3664,
"max_bytes": 786
}, {
"kind": 22571,
"count": 5,
"total_bytes": 4055,
"max_bytes": 1127
}, {
"kind": 23004,
"count": 5,
"total_bytes": 2145,
"max_bytes": 429
}, {
"kind": 22834,
"count": 5,
"total_bytes": 4050,
"max_bytes": 1126
}, {
"kind": 22636,
"count": 5,
"total_bytes": 3280,
"max_bytes": 1164
}, {
"kind": 22786,
"count": 4,
"total_bytes": 1720,
"max_bytes": 430
}, {
"kind": 25195,
"count": 4,
"total_bytes": 2712,
"max_bytes": 678
}, {
"kind": 4243,
"count": 4,
"total_bytes": 2256,
"max_bytes": 564
}, {
"kind": 22168,
"count": 4,
"total_bytes": 1774,
"max_bytes": 444
}, {
"kind": 22760,
"count": 4,
"total_bytes": 2239,
"max_bytes": 695
}, {
"kind": 22733,
"count": 4,
"total_bytes": 1696,
"max_bytes": 424
}, {
"kind": 22878,
"count": 3,
"total_bytes": 4059,
"max_bytes": 3203
}, {
"kind": 22902,
"count": 3,
"total_bytes": 5023,
"max_bytes": 1675
}, {
"kind": 22428,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 25196,
"count": 3,
"total_bytes": 4938,
"max_bytes": 2214
}, {
"kind": 22325,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 4245,
"count": 3,
"total_bytes": 1332,
"max_bytes": 444
}, {
"kind": 22258,
"count": 3,
"total_bytes": 1331,
"max_bytes": 444
}, {
"kind": 22625,
"count": 3,
"total_bytes": 1284,
"max_bytes": 428
}, {
"kind": 22631,
"count": 3,
"total_bytes": 1281,
"max_bytes": 427
}, {
"kind": 22551,
"count": 3,
"total_bytes": 1816,
"max_bytes": 695
}, {
"kind": 22763,
"count": 3,
"total_bytes": 2988,
"max_bytes": 1331
}, {
"kind": 3927,
"count": 2,
"total_bytes": 1066,
"max_bytes": 533
}, {
"kind": 22186,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30213,
"count": 2,
"total_bytes": 1264,
"max_bytes": 632
}, {
"kind": 22367,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22783,
"count": 2,
"total_bytes": 1391,
"max_bytes": 697
}, {
"kind": 22616,
"count": 2,
"total_bytes": 858,
"max_bytes": 429
}, {
"kind": 22268,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22279,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22474,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22407,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22729,
"count": 2,
"total_bytes": 854,
"max_bytes": 427
}, {
"kind": 22234,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 10003,
"count": 2,
"total_bytes": 18782,
"max_bytes": 15076
}, {
"kind": 30815,
"count": 2,
"total_bytes": 75068,
"max_bytes": 37534
}, {
"kind": 1111,
"count": 2,
"total_bytes": 1775,
"max_bytes": 922
}, {
"kind": 30087,
"count": 2,
"total_bytes": 13286,
"max_bytes": 7325
}, {
"kind": 22256,
"count": 2,
"total_bytes": 886,
"max_bytes": 443
}, {
"kind": 22299,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30090,
"count": 2,
"total_bytes": 2911,
"max_bytes": 1515
}, {
"kind": 22260,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 7376,
"count": 2,
"total_bytes": 1340,
"max_bytes": 670
}, {
"kind": 22012,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 30618,
"count": 2,
"total_bytes": 920,
"max_bytes": 460
}, {
"kind": 1078,
"count": 2,
"total_bytes": 28397,
"max_bytes": 14219
}, {
"kind": 22313,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 31339,
"count": 2,
"total_bytes": 2940,
"max_bytes": 1470
}, {
"kind": 15750,
"count": 2,
"total_bytes": 1184,
"max_bytes": 620
}, {
"kind": 20,
"count": 2,
"total_bytes": 1664,
"max_bytes": 834
}, {
"kind": 22326,
"count": 2,
"total_bytes": 887,
"max_bytes": 444
}, {
"kind": 22841,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 22738,
"count": 1,
"total_bytes": 1668,
"max_bytes": 1668
}, {
"kind": 22295,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22296,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 31510,
"count": 1,
"total_bytes": 979,
"max_bytes": 979
}, {
"kind": 22691,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 22340,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30003,
"count": 1,
"total_bytes": 387,
"max_bytes": 387
}, {
"kind": 22197,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22257,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30079,
"count": 1,
"total_bytes": 875,
"max_bytes": 875
}, {
"kind": 22811,
"count": 1,
"total_bytes": 3211,
"max_bytes": 3211
}, {
"kind": 38385,
"count": 1,
"total_bytes": 1350,
"max_bytes": 1350
}, {
"kind": 22339,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 13196,
"count": 1,
"total_bytes": 656,
"max_bytes": 656
}, {
"kind": 20200,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 20201,
"count": 1,
"total_bytes": 491,
"max_bytes": 491
}, {
"kind": 22395,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 30023,
"count": 1,
"total_bytes": 27062,
"max_bytes": 27062
}, {
"kind": 7403,
"count": 1,
"total_bytes": 490,
"max_bytes": 490
}, {
"kind": 22618,
"count": 1,
"total_bytes": 1668,
"max_bytes": 1668
}, {
"kind": 17,
"count": 1,
"total_bytes": 593,
"max_bytes": 593
}, {
"kind": 30000,
"count": 1,
"total_bytes": 823,
"max_bytes": 823
}, {
"kind": 22558,
"count": 1,
"total_bytes": 428,
"max_bytes": 428
}, {
"kind": 22143,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22198,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 7375,
"count": 1,
"total_bytes": 2846,
"max_bytes": 2846
}, {
"kind": 22506,
"count": 1,
"total_bytes": 426,
"max_bytes": 426
}, {
"kind": 22243,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 23086,
"count": 1,
"total_bytes": 431,
"max_bytes": 431
}, {
"kind": 22688,
"count": 1,
"total_bytes": 3201,
"max_bytes": 3201
}, {
"kind": 22975,
"count": 1,
"total_bytes": 429,
"max_bytes": 429
}, {
"kind": 22270,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22584,
"count": 1,
"total_bytes": 1675,
"max_bytes": 1675
}, {
"kind": 22764,
"count": 1,
"total_bytes": 551,
"max_bytes": 551
}, {
"kind": 23071,
"count": 1,
"total_bytes": 1675,
"max_bytes": 1675
}, {
"kind": 22817,
"count": 1,
"total_bytes": 559,
"max_bytes": 559
}, {
"kind": 22614,
"count": 1,
"total_bytes": 3200,
"max_bytes": 3200
}, {
"kind": 22330,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 22229,
"count": 1,
"total_bytes": 443,
"max_bytes": 443
}, {
"kind": 24421,
"count": 1,
"total_bytes": 525,
"max_bytes": 525
}]
}
+63
View File
@@ -0,0 +1,63 @@
import json, sys, os
from collections import Counter
def analyze(filepath):
kinds = Counter()
sizes = Counter()
pubkeys = Counter()
total = 0
total_bytes = 0
max_size = 0
max_size_kind = None
relay_msgs = []
with open(filepath) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
ev = json.loads(line)
except:
relay_msgs.append(line)
continue
kind = ev.get('kind', -1)
kinds[kind] += 1
total += 1
pk = ev.get('pubkey', '')
pubkeys[pk] += 1
sz = len(line)
sizes[kind] += sz
total_bytes += sz
if sz > max_size:
max_size = sz
max_size_kind = kind
print(f"{'='*60}")
print(f"File: {os.path.basename(filepath)}")
print(f"{'='*60}")
print(f"Total events: {total}")
print(f"Total bytes: {total_bytes:,}")
print(f"Avg size: {total_bytes/max(total,1):,.0f} bytes")
print(f"Max event: {max_size:,} bytes (kind {max_size_kind})")
print(f"Unique pubkeys: {len(pubkeys)}")
print(f"Relay messages: {len(relay_msgs)}")
if relay_msgs:
print(f" First few: {relay_msgs[:3]}")
print()
print(f"{'Kind':>8} {'Count':>8} {'%':>6} {'Total Bytes':>14} {'Avg Bytes':>10}")
print(f"{'-'*8} {'-'*8} {'-'*6} {'-'*14} {'-'*10}")
for kind, count in kinds.most_common():
pct = count / total * 100
tb = sizes[kind]
avg = tb / count
print(f"{kind:>8} {count:>8} {pct:>5.1f}% {tb:>12,} {avg:>8,.0f}")
print()
if __name__ == '__main__':
for f in sys.argv[1:]:
analyze(f)
View File
+2
View File
@@ -0,0 +1,2 @@
connecting to relay.damus.io... status 503
failed to connect to any of the given relays.
+1
View File
@@ -0,0 +1 @@
connecting to nos.lol... ok.
@@ -0,0 +1,100 @@
{"kind":1059,"id":"3b112c9920bc79584669face207b9491a2a2da388110b84aa085a543d99ac841","pubkey":"c91a8fba68b9d08b9917e3a7443083a9b1364c984685bc56e2310024507612e9","created_at":1785677036,"tags":[["p","0ac458d69b9340c2e8a20a4d5f3afac1211b59e372a646beec37ac32b05ea4cb"]],"content":"v2:v4hmDySAiOH_Iv0uoZ0c5ctYsNwIFjWYQaAr5RPg5s-jqC95LxG56m_gZyio5uKm_7bImZ7hKuyoKXPx8_lfvJsg042FpZnUE8kCU8gAbl0lPTlHbwi-uPL43u0p1SACh1HHf8zyQMLaSBEIV-FTIghxtz9nIjo962bVbCqbh1sk_iMJ6qeEkmmYOCukb8ryRBYnW_7RsruPUsXnbGq5-JS4LXaKwmFmk78GYnaVaRzr_9IlQfUNKe2KD89hcsg5-SbOfLX-YEXeIeB7fKGT741tCtHs9JOBvwA7pgGwdfvUHUmz7WbSv4E6au2JENZ944rkxkv-8LinUAt0vlkXvzmHh-vwhnF9d09BZSj_tHvX2d7PEVg70EgifE6p6X5NnOjILq7VaSYTf2BLfkGl_dwV0R3pGL5_EtHr4abVGCeWTn30YJdUYHu7WV_7YuaX8tAxi0QtZ_5wstZEi8EkIS7R-ocJHNLVX8V3qp8hbFJ9jw_ZZv2Yjfjs_1CZLY7z-1u1WVjcBeXzEGQj5jnQTsf05D9Lv0fE9dmz5UL172LIJhDRQarhtowUJihycoQvRgcY8QH9wZzND-xCclxoncgku2x86TPyMmOPE9vhZX-UeimbJVSFO_PUt71quXF3l6kSLJqGK9Kpjf_yTKlcOiWqRlZ89QYuPfX5jNWEvWfv-g2rLmq2HM59Yq_J7iZZmXB_j0MH0Wz95Vdy6BdFkKA2boYWh7EKTslQHNw7uFZAsbH4keW64urcx1f21y6ccssz3snUGesmxlflSgbQCaY0C4J91OSOU8HsjGwbF1JIAK-UmLwNXsr645Z8iVingbQnSiUyR_RCWgufGaT03bJVp0uZNhrEAHckvYQ15mhHLDSIJoNCiTr0nBfpc1_iupYVQYXjWLgDcFIXhYbrr_cGPL_7sCVrfxKzrCbcm4Nz2LQo2ICrKWFUvWsDOw7Rp8e-gjLaLQxf349rgPPH0bz3VpA_vyiina8G8MGjZ1DnmRRyWn7IqN1Zc2x81_eYu4d25QAeqtctF86keK-nwQcQJt-frFmn9_Ei4uaj56g7YPTIFJF7VLV1af5xf8QfnsDnstmXejfIfq59k6H8-jJKrYbQ3Yue58qTin0wgUOKkPUmq0hml7BeQCykConuV33wV8Y0pJcPkTo69ufeaB8q8t-6xx78-mYNJWLM1KjXLLwqoG6Qqs0-CaASLVYJL7oVFZBpzD1FM8M4h_1d4YC9-_g22ilzm2wu2Z_EHaTOn184oiIpjg80umDtGoNgDejcAb9R8gdnI5SbLo-ianC52UO4piD1DheYurE0sDIuTbbnGgT03q4xtu3xxi-olmPScr3CHvH3mt8wWgjwRGIUgH71yoT0dvPUq6APmqOz_FAMLhFws8rMFaYLzoyzO3knSdwVkNyBqrzapRtP4DiD2pzx8w7mHI6bEpc7zMJZquJ6ksM8g9k","sig":"909df887f9f6ff688a919d16cf49a9cfc542c70e57676a663baa020aa10a28ae01d39aadf79b9ead4ef0f9a95e6af8205ca371b928f2dc498ff5f3b184d75e97"}
{"kind":38501,"id":"73b2e63fc89ae708f89d2d355cffebcdd5bbfcea39da46e6410b408f8b9fe2b9","pubkey":"45ac97d8c6b2896d43dbc903105ad722548ff1b986269654ebdc0228ffed4bb3","created_at":1785676888,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676888,\"enc\":\"3d7e99d510750b3af7b20e45c2116c88f2fca84c5147f3c839a83b4be80a982f\",\"meeting_node\":\"92.246.128.180:8434\",\"pubkey\":\"45ac97d8c6b2896d43dbc903105ad722548ff1b986269654ebdc0228ffed4bb3\",\"sig\":\"a32af8b0a024dc7a67fa881041ccd4f1c50bcd600d7e8bdac5aadc143a3c2fbeca6ba616ded91df6e92d3669cc8f8114a15778786293e1822f8b6f99867efb55\",\"v\":2}","sig":"b00356fe4f3371d47d3348ca1d5d8d66b92da75ee8f3fe7799fb5744127fa4a76758a97db7bee777b30d4da3e710d6497009e6f9775275ac516f3dbe3cd52830"}
{"kind":30078,"id":"769fffcd14f4044105b49e1233169861c48eee0f156e70a0d3c5bbe4dce4caee","pubkey":"45ac97d8c6b2896d43dbc903105ad722548ff1b986269654ebdc0228ffed4bb3","created_at":1785676888,"tags":[["d","bitflash-mainnet-2"],["t","bitflash-mainnet-2"]],"content":"82.67.0.226:8433","sig":"60557df1a6c814e0f815d5cd4d5e8285e7e5a724080af10b5ab0ae382e70a42e1e6af0a9ecf62cbba4e0ba8ff60ef16f485be5ffdc2ee809b7769799a0465a11"}
{"kind":445,"id":"1cb1e652eee8d63d61792139730edae9141329d91c37afb61b6cd5cc3c2cfa27","pubkey":"6bf7822615d8091f8d5b76879cf8e8bef5f20b2a73d81e36bc06bb5b4d5e3034","created_at":1785676878,"tags":[["h","8c3f1c25eefc49fb83a272a826d5a36235381ffdd1ba0c7fa4d774f0c7f1b55b"],["encoding","base64"]],"content":"Ygn9YOUc48RIHWWOgepFIxy0yRbxXB1LWu3Md/RE8sbwnnF50ijaMTQTiVCxlb5s6dITCwpFaz4oPOHIwre/vn4Se9+WXFA2sgl9iNAanzh4ltnzP1c9mG6zQtZRRSYyvCBtBrsAx333eiOKMzSPFov9QqCK0F9Pwz+5Ee3CQpVC/hJfmo0vz4RHOe6WvxW4tjPbGFVd5PrMqeGv8xtMKlAYDdKZIXmRPySxllV7+zH17sN56eRITT/6zRq0enPORGqIAsgQqIZOSe652mRyOqkvkaJ9sxv3IyEb/u8gnZ9sdOuSQs1iRAGQgvLoCKBoTqoIAqMZT6YL/NjhuQ0NzZVZLXJwggfM7rBlv3F0Ad0MgRO6HMpSXxyxKSW1T0/+rgxcvNV7Q3DNOEMOPugCyfmrKboLIHfBSTyFgKdrQ6gZZtwUA72TgGLQctXjCzXwudG+n/q2s1KAItcmG6BpxiqQYAi7iw+fOivUwz41ra8x0hpZEVbU6K9wHwpjhN9tcqq+j6ALdjkFdnffRCMUMXB5GjLubaTdA4MCWZvEGdM5YACnmcCNhTMJMvLzurm3d1+8V/wwQvsIiYIYoa6Ph/ZoXvvE6P/Pvmrg+mARugiS3rSPyE7wZDcTXrhC/gGzBJdLrTW1hDgse5QFh+/m21O0eWJFweOnZSBWYkgDotuyAhzInsHZ4Lh3KtCMnAV/O12j5R78VtwwZHrT4nnejtenNxzIcwmSNpn10kTalHPYWOw=","sig":"a7d756444139f09ebb1c69c38cbd76787d6a2d9d30a2262853f017c78420177fc40307d803b7fe4f472332cfa312f0e0549da0d258648ea58c3792905a2ea05c"}
{"kind":7,"id":"7db435801117613a1eeffa3eaf68a9fc51fbade2951f16b7ddfa0b44ea367113","pubkey":"21c1a52506a343d12c34c0a434f7c6cda276a609ee51a0a02533febf0c84fb3a","created_at":1785676832,"tags":[["e","0000352da8dcd26bb6cbbada87e4ad466090ee86caa2e1a039eb7ee7d9b5a415"],["p","40b9c85fffeafc1cadf8c30a4e5c88660ff6e4971a0dc723d5ab674b5e61b451"],["k","1"]],"content":"😂","sig":"865e4ddde04a31a03dd07c7f3db1f87a87dee1057d60497b9f234b1f5d4d2919fe2db33b8810b8a0f557e5fb7bcde1fd2cb3a87336857f7e24333250b3a674a6"}
{"kind":22749,"id":"e1abe91e8827ce0b42b55440d14182531ec10413503e0f0df44bf6ffb1d2ce77","pubkey":"38879d5f7d6ca197f5a43f4043cde208002391eda3d7a66529cc29f95754fb5f","created_at":1785676832,"tags":[["x","14305u172l2k3a2wn1e103y5j6k5v4q69c214a"]],"content":"{\"peerId\":\"hMQ2j8o62EZB1aaiWa0D\"}","sig":"d2a05f9d679f36d1ad95b172ec7343e42f26225621e2a05e8ae79cdd779bec6b0800a74165c66bfc6b3af283a1e1f833890ac87301b15188cd7d6775f86e5805"}
{"kind":21059,"id":"ffc75a95b53a380a2de1114ecfa1426c080d5908df48fc66ce209c429ec8d4d3","pubkey":"a249d052bdf16251537b47555586389c8090db4a08687811edfb8a5dbe574044","created_at":1785676831,"tags":[["p","32a6df90883d1d026ce75ca91bfed54353fd2feb9e9f7329f7a94cb467037b05"],["expiration","1785676951"]],"content":"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","sig":"8a6d018e1563b415ed70a13ebd5adf1fe2ee36f92006c570bcfffa198646314280f07fa6788801ce809fcf8e8b5470f629f836076ba19d30600eb273ffbfc542"}
{"kind":21059,"id":"518b7655489f9339f73a7d4cc9013b817e731288dea4b45d2e5eb6485f438489","pubkey":"7b353d5d4669607809fdc4e22690fe19e068912e0aa67c5d83c65ed12803e7e7","created_at":1785676831,"tags":[["p","32a6df90883d1d026ce75ca91bfed54353fd2feb9e9f7329f7a94cb467037b05"],["expiration","1785676935"]],"content":"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","sig":"c7c4162fc010944e98390ba22f03d2027dc635c2ec5bc52501d43cec6bc2bd78bd515de180b5586d961d9dc8b374e29597b548c28376b018b8264e28c3860f6a"}
{"kind":21059,"id":"5e8ffd1a6c5f869a3a04aef3bc0cb701930c4348110ba8d52ff0549c0f463d58","pubkey":"79e0b421158103026cfd58b00ed88d52fea107e5974f419879d80a5b7ce3d40a","created_at":1785676831,"tags":[["p","32a6df90883d1d026ce75ca91bfed54353fd2feb9e9f7329f7a94cb467037b05"],["expiration","1785676951"]],"content":"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","sig":"bc53226df7c6b32d70168c939b2f810791493f408f365d779e652ab9cf9a88203b25b8d2f40d12ffa29e90101528de9078be4a146317b1ea1ccd80b56af6022a"}
{"kind":38501,"id":"1a8b39de152c04c4cf7d46e9ecd4b05d718cc83f8c283e89c920b5ef91a72a81","pubkey":"bb4f1716ebb55c0cc2759c6ec70be71884486568237be5b0a24fb94b87ab0d5a","created_at":1785676831,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676831,\"enc\":\"d395724b1f1d6cec4f76cb341404015ace916a410ce93993358b6ee568051329\",\"meeting_node\":\"92.246.128.180:8434\",\"pubkey\":\"bb4f1716ebb55c0cc2759c6ec70be71884486568237be5b0a24fb94b87ab0d5a\",\"sig\":\"56006b2ad9357724b0848a25f049343e59177e414550d609a1970900b80fe679335e7fbf09503d5581513676daed140d68313c6d6f0efd075e9b2cd4ca52d8cb\",\"v\":2}","sig":"111a08e451e1733ecbf6fa8e89ad9bff22f9e49bb5059688cdb741db58851656143013b8ee33fe0668e13ff5bc2348764631bad217da59d26ab82775002ab9e5"}
{"kind":21059,"id":"1fa1cc7fb9b195dfb7bb587649e1f57a13c22a716d5b6b7ad1eaa35f3682ba36","pubkey":"df37a8e8a754e2aa7c61085a67f70c11c548381b45f6bfa45c1fe4fc6a05d60f","created_at":1785676831,"tags":[["p","9131564f29a6caa4884f5dc2bc7f2f37cf9f9f3ea79eac6cbee56f23faacfdc4"],["expiration","1785676951"]],"content":"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","sig":"b4f89cad58ddb2840fce5d18b59703412e7781d721482486e53c10a540ca6bb91266bd747ee03301cc4bc638838c2f8e1d19aeb7959597ad3a9c667c808e52a1"}
{"kind":21059,"id":"c6a07a7a6c5efe9492211f5c7ac91af555b672564450a29a13d6c4d480da58e5","pubkey":"33f9b8d989cc0db6caa5503b2e59a174550054c8eb93688641d71371ae305338","created_at":1785676831,"tags":[["p","605e6516fc17fab93c5e1c66534b5d4a8a2b3de64942c3ed742b51626d267cf5"],["expiration","1785676945"]],"content":"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","sig":"291c834d1ba72b4cc0830e8f69bc4a07cee371fdfdad2466d1cfc6cc69b4866cf5e3895ebdfdc43255320a9473423f83f1a2c92d37d7e9acf51e46e344a6e814"}
{"kind":21059,"id":"0f9efae4bd3dc697c547733b74c1c0b2f36c17a14031ad3f9257e35ce8c11d18","pubkey":"a6b8b3720e7a311904a9d6d5ddfcacedd7e0a70654bd41c043072de6f53f8107","created_at":1785676831,"tags":[["p","4f8e06e55adc2dff717adbde066c79a192468858816257c8c717dcaa8d51a28e"],["expiration","1785676951"]],"content":"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","sig":"70c6036ef7b66b2954d3ac4a944f8c5234bad1b37b43a50cfd947fe8e984e0cbf91b3ac51b17aa8babcfadfbd27affe5dee578c36103a9660290e7aabcc814b8"}
{"kind":21059,"id":"233787bfae7c7d89e6d09ee4d1348e59be138204edec57ab399abed267e9ad43","pubkey":"38cd9712528928a6b85f526abf6613917c52c636608e1f7411b33ee457d5bc4b","created_at":1785676831,"tags":[["p","7b938b906ad984ea438f8af5e464ae3c2fcca4acefcfadbe7a3d66a7a69212c0"],["expiration","1785676951"]],"content":"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","sig":"e7894c60e30fdbcb92032f6e2a80090b2474996796771becec02a2e1af748f3c893b113cc6ab197b6fadeceb10805fcdba74ec43617cb665199ebad82209a2d4"}
{"kind":21059,"id":"f22714bcb70a4b41ffd4712f8e5764a0d6bd3af78f6107134932bf8531f52621","pubkey":"fbae19b87545bf7615aeb74ac04cf926a50bf332f3b8c2a0aa5f4c877d36e351","created_at":1785676831,"tags":[["p","fc62a3263abf4a39c09a7b7064aa40480788f868b653c66dc6e3a32c6880a506"],["expiration","1785676951"]],"content":"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","sig":"69949c1cb10548c9a991212c50390aff75777fa2eaf5f979c23f7e031d48fbd0202b7882248552330464dccd8fdd089b613e46e343e540ea49c111005da39d01"}
{"kind":21059,"id":"83cd0f7e4b2e434807599c79a7f0549266bd37f0b91847b0c1cc9681417c9041","pubkey":"b6b05517cc607db1425e192c3ced2df864b46000526d5f50e47b74d25c99436a","created_at":1785676831,"tags":[["p","08e99116ad58c5aa5e6e7dc40668d5d7f2c080fc9fd697ec0aeb76880cf54bd9"],["expiration","1785676951"]],"content":"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","sig":"466fcb9413f41abf5337adc2032829603aed33963cdaa304cdf5522bd45b59905298465515310b00588aea216c0aa3249618d60e907a19363bf84bddac67cd25"}
{"kind":21059,"id":"0afb26e3a41a100c304aedc5b80548e679da585e4af81391b7e0196f5eb694ee","pubkey":"d66b7344ecf950b1261dd49de99deea8b325f038dcc1ed9e436b57f9fccefbe3","created_at":1785676831,"tags":[["p","fde8ca9b4a9d1f5d2ed8b82ea13766c37bdbf6cb50bd272d642eaeacc713563d"],["expiration","1785676951"]],"content":"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","sig":"6e8c0633ec51e9d5663a865754116c27582180150f5eb7581570cef47b58449b79315c1623c2684eb4075f46120cdcdecf7fcdd59e57181132e754be7fd8d4de"}
{"kind":21059,"id":"eea2139089ca939ab3e858c7c626609fe629f4dec943497e0aa8bc5c49d13b78","pubkey":"71654ee31d04384b529f095a9cca21b58530ab86c35e239257ae5a6b86b1f70c","created_at":1785676831,"tags":[["p","5376ff74b7bb5a5c9e97cb60a1d068dbdbc5ec39c4346156335747b776ec6d84"],["expiration","1785676951"]],"content":"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","sig":"e03f0a94db48468a6a07bd0ae0a9792c4752f3d498497bbbb3b6fb1873bcea3ba8f2d08efd7e2ca67928a5183f3bf9361699785adbad273a1da60c58cbbd31d7"}
{"kind":10002,"id":"0ff496f20a3bc170afc15a4141498259c26fb487702ba35b9260a96b7b5ba0bb","pubkey":"0000521bbc284afcbc646771c0fda5b1a839a0d3cd7b6f4fe2eda9be15ecbc6b","created_at":1785676831,"tags":[["r","wss://relay.damus.io"],["r","wss://relay.mostro.network"],["r","wss://nos.lol"]],"content":"","sig":"111925db0e99748aedfae54d2442ea8053130095695c5cecf798f3d34cd4039afc4b41662ee128fde49e3c3d517523fe8a828b21d31033c7f4d264e43c2ed35a"}
{"kind":21059,"id":"7f897e9c805c499c963dd8999caf3645b5c6aa968568d5f37462ab8659e41853","pubkey":"6e7e9c51e2739dafb6348212fbf48fb6d768002b018125c8cfbbadfe70b37dd6","created_at":1785676831,"tags":[["p","aaf802c63614f6028fca31eedc3d0021bc3320fc95d0e3af5ddf8e7cae871472"],["expiration","1785676951"]],"content":"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","sig":"24b691e5868fdfa5546625783063d68c5d6bd412e389851e5555bb15b4c6aff3251474fa3580501b8cce93e9711a982dc2f1e7da6ae167ddf459ffec1af0d94f"}
{"kind":21059,"id":"ee1cdd05723f5a6ef4ad3caf9db988f33f9a2921a82cce6f7a24158218ab38ec","pubkey":"755a026b88668c9ce69857b49983a95039dd7689739acabc169a21315ee04783","created_at":1785676831,"tags":[["p","e15d56d1b0ca49d50a4f71cbf04774ae606c4632ad7752195ff9b80c5574e0b3"],["expiration","1785676951"]],"content":"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","sig":"0f74f06f1c14373856bac66f45fbef85bb774aa1e6a7ef82ee821c5dd81b3d32a6666b815d2b53613fb28d7eb63b7a6859c15dcc9ee6b67930ecfa086b10ded8"}
{"kind":22653,"id":"35b97981fe7f50b3f20cae1277b4861b9fa3d49ee7dc7acb8da9d45c1287399e","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676831,"tags":[["x","1w3v3i5c503a1e531y6b136q2s5466t39322o4"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"180,27,136,119,41,56,194,227,43,3,188,67,19,22,123,115$ZeuNQlPcrzNPMpEycw20kbmWmAQ9LQA2e4EBLwCM4jl3ajWwnnK7utcxXmapzWDz2xsFhpNRr1jg81viwHLkwzGfOHC4/0o7KKF5Jl4TnLLwSj8g6oToW1FNupDZeu9NkaY0j8JEJCU/G9iTJ/mnnTFSyMZUY9Y3AB5v9+uk+uBij+wmOk1Q4CBDPScFZll2oBNoq/GhlUP9BqhSavsUerOnATJMjqzcMT1m8pZYe8RV5KnCDn6YnSaIlje9LmePx1qMBQBWugymQGuaX5OufdXkvabgjXBMkoj2X1/I9KbAsNGWU2ePqgfGhYXO2IrpEuqwsyiYn9UaZN+RI5v6c847DIpLpVo12xlDg+gog8L1ueTawBSBLazJmCytxqvLROFKCv0EctnbbMKpGu79nsa8NPYcp6c5BVaYFZgWbDxHaEJx2vz85n4STQ8OhjyvA003IwSrE60SUu78URUKA9SXTQoQMbu7H9Uc3+JukT4W3wCMOiMgnJNqQYvYgr2+HJ5EFWLEyD8dlnmI838FJGwnHl05fkAv5vHmdT1Eyrbbstlrn9wrSEmla5mq3cf4lLSkb+06a4D47/j3QBrtfREhnc31O/+XCzhGYZKs5HajWQF3YVW3Md9nHCGFe+yYplq5pCe6HlVSbooXQsYuYyHH8BmfdZCA+Jkrhfbasr+MKTXvvR1ARfXuddxvOjuxk9uQbIN+k1eJmnlyzxODDhMi7gJmQeOpudYWlcquuPhSYHBzKlPs15ibSr/+/Ki/upX0+AZCywGZEyPQE5MYYD6jGoX1SyoZ4jIfl8Xw6oTOTJ+esJc9Ub5LQNBNLUhU8Lh5lxw4LPkZpnepAfyGno1BbrN6V5gg3LA+aoD8pQY8DajozXQX3OWTCu8CGZTu/RXK/VCEnUykrfYaXBU+88+F+3xAxS38G1jdek7VmAMqlfxGY/21roM4zVKahtTJTSZBDK6RtK60tZffGkxOugNHGTY6+DbOAa+NfLg2GWsSkcXSStUmCzFQbql5p4g8FiiO/3udMNlqIx1nxX0DWclYGgHi4DGJw7E/wyuXn7324Tvw/UjVQvm5fHVLc7LnClr//dsQArzagNjdIolCSC+pnI/EUkWAFVwcnwu5RpcPam6c6ZtLkXPhB0bK15vdRY4QUKLNSm0kGDDs3HC6/xi6oZQgF6j49ziOGeV5YYS42XVk41mZG4cGqX9R1EWGd4ZQFiWyRXNLttaMol2CJTQ+yJC1DYjD2N793qoMRuzLVXStld6436buBHJrqL4Wzywzi6eS6ANXyX4jqKxQlXOl9nTxCQ==\"}}","sig":"849aeadaa749a4d431cb91f8a282ac09b9fb322872ff0895430f978ee69108deb02f0cbd323a4b0a92148d4014d8bc8c503e22f9b05569d7d751824737661f9a"}
{"kind":22842,"id":"a497bc4e9330b78979645581cb7422c54b64d990b295d18fb965c15c8b28e0e4","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676831,"tags":[["x","3b47662o2j5h6w4431116d4l6k6z6z2o3s2tk51"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"167,11,114,204,116,88,224,166,141,164,164,64,134,197,97,18$zMoeDUlzCvWcmm9Mn8HB1tuqvdDd4BvM3uJBCzwaUKQ0gnUWY+HmKkpaABQex/UCuKjf0fdsRr7SHE3MQ2j4vbSabKo+LD9mdyeQt1vDGpiu1uWbq/Em0HVEEWxTTTAuySuo/cV34xa4On1Ju8LjUaaF2+Bf5udktCf+/EzzVZmGWlyWqbMmw1mg1D3UcSsye2IBiD0FpjDzRiqsweHRoqfESL6XuL5XZ1ka2oipiN4t3DtkIf+QncvSElMm4lxyOnqDR2HDcuQuJKsi9NtuXbZoyJ+rgdfbHAmKsT1hrywjk08aBI56JBUQvVv1ClKq5gRplDRscxlj4CYeksMayW8cHjxqchUEtPdqzOvvjbS/fS1hzEwplaQbYqkEINUSxCx0llT5023uwSMcZXFuPPzEpuu9L91zI1EwBYIjpIeUti2bDG+RtseCcdp8TQh6Ygwqc8Ne5HDzYQHrWqEQRSuurZp/w7+tES8NlAAmPSCPoPLyyKG57uvaWKysmRr3bL1jPdVS+Wqo0WbUMvrS6xEwYle1qvnK/6bP3JeaMhnriq6xTJiMDBplL21vOC7qwvA+sQgu/R/w7q3qcYFre9HbXipFTWY4x8QOGv/JMAf9YSSHmDYUsIKt0Rh3Ow6AJhJjC4bjem6QKpuznLwwRlWcv0sJaF0LtibH/z2bwKs6Q4OozajwS3aJ9YupJT1NcNBOdf0+9R3zZEENOghm8mbLIN0YIkSZXepR/gzn8D7fAt3Bh7gihZvZHReMytPWL3Hjoev30HO8ehBLGbJrpe+gkoxsnbU/zQT40ucy3Vp1KlbOCd7aGGSKqWvlFxbz+EraunwiLtp6gqML257CdGa03iZFKu9joOS98uSzUf3CRn9A28cbgHPvLTQ4UYj00TBb8v04uW6Kv1s0VCm0kZDt1V6moMlFGXu592B6I0aDBkWEr6/uKX1UymIsRBm3Wj5oWXVf8VEvDOYExMFJtHmUJg/z3/KUJASjoX+LAENQO8k28vlWh8b41YTVkA3sB06SZIRGoCVXcEs6+IJgqvuLv58OvZrfK20gSdGEeYdEWwy+w+4zJeaL3OZNbDJZIHCBRRvruSqiXG0OdKT2QnknrJ9otReAc/mN8E4wPwciDUsKV4278gWSZrFWuIPERcf7xxwmbxc/gXCE2bf84V+khN2YH15WtGBDErRNAbVfEwQdrAYzyGKiy3CaoxIIbOn/wR6WPFnImdzMlT2zcIlhLgTHCGo+5186vOk1M/AWsUyah36Pf0eQW3aCtb/nweENd/aGWqSaFzX+L4BHYuKSsT7stFc=\"}}","sig":"5358909a435dd89d34a1e61e94e5294945eb1eb021242dac22067aac7f0652228d9b4a7793a2b9956f879d41cbe70793eb2ff79088470ee3b6af3b01e99d136b"}
{"kind":5,"id":"6e783cf2000cb5793a94ae2b09162e1d3f1304be6eb7c774b852e66b59fd521d","pubkey":"1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8","created_at":1785676831,"tags":[["e","1f4d0d3d55c0fd387950ea4e0de7e973457039bfb03b824cc71b13bbcd39f293"],["e","3aed713425ab4a0459176d4ba589876e6a313d879fad18152a11347c22c1114a"]],"content":"","sig":"8bbc6c7ee40ccec7f402c48a5230f71bad3240599ab841e46a3e155841953edc52de47f96fd5b4dd35f8f784e1cc4fc9062303395d6d049d2c78744bb5c176ef"}
{"kind":5,"id":"df31df8ea137659a43641ec7aa14f85f6935907ae71e0ac42d5196f1c7f3022a","pubkey":"a6cd35843a1471debb1b174b5bab472d9f5a2e2391e92207c2279468c3e9ac9d","created_at":1785676831,"tags":[["e","3aed713425ab4a0459176d4ba589876e6a313d879fad18152a11347c22c1114a"]],"content":"","sig":"fcbe7b0f5cf5fe044cd295ce6ae5970ca9bcea40e0584b212c027426a1721c3f15c60e387b6f390c7568e7c68e6153512302fc3898a27b0094c2062409788dbb"}
{"kind":21059,"id":"bb18168cfcd10c7cfec63ae32e39d5c01ff248531c05ed8d42851751c56a8c85","pubkey":"99f289641b017b67d71e948bad494b63bcd016724e95e425bba176bc8cc03cd3","created_at":1785676831,"tags":[["p","29d1d9e11edbdbf04538d90d053b7b2d32c1c253fc3df1893c11b68826630609"],["expiration","1785676951"]],"content":"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","sig":"3504f1566dc03ab9fc19e597da5baf4a3734fdcb2f7a24316d0a5f43c37bfcd54e6f9bd1d4a0d8960d1ab5d38d77d7078ee990a2c874db0960300731266e1937"}
{"kind":31991,"id":"f8a2a8a05db114a7096435a285c45498846a0306d9538e7362b9c9665032b6db","pubkey":"537f2bbfdce17c83ee7338dc26229edd9b26226a2f258b578f91f4213611afc3","created_at":1785676831,"tags":[["d","537f2bbf-v1"],["s","available"],["L","agent-reach"],["l","heartbeat","agent-reach"]],"content":"{\"status\":\"available\"}","sig":"09a021823e02d5c2ae3ddda7d05c22e05033012645617cdd6313b5b425b491cc32c61d849d704c7cd9ff294c757d8e0ff26d553f457cd3d7e26c158a2651552c"}
{"kind":22543,"id":"75e596503238aa0f8c242cdb2011667c813515e0f02b006c34026105f67dc2c6","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676831,"tags":[["x","456a1r2o456h222f3g036k594s5x2333l2v45"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"36,99,139,51,68,73,226,255,159,72,228,209,82,242,77,19$m/DD2pfpEe+VV4XvRyouNbCVzG2hxkrSt+3zBRI1H8ihRyklrKo5mkvQvvG97YeFy79GxjZhneAlrf1ZzwSoxO/KsE7AtZiuYI/onBlne1ueaqNZca0ZLs/eVvLA3nksV0gf4IMc8g7zJ/IXHGK+9Ha2cSW81dPPlnIsL/7DPyzvtOTpTqENqXYwkpOe5yYa/lOhEF7XNWHDcJCiDonINlMc9JFvXqd+rXlBmrXMsI2De1l4EPGHAk0hnY04uLbpE9j2AFHNMU7JaLLfYyAB2hL4GEjxCqGXuCETm+oJ6hGfsmMeF1dHFfVj+JqsVZAy+pWYr81I5F8xzzhIuLI1DN0mmC0hVV+dDTQw9AzNSoSR/ANICZxkDD6OLeepNkhLZ/h7p5sd/ue5BWONH7mBtKFZ84e1c/OTwJyteTPTokoifMWS3AS1CLAau6Uy6QFXtFz/WhmtVj5MQ1axmzM9asKaJghl4dOi7ScPYLXTluWuAJH9azpjrw9qL4PR9nJG6chRoJAbkRYAodXhT5LmVe64CyWS8GDrAjGOgqY5rmWMJQJgjPJEFVh7xX5yP6tok8z/7K53Rj2XtsCXE5yM+7WI1/l/GSv0Pv7A5LRt/8mndt7tUtg2ez8UyfFsnuZDzPlWwkWCO3jLHLSPvLxYK+X6CiPHKqcZDuvGqNQUTYC0kT0Dhv7ZBPZvVDYgvooA9KgPYT2J1um0MPctIgIzk9TmXCT845IL4aKWOwwMPFkiHc49btdDzCPmEJ9IAI++yUb1QYcnXRhe02jcMcj2t8x9Bmdu4cW7c9syePqWYpytfgsAhCnbT9gfqOoLkrPxmvNP86lbTYLq1KcFFIYIqOrG5dmV6kQQlvbzQbaP+zIk1I3ACOh2KYd1CZpE2FiSPZ9yq7dZ5LZdKd38tM75/LPDQwrLNKvg/yvm5CAMXWNpEeCSfLYUvhSKmFgYVlBLd1aUODOma7pWuRH9A+3V2Sg3rqn3ux6yHASkqvVZN5zQZ38lwvHgfc2SsoB5OkAXwAKPKC7YajsoU9p3VNHvdf6vfL12udGeFODqGqcKbTHTMJC4Q/U4/Ch40HMFbnWdTIK7+Cr4bdQVsML5kUNEqxFSoIO1mcADBxXFP4rIG/tB9GzyQZjmSHJ3bnKvBtvVih+vZZMju8cxrFtW5qedAcDw2qJhhN0dkDRQPOdbq7KkgPktbqe/j1D7NbetdGH7qJ4eeCVb4W7MZkWoNJdXCII16vh8Wv6nzZBK2ItYyY+sKGv0LHR/g76c33zyNzkGOV6AeKb8/wMHfrq1/cqut2T9E8vAPLU=\"}}","sig":"e8418147619db01eacb6f22e5d363226a5b650b2df0b2facb3c33b8c93951c08d63f76a2441beac1facb213e569ce0ba3675907d8d2348d9f1631b568aaa6584"}
{"kind":22668,"id":"d05164ec247e35c2dccc14ae8700bcbcf5355d1f9fb589b97939d37f97f7b4ad","pubkey":"c938fb345106fb08fe6680f7bbc7b2e635967c636e763f6579accb3d0627ca73","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"LE1qO6qizx5jMA2erFhj\"}","sig":"708e7d3c91e43ab8f65630208c294307d2a0472ce5c8e57cc6ba0d548dec4a0ad6ca5858b5de1f98bf52ad7f9c5ff86b61b2c80775edad0a8e251d24d1f0c9ee"}
{"kind":22668,"id":"f10a1eaab979692852318eade7c2fcf8ba3cec9f659867011647580bea0bcd99","pubkey":"129614faaeaf11f6606d45e39a591fd02f1a3d297cf5911c39a007c7c7f3cedd","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"ZZEfCmLfVXsZXTgS8Wil\"}","sig":"fc92eda249f298ad247000192e68a7260e25ee237429cfec40245d4c674568b6938bd9a3ee9795a432fe81e5565425802a037f2bbaeb66d83d46977c968021ea"}
{"kind":9735,"id":"80ef4548d68d36095f62d0c6081b02a021fff27d628262cf4bfce334f8dee039","pubkey":"be1d89794bf92de5dd64c1e60f6a2c70c140abac9932418fee30c5c637fe9479","created_at":1785676830,"tags":[["p","e4146d1d7fb2f7f6e6b3c799eaeaba6f7446fd28ccea2592671cb239a13f4d81"],["bolt11","lnbc100n1p4x73qupp563fjv7p2j9qlh4cmkqhty5808avtjhxvr90mc4rhqxtpfyly8ysqhp5qq5hwkrnpgm97lt4sfh7gt82ccspge9mvwjrr45y9ej6a80tsnmscqzzsxqyz5vqsp59kuq05evhglk4j8efpcklc78pudrs3tux6slfjjnk2pzdgam343s9qxpqysgq25yxnew6a0humrzhg4fym7xtrxy8f4s23vfjert7wq4rkp88pf8qhe9kpagpt9qaf37w0z3ugfnjeqg2pqew0y2keythw36l6f36t5cqvu99dn"],["description","{\"kind\":9734,\"content\":\"⚡ AQSTR Reward: Btcnews earned 10 sats for like\",\"pubkey\":\"6c27a5aa02b769c6b51fd7edd80c5b21b9ba32c16d913b2bc8e1617fb4573680\",\"created_at\":1785676828,\"tags\":[[\"relays\",\"wss://relay.bullishbounty.com\",\"wss://relay.damus.io\",\"wss://relay.nostr.band\",\"wss://relay.primal.net\",\"wss://buzzbot-relay.bullishbounty.com\",\"wss://at.nostrworks.com\",\"wss://nos.lol\",\"wss://libretechsystems.nostr1.com\",\"wss://btc.klendazu.com\",\"wss://nostr-pub.wellorder.net\",\"wss://nostr.bitcoiner.social\",\"wss://knostr.neutrine.com\",\"wss://nostr-verif.slothy.win\",\"wss://nostr.cercatrova.me\",\"wss://nostr.einundzwanzig.space\",\"wss://nostr.wine\",\"wss://relay.westernbtc.com\",\"wss://nostr.roundrockbitcoiners.com\",\"wss://nostr.land\",\"wss://nostr.mom\",\"wss://purplepag.es\",\"wss://relay.lexingtonbitcoin.org\",\"wss://relay.snort.social\",\"wss://nostr.semisol.dev\"],[\"amount\",\"10000\"],[\"lnurl\",\"spritebeet37@walletofsatoshi.com\"],[\"p\",\"e4146d1d7fb2f7f6e6b3c799eaeaba6f7446fd28ccea2592671cb239a13f4d81\"]],\"id\":\"6e64b14ed5da47c6d4ee3caf4649dba7e9259ff2173fe9a092d46f838dcb4984\",\"sig\":\"08396838f0645989cba3c7ac753aa5a39cd7ea113ce588f4ca39b1061f1c17cfed46206b91545912de5470ee556d08fb2218f34593cc8abc2f7e317961407dde\"}"],["preimage","833bb4c5c573dfe24cea6306294aec8ee1bcf903c4b1af0af174aab14bbe91ae"]],"content":"","sig":"64460054919c2703eac063ff7dd4807610f123dfa6a3a5c838e87ca7686e9d8c697f5f135dbd4088d1688caf0f79869603b80ccca2b6c7a8f32d6622554d8651"}
{"kind":22668,"id":"e93ca07e3919f3f216dcf0a2e0a96a016840eebf41e5fd706773903af39c84e8","pubkey":"dcb7a55095f3761726ad6d5a53209c3ac46cf9be9c8e5013313fce6de948ee33","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"qLShkXb42nycV1mZOj3N\"}","sig":"de497859d5a80968957b9cf61624e6ac7451d71249cca0a3f2945e888453946aaaf1d42844ab01e620f27c0c1f612d8eea41395449b4354def8eb6f1db91997a"}
{"kind":21059,"id":"6275550f617b1e838bb333227f5567e49c5039508ca2fbe67392df600352fc4a","pubkey":"b6e091b72c36a10e1b33f22e798bc2c497fddaf4334c8b90d9064cd40c969da5","created_at":1785676830,"tags":[["p","2e26907e7e535fadfdadbb3c5c2ca41f39c3f86a78143642fd0e435a4a6da6e4"],["expiration","1785676950"]],"content":"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","sig":"c80ff23a16b2aaa5607fd4930c15fc2b5dc99f5e56d4cb38e4f17d1322fd9f337eb2e82573fd6cf79f7dd1effb1facbed1b429cca561500052474979e5b87050"}
{"kind":20004,"id":"a44cef5e7a3d808548ac3300b5b214d2307c957b1b02c106bce6988fe2a760e4","pubkey":"4e8ec641f5340254c1228c02d69995ca9246c7b3b0295328cd6ac418efbbd9fc","created_at":1785676830,"tags":[["p","04504ed6eed7a46e07b708e8805dcfc2189a49108e3f42ff1137d879f21b6824"]],"content":"zDcS1eEbhLUg6zfwDZ0vRCaLCfKkltMD0OL-e69jva-BPkKQrHIrpJITgSq36FBl-cTK9wyojnUUzld76_OJDBzin-8fJSU?iv=CZo2CiPoKnVD_9S8dF11mtAOomcyZYQn","sig":"cf941df89b47a2144d2650a8c6313c976845e42d856136be7e932ddb5de816eb84ccc725f9237aa4f0a6d46f7148422bc8e956d133022bbe021ddf4f50a71fb9"}
{"kind":21059,"id":"5d90f55f4d87f435f650e0da26e501e7d2376c74b56e259ab807ed3d89f4b313","pubkey":"90e84244c692310d3b93fde994d7a25112a00613f7e6ae8d18f141e66930ca8b","created_at":1785676830,"tags":[["p","08e99116ad58c5aa5e6e7dc40668d5d7f2c080fc9fd697ec0aeb76880cf54bd9"],["expiration","1785676950"]],"content":"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","sig":"c1ddfe11c41e265d28ad6787a6f81d5bcbcb0038367424bdf047c78dbd349bb375c6c4a758a89644faecaf94b56d8851da1006e7259a255a42cfd539268a5abc"}
{"kind":1059,"id":"6293744ffa82db6aa84f749cf1a40f069297865f4f47e4b9fa998afdafc91d62","pubkey":"1ecbd9f1a29cf784475500f5bb060692c168beb8a73e7857b90ca4e9e39235ec","created_at":1785676830,"tags":[["p","15df570d3edadf50eefbe7d46ab9896356e52b7464774cfa865262f912cfd577"]],"content":"AtWL2vMtnEVk0TtSaEnpkAJUh11hZ27ssFwJ02ZgyWlBRzOPL8Q2fmI8+RllwHvEmnfbpLVGxirqUQo7n/Rek7eu0Noyyyg4D6tYwY4Ds3CIkSFDK/SvLL4//esCJEqeUQ0E8fd1b5j7bKoNwDcAgVZ09epbzw3uvWgl3frc7sqUf5YjXaCVrzUZiVMaQbTdknZDu921LfW6hm+w5+a+JnoGEp1MHjPVUT3HsmN4zkpLCuuF7pmGTLlyzE0Be+NGvhXqdZ2Mcv/Y0JL9PV33v5hwnWnXLB51I/u8K5BKBI7hBQ6jyMSrs9qarVFSHDF1rjmEbvNC7B29dyBn4AsasKcdlcFwIPN/5wdyNsoEfE3uZOFvxyK0iMBGQa0mgYr7xKthEhuUitskifvBF2bVrTjz3FoslEVqmqkz87qPpk2jlkWEC5rWqOgOb6G7CtJwQIJSHvzL5R0baDxGF3ZzfoosZ7nwHK4f14Els/8541JBYDIhai/nQAkvBKvL6dBT5fZi9FjQLjOXDShNnDRImjCex2CafSMIoEBhqln9A/Dhe0SbtM/SssAYzO+asvzrC19hWGmBI/eGqbcVnsHjj12+KQHSXpdY1iZo/6bdlA5xoPyu00n1NJHHqSPM0WIIzGSJvesmQyLh37sZ8k8cu5ytSuQbpJhJ5u2BoNSXm+0maeHOglPYIkZLWwEjRekw8m90n2OsnZ09fKF84V5EXV7NRYZl5hwEbOym5uc6iSeyNLUF6cJFOb1hfAqxwYMSVKDT+mdmVgsQAbbzKB0wuqd91eJftvzu1qArKIH/2AG1tXI7HQoFzVmrIxZFtUdcxnq3Og4veJmEfEVElZHQOuhEqcaCNGgmFp5Yk1qrBzf+3/IOIPg4D4dZSM6UJxbnda8Jp7pLuYI3M36r9o9YNZLtF/Ff2kYMmhmOowhHmwZeXrFRi20gw79fH2FbgFM6CWUuO1+fx/di1zBi9QNrAnHWi21jxOPXJ4SxZJNkzD4sx6kmt0FoPBDOlGoyRNqOcjesV8EoMugi+My7ysys4jo1sQdLCMlrAyAeGzIlRTXPhmdSAVC40SOhooPtnPbPl2YBFo4vjvPR5dh5jZwuj4UqS/eZRk+lhDP5tGFJdfHOQZnZ4l1kBpT4cmoQOrxLa0eS2Eja5uj0uiCh281ml2vOHOmYDNXep1VioheF3tsspmFRqM8VdaPD71h+kVMY1vCiDicFRzBCr31545B4pFc4eA7yQgyiYKxOF2NFCPcRzu2EVL4LXGj4OJEZ3+jex7P9J1KR2Lu1AMOH+mec5WR9BZXY5GGnLnlIc5colTMqHW7thYjyku5YW7vQVmD6L2gNFh9U4FvHWNbotXqEtTgPvKdfu/sJkAOJ9YwJiN8Vtq6dqqyvc5fWSACGNu8nq4L7QGLI0LoKuk2/htUM9jmDHS9qSQITV7qVuUm80Mk2I+hfelNS2vko32ARuO6BJ3AnpaSCuxZe1+jQ8DwPcSXZ7wjOOmUWqKzYAjeuCyU2vXeihiu4Hep2dQw+MCRmi7Iyxe/EnabG5hU9jWc8HV0LVYfYIvw16RoGkBnLn3TEM+u1x46sNIizoSsx4jtsyvEkSA2ZsALLAvRxltUQJj6/pGlOoZv4cI9uI0mWARWPChLBJxWNAj3Ws5tbAR7MVPi2f/94lubGHUxofEAfOAWGQ9txrcvn6jl/CvYdUDcWB9uDuXIAY7l0ukLXESaIp8K40LuHBgB9GuJKKZn30udv75LJAyHy4/JX7HSUlTD9AcbkpcvXhCLyRF9OYLkuTfM2Ye9Z1B5gVfuaqWn/XbeF4e8HbLWl/A7y44YqHQbCPyMAjyH5FxiKPm9wd8AtJV0Jk0jMiz5oL7EYgRsXrUVrwGZqWLo606e4oFBI1UHuVpA5CTeKedbuNgPyUmRLIvp60ZgBs58l4lDay7gNysQZlyZPPTY+MWdEEzZipbF8kjqjPzJSMXT1xbLvT0p6RwXWNasQsR6vNL+VOqh1vT5HExsKxXk5GnaGe4NrRsy0ZzhLH0SpTeTW4MV5jLopbfNez5t3rXLJbUTwdeprfBVSBXSaIg+jA97/3uP0Ho3TdCH2ltHX+DnOjiPcV8SZyD+MgnzMXkANrcoXoBcAI6SGnw==","sig":"dc3abfc7a4d9a7689a82a945cd48dc7ea1f315254928111ec8a477f108fd28b59220c76377174082a5311790fbafd13d58018394399e28f6d949af95c4e59f25"}
{"kind":21059,"id":"6b85fbd7cd052c7f39df3de5dfa3ab7959f2a7b2131c22c9f1d03b9f3d11cbcd","pubkey":"61612c62844bb7bb89a96d59667be268c8db7f5a6dc4dc1dc915769929704f93","created_at":1785676830,"tags":[["p","48092008b3f8aedbf147268ae9a191bfd003e97d3521f706d738494d55a05079"],["expiration","1785676950"]],"content":"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","sig":"0c74c118a80bca7ee3ba466eea0cd2ba45c1877f8cfce631185524e2a40b7bbea4f01fd2a4728c1ab4d503ec6d2b5d4438c74314ae18a7e506f7dd804f86f59a"}
{"kind":5,"id":"e004071b98835065bd79466e9694e922eca9fcaf1a9699756da6ff7af1d50f9a","pubkey":"f32f0405b1976474d2eaa74ecd15c257dc7c5854e405996526023a7aef7f9ff0","created_at":1785676830,"tags":[["e","c3d841341e03f0f22b2e6e7cd0811954f7590ff69c785965c2afe1e91cc6558a"],["e","e7558455c28020c88895334d03cfa2567922e7010873686a5d988b73fdc412e1"]],"content":"","sig":"1931a895da1d66d643a2c3ea0e2d84f7c84b75be0c480f7900db9925ad0ad939efcf188d443ecfdf318649b5c5485cba9630d283a285416fc9b137153c4a52fe"}
{"kind":13194,"id":"a3fe853699b7641349db48aaa2f100a6d3c1c4cff0f7d6fff89fc1647d6a2a32","pubkey":"d03ca1662e2bebb12d280299b09a07a9c77450b7c5b6eacb357e3bacde1e14b4","created_at":1785676830,"tags":[["p","d03ca1662e2bebb12d280299b09a07a9c77450b7c5b6eacb357e3bacde1e14b4"]],"content":"pay_invoice multi_pay_invoice make_invoice lookup_invoice list_transactions get_balance get_info","sig":"000f134426656ad3b8b5a4c4b1cd2a208bebf2392dee1763a339d76ebf4585d847ff8fdb0793fb8fd7aed2cca83a78ec21d57635447d353ba5c528a351974123"}
{"kind":22668,"id":"c705e88ba63b117f281ef394600d2f33a03c72dd67c22b8517c689e00853d722","pubkey":"651ef346f974c18f3a781247a39ad0c1812f7817f52c3e1e48d09fb277c62751","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"gnZTneCwL6Qb5ZD4UewN\"}","sig":"85550bc17c5328cafd4f1172106e4e7af93122861b8ea3be462271b6747e64b5baaa6c952ad12c2f4d7160b6431aee4dd3df77e850b45212292816f1177c1a27"}
{"kind":5,"id":"1e8a289daa492596bbe1d34fc8486cfda8f284883d0007599c21655e1ef7cb01","pubkey":"c3d2d513091f404497181534e7965d2b80da38f82ac76bdc8faa2ee9b20b7fe8","created_at":1785676830,"tags":[["e","b7e189df82207c6e3c837afe4900dbbcdbed7e8a962fb24bf3a9ddd2158729ab"]],"content":"","sig":"824d9bfa8b1860acb395c869895e1a950b8d067336265f28f164cfa11ba00dbf398913d00e2acde7f9ac4f81c34f13dec78394ad37376e916c28cd9df6574e60"}
{"kind":5,"id":"504765ce365092825d7185424246b11b1b510ae68c663424a47117e64ce896f3","pubkey":"92e744f3d9e8ca5888da31ac871ef9eed2a0b198ba08f417937cbff3030b0000","created_at":1785676830,"tags":[["e","84780070c1d4646031d9d096b035b73fbc5f1c8e8cf88708b0ee5260dba22e86"],["e","b7e189df82207c6e3c837afe4900dbbcdbed7e8a962fb24bf3a9ddd2158729ab"]],"content":"","sig":"f15d8a5be186bee060ed55fb432679cfa829a5e28f7f142b4004d5ed3c5e06cebb8cea9813cea8c391b541131aac750e4c9c3d189451de67068dddf6ef946603"}
{"kind":31990,"id":"b982613c77be9bd1202a48c630833a7949f07ee83fb76d879d4686af2704d8f3","pubkey":"867a67b1f464d9099e009e73f93ef6e5df8091be0b149bd38c85a88d29af7cf0","created_at":1785676830,"tags":[["l","hello"],["expiration","1785677130"]],"content":"","sig":"88bd7ab0d68817ee099e8d24673091a41f817f1f3c8bf849d2c91d672a35a3b191dfc52ddd485b889b6a6fe8c9654d95ba31591e32029f4490bbdd222526429c"}
{"kind":22668,"id":"22af285ccb3f6287cdcf363ebb06b648add8ebdda93d4ad49a40330abf8250b2","pubkey":"f50ddc7a8ab4a5ae2abc2802f7e33afc40f32acc157c749665cd6b78454690a4","created_at":1785676830,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"MUzRfOSvVHCPwAQ1zQlC\"}","sig":"77518b61f5756b7fcaccfd68a9c63164deea9558b0bf9fdab40d79fd9036b946848fc85241dbc797c11762d24b62265da569fcf91244c571cf2b070e53c8dbbf"}
{"kind":37195,"id":"784c21c5b8d1ae8569d4b663711c9579bc92e6417daa76b160ce8fb764c7fae7","pubkey":"522bf69c8bcc74bcf6e8ba3dd4e2322fce057892ad9b9f81ce93f05e65ab7564","created_at":1785676830,"tags":[["d","iris-chat-device-sync-v1:fdacc68213faacb25010d10c2c6bf9770a3a6006696a8282c906c6bc538315e7"],["protocol","iris-chat-device-sync-v1:fdacc68213faacb25010d10c2c6bf9770a3a6006696a8282c906c6bc538315e7"],["version","1"],["expiration","1785676838"]],"content":"{\"identifier\":\"fips-overlay-v1\",\"version\":1,\"endpoints\":[{\"transport\":\"webrtc\",\"addr\":\"02522bf69c8bcc74bcf6e8ba3dd4e2322fce057892ad9b9f81ce93f05e65ab7564\"}]}","sig":"591246b446dadbe3a2067fd458b72edcd7db27fa9889b22a5b032137bcd14c814c8039041d7ebc1678bb73fc0b8baf5285e54ea09276db25ad1c713ac48c4ae4"}
{"kind":22891,"id":"c4f37c58db11bd3c2d90196f859b88254d8953e85ea2e319b7367755c825ebe6","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676830,"tags":[["x","6y2v464t15574k2a276u1z5o2kw4m6i6d222e1a"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"202,14,200,159,96,222,82,153,64,34,246,116,22,182,211,172$FIiGiFA+45swhd9X7BA8TJjG/LGiO079N5BFevU3Otgzgz8iB+/cHqfFuKfuzApWtHPEqlVp06wXUEaLjvnbAkR6EBEaZP9ImobFwwLkw70gvM0zegLQCxJNxFDUOCvZtONUTjyFneKIqKkM06DTRhxOMKiuYKPR0C1RpvTIwfCnZv89I4+bQPyc56SdHLrAVX20SZO0YHJqe9c6cf7BGLlLHythEawKUNuqs9KK+8KLzQ1THHOBWjYBkvho8bBAzChGkx/tuqt58JsBjkCDbrV1QbhV2sfA6e0ypFMR29KLBU26mQLbMllAgdv8YCY3ceD6a3pLQ5njmMZrQyIno5GC0OWO/14efdYHF88iDknVdgprL2zhL00emLviW14b+lV1UnuedFROYBbqzidDIadnNIPoJO3bxuUIf/Kb+P3oKV+etfnz0/86cY3vuno28op6ixoNjt93v+3bsdZtNTVto6v41qRWj9H2D8Ap2TYpqnAyvZNCl7F67K53ONVxqw5lC2DooGfsUP7nbPBztZrkWHqH7pZ8y390FqEBM+QoqlYT3p562xAM64LYUe3ynEd+cUVOdJn9WjD5qIbk0dvl3N6ykV/IPb6lhWBN6zDrldR+gbZ6muhmJf/LOogRZtEVmYqvy/ObJIJukPBqTCL2TEVgPlmhNgH1FXzl7tIRlp1mI+aIHJwXdaAL4w7pbxP/QxNKru1WUAB9HJI2gasx0NEuFLwD16noWeZeSZfbZSgmKE7qQgHLIRRkp5qWk5AIUaUG0YTiHAaTaH4HakK/pEx5+/i2Ybz4BqdlADCDCHH7ys9/D7l7Kt1UoEKL2dbwrSFjAM2kCyVy8EHz6A0ikXv6Db8yEVLKgfc5dYGAgoe9wtHxxFmAuOq8yq0yVMKDvXoJCN5FlVNh0V62GXE+2My/BY9j/VxgOGYnXbRPEEaMTGqTX4RH1+a5hM8aDivK13BIXMC8j0/F3WMZOW8yP9WDGugWAlMb9Q8yTwVy9upG1XMXFJtCUSGHEhx00GDr3nJZ4QHrjpu/zAIvrBwXWxA2HNMumhbx0lYXUa6Ldpm7CoZMDQIXcSEzypxi/DD603pObMSPJE0pAyLIDdlTJntl9DW9Qmsff3ywVTAlc5dZEBaDmurZeretP7VGP4Q4dx2kpfHrUH0Bm/sNJLRZJIFuLseaOOxDn3s82f0eHNBrrNPorNEUOo0BHixswtEcOO7sYfYUvLVeD239zw47wVvYD3l3oPx22sbNG4i8d4v2B5QvGZYzdS0RTJUKgfJwIoYauUCYIzvhkwZskQvyKIlXcg==\"}}","sig":"292aee436ed0aa9625cc416ad5aa90e2b01729a096d66a13b31d7883fb8cc40167b6f12b90d6c206559d03812b347f85c64cdc00bd91692279b8ff0189930ce5"}
{"kind":1059,"id":"92d46f9c73844d0e73a4da9678fba013135470043952ee249b0a1b36189768bd","pubkey":"af36a76eabe37abb29d257916b8004817163931e8d7acb6e14ee83150b6b4b7b","created_at":1785676830,"tags":[["p","f6ddda433221cd4512a1dd7ed37eea610422964044cb96a739ef15f568670367"]],"content":"v2:5D8IFWcX1XB94nMgs75pjKMWV-qH7kJKJ7cp922s7b6dnXZNRUjTXSY07Zf5cDRkU-NUGfrixmrKSeCH_9m6Oboo65haHfHck4IMZFVikRtsb20ZmDKWmFz_IchtRETAbjUBrVFCA0Pz1M_VkVtH59RjK4E1J7ij4gw0lUacIKVaBau-0ujZEouYRLXtEPKVLuxXI780Gya2OONw_m352_AU638i_KGasfa9SMQZZdlR5E1YcYJi6-sSuonmpYdqrq62EXOue5ovHujxiTxC3Ra8P2xvcMH_VwI4-_e8dt_EQ0EG8WkdqQTEwETT3h_eed-KkuCDXda68mqpbywpJSzJD_R7ys79x5vEwkvjUiEFKHiixaXDAm-s6ssYxTVc7J7vPkEFkYIaPqi3ClS5Tbnf8Iv4eLngmFrABUtjGip1XHZ_2QVVv56-2qIjra3-4BBKMSq7qm2eQslTJBgbnuh9zxdjfhqAw7yefff4-hVCn2VBTr3EQdd2bsILheXzWOEj0vePQ-OAG1WPqWD3ruiXBsJPyJs7VWsPP2_PyBfrGWCvamVadPdekc-vPjPrX1qkJustPn1m5AOTW_pFmC8p-zVe-HTPTyNvtkXZe4oOIDBTK2sctviWcrrLF_3gQewIlOD7_KRFPUA6LSMXcK3Ns7saa-jhVfxNIz0nYeW7VgxLbhgveozh7B9b9kHTgTHOcQe6_XEAa3wSHXosYYRJUe0NVMwUN5OC-mmzwCYniyDnWbinTqOdx_EqVQBOoX1VPh8eMqEv499HWnbALCn4iNcY0FA1Qe9KanHpfC-VF8Hz7hz0KB_ZaqNCUSTl0SC_vHmoeEnx_h7Shyn7vdloBXvdaaZjgDfp0otcrC_oa6ERCZg2YEkwqnmytmHOVf6zoY7OsOoRFTTQtpvd4cintjIqVg-V_WvN9M6Bth6g7iTL8Uh1aTDq2efEOeto5GHVGIQftL_uMwSjAAlphCv17a_EreXQM7V55f056Z5OIQAbOS-tXkoZ0O9SRgQurDFsc5mC39H1GoVrpPofIGUFFAgnCTB4mbuw-V3Y8xm0_MJG-xEE4fr7ymp2syDpgCedy_QkFgU9-0fv5FaDH11uWs7ulJlZ1o_WHuzC02ydpOUK_2FseAWTYw1KhN_9ddes8d7xBRF8Ta8pLPbVBNYwOitHb17cspuv1LSind9bjT0bH6dBx0vD22tHRIvkieIuHjtypcz8U5NRVmbchmw4WhpvptvWUoVqySxibpLBNTbUVqlBxXJole6_qkpnDOn1qkGuJTN-tWqQfzZyhXCNULWZSlZ1NYpBrSgTQTO6QBh_tr9oe8Fc-oip7_c7o9gxcvYtmTgghqW7meYhi_tb6dO9b4JM8VAj9VoiFPd6gBXNSWTJ2ej36aqqzF_d_KEgpVr8a01-4owIZMPTsefZCMLzoMux-q_aYfvtvhs3wM7YYHWfyIw","sig":"52aa16a82ed4922de3a8c78fcf5be1066a328cd99ef0b2268e3133e9cf575605c3c23b42d6c5921c00e19718ad446eea051d4fa601527694f01257fc53f830c4"}
{"kind":20001,"id":"0008627e93710f902251efbebea4f25418fb8263eda4cf113ce40d49100593e6","pubkey":"d4dd33c3e92280a7cc8a4bd65e5b3876a572218902921df450db07badb1bb00b","created_at":1785676829,"tags":[["g","td"],["t","teleport"],["n","Boob"],["client","glub.chat"],["nonce","1534","12"]],"content":"","sig":"5175c979a4127301f00119c041ddb30a0b2836485a1d05d824aa933c84a4775ef163588118cf8d9a09f805860341df300ee4a8e9471443544946972497e0e80b"}
{"kind":37195,"id":"b3848e2644e70a784d948eef64eca608ce57ba1a8a75ae5e26565ddcaa9bb8f3","pubkey":"14b1d34a9d862fa6073e2f9ad689ff11af8411751f71f10e5440cde3876c6710","created_at":1785676829,"tags":[["d","fips-overlay-v1"],["protocol","fips-overlay-v1"],["version","1"],["expiration","1785680424"]],"content":"{\"identifier\":\"fips-overlay-v1\",\"version\":1,\"endpoints\":[{\"transport\":\"udp\",\"addr\":\"nat\"}],\"signalRelays\":[\"wss://nos.lol\",\"wss://offchain.pub\",\"wss://relay.damus.io\",\"wss://temp.iris.to\"],\"stunServers\":[\"stun:stun.iris.to:3478\",\"stun:stun.l.google.com:19302\",\"stun:stun.cloudflare.com:3478\"]}","sig":"be31af4686ebfe3d850c516be763e74f2c5588a19427dcee1577b9a3b6dba59da5ac920fc6c6a524631e9e9bc32161b8de01116f34758055101c7cbf406c6995"}
{"kind":21059,"id":"7e8ac972c6a94ef4fd8a07ced607d6229065d421d6c480fc13e7a40979f981a0","pubkey":"7be527855bd9034f673f08570a74b91411141a1c07b20f0d096386f1f2552a91","created_at":1785676829,"tags":[["p","4571e6236c45bb75ca1bf6c142b996d9d451ec6c3fa14ce279dd53a827b6525d"],["expiration","1785676949"]],"content":"Aii+iv9ltGTj9CmsdoNvz6HpNCN4iElt0Xd6ETovdB3zPh1T+w8Fdcu0/7jK9z+WZwAfpxeMs/xEJGK1ZgD6xRPNthsqDZ5/j9Qmmi3KFB49khXg2t/cNLisbAdFgwL6R+0zfRb7qZhIkviXKMKeuMK16caWFjjzhYmbcsxfeOUCQILoFu2LeDk8eGBchPc+zDazmgWYQbMOSy7e05M1IWq9oUNNmPHcfwBLrct6mnUeaP6TMU8J0akzNVxRae91ICLPSspTbu36Pudc9UP3cbXI2XO7y9Qt0EV8qJahanlarP0627S8nWGjOQXTcVXTlPZXyXifcLQ7XHlL7q60YXwGAcQFIsrxjtlFUMHr0sZ3M7YSPST5dqfJJ+doIAzVuPDXG+j16fAY0Fub9Y3Dn8Wan7SK+UUhXoFNBINL7jhZZfJUcMVPMqslcIzp0urzWJSpRDW0Cj/ygxSPl8ucaIcUl59KvUEni/1VgRi7yQAeI0IVU/wz8z/wXXKsiJZReUhpVpAJh+ek3TfKdolInSQFLavm1KhCde2NtNOW9fCyUlN0/lpEuyKxbDw/xkINqQHbWuUhJtJ3u3cc7kJFxq9FoOCp8oM7JusiHY5wVGNbkxFsasaole06wPhtPmCJNRgs4KhBRCyVxRrKYf5n4knBQSKf3Kzl1iePH6ZaBXku0rwF/UPnmzqQ4vOX80epDv8A198GDwAJzOEi6rZOIODXuFl1/2etOVquRAsqloxK6Q3hPY41+GMaJahACf/Ip7KjrcgIsfBMPSRqjESbk/R3lw7DaMIPtA8KcY5h0QtmjSjwQXFznXPpABXx5C0/Oj+V7Zkp24mtlhiTpOLq9tTOQXHDzGjiPr1Ft8DKKfnP9ACxqYaegnUWtKENad4Msp3lIgMsqizqmW6PAvPyRPfvdhF+t66xCkkytrDghMyy0nDamU/p/m+uHQRMycjdLvMFW7yfTyLjzLov04IX8bYqfwuWy0fmYWwTe31cjwzBIEZx1p+WH4RPXzsaSNAK0k9jGT2WqtYBe7MMGG8VEhVRypQuNM6eWE1a6Z820LRWlsJU0ttUjn1LAPjbqE+tdJBlgAbF5U/ww5sUbDzHnIJKFAEBrKc0yaexPASVc3q1hnpqKK5RS1KnZ6tngdaVqJDkeRA/cSE4BTgHz3vnC9iVGFJFiyZkYqaUn7KlLe/QLfYgQ9Py01LOyR+J3IyPAsp25B/KxaZi+RY7eTozsetVXO2LEFV2NhoDOaK4n5+ijHy46sxCpGFI4Ml6kgFpqFxp/1i5LTN28XGHlF5uyVErbDk+2EMFfw+vqccAJzhl8PmUUorU5ybimQcNgtjgx6mjn+PsGylHBnKwvVccHPk5kCaCGK7b89mKPwrsA3/zPwIJDJEz81FY8Ue3v480mHV3FENdyKELB1QT9E6H7cZ4DfSHLycHMioRnPnB7oIinHZJy8l/k0T1txwfNDMbpn3o4mgBJL3BwcSdGCYKLVDlV6H1rpFA4QL2BwMCHMs5syW+LHFS/wrDA76T/YtFK1xHAte7nmvWAYN44QaU3ILQhkdg4iW5HnEU5mASi2qIzYQaT5IvEzsVdH8NeK+nr3bMdzlD+VMDAQOR0Dqy6Mi9KxkMv3yPvia0d+8GReksGG5NHnRGfYW8OzQ3oo0hXoc55j37rfZpHJvp8xZOjad6dq5MeUbMxEI81OEQfonpMt9XZYcbgdp6uVQWo1C/cD7TxRfU4NIhQPDBK2HMU30V1UNZmTDK4gwOBaaj2LFk+Xu8pU4g7qZycuG7bpZV8mlIvxyRHLm9P4ZZRxyISNWZX9D2mtyuY6WNrlvJrCcU5Y91Few/ODerkWyktqBkSlNqKDb7uwQOpdvipBzlyDga6wumVzEb3NFuzUP2Qfoy9Q2E8zFvjws0HFPRR7IUv9nSg8GUiEDTygyVzCWwSEVCDk3jNIP5GUWKL0kGCekDQEVq71g68nMGICTmzIByYuzDXrAmqYXevw37TY96fFjzny5uFyaALieORYkLfEmPozLj4kRDlxb0FiAMXT5J1BvgQbYRvKmTTlaIFjDfVbYs2d5uOAQ/0znRE6hj4b6KckufDeRomfYzZql3r/SwccRsbRkg+s0FQoERCr/gSicS6WgUYV7AhOonogzZlTeR0KX8bcvEcymuk3M6km9ItYdLL9JKorCIkF/tNZ/bduvf66mjS7GojmqWQp/xY9QnVpHGWo3LKXIReBLq30/UZ0/nu/duOS5+I0sq3JMfMk4q6vJxKTgGqeLSEuCtX/k1YjQXKmte7rOYW3ELeuU9/L6mrBQCQXhADZvaPEA+ZWH7ujOsrL5+XTxWtdha9g6i4EugmKZJjPIs04bEgp9FaIhqYe/g1LEZ3enlZDVFB7gwzVli0TtDvo3KwHWDQ6L3G2GxdPShWc/grZ0yRoYblglA8Je8jxp0Z6GeIBfA1QcJiDe6/4ao3cM+7v+gE6tJLrpj2520gfSE4yQeRpEUBoLA+E4y1p4xDd1+Rk/8Fhq5O7hetZpFXSc9ZMnX1DuQYZZRUZBT1BGdUU3Th+HpBdDCsR9stDgsBgiFP7xVthOm+dMb0xAV2Iv73lQuSZ8Mw/C0L7uUB0qjgu1j0SCtkVCVH/p7/8VqPbvGLOSD7HtevbK7jVaoFqYe7reSuOFN72vu6t4flsKyvknLnPjYsOyAhfEw+zRFRSFjFvWL08772TvLZCpzDP1ycNYC0YTZOOvvxUWJRj5tg8+gQohGVXSQerLciRe7WOD2XAfftG2bLnf7/t6zRHqnUouN6K5Rt4+fC2wdHrgL3FjtjYby3Ph1vmOu4fT1RcQ1c03QIYdTY8bRUxPAK1lTMXWlYdye/nm1MO0kvfgPZTi7iOHDu8T9ucNJIjUNMTUpNRJRgGsMg5o2cyNZLleviERi7LYc9SQ0wWgXFTJHoI9ZdHZlyx6vCN2cxT2Ffx0qH4uD4kipxGUqLuXKkYyftXrHeJ8MhgZ9QTYdQM1UfNdDyqu+czl65Ft3Po3soFcOoIIwmg0s3wZ3J7MsSxYlBBFrqgSDlfkMPsblaYvtaTWzQL3IfsnBTcvVIsKBzhm9FmEKUUhhL3tgS6ag0cFoBSGhJUhkrjXO3B7Bg+Or5bEKOxA0ZkEjiCyqyiy4BTmMvIE7aNGm6ayh/j2x7SfJQMx6IOkDDonaStViJiBEJ2IsAQ4vXPDkd41Piyv5xtYOFKV6qMmHVdG+aQbnS43lqGtSYA1pYTrAgt5Ws1H8q0gyUG677cAx+Nj2kr6ilro/OcFl03IjWs4KsPfIpZXoJnhap5WzRwjNm+PLf/mk5hKjcK6QvlCPuXeySjTpVL1FvYzAHiXsF3yMdpdulxlEu/6hICJAzAn6re+UqaSMgsWtZvodv0fDhupjaNH1pwQAY7Ag9mKqWFN0fAI80K9YPOtJMHO+AM+6T7jIGD3Zb3ZXhshmbPGlLzFEKlU7vfgrhBoAJbnTYWhXELlD5Tst60SYiqs2okE=","sig":"8af0bb1316d7577439f261ad76875b1babedb47262565eb2a8276dec88691a80dd64e2a894dbe78562a4b69696141d860ec6c9f890a14c84549a1225a38fe47b"}
{"kind":22795,"id":"b0806a8559de7756b708116bca32573e9a11d6bbdf0a51fcff4cff7628d7a6df","pubkey":"984094d4207e9130fbb6c28bb438e2cfce471ae19c71046d6950fd57f20179cd","created_at":1785676829,"tags":[["x","s1c1b5i40406k26b1f1f5y3s3f5o4x4l534s24"]],"content":"{\"peerId\":\"5LAzUDGlviJBcbB5kzJl\"}","sig":"eee99b6279b298bf32d5bd110c24176560e6144d36d134fb2c981eba9c8bc803378ccd154a31620938d1d90a9d9dd248be70321ebb4943d1e96019d40244d190"}
{"kind":21059,"id":"27bc4a662ebbc319927530f64000150760e270bdce69cb12b442529ac33ca680","pubkey":"73399d3a149703031fa0d4701ea9e319226c2979343e89c4915230a533572656","created_at":1785676829,"tags":[["p","fe3cf170bd88be377f684d98d098aeffe3438882eb2a81013ddc4e66ea00dc46"],["expiration","1785676947"]],"content":"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","sig":"4813c3412e748c9d99e4ec4ccc6b667a4140714a094914df05d111025e442bd30a594714f03cb0df3537ecc82653c7045b98531bbc0cf49faec2bd67a107c175"}
{"kind":21059,"id":"e323e7671e52fa1f93b1c38b74d9045f0e7e0c57c5eb64f0dd9dc0aa4cb7e9f2","pubkey":"42dcdcedf8f9fb5a343aca2f324985960a296f89a40b186ab2663ae544649098","created_at":1785676829,"tags":[["p","db4332cd601e12593fbe239cc5af98f27401d46012c778694c22cb147ae3efdc"],["expiration","1785676949"]],"content":"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","sig":"269bdd6b0a63d8d7842c04b8f6e743848caea5442047c3bf9aef46d389be02b61d6b43a2377b33f6ed6b199c64f2775120447c2281560ed6cdffd14c33192476"}
{"kind":21059,"id":"80b6cea3ad0b0abedd667b18d3804879c9b053bd309d388e58aad701d3c4e129","pubkey":"764da1376080b4c50b442e8682283e911a211675ac019a354eb4abcb3e6febf4","created_at":1785676829,"tags":[["p","c90adab9315b97f7bdd9bff861bb026c9c454be1f1fb3a9d64812b75ac5e2bcd"],["expiration","1785676949"]],"content":"ApFrplGKRegai0HKJh05GfZpgK1tEFKqlMh5fp5yHMcyopN4QL18kX9qiVsfry08clgYtdpqV/YJ7OltdiqiEU1jf2RFWN0Ac5mYYmpHZjFqOvlwcePB/XinLPKILe7u2ISg+AY5/Y1KS0r5ZCGzbKQBXB1Lz/jV7Hq4XMVnDHSt4ruW/MpBMLsQxkIYBe+AwoB0RaW0d9FhZeDiiGnCWUUpx8P+K4a8vnXnYKPjKl1RJxJ1Q96ZOvitcZanJLzdGpK9ex6dxFqakdWLwo5iCiSHlZeZfVfhDIGn58F3658X1jmxZe6vomkLx9Iz2N0reOgq2OoudogwXLyXxULOPYHd4lKg45DptZr19MOmZ56oi7FeH3rz+dt9pf+vgVU75mMNAuTXRFO9Z1bcZjtAbiqM57d2PgcVNbKqau/OuyhM36Gdvm24XWvgxxK1epuLuzSLrVZa3ZWYI3TiOzQ8p8CpNCDuFG5gt4lieVjSiwCZ3+8ZHI8BNtMHXXtN7lVI3sUaUyH2d5q9V27adk3zV1V/kSeMs7dmJgdA2i+CiP7Ywt8sxJztN5vfkmcejTYJnI9BzKHUKJW4hixPzPl5AN8T8tISvDjNW2zW50yjhI8aIAtEoQWUXgj/VoHfYqIImwenFTH+GDJjLWxs7nQSj8HP3xbSPJl2R9/P1UpAzBL1ZvFNtJ9KK4i/4WWgyDPPMwDW3iFUEFE60kK49SWmQecv2HyfylwkMqBzbMD+qR6/G1P0vnLPfvZaTGIFuU8m5adY/ta55WtaAnlBFC9pQWzzj+Mw47G2uX1nzf/MNheDBxhQWax7cBRhFu+9Mk7Fw/SuVz5qVZC9txnmEleyFrnlW9BvLBRlifj+j1OW7k3pAKX3jXB+bn11C20EDDI0cTHmdH/XcFQ52adGmlNz+UHgNhTwwvrnNuhoeJKGieQ+t9f8FHDL5m07waV+FMgk+wQYAjCvONsicRe02SxOtVFN8AoXy5+zMvteiAkE77XveWAICcXwDnAsyQNAS5j7Ivlrzp0RSKl1zzE3LNb/SrT/II0SOly//Fl5hiPBHHIvtcPZzpt4kzKeXBLy47pZ90gY00r3nxDXKikMLG0ueF5UyxM8e+tJMFngdJd5e6yi7PpOEhx0inj18fFix6BoJfJirzDTGtRyV7pfvimLysXQ4GTD4lUDL3l6YN2Z8RPHZj+nun+dv/qCIohchCN+S8+jjz2+98ojwpT3PFuwe8Tcb1gRxpDsbu+yYzNyz6uep/+U1s/0Zl53LhnkBKNpBmsVXlddBuncW+eDdbYJVAw2r88aeIFLgHzOgI+R3evXfjRjCvIm7w+SxPs0S+aVUbl5hdmAtnDspDzzlNWuim87mlgD+Zj/jF5XkyEz7x6Q4HTSJbScZ0gSkRwW2S1a265Mq572mHSKUvwTh4q/PnguiFDeY0xmaNuICosWYaotNEbqkpEbrn3amZhi46ypCP9WWn4uVaTyLQH1rKw+usVhhYIsYCQzc/ycBAQYZQxmSk0nOD81jUZf2mU63C0Igobvbm0+Miul1SJNmFsuiGpkwRwp1jaCTPHjI/ymZnbDMpLX6RHy8Fdx4k39VJv2m8XA4c2Ke35HQNDMhQpPQixRKv823CCWmWtzTAgNYi5FIetUS07TbwGNilQrLttlRZiAHKIdjxpzTyHLVlpdObLxv/gQNIG6tzOyGtoXco6DwzZjTdpzsoFofAZyGiEWvTaGGAjU0XFcyF+B51JViu8b9Ww0YPU6t6/k/ZbrZYgrfoXxMRpyzXQC6tl9lWHOoal5AnAsZol5JV4/DqLyPeSm8BPNyf6xuUx/EtiSQRiQbal2C5Wu+kXcjPv/VS5iQ0r35z6CCdK9BPKGHIFdpF/NkC6K/2hXZCAJDzlgSYG6c6nVuf1BC2M/zzfQ8WB8ZFRqAoTDexp4trTpVvKaZOj3uRR5ESNGajmCJWrfn8yuHIA1cEVKLtsSebj1iWYujiw6UH0U2F3PRsnK58M5FY0FLi7/xd81SQgeLNkZ8s9209C5x0tpaZQpCFa702VZcFXle+NpZLHUTExxOT06SAQu4GpzMUGT6fPK5vCCRrmgSF7sokxvIfv0QO9hb5pMPAHqCjfKdpZUAG5ygSPTq5lSKxdCZjjVOlSExlS4WkSIzEmdZkYAhW6Xa+t5bxmNFxaNKhI/p9ogxfmpppsGm/rKpy6vL0xiVJxb18C3gT5EttPnicWiudparv3oKRR+upI95MPOk281dXnyKChky/RkF5vU3sISIMyDBycU1X1A8y1q267zkmJqDoxzbOEqK+9I0PGTXGUd9cB1iSOxeqDPn4gVJf//vcuwuvR+opRjKTX9pj9k7htmIsbOjqVhEiWAHqL6+AEDqnjPAun/zJnWoVWwPo3tGgSd//xgEnRv/soEQJYZQia/ngKdF5lBme2MXjw3N2HKY1zUmyBqNoiatgzBwZnhU5VkLFHBm6ienJoqyqE7euC1NEmfFHshhAwgAYgZi9c4C8NlWPLe6D+ZQNyLE5KvV52fMAOiZpvNbtWAMFf/kk1mv/8p5EbuvHSlp8YdIzOYlkvXstBUMeFAH66RMK4rXoomhax5U5oiqpdbYMFjzhnNmayvuxFNRECWlo2StIKCBr41QjjpzQaX86lKU3c4xuzP02u3e4Dy9ew16Cf2lkNZIeCgVBK22MwQMe8W0a5zeVEgkLPzd63Do2+wxpvbb4jgaKue+kllInNUbKBqTgXK3augpjX9ZiFdqsxdrS3hC5hRSqzLx+ESG8rUKwpElPhqXQSdzeVHp9UjAVNFotKG5DSi/gMsWFRN","sig":"30e2bdf73205825bf3a19ff6d5b0fffe49b2d427470d7670e9464628f58791f491b41f78ac8a137ebbe64fb0d603c1b516e857ce8561c8812e53c04e313cb3d5"}
{"kind":21059,"id":"7d821dae2407dc0ce17b7be76558fea2a7b42332a80fa26a775bb481e7ab4936","pubkey":"51ab81dee29a9a2cdf3adaa4820d00f4018cd540d6c4b6f5b7cd70a155193e7d","created_at":1785676829,"tags":[["p","4571e6236c45bb75ca1bf6c142b996d9d451ec6c3fa14ce279dd53a827b6525d"],["expiration","1785676949"]],"content":"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","sig":"d2ecfdef11672886570bb8da9f00e89ed488351faa377f7cfd9625455b752d6765a1415ef351bce454bb4c8506e9a7795572fcd0ca3799a5e1c88a96ad164287"}
{"kind":22668,"id":"e5bd8d0ba4903407e15dccff8c3847a142403fa8808f08d48927eb1989630530","pubkey":"011290d507f716e7e28b78431c6dacaef737ed0c296eea006b3aeed76162aad2","created_at":1785676829,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"p57BF10Qa2QR5CWwRPwk\"}","sig":"bec46000893446903a21a2cc3c153fd0caeaa2863d420f613d5c86eeeea86252a977c415c08782e52af2dd68e12ede07786e437f4b4e5995d3cc9dab901715e4"}
{"kind":20001,"id":"0f03ec93ee1b132d83fe87a2a11488a2457ee29c32d012ef86d6499be2b6aea2","pubkey":"b5c861e028296358eeda540fdc4bf0a55620fcfbc8402c2aa6c0e374ca4caa93","created_at":1785676829,"tags":[["d","781ea0904b118a95"]],"content":"{\"e\":\"🐝\"}","sig":"8c1f43b54758888bab77b247e125d39cbd0f851a9a866e8cea094740fa972dc3545245e97c3b013a10c4ec85ddd162698c377e4335b17f9e3c5e4c834e5d8469"}
{"kind":21059,"id":"efa8558e04b1018251c1ca9f75ade84c3665236ac0dd4e7531a5aa4ac50f382e","pubkey":"61e46286db0f8b3a657c97a18b24f941c2bfcfee82e4dee7c43a27dc73d9ea9b","created_at":1785676829,"tags":[["p","62cf088f0fa0f198b341ce6c3c4d16f1bf06c4c94b11b54e5e7ca6290e22e94f"],["expiration","1785676949"]],"content":"AmLSxTS2mYEK/k8nTcs9FEhfm5QWw+UyMTgkOIzwuIyFUuAo7WrTnWe0m8XUKDCbzd5xPGCFOLiLsT5w4wzG9JNXYaO+XpvqqyrV2gvrnX6hgpu55BEU9GnJKbHRAsaBqSdwtilO/pRpC9xRAk15lUawcyN1rRhc1Gqc97vdnn7fTDXb2r//bqouA3ccMEs5YvwfxUqy9nvbqoY0v5LwTxH1e8p4MgR/PE1T0WqbjMY5sL097rfe4XwEW323GaWI1QWV3Z0d9u8dBh0QFgP+X99KVWMgbBF4Ruzaa2DJjVVo+FOUeTdESwFVbEUwKjnsNhdW7Yy+LPM8riCmUWDY+1YcWOEx+GXcnhLMTBvtK9m3fE0c9dY8GzMxH49mKCQdmGhyfeq/l1YOGJlGf1tY9tUpzJXKPd65cxw1OyLsiLRb9x+OKDCtpDJfQRs6zTzxFfIylvVb6W12QYh8Q9rajB3gDpHgdMkUnFPuk+TBa8tor4HrAWyEC0HBi0MIo9sdw1JQoBBk+tMclytw3qC3SdyXqoBQDuFFlmxbNzZVOe3vs3vi9Bo5+VJzKJzSfZjvmdSxRBsMzuRDbizLSnd9G32d++vmBNKKOzMXp/l9DBHXEdx6agukfIOALTcCGsbKLdt2loM2QBPerbjjbAdisn0zIrelJOKlyFVRdzSYMsVKQN9ykdN8dYoagSTE8893hmJyYDerEPv7GFDpgZqbsSn+MiKkQpRBpn+4fSEqY4oU+kNeR9LMLEEBnw6hiydoqZ6WTz/kRsdLz51XXNdN9oE3p7AF3RAKxTb2RNO7D5jUxx7EPxMbWHjkfno5MvOuqLSjfBn3mpr+9trRwlrgWV7YGHRWlrp2TdWfuy8EuBUU9y+sRQrMX3/DSxZlsAgK/fNtBCEaRXy+v4BdvvnKpSI6nWgcnN3syqSSjty2A41J7de0ynGfFYYtXwx6glspwK3EBDO2djCwN2hsUcb8XHyJyImr24GbTzW2QUz80x0qbj19xjUYVEBNGxK8jJbejU5J5T78XlJfUQ58fUgG1g1Ui2ZbvYKUB3rkP1pnkC0v1DFHHuKKVwom/mp7H0+kDKYNF4hT22M+03vp+6bvBqymlX/Ek6jAKRpNk6rHsw7rS+SZ2ngE1MoZqF653HSvJRce227V/qwBOSxg5wSd5/JsLO8XIO/RnWI73ftPVvWxSNaVITNQru/nAYch3TjHGlIncLgC8K7PTYi0H8dGsWQUENy5Yyvg/VYRGej3NcGqUhEHxhnacpYfZGKzDPSTO4BbjZAxFnzEVnbU6YSMuel49t2IRKSnSljsJ2U6TA+kPXoK/DFsB4GL7UfKa+UH5bwMgON5RjR7ssluJq3L9FnwqPoa4xfWlqbt1hIhw4DWpMx/M4escnaKGWf2fOSjFHBSBDMet+Rxt9IFaDbZl7kzw4Saj6suZzsUkHjoZjiasCOQFEj5kFSi76RiQMpJ+wwyWbgnn5beXeSbIJ/Wj1QJ+FlUhRFpv+8RCaEybYDIXU4ZmpsBjxgU37REHpxiwS5Qs4QsMMIrI4V0ngP2zbDECPwKK5KLWnS8YcQKzAKKGhnwBxkq8BuWn9PsNBQjUhV0pCGV/wd4m6ysfW0CCogHMjLmqEn+r1URyEsZP8wnal5+xFPhSKrKINDcAwSwqSp1HYT2Z0b+b0N/zjHbTCXDi6IzC7gcA+4gYDUahiTjW/GG7ex05XfdsJ/hjJVuRY2t5kINatUmcunzXl2updETHasW0po5UyPQIzRGywiuVn5hXwoPyAzH4CWe3YmZbi3YEOsWl1ukgT0uJvE5dTl78QqXDtU+yLX9xr/yWP8DnWbJLVCcgGmUyJhIDTrZ4sKnQH0/pPg5pCaZHd5JYFrqpCw14VB83uN5csPdLL2zrJQILK9hlVxQKor7A6NHzb4QoJzf/L2QzqhV0FtHUyjU6OBsneCTE4luEKdJf/V59RKsq89TsOiedKWsIGttmPE2PhXYIAbW4bBpL0Bf0W0H3/WEg1E2E/e5slyd0Chu5lGroPmmnIgo05CsA8JnhhQGGe/aCgDrRaXLB4BvgkMez+ukkKzWkgT0BMcqRJx96m5ZUUDUSHr26icQwJxCz1mYoB/hO/26gdU0D8RcwvhC4cqaas/es35veP2l0axIBmzlPA/UXqczQdHAsGrzLfJzkDkBvW1OO8F5ZyoyJKv8U7EsympI/jrhuhVBnL0t7GJ/gmdivQjuVxKwfTcDIU1HClqQ7Y2VsVLBFFVlKk7wxS9Sdxi5vaDxPByjskY50cH6S8B0WTEMrbkXYVQngjFzMHwSCynDbJ/hOhQXDLLsIRRMm74zZxVNHSPv9Dzboz+nIoo0p8xXkG3rmR0239/mzrfUaGHqZC5RaHxMpqjmP+r+GhHFbXOFzxyk9gRdgPDwkn7T+wviA0EoUIgX/E2YDt6bNh8ok7htVQMnqPoYSs4WqjmzyI2brbwL3D7+SmpI3kMwk+l9OOmr6qthOfoOKv9nPQxQ3+UE7cRd+No7QEOVT9FaitLYKv+T6zLeoKwffwL66RrGemokBE5fa+dE3TSfgvF/m206uS3mrOOOZqwFtke7dUCQm4YGsHviNBSViObJqEQ/NecGQeaU2XSuC0ScUiwCRpK6gAeXDxTsLbvScgc7caTCu9vWG5fzC+UjdWyWhEEtnjtMAzazA1zXIVQYJzsqOi8z4Ag9x0/nvURMZaJncB6+mP0tbhquJg1Dj2tKdHyub9SUOmGf4F892KhdtHHhLQEKTVDRiTe7f0xbw/p/JF46ZxE3cm5rOpaevOiKu32QyYTXnNTQaihk","sig":"6d06c924a3cd1d73deaccc52a86ab472a3a30975ab54311f5e3c83d0896f18abb7c921e5726317532de6382bec35f2237c4c225c03abb8cdd52fd143a1c0e93b"}
{"kind":21059,"id":"cac1ddbfca49abfed7106b5e8b35799e803a7323c88f4ab9311c55904dff8038","pubkey":"cc06b791544dbf3924fa4839b43e6db9df75329b67278fd32963cd19e9cee582","created_at":1785676829,"tags":[["p","f3d5c038e4ce5015ff2069c5984c8a47571a427b40e120d5c412e6ae6647744d"],["expiration","1785676949"]],"content":"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","sig":"0648a3e97630d479672873ddc45636b50fda69d79145c6cfed77d518a27808417169dc87ed861eb0e6ca1a8bb274dcd8cf970b37cd3f0d42c09ff7d71be20e4b"}
{"kind":22751,"id":"cc1b2bbb1bd8ef560837d280d1510d93d5199aa019673d00a871aaa14bf4c848","pubkey":"3d5ce431f2987ab0f0ea0745e5703bac780ea9384e90c8a198ed5dc8978f1ea8","created_at":1785676829,"tags":[["x","5bb4v4t492d291p6m82o5g2eb2m4w1iho1h"]],"content":"{\"peerId\":\"KInutrde21v4Es53GmBx\"}","sig":"b3875007e6999506f815c52162492680a92f5323dfa51358a5481174692a5f53cee4f8b6bf295f0b69704c777c2d15963bc2fec70d5cb1238569e3189d1097a3"}
{"kind":21059,"id":"ce16cb9b2bf391663f91c94f158745bc94760d383dabdc35ffe728462aba63e6","pubkey":"d5bccb32f8efc4810802306118e3a2e2bc39b690f6e1defcc06e007b3da9e591","created_at":1785676829,"tags":[["p","2651be916e121c6baa67236dc6882371562f2dba151ddac18948b8edd88c1321"],["expiration","1785676943"]],"content":"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","sig":"67272aed2ffbe1417bdeb2362a9d49e6535253b5792ac600ea3992395b7fed7cae545a5e14ed4a0f037390707f5d0e50c6132b07a480249df008e13e724b8954"}
{"kind":13194,"id":"3c0da6d6cfaa5f198bf2dfda454096968434028808a2e29e760a63a824de62d7","pubkey":"18ab43661d7848e6e3843a09c6aaafb007af2ce2ff3481f81469ddae1e8e8fb3","created_at":1785676829,"tags":[["p","18ab43661d7848e6e3843a09c6aaafb007af2ce2ff3481f81469ddae1e8e8fb3"]],"content":"pay_invoice multi_pay_invoice make_invoice lookup_invoice list_transactions get_balance get_info","sig":"29c9cef993fd1012c2a21691671afd32f057f9364eed31b40f0906348ddcc6606aa5b2e4ef8aaf9198e8cc97488cd4133ae650763a3b8da3293e72e07c9801fa"}
{"kind":21059,"id":"38274f3cf136f9a19359e866d55b86e661b9c1da65c9e826c08f7669e84a7337","pubkey":"931128665954f1a3f0fa09a5e3f371ab192fe840575daaf43040453d838285af","created_at":1785676829,"tags":[["p","2ec845714f95e64d1de364181734c2e58d33e0534c33402d2bce2c0c10a53b65"],["expiration","1785676949"]],"content":"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","sig":"77927d477d07993c9ccf912e488b3384d69aba61b70f13c85cfbfffc2cb3fab7d384ddc4b377470bef2a93ad4635b1ebb74d05dc0c6f3f21aa15d4b6385ace43"}
{"kind":22668,"id":"4d59c8963f927fa9ea3c116a710049275033c52fe5eee1c2d970b1e55a021f34","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676829,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\"}","sig":"7a569489f112258890c563d4924c4f7344537aef198f64a7cc0b9f32b96ec7306ea98cfe1fff4fa6ac2a162ffa4944ba3699cf2751bea61b765ab32b90122651"}
{"kind":21059,"id":"5e5b504bc37e7a30f376cf1ecc6884e31163dae41f5ca3af1c7d5462e972dddb","pubkey":"fd5a12fdce164794145f3a5c4b35d798def92f98242ff4609673de9214f2895e","created_at":1785676829,"tags":[["p","1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8"],["expiration","1785676948"]],"content":"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","sig":"dd1e19594cf2758b966c27f773e20c5f1ce5127e7a6c046f40df32587a6294e23ce91cbcf789f25ce93ca17408ba16d17a82449d734c80d328ec53f096047e74"}
{"kind":5,"id":"782cd31915d712063037d3948ee8e7aabfd7560dd332227d502ef641cdca1ce6","pubkey":"b454400c0f61af322680e46825a10c69aaa4226be2f474dc21fab2c7271d4ddd","created_at":1785676829,"tags":[["e","9aa8c8f7a20f62091f9b3ba3ac8262c9c2bc1a40d87ad1c75f0683c0e757a983"]],"content":"","sig":"ca9858bbbd3a7d46a9930122981ad8402cb302dd4a1802df2270c115c12971fff8cbe9b740898d5f618f3f0a9ddcf79ff8f3fbfbefebd21d60670ce15982bcf5"}
{"kind":21059,"id":"3aed713425ab4a0459176d4ba589876e6a313d879fad18152a11347c22c1114a","pubkey":"842130f8fe9ce8aac0eb1e1f310de93b32e6968a5553dfd63eb5d8eb7874e0c3","created_at":1785676829,"tags":[["p","1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8"],["expiration","1785676949"]],"content":"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","sig":"aee0be687ade030557e86ba811b5d03c590fe6cd2a62085015cbb4391960b6a24fd0d0caffc5ebc5ba765006c3d98a5c847b15bd81ef38617d491a6a11cb6705"}
{"kind":5,"id":"137d08e7ffb7fe1cb7bc8b2be596e6b65d5699f7cd64f13a23bf390f70807c90","pubkey":"29d1d9e11edbdbf04538d90d053b7b2d32c1c253fc3df1893c11b68826630609","created_at":1785676829,"tags":[["e","810bde802c36ad6d9aa0e017ac0f8bbd6e66ad80df5b9419c0313be8054f2d48"],["e","9d28fd6286648d315a0c47db82e20b67e985a54f10eef09745d5cf0241b85e28"]],"content":"","sig":"bc21735717ac3f284a372f2bc72ca9d15badca8f7fd8016ee89015ec19d9a212f528fce8b16a98b62cb5108f86b5ae2698b0049eca59f70b710eecc303765544"}
{"kind":21059,"id":"16bdf72f80f5945fb5e08e5f50701615cdf8d9f016c8f48a6f8f22993da33080","pubkey":"7255fdb4025a1afe269132658221bc6e698108cf7b43571e929cfaa712063ebd","created_at":1785676829,"tags":[["p","1ec71c39a30373cbb10e91d6efa6ef1e9dad0756d610c1a4e337df016eeaaff8"],["expiration","1785676949"]],"content":"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","sig":"e113e7856c06159dc5cedb6262892bc5014d0cb5fb21e749eb23509413548dc8df95cda93e1dc8833a3987e5227bf51c578fc990f3689e7c0fef9da250230350"}
{"kind":21059,"id":"9aa8c8f7a20f62091f9b3ba3ac8262c9c2bc1a40d87ad1c75f0683c0e757a983","pubkey":"8f3f5b88fc7641fc8cba3f85d31023c07562c05290bd8dcda72efbf444f69926","created_at":1785676829,"tags":[["p","de2474ebae76da5fc0dbbfde5128465ece9adb6174dc5210014f277e2c54a093"],["expiration","1785676948"]],"content":"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","sig":"857148777b63e5e0ee1eeb98b12539466cbcd35b21514484fad507c6230b9e71d3d565af406cd8b97b0966f7e83bb212ae21eaf8a57162b2f47bcac7c293e427"}
{"kind":24242,"id":"443bf61bd52ead74bbf7f3e0d147845f0ed9edf680334029c1252c41a58caa4f","pubkey":"0214d7edd25fe949a9978de3b36f90f7a4ec432dc9753087044be638e7f2d3a8","created_at":1785676829,"tags":[["r","2145c86a56b377b435b1a17888dd93707e0991ed524c613f9ba73541c94e14f0"],["t","P"]],"content":"{\"t\": \"P\", \"d\": \"VeNHJfdrPLMeFtNrqqVREOclr1Pj8K8tan/m+4DPgHeYoq1KI2ryYNcOurYaDy13Cq5YEJGD4EZtB0CxAl4Su48pTzXdmuSs1IHm0lBYtkHuxEhUZigIGi9aNYY=\"}","sig":"c350662d3c997e8238be51564efb0172005507fe2e73f882bae32108fa272d6c4fa2edc55866d899b01ec3944578133092220e5451cbad4d5ac171827ab1744c"}
{"kind":5,"id":"22f5949ec55c1ed8ce97c6d7d2bac71992efccff6b5eec37d4329c5f44b79f4a","pubkey":"a104303328d41ca62add479f54d19467aaf0969ce09024e0d46dd577706c9bae","created_at":1785676829,"tags":[["e","958d7a6b0092f0d0e331b2dbb7f197504de75ba442b7fea806be2098c7c2eb2a"]],"content":"","sig":"dce8d65f9939f32c404db12b3ac29ebf01d28bea8a58346a8d886cd174e799e46d945c97d96bdc0ba4345c5ca44b6e9827cede44ac10efb931bb3f831e8935d2"}
{"kind":22455,"id":"173ac3706d5caf2f883cede799f54e93a1b89dbb4cafa1885703562208a2350a","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676829,"tags":[["x","i10614r6s6x481r3l6875fg1631u55a564j"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"214,198,243,224,5,154,106,180,224,102,15,15,79,30,115,171$ff2wqB0tK5EY9LkoZ8yHi/oyz+MfFpi/6sLjRP++zumM+33SIHLHt5lk9bEZeCZGnWaJm/xGeBLkAjrIvGLeCU4T5FZ3vuFvQUWJmo2MmdbCPISD6scgXXpVx854ExqWcJjRrTa6eFY1/2bhJZ1/9gxjPZ+gX4NoXcQpk5FbTFI9scY+tokJCGS/VWIt9on3dKA1ABXvfHUaV49hlTi2VmR1/RZsz6UEzSdz4RJ1erFZer0R12CGhJ5QAg8YTAcfbFe7D1gKQPb2RnFUMNyBEHbJNys9jPnq5sF3QoA8JnxgSZgDs0nttXfmuTtywGqY9zvCNp3kV/n0BatZUlAyzhcPgAqlkA+I6L82hT2vY6Z2uUJOYRi/yiAswm7BU8PaV5vkgViODnqANVnnOHHyDpRrMhWEKmFuenxp5cBs505WkauYI5gSIuAJCE2aWhcQs1jX5uGMU6UsqJkUv83FdEZ6Syi4EEVxT9waIZmBuNE6XOKn2WG7DhY56FZoquzH9b+Nh1yeJiYkBldP5AMQhmzUHK0oP0iF1EfmxW8D6sJEWYhR+3mNkOF+cITYdVbwotN6Ijpitj07yP7/QtzeaWENsEJ4hEz9vODuSkgiFVNX8bwSjwHoreD6O/71DSsHve5WUe8Tld9K4xMv7+Qwe1pnbLea3F8qxEfBAnJVVpVXUZ1u97NuO4bsQ1TduEH+ie2cFE3MKZq16IIz3+8+fjMa0DO0UrxaBa4dX1au6Yxgd0EV42gCEu6uGqSnf6VzTjBwMoGdQsDK4PswZ8M6052ik7MBBYbq18VMm0eqBfhN19R2IFB6s9k7P20gzQYd+r4no4ynERuOyJrGKD9RrSJ8L9Ta8n+5ssFcHOFrFAehxoD82XWUtv2tviwLvjKUlU0ocvbiHAOssJ60cFha0Bt+zJqlPjhjH93MciW6Sk3FeaFsFZteilwQI1sn0f3rmk5p8xXFG7An21gswdcCEXnssKxLi5dQ5CsBD3+qz7sNzxHALi2KnVWZbqhi347XZ8HMcXeGE3HM+bKkKJ/RkJ6c0ofhqAwp5ign3Njtey6hLkFKz6sF9Oh6x6e80ec58nAsIQxRApzLgojJ5lUKVCb7dyhtJpkeGrCNVuVqiwe5dttQVNeWk8HHsbBznLF65OKy7tJ9CjVRibeybSLpNNZPDyFk2H4UGC1jSbwRWQM9kymWRruuIGrUdLLtvHtbakA+RVDItKHh+tuchA3ghtsBw+0x1YTzNg/uewokU6Ts6g1xcYn34PKCuoEbAm7B0xijw8HRaUA7/1lSb6XupDcddAMsdXDR\"}}","sig":"b4df17fe73796261f380dbf59cd4c253691f7df7c5ed02ea7ce17ff08ece4e1244ae692d384577c3d5a4b3a52ed1823f4e21a3b709fb8228966e01fd9cd96446"}
{"kind":22795,"id":"51840075a161979e0960dee2cf985dcff29ce8024542224f1e8d270f05d266dc","pubkey":"146c70d40e1f4f96528309629258f8dc97e9a61aa53e54e13b999ba5d2276968","created_at":1785676829,"tags":[["x","2t6r5t3g2w4ke3c5q5fe155b4733m3m4d204h"]],"content":"{\"peerId\":\"9IVDCiG4hlib9t5iG017\",\"offer\":{\"type\":\"offer\",\"sdp\":\"223,148,124,74,154,14,67,150,164,85,250,22,207,70,113,66$GQ31axJJQOIow+lfqV1YKLNxUh+V1rAioseBNHgVEhYONSeICwYKTUGHaVGQjflxRs1atY42oGidpCqpxjDXdpUgIQb3/EzXDCEydaqqyLy5l3m25dy5k7ufZFX15VcXR8wcDi0RYRFN/iGE9mSHsLV5w6lQqv3jpTsKAeFYqwCkvzh5WnUwM8PvmR+PYFTXbb3HXYwJp76QGR3POTyJxvijjtjkP0Or3V1JrPLxMDJIeNlNjWi4r5CzFzO79ymTE2/5b2IBlN5FaZbTYxRvICaKVA42gztpDDUFUaOfsS74xCun+wQfnwf4cefm7h8HochY45m/43oZ+B69kHmQlXdzRDtxPyW4Nlhakp4+fpvMuaO+AbAjLuJeKxOXbBdheXjQ8WBJyJU6xXgDmUy/x8cSJtMfshnAR86ENlKSe5TNR4ZGEkDGt8ZibqtHzr3U0lUFhfCNTnqdGhPKPFCr89W6ifbWkY6Ap+8DsiqzBZdS9Jm/rj6dBpADWXu1eCH95TNRUhA344wAGCdLITq+5HFEG3U1+F9e9Kv9anp+QXFRo1DSs9sBdKIzH2GOoYgvv/amzP0t/ujF0jVC50YbJ+vcD2Mk738T/RoSw6fenLHR+vuSo5GEQZKfIqsDnLol9MuHwilistA/EiTl+g/5oVoZfFz9Ka+paRsyE3jVx/r9q0/XqGqCIVGPuq8Zq8Q9upXxHZJ6GDWWDnkbW/Jdh7lzkPEgPk8A6mhv6tKVo9yKPGQvcRC6ir65pvnpr8LkcmGD0ZcmyUJZgcS7QYtHdFr5nEm97uOkDEcjWNsEY4BGuZLquE5YXA/FEKT9OfF9IGANGeFCejUxYC6wmfBgNKPHn4y8dwF4O1VS2dyzvIEMeQOWZfLX18Rfzwer2DUqhy9Vug0UlH85Zg7BO3xsd7HckWkGhaQe9/OoYc/J7qu7kgGGaJEot4tkVx1jqodqfQ8231pFSFYAPzqO7ARdk5Iah+4o5leT7/3etL+NGI0P5NRngirLDwDf3usWIoP8rZHH3X80VvUdSaXfNNVIfGtVtVEoldpJdLMgBjjrZN4bSfw0Vrxq8ARXPF1XhziTKhhUknepY9kinMynYwebwmr9jxFJhFOvrh40Pm1SNAEegoMxrBoz/BfSUuFlo4ZUOdT4H29npvGYrQQ6icPfu3ksOI/a1jEzIlf3Mi97gW3q+rVj4vNVx3PoqOi6Ndbp/vSqa3cMNJXXPfTQ0V8w+4SW/PwyVqnhIl01kqxjNdtdis4EMQ4ceGJ3MDbgCdLA6lG6tGvogy4V9/esn71GfC/fD1Wz\"}}","sig":"88644bb037cd5b75c311efc06f45f39c6e8100615abcc08ed0f285ec9e282733a88838369e318af75e2687b7bdd6d0904a4bb90fc2f16027755d3d060f5e30e7"}
{"kind":38501,"id":"bc1428364c22cadcdda9e97f47a30dd3da07d32bef447c987e007e6cf33b7cbb","pubkey":"8b46fa734a7230497197cb033e6f10e55fa400e90447cfe5651c9ad30a9f7b4a","created_at":1785676829,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676829,\"enc\":\"97f504ab5a031b93d29a813de02ccbec80f0345b11ade65b66cc0f89c8470920\",\"meeting_node\":\"90.156.222.107:8434\",\"pubkey\":\"8b46fa734a7230497197cb033e6f10e55fa400e90447cfe5651c9ad30a9f7b4a\",\"sig\":\"eb1094bc2e5aa1c3d49bb21f9ccd24b96a8066e1efe7400d649c97a03c7e599b1f5c820dd700a3502b675c52513bd0f2bea34a4dc41fd4c101dc621e4971378c\",\"v\":2}","sig":"2053e323b44d3fdff3f956edf9a61ceb3651ee517eea4bf11adbe7e4438fe63234aa7304ed3cf5e13455ef6037576e89c6587a771e514540624b2f4157531e27"}
{"kind":20001,"id":"6c2fac4c1a3d24f3c83db08fd810cbdb92e47b48b9b0bb3e54ff7081882250f5","pubkey":"bf5ceea34cb45bd0d4903e83c62a49ba1c80d589934d83fd4b2c616aa7c3df57","created_at":1785676829,"tags":[["g","u3"]],"content":"","sig":"ad150f48ecf1ccfb096cdcde24a846a8396d1f8586e238a778916836c78a56752070c2833ec655350d9241cabb4785dc4e48501dba5ee1b35ee442b80f677de5"}
{"kind":7,"id":"c138c6daa6626d75a2621f1c15729f63abbc682773bbfb278412b6545131ac5f","pubkey":"e4146d1d7fb2f7f6e6b3c799eaeaba6f7446fd28ccea2592671cb239a13f4d81","created_at":1785676829,"tags":[["e","f148576d2a0f9754d8006ea10f31531c600ca6abbbb64f92b13146ab32316e12"],["p","c79200d36c345e41d46c135b30077b2f15d43cb286069f1e4f2e65c09162a900"]],"content":"+","sig":"f02914b8b9345f45f4ee7cbdf7e058e3a15c976436df11b71ebbbb7f62d08bfbb8398cfbd202095bf9dd3d4bee6ad597d9289d12c8bf32a8b57323666ccf5028"}
{"kind":5,"id":"05a69cae2f6f03f85d49ce912ffb234405ebd40b4619afd0f48e79f4c9afaf8a","pubkey":"3b6b4b29eb5b65aedb5d5291ed382d64dd62c555911380bd0dd27f160446a025","created_at":1785676828,"tags":[["e","64b710cf656b37c7d2df8935752af4914b88f8bb1ce3536c1315da43b72b6820"]],"content":"","sig":"f62372888a6e0ecc9b865ea00c18425f46984212a929441def9ca841e7216f95e4ceaa6eb39e25dc8a533d3455dbc1191450c053e894cd7a6a752b3026694d86"}
{"kind":22850,"id":"d502724e18c11af192ee65a8798828a5839de91251fb80dde196e09751600622","pubkey":"972698b75bab763abf02e914c05ab10ba92946d949e44994914bc8ec0f0c8356","created_at":1785676828,"tags":[["x","154a6xc4s1134333k6j1w3i5z2u4x2g27363w6q"]],"content":"{\"peerId\":\"E4BBrrzMaIncSQjB5BIY\"}","sig":"ce077337ae276256eb927f006fe5ebd9062272852330909b8e372176ac17e9cde6c75f95a91a8a31971fd50e39d3f09bb78a8469444feaf934e220ffe295b1cf"}
{"kind":21059,"id":"c70dc8831fdbe3c9e881ec501b0164baad462fb4cdf966b232771faa15faf8d1","pubkey":"f6618de562d05fc1544e473cd2de7d19172886a84413f09be3580091b0c6166c","created_at":1785676828,"tags":[["p","d71691e02c10d3564530a86b51598532eef5f3d62317ff93cea2c3b9c4d4526a"],["expiration","1785676949"]],"content":"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","sig":"15cb4aa077947993d94481c3ec275fb4905a66a0b0309ed2962170c104900322ef9cebdfb10c28f55e91d2ea1c5ab66682d570f8146db4b1e35ce71eb465ba15"}
{"kind":5,"id":"4465287c1593ab85700231ff2bdb75b84cdcd7f1110333bb4d615fad2d73b147","pubkey":"fde8ca9b4a9d1f5d2ed8b82ea13766c37bdbf6cb50bd272d642eaeacc713563d","created_at":1785676828,"tags":[["e","5982eb4b4dc892cdaeb1a37d3329c3b11633be2b19401d1e0f2881309be0616b"],["e","64b710cf656b37c7d2df8935752af4914b88f8bb1ce3536c1315da43b72b6820"]],"content":"","sig":"806cc8d8fb44a1b2fddc62950e525f42bcc3dd184ded730235b0c4bc36c13017e86a512f98a3d005c20affd8a546fcdb99ec3c90e4550a47e1dc2034cd86cb16"}
{"kind":21059,"id":"638a053daeb7e0a7a0f36a2e2b1c0e2e36d25c58142139f99f706244fc7c09d8","pubkey":"1a54ab43a4bdcf983de90f5bcb7ec25cb61b7cee8a5a719faa7c7f0abdbbc88b","created_at":1785676828,"tags":[["p","92e744f3d9e8ca5888da31ac871ef9eed2a0b198ba08f417937cbff3030b0000"],["expiration","1785676948"]],"content":"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","sig":"134ad847b73bdec6eb45c66e2971175bedd00fb7a6c1a6c708beaa65fe8d4089fdb58e3c347631cb298e209df1a5183a0b84513e271bb8fd1f7a20696da68c66"}
{"kind":22668,"id":"ddc1eb28e2625a90f93416b40326661d5f5b627056519d83e58625ea117a5a67","pubkey":"645d864f8e4df2e7961010c726ca1dfee79c1f849e89981f38c67954ab27ab44","created_at":1785676828,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"XLa4RppouLPlwZlPEN1L\"}","sig":"9bf8af313f3c5596cb8f4d0696b8d72cd268af14bfaa38b831caafe1248eb58a40af81fb3a43fadd6491b086f811f18fa6c4422548ec58c37fd3d157f4bf8eb3"}
{"kind":5,"id":"0b34123c6c388de6d35d821348dcc9651adb2eec495f020f9bf29e32a9ee5282","pubkey":"a6cd35843a1471debb1b174b5bab472d9f5a2e2391e92207c2279468c3e9ac9d","created_at":1785676828,"tags":[["e","8290a6025d0f18c48f30f52570181ff53bb29b44cb2af6234857b7792650cc17"]],"content":"","sig":"a00555524925631aa1e50be3f5bb87518191259643f46806d55e13a5c791a3f073eb82e8e442b0979f269eaf323c846b8d3e0bb6185f622c1f734eeb444c4216"}
{"kind":5,"id":"290ae3e79db0ee20be75bb4368fcd1ca3e4e97fce13390c135f5a00e24daab6d","pubkey":"f562b3ec661872d7e53a9babc2e75aefcc3d1d658aa32070d7a6e3948dab2af2","created_at":1785676828,"tags":[["e","c75edbfe2657b06d905d775d476618bc97a68398447ed36b5b53e381ff34b99b"]],"content":"","sig":"ad8cb04b8a8d861e079d122c5e459fb9d505a4e024fca20d606df2cb0cd5a5701fff566d0b52e22d978e76eb2bfa8b115a207a1de074dbb307bb8b8c1b0bb614"}
{"kind":5,"id":"da568c14b08dd42c0455f01d8239aade88c77ce1994de16260dd198b308a3421","pubkey":"b454400c0f61af322680e46825a10c69aaa4226be2f474dc21fab2c7271d4ddd","created_at":1785676828,"tags":[["e","098b11ac3a658a5f260afe741b1e702b6ceb795073d0fd67f2b39893c9f5f1a5"]],"content":"","sig":"860316ca6bfbf5032c9086ae56d89b65085e4adb6c083f0f2a5b369cdf4951673b4a4dbd565190b071d12681f11e55495b3a354a41b85a19f68c064d3f010bc8"}
{"kind":22668,"id":"2e19b89f9daadb9436e59f893fbe4cfb06d233052bb1aa2e8e2a5f2ded14bd1a","pubkey":"983451ee5aa44cfd054418a4b4d61389f6cacfe5bc8ad63f247040506a7bc8f1","created_at":1785676828,"tags":[["x","3h4b5840o6s3o6aq23294k3l3u32544u3s7266"]],"content":"{\"peerId\":\"MOfdN8BFCzSXK2ClbLZp\"}","sig":"7a7fa69ff865ca2548336fe3bb0c8fc72033822efc524f28f0056654fd8f70ddf0c4d0bf6266bcea2d320204fd7e87b9304ebfddbbb46dd528816e7a5adbd93d"}
{"kind":21059,"id":"c76184eeacfdd807619fc4cfea4d898e87cd44a25b4f534f22f4de068974a659","pubkey":"756284430945b4e12636df19fb8892364d6b685f15724c3cd4cf8089aebce8e8","created_at":1785676828,"tags":[["p","c3d2d513091f404497181534e7965d2b80da38f82ac76bdc8faa2ee9b20b7fe8"],["expiration","1785676948"]],"content":"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","sig":"8a7c9e47f5e301802cb6eddf0c11109a161c7873aebfc4bdfb6660dd4da3d772d4c9f13ed6708a6726770a19a931b0d912ad7ef9b2289e1471e0503b64a9de9c"}
{"kind":22734,"id":"c4eb84f16a0b91da08b80674809ff770cf2fbbbea535222f71a6f70b1afaae2f","pubkey":"62131eedf03e285f05e7327cd868d6cf04567658e72f09f6a027a2cb526491b2","created_at":1785676828,"tags":[["x","2n2c211h5a65204bmik1t4y663e1k2jq1j2w"]],"content":"{\"peerId\":\"5JyB3m4D09Af6swaeL6g\"}","sig":"b025b0f3d0f2d5de0256a1401816a8393a3109bdb2446b4a4986fde228438a1837a54a3b0d9debda4528c9e840e6acd458daa7d7e88d45c735ebe0149868160d"}
{"kind":21059,"id":"f8bb284d3b89bf6edc85ac8157f4fe146b1fe1201164a094e85dfdd6bc9004e5","pubkey":"8a67ab6ff23ea573d894131bbee2f3b49dddd2cbbe9c532b0ea8bab5dde6c27e","created_at":1785676828,"tags":[["p","2b794b141e7490a4f53e23d41286cecdecdc1bba9aac19dc92ddc3984d361992"],["expiration","1785676948"]],"content":"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","sig":"726a670d5ca1bd6ce671f3c34ec0f4102cf8a9253380154b0621849290fe856b805fb69b772e55cee187879f4a4759e044e679c03455e513764ec7dbc50bdcf5"}
{"kind":22734,"id":"69a3ed5c758ed45a82812314ef3dac047107069f56f1f938dc6e26a0d87ea86e","pubkey":"fe577acc08542c36add5fd831b6ca9e248d98f2a24f9648ec71f42947e289710","created_at":1785676828,"tags":[["x","2n2c211h5a65204bmik1t4y663e1k2jq1j2w"]],"content":"{\"peerId\":\"FjdIMX0nehvvfKeE8uS3\"}","sig":"8c52eb4118de1f91350941bd08b6953d6681afce8158c4fdf2da1addf2e15e07454a671bfb8b5600bccb4544380b3f2ff49161d22330a1138943781392a1ff75"}
{"kind":38501,"id":"8bb6cd8e927c497c1d0d43380fae119b3e1de4bbed9df532ca8c85b956508b14","pubkey":"be43349f0292354b71e5857cd3ef5577e4d8882b50d5853b8d0d2e6f33cb9b17","created_at":1785676828,"tags":[["d","btf-descriptor-2"]],"content":"{\"created\":1785676828,\"enc\":\"ae5076051e63594c2bbaca4fbd1dfdeb661276f7bfc325f7b6e6e227068e6248\",\"meeting_node\":\"90.156.222.107:8434\",\"pubkey\":\"be43349f0292354b71e5857cd3ef5577e4d8882b50d5853b8d0d2e6f33cb9b17\",\"sig\":\"9199cc8116a79d3de74e28e1171a5f88f8ba0d3ee58fd620a9bc06b63652f85b9b03e583b4a3f5960a2d56a1f4d9c3eb2731def42d75bd28ee6ed7c65609b4a1\",\"v\":2}","sig":"de12e3a0b7ca656aad163076e2f32e5de2878a55b163e7ddad075e6e8ae9d98d4aac1502bb76f69c03086d4e7350f5d32686dba1295788ceb9dfd4049cc657f2"}
{"kind":24242,"id":"967ecb7f628c4c40e6142b01d1e4fcfc021af09ceffabdf3e6e5616acebbba13","pubkey":"a975b6d0b2376a560d115745432db9828936b586e85baab34b0faedc1ec69cc9","created_at":1785676828,"tags":[["r","5ae4bcc88c92069957e24564ee8f8f0bbc50aa0ec33740fa6df513c5b4261c58"],["t","P"]],"content":"{\"t\": \"P\", \"d\": \"i22abIUK979JGbf+XiW0SEkynRl3oeD+ub6fdvXs9e1HiuSYhHzExoBu9kIDplEXnBZLQ5GckxdvfPVMCI5wLRruGhCQ8FyJxZMN3BOlkS7ExKGTXSCX7ckXAro=\"}","sig":"9ae34fa8b69db93c5469d472d3fe3e357370412926cbbd161c1c65ddf8a01531b0cbd184f46531169d738dc97f9ad647a8ad0eb0b8f768bc1bd05b21e424e086"}
{"kind":10011,"id":"9fc9a8ddbcea381b42dc97807b9eda3e22ef405d389804ad1ed86e2c72f39534","pubkey":"5de310432a95a13ba1905b5c78b511b931fa7efa86bea15057ebe16424cb9273","created_at":1785676828,"tags":[["t","relay-monitor"]],"content":"[relay-monitor] health check at 2026-08-02T13:20:28.031870+00:00","sig":"aa3d24c702a8172c6a28e59ab84ef4e831c7ebe2153f19dea71f92cdb8d4c35495669bc5eb80df2c9c7eea273d919fbce1e26fc226ab22cd4cfa202aae6e0aef"}
{"kind":5,"id":"a9a5a2840cdedd585ef53eb3fb086119b09d6ec97614a19e7014edc8e9fbfdd8","pubkey":"abb85588adc1f9fc70c07c2d6119c7ea8483e05674d85e89fd12fe26f37d7b80","created_at":1785676828,"tags":[["e","6baaf169c5a412c48bc05796ae31420fbf0abf7f4425030a02a5b5fb6e89095f"],["p","abb85588adc1f9fc70c07c2d6119c7ea8483e05674d85e89fd12fe26f37d7b80"],["k","7"],["client","Amethyst"]],"content":"","sig":"49e4211186e0ba933c513078fad6bc0e40c496573adef997be03e343ceff2dc309616a22f44b110ae2d7ce840f969140ac83dd764d4afb0c7e52fcd47442aeeb"}
{"kind":24133,"id":"65468cf8859c2651655aef77fc73e162475671a1b9fe78507a75e8e1b0fce9c7","pubkey":"adc534e19063daed4a5fcffafcabac5c69ea92781fb870b800ada248494388e8","created_at":1785676827,"tags":[["p","8bf629b3d519a0f8a8390137a445c0eb2f5f2b4a8ed71151de898051e8006f13"]],"content":"AnvGOKiC2qxlzPRhokNkl7/Mv18NnRtNUDK3VpuOS8rGYCpUPhpICdZpq+OUZWQ12gFqaWNWEinqLyR8FXRCGKxeJ5iArhWFMF2+IpFe4mCj3Kg/bdu02V3waBzg0T4rAOaRWqsWQBSUz8ENTaeJUUhAyx11INFOWvh4MEh4ablqPY1ONZrMlfOrK5h/rqNQ0b9zbbzoeocfSUKrblTU/TcAa7bm8EnCOGjkyvfkW0PHbIND12NZG/zOBmvQIUTOkCkC","sig":"2e56dfe53f064ddcd45d09ef9211fb7d685cec414fd75537504a023be2fb8ad13b7c2ff34117e1b467902ec7fb207bd5bf17f28f83569ecbbe724e484a560767"}
{"kind":20001,"id":"828803cdb45f3fe3f1d3ad30cec52a0a608cd44aa575eb61e4781236d206dac6","pubkey":"e1ef740bbab4a0d7fa489084a5dc41bd3c829bfa80cacbd2c3fe104ef41b258c","created_at":1785676827,"tags":[["g","u2sh7"]],"content":"","sig":"e3b6b73b3d9583c71795ed5d9a73375cca25202a48f9083c6e2fd001951ab60fbdb03f30ae4958570dfb62999d8c776d5cbe6e3f0278591873ebabf0e260d40e"}
{"kind":21059,"id":"add2aaee09d5b4616d2688c3c1acd0c093f8f3e609676adb8433e7a4802b3d80","pubkey":"362e1183392e4916b8c309fd22d11b54ff7f50fb31f775c4ed7cb6c6da4cb9c4","created_at":1785676827,"tags":[["p","904d65c28c1bc185277f9e60de40e9044650b0c59e2729a4458e3c4ab6ba4efc"],["expiration","1785676947"]],"content":"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","sig":"0b358a00881890d1170b455ad5f23800a8620026939af6e98fa1d12bf5ac052b57347ce0cb15acf46c2b800be9a1e4f4ea331ef3a6186920db69fc9c43c0172b"}
{"kind":5,"id":"8cb0ebc4d6307a3df82ac8e660e4ef9e7988cea03fde286f21277ab17e67991f","pubkey":"0a4f177b087b133099e52e5b50456f0d4c832db60c0f35d3269a6a055f25522a","created_at":1785676827,"tags":[["e","ad604a106024742fe765459c43b7f45f2c7cc3123454d4d517d34c3c539686f4"]],"content":"","sig":"217950b519bd5343ca498e48b4b04637ce09ec405d211913d85201bdc76561d61b6628424ad33dfb607c0150fe63b5197230b66991ce011b8dea3734cba0b66b"}
{"kind":21059,"id":"c3d841341e03f0f22b2e6e7cd0811954f7590ff69c785965c2afe1e91cc6558a","pubkey":"608696506aca54466d337ef6dccb9cc714dd94ca1f88e91939fabec7d3114024","created_at":1785676827,"tags":[["p","08e99116ad58c5aa5e6e7dc40668d5d7f2c080fc9fd697ec0aeb76880cf54bd9"],["expiration","1785676947"]],"content":"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","sig":"887844fc5f7ce096fe528ed0a1891992556d30ae6f3e9a06177ce638f2a0dc21433678ad30483ddef40584048986d2d0cc3090975b9c96e13d8ad163b5aafbff"}
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More