// A Hono x402 v2 service (@x402/hono) wrapped by the Forge SDK. Runs on Node via @hono/node-server;
// on Bun or Cloudflare Workers, `export default app` instead of calling serve(). Forge lines are marked (forge).
import { serve } from "@hono/node-server";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
import { Hono } from "hono";
import { createForge } from "@forgeintel/sdk/hono"; // (forge)
const required = (name: string) => {
const value = process.env[name];
if (!value) throw new Error(`Set ${name}`);
return value;
};
const PAY_TO = required("PAY_TO");
const NETWORK = (process.env.NETWORK ?? "eip155:84532") as `${string}:${string}`; // Base Sepolia; eip155:8453 for mainnet
const FACILITATOR_URL = process.env.FACILITATOR_URL ?? "https://x402.org/facilitator";
// (forge) PUBLIC_URL is this service's public origin; it's used in the rating URLs agents see.
const forge = createForge({
apiKey: required("FORGE_API_KEY"),
backendUrl: required("FORGE_BACKEND_URL"),
publicUrl: required("PUBLIC_URL"),
});
const resourceServer = new x402ResourceServer(new HTTPFacilitatorClient({ url: FACILITATOR_URL })).register(NETWORK, new ExactEvmScheme());
const app = new Hono();
app.use(forge.middleware()); // (forge) before payment middleware
app.use(
paymentMiddleware(
{
"POST /api/text-stats": {
accepts: { scheme: "exact", price: "$0.001", network: NETWORK, payTo: PAY_TO },
description: "Count characters, words and sentences in a text and estimate reading time.",
mimeType: "application/json",
},
},
resourceServer,
),
);
app.get("/health", (c) => c.json({ status: "ok" }));
app.post("/api/text-stats", async (c) => {
const body = await c.req.json().catch(() => ({}));
const text = typeof body?.text === "string" ? body.text : "";
if (!text) return c.json({ error: "text is required" }, 400);
const words = text.split(/\s+/).filter(Boolean).length;
return c.json({
characters: text.length,
words,
sentences: text.split(/[.!?]+/).filter((s: string) => s.trim()).length,
reading_time_seconds: Math.ceil((words / 238) * 60),
});
});
const port = Number(process.env.PORT ?? 4025);
const server = serve({ fetch: app.fetch, port }, () => console.log(`Hono example on http://localhost:${port}`));
process.on("SIGTERM", () => server.close(() => void forge.shutdown())); // (forge) flush pending events