# Relay Pool API Reference This document describes the public API for the Nostr Relay Pool implementation in [`core_relay_pool.c`](nostr_core/core_relay_pool.c). ## Function Summary | Function | Description | |----------|-------------| | [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219) | Create and initialize a new relay pool | | [`nostr_relay_pool_destroy()`](nostr_core/core_relay_pool.c:304) | Destroy pool and cleanup all resources | | [`nostr_relay_pool_add_relay()`](nostr_core/core_relay_pool.c:229) | Add a relay URL to the pool | | [`nostr_relay_pool_remove_relay()`](nostr_core/core_relay_pool.c:273) | Remove a relay URL from the pool | | [`nostr_relay_pool_subscribe()`](nostr_core/core_relay_pool.c:399) | Create async subscription with callbacks | | [`nostr_pool_subscription_close()`](nostr_core/core_relay_pool.c:491) | Close subscription and free resources | | [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) | Run event loop for specified timeout | | [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) | Single iteration poll and dispatch | | [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695) | Synchronous query returning event array | | [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825) | Get single most recent event | | [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866) | Publish event and wait for OK responses | | [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944) | Get connection status for a relay | | [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960) | List all relays and their statuses | | [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992) | Get detailed statistics for a relay | | [`nostr_relay_pool_reset_relay_stats()`](nostr_core/core_relay_pool.c:1008) | Reset statistics for a relay | | [`nostr_relay_pool_get_relay_query_latency()`](nostr_core/core_relay_pool.c:1045) | Get average query latency for a relay | ## Pool Lifecycle ### Create Pool **Function:** [`nostr_relay_pool_create()`](nostr_core/core_relay_pool.c:219) ```c nostr_relay_pool_t* nostr_relay_pool_create(void); ``` **Example:** ```c #include "nostr_core.h" int main() { // Create a new relay pool nostr_relay_pool_t* pool = nostr_relay_pool_create(); if (!pool) { fprintf(stderr, "Failed to create relay pool\n"); return -1; } // Use the pool... // Clean up nostr_relay_pool_destroy(pool); return 0; } ``` ### Destroy Pool **Function:** [`nostr_relay_pool_destroy()`](nostr_core/core_relay_pool.c:304) ```c void nostr_relay_pool_destroy(nostr_relay_pool_t* pool); ``` **Example:** ```c // Properly cleanup a relay pool void cleanup_pool(nostr_relay_pool_t* pool) { if (pool) { // This will close all active subscriptions and relay connections nostr_relay_pool_destroy(pool); pool = NULL; } } ``` ## Relay Management ### Add Relay **Function:** [`nostr_relay_pool_add_relay()`](nostr_core/core_relay_pool.c:229) ```c int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url); ``` **Example:** ```c int setup_relays(nostr_relay_pool_t* pool) { const char* relays[] = { "wss://relay.damus.io", "wss://nos.lol", "wss://relay.nostr.band" }; for (int i = 0; i < 3; i++) { int result = nostr_relay_pool_add_relay(pool, relays[i]); if (result != NOSTR_SUCCESS) { fprintf(stderr, "Failed to add relay %s: %d\n", relays[i], result); return -1; } printf("Added relay: %s\n", relays[i]); } return 0; } ``` ### Remove Relay **Function:** [`nostr_relay_pool_remove_relay()`](nostr_core/core_relay_pool.c:273) ```c int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url); ``` **Example:** ```c int remove_slow_relay(nostr_relay_pool_t* pool) { const char* slow_relay = "wss://slow-relay.example.com"; int result = nostr_relay_pool_remove_relay(pool, slow_relay); if (result == NOSTR_SUCCESS) { printf("Successfully removed relay: %s\n", slow_relay); } else { printf("Failed to remove relay %s (may not exist)\n", slow_relay); } return result; } ``` ## Subscriptions (Asynchronous) ### Subscribe to Events **Function:** [`nostr_relay_pool_subscribe()`](nostr_core/core_relay_pool.c:399) ```c nostr_pool_subscription_t* nostr_relay_pool_subscribe( nostr_relay_pool_t* pool, const char** relay_urls, int relay_count, cJSON* filter, void (*on_event)(cJSON* event, const char* relay_url, void* user_data), void (*on_eose)(void* user_data), void* user_data); ``` **Example:** ```c #include "cjson/cJSON.h" // Event callback - called for each received event void handle_event(cJSON* event, const char* relay_url, void* user_data) { cJSON* content = cJSON_GetObjectItem(event, "content"); cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey"); if (content && pubkey) { printf("Event from %s: %s (by %s)\n", relay_url, cJSON_GetStringValue(content), cJSON_GetStringValue(pubkey)); } } // EOSE callback - called when all relays finish sending stored events void handle_eose(void* user_data) { printf("All relays finished sending stored events\n"); } int subscribe_to_notes(nostr_relay_pool_t* pool) { // Create filter for kind 1 (text notes) from last hour cJSON* filter = cJSON_CreateObject(); cJSON* kinds = cJSON_CreateArray(); cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); cJSON_AddItemToObject(filter, "kinds", kinds); time_t since = time(NULL) - 3600; // Last hour cJSON_AddItemToObject(filter, "since", cJSON_CreateNumber(since)); cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(50)); // Subscribe to specific relays const char* relay_urls[] = { "wss://relay.damus.io", "wss://nos.lol" }; nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe( pool, relay_urls, 2, filter, handle_event, handle_eose, NULL // user_data ); cJSON_Delete(filter); // Pool makes its own copy if (!sub) { fprintf(stderr, "Failed to create subscription\n"); return -1; } // Drive the event loop to receive events printf("Listening for events for 30 seconds...\n"); nostr_relay_pool_run(pool, 30000); // 30 seconds // Close subscription nostr_pool_subscription_close(sub); return 0; } ``` ### Close Subscription **Function:** [`nostr_pool_subscription_close()`](nostr_core/core_relay_pool.c:491) ```c int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription); ``` **Example:** ```c // Subscription management with cleanup typedef struct { nostr_pool_subscription_t* subscription; int event_count; int should_stop; } subscription_context_t; void event_counter(cJSON* event, const char* relay_url, void* user_data) { subscription_context_t* ctx = (subscription_context_t*)user_data; ctx->event_count++; printf("Received event #%d from %s\n", ctx->event_count, relay_url); // Stop after 10 events if (ctx->event_count >= 10) { ctx->should_stop = 1; } } int limited_subscription(nostr_relay_pool_t* pool) { subscription_context_t ctx = {0}; // Create filter cJSON* filter = cJSON_CreateObject(); cJSON* kinds = cJSON_CreateArray(); cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); cJSON_AddItemToObject(filter, "kinds", kinds); const char* relay_urls[] = {"wss://relay.damus.io"}; ctx.subscription = nostr_relay_pool_subscribe( pool, relay_urls, 1, filter, event_counter, NULL, &ctx); cJSON_Delete(filter); if (!ctx.subscription) { return -1; } // Poll until we should stop while (!ctx.should_stop) { int events = nostr_relay_pool_poll(pool, 100); if (events < 0) break; } // Clean up int result = nostr_pool_subscription_close(ctx.subscription); printf("Subscription closed with result: %d\n", result); return 0; } ``` ## Event Loop ### Run Timed Loop **Function:** [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) ```c int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms); ``` **Example:** ```c int run_event_loop(nostr_relay_pool_t* pool) { printf("Starting event loop for 60 seconds...\n"); // Run for 60 seconds, processing all incoming events int total_events = nostr_relay_pool_run(pool, 60000); if (total_events < 0) { fprintf(stderr, "Event loop error\n"); return -1; } printf("Processed %d events total\n", total_events); return 0; } ``` ### Single Poll Iteration **Function:** [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) ```c int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms); ``` **Example:** ```c // Integration with custom main loop int custom_main_loop(nostr_relay_pool_t* pool) { int running = 1; int total_events = 0; while (running) { // Poll for Nostr events (non-blocking with 50ms timeout) int events = nostr_relay_pool_poll(pool, 50); if (events > 0) { total_events += events; printf("Processed %d events this iteration\n", events); } // Do other work in your application // handle_ui_events(); // process_background_tasks(); // Check exit condition // running = !should_exit(); // Simple exit after 100 events for demo if (total_events >= 100) { running = 0; } } printf("Main loop finished, processed %d total events\n", total_events); return 0; } ``` ## Synchronous Operations ### Query Multiple Events **Function:** [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695) ```c cJSON** nostr_relay_pool_query_sync( nostr_relay_pool_t* pool, const char** relay_urls, int relay_count, cJSON* filter, int* event_count, int timeout_ms); ``` **Example:** ```c int query_recent_notes(nostr_relay_pool_t* pool) { // Create filter for recent text notes cJSON* filter = cJSON_CreateObject(); cJSON* kinds = cJSON_CreateArray(); cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); cJSON_AddItemToObject(filter, "kinds", kinds); cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(20)); const char* relay_urls[] = { "wss://relay.damus.io", "wss://nos.lol" }; int event_count = 0; cJSON** events = nostr_relay_pool_query_sync( pool, relay_urls, 2, filter, &event_count, 10000); // 10 second timeout cJSON_Delete(filter); if (!events) { printf("No events received or query failed\n"); return -1; } printf("Received %d events:\n", event_count); for (int i = 0; i < event_count; i++) { cJSON* content = cJSON_GetObjectItem(events[i], "content"); if (content) { printf(" %d: %s\n", i + 1, cJSON_GetStringValue(content)); } // Free each event cJSON_Delete(events[i]); } // Free the events array free(events); return event_count; } ``` ### Get Single Most Recent Event **Function:** [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825) ```c cJSON* nostr_relay_pool_get_event( nostr_relay_pool_t* pool, const char** relay_urls, int relay_count, cJSON* filter, int timeout_ms); ``` **Example:** ```c int get_latest_note_from_pubkey(nostr_relay_pool_t* pool, const char* pubkey_hex) { // Create filter for specific author's notes cJSON* filter = cJSON_CreateObject(); cJSON* kinds = cJSON_CreateArray(); cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); cJSON_AddItemToObject(filter, "kinds", kinds); cJSON* authors = cJSON_CreateArray(); cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey_hex)); cJSON_AddItemToObject(filter, "authors", authors); cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(1)); const char* relay_urls[] = {"wss://relay.damus.io"}; cJSON* event = nostr_relay_pool_get_event( pool, relay_urls, 1, filter, 5000); // 5 second timeout cJSON_Delete(filter); if (!event) { printf("No recent event found for pubkey %s\n", pubkey_hex); return -1; } cJSON* content = cJSON_GetObjectItem(event, "content"); cJSON* created_at = cJSON_GetObjectItem(event, "created_at"); if (content && created_at) { printf("Latest note: %s (created at %ld)\n", cJSON_GetStringValue(content), (long)cJSON_GetNumberValue(created_at)); } cJSON_Delete(event); return 0; } ``` ### Publish Event **Function:** [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866) ```c int nostr_relay_pool_publish( nostr_relay_pool_t* pool, const char** relay_urls, int relay_count, cJSON* event); ``` **Example:** ```c int publish_text_note(nostr_relay_pool_t* pool, const char* content) { // Create a basic text note event (this is simplified - real implementation // would need proper signing with private key) cJSON* event = cJSON_CreateObject(); cJSON_AddItemToObject(event, "kind", cJSON_CreateNumber(1)); cJSON_AddItemToObject(event, "content", cJSON_CreateString(content)); cJSON_AddItemToObject(event, "created_at", cJSON_CreateNumber(time(NULL))); // In real usage, you'd add pubkey, id, sig fields here cJSON_AddItemToObject(event, "pubkey", cJSON_CreateString("your_pubkey_hex")); cJSON_AddItemToObject(event, "id", cJSON_CreateString("event_id_hash")); cJSON_AddItemToObject(event, "sig", cJSON_CreateString("event_signature")); cJSON_AddItemToObject(event, "tags", cJSON_CreateArray()); const char* relay_urls[] = { "wss://relay.damus.io", "wss://nos.lol", "wss://relay.nostr.band" }; printf("Publishing note: %s\n", content); int success_count = nostr_relay_pool_publish( pool, relay_urls, 3, event); cJSON_Delete(event); printf("Successfully published to %d out of 3 relays\n", success_count); if (success_count == 0) { fprintf(stderr, "Failed to publish to any relay\n"); return -1; } return success_count; } ``` ## Status and Statistics ### Get Relay Status **Function:** [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944) ```c nostr_pool_relay_status_t nostr_relay_pool_get_relay_status( nostr_relay_pool_t* pool, const char* relay_url); ``` **Example:** ```c void check_relay_status(nostr_relay_pool_t* pool, const char* relay_url) { nostr_pool_relay_status_t status = nostr_relay_pool_get_relay_status(pool, relay_url); const char* status_str; switch (status) { case NOSTR_POOL_RELAY_DISCONNECTED: status_str = "DISCONNECTED"; break; case NOSTR_POOL_RELAY_CONNECTING: status_str = "CONNECTING"; break; case NOSTR_POOL_RELAY_CONNECTED: status_str = "CONNECTED"; break; case NOSTR_POOL_RELAY_ERROR: status_str = "ERROR"; break; default: status_str = "UNKNOWN"; break; } printf("Relay %s status: %s\n", relay_url, status_str); } ``` ### List All Relays **Function:** [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960) ```c int nostr_relay_pool_list_relays( nostr_relay_pool_t* pool, char*** relay_urls, nostr_pool_relay_status_t** statuses); ``` **Example:** ```c void print_all_relays(nostr_relay_pool_t* pool) { char** relay_urls = NULL; nostr_pool_relay_status_t* statuses = NULL; int count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses); if (count < 0) { printf("Failed to list relays\n"); return; } if (count == 0) { printf("No relays configured\n"); return; } printf("Configured relays (%d):\n", count); for (int i = 0; i < count; i++) { const char* status_str = (statuses[i] == NOSTR_POOL_RELAY_CONNECTED) ? "CONNECTED" : "DISCONNECTED"; printf(" %s - %s\n", relay_urls[i], status_str); // Free the duplicated URL string free(relay_urls[i]); } // Free the arrays free(relay_urls); free(statuses); } ``` ### Get Relay Statistics **Function:** [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992) ```c const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats( nostr_relay_pool_t* pool, const char* relay_url); ``` **Example:** ```c void print_relay_stats(nostr_relay_pool_t* pool, const char* relay_url) { const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_url); if (!stats) { printf("No stats available for relay %s\n", relay_url); return; } printf("Statistics for %s:\n", relay_url); printf(" Connection attempts: %d\n", stats->connection_attempts); printf(" Connection failures: %d\n", stats->connection_failures); printf(" Events received: %d\n", stats->events_received); printf(" Events published: %d\n", stats->events_published); printf(" Events published OK: %d\n", stats->events_published_ok); printf(" Events published failed: %d\n", stats->events_published_failed); printf(" Query latency avg: %.2f ms\n", stats->query_latency_avg); printf(" Query samples: %d\n", stats->query_samples); printf(" Publish latency avg: %.2f ms\n", stats->publish_latency_avg); printf(" Publish samples: %d\n", stats->publish_samples); if (stats->last_event_time > 0) { printf(" Last event: %ld seconds ago\n", time(NULL) - stats->last_event_time); } } ``` ### Reset Relay Statistics **Function:** [`nostr_relay_pool_reset_relay_stats()`](nostr_core/core_relay_pool.c:1008) ```c int nostr_relay_pool_reset_relay_stats( nostr_relay_pool_t* pool, const char* relay_url); ``` **Example:** ```c void reset_stats_for_relay(nostr_relay_pool_t* pool, const char* relay_url) { int result = nostr_relay_pool_reset_relay_stats(pool, relay_url); if (result == NOSTR_SUCCESS) { printf("Successfully reset statistics for %s\n", relay_url); } else { printf("Failed to reset statistics for %s\n", relay_url); } } ``` ### Get Query Latency **Function:** [`nostr_relay_pool_get_relay_query_latency()`](nostr_core/core_relay_pool.c:1045) ```c double nostr_relay_pool_get_relay_query_latency( nostr_relay_pool_t* pool, const char* relay_url); ``` **Example:** ```c void check_relay_performance(nostr_relay_pool_t* pool) { const char* relays[] = { "wss://relay.damus.io", "wss://nos.lol", "wss://relay.nostr.band" }; printf("Relay performance comparison:\n"); for (int i = 0; i < 3; i++) { double latency = nostr_relay_pool_get_relay_query_latency(pool, relays[i]); if (latency >= 0) { printf(" %s: %.2f ms average query latency\n", relays[i], latency); } else { printf(" %s: No latency data available\n", relays[i]); } } } ``` ## Complete Example Application ```c #include "nostr_core.h" #include "cjson/cJSON.h" #include #include #include // Global context for the example typedef struct { int event_count; int max_events; } app_context_t; void on_text_note(cJSON* event, const char* relay_url, void* user_data) { app_context_t* ctx = (app_context_t*)user_data; cJSON* content = cJSON_GetObjectItem(event, "content"); if (content && cJSON_IsString(content)) { printf("[%s] Note #%d: %s\n", relay_url, ++ctx->event_count, cJSON_GetStringValue(content)); } } void on_subscription_complete(void* user_data) { printf("All relays finished sending stored events\n"); } int main() { // Initialize pool nostr_relay_pool_t* pool = nostr_relay_pool_create(); if (!pool) { fprintf(stderr, "Failed to create relay pool\n"); return 1; } // Add relays const char* relays[] = { "wss://relay.damus.io", "wss://nos.lol" }; for (int i = 0; i < 2; i++) { if (nostr_relay_pool_add_relay(pool, relays[i]) != NOSTR_SUCCESS) { fprintf(stderr, "Failed to add relay: %s\n", relays[i]); } } // Create filter for recent text notes cJSON* filter = cJSON_CreateObject(); cJSON* kinds = cJSON_CreateArray(); cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); cJSON_AddItemToObject(filter, "kinds", kinds); cJSON_AddItemToObject(filter, "limit", cJSON_CreateNumber(10)); // Set up context app_context_t ctx = {0, 10}; // Subscribe nostr_pool_subscription_t* sub = nostr_relay_pool_subscribe( pool, relays, 2, filter, on_text_note, on_subscription_complete, &ctx); cJSON_Delete(filter); if (!sub) { fprintf(stderr, "Failed to create subscription\n"); nostr_relay_pool_destroy(pool); return 1; } // Run event loop for 30 seconds printf("Listening for events...\n"); nostr_relay_pool_run(pool, 30000); // Print final stats for (int i = 0; i < 2; i++) { print_relay_stats(pool, relays[i]); } // Cleanup nostr_pool_subscription_close(sub); nostr_relay_pool_destroy(pool); printf("Application finished. Received %d events total.\n", ctx.event_count); return 0; } ``` ## Notes - All functions are **not thread-safe**. Use from a single thread or add external synchronization. - **Memory ownership**: The pool duplicates filters and URLs internally. Caller owns returned events and must free them. - **Event deduplication** is applied pool-wide using a circular buffer of 1000 event IDs. - **Ping functionality** is currently disabled in this build. - **Reconnection** happens on-demand when sending, but active subscriptions are not automatically re-sent after reconnect. - **Polling model**: You must drive the event loop via [`nostr_relay_pool_run()`](nostr_core/core_relay_pool.c:1192) or [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) to receive events.