← Back to blog

Building a game-buying AI agent in 200 lines with x402 + MCP

Today an AI agent can read your email, search the web, summarize a PDF, and write code. Ask it to buy you a video game and it stops. Not because models are incapable — they aren't — but because the entire commerce layer was designed for human checkout: shopping carts, address forms, 3-D Secure popups, captchas. Agents bounce off it.

This post is a working example of the opposite: an agent that buys any of 40,000+ video game keys autonomously, paying in stablecoins and other crypto (USDC, USDT, EURC) on Base chain via an x402-style payment flow. End-to-end. No accounts, no API keys, no human in the loop. Total agent code: about 200 lines.

You can clone the repo and run it end-to-end on Base Sepolia testnet today: github.com/cdkbotmcp/cdk-agent-example.

The two-step problem with agent commerce

Real commerce has two integrations: find the product and pay for it. Today's APIs solve neither well for agents.

Most catalog APIs assume a human shopper: paginated lists, marketing copy in titles, regional pricing surfaced via geo-IP. An agent that needs to pick the cheapest legitimate offer for a specific platform and region is left to scrape and reconcile mismatched data itself.

Payment is worse. Stripe and friends were built around forms, billing addresses, 3-D Secure flows. Agents can't do those. Crypto sidesteps them but typically requires a wallet UX humans approve. Agent-native commerce needs payment that an agent can complete with zero human interaction and that the merchant can verify deterministically.

Two pieces fix this:

  • An API designed for agents — structured filters, one product per request, live competitor prices included in the response so the agent can rank without scraping.
  • A payment protocol that lives in HTTPx402. Server returns HTTP 402 with payment instructions, client sends crypto, server verifies onchain, returns the product.

What x402 actually does

x402 is the cleanest piece of agentic infrastructure we've seen in the last year. It's literally HTTP status 402 — "Payment Required" — combined with a structured response describing how to pay.

The fast path is a single call. POST /purchase with an X-PAYMENT header — a base64-encoded EIP-3009 "exact" transferWithAuthorization signed by the buyer's wallet for the accepts[] the server advertises. A facilitator settles it on-chain gaslessly — the buyer pays no gas — and the server returns the product key in that same response. No tx_hash, no second round trip, no quote_id to thread through:

Agent                          Server                    Base chain
  │                              │                          │
  ├── POST /purchase ─────────→  │                          │
  │   { "game_id": "..." }       │                          │
  │   X-PAYMENT:    │  facilitator settles ──→ │
  │                              │  (gasless, on-chain)     │
  │←── 200 { key_code } ─────────┤                          │

One signature, one HTTP call, key in hand. The agent never broadcasts a transaction itself and never pays gas.

The two-step flow is the universal fallback — for wallets or agents that would rather send the USDC themselves. It's three HTTP calls and one onchain transaction:

Agent                     Server                   Base chain
  │                         │                         │
  ├── POST /purchase ─────→  │                         │
  │   { "game_id": "..." }   │                         │
  │←── 402 + pay details ────┤  price locked (5 min)   │
  │   address, amount,       │                         │
  │   token, chain, quote_id │                         │
  │                          │                         │
  ├── ERC-20 transfer ─────────────────────────────→   │
  │   USDC to address        │                         │
  │                          │                         │
  ├── POST /purchase ─────→  │                         │
  │   + tx_hash + quote_id   │  verify on-chain ────→  │
  │                          │  (5 block confirmations) │
  │←── 200 { key_code } ─────┤                         │

No accounts. No webhooks. No callback URLs. The price is locked by quote_id for 5 minutes so it can't drift between browse and pay. The server extracts the sender wallet from the Transfer event and binds it to the order — so only the wallet that paid can fetch the key or request a refund.

A note for x402 purists: the one-shot above is the canonical x402 "exact" scheme — the client signs an EIP-3009 authorization that a facilitator settles. The two-step fallback is the variant where the agent broadcasts the USDC transfer itself and we verify it onchain. Both share the same HTTP-402 shape and the same no-human-in-the-loop result; pick the one your wallet supports.

The agent code

If you just want the key, the cdkbot CLI (on npm) does the whole one-shot for you — it builds the signed authorization and posts it with the X-PAYMENT header, so a buy is a single command:

cdkbot buy <game_id> --key <privateKey>

Under the hood that's one POST to /purchase carrying the signed EIP-3009 authorization, and the key comes back in the response. To wire the same one-shot into your own agent, the core is a single call:

def buy_oneshot(game_id: str, wallet) -> str:
    # 1. Get the 402 + its accepts[] (what to sign)
    quote = requests.post(f"{API}/purchase",
        json={"game_id": game_id})
    accepts = quote.json()["accepts"][0]

    # 2. Sign an EIP-3009 transferWithAuthorization for it
    x_payment = build_x_payment(wallet, accepts)   # base64 signed auth

    # 3. Post it back with X-PAYMENT — facilitator settles gaslessly,
    #    key returns in the same call. No tx_hash, no quote_id.
    result = requests.post(f"{API}/purchase",
        json={"game_id": game_id},
        headers={"X-PAYMENT": x_payment}).json()
    return result["key_code"]

The two-step fallback — broadcast the USDC yourself, then confirm with tx_hash — is just as short. The Python version of agent.py is below; minus boilerplate, this is the entire fallback flow:

def buy(title: str) -> str:
    # 1. Find best product match
    product = requests.get(f"{API}/games/match",
        params={"title": title, "platform": "Steam"}).json()["best_match"]

    # 2. Get price quote (HTTP 402)
    quote = requests.post(f"{API}/purchase",
        json={"game_id": product["id"], "chain": "base-sepolia"}).json()

    # 3. Send USDC on-chain
    tx_hash = send_usdc_transfer(
        to=quote["payment"]["address"],
        amount=quote["payment"]["amount"],
    )

    # 4. Confirm + receive key
    result = requests.post(f"{API}/purchase", json={
        "game_id": product["id"],
        "tx_hash": tx_hash,
        "quote_id": quote["quote_id"],
        "chain": "base-sepolia",
    }).json()
    return result["key_code"]

That's it. Wrap it in asyncio or hand it to a tool-calling LLM and you have an agent that buys games. The TypeScript version is structurally identical and uses viem for the onchain transfer.

Three things that mattered more than we expected

1. Live competitor prices in the catalog response

Every GET /games/{id} response includes a marketplace_offers array — the cheapest current offer from major keyshops, in USD. Why ship competitor prices in our own response? Because an agent's first instinct is "is this a good price?" and without an answer it'll go scrape. Saving the agent that round trip is the difference between integration in 10 minutes and integration in two weeks.

The downstream effect: agents that integrate CDK Bot can rank by price and seller trust across the whole market with one HTTP call. That's a feature that's almost impossible for a human-facing site to offer, because no human shopping site wants to surface its competitors. An agent-native API has different incentives.

2. Price locking via quote_id

Wholesale game prices change. If an agent quotes $24.99, sends $24.99 in USDC, and the price ticks to $25.49 between those two events, who eats the difference? With a quote_id issued on the 402 response and a 5-minute TTL, the answer is "neither, because the price is locked." Without it, every onchain confirmation becomes a haggling session.

3. The sender-wallet-binds-order rule

We extract the from address from the ERC-20 Transfer event and permanently bind it to the order. The key can only be retrieved by that wallet. Refunds can only be requested by that wallet. This kills an entire class of front-running attack: even if an attacker sees the tx_hash onchain, they can't claim the key because they don't have quote_id and they aren't the sending wallet.

Zero-code option: Claude Desktop + MCP

Not everyone wants to write 200 lines of Python. The same flow is wrapped in an MCP (Model Context Protocol) server with 8 tools, so you can just paste a config into Claude Desktop and chat with the catalog:

{
  "mcpServers": {
    "cdk": {
      "url": "https://mcp.cdk.bot/mcp",
      "transport": "streamable-http"
    }
  }
}

Restart Claude. Type "search for Hollow Knight Silksong and quote me a price." Claude calls search_games (the MCP equivalent of the REST /games/match call above) → get_price_quote natively. The same config works in Cursor and Cline. Zero install, no API keys.

What this unlocks

We think the more interesting question isn't "how do you write an agent that buys a game" — it's what new things become possible when commerce is HTTP-native and agent-readable. (We argued the bigger picture separately: why agents won't buy pizza first.)

  • A holiday-gift agent that buys 12 different Steam keys for your friends and emails them the codes — runs unattended overnight.
  • A market-watch agent that monitors price drops on a wishlist and buys when a threshold is hit.
  • A travel-companion agent that buys you a Netflix or Spotify gift card in the local currency when you land in a new country.
  • A budget-coach agent that pools spare USDC across multiple agents you run, deploying it against pre-approved categories.

None of these existed two years ago because the payment and product layers couldn't be agent-driven. They can now. The infrastructure is small enough — one signed call for the gasless one-shot, or three HTTP calls for the two-step fallback — that you can build them in a weekend.

Try it

Repo with Python + TypeScript + MCP examples: github.com/cdkbotmcp/cdk-agent-example. Defaults to Base Sepolia testnet so you can run end-to-end without spending real money — testnet USDC is free from Circle's faucet.

API docs and live OpenAPI spec: api.cdk.bot/docs. The agent discovery card lives at /.well-known/agent.json so other agents can find us programmatically.

Questions, weird use cases, requests for new product categories — info@cdk.bot.


Want to build on CDK Bot? Start with the cdk-agent-example repo or read the API docs. Questions: info@cdk.bot.