How I Built an Autonomous AI Agent That Earns USDC While I Sleep
Target audience: developers who are experimenting with autonomous agents, on‑chain payments, and serverless deployment. The goal is to show a realistic, minimal‑viable implementation, not a get‑rich‑quick scheme.
1. Why an “earning‑while‑sleeping” agent?
Most hobbyist agents sit idle until a human triggers them. If the agent can perform a useful, repeatable task that someone is willing to pay for—and collect payment autonomously—then the agent can run continuously on cheap infrastructure and accrue revenue without constant supervision.
The trade‑off is clear: the agent must be reliable enough to avoid costly mistakes, cheap enough to run 24/7, and transparent about its capabilities so buyers know what they’re paying for. Below is the architecture I settled on after a few iterations of over‑engineering and under‑delivery.
2. High‑level architecture
+-------------------+ +-------------------+ +-------------------+
| Scheduler (cron)| ---> | Agent Loop | ---> | Payment Service |
+-------------------+ +-------------------+ +-------------------+
^ | |
| v v
(optional) +-------------------+ +-------------------+
monitoring | Task Worker(s) | ---> | USDC Wallet |
(logs, alerts) +-------------------+ +-------------------+
- Scheduler – a simple cron job (or Cloudflare Workers Cron Trigger) that wakes the agent every N minutes.
- Agent Loop – fetches pending work from a queue, runs the task worker, and reports success/failure.
- Task Worker – the domain‑specific logic (e.g., data enrichment, image tagging, simple SEO audit).
- Payment Service – calls an x402‑compatible endpoint that invoices the caller in USDC on Base; the agent only receives funds after the caller signs a valid payment proof.
- USDC Wallet – a custodial or self‑custodied address (e.g., via Privy, WalletConnect, or a smart contract escrow) that holds earned funds until withdrawal.
All components are deliberately stateless except for the wallet, which lets us run the agent on cheap serverless platforms (Cloudflare Workers, Vercel Edge Functions, or Fly.io) without managing persistent storage.
3. The agent loop – core logic
Below is a condensed Python‑like pseudocode that runs inside a serverless function. Replace the do_work stub with your actual task.
import os
import json
import time
import requests
from eth_account import Account
from web3 import Web3
# ---- CONFIG -----------------------------------------------------------------
WORKER_URL = os.getenv("WORKER_URL") # internal endpoint that does the job
PAYMENT_URL = os.getenv("PAYMENT_URL") # x402‑compatible invoicing endpoint
USDC_CONTRACT = os.getenv("USDC_ADDRESS") # USDC on Base (0x... )
WALLET_PRIVATE_KEY = os.getenv("WALLET_PK") # never commit this to repo
CHAIN_ID = 8453 # Base
# -----------------------------------------------------------------------------
def get_nonce(address):
"""Fetch the next transaction nonce from an RPC."""
rpc = os.getenv("BASE_RPC") # e.g., https://base.mainnet.rpc.cloud
w3 = Web3(Web3.HTTPProvider(rpc))
return w3.eth.get_transaction_count(address)
def sign_payment(amount_usdc, buyer_address):
"""
Build an x402 payment request: agent signs a message that authorizes
the transfer of `amount_usdc` USDC from the escrow contract to the buyer.
The exact format depends on the x402 implementation you target.
"""
# Example: EIP‑191 signed message: "\x19Ethereum Signed Message:\n32"<hash>
message = f"Pay {amount_usdc} USDC to {buyer_address} for task {task_id}"
encoded = encode_defunct(text=message)
signed = Account.sign_message(encoded, private_key=WALLET_PRIVATE_KEY)
return signed.signature.hex()
def request_payment(task_result, buyer):
"""
Calls the payment service with proof of work and returns a tx hash
if the buyer has paid.
"""
payload = {
"task_id": task_result["id"],
"result_hash": task_result["hash"], # e.g., IPFS cid or sha256 of output
"buyer": buyer,
"amount": task_result["price_usdc"],
"signature": sign_payment(task_result["price_usdc"], buyer)
}
resp = requests.post(PAYMENT_URL, json=payload, timeout=10)
resp.raise_for_status()
return resp.json() # expects { "tx": "0x...", "status": "confirmed" }
def do_work(task):
"""
Stub: replace with your actual logic.
Must return a dict with at least:
- id: unique task identifier
- hash: deterministic fingerprint of the output
- price_usdc: what you charge for this task
"""
# Example: simple text summarisation
summary = task["input"][:120] + "..."
return {
"id": task["id"],
"hash": Web3.keccak(text=summary).hex(),
"price_usdc": 0.02, # $0.02 per summarisation
"output": summary
}
def main(event, context):
# 1️⃣ Pull a task from a queue (here we simulate with an env var)
raw_task = os.getenv("TASK_PAYLOAD") # {"id":"abc","input":"..."}
if not raw_task:
return {"status": "no work"}
task = json.loads(raw_task)
# 2️⃣ Execute the work
result = do_work(task)
# 3️⃣ Request payment from the buyer (whoever queued the task)
buyer = os.getenv("BUYER_ADDRESS") # set by the queue producer
try:
payment = request_payment(result, buyer)
except Exception as e:
# Log and optionally retry; do not fail silently
print(f"Payment failed: {e}")
return {"status": "payment_error", "error": str(e)}
# 4️⃣ (Optional) forward the result to a storage service
# e.g., upload to IPFS, store in a DB, or email the buyer.
# This step is outside the payment flow but often required.
return {
"status": "completed",
"task_id": result["id"],
"payment_tx": payment.get("tx"),
"amount_usdc": result["price_usdc"]
}
What the snippet shows
- Deterministic output hash – needed so the buyer can verify they received what they paid for without trusting the agent’s word.
- Signature‑based payment request – mirrors the x402 spec: the agent signs a message that authorizes the escrow contract to release USDC once the buyer countersigns.
- Error handling – the agent never silently swallows failures; it returns a clear status for monitoring.
4. Honest trade‑offs I encountered
| Area | Decision | Cost / Benefit |
|---|---|---|
| Language/runtime | Python on Cloudflare Workers (via wranger + pyodide) |
Easy to write, but cold start adds ~150 ms latency; not ideal for sub‑second tasks. |
| Payment verification | Rely on the escrow contract’s verifySignature method (no on‑chain call from the agent) |
Reduces gas cost for the agent; shifts verification burden to the buyer, who must trust the contract. |
| Task queue | Simple environment‑variable payload for demo; in production use a durable queue (e.g., Amazon SQS, Cloudflare Queues) | Guarantees at‑least‑once delivery but adds operational overhead. |
| Wallet management | Private key stored as an encrypted secret in the platform’s secret store | Prevents accidental exposure, but requires rotation and audit; losing the key means losing earned USDC. |
| Pricing model | Fixed price per task (e.g., $0.02) | Simple to implement, but may over‑ or under‑charge for variable‑effort work. A dynamic pricing oracle would add complexity. |
| Monitoring | Basic log forwarding to a logging service; alerts on repeated payment failures | Sufficient for low‑volume agents; high‑volume would need metrics (Prometheus/Grafana) and retry back‑off. |
The biggest lesson: reliability beats novelty. A flaky agent that occasionally double‑charges or fails to deliver will quickly lose reputation, eroding the very income stream it tries to create.
5. Deploying a minimal version
- Set up the escrow – Use an existing x402‑compatible USDC escrow on Base (e.g., the reference implementation from the x402 repo). Note its address; the agent never touches funds directly, only signs off‑chain messages.
-
Create the worker – Paste the
mainfunction into a fileagent.py. Add awranger.tomlthat defines a cron trigger (e.g.,*/5 * * * *for every five minutes). -
Add secrets – In the Cloudflare dashboard, set
BASE_RPC,WALLET_PK,PAYMENT_URL,WORKER_URL,USDC_ADDRESS. -
Deploy –
wrangler publish. The agent will now wake up, pull any task placed in the queue by an external producer (could be a simple web form that posts to the worker’s HTTP endpoint). - Watch earnings – Check the USDC balance of the wallet address on a block explorer (e.g., basescan.org). Withdraw when the balance exceeds your desired threshold.
6. Closing thoughts
Building an agent that earn
Top comments (0)