/** * Relatr API Integration * * Relatr is a decentralized Web of Trust service that provides global trust scores * for Nostr pubkeys. This module provides integration with the Relatr service. * * Documentation: https://www.relatr.xyz/ * GitHub: https://github.com/ContextVM/relatr */ export interface RelatrScore { source: string; // Source pubkey (perspective) target: string; // Target pubkey being scored score: number; // Trust score (0-100) distance?: number; // Social graph distance hasNip05?: boolean; // NIP-05 verification status hasLud16?: boolean; // Lightning address status timestamp: number; // When score was calculated } export interface RelatrBatchRequest { source: string; // Perspective pubkey targets: string[]; // Array of pubkeys to score } export interface RelatrBatchResponse { scores: Map; } /** * Relatr API client * * NOTE: This is a placeholder implementation. The actual Relatr API * endpoint structure needs to be confirmed from the official documentation. * * Relatr provides an MCP (Model Context Protocol) server interface, which * may require different integration patterns than a traditional REST API. * * Potential API patterns: * 1. REST API: https://api.relatr.xyz/v1/scores?source=...&target=... * 2. MCP Server: Requires MCP client connection * 3. Nostr Events: Trust scores published as Nostr events */ export class RelatrClient { private apiUrl: string; private corsProxy: string; constructor( apiUrl = 'https://api.relatr.xyz', corsProxy = 'https://proxy.shakespeare.diy/?url=' ) { this.apiUrl = apiUrl; this.corsProxy = corsProxy; } /** * Fetch trust score for a single target from source's perspective * * @param source - Source pubkey (viewer's perspective) * @param target - Target pubkey to score * @returns Trust score or null if unavailable */ async getScore(source: string, target: string): Promise { try { // TODO: Confirm actual Relatr API endpoint structure // This is a placeholder based on common REST patterns const url = `${this.corsProxy}${encodeURIComponent( `${this.apiUrl}/v1/score?source=${source}&target=${target}` )}`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', }, signal: AbortSignal.timeout(10000), }); if (!response.ok) { console.warn(`[Relatr] API returned ${response.status}`); return null; } const data = await response.json(); return { source, target, score: data.score || 0, distance: data.distance, hasNip05: data.hasNip05, hasLud16: data.hasLud16, timestamp: Date.now(), }; } catch (error) { console.warn('[Relatr] Failed to fetch score:', error); return null; } } /** * Fetch trust scores for multiple targets in batch * * @param source - Source pubkey (viewer's perspective) * @param targets - Array of target pubkeys to score * @returns Map of target pubkey to score */ async getBatchScores( source: string, targets: string[] ): Promise> { const scores = new Map(); try { // TODO: Confirm if Relatr supports batch requests // Option 1: Batch endpoint const url = `${this.corsProxy}${encodeURIComponent(`${this.apiUrl}/v1/batch`)}`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ source, targets }), signal: AbortSignal.timeout(15000), }); if (!response.ok) { console.warn(`[Relatr] Batch API returned ${response.status}`); return scores; } const data = await response.json(); // Parse batch response if (Array.isArray(data.scores)) { for (const scoreData of data.scores) { scores.set(scoreData.target, { source, target: scoreData.target, score: scoreData.score || 0, distance: scoreData.distance, hasNip05: scoreData.hasNip05, hasLud16: scoreData.hasLud16, timestamp: Date.now(), }); } } return scores; } catch (error) { console.warn('[Relatr] Batch request failed, falling back to individual requests:', error); // Fallback: Make individual requests (slower but reliable) for (const target of targets.slice(0, 10)) { // Limit to 10 to avoid rate limits const score = await this.getScore(source, target); if (score) { scores.set(target, score); } // Small delay between requests await new Promise(resolve => setTimeout(resolve, 100)); } return scores; } } } /** * Global Relatr client instance * * NOTE: Set RELATR_ENABLED = false until API integration is confirmed */ export const RELATR_ENABLED = false; // Toggle when API is ready export const relatrClient = new RelatrClient(); /** * Normalize Relatr score to 0-100 scale * Relatr may use different scoring systems, this ensures compatibility */ export function normalizeRelatrScore(score: number): number { // Assume Relatr uses 0-1 scale, convert to 0-100 if (score <= 1) { return Math.round(score * 100); } // Already 0-100 scale return Math.round(Math.max(0, Math.min(100, score))); }