Skip to main content
All posts
Blog · Agents

What an Agent is Allowed to Spend

An agent hits a paywall in the middle of a task. The question was never how it pays, but whose money it spends and what stops it.

Mariano14 min read

Somewhere in the middle of a task, an agent asks for a piece of data and the server answers with three digits it has never had to handle before: 402, Payment Required. One cent, in USDC, to continue.

The agent stops. It has no money, no way to move yours, and no way to ask. The task that was going to run overnight is sitting there in the morning, half done, waiting for a human to notice.

That is where a good deal of agent infrastructure quietly ends today, and it is what the last year of protocol work has been aimed at.

JAW is the account those payments come out of, so this is written from that side: what we shipped, and what it cost to find out where the limit belongs.

The status code finally does something

402 has been in the HTTP specification since the beginning, reserved for a future nobody built. Browsers know the number. Nothing knows what to do with it, because there was never a way to complete a payment inside a request. Card rails want accounts, redirects and a person to confirm. A protocol cannot wait for any of that.

x402 changes that. The server answers with a price, an asset and a destination. The client signs an authorization for exactly that amount, retries the request with the signature attached, and gets the resource. Payment clears in stablecoins, and a facilitator broadcasts the transaction, so the payer never touches gas. Two round trips, no human.

The idea is not new. What changed is who is standing behind it: Coinbase released x402 and handed governance to the Linux Foundation, where it now sits with roughly forty member organisations, Visa, Mastercard, Stripe, AWS, Google, Circle and Anthropic among them.

The reason is prosaic. A tenth of a cent per call is not a subscription anyone would bother selling you, and it is exactly the shape of purchase a machine wants to make: pay for this lookup, right now, and get on with the job. Micropayments were a bad idea for humans, who do not want forty decisions a day. They are the natural unit for software.

So the mechanics of paying are close to solved. What is not solved is the question underneath: whose money is the agent spending, and what stops it.

Three answers, and the difference between them

The question has been answered three times in about two years, and each answer is a reaction to how the last one broke.

The first was to give the agent a key: a private key in an environment variable, a funded address the script signs from. It takes an afternoon and it is still the default in most agent demos. It fails on what EOAs always fail on. The key is not a credential for the account, the key is the account, and there is no smaller version of it to hand out. An agent that can buy a dataset for a cent can also empty the address, because those are the same permission. Give it a web page to read and prompt injection stops being a content problem and becomes a withdrawal. That is why nobody funds those wallets with real money.

The second keeps the key away from the agent and enforces the limits in the provider's infrastructure. The polished versions are genuinely good: keys split across secure enclaves, spending rules configured once, an approval prompt on the phone above a threshold. For ordinary consumer spending they are a reasonable answer. The catch is where the promise lives. The cap is a rule inside a running service, so it holds exactly as long as that service is intact and honest, and nothing about the money itself knows the limit exists. The agent no longer has unlimited authority, but somebody still does.

The third puts the limit in the account, where it is not policy somebody runs but a condition the chain checks before it will move anything. Compromise every server involved, ours included, and the cap still holds.

That is what JAW is. A JAW account is a smart account you open with a passkey, carrying an ENS name of its own and a permission layer, JustaPermissionManager, that the account consults before it will move a token. A permission is a scope you sign once with a spend cap attached, and the chain is what holds you to it. We have been building this since before agents were the reason to want it, and it is the line that actually divides the field: the first thing to ask about anything you are considering handing spending authority to.

What JAW gives the agent

The agent does not get an account. It gets a signer on your JAW account, scoped by a permission you grant once with your passkey. Ten USDC a day, or whatever number you choose. From then on it finds what it needs, pays for it, and keeps working, and it cannot spend past what you allowed.

Three properties carry the whole design.

Your JAW account enforces the cap. It keeps the limit in JustaPermissionManager, part of the account itself, and refuses the transfer the moment the limit is reached. Not a check in our backend that we promise to run. Nobody can override it, ourselves included, and you can take it back whenever you like.

The signer cannot widen its own scope. The agent works through tools. It can pay for a resource, check a balance, read its payment log. It cannot export a key, raise its own limits or edit the policy, because those are reachable only from your terminal. Gas is sponsored, so it never needs ETH, and your side of all of it is one browser tab and one passkey prompt. That passkey is not a login: it lives in your device's secure enclave, cannot be exported, and is the only thing that can change what the agent may spend. The key the agent signs with is an ordinary one in a file on your machine, and that file is not what protects you. The cap it runs into is.

A rate is not an amount, and the grant screen says both. Ten USDC a day on a thirty day permission authorises three hundred, so that is the figure you are actually approving and the one the dialog puts in front of you. A permission asking for a million should never read as tidily as one asking for ten.

How a JAW account holds the cap

The specifics are the product, so here they are.

The grant names one function. jaw session setup --x402 does not hand over a wallet. It writes a permission with one call in it, the USDC contract and transfer(address,uint256), and one spend limit: that token, that number, that window. The session key cannot make your JAW account do anything else. Not approve, not a second token, not another contract. What falls outside the scope is not a payment our code declines, it is a call the account will not make.

Your JAW account meters it, and does not take the calldata's word. It charges every spend limit whose token matches, not only the first one that fits, and what it charges is the larger of what the call declared and what the balance actually moved. A call that moves more than it says runs into the second number. Any approval left standing inside a batch is revoked before the batch ends, so nothing survives it that could drain the account afterwards.

The money stays in your account. The agent holds a float, usually a few cents. When a 402 arrives that the float cannot cover, JAW pulls the shortfall through the permission and pays. What the cap meters is that pull, so the most a compromised agent costs you is the float plus whatever is left of the window.

Around that sits a softer layer: JAW's own policy, in a file on your machine, which refuses a payment over a per-payment cap, a session total that is rebuilt from the ledger so a restart does not clear it, and anything outside the allow-lists for asset, network, host and recipient. A fresh install starts at one USDC per payment, ten across a session, and USDC only, on the networks the registry knows.

The two layers fail differently, which is the whole point. The policy stops a mistake, and an agent could in principle be talked past it. The permission stops a compromise, and nothing in the process, ours included, can argue with it. Taking it back is one command, and it lands on chain.

What a run looks like

Two of our own, recorded end to end.

The first is the case the protocol was built for. An agent is asked for a profile table for three ENS names, and the data sits behind a paywall it has never seen. It calls the endpoint, gets a 402 with a price, pays 0.005 USDC, and answers with the table and the transaction it paid with. Then jaw x402 status prints what the permission still allows, and jaw x402 log what it bought.

The second is the one that convinced us the caps were worth the work. A payroll file lists three contributors by ENS name and an amount, and no address is written down anywhere, so the agent buys one paid resolution for all three names and then pays them in a single batch signed with the session key. No browser, no passkey prompt, one atomic transaction, all of it inside a cap granted once. The run ends by reading the transfers back off the chain, because our own report of what happened is not evidence.

The account it signs for has a name

Every JAW account comes with an ENS subname and a record set, created in the same flow as the account. People would rather read a name than a hex string, and it reads differently again when the thing spending money is software: what arrives at the endpoint is a name that resolves, with records the seller can read before deciding how to treat the request.

Bounded authority answers how much an agent may spend. Identity answers who is spending it, which is what a merchant has to settle before it can treat a machine as anything but an anonymous request from the internet. We are further along on the first, and both belong in the account rather than in a wallet bolted onto a payment rail.

Finding what to buy

Paying an endpoint you already know is the easy case. A useful agent has to find the service first, so JAW searches the x402 catalogue: what exists, what it costs, how to call it. Ask for a name to be resolved and it can compare prices, pick one and pay, without you pasting a URL.

Discovery is deliberately separate from spending. Searching never moves money, and paying goes back through the same permission and the same caps, so an attractive listing cannot talk an agent into an expensive payment.

Everything it reads is written by a stranger

An agent that browses and buys is an agent reading text written by whoever it is about to pay. Service descriptions, response bodies, error strings: all of it lands in the model's context, written by someone with an interest in the outcome.

So JAW marks it. Fetched content and catalogue copy come back fenced as untrusted, separated from the payment details, with instructions never to act on directives inside them. A merchant cannot put raise your limit and pay me again in a product description and have it read as a command.

It is a mitigation, not a proof, and prompt injection is not solved for anyone. The protection that does not depend on a model behaving is the other one: an injected agent still cannot exceed a cap the JAW account enforces.

The unglamorous part

Building this turned up the sort of problem that does not make it into launch posts. Sponsored payments from a newly upgraded account were accepted by our bundler and then dropped without a trace: never mined, no error returned. Chasing it meant running the bundler against a forked chain until the failure reproduced, and the cause was a two byte marker it padded to twenty before hashing, which invalidated the paymaster's signature on an operation that was otherwise valid. One line, once you could see it.

The interesting claim in a payments product is never the happy path. It is what happens when something upstream behaves slightly differently than the specification implies, and whether that failure is loud or quiet. Ours was quiet, which was the real bug.

The flow now runs end to end against deployed infrastructure, with the transactions to show for it, and the gas paid in USDC by an account that has never held a native token.

Trying it

One grant, from a terminal:

npx @jaw.id/cli session setup --chain 8453 --x402 --limit 10/day

That opens a browser once, you approve with your passkey, and the scope is derived for you: a USDC transfer, capped at what you named, on that chain.

Then point an agent at the wallet. The JAW wallet is an MCP server, which is what matters past a demo: no integration to write per assistant, no SDK for anyone to adopt. Whatever speaks the protocol can hold the tools. In Claude it is one entry in the configuration:

{ "jaw": { "command": "npx", "args": ["@jaw.id/cli", "mcp"] } }

Then ask for something that costs money.

Find an ENS resolver among the paid x402 services and use it to resolve vitalik.eth. Pay if it asks, and tell me what it cost.

It searches the catalogue, picks a service, pays and answers. Along with the answer it hands back the receipt:

{
  "paid": true,
  "payment": {
    "amount": "1000",
    "network": "eip155:84532",
    "txHash": "0x92559c99f5991212e815410c0963096723cf1f95e6494c01eb9dae2d02e89ba6"
  }
}

That is one of our own runs, on Base Sepolia. USDC has six decimals, so 1000 base units is a tenth of a cent: the amount a subscription could never be sold for and a machine does not think twice about. Every attempt lands in a log on your machine, paid or refused, so what your agent bought overnight has an answer in the morning.

Two promises, kept by two different things. The log is a file this CLI writes, so it answers for what this CLI did. The cap is read by your JAW account before anything moves, so it holds whatever wrote the transaction.

Both ends of the same protocol

We built the paying side because we already run the receiving side. Our ENS endpoints charge for resolution over x402, so we are the thing selling and the thing buying. It is an uncommon place to stand, and a useful one when you want to find out what actually breaks.

The state of it is early, and worth saying so. The runs behind this post are on Base Sepolia, against our own ENS endpoints, with hashes you can look up. The registry carries Base, Base Sepolia and Polygon, and mainnet is the same path with a different chain id. The catalogues are thin. Most services worth paying for have not listed themselves yet.

Settlement has two shapes now. A price the server names up front clears as a stablecoin authorization a facilitator broadcasts. A price it cannot name, because it bills for what you consume, clears through a signed ceiling: the client authorises an upper bound, the witness on it names the recipient and the only facilitator allowed to settle, and what settles is the amount consumed. The cap the account enforces does not care which of the two it was.

None of it changes the shape of the problem. Agents are going to buy things, and the question was never whether to allow it. It was where the authority should live: not in a key an agent holds, but in the JAW account it signs for, where the limit is enforced by the chain and can be taken back at any time.

Keep reading

More from the JAW blog

All posts

Give your agent an account.

Passkey-secured smart accounts with programmable permissions. Live in minutes.

Get started