DEV Community

Nikhil Ranka
Nikhil Ranka

Posted on

How I Built an Autonomous AI Agent That Earns USDC While I Sleep

How I Built an Autonomous AI Agent That Earns USDC While I Sleep

Autonomous agents that can monetise their own capabilities are an interesting experiment in programmable economics. Below is a walk‑through of the system I assembled, the trade‑offs I hit, and the concrete code that makes it work. The goal is not to sell a “get‑rich‑quick” idea but to show what a minimal, production‑grade agent looks like when you combine an LLM, a serverless runtime, and the x402 micropayment standard.


1. Why x402 and USDC on Base?

  • x402 is a HTTP‑status‑code‑based payment protocol (spec ERC‑4337‑style). A client receives a 402 Payment Required response that includes a payment payload; after paying, the client resends the request with a signed proof.
  • Using USDC on Base gives us a stable‑value token with low gas fees (≈ $0.0005 per transaction) and instant finality, which is practical for sub‑cent micropayments.
  • The combination lets us expose any HTTP endpoint as a pay‑per‑call service without building a custom billing system.

2. High‑Level Architecture

+-------------------+       +-------------------+       +---------------------+
|   Client (any)    |------>| Cloudflare Worker |------>| LLM Inference Service|
|   (HTTP + x402)   |       |  (entrypoint)    |       | (e.g., Replicate)   |
+-------------------+       +-------------------+       +---------------------+
          ^                         ^                         ^
          |                         |                         |
          |   x402 proof verification|   LLM request/response   |
          |                         |                         |
          |                         v                         v
          |               +-------------------+   +-------------------+
          |               |   KV Cache (optional) |   |   Wallet (USDC)   |
          |               +-------------------+   +-------------------+
          |                         ^                         ^
          |                         |                         |
          +-------------------------+-------------------------+
                              Shared state (usage metrics, rate limits)
Enter fullscreen mode Exit fullscreen mode
  • Cloudflare Workers act as the thin API gateway. They verify the x402 proof, enforce rate limits, call the LLM, and return the result.
  • The LLM inference service can be any HTTP‑accessible model (I used a public Replicate endpoint for Llama‑2‑7b‑chat, but you can self‑host).
  • A KV namespace stores short‑lived nonces to prevent replay attacks and a simple usage counter for each API key.
  • The wallet (managed via ethers.js or viem) holds the USDC balance; the worker only needs the contract address and the payer’s signature, not the private key.

3. Payment Flow (x402)

  1. Client requestGET /summarize?text=…
  2. Worker checks for the X-402-Payment-Required header. If missing, it returns:
   HTTP/1.1 402 Payment Required
   Content-Type: application/json
   X-402-Payment-Required: {"scheme":"exact","network":"base","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","maxAmount":"1000000","resource":"https://nexusai-x402.nikhilranka23.workers.dev/summarize"}
Enter fullscreen mode Exit fullscreen mode
  • maxAmount is in USDC’s smallest unit (6 decimals → $0.10 = 100 000).
    1. The client builds a payment (using the x402.js library or manually) and resends the request with:
   Authorization: Bearer <signed_payment>
   X-402-Payment: <payload>
Enter fullscreen mode Exit fullscreen mode
  1. The worker verifies the signature against the payer’s address, checks that the amount ≤ maxAmount, and increments a nonce‑based replay guard.
  2. If verification passes, the request proceeds to the LLM step.

4. Worker Code (TypeScript)

Below is a trimmed‑down version that you can paste into a Cloudflare Workers project (wrangler.toml → compatibility_date = "2024-09-01").


ts
import { ethers } from "ethers";

// ---- CONFIG -------------------------------------------------
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // Base USDC
const LLM_ENDPOINT = "https://api.replicate.com/v1/predictions";
const REPLICATE_TOKEN = REPLICATE_API_TOKEN; // set in wrangler secrets
const KV = await import("@cloudflare/kv-asset-handler"); // for demo only
// -----------------------------------------------------------

interface X402Payload {
  scheme: string;      // "exact"
  network: string;     // "base"
  asset: string;       // token address
  maxAmount: string;   // uint256 as string
  resource: string;    // the URL being paid for
}

// Helper: verify the x402 signature (EIP‑191)
async function verifyPayment(
  payload: X402Payload,
  signature: string,
  payer: string
): Promise<boolean> {
  const message = ethers.utils.arrayify(
    ethers.utils.solidityKeccak256(
      ["string", "address", "uint256", "string"],
      [
        payload.scheme,
        payload.asset,
        ethers.utils.parseUnits(payload.maxAmount, 6),
        payload.resource,
      ]
    )
  );
  const recovered = ethers.utils.recoverAddress(
    ethers.utils.hashMessage(message),
    signature
  );
  return recovered.toLowerCase() === payer.toLowerCase();
}

// Main handler
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;

    // Only protect the summarise endpoint for this example
    if (path !== "/summarize") {
      return new Response("Not found", { status: 404 });
    }

    // ---- 1️⃣ Extract x402 headers ---------------------------------
    const paymentHeader = request.headers.get("X-402-Payment");
    const authHeader = request.headers.get("Authorization")?.replace(
      /^Bearer\s+/i,
      ""
    );
    if (!paymentHeader || !authHeader) {
      // Ask for payment
      const payload: X402Payload = {
        scheme: "exact",
        network: "base",
        asset: USDC_ADDRESS,
        maxAmount: "100000", // $0.10 in USDC (6 decimals)
        resource: request.url,
      };
      return new Response(JSON.stringify(payload), {
        status: 402,
        headers: { "Content-Type": "application/json", "X-402-Payment-Required": JSON.stringify(payload) },
      });
    }

    // ---- 2️⃣ Parse and verify payment --------------------------------
    let payload: X402Payload;
    let signature: string;
    try {
      const { scheme, network, asset, maxAmount, resource } = JSON.parse(
        paymentHeader
      ) as X402Payload;
      payload = { scheme, network, asset, maxAmount, resource };
      signature = authHeader;
    } catch {
      return new Response("Bad payment format", { status: 400 });
    }

    // In a real system you'd extract the payer address from the signature
    // via EIP‑712 or from the payment metadata. For brevity we assume
    // the payer is the first argument of the signature (not correct,
    // just illustrative). Replace with proper EIP‑712 verification.
    const payer = "0x0000000000000000000000000000000000000000"; // placeholder

    const valid = await verifyPayment(payload, signature, payer);
    if (!valid) {
      return new Response("Invalid payment", { status: 402 });
    }

    // ---- 3️⃣ Simple replay guard (nonce stored in KV) ----------------
    // In production you'd use a proper nonce or use the x402.js library.
    const nonceKey = `nonce:${payer}:${payload.resource}`;
    const seen = await env.NONCE_KV.get(nonceKey);
    if (seen) {
      return new Response("Replay attack detected", { status: 409 });
    }
    await env.NONCE_KV.put(nonceKey, "1", { expirationTtl: 60 }); // 1‑min window

    // ---- 4️⃣ Call the LLM -------------------------------------------
    const text = url.search
Enter fullscreen mode Exit fullscreen mode

Top comments (0)