@parallel-protocol/mpp 0.1.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 +566 -0
- package/dist/chunk-DXHOWEAQ.js +17 -0
- package/dist/chunk-HTUIO2MB.js +340 -0
- package/dist/chunk-ILKUNJBE.cjs +355 -0
- package/dist/chunk-RNM3RDKR.cjs +20 -0
- package/dist/express.cjs +131 -0
- package/dist/express.d.cts +20 -0
- package/dist/express.d.ts +20 -0
- package/dist/express.js +128 -0
- package/dist/fastify.cjs +75 -0
- package/dist/fastify.d.cts +17 -0
- package/dist/fastify.d.ts +17 -0
- package/dist/fastify.js +72 -0
- package/dist/hono.cjs +77 -0
- package/dist/hono.d.cts +19 -0
- package/dist/hono.d.ts +19 -0
- package/dist/hono.js +74 -0
- package/dist/index.cjs +63 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +2 -0
- package/dist/next.cjs +79 -0
- package/dist/next.d.cts +18 -0
- package/dist/next.d.ts +18 -0
- package/dist/next.js +76 -0
- package/dist/types-CCE-FI8i.d.cts +81 -0
- package/dist/types-CCE-FI8i.d.ts +81 -0
- package/package.json +125 -0
package/README.md
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
# @parallel-protocol/mpp
|
|
2
|
+
|
|
3
|
+
Merchant middleware for the **MPP** (Machine Payments Protocol) charge intent — the `Payment` HTTP auth-scheme dance, settled through the Parallel facilitator.
|
|
4
|
+
|
|
5
|
+
MPP is the sibling of [`@parallel-protocol/x402`](../x402): same merchant ergonomics (a middleware in front of a paid route), same facilitator backend, different wire protocol. Where x402 uses a `402` response with a custom `payment-required` header, MPP follows the RFC 9110 `WWW-Authenticate` convention with a `Payment` auth-scheme.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Agent Merchant (this SDK) Facilitator
|
|
13
|
+
| | |
|
|
14
|
+
|── GET /api/quote ─────────────>| |
|
|
15
|
+
| | (no Authorization header) |
|
|
16
|
+
|<─ 402 + WWW-Authenticate ──────| |
|
|
17
|
+
| Payment id="...", | |
|
|
18
|
+
| method="evm", | |
|
|
19
|
+
| intent="charge", | |
|
|
20
|
+
| request="<base64url>" | |
|
|
21
|
+
| | |
|
|
22
|
+
| [agent decodes request, | |
|
|
23
|
+
| signs EIP-3009 authorization, | |
|
|
24
|
+
| builds credential JSON] | |
|
|
25
|
+
| | |
|
|
26
|
+
|── GET /api/quote ─────────────>| |
|
|
27
|
+
| Authorization: Payment | |
|
|
28
|
+
| <base64url(credential)> |── POST /mpp/verify ────────>|
|
|
29
|
+
| |<─ { valid: true } ──────────|
|
|
30
|
+
| | |
|
|
31
|
+
| | [handler runs] |
|
|
32
|
+
| | |
|
|
33
|
+
| |── POST /mpp/settle ────────>|
|
|
34
|
+
| |<─ MppReceipt ───────────────|
|
|
35
|
+
| | |
|
|
36
|
+
|<─ 200 + Payment-Receipt ───────| |
|
|
37
|
+
| <base64url(receipt)> | |
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Key properties:**
|
|
41
|
+
- The handler only runs after the credential is verified.
|
|
42
|
+
- Settlement (on-chain) happens after a 2xx handler response — the agent is never charged on errors.
|
|
43
|
+
- If settlement fails, the middleware returns `402` and discards the handler response.
|
|
44
|
+
- CORS preflight (`OPTIONS`) is always passed through without challenge.
|
|
45
|
+
- The `402` challenge body also includes the `challenge` object as JSON for agents that prefer parsing the body over parsing the header.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @parallel-protocol/mpp
|
|
53
|
+
# or
|
|
54
|
+
bun add @parallel-protocol/mpp
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Install your framework separately (all are optional peer dependencies):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install express # Express 4 or 5
|
|
61
|
+
npm install next # Next.js 14 or 15
|
|
62
|
+
npm install fastify # Fastify 4 or 5
|
|
63
|
+
npm install hono # Hono 4+
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
### Hono
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Hono } from "hono";
|
|
74
|
+
import { paymentMiddleware } from "@parallel-protocol/mpp/hono";
|
|
75
|
+
|
|
76
|
+
const app = new Hono();
|
|
77
|
+
|
|
78
|
+
app.use(
|
|
79
|
+
paymentMiddleware({
|
|
80
|
+
facilitator: { url: "https://facilitator.parallel.best" },
|
|
81
|
+
routes: {
|
|
82
|
+
"/api/quote": {
|
|
83
|
+
price: "0.10", // 0.10 USDC (6 decimals by default)
|
|
84
|
+
network: "base",
|
|
85
|
+
currency: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
|
|
86
|
+
payTo: "0xYourAddress",
|
|
87
|
+
method: "evm",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
app.get("/api/quote", (c) => c.json({ quote: 42 }));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Express
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import express from "express";
|
|
100
|
+
import { paymentMiddleware } from "@parallel-protocol/mpp/express";
|
|
101
|
+
|
|
102
|
+
const app = express();
|
|
103
|
+
|
|
104
|
+
app.use(
|
|
105
|
+
paymentMiddleware({
|
|
106
|
+
facilitator: {
|
|
107
|
+
url: "https://facilitator.parallel.best",
|
|
108
|
+
},
|
|
109
|
+
routes: {
|
|
110
|
+
"/api/data": {
|
|
111
|
+
price: "0.01",
|
|
112
|
+
decimals: 18, // USDp has 18 decimals
|
|
113
|
+
network: "ethereum",
|
|
114
|
+
currency: "0x9B3a8f7CEC208e247d97dEE13313690977e24459", // USDp on Ethereum
|
|
115
|
+
payTo: "0xYourAddress",
|
|
116
|
+
method: "parallel", // unlocks all routes A–L
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
app.get("/api/data", (req, res) => {
|
|
123
|
+
res.json({ data: "protected content" });
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Next.js (App Router)
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// app/api/data/route.ts
|
|
131
|
+
import { withPayment } from "@parallel-protocol/mpp/next";
|
|
132
|
+
|
|
133
|
+
export const GET = withPayment(
|
|
134
|
+
{
|
|
135
|
+
facilitator: { url: "https://facilitator.parallel.best" },
|
|
136
|
+
routes: {
|
|
137
|
+
"/api/data": {
|
|
138
|
+
price: "0.05",
|
|
139
|
+
decimals: 18, // USDp has 18 decimals
|
|
140
|
+
network: "base",
|
|
141
|
+
currency: "0x76A9A0062ec6712b99B4f63bD2b4270185759dd5", // USDp on Base
|
|
142
|
+
payTo: "0xYourAddress",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
async (req) => {
|
|
147
|
+
return Response.json({ data: "protected content" });
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Fastify
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import Fastify from "fastify";
|
|
156
|
+
import { paymentMiddleware } from "@parallel-protocol/mpp/fastify";
|
|
157
|
+
|
|
158
|
+
const app = Fastify();
|
|
159
|
+
|
|
160
|
+
await app.register(
|
|
161
|
+
paymentMiddleware({
|
|
162
|
+
facilitator: { url: "https://facilitator.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",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
app.get("/api/data", async () => ({ data: "protected content" }));
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Configuration
|
|
180
|
+
|
|
181
|
+
### `MppMiddlewareConfig`
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface MppMiddlewareConfig {
|
|
185
|
+
facilitator: MppFacilitatorConfig;
|
|
186
|
+
routes: Record<string, MppRouteConfig>;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `MppFacilitatorConfig`
|
|
191
|
+
|
|
192
|
+
| Field | Type | Required | Description |
|
|
193
|
+
|-------|------|----------|-------------|
|
|
194
|
+
| `url` | `string` | Yes | Base URL of the Parallel facilitator (e.g. `https://facilitator.parallel.best`). The SDK internally appends `/mpp/verify` and `/mpp/settle` to this value. |
|
|
195
|
+
| `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
|
+
|
|
197
|
+
### `MppRouteConfig`
|
|
198
|
+
|
|
199
|
+
| Field | Type | Required | Description |
|
|
200
|
+
|-------|------|----------|-------------|
|
|
201
|
+
| `price` | `string \| bigint` | Yes | Amount required. A decimal string like `"0.10"` is parsed using `decimals`. Pass a `bigint` to skip parsing. |
|
|
202
|
+
| `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. |
|
|
204
|
+
| `payTo` | `Address` | Yes | Merchant receiving address. |
|
|
205
|
+
| `currency` | `Address` | Yes | ERC-20 token the payer authorizes. One token per route. |
|
|
206
|
+
| `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. |
|
|
208
|
+
| `description` | `string` | No | Human-readable description included in the challenge. |
|
|
209
|
+
| `realm` | `string` | No | Optional realm identifier in the `WWW-Authenticate` header. |
|
|
210
|
+
|
|
211
|
+
Route matching uses **longest prefix wins**: `/api/v1/data` matches `/api/v1` before `/api`.
|
|
212
|
+
|
|
213
|
+
> **Note:** Unlike x402's `acceptedTokens` list, MPP charges a **single currency per route**. To accept multiple tokens, declare multiple routes.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Methods
|
|
218
|
+
|
|
219
|
+
The `method` field controls what the agent must sign:
|
|
220
|
+
|
|
221
|
+
### `"evm"` (default) — Route A only
|
|
222
|
+
|
|
223
|
+
Standard MPP `evm/authorization`. The agent provides a flat EIP-3009 authorization:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// credential.payload shape for method="evm"
|
|
227
|
+
{
|
|
228
|
+
type: "authorization",
|
|
229
|
+
from: "0xAgentAddress",
|
|
230
|
+
to: "0xMerchantAddress",
|
|
231
|
+
value: "100000", // in token base units (e.g. 0.10 USDC = 100000 with 6 decimals)
|
|
232
|
+
validAfter: "0",
|
|
233
|
+
validBefore: "1750000000", // Unix timestamp
|
|
234
|
+
nonce: "0xabc...", // 32-byte random
|
|
235
|
+
signature: "0xabc...", // EIP-712 signature over TransferWithAuthorization
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Settles as Route A: a direct `transferWithAuthorization` on-chain. Maximum compatibility — payable by any MPP `evm` client.
|
|
240
|
+
|
|
241
|
+
### `"parallel"` — Routes A–L
|
|
242
|
+
|
|
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:
|
|
244
|
+
|
|
245
|
+
- Routes A / F / J: direct transfers (USDp→USDp, USDC→USDC, sUSDp→sUSDp)
|
|
246
|
+
- Routes B / D: USDp → collateral swap via Parallelizer (exact output)
|
|
247
|
+
- Routes E / G: USDC → USDp or sUSDp swap via Parallelizer (exact input)
|
|
248
|
+
- Route C: USDp deposit into sUSDp savings vault
|
|
249
|
+
- Routes H / I / K: sUSDp redemption (→ USDp, USDC, or other collateral)
|
|
250
|
+
- Route L: partial sUSDp redeem combined with USDp
|
|
251
|
+
|
|
252
|
+
See [`@parallel-protocol/payment-core`](../payment-core) for signing helpers and [`@parallel-protocol/x402`](../x402) for a full route table.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Wire protocol
|
|
257
|
+
|
|
258
|
+
### 402 challenge response
|
|
259
|
+
|
|
260
|
+
When no valid `Authorization: Payment` header is present:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
HTTP/1.1 402 Payment Required
|
|
264
|
+
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"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The `request` param is `base64url(JSON<EvmChargeRequest>)`:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// Decoded EvmChargeRequest
|
|
272
|
+
{
|
|
273
|
+
amount: "100000", // bigint as string, in token base units
|
|
274
|
+
currency: "0x8335...", // token address
|
|
275
|
+
recipient: "0xMerchant", // merchant's payTo
|
|
276
|
+
description?: "...",
|
|
277
|
+
methodDetails: { chainId: 8453 }
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The body is JSON and also includes the challenge object for agents that parse the body:
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"challenge": {
|
|
286
|
+
"id": "a1b2c3d4",
|
|
287
|
+
"method": "evm",
|
|
288
|
+
"intent": "charge",
|
|
289
|
+
"request": "<base64url>",
|
|
290
|
+
"expires": "2025-01-15T12:05:00.000Z"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
On re-challenge (verification failed), the body also includes `{ "error": "INVALID_SIGNATURE" }`.
|
|
296
|
+
|
|
297
|
+
### Request: `Authorization: Payment`
|
|
298
|
+
|
|
299
|
+
The agent encodes the full `MppChargeCredential` as `base64url(JSON(...))` and passes it in the `Authorization` header:
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
Authorization: Payment eyJjaGFsbGVuZ2UiOnsiLi4uIn0sInBheWxvYWQiOnsidHlwZSI6ImF1dGhvcml6YXRpb24iLC4uLn19
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The credential JSON structure:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
interface MppChargeCredential {
|
|
309
|
+
challenge: MppChallenge; // Echo of the server's challenge
|
|
310
|
+
source?: string; // Payer DID (optional, org.paymentauth)
|
|
311
|
+
payload: EvmAuthorizationPayload // method="evm"
|
|
312
|
+
| SignedPaymentPayload; // method="parallel"
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 200 response: `Payment-Receipt`
|
|
317
|
+
|
|
318
|
+
On success, the middleware sets a `Payment-Receipt` header with `base64url(JSON<MppReceipt>)`:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
interface MppReceipt {
|
|
322
|
+
status: "success";
|
|
323
|
+
method: "evm" | "parallel";
|
|
324
|
+
timestamp: string; // RFC 3339
|
|
325
|
+
reference: string; // on-chain txHash
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Exports
|
|
332
|
+
|
|
333
|
+
### Main entry (`@parallel-protocol/mpp`)
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import {
|
|
337
|
+
// Middleware (framework-agnostic)
|
|
338
|
+
createMppPaymentGate,
|
|
339
|
+
createMppPaymentMiddleware,
|
|
340
|
+
|
|
341
|
+
// Facilitator client
|
|
342
|
+
MppFacilitatorClient,
|
|
343
|
+
|
|
344
|
+
// Challenge codec
|
|
345
|
+
buildChallenge,
|
|
346
|
+
serializeChallenge,
|
|
347
|
+
parseAuthorizationHeader,
|
|
348
|
+
type MppChallenge,
|
|
349
|
+
|
|
350
|
+
// Errors
|
|
351
|
+
MppConfigError,
|
|
352
|
+
MppRuntimeError,
|
|
353
|
+
MPP_ERROR_CODES,
|
|
354
|
+
type MppErrorCode,
|
|
355
|
+
|
|
356
|
+
// Types
|
|
357
|
+
type MppFacilitatorConfig,
|
|
358
|
+
type MppMiddlewareConfig,
|
|
359
|
+
type MppRouteConfig,
|
|
360
|
+
type MppSettleResult,
|
|
361
|
+
type MppSettleSuccess,
|
|
362
|
+
type MppSettleFailure,
|
|
363
|
+
type PaymentGateResult,
|
|
364
|
+
type HTTPAdapter,
|
|
365
|
+
|
|
366
|
+
// Wire helpers (re-exported from mpp-types)
|
|
367
|
+
encodeBase64Url,
|
|
368
|
+
decodeBase64Url,
|
|
369
|
+
type MppChargeCredential,
|
|
370
|
+
type MppReceipt,
|
|
371
|
+
|
|
372
|
+
// Utils
|
|
373
|
+
chainIdFromSlug,
|
|
374
|
+
matchRoute,
|
|
375
|
+
parsePrice,
|
|
376
|
+
} from "@parallel-protocol/mpp";
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Framework adapters
|
|
380
|
+
|
|
381
|
+
| Import path | Export | Framework |
|
|
382
|
+
|-------------|--------|-----------|
|
|
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+ |
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Advanced: `createMppPaymentGate`
|
|
391
|
+
|
|
392
|
+
For custom integrations or frameworks not listed above:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { createMppPaymentGate } from "@parallel-protocol/mpp";
|
|
396
|
+
|
|
397
|
+
const gate = createMppPaymentGate(config);
|
|
398
|
+
|
|
399
|
+
const result = await gate({
|
|
400
|
+
getHeader: (name) => request.headers[name],
|
|
401
|
+
getMethod: () => request.method,
|
|
402
|
+
getPath: () => request.path,
|
|
403
|
+
getUrl: () => request.url,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
if (result.type === "pass") {
|
|
407
|
+
// Route not configured, or OPTIONS — proceed normally
|
|
408
|
+
} else if (result.type === "error") {
|
|
409
|
+
// 402 challenge: result.result.status / .headers / .body
|
|
410
|
+
} else {
|
|
411
|
+
// result.type === "verified"
|
|
412
|
+
// Run your handler...
|
|
413
|
+
const handlerStatus = await runHandler();
|
|
414
|
+
|
|
415
|
+
if (handlerStatus >= 200 && handlerStatus < 300) {
|
|
416
|
+
const settlement = await result.settle();
|
|
417
|
+
if (settlement.success) {
|
|
418
|
+
// settlement.receipt.reference = txHash
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Challenge codec (server utilities)
|
|
427
|
+
|
|
428
|
+
### `buildChallenge(route)`
|
|
429
|
+
|
|
430
|
+
Builds an `MppChallenge` for a route. Generates a random `id`, encodes the charge request as `base64url`, and computes `expires` from `maxTimeoutSeconds`.
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { buildChallenge, serializeChallenge } from "@parallel-protocol/mpp";
|
|
434
|
+
|
|
435
|
+
const challenge = buildChallenge(routeConfig);
|
|
436
|
+
// { id: "uuid-v4", method: "evm", intent: "charge", request: "base64url...", expires: "..." }
|
|
437
|
+
|
|
438
|
+
const headerValue = serializeChallenge(challenge);
|
|
439
|
+
// 'Payment id="...", method="evm", intent="charge", request="...", expires="..."'
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### `serializeChallenge(challenge)`
|
|
443
|
+
|
|
444
|
+
Serializes a challenge to a `WWW-Authenticate: Payment …` header value (RFC 9110 auth-params format). Undefined fields are omitted automatically.
|
|
445
|
+
|
|
446
|
+
### `parseAuthorizationHeader(header)`
|
|
447
|
+
|
|
448
|
+
Parses an `Authorization: Payment <token>` header into a validated `MppChargeCredential`. Returns `null` when the header is absent, not the `Payment` scheme, the base64url token is malformed, or the JSON fails schema validation.
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { parseAuthorizationHeader } from "@parallel-protocol/mpp";
|
|
452
|
+
|
|
453
|
+
const credential = parseAuthorizationHeader(req.headers.authorization);
|
|
454
|
+
if (!credential) {
|
|
455
|
+
// Issue 402 challenge
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## `MppFacilitatorClient`
|
|
462
|
+
|
|
463
|
+
Low-level client for the facilitator's MPP endpoints:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { MppFacilitatorClient } from "@parallel-protocol/mpp";
|
|
467
|
+
|
|
468
|
+
const client = new MppFacilitatorClient({
|
|
469
|
+
url: "https://facilitator.parallel.best",
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Phase 1: verify signature + reserve nonce (no on-chain tx)
|
|
473
|
+
await client.verify(credential); // throws MppRuntimeError on failure
|
|
474
|
+
|
|
475
|
+
// Phase 2: submit on-chain, return receipt
|
|
476
|
+
const result = await client.settle(credential);
|
|
477
|
+
if (result.success) {
|
|
478
|
+
console.log(result.receipt.reference); // txHash
|
|
479
|
+
} else {
|
|
480
|
+
console.error(result.error.code); // e.g. "SUBMISSION_FAILED"
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
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.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Error codes
|
|
489
|
+
|
|
490
|
+
### Thrown by the SDK
|
|
491
|
+
|
|
492
|
+
| Error class | When |
|
|
493
|
+
|-------------|------|
|
|
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 |
|
|
496
|
+
|
|
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 |
|
|
503
|
+
|
|
504
|
+
### Propagated from the facilitator
|
|
505
|
+
|
|
506
|
+
When the facilitator rejects a credential, its error code is forwarded as the `error` field in the re-issued 402 body:
|
|
507
|
+
|
|
508
|
+
| Code | Meaning |
|
|
509
|
+
|------|---------|
|
|
510
|
+
| `INVALID_SIGNATURE` | EIP-712 signature recovery failed |
|
|
511
|
+
| `INVALID_NONCE` | Nonce already used (replay detected) |
|
|
512
|
+
| `PAYMENT_EXPIRED` | `validBefore` has passed |
|
|
513
|
+
| `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) |
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## Utilities
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
import { chainIdFromSlug, matchRoute, parsePrice } from "@parallel-protocol/mpp";
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
| Function | Signature | Description |
|
|
527
|
+
|----------|-----------|-------------|
|
|
528
|
+
| `parsePrice` | `(price: string \| bigint, decimals?: number) => bigint` | Parse `"0.10"` → `100000n` (6 decimals by default) |
|
|
529
|
+
| `chainIdFromSlug` | `(network: string) => number \| undefined` | `"base"` → `8453`, `undefined` for unknown chains |
|
|
530
|
+
| `matchRoute` | `<T>(path: string, routes: Record<string, T>) => T \| undefined` | Longest-prefix route matching |
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## Comparison with x402
|
|
535
|
+
|
|
536
|
+
| | `@parallel-protocol/x402` | `@parallel-protocol/mpp` |
|
|
537
|
+
|-|--------------------------|--------------------------|
|
|
538
|
+
| Challenge status | `402` | `402` |
|
|
539
|
+
| Challenge header | `payment-required: <base64>` | `WWW-Authenticate: Payment …` |
|
|
540
|
+
| Credential header | `payment-signature: <base64>` | `Authorization: Payment <base64url>` |
|
|
541
|
+
| Receipt header | `payment-response: <base64>` | `Payment-Receipt: <base64url>` |
|
|
542
|
+
| Token per route | list (`acceptedTokens`) | single (`currency`) |
|
|
543
|
+
| Default decimals | 18 | 6 |
|
|
544
|
+
| Auth standard | x402 / Coinbase | RFC 9110 `Payment` auth-scheme |
|
|
545
|
+
| Facilitator endpoints | `/x402/verify`, `/x402/settle` | `/mpp/verify`, `/mpp/settle` |
|
|
546
|
+
| On-chain routes | A–L | A–L (same) |
|
|
547
|
+
| Cashback | Yes | Yes (same) |
|
|
548
|
+
|
|
549
|
+
Both protocols share the same facilitator backend, the same `SignedPaymentPayload` authorization format, and the same settlement routes.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Related packages
|
|
554
|
+
|
|
555
|
+
| Package | Purpose |
|
|
556
|
+
|---------|---------|
|
|
557
|
+
| [`@parallel-protocol/mpp-types`](../mpp-types) | Wire-format Zod schemas + `mppCredentialToPayment()` helper |
|
|
558
|
+
| [`@parallel-protocol/x402`](../x402) | HTTP-402 payment middleware (x402 / Coinbase protocol) |
|
|
559
|
+
| [`@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 |
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## License
|
|
565
|
+
|
|
566
|
+
MIT © Parallel Protocol
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/timeout.ts
|
|
2
|
+
var HANDLER_TIMEOUT_MS = 3e4;
|
|
3
|
+
var HANDLER_TIMEOUT = /* @__PURE__ */ Symbol("mpp: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 };
|