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 or planning to run autonomous AI agents that generate revenue on‑chain.


Why I started

When I first tried to monetize a language‑model‑driven helper bot, I quickly hit three practical limits:

  1. Human‑in‑the‑loop friction – every useful task still required me to approve a prompt or review output.
  2. Unpredictable cost – paying for each LLM call ate into any earnings before I could see a profit.
  3. Payment latency – traditional fiat payouts took days, which made “earn while you sleep” feel like a marketing slogan rather than an engineering reality.

I wanted a system where the agent could:

  • Decide on a useful micro‑task (e.g., summarizing a public dataset, checking a website for a price change).
  • Execute that task autonomously, using only an API key for the model.
  • Receive payment instantly in a stablecoin (USDC) on a low‑fee L2 (Base).
  • Operate without me having to wake up to approve anything.

The result is a minimal‑viable autonomous agent that loops, performs a task, invoices via the x402 protocol, and settles in USDC. Below I walk through the architecture, the code I actually ran, and the trade‑offs I encountered.


System Overview

+----------------+       +-------------------+       +------------------+
|  Scheduler (cron) | ---> |  Task Runner (Python) | ---> |  LLM API + Wallet |
+----------------+       +-------------------+       +------------------+
        ^                         |                         |
        |                         v                         v
        |                +----------------+        +------------------+
        |                |  x402 Invoice  | <------|  USDC on Base    |
        |                +----------------+        +------------------+
        |                         |
        +-------------------------+
Enter fullscreen mode Exit fullscreen mode
  • Scheduler – a simple Unix cron job (or Cloudflare Workers Cron Trigger) that wakes the agent every 15 minutes.
  • Task Runner – a Python script that picks a task from a predefined queue, runs it, builds an x402 payment request, and posts it to a relay.
  • LLM API – any provider that offers a completions endpoint (I used OpenAI’s GPT‑4o‑mini for cost control).
  • Wallet – a deterministic Ethereum‑compatible key derived from a mnemonic; the agent signs x402 invoices with it.
  • x402 Relay – a lightweight HTTP server that validates the invoice, forwards the request to the LLM, and returns the signed USDC receipt once the payer (the relay itself) has funded the invoice. In practice I ran the relay on a Cloudflare Worker; the same code works anywhere that can execute JavaScript.

The flow is:

  1. Scheduler → Task Runner → pick task.
  2. Task Runner → LLM (via API) → get result.
  3. Task Runner → build x402 invoice (amount in USDC, payload hash).
  4. Task Runner → POST invoice to x402 Relay.
  5. Relay verifies signature, escrows USDC from its own wallet, calls the LLM (if not already done), and returns a signed receipt.
  6. Task Runner stores receipt; the agent’s balance increases.

Working Code Snippets

Below are the exact files I used (minus secrets). Feel free to copy‑paste, replace the placeholders, and run them in a Python 3.11 environment with pip install web3 eth-account requests.

1. Task Runner (agent.py)

#!/usr/bin/env python3
import os
import json
import hashlib
import time
import requests
from eth_account import Account
from eth_account.messages import encode_defunct
from web3 import Web3

# ---- Configuration (replace with your own) ----
MNEMONIC = os.getenv("AGENT_MNEMONIC")          # 12‑word phrase
LLM_API_KEY = os.getenv("OPENAI_API_KEY")
LLM_ENDPOINT = "https://api.openai.com/v1/chat/completions"
X402_RELAY = "https://x402-relay.example.org/invoice"  # your relay
USDC_CONTRACT = Web3.to_checksum_address("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")  # Base USDC
CHAIN_ID = 8453   # Base
# ------------------------------------------------

def get_wallet():
    acct = Account.from_mnemonic(MNEMONIC)
    return acct

def sign_message(msg: str, acct: Account) -> str:
    encoded = encode_defunct(text=msg)
    signed = acct.sign_message(encoded)
    return signed.signature.hex()

def build_invoice(task_id: str, payload: str, amount_usdc: float, acct: Account):
    # amount in smallest unit (6 decimals for USDC)
    amount = int(amount_usdc * 1_000_000)
    payload_hash = hashlib.sha256(payload.encode()).hexdigest()
    invoice = {
        "task_id": task_id,
        "payload_hash": payload_hash,
        "amount": amount,
        "currency": "USDC",
        "chain": CHAIN_ID,
        "token": USDC_CONTRACT,
        "timestamp": int(time.time()),
    }
    # sign the canonical JSON (sorted keys)
    msg = json.dumps(invoice, sort_keys=True)
    invoice["signature"] = sign_message(msg, acct)
    return invoice, amount

def run_task(task_id: str):
    # Example task: summarize a public RSS feed
    feed_url = "https://hnrss.org/newest"
    resp = requests.get(feed_url, timeout=10)
    resp.raise_for_status()
    # Very naive summary: just return first title
    data = resp.json()
    summary = data[0]["title"] if data else "no items"
    return summary

def main():
    acct = get_wallet()
    task_id = f"task-{int(time.time())}"
    payload = run_task(task_id)
    invoice, amount = build_invoice(task_id, payload, 0.02, acct)  # $0.02 per call

    headers = {"Content-Type": "application/json"}
    r = requests.post(X402_RELAY, json=invoice, headers=headers, timeout=15)
    r.raise_for_status()
    receipt = r.json()
    print(f"Task {task_id} completed. Receipt: {receipt}")
    # Optionally persist receipt to a DB or file for accounting

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

What it does

  • Generates a deterministic wallet from a mnemonic (so you can fund it once and reuse).
  • Picks a simple task – fetching the newest Hacker News title via an RSS proxy. In a real deployment you’d swap this for whatever micro‑service you want to monetize (e.g., checking a price feed, translating a short text, validating a CSV).
  • Builds an x402 invoice: a JSON object that includes the task ID, a SHA‑256 hash of the payload, the amount in USDC (6‑decimal units), chain ID, token address, timestamp, and an EIP‑191 signature.
  • Posts the invoice to the relay and prints the signed receipt.

2. Minimal x402 Relay (relay.js) – Cloudflare Worker


javascript
// wrangler.toml should include: 
//   compatibility_date = "2024-09-01"
//   [vars]
//   MNEMONIC = "your agent mnemonic"
//   USDC_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
//   CHAIN_ID = "8453"
//   LLM_API_KEY = "sk-..."
//
// npm install viem axios

import { createPublicClient, http, parseAbiItem } from 'viem';
import { base } from 'viem/chains';
import axios from 'axios';

const USDC_ABI = [
  "function balanceOf(address) view returns (uint256)",
  "function transfer(address to, uint256 amount) returns (bool)",
];

const client = createPublicClient({
  chain: base,
  transport: http(),
});

export default {
  async fetch(request, env) {
    if (request.method !== 'POST') return new Response('Method not allowed', { status: 405 });
    const body = await request.json();
    // ---- Verify signature ----
    const { task_id, payload_hash, amount, currency, chain, token, timestamp, signature } = body;
    if (currency !== 'USDC' || Number(chain) !== 8453 || token.toLowerCase() !== env.USDC_CONTRACT.toLowerCase())
      return new Response('Invalid invoice params', { status: 400 });

    const msgHash = ethers.utils.hashMessage(
      ethers.utils.arrayify(
        ethers.utils.defaultAbiCoder.encode(
          ['string','bytes32','uint256','
Enter fullscreen mode Exit fullscreen mode

Top comments (0)