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 self‑service AI agents and want to see a concrete, low‑hype implementation that can receive micropayments in USDC on the Base layer‑2.


1. Why an autonomous, paid agent?

When you expose a capability as a HTTP service you can let callers pay per‑use instead of relying on subscriptions or ad‑based revenue. The x402 proposal (HTTP 402 Payment Required) gives a lightweight way to gate an endpoint until a verifiable payment is made. By chaining this with an agent that can perform useful work (e.g., data enrichment, simple inference, or report generation) you create a loop:

  1. Idle – the agent waits for incoming requests.
  2. Request arrives – the middleware checks for a valid USDC payment on Base.
  3. If paid – the agent runs its workload and returns the result.
  4. If not paid – the client receives a 402 response with payment instructions.

The agent can therefore “earn while I sleep” because it only needs to be reachable; the heavy lifting happens on demand.


2. Architecture Overview

+-------------------+        +-------------------+        +-------------------+
|   Client (HTTP)   |  --->  |   x402 Middleware |  --->  |   Agent Worker    |
+-------------------+        +-------------------+        +-------------------+
          ^                         |                         |
          |                         v                         v
          |                +-------------------+   +-------------------+
          |                |   Payment Verifier|   |   Task Executor   |
          |                +-------------------+   +-------------------+
          |                         |                         |
          +-------------------------+-------------------------+
                                    |
                                    v
                             +-------------------+
                             |   USDC on Base    |
                             +-------------------+
Enter fullscreen mode Exit fullscreen mode
  • x402 Middleware – a thin wrapper around your web framework that inspects the Payment header, forwards the transaction hash to the verifier, and either lets the request proceed or returns a 402 with the required amount and destination address.
  • Payment Verifier – calls a read‑only RPC endpoint (e.g., Base’s public RPC) to confirm that a USDC transfer of at least the requested amount has been mined and is addressed to the agent’s wallet.
  • Task Executor – the actual AI workload. In the example below it’s a simple sentiment‑analysis model, but you can swap in any CPU‑ or GPU‑bound function.
  • USDC on Base – we use the ERC‑20 contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (the canonical USDC on Base).

3. Setting Up the Environment

# Python 3.11+ recommended
python -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn web3==6.11.0 eth-account==0.9.0 transformers torch
Enter fullscreen mode Exit fullscreen mode
  • fastapi gives us an async‑friendly server with minimal boilerplate.
  • web3 handles RPC calls and transaction decoding.
  • transformers supplies a tiny sentiment model (distilbert-base-uncased-finetuned-sst-2-english).

4. Payment Verifier Implementation

The verifier does three things:

  1. Fetch the transaction from the hash supplied in the x402-payment header.
  2. Check that the to address matches our USDC wallet.
  3. Confirm that the transferred amount (in wei) ≥ required amount.
# payment_verifier.py
from web3 import Web3
from eth_account.messages import encode_defunct
import os

BASE_RPC = os.getenv("BASE_RPC", "https://base.mainnet.rpc.v2")
USDC_ADDRESS = Web3.to_checksum_address(
    "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
)
AGENT_WALLET = os.getenv("AGENT_WALLET")  # must be funded with USDC

w3 = Web3(Web3.HTTPProvider(BASE_RPC))

# ERC‑20 minimal ABI for transfer events
ERC20_ABI = [
    {
        "anonymous": False,
        "inputs": [
            {"indexed": True, "internalType": "address", "name": "from", "type": "address"},
            {"indexed": True, "internalType": "address", "name": "to", "type": "address"},
            {"indexed": False, "internalType": "uint256", "name": "value", "type": "uint256"},
        ],
        "name": "Transfer",
        "type": "event",
    }
]

usdc_contract = w3.eth.contract(address=USDC_ADDRESS, abi=ERC20_ABI)


def verify_payment(tx_hash: str, required_usdc: float) -> bool:
    """
    Returns True if tx_hash corresponds to a USDC transfer of at least
    required_usdc to AGENT_WALLET.
    """
    try:
        tx = w3.eth.get_transaction(tx_hash)
        receipt = w3.eth.get_transaction_receipt(tx_hash)

        # The transaction must be to the USDC contract (a call to its transfer function)
        if tx.to.lower() != USDC_ADDRESS.lower():
            return False

        # Decode logs to find the Transfer event
        logs = usdc_contract.events.Transfer().process_receipt(receipt)
        for log in logs:
            args = log["args"]
            if (
                args["to"].lower() == AGENT_WALLET.lower()
                and args["value"] >= w3.to_wei(required_usdc, "ether")
            ):
                return True
        return False
    except Exception as e:
        # In production you’d log and possibly retry; here we fail closed.
        print(f"Verification error: {e}")
        return False
Enter fullscreen mode Exit fullscreen mode

Trade‑offs

  • Reliance on a public RPC – free tiers are rate‑limited; for production you’d run your own node or use a paid service to avoid throttling.
  • Confirmation latency – we accept the transaction as soon as it’s in a block (≈2 s on Base). If you need stronger finality you could wait for additional confirmations, which adds latency.
  • Gas cost – the verifier only makes read calls; the payer pays the gas for the USDC transfer.

5. x402 Middleware (FastAPI)

FastAPI doesn’t have built‑in x402 support, so we add a dependency that checks the payment header before calling the endpoint.

# main.py
from fastapi import FastAPI, Header, HTTPException, Depends
from pydantic import BaseModel
from payment_verifier import verify_payment
import os

app = FastAPI()

# How much we charge per call (in USDC)
PRICE_USDC = float(os.getenv("PRICE_USDC", "0.02"))  # $0.02 per request


class SentimentRequest(BaseModel):
    text: str


class SentimentResponse(BaseModel):
    label: str
    score: float


def x402_payment_required(required: float = PRICE_USDC):
    async def dependency(
        x402_payment: str = Header(None, alias="x402-payment")
    ):
        if not x402_payment:
            raise HTTPException(
                status_code=402,
                headers={
                    "Accept-Payment": f"usdc:{AGENT_WALLET}:{required}"
                },
                detail="Payment required",
            )
        # The client should send: "<tx_hash>"
        if not verify_payment(x402_payment, required):
            raise HTTPException(
                status_code=402,
                headers={
                    "Accept-Payment": f"usdc:{AGENT_WALLET}:{required}"
                },
                detail="Invalid or insufficient payment",
            )
    return dependency


@app.post("/sentiment", response_model=SentimentResponse)
async def sentiment(
    req: SentimentRequest,
    _: None = Depends(x402_payment_required()),
):
    # ---- Simple model inference (replace with your own workload) ----
    from transformers import pipeline

    classifier = pipeline(
        "sentiment-analysis",
        model="distilbert-base-uncased-finetuned-sst-2-english",
        return_all_scores=False,
    )
    result = classifier(req.text)[0]
    return SentimentResponse(label=result["label"], score=result["score"])
Enter fullscreen mode Exit fullscreen mode

How it works

  • The client includes a header x402-payment: <tx_hash> where <tx_hash> is the hash of a USDC transfer they just sent to the agent’s wallet.
  • The middleware runs verify_payment. If the check passes, the request proceeds to the sentiment endpoint; otherwise a 402 is returned with an Accept-Payment header that tells the client exactly what to pay and where.

Trade‑offs

  • Header‑based payment is simple but puts the

Top comments (0)