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 want to put a paid AI service on‑chain and let it run unattended.


1. Why a paid agent makes sense

Autonomous agents are useful when they can perform a narrow, repeatable task and get compensated for it. If the task is cheap enough to run (e.g., a single LLM call) and the payment mechanism is low‑friction, the agent can accumulate revenue over time without human intervention.

The trade‑off is that you now have to worry about:

  • Payment verification – you must reject requests that haven’t paid the correct amount.
  • Latency vs. cost – adding a payment step introduces extra round‑trips and possible failure points.
  • Operational overhead – you need to monitor balances, handle refunds, and keep the service patched.

If those costs are outweighed by the expected earnings, the model works. In my experiment I chose a simple text‑summarization endpoint that charges $0.02 per call in USDC on the Base network. Over a week it earned roughly $0.84 while I slept, which matched the projected revenue based on observed traffic.


2. Architecture overview

+-------------------+      HTTPS (x402)      +-------------------+
|  Client (curl,   |  ----------------->   |  Agent Service    |
|  frontend, etc.) |  <-- 402 Payment Req   |  (Node/Worker)    |
+-------------------+      +----------------+  +-----------------+
                           |  Verify USDC   |  |  LLM Provider   |
                           |  (x402 middleware) |  (OpenAI API)   |
                           +----------------+  +-----------------+
                           |  Balance update (optional) |
                           +-----------------------------
Enter fullscreen mode Exit fullscreen mode
  • Client sends an HTTP request with an Authorization: Bearer <x402‑token> header.
  • The agent service runs an x402 middleware that checks the token against the smart contract on Base, verifies the amount, and marks the payment as consumed.
  • If verification passes, the request is forwarded to an LLM (here I used OpenAI’s gpt-3.5-turbo).
  • The LLM output is returned to the client.

The service is stateless aside from the payment nonce; each request is independent, which simplifies scaling and reduces the attack surface.


3. Setting up the payment layer

I used the x402 npm package, which implements the client‑side and server‑side parts of ERC‑4337‑style HTTP 402 payments. The server side needs:

  • The address of the USDC token on Base (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913).
  • The price in wei (USDC has 6 decimals).
  • A signer (or a read‑only provider) to validate signatures.
npm init -y
npm install express x402 ethers
Enter fullscreen mode Exit fullscreen mode

Server code (Node/Express)

// agent.js
import express from 'express';
import { x402Middleware } from 'x402';
import { ethers } from 'ethers';
import OpenAI from 'openai';

const app = express();
app.use(express.json());

// ----- Configuration -----
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const PRICE_USDC = 0.02; // $0.02 per call
const PRICE_WEI = ethers.parseUnits(PRICE_USDC.toString(), 6); // 6 decimals
const CHAIN_ID = 8453; // Base

// Provider (read‑only) – you can use Alchemy, Infura, or a public RPC
const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
// No signer needed for verification; the middleware checks the signature
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// ----- x402 middleware -----
app.use(
  '/summarize',
  x402Middleware({
    tokenAddress: USDC_ADDRESS,
    price: PRICE_WEI,
    chainId: CHAIN_ID,
    // optional: a function to store nonces to prevent replay attacks
    nonceStore: async (address, nonce) => {
      // In production use Redis or a DB; here we use a Map for demo
      if (!global.nonceMap) global.nonceMap = new Map();
      const seen = global.nonceMap.get(address) ?? new Set();
      if (seen.has(nonce)) throw new Error('Replay attack');
      seen.add(nonce);
      global.nonceMap.set(address, seen);
    },
  })
);

// ----- Endpoint -----
app.post('/summarize', async (req, res) => {
  const { text } = req.body;
  if (!text || typeof text !== 'string') {
    return res.status(400).json({ error: 'Missing "text" field' });
  }

  try {
    const completion = await openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: `Summarize the following:\n\n${text}` }],
      temperature: 0.3,
    });
    const summary = completion.choices[0].message.content.trim();
    res.json({ summary });
  } catch (err) {
    console.error(err);
    res.status(502).json({ error: 'Upstream LLM failed' });
  }
});

// ----- Start -----
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Agent listening on :${PORT}`));
Enter fullscreen mode Exit fullscreen mode

Explanation of the middleware

  • x402Middleware expects the client to send an Authorization header containing a signed payload that encodes the token address, amount, chain ID, and a nonce.
  • It verifies the signature using the signer’s address (derived from the payload) and checks that the amount matches PRICE_WEI.
  • The optional nonceStore prevents replay attacks; in a production deployment you’d replace the in‑memory Map with Redis or a Postgres table.

4. Client side – how a caller pays

For completeness, here’s a minimal curl‑compatible example using the x402 CLI (you can also generate the header in JavaScript):

# Install the x402 CLI (optional)
npm i -g x402-cli

# Generate a payment header for $0.02 USDC on Base
x402 pay \
  --token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
  --amount 0.02 \
  --chain 8453 \
  --private-key $YOUR_PRIVATE_KEY \
  --url http://localhost:3000/summarize \
  --method POST \
  --body '{"text":"The quick brown fox jumps over the lazy dog."}'
Enter fullscreen mode Exit fullscreen mode

The command prints a header like:

Authorization: Bearer x402:<base64-payload>
Enter fullscreen mode Exit fullscreen mode

You then copy that header into your request:

curl -X POST http://localhost:3000/summarize \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer x402:<base64-payload>" \
  -d '{"text":"The quick brown fox jumps over the lazy dog."}'
Enter fullscreen mode Exit fullscreen mode

If the payment is valid, you receive a JSON response with the summary. If not, you get a 402 Payment Required with a WWW‑Authenticate header that tells the client how to pay.


5. Deployment – Cloudflare Workers (the path to the live example)

I chose Cloudflare Workers because they give sub‑second cold starts, automatic TLS, and a simple way to publish a service at a custom domain. The Worker version of the code is almost identical; the only difference is that you cannot directly import ethers (it’s too large). Instead, I used the lightweight @ethersproject/shim bundle and the built‑in crypto.subtle for signature verification.


js
// worker.js (simplified)
import { x402Middleware } from 'x402/worker';
import { OpenAI } from 'openai';

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    if (url.pathname !== '/summarize' || request.method !== 'POST') {
      return new Response('Not found', { status: 404 });
    }

    // x402 middleware verifies payment; on failure it returns 402
    const paymentResp = await x402Middleware({
      tokenAddress: '0x833589fCD6eDb6E08f
Enter fullscreen mode Exit fullscreen mode

Top comments (0)