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 |
+----------------+ +----------------+ +-------------------+
- Task Queue – a simple Redis list (or even a file‑based queue) that holds JSON‑encoded work items.
- Planning Loop – the LLM decides, given the current state and the next task, which micro‑action to take.
- Executor – carries out the action (HTTP request, blockchain call, etc.) and reports success/failure.
- 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.
- 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
-
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"
}]
)
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"}
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
Top comments (0)