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

Target audience: developers who are experimenting with long‑running LLM‑driven services and want to see a concrete, production‑ish skeleton.


1. Why an “earning” agent?

The idea is simple: expose a set of deterministic, stateless functions (e.g., token‑level text summarisation, simple data enrichment, or a tiny classification model) behind a pay‑per‑call API that settles in USDC on the Base layer‑2. If the functions are useful enough that external callers are willing to pay a few cents, the agent can run continuously without a traditional SaaS billing backend.

The trade‑off is that you must handle:

  • Wallet custody – the agent needs a private key to sign USDC transfers.
  • Price discovery – you must decide a static price or implement a simple oracle.
  • Reliability – the agent must stay up, retry failed payments, and survive restarts.
  • Cost vs. revenue – compute (CPU, memory, network) must stay below the per‑call price, otherwise you lose money.

Below is a minimal, working implementation that satisfies those constraints while staying easy to audit.


2. High‑level architecture

+----------------+      +----------------+      +----------------+
|   Invoker (HTTP) | --> |   Cloudflare   | --> |   Agent Worker   |
|   (curl, postman) |    |   Workers (edge) |    |   (Node.js)      |
+----------------+      +----------------+      +----------------+
          ^                         ^                         ^
          |                         |                         |
   USDC payment (x402)    Verifies signature   Executes LLM task
          |                         |                         |
          v                         v                         v
+----------------+      +----------------+      +----------------+
|   Wallet (USDC) | <-- |   x402 Verifier | <-- |   Task Queue     |
+----------------+      +----------------+      +----------------+
Enter fullscreen mode Exit fullscreen mode
  • Cloudflare Workers act as a cheap, globally distributed entry point that enforces the x402 payment header before forwarding the request to the actual logic.
  • The Agent Worker (a long‑running Node.js process) receives the validated request, runs the LLM inference, and returns the result.
  • A task queue (here we use a simple in‑memory BullMQ backed by Redis) decouples payment verification from heavy compute, allowing retries if the model loads slowly.
  • The wallet holds a small USDC balance on Base; the agent never moves funds out of it—it only receives inbound payments.

3. Prerequisites

  • Node.js ≥ 20
  • A wallet with a small USDC balance on Base (you can fund via a faucet or a bridge).
  • Redis instance (local Docker or managed).
  • Access to an LLM inference endpoint (we’ll use a local Hugging Face Transformers model for demo; replace with your own API).

Install the core dependencies:

npm i @cloudflare/workers-types wrangler bullmq ioredis ethers dotenv
npm i -D typescript @types/node
Enter fullscreen mode Exit fullscreen mode

4. Wallet & USDC handling

We keep the private key in an environment variable (AGENT_PRIVATE_KEY). Never commit it.

// src/wallet.ts
import { ethers } from "ethers";

export const getWallet = () => {
  const pk = process.env.AGENT_PRIVATE_KEY;
  if (!pk) throw new Error("AGENT_PRIVATE_KEY not set");
  return new ethers.Wallet(pk);
};

// USDC contract on Base (address: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)
export const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
export const USDC_ABI = [
  "function balanceOf(address) view returns (uint256)",
  "function transfer(address to, uint256 amount) returns (bool)",
];
Enter fullscreen mode Exit fullscreen mode

The agent never initiates a transfer; it only reads its balance to display stats.


5. x402 payment verification in Cloudflare Workers

The worker checks for the X402-Payment header, validates the signature against the known USDC contract, and forwards the request if the amount meets the price we set.

// src/x402-verifier.ts
import { ethers } from "ethers";

const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const PRICE_USDC = ethers.parseUnits("0.05", 6); // $0.05 per call

export async function verifyPayment(request: Request): Promise<boolean> {
  const auth = request.headers.get("X402-Payment");
  if (!auth) return false;

  // Expected format: "usdc:<amount>:<signature>"
  const [, amountStr, signature] = auth.split(":");
  if (!amountStr || !signature) return false;

  const amount = ethers.parseUnits(amountStr, 6);
  if (amount < PRICE_USDC) return false;

  // Recover signer from signature over the request body
  const body = await request.clone().text();
  const messageHash = ethers.hashMessage(body);
  const recovered = ethers.recoverAddress(messageHash, signature);
  // The signer must match the agent's wallet address
  const walletAddress = (await getWallet()).address;
  return ethers.getAddress(recovered) === ethers.getAddress(walletAddress);
}
Enter fullscreen mode Exit fullscreen mode

Attach this to your Worker:

// src/index.ts
import { verifyPayment } from "./x402-verifier";

export default {
  async fetch(request, env, ctx): Promise<Response> {
    if (!(await verifyPayment(request))) {
      return new Response("Payment required or invalid", { status 402 });
    }
    // Forward to the agent service (could be another Worker or external URL)
    return fetch("https://agent-service.example.com/run", request);
  },
};
Enter fullscreen mode Exit fullscreen mode

Trade‑off: The verification adds ~2‑3 ms latency (mostly signature recovery). If you need sub‑millisecond response, you could move verification to a dedicated edge KV store that caches recent signatures, but that introduces a small replay‑attack surface you must mitigate with nonces.


6. Agent logic (LLM task)

For illustration we run a small‑parameter summarisation model (sshleifer/distilbart-cnn-12-6) via @xenova/transformers. In production you would swap this for a GPU‑accelerated endpoint (e.g., Replicate, Together.ai) and keep the worker thin.

// src/agent.ts
import { pipeline } from "@xenova/transformers";
import { Queue, Worker } from "bullmq";
import { IORedis } from "ioredis";

const redis = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
const taskQueue = new Queue("llm-tasks", { connection: });

// Load model once (cold start ~1‑2 s on a modest CPU)
let summarizer = null;
async function getSummarizer() {
  if (!summarizer) {
    summarizer = await pipeline("summarization", "Xenova/distilbart-cnn-12-6");
  }
  return summarizer;
}

// Worker processes queued jobs
new Worker(
  "llm-tasks",
  async (job) => {
    const { text } = job.data;
    const model = await getSummarizer();
    const result = await model(text, {
      max_length: 130,
      min_length: 30,
      do_sample: false,
    });
    return result[0].summary_text;
  },
  { connection: redis }
);

// HTTP endpoint that enqueues a job and waits for the result
export async function handleRun(request: Request): Promise<Response> {
  const { text } = await request.json();
  if (!typeof text === "string" || text.length === 0) {
    return new Response("Missing 'text' field", { status: 400 });
  }

  const job = await taskQueue.add("summarize", { text }, { attempts: 3 });
  const result = await job.waitUntilFinished(); // resolves when worker finishes
  return new Response(JSON.stringify({ summary: result.returnvalue }), {
    headers: { "Content-Type": "application/json" },
  });
}
Enter fullscreen mode Exit fullscreen mode

Trade‑offs:

  • Cold start: Loading the model takes ~1‑2 s on a CPU‑only instance. If you need sub‑second latency, pre‑warm the instance (keep a minimal ping) or move inference to a GPU‑enabled service and keep the worker as a thin proxy.
  • Cost: A small CPU instance (e.g., Cloudflare Workers Unbound or a cheap VPS) runs at ~$0.005/hr. At $0.05 per call you need ~10 calls/hr to break even; actual usage will vary.
  • Reliability: BullMQ retries failed jobs; the worker can be restarted without losing in‑flight tasks because they stay in Redis.

7. Deployment checklist

Step What to do Why
1️⃣ Store AGENT_PRIVATE_KEY and REDIS_URL in a secret manager (e.g., Cloudflare Workers Secrets, Docker env, or Vault). Prevent key leakage.
2️⃣ Deploy the x402 verifier Worker (wrangler publish). Edge entry point, cheap and globally distributed.
3️⃣ Spin up a small VM/Container (e.g

Top comments (0)