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

An honest walk‑through of the architecture, trade‑offs, and code that actually runs.


1. Why “earn while you sleep” is a misnomer

When you hear “autonomous AI agent that makes money”, the mental picture is often a black‑box that prints cash with zero oversight. In reality, an autonomous agent is just a deterministic (or mildly stochastic) program that:

  1. Receives a request – usually via an HTTP endpoint or a message queue.
  2. Performs a computation – LLM inference, data lookup, or a simple rule‑based transformation.
  3. Returns a result – and, if the caller has prepaid, releases the result only after payment verification.

The “earning” part comes from charging for each successful invocation in a stablecoin (USDC) on a low‑fee L2 like Base. The agent itself does not create value; it merely exposes a service that others are willing to pay for. Understanding this keeps expectations realistic and helps you focus on the engineering concerns that actually matter: latency, reliability, cost, and security.


2. High‑level architecture

+----------------+      +----------------+      +----------------+
|   Client (Web) | ---> |   API Gateway  | ---> |   Agent Worker |
+----------------+      +----------------+      +----------------+
                                 |                     |
                                 v                     v
                        +--------------+      +-----------------+
                        |  Payment Verifier (x402) |<--|  USDC Escrow (Base) |
                        +--------------+      +-----------------+
                                 |
                                 v
                        +-----------------+
                        |  Result Store   |
                        +-----------------+
Enter fullscreen mode Exit fullscreen mode
  • API Gateway – a thin Cloudflare Worker that does routing, rate‑limiting, and forwards the request to the worker pool.
  • Agent Worker – another Cloudflare Worker (or a Durable Object) that loads the model, runs inference, and returns the answer.
  • Payment Verifier – a tiny contract‑like service that checks the x402 payment header, confirms the USDC transfer on Base, and unlocks the worker.
  • Result Store – optional KV or D1 cache for idempotent retries; not required for stateless agents.

All components run on Cloudflare’s edge network, which gives sub‑50 ms latency for most users and eliminates the need to manage VMs.


3. Choosing the model size & inference backend

The biggest cost driver is GPU time. I experimented with three options:

Option Approx. latency (95th) Cost per 1k tokens (USDC) Trade‑off
tiny‑llama‑1.1B (CPU‑only, ONNX) 120 ms $0.0004 Good for trivial classification; struggles with nuanced prompts.
mistral‑7b‑instruct (GPU via Workers AI) 350 ms $0.0012 Balanced quality & cost; still occasional hallucinations.
llama‑3‑8b‑instruct (GPU via Workers AI) 620 ms $0.0025 Highest quality; latency may breach SLA for interactive apps.

I settled on mistral‑7b‑instruct because the agent’s primary use case is short‑form summarization (< 200 tokens). The extra 230 ms latency over the tiny model is acceptable given the quality jump, and the per‑invocation cost stays well under the $0.01‑$0.10 range we target for paid calls.

Honest note: If you need sub‑100 ms latency, you must either shrink the model further or accept a higher error rate. There is no free lunch.


4. Payment flow with x402

x402 is a simple HTTP‑based payment protocol: the client includes an X402-Payment header that contains a signed claim (ECDSA over Secp256k1) referencing a USDC transfer on Base. The verifier checks:

  1. The claim’s signature matches the payer’s public key.
  2. The transferred amount meets the price set in the agent’s metadata.
  3. The nonce hasn’t been reused (prevents replay).

Below is the verifier I deployed as a Cloudflare Worker. It uses the @x402/verify helper (a tiny wrapper around viem for Base RPC calls).

// payment-verifier.ts
import { verifyPayment } from '@x402/verify';
import { env } from 'cloudflare:workers';

const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // Base USDC
const PRICE_USDC = 0.02; // $0.02 per call

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const paymentHeader = request.headers.get('X402-Payment');
    if (!paymentHeader) {
      return new Response('Missing X402-Payment header', { status: 402 });
    }

    try {
      const { valid, payer, amount } = await verifyPayment({
        payment: paymentHeader,
        token: USDC_ADDRESS,
        chainId: 8453, // Base
        expectedAmount: PRICE_USDC,
      });

      if (!valid || amount < PRICE_USDC) {
        return new Response('Insufficient or invalid payment', { status: 402 });
      }

      // Attach payer info for downstream logging
      request.headers.set('X-Payer', payer);
      // Forward to the actual agent worker
      return env.AGENT.fetch(request);
    } catch (e) {
      console.error('Payment verification failed:', e);
      return new Response('Payment error', { status: 500 });
    }
  },
};
Enter fullscreen mode Exit fullscreen mode

Trade‑offs:

  • Pros: No on‑chain contract deployment; verification is cheap (~0.0002 USDC) and can be done at the edge.
  • Cons: Relies on a public RPC endpoint (I used Alchemy’s Base tier). If the RPC is down, payments fail even though the agent is healthy. Mitigation: keep a fallback RPC or cache recent successful verification nonces for a short window.

5. The agent worker – inference & idempotency

The worker loads the model once (using Cloudflare Workers AI’s built‑in model hub) and reuses the instance across requests. Cold starts add ~200 ms; after that, latency is dominated by GPU compute.

// agent-worker.ts
import { env } from 'cloudflare:workers';

const MODEL = '@hf/mistral/mistral-7b-instruct-v0.2';

// Simple in‑flight dedup cache to avoid double‑charging on retries
const dedupCache = new Map<string, { ts: number; result: string }>();
const DEDUP_TTL_MS = 60_000; // 1 minute

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const uid = request.headers.get('X-Request-ID') || crypto.randomUUID();
    const now = Date.now();

    // Idempotency check
    const cached = dedupCache.get(uid);
    if (cached && now - cached.ts < DEDUP_TTL_MS) {
      return new Response(cached.result, { headers: { 'X-Cache': 'HIT' } });
    }

    const { prompt } = await request.json();
    if (!prompt || typeof prompt !== 'string') {
      return new Response('Invalid payload: expecting {prompt:string}', { status: 400 });
    }

    try {
      const completion = await env.AI.run(MODEL, {
        prompt,
        max_tokens: 150,
        temperature: 0.7,
      });

      const output = completion.response as string;
      dedupCache.set(uid, { ts: now, result: output });
      // Optional: prune old entries (simple sweep every 100 reqs)
      if (dedupCache.size > 1000) {
        for (const [k, v] of dedupCache) {
          if (now - v.ts > DEDUP_TTL_MS) dedupCache.delete(k);
        }
      }

      return new Response(output, {
        headers: {
          'Content-Type': 'text/plain',
          'X-Request-ID': uid,
        },
      });
    } catch (err) {
      console.error('Inference error:', err);
      return new Response('Inference failed', { status: 500 });
    }
  },
};
Enter fullscreen mode Exit fullscreen mode

Key trade‑offs:

  • Model caching: Keeping the model in memory reduces per‑call compute but raises the worker’s memory footprint (~2 GB for Mistral‑7B). Cloudflare Workers AI abstracts this away, but if you self‑host on a VM you’d need to size the instance accordingly.
  • Idempotency: The dedup cache protects against retries caused by flaky networks, but it introduces a small stateful component. In a pure‑stateless design you’d rely on the client to send a unique idempotency key and store it in a durable KV store—more reliable but adds latency and cost.
  • Safety filtering: I omitted a moderation layer for brevity. In production you’d run a lightweight classifier (e.g., Perspective API) on the output before returning it, which adds ~30 ms and a modest fee.

6. Observability & cost tracking

Because revenue is tied to each successful call, you need granular metrics:

Metric Source Why it matters
Request count Cloudflare Workers analytics Directly maps to revenue.
Average latency Worker timings (event.waitUntil) Helps spot model loading bottlenecks or GPU throttling.

Top comments (0)