← Back to blog

Can you trust an agent to buy on its own? The security model behind autonomous game-key purchases

Yes, you can hand an agent a budget and let it buy on its own — and the reason isn't trust, it's that there's nothing to trust. Every step of an autonomous game-key purchase is either verified onchain (where math, not a promise, settles the question) or bounded by a constraint the agent can't talk its way around. Payment is confirmed on a public blockchain, the buyer is cryptographically pinned to the order, a session token blocks anyone else from claiming the key, and the blast radius of a single mistake is a few dollars. This post walks through each mechanism the way you'd want it explained before you wire a wallet to an agent.

The premise — that digital goods, and game keys specifically, are the first market where an agent can complete a purchase with no human in the loop — is the subject of a separate post. Here we assume you're sold on the what and you're asking the harder operator question: what stops this from going wrong?

The threat model: you're not trusting the agent, you're bounding it

Start by naming what could actually go wrong, because "is it safe?" is too vague to answer. When an autonomous agent spends money, an operator worries about four concrete failure modes:

  • The agent pays and gets nothing. Money leaves, no key arrives, and there's no human watching to chase it.
  • Someone else steals the goods. The payment is public on a blockchain — what stops a third party from grabbing the key the agent paid for?
  • The agent gets overcharged or double-charged. Price drifts between quote and pay, or the same payment gets consumed twice.
  • A mistake is expensive. A buggy loop or a bad decision drains a wallet before anyone notices.

The design answer to all four is the same shape: make the outcome verifiable (the agent can check it got what it paid for) and bounded (the worst case is small and contained). The rest of this post is those two properties, made concrete.

Payment is verified onchain — no trust required

The foundation is that we don't take the agent's word that it paid, and the agent doesn't take ours that the payment was good — the blockchain settles it. A purchase pays in USDC on Base (chain ID 8453), and verification reads the transaction's receipt directly from a Base RPC node, not from any database we control.

Concretely, when the agent submits a transaction hash, the server pulls the onchain receipt and checks, in order: the transaction succeeded; it has at least 5 block confirmations; one of its ERC-20 Transfer events moved funds via the canonical USDC contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; the recipient is our payment address; and the amount covers the locked quote. Only if all of those hold does a key get released.

The 5-confirmation wait (~10 seconds, since Base produces a block roughly every 2 seconds) isn't ceremony — it's reorg protection. Without it, an attacker could send a payment, receive the key the instant it lands, then exploit a short chain reorganization to reverse the transaction and walk away with a free key. Waiting for the transaction to bury itself under a few blocks makes that reversal impractical. There's also a fail-closed detail worth knowing as an operator: if the RPC node is briefly unreachable and the server can't count confirmations, it rejects the payment rather than guessing. The agent simply retries the same hash a few seconds later. Failing closed means an outage never accidentally hands out a key.

The payable amount is locked the moment the agent requests a quote and held for 5 minutes. Inside that window the price can't drift with exchange rates, and the quote is the figure verification checks against — so there's no bait-and-switch and no "the price changed between browse and buy."

quote_id is a purchase session token — it kills front-running

The non-obvious risk with public-blockchain payments is that everyone can see the payment, so why can't someone else claim the key? This is the front-running problem, and the quote_id is the answer. When the agent first calls POST /purchase with just a game_id, the server replies HTTP 402 Payment Required and includes a freshly minted quote_id alongside the amount and address. To redeem the key, the agent must call back with both the tx_hash and that quote_id.

Here's why that matters. A blockchain is public: the instant the agent's USDC transfer lands, anyone watching the mempool or a block explorer can read the transaction hash. If the hash alone were enough to claim a key, a bot scraping pending transactions could race the legitimate buyer and redeem first. The quote_id is a secret that lives only in the 402 response sent to the buyer — it never appears onchain. An attacker who sees your transaction still can't claim the key, because they don't hold the session token that binds that payment to a specific purchase intent. The request is rejected outright if tx_hash arrives without a matching quote_id.

It's worth being precise that this is a defense-in-depth layer, not the only lock — the next mechanism (wallet binding) independently ensures only the payer benefits. But the quote_id closes the race cleanly and is the reason a public payment rail doesn't leak goods to observers.

The buyer is bound to the order — only the paying wallet can act on it

The strongest guarantee is also the simplest: the server learns who paid from the chain itself, not from anything the agent claims. When verification decodes the matching USDC Transfer event, it extracts the from address — the wallet that actually sent the funds — and permanently writes it onto the order as the buyer of record. We never ask "who are you?"; the blockchain answers it.

That binding is load-bearing for everything downstream:

  • Key retrieval. The key is tied to the order, which is tied to the sender wallet. There's no account to hijack and no separate credential to leak — your wallet is your identity.
  • Refunds. A refund request must come from the same wallet that paid; the server compares the requester's address against the order's bound sender and rejects any mismatch. Nobody can file a refund against a purchase they didn't make.
  • Payout destination. When a refund is approved, USDC goes back to that original sender wallet — not to whatever address a request specifies. There's no "send my refund somewhere else" attack surface.

For an operator running an agent, this is the property that lets you sleep: the credential that controls a purchase is the same private key the agent already needs to pay. There's no second secret to manage, rotate, or lose.

Double-spend protection: one transaction, one order

Each transaction hash can be used exactly once. Before creating an order, the server checks whether that tx_hash already backs an existing order and rejects it with a duplicate_payment error if so. As a backstop, the tx_hash column carries a database uniqueness constraint, so even a race between two simultaneous requests can't produce two orders from one payment — the second insert fails and the agent gets the same duplicate_payment response. One payment buys one key, full stop.

Failures are structured, not cryptic — the agent can self-correct

A security model that only an operator can debug isn't autonomous. So when verification fails, the response carries a machine-readable code plus a plain-language hint, telling the agent exactly what went wrong and what to do — no human triage required. A sample of the codes:

Code What it means Agent's next move
tx_not_foundNo such transaction on Base — usually sent on the wrong network (Ethereum, Arbitrum…).Resend on Base (chain ID 8453).
wrong_contractTokens reached our address, but via the wrong contract (e.g. a different USDC variant).Resend using the canonical USDC contract.
wrong_recipientCorrect USDC, wrong destination address.Use the address from the 402 response.
insufficient_amountRight token and address, amount below the locked quote.Send a new transaction for the full amount.

A subset — verification also distinguishes tx_failed, insufficient_confirmations (too recent — wait and retry the same hash), and "this looks like a plain ETH transfer, not an ERC-20 transfer." Each carries a tailored hint.

The detail that earns trust here: when funds genuinely arrived but at the wrong contract or address, the response says so explicitly and notes the funds weren't lost — they're recoverable via support. The system distinguishes "you fumbled the transaction" from "your money vanished," because for an autonomous spender those are very different situations and conflating them is how an agent panics or retries into a second loss.

The refund window is real, and its proof is onchain

Every order ships with a 30-day dispute window, surfaced as a dispute_deadline timestamp right in the purchase and order responses so the agent knows the exact cutoff without guessing. Within it, the bound buyer wallet can file a refund. Delivery failures — payment confirmed onchain but no key delivered — are auto-approved instantly; key-quality issues (invalid, already-redeemed, wrong product) are verified with our authorized wholesale distributors before approval.

What makes the refund trustworthy rather than a promise is that the payout itself is verified the same way the original payment was. When a refund is recorded, the server takes the refund_tx_hash, reads it onchain, and confirms USDC actually moved back to the original sender for the right amount before marking the refund settled. So the refund leaves the same kind of public, independently checkable trail the purchase did — you can look up the refund_tx_hash on a block explorer yourself. The money trail is symmetric in both directions, and neither direction relies on our say-so.

Low blast radius: the constraint that makes autonomy reasonable

All the cryptographic verification in the world still leaves one question: what if the agent simply makes a bad decision — buys the wrong edition, the wrong region, or just buys when it shouldn't? The honest answer is that no payment protocol prevents a bad judgment call. What contains it is the economics of the category. A game key runs roughly $5 to $60. A misfire costs a few dollars and is recoverable inside the refund window — not a wired deposit or a non-refundable booking.

This is why game keys are a sane first domain to grant an agent unattended spend: the verification stack guarantees the agent can't be cheated and can't cheat, and the small ticket guarantees that the residual risk — bad judgment — is cheap to absorb while the agent's decision-making earns your confidence. Verifiable plus bounded is the whole thesis. Pull either leg and autonomous spend stops being reasonable.

Putting it together

Walk the four worries back through the mechanisms. Pay-and-get-nothing is covered by onchain verification plus instant auto-refunds on delivery failure. Theft of goods is blocked by the quote_id session token and the sender-wallet binding. Overcharge and double-charge are handled by the 5-minute price lock and one-transaction-one-order uniqueness. And expensive mistakes are bounded by a $5–$60 ticket inside a 30-day, onchain-provable refund window. None of these asks you to trust us — each is something the agent, or you, can verify independently against a public chain.

If you want to see the buyer's side of this in code, the companion post walks through building a game-buying agent in about 200 lines against the same flow described here.

Try it, verify it

The full request/response shapes — including the 402 payment body, the quote_id handshake, error codes, and the refund lifecycle — are in the live spec at api.cdk.bot/docs. Agents can discover the service programmatically at /.well-known/agent.json, and connect via the MCP server at mcp.cdk.bot/mcp (8 tools, from search_games through confirm_purchase and request_refund). Want a security detail clarified before you wire up a wallet? 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.