Read the transaction you are about to sign
inspect_paymentCall this with the CALLDATA you are about to sign, before you sign it. Every other check here asks whether an address is worth dealing with; this asks whether the transaction is the one you think it is, and the two catch different losses. No amount of reputation makes the recipient in the bytes match the recipient on your screen, and a spotless counterparty record says nothing about an UNLIMITED APPROVAL granted to it, which is not a payment at all but a standing permission that outlives the transaction. It reads three calls — transferWithAuthorization, approve, transfer — and REFUSES to guess at any other: decoding unknown calldata without the ABI means guessing where each argument begins, and a confident wrong answer about where money goes is worse than none. Amounts are atomic units, never dollars, because the token owns its decimals. Free, needs no key, stores nothing.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | The contract being called, where you know it. On an ERC-20 payment this IS the token: the calldata carries a recipient and an amount and never the asset, so without `to` there is nothing to compare the endpoint’s quoted asset against. | |
| data | No | The calldata, 0x-prefixed. Without it nothing about the transaction is read, and the answer says so rather than passing. | |
| expect | No | What you believe you are doing. Anything you leave out is not compared, and is reported as not compared rather than as agreeing. | |
| resource | No | The http(s) URL of the endpoint you are paying, where there is one. Send it and the answer also compares the bytes with the amount and the payee that endpoint demanded in its own 402 — the check that catches a transaction which is exactly what you meant and is not what the seller asked for. | |
| refreshQuote | No | Knock on `resource` NOW instead of comparing against a stored reading, and set what comes back against the reading it replaced. `againstEndpoint.refreshed.quoteStillMatches` is true, false, or null where the two cannot be compared, with `verdict` as match/changed/unknown and `differs` naming which of amount, asset, network or payee moved. A CHANGED PAYEE is the substitution this whole check exists to catch. Only for an endpoint already in the x402 catalogue we read — we do not fetch a URL because a request named it — and at most one live handshake a minute per resource, shared by every caller. A match is what the endpoint said a moment ago, not a promise about the next moment. | |
| maxQuoteAgeSeconds | No | How old a quote you are willing to act on, in seconds. YOURS, not ours: four hours is alarming for a market quote and irrelevant for a tariff unchanged since March, so a threshold published here would be a number we invented wearing the clothes of a measurement. The answer comes back as `againstEndpoint.standing.freshness` — `within-your-limit` or `older-than-your-limit` — with `freshEnough` as the boolean and your limit echoed back. Without it the answer says `no-limit-given` rather than grading the age. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| call | No | What the bytes actually are, or null where it is not a call this decodes. | |
| limits | No | What decoding a payment establishes. It reads what the transaction SAYS it will do; it does not simulate it and cannot tell you the recipient is honest. | |
| spender | No | Who would be allowed to move your tokens later. Only an approve() has one, and an approve() has nothing else. | |
| verdict | Yes | `read` means every comparison you asked for agreed. `mismatch` means at least one did not. `undecodable` means the call is not one this reads and NOTHING was checked — it is not a pass. | |
| findings | Yes | One per check, each carrying whether it ran: observation, mismatch, or unknown. | |
| selector | No | The four-byte function selector the calldata begins with, where we could read one. Null means the payload is not a call we can decode, which is not evidence that it is bad. | |
| recipient | No | Who RECEIVES the money. Null on an approve(), which pays nobody: branch on this rather than on the prose. | |
| amountAtomic | No | Atomic units, as a decimal string. The token owns its decimals and this does not apply them. | |
| actualTransfer | No | Whether tokens move in THIS transaction. False for an approval, where the amount is a ceiling on what somebody else may take later rather than a sum leaving now. | |
| againstEndpoint | No | SENT `resource`? THEN THIS COMPARES THE BYTES WITH WHAT THE ENDPOINT ITSELF IS DEMANDING, which is the one thing `expect` cannot catch: bytes that are exactly what you meant and are not what the seller is asking for. `amount` is `exact-match`, `below-quote`, `above-quote` or `unknown`, in BASE UNITS with no scaling and only where the token the call is made against is the token the quote prices in. **`below-quote` is the expensive one**: the call is refused and the tokens have left. `payee` is `matches`, `differs` or `unknown` against the address the 402 named, and it reads the approved SPENDER on an approval, which pays nobody. `asked: false` means we have never probed that endpoint, which is our gap and not evidence about it. `verdict` above is UNCHANGED by any of this — that is the manifest against YOUR expectation, and the two questions are kept apart on purpose. | |
| unlimitedAllowance | No | True where this grants permission to move every token of this kind you will ever hold. |