@parallel-protocol/x402 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -58
- package/dist/{chunk-AENXD4OC.js → chunk-GXPUE3ES.js} +233 -77
- package/dist/{chunk-JPMK5YBU.cjs → chunk-KZZY5LS6.cjs} +13 -2
- package/dist/{chunk-AFRDHGDT.js → chunk-QAECRSWF.js} +13 -2
- package/dist/{chunk-POU7EH3E.cjs → chunk-SNW5Z5HB.cjs} +255 -97
- package/dist/express.cjs +6 -6
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/express.js +2 -2
- package/dist/fastify.cjs +20 -29
- package/dist/fastify.d.cts +1 -1
- package/dist/fastify.d.ts +1 -1
- package/dist/fastify.js +13 -22
- package/dist/hono.cjs +7 -7
- package/dist/hono.d.cts +1 -1
- package/dist/hono.d.ts +1 -1
- package/dist/hono.js +2 -2
- package/dist/index.cjs +27 -19
- package/dist/index.d.cts +56 -7
- package/dist/index.d.ts +56 -7
- package/dist/index.js +2 -2
- package/dist/llm.cjs +13 -13
- package/dist/llm.d.cts +1 -1
- package/dist/llm.d.ts +1 -1
- package/dist/llm.js +1 -1
- package/dist/next.cjs +28 -44
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +20 -36
- package/dist/{types-q_gu5wjz.d.cts → types-BkGQ2lPA.d.cts} +30 -1
- package/dist/{types-q_gu5wjz.d.ts → types-BkGQ2lPA.d.ts} +30 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,11 +24,12 @@ Agent Merchant (this SDK) Facilitator
|
|
|
24
24
|
| | |
|
|
25
25
|
|── GET /api/data ──────────────>| |
|
|
26
26
|
| payment-signature: <base64> |── POST /x402/verify ───────>|
|
|
27
|
-
| |<─ { isValid
|
|
27
|
+
| |<─ { isValid, settleToken } ─|
|
|
28
28
|
| | |
|
|
29
29
|
| | [handler runs] |
|
|
30
30
|
| | |
|
|
31
31
|
| |── POST /x402/settle ───────>|
|
|
32
|
+
| | (+ settleToken) |
|
|
32
33
|
| |<─ { txHash, route, ... } ───|
|
|
33
34
|
| | |
|
|
34
35
|
|<─ 200 + payment-response ──────| |
|
|
@@ -38,8 +39,10 @@ Agent Merchant (this SDK) Facilitator
|
|
|
38
39
|
**Key properties:**
|
|
39
40
|
- The handler only runs after the signature is verified.
|
|
40
41
|
- The on-chain settlement happens after the handler produces a 2xx response — the agent is not charged on errors.
|
|
41
|
-
-
|
|
42
|
-
-
|
|
42
|
+
- The settle is bound to the merchant that verified: `POST /x402/verify` hands the middleware a settle token that `POST /x402/settle` must present. The token stays in memory for the life of the request and never reaches the agent.
|
|
43
|
+
- If settlement fails, the middleware discards the handler's response and returns `402` with `{ error: "PAYMENT_SETTLEMENT_FAILED", reason, message }`: `reason` is the facilitator's code (`INSUFFICIENT_BALANCE`, `AUTHORIZATION_USED`, `PAYMENT_EXPIRED`, ...) or this SDK's (`SETTLE_TIMEOUT`, `FACILITATOR_UNAVAILABLE`, `FACILITATOR_INVALID_RESPONSE`, `AMOUNT_MISMATCH`, `UNKNOWN`), `message` the facilitator's text or this SDK's, and the facilitator's `Retry-After` is copied when it sent one. A settle that timed out or could not reach the facilitator is retried once first: the same settle is idempotent, and the facilitator answers the recorded outcome if the first attempt went through.
|
|
44
|
+
- A metered settle must settle the amount the handler asked for: a confirmation for another amount is refused and the response discarded.
|
|
45
|
+
- Handlers are bounded by a 30-second timeout on Express, Hono and Next.js — a slow handler returns `504` and the agent is not charged. (Fastify manages handler execution itself, so no timeout is applied there.) Metered routes get their announced window minus two minutes kept for the settlement and its retry.
|
|
43
46
|
- On paid Fastify routes, writing to `reply.raw` (the SSE/streaming pattern) fails loudly instead of sending: a raw response skips the settlement hooks and would go out unpaid. Streaming is not supported on paid routes.
|
|
44
47
|
- CORS preflight (`OPTIONS`) is always passed through without challenge.
|
|
45
48
|
|
|
@@ -101,9 +104,10 @@ app.get("/api/data", (req, res) => {
|
|
|
101
104
|
```typescript
|
|
102
105
|
// app/api/data/route.ts
|
|
103
106
|
import { NextResponse } from "next/server";
|
|
107
|
+
import type { PaymentMiddlewareConfig } from "@parallel-protocol/x402";
|
|
104
108
|
import { withPayment } from "@parallel-protocol/x402/next";
|
|
105
109
|
|
|
106
|
-
const config = {
|
|
110
|
+
const config: PaymentMiddlewareConfig = {
|
|
107
111
|
facilitator: { url: "https://agents.parallel.best" },
|
|
108
112
|
routes: {
|
|
109
113
|
"/api/data": {
|
|
@@ -186,7 +190,7 @@ interface PaymentMiddlewareConfig {
|
|
|
186
190
|
|
|
187
191
|
| Field | Type | Required | Description |
|
|
188
192
|
|-------|------|----------|-------------|
|
|
189
|
-
| `url` | `string` | Yes | Base URL of the Parallel facilitator (e.g. `https://agents.parallel.best`). The SDK internally appends `/x402/verify` and `/x402/settle` to this value. |
|
|
193
|
+
| `url` | `string` | Yes | Base URL of the Parallel facilitator (e.g. `https://agents.parallel.best`). The SDK internally appends `/x402/verify` and `/x402/settle` to this value. It must use https: plain http is accepted only for `localhost`, `127.0.0.1` and `[::1]`. A trailing slash is ignored. The SDK calls this address only and never follows a redirect, which fails the call with `FACILITATOR_UNAVAILABLE`. |
|
|
190
194
|
| `apiKey` | `string` | No | Optional API key, sent as the `X-API-Key` header on facilitator requests. |
|
|
191
195
|
|
|
192
196
|
### `RouteConfig`
|
|
@@ -194,14 +198,14 @@ interface PaymentMiddlewareConfig {
|
|
|
194
198
|
| Field | Type | Required | Description |
|
|
195
199
|
|-------|------|----------|-------------|
|
|
196
200
|
| `price` | `string \| bigint \| (ctx) => string \| bigint` | Yes | Amount required. A string like `"0.01"` is parsed with each accepted token's own decimals (or with `decimals` when set explicitly). Pass a `bigint` to skip parsing. A function prices each request from its content, see [Per-request pricing](#per-request-pricing). |
|
|
197
|
-
| `decimals` | `number` | No | Override for the decimals used to parse a string price. By default each accepted token is priced with its own decimals from the `@parallel-protocol/chains` catalog
|
|
201
|
+
| `decimals` | `number` | No | Override for the decimals used to parse a string price. By default each accepted token is priced with its own decimals from the `@parallel-protocol/chains` catalog, so a `"0.01"` price is correct whether paid in USDp (18) or USDC (6), with no configuration. Only needed for a custom token outside the catalog: without it, an unknown token raises `X402ConfigError` rather than being silently guessed. When set, it applies to every token of the route. |
|
|
198
202
|
| `network` | `string` | Yes | Chain slug: `"ethereum"`, `"base"`, `"avalanche"`, `"hyperevm"`. |
|
|
199
203
|
| `payTo` | `Address` | Yes | The merchant's receiving address. |
|
|
200
204
|
| `acceptedTokens` | `Address[]` | No | List of accepted token addresses. Defaults to `[USDp, USDC, sUSDp]` for known networks (see [Default tokens](#default-tokens)). On an `upto` route it defaults to USDp alone, and any other token is rejected at construction: metered payments are USDp only. |
|
|
201
205
|
| `description` | `string` | No | Human-readable description of the resource, included in the 402 body. |
|
|
202
206
|
| `scheme` | `"exact" \| "upto"` | No | `exact` (default) charges `price` as invoiced. `upto` invoices `price` as a **cap** the payer signs; the handler names the actual amount to charge and the rest is refunded on-chain — see [Metered routes](#metered-routes-upto). |
|
|
203
207
|
| `meter` | `(ctx) => string \| bigint \| undefined` | No | `upto` routes only: reads the amount to charge from the handler's response, see [Reading the amount from the response](#reading-the-amount-from-the-response-meter). |
|
|
204
|
-
| `maxTimeoutSeconds` | `number` | No | Settlement window the payer
|
|
208
|
+
| `maxTimeoutSeconds` | `number` | No | Settlement window announced to the payer (default 300). `@parallel-protocol/x402-fetch` signs for it on `upto` routes, and for five minutes on `exact` routes. Metered routes announce how long their handler may take. On an `upto` route it is 3600 (one hour) at most: a larger value raises `X402ConfigError` at construction, and the facilitator refuses an authorization valid for longer with `WINDOW_TOO_LONG`. On metered routes, Express, Hono and Next.js derive the handler timeout from it, minus two minutes kept for the settlement and its retry: a 300 s window leaves the handler 180 s, and a handler always gets at least 30 s. Fastify does not time handlers out, set the server's `connectionTimeout` / `requestTimeout` yourself. |
|
|
205
209
|
|
|
206
210
|
Route matching uses **longest prefix wins**: `/api/v1/users` will match a `/api/v1` route config before a `/api` one.
|
|
207
211
|
|
|
@@ -218,7 +222,7 @@ For the four natively supported networks, `acceptedTokens` defaults to `[USDp, U
|
|
|
218
222
|
| `avalanche` | `0x9eE1963f05553eF838604Dd39403be21ceF26AA4` | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` | `0x9d92c21205383651610f90722131655a5b8ed3e0` |
|
|
219
223
|
| `hyperevm` | `0xBE65F0F410A72BeC163dC65d46c83699e957D588` | `0xb88339cb7199b77e23db6e890353e22632ba630f` | `0x9B3a8f7CEC208e247d97dEE13313690977e24459` |
|
|
220
224
|
|
|
221
|
-
For any other network (from the full Parallel
|
|
225
|
+
For any other network (from the full Parallel 24-chain catalog), you must provide `acceptedTokens` explicitly, or the middleware throws `X402ConfigError` on the first request to that route. The facilitator advertises routes on the four networks above only, and the invoice of any other network names it `eip155:<slug>`, which `@parallel-protocol/x402-fetch` cannot pay.
|
|
222
226
|
|
|
223
227
|
You can also import the token list directly:
|
|
224
228
|
|
|
@@ -232,7 +236,7 @@ const defaults = getDefaultAcceptedTokens("base");
|
|
|
232
236
|
// ["0x76A9...", "0x8335...", "0x472e..."] (USDp + USDC + sUSDp)
|
|
233
237
|
```
|
|
234
238
|
|
|
235
|
-
**sUSDp** is included in the defaults
|
|
239
|
+
**sUSDp** is included in the defaults, so the merchant can be paid in sUSDp: routes C, G and J deliver it. Routes H, I, K and L, where the agent pays with sUSDp, deliver USDp, USDC or a collateral and do not need it. To opt out and accept only USDp and USDC, pass `acceptedTokens` explicitly:
|
|
236
240
|
|
|
237
241
|
```typescript
|
|
238
242
|
import { PARALLEL_TOKENS } from "@parallel-protocol/x402";
|
|
@@ -245,7 +249,7 @@ routes: {
|
|
|
245
249
|
acceptedTokens: [
|
|
246
250
|
PARALLEL_TOKENS.base.usdp,
|
|
247
251
|
PARALLEL_TOKENS.base.usdc,
|
|
248
|
-
// sUSDp omitted
|
|
252
|
+
// sUSDp omitted: routes C, G and J disabled for this route
|
|
249
253
|
],
|
|
250
254
|
},
|
|
251
255
|
}
|
|
@@ -276,14 +280,14 @@ routes: {
|
|
|
276
280
|
acceptedTokens: [
|
|
277
281
|
PARALLEL_TOKENS.ethereum.usdp,
|
|
278
282
|
PARALLEL_TOKENS.ethereum.usdc,
|
|
279
|
-
PARALLEL_TOKENS.ethereum.sUSDe, // opt-in
|
|
280
|
-
PARALLEL_TOKENS.ethereum.sfrxUSD, // opt-in
|
|
283
|
+
PARALLEL_TOKENS.ethereum.sUSDe, // opt-in: the merchant can be paid in sUSDe
|
|
284
|
+
PARALLEL_TOKENS.ethereum.sfrxUSD, // opt-in: the merchant can be paid in sfrxUSD
|
|
281
285
|
],
|
|
282
286
|
},
|
|
283
287
|
}
|
|
284
288
|
```
|
|
285
289
|
|
|
286
|
-
> Note: the facilitator
|
|
290
|
+
> Note: the facilitator only delivers the chain's USDp, sUSDp and USDC and the collaterals in the table above. A payment that would deliver any other token is refused at verify, before the handler runs: a swap to such a token gets `COLLATERAL_NOT_SUPPORTED`. Listing such a token in `acceptedTokens` invites payments the facilitator refuses.
|
|
287
291
|
|
|
288
292
|
---
|
|
289
293
|
|
|
@@ -297,6 +301,7 @@ When no valid payment is present, the middleware returns HTTP 402 with a `paymen
|
|
|
297
301
|
interface PaymentRequired {
|
|
298
302
|
x402Version: 2;
|
|
299
303
|
error?: string; // present on re-challenge (e.g. "INVALID_SIGNATURE")
|
|
304
|
+
message?: string; // the facilitator's text for `error`, when it sent one
|
|
300
305
|
resource: {
|
|
301
306
|
url: string;
|
|
302
307
|
description?: string;
|
|
@@ -322,7 +327,7 @@ The agent attaches a `payment-signature` (or `X-PAYMENT`) header with a **base64
|
|
|
322
327
|
// Minimal Route A payload (USDp transfer)
|
|
323
328
|
const signedPayload = {
|
|
324
329
|
scheme: "exact",
|
|
325
|
-
network: "base",
|
|
330
|
+
network: "base", // the chain slug, not the eip155 form of the 402
|
|
326
331
|
method: "transferWithAuthorization",
|
|
327
332
|
payload: {
|
|
328
333
|
signature: "0x...",
|
|
@@ -355,6 +360,7 @@ interface PaymentConfirmation {
|
|
|
355
360
|
gasSponsored: boolean;
|
|
356
361
|
networkId: string; // "eip155:8453"
|
|
357
362
|
amountSettled?: string; // metered routes: what was actually charged (smallest unit)
|
|
363
|
+
alreadySettled?: true; // the facilitator answered from a recorded outcome, this call broadcast nothing
|
|
358
364
|
}
|
|
359
365
|
```
|
|
360
366
|
|
|
@@ -364,14 +370,14 @@ The `access-control-expose-headers` header is set automatically so browsers can
|
|
|
364
370
|
|
|
365
371
|
## Metered routes (`upto`)
|
|
366
372
|
|
|
367
|
-
Some resources only know their cost after the work is done
|
|
373
|
+
Some resources only know their cost after the work is done: an LLM completion is priced by the tokens it generates. A route declared with `scheme: "upto"` invoices `price` as a **cap**: the payer signs an authorization for that cap, the handler serves the request and names the amount it actually charges, and the facilitator settles that amount through Parallel's escrow contract, refunding the remainder to the payer in the same transaction. Metered payments are made in USDp: `acceptedTokens` defaults to the network's USDp and may not list any other token. They settle only on chains where the escrow is deployed, which today is Base alone (`UPTO_CHAINS` in `@parallel-protocol/chains`). On another chain the facilitator refuses the payment at verify with `PAYMENT_METHOD_NOT_SUPPORTED`.
|
|
368
374
|
|
|
369
375
|
```typescript
|
|
370
376
|
routes: {
|
|
371
377
|
"/v1/chat/completions": {
|
|
372
378
|
scheme: "upto",
|
|
373
379
|
price: "2", // the cap, in USDp
|
|
374
|
-
maxTimeoutSeconds: 600, // the handler may take up to
|
|
380
|
+
maxTimeoutSeconds: 600, // the handler may take up to 8 minutes
|
|
375
381
|
network: "base",
|
|
376
382
|
payTo: "0xYourAddress",
|
|
377
383
|
},
|
|
@@ -390,7 +396,7 @@ fastify.post("/v1/chat/completions", async (request, reply) => {
|
|
|
390
396
|
});
|
|
391
397
|
```
|
|
392
398
|
|
|
393
|
-
The middleware strips the header before the response leaves, whatever the status code: a handler that prices the request and then fails never tells the client what it was about to charge. The value must follow the same format as `price` (a plain decimal such as `"0.0137"
|
|
399
|
+
The middleware strips the header before the response leaves, whatever the status code: a handler that prices the request and then fails never tells the client what it was about to charge. The value must follow the same format as `price` (a plain decimal such as `"0.0137"`: digits with an optional fractional part, no sign, no exponent, no unit) and must not carry more decimals than the token has. Rules on a `2xx` response:
|
|
394
400
|
|
|
395
401
|
| Header value | What happens |
|
|
396
402
|
|---|---|
|
|
@@ -423,7 +429,7 @@ Instead of a header the handler sets, a route may carry a `meter` function that
|
|
|
423
429
|
|
|
424
430
|
The response body is read only on routes that have a `meter` function, from a clone or from the buffered chunks the middleware already holds, so other routes are untouched. Reading it gets the handler's own time budget: a body that streams from a slow upstream and is not complete within the window is treated like a failing meter. Return a buffered body on metered routes. `meter` only applies to `upto` routes.
|
|
425
431
|
|
|
426
|
-
Timing: the payer's authorization expires after `maxTimeoutSeconds`, and settlement
|
|
432
|
+
Timing: the payer's authorization expires after `maxTimeoutSeconds`, and settlement may take up to two minutes, an attempt of up to 60 s and one retry. On Express, Hono and Next.js the handler timeout is raised to `maxTimeoutSeconds − 120 s` for metered routes. Fastify handlers are never timed out by the middleware, so size the server's own `connectionTimeout` and `requestTimeout` to the window. A handler that finishes after the window has done the work, but the payment can no longer settle: the client gets 402 `PAYMENT_SETTLEMENT_FAILED` with reason `PAYMENT_EXPIRED`, and the merchant is not paid. Keep a margin.
|
|
427
433
|
|
|
428
434
|
---
|
|
429
435
|
|
|
@@ -456,7 +462,7 @@ The function gets `{ method, path, url, header(name), body }`. `body` is the req
|
|
|
456
462
|
|
|
457
463
|
The function runs twice per paid request: on the unpaid request, to build the 402, and on the paid retry, to size what the facilitator verifies. It must therefore be **pure**: same request, same price, no clock and no randomness. A retry whose body changed in between is priced again, and refused by the facilitator if the signed amount no longer covers the new price. It may be `async`.
|
|
458
464
|
|
|
459
|
-
A function that throws or returns something that is not a price is a merchant bug: the request fails with `X402ConfigError` rather than being served for free or mispriced. To refuse a request the route will not serve (an unsupported option, a resource that does not exist) throw `X402PriceRejectedError(code, message, status = 400)` instead: the client gets that status with `{ error: code, message }`, and nothing is invoiced or signed.
|
|
465
|
+
A function that throws or returns something that is not a price is a merchant bug: the request fails with `X402ConfigError` rather than being served for free or mispriced. To refuse a request the route will not serve (an unsupported option, a resource that does not exist) throw `X402PriceRejectedError(code, message, status = 400)` instead: the client gets that status with `{ error: code, message }`, and nothing is invoiced or signed. A status outside 400 to 599 is sent as 400.
|
|
460
466
|
|
|
461
467
|
---
|
|
462
468
|
|
|
@@ -502,13 +508,13 @@ app.post("/v1/chat/completions", async (request, reply) => {
|
|
|
502
508
|
});
|
|
503
509
|
```
|
|
504
510
|
|
|
505
|
-
The same handler on the other frameworks: Express `res.status(upstream.status).json(upstream.body)`, Hono `c.json(upstream.body, upstream.status)
|
|
511
|
+
The same handler on the other frameworks: Express `res.status(upstream.status).json(upstream.body)`, Hono `c.json(upstream.body, upstream.status as ContentfulStatusCode)` (the type comes from `hono/utils/http-status`), Next.js `NextResponse.json(upstream.body, { status: upstream.status })`.
|
|
506
512
|
|
|
507
|
-
`forwardChatCompletion(body, { baseUrl, apiKey, maxOutputTokens, outputTokensField, timeout, headers, fetch, fields })` posts to `${baseUrl}/chat/completions` with only the fields the cap accounts for (`FORWARDED_FIELDS`: model, messages, tools, sampling parameters, reasoning options), `stream: false
|
|
513
|
+
`forwardChatCompletion(body, { baseUrl, apiKey, maxOutputTokens, outputTokensField, timeout, headers, fetch, fields })` posts to `${baseUrl}/chat/completions` with only the fields the cap accounts for (`FORWARDED_FIELDS`: model, messages, tools and tool choice, response format, sampling parameters and penalties, stop sequences, seed, reasoning options, parallel tool calls, logit bias and `user`), `stream: false`, and the completion bounded by `maxOutputTokens`. The bound is sent as `max_tokens`, or as `max_completion_tokens` for providers that want it, and a `reasoning.max_tokens` budget is bounded the same way. It returns the provider's `{ status, body }`. A 2xx and the statuses about the client's request (400, 404, 405, 408, 409, 413, 415, 422 and 429) are handed back as they are, so nothing is settled on an error. Any other status, such as a 401, 402, 403 or 5xx about the merchant's account with the provider, is folded into 502 `UPSTREAM_ERROR` with a fixed body, so the client never sees the merchant's provider details. A provider that cannot be reached answers 502 `UPSTREAM_UNAVAILABLE`, and a timeout 504 `UPSTREAM_TIMEOUT` (five minutes by default).
|
|
508
514
|
|
|
509
515
|
`chatCompletionMeter({ prices? })` reads the amount from the response: `usage.cost` in dollars when the provider reports one (OpenRouter does), otherwise `usage.prompt_tokens` and `usage.completion_tokens` at the model's prices when `prices` is given, otherwise nothing, which charges the cap. So a provider that reports no cost (DeepSeek, most direct APIs) needs the same `prices` as the cap, or every request settles at the cap with nothing refunded and no error to show it. A zero cost serves the request for free. Cached input tokens are charged at the full input price in the token-count path.
|
|
510
516
|
|
|
511
|
-
The cap of a request is a bound on its input tokens at the model's input price, plus the largest completion the request may produce (`max_tokens`, bounded by `maxOutputTokens`, which also stands in for it when absent) at its output price. One USDp per dollar. The cap must cover the real count whichever tokenizer the provider uses, or the response is refused and the merchant has paid the provider for nothing, so the built-in bound counts one token per UTF-8 byte of the messages and tools, which no tokenizer exceeds. English prose runs at about four characters per token, so the cap is loose there: the payer only pays the provider's real count and the rest is refunded. A merchant who wants a tighter cap plugs a real tokenizer through `estimateInputTokens`.
|
|
517
|
+
The cap of a request is a bound on its input tokens at the model's input price, plus the largest completion the request may produce (`max_tokens` or `max_completion_tokens`, bounded by `maxOutputTokens`, which also stands in for it when absent) at its output price. One USDp per dollar. The cap must cover the real count whichever tokenizer the provider uses, or the response is refused and the merchant has paid the provider for nothing, so the built-in bound counts one token per UTF-8 byte of the messages and tools, which no tokenizer exceeds. English prose runs at about four characters per token, so the cap is loose there: the payer only pays the provider's real count and the rest is refunded. A merchant who wants a tighter cap plugs a real tokenizer through `estimateInputTokens`.
|
|
512
518
|
|
|
513
519
|
| Option | Default | Description |
|
|
514
520
|
|--------|---------|-------------|
|
|
@@ -523,7 +529,7 @@ A handler that does not use `forwardChatCompletion` should still forward only th
|
|
|
523
529
|
|
|
524
530
|
`openRouterPrices({ ttl: "24h", timeout: "10s" })` loads the public models listing (no API key) on first use, serves it from memory and refreshes it in the background once older than `ttl`, so no request waits for OpenRouter after the first one; a load is abandoned after `timeout`, a refresh that fails keeps the current list, and a failed load is not retried for five seconds. With no list at all, requests are refused with a 503 `PRICING_UNAVAILABLE`. A model the list does not know may have been released since the list was loaded: once the list is more than five minutes old, such a request refreshes it in the background and is refused with an `UNKNOWN_MODEL` message that says so, and the next request finds the model. A fixed table never touches the network: `prices: { "deepseek-flash": { input: 0.30, output: 1.20 } }`.
|
|
525
531
|
|
|
526
|
-
Requests the route cannot meter are refused before anything is invoiced, with a 400 and a JSON body: an unknown model (`UNKNOWN_MODEL`), a model outside `models` (`MODEL_NOT_ALLOWED`), more than one choice (`n`), non-text content, plugins, web search, image or audio output, or streaming (`UNSUPPORTED_OPTION`), a malformed body (`INVALID_REQUEST`). Live prices are the one accepted exception to the purity rule of price functions: a list refreshed between the unpaid request and the paid retry may re-price the retry
|
|
532
|
+
Requests the route cannot meter are refused before anything is invoiced, with a 400 and a JSON body: an unknown model (`UNKNOWN_MODEL`), a model outside `models` (`MODEL_NOT_ALLOWED`), more than one choice (`n`), non-text content, plugins, web search, image or audio output, or streaming (`UNSUPPORTED_OPTION`), a malformed body (`INVALID_REQUEST`). Live prices are the one accepted exception to the purity rule of price functions: a list refreshed between the unpaid request and the paid retry may re-price the retry. A retry priced above the amount the payer signed is refused and re-invoiced.
|
|
527
533
|
|
|
528
534
|
Without `meter`, what is charged after the response is the handler's call: set the `payment-settle-amount` header to the provider's cost, see [Metered routes](#metered-routes-upto).
|
|
529
535
|
|
|
@@ -537,11 +543,11 @@ The facilitator supports 13 settlement routes. The route is selected automatical
|
|
|
537
543
|
|-------|--------------------------|---------------------|------------------------------|----------|-------|
|
|
538
544
|
| **A** | `transferWithAuthorization` | USDp | USDp | ~65K | Direct USDp transfer |
|
|
539
545
|
| **B** | `swapExactOutputWithAuthorization` | USDp | USDC (exact) | ~120K | Parallelizer swaps agent's USDp → exact USDC for merchant |
|
|
540
|
-
| **C** | `depositWithAuthorization` | USDp | sUSDp | ~
|
|
546
|
+
| **C** | `depositWithAuthorization` | USDp | sUSDp | ~110K | Agent's USDp deposited into the savings vault, the merchant receives sUSDp shares |
|
|
541
547
|
| **D** | `swapExactOutputWithAuthorization` | USDp | non-USDC backing collateral (exact) | ~120K | Same as B but for other Parallelizer collaterals |
|
|
542
|
-
| **E** | `swapExactInputWithAuthorization` | USDC (exact) | USDp (min) | ~120K | Agent pays exact
|
|
543
|
-
| **F** | `transferWithAuthorization` | USDC
|
|
544
|
-
| **G** | `swapExactInputWithAuthorization` | USDC (exact) | sUSDp | ~200K |
|
|
548
|
+
| **E** | `swapExactInputWithAuthorization` | USDC or a collateral (exact) | USDp (min) | ~120K | Agent pays an exact amount, the Parallelizer delivers at least the minimum USDp to the merchant |
|
|
549
|
+
| **F** | `transferWithAuthorization` | USDC or a collateral | same token | ~65K | Direct EIP-3009 token push |
|
|
550
|
+
| **G** | `swapExactInputWithAuthorization` | USDC or a collateral (exact) | sUSDp | ~200K | The token is swapped to USDp, then the USDp is deposited as sUSDp for the merchant (atomic multicall3) |
|
|
545
551
|
| **H** | `redeemWithAuthorization` | sUSDp shares | USDp | ~130K | Agent burns sUSDp shares; merchant receives USDp assets |
|
|
546
552
|
| **I** | `redeemWithAuthorization` + `swapExactOutputWithAuthorization` | sUSDp | USDC (exact) | ~180K | Agent redeems sUSDp → USDp, then swaps USDp → exact USDC for merchant (atomic multicall3) |
|
|
547
553
|
| **J** | `transferWithAuthorization` | sUSDp | sUSDp | ~65K | Direct sUSDp share transfer |
|
|
@@ -551,9 +557,12 @@ The facilitator supports 13 settlement routes. The route is selected automatical
|
|
|
551
557
|
|
|
552
558
|
**Chain requirements:**
|
|
553
559
|
- Routes B, D, E, G, I, K, L require the **Parallelizer** to be deployed on the target chain (Ethereum, Base, Avalanche, HyperEVM).
|
|
560
|
+
- Routes B and I swap to USDC, so they need USDC among the chain's collaterals, which is the case on Base and Avalanche.
|
|
554
561
|
- Routes C, G, H, I, J, K, L require **sUSDp** (savings module) to be deployed on the target chain.
|
|
555
|
-
- Route A
|
|
562
|
+
- Route A needs USDp on the chain. Routes E, F and G take as `tokenIn` the chain's USDC or one of its collaterals (see [Default tokens](#default-tokens)), and the token must support EIP-3009. Any other `tokenIn` is refused at verify, with `PAYMENT_METHOD_NOT_SUPPORTED` when it gets past the signature checks.
|
|
563
|
+
- Route U needs the escrow contract, deployed on Base only. A metered payment on another chain is refused at verify with `PAYMENT_METHOD_NOT_SUPPORTED`.
|
|
556
564
|
- Gas sponsorship is decided by the facilitator per payment and reported as `gasSponsored` in the settlement receipt.
|
|
565
|
+
- The facilitator's `GET /capabilities?chain=<slug>` lists the routes available on a chain right now, with the gas estimates above and the burn fees.
|
|
557
566
|
|
|
558
567
|
---
|
|
559
568
|
|
|
@@ -568,7 +577,14 @@ import {
|
|
|
568
577
|
createPaymentMiddleware,
|
|
569
578
|
resolveCharge,
|
|
570
579
|
resolveSettleAmount,
|
|
580
|
+
settleVerified,
|
|
581
|
+
settlementFailureResponse,
|
|
571
582
|
SETTLE_AMOUNT_HEADER,
|
|
583
|
+
type ChargeResponse,
|
|
584
|
+
type SettleAmountResolution,
|
|
585
|
+
type SettlementFailure,
|
|
586
|
+
type SettlementFailureBody,
|
|
587
|
+
type SettlementOutcome,
|
|
572
588
|
|
|
573
589
|
// Facilitator client
|
|
574
590
|
FacilitatorClient,
|
|
@@ -588,7 +604,9 @@ import {
|
|
|
588
604
|
|
|
589
605
|
// Types
|
|
590
606
|
type FacilitatorConfig,
|
|
607
|
+
type FacilitatorPaymentRequirements,
|
|
591
608
|
type FacilitatorResponse,
|
|
609
|
+
type FacilitatorSettleOptions,
|
|
592
610
|
type HTTPAdapter,
|
|
593
611
|
type MeterContext,
|
|
594
612
|
type MeterFunction,
|
|
@@ -604,7 +622,11 @@ import {
|
|
|
604
622
|
type PriceFunction,
|
|
605
623
|
type ResourceInfo,
|
|
606
624
|
type RouteConfig,
|
|
625
|
+
type RunHandlerOptions,
|
|
607
626
|
type RunHandlerResult,
|
|
627
|
+
type SettleOptions,
|
|
628
|
+
type VerifiedPayment,
|
|
629
|
+
type VerifyResult,
|
|
608
630
|
|
|
609
631
|
// Utils
|
|
610
632
|
encodeBase64,
|
|
@@ -644,9 +666,11 @@ import {
|
|
|
644
666
|
type ChatCompletionCapOptions,
|
|
645
667
|
type ChatCompletionMeterOptions,
|
|
646
668
|
type ChatCompletionBody,
|
|
669
|
+
type ChatMessage,
|
|
647
670
|
type ForwardChatCompletionOptions,
|
|
648
671
|
type ForwardedResponse,
|
|
649
672
|
type ModelPrice,
|
|
673
|
+
type OpenRouterPricesOptions,
|
|
650
674
|
type PriceSource,
|
|
651
675
|
type PriceTable,
|
|
652
676
|
} from "@parallel-protocol/x402/llm";
|
|
@@ -669,19 +693,21 @@ const result = await gate({
|
|
|
669
693
|
getMethod: () => request.method,
|
|
670
694
|
getPath: () => request.path,
|
|
671
695
|
getUrl: () => request.url,
|
|
696
|
+
getBody: async () => request.body, // optional: the parsed body, for price and meter functions
|
|
672
697
|
});
|
|
673
698
|
|
|
674
699
|
if (result.type === "pass") {
|
|
675
700
|
// Route not configured for payment, proceed normally
|
|
676
701
|
} else if (result.type === "error") {
|
|
677
|
-
// Send
|
|
702
|
+
// Send result.result.status (402, or the status of a price refusal)
|
|
703
|
+
// with result.result.headers and result.result.body
|
|
678
704
|
} else {
|
|
679
705
|
// result.type === "verified"
|
|
680
706
|
// Run your handler...
|
|
681
707
|
const handlerStatus = await runHandler();
|
|
682
708
|
|
|
683
709
|
if (handlerStatus >= 200 && handlerStatus < 300) {
|
|
684
|
-
const settlement = await result.settle();
|
|
710
|
+
const settlement = await result.settle(); // a metered route passes { amount }
|
|
685
711
|
if (settlement.success) {
|
|
686
712
|
// settlement.txHash / settlement.route / settlement.gasSponsored
|
|
687
713
|
}
|
|
@@ -689,7 +715,7 @@ if (result.type === "pass") {
|
|
|
689
715
|
}
|
|
690
716
|
```
|
|
691
717
|
|
|
692
|
-
`createPaymentGate` only verifies
|
|
718
|
+
`createPaymentGate` only verifies, it does not run the handler. Use `createPaymentMiddleware` if you want the whole flow (verify, run the handler, settle) managed automatically. The gate keeps the settle token the facilitator issued at verify and presents it in `result.settle()`, so a custom integration has nothing to carry. On a metered route, `result.settle({ amount })` takes the amount to charge in the token's smallest unit, which `resolveCharge` computes from the handler's response as the adapters do. `result.settle()` makes a single call and throws as [`FacilitatorClient.settle`](#facilitatorclient) does. `settleVerified(result, options)` runs the same settlement the adapters use (one retry when the outcome is unknown, the settled amount checked on metered routes) and `settlementFailureResponse(outcome)` builds the 402 for a failed one.
|
|
693
719
|
|
|
694
720
|
---
|
|
695
721
|
|
|
@@ -711,33 +737,49 @@ const requirements: FacilitatorPaymentRequirements = {
|
|
|
711
737
|
network: "base", // plain chain slug
|
|
712
738
|
};
|
|
713
739
|
|
|
714
|
-
// Phase 1: verify signature + reserve nonce (no on-chain tx)
|
|
715
|
-
|
|
740
|
+
// Phase 1: verify signature + reserve nonce (no on-chain tx). The facilitator
|
|
741
|
+
// hands back the settle token this payment must be settled with.
|
|
742
|
+
const { settleToken } = await client.verify(paymentHeader, requirements); // throws X402RuntimeError on failure
|
|
716
743
|
|
|
717
744
|
// Phase 2: submit on-chain, return settlement result
|
|
718
|
-
const result = await client.settle(paymentHeader, requirements);
|
|
745
|
+
const result = await client.settle(paymentHeader, requirements, { settleToken });
|
|
719
746
|
if (result.success) {
|
|
720
|
-
console.log(result.txHash);
|
|
721
|
-
console.log(result.route);
|
|
722
|
-
console.log(result.gasSponsored);
|
|
747
|
+
console.log(result.txHash); // on-chain tx hash
|
|
748
|
+
console.log(result.route); // "A", "B", ...
|
|
749
|
+
console.log(result.gasSponsored); // boolean
|
|
750
|
+
console.log(result.alreadySettled); // true when an earlier settle already did it
|
|
723
751
|
} else {
|
|
724
|
-
console.error(result.error.code);
|
|
752
|
+
console.error(result.error.code); // e.g. "INSUFFICIENT_BALANCE"
|
|
753
|
+
console.error(result.error.message); // the facilitator's text
|
|
754
|
+
console.error(result.error.status); // its HTTP status, result.error.retryAfter when it sent one
|
|
725
755
|
}
|
|
726
756
|
```
|
|
727
757
|
|
|
728
|
-
`verify` uses a 10-second timeout. `settle` uses a 60-second timeout to accommodate on-chain confirmation latency. `verify` throws on any failure
|
|
758
|
+
The constructor throws `X402ConfigError` when the URL is neither https nor plain http on `localhost`, `127.0.0.1` or `[::1]`, and the client keeps the address it checked. No call follows a redirect. `verify` uses a 10-second timeout. `settle` uses a 60-second timeout to accommodate on-chain confirmation latency. `verify` throws on any failure, with the facilitator's message in `.detail`, its HTTP status in `.status` and its `Retry-After` in `.retryAfter`. A facilitator that issues no settle token is an invalid response. `settle` returns a `FacilitatorResponse` union on facilitator errors, with the facilitator's status and `Retry-After` on `error`, and throws `X402RuntimeError` on connectivity issues (`SETTLE_TIMEOUT` when the 60 seconds ran out, the payment may still settle), an undecodable payment header, or a malformed facilitator response. A settle refused with a status below 500 and no `error` field comes back as `SUBMISSION_FAILED`, one at 500 or above with no `error` field throws `FACILITATOR_UNAVAILABLE`. The settle token is a secret for the life of the request: keep it in memory, never log it, never send it to the payer.
|
|
729
759
|
|
|
730
|
-
|
|
760
|
+
For a metered payment, the requirements also carry `scheme: "upto"` and `maxTimeoutSeconds`, and `settle` takes the amount to charge, in the token's smallest unit, next to the settle token: `{ settleToken, amount }`.
|
|
731
761
|
|
|
732
762
|
---
|
|
733
763
|
|
|
734
764
|
## Error codes
|
|
735
765
|
|
|
766
|
+
### Responses to the agent
|
|
767
|
+
|
|
768
|
+
What the middleware answers on a paid route instead of the handler's response:
|
|
769
|
+
|
|
770
|
+
| Status | Body | When |
|
|
771
|
+
|--------|------|------|
|
|
772
|
+
| 402 | `PaymentRequired`, with `error` and `message` when a payment was refused | No payment header, or the payment was refused at verify, see [Propagated from the facilitator](#propagated-from-the-facilitator) |
|
|
773
|
+
| 402 | `{ error: "PAYMENT_SETTLEMENT_FAILED", reason, message }` | The handler answered 2xx and the payment could not be settled. The handler's response is discarded |
|
|
774
|
+
| 402 | `{ error: "INVALID_SETTLE_AMOUNT", message }` | A metered route named an amount the middleware cannot settle, see [Metered routes](#metered-routes-upto) |
|
|
775
|
+
| 504 | `{ error: "HANDLER_TIMEOUT", message }` on Express and Hono, an empty body on Next.js | The handler did not answer within its timeout, and nothing is settled. Next.js also answers this empty 504 when the handler throws |
|
|
776
|
+
| 400, or the status the price function chose | `{ error, message }` | A price function refused the request with `X402PriceRejectedError`, and nothing was invoiced |
|
|
777
|
+
|
|
736
778
|
### Thrown by the SDK
|
|
737
779
|
|
|
738
780
|
| Error class | When |
|
|
739
781
|
|-------------|------|
|
|
740
|
-
| `X402ConfigError` | Invalid config at construction
|
|
782
|
+
| `X402ConfigError` | Invalid config at construction, the message naming the first field at fault: a bad `payTo`, a facilitator URL that is neither https nor plain http on `localhost`, `127.0.0.1` or `[::1]`, empty `routes`, a malformed or non-positive `price`, a `meter` on a route that is not `upto`, or an `upto` route with `maxTimeoutSeconds` above 3600 or a token other than USDp. Then, on a request to a route: an unknown network with no `acceptedTokens`, an accepted token whose decimals are neither in the catalog nor set via `decimals`, a price finer than the token's precision, or a price function that throws or returns something that is not a price |
|
|
741
783
|
| `X402RuntimeError` | Runtime failure; `.code` is one of the SDK codes below, or a facilitator error code propagated verbatim (see next table) |
|
|
742
784
|
| `X402PriceRejectedError` | Thrown by a price function to refuse a request before anything is invoiced; the client gets `.status` (400 by default) with `{ error: .code, message }`. `isPriceRejection(err)` recognises one by shape. |
|
|
743
785
|
|
|
@@ -753,23 +795,80 @@ Refusals `chatCompletionCap` makes, all before payment:
|
|
|
753
795
|
|
|
754
796
|
| `X402RuntimeError.code` | Meaning |
|
|
755
797
|
|-------------------------|---------|
|
|
756
|
-
| `FACILITATOR_UNAVAILABLE` |
|
|
757
|
-
| `
|
|
758
|
-
| `
|
|
798
|
+
| `FACILITATOR_UNAVAILABLE` | The facilitator gave no usable answer: unreachable, too slow on verify, a redirect, an error without an `error` field, or a body that is not JSON (see below) |
|
|
799
|
+
| `SETTLE_TIMEOUT` | Facilitator did not answer a settle within 60s. The payment may still settle |
|
|
800
|
+
| `FACILITATOR_INVALID_RESPONSE` | Facilitator returned an unexpected response shape, including a verify without a settle token. The middleware also gives this code to a verify refusal whose `error` is not a code, see [Propagated from the facilitator](#propagated-from-the-facilitator) |
|
|
801
|
+
| `INVALID_PAYMENT` | `payment-signature` / `X-PAYMENT` header is not base64-encoded JSON |
|
|
759
802
|
|
|
760
|
-
|
|
803
|
+
#### `FACILITATOR_UNAVAILABLE`
|
|
761
804
|
|
|
762
|
-
|
|
805
|
+
The SDK raises it when the facilitator gave no usable answer:
|
|
806
|
+
|
|
807
|
+
- It could not be reached: a DNS, connection or TLS failure.
|
|
808
|
+
- It answered with a redirect, which the SDK never follows. The error's `status` is the redirect's, and the configured `url` is then usually not the facilitator's own https address.
|
|
809
|
+
- It did not answer a `verify` within 10 seconds. A `settle` that runs out of its 60 seconds raises `SETTLE_TIMEOUT` instead.
|
|
810
|
+
- It answered a `verify` with an error that carries no `error` field, such as an error page from a gateway in front of it, or a `settle` with a 5xx error that carries no `error` field.
|
|
811
|
+
- It answered with a success status but a body that is not JSON, or that was cut off.
|
|
812
|
+
|
|
813
|
+
At `verify`, nothing was settled. The middleware answers the agent 402 with a new invoice and `error: "FACILITATOR_UNAVAILABLE"`, and the agent can pay again later.
|
|
814
|
+
|
|
815
|
+
At `settle`, the outcome is unknown, since the facilitator may have settled before its answer was lost. The middleware retries the settle once with the same settle token, which returns the recorded outcome if there is one. If the retry raises it too, the response is not served, and the agent gets a 402 `{ error: "PAYMENT_SETTLEMENT_FAILED", reason: "FACILITATOR_UNAVAILABLE" }` whose message tells it not to sign another payment before checking with the merchant. A retry that fails another way reports that reason instead.
|
|
816
|
+
|
|
817
|
+
### Propagated from the facilitator
|
|
763
818
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
|
767
|
-
|
|
768
|
-
| `
|
|
769
|
-
| `
|
|
770
|
-
| `
|
|
771
|
-
| `
|
|
772
|
-
| `
|
|
819
|
+
When the facilitator refuses a payment at verify, its code is forwarded as the `error` field on the re-issued 402 body, its text as `message`, and its `Retry-After` header is copied when it sent one. The agent always gets a 402. The facilitator's own status is the `.status` of the `X402RuntimeError` that `FacilitatorClient.verify` throws. The codes:
|
|
820
|
+
|
|
821
|
+
| Code | Facilitator status | Meaning | What the agent should do |
|
|
822
|
+
|------|--------------------|---------|--------------------------|
|
|
823
|
+
| `INVALID_PAYLOAD` | 400 | The payload does not have the shape of its route, carries a field another route would read, or uses a method of the other scheme | Sign a payment that matches one of the `accepts` entries |
|
|
824
|
+
| `INVALID_SIGNATURE` | 400 | EIP-712 signature recovery failed | Sign again |
|
|
825
|
+
| `INVALID_NONCE` | 409 | Nonce already used (replay detected) | Sign a new payment with a fresh nonce |
|
|
826
|
+
| `PAYMENT_EXPIRED` | 402 | `validBefore` has passed | Sign a new payment |
|
|
827
|
+
| `INSUFFICIENT_AMOUNT` | 402 | The amount the route guarantees the merchant is below the price, or the authorization is for zero | Sign for the invoiced `amount` |
|
|
828
|
+
| `INSUFFICIENT_BALANCE` | 402 | The payer does not hold the signed amount (metered payments) | Fund the wallet, sign a new payment |
|
|
829
|
+
| `ASSET_MISMATCH` | 400 | The route does not deliver the token the merchant requires | Pay so that the merchant receives one of the `accepts` assets |
|
|
830
|
+
| `RECIPIENT_MISMATCH` | 400 | The signed recipient is not the merchant's `payTo` | Sign to the invoiced `payTo` |
|
|
831
|
+
| `NETWORK_MISMATCH` | 400 | The facilitator does not support the payload's chain, or it is not the route's chain | Sign on the invoiced network |
|
|
832
|
+
| `WINDOW_TOO_LONG` | 400 | A metered authorization valid for more than 3600 s (one hour) | Sign again with a `validBefore` within the hour |
|
|
833
|
+
| `PAYMENT_METHOD_NOT_SUPPORTED` | 400 | No route for this method or token on this chain, including a metered payment on a chain without the escrow | Pay with another method or token |
|
|
834
|
+
| `COLLATERAL_NOT_SUPPORTED` | 400 | The swap delivers a token that is not a collateral of the chain | Pay for another of the `accepts` assets: the merchant lists one the facilitator does not deliver |
|
|
835
|
+
| `RATE_LIMIT_EXCEEDED` | 429 | The payer passed 1,000 verifies in the current UTC hour | Pay again after `Retry-After`, which points to the next hour |
|
|
836
|
+
| `SERVICE_UNAVAILABLE` | 503 | The facilitator could not read the chain or its own store | Pay again after a short wait |
|
|
837
|
+
|
|
838
|
+
An `error` that is not a code (capitals, digits and underscores) is not forwarded: the 402 carries `error: "FACILITATOR_INVALID_RESPONSE"` with the facilitator's message instead. The facilitator answers this way with `Bad Request` to a payment payload that does not match its request schema, and with `Too Many Requests` from its per-IP limit (see [Rate limits](#rate-limits)).
|
|
839
|
+
|
|
840
|
+
When the facilitator refuses at settle, the 402 body is `{ error: "PAYMENT_SETTLEMENT_FAILED", reason, message }`, with the facilitator's `Retry-After` header copied when it sent one. `reason` values from the facilitator:
|
|
841
|
+
|
|
842
|
+
| Reason | Facilitator status | Meaning | What the agent should do |
|
|
843
|
+
|--------|--------------------|---------|--------------------------|
|
|
844
|
+
| `INSUFFICIENT_BALANCE` | 402 | The payer no longer holds the signed amount | Fund the wallet, sign a new payment |
|
|
845
|
+
| `AUTHORIZATION_USED` | 409 | The authorization nonce is consumed or cancelled on-chain, this facilitator did not settle it | Sign a new payment |
|
|
846
|
+
| `PAYMENT_EXPIRED` | 402 | The authorization expired before settlement | Sign a new payment |
|
|
847
|
+
| `PAYMENT_NOT_VERIFIED` | 402 | No verified payment for this authorization, or it already failed | Sign a new payment |
|
|
848
|
+
| `SETTLE_IN_PROGRESS` | 409 | Another settle of the same payment is still running | Do not sign another payment before checking with the merchant |
|
|
849
|
+
| `SETTLE_TOKEN_INVALID`, `SETTLE_TOKEN_REQUIRED` | 403 | The merchant did not present the settle token verify issued: a merchant client issue, not the payer's, and nothing was settled | Do not sign another payment before checking with the merchant |
|
|
850
|
+
| `RELAYER_UNAVAILABLE`, `PARALLELIZER_PAUSED`, `SERVICE_UNAVAILABLE` | 503 | The facilitator or the chain is temporarily unable to settle | Retry later with a new payment, after `Retry-After` when given |
|
|
851
|
+
| `TX_REVERTED` | 500 | The transaction reverted for a reason the facilitator could not attribute | Sign a new payment |
|
|
852
|
+
| `SUBMISSION_FAILED` | 500 | The submission failed. When the message says the outcome is unknown, a transaction may have been sent, and the payment may have settled or may still settle | Sign a new payment, unless the message says the outcome is unknown: then check with the merchant first |
|
|
853
|
+
| `RECEIPT_UNAVAILABLE` | 500 | The transaction was sent but its confirmation could not be read | Do not sign another payment before checking with the merchant |
|
|
854
|
+
| `INVALID_PAYLOAD`, `NETWORK_MISMATCH`, `PAYMENT_METHOD_NOT_SUPPORTED` | 400 | The settle request does not match the payment that was verified, or a metered amount is missing, malformed or above the signed cap. Nothing was settled | Sign a new payment |
|
|
855
|
+
| `INSUFFICIENT_AMOUNT` | 402 | A metered amount of zero, or an authorization for zero. Nothing was settled | Sign a new payment |
|
|
856
|
+
|
|
857
|
+
Reasons from this SDK: `SETTLE_TIMEOUT`, `FACILITATOR_UNAVAILABLE` and `FACILITATOR_INVALID_RESPONSE` (the settle may still have gone through, do not pay again before checking with the merchant), `AMOUNT_MISMATCH` (the facilitator settled another amount than the one asked, money moved, the response was not served, do not pay again before checking), `UNKNOWN` (anything that is not a code, such as the `Too Many Requests` of the facilitator's per-IP limit). For `SETTLE_IN_PROGRESS`, `RECEIPT_UNAVAILABLE`, `SETTLE_TOKEN_INVALID` and `SETTLE_TOKEN_REQUIRED` the message is this SDK's as well, since the facilitator's text addresses the merchant.
|
|
858
|
+
|
|
859
|
+
`AMOUNT_MISMATCH` is also the one reason that points at the facilitator rather than at the request: it confirmed a settlement of another amount than the one the route asked for, so money moved. Alert on it, for example on 402 responses whose `reason` is `AMOUNT_MISMATCH`, rather than treating it as a routine refusal.
|
|
860
|
+
|
|
861
|
+
### Rate limits
|
|
862
|
+
|
|
863
|
+
The facilitator counts the requests of each IP address that calls it, which is the merchant's server: 60 `POST /x402/verify` a minute and 600 `POST /x402/settle` a minute, each on its own counter. It also counts the verifies of each payer: 1,000 per UTC clock hour.
|
|
864
|
+
|
|
865
|
+
| Limit | Facilitator answer | What the agent gets | What the agent should do |
|
|
866
|
+
|-------|--------------------|---------------------|--------------------------|
|
|
867
|
+
| Verifies per IP | 429 `{ error: "Too Many Requests", message }`, the message naming the time left in the minute (`Rate limit exceeded, retry in 42 seconds`, for instance), with `Retry-After` in seconds | 402 with a new invoice, `error: "FACILITATOR_INVALID_RESPONSE"`, the facilitator's message and its `Retry-After`. Nothing was verified | Send the payment again after `Retry-After` |
|
|
868
|
+
| Settles per IP | The same 429 | 402 `{ error: "PAYMENT_SETTLEMENT_FAILED", reason: "UNKNOWN", message: "The payment could not be settled." }` with the facilitator's `Retry-After`. The handler's response is discarded, the settle is not retried, and nothing was settled | The agent cannot tell this `UNKNOWN` from another one, and x402-fetch reports that the payment may still settle: check with the merchant, then sign a new payment after `Retry-After` |
|
|
869
|
+
| Verifies per payer | 429 `RATE_LIMIT_EXCEEDED`, with `Retry-After` until the next UTC hour | 402 with a new invoice, `error: "RATE_LIMIT_EXCEEDED"`, the facilitator's message and its `Retry-After` | Pay again after `Retry-After` |
|
|
870
|
+
|
|
871
|
+
The `error` of the per-IP answer is not a code, which is why the middleware reports `FACILITATOR_INVALID_RESPONSE` at verify and `UNKNOWN` at settle. A direct caller of `FacilitatorClient` sees it as it is: `verify` throws an `X402RuntimeError` whose `.code` is `"Too Many Requests"`, with `.status` 429 and `.retryAfter`, and `settle` returns it as a failure with the same code.
|
|
773
872
|
|
|
774
873
|
---
|
|
775
874
|
|
|
@@ -808,9 +907,9 @@ import {
|
|
|
808
907
|
| Decimals | per accepted token (chains catalog) | 6 (fixed default) |
|
|
809
908
|
| Auth standard | x402 / Coinbase | RFC 9110 `Payment` auth-scheme |
|
|
810
909
|
| Facilitator endpoints | `/x402/verify`, `/x402/settle` | `/mpp/verify`, `/mpp/settle` |
|
|
811
|
-
| On-chain routes | A
|
|
910
|
+
| On-chain routes | A to L, and U for `upto` | A to L, except G |
|
|
812
911
|
|
|
813
|
-
Both protocols
|
|
912
|
+
Both protocols settle through the same facilitator backend. MPP reaches routes A to L except G, its `parallel` method carrying the same `SignedPaymentPayload` as x402. Route U, for metered payments, is x402 only.
|
|
814
913
|
|
|
815
914
|
---
|
|
816
915
|
|
|
@@ -818,10 +917,11 @@ Both protocols share the same facilitator backend, the same `SignedPaymentPayloa
|
|
|
818
917
|
|
|
819
918
|
| Package | Purpose |
|
|
820
919
|
|---------|---------|
|
|
920
|
+
| [`@parallel-protocol/x402-fetch`](../x402-fetch) | The payer side: a `fetch` that pays these 402 challenges |
|
|
821
921
|
| [`@parallel-protocol/payment-core`](../payment-core) | EIP-3009 authorization builders and EIP-712 signing helpers (agent-side) |
|
|
822
922
|
| [`@parallel-protocol/x402-types`](../x402-types) | The facilitator's HTTP wire contract (Zod schemas) |
|
|
823
923
|
| [`@parallel-protocol/mpp`](../mpp) | Machine Payments Protocol middleware (IETF `Payment` auth-scheme) |
|
|
824
|
-
| [`@parallel-protocol/chains`](../chains) |
|
|
924
|
+
| [`@parallel-protocol/chains`](../chains) | 24-chain catalog with contract addresses |
|
|
825
925
|
|
|
826
926
|
---
|
|
827
927
|
|