How I Built an Autonomous AI Agent That Earns USDC While I Sleep
Why x402?
x402 is a lightweight HTTP-based payment scheme that lets a server respond with a 402 Payment Required status and a payment request in the WWW-Authenticate header. Clients that understand x402 can automatically fetch USDC, sign a transaction, and retry the request. For an autonomous agent this means:
- Statelessness - the agent doesn't need to keep a user-side balance; payment is enforced at the API boundary.
- Compatibility - any HTTP client (curl, Postman, a custom SDK) can be upgraded to pay without changing business logic.
- Low overhead - the protocol adds only a few bytes to the response; the heavy lifting stays in the payment SDK.
The trade-off is that you must accept the extra round-trip for unauthenticated callers and you need to host a wallet that can sign USDC transfers on the target chain (here, Base).
High-level Architecture
+-------------------+ HTTP/x402 +-------------------+
| Client (any) |<--------->| Agent Service |
+-------------------+ (FastAPI) +-------------------+
^
|
v
+-------------------+
| Wallet Manager |
| (web3.py + private|
| key, USDC ABI) |
+-------------------+
|
v
+-------------------+ +----------------------------+
| USDC Ledger |-->| (Base testnet/main) |
| | | 0x833589fCD6eDb6E08f4c7C32 |
+-------------------+ | D4f71b54bdA02913 |
+----------------------------+
- Agent Service - a FastAPI app that exposes one or more useful endpoints (e.g., text summarization, image tagging). Each endpoint checks for a valid x402 payment; if missing, it returns a 402 with payment details.
- Wallet Manager - a singleton that loads an Ethereum private key, constructs USDC transfer transactions, and signs them using web3.py.
- USDC Ledger - the Base network contract (
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913on Base mainnet).
Code Walk-through
Below is a complete, runnable example (≈120 lines) that you can copy into a file agent.py and run with uvicorn agent:app --host 0.0.0.0 --port 8000.
3.1 Dependencies
# pyproject.toml
[project]
name = "x402-agent"
dependencies = [
"fastapi==0.110.0",
"uvicorn[standard]==0.30.0",
"web3==7.8.0",
"eth-account==0.10.0",
"pydantic==2.7.1",
]
3.2 Core implementation
# agent.py
import os
from typing import Literal
from fastapi import FastAPI, Header, HTTPException, Request, Response
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from web3 import Web3
from eth_account import Account
from eth_account.messages import encode_defunct
app = FastAPI(title="Autonomous USDC‑earning Agent")
# ----------------------------------------------------------------------
# Configuration - replace with your own values or load from env/secrets
# ----------------------------------------------------------------------
BASE_RPC = os.getenv("BASE_RPC", "https://mainnet.base.org") # public RPC, OK for read‑only
USDC_CONTRACT = Web3.to_checksum_address(
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
) # USDC on Base
PRIVATE_KEY = os.getenv("AGENT_PRIVATE_KEY") # must fund this address with USDC
if not PRIVATE_KEY:
raise RuntimeError("Set AGENT_PRIVATE_KEY env var")
w3 = Web3(Web3.HTTPProvider(BASE_RPC))
account = Account.from_key(PRIVATE_KEY)
usdc_abi = [
{
"constant": False,
"inputs": [
{"name": "_to", "type": "address"},
{"name": "_value", "type": "uint256"},
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function",
},
{
"constant": True,
"inputs": [{"name": "_owner", "type": "address"}],
"name": "balanceOf",
"outputs": [{"name": "balance", "type": "uint256"}],
"type": "function",
},
]
usdc_contract = w3.eth.contract(address=USDC_CONTRACT, abi=usdc_abi)
# ----------------------------------------------------------------------
# x402 helpers
# ----------------------------------------------------------------------
X402_SCHEME = "Bearer"
PRICE_USDC = 0.01 # price per call, in USDC (6 decimals)
USDC_DECIMALS = 6
def price_in_wei() -> int:
"""Return price in the smallest USDC unit (wei)."""
return int(PRICE_USDC * 10**USDC_DECIMALS)
def build_402(challenge_id: str) -> Response:
"""Create a 402 response with x402 WWW‑Authenticate header."""
header = (
f'{X402_SCHEME} x402; '
f'price="{price_in_wei()}"; '
f'currency="USDC"; '
f'network="base"; '
f'payee="{account.address}"; '
f'payload="{challenge_id}"'
)
return Response(
status_code=402,
headers={"WWW-Authenticate": header},
media_type="application/json",
content='{"error":"payment required"}',
)
def verify_payment(auth_header: str | None, payload: str) -> bool:
"""
Very small subset of x402 verification: we expect the client to have sent
a signed transaction hash in the Authorization header. In practice you would
use the official x402-py SDK; here we keep it illustrative.
"""
if not auth_header or not auth_header.startswith(f"{X402_SCHEME} "):
return False
tx_hash = auth_header.split(" ", 1)[1]
# Basic sanity: transaction must exist and transfer correct amount to us
try:
tx = w3.eth.get_transaction(tx_hash)
except Exception:
return False
if tx["to"].lower() != USDC_CONTRACT.lower():
return False
# decode input data (simple transfer)
func_obj, func_params = usdc_contract.decode_function_input(tx["input"])
if func_obj.fn_name != "transfer":
return False
if func_params["_to"].lower() != account.address.lower():
return False
if func_params["_value"] != price_in_wei():
return False
# optional: check receipt
status = 1
receipt = w3.eth.get_transaction_receipt(tx_hash)
return receipt.status == status
# ----------------------------------------------------------------------
# Example AI endpoint - replace with your own model inference
# ----------------------------------------------------------------------
class SummarizeRequest(BaseModel):
text: str
max_length: int = 130
@app.post("/summarize")
async def summarize(
req: SummarizeRequest,
authorization: str = Header(None),
x_payload: str = Header(None),
):
"""Returns a naive extractive summary. Payment is enforced via x402."""
# 1️⃣ Verify payment (if any)
if not verify_payment(authorization, x_payload or ""):
# generate a random challenge to avoid replay attacks
import uuid
challenge = str(uuid.uuid4())
return build_402(challenge)
# 2️⃣ Actual work - placeholder logic
sentences = req.text.split(".")
summary = ".".join(sentences[: max(1, req.max_length // 20)]) + "."
return JSONResponse({"summary": summary})
# ----------------------------------------------------------------------
# Health check - no payment required
# ----------------------------------------------------------------------
@app.get("/healthz")
async def healthz():
return {
"status": "ok",
"usdc_balance": usdc_contract.functions.balanceOf(account.address).call()
/ 10**USDC_DECIMALS,
}
What the snippet does
- Wallet setup - loads a private key from the environment, connects to Base via a public RPC, and prepares a minimal USDC ABI (only
transferandbalanceOf). - x402 response -
build_402creates a402 Payment Requiredheader that tells the client how much USDC to send, to which address, and on which chain. - Payment verification -
verify_paymentexpects the client to resend the request with anAuthorization: Bearer <txHash>header. It checks that the transaction actually transferred the correct amount to the agent's address. (In a real deployment you'd use the official x402-py library which also validates signatures and replays.)
Comments
No comments yet. Start the discussion.