@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 +21 -0
- package/README.md +610 -0
- package/dist/chunk-2UEAVMCH.js +17 -0
- package/dist/chunk-FDQULXKG.js +452 -0
- package/dist/chunk-W3SMR5LQ.cjs +465 -0
- package/dist/chunk-XUNNMCO5.cjs +20 -0
- package/dist/express.cjs +131 -0
- package/dist/express.d.cts +19 -0
- package/dist/express.d.ts +19 -0
- package/dist/express.js +128 -0
- package/dist/fastify.cjs +79 -0
- package/dist/fastify.d.cts +16 -0
- package/dist/fastify.d.ts +16 -0
- package/dist/fastify.js +72 -0
- package/dist/hono.cjs +77 -0
- package/dist/hono.d.cts +18 -0
- package/dist/hono.d.ts +18 -0
- package/dist/hono.js +74 -0
- package/dist/index.cjs +54 -0
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +1 -0
- package/dist/next.cjs +81 -0
- package/dist/next.d.cts +17 -0
- package/dist/next.d.ts +17 -0
- package/dist/next.js +78 -0
- package/dist/types-CUi55YSx.d.cts +95 -0
- package/dist/types-CUi55YSx.d.ts +95 -0
- package/package.json +145 -0
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 };
|