@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 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 };