How I Built an Autonomous AI Agent That Earns USDC While I Sleep
A pragmatic walk‑through for developers who want to experiment with paid micro‑services on‑chain.
1. Why bother with an “earning while you sleep” agent?
The idea is simple: expose a tiny, useful function (e.g., “return the latest ETH/USDC price from three exchanges”) as an HTTP endpoint that requires payment before it returns data. If the function is cheap to run and the price per call is set low enough, a steady stream of requesters can generate a trickle of revenue that pays for the host’s compute cost—and maybe a little extra.
In practice the earnings are modest (fractions of a cent per call) and the system needs constant uptime, monitoring, and a way to handle failed payments. The goal of this post is to show how you can put the pieces together, not to promise passive income.
2. High‑level architecture
+-------------------+ x402 payment flow +-------------------+
| Caller (any HTTP) | <----------------------> | Agent Service |
| (browser, script) | USDC micropayment (x402) | (Cloudflare Worker|
+-------------------+ +-------------------+ + / Python Flask) |
| Verify & sign receipt | |
v v |
+-------------------+ +-------------------+
| USDC on Base | | Agent Logic |
| (ERC‑20 contract)| | (price fetch, etc)|
+-------------------+ +-------------------+
- x402 – a lightweight HTTP 402‑Payment Required extension that lets a service demand a signed USDC transfer before fulfilling a request.
- Agent Logic – the actual work the agent does (here: a simple price aggregator).
- Host – any cheap, always‑on compute. I used a Cloudflare Workers script because it gives free sub‑second cold starts and built‑in KV for caching, but a tiny Flask app on a $5 VPS works just as well.
The loop is:
- Caller hits
/price?symbol=ETH. - Middleware checks for a valid x402 payment header.
- If missing/invalid, returns
402 Payment Requiredwith payment details. - If valid, the agent runs its logic, returns the result, and signs a receipt that the caller can verify on‑chain.
3. Setting up a USDC wallet on Base
You need an address that holds enough USDC to cover the gas for the receipt signature (the agent never spends USDC; it only signs).
# pip install web3 eth-account
from web3 import Web3
from eth_account import Account
import os
# RPC for Base (mainnet)
BASE_RPC = os.getenv("BASE_RPC", "https://mainnet.base.org")
w3 = Web3(Web3.HTTPProvider(BASE_RPC))
# Generate or load a key – NEVER commit the private key to repo!
PRIVATE_KEY = os.getenv("AGENT_PRIVATE_KEY")
if not PRIVATE_KEY:
raise RuntimeError("Set AGENT_PRIVATE_KEY env var")
acct = Account.from_key(PRIVATE_KEY)
ADDRESS = acct.address
print(f"Agent address: {ADDRESS}")
# Optional: check USDC balance (USDC contract on Base)
USDC_ADDRESS = Web3.to_checksum_address("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")
USDC_ABI = [...] # standard ERC20 abi (balanceOf, decimals)
usdc = w3.eth.contract(address=USDC_ADDRESS, abi=USDC_ABI)
balance = usdc.functions.balanceOf(ADDRESS).call()
print(f"USDC balance: {balance / 1e6:.2f} USDC")
Trade‑off: Keeping the private key in an environment variable is fine for a demo, but production workloads should use a managed KMS or a hardware signer to reduce exposure.
4. Minimal x402 middleware (Python/Flask)
The x402 spec defines a Payment-Required header that contains:
x402-version: 1
network: base
currency: usdc
maxAmountRequired: 100000 # in smallest units (0.001 USDC)
resource: https://example.com/price
The client must respond with a signed transfer of USDC to the agent’s address, then retry with the X-Payment header containing the transaction hash.
# app.py
from flask import Flask, request, jsonify, make_response
import json
import hashlib
import time
from web3 import Web3
from eth_account.messages import encode_defunct
app = Flask(__name__)
w3 = Web3(Web3.HTTPProvider(os.getenv("BASE_RPC")))
AGENT_ACCOUNT = Account.from_key(os.getenv("AGENT_PRIVATE_KEY"))
# ---- CONFIG -------------------------------------------------
PRICE_PER_CALL = 1_000_000 # 0.001 USDC (6 decimals)
NETWORK = "base"
CURRENCY = "usdc"
# -----------------------------------------------------------
def build_402_response(resource_path):
resp = make_response(jsonify({"error": "payment required"}), 402)
resp.headers["x402-version"] = "1"
resp.headers["network"] = NETWORK
resp.headers["currency"] = CURRENCY
resp.headers["maxAmountRequired"] = str(PRICE_PER_CALL)
resp.headers["resource"] = f"https://{request.host}{resource_path}"
return resp
def verify_payment(tx_hash):
"""Very lightweight check: tx succeeded and sent >= PRICE_PER_CALL."""
try:
tx = w3.eth.get_transaction(tx_hash)
receipt = w3.eth.get_transaction_receipt(tx_hash)
if receipt.status != 1:
return False
# Ensure it's a USDC transfer to our address
if tx["to"].lower() != USDC_ADDRESS.lower():
return False
# Decode transfer data (simplified; in prod use abi)
# For brevity we assume the caller sent exactly the amount.
return True
except Exception:
return False
@app.route("/price")
def price():
resource = request.path
# 1️⃣ Check for payment header
pay_header = request.headers.get("X-Payment")
if not pay_header:
return build_402_response(resource)
# Expect JSON: {"txHash": "0x..."}
try:
payload = json.loads(pay_header)
tx_hash = payload["txHash"]
except (json.JSONDecodeError, KeyError):
return build_402_response(resource)
if not verify_payment(tx_hash):
return build_402_response(resource)
# 2️⃣ Payment good – run agent logic
symbol = request.args.get("symbol", "ETH").upper()
price_usd = fetch_price(symbol) # defined below
return jsonify({"symbol": symbol, "priceUSDC": price_usd})
def fetch_price(symbol):
"""Stub: call three public APIs and return the median."""
import requests, statistics
urls = [
f"https://api.coingecko.com/api/v3/simple/price?ids={symbol.lower()}&vs_currencies=usdc",
"https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDC",
"https://api.kraken.com/0/public/Ticker?pair=ETHUSDC",
]
prices = []
for u in urls:
try:
r = requests.get(u, timeout=3)
data = r.json()
# very naive parsing – replace with proper handling per API
if "coingecko" in u:
prices.append(data[symbol.lower()]["usdc"])
elif "binance" in u:
prices.append(float(data["price"]))
elif "kraken" in u:
prices.append(float(data["result"]["XETHZUSDC"]["c"][0]))
except Exception:
continue
if not prices:
raise RuntimeError("price fetch failed")
return statistics.median(prices)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
What this does
- Returns
402with payment details if noX-Paymentheader is present. - Verifies that the supplied transaction is a successful USDC transfer of at least the configured amount to the agent’s address.
- Runs a simple price‑aggregator and returns the median USDC price.
Trade‑offs
- The verification is deliberately lightweight (no full ERC‑20 decode). In production you’d want to use a contract that escrows funds or a verification service to avoid replay attacks.
- The price fetcher uses free public APIs; they rate‑limit aggressively. For a real service you’d either pay for a reliable data feed or cache results for a few seconds.
5. Deploying the agent
I chose Cloudflare Workers because it gives:
- Sub‑second global edge latency (important for micro‑services that may be called from dApps or bots).
- Built‑
Top comments (0)