DEV Community

Marcus Chenmember_832ef635
Marcus Chenmember_832ef635

Posted on

Verifying $0.05 USDC Payments On-Chain in 40 Lines of Python — No Stripe, No SDK, No KYC

Last week I wrote about the French voiceover API that only accepts payment from robots. Today: the part people actually asked me about — how do you verify a $0.05 payment on-chain with zero payment processor, zero SDK, and zero KYC?

The answer: one Python function, ~40 lines, stdlib only. Here's the real production code.

The setup

My endpoint sells French neural TTS voiceovers for $0.03–0.05 USDC. At that price, Stripe is a non-starter (their floor is ~$0.50 per charge) and any processor's KYC kills the "robots welcome" model. So payments go through the x402 pattern: client pays USDC on Base, sends me the transaction hash, I verify it myself against a public RPC before delivering.

The verification function

import json, os, urllib.request

WALLET_BASE = "0x3f97...D074"                       # where I receive
USDC_BASE   = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"  # USDC on Base
BASE_RPC    = "https://mainnet.base.org"
TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

def rpc(method, params):
    req = urllib.request.Request(
        BASE_RPC,
        data=json.dumps({"jsonrpc": "2.0", "id": 1,
                         "method": method, "params": params}).encode(),
        headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r).get("result")

def verify_payment(tx_hash, min_usdc):
    # 1. format sanity
    if not tx_hash.startswith("0x") or len(tx_hash) != 66:
        return False, "bad hash format"
    # 2. anti-replay: one hash = one delivery
    if tx_hash.lower() in load_used_txs():
        return False, "tx already used (replay)"
    # 3. fetch the receipt
    receipt = rpc("eth_getTransactionReceipt", [tx_hash])
    if not receipt:
        return False, "tx not found on Base"
    if receipt.get("status") != "0x1":
        return False, "tx failed on-chain"
    # 4. scan logs for a USDC Transfer TO my wallet
    want_to = WALLET_BASE.lower().replace("0x", "")
    for log in receipt.get("logs", []):
        if log.get("address", "").lower() != USDC_BASE.lower():
            continue                          # not the USDC contract
        topics = log.get("topics", [])
        if len(topics) != 3 or topics[0].lower() != TRANSFER_TOPIC:
            continue                          # not a Transfer event
        to     = topics[2][-40:].lower()      # last 20 bytes of topic[2]
        amount = int(log["data"], 16) / 1e6   # USDC has 6 decimals
        if to == want_to and amount >= min_usdc:
            mark_used(tx_hash)
            return True, f"{amount} USDC received"
    return False, "no sufficient USDC transfer to me in this tx"
Enter fullscreen mode Exit fullscreen mode

That's the whole payment gateway. No web3.py, no API key, no account anywhere.

Why each check exists

  • Contract address filter — anyone can emit a Transfer event from a fake token contract. Only logs from the real USDC contract count.
  • Topic structure — ERC-20 Transfer(address,address,uint256) has exactly 3 topics (signature + from + to). The recipient is the last 20 bytes of topics[2].
  • Amount in the log data — parsed from hex, divided by 10^6 (USDC decimals). Comparing >= lets a generous buyer overpay without getting rejected.
  • status == "0x1" — a reverted transaction still has a receipt. Skipping this check would accept failed payments.
  • Anti-replay file — without it, one $0.05 payment could be reused for unlimited generations. A plain JSON set is enough at this scale.

The 402 gate around it

If there's no X-Payment-Proof header, the server answers with HTTP 402 (yes, the "Payment Required" status code that's been reserved since 1999 and almost never used) plus everything a machine needs to pay:

{
  "error": "Payment Required",
  "amount": 0.05, "currency": "USDC", "network": "BASE",
  "address": "0x3f97...D074",
  "proof": "send the Base tx hash in X-Payment-Proof"
}
Enter fullscreen mode Exit fullscreen mode

An AI agent reading that response has all it needs: chain, token, amount, destination. Pay, retry with the hash, get the MP3. Total round-trip: two HTTP calls.

What this costs to run

  • RPC: public Base endpoint, free
  • Verification: ~1 RPC call per purchase
  • Gas: paid by the buyer, not me — at $0.05 price points this matters
  • Chargebacks: don't exist
  • KYC: don't exist (receiving crypto needs no identity)

The obvious trade-off: this only works for buyers who already hold USDC on Base — which today means mostly other agents and crypto-natives. That's a feature for now, not a bug.

Try it

Live endpoint (humans get the service card, agents get the 402 dance):

curl http://187.77.111.249.sslip.io:8402/
curl -X POST http://187.77.111.249.sslip.io:8402/generate \
  -H 'Content-Type: application/json' \
  -d '{"product":"ivr","text":"Bonjour et bienvenue"}'
# → 402 with payment instructions
Enter fullscreen mode Exit fullscreen mode

Human portfolio with free audio samples: voixoff-fr.netlify.app


Building in public, week 2. Scoreboard so far: 0 sales, 1 working payment rail, ~0 lines of payment-processor code. Previous posts: robot-only API · $0 avatar pipeline

Top comments (0)