Over the last two weeks I kept running into AI agent payment projects: a pay-per-call facilitator, a swap API built for agents, a test suite for x402 payments, a guard that sits between an agent and its wallet, and a banking agent.
The payments worked in all five. The gap was somewhere else, in something the payment path trusted without checking, and it was never the model.
Every team took the feedback well and most shipped a fix within days, so this isn't about anyone being careless. It's a blind spot that's easy to have when the model gets all the attention.
Who decides where the money goes
An x402 facilitator live on mainnet gives each merchant their own vault contract, which pulls the payment and splits it in one transaction, the net to the merchant and the fee to the protocol.
To change where a vault pays, the merchant has to sign. That's the strong control. But the operator deploys the vault, and the first payout address comes from what was saved in the dashboard. The docs call that setting a preference, not an authority. You only see that it decides the first payments in the vault code, not in the docs.
So the weaker path picks where the money goes, and the merchant's signature only matters after that. If the operator key leaks, every merchant who hasn't been paid yet can get a vault that pays someone else.
The dev agreed and is fixing it so operator-created vaults always pay the merchant. If you ever fix something like this, remember the payout is written once, when the vault is created. A fix in the factory only covers new vaults. The existing ones keep whatever they were given, so you go through the creation events and ask those merchants to confirm.
If the address comes from the API, it's not a check
Then there was a free swap API and MCP server for agents. It asks several aggregators and bridges for quotes, ranks the routes and returns calldata, and the agent signs whatever comes back.
That makes the upstream APIs part of the agent's wallet. If one of them is compromised, it can hand back calldata that sends the swap to someone else, and every agent using the API signs it.
I suggested decoding the calldata on the server first: the router is on an allowlist, the recipient is the user, and the minimum output matches the quote. They shipped it within a day.
Then they hit the harder case, a bridge where the recipient is bound off chain. Their fix was good: fetch the order, recompute its id, check the recipient and the minimum amount, and require the deposit to go to the bridge's deposit contract. But the deposit contract address came from the same bridge API at runtime. A compromised API could list its own contract, and every order check would still pass, because the funds never reach the real one.
Pin addresses like that per chain in your config, and alert when the API disagrees.
"A payment exists" is not "this payment paid for this"
This one is an open-source CI suite that injects faults into x402 settlement on Solana, so a server can test how it handles bad payments. The author asked which checks were missing.
I'd start with a valid payment made for a cheaper route, or to a different recipient, replayed against a pricier route. A server that only checks that a settled payment exists will serve it.
They took it as tests for amount, recipient and resource substitution. Then I asked about the same amount paid in a different token, or on devnet, replayed against a mainnet route. Checks that only compare amount and recipient let that through. They shipped both as new checks in their next release.
The same thing comes up on the client side. In x402 the 402 response tells the agent which token contract and which network to pay on. So a spend limit that pins the recipient but not the asset and the network can be walked around by an endpoint that keeps the number the same and changes the token. And when a human approves a payment, they should see the exact requirements that get signed, not the agent's summary of them.
The signature your guard doesn't know about
An open-source guard for agent wallets holds unlimited approvals, Permit signatures and long expiries by default, which are the ones that usually bite.
The one missing was EIP-7702. An authorization is a signature over a chain id, an address and a nonce. It isn't typed data and it isn't a normal transaction, but it delegates the whole account to a contract, which is worse than any unlimited approval. And a chain id of 0 makes it valid on every chain.
The guard approved a type 4 transaction even when its authorization list delegated the account, and the framework plugin didn't look at the authorization list at all. The author fixed both and published within three hours.
He was also honest about the limit: the guard only sees what goes through the wrapped client. If the agent can reach the raw key, nothing on the client side sees the authorization. One thing a guard can still do on its own is read its account's code before each send. A delegated account returns 0xef0100 followed by the delegate's address. If that points somewhere you don't expect, stop signing.
The text the model still writes
The banking agent was already well built. The customer types the payee and the amount, and the confirm screen sits outside the chat, so the model can't misdescribe the transfer.
But the transfer note is still written by the model, and it leaves the bank and lands with someone outside. A model that read a malicious message can put the customer's balance or another account number in the note of a small transfer. It's the same problem as email tools where the recipient is pinned but the model writes the body.
The simplest fix I can think of is a note field on the confirm screen that the customer fills in. Then the model never writes it.
What I check now
These are the questions I ask about an agent payment design now:
- Who can set where the money goes, and in what order? If a weaker control decides first and the strong one only applies later, the weak one is the real control.
- Where does every address come from? If it comes from the same API you're checking, it's not a check. Pin it.
- What is the payment bound to? Amount, recipient, resource, token and network. Not just "a settled payment exists".
- Which signatures can the agent's key produce? Transactions, typed data, permits and EIP-7702 authorizations. A guard that only knows some of them isn't a guard for the others.
- What free text does the model still write that reaches someone outside? Notes, memos, email bodies, metadata. That's a way to send data out.
None of the fixes needed a smarter model.
If you're building agent payments, I'm curious which address in your flow still comes from an API you don't control.
I first wrote a short version of this on X.
I wrote this with help from AI for drafting and editing. The opinions and the review experience are mine.
Top comments (0)