@relai-fi/x402 0.6.1-rc.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugins.cjs CHANGED
@@ -1122,7 +1122,7 @@ function solanaScore(config) {
1122
1122
  ...response,
1123
1123
  extensions: {
1124
1124
  ...response.extensions ?? {},
1125
- score: scoreData
1125
+ solanaScore: scoreData
1126
1126
  }
1127
1127
  };
1128
1128
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugins.ts","../src/relay-feedback.ts"],"sourcesContent":["// src/plugins.ts\n// RelAI Plugin System - extensible middleware hooks for Relai.protect()\n\nimport crypto from 'crypto';\nimport { ethers } from 'ethers';\nimport type { RelaiNetwork } from './types';\nimport type { SettleResult } from './server';\n\nconst RELAI_API_BASE = 'https://api.relai.fi';\n\n// ============================================================================\n// Plugin Interface\n// ============================================================================\n\nexport interface PluginContext {\n /** Network for this endpoint */\n network: RelaiNetwork;\n /** Price in USD */\n price: number;\n /** Request path */\n path: string;\n /** HTTP method */\n method: string;\n}\n\nexport interface PluginResult {\n /** If true, skip payment and serve content for free */\n skip?: boolean;\n /** If true, reject the request entirely (e.g. service unhealthy) */\n reject?: boolean;\n /** HTTP status code when rejecting (default: 503) */\n rejectStatus?: number;\n /** Error message when rejecting */\n rejectMessage?: string;\n /** Extra response headers to set */\n headers?: Record<string, string>;\n /** Metadata attached to req.pluginMeta */\n meta?: Record<string, unknown>;\n}\n\nexport interface RelaiPlugin {\n /** Unique plugin name */\n name: string;\n\n /**\n * Called before the 402 payment check.\n * Return { skip: true } to bypass payment entirely.\n */\n beforePaymentCheck?(req: any, ctx: PluginContext): Promise<PluginResult>;\n\n /**\n * Called after a successful payment settlement.\n * Use for analytics, logging, webhooks, etc.\n */\n afterSettled?(req: any, result: SettleResult, ctx: PluginContext): Promise<void>;\n\n /**\n * Called once when the Relai instance initializes (server start).\n * Use to sync config to RelAI backend or validate credentials.\n */\n onInit?(): Promise<void>;\n\n /**\n * Called before sending the 402 response. Allows plugins to add\n * extensions or modify the response body (e.g. bridge info).\n */\n enrich402Response?(response: any, ctx: PluginContext): any;\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\nexport interface FreeTierPluginConfig {\n /** Service key (sk_live_...) for syncing with RelAI backend. If omitted, runs in local in-memory mode. */\n serviceKey?: string;\n /** Max free calls per buyer per period */\n perBuyerLimit: number;\n /** Reset period for per-buyer counters */\n resetPeriod?: 'none' | 'daily' | 'monthly';\n /** Optional global cap across all buyers */\n globalCap?: number;\n /** Specific paths to apply free tier to (default: '*' = all) */\n paths?: string[];\n /** Override RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Cache TTL in ms for check results (default: 5000) */\n cacheTtlMs?: number;\n}\n\ninterface FreeTierCheckResponse {\n free: boolean;\n remaining?: number;\n total?: number;\n reason?: string;\n globalRemaining?: number;\n}\n\n/**\n * Free Tier plugin - gives buyers a number of free API calls\n * before requiring payment.\n *\n * State is stored in the RelAI backend, keyed by your service key.\n * Config can be set here (SDK-side) or overridden in the relai.fi dashboard.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { freeTier } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * freeTier({\n * serviceKey: process.env.RELAI_SERVICE_KEY!,\n * perBuyerLimit: 10,\n * resetPeriod: 'daily',\n * }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\n// ============================================================================\n// Bridge Plugin\n// ============================================================================\n\nexport interface BridgePluginConfig {\n /** Service key (sk_live_...) for tracking bridge usage per API owner */\n serviceKey?: string;\n /** RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Override settle endpoint (auto-discovered from /bridge/info if not set) */\n settleEndpoint?: string;\n /** Override supported source chains (auto-discovered if not set) */\n supportedSourceChains?: string[];\n /** Override supported source assets (auto-discovered if not set) */\n supportedSourceAssets?: string[];\n /** Override bridge payTo map: { [caip2]: address } */\n payTo?: Record<string, string>;\n /** Override Solana fee payer address (auto-discovered if not set) */\n feePayerSvm?: string;\n /** Override payment facilitator URL */\n paymentFacilitator?: string;\n /** Bridge fee in basis points (default: auto-discovered) */\n feeBps?: number;\n}\n\ninterface BridgeInfo {\n settleEndpoint: string;\n supportedSourceChains: string[];\n supportedSourceAssets: string[];\n payTo: Record<string, string>;\n feePayerSvm: string | null;\n feeBps: number;\n paymentFacilitator: string;\n}\n\n/**\n * Bridge plugin - enables cross-chain payments via the RelAI bridge.\n *\n * When a buyer's wallet is on a different chain than the merchant accepts,\n * the client SDK can automatically route the payment through the bridge.\n * This plugin adds `extensions.bridge` to the 402 response with all the\n * info the client needs to execute a cross-chain payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { bridge } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'skale-base',\n * plugins: [\n * bridge(), // auto-discovers from https://api.relai.fi\n * ],\n * });\n *\n * // Buyer on Solana can now pay for a SKALE endpoint\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.05,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\nexport function bridge(config?: BridgePluginConfig): RelaiPlugin {\n const base = (config?.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n let bridgeInfo: BridgeInfo | null = null;\n\n // Hash service key for tracking (never expose raw key to clients)\n // Must match backend: crypto.createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16)\n let serviceKeyHash: string | null = null;\n if (config?.serviceKey) {\n serviceKeyHash = crypto.createHash('sha256').update(config.serviceKey).digest('hex').slice(0, 16);\n }\n\n async function fetchBridgeInfo(): Promise<BridgeInfo | null> {\n try {\n const res = await fetch(`${base}/bridge/info`);\n if (!res.ok) {\n console.warn(`[relai:bridge] Failed to fetch /bridge/info: ${res.status}`);\n return null;\n }\n const data = await res.json() as any;\n return {\n settleEndpoint: config?.settleEndpoint || data.settleEndpoint,\n supportedSourceChains: config?.supportedSourceChains || data.supportedSourceChains || [],\n supportedSourceAssets: config?.supportedSourceAssets || data.supportedSourceAssets || [],\n payTo: config?.payTo || data.payTo || {},\n feePayerSvm: config?.feePayerSvm ?? data.feePayerSvm ?? null,\n feeBps: config?.feeBps ?? data.feeBps ?? 100,\n paymentFacilitator: config?.paymentFacilitator || data.paymentFacilitator || 'https://facilitator.x402.fi',\n };\n } catch (err) {\n console.warn(`[relai:bridge] Failed to fetch bridge info: ${err}`);\n return null;\n }\n }\n\n return {\n name: 'bridge',\n\n async onInit() {\n bridgeInfo = await fetchBridgeInfo();\n if (bridgeInfo) {\n console.log(`[relai:bridge] Initialized — ${bridgeInfo.supportedSourceChains.length} source chains, settle: ${bridgeInfo.settleEndpoint}`);\n } else {\n console.warn('[relai:bridge] Bridge info not available — cross-chain payments disabled');\n }\n },\n\n enrich402Response(response: any, ctx: PluginContext) {\n if (!bridgeInfo || bridgeInfo.supportedSourceChains.length === 0) {\n return response;\n }\n\n // Don't add bridge extension if merchant's network is already a source chain\n // and there's only that one chain — no bridging needed\n const merchantCaip2 = response?.accepts?.[0]?.network;\n\n // Filter out the merchant's own chain from source chains\n // (buyer on same chain should pay directly, not bridge)\n const otherSourceChains = bridgeInfo.supportedSourceChains.filter(\n (c: string) => c !== merchantCaip2,\n );\n\n if (otherSourceChains.length === 0) {\n return response;\n }\n\n // Find the payTo for the first available source chain (Solana-first for UX)\n const solanaChain = otherSourceChains.find((c: string) => c.startsWith('solana:'));\n const primaryPayTo = solanaChain\n ? bridgeInfo.payTo[solanaChain]\n : bridgeInfo.payTo[otherSourceChains[0]];\n\n response.extensions = response.extensions || {};\n response.extensions.bridge = {\n info: {\n settleEndpoint: bridgeInfo.settleEndpoint,\n supportedSourceChains: otherSourceChains,\n supportedSourceAssets: bridgeInfo.supportedSourceAssets,\n payTo: primaryPayTo || null,\n payToMap: bridgeInfo.payTo,\n feePayerSvm: bridgeInfo.feePayerSvm,\n feeBps: bridgeInfo.feeBps,\n paymentFacilitator: bridgeInfo.paymentFacilitator,\n ...(serviceKeyHash ? { serviceKeyHash } : {}),\n },\n };\n\n return response;\n },\n };\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\n/**\n * Extended plugin interface with data export for free tier.\n */\nexport interface FreeTierPlugin extends RelaiPlugin {\n /** Export all in-memory usage data (local mode only). Returns null when using cloud mode. */\n getUsageData(): FreeTierUsageExport | null;\n /** Whether plugin is running in local (in-memory) or cloud (RelAI backend) mode. */\n readonly mode: 'local' | 'cloud';\n}\n\nexport interface FreeTierUsageExport {\n mode: 'local';\n config: {\n perBuyerLimit: number;\n resetPeriod: string;\n globalCap: number | null;\n paths: string[];\n };\n /** Per-buyer usage entries */\n usage: Array<{\n buyerId: string;\n path: string;\n count: number;\n periodStart: string;\n lastCall: string;\n }>;\n globalCount: number;\n exportedAt: string;\n}\n\nexport function freeTier(config: FreeTierPluginConfig): FreeTierPlugin {\n const base = (config.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const isLocal = !config.serviceKey;\n const resetPeriod = config.resetPeriod ?? 'none';\n const limit = config.perBuyerLimit;\n const globalCap = config.globalCap ?? null;\n const pluginPaths = config.paths ?? ['*'];\n\n /**\n * Resolve buyer identity from the request.\n * Priority: JWT sub > x-wallet-address > IP fallback\n */\n function resolveBuyerId(req: any): string {\n // 1. JWT Bearer token\n try {\n const auth = req.headers?.authorization || '';\n if (auth.startsWith('Bearer ')) {\n const token = auth.slice(7).trim();\n const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());\n if (payload?.sub) return `user:${payload.sub}`;\n }\n } catch { /* ignore */ }\n\n // 2. Explicit wallet header\n const wallet = req.headers?.['x-wallet-address'] || req.headers?.['x-buyer-address'];\n if (wallet) return `wallet:${wallet}`;\n\n // 3. IP fallback\n const ip =\n (req.headers?.['x-forwarded-for'] || '').split(',')[0].trim() ||\n req.socket?.remoteAddress ||\n req.ip ||\n 'unknown';\n return `ip:${ip}`;\n }\n\n function pathMatches(requestPath: string): boolean {\n return pluginPaths.includes('*') || pluginPaths.some((p) => {\n const normalized = p.toLowerCase().replace(/\\/+$/, '') || '/';\n const reqNormalized = requestPath.toLowerCase().replace(/\\/+$/, '') || '/';\n return normalized === reqNormalized || normalized === '*';\n });\n }\n\n // ---------------------------------------------------------------------------\n // LOCAL IN-MEMORY MODE\n // ---------------------------------------------------------------------------\n\n interface LocalUsageEntry {\n count: number;\n periodStart: string;\n lastCall: string;\n }\n\n // Map key: \"path:buyerId\"\n const localUsage = new Map<string, LocalUsageEntry>();\n let localGlobalCount = 0;\n\n function currentPeriodStart(): string {\n const now = new Date();\n if (resetPeriod === 'daily') {\n return new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();\n }\n if (resetPeriod === 'monthly') {\n return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();\n }\n return 'none'; // permanent\n }\n\n function isCurrentPeriod(entry: LocalUsageEntry): boolean {\n if (resetPeriod === 'none') return true;\n return entry.periodStart === currentPeriodStart();\n }\n\n function localKey(path: string, buyerId: string): string {\n return `${path}:${buyerId}`;\n }\n\n function localCheck(path: string, buyerId: string): FreeTierCheckResponse {\n const key = localKey(path, buyerId);\n const entry = localUsage.get(key);\n\n // If entry is from a previous period, it's stale — treat as 0\n const count = (entry && isCurrentPeriod(entry)) ? entry.count : 0;\n const remaining = Math.max(0, limit - count);\n\n // Global cap check\n if (globalCap != null && localGlobalCount >= globalCap) {\n return { free: false, remaining: 0, total: limit, reason: 'global_cap_reached', globalRemaining: 0 };\n }\n\n if (count >= limit) {\n return { free: false, remaining: 0, total: limit, reason: 'limit_reached' };\n }\n\n return {\n free: true,\n remaining: remaining - 1, // after this call\n total: limit,\n ...(globalCap != null ? { globalRemaining: globalCap - localGlobalCount - 1 } : {}),\n };\n }\n\n function localRecord(path: string, buyerId: string): void {\n const key = localKey(path, buyerId);\n const period = currentPeriodStart();\n const entry = localUsage.get(key);\n const now = new Date().toISOString();\n\n if (entry && isCurrentPeriod(entry)) {\n entry.count++;\n entry.lastCall = now;\n } else {\n localUsage.set(key, { count: 1, periodStart: period, lastCall: now });\n }\n localGlobalCount++;\n }\n\n function exportLocalData(): FreeTierUsageExport {\n const usage: FreeTierUsageExport['usage'] = [];\n for (const [key, entry] of localUsage.entries()) {\n // key format is \"path:buyerId\" but buyerId itself can contain ':'\n // We stored it as `${path}:${buyerId}` where path starts with '/'\n // So we find the first ':' after the path portion\n const firstSlash = key.indexOf('/');\n const colonAfterPath = key.indexOf(':', firstSlash > -1 ? firstSlash : 0);\n const path = colonAfterPath > -1 ? key.slice(0, colonAfterPath) : key;\n const buyerId = colonAfterPath > -1 ? key.slice(colonAfterPath + 1) : 'unknown';\n usage.push({\n buyerId,\n path,\n count: entry.count,\n periodStart: entry.periodStart,\n lastCall: entry.lastCall,\n });\n }\n return {\n mode: 'local',\n config: { perBuyerLimit: limit, resetPeriod, globalCap, paths: pluginPaths },\n usage,\n globalCount: localGlobalCount,\n exportedAt: new Date().toISOString(),\n };\n }\n\n // ---------------------------------------------------------------------------\n // CLOUD MODE (original — requires serviceKey)\n // ---------------------------------------------------------------------------\n\n // Simple in-memory cache: \"serviceKey:path:buyerId\" -> { result, expiresAt }\n const cache = new Map<string, { result: FreeTierCheckResponse; expiresAt: number }>();\n\n function resolveHeaders(): Record<string, string> {\n return {\n 'X-Service-Key': config.serviceKey!,\n 'Content-Type': 'application/json',\n };\n }\n\n function cloudCacheKey(path: string, buyerId: string): string {\n return `${config.serviceKey}:${path}:${buyerId}`;\n }\n\n async function checkFreeTier(path: string, buyerId: string): Promise<FreeTierCheckResponse> {\n const key = cloudCacheKey(path, buyerId);\n const now = Date.now();\n const cached = cache.get(key);\n if (cached && cached.expiresAt > now) {\n return cached.result;\n }\n\n try {\n const res = await fetch(`${base}/v1/plugins/free-tier/check`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n\n if (!res.ok) {\n // Non-blocking: if API unreachable, default to not-free\n return { free: false, reason: `api_error_${res.status}` };\n }\n\n const result = await res.json() as FreeTierCheckResponse;\n cache.set(key, { result, expiresAt: now + cacheTtl });\n return result;\n } catch (err) {\n // Network error - non-blocking, default to paid\n return { free: false, reason: 'network_error' };\n }\n }\n\n async function recordCall(path: string, buyerId: string): Promise<void> {\n try {\n await fetch(`${base}/v1/plugins/free-tier/record`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n // Invalidate cache for this buyer+path after recording\n cache.delete(cloudCacheKey(path, buyerId));\n } catch {\n // Fire-and-forget\n }\n }\n\n async function syncConfig(): Promise<void> {\n try {\n // Check if config already exists on backend — don't overwrite dashboard-managed configs\n const existing = await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'GET',\n headers: resolveHeaders(),\n });\n if (existing.ok) {\n const data = await existing.json();\n if (data.configs && data.configs.length > 0) {\n return; // Config managed via dashboard or previous sync — skip\n }\n }\n\n await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'PUT',\n headers: resolveHeaders(),\n body: JSON.stringify({\n perBuyerLimit: config.perBuyerLimit,\n resetPeriod: config.resetPeriod ?? 'none',\n globalCap: config.globalCap ?? null,\n paths: pluginPaths,\n }),\n });\n } catch (err) {\n console.warn(`[relai:freeTier] Failed to sync config to RelAI: ${err}`);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Plugin instance\n // ---------------------------------------------------------------------------\n\n return {\n name: 'free-tier',\n mode: isLocal ? 'local' : 'cloud',\n\n getUsageData(): FreeTierUsageExport | null {\n if (!isLocal) return null;\n return exportLocalData();\n },\n\n async onInit() {\n if (isLocal) {\n console.log(`[relai:freeTier] Running in local mode (in-memory). perBuyerLimit=${limit}, resetPeriod=${resetPeriod}`);\n return;\n }\n await syncConfig();\n },\n\n async beforePaymentCheck(req, ctx) {\n const requestPath = ctx.path || '/';\n if (!pathMatches(requestPath)) return {};\n\n const buyerId = resolveBuyerId(req);\n\n // ── Local mode ──\n if (isLocal) {\n const result = localCheck(requestPath, buyerId);\n if (result.free) {\n localRecord(requestPath, buyerId);\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? limit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n 'X-Free-Tier-Mode': 'local',\n },\n meta: { freeTier: true, buyerId, remaining: result.remaining, total: result.total ?? limit, mode: 'local' },\n };\n }\n return {};\n }\n\n // ── Cloud mode ──\n const result = await checkFreeTier(requestPath, buyerId);\n\n if (result.free) {\n // Record the call (fire-and-forget)\n recordCall(requestPath, buyerId).catch(() => {});\n\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? config.perBuyerLimit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n },\n meta: {\n freeTier: true,\n buyerId,\n remaining: result.remaining,\n total: result.total ?? config.perBuyerLimit,\n },\n };\n }\n\n return {};\n },\n };\n}\n\n// ============================================================================\n// Shield Plugin\n// ============================================================================\n\nexport interface ShieldPluginConfig {\n /**\n * Custom health check function. Return true if the service is healthy.\n * Takes priority over `healthUrl` if both are provided.\n */\n healthCheck?: () => Promise<boolean> | boolean;\n /**\n * URL to probe. A 2xx response means healthy.\n * Ignored if `healthCheck` is provided.\n */\n healthUrl?: string;\n /** Timeout in ms for the health probe (default: 5000) */\n timeoutMs?: number;\n /** Cache the health result for this many ms (default: 10000) */\n cacheTtlMs?: number;\n /** Message returned to the caller when unhealthy (default: 'Service temporarily unavailable. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when unhealthy (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Shield plugin — protects buyers from paying for unhealthy endpoints.\n *\n * Before the server returns a 402, Shield runs a health check (custom\n * function or URL probe). If the service is down, the request is rejected\n * with 503 instead of asking for payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { shield } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * shield({\n * healthUrl: 'https://my-api.com/health',\n * timeoutMs: 3000,\n * }),\n * ],\n * });\n *\n * // If /health is down, buyers get 503 instead of 402\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function shield(config?: ShieldPluginConfig): RelaiPlugin {\n const timeoutMs = config?.timeoutMs ?? 5000;\n const cacheTtl = config?.cacheTtlMs ?? 10000;\n const unhealthyMessage = config?.unhealthyMessage ?? 'Service temporarily unavailable. Please try again later.';\n const unhealthyStatus = config?.unhealthyStatus ?? 503;\n\n let cached: { healthy: boolean; expiresAt: number } | null = null;\n\n async function checkHealth(): Promise<boolean> {\n const now = Date.now();\n if (cached && cached.expiresAt > now) {\n return cached.healthy;\n }\n\n let healthy = true;\n\n try {\n if (config?.healthCheck) {\n healthy = await Promise.resolve(config.healthCheck());\n } else if (config?.healthUrl) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetch(config.healthUrl, {\n method: 'GET',\n signal: controller.signal,\n });\n healthy = res.ok;\n } finally {\n clearTimeout(timer);\n }\n }\n // No healthCheck and no healthUrl → always healthy (no-op mode)\n } catch {\n healthy = false;\n }\n\n cached = { healthy, expiresAt: now + cacheTtl };\n return healthy;\n }\n\n return {\n name: 'shield',\n\n async onInit() {\n const healthy = await checkHealth();\n console.log(`[relai:shield] Initial health check: ${healthy ? 'healthy ✓' : 'UNHEALTHY ✗'}`);\n },\n\n async beforePaymentCheck(_req, _ctx): Promise<PluginResult> {\n const healthy = await checkHealth();\n\n if (!healthy) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Shield-Status': 'unhealthy',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Shield-Status': 'healthy' },\n };\n },\n };\n}\n\n// ============================================================================\n// Preflight Plugin\n// ============================================================================\n\nexport interface PreflightPluginConfig {\n /**\n * Base URL of the API server (e.g. 'https://my-api.com').\n * The plugin appends the request path to form the probe URL.\n * **Required** — the plugin needs to know where to send the probe.\n */\n baseUrl: string;\n /** Timeout in ms for the preflight probe (default: 3000) */\n timeoutMs?: number;\n /** Cache the probe result for this many ms (default: 5000) */\n cacheTtlMs?: number;\n /** Custom unhealthy message (default: 'Endpoint not responding. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when endpoint unreachable (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Preflight plugin — verifies the specific endpoint responds before payment.\n *\n * Unlike Shield (global health check), Preflight probes the **actual endpoint**\n * the buyer is requesting. It sends a HEAD request with `X-Preflight: true` —\n * the Relai middleware responds 200 instantly without triggering payment.\n * If the endpoint doesn't respond, the buyer gets 503 instead of 402.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { preflight } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * preflight({ baseUrl: 'https://my-api.com' }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function preflight(config: PreflightPluginConfig): RelaiPlugin {\n const baseUrl = config.baseUrl.replace(/\\/+$/, '');\n const timeoutMs = config.timeoutMs ?? 3000;\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const unhealthyMessage = config.unhealthyMessage ?? 'Endpoint not responding. Please try again later.';\n const unhealthyStatus = config.unhealthyStatus ?? 503;\n\n const cache = new Map<string, { ok: boolean; expiresAt: number }>();\n\n async function probeEndpoint(path: string): Promise<boolean> {\n const now = Date.now();\n const cached = cache.get(path);\n if (cached && cached.expiresAt > now) {\n return cached.ok;\n }\n\n let ok = false;\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n const res = await fetch(url, {\n method: 'HEAD',\n headers: { 'X-Preflight': 'true' },\n signal: controller.signal,\n });\n ok = res.ok;\n } finally {\n clearTimeout(timer);\n }\n } catch {\n ok = false;\n }\n\n cache.set(path, { ok, expiresAt: now + cacheTtl });\n return ok;\n }\n\n return {\n name: 'preflight',\n\n async onInit() {\n // Probe the base URL on startup\n const ok = await probeEndpoint('/');\n console.log(`[relai:preflight] Initial probe ${baseUrl}: ${ok ? 'reachable ✓' : 'UNREACHABLE ✗'}`);\n },\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const path = ctx.path || '/';\n const ok = await probeEndpoint(path);\n\n if (!ok) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Preflight-Status': 'unreachable',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Preflight-Status': 'ok' },\n };\n },\n };\n}\n\n\n// ============================================================================\n// Circuit Breaker Plugin\n// ============================================================================\n\nexport type CircuitState = 'closed' | 'open' | 'half-open';\n\nexport interface CircuitBreakerPluginConfig {\n /**\n * Number of consecutive failures before the circuit opens.\n * Default: 5\n */\n failureThreshold?: number;\n /**\n * Time in ms the circuit stays open before moving to half-open.\n * Default: 30000 (30 seconds)\n */\n resetTimeMs?: number;\n /**\n * Number of successful requests in half-open state before closing the circuit.\n * Default: 2\n */\n halfOpenSuccesses?: number;\n /** HTTP status code when circuit is open (default: 503) */\n openStatus?: number;\n /** Error message when circuit is open */\n openMessage?: string;\n /**\n * HTTP status codes considered as failures.\n * Default: [500, 502, 503, 504]\n */\n failureCodes?: number[];\n /**\n * Track failures globally or per-path.\n * Default: 'per-path'\n */\n scope?: 'global' | 'per-path';\n}\n\ninterface CircuitEntry {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureAt: number;\n}\n\n/**\n * Circuit Breaker plugin — tracks failure history and opens the circuit\n * after repeated failures, preventing buyers from paying for broken endpoints.\n *\n * Unlike Shield/Preflight (real-time probes), Circuit Breaker is **zero-latency** —\n * it makes no additional HTTP requests. Instead, it tracks `afterSettled` outcomes\n * and rejects requests when the failure rate exceeds the threshold.\n *\n * States:\n * - **closed** — normal operation, requests flow through\n * - **open** — too many failures, requests are rejected with 503\n * - **half-open** — after resetTime, allows a few test requests through\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { circuitBreaker } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * circuitBreaker({ failureThreshold: 5, resetTimeMs: 30000 }),\n * ],\n * });\n * ```\n */\nexport function circuitBreaker(config?: CircuitBreakerPluginConfig): RelaiPlugin {\n const failureThreshold = config?.failureThreshold ?? 5;\n const resetTimeMs = config?.resetTimeMs ?? 30000;\n const halfOpenSuccesses = config?.halfOpenSuccesses ?? 2;\n const openStatus = config?.openStatus ?? 503;\n const openMessage = config?.openMessage ?? 'Service temporarily unavailable (circuit open). Please try again later.';\n const failureCodes = new Set(config?.failureCodes ?? [500, 502, 503, 504]);\n const scope = config?.scope ?? 'per-path';\n\n const circuits = new Map<string, CircuitEntry>();\n\n function getKey(path: string): string {\n return scope === 'global' ? '__global__' : path;\n }\n\n function getCircuit(key: string): CircuitEntry {\n let entry = circuits.get(key);\n if (!entry) {\n entry = { state: 'closed', failures: 0, successes: 0, lastFailureAt: 0 };\n circuits.set(key, entry);\n }\n return entry;\n }\n\n function recordFailure(key: string): void {\n const circuit = getCircuit(key);\n circuit.failures++;\n circuit.successes = 0;\n circuit.lastFailureAt = Date.now();\n\n if (circuit.failures >= failureThreshold) {\n circuit.state = 'open';\n }\n }\n\n function recordSuccess(key: string): void {\n const circuit = getCircuit(key);\n if (circuit.state === 'half-open') {\n circuit.successes++;\n if (circuit.successes >= halfOpenSuccesses) {\n // Enough successes in half-open → close the circuit\n circuit.state = 'closed';\n circuit.failures = 0;\n circuit.successes = 0;\n }\n } else {\n // Reset failures on success in closed state\n circuit.failures = 0;\n circuit.successes = 0;\n }\n }\n\n return {\n name: 'circuit-breaker',\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const key = getKey(ctx.path || '/');\n const circuit = getCircuit(key);\n const now = Date.now();\n\n // Check if open circuit should transition to half-open\n if (circuit.state === 'open' && (now - circuit.lastFailureAt) >= resetTimeMs) {\n circuit.state = 'half-open';\n circuit.successes = 0;\n }\n\n if (circuit.state === 'open') {\n const retryAfter = Math.max(1, Math.ceil((resetTimeMs - (now - circuit.lastFailureAt)) / 1000));\n return {\n reject: true,\n rejectStatus: openStatus,\n rejectMessage: openMessage,\n headers: {\n 'X-Circuit-State': 'open',\n 'X-Circuit-Failures': String(circuit.failures),\n 'Retry-After': String(retryAfter),\n },\n };\n }\n\n return {\n headers: {\n 'X-Circuit-State': circuit.state,\n ...(circuit.state === 'half-open' ? { 'X-Circuit-Successes': String(circuit.successes) } : {}),\n },\n };\n },\n\n async afterSettled(_req, result, ctx): Promise<void> {\n const key = getKey(ctx.path || '/');\n\n // Check if the settlement itself failed or returned error codes\n if (!result.success) {\n recordFailure(key);\n return;\n }\n\n // Check response status if available\n const status = (result as any).statusCode || (result as any).status;\n if (status && failureCodes.has(status)) {\n recordFailure(key);\n } else {\n recordSuccess(key);\n }\n },\n };\n}\n\n\n// ============================================================================\n// Refund Plugin\n// ============================================================================\n\nexport interface RefundPluginConfig {\n /**\n * HTTP status codes that trigger a refund/credit after payment.\n * Default: [500, 502, 503, 504]\n */\n triggerCodes?: number[];\n /**\n * What to do when a refund is triggered.\n * - 'credit': record a credit that the free-tier plugin can consume later\n * - 'log': just log the event (useful with a custom onRefund callback)\n * Default: 'credit'\n */\n mode?: 'credit' | 'log';\n /**\n * Called whenever a refund event is triggered.\n * Use for external refund processing, logging, or notifications.\n */\n onRefund?: (event: RefundEvent) => void | Promise<void>;\n /**\n * Also trigger refund when settlement itself fails (result.success === false).\n * Default: true\n */\n refundOnSettlementFailure?: boolean;\n}\n\nexport interface RefundEvent {\n /** Buyer wallet address */\n payer: string;\n /** Transaction ID of the original payment */\n transactionId: string;\n /** Network used */\n network: string;\n /** Amount paid in USD */\n amount: number;\n /** Request path */\n path: string;\n /** Reason for the refund */\n reason: 'endpoint_error' | 'settlement_failure' | 'timeout';\n /** HTTP status code that triggered the refund (if applicable) */\n statusCode?: number;\n /** Timestamp */\n timestamp: number;\n}\n\n/**\n * Refund plugin — automatically handles refund/credit when a paid request fails.\n *\n * After payment settles, if the endpoint returns an error (e.g. 500),\n * the plugin records a credit or calls your custom `onRefund` handler.\n * Credits can be consumed by the free-tier plugin on the next request.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { refund } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * refund({\n * triggerCodes: [500, 502, 503],\n * onRefund: (event) => {\n * console.log(`Refund for ${event.payer}: $${event.amount} on ${event.path}`);\n * // Notify buyer, record in DB, etc.\n * },\n * }),\n * ],\n * });\n * ```\n */\nexport function refund(config?: RefundPluginConfig): RelaiPlugin {\n const triggerCodes = new Set(config?.triggerCodes ?? [500, 502, 503, 504]);\n const mode = config?.mode ?? 'credit';\n const onRefund = config?.onRefund;\n const refundOnSettlementFailure = config?.refundOnSettlementFailure ?? true;\n\n // In-memory credit ledger: payer → number of credits\n const credits = new Map<string, number>();\n\n return {\n name: 'refund',\n\n async beforePaymentCheck(req, ctx): Promise<PluginResult> {\n // If buyer has credits from previous refunds, skip payment\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.headers?.['authorization']?.replace(/^Bearer\\s+/i, '') ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n\n const buyerCredits = credits.get(buyerId) || 0;\n if (buyerCredits > 0) {\n credits.set(buyerId, buyerCredits - 1);\n return {\n skip: true,\n headers: {\n 'X-Refund-Credit': 'applied',\n 'X-Refund-Credits-Remaining': String(buyerCredits - 1),\n },\n meta: {\n refundCredit: true,\n creditsRemaining: buyerCredits - 1,\n },\n };\n }\n }\n\n return {};\n },\n\n async afterSettled(req, result, ctx): Promise<void> {\n const payer = result.payer || 'unknown';\n const transactionId = result.transaction || '';\n\n // Settlement failure\n if (!result.success && refundOnSettlementFailure) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'settlement_failure',\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n return;\n }\n\n // Check for endpoint error codes in settlement result\n const status = (result as any).statusCode || (result as any).status;\n if (status && triggerCodes.has(status)) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'endpoint_error',\n statusCode: status,\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n }\n },\n };\n}\n\n// Re-export for backwards-compatible import path '@relai-fi/x402/plugins'\nexport { submitRelayFeedback, type RelayFeedbackConfig } from './relay-feedback';\n\n// ============================================================================\n// Score Plugin\n// ============================================================================\n\nexport interface ScorePluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n * Find yours in the RelAI dashboard under \"On-chain Identity\".\n */\n agentId: string | number;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha\n */\n rpcUrl?: string;\n /**\n * ERC-8004 IdentityRegistry contract address.\n * Default: process.env.ERC8004_IDENTITY_REGISTRY\n */\n identityRegistryAddress?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\ninterface CachedScore {\n score: Record<string, unknown>;\n expiresAt: number;\n}\n\nconst SCORE_IDENTITY_ABI = [\n 'function ownerOf(uint256 tokenId) external view returns (address)',\n];\n\nconst SCORE_FEEDBACK_TOPIC = ethers.id(\n 'NewFeedback(uint256,address,uint64,int128,uint8,string,string,string,string,string,bytes32)'\n);\n\nconst SCORE_FEEDBACK_IFACE = new ethers.Interface([\n 'event NewFeedback(uint256 indexed agentId, address indexed giver, uint64 index, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, string responseURI, bytes32 feedbackHash)',\n]);\n\nconst SCORE_BATCH = 1900;\nconst SCORE_HISTORY = 10000;\n\n/**\n * Score plugin — fetches ERC-8004 on-chain reputation for your API\n * directly via RPC and injects it into the 402 response `extensions.score`.\n *\n * Agents can read the score **before paying**, enabling trust-based routing:\n * - `feedbackCount` — number of on-chain feedback entries\n * - `successRate` — 0–100 (% of successful calls)\n * - `avgResponseMs` — average response time in milliseconds\n * - `verified` — true if agentId is registered on-chain\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * ],\n * });\n * ```\n */\nexport function score(config: ScorePluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:score] ERC8004_AGENT_ID not set — score plugin is a no-op');\n return { name: 'score', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n let cached: CachedScore | null = null;\n\n function getContracts() {\n const identityAddress = config.identityRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_IDENTITY_REGISTRY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n if (!identityAddress || !reputationAddress) return null;\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const identity = new ethers.Contract(identityAddress, SCORE_IDENTITY_ABI, provider);\n return { provider, identity, reputationAddress };\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && cached.expiresAt > Date.now()) {\n return cached.score;\n }\n\n const contracts = getContracts();\n if (!contracts) {\n console.warn(`[relai:score] ERC8004_IDENTITY_REGISTRY or ERC8004_REPUTATION_REGISTRY not set — score disabled`);\n return null;\n }\n\n const { provider, identity, reputationAddress } = contracts;\n const bigId = BigInt(agentId);\n\n try {\n let verified = false;\n try {\n const owner = await identity.ownerOf(bigId);\n verified = !!owner;\n } catch {\n // Not registered\n }\n\n if (!verified) {\n const scoreObj = { feedbackCount: 0, successRate: null, avgResponseMs: null, verified: false, agentId, source: 'erc8004-skale' };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n }\n\n const latest = await provider.getBlockNumber();\n const agentIdTopic = ethers.zeroPadValue(ethers.toBeHex(bigId), 32);\n const logs: any[] = [];\n\n for (let from = Math.max(0, latest - SCORE_HISTORY); from <= latest; from += SCORE_BATCH) {\n const to = Math.min(from + SCORE_BATCH - 1, latest);\n const chunk = await provider.getLogs({\n address: reputationAddress,\n topics: [SCORE_FEEDBACK_TOPIC, agentIdTopic],\n fromBlock: from,\n toBlock: to,\n });\n logs.push(...chunk);\n }\n\n const srValues: number[] = [];\n const rtValues: number[] = [];\n const endpointMap: Record<string, { sr: number[]; rt: number[] }> = {};\n\n for (const log of logs) {\n try {\n const p = SCORE_FEEDBACK_IFACE.parseLog({ topics: log.topics as string[], data: log.data });\n if (!p) continue;\n const val = Number(p.args.value) / Math.pow(10, Number(p.args.valueDecimals));\n const ep: string = p.args.endpoint || '';\n if (!endpointMap[ep]) endpointMap[ep] = { sr: [], rt: [] };\n if (p.args.tag1 === 'successRate') { srValues.push(val); endpointMap[ep].sr.push(val); }\n else if (p.args.tag1 === 'responseTime') { rtValues.push(val); endpointMap[ep].rt.push(val); }\n } catch { /* skip */ }\n }\n\n const feedbackCount = srValues.length + rtValues.length;\n const successRate = srValues.length\n ? Math.round(srValues.reduce((a, b) => a + b, 0) / srValues.length)\n : null;\n const avgResponseMs = rtValues.length\n ? Math.round(rtValues.reduce((a, b) => a + b, 0) / rtValues.length)\n : null;\n\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n for (const [ep, { sr, rt }] of Object.entries(endpointMap)) {\n endpoints[ep] = {\n feedbackCount: sr.length + rt.length,\n successRate: sr.length ? Math.round(sr.reduce((a, b) => a + b, 0) / sr.length) : null,\n avgResponseMs: rt.length ? Math.round(rt.reduce((a, b) => a + b, 0) / rt.length) : null,\n };\n }\n\n const scoreObj = { feedbackCount, successRate, avgResponseMs, verified: true, agentId, source: 'erc8004-skale', endpoints };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err) {\n console.warn(`[relai:score] fetchScore RPC error for agentId=${agentId}:`, err);\n return null;\n }\n }\n\n return {\n name: 'score',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:score] Initialized — agentId=${agentId}, feedback=${s.feedbackCount}, successRate=${s.successRate}%, avgResp=${s.avgResponseMs}ms`);\n } else {\n console.warn(`[relai:score] Could not load score for agentId=${agentId} — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any, _ctx: PluginContext) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n score: scoreData,\n },\n };\n },\n };\n}\n\n// ============================================================================\n// Feedback Plugin\n// ============================================================================\n\nexport interface FeedbackPluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n */\n agentId: string | number;\n /**\n * Private key of the wallet that signs feedback transactions.\n * The wallet must hold CREDIT tokens on SKALE Base to pay gas.\n * Default: process.env.BACKEND_WALLET_PRIVATE_KEY\n */\n walletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Tag2 label for all feedback entries (default: 'x402').\n */\n tag2?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n * Value: 1 (success) or 0 (failure).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n * Value: milliseconds elapsed since request start.\n * Requires req.x402StartTime (set automatically if using Relai.protect()).\n */\n submitResponseTime?: boolean;\n}\n\nconst FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Feedback plugin — submits ERC-8004 on-chain reputation after every successful x402 settlement.\n *\n * Records `successRate` and `responseTime` signals to the ReputationRegistry on SKALE Base.\n * This is the mechanism that **builds** the score that `score()` plugin later reads.\n *\n * Runs fire-and-forget — never blocks the response.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score, feedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * feedback({ agentId: '5' }), // needs BACKEND_WALLET_PRIVATE_KEY in env\n * ],\n * });\n * ```\n */\nexport function feedback(config: FeedbackPluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:feedback] ERC8004_AGENT_ID not set — feedback plugin is a no-op');\n return { name: 'feedback', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const tag2 = config.tag2 ?? 'x402';\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n let _reputation: any = null;\n\n function getReputation() {\n if (_reputation) return _reputation;\n\n const privateKey = config.walletPrivateKey\n ?? (typeof process !== 'undefined' ? process.env?.BACKEND_WALLET_PRIVATE_KEY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) return null;\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n _reputation = new ethers.Contract(reputationAddress, FEEDBACK_REPUTATION_ABI, signer);\n return _reputation;\n }\n\n function submitAsync(tag1: string, value: number, valueDecimals: number, endpoint: string) {\n const reputation = getReputation();\n if (!reputation) return;\n\n (async () => {\n try {\n const tx = await reputation.giveFeedback(\n BigInt(agentId),\n BigInt(Math.round(value)),\n valueDecimals,\n tag1,\n tag2,\n endpoint,\n '',\n ethers.ZeroHash,\n );\n console.log(`[relai:feedback] Submitted agentId=${agentId} tag=${tag1} value=${value} tx=${tx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:feedback] giveFeedback failed (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'feedback',\n\n async onInit() {\n const reputation = getReputation();\n if (reputation) {\n console.log(`[relai:feedback] Initialized — agentId=${agentId}, submitSuccessRate=${submitSuccessRate}, submitResponseTime=${submitResponseTime}`);\n } else {\n console.warn(`[relai:feedback] BACKEND_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — feedback disabled`);\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n\n // successRate: 1 = success, 0 = failure\n if (submitSuccessRate) {\n submitAsync('successRate', result.success ? 1 : 0, 0, endpoint);\n }\n\n // responseTime in ms — use req.x402StartTime if available\n if (submitResponseTime) {\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n if (responseTimeMs > 0) {\n submitAsync('responseTime', responseTimeMs, 0, endpoint);\n }\n }\n },\n };\n}\n\n// ============================================================================\n// Solana Feedback Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaFeedbackPluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Private key of the feedback wallet — base58 or JSON array format.\n * Should differ from the owner's registration wallet (avoids self-feedback restriction).\n * Default: process.env.SOLANA_8004_FEEDBACK_KEY ?? process.env.SOLANA_8004_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n */\n submitResponseTime?: boolean;\n}\n\n/**\n * Solana Feedback plugin — submits 8004-solana on-chain reputation after x402 settlement.\n *\n * Uses the native Solana `8004-solana` program (MPL Core NFT registry),\n * separate from the SKALE EVM ReputationRegistry used by `feedback()`.\n *\n * Requires `8004-solana` package to be installed:\n * ```\n * npm install 8004-solana\n * ```\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaFeedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaFeedback({\n * assetPubkey: process.env.SOLANA_AGENT_ASSET!, // e.g. 'GH93tGR8...'\n * }),\n * ],\n * });\n * ```\n */\nexport function solanaFeedback(config: SolanaFeedbackPluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n function getPrivateKey(): string | null {\n return config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.SOLANA_8004_FEEDBACK_KEY ?? process.env?.SOLANA_8004_PRIVATE_KEY\n : undefined)\n ?? null;\n }\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function buildSDK(): Promise<any> {\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n const privateKeyRaw = getPrivateKey();\n if (!privateKeyRaw) return null;\n\n let secretKey: Uint8Array;\n try {\n const parsed = JSON.parse(privateKeyRaw);\n if (Array.isArray(parsed)) {\n secretKey = Uint8Array.from(parsed);\n } else {\n throw new Error('not array');\n }\n } catch {\n // base58\n try {\n const bs58Mod = await import('bs58' as any);\n const decode = bs58Mod.default?.decode ?? bs58Mod.decode;\n secretKey = decode(privateKeyRaw);\n } catch {\n console.warn('[relai:solanaFeedback] Could not parse feedbackWalletPrivateKey');\n return null;\n }\n }\n\n const { Keypair } = await import('@solana/web3.js');\n const signer = Keypair.fromSecretKey(secretKey);\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n\n return new SolanaSDK({ cluster, signer, ...(rpcUrl ? { rpcUrl } : {}) });\n }\n\n function submitAsync(isSuccess: boolean, responseTimeMs: number, endpoint: string) {\n (async () => {\n try {\n const sdk = await buildSDK();\n if (!sdk) return;\n\n const { PublicKey } = await import('@solana/web3.js');\n const pubkey = new PublicKey(assetPubkey);\n\n if (submitSuccessRate) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(isSuccess ? 10000 : 0),\n tag1: 'successRate',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] successRate submitted asset=${assetPubkey.slice(0, 8)}... tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n }\n\n if (submitResponseTime && responseTimeMs > 0) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(Math.round(responseTimeMs)),\n tag1: 'responseTime',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] responseTime submitted asset=${assetPubkey.slice(0, 8)}... ms=${responseTimeMs} tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] feedback error (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'solanaFeedback',\n\n async onInit() {\n const privateKey = getPrivateKey();\n if (!privateKey) {\n console.warn('[relai:solanaFeedback] SOLANA_8004_FEEDBACK_KEY not set — Solana feedback disabled');\n return;\n }\n try {\n await import('8004-solana' as any);\n console.log(`[relai:solanaFeedback] Initialized — asset=${assetPubkey.slice(0, 8)}... cluster=${getCluster()}`);\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n\n submitAsync(result.success, responseTimeMs, endpoint);\n },\n };\n}\n\n// ============================================================================\n// Solana Score Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaScorePluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\n/**\n * Solana Score plugin — fetches 8004-solana on-chain reputation and injects it\n * into the 402 response `extensions.score` before the client pays.\n *\n * Mirrors the EVM `score()` plugin but reads from the native Solana program\n * instead of the SKALE EVM ReputationRegistry.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaScore } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaScore({ assetPubkey: process.env.SOLANA_AGENT_ASSET! }),\n * ],\n * });\n * ```\n */\nexport function solanaScore(config: SolanaScorePluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n\n if (!assetPubkey || String(assetPubkey) === 'undefined') {\n console.warn('[relai:solanaScore] assetPubkey not set — solanaScore plugin is a no-op');\n return { name: 'solanaScore', async onInit() {} };\n }\n\n let cached: CachedScore | null = null;\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && Date.now() < cached.expiresAt) return cached.score;\n\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaScore] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n try {\n const { PublicKey } = await import('@solana/web3.js');\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n const sdk = new SolanaSDK({ cluster, ...(rpcUrl ? { rpcUrl } : {}) });\n const pubkey = new PublicKey(assetPubkey);\n\n // Try getReputationSummary first (lightweight)\n let feedbackCount = 0;\n let successRate: number | null = null;\n let avgResponseMs: number | null = null;\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n\n // Try to read all feedback for per-endpoint breakdown\n let allFeedbacks: any[] = [];\n try {\n allFeedbacks = await sdk.readAllFeedback(pubkey);\n } catch {\n // fallback to summary only\n }\n\n if (allFeedbacks.length > 0) {\n // Group by endpoint\n const byEndpoint: Record<string, { successValues: number[]; responseTimes: number[] }> = {};\n let globalSuccessValues: number[] = [];\n let globalResponseTimes: number[] = [];\n\n for (const entry of allFeedbacks) {\n const tag1: string = entry.tag1 ?? '';\n const rawValue = entry.value ?? entry.score ?? 0;\n const value = typeof rawValue === 'bigint' ? Number(rawValue) : Number(rawValue);\n const ep: string = entry.endpoint ?? '';\n\n if (!byEndpoint[ep]) byEndpoint[ep] = { successValues: [], responseTimes: [] };\n\n if (tag1 === 'successRate') {\n globalSuccessValues.push(value);\n byEndpoint[ep].successValues.push(value);\n } else if (tag1 === 'responseTime') {\n globalResponseTimes.push(value);\n byEndpoint[ep].responseTimes.push(value);\n }\n }\n\n feedbackCount = globalSuccessValues.length;\n if (feedbackCount > 0) {\n const avgSuccess = globalSuccessValues.reduce((a, b) => a + b, 0) / feedbackCount;\n // value=10000 means success (100%), value=0 means failure\n successRate = Math.round((avgSuccess / 10000) * 100);\n }\n if (globalResponseTimes.length > 0) {\n avgResponseMs = Math.round(globalResponseTimes.reduce((a, b) => a + b, 0) / globalResponseTimes.length);\n }\n\n for (const [ep, data] of Object.entries(byEndpoint)) {\n const epCount = data.successValues.length;\n const epAvgSuccess = epCount > 0\n ? data.successValues.reduce((a, b) => a + b, 0) / epCount\n : null;\n const epAvgMs = data.responseTimes.length > 0\n ? Math.round(data.responseTimes.reduce((a, b) => a + b, 0) / data.responseTimes.length)\n : null;\n endpoints[ep || '/'] = {\n feedbackCount: epCount,\n successRate: epAvgSuccess !== null ? Math.round((epAvgSuccess / 10000) * 100) : null,\n avgResponseMs: epAvgMs,\n };\n }\n } else {\n // Fallback to summary\n try {\n const summary = await sdk.getReputationSummary(pubkey);\n feedbackCount = summary.count ?? 0;\n if (feedbackCount > 0) {\n const avg = summary.averageScore ?? 0;\n successRate = Math.round((avg / 10000) * 100);\n }\n } catch {\n // no data\n }\n }\n\n const scoreObj = {\n feedbackCount,\n successRate,\n avgResponseMs,\n assetPubkey,\n source: '8004-solana',\n cluster,\n ...(Object.keys(endpoints).length > 0 ? { endpoints } : {}),\n };\n\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err: any) {\n console.warn(`[relai:solanaScore] fetch error (non-fatal): ${err?.message}`);\n return null;\n }\n }\n\n return {\n name: 'solanaScore',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:solanaScore] Initialized — asset=${assetPubkey.slice(0, 8)}... feedbackCount=${(s as any).feedbackCount} cluster=${getCluster()}`);\n } else {\n console.warn(`[relai:solanaScore] Could not load score for asset=${assetPubkey.slice(0, 8)}... — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n score: scoreData,\n },\n };\n },\n };\n}\n","// src/relay-feedback.ts\n// Standalone utility for submitting ERC-8004 feedback about third-party APIs.\n// Not a plugin — call directly from your relay/aggregator application code.\n\nimport { ethers } from 'ethers';\n\nexport interface RelayFeedbackConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) of the **target API** you are calling.\n */\n agentId: string | number;\n /**\n * Whether the API call succeeded.\n */\n success: boolean;\n /**\n * Elapsed milliseconds for the API call.\n */\n responseTimeMs?: number;\n /**\n * Endpoint path of the called API (e.g. '/v1/data').\n */\n endpoint?: string;\n /**\n * Private key of the **relay/third-party** wallet that signs feedback.\n * MUST be different from the API owner's wallet — ReputationRegistry\n * may restrict self-feedback. Wallet needs CREDIT tokens on SKALE Base.\n * Default: process.env.FEEDBACK_WALLET_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n}\n\nconst RELAY_FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Submit ERC-8004 on-chain feedback about a **third-party API** you called.\n *\n * Call this fire-and-forget from your relay/aggregator after every external API call.\n * Uses a separate relay wallet (not the API owner's key) to avoid self-feedback restrictions.\n *\n * Records:\n * - `successRate`: 10000 (= 100%) on success, 0 on failure — 2 decimal places\n * - `responseTime`: elapsed milliseconds\n *\n * @example\n * ```typescript\n * import { submitRelayFeedback } from '@relai-fi/x402/relay-feedback';\n *\n * // after calling an external API:\n * const start = Date.now();\n * const result = await fetch('https://other-api.com/data');\n * submitRelayFeedback({\n * agentId: '5',\n * success: result.ok,\n * responseTimeMs: Date.now() - start,\n * endpoint: '/data',\n * });\n * ```\n */\nexport function submitRelayFeedback(config: RelayFeedbackConfig): void {\n const agentId = String(config.agentId);\n const endpoint = config.endpoint ?? '';\n const responseTimeMs = config.responseTimeMs ?? 0;\n\n const privateKey = config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.FEEDBACK_WALLET_PRIVATE_KEY ?? process.env?.ERC8004_FEEDBACK_WALLET_PRIVATE_KEY\n : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) {\n console.warn('[relai:submitRelayFeedback] FEEDBACK_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — skipping');\n return;\n }\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n const reputation = new ethers.Contract(reputationAddress, RELAY_FEEDBACK_REPUTATION_ABI, signer);\n const id = BigInt(agentId);\n\n (async () => {\n const successValue = config.success ? 10000n : 0n;\n try {\n const srTx = await reputation.giveFeedback(\n id, successValue, 2, 'successRate', '', endpoint, '', ethers.ZeroHash,\n );\n await srTx.wait();\n console.log(`[relai:submitRelayFeedback] successRate confirmed agentId=${agentId} success=${config.success}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n\n if (responseTimeMs > 0) {\n try {\n const rtTx = await reputation.giveFeedback(\n id, BigInt(Math.max(0, Math.round(responseTimeMs))), 0, 'responseTime', '', endpoint, '', ethers.ZeroHash,\n );\n console.log(`[relai:submitRelayFeedback] responseTime sent agentId=${agentId} ms=${responseTimeMs} tx=${rtTx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n })();\n}\n"],"mappings":"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","names":["import_ethers","crypto","result","scoreObj"]}
1
+ {"version":3,"sources":["../src/plugins.ts","../src/relay-feedback.ts"],"sourcesContent":["// src/plugins.ts\n// RelAI Plugin System - extensible middleware hooks for Relai.protect()\n\nimport crypto from 'crypto';\nimport { ethers } from 'ethers';\nimport type { RelaiNetwork } from './types';\nimport type { SettleResult } from './server';\n\nconst RELAI_API_BASE = 'https://api.relai.fi';\n\n// ============================================================================\n// Plugin Interface\n// ============================================================================\n\nexport interface PluginContext {\n /** Network for this endpoint */\n network: RelaiNetwork;\n /** Price in USD */\n price: number;\n /** Request path */\n path: string;\n /** HTTP method */\n method: string;\n}\n\nexport interface PluginResult {\n /** If true, skip payment and serve content for free */\n skip?: boolean;\n /** If true, reject the request entirely (e.g. service unhealthy) */\n reject?: boolean;\n /** HTTP status code when rejecting (default: 503) */\n rejectStatus?: number;\n /** Error message when rejecting */\n rejectMessage?: string;\n /** Extra response headers to set */\n headers?: Record<string, string>;\n /** Metadata attached to req.pluginMeta */\n meta?: Record<string, unknown>;\n}\n\nexport interface RelaiPlugin {\n /** Unique plugin name */\n name: string;\n\n /**\n * Called before the 402 payment check.\n * Return { skip: true } to bypass payment entirely.\n */\n beforePaymentCheck?(req: any, ctx: PluginContext): Promise<PluginResult>;\n\n /**\n * Called after a successful payment settlement.\n * Use for analytics, logging, webhooks, etc.\n */\n afterSettled?(req: any, result: SettleResult, ctx: PluginContext): Promise<void>;\n\n /**\n * Called once when the Relai instance initializes (server start).\n * Use to sync config to RelAI backend or validate credentials.\n */\n onInit?(): Promise<void>;\n\n /**\n * Called before sending the 402 response. Allows plugins to add\n * extensions or modify the response body (e.g. bridge info).\n */\n enrich402Response?(response: any, ctx: PluginContext): any;\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\nexport interface FreeTierPluginConfig {\n /** Service key (sk_live_...) for syncing with RelAI backend. If omitted, runs in local in-memory mode. */\n serviceKey?: string;\n /** Max free calls per buyer per period */\n perBuyerLimit: number;\n /** Reset period for per-buyer counters */\n resetPeriod?: 'none' | 'daily' | 'monthly';\n /** Optional global cap across all buyers */\n globalCap?: number;\n /** Specific paths to apply free tier to (default: '*' = all) */\n paths?: string[];\n /** Override RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Cache TTL in ms for check results (default: 5000) */\n cacheTtlMs?: number;\n}\n\ninterface FreeTierCheckResponse {\n free: boolean;\n remaining?: number;\n total?: number;\n reason?: string;\n globalRemaining?: number;\n}\n\n/**\n * Free Tier plugin - gives buyers a number of free API calls\n * before requiring payment.\n *\n * State is stored in the RelAI backend, keyed by your service key.\n * Config can be set here (SDK-side) or overridden in the relai.fi dashboard.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { freeTier } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * freeTier({\n * serviceKey: process.env.RELAI_SERVICE_KEY!,\n * perBuyerLimit: 10,\n * resetPeriod: 'daily',\n * }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\n// ============================================================================\n// Bridge Plugin\n// ============================================================================\n\nexport interface BridgePluginConfig {\n /** Service key (sk_live_...) for tracking bridge usage per API owner */\n serviceKey?: string;\n /** RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Override settle endpoint (auto-discovered from /bridge/info if not set) */\n settleEndpoint?: string;\n /** Override supported source chains (auto-discovered if not set) */\n supportedSourceChains?: string[];\n /** Override supported source assets (auto-discovered if not set) */\n supportedSourceAssets?: string[];\n /** Override bridge payTo map: { [caip2]: address } */\n payTo?: Record<string, string>;\n /** Override Solana fee payer address (auto-discovered if not set) */\n feePayerSvm?: string;\n /** Override payment facilitator URL */\n paymentFacilitator?: string;\n /** Bridge fee in basis points (default: auto-discovered) */\n feeBps?: number;\n}\n\ninterface BridgeInfo {\n settleEndpoint: string;\n supportedSourceChains: string[];\n supportedSourceAssets: string[];\n payTo: Record<string, string>;\n feePayerSvm: string | null;\n feeBps: number;\n paymentFacilitator: string;\n}\n\n/**\n * Bridge plugin - enables cross-chain payments via the RelAI bridge.\n *\n * When a buyer's wallet is on a different chain than the merchant accepts,\n * the client SDK can automatically route the payment through the bridge.\n * This plugin adds `extensions.bridge` to the 402 response with all the\n * info the client needs to execute a cross-chain payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { bridge } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'skale-base',\n * plugins: [\n * bridge(), // auto-discovers from https://api.relai.fi\n * ],\n * });\n *\n * // Buyer on Solana can now pay for a SKALE endpoint\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.05,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\nexport function bridge(config?: BridgePluginConfig): RelaiPlugin {\n const base = (config?.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n let bridgeInfo: BridgeInfo | null = null;\n\n // Hash service key for tracking (never expose raw key to clients)\n // Must match backend: crypto.createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16)\n let serviceKeyHash: string | null = null;\n if (config?.serviceKey) {\n serviceKeyHash = crypto.createHash('sha256').update(config.serviceKey).digest('hex').slice(0, 16);\n }\n\n async function fetchBridgeInfo(): Promise<BridgeInfo | null> {\n try {\n const res = await fetch(`${base}/bridge/info`);\n if (!res.ok) {\n console.warn(`[relai:bridge] Failed to fetch /bridge/info: ${res.status}`);\n return null;\n }\n const data = await res.json() as any;\n return {\n settleEndpoint: config?.settleEndpoint || data.settleEndpoint,\n supportedSourceChains: config?.supportedSourceChains || data.supportedSourceChains || [],\n supportedSourceAssets: config?.supportedSourceAssets || data.supportedSourceAssets || [],\n payTo: config?.payTo || data.payTo || {},\n feePayerSvm: config?.feePayerSvm ?? data.feePayerSvm ?? null,\n feeBps: config?.feeBps ?? data.feeBps ?? 100,\n paymentFacilitator: config?.paymentFacilitator || data.paymentFacilitator || 'https://facilitator.x402.fi',\n };\n } catch (err) {\n console.warn(`[relai:bridge] Failed to fetch bridge info: ${err}`);\n return null;\n }\n }\n\n return {\n name: 'bridge',\n\n async onInit() {\n bridgeInfo = await fetchBridgeInfo();\n if (bridgeInfo) {\n console.log(`[relai:bridge] Initialized — ${bridgeInfo.supportedSourceChains.length} source chains, settle: ${bridgeInfo.settleEndpoint}`);\n } else {\n console.warn('[relai:bridge] Bridge info not available — cross-chain payments disabled');\n }\n },\n\n enrich402Response(response: any, ctx: PluginContext) {\n if (!bridgeInfo || bridgeInfo.supportedSourceChains.length === 0) {\n return response;\n }\n\n // Don't add bridge extension if merchant's network is already a source chain\n // and there's only that one chain — no bridging needed\n const merchantCaip2 = response?.accepts?.[0]?.network;\n\n // Filter out the merchant's own chain from source chains\n // (buyer on same chain should pay directly, not bridge)\n const otherSourceChains = bridgeInfo.supportedSourceChains.filter(\n (c: string) => c !== merchantCaip2,\n );\n\n if (otherSourceChains.length === 0) {\n return response;\n }\n\n // Find the payTo for the first available source chain (Solana-first for UX)\n const solanaChain = otherSourceChains.find((c: string) => c.startsWith('solana:'));\n const primaryPayTo = solanaChain\n ? bridgeInfo.payTo[solanaChain]\n : bridgeInfo.payTo[otherSourceChains[0]];\n\n response.extensions = response.extensions || {};\n response.extensions.bridge = {\n info: {\n settleEndpoint: bridgeInfo.settleEndpoint,\n supportedSourceChains: otherSourceChains,\n supportedSourceAssets: bridgeInfo.supportedSourceAssets,\n payTo: primaryPayTo || null,\n payToMap: bridgeInfo.payTo,\n feePayerSvm: bridgeInfo.feePayerSvm,\n feeBps: bridgeInfo.feeBps,\n paymentFacilitator: bridgeInfo.paymentFacilitator,\n ...(serviceKeyHash ? { serviceKeyHash } : {}),\n },\n };\n\n return response;\n },\n };\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\n/**\n * Extended plugin interface with data export for free tier.\n */\nexport interface FreeTierPlugin extends RelaiPlugin {\n /** Export all in-memory usage data (local mode only). Returns null when using cloud mode. */\n getUsageData(): FreeTierUsageExport | null;\n /** Whether plugin is running in local (in-memory) or cloud (RelAI backend) mode. */\n readonly mode: 'local' | 'cloud';\n}\n\nexport interface FreeTierUsageExport {\n mode: 'local';\n config: {\n perBuyerLimit: number;\n resetPeriod: string;\n globalCap: number | null;\n paths: string[];\n };\n /** Per-buyer usage entries */\n usage: Array<{\n buyerId: string;\n path: string;\n count: number;\n periodStart: string;\n lastCall: string;\n }>;\n globalCount: number;\n exportedAt: string;\n}\n\nexport function freeTier(config: FreeTierPluginConfig): FreeTierPlugin {\n const base = (config.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const isLocal = !config.serviceKey;\n const resetPeriod = config.resetPeriod ?? 'none';\n const limit = config.perBuyerLimit;\n const globalCap = config.globalCap ?? null;\n const pluginPaths = config.paths ?? ['*'];\n\n /**\n * Resolve buyer identity from the request.\n * Priority: JWT sub > x-wallet-address > IP fallback\n */\n function resolveBuyerId(req: any): string {\n // 1. JWT Bearer token\n try {\n const auth = req.headers?.authorization || '';\n if (auth.startsWith('Bearer ')) {\n const token = auth.slice(7).trim();\n const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());\n if (payload?.sub) return `user:${payload.sub}`;\n }\n } catch { /* ignore */ }\n\n // 2. Explicit wallet header\n const wallet = req.headers?.['x-wallet-address'] || req.headers?.['x-buyer-address'];\n if (wallet) return `wallet:${wallet}`;\n\n // 3. IP fallback\n const ip =\n (req.headers?.['x-forwarded-for'] || '').split(',')[0].trim() ||\n req.socket?.remoteAddress ||\n req.ip ||\n 'unknown';\n return `ip:${ip}`;\n }\n\n function pathMatches(requestPath: string): boolean {\n return pluginPaths.includes('*') || pluginPaths.some((p) => {\n const normalized = p.toLowerCase().replace(/\\/+$/, '') || '/';\n const reqNormalized = requestPath.toLowerCase().replace(/\\/+$/, '') || '/';\n return normalized === reqNormalized || normalized === '*';\n });\n }\n\n // ---------------------------------------------------------------------------\n // LOCAL IN-MEMORY MODE\n // ---------------------------------------------------------------------------\n\n interface LocalUsageEntry {\n count: number;\n periodStart: string;\n lastCall: string;\n }\n\n // Map key: \"path:buyerId\"\n const localUsage = new Map<string, LocalUsageEntry>();\n let localGlobalCount = 0;\n\n function currentPeriodStart(): string {\n const now = new Date();\n if (resetPeriod === 'daily') {\n return new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();\n }\n if (resetPeriod === 'monthly') {\n return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();\n }\n return 'none'; // permanent\n }\n\n function isCurrentPeriod(entry: LocalUsageEntry): boolean {\n if (resetPeriod === 'none') return true;\n return entry.periodStart === currentPeriodStart();\n }\n\n function localKey(path: string, buyerId: string): string {\n return `${path}:${buyerId}`;\n }\n\n function localCheck(path: string, buyerId: string): FreeTierCheckResponse {\n const key = localKey(path, buyerId);\n const entry = localUsage.get(key);\n\n // If entry is from a previous period, it's stale — treat as 0\n const count = (entry && isCurrentPeriod(entry)) ? entry.count : 0;\n const remaining = Math.max(0, limit - count);\n\n // Global cap check\n if (globalCap != null && localGlobalCount >= globalCap) {\n return { free: false, remaining: 0, total: limit, reason: 'global_cap_reached', globalRemaining: 0 };\n }\n\n if (count >= limit) {\n return { free: false, remaining: 0, total: limit, reason: 'limit_reached' };\n }\n\n return {\n free: true,\n remaining: remaining - 1, // after this call\n total: limit,\n ...(globalCap != null ? { globalRemaining: globalCap - localGlobalCount - 1 } : {}),\n };\n }\n\n function localRecord(path: string, buyerId: string): void {\n const key = localKey(path, buyerId);\n const period = currentPeriodStart();\n const entry = localUsage.get(key);\n const now = new Date().toISOString();\n\n if (entry && isCurrentPeriod(entry)) {\n entry.count++;\n entry.lastCall = now;\n } else {\n localUsage.set(key, { count: 1, periodStart: period, lastCall: now });\n }\n localGlobalCount++;\n }\n\n function exportLocalData(): FreeTierUsageExport {\n const usage: FreeTierUsageExport['usage'] = [];\n for (const [key, entry] of localUsage.entries()) {\n // key format is \"path:buyerId\" but buyerId itself can contain ':'\n // We stored it as `${path}:${buyerId}` where path starts with '/'\n // So we find the first ':' after the path portion\n const firstSlash = key.indexOf('/');\n const colonAfterPath = key.indexOf(':', firstSlash > -1 ? firstSlash : 0);\n const path = colonAfterPath > -1 ? key.slice(0, colonAfterPath) : key;\n const buyerId = colonAfterPath > -1 ? key.slice(colonAfterPath + 1) : 'unknown';\n usage.push({\n buyerId,\n path,\n count: entry.count,\n periodStart: entry.periodStart,\n lastCall: entry.lastCall,\n });\n }\n return {\n mode: 'local',\n config: { perBuyerLimit: limit, resetPeriod, globalCap, paths: pluginPaths },\n usage,\n globalCount: localGlobalCount,\n exportedAt: new Date().toISOString(),\n };\n }\n\n // ---------------------------------------------------------------------------\n // CLOUD MODE (original — requires serviceKey)\n // ---------------------------------------------------------------------------\n\n // Simple in-memory cache: \"serviceKey:path:buyerId\" -> { result, expiresAt }\n const cache = new Map<string, { result: FreeTierCheckResponse; expiresAt: number }>();\n\n function resolveHeaders(): Record<string, string> {\n return {\n 'X-Service-Key': config.serviceKey!,\n 'Content-Type': 'application/json',\n };\n }\n\n function cloudCacheKey(path: string, buyerId: string): string {\n return `${config.serviceKey}:${path}:${buyerId}`;\n }\n\n async function checkFreeTier(path: string, buyerId: string): Promise<FreeTierCheckResponse> {\n const key = cloudCacheKey(path, buyerId);\n const now = Date.now();\n const cached = cache.get(key);\n if (cached && cached.expiresAt > now) {\n return cached.result;\n }\n\n try {\n const res = await fetch(`${base}/v1/plugins/free-tier/check`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n\n if (!res.ok) {\n // Non-blocking: if API unreachable, default to not-free\n return { free: false, reason: `api_error_${res.status}` };\n }\n\n const result = await res.json() as FreeTierCheckResponse;\n cache.set(key, { result, expiresAt: now + cacheTtl });\n return result;\n } catch (err) {\n // Network error - non-blocking, default to paid\n return { free: false, reason: 'network_error' };\n }\n }\n\n async function recordCall(path: string, buyerId: string): Promise<void> {\n try {\n await fetch(`${base}/v1/plugins/free-tier/record`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n // Invalidate cache for this buyer+path after recording\n cache.delete(cloudCacheKey(path, buyerId));\n } catch {\n // Fire-and-forget\n }\n }\n\n async function syncConfig(): Promise<void> {\n try {\n // Check if config already exists on backend — don't overwrite dashboard-managed configs\n const existing = await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'GET',\n headers: resolveHeaders(),\n });\n if (existing.ok) {\n const data = await existing.json();\n if (data.configs && data.configs.length > 0) {\n return; // Config managed via dashboard or previous sync — skip\n }\n }\n\n await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'PUT',\n headers: resolveHeaders(),\n body: JSON.stringify({\n perBuyerLimit: config.perBuyerLimit,\n resetPeriod: config.resetPeriod ?? 'none',\n globalCap: config.globalCap ?? null,\n paths: pluginPaths,\n }),\n });\n } catch (err) {\n console.warn(`[relai:freeTier] Failed to sync config to RelAI: ${err}`);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Plugin instance\n // ---------------------------------------------------------------------------\n\n return {\n name: 'free-tier',\n mode: isLocal ? 'local' : 'cloud',\n\n getUsageData(): FreeTierUsageExport | null {\n if (!isLocal) return null;\n return exportLocalData();\n },\n\n async onInit() {\n if (isLocal) {\n console.log(`[relai:freeTier] Running in local mode (in-memory). perBuyerLimit=${limit}, resetPeriod=${resetPeriod}`);\n return;\n }\n await syncConfig();\n },\n\n async beforePaymentCheck(req, ctx) {\n const requestPath = ctx.path || '/';\n if (!pathMatches(requestPath)) return {};\n\n const buyerId = resolveBuyerId(req);\n\n // ── Local mode ──\n if (isLocal) {\n const result = localCheck(requestPath, buyerId);\n if (result.free) {\n localRecord(requestPath, buyerId);\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? limit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n 'X-Free-Tier-Mode': 'local',\n },\n meta: { freeTier: true, buyerId, remaining: result.remaining, total: result.total ?? limit, mode: 'local' },\n };\n }\n return {};\n }\n\n // ── Cloud mode ──\n const result = await checkFreeTier(requestPath, buyerId);\n\n if (result.free) {\n // Record the call (fire-and-forget)\n recordCall(requestPath, buyerId).catch(() => {});\n\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? config.perBuyerLimit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n },\n meta: {\n freeTier: true,\n buyerId,\n remaining: result.remaining,\n total: result.total ?? config.perBuyerLimit,\n },\n };\n }\n\n return {};\n },\n };\n}\n\n// ============================================================================\n// Shield Plugin\n// ============================================================================\n\nexport interface ShieldPluginConfig {\n /**\n * Custom health check function. Return true if the service is healthy.\n * Takes priority over `healthUrl` if both are provided.\n */\n healthCheck?: () => Promise<boolean> | boolean;\n /**\n * URL to probe. A 2xx response means healthy.\n * Ignored if `healthCheck` is provided.\n */\n healthUrl?: string;\n /** Timeout in ms for the health probe (default: 5000) */\n timeoutMs?: number;\n /** Cache the health result for this many ms (default: 10000) */\n cacheTtlMs?: number;\n /** Message returned to the caller when unhealthy (default: 'Service temporarily unavailable. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when unhealthy (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Shield plugin — protects buyers from paying for unhealthy endpoints.\n *\n * Before the server returns a 402, Shield runs a health check (custom\n * function or URL probe). If the service is down, the request is rejected\n * with 503 instead of asking for payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { shield } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * shield({\n * healthUrl: 'https://my-api.com/health',\n * timeoutMs: 3000,\n * }),\n * ],\n * });\n *\n * // If /health is down, buyers get 503 instead of 402\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function shield(config?: ShieldPluginConfig): RelaiPlugin {\n const timeoutMs = config?.timeoutMs ?? 5000;\n const cacheTtl = config?.cacheTtlMs ?? 10000;\n const unhealthyMessage = config?.unhealthyMessage ?? 'Service temporarily unavailable. Please try again later.';\n const unhealthyStatus = config?.unhealthyStatus ?? 503;\n\n let cached: { healthy: boolean; expiresAt: number } | null = null;\n\n async function checkHealth(): Promise<boolean> {\n const now = Date.now();\n if (cached && cached.expiresAt > now) {\n return cached.healthy;\n }\n\n let healthy = true;\n\n try {\n if (config?.healthCheck) {\n healthy = await Promise.resolve(config.healthCheck());\n } else if (config?.healthUrl) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetch(config.healthUrl, {\n method: 'GET',\n signal: controller.signal,\n });\n healthy = res.ok;\n } finally {\n clearTimeout(timer);\n }\n }\n // No healthCheck and no healthUrl → always healthy (no-op mode)\n } catch {\n healthy = false;\n }\n\n cached = { healthy, expiresAt: now + cacheTtl };\n return healthy;\n }\n\n return {\n name: 'shield',\n\n async onInit() {\n const healthy = await checkHealth();\n console.log(`[relai:shield] Initial health check: ${healthy ? 'healthy ✓' : 'UNHEALTHY ✗'}`);\n },\n\n async beforePaymentCheck(_req, _ctx): Promise<PluginResult> {\n const healthy = await checkHealth();\n\n if (!healthy) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Shield-Status': 'unhealthy',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Shield-Status': 'healthy' },\n };\n },\n };\n}\n\n// ============================================================================\n// Preflight Plugin\n// ============================================================================\n\nexport interface PreflightPluginConfig {\n /**\n * Base URL of the API server (e.g. 'https://my-api.com').\n * The plugin appends the request path to form the probe URL.\n * **Required** — the plugin needs to know where to send the probe.\n */\n baseUrl: string;\n /** Timeout in ms for the preflight probe (default: 3000) */\n timeoutMs?: number;\n /** Cache the probe result for this many ms (default: 5000) */\n cacheTtlMs?: number;\n /** Custom unhealthy message (default: 'Endpoint not responding. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when endpoint unreachable (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Preflight plugin — verifies the specific endpoint responds before payment.\n *\n * Unlike Shield (global health check), Preflight probes the **actual endpoint**\n * the buyer is requesting. It sends a HEAD request with `X-Preflight: true` —\n * the Relai middleware responds 200 instantly without triggering payment.\n * If the endpoint doesn't respond, the buyer gets 503 instead of 402.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { preflight } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * preflight({ baseUrl: 'https://my-api.com' }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function preflight(config: PreflightPluginConfig): RelaiPlugin {\n const baseUrl = config.baseUrl.replace(/\\/+$/, '');\n const timeoutMs = config.timeoutMs ?? 3000;\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const unhealthyMessage = config.unhealthyMessage ?? 'Endpoint not responding. Please try again later.';\n const unhealthyStatus = config.unhealthyStatus ?? 503;\n\n const cache = new Map<string, { ok: boolean; expiresAt: number }>();\n\n async function probeEndpoint(path: string): Promise<boolean> {\n const now = Date.now();\n const cached = cache.get(path);\n if (cached && cached.expiresAt > now) {\n return cached.ok;\n }\n\n let ok = false;\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n const res = await fetch(url, {\n method: 'HEAD',\n headers: { 'X-Preflight': 'true' },\n signal: controller.signal,\n });\n ok = res.ok;\n } finally {\n clearTimeout(timer);\n }\n } catch {\n ok = false;\n }\n\n cache.set(path, { ok, expiresAt: now + cacheTtl });\n return ok;\n }\n\n return {\n name: 'preflight',\n\n async onInit() {\n // Probe the base URL on startup\n const ok = await probeEndpoint('/');\n console.log(`[relai:preflight] Initial probe ${baseUrl}: ${ok ? 'reachable ✓' : 'UNREACHABLE ✗'}`);\n },\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const path = ctx.path || '/';\n const ok = await probeEndpoint(path);\n\n if (!ok) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Preflight-Status': 'unreachable',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Preflight-Status': 'ok' },\n };\n },\n };\n}\n\n\n// ============================================================================\n// Circuit Breaker Plugin\n// ============================================================================\n\nexport type CircuitState = 'closed' | 'open' | 'half-open';\n\nexport interface CircuitBreakerPluginConfig {\n /**\n * Number of consecutive failures before the circuit opens.\n * Default: 5\n */\n failureThreshold?: number;\n /**\n * Time in ms the circuit stays open before moving to half-open.\n * Default: 30000 (30 seconds)\n */\n resetTimeMs?: number;\n /**\n * Number of successful requests in half-open state before closing the circuit.\n * Default: 2\n */\n halfOpenSuccesses?: number;\n /** HTTP status code when circuit is open (default: 503) */\n openStatus?: number;\n /** Error message when circuit is open */\n openMessage?: string;\n /**\n * HTTP status codes considered as failures.\n * Default: [500, 502, 503, 504]\n */\n failureCodes?: number[];\n /**\n * Track failures globally or per-path.\n * Default: 'per-path'\n */\n scope?: 'global' | 'per-path';\n}\n\ninterface CircuitEntry {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureAt: number;\n}\n\n/**\n * Circuit Breaker plugin — tracks failure history and opens the circuit\n * after repeated failures, preventing buyers from paying for broken endpoints.\n *\n * Unlike Shield/Preflight (real-time probes), Circuit Breaker is **zero-latency** —\n * it makes no additional HTTP requests. Instead, it tracks `afterSettled` outcomes\n * and rejects requests when the failure rate exceeds the threshold.\n *\n * States:\n * - **closed** — normal operation, requests flow through\n * - **open** — too many failures, requests are rejected with 503\n * - **half-open** — after resetTime, allows a few test requests through\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { circuitBreaker } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * circuitBreaker({ failureThreshold: 5, resetTimeMs: 30000 }),\n * ],\n * });\n * ```\n */\nexport function circuitBreaker(config?: CircuitBreakerPluginConfig): RelaiPlugin {\n const failureThreshold = config?.failureThreshold ?? 5;\n const resetTimeMs = config?.resetTimeMs ?? 30000;\n const halfOpenSuccesses = config?.halfOpenSuccesses ?? 2;\n const openStatus = config?.openStatus ?? 503;\n const openMessage = config?.openMessage ?? 'Service temporarily unavailable (circuit open). Please try again later.';\n const failureCodes = new Set(config?.failureCodes ?? [500, 502, 503, 504]);\n const scope = config?.scope ?? 'per-path';\n\n const circuits = new Map<string, CircuitEntry>();\n\n function getKey(path: string): string {\n return scope === 'global' ? '__global__' : path;\n }\n\n function getCircuit(key: string): CircuitEntry {\n let entry = circuits.get(key);\n if (!entry) {\n entry = { state: 'closed', failures: 0, successes: 0, lastFailureAt: 0 };\n circuits.set(key, entry);\n }\n return entry;\n }\n\n function recordFailure(key: string): void {\n const circuit = getCircuit(key);\n circuit.failures++;\n circuit.successes = 0;\n circuit.lastFailureAt = Date.now();\n\n if (circuit.failures >= failureThreshold) {\n circuit.state = 'open';\n }\n }\n\n function recordSuccess(key: string): void {\n const circuit = getCircuit(key);\n if (circuit.state === 'half-open') {\n circuit.successes++;\n if (circuit.successes >= halfOpenSuccesses) {\n // Enough successes in half-open → close the circuit\n circuit.state = 'closed';\n circuit.failures = 0;\n circuit.successes = 0;\n }\n } else {\n // Reset failures on success in closed state\n circuit.failures = 0;\n circuit.successes = 0;\n }\n }\n\n return {\n name: 'circuit-breaker',\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const key = getKey(ctx.path || '/');\n const circuit = getCircuit(key);\n const now = Date.now();\n\n // Check if open circuit should transition to half-open\n if (circuit.state === 'open' && (now - circuit.lastFailureAt) >= resetTimeMs) {\n circuit.state = 'half-open';\n circuit.successes = 0;\n }\n\n if (circuit.state === 'open') {\n const retryAfter = Math.max(1, Math.ceil((resetTimeMs - (now - circuit.lastFailureAt)) / 1000));\n return {\n reject: true,\n rejectStatus: openStatus,\n rejectMessage: openMessage,\n headers: {\n 'X-Circuit-State': 'open',\n 'X-Circuit-Failures': String(circuit.failures),\n 'Retry-After': String(retryAfter),\n },\n };\n }\n\n return {\n headers: {\n 'X-Circuit-State': circuit.state,\n ...(circuit.state === 'half-open' ? { 'X-Circuit-Successes': String(circuit.successes) } : {}),\n },\n };\n },\n\n async afterSettled(_req, result, ctx): Promise<void> {\n const key = getKey(ctx.path || '/');\n\n // Check if the settlement itself failed or returned error codes\n if (!result.success) {\n recordFailure(key);\n return;\n }\n\n // Check response status if available\n const status = (result as any).statusCode || (result as any).status;\n if (status && failureCodes.has(status)) {\n recordFailure(key);\n } else {\n recordSuccess(key);\n }\n },\n };\n}\n\n\n// ============================================================================\n// Refund Plugin\n// ============================================================================\n\nexport interface RefundPluginConfig {\n /**\n * HTTP status codes that trigger a refund/credit after payment.\n * Default: [500, 502, 503, 504]\n */\n triggerCodes?: number[];\n /**\n * What to do when a refund is triggered.\n * - 'credit': record a credit that the free-tier plugin can consume later\n * - 'log': just log the event (useful with a custom onRefund callback)\n * Default: 'credit'\n */\n mode?: 'credit' | 'log';\n /**\n * Called whenever a refund event is triggered.\n * Use for external refund processing, logging, or notifications.\n */\n onRefund?: (event: RefundEvent) => void | Promise<void>;\n /**\n * Also trigger refund when settlement itself fails (result.success === false).\n * Default: true\n */\n refundOnSettlementFailure?: boolean;\n}\n\nexport interface RefundEvent {\n /** Buyer wallet address */\n payer: string;\n /** Transaction ID of the original payment */\n transactionId: string;\n /** Network used */\n network: string;\n /** Amount paid in USD */\n amount: number;\n /** Request path */\n path: string;\n /** Reason for the refund */\n reason: 'endpoint_error' | 'settlement_failure' | 'timeout';\n /** HTTP status code that triggered the refund (if applicable) */\n statusCode?: number;\n /** Timestamp */\n timestamp: number;\n}\n\n/**\n * Refund plugin — automatically handles refund/credit when a paid request fails.\n *\n * After payment settles, if the endpoint returns an error (e.g. 500),\n * the plugin records a credit or calls your custom `onRefund` handler.\n * Credits can be consumed by the free-tier plugin on the next request.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { refund } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * refund({\n * triggerCodes: [500, 502, 503],\n * onRefund: (event) => {\n * console.log(`Refund for ${event.payer}: $${event.amount} on ${event.path}`);\n * // Notify buyer, record in DB, etc.\n * },\n * }),\n * ],\n * });\n * ```\n */\nexport function refund(config?: RefundPluginConfig): RelaiPlugin {\n const triggerCodes = new Set(config?.triggerCodes ?? [500, 502, 503, 504]);\n const mode = config?.mode ?? 'credit';\n const onRefund = config?.onRefund;\n const refundOnSettlementFailure = config?.refundOnSettlementFailure ?? true;\n\n // In-memory credit ledger: payer → number of credits\n const credits = new Map<string, number>();\n\n return {\n name: 'refund',\n\n async beforePaymentCheck(req, ctx): Promise<PluginResult> {\n // If buyer has credits from previous refunds, skip payment\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.headers?.['authorization']?.replace(/^Bearer\\s+/i, '') ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n\n const buyerCredits = credits.get(buyerId) || 0;\n if (buyerCredits > 0) {\n credits.set(buyerId, buyerCredits - 1);\n return {\n skip: true,\n headers: {\n 'X-Refund-Credit': 'applied',\n 'X-Refund-Credits-Remaining': String(buyerCredits - 1),\n },\n meta: {\n refundCredit: true,\n creditsRemaining: buyerCredits - 1,\n },\n };\n }\n }\n\n return {};\n },\n\n async afterSettled(req, result, ctx): Promise<void> {\n const payer = result.payer || 'unknown';\n const transactionId = result.transaction || '';\n\n // Settlement failure\n if (!result.success && refundOnSettlementFailure) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'settlement_failure',\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n return;\n }\n\n // Check for endpoint error codes in settlement result\n const status = (result as any).statusCode || (result as any).status;\n if (status && triggerCodes.has(status)) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'endpoint_error',\n statusCode: status,\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n }\n },\n };\n}\n\n// Re-export for backwards-compatible import path '@relai-fi/x402/plugins'\nexport { submitRelayFeedback, type RelayFeedbackConfig } from './relay-feedback';\n\n// ============================================================================\n// Score Plugin\n// ============================================================================\n\nexport interface ScorePluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n * Find yours in the RelAI dashboard under \"On-chain Identity\".\n */\n agentId: string | number;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha\n */\n rpcUrl?: string;\n /**\n * ERC-8004 IdentityRegistry contract address.\n * Default: process.env.ERC8004_IDENTITY_REGISTRY\n */\n identityRegistryAddress?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\ninterface CachedScore {\n score: Record<string, unknown>;\n expiresAt: number;\n}\n\nconst SCORE_IDENTITY_ABI = [\n 'function ownerOf(uint256 tokenId) external view returns (address)',\n];\n\nconst SCORE_FEEDBACK_TOPIC = ethers.id(\n 'NewFeedback(uint256,address,uint64,int128,uint8,string,string,string,string,string,bytes32)'\n);\n\nconst SCORE_FEEDBACK_IFACE = new ethers.Interface([\n 'event NewFeedback(uint256 indexed agentId, address indexed giver, uint64 index, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, string responseURI, bytes32 feedbackHash)',\n]);\n\nconst SCORE_BATCH = 1900;\nconst SCORE_HISTORY = 10000;\n\n/**\n * Score plugin — fetches ERC-8004 on-chain reputation for your API\n * directly via RPC and injects it into the 402 response `extensions.score`.\n *\n * Agents can read the score **before paying**, enabling trust-based routing:\n * - `feedbackCount` — number of on-chain feedback entries\n * - `successRate` — 0–100 (% of successful calls)\n * - `avgResponseMs` — average response time in milliseconds\n * - `verified` — true if agentId is registered on-chain\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * ],\n * });\n * ```\n */\nexport function score(config: ScorePluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:score] ERC8004_AGENT_ID not set — score plugin is a no-op');\n return { name: 'score', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n let cached: CachedScore | null = null;\n\n function getContracts() {\n const identityAddress = config.identityRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_IDENTITY_REGISTRY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n if (!identityAddress || !reputationAddress) return null;\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const identity = new ethers.Contract(identityAddress, SCORE_IDENTITY_ABI, provider);\n return { provider, identity, reputationAddress };\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && cached.expiresAt > Date.now()) {\n return cached.score;\n }\n\n const contracts = getContracts();\n if (!contracts) {\n console.warn(`[relai:score] ERC8004_IDENTITY_REGISTRY or ERC8004_REPUTATION_REGISTRY not set — score disabled`);\n return null;\n }\n\n const { provider, identity, reputationAddress } = contracts;\n const bigId = BigInt(agentId);\n\n try {\n let verified = false;\n try {\n const owner = await identity.ownerOf(bigId);\n verified = !!owner;\n } catch {\n // Not registered\n }\n\n if (!verified) {\n const scoreObj = { feedbackCount: 0, successRate: null, avgResponseMs: null, verified: false, agentId, source: 'erc8004-skale' };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n }\n\n const latest = await provider.getBlockNumber();\n const agentIdTopic = ethers.zeroPadValue(ethers.toBeHex(bigId), 32);\n const logs: any[] = [];\n\n for (let from = Math.max(0, latest - SCORE_HISTORY); from <= latest; from += SCORE_BATCH) {\n const to = Math.min(from + SCORE_BATCH - 1, latest);\n const chunk = await provider.getLogs({\n address: reputationAddress,\n topics: [SCORE_FEEDBACK_TOPIC, agentIdTopic],\n fromBlock: from,\n toBlock: to,\n });\n logs.push(...chunk);\n }\n\n const srValues: number[] = [];\n const rtValues: number[] = [];\n const endpointMap: Record<string, { sr: number[]; rt: number[] }> = {};\n\n for (const log of logs) {\n try {\n const p = SCORE_FEEDBACK_IFACE.parseLog({ topics: log.topics as string[], data: log.data });\n if (!p) continue;\n const val = Number(p.args.value) / Math.pow(10, Number(p.args.valueDecimals));\n const ep: string = p.args.endpoint || '';\n if (!endpointMap[ep]) endpointMap[ep] = { sr: [], rt: [] };\n if (p.args.tag1 === 'successRate') { srValues.push(val); endpointMap[ep].sr.push(val); }\n else if (p.args.tag1 === 'responseTime') { rtValues.push(val); endpointMap[ep].rt.push(val); }\n } catch { /* skip */ }\n }\n\n const feedbackCount = srValues.length + rtValues.length;\n const successRate = srValues.length\n ? Math.round(srValues.reduce((a, b) => a + b, 0) / srValues.length)\n : null;\n const avgResponseMs = rtValues.length\n ? Math.round(rtValues.reduce((a, b) => a + b, 0) / rtValues.length)\n : null;\n\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n for (const [ep, { sr, rt }] of Object.entries(endpointMap)) {\n endpoints[ep] = {\n feedbackCount: sr.length + rt.length,\n successRate: sr.length ? Math.round(sr.reduce((a, b) => a + b, 0) / sr.length) : null,\n avgResponseMs: rt.length ? Math.round(rt.reduce((a, b) => a + b, 0) / rt.length) : null,\n };\n }\n\n const scoreObj = { feedbackCount, successRate, avgResponseMs, verified: true, agentId, source: 'erc8004-skale', endpoints };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err) {\n console.warn(`[relai:score] fetchScore RPC error for agentId=${agentId}:`, err);\n return null;\n }\n }\n\n return {\n name: 'score',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:score] Initialized — agentId=${agentId}, feedback=${s.feedbackCount}, successRate=${s.successRate}%, avgResp=${s.avgResponseMs}ms`);\n } else {\n console.warn(`[relai:score] Could not load score for agentId=${agentId} — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any, _ctx: PluginContext) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n score: scoreData,\n },\n };\n },\n };\n}\n\n// ============================================================================\n// Feedback Plugin\n// ============================================================================\n\nexport interface FeedbackPluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n */\n agentId: string | number;\n /**\n * Private key of the wallet that signs feedback transactions.\n * The wallet must hold CREDIT tokens on SKALE Base to pay gas.\n * Default: process.env.BACKEND_WALLET_PRIVATE_KEY\n */\n walletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Tag2 label for all feedback entries (default: 'x402').\n */\n tag2?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n * Value: 1 (success) or 0 (failure).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n * Value: milliseconds elapsed since request start.\n * Requires req.x402StartTime (set automatically if using Relai.protect()).\n */\n submitResponseTime?: boolean;\n}\n\nconst FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Feedback plugin — submits ERC-8004 on-chain reputation after every successful x402 settlement.\n *\n * Records `successRate` and `responseTime` signals to the ReputationRegistry on SKALE Base.\n * This is the mechanism that **builds** the score that `score()` plugin later reads.\n *\n * Runs fire-and-forget — never blocks the response.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score, feedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * feedback({ agentId: '5' }), // needs BACKEND_WALLET_PRIVATE_KEY in env\n * ],\n * });\n * ```\n */\nexport function feedback(config: FeedbackPluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:feedback] ERC8004_AGENT_ID not set — feedback plugin is a no-op');\n return { name: 'feedback', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const tag2 = config.tag2 ?? 'x402';\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n let _reputation: any = null;\n\n function getReputation() {\n if (_reputation) return _reputation;\n\n const privateKey = config.walletPrivateKey\n ?? (typeof process !== 'undefined' ? process.env?.BACKEND_WALLET_PRIVATE_KEY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) return null;\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n _reputation = new ethers.Contract(reputationAddress, FEEDBACK_REPUTATION_ABI, signer);\n return _reputation;\n }\n\n function submitAsync(tag1: string, value: number, valueDecimals: number, endpoint: string) {\n const reputation = getReputation();\n if (!reputation) return;\n\n (async () => {\n try {\n const tx = await reputation.giveFeedback(\n BigInt(agentId),\n BigInt(Math.round(value)),\n valueDecimals,\n tag1,\n tag2,\n endpoint,\n '',\n ethers.ZeroHash,\n );\n console.log(`[relai:feedback] Submitted agentId=${agentId} tag=${tag1} value=${value} tx=${tx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:feedback] giveFeedback failed (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'feedback',\n\n async onInit() {\n const reputation = getReputation();\n if (reputation) {\n console.log(`[relai:feedback] Initialized — agentId=${agentId}, submitSuccessRate=${submitSuccessRate}, submitResponseTime=${submitResponseTime}`);\n } else {\n console.warn(`[relai:feedback] BACKEND_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — feedback disabled`);\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n\n // successRate: 1 = success, 0 = failure\n if (submitSuccessRate) {\n submitAsync('successRate', result.success ? 1 : 0, 0, endpoint);\n }\n\n // responseTime in ms — use req.x402StartTime if available\n if (submitResponseTime) {\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n if (responseTimeMs > 0) {\n submitAsync('responseTime', responseTimeMs, 0, endpoint);\n }\n }\n },\n };\n}\n\n// ============================================================================\n// Solana Feedback Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaFeedbackPluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Private key of the feedback wallet — base58 or JSON array format.\n * Should differ from the owner's registration wallet (avoids self-feedback restriction).\n * Default: process.env.SOLANA_8004_FEEDBACK_KEY ?? process.env.SOLANA_8004_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n */\n submitResponseTime?: boolean;\n}\n\n/**\n * Solana Feedback plugin — submits 8004-solana on-chain reputation after x402 settlement.\n *\n * Uses the native Solana `8004-solana` program (MPL Core NFT registry),\n * separate from the SKALE EVM ReputationRegistry used by `feedback()`.\n *\n * Requires `8004-solana` package to be installed:\n * ```\n * npm install 8004-solana\n * ```\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaFeedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaFeedback({\n * assetPubkey: process.env.SOLANA_AGENT_ASSET!, // e.g. 'GH93tGR8...'\n * }),\n * ],\n * });\n * ```\n */\nexport function solanaFeedback(config: SolanaFeedbackPluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n function getPrivateKey(): string | null {\n return config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.SOLANA_8004_FEEDBACK_KEY ?? process.env?.SOLANA_8004_PRIVATE_KEY\n : undefined)\n ?? null;\n }\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function buildSDK(): Promise<any> {\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n const privateKeyRaw = getPrivateKey();\n if (!privateKeyRaw) return null;\n\n let secretKey: Uint8Array;\n try {\n const parsed = JSON.parse(privateKeyRaw);\n if (Array.isArray(parsed)) {\n secretKey = Uint8Array.from(parsed);\n } else {\n throw new Error('not array');\n }\n } catch {\n // base58\n try {\n const bs58Mod = await import('bs58' as any);\n const decode = bs58Mod.default?.decode ?? bs58Mod.decode;\n secretKey = decode(privateKeyRaw);\n } catch {\n console.warn('[relai:solanaFeedback] Could not parse feedbackWalletPrivateKey');\n return null;\n }\n }\n\n const { Keypair } = await import('@solana/web3.js');\n const signer = Keypair.fromSecretKey(secretKey);\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n\n return new SolanaSDK({ cluster, signer, ...(rpcUrl ? { rpcUrl } : {}) });\n }\n\n function submitAsync(isSuccess: boolean, responseTimeMs: number, endpoint: string) {\n (async () => {\n try {\n const sdk = await buildSDK();\n if (!sdk) return;\n\n const { PublicKey } = await import('@solana/web3.js');\n const pubkey = new PublicKey(assetPubkey);\n\n if (submitSuccessRate) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(isSuccess ? 10000 : 0),\n tag1: 'successRate',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] successRate submitted asset=${assetPubkey.slice(0, 8)}... tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n }\n\n if (submitResponseTime && responseTimeMs > 0) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(Math.round(responseTimeMs)),\n tag1: 'responseTime',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] responseTime submitted asset=${assetPubkey.slice(0, 8)}... ms=${responseTimeMs} tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] feedback error (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'solanaFeedback',\n\n async onInit() {\n const privateKey = getPrivateKey();\n if (!privateKey) {\n console.warn('[relai:solanaFeedback] SOLANA_8004_FEEDBACK_KEY not set — Solana feedback disabled');\n return;\n }\n try {\n await import('8004-solana' as any);\n console.log(`[relai:solanaFeedback] Initialized — asset=${assetPubkey.slice(0, 8)}... cluster=${getCluster()}`);\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n\n submitAsync(result.success, responseTimeMs, endpoint);\n },\n };\n}\n\n// ============================================================================\n// Solana Score Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaScorePluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\n/**\n * Solana Score plugin — fetches 8004-solana on-chain reputation and injects it\n * into the 402 response `extensions.score` before the client pays.\n *\n * Mirrors the EVM `score()` plugin but reads from the native Solana program\n * instead of the SKALE EVM ReputationRegistry.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaScore } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaScore({ assetPubkey: process.env.SOLANA_AGENT_ASSET! }),\n * ],\n * });\n * ```\n */\nexport function solanaScore(config: SolanaScorePluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n\n if (!assetPubkey || String(assetPubkey) === 'undefined') {\n console.warn('[relai:solanaScore] assetPubkey not set — solanaScore plugin is a no-op');\n return { name: 'solanaScore', async onInit() {} };\n }\n\n let cached: CachedScore | null = null;\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && Date.now() < cached.expiresAt) return cached.score;\n\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaScore] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n try {\n const { PublicKey } = await import('@solana/web3.js');\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n const sdk = new SolanaSDK({ cluster, ...(rpcUrl ? { rpcUrl } : {}) });\n const pubkey = new PublicKey(assetPubkey);\n\n // Try getReputationSummary first (lightweight)\n let feedbackCount = 0;\n let successRate: number | null = null;\n let avgResponseMs: number | null = null;\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n\n // Try to read all feedback for per-endpoint breakdown\n let allFeedbacks: any[] = [];\n try {\n allFeedbacks = await sdk.readAllFeedback(pubkey);\n } catch {\n // fallback to summary only\n }\n\n if (allFeedbacks.length > 0) {\n // Group by endpoint\n const byEndpoint: Record<string, { successValues: number[]; responseTimes: number[] }> = {};\n let globalSuccessValues: number[] = [];\n let globalResponseTimes: number[] = [];\n\n for (const entry of allFeedbacks) {\n const tag1: string = entry.tag1 ?? '';\n const rawValue = entry.value ?? entry.score ?? 0;\n const value = typeof rawValue === 'bigint' ? Number(rawValue) : Number(rawValue);\n const ep: string = entry.endpoint ?? '';\n\n if (!byEndpoint[ep]) byEndpoint[ep] = { successValues: [], responseTimes: [] };\n\n if (tag1 === 'successRate') {\n globalSuccessValues.push(value);\n byEndpoint[ep].successValues.push(value);\n } else if (tag1 === 'responseTime') {\n globalResponseTimes.push(value);\n byEndpoint[ep].responseTimes.push(value);\n }\n }\n\n feedbackCount = globalSuccessValues.length;\n if (feedbackCount > 0) {\n const avgSuccess = globalSuccessValues.reduce((a, b) => a + b, 0) / feedbackCount;\n // value=10000 means success (100%), value=0 means failure\n successRate = Math.round((avgSuccess / 10000) * 100);\n }\n if (globalResponseTimes.length > 0) {\n avgResponseMs = Math.round(globalResponseTimes.reduce((a, b) => a + b, 0) / globalResponseTimes.length);\n }\n\n for (const [ep, data] of Object.entries(byEndpoint)) {\n const epCount = data.successValues.length;\n const epAvgSuccess = epCount > 0\n ? data.successValues.reduce((a, b) => a + b, 0) / epCount\n : null;\n const epAvgMs = data.responseTimes.length > 0\n ? Math.round(data.responseTimes.reduce((a, b) => a + b, 0) / data.responseTimes.length)\n : null;\n endpoints[ep || '/'] = {\n feedbackCount: epCount,\n successRate: epAvgSuccess !== null ? Math.round((epAvgSuccess / 10000) * 100) : null,\n avgResponseMs: epAvgMs,\n };\n }\n } else {\n // Fallback to summary\n try {\n const summary = await sdk.getReputationSummary(pubkey);\n feedbackCount = summary.count ?? 0;\n if (feedbackCount > 0) {\n const avg = summary.averageScore ?? 0;\n successRate = Math.round((avg / 10000) * 100);\n }\n } catch {\n // no data\n }\n }\n\n const scoreObj = {\n feedbackCount,\n successRate,\n avgResponseMs,\n assetPubkey,\n source: '8004-solana',\n cluster,\n ...(Object.keys(endpoints).length > 0 ? { endpoints } : {}),\n };\n\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err: any) {\n console.warn(`[relai:solanaScore] fetch error (non-fatal): ${err?.message}`);\n return null;\n }\n }\n\n return {\n name: 'solanaScore',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:solanaScore] Initialized — asset=${assetPubkey.slice(0, 8)}... feedbackCount=${(s as any).feedbackCount} cluster=${getCluster()}`);\n } else {\n console.warn(`[relai:solanaScore] Could not load score for asset=${assetPubkey.slice(0, 8)}... — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n solanaScore: scoreData,\n },\n };\n },\n };\n}\n","// src/relay-feedback.ts\n// Standalone utility for submitting ERC-8004 feedback about third-party APIs.\n// Not a plugin — call directly from your relay/aggregator application code.\n\nimport { ethers } from 'ethers';\n\nexport interface RelayFeedbackConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) of the **target API** you are calling.\n */\n agentId: string | number;\n /**\n * Whether the API call succeeded.\n */\n success: boolean;\n /**\n * Elapsed milliseconds for the API call.\n */\n responseTimeMs?: number;\n /**\n * Endpoint path of the called API (e.g. '/v1/data').\n */\n endpoint?: string;\n /**\n * Private key of the **relay/third-party** wallet that signs feedback.\n * MUST be different from the API owner's wallet — ReputationRegistry\n * may restrict self-feedback. Wallet needs CREDIT tokens on SKALE Base.\n * Default: process.env.FEEDBACK_WALLET_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n}\n\nconst RELAY_FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Submit ERC-8004 on-chain feedback about a **third-party API** you called.\n *\n * Call this fire-and-forget from your relay/aggregator after every external API call.\n * Uses a separate relay wallet (not the API owner's key) to avoid self-feedback restrictions.\n *\n * Records:\n * - `successRate`: 10000 (= 100%) on success, 0 on failure — 2 decimal places\n * - `responseTime`: elapsed milliseconds\n *\n * @example\n * ```typescript\n * import { submitRelayFeedback } from '@relai-fi/x402/relay-feedback';\n *\n * // after calling an external API:\n * const start = Date.now();\n * const result = await fetch('https://other-api.com/data');\n * submitRelayFeedback({\n * agentId: '5',\n * success: result.ok,\n * responseTimeMs: Date.now() - start,\n * endpoint: '/data',\n * });\n * ```\n */\nexport function submitRelayFeedback(config: RelayFeedbackConfig): void {\n const agentId = String(config.agentId);\n const endpoint = config.endpoint ?? '';\n const responseTimeMs = config.responseTimeMs ?? 0;\n\n const privateKey = config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.FEEDBACK_WALLET_PRIVATE_KEY ?? process.env?.ERC8004_FEEDBACK_WALLET_PRIVATE_KEY\n : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) {\n console.warn('[relai:submitRelayFeedback] FEEDBACK_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — skipping');\n return;\n }\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n const reputation = new ethers.Contract(reputationAddress, RELAY_FEEDBACK_REPUTATION_ABI, signer);\n const id = BigInt(agentId);\n\n (async () => {\n const successValue = config.success ? 10000n : 0n;\n try {\n const srTx = await reputation.giveFeedback(\n id, successValue, 2, 'successRate', '', endpoint, '', ethers.ZeroHash,\n );\n await srTx.wait();\n console.log(`[relai:submitRelayFeedback] successRate confirmed agentId=${agentId} success=${config.success}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n\n if (responseTimeMs > 0) {\n try {\n const rtTx = await reputation.giveFeedback(\n id, BigInt(Math.max(0, Math.round(responseTimeMs))), 0, 'responseTime', '', endpoint, '', ethers.ZeroHash,\n );\n console.log(`[relai:submitRelayFeedback] responseTime sent agentId=${agentId} ms=${responseTimeMs} tx=${rtTx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n })();\n}\n"],"mappings":"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","names":["import_ethers","crypto","result","scoreObj"]}
package/dist/plugins.js CHANGED
@@ -1078,7 +1078,7 @@ function solanaScore(config) {
1078
1078
  ...response,
1079
1079
  extensions: {
1080
1080
  ...response.extensions ?? {},
1081
- score: scoreData
1081
+ solanaScore: scoreData
1082
1082
  }
1083
1083
  };
1084
1084
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugins.ts","../src/relay-feedback.ts"],"sourcesContent":["// src/plugins.ts\n// RelAI Plugin System - extensible middleware hooks for Relai.protect()\n\nimport crypto from 'crypto';\nimport { ethers } from 'ethers';\nimport type { RelaiNetwork } from './types';\nimport type { SettleResult } from './server';\n\nconst RELAI_API_BASE = 'https://api.relai.fi';\n\n// ============================================================================\n// Plugin Interface\n// ============================================================================\n\nexport interface PluginContext {\n /** Network for this endpoint */\n network: RelaiNetwork;\n /** Price in USD */\n price: number;\n /** Request path */\n path: string;\n /** HTTP method */\n method: string;\n}\n\nexport interface PluginResult {\n /** If true, skip payment and serve content for free */\n skip?: boolean;\n /** If true, reject the request entirely (e.g. service unhealthy) */\n reject?: boolean;\n /** HTTP status code when rejecting (default: 503) */\n rejectStatus?: number;\n /** Error message when rejecting */\n rejectMessage?: string;\n /** Extra response headers to set */\n headers?: Record<string, string>;\n /** Metadata attached to req.pluginMeta */\n meta?: Record<string, unknown>;\n}\n\nexport interface RelaiPlugin {\n /** Unique plugin name */\n name: string;\n\n /**\n * Called before the 402 payment check.\n * Return { skip: true } to bypass payment entirely.\n */\n beforePaymentCheck?(req: any, ctx: PluginContext): Promise<PluginResult>;\n\n /**\n * Called after a successful payment settlement.\n * Use for analytics, logging, webhooks, etc.\n */\n afterSettled?(req: any, result: SettleResult, ctx: PluginContext): Promise<void>;\n\n /**\n * Called once when the Relai instance initializes (server start).\n * Use to sync config to RelAI backend or validate credentials.\n */\n onInit?(): Promise<void>;\n\n /**\n * Called before sending the 402 response. Allows plugins to add\n * extensions or modify the response body (e.g. bridge info).\n */\n enrich402Response?(response: any, ctx: PluginContext): any;\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\nexport interface FreeTierPluginConfig {\n /** Service key (sk_live_...) for syncing with RelAI backend. If omitted, runs in local in-memory mode. */\n serviceKey?: string;\n /** Max free calls per buyer per period */\n perBuyerLimit: number;\n /** Reset period for per-buyer counters */\n resetPeriod?: 'none' | 'daily' | 'monthly';\n /** Optional global cap across all buyers */\n globalCap?: number;\n /** Specific paths to apply free tier to (default: '*' = all) */\n paths?: string[];\n /** Override RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Cache TTL in ms for check results (default: 5000) */\n cacheTtlMs?: number;\n}\n\ninterface FreeTierCheckResponse {\n free: boolean;\n remaining?: number;\n total?: number;\n reason?: string;\n globalRemaining?: number;\n}\n\n/**\n * Free Tier plugin - gives buyers a number of free API calls\n * before requiring payment.\n *\n * State is stored in the RelAI backend, keyed by your service key.\n * Config can be set here (SDK-side) or overridden in the relai.fi dashboard.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { freeTier } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * freeTier({\n * serviceKey: process.env.RELAI_SERVICE_KEY!,\n * perBuyerLimit: 10,\n * resetPeriod: 'daily',\n * }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\n// ============================================================================\n// Bridge Plugin\n// ============================================================================\n\nexport interface BridgePluginConfig {\n /** Service key (sk_live_...) for tracking bridge usage per API owner */\n serviceKey?: string;\n /** RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Override settle endpoint (auto-discovered from /bridge/info if not set) */\n settleEndpoint?: string;\n /** Override supported source chains (auto-discovered if not set) */\n supportedSourceChains?: string[];\n /** Override supported source assets (auto-discovered if not set) */\n supportedSourceAssets?: string[];\n /** Override bridge payTo map: { [caip2]: address } */\n payTo?: Record<string, string>;\n /** Override Solana fee payer address (auto-discovered if not set) */\n feePayerSvm?: string;\n /** Override payment facilitator URL */\n paymentFacilitator?: string;\n /** Bridge fee in basis points (default: auto-discovered) */\n feeBps?: number;\n}\n\ninterface BridgeInfo {\n settleEndpoint: string;\n supportedSourceChains: string[];\n supportedSourceAssets: string[];\n payTo: Record<string, string>;\n feePayerSvm: string | null;\n feeBps: number;\n paymentFacilitator: string;\n}\n\n/**\n * Bridge plugin - enables cross-chain payments via the RelAI bridge.\n *\n * When a buyer's wallet is on a different chain than the merchant accepts,\n * the client SDK can automatically route the payment through the bridge.\n * This plugin adds `extensions.bridge` to the 402 response with all the\n * info the client needs to execute a cross-chain payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { bridge } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'skale-base',\n * plugins: [\n * bridge(), // auto-discovers from https://api.relai.fi\n * ],\n * });\n *\n * // Buyer on Solana can now pay for a SKALE endpoint\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.05,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\nexport function bridge(config?: BridgePluginConfig): RelaiPlugin {\n const base = (config?.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n let bridgeInfo: BridgeInfo | null = null;\n\n // Hash service key for tracking (never expose raw key to clients)\n // Must match backend: crypto.createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16)\n let serviceKeyHash: string | null = null;\n if (config?.serviceKey) {\n serviceKeyHash = crypto.createHash('sha256').update(config.serviceKey).digest('hex').slice(0, 16);\n }\n\n async function fetchBridgeInfo(): Promise<BridgeInfo | null> {\n try {\n const res = await fetch(`${base}/bridge/info`);\n if (!res.ok) {\n console.warn(`[relai:bridge] Failed to fetch /bridge/info: ${res.status}`);\n return null;\n }\n const data = await res.json() as any;\n return {\n settleEndpoint: config?.settleEndpoint || data.settleEndpoint,\n supportedSourceChains: config?.supportedSourceChains || data.supportedSourceChains || [],\n supportedSourceAssets: config?.supportedSourceAssets || data.supportedSourceAssets || [],\n payTo: config?.payTo || data.payTo || {},\n feePayerSvm: config?.feePayerSvm ?? data.feePayerSvm ?? null,\n feeBps: config?.feeBps ?? data.feeBps ?? 100,\n paymentFacilitator: config?.paymentFacilitator || data.paymentFacilitator || 'https://facilitator.x402.fi',\n };\n } catch (err) {\n console.warn(`[relai:bridge] Failed to fetch bridge info: ${err}`);\n return null;\n }\n }\n\n return {\n name: 'bridge',\n\n async onInit() {\n bridgeInfo = await fetchBridgeInfo();\n if (bridgeInfo) {\n console.log(`[relai:bridge] Initialized — ${bridgeInfo.supportedSourceChains.length} source chains, settle: ${bridgeInfo.settleEndpoint}`);\n } else {\n console.warn('[relai:bridge] Bridge info not available — cross-chain payments disabled');\n }\n },\n\n enrich402Response(response: any, ctx: PluginContext) {\n if (!bridgeInfo || bridgeInfo.supportedSourceChains.length === 0) {\n return response;\n }\n\n // Don't add bridge extension if merchant's network is already a source chain\n // and there's only that one chain — no bridging needed\n const merchantCaip2 = response?.accepts?.[0]?.network;\n\n // Filter out the merchant's own chain from source chains\n // (buyer on same chain should pay directly, not bridge)\n const otherSourceChains = bridgeInfo.supportedSourceChains.filter(\n (c: string) => c !== merchantCaip2,\n );\n\n if (otherSourceChains.length === 0) {\n return response;\n }\n\n // Find the payTo for the first available source chain (Solana-first for UX)\n const solanaChain = otherSourceChains.find((c: string) => c.startsWith('solana:'));\n const primaryPayTo = solanaChain\n ? bridgeInfo.payTo[solanaChain]\n : bridgeInfo.payTo[otherSourceChains[0]];\n\n response.extensions = response.extensions || {};\n response.extensions.bridge = {\n info: {\n settleEndpoint: bridgeInfo.settleEndpoint,\n supportedSourceChains: otherSourceChains,\n supportedSourceAssets: bridgeInfo.supportedSourceAssets,\n payTo: primaryPayTo || null,\n payToMap: bridgeInfo.payTo,\n feePayerSvm: bridgeInfo.feePayerSvm,\n feeBps: bridgeInfo.feeBps,\n paymentFacilitator: bridgeInfo.paymentFacilitator,\n ...(serviceKeyHash ? { serviceKeyHash } : {}),\n },\n };\n\n return response;\n },\n };\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\n/**\n * Extended plugin interface with data export for free tier.\n */\nexport interface FreeTierPlugin extends RelaiPlugin {\n /** Export all in-memory usage data (local mode only). Returns null when using cloud mode. */\n getUsageData(): FreeTierUsageExport | null;\n /** Whether plugin is running in local (in-memory) or cloud (RelAI backend) mode. */\n readonly mode: 'local' | 'cloud';\n}\n\nexport interface FreeTierUsageExport {\n mode: 'local';\n config: {\n perBuyerLimit: number;\n resetPeriod: string;\n globalCap: number | null;\n paths: string[];\n };\n /** Per-buyer usage entries */\n usage: Array<{\n buyerId: string;\n path: string;\n count: number;\n periodStart: string;\n lastCall: string;\n }>;\n globalCount: number;\n exportedAt: string;\n}\n\nexport function freeTier(config: FreeTierPluginConfig): FreeTierPlugin {\n const base = (config.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const isLocal = !config.serviceKey;\n const resetPeriod = config.resetPeriod ?? 'none';\n const limit = config.perBuyerLimit;\n const globalCap = config.globalCap ?? null;\n const pluginPaths = config.paths ?? ['*'];\n\n /**\n * Resolve buyer identity from the request.\n * Priority: JWT sub > x-wallet-address > IP fallback\n */\n function resolveBuyerId(req: any): string {\n // 1. JWT Bearer token\n try {\n const auth = req.headers?.authorization || '';\n if (auth.startsWith('Bearer ')) {\n const token = auth.slice(7).trim();\n const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());\n if (payload?.sub) return `user:${payload.sub}`;\n }\n } catch { /* ignore */ }\n\n // 2. Explicit wallet header\n const wallet = req.headers?.['x-wallet-address'] || req.headers?.['x-buyer-address'];\n if (wallet) return `wallet:${wallet}`;\n\n // 3. IP fallback\n const ip =\n (req.headers?.['x-forwarded-for'] || '').split(',')[0].trim() ||\n req.socket?.remoteAddress ||\n req.ip ||\n 'unknown';\n return `ip:${ip}`;\n }\n\n function pathMatches(requestPath: string): boolean {\n return pluginPaths.includes('*') || pluginPaths.some((p) => {\n const normalized = p.toLowerCase().replace(/\\/+$/, '') || '/';\n const reqNormalized = requestPath.toLowerCase().replace(/\\/+$/, '') || '/';\n return normalized === reqNormalized || normalized === '*';\n });\n }\n\n // ---------------------------------------------------------------------------\n // LOCAL IN-MEMORY MODE\n // ---------------------------------------------------------------------------\n\n interface LocalUsageEntry {\n count: number;\n periodStart: string;\n lastCall: string;\n }\n\n // Map key: \"path:buyerId\"\n const localUsage = new Map<string, LocalUsageEntry>();\n let localGlobalCount = 0;\n\n function currentPeriodStart(): string {\n const now = new Date();\n if (resetPeriod === 'daily') {\n return new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();\n }\n if (resetPeriod === 'monthly') {\n return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();\n }\n return 'none'; // permanent\n }\n\n function isCurrentPeriod(entry: LocalUsageEntry): boolean {\n if (resetPeriod === 'none') return true;\n return entry.periodStart === currentPeriodStart();\n }\n\n function localKey(path: string, buyerId: string): string {\n return `${path}:${buyerId}`;\n }\n\n function localCheck(path: string, buyerId: string): FreeTierCheckResponse {\n const key = localKey(path, buyerId);\n const entry = localUsage.get(key);\n\n // If entry is from a previous period, it's stale — treat as 0\n const count = (entry && isCurrentPeriod(entry)) ? entry.count : 0;\n const remaining = Math.max(0, limit - count);\n\n // Global cap check\n if (globalCap != null && localGlobalCount >= globalCap) {\n return { free: false, remaining: 0, total: limit, reason: 'global_cap_reached', globalRemaining: 0 };\n }\n\n if (count >= limit) {\n return { free: false, remaining: 0, total: limit, reason: 'limit_reached' };\n }\n\n return {\n free: true,\n remaining: remaining - 1, // after this call\n total: limit,\n ...(globalCap != null ? { globalRemaining: globalCap - localGlobalCount - 1 } : {}),\n };\n }\n\n function localRecord(path: string, buyerId: string): void {\n const key = localKey(path, buyerId);\n const period = currentPeriodStart();\n const entry = localUsage.get(key);\n const now = new Date().toISOString();\n\n if (entry && isCurrentPeriod(entry)) {\n entry.count++;\n entry.lastCall = now;\n } else {\n localUsage.set(key, { count: 1, periodStart: period, lastCall: now });\n }\n localGlobalCount++;\n }\n\n function exportLocalData(): FreeTierUsageExport {\n const usage: FreeTierUsageExport['usage'] = [];\n for (const [key, entry] of localUsage.entries()) {\n // key format is \"path:buyerId\" but buyerId itself can contain ':'\n // We stored it as `${path}:${buyerId}` where path starts with '/'\n // So we find the first ':' after the path portion\n const firstSlash = key.indexOf('/');\n const colonAfterPath = key.indexOf(':', firstSlash > -1 ? firstSlash : 0);\n const path = colonAfterPath > -1 ? key.slice(0, colonAfterPath) : key;\n const buyerId = colonAfterPath > -1 ? key.slice(colonAfterPath + 1) : 'unknown';\n usage.push({\n buyerId,\n path,\n count: entry.count,\n periodStart: entry.periodStart,\n lastCall: entry.lastCall,\n });\n }\n return {\n mode: 'local',\n config: { perBuyerLimit: limit, resetPeriod, globalCap, paths: pluginPaths },\n usage,\n globalCount: localGlobalCount,\n exportedAt: new Date().toISOString(),\n };\n }\n\n // ---------------------------------------------------------------------------\n // CLOUD MODE (original — requires serviceKey)\n // ---------------------------------------------------------------------------\n\n // Simple in-memory cache: \"serviceKey:path:buyerId\" -> { result, expiresAt }\n const cache = new Map<string, { result: FreeTierCheckResponse; expiresAt: number }>();\n\n function resolveHeaders(): Record<string, string> {\n return {\n 'X-Service-Key': config.serviceKey!,\n 'Content-Type': 'application/json',\n };\n }\n\n function cloudCacheKey(path: string, buyerId: string): string {\n return `${config.serviceKey}:${path}:${buyerId}`;\n }\n\n async function checkFreeTier(path: string, buyerId: string): Promise<FreeTierCheckResponse> {\n const key = cloudCacheKey(path, buyerId);\n const now = Date.now();\n const cached = cache.get(key);\n if (cached && cached.expiresAt > now) {\n return cached.result;\n }\n\n try {\n const res = await fetch(`${base}/v1/plugins/free-tier/check`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n\n if (!res.ok) {\n // Non-blocking: if API unreachable, default to not-free\n return { free: false, reason: `api_error_${res.status}` };\n }\n\n const result = await res.json() as FreeTierCheckResponse;\n cache.set(key, { result, expiresAt: now + cacheTtl });\n return result;\n } catch (err) {\n // Network error - non-blocking, default to paid\n return { free: false, reason: 'network_error' };\n }\n }\n\n async function recordCall(path: string, buyerId: string): Promise<void> {\n try {\n await fetch(`${base}/v1/plugins/free-tier/record`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n // Invalidate cache for this buyer+path after recording\n cache.delete(cloudCacheKey(path, buyerId));\n } catch {\n // Fire-and-forget\n }\n }\n\n async function syncConfig(): Promise<void> {\n try {\n // Check if config already exists on backend — don't overwrite dashboard-managed configs\n const existing = await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'GET',\n headers: resolveHeaders(),\n });\n if (existing.ok) {\n const data = await existing.json();\n if (data.configs && data.configs.length > 0) {\n return; // Config managed via dashboard or previous sync — skip\n }\n }\n\n await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'PUT',\n headers: resolveHeaders(),\n body: JSON.stringify({\n perBuyerLimit: config.perBuyerLimit,\n resetPeriod: config.resetPeriod ?? 'none',\n globalCap: config.globalCap ?? null,\n paths: pluginPaths,\n }),\n });\n } catch (err) {\n console.warn(`[relai:freeTier] Failed to sync config to RelAI: ${err}`);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Plugin instance\n // ---------------------------------------------------------------------------\n\n return {\n name: 'free-tier',\n mode: isLocal ? 'local' : 'cloud',\n\n getUsageData(): FreeTierUsageExport | null {\n if (!isLocal) return null;\n return exportLocalData();\n },\n\n async onInit() {\n if (isLocal) {\n console.log(`[relai:freeTier] Running in local mode (in-memory). perBuyerLimit=${limit}, resetPeriod=${resetPeriod}`);\n return;\n }\n await syncConfig();\n },\n\n async beforePaymentCheck(req, ctx) {\n const requestPath = ctx.path || '/';\n if (!pathMatches(requestPath)) return {};\n\n const buyerId = resolveBuyerId(req);\n\n // ── Local mode ──\n if (isLocal) {\n const result = localCheck(requestPath, buyerId);\n if (result.free) {\n localRecord(requestPath, buyerId);\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? limit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n 'X-Free-Tier-Mode': 'local',\n },\n meta: { freeTier: true, buyerId, remaining: result.remaining, total: result.total ?? limit, mode: 'local' },\n };\n }\n return {};\n }\n\n // ── Cloud mode ──\n const result = await checkFreeTier(requestPath, buyerId);\n\n if (result.free) {\n // Record the call (fire-and-forget)\n recordCall(requestPath, buyerId).catch(() => {});\n\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? config.perBuyerLimit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n },\n meta: {\n freeTier: true,\n buyerId,\n remaining: result.remaining,\n total: result.total ?? config.perBuyerLimit,\n },\n };\n }\n\n return {};\n },\n };\n}\n\n// ============================================================================\n// Shield Plugin\n// ============================================================================\n\nexport interface ShieldPluginConfig {\n /**\n * Custom health check function. Return true if the service is healthy.\n * Takes priority over `healthUrl` if both are provided.\n */\n healthCheck?: () => Promise<boolean> | boolean;\n /**\n * URL to probe. A 2xx response means healthy.\n * Ignored if `healthCheck` is provided.\n */\n healthUrl?: string;\n /** Timeout in ms for the health probe (default: 5000) */\n timeoutMs?: number;\n /** Cache the health result for this many ms (default: 10000) */\n cacheTtlMs?: number;\n /** Message returned to the caller when unhealthy (default: 'Service temporarily unavailable. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when unhealthy (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Shield plugin — protects buyers from paying for unhealthy endpoints.\n *\n * Before the server returns a 402, Shield runs a health check (custom\n * function or URL probe). If the service is down, the request is rejected\n * with 503 instead of asking for payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { shield } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * shield({\n * healthUrl: 'https://my-api.com/health',\n * timeoutMs: 3000,\n * }),\n * ],\n * });\n *\n * // If /health is down, buyers get 503 instead of 402\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function shield(config?: ShieldPluginConfig): RelaiPlugin {\n const timeoutMs = config?.timeoutMs ?? 5000;\n const cacheTtl = config?.cacheTtlMs ?? 10000;\n const unhealthyMessage = config?.unhealthyMessage ?? 'Service temporarily unavailable. Please try again later.';\n const unhealthyStatus = config?.unhealthyStatus ?? 503;\n\n let cached: { healthy: boolean; expiresAt: number } | null = null;\n\n async function checkHealth(): Promise<boolean> {\n const now = Date.now();\n if (cached && cached.expiresAt > now) {\n return cached.healthy;\n }\n\n let healthy = true;\n\n try {\n if (config?.healthCheck) {\n healthy = await Promise.resolve(config.healthCheck());\n } else if (config?.healthUrl) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetch(config.healthUrl, {\n method: 'GET',\n signal: controller.signal,\n });\n healthy = res.ok;\n } finally {\n clearTimeout(timer);\n }\n }\n // No healthCheck and no healthUrl → always healthy (no-op mode)\n } catch {\n healthy = false;\n }\n\n cached = { healthy, expiresAt: now + cacheTtl };\n return healthy;\n }\n\n return {\n name: 'shield',\n\n async onInit() {\n const healthy = await checkHealth();\n console.log(`[relai:shield] Initial health check: ${healthy ? 'healthy ✓' : 'UNHEALTHY ✗'}`);\n },\n\n async beforePaymentCheck(_req, _ctx): Promise<PluginResult> {\n const healthy = await checkHealth();\n\n if (!healthy) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Shield-Status': 'unhealthy',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Shield-Status': 'healthy' },\n };\n },\n };\n}\n\n// ============================================================================\n// Preflight Plugin\n// ============================================================================\n\nexport interface PreflightPluginConfig {\n /**\n * Base URL of the API server (e.g. 'https://my-api.com').\n * The plugin appends the request path to form the probe URL.\n * **Required** — the plugin needs to know where to send the probe.\n */\n baseUrl: string;\n /** Timeout in ms for the preflight probe (default: 3000) */\n timeoutMs?: number;\n /** Cache the probe result for this many ms (default: 5000) */\n cacheTtlMs?: number;\n /** Custom unhealthy message (default: 'Endpoint not responding. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when endpoint unreachable (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Preflight plugin — verifies the specific endpoint responds before payment.\n *\n * Unlike Shield (global health check), Preflight probes the **actual endpoint**\n * the buyer is requesting. It sends a HEAD request with `X-Preflight: true` —\n * the Relai middleware responds 200 instantly without triggering payment.\n * If the endpoint doesn't respond, the buyer gets 503 instead of 402.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { preflight } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * preflight({ baseUrl: 'https://my-api.com' }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function preflight(config: PreflightPluginConfig): RelaiPlugin {\n const baseUrl = config.baseUrl.replace(/\\/+$/, '');\n const timeoutMs = config.timeoutMs ?? 3000;\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const unhealthyMessage = config.unhealthyMessage ?? 'Endpoint not responding. Please try again later.';\n const unhealthyStatus = config.unhealthyStatus ?? 503;\n\n const cache = new Map<string, { ok: boolean; expiresAt: number }>();\n\n async function probeEndpoint(path: string): Promise<boolean> {\n const now = Date.now();\n const cached = cache.get(path);\n if (cached && cached.expiresAt > now) {\n return cached.ok;\n }\n\n let ok = false;\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n const res = await fetch(url, {\n method: 'HEAD',\n headers: { 'X-Preflight': 'true' },\n signal: controller.signal,\n });\n ok = res.ok;\n } finally {\n clearTimeout(timer);\n }\n } catch {\n ok = false;\n }\n\n cache.set(path, { ok, expiresAt: now + cacheTtl });\n return ok;\n }\n\n return {\n name: 'preflight',\n\n async onInit() {\n // Probe the base URL on startup\n const ok = await probeEndpoint('/');\n console.log(`[relai:preflight] Initial probe ${baseUrl}: ${ok ? 'reachable ✓' : 'UNREACHABLE ✗'}`);\n },\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const path = ctx.path || '/';\n const ok = await probeEndpoint(path);\n\n if (!ok) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Preflight-Status': 'unreachable',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Preflight-Status': 'ok' },\n };\n },\n };\n}\n\n\n// ============================================================================\n// Circuit Breaker Plugin\n// ============================================================================\n\nexport type CircuitState = 'closed' | 'open' | 'half-open';\n\nexport interface CircuitBreakerPluginConfig {\n /**\n * Number of consecutive failures before the circuit opens.\n * Default: 5\n */\n failureThreshold?: number;\n /**\n * Time in ms the circuit stays open before moving to half-open.\n * Default: 30000 (30 seconds)\n */\n resetTimeMs?: number;\n /**\n * Number of successful requests in half-open state before closing the circuit.\n * Default: 2\n */\n halfOpenSuccesses?: number;\n /** HTTP status code when circuit is open (default: 503) */\n openStatus?: number;\n /** Error message when circuit is open */\n openMessage?: string;\n /**\n * HTTP status codes considered as failures.\n * Default: [500, 502, 503, 504]\n */\n failureCodes?: number[];\n /**\n * Track failures globally or per-path.\n * Default: 'per-path'\n */\n scope?: 'global' | 'per-path';\n}\n\ninterface CircuitEntry {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureAt: number;\n}\n\n/**\n * Circuit Breaker plugin — tracks failure history and opens the circuit\n * after repeated failures, preventing buyers from paying for broken endpoints.\n *\n * Unlike Shield/Preflight (real-time probes), Circuit Breaker is **zero-latency** —\n * it makes no additional HTTP requests. Instead, it tracks `afterSettled` outcomes\n * and rejects requests when the failure rate exceeds the threshold.\n *\n * States:\n * - **closed** — normal operation, requests flow through\n * - **open** — too many failures, requests are rejected with 503\n * - **half-open** — after resetTime, allows a few test requests through\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { circuitBreaker } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * circuitBreaker({ failureThreshold: 5, resetTimeMs: 30000 }),\n * ],\n * });\n * ```\n */\nexport function circuitBreaker(config?: CircuitBreakerPluginConfig): RelaiPlugin {\n const failureThreshold = config?.failureThreshold ?? 5;\n const resetTimeMs = config?.resetTimeMs ?? 30000;\n const halfOpenSuccesses = config?.halfOpenSuccesses ?? 2;\n const openStatus = config?.openStatus ?? 503;\n const openMessage = config?.openMessage ?? 'Service temporarily unavailable (circuit open). Please try again later.';\n const failureCodes = new Set(config?.failureCodes ?? [500, 502, 503, 504]);\n const scope = config?.scope ?? 'per-path';\n\n const circuits = new Map<string, CircuitEntry>();\n\n function getKey(path: string): string {\n return scope === 'global' ? '__global__' : path;\n }\n\n function getCircuit(key: string): CircuitEntry {\n let entry = circuits.get(key);\n if (!entry) {\n entry = { state: 'closed', failures: 0, successes: 0, lastFailureAt: 0 };\n circuits.set(key, entry);\n }\n return entry;\n }\n\n function recordFailure(key: string): void {\n const circuit = getCircuit(key);\n circuit.failures++;\n circuit.successes = 0;\n circuit.lastFailureAt = Date.now();\n\n if (circuit.failures >= failureThreshold) {\n circuit.state = 'open';\n }\n }\n\n function recordSuccess(key: string): void {\n const circuit = getCircuit(key);\n if (circuit.state === 'half-open') {\n circuit.successes++;\n if (circuit.successes >= halfOpenSuccesses) {\n // Enough successes in half-open → close the circuit\n circuit.state = 'closed';\n circuit.failures = 0;\n circuit.successes = 0;\n }\n } else {\n // Reset failures on success in closed state\n circuit.failures = 0;\n circuit.successes = 0;\n }\n }\n\n return {\n name: 'circuit-breaker',\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const key = getKey(ctx.path || '/');\n const circuit = getCircuit(key);\n const now = Date.now();\n\n // Check if open circuit should transition to half-open\n if (circuit.state === 'open' && (now - circuit.lastFailureAt) >= resetTimeMs) {\n circuit.state = 'half-open';\n circuit.successes = 0;\n }\n\n if (circuit.state === 'open') {\n const retryAfter = Math.max(1, Math.ceil((resetTimeMs - (now - circuit.lastFailureAt)) / 1000));\n return {\n reject: true,\n rejectStatus: openStatus,\n rejectMessage: openMessage,\n headers: {\n 'X-Circuit-State': 'open',\n 'X-Circuit-Failures': String(circuit.failures),\n 'Retry-After': String(retryAfter),\n },\n };\n }\n\n return {\n headers: {\n 'X-Circuit-State': circuit.state,\n ...(circuit.state === 'half-open' ? { 'X-Circuit-Successes': String(circuit.successes) } : {}),\n },\n };\n },\n\n async afterSettled(_req, result, ctx): Promise<void> {\n const key = getKey(ctx.path || '/');\n\n // Check if the settlement itself failed or returned error codes\n if (!result.success) {\n recordFailure(key);\n return;\n }\n\n // Check response status if available\n const status = (result as any).statusCode || (result as any).status;\n if (status && failureCodes.has(status)) {\n recordFailure(key);\n } else {\n recordSuccess(key);\n }\n },\n };\n}\n\n\n// ============================================================================\n// Refund Plugin\n// ============================================================================\n\nexport interface RefundPluginConfig {\n /**\n * HTTP status codes that trigger a refund/credit after payment.\n * Default: [500, 502, 503, 504]\n */\n triggerCodes?: number[];\n /**\n * What to do when a refund is triggered.\n * - 'credit': record a credit that the free-tier plugin can consume later\n * - 'log': just log the event (useful with a custom onRefund callback)\n * Default: 'credit'\n */\n mode?: 'credit' | 'log';\n /**\n * Called whenever a refund event is triggered.\n * Use for external refund processing, logging, or notifications.\n */\n onRefund?: (event: RefundEvent) => void | Promise<void>;\n /**\n * Also trigger refund when settlement itself fails (result.success === false).\n * Default: true\n */\n refundOnSettlementFailure?: boolean;\n}\n\nexport interface RefundEvent {\n /** Buyer wallet address */\n payer: string;\n /** Transaction ID of the original payment */\n transactionId: string;\n /** Network used */\n network: string;\n /** Amount paid in USD */\n amount: number;\n /** Request path */\n path: string;\n /** Reason for the refund */\n reason: 'endpoint_error' | 'settlement_failure' | 'timeout';\n /** HTTP status code that triggered the refund (if applicable) */\n statusCode?: number;\n /** Timestamp */\n timestamp: number;\n}\n\n/**\n * Refund plugin — automatically handles refund/credit when a paid request fails.\n *\n * After payment settles, if the endpoint returns an error (e.g. 500),\n * the plugin records a credit or calls your custom `onRefund` handler.\n * Credits can be consumed by the free-tier plugin on the next request.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { refund } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * refund({\n * triggerCodes: [500, 502, 503],\n * onRefund: (event) => {\n * console.log(`Refund for ${event.payer}: $${event.amount} on ${event.path}`);\n * // Notify buyer, record in DB, etc.\n * },\n * }),\n * ],\n * });\n * ```\n */\nexport function refund(config?: RefundPluginConfig): RelaiPlugin {\n const triggerCodes = new Set(config?.triggerCodes ?? [500, 502, 503, 504]);\n const mode = config?.mode ?? 'credit';\n const onRefund = config?.onRefund;\n const refundOnSettlementFailure = config?.refundOnSettlementFailure ?? true;\n\n // In-memory credit ledger: payer → number of credits\n const credits = new Map<string, number>();\n\n return {\n name: 'refund',\n\n async beforePaymentCheck(req, ctx): Promise<PluginResult> {\n // If buyer has credits from previous refunds, skip payment\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.headers?.['authorization']?.replace(/^Bearer\\s+/i, '') ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n\n const buyerCredits = credits.get(buyerId) || 0;\n if (buyerCredits > 0) {\n credits.set(buyerId, buyerCredits - 1);\n return {\n skip: true,\n headers: {\n 'X-Refund-Credit': 'applied',\n 'X-Refund-Credits-Remaining': String(buyerCredits - 1),\n },\n meta: {\n refundCredit: true,\n creditsRemaining: buyerCredits - 1,\n },\n };\n }\n }\n\n return {};\n },\n\n async afterSettled(req, result, ctx): Promise<void> {\n const payer = result.payer || 'unknown';\n const transactionId = result.transaction || '';\n\n // Settlement failure\n if (!result.success && refundOnSettlementFailure) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'settlement_failure',\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n return;\n }\n\n // Check for endpoint error codes in settlement result\n const status = (result as any).statusCode || (result as any).status;\n if (status && triggerCodes.has(status)) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'endpoint_error',\n statusCode: status,\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n }\n },\n };\n}\n\n// Re-export for backwards-compatible import path '@relai-fi/x402/plugins'\nexport { submitRelayFeedback, type RelayFeedbackConfig } from './relay-feedback';\n\n// ============================================================================\n// Score Plugin\n// ============================================================================\n\nexport interface ScorePluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n * Find yours in the RelAI dashboard under \"On-chain Identity\".\n */\n agentId: string | number;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha\n */\n rpcUrl?: string;\n /**\n * ERC-8004 IdentityRegistry contract address.\n * Default: process.env.ERC8004_IDENTITY_REGISTRY\n */\n identityRegistryAddress?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\ninterface CachedScore {\n score: Record<string, unknown>;\n expiresAt: number;\n}\n\nconst SCORE_IDENTITY_ABI = [\n 'function ownerOf(uint256 tokenId) external view returns (address)',\n];\n\nconst SCORE_FEEDBACK_TOPIC = ethers.id(\n 'NewFeedback(uint256,address,uint64,int128,uint8,string,string,string,string,string,bytes32)'\n);\n\nconst SCORE_FEEDBACK_IFACE = new ethers.Interface([\n 'event NewFeedback(uint256 indexed agentId, address indexed giver, uint64 index, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, string responseURI, bytes32 feedbackHash)',\n]);\n\nconst SCORE_BATCH = 1900;\nconst SCORE_HISTORY = 10000;\n\n/**\n * Score plugin — fetches ERC-8004 on-chain reputation for your API\n * directly via RPC and injects it into the 402 response `extensions.score`.\n *\n * Agents can read the score **before paying**, enabling trust-based routing:\n * - `feedbackCount` — number of on-chain feedback entries\n * - `successRate` — 0–100 (% of successful calls)\n * - `avgResponseMs` — average response time in milliseconds\n * - `verified` — true if agentId is registered on-chain\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * ],\n * });\n * ```\n */\nexport function score(config: ScorePluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:score] ERC8004_AGENT_ID not set — score plugin is a no-op');\n return { name: 'score', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n let cached: CachedScore | null = null;\n\n function getContracts() {\n const identityAddress = config.identityRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_IDENTITY_REGISTRY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n if (!identityAddress || !reputationAddress) return null;\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const identity = new ethers.Contract(identityAddress, SCORE_IDENTITY_ABI, provider);\n return { provider, identity, reputationAddress };\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && cached.expiresAt > Date.now()) {\n return cached.score;\n }\n\n const contracts = getContracts();\n if (!contracts) {\n console.warn(`[relai:score] ERC8004_IDENTITY_REGISTRY or ERC8004_REPUTATION_REGISTRY not set — score disabled`);\n return null;\n }\n\n const { provider, identity, reputationAddress } = contracts;\n const bigId = BigInt(agentId);\n\n try {\n let verified = false;\n try {\n const owner = await identity.ownerOf(bigId);\n verified = !!owner;\n } catch {\n // Not registered\n }\n\n if (!verified) {\n const scoreObj = { feedbackCount: 0, successRate: null, avgResponseMs: null, verified: false, agentId, source: 'erc8004-skale' };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n }\n\n const latest = await provider.getBlockNumber();\n const agentIdTopic = ethers.zeroPadValue(ethers.toBeHex(bigId), 32);\n const logs: any[] = [];\n\n for (let from = Math.max(0, latest - SCORE_HISTORY); from <= latest; from += SCORE_BATCH) {\n const to = Math.min(from + SCORE_BATCH - 1, latest);\n const chunk = await provider.getLogs({\n address: reputationAddress,\n topics: [SCORE_FEEDBACK_TOPIC, agentIdTopic],\n fromBlock: from,\n toBlock: to,\n });\n logs.push(...chunk);\n }\n\n const srValues: number[] = [];\n const rtValues: number[] = [];\n const endpointMap: Record<string, { sr: number[]; rt: number[] }> = {};\n\n for (const log of logs) {\n try {\n const p = SCORE_FEEDBACK_IFACE.parseLog({ topics: log.topics as string[], data: log.data });\n if (!p) continue;\n const val = Number(p.args.value) / Math.pow(10, Number(p.args.valueDecimals));\n const ep: string = p.args.endpoint || '';\n if (!endpointMap[ep]) endpointMap[ep] = { sr: [], rt: [] };\n if (p.args.tag1 === 'successRate') { srValues.push(val); endpointMap[ep].sr.push(val); }\n else if (p.args.tag1 === 'responseTime') { rtValues.push(val); endpointMap[ep].rt.push(val); }\n } catch { /* skip */ }\n }\n\n const feedbackCount = srValues.length + rtValues.length;\n const successRate = srValues.length\n ? Math.round(srValues.reduce((a, b) => a + b, 0) / srValues.length)\n : null;\n const avgResponseMs = rtValues.length\n ? Math.round(rtValues.reduce((a, b) => a + b, 0) / rtValues.length)\n : null;\n\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n for (const [ep, { sr, rt }] of Object.entries(endpointMap)) {\n endpoints[ep] = {\n feedbackCount: sr.length + rt.length,\n successRate: sr.length ? Math.round(sr.reduce((a, b) => a + b, 0) / sr.length) : null,\n avgResponseMs: rt.length ? Math.round(rt.reduce((a, b) => a + b, 0) / rt.length) : null,\n };\n }\n\n const scoreObj = { feedbackCount, successRate, avgResponseMs, verified: true, agentId, source: 'erc8004-skale', endpoints };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err) {\n console.warn(`[relai:score] fetchScore RPC error for agentId=${agentId}:`, err);\n return null;\n }\n }\n\n return {\n name: 'score',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:score] Initialized — agentId=${agentId}, feedback=${s.feedbackCount}, successRate=${s.successRate}%, avgResp=${s.avgResponseMs}ms`);\n } else {\n console.warn(`[relai:score] Could not load score for agentId=${agentId} — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any, _ctx: PluginContext) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n score: scoreData,\n },\n };\n },\n };\n}\n\n// ============================================================================\n// Feedback Plugin\n// ============================================================================\n\nexport interface FeedbackPluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n */\n agentId: string | number;\n /**\n * Private key of the wallet that signs feedback transactions.\n * The wallet must hold CREDIT tokens on SKALE Base to pay gas.\n * Default: process.env.BACKEND_WALLET_PRIVATE_KEY\n */\n walletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Tag2 label for all feedback entries (default: 'x402').\n */\n tag2?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n * Value: 1 (success) or 0 (failure).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n * Value: milliseconds elapsed since request start.\n * Requires req.x402StartTime (set automatically if using Relai.protect()).\n */\n submitResponseTime?: boolean;\n}\n\nconst FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Feedback plugin — submits ERC-8004 on-chain reputation after every successful x402 settlement.\n *\n * Records `successRate` and `responseTime` signals to the ReputationRegistry on SKALE Base.\n * This is the mechanism that **builds** the score that `score()` plugin later reads.\n *\n * Runs fire-and-forget — never blocks the response.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score, feedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * feedback({ agentId: '5' }), // needs BACKEND_WALLET_PRIVATE_KEY in env\n * ],\n * });\n * ```\n */\nexport function feedback(config: FeedbackPluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:feedback] ERC8004_AGENT_ID not set — feedback plugin is a no-op');\n return { name: 'feedback', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const tag2 = config.tag2 ?? 'x402';\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n let _reputation: any = null;\n\n function getReputation() {\n if (_reputation) return _reputation;\n\n const privateKey = config.walletPrivateKey\n ?? (typeof process !== 'undefined' ? process.env?.BACKEND_WALLET_PRIVATE_KEY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) return null;\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n _reputation = new ethers.Contract(reputationAddress, FEEDBACK_REPUTATION_ABI, signer);\n return _reputation;\n }\n\n function submitAsync(tag1: string, value: number, valueDecimals: number, endpoint: string) {\n const reputation = getReputation();\n if (!reputation) return;\n\n (async () => {\n try {\n const tx = await reputation.giveFeedback(\n BigInt(agentId),\n BigInt(Math.round(value)),\n valueDecimals,\n tag1,\n tag2,\n endpoint,\n '',\n ethers.ZeroHash,\n );\n console.log(`[relai:feedback] Submitted agentId=${agentId} tag=${tag1} value=${value} tx=${tx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:feedback] giveFeedback failed (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'feedback',\n\n async onInit() {\n const reputation = getReputation();\n if (reputation) {\n console.log(`[relai:feedback] Initialized — agentId=${agentId}, submitSuccessRate=${submitSuccessRate}, submitResponseTime=${submitResponseTime}`);\n } else {\n console.warn(`[relai:feedback] BACKEND_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — feedback disabled`);\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n\n // successRate: 1 = success, 0 = failure\n if (submitSuccessRate) {\n submitAsync('successRate', result.success ? 1 : 0, 0, endpoint);\n }\n\n // responseTime in ms — use req.x402StartTime if available\n if (submitResponseTime) {\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n if (responseTimeMs > 0) {\n submitAsync('responseTime', responseTimeMs, 0, endpoint);\n }\n }\n },\n };\n}\n\n// ============================================================================\n// Solana Feedback Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaFeedbackPluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Private key of the feedback wallet — base58 or JSON array format.\n * Should differ from the owner's registration wallet (avoids self-feedback restriction).\n * Default: process.env.SOLANA_8004_FEEDBACK_KEY ?? process.env.SOLANA_8004_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n */\n submitResponseTime?: boolean;\n}\n\n/**\n * Solana Feedback plugin — submits 8004-solana on-chain reputation after x402 settlement.\n *\n * Uses the native Solana `8004-solana` program (MPL Core NFT registry),\n * separate from the SKALE EVM ReputationRegistry used by `feedback()`.\n *\n * Requires `8004-solana` package to be installed:\n * ```\n * npm install 8004-solana\n * ```\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaFeedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaFeedback({\n * assetPubkey: process.env.SOLANA_AGENT_ASSET!, // e.g. 'GH93tGR8...'\n * }),\n * ],\n * });\n * ```\n */\nexport function solanaFeedback(config: SolanaFeedbackPluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n function getPrivateKey(): string | null {\n return config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.SOLANA_8004_FEEDBACK_KEY ?? process.env?.SOLANA_8004_PRIVATE_KEY\n : undefined)\n ?? null;\n }\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function buildSDK(): Promise<any> {\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n const privateKeyRaw = getPrivateKey();\n if (!privateKeyRaw) return null;\n\n let secretKey: Uint8Array;\n try {\n const parsed = JSON.parse(privateKeyRaw);\n if (Array.isArray(parsed)) {\n secretKey = Uint8Array.from(parsed);\n } else {\n throw new Error('not array');\n }\n } catch {\n // base58\n try {\n const bs58Mod = await import('bs58' as any);\n const decode = bs58Mod.default?.decode ?? bs58Mod.decode;\n secretKey = decode(privateKeyRaw);\n } catch {\n console.warn('[relai:solanaFeedback] Could not parse feedbackWalletPrivateKey');\n return null;\n }\n }\n\n const { Keypair } = await import('@solana/web3.js');\n const signer = Keypair.fromSecretKey(secretKey);\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n\n return new SolanaSDK({ cluster, signer, ...(rpcUrl ? { rpcUrl } : {}) });\n }\n\n function submitAsync(isSuccess: boolean, responseTimeMs: number, endpoint: string) {\n (async () => {\n try {\n const sdk = await buildSDK();\n if (!sdk) return;\n\n const { PublicKey } = await import('@solana/web3.js');\n const pubkey = new PublicKey(assetPubkey);\n\n if (submitSuccessRate) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(isSuccess ? 10000 : 0),\n tag1: 'successRate',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] successRate submitted asset=${assetPubkey.slice(0, 8)}... tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n }\n\n if (submitResponseTime && responseTimeMs > 0) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(Math.round(responseTimeMs)),\n tag1: 'responseTime',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] responseTime submitted asset=${assetPubkey.slice(0, 8)}... ms=${responseTimeMs} tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] feedback error (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'solanaFeedback',\n\n async onInit() {\n const privateKey = getPrivateKey();\n if (!privateKey) {\n console.warn('[relai:solanaFeedback] SOLANA_8004_FEEDBACK_KEY not set — Solana feedback disabled');\n return;\n }\n try {\n await import('8004-solana' as any);\n console.log(`[relai:solanaFeedback] Initialized — asset=${assetPubkey.slice(0, 8)}... cluster=${getCluster()}`);\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n\n submitAsync(result.success, responseTimeMs, endpoint);\n },\n };\n}\n\n// ============================================================================\n// Solana Score Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaScorePluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\n/**\n * Solana Score plugin — fetches 8004-solana on-chain reputation and injects it\n * into the 402 response `extensions.score` before the client pays.\n *\n * Mirrors the EVM `score()` plugin but reads from the native Solana program\n * instead of the SKALE EVM ReputationRegistry.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaScore } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaScore({ assetPubkey: process.env.SOLANA_AGENT_ASSET! }),\n * ],\n * });\n * ```\n */\nexport function solanaScore(config: SolanaScorePluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n\n if (!assetPubkey || String(assetPubkey) === 'undefined') {\n console.warn('[relai:solanaScore] assetPubkey not set — solanaScore plugin is a no-op');\n return { name: 'solanaScore', async onInit() {} };\n }\n\n let cached: CachedScore | null = null;\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && Date.now() < cached.expiresAt) return cached.score;\n\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaScore] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n try {\n const { PublicKey } = await import('@solana/web3.js');\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n const sdk = new SolanaSDK({ cluster, ...(rpcUrl ? { rpcUrl } : {}) });\n const pubkey = new PublicKey(assetPubkey);\n\n // Try getReputationSummary first (lightweight)\n let feedbackCount = 0;\n let successRate: number | null = null;\n let avgResponseMs: number | null = null;\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n\n // Try to read all feedback for per-endpoint breakdown\n let allFeedbacks: any[] = [];\n try {\n allFeedbacks = await sdk.readAllFeedback(pubkey);\n } catch {\n // fallback to summary only\n }\n\n if (allFeedbacks.length > 0) {\n // Group by endpoint\n const byEndpoint: Record<string, { successValues: number[]; responseTimes: number[] }> = {};\n let globalSuccessValues: number[] = [];\n let globalResponseTimes: number[] = [];\n\n for (const entry of allFeedbacks) {\n const tag1: string = entry.tag1 ?? '';\n const rawValue = entry.value ?? entry.score ?? 0;\n const value = typeof rawValue === 'bigint' ? Number(rawValue) : Number(rawValue);\n const ep: string = entry.endpoint ?? '';\n\n if (!byEndpoint[ep]) byEndpoint[ep] = { successValues: [], responseTimes: [] };\n\n if (tag1 === 'successRate') {\n globalSuccessValues.push(value);\n byEndpoint[ep].successValues.push(value);\n } else if (tag1 === 'responseTime') {\n globalResponseTimes.push(value);\n byEndpoint[ep].responseTimes.push(value);\n }\n }\n\n feedbackCount = globalSuccessValues.length;\n if (feedbackCount > 0) {\n const avgSuccess = globalSuccessValues.reduce((a, b) => a + b, 0) / feedbackCount;\n // value=10000 means success (100%), value=0 means failure\n successRate = Math.round((avgSuccess / 10000) * 100);\n }\n if (globalResponseTimes.length > 0) {\n avgResponseMs = Math.round(globalResponseTimes.reduce((a, b) => a + b, 0) / globalResponseTimes.length);\n }\n\n for (const [ep, data] of Object.entries(byEndpoint)) {\n const epCount = data.successValues.length;\n const epAvgSuccess = epCount > 0\n ? data.successValues.reduce((a, b) => a + b, 0) / epCount\n : null;\n const epAvgMs = data.responseTimes.length > 0\n ? Math.round(data.responseTimes.reduce((a, b) => a + b, 0) / data.responseTimes.length)\n : null;\n endpoints[ep || '/'] = {\n feedbackCount: epCount,\n successRate: epAvgSuccess !== null ? Math.round((epAvgSuccess / 10000) * 100) : null,\n avgResponseMs: epAvgMs,\n };\n }\n } else {\n // Fallback to summary\n try {\n const summary = await sdk.getReputationSummary(pubkey);\n feedbackCount = summary.count ?? 0;\n if (feedbackCount > 0) {\n const avg = summary.averageScore ?? 0;\n successRate = Math.round((avg / 10000) * 100);\n }\n } catch {\n // no data\n }\n }\n\n const scoreObj = {\n feedbackCount,\n successRate,\n avgResponseMs,\n assetPubkey,\n source: '8004-solana',\n cluster,\n ...(Object.keys(endpoints).length > 0 ? { endpoints } : {}),\n };\n\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err: any) {\n console.warn(`[relai:solanaScore] fetch error (non-fatal): ${err?.message}`);\n return null;\n }\n }\n\n return {\n name: 'solanaScore',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:solanaScore] Initialized — asset=${assetPubkey.slice(0, 8)}... feedbackCount=${(s as any).feedbackCount} cluster=${getCluster()}`);\n } else {\n console.warn(`[relai:solanaScore] Could not load score for asset=${assetPubkey.slice(0, 8)}... — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n score: scoreData,\n },\n };\n },\n };\n}\n","// src/relay-feedback.ts\n// Standalone utility for submitting ERC-8004 feedback about third-party APIs.\n// Not a plugin — call directly from your relay/aggregator application code.\n\nimport { ethers } from 'ethers';\n\nexport interface RelayFeedbackConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) of the **target API** you are calling.\n */\n agentId: string | number;\n /**\n * Whether the API call succeeded.\n */\n success: boolean;\n /**\n * Elapsed milliseconds for the API call.\n */\n responseTimeMs?: number;\n /**\n * Endpoint path of the called API (e.g. '/v1/data').\n */\n endpoint?: string;\n /**\n * Private key of the **relay/third-party** wallet that signs feedback.\n * MUST be different from the API owner's wallet — ReputationRegistry\n * may restrict self-feedback. Wallet needs CREDIT tokens on SKALE Base.\n * Default: process.env.FEEDBACK_WALLET_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n}\n\nconst RELAY_FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Submit ERC-8004 on-chain feedback about a **third-party API** you called.\n *\n * Call this fire-and-forget from your relay/aggregator after every external API call.\n * Uses a separate relay wallet (not the API owner's key) to avoid self-feedback restrictions.\n *\n * Records:\n * - `successRate`: 10000 (= 100%) on success, 0 on failure — 2 decimal places\n * - `responseTime`: elapsed milliseconds\n *\n * @example\n * ```typescript\n * import { submitRelayFeedback } from '@relai-fi/x402/relay-feedback';\n *\n * // after calling an external API:\n * const start = Date.now();\n * const result = await fetch('https://other-api.com/data');\n * submitRelayFeedback({\n * agentId: '5',\n * success: result.ok,\n * responseTimeMs: Date.now() - start,\n * endpoint: '/data',\n * });\n * ```\n */\nexport function submitRelayFeedback(config: RelayFeedbackConfig): void {\n const agentId = String(config.agentId);\n const endpoint = config.endpoint ?? '';\n const responseTimeMs = config.responseTimeMs ?? 0;\n\n const privateKey = config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.FEEDBACK_WALLET_PRIVATE_KEY ?? process.env?.ERC8004_FEEDBACK_WALLET_PRIVATE_KEY\n : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) {\n console.warn('[relai:submitRelayFeedback] FEEDBACK_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — skipping');\n return;\n }\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n const reputation = new ethers.Contract(reputationAddress, RELAY_FEEDBACK_REPUTATION_ABI, signer);\n const id = BigInt(agentId);\n\n (async () => {\n const successValue = config.success ? 10000n : 0n;\n try {\n const srTx = await reputation.giveFeedback(\n id, successValue, 2, 'successRate', '', endpoint, '', ethers.ZeroHash,\n );\n await srTx.wait();\n console.log(`[relai:submitRelayFeedback] successRate confirmed agentId=${agentId} success=${config.success}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n\n if (responseTimeMs > 0) {\n try {\n const rtTx = await reputation.giveFeedback(\n id, BigInt(Math.max(0, Math.round(responseTimeMs))), 0, 'responseTime', '', endpoint, '', ethers.ZeroHash,\n );\n console.log(`[relai:submitRelayFeedback] responseTime sent agentId=${agentId} ms=${responseTimeMs} tx=${rtTx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n })();\n}\n"],"mappings":"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","names":["ethers","result","ethers","scoreObj"]}
1
+ {"version":3,"sources":["../src/plugins.ts","../src/relay-feedback.ts"],"sourcesContent":["// src/plugins.ts\n// RelAI Plugin System - extensible middleware hooks for Relai.protect()\n\nimport crypto from 'crypto';\nimport { ethers } from 'ethers';\nimport type { RelaiNetwork } from './types';\nimport type { SettleResult } from './server';\n\nconst RELAI_API_BASE = 'https://api.relai.fi';\n\n// ============================================================================\n// Plugin Interface\n// ============================================================================\n\nexport interface PluginContext {\n /** Network for this endpoint */\n network: RelaiNetwork;\n /** Price in USD */\n price: number;\n /** Request path */\n path: string;\n /** HTTP method */\n method: string;\n}\n\nexport interface PluginResult {\n /** If true, skip payment and serve content for free */\n skip?: boolean;\n /** If true, reject the request entirely (e.g. service unhealthy) */\n reject?: boolean;\n /** HTTP status code when rejecting (default: 503) */\n rejectStatus?: number;\n /** Error message when rejecting */\n rejectMessage?: string;\n /** Extra response headers to set */\n headers?: Record<string, string>;\n /** Metadata attached to req.pluginMeta */\n meta?: Record<string, unknown>;\n}\n\nexport interface RelaiPlugin {\n /** Unique plugin name */\n name: string;\n\n /**\n * Called before the 402 payment check.\n * Return { skip: true } to bypass payment entirely.\n */\n beforePaymentCheck?(req: any, ctx: PluginContext): Promise<PluginResult>;\n\n /**\n * Called after a successful payment settlement.\n * Use for analytics, logging, webhooks, etc.\n */\n afterSettled?(req: any, result: SettleResult, ctx: PluginContext): Promise<void>;\n\n /**\n * Called once when the Relai instance initializes (server start).\n * Use to sync config to RelAI backend or validate credentials.\n */\n onInit?(): Promise<void>;\n\n /**\n * Called before sending the 402 response. Allows plugins to add\n * extensions or modify the response body (e.g. bridge info).\n */\n enrich402Response?(response: any, ctx: PluginContext): any;\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\nexport interface FreeTierPluginConfig {\n /** Service key (sk_live_...) for syncing with RelAI backend. If omitted, runs in local in-memory mode. */\n serviceKey?: string;\n /** Max free calls per buyer per period */\n perBuyerLimit: number;\n /** Reset period for per-buyer counters */\n resetPeriod?: 'none' | 'daily' | 'monthly';\n /** Optional global cap across all buyers */\n globalCap?: number;\n /** Specific paths to apply free tier to (default: '*' = all) */\n paths?: string[];\n /** Override RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Cache TTL in ms for check results (default: 5000) */\n cacheTtlMs?: number;\n}\n\ninterface FreeTierCheckResponse {\n free: boolean;\n remaining?: number;\n total?: number;\n reason?: string;\n globalRemaining?: number;\n}\n\n/**\n * Free Tier plugin - gives buyers a number of free API calls\n * before requiring payment.\n *\n * State is stored in the RelAI backend, keyed by your service key.\n * Config can be set here (SDK-side) or overridden in the relai.fi dashboard.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { freeTier } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * freeTier({\n * serviceKey: process.env.RELAI_SERVICE_KEY!,\n * perBuyerLimit: 10,\n * resetPeriod: 'daily',\n * }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\n// ============================================================================\n// Bridge Plugin\n// ============================================================================\n\nexport interface BridgePluginConfig {\n /** Service key (sk_live_...) for tracking bridge usage per API owner */\n serviceKey?: string;\n /** RelAI API base URL (default: https://api.relai.fi) */\n baseUrl?: string;\n /** Override settle endpoint (auto-discovered from /bridge/info if not set) */\n settleEndpoint?: string;\n /** Override supported source chains (auto-discovered if not set) */\n supportedSourceChains?: string[];\n /** Override supported source assets (auto-discovered if not set) */\n supportedSourceAssets?: string[];\n /** Override bridge payTo map: { [caip2]: address } */\n payTo?: Record<string, string>;\n /** Override Solana fee payer address (auto-discovered if not set) */\n feePayerSvm?: string;\n /** Override payment facilitator URL */\n paymentFacilitator?: string;\n /** Bridge fee in basis points (default: auto-discovered) */\n feeBps?: number;\n}\n\ninterface BridgeInfo {\n settleEndpoint: string;\n supportedSourceChains: string[];\n supportedSourceAssets: string[];\n payTo: Record<string, string>;\n feePayerSvm: string | null;\n feeBps: number;\n paymentFacilitator: string;\n}\n\n/**\n * Bridge plugin - enables cross-chain payments via the RelAI bridge.\n *\n * When a buyer's wallet is on a different chain than the merchant accepts,\n * the client SDK can automatically route the payment through the bridge.\n * This plugin adds `extensions.bridge` to the 402 response with all the\n * info the client needs to execute a cross-chain payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { bridge } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'skale-base',\n * plugins: [\n * bridge(), // auto-discovers from https://api.relai.fi\n * ],\n * });\n *\n * // Buyer on Solana can now pay for a SKALE endpoint\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.05,\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\nexport function bridge(config?: BridgePluginConfig): RelaiPlugin {\n const base = (config?.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n let bridgeInfo: BridgeInfo | null = null;\n\n // Hash service key for tracking (never expose raw key to clients)\n // Must match backend: crypto.createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16)\n let serviceKeyHash: string | null = null;\n if (config?.serviceKey) {\n serviceKeyHash = crypto.createHash('sha256').update(config.serviceKey).digest('hex').slice(0, 16);\n }\n\n async function fetchBridgeInfo(): Promise<BridgeInfo | null> {\n try {\n const res = await fetch(`${base}/bridge/info`);\n if (!res.ok) {\n console.warn(`[relai:bridge] Failed to fetch /bridge/info: ${res.status}`);\n return null;\n }\n const data = await res.json() as any;\n return {\n settleEndpoint: config?.settleEndpoint || data.settleEndpoint,\n supportedSourceChains: config?.supportedSourceChains || data.supportedSourceChains || [],\n supportedSourceAssets: config?.supportedSourceAssets || data.supportedSourceAssets || [],\n payTo: config?.payTo || data.payTo || {},\n feePayerSvm: config?.feePayerSvm ?? data.feePayerSvm ?? null,\n feeBps: config?.feeBps ?? data.feeBps ?? 100,\n paymentFacilitator: config?.paymentFacilitator || data.paymentFacilitator || 'https://facilitator.x402.fi',\n };\n } catch (err) {\n console.warn(`[relai:bridge] Failed to fetch bridge info: ${err}`);\n return null;\n }\n }\n\n return {\n name: 'bridge',\n\n async onInit() {\n bridgeInfo = await fetchBridgeInfo();\n if (bridgeInfo) {\n console.log(`[relai:bridge] Initialized — ${bridgeInfo.supportedSourceChains.length} source chains, settle: ${bridgeInfo.settleEndpoint}`);\n } else {\n console.warn('[relai:bridge] Bridge info not available — cross-chain payments disabled');\n }\n },\n\n enrich402Response(response: any, ctx: PluginContext) {\n if (!bridgeInfo || bridgeInfo.supportedSourceChains.length === 0) {\n return response;\n }\n\n // Don't add bridge extension if merchant's network is already a source chain\n // and there's only that one chain — no bridging needed\n const merchantCaip2 = response?.accepts?.[0]?.network;\n\n // Filter out the merchant's own chain from source chains\n // (buyer on same chain should pay directly, not bridge)\n const otherSourceChains = bridgeInfo.supportedSourceChains.filter(\n (c: string) => c !== merchantCaip2,\n );\n\n if (otherSourceChains.length === 0) {\n return response;\n }\n\n // Find the payTo for the first available source chain (Solana-first for UX)\n const solanaChain = otherSourceChains.find((c: string) => c.startsWith('solana:'));\n const primaryPayTo = solanaChain\n ? bridgeInfo.payTo[solanaChain]\n : bridgeInfo.payTo[otherSourceChains[0]];\n\n response.extensions = response.extensions || {};\n response.extensions.bridge = {\n info: {\n settleEndpoint: bridgeInfo.settleEndpoint,\n supportedSourceChains: otherSourceChains,\n supportedSourceAssets: bridgeInfo.supportedSourceAssets,\n payTo: primaryPayTo || null,\n payToMap: bridgeInfo.payTo,\n feePayerSvm: bridgeInfo.feePayerSvm,\n feeBps: bridgeInfo.feeBps,\n paymentFacilitator: bridgeInfo.paymentFacilitator,\n ...(serviceKeyHash ? { serviceKeyHash } : {}),\n },\n };\n\n return response;\n },\n };\n}\n\n// ============================================================================\n// Free Tier Plugin\n// ============================================================================\n\n/**\n * Extended plugin interface with data export for free tier.\n */\nexport interface FreeTierPlugin extends RelaiPlugin {\n /** Export all in-memory usage data (local mode only). Returns null when using cloud mode. */\n getUsageData(): FreeTierUsageExport | null;\n /** Whether plugin is running in local (in-memory) or cloud (RelAI backend) mode. */\n readonly mode: 'local' | 'cloud';\n}\n\nexport interface FreeTierUsageExport {\n mode: 'local';\n config: {\n perBuyerLimit: number;\n resetPeriod: string;\n globalCap: number | null;\n paths: string[];\n };\n /** Per-buyer usage entries */\n usage: Array<{\n buyerId: string;\n path: string;\n count: number;\n periodStart: string;\n lastCall: string;\n }>;\n globalCount: number;\n exportedAt: string;\n}\n\nexport function freeTier(config: FreeTierPluginConfig): FreeTierPlugin {\n const base = (config.baseUrl ?? RELAI_API_BASE).replace(/\\/$/, '');\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const isLocal = !config.serviceKey;\n const resetPeriod = config.resetPeriod ?? 'none';\n const limit = config.perBuyerLimit;\n const globalCap = config.globalCap ?? null;\n const pluginPaths = config.paths ?? ['*'];\n\n /**\n * Resolve buyer identity from the request.\n * Priority: JWT sub > x-wallet-address > IP fallback\n */\n function resolveBuyerId(req: any): string {\n // 1. JWT Bearer token\n try {\n const auth = req.headers?.authorization || '';\n if (auth.startsWith('Bearer ')) {\n const token = auth.slice(7).trim();\n const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());\n if (payload?.sub) return `user:${payload.sub}`;\n }\n } catch { /* ignore */ }\n\n // 2. Explicit wallet header\n const wallet = req.headers?.['x-wallet-address'] || req.headers?.['x-buyer-address'];\n if (wallet) return `wallet:${wallet}`;\n\n // 3. IP fallback\n const ip =\n (req.headers?.['x-forwarded-for'] || '').split(',')[0].trim() ||\n req.socket?.remoteAddress ||\n req.ip ||\n 'unknown';\n return `ip:${ip}`;\n }\n\n function pathMatches(requestPath: string): boolean {\n return pluginPaths.includes('*') || pluginPaths.some((p) => {\n const normalized = p.toLowerCase().replace(/\\/+$/, '') || '/';\n const reqNormalized = requestPath.toLowerCase().replace(/\\/+$/, '') || '/';\n return normalized === reqNormalized || normalized === '*';\n });\n }\n\n // ---------------------------------------------------------------------------\n // LOCAL IN-MEMORY MODE\n // ---------------------------------------------------------------------------\n\n interface LocalUsageEntry {\n count: number;\n periodStart: string;\n lastCall: string;\n }\n\n // Map key: \"path:buyerId\"\n const localUsage = new Map<string, LocalUsageEntry>();\n let localGlobalCount = 0;\n\n function currentPeriodStart(): string {\n const now = new Date();\n if (resetPeriod === 'daily') {\n return new Date(now.getFullYear(), now.getMonth(), now.getDate()).toISOString();\n }\n if (resetPeriod === 'monthly') {\n return new Date(now.getFullYear(), now.getMonth(), 1).toISOString();\n }\n return 'none'; // permanent\n }\n\n function isCurrentPeriod(entry: LocalUsageEntry): boolean {\n if (resetPeriod === 'none') return true;\n return entry.periodStart === currentPeriodStart();\n }\n\n function localKey(path: string, buyerId: string): string {\n return `${path}:${buyerId}`;\n }\n\n function localCheck(path: string, buyerId: string): FreeTierCheckResponse {\n const key = localKey(path, buyerId);\n const entry = localUsage.get(key);\n\n // If entry is from a previous period, it's stale — treat as 0\n const count = (entry && isCurrentPeriod(entry)) ? entry.count : 0;\n const remaining = Math.max(0, limit - count);\n\n // Global cap check\n if (globalCap != null && localGlobalCount >= globalCap) {\n return { free: false, remaining: 0, total: limit, reason: 'global_cap_reached', globalRemaining: 0 };\n }\n\n if (count >= limit) {\n return { free: false, remaining: 0, total: limit, reason: 'limit_reached' };\n }\n\n return {\n free: true,\n remaining: remaining - 1, // after this call\n total: limit,\n ...(globalCap != null ? { globalRemaining: globalCap - localGlobalCount - 1 } : {}),\n };\n }\n\n function localRecord(path: string, buyerId: string): void {\n const key = localKey(path, buyerId);\n const period = currentPeriodStart();\n const entry = localUsage.get(key);\n const now = new Date().toISOString();\n\n if (entry && isCurrentPeriod(entry)) {\n entry.count++;\n entry.lastCall = now;\n } else {\n localUsage.set(key, { count: 1, periodStart: period, lastCall: now });\n }\n localGlobalCount++;\n }\n\n function exportLocalData(): FreeTierUsageExport {\n const usage: FreeTierUsageExport['usage'] = [];\n for (const [key, entry] of localUsage.entries()) {\n // key format is \"path:buyerId\" but buyerId itself can contain ':'\n // We stored it as `${path}:${buyerId}` where path starts with '/'\n // So we find the first ':' after the path portion\n const firstSlash = key.indexOf('/');\n const colonAfterPath = key.indexOf(':', firstSlash > -1 ? firstSlash : 0);\n const path = colonAfterPath > -1 ? key.slice(0, colonAfterPath) : key;\n const buyerId = colonAfterPath > -1 ? key.slice(colonAfterPath + 1) : 'unknown';\n usage.push({\n buyerId,\n path,\n count: entry.count,\n periodStart: entry.periodStart,\n lastCall: entry.lastCall,\n });\n }\n return {\n mode: 'local',\n config: { perBuyerLimit: limit, resetPeriod, globalCap, paths: pluginPaths },\n usage,\n globalCount: localGlobalCount,\n exportedAt: new Date().toISOString(),\n };\n }\n\n // ---------------------------------------------------------------------------\n // CLOUD MODE (original — requires serviceKey)\n // ---------------------------------------------------------------------------\n\n // Simple in-memory cache: \"serviceKey:path:buyerId\" -> { result, expiresAt }\n const cache = new Map<string, { result: FreeTierCheckResponse; expiresAt: number }>();\n\n function resolveHeaders(): Record<string, string> {\n return {\n 'X-Service-Key': config.serviceKey!,\n 'Content-Type': 'application/json',\n };\n }\n\n function cloudCacheKey(path: string, buyerId: string): string {\n return `${config.serviceKey}:${path}:${buyerId}`;\n }\n\n async function checkFreeTier(path: string, buyerId: string): Promise<FreeTierCheckResponse> {\n const key = cloudCacheKey(path, buyerId);\n const now = Date.now();\n const cached = cache.get(key);\n if (cached && cached.expiresAt > now) {\n return cached.result;\n }\n\n try {\n const res = await fetch(`${base}/v1/plugins/free-tier/check`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n\n if (!res.ok) {\n // Non-blocking: if API unreachable, default to not-free\n return { free: false, reason: `api_error_${res.status}` };\n }\n\n const result = await res.json() as FreeTierCheckResponse;\n cache.set(key, { result, expiresAt: now + cacheTtl });\n return result;\n } catch (err) {\n // Network error - non-blocking, default to paid\n return { free: false, reason: 'network_error' };\n }\n }\n\n async function recordCall(path: string, buyerId: string): Promise<void> {\n try {\n await fetch(`${base}/v1/plugins/free-tier/record`, {\n method: 'POST',\n headers: resolveHeaders(),\n body: JSON.stringify({ path, buyerId }),\n });\n // Invalidate cache for this buyer+path after recording\n cache.delete(cloudCacheKey(path, buyerId));\n } catch {\n // Fire-and-forget\n }\n }\n\n async function syncConfig(): Promise<void> {\n try {\n // Check if config already exists on backend — don't overwrite dashboard-managed configs\n const existing = await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'GET',\n headers: resolveHeaders(),\n });\n if (existing.ok) {\n const data = await existing.json();\n if (data.configs && data.configs.length > 0) {\n return; // Config managed via dashboard or previous sync — skip\n }\n }\n\n await fetch(`${base}/v1/plugins/free-tier/config`, {\n method: 'PUT',\n headers: resolveHeaders(),\n body: JSON.stringify({\n perBuyerLimit: config.perBuyerLimit,\n resetPeriod: config.resetPeriod ?? 'none',\n globalCap: config.globalCap ?? null,\n paths: pluginPaths,\n }),\n });\n } catch (err) {\n console.warn(`[relai:freeTier] Failed to sync config to RelAI: ${err}`);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Plugin instance\n // ---------------------------------------------------------------------------\n\n return {\n name: 'free-tier',\n mode: isLocal ? 'local' : 'cloud',\n\n getUsageData(): FreeTierUsageExport | null {\n if (!isLocal) return null;\n return exportLocalData();\n },\n\n async onInit() {\n if (isLocal) {\n console.log(`[relai:freeTier] Running in local mode (in-memory). perBuyerLimit=${limit}, resetPeriod=${resetPeriod}`);\n return;\n }\n await syncConfig();\n },\n\n async beforePaymentCheck(req, ctx) {\n const requestPath = ctx.path || '/';\n if (!pathMatches(requestPath)) return {};\n\n const buyerId = resolveBuyerId(req);\n\n // ── Local mode ──\n if (isLocal) {\n const result = localCheck(requestPath, buyerId);\n if (result.free) {\n localRecord(requestPath, buyerId);\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? limit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n 'X-Free-Tier-Mode': 'local',\n },\n meta: { freeTier: true, buyerId, remaining: result.remaining, total: result.total ?? limit, mode: 'local' },\n };\n }\n return {};\n }\n\n // ── Cloud mode ──\n const result = await checkFreeTier(requestPath, buyerId);\n\n if (result.free) {\n // Record the call (fire-and-forget)\n recordCall(requestPath, buyerId).catch(() => {});\n\n return {\n skip: true,\n headers: {\n 'X-Free-Calls-Remaining': String(result.remaining ?? 0),\n 'X-Free-Calls-Total': String(result.total ?? config.perBuyerLimit),\n ...(result.globalRemaining != null\n ? { 'X-Free-Calls-Global-Remaining': String(result.globalRemaining) }\n : {}),\n },\n meta: {\n freeTier: true,\n buyerId,\n remaining: result.remaining,\n total: result.total ?? config.perBuyerLimit,\n },\n };\n }\n\n return {};\n },\n };\n}\n\n// ============================================================================\n// Shield Plugin\n// ============================================================================\n\nexport interface ShieldPluginConfig {\n /**\n * Custom health check function. Return true if the service is healthy.\n * Takes priority over `healthUrl` if both are provided.\n */\n healthCheck?: () => Promise<boolean> | boolean;\n /**\n * URL to probe. A 2xx response means healthy.\n * Ignored if `healthCheck` is provided.\n */\n healthUrl?: string;\n /** Timeout in ms for the health probe (default: 5000) */\n timeoutMs?: number;\n /** Cache the health result for this many ms (default: 10000) */\n cacheTtlMs?: number;\n /** Message returned to the caller when unhealthy (default: 'Service temporarily unavailable. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when unhealthy (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Shield plugin — protects buyers from paying for unhealthy endpoints.\n *\n * Before the server returns a 402, Shield runs a health check (custom\n * function or URL probe). If the service is down, the request is rejected\n * with 503 instead of asking for payment.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { shield } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * shield({\n * healthUrl: 'https://my-api.com/health',\n * timeoutMs: 3000,\n * }),\n * ],\n * });\n *\n * // If /health is down, buyers get 503 instead of 402\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function shield(config?: ShieldPluginConfig): RelaiPlugin {\n const timeoutMs = config?.timeoutMs ?? 5000;\n const cacheTtl = config?.cacheTtlMs ?? 10000;\n const unhealthyMessage = config?.unhealthyMessage ?? 'Service temporarily unavailable. Please try again later.';\n const unhealthyStatus = config?.unhealthyStatus ?? 503;\n\n let cached: { healthy: boolean; expiresAt: number } | null = null;\n\n async function checkHealth(): Promise<boolean> {\n const now = Date.now();\n if (cached && cached.expiresAt > now) {\n return cached.healthy;\n }\n\n let healthy = true;\n\n try {\n if (config?.healthCheck) {\n healthy = await Promise.resolve(config.healthCheck());\n } else if (config?.healthUrl) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetch(config.healthUrl, {\n method: 'GET',\n signal: controller.signal,\n });\n healthy = res.ok;\n } finally {\n clearTimeout(timer);\n }\n }\n // No healthCheck and no healthUrl → always healthy (no-op mode)\n } catch {\n healthy = false;\n }\n\n cached = { healthy, expiresAt: now + cacheTtl };\n return healthy;\n }\n\n return {\n name: 'shield',\n\n async onInit() {\n const healthy = await checkHealth();\n console.log(`[relai:shield] Initial health check: ${healthy ? 'healthy ✓' : 'UNHEALTHY ✗'}`);\n },\n\n async beforePaymentCheck(_req, _ctx): Promise<PluginResult> {\n const healthy = await checkHealth();\n\n if (!healthy) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Shield-Status': 'unhealthy',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Shield-Status': 'healthy' },\n };\n },\n };\n}\n\n// ============================================================================\n// Preflight Plugin\n// ============================================================================\n\nexport interface PreflightPluginConfig {\n /**\n * Base URL of the API server (e.g. 'https://my-api.com').\n * The plugin appends the request path to form the probe URL.\n * **Required** — the plugin needs to know where to send the probe.\n */\n baseUrl: string;\n /** Timeout in ms for the preflight probe (default: 3000) */\n timeoutMs?: number;\n /** Cache the probe result for this many ms (default: 5000) */\n cacheTtlMs?: number;\n /** Custom unhealthy message (default: 'Endpoint not responding. Please try again later.') */\n unhealthyMessage?: string;\n /** HTTP status code when endpoint unreachable (default: 503) */\n unhealthyStatus?: number;\n}\n\n/**\n * Preflight plugin — verifies the specific endpoint responds before payment.\n *\n * Unlike Shield (global health check), Preflight probes the **actual endpoint**\n * the buyer is requesting. It sends a HEAD request with `X-Preflight: true` —\n * the Relai middleware responds 200 instantly without triggering payment.\n * If the endpoint doesn't respond, the buyer gets 503 instead of 402.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { preflight } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * preflight({ baseUrl: 'https://my-api.com' }),\n * ],\n * });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01,\n * }), (req, res) => {\n * res.json({ data: 'premium content' });\n * });\n * ```\n */\nexport function preflight(config: PreflightPluginConfig): RelaiPlugin {\n const baseUrl = config.baseUrl.replace(/\\/+$/, '');\n const timeoutMs = config.timeoutMs ?? 3000;\n const cacheTtl = config.cacheTtlMs ?? 5000;\n const unhealthyMessage = config.unhealthyMessage ?? 'Endpoint not responding. Please try again later.';\n const unhealthyStatus = config.unhealthyStatus ?? 503;\n\n const cache = new Map<string, { ok: boolean; expiresAt: number }>();\n\n async function probeEndpoint(path: string): Promise<boolean> {\n const now = Date.now();\n const cached = cache.get(path);\n if (cached && cached.expiresAt > now) {\n return cached.ok;\n }\n\n let ok = false;\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n const res = await fetch(url, {\n method: 'HEAD',\n headers: { 'X-Preflight': 'true' },\n signal: controller.signal,\n });\n ok = res.ok;\n } finally {\n clearTimeout(timer);\n }\n } catch {\n ok = false;\n }\n\n cache.set(path, { ok, expiresAt: now + cacheTtl });\n return ok;\n }\n\n return {\n name: 'preflight',\n\n async onInit() {\n // Probe the base URL on startup\n const ok = await probeEndpoint('/');\n console.log(`[relai:preflight] Initial probe ${baseUrl}: ${ok ? 'reachable ✓' : 'UNREACHABLE ✗'}`);\n },\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const path = ctx.path || '/';\n const ok = await probeEndpoint(path);\n\n if (!ok) {\n return {\n reject: true,\n rejectStatus: unhealthyStatus,\n rejectMessage: unhealthyMessage,\n headers: {\n 'X-Preflight-Status': 'unreachable',\n 'Retry-After': String(Math.ceil(cacheTtl / 1000)),\n },\n };\n }\n\n return {\n headers: { 'X-Preflight-Status': 'ok' },\n };\n },\n };\n}\n\n\n// ============================================================================\n// Circuit Breaker Plugin\n// ============================================================================\n\nexport type CircuitState = 'closed' | 'open' | 'half-open';\n\nexport interface CircuitBreakerPluginConfig {\n /**\n * Number of consecutive failures before the circuit opens.\n * Default: 5\n */\n failureThreshold?: number;\n /**\n * Time in ms the circuit stays open before moving to half-open.\n * Default: 30000 (30 seconds)\n */\n resetTimeMs?: number;\n /**\n * Number of successful requests in half-open state before closing the circuit.\n * Default: 2\n */\n halfOpenSuccesses?: number;\n /** HTTP status code when circuit is open (default: 503) */\n openStatus?: number;\n /** Error message when circuit is open */\n openMessage?: string;\n /**\n * HTTP status codes considered as failures.\n * Default: [500, 502, 503, 504]\n */\n failureCodes?: number[];\n /**\n * Track failures globally or per-path.\n * Default: 'per-path'\n */\n scope?: 'global' | 'per-path';\n}\n\ninterface CircuitEntry {\n state: CircuitState;\n failures: number;\n successes: number;\n lastFailureAt: number;\n}\n\n/**\n * Circuit Breaker plugin — tracks failure history and opens the circuit\n * after repeated failures, preventing buyers from paying for broken endpoints.\n *\n * Unlike Shield/Preflight (real-time probes), Circuit Breaker is **zero-latency** —\n * it makes no additional HTTP requests. Instead, it tracks `afterSettled` outcomes\n * and rejects requests when the failure rate exceeds the threshold.\n *\n * States:\n * - **closed** — normal operation, requests flow through\n * - **open** — too many failures, requests are rejected with 503\n * - **half-open** — after resetTime, allows a few test requests through\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { circuitBreaker } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * circuitBreaker({ failureThreshold: 5, resetTimeMs: 30000 }),\n * ],\n * });\n * ```\n */\nexport function circuitBreaker(config?: CircuitBreakerPluginConfig): RelaiPlugin {\n const failureThreshold = config?.failureThreshold ?? 5;\n const resetTimeMs = config?.resetTimeMs ?? 30000;\n const halfOpenSuccesses = config?.halfOpenSuccesses ?? 2;\n const openStatus = config?.openStatus ?? 503;\n const openMessage = config?.openMessage ?? 'Service temporarily unavailable (circuit open). Please try again later.';\n const failureCodes = new Set(config?.failureCodes ?? [500, 502, 503, 504]);\n const scope = config?.scope ?? 'per-path';\n\n const circuits = new Map<string, CircuitEntry>();\n\n function getKey(path: string): string {\n return scope === 'global' ? '__global__' : path;\n }\n\n function getCircuit(key: string): CircuitEntry {\n let entry = circuits.get(key);\n if (!entry) {\n entry = { state: 'closed', failures: 0, successes: 0, lastFailureAt: 0 };\n circuits.set(key, entry);\n }\n return entry;\n }\n\n function recordFailure(key: string): void {\n const circuit = getCircuit(key);\n circuit.failures++;\n circuit.successes = 0;\n circuit.lastFailureAt = Date.now();\n\n if (circuit.failures >= failureThreshold) {\n circuit.state = 'open';\n }\n }\n\n function recordSuccess(key: string): void {\n const circuit = getCircuit(key);\n if (circuit.state === 'half-open') {\n circuit.successes++;\n if (circuit.successes >= halfOpenSuccesses) {\n // Enough successes in half-open → close the circuit\n circuit.state = 'closed';\n circuit.failures = 0;\n circuit.successes = 0;\n }\n } else {\n // Reset failures on success in closed state\n circuit.failures = 0;\n circuit.successes = 0;\n }\n }\n\n return {\n name: 'circuit-breaker',\n\n async beforePaymentCheck(_req, ctx): Promise<PluginResult> {\n const key = getKey(ctx.path || '/');\n const circuit = getCircuit(key);\n const now = Date.now();\n\n // Check if open circuit should transition to half-open\n if (circuit.state === 'open' && (now - circuit.lastFailureAt) >= resetTimeMs) {\n circuit.state = 'half-open';\n circuit.successes = 0;\n }\n\n if (circuit.state === 'open') {\n const retryAfter = Math.max(1, Math.ceil((resetTimeMs - (now - circuit.lastFailureAt)) / 1000));\n return {\n reject: true,\n rejectStatus: openStatus,\n rejectMessage: openMessage,\n headers: {\n 'X-Circuit-State': 'open',\n 'X-Circuit-Failures': String(circuit.failures),\n 'Retry-After': String(retryAfter),\n },\n };\n }\n\n return {\n headers: {\n 'X-Circuit-State': circuit.state,\n ...(circuit.state === 'half-open' ? { 'X-Circuit-Successes': String(circuit.successes) } : {}),\n },\n };\n },\n\n async afterSettled(_req, result, ctx): Promise<void> {\n const key = getKey(ctx.path || '/');\n\n // Check if the settlement itself failed or returned error codes\n if (!result.success) {\n recordFailure(key);\n return;\n }\n\n // Check response status if available\n const status = (result as any).statusCode || (result as any).status;\n if (status && failureCodes.has(status)) {\n recordFailure(key);\n } else {\n recordSuccess(key);\n }\n },\n };\n}\n\n\n// ============================================================================\n// Refund Plugin\n// ============================================================================\n\nexport interface RefundPluginConfig {\n /**\n * HTTP status codes that trigger a refund/credit after payment.\n * Default: [500, 502, 503, 504]\n */\n triggerCodes?: number[];\n /**\n * What to do when a refund is triggered.\n * - 'credit': record a credit that the free-tier plugin can consume later\n * - 'log': just log the event (useful with a custom onRefund callback)\n * Default: 'credit'\n */\n mode?: 'credit' | 'log';\n /**\n * Called whenever a refund event is triggered.\n * Use for external refund processing, logging, or notifications.\n */\n onRefund?: (event: RefundEvent) => void | Promise<void>;\n /**\n * Also trigger refund when settlement itself fails (result.success === false).\n * Default: true\n */\n refundOnSettlementFailure?: boolean;\n}\n\nexport interface RefundEvent {\n /** Buyer wallet address */\n payer: string;\n /** Transaction ID of the original payment */\n transactionId: string;\n /** Network used */\n network: string;\n /** Amount paid in USD */\n amount: number;\n /** Request path */\n path: string;\n /** Reason for the refund */\n reason: 'endpoint_error' | 'settlement_failure' | 'timeout';\n /** HTTP status code that triggered the refund (if applicable) */\n statusCode?: number;\n /** Timestamp */\n timestamp: number;\n}\n\n/**\n * Refund plugin — automatically handles refund/credit when a paid request fails.\n *\n * After payment settles, if the endpoint returns an error (e.g. 500),\n * the plugin records a credit or calls your custom `onRefund` handler.\n * Credits can be consumed by the free-tier plugin on the next request.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { refund } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * refund({\n * triggerCodes: [500, 502, 503],\n * onRefund: (event) => {\n * console.log(`Refund for ${event.payer}: $${event.amount} on ${event.path}`);\n * // Notify buyer, record in DB, etc.\n * },\n * }),\n * ],\n * });\n * ```\n */\nexport function refund(config?: RefundPluginConfig): RelaiPlugin {\n const triggerCodes = new Set(config?.triggerCodes ?? [500, 502, 503, 504]);\n const mode = config?.mode ?? 'credit';\n const onRefund = config?.onRefund;\n const refundOnSettlementFailure = config?.refundOnSettlementFailure ?? true;\n\n // In-memory credit ledger: payer → number of credits\n const credits = new Map<string, number>();\n\n return {\n name: 'refund',\n\n async beforePaymentCheck(req, ctx): Promise<PluginResult> {\n // If buyer has credits from previous refunds, skip payment\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.headers?.['authorization']?.replace(/^Bearer\\s+/i, '') ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n\n const buyerCredits = credits.get(buyerId) || 0;\n if (buyerCredits > 0) {\n credits.set(buyerId, buyerCredits - 1);\n return {\n skip: true,\n headers: {\n 'X-Refund-Credit': 'applied',\n 'X-Refund-Credits-Remaining': String(buyerCredits - 1),\n },\n meta: {\n refundCredit: true,\n creditsRemaining: buyerCredits - 1,\n },\n };\n }\n }\n\n return {};\n },\n\n async afterSettled(req, result, ctx): Promise<void> {\n const payer = result.payer || 'unknown';\n const transactionId = result.transaction || '';\n\n // Settlement failure\n if (!result.success && refundOnSettlementFailure) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'settlement_failure',\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n return;\n }\n\n // Check for endpoint error codes in settlement result\n const status = (result as any).statusCode || (result as any).status;\n if (status && triggerCodes.has(status)) {\n const event: RefundEvent = {\n payer,\n transactionId,\n network: ctx.network,\n amount: ctx.price,\n path: ctx.path,\n reason: 'endpoint_error',\n statusCode: status,\n timestamp: Date.now(),\n };\n\n if (mode === 'credit') {\n const buyerId =\n req.headers?.['x-buyer-id'] ||\n req.ip ||\n req.socket?.remoteAddress ||\n 'unknown';\n credits.set(buyerId, (credits.get(buyerId) || 0) + 1);\n }\n\n try {\n await onRefund?.(event);\n } catch (err) {\n console.warn('[relai:refund] onRefund callback error:', err);\n }\n }\n },\n };\n}\n\n// Re-export for backwards-compatible import path '@relai-fi/x402/plugins'\nexport { submitRelayFeedback, type RelayFeedbackConfig } from './relay-feedback';\n\n// ============================================================================\n// Score Plugin\n// ============================================================================\n\nexport interface ScorePluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n * Find yours in the RelAI dashboard under \"On-chain Identity\".\n */\n agentId: string | number;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha\n */\n rpcUrl?: string;\n /**\n * ERC-8004 IdentityRegistry contract address.\n * Default: process.env.ERC8004_IDENTITY_REGISTRY\n */\n identityRegistryAddress?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\ninterface CachedScore {\n score: Record<string, unknown>;\n expiresAt: number;\n}\n\nconst SCORE_IDENTITY_ABI = [\n 'function ownerOf(uint256 tokenId) external view returns (address)',\n];\n\nconst SCORE_FEEDBACK_TOPIC = ethers.id(\n 'NewFeedback(uint256,address,uint64,int128,uint8,string,string,string,string,string,bytes32)'\n);\n\nconst SCORE_FEEDBACK_IFACE = new ethers.Interface([\n 'event NewFeedback(uint256 indexed agentId, address indexed giver, uint64 index, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, string responseURI, bytes32 feedbackHash)',\n]);\n\nconst SCORE_BATCH = 1900;\nconst SCORE_HISTORY = 10000;\n\n/**\n * Score plugin — fetches ERC-8004 on-chain reputation for your API\n * directly via RPC and injects it into the 402 response `extensions.score`.\n *\n * Agents can read the score **before paying**, enabling trust-based routing:\n * - `feedbackCount` — number of on-chain feedback entries\n * - `successRate` — 0–100 (% of successful calls)\n * - `avgResponseMs` — average response time in milliseconds\n * - `verified` — true if agentId is registered on-chain\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * ],\n * });\n * ```\n */\nexport function score(config: ScorePluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:score] ERC8004_AGENT_ID not set — score plugin is a no-op');\n return { name: 'score', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n let cached: CachedScore | null = null;\n\n function getContracts() {\n const identityAddress = config.identityRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_IDENTITY_REGISTRY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n if (!identityAddress || !reputationAddress) return null;\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const identity = new ethers.Contract(identityAddress, SCORE_IDENTITY_ABI, provider);\n return { provider, identity, reputationAddress };\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && cached.expiresAt > Date.now()) {\n return cached.score;\n }\n\n const contracts = getContracts();\n if (!contracts) {\n console.warn(`[relai:score] ERC8004_IDENTITY_REGISTRY or ERC8004_REPUTATION_REGISTRY not set — score disabled`);\n return null;\n }\n\n const { provider, identity, reputationAddress } = contracts;\n const bigId = BigInt(agentId);\n\n try {\n let verified = false;\n try {\n const owner = await identity.ownerOf(bigId);\n verified = !!owner;\n } catch {\n // Not registered\n }\n\n if (!verified) {\n const scoreObj = { feedbackCount: 0, successRate: null, avgResponseMs: null, verified: false, agentId, source: 'erc8004-skale' };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n }\n\n const latest = await provider.getBlockNumber();\n const agentIdTopic = ethers.zeroPadValue(ethers.toBeHex(bigId), 32);\n const logs: any[] = [];\n\n for (let from = Math.max(0, latest - SCORE_HISTORY); from <= latest; from += SCORE_BATCH) {\n const to = Math.min(from + SCORE_BATCH - 1, latest);\n const chunk = await provider.getLogs({\n address: reputationAddress,\n topics: [SCORE_FEEDBACK_TOPIC, agentIdTopic],\n fromBlock: from,\n toBlock: to,\n });\n logs.push(...chunk);\n }\n\n const srValues: number[] = [];\n const rtValues: number[] = [];\n const endpointMap: Record<string, { sr: number[]; rt: number[] }> = {};\n\n for (const log of logs) {\n try {\n const p = SCORE_FEEDBACK_IFACE.parseLog({ topics: log.topics as string[], data: log.data });\n if (!p) continue;\n const val = Number(p.args.value) / Math.pow(10, Number(p.args.valueDecimals));\n const ep: string = p.args.endpoint || '';\n if (!endpointMap[ep]) endpointMap[ep] = { sr: [], rt: [] };\n if (p.args.tag1 === 'successRate') { srValues.push(val); endpointMap[ep].sr.push(val); }\n else if (p.args.tag1 === 'responseTime') { rtValues.push(val); endpointMap[ep].rt.push(val); }\n } catch { /* skip */ }\n }\n\n const feedbackCount = srValues.length + rtValues.length;\n const successRate = srValues.length\n ? Math.round(srValues.reduce((a, b) => a + b, 0) / srValues.length)\n : null;\n const avgResponseMs = rtValues.length\n ? Math.round(rtValues.reduce((a, b) => a + b, 0) / rtValues.length)\n : null;\n\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n for (const [ep, { sr, rt }] of Object.entries(endpointMap)) {\n endpoints[ep] = {\n feedbackCount: sr.length + rt.length,\n successRate: sr.length ? Math.round(sr.reduce((a, b) => a + b, 0) / sr.length) : null,\n avgResponseMs: rt.length ? Math.round(rt.reduce((a, b) => a + b, 0) / rt.length) : null,\n };\n }\n\n const scoreObj = { feedbackCount, successRate, avgResponseMs, verified: true, agentId, source: 'erc8004-skale', endpoints };\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err) {\n console.warn(`[relai:score] fetchScore RPC error for agentId=${agentId}:`, err);\n return null;\n }\n }\n\n return {\n name: 'score',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:score] Initialized — agentId=${agentId}, feedback=${s.feedbackCount}, successRate=${s.successRate}%, avgResp=${s.avgResponseMs}ms`);\n } else {\n console.warn(`[relai:score] Could not load score for agentId=${agentId} — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any, _ctx: PluginContext) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n score: scoreData,\n },\n };\n },\n };\n}\n\n// ============================================================================\n// Feedback Plugin\n// ============================================================================\n\nexport interface FeedbackPluginConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) for this API.\n */\n agentId: string | number;\n /**\n * Private key of the wallet that signs feedback transactions.\n * The wallet must hold CREDIT tokens on SKALE Base to pay gas.\n * Default: process.env.BACKEND_WALLET_PRIVATE_KEY\n */\n walletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n /**\n * Tag2 label for all feedback entries (default: 'x402').\n */\n tag2?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n * Value: 1 (success) or 0 (failure).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n * Value: milliseconds elapsed since request start.\n * Requires req.x402StartTime (set automatically if using Relai.protect()).\n */\n submitResponseTime?: boolean;\n}\n\nconst FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Feedback plugin — submits ERC-8004 on-chain reputation after every successful x402 settlement.\n *\n * Records `successRate` and `responseTime` signals to the ReputationRegistry on SKALE Base.\n * This is the mechanism that **builds** the score that `score()` plugin later reads.\n *\n * Runs fire-and-forget — never blocks the response.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { score, feedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'base',\n * plugins: [\n * score({ agentId: '5' }),\n * feedback({ agentId: '5' }), // needs BACKEND_WALLET_PRIVATE_KEY in env\n * ],\n * });\n * ```\n */\nexport function feedback(config: FeedbackPluginConfig): RelaiPlugin {\n const rawAgentId = config.agentId\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_AGENT_ID : undefined);\n\n if (!rawAgentId || String(rawAgentId) === 'undefined') {\n console.warn('[relai:feedback] ERC8004_AGENT_ID not set — feedback plugin is a no-op');\n return { name: 'feedback', async onInit() {} };\n }\n\n const agentId = String(rawAgentId);\n const tag2 = config.tag2 ?? 'x402';\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n let _reputation: any = null;\n\n function getReputation() {\n if (_reputation) return _reputation;\n\n const privateKey = config.walletPrivateKey\n ?? (typeof process !== 'undefined' ? process.env?.BACKEND_WALLET_PRIVATE_KEY : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) return null;\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n _reputation = new ethers.Contract(reputationAddress, FEEDBACK_REPUTATION_ABI, signer);\n return _reputation;\n }\n\n function submitAsync(tag1: string, value: number, valueDecimals: number, endpoint: string) {\n const reputation = getReputation();\n if (!reputation) return;\n\n (async () => {\n try {\n const tx = await reputation.giveFeedback(\n BigInt(agentId),\n BigInt(Math.round(value)),\n valueDecimals,\n tag1,\n tag2,\n endpoint,\n '',\n ethers.ZeroHash,\n );\n console.log(`[relai:feedback] Submitted agentId=${agentId} tag=${tag1} value=${value} tx=${tx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:feedback] giveFeedback failed (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'feedback',\n\n async onInit() {\n const reputation = getReputation();\n if (reputation) {\n console.log(`[relai:feedback] Initialized — agentId=${agentId}, submitSuccessRate=${submitSuccessRate}, submitResponseTime=${submitResponseTime}`);\n } else {\n console.warn(`[relai:feedback] BACKEND_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — feedback disabled`);\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n\n // successRate: 1 = success, 0 = failure\n if (submitSuccessRate) {\n submitAsync('successRate', result.success ? 1 : 0, 0, endpoint);\n }\n\n // responseTime in ms — use req.x402StartTime if available\n if (submitResponseTime) {\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n if (responseTimeMs > 0) {\n submitAsync('responseTime', responseTimeMs, 0, endpoint);\n }\n }\n },\n };\n}\n\n// ============================================================================\n// Solana Feedback Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaFeedbackPluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Private key of the feedback wallet — base58 or JSON array format.\n * Should differ from the owner's registration wallet (avoids self-feedback restriction).\n * Default: process.env.SOLANA_8004_FEEDBACK_KEY ?? process.env.SOLANA_8004_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Whether to submit successRate feedback (default: true).\n */\n submitSuccessRate?: boolean;\n /**\n * Whether to submit responseTime feedback (default: true).\n */\n submitResponseTime?: boolean;\n}\n\n/**\n * Solana Feedback plugin — submits 8004-solana on-chain reputation after x402 settlement.\n *\n * Uses the native Solana `8004-solana` program (MPL Core NFT registry),\n * separate from the SKALE EVM ReputationRegistry used by `feedback()`.\n *\n * Requires `8004-solana` package to be installed:\n * ```\n * npm install 8004-solana\n * ```\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaFeedback } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaFeedback({\n * assetPubkey: process.env.SOLANA_AGENT_ASSET!, // e.g. 'GH93tGR8...'\n * }),\n * ],\n * });\n * ```\n */\nexport function solanaFeedback(config: SolanaFeedbackPluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const submitSuccessRate = config.submitSuccessRate ?? true;\n const submitResponseTime = config.submitResponseTime ?? true;\n\n function getPrivateKey(): string | null {\n return config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.SOLANA_8004_FEEDBACK_KEY ?? process.env?.SOLANA_8004_PRIVATE_KEY\n : undefined)\n ?? null;\n }\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function buildSDK(): Promise<any> {\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n const privateKeyRaw = getPrivateKey();\n if (!privateKeyRaw) return null;\n\n let secretKey: Uint8Array;\n try {\n const parsed = JSON.parse(privateKeyRaw);\n if (Array.isArray(parsed)) {\n secretKey = Uint8Array.from(parsed);\n } else {\n throw new Error('not array');\n }\n } catch {\n // base58\n try {\n const bs58Mod = await import('bs58' as any);\n const decode = bs58Mod.default?.decode ?? bs58Mod.decode;\n secretKey = decode(privateKeyRaw);\n } catch {\n console.warn('[relai:solanaFeedback] Could not parse feedbackWalletPrivateKey');\n return null;\n }\n }\n\n const { Keypair } = await import('@solana/web3.js');\n const signer = Keypair.fromSecretKey(secretKey);\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n\n return new SolanaSDK({ cluster, signer, ...(rpcUrl ? { rpcUrl } : {}) });\n }\n\n function submitAsync(isSuccess: boolean, responseTimeMs: number, endpoint: string) {\n (async () => {\n try {\n const sdk = await buildSDK();\n if (!sdk) return;\n\n const { PublicKey } = await import('@solana/web3.js');\n const pubkey = new PublicKey(assetPubkey);\n\n if (submitSuccessRate) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(isSuccess ? 10000 : 0),\n tag1: 'successRate',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] successRate submitted asset=${assetPubkey.slice(0, 8)}... tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n }\n\n if (submitResponseTime && responseTimeMs > 0) {\n try {\n const result = await sdk.giveFeedback(pubkey, {\n value: String(Math.round(responseTimeMs)),\n tag1: 'responseTime',\n tag2: 'x402',\n ...(endpoint ? { endpoint } : {}),\n });\n console.log(`[relai:solanaFeedback] responseTime submitted asset=${assetPubkey.slice(0, 8)}... ms=${responseTimeMs} tx=${result.signature}`);\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n } catch (err: any) {\n console.warn(`[relai:solanaFeedback] feedback error (non-fatal): ${err?.message}`);\n }\n })();\n }\n\n return {\n name: 'solanaFeedback',\n\n async onInit() {\n const privateKey = getPrivateKey();\n if (!privateKey) {\n console.warn('[relai:solanaFeedback] SOLANA_8004_FEEDBACK_KEY not set — Solana feedback disabled');\n return;\n }\n try {\n await import('8004-solana' as any);\n console.log(`[relai:solanaFeedback] Initialized — asset=${assetPubkey.slice(0, 8)}... cluster=${getCluster()}`);\n } catch {\n console.warn('[relai:solanaFeedback] 8004-solana package not installed — run: npm install 8004-solana');\n }\n },\n\n async afterSettled(req: any, result: SettleResult, ctx: PluginContext) {\n const endpoint = ctx.path ?? '';\n const startTime = req?.x402StartTime ?? req?.x402StartedAt;\n const responseTimeMs = startTime ? Date.now() - Number(startTime) : 0;\n\n submitAsync(result.success, responseTimeMs, endpoint);\n },\n };\n}\n\n// ============================================================================\n// Solana Score Plugin (8004-solana)\n// ============================================================================\n\nexport interface SolanaScorePluginConfig {\n /**\n * Solana MPL Core NFT public key (base58) of the registered agent.\n * This is the `solanaAgentAsset` stored after calling /api/solana8004/register.\n */\n assetPubkey: string;\n /**\n * Solana cluster to use.\n * Default: process.env.SOLANA_8004_CLUSTER ?? 'mainnet-beta'\n */\n cluster?: 'mainnet-beta' | 'devnet';\n /**\n * Custom Solana RPC URL (Helius / QuickNode recommended).\n * Default: process.env.SOLANA_8004_RPC_URL\n */\n rpcUrl?: string;\n /**\n * Cache TTL in ms (default: 5 minutes).\n */\n cacheTtlMs?: number;\n}\n\n/**\n * Solana Score plugin — fetches 8004-solana on-chain reputation and injects it\n * into the 402 response `extensions.score` before the client pays.\n *\n * Mirrors the EVM `score()` plugin but reads from the native Solana program\n * instead of the SKALE EVM ReputationRegistry.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n * import { solanaScore } from '@relai-fi/x402/plugins';\n *\n * const relai = new Relai({\n * network: 'solana',\n * plugins: [\n * solanaScore({ assetPubkey: process.env.SOLANA_AGENT_ASSET! }),\n * ],\n * });\n * ```\n */\nexport function solanaScore(config: SolanaScorePluginConfig): RelaiPlugin {\n const assetPubkey = config.assetPubkey;\n const cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1000;\n\n if (!assetPubkey || String(assetPubkey) === 'undefined') {\n console.warn('[relai:solanaScore] assetPubkey not set — solanaScore plugin is a no-op');\n return { name: 'solanaScore', async onInit() {} };\n }\n\n let cached: CachedScore | null = null;\n\n function getCluster(): string {\n return config.cluster\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_CLUSTER : undefined)\n ?? 'mainnet-beta';\n }\n\n function getRpcUrl(): string | undefined {\n return config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.SOLANA_8004_RPC_URL : undefined);\n }\n\n async function fetchScore(): Promise<Record<string, unknown> | null> {\n if (cached && Date.now() < cached.expiresAt) return cached.score;\n\n let SolanaSDK: any;\n try {\n const mod = await import('8004-solana' as any);\n SolanaSDK = mod.SolanaSDK;\n } catch {\n console.warn('[relai:solanaScore] 8004-solana package not installed — run: npm install 8004-solana');\n return null;\n }\n\n try {\n const { PublicKey } = await import('@solana/web3.js');\n const cluster = getCluster();\n const rpcUrl = getRpcUrl();\n const sdk = new SolanaSDK({ cluster, ...(rpcUrl ? { rpcUrl } : {}) });\n const pubkey = new PublicKey(assetPubkey);\n\n // Try getReputationSummary first (lightweight)\n let feedbackCount = 0;\n let successRate: number | null = null;\n let avgResponseMs: number | null = null;\n const endpoints: Record<string, { feedbackCount: number; successRate: number | null; avgResponseMs: number | null }> = {};\n\n // Try to read all feedback for per-endpoint breakdown\n let allFeedbacks: any[] = [];\n try {\n allFeedbacks = await sdk.readAllFeedback(pubkey);\n } catch {\n // fallback to summary only\n }\n\n if (allFeedbacks.length > 0) {\n // Group by endpoint\n const byEndpoint: Record<string, { successValues: number[]; responseTimes: number[] }> = {};\n let globalSuccessValues: number[] = [];\n let globalResponseTimes: number[] = [];\n\n for (const entry of allFeedbacks) {\n const tag1: string = entry.tag1 ?? '';\n const rawValue = entry.value ?? entry.score ?? 0;\n const value = typeof rawValue === 'bigint' ? Number(rawValue) : Number(rawValue);\n const ep: string = entry.endpoint ?? '';\n\n if (!byEndpoint[ep]) byEndpoint[ep] = { successValues: [], responseTimes: [] };\n\n if (tag1 === 'successRate') {\n globalSuccessValues.push(value);\n byEndpoint[ep].successValues.push(value);\n } else if (tag1 === 'responseTime') {\n globalResponseTimes.push(value);\n byEndpoint[ep].responseTimes.push(value);\n }\n }\n\n feedbackCount = globalSuccessValues.length;\n if (feedbackCount > 0) {\n const avgSuccess = globalSuccessValues.reduce((a, b) => a + b, 0) / feedbackCount;\n // value=10000 means success (100%), value=0 means failure\n successRate = Math.round((avgSuccess / 10000) * 100);\n }\n if (globalResponseTimes.length > 0) {\n avgResponseMs = Math.round(globalResponseTimes.reduce((a, b) => a + b, 0) / globalResponseTimes.length);\n }\n\n for (const [ep, data] of Object.entries(byEndpoint)) {\n const epCount = data.successValues.length;\n const epAvgSuccess = epCount > 0\n ? data.successValues.reduce((a, b) => a + b, 0) / epCount\n : null;\n const epAvgMs = data.responseTimes.length > 0\n ? Math.round(data.responseTimes.reduce((a, b) => a + b, 0) / data.responseTimes.length)\n : null;\n endpoints[ep || '/'] = {\n feedbackCount: epCount,\n successRate: epAvgSuccess !== null ? Math.round((epAvgSuccess / 10000) * 100) : null,\n avgResponseMs: epAvgMs,\n };\n }\n } else {\n // Fallback to summary\n try {\n const summary = await sdk.getReputationSummary(pubkey);\n feedbackCount = summary.count ?? 0;\n if (feedbackCount > 0) {\n const avg = summary.averageScore ?? 0;\n successRate = Math.round((avg / 10000) * 100);\n }\n } catch {\n // no data\n }\n }\n\n const scoreObj = {\n feedbackCount,\n successRate,\n avgResponseMs,\n assetPubkey,\n source: '8004-solana',\n cluster,\n ...(Object.keys(endpoints).length > 0 ? { endpoints } : {}),\n };\n\n cached = { score: scoreObj, expiresAt: Date.now() + cacheTtlMs };\n return scoreObj;\n } catch (err: any) {\n console.warn(`[relai:solanaScore] fetch error (non-fatal): ${err?.message}`);\n return null;\n }\n }\n\n return {\n name: 'solanaScore',\n\n async onInit() {\n const s = await fetchScore();\n if (s) {\n console.log(`[relai:solanaScore] Initialized — asset=${assetPubkey.slice(0, 8)}... feedbackCount=${(s as any).feedbackCount} cluster=${getCluster()}`);\n } else {\n console.warn(`[relai:solanaScore] Could not load score for asset=${assetPubkey.slice(0, 8)}... — score will be omitted from 402 responses`);\n }\n },\n\n enrich402Response(response: any) {\n const scoreData = cached?.score ?? null;\n if (!scoreData) return response;\n return {\n ...response,\n extensions: {\n ...(response.extensions ?? {}),\n solanaScore: scoreData,\n },\n };\n },\n };\n}\n","// src/relay-feedback.ts\n// Standalone utility for submitting ERC-8004 feedback about third-party APIs.\n// Not a plugin — call directly from your relay/aggregator application code.\n\nimport { ethers } from 'ethers';\n\nexport interface RelayFeedbackConfig {\n /**\n * ERC-8004 agentId (NFT tokenId) of the **target API** you are calling.\n */\n agentId: string | number;\n /**\n * Whether the API call succeeded.\n */\n success: boolean;\n /**\n * Elapsed milliseconds for the API call.\n */\n responseTimeMs?: number;\n /**\n * Endpoint path of the called API (e.g. '/v1/data').\n */\n endpoint?: string;\n /**\n * Private key of the **relay/third-party** wallet that signs feedback.\n * MUST be different from the API owner's wallet — ReputationRegistry\n * may restrict self-feedback. Wallet needs CREDIT tokens on SKALE Base.\n * Default: process.env.FEEDBACK_WALLET_PRIVATE_KEY\n */\n feedbackWalletPrivateKey?: string;\n /**\n * SKALE Base Sepolia RPC URL.\n * Default: process.env.ERC8004_RPC_URL or SKALE Base Sepolia public RPC.\n */\n rpcUrl?: string;\n /**\n * ERC-8004 ReputationRegistry contract address.\n * Default: process.env.ERC8004_REPUTATION_REGISTRY\n */\n reputationRegistryAddress?: string;\n}\n\nconst RELAY_FEEDBACK_REPUTATION_ABI = [\n 'function giveFeedback(uint256 agentId, int128 value, uint8 valueDecimals, string tag1, string tag2, string endpoint, string feedbackURI, bytes32 feedbackHash) external',\n];\n\n/**\n * Submit ERC-8004 on-chain feedback about a **third-party API** you called.\n *\n * Call this fire-and-forget from your relay/aggregator after every external API call.\n * Uses a separate relay wallet (not the API owner's key) to avoid self-feedback restrictions.\n *\n * Records:\n * - `successRate`: 10000 (= 100%) on success, 0 on failure — 2 decimal places\n * - `responseTime`: elapsed milliseconds\n *\n * @example\n * ```typescript\n * import { submitRelayFeedback } from '@relai-fi/x402/relay-feedback';\n *\n * // after calling an external API:\n * const start = Date.now();\n * const result = await fetch('https://other-api.com/data');\n * submitRelayFeedback({\n * agentId: '5',\n * success: result.ok,\n * responseTimeMs: Date.now() - start,\n * endpoint: '/data',\n * });\n * ```\n */\nexport function submitRelayFeedback(config: RelayFeedbackConfig): void {\n const agentId = String(config.agentId);\n const endpoint = config.endpoint ?? '';\n const responseTimeMs = config.responseTimeMs ?? 0;\n\n const privateKey = config.feedbackWalletPrivateKey\n ?? (typeof process !== 'undefined'\n ? process.env?.FEEDBACK_WALLET_PRIVATE_KEY ?? process.env?.ERC8004_FEEDBACK_WALLET_PRIVATE_KEY\n : undefined);\n const reputationAddress = config.reputationRegistryAddress\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_REPUTATION_REGISTRY : undefined);\n const rpcUrl = config.rpcUrl\n ?? (typeof process !== 'undefined' ? process.env?.ERC8004_RPC_URL : undefined)\n ?? 'https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha';\n\n if (!privateKey || !reputationAddress) {\n console.warn('[relai:submitRelayFeedback] FEEDBACK_WALLET_PRIVATE_KEY or ERC8004_REPUTATION_REGISTRY not set — skipping');\n return;\n }\n\n const provider = new ethers.JsonRpcProvider(rpcUrl);\n const signer = new ethers.Wallet(privateKey, provider);\n const reputation = new ethers.Contract(reputationAddress, RELAY_FEEDBACK_REPUTATION_ABI, signer);\n const id = BigInt(agentId);\n\n (async () => {\n const successValue = config.success ? 10000n : 0n;\n try {\n const srTx = await reputation.giveFeedback(\n id, successValue, 2, 'successRate', '', endpoint, '', ethers.ZeroHash,\n );\n await srTx.wait();\n console.log(`[relai:submitRelayFeedback] successRate confirmed agentId=${agentId} success=${config.success}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] successRate failed (non-fatal): ${err?.message}`);\n }\n\n if (responseTimeMs > 0) {\n try {\n const rtTx = await reputation.giveFeedback(\n id, BigInt(Math.max(0, Math.round(responseTimeMs))), 0, 'responseTime', '', endpoint, '', ethers.ZeroHash,\n );\n console.log(`[relai:submitRelayFeedback] responseTime sent agentId=${agentId} ms=${responseTimeMs} tx=${rtTx.hash}`);\n } catch (err: any) {\n console.warn(`[relai:submitRelayFeedback] responseTime failed (non-fatal): ${err?.message}`);\n }\n }\n })();\n}\n"],"mappings":"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","names":["ethers","result","ethers","scoreObj"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relai-fi/x402",
3
- "version": "0.6.1-rc.4",
3
+ "version": "0.6.1",
4
4
  "description": "Unified x402 payment SDK for Solana, Base, Avalanche, SKALE Base, SKALE BITE, Polygon, and Ethereum. Automatic 402 handling with zero gas fees.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",