@parallel-protocol/mpp 0.1.4 → 0.1.5

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 CHANGED
@@ -41,6 +41,7 @@ Agent Merchant (this SDK) Facilitator
41
41
  - The handler only runs after the credential is verified.
42
42
  - Settlement (on-chain) happens after a 2xx handler response — the agent is never charged on errors.
43
43
  - If settlement fails, the middleware returns `402` and discards the handler response.
44
+ - If the handler does not finish within 30 seconds, the Express, Hono and Next.js adapters answer `504` and nothing is settled. Next.js also answers an empty `504` when the handler throws.
44
45
  - CORS preflight (`OPTIONS`) is always passed through without challenge.
45
46
  - The `402` challenge body also includes the `challenge` object as JSON for agents that prefer parsing the body over parsing the header.
46
47
 
@@ -49,18 +50,20 @@ Agent Merchant (this SDK) Facilitator
49
50
  ## Install
50
51
 
51
52
  ```bash
52
- npm install @parallel-protocol/mpp
53
+ npm install @parallel-protocol/mpp viem
53
54
  # or
54
- bun add @parallel-protocol/mpp
55
+ bun add @parallel-protocol/mpp viem
55
56
  ```
56
57
 
58
+ `viem` 2.x is a peer dependency. The public types use its `Address` type and the chain catalog loads it at runtime.
59
+
57
60
  Install your framework separately (all are optional peer dependencies):
58
61
 
59
62
  ```bash
60
63
  npm install express # Express 4 or 5
61
64
  npm install next # Next.js 14 or 15
62
65
  npm install fastify # Fastify 4 or 5
63
- npm install hono # Hono 4+
66
+ npm install hono # Hono 4
64
67
  ```
65
68
 
66
69
  ---
@@ -113,7 +116,7 @@ app.use(
113
116
  network: "ethereum",
114
117
  currency: "0x9B3a8f7CEC208e247d97dEE13313690977e24459", // USDp on Ethereum
115
118
  payTo: "0xYourAddress",
116
- method: "parallel", // unlocks all routes A–L
119
+ method: "parallel", // any route that delivers USDp (see Methods)
117
120
  },
118
121
  },
119
122
  }),
@@ -128,6 +131,7 @@ app.get("/api/data", (req, res) => {
128
131
 
129
132
  ```typescript
130
133
  // app/api/data/route.ts
134
+ import { NextResponse } from "next/server";
131
135
  import { withPayment } from "@parallel-protocol/mpp/next";
132
136
 
133
137
  export const GET = withPayment(
@@ -144,7 +148,7 @@ export const GET = withPayment(
144
148
  },
145
149
  },
146
150
  async (req) => {
147
- return Response.json({ data: "protected content" });
151
+ return NextResponse.json({ data: "protected content" });
148
152
  },
149
153
  );
150
154
  ```
@@ -153,27 +157,32 @@ export const GET = withPayment(
153
157
 
154
158
  ```typescript
155
159
  import Fastify from "fastify";
160
+ import fp from "fastify-plugin";
156
161
  import { paymentMiddleware } from "@parallel-protocol/mpp/fastify";
157
162
 
158
163
  const app = Fastify();
159
164
 
160
165
  await app.register(
161
- paymentMiddleware({
162
- facilitator: { url: "https://agents.parallel.best" },
163
- routes: {
164
- "/api/data": {
165
- price: 1000000n, // 1 USDC as raw bigint (6 decimals → skip parsing)
166
- network: "avalanche",
167
- currency: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
168
- payTo: "0xYourAddress",
166
+ fp(
167
+ paymentMiddleware({
168
+ facilitator: { url: "https://agents.parallel.best" },
169
+ routes: {
170
+ "/api/data": {
171
+ price: 1000000n, // 1 USDC as raw bigint (6 decimals, no parsing)
172
+ network: "avalanche",
173
+ currency: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", // USDC on Avalanche
174
+ payTo: "0xYourAddress",
175
+ },
169
176
  },
170
- },
171
- }),
177
+ }),
178
+ ),
172
179
  );
173
180
 
174
181
  app.get("/api/data", async () => ({ data: "protected content" }));
175
182
  ```
176
183
 
184
+ `paymentMiddleware` returns a Fastify plugin that adds `onRequest` and `onSend` hooks. Fastify keeps a plugin's hooks inside the plugin, so wrap it with [`fastify-plugin`](https://www.npmjs.com/package/fastify-plugin) (`npm install fastify-plugin`) as above. The hooks then also apply to routes declared outside it, like `/api/data` here.
185
+
177
186
  ---
178
187
 
179
188
  ## Configuration
@@ -191,7 +200,7 @@ interface MppMiddlewareConfig {
191
200
 
192
201
  | Field | Type | Required | Description |
193
202
  |-------|------|----------|-------------|
194
- | `url` | `string` | Yes | Base URL of the Parallel facilitator (e.g. `https://agents.parallel.best`). The SDK internally appends `/mpp/verify` and `/mpp/settle` to this value. |
203
+ | `url` | `string` | Yes | Base URL of the Parallel facilitator (e.g. `https://agents.parallel.best`). The SDK internally appends `/mpp/verify` and `/mpp/settle` to this value, so give it without a trailing slash. |
195
204
  | `apiKey` | `string` | No | Reserved for future use. If provided, sent as `X-API-Key` header on facilitator requests but currently not enforced server-side. |
196
205
 
197
206
  ### `MppRouteConfig`
@@ -200,15 +209,15 @@ interface MppMiddlewareConfig {
200
209
  |-------|------|----------|-------------|
201
210
  | `price` | `string \| bigint` | Yes | Amount required. A decimal string like `"0.10"` is parsed using `decimals`. Pass a `bigint` to skip parsing. |
202
211
  | `decimals` | `number` | No | Token decimals for string price parsing. **Default: `6`** (USDC). Use `18` for USDp or sUSDp. |
203
- | `network` | `string` | Yes | Chain slug: `"ethereum"`, `"base"`, `"avalanche"`, `"hyperevm"`, etc. Must be in the Parallel chain catalog. |
212
+ | `network` | `string` | Yes | Chain slug: `"ethereum"`, `"base"`, `"avalanche"` or `"hyperevm"`, the chains the facilitator settles on. Any other network raises `MppConfigError` when the middleware is created. |
204
213
  | `payTo` | `Address` | Yes | Merchant receiving address. |
205
- | `currency` | `Address` | Yes | ERC-20 token the payer authorizes. One token per route. |
214
+ | `currency` | `Address` | Yes | ERC-20 token that `payTo` receives. With `method: "evm"` it is also the token the payer signs for. One token per route. |
206
215
  | `method` | `"evm" \| "parallel"` | No | **Default: `"evm"`**. Controls the credential shape agents must provide (see [Methods](#methods)). |
207
- | `maxTimeoutSeconds` | `number` | No | Challenge validity window in seconds. Default `300`. Informational only — the nonce TTL is enforced by the facilitator. |
216
+ | `maxTimeoutSeconds` | `number` | No | Seconds until the challenge `expires`. Default `300`. Informational only: the facilitator enforces the `validBefore` that the payer signs. |
208
217
  | `description` | `string` | No | Human-readable description included in the challenge. |
209
218
  | `realm` | `string` | No | Optional realm identifier in the `WWW-Authenticate` header. |
210
219
 
211
- Route matching uses **longest prefix wins**: `/api/v1/data` matches `/api/v1` before `/api`.
220
+ Route matching uses **longest prefix wins**, on whole path segments: `/api/v1/data` matches `/api/v1` before `/api`, and `/api/database` does not match `/api/data`.
212
221
 
213
222
  > **Note:** Unlike x402's `acceptedTokens` list, MPP charges a **single currency per route**. To accept multiple tokens, declare multiple routes.
214
223
 
@@ -218,7 +227,7 @@ Route matching uses **longest prefix wins**: `/api/v1/data` matches `/api/v1` be
218
227
 
219
228
  The `method` field controls what the agent must sign:
220
229
 
221
- ### `"evm"` (default) — Route A only
230
+ ### `"evm"` (default): direct transfer
222
231
 
223
232
  Standard MPP `evm/authorization`. The agent provides a flat EIP-3009 authorization:
224
233
 
@@ -236,15 +245,19 @@ Standard MPP `evm/authorization`. The agent provides a flat EIP-3009 authorizati
236
245
  }
237
246
  ```
238
247
 
239
- Settles as Route A: a direct `transferWithAuthorization` on-chain. Maximum compatibility — payable by any MPP `evm` client.
248
+ The facilitator requires `to` to be the challenge `recipient` and `value` to cover the challenge `amount`.
249
+
250
+ Settles as a direct `transferWithAuthorization` of `currency` to `payTo`: Route A when `currency` is USDp, Route J for sUSDp and Route F for USDC. Maximum compatibility: payable by any MPP `evm` client.
251
+
252
+ ### `"parallel"`: any route that delivers `currency`
240
253
 
241
- ### `"parallel"` — Routes A–L
254
+ Parallel's extension method. The credential payload is a full `SignedPaymentPayload` (same wire format as x402). The payer can use any route below that delivers `currency` to `payTo` for at least the charged amount, except Route G, which is not available over MPP. With `currency` set to USDp, for example, routes A, E, H and L qualify.
242
255
 
243
- Parallel's extension method. The credential payload is a full `SignedPaymentPayload` (same wire format as x402). This unlocks all 12 facilitator routes (A–L), including:
256
+ The facilitator routes are:
244
257
 
245
258
  - Routes A / F / J: direct transfers (USDp→USDp, USDC→USDC, sUSDp→sUSDp)
246
259
  - Routes B / D: USDp → collateral swap via Parallelizer (exact output)
247
- - Routes E / G: USDC → USDp or sUSDp swap via Parallelizer (exact input)
260
+ - Routes E / G: USDC swapped via Parallelizer into USDp or sUSDp (exact input), on Base and Avalanche, where USDC is a collateral
248
261
  - Route C: USDp deposit into sUSDp savings vault
249
262
  - Routes H / I / K: sUSDp redemption (→ USDp, USDC, or other collateral)
250
263
  - Route L: partial sUSDp redeem combined with USDp
@@ -262,7 +275,8 @@ When no valid `Authorization: Payment` header is present:
262
275
  ```
263
276
  HTTP/1.1 402 Payment Required
264
277
  Content-Type: application/json
265
- WWW-Authenticate: Payment id="a1b2c3d4", method="evm", intent="charge", request="eyJhbW91bnQiOiIxMDAwMDAiLCJjdXJyZW5jeSI6IjB4ODMzNS4uLiIsInJlY2lwaWVudCI6IjB4TWVyY2hhbnQiLCJtZXRob2REZXRhaWxzIjp7ImNoYWluSWQiOjg0NTN9fQ", expires="2025-01-15T12:05:00.000Z", description="AI model inference"
278
+ WWW-Authenticate: Payment id="a1b2c3d4", method="evm", intent="charge", request="eyJhbW91bnQiOiIxMDAwMDAiLCJjdXJyZW5jeSI6IjB4ODMzNS4uLiIsInJlY2lwaWVudCI6IjB4TWVyY2hhbnQiLCJkZXNjcmlwdGlvbiI6IkFJIG1vZGVsIGluZmVyZW5jZSIsIm1ldGhvZERldGFpbHMiOnsiY2hhaW5JZCI6ODQ1M319", expires="2025-01-15T12:05:00.000Z", description="AI model inference"
279
+ access-control-expose-headers: WWW-Authenticate
266
280
  ```
267
281
 
268
282
  The `request` param is `base64url(JSON<EvmChargeRequest>)`:
@@ -287,12 +301,13 @@ The body is JSON and also includes the challenge object for agents that parse th
287
301
  "method": "evm",
288
302
  "intent": "charge",
289
303
  "request": "<base64url>",
290
- "expires": "2025-01-15T12:05:00.000Z"
304
+ "expires": "2025-01-15T12:05:00.000Z",
305
+ "description": "AI model inference"
291
306
  }
292
307
  }
293
308
  ```
294
309
 
295
- On re-challenge (verification failed), the body also includes `{ "error": "INVALID_SIGNATURE" }`.
310
+ When the credential uses another method, when the facilitator refuses it at verify, or when verify cannot complete, the fresh challenge also carries the error code, for example `{ "error": "INVALID_SIGNATURE" }` or `{ "error": "FACILITATOR_UNAVAILABLE" }` (see [Error codes](#error-codes)). A missing or unreadable credential gets a challenge without `error`.
296
311
 
297
312
  ### Request: `Authorization: Payment`
298
313
 
@@ -315,7 +330,7 @@ interface MppChargeCredential {
315
330
 
316
331
  ### 200 response: `Payment-Receipt`
317
332
 
318
- On success, the middleware sets a `Payment-Receipt` header with `base64url(JSON<MppReceipt>)`:
333
+ On success, the middleware sets a `Payment-Receipt` header with `base64url(JSON<MppReceipt>)`, and `access-control-expose-headers: Payment-Receipt` so that browser clients can read it:
319
334
 
320
335
  ```typescript
321
336
  interface MppReceipt {
@@ -362,6 +377,9 @@ import {
362
377
  type MppSettleFailure,
363
378
  type PaymentGateResult,
364
379
  type HTTPAdapter,
380
+ type MiddlewareResult,
381
+ type PassResult,
382
+ type RunHandlerResult,
365
383
 
366
384
  // Wire helpers (re-exported from mpp-types)
367
385
  encodeBase64Url,
@@ -380,10 +398,10 @@ import {
380
398
 
381
399
  | Import path | Export | Framework |
382
400
  |-------------|--------|-----------|
383
- | `@parallel-protocol/mpp/express` | `paymentMiddleware(config)` | Express 4/5 |
384
- | `@parallel-protocol/mpp/next` | `withPayment(config, handler)` | Next.js 14/15 |
385
- | `@parallel-protocol/mpp/fastify` | `paymentMiddleware(config)` | Fastify 4/5 |
386
- | `@parallel-protocol/mpp/hono` | `paymentMiddleware(config)` | Hono 4+ |
401
+ | `@parallel-protocol/mpp/express` | `paymentMiddleware(config)`, `ExpressAdapter` | Express 4/5 |
402
+ | `@parallel-protocol/mpp/next` | `withPayment(config, handler)`, `NextAdapter` | Next.js 14/15 |
403
+ | `@parallel-protocol/mpp/fastify` | `paymentMiddleware(config)` (a plugin, see [Fastify](#fastify)), `FastifyAdapter` | Fastify 4/5 |
404
+ | `@parallel-protocol/mpp/hono` | `paymentMiddleware(config)`, `HonoAdapter` | Hono 4 |
387
405
 
388
406
  ---
389
407
 
@@ -413,9 +431,12 @@ if (result.type === "pass") {
413
431
  const handlerStatus = await runHandler();
414
432
 
415
433
  if (handlerStatus >= 200 && handlerStatus < 300) {
416
- const settlement = await result.settle();
434
+ const settlement = await result.settle(); // can throw MppRuntimeError, see MppFacilitatorClient
417
435
  if (settlement.success) {
418
436
  // settlement.receipt.reference = txHash
437
+ // set the Payment-Receipt header to encodeBase64Url(settlement.receipt)
438
+ } else {
439
+ // settlement.error.code: discard the handler response and answer 402
419
440
  }
420
441
  }
421
442
  }
@@ -427,7 +448,7 @@ if (result.type === "pass") {
427
448
 
428
449
  ### `buildChallenge(route)`
429
450
 
430
- Builds an `MppChallenge` for a route. Generates a random `id`, encodes the charge request as `base64url`, and computes `expires` from `maxTimeoutSeconds`.
451
+ Builds an `MppChallenge` for a route. Generates a random `id`, encodes the charge request as `base64url`, and computes `expires` from `maxTimeoutSeconds`. Throws `MppConfigError` when the route's `network` is not in the chain catalog.
431
452
 
432
453
  ```typescript
433
454
  import { buildChallenge, serializeChallenge } from "@parallel-protocol/mpp";
@@ -481,7 +502,7 @@ if (result.success) {
481
502
  }
482
503
  ```
483
504
 
484
- Both methods use a 10-second timeout. `verify` throws on any failure; `settle` returns a `MppSettleResult` union on facilitator errors and throws `MppRuntimeError` only on connectivity issues.
505
+ Both methods use a 10-second timeout. `verify` throws `MppRuntimeError` on any failure, with the facilitator's error code as `.code` when it sent one. `settle` returns a `MppSettleResult` union when the facilitator refuses the settlement, with `SETTLEMENT_FAILED` as the code when the refusal carries none. It throws `MppRuntimeError` when the facilitator cannot be reached, does not answer in time, or answers 2xx without a valid receipt.
485
506
 
486
507
  ---
487
508
 
@@ -491,29 +512,54 @@ Both methods use a 10-second timeout. `verify` throws on any failure; `settle` r
491
512
 
492
513
  | Error class | When |
493
514
  |-------------|------|
494
- | `MppConfigError` | Invalid config at startup (bad `payTo`, bad `currency`, unknown `network`, invalid facilitator URL) |
495
- | `MppRuntimeError` | Runtime failure; `.code` is one of the values below |
515
+ | `MppConfigError` | Invalid config at startup, when the middleware is created: for example no routes, a bad `price`, `payTo` or `currency`, a `network` other than `ethereum`, `base`, `avalanche` or `hyperevm`, or a facilitator URL that does not parse |
516
+ | `MppRuntimeError` | Thrown by `MppFacilitatorClient`. `.code` is one of the SDK codes below, or the facilitator's own code |
496
517
 
497
- | `MppRuntimeError.code` | Meaning |
498
- |------------------------|---------|
499
- | `FACILITATOR_UNAVAILABLE` | Facilitator timed out (10s) or returned a non-JSON error |
500
- | `FACILITATOR_INVALID_RESPONSE` | Facilitator returned an unexpected response shape |
501
- | `INVALID_CREDENTIAL` | Credential could not be parsed or failed schema validation |
502
- | `METHOD_MISMATCH` | Credential `challenge.method` doesn't match the route's configured method |
518
+ | `MPP_ERROR_CODES` | Meaning |
519
+ |-------------------|---------|
520
+ | `FACILITATOR_UNAVAILABLE` | The facilitator could not be reached, did not answer within 10 seconds, or answered 2xx with a body that is not JSON. `verify` also uses it for a refusal that carries no error code |
521
+ | `FACILITATOR_INVALID_RESPONSE` | The facilitator answered 2xx with something other than `{ "valid": true }` at verify, or other than a receipt at settle |
522
+ | `METHOD_MISMATCH` | The credential's `challenge.method` is not the route's `method`. The middleware returns it in the 402 body and does not throw it |
523
+ | `INVALID_CREDENTIAL` | Not emitted. A credential that cannot be read gets a challenge without `error` |
503
524
 
504
525
  ### Propagated from the facilitator
505
526
 
506
- When the facilitator rejects a credential, its error code is forwarded as the `error` field in the re-issued 402 body:
527
+ When the facilitator refuses a credential at verify, its error code is forwarded as the `error` field in the re-issued 402 body:
507
528
 
508
529
  | Code | Meaning |
509
530
  |------|---------|
531
+ | `INVALID_PAYLOAD` | The credential, its charge request or its payload is malformed, or the payload does not fit its route |
510
532
  | `INVALID_SIGNATURE` | EIP-712 signature recovery failed |
511
533
  | `INVALID_NONCE` | Nonce already used (replay detected) |
512
534
  | `PAYMENT_EXPIRED` | `validBefore` has passed |
513
535
  | `INSUFFICIENT_AMOUNT` | Signed amount < required amount |
514
- | `NETWORK_MISMATCH` | Payload chain ≠ route config chain |
515
- | `RATE_LIMIT_EXCEEDED` | Signer exceeded the hourly transaction limit |
516
- | `PARALLELIZER_PAUSED` | On-chain router temporarily paused (swap routes only) |
536
+ | `RECIPIENT_MISMATCH` | The signed recipient is not the challenge `recipient` |
537
+ | `ASSET_MISMATCH` | The payment does not deliver the route's `currency` |
538
+ | `NETWORK_MISMATCH` | The facilitator does not support this chain, or a token or contract the payment needs is missing on it |
539
+ | `PAYMENT_METHOD_NOT_SUPPORTED` | No settlement route matches this payment on this chain |
540
+ | `COLLATERAL_NOT_SUPPORTED` | The swap output is not a collateral accepted on this chain |
541
+ | `RATE_LIMIT_EXCEEDED` | The payer reached the facilitator's hourly limit of verified payments |
542
+ | `PARALLELIZER_PAUSED` | The Parallelizer has closed the swap the payment needs, or its state could not be read. Nothing was taken. Pay with another token, or retry later |
543
+ | `SAVINGS_PAUSED` | The sUSDp savings vault the payment deposits into or redeems from is paused, or its state could not be read. Nothing was taken. Pay with another token, or retry later |
544
+ | `SERVICE_UNAVAILABLE` | The facilitator could not reach its storage or the chain. Retry later |
545
+
546
+ Settlement failures are not forwarded to the agent. The facilitator's code is available as `error.code` on the result of `settle()`, from `createMppPaymentGate` or `MppFacilitatorClient`. Settlement can return some of the verify codes above. Its own codes include:
547
+
548
+ | Code | Meaning |
549
+ |------|---------|
550
+ | `PAYMENT_NOT_VERIFIED` | No verified payment matches this credential, or it already failed to settle. After `AUTHORIZATION_USED`, `RECEIPT_UNAVAILABLE` or `SUBMISSION_FAILED`, a repeated settle gets that code again instead. The payer must sign a new payment |
551
+ | `PAYMENT_EXPIRED` | The authorization expired before settlement |
552
+ | `INSUFFICIENT_BALANCE` | The payer's balance is below the signed amount |
553
+ | `AUTHORIZATION_USED` | The authorization nonce is no longer usable on-chain. This facilitator did not settle it |
554
+ | `PARALLELIZER_PAUSED` | The Parallelizer has closed this swap, or its state could not be read. No tokens moved, but the payment may still settle until its authorization expires |
555
+ | `SAVINGS_PAUSED` | The sUSDp savings vault is paused, or its state could not be read. No tokens moved, but the payment may still settle until its authorization expires |
556
+ | `RELAYER_UNAVAILABLE` | The facilitator cannot submit transactions for now |
557
+ | `SETTLE_IN_PROGRESS` | Another settlement of this payment is running. Retry shortly to get its outcome |
558
+ | `TX_REVERTED` | The transaction reverted. The payment did not settle |
559
+ | `SWAP_PRICE_MOVED` | The Parallelizer refused the swap: its price or fee moved, between signing and settlement, beyond the margin signed with the payment. Nothing was taken, and the facilitator will not settle it. The payer must sign a new payment |
560
+ | `SWAP_UNAVAILABLE` | The Parallelizer refused the swap because it cannot make it now: the swap closed since the facilitator checked it, a cap or a maximum fee is reached, the amount exceeds what it can release, or a price feed is stale. Nothing was taken, and the facilitator will not settle it. The payer can pay with another token, or later |
561
+ | `RECEIPT_UNAVAILABLE` | A transaction was sent but its outcome is unknown. It may still settle |
562
+ | `SUBMISSION_FAILED` | The settle failed. When the message says the outcome is unknown, a transaction may have been sent, or the facilitator could not read whether the authorization was used, and the payment may have settled or may still settle |
517
563
 
518
564
  ---
519
565
 
@@ -540,13 +586,13 @@ import { chainIdFromSlug, matchRoute, parsePrice } from "@parallel-protocol/mpp"
540
586
  | Credential header | `payment-signature: <base64>` | `Authorization: Payment <base64url>` |
541
587
  | Receipt header | `payment-response: <base64>` | `Payment-Receipt: <base64url>` |
542
588
  | Token per route | list (`acceptedTokens`) | single (`currency`) |
543
- | Default decimals | 18 | 6 |
589
+ | Decimals | per accepted token (chains catalog) | 6 (default) |
544
590
  | Auth standard | x402 / Coinbase | RFC 9110 `Payment` auth-scheme |
545
591
  | Facilitator endpoints | `/x402/verify`, `/x402/settle` | `/mpp/verify`, `/mpp/settle` |
546
- | On-chain routes | A–L | A–L (same) |
592
+ | On-chain routes | A to L, and U for metered `upto` payments | A to L, except G |
547
593
  | Cashback | Yes | Yes (same) |
548
594
 
549
- Both protocols share the same facilitator backend, the same `SignedPaymentPayload` authorization format, and the same settlement routes.
595
+ Both protocols settle through the same facilitator backend and its routes. The `parallel` method carries the same `SignedPaymentPayload` as x402, and the `evm` method a plain EIP-3009 authorization.
550
596
 
551
597
  ---
552
598
 
@@ -557,7 +603,7 @@ Both protocols share the same facilitator backend, the same `SignedPaymentPayloa
557
603
  | [`@parallel-protocol/mpp-types`](../mpp-types) | Wire-format Zod schemas + `mppCredentialToPayment()` helper |
558
604
  | [`@parallel-protocol/x402`](../x402) | HTTP-402 payment middleware (x402 / Coinbase protocol) |
559
605
  | [`@parallel-protocol/payment-core`](../payment-core) | EIP-3009 signing helpers shared by x402 and MPP |
560
- | [`@parallel-protocol/chains`](../chains) | 25-chain catalog with contract addresses |
606
+ | [`@parallel-protocol/chains`](../chains) | Chain catalog with chain ids and contract addresses |
561
607
 
562
608
  ---
563
609
 
@@ -1,4 +1,4 @@
1
- import { CHAIN_NAMES, CHAINS_STATIC } from '@parallel-protocol/chains';
1
+ import { CHAIN_NAMES, CHAINS_STATIC, isActiveParallelizerChain, ACTIVE_PARALLELIZER_CHAINS } from '@parallel-protocol/chains';
2
2
  import { MppChargeCredentialSchema, MppVerifyResponseSchema, MppReceiptSchema, encodeBase64Url } from '@parallel-protocol/mpp-types';
3
3
  import { z } from 'zod';
4
4
 
@@ -212,7 +212,11 @@ var facilitatorConfigSchema = z.object({
212
212
  var routeConfigSchema = z.object({
213
213
  price: priceSchema,
214
214
  decimals: z.number().int().nonnegative().optional(),
215
- network: z.string().min(1, "Network is required"),
215
+ // The facilitator settles on the chains whose Parallelizer is live.
216
+ network: z.string().min(1, "Network is required").refine(
217
+ isActiveParallelizerChain,
218
+ `network must be one of the chains the facilitator settles on: ${ACTIVE_PARALLELIZER_CHAINS.join(", ")}`
219
+ ),
216
220
  payTo: ethereumAddressSchema,
217
221
  currency: ethereumAddressSchema,
218
222
  method: z.enum(["evm", "parallel"]).optional(),
@@ -214,7 +214,11 @@ var facilitatorConfigSchema = zod.z.object({
214
214
  var routeConfigSchema = zod.z.object({
215
215
  price: priceSchema,
216
216
  decimals: zod.z.number().int().nonnegative().optional(),
217
- network: zod.z.string().min(1, "Network is required"),
217
+ // The facilitator settles on the chains whose Parallelizer is live.
218
+ network: zod.z.string().min(1, "Network is required").refine(
219
+ chains.isActiveParallelizerChain,
220
+ `network must be one of the chains the facilitator settles on: ${chains.ACTIVE_PARALLELIZER_CHAINS.join(", ")}`
221
+ ),
218
222
  payTo: ethereumAddressSchema,
219
223
  currency: ethereumAddressSchema,
220
224
  method: zod.z.enum(["evm", "parallel"]).optional(),
package/dist/express.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkRNM3RDKR_cjs = require('./chunk-RNM3RDKR.cjs');
4
- var chunkILKUNJBE_cjs = require('./chunk-ILKUNJBE.cjs');
4
+ var chunkMEDPOLT4_cjs = require('./chunk-MEDPOLT4.cjs');
5
5
 
6
6
  // src/express/adapter.ts
7
7
  var TIMEOUT_BODY = JSON.stringify({
@@ -113,7 +113,7 @@ var ExpressAdapter = class {
113
113
 
114
114
  // src/express/index.ts
115
115
  function paymentMiddleware(config) {
116
- const middleware = chunkILKUNJBE_cjs.createMppPaymentMiddleware(config);
116
+ const middleware = chunkMEDPOLT4_cjs.createMppPaymentMiddleware(config);
117
117
  return async (req, res, next) => {
118
118
  const result = await middleware(new ExpressAdapter(req, res, next));
119
119
  if ("pass" in result) {
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { raceTimeout, HANDLER_TIMEOUT } from './chunk-DXHOWEAQ.js';
2
- import { createMppPaymentMiddleware } from './chunk-HTUIO2MB.js';
2
+ import { createMppPaymentMiddleware } from './chunk-CQ62EEHN.js';
3
3
 
4
4
  // src/express/adapter.ts
5
5
  var TIMEOUT_BODY = JSON.stringify({
package/dist/fastify.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkILKUNJBE_cjs = require('./chunk-ILKUNJBE.cjs');
3
+ var chunkMEDPOLT4_cjs = require('./chunk-MEDPOLT4.cjs');
4
4
  var mppTypes = require('@parallel-protocol/mpp-types');
5
5
 
6
6
  // src/fastify/adapter.ts
@@ -27,7 +27,7 @@ var FastifyAdapter = class {
27
27
  // src/fastify/index.ts
28
28
  function paymentMiddleware(config) {
29
29
  return async (fastify) => {
30
- const gate = chunkILKUNJBE_cjs.createMppPaymentGate(config);
30
+ const gate = chunkMEDPOLT4_cjs.createMppPaymentGate(config);
31
31
  const settleMap = /* @__PURE__ */ new WeakMap();
32
32
  fastify.addHook("onRequest", async (req, reply) => {
33
33
  const gateResult = await gate(new FastifyAdapter(req));
@@ -43,7 +43,7 @@ function paymentMiddleware(config) {
43
43
  });
44
44
  fastify.addHook("onSend", async (req, reply, payload) => {
45
45
  const settle = settleMap.get(req);
46
- if (!settle || !chunkILKUNJBE_cjs.isSuccessStatus(reply.statusCode)) {
46
+ if (!settle || !chunkMEDPOLT4_cjs.isSuccessStatus(reply.statusCode)) {
47
47
  return payload;
48
48
  }
49
49
  let settled = false;
@@ -60,7 +60,7 @@ function paymentMiddleware(config) {
60
60
  } catch {
61
61
  }
62
62
  if (!settled) {
63
- const body = JSON.stringify(chunkILKUNJBE_cjs.SETTLE_FAILURE_BODY);
63
+ const body = JSON.stringify(chunkMEDPOLT4_cjs.SETTLE_FAILURE_BODY);
64
64
  reply.status(402);
65
65
  reply.header("Content-Type", "application/json");
66
66
  reply.header("Content-Length", Buffer.byteLength(body));
package/dist/fastify.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createMppPaymentGate, isSuccessStatus, SETTLE_FAILURE_BODY } from './chunk-HTUIO2MB.js';
1
+ import { createMppPaymentGate, isSuccessStatus, SETTLE_FAILURE_BODY } from './chunk-CQ62EEHN.js';
2
2
  import { encodeBase64Url } from '@parallel-protocol/mpp-types';
3
3
 
4
4
  // src/fastify/adapter.ts
package/dist/hono.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkRNM3RDKR_cjs = require('./chunk-RNM3RDKR.cjs');
4
- var chunkILKUNJBE_cjs = require('./chunk-ILKUNJBE.cjs');
4
+ var chunkMEDPOLT4_cjs = require('./chunk-MEDPOLT4.cjs');
5
5
 
6
6
  // src/hono/adapter.ts
7
7
  var TIMEOUT_BODY = JSON.stringify({
@@ -58,7 +58,7 @@ var HonoAdapter = class {
58
58
 
59
59
  // src/hono/index.ts
60
60
  function paymentMiddleware(config) {
61
- const middleware = chunkILKUNJBE_cjs.createMppPaymentMiddleware(config);
61
+ const middleware = chunkMEDPOLT4_cjs.createMppPaymentMiddleware(config);
62
62
  return async (c, next) => {
63
63
  const result = await middleware(new HonoAdapter(c, next));
64
64
  if ("pass" in result) {
package/dist/hono.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { raceTimeout, HANDLER_TIMEOUT } from './chunk-DXHOWEAQ.js';
2
- import { createMppPaymentMiddleware } from './chunk-HTUIO2MB.js';
2
+ import { createMppPaymentMiddleware } from './chunk-CQ62EEHN.js';
3
3
 
4
4
  // src/hono/adapter.ts
5
5
  var TIMEOUT_BODY = JSON.stringify({
package/dist/index.cjs CHANGED
@@ -1,57 +1,57 @@
1
1
  'use strict';
2
2
 
3
- var chunkILKUNJBE_cjs = require('./chunk-ILKUNJBE.cjs');
3
+ var chunkMEDPOLT4_cjs = require('./chunk-MEDPOLT4.cjs');
4
4
  var mppTypes = require('@parallel-protocol/mpp-types');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "MPP_ERROR_CODES", {
9
9
  enumerable: true,
10
- get: function () { return chunkILKUNJBE_cjs.MPP_ERROR_CODES; }
10
+ get: function () { return chunkMEDPOLT4_cjs.MPP_ERROR_CODES; }
11
11
  });
12
12
  Object.defineProperty(exports, "MppConfigError", {
13
13
  enumerable: true,
14
- get: function () { return chunkILKUNJBE_cjs.MppConfigError; }
14
+ get: function () { return chunkMEDPOLT4_cjs.MppConfigError; }
15
15
  });
16
16
  Object.defineProperty(exports, "MppFacilitatorClient", {
17
17
  enumerable: true,
18
- get: function () { return chunkILKUNJBE_cjs.MppFacilitatorClient; }
18
+ get: function () { return chunkMEDPOLT4_cjs.MppFacilitatorClient; }
19
19
  });
20
20
  Object.defineProperty(exports, "MppRuntimeError", {
21
21
  enumerable: true,
22
- get: function () { return chunkILKUNJBE_cjs.MppRuntimeError; }
22
+ get: function () { return chunkMEDPOLT4_cjs.MppRuntimeError; }
23
23
  });
24
24
  Object.defineProperty(exports, "buildChallenge", {
25
25
  enumerable: true,
26
- get: function () { return chunkILKUNJBE_cjs.buildChallenge; }
26
+ get: function () { return chunkMEDPOLT4_cjs.buildChallenge; }
27
27
  });
28
28
  Object.defineProperty(exports, "chainIdFromSlug", {
29
29
  enumerable: true,
30
- get: function () { return chunkILKUNJBE_cjs.chainIdFromSlug; }
30
+ get: function () { return chunkMEDPOLT4_cjs.chainIdFromSlug; }
31
31
  });
32
32
  Object.defineProperty(exports, "createMppPaymentGate", {
33
33
  enumerable: true,
34
- get: function () { return chunkILKUNJBE_cjs.createMppPaymentGate; }
34
+ get: function () { return chunkMEDPOLT4_cjs.createMppPaymentGate; }
35
35
  });
36
36
  Object.defineProperty(exports, "createMppPaymentMiddleware", {
37
37
  enumerable: true,
38
- get: function () { return chunkILKUNJBE_cjs.createMppPaymentMiddleware; }
38
+ get: function () { return chunkMEDPOLT4_cjs.createMppPaymentMiddleware; }
39
39
  });
40
40
  Object.defineProperty(exports, "matchRoute", {
41
41
  enumerable: true,
42
- get: function () { return chunkILKUNJBE_cjs.matchRoute; }
42
+ get: function () { return chunkMEDPOLT4_cjs.matchRoute; }
43
43
  });
44
44
  Object.defineProperty(exports, "parseAuthorizationHeader", {
45
45
  enumerable: true,
46
- get: function () { return chunkILKUNJBE_cjs.parseAuthorizationHeader; }
46
+ get: function () { return chunkMEDPOLT4_cjs.parseAuthorizationHeader; }
47
47
  });
48
48
  Object.defineProperty(exports, "parsePrice", {
49
49
  enumerable: true,
50
- get: function () { return chunkILKUNJBE_cjs.parsePrice; }
50
+ get: function () { return chunkMEDPOLT4_cjs.parsePrice; }
51
51
  });
52
52
  Object.defineProperty(exports, "serializeChallenge", {
53
53
  enumerable: true,
54
- get: function () { return chunkILKUNJBE_cjs.serializeChallenge; }
54
+ get: function () { return chunkMEDPOLT4_cjs.serializeChallenge; }
55
55
  });
56
56
  Object.defineProperty(exports, "decodeBase64Url", {
57
57
  enumerable: true,
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { MPP_ERROR_CODES, MppConfigError, MppFacilitatorClient, MppRuntimeError, buildChallenge, chainIdFromSlug, createMppPaymentGate, createMppPaymentMiddleware, matchRoute, parseAuthorizationHeader, parsePrice, serializeChallenge } from './chunk-HTUIO2MB.js';
1
+ export { MPP_ERROR_CODES, MppConfigError, MppFacilitatorClient, MppRuntimeError, buildChallenge, chainIdFromSlug, createMppPaymentGate, createMppPaymentMiddleware, matchRoute, parseAuthorizationHeader, parsePrice, serializeChallenge } from './chunk-CQ62EEHN.js';
2
2
  export { decodeBase64Url, encodeBase64Url } from '@parallel-protocol/mpp-types';
package/dist/next.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkRNM3RDKR_cjs = require('./chunk-RNM3RDKR.cjs');
4
- var chunkILKUNJBE_cjs = require('./chunk-ILKUNJBE.cjs');
4
+ var chunkMEDPOLT4_cjs = require('./chunk-MEDPOLT4.cjs');
5
5
  var mppTypes = require('@parallel-protocol/mpp-types');
6
6
  var server = require('next/server');
7
7
 
@@ -27,7 +27,7 @@ var NextAdapter = class {
27
27
 
28
28
  // src/next/index.ts
29
29
  function withPayment(config, handler) {
30
- const gate = chunkILKUNJBE_cjs.createMppPaymentGate(config);
30
+ const gate = chunkMEDPOLT4_cjs.createMppPaymentGate(config);
31
31
  return async (req) => {
32
32
  const gateResult = await gate(new NextAdapter(req));
33
33
  if (gateResult.type === "pass") {
@@ -52,7 +52,7 @@ function withPayment(config, handler) {
52
52
  } catch {
53
53
  return new server.NextResponse(null, { status: 504 });
54
54
  }
55
- if (chunkILKUNJBE_cjs.isSuccessStatus(response.status)) {
55
+ if (chunkMEDPOLT4_cjs.isSuccessStatus(response.status)) {
56
56
  try {
57
57
  const settleResponse = await gateResult.settle();
58
58
  if (settleResponse.success) {
@@ -69,7 +69,7 @@ function withPayment(config, handler) {
69
69
  }
70
70
  } catch {
71
71
  }
72
- return server.NextResponse.json(chunkILKUNJBE_cjs.SETTLE_FAILURE_BODY, { status: 402 });
72
+ return server.NextResponse.json(chunkMEDPOLT4_cjs.SETTLE_FAILURE_BODY, { status: 402 });
73
73
  }
74
74
  return response;
75
75
  };
package/dist/next.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { raceTimeout, HANDLER_TIMEOUT } from './chunk-DXHOWEAQ.js';
2
- import { createMppPaymentGate, isSuccessStatus, SETTLE_FAILURE_BODY } from './chunk-HTUIO2MB.js';
2
+ import { createMppPaymentGate, isSuccessStatus, SETTLE_FAILURE_BODY } from './chunk-CQ62EEHN.js';
3
3
  import { encodeBase64Url } from '@parallel-protocol/mpp-types';
4
4
  import { NextResponse } from 'next/server';
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parallel-protocol/mpp",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Parallel Protocol MPP (Machine Payments Protocol) merchant middleware — the Payment HTTP auth-scheme dance (402 challenge → credential → receipt) over the facilitator's /mpp endpoints",
5
5
  "author": "Parallel Protocol <contact@parallel.best>",
6
6
  "repository": "github:parallel-protocol/sdk-merchant",
@@ -118,7 +118,7 @@
118
118
  "vitest": "^4.0.14"
119
119
  },
120
120
  "dependencies": {
121
- "@parallel-protocol/chains": "^0.5.0",
121
+ "@parallel-protocol/chains": "^0.5.1",
122
122
  "@parallel-protocol/mpp-types": "^0.1.5",
123
123
  "zod": "^4.4.3"
124
124
  }