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 comfortable with Python, basic blockchain concepts, and want to see a concrete, minimal‑working example rather than a marketing pitch.


Why bother with an “earning while you sleep” agent?

If you have a server or a cheap VM that sits idle most of the day, you can put that compute to work performing tiny, well‑defined tasks that pay in USDC. The idea isn’t to replace a salary; it’s to capture a few cents of value that would otherwise be wasted. The trade‑off is that you accept:

  • Low per‑task payout (typically $0.01‑$0.10).
  • Reliance on external APIs that may rate‑limit or change format.
  • Operational overhead (wallet security, monitoring, fallback logic).

If those are acceptable, the pattern below can be reused for many micro‑service‑style agents.


High‑level architecture

+----------------+      +----------------+      +-------------------+
|   Task Queue   | ---> |  Planning Loop | ---> |  Executor + Wallet|
+----------------+      +----------------+      +-------------------+
        ^                         |                         |
        |                         v                         v
+----------------+      +----------------+      +-------------------+
|  Data Sources  | <--- |  Payment (x402)| <--- |  USDC on Base     |
+----------------+      +----------------+      +-------------------+
Enter fullscreen mode Exit fullscreen mode
  1. Task Queue – a simple Redis list (or even a file‑based queue) that holds JSON‑encoded work items.
  2. Planning Loop – the LLM decides, given the current state and the next task, which micro‑action to take.
  3. Executor – carries out the action (HTTP request, blockchain call, etc.) and reports success/failure.
  4. Payment (x402) – each successful execution triggers a micropayment request; the payer (the service that posted the task) pays in USDC via the x402 protocol on Base.
  5. Wallet – a hot‑wallet holding USDC; we keep the private key encrypted at rest and only decrypt it in memory for signing.

All components run on a single modest VM (e.g., 1 vCPU, 2 GB RAM) to keep costs low.


Setting up the environment

# Python 3.11+ recommended
python -m venv .venv
source .venv/bin/activate
pip install redis openai web3 eth-account eth-utils x402-py
Enter fullscreen mode Exit fullscreen mode
  • redis – lightweight queue.
  • openai – the LLM we use for planning (you can swap any compatible model).
  • web3 + eth-account – to manage the USDC wallet on Base.
  • x402-py – helper for signing and verifying x402 micropayments.

Trade‑off note: Using a hosted LLM adds latency and cost per call. If you need sub‑second response times, consider a smaller open‑source model served locally, but then you lose the general‑purpose reasoning power of larger models.


Wallet handling (USDC on Base)

# wallet.py
from eth_account import Account
from eth_utils import to_checksum_address
import os
import json
from pathlib import Path

KEY_FILE = Path.home() / ".nexusai_wallet.json"

def load_or_create_wallet() -> Account:
    if KEY_FILE.exists():
        data = json.loads(KEY_FILE.read_text())
        priv = data["private_key"]
    else:
        acct = Account.create()
        data = {"private_key": acct.key.hex(), "address": acct.address}
        KEY_FILE.write_text(json.dumps(data, indent=2))
        # NOTE: In production encrypt the file with a passphrase or use a KMS.
        priv = data["private_key"]
    return Account.from_key(priv)

def usdc_contract():
    # USDC on Base (mainnet) – address from https://docs.base.org
    return web3.eth.contract(
        address=to_checksum_address("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
        abi=[{
            "constant":True,
            "inputs":[{"name":"_owner","type":"address"}],
            "name":"balanceOf",
            "outputs":[{"name":"balance","type":"uint256"}],
            "type":"function"
        },{
            "constant":False,
            "inputs":[
                {"name":"_to","type":"address"},
                {"name":"_value","type":"uint256"}
            ],
            "name":"transfer",
            "outputs":[{"name":"","type":"bool"}],
            "type":"function"
        }]
    )
Enter fullscreen mode Exit fullscreen mode

The private key lives only in memory after loading; the file is protected by OS permissions. For anything beyond a demo, move to a hardware signer or a cloud KMS.


The planning loop (LLM‑driven task selection)

# planner.py
import openai
import json
from typing import Dict, Any

openai.api_key = os.getenv("OPENAI_API_KEY")

SYSTEM_PROMPT = """
You are an autonomous agent that selects the next micro‑task to execute.
You receive a JSON description of the task and the current agent state.
Reply with a JSON object containing:
- "action": one of ["fetch_price", "compute_moving_average", "place_limit_order"]
- "params": dict of arguments needed for the action.
If no action is suitable, return {"action": "idle"}.
"""

def decide_next_task(task: Dict[str, Any], state: Dict[str, Any]) -> Dict[str, Any]:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": json.dumps({
            "task": task,
            "state": state
        })}
    ]
    resp = openai.ChatCompletion.create(
        model="gpt-4o-mini",   # cheap, low‑latency model
        messages=messages,
        temperature=0.0,
        max_tokens=200
    )
    content = resp.choices[0].message["content"].strip()
    try:
        return json.loads(content)
    except json.JSONDecodeError:
        # fallback to idle on malformed output
        return {"action": "idle"}
Enter fullscreen mode Exit fullscreen mode

Why a low‑temperature setting? We want deterministic behavior for a production‑like agent; creativity isn’t needed here.


Executing a sample micro‑task: price fetch + simple moving average


python
# executor.py
import requests
import time
from web3 import Web3
from wallet import load_or_create_wallet, usdc_contract
from x402 import x402_payment_required, generate_x402_header

WEB3_PROVIDER = "https://mainnet.base.org"
w3 = Web3(Web3.HTTPProvider(WEB3_PROVIDER))
acct = load_or_create_wallet()
usdc = usdc_contract()

def fetch_price(symbol: str) -> float:
    # Example: CoinGecko public API (free, rate‑limited)
    url = f"https://api.coingecko.com/api/v3/simple/price?ids={symbol}&vs_currencies=usd"
    data = requests.get(url, timeout=5).json()
    return float(data[symbol]["usd"])

def compute_moving_average(prices, window=5):
    if len(prices) < window:
        return None
    return sum(prices[-window:]) / window

def place_limit_order(symbol: str, price_usd: float, amount_usdc: float):
    # This is a *mock* order; replace with a real DEX router call if you want on‑chain execution.
    # For demonstration we just log and pretend the order filled instantly.
    print(f"[EXEC] Placing limit BUY {symbol} at ${price_usd:.4f} for {amount_usdc} USDC")
    # Simulate a fill after a short delay
    time.sleep(0.5)
    return amount_usdc  # pretend we earned the full amount as a fee

@x402_payment_required(amount=0.01, token="USDC", network="base")  # x402 decorator
def execute_task(action: str, params: dict) -> dict:
    """
    The x402 decorator will:
    1. Verify the incoming request carries
Enter fullscreen mode Exit fullscreen mode

Top comments (0)