/** * x402 Hono middleware — reusable payment gate for rSpace modules. * * When X402_PAY_TO env is set, protects routes with x402 micro-transactions. * When not set, acts as a no-op passthrough. */ import type { Context, Next, MiddlewareHandler } from "hono"; export interface X402Config { payTo: string; network: string; amount: string; facilitatorUrl: string; resource?: string; description?: string; } /** * Create x402 payment middleware for Hono routes. * Returns null if X402_PAY_TO is not configured (disabled). */ export function createX402Middleware(config: X402Config): MiddlewareHandler { return async (c: Context, next: Next) => { const paymentHeader = c.req.header("X-PAYMENT"); if (!paymentHeader) { // Return 402 with payment requirements const requirements = { x402Version: 1, scheme: "exact", network: config.network, maxAmountRequired: config.amount, resource: config.resource || c.req.url, description: config.description || "Payment required for upload", payTo: config.payTo, maxTimeoutSeconds: 300, }; return c.json( { error: "Payment Required", paymentRequirements: requirements }, 402, { "X-PAYMENT-REQUIREMENTS": JSON.stringify(requirements) } ); } // Verify payment via facilitator try { const verifyRes = await fetch(`${config.facilitatorUrl}/verify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ payment: paymentHeader, requirements: { scheme: "exact", network: config.network, maxAmountRequired: config.amount, payTo: config.payTo, }, }), }); if (!verifyRes.ok) { const err = await verifyRes.text(); return c.json({ error: "Payment verification failed", details: err }, 402); } const result = await verifyRes.json() as { valid?: boolean }; if (!result.valid) { return c.json({ error: "Payment invalid or insufficient" }, 402); } // Payment valid — store tx info for downstream handlers c.set("x402Payment", paymentHeader); await next(); } catch (e) { console.error("[x402] Verification error:", e); return c.json({ error: "Payment verification service unavailable" }, 503); } }; } /** * Initialize x402 from environment variables. * Returns middleware or null if disabled. */ export function setupX402FromEnv(overrides?: Partial): MiddlewareHandler | null { const payTo = process.env.X402_PAY_TO; if (!payTo) { console.log("[x402] Disabled — X402_PAY_TO not set"); return null; } const config: X402Config = { payTo, network: process.env.X402_NETWORK || "eip155:84532", amount: process.env.X402_UPLOAD_PRICE || "0.01", facilitatorUrl: process.env.X402_FACILITATOR_URL || "https://x402.org/facilitator", ...overrides, }; console.log(`[x402] Enabled — payTo=${payTo}, network=${config.network}, amount=${config.amount}`); return createX402Middleware(config); }