C-Nostr Relay
A high-performance Nostr relay implemented in C with SQLite backend, featuring nostr event-based management.
Supported NIPs
- 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
Quick Start
Get your C-Relay up and running in minutes with a static binary (no dependencies required):
1. Download Static Binary
Download the latest static release from the releases page:
# 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
2. Start the Relay
Simply run the binary - no configuration files needed:
./c-relay
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
3. Access the Web Interface
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
4. Test Your Relay
Test basic connectivity:
# Test WebSocket connection
curl -H "Accept: application/nostr+json" http://localhost:8888
# Test with a Nostr client
# Add ws://localhost:8888 to your client's relay list
5. Configure Your Relay (Optional)
Use the web interface or send admin commands to customize:
- Relay name and description
- Authentication rules (whitelist/blacklist)
- Connection limits
- Proof-of-work requirements
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:
{
"id": "event_id",
"pubkey": "admin_public_key",
"created_at": 1234567890,
"kind": 23456,
"content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...",
"tags": [
["p", "relay_public_key"]
],
"sig": "event_signature"
}
The content field contains a NIP-44 encrypted JSON array representing the command.
Admin Response Event:
["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"
}]
The content field contains a NIP-44 encrypted JSON response object.
Admin Commands
All commands are sent as NIP-44 encrypted JSON arrays in the event content. The following table lists all available commands:
| 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 |
| Database Queries | ||
sql_query |
["sql_query", "SELECT * FROM events LIMIT 10"] |
Execute read-only SQL query against relay database |
Available Configuration Keys
Basic Relay Settings:
relay_name: Relay name (displayed in NIP-11)relay_description: Relay description textrelay_contact: Contact informationrelay_software: Software URLrelay_version: Software versionsupported_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 textpayments_url: Payment URL for premium featuresmax_connections: Maximum concurrent connectionsmax_subscriptions_per_client: Max subscriptions per clientmax_event_tags: Maximum tags per eventmax_content_length: Maximum event content length
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
Proof of Work & Validation:
pow_min_difficulty: Minimum proof-of-work difficultynip40_expiration_enabled: Enable event expiration (true/false)
Dynamic Configuration Updates
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).
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
Restart-Required Configuration Parameters:
- Connection settings:
max_connections,relay_port - Database and core system settings
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.
Response Format
All admin commands return signed EVENT responses via WebSocket following standard Nostr protocol. Responses use JSON content with structured data.
Response Examples
Success Response:
["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"
}]
Error Response:
["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"
}]
Auth Rules Query Response:
["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"
}]
Configuration Query Response:
["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"
}]
Configuration Update Success Response:
["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"
}]
Configuration Update Error Response:
["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"
}]
Database Statistics Query Response:
["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"
}]
SQL Query Response:
["EVENT", "temp_sub_id", {
"id": "response_event_id",
"pubkey": "relay_public_key",
"created_at": 1234567890,
"kind": 23457,
"content": "nip44 encrypted:{\"query_type\": \"sql_query\", \"request_id\": \"request_event_id\", \"timestamp\": 1234567890, \"query\": \"SELECT * FROM events LIMIT 10\", \"execution_time_ms\": 45, \"row_count\": 10, \"columns\": [\"id\", \"pubkey\", \"created_at\", \"kind\", \"content\"], \"rows\": [[\"abc123...\", \"def456...\", 1234567890, 1, \"Hello world\"], ...]}",
"tags": [
["p", "admin_public_key"],
["e", "request_event_id"]
],
"sig": "response_event_signature"
}]
SQL Query Command
The sql_query command allows administrators to execute read-only SQL queries against the relay database. This provides powerful analytics and debugging capabilities through the admin API.
Request/Response Correlation:
- Each response includes the request event ID in both the
tagsarray (["e", "request_event_id"]) and the decrypted content ("request_id": "request_event_id") - This allows proper correlation when multiple queries are submitted concurrently
- Frontend can track pending queries and match responses to requests
Security Features:
- Only SELECT statements allowed (INSERT, UPDATE, DELETE, DROP, etc. are blocked)
- Query timeout: 5 seconds (configurable)
- Result row limit: 1000 rows (configurable)
- All queries logged with execution time
Available Tables and Views:
events- All Nostr eventsconfig- Configuration parametersauth_rules- Authentication rulessubscription_events- Subscription lifecycle logevent_broadcasts- Event broadcast logrecent_events- Last 1000 events (view)event_stats- Event statistics by type (view)subscription_analytics- Subscription metrics (view)active_subscriptions_log- Currently active subscriptions (view)event_kinds_view- Event distribution by kind (view)top_pubkeys_view- Top 10 pubkeys by event count (view)time_stats_view- Time-based statistics (view)
Example Queries:
-- Recent events
SELECT id, pubkey, created_at, kind FROM events ORDER BY created_at DESC LIMIT 20
-- Event distribution by kind
SELECT * FROM event_kinds_view ORDER BY count DESC
-- Active subscriptions
SELECT * FROM active_subscriptions_log ORDER BY created_at DESC
-- Database statistics
SELECT
(SELECT COUNT(*) FROM events) as total_events,
(SELECT COUNT(*) FROM subscription_events) as total_subscriptions
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 statisticsconfig|configuration: Relay configuration