How I Built an Autonomous AI Agent That Earns USDC While I Sleep
Target audience: developers who want to put a language‑model‑driven agent behind a pay‑per‑use API and settle payments in USDC on Base.
1. Why a “sleep‑earning” agent?
The idea isn’t magic; it’s simply exposing a useful AI capability as a metered service. If each invocation costs the caller a fraction of a cent and the agent can run on cheap, stateless infrastructure, the revenue stream can keep ticking while you’re offline. The hard part is making the agent reliable enough that people will pay for it, and integrating a payment rail that doesn’t add operational overhead.
2. High‑level architecture
+----------------+ x402 payment +-------------------+
| Caller (any) | ───────────────────────► | Cloudflare Worker |
+----------------+ (USDC on Base) +-------------------+
│
▼
+-------------------+
| AI Agent Core |
| (LangChain + LLM)│
+-------------------+
│
▼
+-------------------+
| Result / Data |
+-------------------+
- Caller – any HTTP client (web app, CLI, another agent).
- x402 – a lightweight micropayment protocol that attaches a payment receipt to the request header. The Worker validates the receipt before forwarding the request to the agent logic.
- Cloudflare Worker – stateless, globally distributed, executes in < 5 ms cold start for JavaScript/Wasmi‑compiled Rust. Keeps ops cheap (≈ $0.000005 per 10 ms).
- AI Agent Core – a small LangChain‑style pipeline that calls a hosted LLM (e.g., OpenAI GPT‑4o‑mini) or an open‑source model served via Together.ai. The core is pure Python; we run it inside the Worker via Pyodide or, more practically, as a separate Docker container invoked through a lightweight RPC (e.g., Hypercorn + Uvicorn) when the Worker needs > 100 ms of CPU. For the demo I kept the model call inside the Worker using the OpenAI API (network‑bound, not CPU‑bound).
3. Building the agent core
Below is a minimal, functional agent that answers a single‑turn question and returns a JSON payload. It uses LangChain for prompt templating and the OpenAI chat completion API.
# agent.py
import os
import json
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain_openai import ChatOpenAI
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2, api_key=OPENAI_API_KEY)
prompt = PromptTemplate.from_template(
"You are a helpful assistant. Answer concisely: {question}"
)
chain = LLMChain(llm=llm, prompt=prompt)
def run_agent(question: str) -> dict:
"""Execute the agent and return a serializable dict."""
answer = chain.run(question)
return {"question": question, "answer": answer.strip()}
Trade‑off: Using a hosted LLM removes the need to manage GPU inference, but it introduces a per‑call cost (≈ $0.0006 for gpt‑4o‑mini) and a network latency of 200‑400 ms. If you need sub‑100 ms latency, you’d have to self‑host a smaller model (e.g., Phi‑2) and accept higher engineering complexity.
4. Integrating x402 payments
x402 works by attaching a payment receipt to the X-Payment header. The Worker validates the receipt against the x402 smart contract on Base (USDC contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913).
Below is the Cloudflare Worker (written in JavaScript) that:
- Checks for a valid x402 receipt.
- Calls the Python agent via a sub‑request to a separate
/agentendpoint (running on the same Workers site via a Python‑Wasm wrapper or a lightweight Durable Object). - Returns the agent’s JSON response with a 200 status, or a 402 if payment is missing/invalid.
// worker.js
import { verifyPayment } from "./x402.js"; // helper that calls the Base RPC
export default {
async fetch(request, env, ctx) {
const paymentHeader = request.headers.get("x-payment");
if (!paymentHeader) {
return new Response("Missing payment header", { status: 402 });
}
// Verify the receipt: amount, token, payer, and nonce
const { valid, error } = await verifyPayment(paymentHeader, {
amount: "0.01", // USDC amount we charge per call (adjustable)
token: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
payer: env.PAYER_ADDRESS, // set in wrangler.toml
});
if (!valid) {
return new Response(`Payment invalid: ${error}`, { status: 402 });
}
// Forward to the internal agent endpoint (same origin)
const agentResp = await fetch(`${request.url}/agent`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question: await request.text() }),
});
// Propagate status and body
return new Response(agentResp.body, {
status: agentResp.status,
headers: agentResp.headers,
});
},
};
Helper (x402.js) – a thin wrapper that uses ethers.js to call the x402 view function on Base. In production you’d cache the latest block hash to reduce RPC calls.
// x402.js
import { ethers } from "ethers";
const X402_ABI = [/* minimal ABI for verifyPayment(uint256 amount, address token, address payer, bytes calldata receipt) */];
const X402_ADDRESS = "0xX402..."; // deployed on Base
export async function verifyPayment(receiptHex, { amount, token, payer }) {
const provider = new ethers.JsonRpcProvider(env.BASE_RPC_URL);
const contract = new ethers.Contract(X402_ADDRESS, X402_ABI, provider);
try {
const result = await contract.verifyPayment(
ethers.parseUnits(amount, 6), // USDC has 6 decimals
token,
payer,
receiptHex
);
return { valid: result, error: null };
} catch (e) {
return { valid: false, error: e.message };
}
}
Trade‑off: Adding payment verification introduces an extra RPC round‑trip (~120 ms on Base) and requires you to manage a payer address with sufficient USDC balance. If you want truly zero‑fee ingestion you could move to a gas‑less meta‑transaction approach, but that adds custodial complexity.
5. Deploying the agent
-
Wrangler setup –
wrangler init x402-agentcreates a Worker project. -
Dependencies – add
ethersand optionallylangchain(via a Python‑Wasm build) topackage.json. -
Secrets – store
OPENAI_API_KEY,BASE_RPC_URL, andPAYER_ADDRESSinwrangler.tomlunder[vars]or use Cloudflare Secrets (wrangler secret put). -
Build – if you embed the Python agent via Pyodide, run
wrangler buildwhich bundles the WASM. For a simpler approach, expose a separate HTTP endpoint (e.g., a Fly.io VPS) that runsagent.pywith Uvicorn; the Worker just proxies to it.
# wrangler.toml
name = "x402-agent"
main = "src/worker.js"
compatibility_date = "2024-09-01"
[vars]
OPENAI_API_KEY = "<from secret>"
BASE_RPC_URL = "https://base.mainnet.rpc.dev"
PAYER_ADDRESS = "0xYourPayerAddress"
Deploy with wrangler publish. The Worker will be reachable at https://x402-agent.<your-subdomain>.workers.dev/.
6. Honest trade‑offs & lessons learned
| Aspect | What worked | What was painful |
|---|---|---|
| Cost per call | USDC 0.01 – 0.10 covers LLM |
Top comments (0)