@parallel-protocol/x402 0.7.1 → 0.8.0
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 +53 -20
- package/dist/{chunk-JPMK5YBU.cjs → chunk-KZZY5LS6.cjs} +13 -2
- package/dist/{chunk-POU7EH3E.cjs → chunk-NA7245L5.cjs} +201 -84
- package/dist/{chunk-AFRDHGDT.js → chunk-QAECRSWF.js} +13 -2
- package/dist/{chunk-AENXD4OC.js → chunk-SA5ULSG2.js} +179 -64
- 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 +50 -6
- package/dist/index.d.ts +50 -6
- 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
|
|
|
@@ -201,7 +204,7 @@ interface PaymentMiddlewareConfig {
|
|
|
201
204
|
| `description` | `string` | No | Human-readable description of the resource, included in the 402 body. |
|
|
202
205
|
| `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
206
|
| `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 signs for (default 300). Metered routes announce how long their handler may take; the facilitator caps it at 3600 (one hour). Express, Hono and Next.js derive the handler timeout from it
|
|
207
|
+
| `maxTimeoutSeconds` | `number` | No | Settlement window the payer signs for (default 300). Metered routes announce how long their handler may take; the facilitator caps it at 3600 (one hour). 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. Fastify does not time handlers out, set the server's `connectionTimeout` / `requestTimeout` yourself. |
|
|
205
208
|
|
|
206
209
|
Route matching uses **longest prefix wins**: `/api/v1/users` will match a `/api/v1` route config before a `/api` one.
|
|
207
210
|
|
|
@@ -297,6 +300,7 @@ When no valid payment is present, the middleware returns HTTP 402 with a `paymen
|
|
|
297
300
|
interface PaymentRequired {
|
|
298
301
|
x402Version: 2;
|
|
299
302
|
error?: string; // present on re-challenge (e.g. "INVALID_SIGNATURE")
|
|
303
|
+
message?: string; // the facilitator's text for `error`, when it sent one
|
|
300
304
|
resource: {
|
|
301
305
|
url: string;
|
|
302
306
|
description?: string;
|
|
@@ -355,6 +359,7 @@ interface PaymentConfirmation {
|
|
|
355
359
|
gasSponsored: boolean;
|
|
356
360
|
networkId: string; // "eip155:8453"
|
|
357
361
|
amountSettled?: string; // metered routes: what was actually charged (smallest unit)
|
|
362
|
+
alreadySettled?: true; // the facilitator answered from a recorded outcome, this call broadcast nothing
|
|
358
363
|
}
|
|
359
364
|
```
|
|
360
365
|
|
|
@@ -371,7 +376,7 @@ routes: {
|
|
|
371
376
|
"/v1/chat/completions": {
|
|
372
377
|
scheme: "upto",
|
|
373
378
|
price: "2", // the cap, in USDp
|
|
374
|
-
maxTimeoutSeconds: 600, // the handler may take up to
|
|
379
|
+
maxTimeoutSeconds: 600, // the handler may take up to 8 minutes
|
|
375
380
|
network: "base",
|
|
376
381
|
payTo: "0xYourAddress",
|
|
377
382
|
},
|
|
@@ -423,7 +428,7 @@ Instead of a header the handler sets, a route may carry a `meter` function that
|
|
|
423
428
|
|
|
424
429
|
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
430
|
|
|
426
|
-
Timing: the payer's authorization expires after `maxTimeoutSeconds`, and settlement
|
|
431
|
+
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 is served but cannot be settled: the client gets 402 `PAYMENT_SETTLEMENT_FAILED` and the merchant has done the work for nothing — keep a margin.
|
|
427
432
|
|
|
428
433
|
---
|
|
429
434
|
|
|
@@ -689,7 +694,7 @@ if (result.type === "pass") {
|
|
|
689
694
|
}
|
|
690
695
|
```
|
|
691
696
|
|
|
692
|
-
`createPaymentGate` only verifies; it does not run the handler. Use `createPaymentMiddleware` if you want the full verify → run → settle flow managed automatically.
|
|
697
|
+
`createPaymentGate` only verifies; it does not run the handler. Use `createPaymentMiddleware` if you want the full verify → run → settle flow 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. `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
698
|
|
|
694
699
|
---
|
|
695
700
|
|
|
@@ -711,21 +716,25 @@ const requirements: FacilitatorPaymentRequirements = {
|
|
|
711
716
|
network: "base", // plain chain slug
|
|
712
717
|
};
|
|
713
718
|
|
|
714
|
-
// Phase 1: verify signature + reserve nonce (no on-chain tx)
|
|
715
|
-
|
|
719
|
+
// Phase 1: verify signature + reserve nonce (no on-chain tx). The facilitator
|
|
720
|
+
// hands back the settle token this payment must be settled with.
|
|
721
|
+
const { settleToken } = await client.verify(paymentHeader, requirements); // throws X402RuntimeError on failure
|
|
716
722
|
|
|
717
723
|
// Phase 2: submit on-chain, return settlement result
|
|
718
|
-
const result = await client.settle(paymentHeader, requirements);
|
|
724
|
+
const result = await client.settle(paymentHeader, requirements, { settleToken });
|
|
719
725
|
if (result.success) {
|
|
720
|
-
console.log(result.txHash);
|
|
721
|
-
console.log(result.route);
|
|
722
|
-
console.log(result.gasSponsored);
|
|
726
|
+
console.log(result.txHash); // on-chain tx hash
|
|
727
|
+
console.log(result.route); // "A", "B", ...
|
|
728
|
+
console.log(result.gasSponsored); // boolean
|
|
729
|
+
console.log(result.alreadySettled); // true when an earlier settle already did it
|
|
723
730
|
} else {
|
|
724
|
-
console.error(result.error.code);
|
|
731
|
+
console.error(result.error.code); // e.g. "INSUFFICIENT_BALANCE"
|
|
732
|
+
console.error(result.error.message); // the facilitator's text
|
|
733
|
+
console.error(result.error.status); // its HTTP status, result.error.retryAfter when it sent one
|
|
725
734
|
}
|
|
726
735
|
```
|
|
727
736
|
|
|
728
|
-
`verify` uses a 10-second timeout. `settle` uses a 60-second timeout to accommodate on-chain confirmation latency. `verify` throws on any failure
|
|
737
|
+
`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` and its HTTP status in `.status`; a facilitator that issues no settle token is an invalid response. `settle` returns a `FacilitatorResponse` union on facilitator errors 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. 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
738
|
|
|
730
739
|
A single-call `client.pay(paymentHeader, requirements)` (`POST /x402/pay`) also exists — it verifies and settles in one round trip, without the two-phase guarantees the middleware relies on.
|
|
731
740
|
|
|
@@ -753,23 +762,47 @@ Refusals `chatCompletionCap` makes, all before payment:
|
|
|
753
762
|
|
|
754
763
|
| `X402RuntimeError.code` | Meaning |
|
|
755
764
|
|-------------------------|---------|
|
|
756
|
-
| `FACILITATOR_UNAVAILABLE` | Facilitator timed out
|
|
757
|
-
| `
|
|
765
|
+
| `FACILITATOR_UNAVAILABLE` | Facilitator timed out on verify (10s) or was unreachable |
|
|
766
|
+
| `SETTLE_TIMEOUT` | Facilitator did not answer a settle within 60s. The payment may still settle |
|
|
767
|
+
| `FACILITATOR_INVALID_RESPONSE` | Facilitator returned an unexpected response shape, including a verify without a settle token |
|
|
758
768
|
| `INVALID_PAYMENT` | `payment-signature` / `X-PAYMENT` header could not be base64-decoded |
|
|
759
769
|
|
|
760
770
|
### Propagated from the facilitator
|
|
761
771
|
|
|
762
|
-
When the facilitator
|
|
772
|
+
When the facilitator refuses a payment at verify, its code is forwarded as the `error` field on the re-issued 402 body and its text as `message`. Common values:
|
|
763
773
|
|
|
764
774
|
| Code | Meaning |
|
|
765
775
|
|------|---------|
|
|
776
|
+
| `INVALID_PAYLOAD` | The payload does not have the shape of its route, or carries a field another route would read |
|
|
766
777
|
| `INVALID_SIGNATURE` | EIP-712 signature recovery failed |
|
|
767
778
|
| `INVALID_NONCE` | Nonce already used (replay detected) |
|
|
768
779
|
| `PAYMENT_EXPIRED` | `validBefore` has passed |
|
|
769
|
-
| `INSUFFICIENT_AMOUNT` |
|
|
780
|
+
| `INSUFFICIENT_AMOUNT` | The amount the route guarantees the merchant is below the price |
|
|
781
|
+
| `INSUFFICIENT_BALANCE` | The payer does not hold the signed amount (metered payments) |
|
|
782
|
+
| `ASSET_MISMATCH` | The route does not deliver the token the merchant requires |
|
|
783
|
+
| `RECIPIENT_MISMATCH` | The signed recipient is not the merchant's `payTo` |
|
|
770
784
|
| `NETWORK_MISMATCH` | Payload chain ≠ route config chain |
|
|
785
|
+
| `WINDOW_TOO_LONG` | A metered authorization valid for longer than the facilitator allows |
|
|
786
|
+
| `PAYMENT_METHOD_NOT_SUPPORTED` | No route for this method or token on this chain |
|
|
771
787
|
| `RATE_LIMIT_EXCEEDED` | Signer has exceeded the hourly transaction limit |
|
|
772
|
-
|
|
788
|
+
|
|
789
|
+
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:
|
|
790
|
+
|
|
791
|
+
| Reason | Meaning | What the agent should do |
|
|
792
|
+
|--------|---------|--------------------------|
|
|
793
|
+
| `INSUFFICIENT_BALANCE` | The payer no longer holds the signed amount | Fund the wallet, sign a new payment |
|
|
794
|
+
| `AUTHORIZATION_USED` | The authorization nonce is consumed or cancelled on-chain, this facilitator did not settle it | Sign a new payment |
|
|
795
|
+
| `PAYMENT_EXPIRED` | The authorization expired before settlement | Sign a new payment |
|
|
796
|
+
| `PAYMENT_NOT_VERIFIED` | No verified payment for this authorization, or it already failed | Sign a new payment |
|
|
797
|
+
| `SETTLE_IN_PROGRESS` | Another settle of the same payment is still running | Do not sign another payment before checking with the merchant |
|
|
798
|
+
| `SETTLE_TOKEN_INVALID`, `SETTLE_TOKEN_REQUIRED` | 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 |
|
|
799
|
+
| `RELAYER_UNAVAILABLE`, `PARALLELIZER_PAUSED`, `SERVICE_UNAVAILABLE` | The facilitator or the chain is temporarily unable to settle | Retry later with a new payment, after `Retry-After` when given |
|
|
800
|
+
| `TX_REVERTED`, `SUBMISSION_FAILED` | The chain refused for a reason the facilitator could not attribute | Sign a new payment |
|
|
801
|
+
| `RECEIPT_UNAVAILABLE` | The transaction was sent but its confirmation could not be read | Do not sign another payment before checking with the merchant |
|
|
802
|
+
|
|
803
|
+
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). 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.
|
|
804
|
+
|
|
805
|
+
`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.
|
|
773
806
|
|
|
774
807
|
---
|
|
775
808
|
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
var X402_ERROR_CODES = {
|
|
5
5
|
FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
|
|
6
6
|
FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
|
|
7
|
-
INVALID_PAYMENT: "INVALID_PAYMENT"
|
|
7
|
+
INVALID_PAYMENT: "INVALID_PAYMENT",
|
|
8
|
+
/** The facilitator did not answer a settle within the client's timeout: the payment may still settle. */
|
|
9
|
+
SETTLE_TIMEOUT: "SETTLE_TIMEOUT"
|
|
8
10
|
};
|
|
9
11
|
var X402ConfigError = class extends Error {
|
|
10
12
|
constructor(message, cause) {
|
|
@@ -14,12 +16,21 @@ var X402ConfigError = class extends Error {
|
|
|
14
16
|
}
|
|
15
17
|
};
|
|
16
18
|
var X402RuntimeError = class extends Error {
|
|
17
|
-
constructor(code) {
|
|
19
|
+
constructor(code, extra) {
|
|
18
20
|
super(code);
|
|
19
21
|
this.code = code;
|
|
20
22
|
this.name = "X402RuntimeError";
|
|
23
|
+
if (extra?.detail !== void 0) this.detail = extra.detail;
|
|
24
|
+
if (extra?.status !== void 0) this.status = extra.status;
|
|
25
|
+
if (extra?.retryAfter !== void 0) this.retryAfter = extra.retryAfter;
|
|
21
26
|
}
|
|
22
27
|
code;
|
|
28
|
+
/** The facilitator's message for this code, when it answered with one. */
|
|
29
|
+
detail;
|
|
30
|
+
/** The facilitator's HTTP status, when the error comes from its answer. */
|
|
31
|
+
status;
|
|
32
|
+
/** The facilitator's `Retry-After` header, when it sent one. */
|
|
33
|
+
retryAfter;
|
|
23
34
|
};
|
|
24
35
|
var X402PriceRejectedError = class extends Error {
|
|
25
36
|
constructor(code, message, status = 400) {
|