# Nostr Discovery Routstr Core integrates with Nostr (Notes and Other Stuff Transmitted by Relays) for decentralized provider discovery. This enables users to find Routstr nodes without relying on centralized directories. ## Overview Nostr integration provides: - **Decentralized Discovery**: Find providers through relay network - **Cryptographic Identity**: Providers identified by public keys - **Real-time Updates**: Live provider status and pricing - **Censorship Resistance**: No central point of control ## How It Works ```mermaid graph LR A[Routstr Node] --> B[Nostr Relay] B --> C[Nostr Relay] B --> D[Nostr Relay] E[User Client] --> B E --> C E --> D B --> F[Provider List] C --> F D --> F ``` Providers announce themselves by publishing signed events to Nostr relays. Clients can query these relays to discover available providers. ## Provider Configuration ### Setting Up Nostr Identity 1. **Generate Nostr Keys** ```bash # Using nostril or similar tool nostril --generate-keypair # Output: # Private key (nsec): nsec1abc... # Public key (npub): npub1xyz... ``` 2. **Configure Environment** ```bash # .env NPUB=npub1xyz... # Your public key NSEC=nsec1abc... # Your private key (keep secret!) NAME=Lightning AI Gateway DESCRIPTION=Fast and reliable AI API with Bitcoin payments HTTP_URL=https://api.lightning-ai.com ONION_URL=http://lightningai.onion ``` ### Publishing to Nostr Routstr automatically publishes provider information to configured relays: ```python # Published event structure (NIP-89) { "kind": 31990, # Application handler event "pubkey": "your_public_key", "content": { "name": "Lightning AI Gateway", "description": "Fast and reliable AI API", "endpoints": { "http": "https://api.lightning-ai.com", "onion": "http://lightningai.onion" }, "models": ["gpt-3.5-turbo", "gpt-4", "claude-3"], "pricing": { "gpt-3.5-turbo": { "prompt_sats_per_1k": 3, "completion_sats_per_1k": 4 } }, "cashu_mints": [ "https://mint.minibits.cash/Bitcoin" ] }, "tags": [ ["d", "routstr"], ["t", "ai-api"], ["t", "bitcoin"], ["p", "payment-proxy"] ] } ``` ### Relay Configuration Configure which relays to publish to: ```python # Default relays DEFAULT_RELAYS = [ "wss://relay.damus.io", "wss://relay.nostr.band", "wss://relay.routstr.com", "wss://nos.lol" ] # Custom relay configuration RELAYS=wss://relay1.com,wss://relay2.com ``` ## Client Discovery ### Using the Discovery Endpoint Find providers through the API: ```bash GET /v1/providers Response: { "providers": [ { "name": "Lightning AI Gateway", "npub": "npub1xyz...", "description": "Fast and reliable AI API", "endpoints": { "http": "https://api.lightning-ai.com", "onion": "http://lightningai.onion" }, "models": ["gpt-3.5-turbo", "gpt-4"], "pricing": { "gpt-3.5-turbo": { "prompt_sats_per_1k": 3, "completion_sats_per_1k": 4 } }, "last_seen": "2024-01-01T12:00:00Z", "reliability_score": 0.99 } ] } ``` ### Direct Nostr Queries Query Nostr relays directly: ```python import json import websocket def discover_providers(relay_url: str): """Discover Routstr providers from Nostr relay.""" ws = websocket.create_connection(relay_url) # Subscribe to provider events subscription = { "kinds": [31990], "tags": { "d": ["routstr"] } } ws.send(json.dumps(["REQ", "sub1", subscription])) providers = [] while True: response = json.loads(ws.recv()) if response[0] == "EVENT": event = response[2] providers.append(parse_provider_event(event)) elif response[0] == "EOSE": # End of stored events break ws.close() return providers ``` ### JavaScript/TypeScript ```typescript import { SimplePool } from 'nostr-tools'; async function discoverProviders(): Promise { const pool = new SimplePool(); const relays = [ 'wss://relay.damus.io', 'wss://relay.nostr.band' ]; const filter = { kinds: [31990], '#d': ['routstr'] }; const events = await pool.list(relays, [filter]); return events.map(event => ({ name: event.content.name, npub: nip19.npubEncode(event.pubkey), url: event.content.endpoints.http, models: event.content.models, pricing: event.content.pricing })); } ``` ## Provider Ranking ### Reliability Scoring Providers are ranked based on: ```python class ProviderScore: def calculate(self, provider: Provider) -> float: score = 1.0 # Uptime (based on recent checks) uptime_ratio = provider.successful_pings / provider.total_pings score *= uptime_ratio # Response time if provider.avg_response_time < 500: # ms score *= 1.0 elif provider.avg_response_time < 1000: score *= 0.9 else: score *= 0.7 # Model availability model_score = len(provider.models) / 10 # Max 10 models score *= min(1.0, 0.5 + model_score * 0.5) # Price competitiveness if provider.is_cheapest_for_any_model(): score *= 1.1 return min(1.0, score) ``` ### Provider Selection Choose optimal provider: ```python def select_provider( providers: list[Provider], model: str, requirements: dict ) -> Provider: """Select best provider for requirements.""" # Filter by model availability candidates = [p for p in providers if model in p.models] # Filter by requirements if requirements.get('tor_required'): candidates = [p for p in candidates if p.onion_url] if requirements.get('max_price_per_1k'): max_price = requirements['max_price_per_1k'] candidates = [ p for p in candidates if p.pricing[model]['prompt_sats_per_1k'] <= max_price ] # Sort by score candidates.sort(key=lambda p: p.reliability_score, reverse=True) return candidates[0] if candidates else None ``` ## Publishing Updates ### Automatic Updates Routstr publishes updates when: - Node starts up - Configuration changes - Models are added/removed - Pricing updates ### Manual Publishing Force publish current state: ```python async def publish_provider_info(): """Manually publish provider information.""" event = create_provider_event( name=os.getenv("NAME"), description=os.getenv("DESCRIPTION"), models=get_available_models(), pricing=get_current_pricing() ) await publish_to_relays(event, RELAYS) ``` ### Event Lifecycle ```python # Publish every 6 hours @periodic_task(hours=6) async def update_nostr_presence(): """Keep provider information fresh.""" try: await publish_provider_info() logger.info("Updated Nostr presence") except Exception as e: logger.error(f"Failed to update Nostr: {e}") # Delete on shutdown async def remove_nostr_presence(): """Remove provider from discovery.""" deletion_event = create_deletion_event() await publish_to_relays(deletion_event, RELAYS) ``` ## Security Considerations ### Key Management 1. **Secure Storage** ```python # Never log private keys SENSITIVE_VARS = ['NSEC', 'ADMIN_PASSWORD'] def sanitize_env(env_dict: dict) -> dict: return { k: '***' if k in SENSITIVE_VARS else v for k, v in env_dict.items() } ``` 2. **Key Rotation** ```bash # Generate new keys nostril --generate-keypair # Update configuration # Publish transition event # Update all references ``` ### Event Validation Verify provider events: ```python def validate_provider_event(event: dict) -> bool: """Validate provider announcement.""" # Check signature if not verify_signature(event): return False # Check required fields required = ['name', 'endpoints', 'models', 'pricing'] content = json.loads(event['content']) if not all(field in content for field in required): return False # Verify endpoints are reachable if not await check_endpoints(content['endpoints']): return False return True ``` ### Relay Security Choose relays carefully: ```python TRUSTED_RELAYS = { 'wss://relay.damus.io': { 'operator': 'Damus', 'reputation': 'high', 'filters_spam': True }, 'wss://relay.nostr.band': { 'operator': 'Nostr.Band', 'reputation': 'high', 'paid_tier': True } } ``` ## Advanced Features ### Multi-Relay Broadcasting Ensure wide distribution: ```python async def broadcast_to_relays(event: dict, relays: list[str]): """Broadcast event to multiple relays.""" tasks = [] for relay in relays: task = asyncio.create_task( publish_to_relay(event, relay) ) tasks.append(task) results = await asyncio.gather(*tasks, return_exceptions=True) successful = sum(1 for r in results if not isinstance(r, Exception)) logger.info(f"Published to {successful}/{len(relays)} relays") ``` ### Provider Metadata Extended metadata in events: ```json { "kind": 31990, "content": { "name": "Lightning AI", "description": "Enterprise AI API", "metadata": { "established": "2024-01-01", "total_requests": 1000000, "average_response_ms": 250, "supported_features": [ "streaming", "function_calling", "vision", "embeddings" ], "certifications": ["SOC2", "GDPR"], "contact": { "nostr": "npub1contact...", "email": "support@lightning-ai.com" } } } } ``` ### Discovery Filters Advanced filtering options: ```python # Find providers with specific features GET /v1/providers?features=streaming,vision&max_price=5&min_reliability=0.95 # Response includes filtered results { "providers": [...], "filters_applied": { "features": ["streaming", "vision"], "max_price_sats_per_1k": 5, "min_reliability": 0.95 }, "total_providers": 50, "matching_providers": 12 } ``` ## Monitoring ### Discovery Metrics Track discovery performance: ```python class DiscoveryMetrics: def __init__(self): self.relay_health = {} self.provider_count = 0 self.query_latency = [] async def check_relay_health(self, relay_url: str): """Monitor relay connectivity.""" start = time.time() try: await connect_to_relay(relay_url) latency = time.time() - start self.relay_health[relay_url] = { 'status': 'healthy', 'latency_ms': latency * 1000 } except Exception as e: self.relay_health[relay_url] = { 'status': 'unhealthy', 'error': str(e) } ``` ### Provider Monitoring ```python @periodic_task(minutes=5) async def monitor_providers(): """Check provider health.""" providers = await discover_providers() for provider in providers: try: # Test endpoint response = await test_provider_endpoint(provider.http_url) # Update metrics await update_provider_metrics( provider.npub, success=response.status_code == 200, response_time=response.elapsed ) except Exception as e: logger.warning(f"Provider {provider.name} check failed: {e}") ``` ## Troubleshooting ### No Providers Found ```python # Debug discovery issues async def debug_discovery(): """Diagnose discovery problems.""" issues = [] # Check relay connectivity for relay in RELAYS: if not await can_connect_to_relay(relay): issues.append(f"Cannot connect to {relay}") # Check event publishing if not await verify_own_events_visible(): issues.append("Own events not visible on relays") # Check filters if len(await get_all_provider_events()) == 0: issues.append("No provider events on any relay") return issues ``` ### Relay Connection Issues ```bash # Test relay connection wscat -c wss://relay.damus.io # Send subscription ["REQ","test",{"kinds":[31990],"#d":["routstr"]}] ``` ## Best Practices ### For Providers 1. **Consistent Identity** - Use same npub across services - Maintain profile metadata - Verify identity on multiple platforms 2. **Regular Updates** - Publish status every few hours - Update pricing promptly - Remove stale information 3. **Relay Diversity** - Publish to 5+ relays - Include regional relays - Monitor relay health ### For Clients 1. **Verify Providers** - Check multiple relays - Verify endpoints work - Monitor reliability over time 2. **Cache Discovery** - Cache provider list - Refresh periodically - Handle stale data gracefully 3. **Fallback Options** - Keep backup providers - Handle discovery failures - Support manual configuration ## Next Steps - [Tor Support](tor.md) - Anonymous provider access - [Custom Pricing](custom-pricing.md) - Dynamic pricing strategies - [API Reference](../api/endpoints.md) - Discovery API details