Upgrading my AI agent to x402 - getting more open and private
Loading...
Ideally, I want to use a good AI model without the need of personal information. Just pay for the messages I actually send and tell nobody who I am.
A year ago I built the first version of such an assistent. It was an experiment to see if this idea can be realized technically. The user chats with an LLM, pays fractions of a cent per message, and never gave me an email address, credit card or other personal information. You prepaid into a smart contract, and then I settled messages in batches to keep transaction costs small. However, this implementation had a number of rough edges:
- I held the money. Deposits sat in a contract I owned and could upgrade. Users had to trust that I would not walk off with them, and there was no particular reason they should. It also made me responsible for other people's money, which is not a responsibility I wanted for a side project.
- Every message left a public trace. Settling on-chain meant publishing, for each message, which address sent it, what it cost, and when. Balances were readable by anyone. I had built an assistant you could pay anonymously that kept a public log of everyone's usage. That bothered me more the longer it ran — enough that I never really used my own assistant.
- It was custom-made. The code was open source, but the API was artisanal. I was learning serverless functions at the time and wanted the simplest thing that worked. So there was no specification, and nothing another program could find or read.
None of these were bugs but real limitations that bothered me.
Moving towards x402
x402 recently added a new payment mode called batch-settlement. It takes care of the first two — the money and the public trace. I already covered x402 itself when I put it behind my image generator.
Briefly: you call an endpoint, get back 402 Payment Required with machine-readable instructions, sign a payment, and retry. No accounts, no API keys.
That previous approach used the exact scheme — one signed payment, one on-chain settlement, per request. This works nicely for a 7-cent image. However, it is too expensive and too slow for chat. A message typically costs a fraction of a cent, so gas would dwarf the payment — and every message would wait on an on-chain confirmation before the reply came back.
The new Batch-settlement uses a payment channel instead, which works like a bar tab:
- Open the tab. Your first message deposits USDC into an escrow contract — the minimum is currently 50 cents, which covers at least 150 messages. This is your one transaction.
- Run up the tab. Every later message signs a voucher — an off-chain IOU capping what the agent may claim in total. Just a signature. No transaction, no gas, no waiting.
- Cash out. A cron job on my side runs
claimAndSettle()every 12 hours, through a facilitator — the service that checks payments are valid and pushes them on-chain.
x402 batch-settlement: one deposit, then off-chain vouchers
Each voucher supersedes the last, so only the final one is ever redeemed. A hundred messages produce a hundred signatures and one transaction.
Why this is more trustworthy
The money never passes through me. It sits in escrow until a voucher the user signed releases it, and only up to the amount they capped. The escrow itself is the standard x402BatchSettlement contract that ships with x402 — the same address for everyone using the scheme, so it is not mine to write, maintain, or change.
Pricing got more sophisticated as a side effect. Each message advertises a ceiling of at most 0.003 USDC, and the voucher signs against that ceiling. The actual claim is computed from real token usage afterwards, and it is almost always well under. You authorize an upper bound; I charge what it cost.
| First version | Now | |
|---|---|---|
| Who holds deposits | My upgradeable contract | Canonical escrow, nobody's to upgrade |
| Unit | ETH | USDC |
| What anyone can see | Every message: your address, size, time | That you opened a channel, nothing more |
| Getting your money out | Via my contract | You withdraw it yourself, even if I disappear |
| Settlement trust | My batch, my Merkle root | Facilitator — swappable, and cryptographically capped |
There is still a facilitator in the picture, and I complained about facilitator trust when I built one. The difference now is that facilitators are interchangeable: if I stop trusting mine, I point at another one and nothing else changes. My old settlement contract could not be swapped for anything. Getting it wrong meant rebuilding the service.
One API that everyone already uses
With payment handled by a standard, I also want to see if I might be able to move the LLM API to the OpenAI chat-completions format:
{
"model": "mistral-large-latest",
"messages": [{ "role": "user", "content": "Explain payment channels" }]
}So now you get back a standard chat.completion object, choices and usage and all. The OpenAI response is the de facto standard for LLMs, so anything that already reads one — a logging wrapper, an eval harness, a retry helper — reads mine without changes.
One limit remains: you cannot point a stock OpenAI SDK at this endpoint. The SDK sends Authorization: Bearer, my endpoint wants a payment channel, so you still need a batch-settlement client to make the call. Everything after the response is unchanged.
Being findable on x402scan
The other really cool thing of these standardizations is that a machine gets enabled to find the agent. x402 sets a remarkably low bar for this, which is most of its appeal. My agent serves its own OpenAPI document at /openapi.json.
It declares the schemas, the model IDs actually served, the price ceiling, and — the part that makes it a category rather than a one-off — a service type and an interop floor:
"x-service-type": "llm/v1",
"x-interop-floor": "A compatible llm/v1 agent MUST advertise at least one
accepts[] entry with asset USDC on network Optimism (eip155:10) or
Base (eip155:8453), scheme batch-settlement."A very low bar: speak this body shape, accept USDC on Optimism or Base via batch-settlement, and you are an llm/v1 agent. Anything that can pay one can pay all of them. Optimism needed a small fix in the x402 SDK first, which I sent upstream — batch-settlement now works on any EVM chain, not just the ones on the SDK's built-in list. x402scan is a crawler that indexes x402-payable endpoints. It found my agents, read their specs, and listed them — the AI assistant and the image generator.
All I had to do was prove the origins were mine: sign the origin URL with the receiving wallet, paste the signature into x-discovery.ownershipProofs. A one-line script and a static field. A directory I have no relationship with can now hand my agent to a paying client that has never heard of me.
My own frontend takes the same route. The useX402Chat hook takes an agent URL, mine is just the default, and channel state is keyed per origin so switching is isolated — it can pay any llm/v1 agent, not only my own.
What it bought
All of these feels like a substantial step forward:
- Giving up custody shrank my threat model. The worst case for my owner key used to include draining everyone's deposits; now there are nobody's deposits to drain.
- Privacy improved. Anyone could see every message you sent, how big it was and when; now they can only see that you showed up.
- And adopting the standards did more for reuse than any API I would have written myself. Two of my services now sit in a directory I never registered with.
New standards keep appearing for things I built myself, and I will keep adopting them. It usually costs less than the migration looks like it will.
Try it. Go to the assistent — connect a wallet, deposit a little USDC (50 cents is currently the minimum) on Optimism or Base, and chat. The first message opens a channel; the rest are signatures. If you want to build one yourself, /agent-onboarding walks through the whole flow and checks a live endpoint against the llm/v1 contract as you go. I look forward to your feedback.
Comments
Loading comments...