@parallel-protocol/mpp 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -52
- package/dist/{chunk-HTUIO2MB.js → chunk-CQ62EEHN.js} +6 -2
- package/dist/{chunk-ILKUNJBE.cjs → chunk-MEDPOLT4.cjs} +5 -1
- package/dist/express.cjs +2 -2
- package/dist/express.js +1 -1
- package/dist/fastify.cjs +4 -4
- package/dist/fastify.js +1 -1
- package/dist/hono.cjs +2 -2
- package/dist/hono.js +1 -1
- package/dist/index.cjs +13 -13
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/package.json +4 -4
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", //
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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"
|
|
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
|
|
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 |
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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="
|
|
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
|
-
|
|
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
|
|
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
|
|
495
|
-
| `MppRuntimeError` |
|
|
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
|
-
| `
|
|
498
|
-
|
|
499
|
-
| `FACILITATOR_UNAVAILABLE` |
|
|
500
|
-
| `FACILITATOR_INVALID_RESPONSE` |
|
|
501
|
-
| `
|
|
502
|
-
| `
|
|
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
|
|
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
|
-
| `
|
|
515
|
-
| `
|
|
516
|
-
| `
|
|
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
|
-
|
|
|
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
|
|
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
|
|
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) |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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-
|
|
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
|
|
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 =
|
|
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 || !
|
|
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(
|
|
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-
|
|
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
|
|
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 =
|
|
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
package/dist/index.cjs
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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
|
|
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
|
|
14
|
+
get: function () { return chunkMEDPOLT4_cjs.MppConfigError; }
|
|
15
15
|
});
|
|
16
16
|
Object.defineProperty(exports, "MppFacilitatorClient", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunkMEDPOLT4_cjs.MppFacilitatorClient; }
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(exports, "MppRuntimeError", {
|
|
21
21
|
enumerable: true,
|
|
22
|
-
get: function () { return
|
|
22
|
+
get: function () { return chunkMEDPOLT4_cjs.MppRuntimeError; }
|
|
23
23
|
});
|
|
24
24
|
Object.defineProperty(exports, "buildChallenge", {
|
|
25
25
|
enumerable: true,
|
|
26
|
-
get: function () { return
|
|
26
|
+
get: function () { return chunkMEDPOLT4_cjs.buildChallenge; }
|
|
27
27
|
});
|
|
28
28
|
Object.defineProperty(exports, "chainIdFromSlug", {
|
|
29
29
|
enumerable: true,
|
|
30
|
-
get: function () { return
|
|
30
|
+
get: function () { return chunkMEDPOLT4_cjs.chainIdFromSlug; }
|
|
31
31
|
});
|
|
32
32
|
Object.defineProperty(exports, "createMppPaymentGate", {
|
|
33
33
|
enumerable: true,
|
|
34
|
-
get: function () { return
|
|
34
|
+
get: function () { return chunkMEDPOLT4_cjs.createMppPaymentGate; }
|
|
35
35
|
});
|
|
36
36
|
Object.defineProperty(exports, "createMppPaymentMiddleware", {
|
|
37
37
|
enumerable: true,
|
|
38
|
-
get: function () { return
|
|
38
|
+
get: function () { return chunkMEDPOLT4_cjs.createMppPaymentMiddleware; }
|
|
39
39
|
});
|
|
40
40
|
Object.defineProperty(exports, "matchRoute", {
|
|
41
41
|
enumerable: true,
|
|
42
|
-
get: function () { return
|
|
42
|
+
get: function () { return chunkMEDPOLT4_cjs.matchRoute; }
|
|
43
43
|
});
|
|
44
44
|
Object.defineProperty(exports, "parseAuthorizationHeader", {
|
|
45
45
|
enumerable: true,
|
|
46
|
-
get: function () { return
|
|
46
|
+
get: function () { return chunkMEDPOLT4_cjs.parseAuthorizationHeader; }
|
|
47
47
|
});
|
|
48
48
|
Object.defineProperty(exports, "parsePrice", {
|
|
49
49
|
enumerable: true,
|
|
50
|
-
get: function () { return
|
|
50
|
+
get: function () { return chunkMEDPOLT4_cjs.parsePrice; }
|
|
51
51
|
});
|
|
52
52
|
Object.defineProperty(exports, "serializeChallenge", {
|
|
53
53
|
enumerable: true,
|
|
54
|
-
get: function () { return
|
|
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-
|
|
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
|
|
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 =
|
|
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 (
|
|
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(
|
|
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-
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"viem": "^2.0.0",
|
|
87
87
|
"express": "^4.0.0 || ^5.0.0",
|
|
88
88
|
"next": "^14.0.0 || ^15.0.0",
|
|
89
|
-
"fastify": "^
|
|
89
|
+
"fastify": "^5.12.7",
|
|
90
90
|
"hono": "^4.0.0"
|
|
91
91
|
},
|
|
92
92
|
"peerDependenciesMeta": {
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"devDependencies": {
|
|
110
110
|
"@types/express": "^5.0.1",
|
|
111
111
|
"@types/node": "^22.0.0",
|
|
112
|
-
"fastify": "^5.
|
|
112
|
+
"fastify": "^5.12.7",
|
|
113
113
|
"hono": "^4.0.0",
|
|
114
114
|
"next": "^15.0.0",
|
|
115
115
|
"tsup": "^8.0.0",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"vitest": "^4.0.14"
|
|
119
119
|
},
|
|
120
120
|
"dependencies": {
|
|
121
|
-
"@parallel-protocol/chains": "^0.5.
|
|
121
|
+
"@parallel-protocol/chains": "^0.5.1",
|
|
122
122
|
"@parallel-protocol/mpp-types": "^0.1.5",
|
|
123
123
|
"zod": "^4.4.3"
|
|
124
124
|
}
|