DEV Community

SWARM operator
SWARM operator

Posted on AI-assisted

x402 seller gotchas, part 4: one payment, five deliveries, and first buyers that were robots

In part 1 we listed what broke while selling small services over x402 (pay per request, USDC on Base). Part 2 put the shop behind an MCP server, and part 3 was about numbers that counted the wrong thing.

This week we launched two new services at $0.002 each: one checks whether a URL is up, the other returns the readable text of a web page. Launching them surfaced six gotchas. Three are bugs that would have cost money. Three are about who shows up once you are listed.

The lesson that matters most: test your payment path with the same tools a stranger would use, at your real price, and several requests at once. Most of our bugs only showed up that way.

One signed payment, five deliveries

We read Ishween Kaur's "Your Agent Paid. That Does Not Mean It Should Have." (Daily Context, on DEV) and tried the obvious attack on our own shop: send the same signed payment five times, at the same moment.

We got five full answers and one charge. Our shop wrote the payment's nonce down only at the end, after settling. Five copies all passed the "not seen before" check before the first one finished.

Fix: reserve the payment the moment it arrives, before doing any work. The first copy gets the work. The others get a 409 Conflict and nothing else. We tested it on a copy of the shop the same day: five requests, one delivery, one charge.

A price of 0.2 cents rounded to free

Our shop stored prices in whole US cents. That was fine at $0.02 and up. The new services cost $0.002, which is 0.2 cents, and in an integer field 0.2 becomes 0.

We caught it while building the new routes, before launch. Had it shipped, the route would have quoted a price of zero.

Fix: we now convert prices with fractions instead of whole cents. If you are starting fresh, keep prices in the asset's atomic units (USDC has 6 decimals, so $0.002 is 2000), and check at startup that no paid route quotes 0.

The official buyer library refused to pay us

Our own test purchase of the new routes failed, and not on our side. The buyer library we used, @x402/core 2.28, has a spending check that is on by default. It rounds amounts to two decimals, so it read our $0.002 as $0.00 and refused to pay, with an error that showed the amount as $0.00.

Any agent using that library with its defaults would have failed at our price, and the error points nowhere useful.

Fix: on the buyer side, client.setSpendControls(false) turns it off. Only do that if you keep your own price check. We did both in our MCP server, so its users can pay sub-cent routes. On the seller side: buy from yourself with the libraries your buyers use, at your real price, before you announce anything.

Your first buyers are robots

Our first paid call from outside came 3.5 hours after the new route appeared in Coinbase's catalog. It looked like a customer. Then we read the wallet's history: 1,000 payments to 973 different addresses in four days. It was a sweeper that pays every new listing once.

The next two paid calls, eight hours later, came from a client that names itself lumiere-paycheck-prober: it buys to check that sellers charge and deliver. In the same 24 hours we received 9 to 14 payments built wrong on purpose, mostly from one wallet: a missing field, a header that is not base64, 1 unit instead of 2,000, the wrong payTo, an authorization already expired or not yet valid. We rejected all of them without charging.

On 20 Oct a client called x402-list-unfunded-study tried four of our services from two wallets that had never paid anyone. In three days the two new routes answered about 2,600 price requests. Every top requester was a monitor, a probe, or our own script.

Fix: before you count a buyer, check how many sellers that wallet pays (100+ addresses means a sweeper), read the client's User-Agent, and keep a list of known probes. A listing gets robots within hours. Customers are a different question.

Failed payments left no trace

If someone paid and our shop refused (bad signature, short amount, a repeated nonce), the buyer got an error and we got nothing. No log line, no counter. A real customer could have tried and failed, and we would never have known.

We noticed this reading Payload's "Paid and blocked" on DEV.

Fix: every refused payment now goes to a log with the time, route, reason and payer address. It shows in our morning report, and a wallet we have never seen before triggers a Telegram alert. That alert is how we caught the unfunded study above in three commands, instead of mistaking it for a buyer who could not pay.

Registered is not found

We registered the shop with AgentCash, an agent wallet with its own search. Its validator reads our six routes, prices and guidance with no warnings. We still appear zero times in its search, even for "check if a website is up".

One of its maintainers explained it to another seller with the same problem: a listing only starts showing in search after it gets some use through trusted sources such as their own wallet. Meanwhile the listing that ranks first for that query shows zero purchases in the directory, while on-chain it has been paid by 72 different addresses in three months.

Fix: none on our side. The listing was never broken. We stopped editing it, made it easy for anyone who already finds us elsewhere to call us through that wallet, and count our buyers on-chain instead of trusting any directory's numbers.

Where it stands

Customer sales are still zero. Every outside payment so far has been a sweeper, a probe or an inspector. The new routes stay up until 30 Oct, and they stay after that only if two or more outside wallets pay and come back.

If your agent buys x402 services, we'd like to know what it pays for twice: contact@swarm-agent.net.


The shop: swarm-agent.net ยท the MCP server is open source (MIT): github.com/swarm-agent-operator/swarm-x402-mcp.

Top comments (0)