branch:
server.ts
2618 bytesRaw
import { Hono } from "hono";
import { Agent, callable, routeAgentRequest } from "agents";
import { wrapFetchWithPayment } from "@x402/fetch";
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
import { x402Client } from "@x402/core/client";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { registerExactEvmScheme as registerClientEvmScheme } from "@x402/evm/exact/client";
import { registerExactEvmScheme as registerServerEvmScheme } from "@x402/evm/exact/server";
import { toClientEvmSigner } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

export class PayAgent extends Agent<Env> {
  fetchWithPay?: ReturnType<typeof wrapFetchWithPayment>;

  onStart() {
    const pk = process.env.CLIENT_TEST_PK;
    if (!pk) {
      console.warn(
        "CLIENT_TEST_PK not set — copy .env.example to .env and add a test private key"
      );
      return;
    }

    const account = privateKeyToAccount(pk as `0x${string}`);
    console.log("Agent will pay from:", account.address);

    const client = new x402Client();
    registerClientEvmScheme(client, { signer: toClientEvmSigner(account) });
    this.fetchWithPay = wrapFetchWithPayment(fetch, client);
  }

  @callable()
  async fetchProtectedRoute() {
    if (!this.fetchWithPay) {
      return {
        text: "Agent not ready — CLIENT_TEST_PK not configured",
        isError: true
      };
    }

    const paidUrl = "http://localhost:5173/protected-route";
    const res = await this.fetchWithPay(paidUrl, {});
    const data = await res.json();

    return {
      text: JSON.stringify(data, null, 2),
      isError: !res.ok
    };
  }
}

const app = new Hono<{ Bindings: Env }>();

const facilitatorClient = new HTTPFacilitatorClient({
  url: "https://x402.org/facilitator"
});
const resourceServer = new x402ResourceServer(facilitatorClient);
registerServerEvmScheme(resourceServer);

app.use(
  paymentMiddleware(
    {
      "GET /protected-route": {
        accepts: [
          {
            scheme: "exact",
            price: "$0.10",
            network: "eip155:84532",
            payTo: process.env.SERVER_ADDRESS as `0x${string}`
          }
        ],
        description: "Access to premium content",
        mimeType: "application/json"
      }
    },
    resourceServer
  )
);

app.get("/protected-route", (c) => {
  return c.json({
    message: "This content is behind a paywall. Thanks for paying!"
  });
});

app.all("/agents/*", async (c) => {
  const res = await routeAgentRequest(c.req.raw, c.env);
  return res || c.json({ error: "Not found" }, 404);
});

export default app;