@parallel-protocol/x402 0.5.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Parallel Protocol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,610 @@
1
+ # @parallel-protocol/x402
2
+
3
+ HTTP-402 payment middleware for AI agents — Express, Next.js, Fastify, Hono.
4
+
5
+ Wrap any route with a price tag. When an agent (or any HTTP client) hits the route without a valid payment, it receives a `402 Payment Required` response describing exactly what is owed and to whom. The agent signs an EIP-3009 authorization off-chain and retries the request with a `payment-signature` header; the middleware has the payment verified by the facilitator before the handler runs, and settled on-chain only after the handler returns a 2xx.
6
+
7
+ Zero smart-contract interaction on the merchant side. The [Parallel facilitator](https://agents.parallel.best) verifies signatures and handles all on-chain execution.
8
+
9
+ Building the agent side instead? [`@parallel-protocol/x402-fetch`](https://www.npmjs.com/package/@parallel-protocol/x402-fetch) is the payer counterpart — a `fetch` that settles these 402 challenges automatically.
10
+
11
+ ---
12
+
13
+ ## How it works
14
+
15
+ ```
16
+ Agent Merchant (this SDK) Facilitator
17
+ | | |
18
+ |── GET /api/data ──────────────>| |
19
+ | | (route matches, no header) |
20
+ |<─ 402 + payment-required ──────| |
21
+ | (base64 PaymentRequired) | |
22
+ | | |
23
+ | [agent signs EIP-3009 auth] | |
24
+ | | |
25
+ |── GET /api/data ──────────────>| |
26
+ | payment-signature: <base64> |── POST /x402/verify ───────>|
27
+ | |<─ { isValid: true } ────────|
28
+ | | |
29
+ | | [handler runs] |
30
+ | | |
31
+ | |── POST /x402/settle ───────>|
32
+ | |<─ { txHash, route, ... } ───|
33
+ | | |
34
+ |<─ 200 + payment-response ──────| |
35
+ | (base64 settlement proof) | |
36
+ ```
37
+
38
+ **Key properties:**
39
+ - The handler only runs after the signature is verified.
40
+ - The on-chain settlement happens after the handler produces a 2xx response — the agent is not charged on errors.
41
+ - If settlement fails (on-chain revert, facilitator timeout), the middleware returns `402` and discards the handler's response.
42
+ - 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.)
43
+ - CORS preflight (`OPTIONS`) is always passed through without challenge.
44
+
45
+ ---
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ npm install @parallel-protocol/x402
51
+ # or
52
+ bun add @parallel-protocol/x402
53
+ ```
54
+
55
+ Install your framework separately (all are optional peer dependencies):
56
+
57
+ ```bash
58
+ npm install express # Express 4 or 5
59
+ npm install next # Next.js 14 or 15
60
+ npm install fastify # Fastify 4 or 5
61
+ npm install hono # Hono 4+
62
+ ```
63
+
64
+ TypeScript consumers should also install `viem` (peer dependency) — the public types use its `Address` type. Node ≥ 18 is required.
65
+
66
+ ---
67
+
68
+ ## Quick start
69
+
70
+ ### Express
71
+
72
+ ```typescript
73
+ import express from "express";
74
+ import { paymentMiddleware } from "@parallel-protocol/x402/express";
75
+
76
+ const app = express();
77
+
78
+ app.use(
79
+ paymentMiddleware({
80
+ facilitator: {
81
+ url: "https://agents.parallel.best",
82
+ },
83
+ routes: {
84
+ "/api/data": {
85
+ price: "0.01", // 0.01 USDp
86
+ network: "base",
87
+ payTo: "0xYourAddress",
88
+ },
89
+ },
90
+ }),
91
+ );
92
+
93
+ app.get("/api/data", (req, res) => {
94
+ res.json({ data: "protected content" });
95
+ });
96
+ ```
97
+
98
+ ### Next.js (App Router)
99
+
100
+ ```typescript
101
+ // app/api/data/route.ts
102
+ import { NextResponse } from "next/server";
103
+ import { withPayment } from "@parallel-protocol/x402/next";
104
+
105
+ const config = {
106
+ facilitator: { url: "https://agents.parallel.best" },
107
+ routes: {
108
+ "/api/data": {
109
+ price: "0.10",
110
+ network: "ethereum",
111
+ payTo: "0xYourAddress",
112
+ },
113
+ },
114
+ };
115
+
116
+ export const GET = withPayment(config, async (req) => {
117
+ return NextResponse.json({ data: "protected content" });
118
+ });
119
+ ```
120
+
121
+ ### Fastify
122
+
123
+ ```typescript
124
+ import Fastify from "fastify";
125
+ import { paymentMiddleware } from "@parallel-protocol/x402/fastify";
126
+
127
+ const app = Fastify();
128
+
129
+ await app.register(
130
+ paymentMiddleware({
131
+ facilitator: { url: "https://agents.parallel.best" },
132
+ routes: {
133
+ "/api/data": {
134
+ price: "0.05",
135
+ network: "avalanche",
136
+ payTo: "0xYourAddress",
137
+ },
138
+ },
139
+ }),
140
+ );
141
+
142
+ app.get("/api/data", async () => {
143
+ return { data: "protected content" };
144
+ });
145
+ ```
146
+
147
+ ### Hono
148
+
149
+ ```typescript
150
+ import { Hono } from "hono";
151
+ import { paymentMiddleware } from "@parallel-protocol/x402/hono";
152
+
153
+ const app = new Hono();
154
+
155
+ app.use(
156
+ paymentMiddleware({
157
+ facilitator: { url: "https://agents.parallel.best" },
158
+ routes: {
159
+ "/api/data": {
160
+ price: "0.01",
161
+ network: "base",
162
+ payTo: "0xYourAddress",
163
+ },
164
+ },
165
+ }),
166
+ );
167
+
168
+ app.get("/api/data", (c) => c.json({ data: "protected content" }));
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Configuration
174
+
175
+ ### `PaymentMiddlewareConfig`
176
+
177
+ ```typescript
178
+ interface PaymentMiddlewareConfig {
179
+ facilitator: FacilitatorConfig;
180
+ routes: Record<string, RouteConfig>;
181
+ }
182
+ ```
183
+
184
+ ### `FacilitatorConfig`
185
+
186
+ | Field | Type | Required | Description |
187
+ |-------|------|----------|-------------|
188
+ | `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. |
189
+ | `apiKey` | `string` | No | Optional API key, sent as the `X-API-Key` header on facilitator requests. |
190
+
191
+ ### `RouteConfig`
192
+
193
+ | Field | Type | Required | Description |
194
+ |-------|------|----------|-------------|
195
+ | `price` | `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. |
196
+ | `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 — a `"0.01"` price is correct whether paid in USDp (18) or USDC (6), no configuration. Only needed for a custom token outside the catalog; without it, an unknown token raises `X402ConfigError` rather than being silently guessed. |
197
+ | `network` | `string` | Yes | Chain slug: `"ethereum"`, `"base"`, `"avalanche"`, `"hyperevm"`. |
198
+ | `payTo` | `Address` | Yes | The merchant's receiving address. |
199
+ | `acceptedTokens` | `Address[]` | No | List of accepted token addresses. Defaults to `[USDp, USDC, sUSDp]` for known networks (see [Default tokens](#default-tokens)). |
200
+ | `description` | `string` | No | Human-readable description of the resource, included in the 402 body. |
201
+
202
+ Route matching uses **longest prefix wins**: `/api/v1/users` will match a `/api/v1` route config before a `/api` one.
203
+
204
+ ---
205
+
206
+ ## Default tokens
207
+
208
+ For the four natively supported networks, `acceptedTokens` defaults to `[USDp, USDC, sUSDp]`:
209
+
210
+ | Network | USDp | USDC | sUSDp |
211
+ |---------|------|------|-------|
212
+ | `ethereum` | `0x9B3a8f7CEC208e247d97dEE13313690977e24459` | `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` | `0x0d45b129dc868963025Db79A9074EA9c9e32Cae4` |
213
+ | `base` | `0x76A9A0062ec6712b99B4f63bD2b4270185759dd5` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0x472eD57b376fE400259FB28e5C46eB53f0E3e7E7` |
214
+ | `avalanche` | `0x9eE1963f05553eF838604Dd39403be21ceF26AA4` | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` | `0x9d92c21205383651610f90722131655a5b8ed3e0` |
215
+ | `hyperevm` | `0xBE65F0F410A72BeC163dC65d46c83699e957D588` | `0xb88339cb7199b77e23db6e890353e22632ba630f` | `0x9B3a8f7CEC208e247d97dEE13313690977e24459` |
216
+
217
+ For any other network (from the full Parallel 25-chain catalog), you must provide `acceptedTokens` explicitly — the middleware throws `X402ConfigError` on the first request to that route otherwise.
218
+
219
+ You can also import the token list directly:
220
+
221
+ ```typescript
222
+ import { PARALLEL_TOKENS, getDefaultAcceptedTokens } from "@parallel-protocol/x402";
223
+
224
+ const baseTokens = PARALLEL_TOKENS.base;
225
+ // { usdp: "0x76A9...", susdp: "0x472e...", usdc: "0x8335...", USDS: "0x820C...", sUSDS: "0x5875..." }
226
+
227
+ const defaults = getDefaultAcceptedTokens("base");
228
+ // ["0x76A9...", "0x8335...", "0x472e..."] (USDp + USDC + sUSDp)
229
+ ```
230
+
231
+ **sUSDp** is included in the defaults (routes H, I, J, K, L are enabled out of the box). To opt out and accept only USDp and USDC, pass `acceptedTokens` explicitly:
232
+
233
+ ```typescript
234
+ import { PARALLEL_TOKENS } from "@parallel-protocol/x402";
235
+
236
+ routes: {
237
+ "/api/data": {
238
+ price: "0.01",
239
+ network: "base",
240
+ payTo: "0xYourAddress",
241
+ acceptedTokens: [
242
+ PARALLEL_TOKENS.base.usdp,
243
+ PARALLEL_TOKENS.base.usdc,
244
+ // sUSDp omitted → routes H, I, J, K, L disabled for this route
245
+ ],
246
+ },
247
+ }
248
+ ```
249
+
250
+ **Accepting Parallelizer collateral payments:** Each supported network exposes additional collateral tokens whitelisted in the Parallelizer contract. They are available in `PARALLEL_TOKENS` but excluded from the defaults. Add them to `acceptedTokens` to let merchants receive them directly:
251
+
252
+ | Network | Token | Key | Yield-bearing |
253
+ |---------|-------|-----|---------------|
254
+ | `ethereum` | frxUSD | `PARALLEL_TOKENS.ethereum.frxUSD` | No |
255
+ | `ethereum` | sfrxUSD | `PARALLEL_TOKENS.ethereum.sfrxUSD` | Yes |
256
+ | `ethereum` | USDe | `PARALLEL_TOKENS.ethereum.USDe` | No |
257
+ | `ethereum` | sUSDe | `PARALLEL_TOKENS.ethereum.sUSDe` | Yes |
258
+ | `base` | USDS | `PARALLEL_TOKENS.base.USDS` | No |
259
+ | `base` | sUSDS | `PARALLEL_TOKENS.base.sUSDS` | Yes |
260
+ | `avalanche` | ygamiUSDC | `PARALLEL_TOKENS.avalanche.ygamiUSDC` | Yes |
261
+ | `hyperevm` | USDe | `PARALLEL_TOKENS.hyperevm.USDe` | No |
262
+ | `hyperevm` | sUSDe | `PARALLEL_TOKENS.hyperevm.sUSDe` | Yes |
263
+
264
+ ```typescript
265
+ import { PARALLEL_TOKENS } from "@parallel-protocol/x402";
266
+
267
+ routes: {
268
+ "/api/data": {
269
+ price: "0.01",
270
+ network: "ethereum",
271
+ payTo: "0xYourAddress",
272
+ acceptedTokens: [
273
+ PARALLEL_TOKENS.ethereum.usdp,
274
+ PARALLEL_TOKENS.ethereum.usdc,
275
+ PARALLEL_TOKENS.ethereum.sUSDe, // opt-in → agents can pay with sUSDe
276
+ PARALLEL_TOKENS.ethereum.sfrxUSD, // opt-in → agents can pay with sfrxUSD
277
+ ],
278
+ },
279
+ }
280
+ ```
281
+
282
+ > Note: the facilitator validates that `tokenOut` is a whitelisted collateral on the target chain. If you add an address that is not in the Parallelizer's collateral list, payments to that route will be rejected at settlement.
283
+
284
+ ---
285
+
286
+ ## Payment headers
287
+
288
+ ### 402 response: `payment-required`
289
+
290
+ When no valid payment is present, the middleware returns HTTP 402 with a `payment-required` header containing a base64-encoded `PaymentRequired` object. The same JSON is also sent as the response body, and `access-control-expose-headers: payment-required` is set so browsers can read the header:
291
+
292
+ ```typescript
293
+ interface PaymentRequired {
294
+ x402Version: 2;
295
+ error?: string; // present on re-challenge (e.g. "INVALID_SIGNATURE")
296
+ resource: {
297
+ url: string;
298
+ description?: string;
299
+ mimeType: "application/json";
300
+ };
301
+ accepts: Array<{ // one entry per accepted token
302
+ scheme: "exact";
303
+ network: string; // EIP-155 format: "eip155:8453"
304
+ asset: string; // token address
305
+ amount: string; // smallest unit of `asset`, per its own decimals
306
+ payTo: string;
307
+ maxTimeoutSeconds: 300;
308
+ extra: { decimals: number }; // decimals of `asset` — payers need not guess
309
+ }>;
310
+ }
311
+ ```
312
+
313
+ ### Request header: `payment-signature`
314
+
315
+ The agent attaches a `payment-signature` (or `X-PAYMENT`) header with a **base64-encoded** `SignedPaymentPayload`:
316
+
317
+ ```typescript
318
+ // Minimal Route A payload (USDp transfer)
319
+ const signedPayload = {
320
+ scheme: "exact",
321
+ network: "base",
322
+ method: "transferWithAuthorization",
323
+ payload: {
324
+ signature: "0x...",
325
+ authorization: {
326
+ from: "0xAgentAddress",
327
+ to: "0xMerchantAddress",
328
+ value: "10000000000000000", // 0.01 USDp in wei
329
+ validAfter: "0",
330
+ validBefore: "1234567890", // Unix timestamp
331
+ nonce: "0x...",
332
+ },
333
+ },
334
+ };
335
+
336
+ const header = Buffer.from(JSON.stringify(signedPayload)).toString("base64");
337
+ ```
338
+
339
+ See [`@parallel-protocol/payment-core`](../payment-core) for EIP-3009 signing helpers.
340
+
341
+ ### 200 response: `payment-response`
342
+
343
+ On success, the middleware sets a `payment-response` header with a base64-encoded settlement confirmation:
344
+
345
+ ```typescript
346
+ interface PaymentConfirmation {
347
+ success: true;
348
+ txHash: string;
349
+ route: string; // "A", "B", "C", ...
350
+ chain: string; // "base"
351
+ gasSponsored: boolean;
352
+ networkId: string; // "eip155:8453"
353
+ }
354
+ ```
355
+
356
+ The `access-control-expose-headers` header is set automatically so browsers can read `payment-response`.
357
+
358
+ ---
359
+
360
+ ## Payment routes
361
+
362
+ The facilitator supports 12 settlement routes. The route is selected automatically based on the signed payload:
363
+
364
+ | Route | Credential method(s) | tokenIn (agent pays) | tokenOut (merchant receives) | Gas est. | Notes |
365
+ |-------|--------------------------|---------------------|------------------------------|----------|-------|
366
+ | **A** | `transferWithAuthorization` | USDp | USDp | ~65K | Direct USDp transfer |
367
+ | **B** | `swapExactOutputWithAuthorization` | USDp | USDC (exact) | ~120K | Parallelizer swaps agent's USDp → exact USDC for merchant |
368
+ | **C** | `depositWithAuthorization` | USDp | sUSDp | ~90K | Agent's USDp deposited into savings vault; merchant receives sUSDp shares |
369
+ | **D** | `swapExactOutputWithAuthorization` | USDp | non-USDC backing collateral (exact) | ~120K | Same as B but for other Parallelizer collaterals |
370
+ | **E** | `swapExactInputWithAuthorization` | USDC (exact) | USDp (min) | ~120K | Agent pays exact USDC; Parallelizer delivers minimum USDp to merchant |
371
+ | **F** | `transferWithAuthorization` | USDC (or any EIP-3009 token) | same token | ~65K | Direct EIP-3009 token push |
372
+ | **G** | `swapExactInputWithAuthorization` | USDC (exact) | sUSDp | ~200K | USDC → USDp swap, then USDp deposited as sUSDp for merchant (atomic multicall3) |
373
+ | **H** | `redeemWithAuthorization` | sUSDp shares | USDp | ~130K | Agent burns sUSDp shares; merchant receives USDp assets |
374
+ | **I** | `redeemWithAuthorization` + `swapExactOutputWithAuthorization` | sUSDp | USDC (exact) | ~180K | Agent redeems sUSDp → USDp, then swaps USDp → exact USDC for merchant (atomic multicall3) |
375
+ | **J** | `transferWithAuthorization` | sUSDp | sUSDp | ~65K | Direct sUSDp share transfer |
376
+ | **K** | `redeemWithAuthorization` + `swapExactOutputWithAuthorization` | sUSDp | non-USDC backing (exact) | ~180K | Same as I but for other Parallelizer collaterals (atomic multicall3) |
377
+ | **L** | `partialRedeemWithAuthorization` | sUSDp (partial) + USDp | USDp or backing collateral | ~180K | Partial sUSDp redeem combined with existing USDp; delivers to merchant (atomic multicall3) |
378
+
379
+ **Chain requirements:**
380
+ - Routes B, D, E, G, I, K, L require the **Parallelizer** to be deployed on the target chain (Ethereum, Base, Avalanche, HyperEVM).
381
+ - Routes C, G, H, I, J, K, L require **sUSDp** (savings module) to be deployed on the target chain.
382
+ - Route A and F work on all chains where the respective token is deployed.
383
+ - Gas sponsorship is decided by the facilitator per payment and reported as `gasSponsored` in the settlement receipt.
384
+
385
+ ---
386
+
387
+ ## Exports
388
+
389
+ ### Main entry (`@parallel-protocol/x402`)
390
+
391
+ ```typescript
392
+ import {
393
+ // Middleware (framework-agnostic)
394
+ createPaymentGate,
395
+ createPaymentMiddleware,
396
+
397
+ // Facilitator client
398
+ FacilitatorClient,
399
+
400
+ // Errors
401
+ X402ConfigError,
402
+ X402RuntimeError,
403
+ X402_ERROR_CODES,
404
+ type X402ErrorCode,
405
+
406
+ // Tokens
407
+ PARALLEL_TOKENS,
408
+ getDefaultAcceptedTokens,
409
+ type ParallelNetwork,
410
+
411
+ // Types
412
+ type FacilitatorConfig,
413
+ type FacilitatorResponse,
414
+ type HTTPAdapter,
415
+ type MiddlewareResult,
416
+ type PassResult,
417
+ type PaymentConfirmation,
418
+ type PaymentFailure,
419
+ type PaymentGateResult,
420
+ type PaymentMiddlewareConfig,
421
+ type PaymentRequired,
422
+ type PaymentRequirements,
423
+ type ResourceInfo,
424
+ type RouteConfig,
425
+ type RunHandlerResult,
426
+
427
+ // Utils
428
+ encodeBase64,
429
+ parsePrice,
430
+ toEip155Network,
431
+ validateAddress,
432
+ } from "@parallel-protocol/x402";
433
+ ```
434
+
435
+ ### Framework adapters
436
+
437
+ | Import path | Export | Framework |
438
+ |-------------|--------|-----------|
439
+ | `@parallel-protocol/x402/express` | `paymentMiddleware(config)` | Express 4/5 |
440
+ | `@parallel-protocol/x402/next` | `withPayment(config, handler)` | Next.js 14/15 |
441
+ | `@parallel-protocol/x402/fastify` | `paymentMiddleware(config)` | Fastify 4/5 |
442
+ | `@parallel-protocol/x402/hono` | `paymentMiddleware(config)` | Hono 4+ |
443
+
444
+ Each subpath also exports its adapter class (`ExpressAdapter`, `NextAdapter`, `FastifyAdapter`, `HonoAdapter`) for use with [`createPaymentGate`](#advanced-createpaymentgate).
445
+
446
+ ---
447
+
448
+ ## Advanced: `createPaymentGate`
449
+
450
+ For frameworks not covered above, or for custom integrations, you can use the lower-level `createPaymentGate` function:
451
+
452
+ ```typescript
453
+ import { createPaymentGate } from "@parallel-protocol/x402";
454
+
455
+ const gate = createPaymentGate(config);
456
+
457
+ // In your request handler:
458
+ const result = await gate({
459
+ getHeader: (name) => request.headers[name],
460
+ getMethod: () => request.method,
461
+ getPath: () => request.path,
462
+ getUrl: () => request.url,
463
+ });
464
+
465
+ if (result.type === "pass") {
466
+ // Route not configured for payment, proceed normally
467
+ } else if (result.type === "error") {
468
+ // Send 402: result.result.status / result.result.headers / result.result.body
469
+ } else {
470
+ // result.type === "verified"
471
+ // Run your handler...
472
+ const handlerStatus = await runHandler();
473
+
474
+ if (handlerStatus >= 200 && handlerStatus < 300) {
475
+ const settlement = await result.settle();
476
+ if (settlement.success) {
477
+ // settlement.txHash / settlement.route / settlement.gasSponsored
478
+ }
479
+ }
480
+ }
481
+ ```
482
+
483
+ `createPaymentGate` only verifies; it does not run the handler. Use `createPaymentMiddleware` if you want the full verify → run → settle flow managed automatically.
484
+
485
+ ---
486
+
487
+ ## `FacilitatorClient`
488
+
489
+ Low-level client for the facilitator's x402 endpoints:
490
+
491
+ ```typescript
492
+ import { FacilitatorClient, PARALLEL_TOKENS, type FacilitatorPaymentRequirements } from "@parallel-protocol/x402";
493
+
494
+ const client = new FacilitatorClient({
495
+ url: "https://agents.parallel.best",
496
+ });
497
+
498
+ const requirements: FacilitatorPaymentRequirements = {
499
+ maxAmountRequired: "10000000000000000", // 0.01 USDp, smallest unit
500
+ asset: PARALLEL_TOKENS.base.usdp,
501
+ payTo: "0xYourAddress",
502
+ network: "base", // plain chain slug
503
+ };
504
+
505
+ // Phase 1: verify signature + reserve nonce (no on-chain tx)
506
+ await client.verify(paymentHeader, requirements); // throws X402RuntimeError on failure
507
+
508
+ // Phase 2: submit on-chain, return settlement result
509
+ const result = await client.settle(paymentHeader, requirements);
510
+ if (result.success) {
511
+ console.log(result.txHash); // on-chain tx hash
512
+ console.log(result.route); // "A", "B", ...
513
+ console.log(result.gasSponsored); // boolean
514
+ } else {
515
+ console.error(result.error.code); // e.g. "SUBMISSION_FAILED"
516
+ }
517
+ ```
518
+
519
+ `verify` uses a 10-second timeout. `settle` uses a 60-second timeout to accommodate on-chain confirmation latency. `verify` throws on any failure; `settle` returns a `FacilitatorResponse` union on facilitator errors and throws `X402RuntimeError` on connectivity issues, an undecodable payment header, or a malformed facilitator response.
520
+
521
+ 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.
522
+
523
+ ---
524
+
525
+ ## Error codes
526
+
527
+ ### Thrown by the SDK
528
+
529
+ | Error class | When |
530
+ |-------------|------|
531
+ | `X402ConfigError` | Invalid config at construction (bad `payTo`, bad facilitator URL, empty `routes`, non-positive `price`) — plus, on the first request to a route, an unknown network with no `acceptedTokens`, or an accepted token whose decimals are neither in the catalog nor set via `decimals` |
532
+ | `X402RuntimeError` | Runtime failure; `.code` is one of the SDK codes below, or a facilitator error code propagated verbatim (see next table) |
533
+
534
+ | `X402RuntimeError.code` | Meaning |
535
+ |-------------------------|---------|
536
+ | `FACILITATOR_UNAVAILABLE` | Facilitator timed out (10s on verify, 60s on settle) or was unreachable |
537
+ | `FACILITATOR_INVALID_RESPONSE` | Facilitator returned an unexpected response shape |
538
+ | `INVALID_PAYMENT` | `payment-signature` / `X-PAYMENT` header could not be base64-decoded |
539
+
540
+ ### Propagated from the facilitator
541
+
542
+ When the facilitator rejects a payment, its error code is forwarded as the `error` field on the re-issued 402 body. Common values:
543
+
544
+ | Code | Meaning |
545
+ |------|---------|
546
+ | `INVALID_SIGNATURE` | EIP-712 signature recovery failed |
547
+ | `INVALID_NONCE` | Nonce already used (replay detected) |
548
+ | `PAYMENT_EXPIRED` | `validBefore` has passed |
549
+ | `INSUFFICIENT_AMOUNT` | Signed amount < required amount |
550
+ | `NETWORK_MISMATCH` | Payload chain ≠ route config chain |
551
+ | `RATE_LIMIT_EXCEEDED` | Signer has exceeded the hourly transaction limit |
552
+ | `PARALLELIZER_PAUSED` | On-chain router temporarily paused (swap routes only) |
553
+
554
+ ---
555
+
556
+ ## Utilities
557
+
558
+ ```typescript
559
+ import {
560
+ parsePrice,
561
+ validateAddress,
562
+ toEip155Network,
563
+ encodeBase64,
564
+ getDefaultAcceptedTokens,
565
+ PARALLEL_TOKENS,
566
+ } from "@parallel-protocol/x402";
567
+ ```
568
+
569
+ | Function | Signature | Description |
570
+ |----------|-----------|-------------|
571
+ | `parsePrice` | `(price: string \| bigint, decimals?: number) => bigint` | Parse `"0.01"` → `10000000000000000n` (18 decimals by default) |
572
+ | `validateAddress` | `(address: string) => address is Address` | Check 0x + 40-hex-char format |
573
+ | `toEip155Network` | `(network: string) => string` | `"base"` → `"eip155:8453"` |
574
+ | `encodeBase64` | `(obj: unknown) => string` | `JSON.stringify` → base64 |
575
+ | `getDefaultAcceptedTokens` | `(network: string) => [Address, Address, Address] \| undefined` | `[USDp, USDC, sUSDp]` for known networks |
576
+
577
+ ---
578
+
579
+ ## Comparison with MPP
580
+
581
+ | | `@parallel-protocol/x402` | `@parallel-protocol/mpp` |
582
+ |-|--------------------------|--------------------------|
583
+ | Challenge status | `402` | `402` |
584
+ | Challenge header | `payment-required: <base64>` | `WWW-Authenticate: Payment …` |
585
+ | Credential header | `payment-signature: <base64>` | `Authorization: Payment <base64url>` |
586
+ | Receipt header | `payment-response: <base64>` | `Payment-Receipt: <base64url>` |
587
+ | Token per route | list (`acceptedTokens`) | single (`currency`) |
588
+ | Decimals | per accepted token (chains catalog) | 6 (fixed default) |
589
+ | Auth standard | x402 / Coinbase | RFC 9110 `Payment` auth-scheme |
590
+ | Facilitator endpoints | `/x402/verify`, `/x402/settle` | `/mpp/verify`, `/mpp/settle` |
591
+ | On-chain routes | A–L | A–L (same) |
592
+
593
+ Both protocols share the same facilitator backend, the same `SignedPaymentPayload` authorization format, and the same settlement routes.
594
+
595
+ ---
596
+
597
+ ## Related packages
598
+
599
+ | Package | Purpose |
600
+ |---------|---------|
601
+ | [`@parallel-protocol/payment-core`](../payment-core) | EIP-3009 authorization builders and EIP-712 signing helpers (agent-side) |
602
+ | [`@parallel-protocol/x402-types`](../x402-types) | The facilitator's HTTP wire contract (Zod schemas) |
603
+ | [`@parallel-protocol/mpp`](../mpp) | Machine Payments Protocol middleware (IETF `Payment` auth-scheme) |
604
+ | [`@parallel-protocol/chains`](../chains) | 25-chain catalog with contract addresses |
605
+
606
+ ---
607
+
608
+ ## License
609
+
610
+ MIT © Parallel Protocol
@@ -0,0 +1,17 @@
1
+ // src/timeout.ts
2
+ var HANDLER_TIMEOUT_MS = 3e4;
3
+ var HANDLER_TIMEOUT = /* @__PURE__ */ Symbol("x402:handler-timeout");
4
+ async function raceTimeout(promise, ms = HANDLER_TIMEOUT_MS) {
5
+ let timer;
6
+ const timeout = new Promise((resolve) => {
7
+ timer = setTimeout(() => resolve(HANDLER_TIMEOUT), ms);
8
+ timer.unref?.();
9
+ });
10
+ try {
11
+ return await Promise.race([promise, timeout]);
12
+ } finally {
13
+ clearTimeout(timer);
14
+ }
15
+ }
16
+
17
+ export { HANDLER_TIMEOUT, raceTimeout };