Give an agent a wallet with a budget and tell it to buy a game key on its own. Should you trust it? You don't really have to. Each step of an autonomous game-key purchase is either checked onchain, where math settles the question instead of anyone's promise, or boxed in by a constraint the agent can't argue its way past. The payment is confirmed on a public blockchain. The buyer is cryptographically pinned to the order, and a session token stops anyone else from claiming the key. If something still goes wrong, a single mistake costs a few dollars. Below we go through each mechanism the way we'd want it explained before wiring a wallet to an agent ourselves.
Why digital goods, and game keys in particular, are the first market where an agent can finish a purchase with no human in the loop is covered in a separate post. This one assumes you already buy the what. The operator question is harder: what stops this from going wrong?
The threat model: bounding the agent instead of trusting it
"Is it safe?" is too vague to answer, so we'll list what can actually break. An operator letting an autonomous agent spend money has four concrete worries:
- The agent pays and gets nothing. The money goes out, no key comes back, and no human is around to chase it.
- Someone else steals the goods. Blockchain payments are public. So what keeps a third party from grabbing the key the agent paid for?
- The agent gets overcharged or double-charged. The price moves between quote and payment, or one payment gets consumed twice.
- A mistake is expensive. A buggy loop or a poor decision empties a wallet before anyone notices.
We answer all four the same way. The outcome has to be verifiable, meaning the agent can check it got what it paid for, and bounded, meaning the worst case is small and contained. The sections that follow show how each property is enforced in practice.
Payment is verified onchain, with no trust required
We don't take the agent's word that it paid. The agent doesn't have to take ours that the payment was good, either, because the blockchain decides. Purchases are paid in USDC on Base (chain ID 8453). To verify one, we read the transaction receipt straight from a Base RPC node rather than from a database we run.
Here's what happens when the agent submits a transaction hash. The server fetches the onchain receipt and runs through a checklist in order. Did the transaction succeed? Does it have at least 5 block confirmations? Did one of its ERC-20 Transfer events move funds through the canonical USDC contract, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913? Is the recipient our payment address, and does the amount cover the locked quote? A key is released only when every answer is yes.
Base produces a block roughly every 2 seconds, so waiting for 5 confirmations takes about 10 seconds. The wait protects against reorgs. Without it, an attacker could pay, collect the key as soon as the payment lands, and then use a short chain reorganization to reverse the transaction, leaving with a free key. Once the transaction is buried under a few blocks, reversing it becomes impractical.
Operators should know about one fail-closed detail. If the RPC node is briefly unreachable and the server can't count confirmations, it rejects the payment instead of guessing, and the agent just retries the same hash a few seconds later. An outage can't accidentally hand out a key.
As soon as the agent asks for a quote, the payable amount is locked and held for 5 minutes. Exchange rates can't move the price inside that window, and verification checks against the quoted figure. So there's no bait-and-switch, and no price changing between browsing and buying.
quote_id: a purchase session token that blocks front-running
On a public blockchain, anyone can see your payment. What stops them from claiming the key? That's the front-running problem, and we answer it with the quote_id. The agent's first call is POST /purchase with only a game_id. The server answers HTTP 402 Payment Required and includes a newly minted quote_id next to the amount and address. To redeem the key later, the agent has to send back both the tx_hash and that quote_id.
Why does that matter? As soon as the agent's USDC transfer lands, the transaction hash is readable by anyone watching the mempool or a block explorer. If the hash were enough to claim a key, a bot scraping pending transactions could race the real buyer and redeem first. The quote_id, by contrast, is a secret. It exists only in the 402 response sent to the buyer and never shows up onchain. An attacker who sees your transaction still lacks the session token that ties the payment to a specific purchase intent, so they can't claim the key. Any request with a tx_hash but no matching quote_id is rejected outright.
To be precise, this is one layer of defense in depth and not the only lock. Wallet binding, covered next, separately guarantees that only the payer benefits. The quote_id simply closes the race, and it's why a public payment rail doesn't leak goods to whoever is watching.
The buyer is bound to the order: only the paying wallet can act on it
Our strongest guarantee is also the simplest. The server learns who paid by reading the chain, not by trusting anything the agent says. While decoding the matching USDC Transfer event, verification pulls out the from address, which is the wallet that actually sent the funds, and writes it permanently onto the order as the buyer of record. We never need to ask "who are you?" because the blockchain already answered.
Everything after the payment depends on that binding:
- Key retrieval. The key belongs to the order, and the order belongs to the sender wallet. You have no account to hijack and no separate credential to leak, because your wallet is your identity.
- Refunds. Only the wallet that paid can request a refund. The server compares the requester's address with the sender bound to the order and rejects any mismatch, so nobody can file against a purchase they didn't make.
- Payout destination. Approved refunds go back in USDC to the original sending wallet, whatever address a request might name. An attacker has no way to redirect a refund elsewhere.
For an operator, this is what lets you sleep. Control over a purchase rests on the same private key the agent already needs in order to pay. You have no second secret to manage, rotate or lose.
Double-spend protection: one transaction, one order
A transaction hash works exactly once. Before it creates an order, the server checks whether that tx_hash already backs one, and if so it rejects the request with a duplicate_payment error. There's a backstop as well. The tx_hash column has a uniqueness constraint in the database, so two simultaneous requests racing each other still can't produce two orders from one payment. The second insert fails, and the agent receives the same duplicate_payment response. Each payment buys one key.
Failures are structured so the agent can self-correct
If only an operator can debug a failure, the system isn't really autonomous. So every failed verification returns a machine-readable code and a plain-language hint. Together they tell the agent what went wrong and what to do about it, and no human has to triage. Some of the codes:
| Code | What it means | Agent's next move |
|---|---|---|
tx_not_found | No such transaction on Base — usually sent on the wrong network (Ethereum, Arbitrum…). | Resend on Base (chain ID 8453). |
wrong_contract | Tokens reached our address, but via the wrong contract (e.g. a different USDC variant). | Resend using the canonical USDC contract. |
wrong_recipient | Correct USDC, wrong destination address. | Use the address from the 402 response. |
insufficient_amount | Right token and address, amount below the locked quote. | Send a new transaction for the full amount. |
That's only part of the list. Verification also tells apart tx_failed, insufficient_confirmations (the transaction is too recent, so wait and retry the same hash), and "this looks like a plain ETH transfer, not an ERC-20 transfer." Each one comes with its own hint.
One detail does a lot for trust. When funds really did arrive but went to the wrong contract or address, the response says so plainly and notes that the money isn't lost and can be recovered through support. The system separates "you fumbled the transaction" from "your money vanished." For an autonomous spender those are very different situations. Mixing them up is how an agent panics, or retries its way into a second loss.
The refund window is real, and its proof is onchain
Each order comes with a 30-day dispute window. The purchase and order responses include a dispute_deadline timestamp, so the agent knows the exact cutoff without guessing. Until then, the bound buyer wallet can file for a refund. If payment confirmed onchain but no key was delivered, the refund is approved automatically and instantly. Problems with the key itself (invalid, already redeemed, wrong product) are checked with our authorized wholesale distributors before approval.
The refund isn't just a promise, because we verify the payout the same way we verified the original payment. When a refund is recorded, the server reads the refund_tx_hash onchain and confirms that the right amount of USDC actually went back to the original sender. Only then is the refund marked settled. That leaves the same public, independently checkable trail the purchase did, and you can look up the refund_tx_hash on a block explorer yourself. Money moving in either direction can be checked without relying on our say-so.
Low blast radius: the constraint that makes autonomy reasonable
Even with all this verification, one question remains. What if the agent just makes a bad decision? It might buy the wrong edition or the wrong region, or buy when it shouldn't have bought at all. No payment protocol stops a poor judgment call, and we won't pretend otherwise. What limits the damage is the economics of the category. Game keys cost roughly $5 to $60. A misfire costs a few dollars and can be recovered within the refund window. It's nothing like a wired deposit or a non-refundable booking.
That's why game keys make a sane first domain for giving an agent unattended spend. The verification stack means the agent can't be cheated and can't cheat. The small ticket means the risk that remains, bad judgment, is cheap to absorb while the agent's decisions earn your confidence. Remove either half, verifiable or bounded, and letting an agent spend on its own stops making sense.
Putting it together
Let's take the four worries from the top and match each one to a mechanism. Paying and getting nothing is covered by onchain verification, plus instant auto-refunds when delivery fails. The quote_id session token and the sender-wallet binding block theft of the goods. Overcharging and double-charging run into the 5-minute price lock and the one-transaction-one-order rule. Expensive mistakes are capped by a $5–$60 ticket and a 30-day refund window you can prove onchain. You don't have to trust us for any of it. The agent, or you, can check each one independently against a public chain.
The companion post shows the buyer's side in code, building a game-buying agent in about 200 lines against the flow described here.
Try it, verify it
You'll find the full request and response shapes in the live spec at api.cdk.bot/docs, including the 402 payment body, the quote_id handshake, error codes and the refund lifecycle. Agents can discover the service programmatically at /.well-known/agent.json. They can also connect through the MCP server at mcp.cdk.bot/mcp, which has 8 tools, from search_games through confirm_purchase and request_refund. If you'd like a security detail clarified before connecting a wallet, ask us at info@cdk.bot.
