rspace-online/shared/x402/hono-middleware.ts

105 lines
3.1 KiB
TypeScript

/**
* 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<X402Config>): 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);
}