@parallel-protocol/x402 0.5.3 → 0.7.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 +226 -6
- package/dist/{chunk-75GLDK7L.js → chunk-AENXD4OC.js} +338 -81
- package/dist/chunk-AFRDHGDT.js +36 -0
- package/dist/chunk-JPMK5YBU.cjs +42 -0
- package/dist/chunk-POU7EH3E.cjs +755 -0
- package/dist/express.cjs +63 -6
- package/dist/express.d.cts +3 -2
- package/dist/express.d.ts +3 -2
- package/dist/express.js +61 -4
- package/dist/fastify.cjs +88 -14
- package/dist/fastify.d.cts +2 -1
- package/dist/fastify.d.ts +2 -1
- package/dist/fastify.js +86 -12
- package/dist/hono.cjs +27 -6
- package/dist/hono.d.cts +3 -2
- package/dist/hono.d.ts +3 -2
- package/dist/hono.js +25 -4
- package/dist/index.cjs +41 -20
- package/dist/index.d.cts +64 -5
- package/dist/index.d.ts +64 -5
- package/dist/index.js +2 -1
- package/dist/llm.cjs +486 -0
- package/dist/llm.d.cts +203 -0
- package/dist/llm.d.ts +203 -0
- package/dist/llm.js +473 -0
- package/dist/next.cjs +57 -7
- package/dist/next.d.cts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +54 -4
- package/dist/types-q_gu5wjz.d.cts +185 -0
- package/dist/types-q_gu5wjz.d.ts +185 -0
- package/package.json +12 -2
- package/dist/chunk-2UEAVMCH.js +0 -17
- package/dist/chunk-KQPIZ6SZ.cjs +0 -491
- package/dist/chunk-XUNNMCO5.cjs +0 -20
- package/dist/types-CUi55YSx.d.cts +0 -95
- package/dist/types-CUi55YSx.d.ts +0 -95
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ Agent Merchant (this SDK) Facilitator
|
|
|
40
40
|
- The on-chain settlement happens after the handler produces a 2xx response — the agent is not charged on errors.
|
|
41
41
|
- If settlement fails (on-chain revert, facilitator timeout), the middleware returns `402` and discards the handler's response.
|
|
42
42
|
- Handlers are bounded by a 30-second timeout on Express, Hono and Next.js — a slow handler returns `504` and the agent is not charged. (Fastify manages handler execution itself, so no timeout is applied there.)
|
|
43
|
+
- On paid Fastify routes, writing to `reply.raw` (the SSE/streaming pattern) fails loudly instead of sending: a raw response skips the settlement hooks and would go out unpaid. Streaming is not supported on paid routes.
|
|
43
44
|
- CORS preflight (`OPTIONS`) is always passed through without challenge.
|
|
44
45
|
|
|
45
46
|
---
|
|
@@ -192,12 +193,15 @@ interface PaymentMiddlewareConfig {
|
|
|
192
193
|
|
|
193
194
|
| Field | Type | Required | Description |
|
|
194
195
|
|-------|------|----------|-------------|
|
|
195
|
-
| `price` | `string \| bigint` | Yes | Amount required. A string like `"0.01"` is parsed with each accepted token's own decimals (or with `decimals` when set explicitly). Pass a `bigint` to skip parsing. |
|
|
196
|
+
| `price` | `string \| bigint \| (ctx) => string \| bigint` | Yes | Amount required. A string like `"0.01"` is parsed with each accepted token's own decimals (or with `decimals` when set explicitly). Pass a `bigint` to skip parsing. A function prices each request from its content, see [Per-request pricing](#per-request-pricing). |
|
|
196
197
|
| `decimals` | `number` | No | Override for the decimals used to parse a string price. By default each accepted token is priced with its own decimals from the `@parallel-protocol/chains` catalog — a `"0.01"` price is correct whether paid in USDp (18) or USDC (6), no configuration. Only needed for a custom token outside the catalog; without it, an unknown token raises `X402ConfigError` rather than being silently guessed. |
|
|
197
198
|
| `network` | `string` | Yes | Chain slug: `"ethereum"`, `"base"`, `"avalanche"`, `"hyperevm"`. |
|
|
198
199
|
| `payTo` | `Address` | Yes | The merchant's receiving address. |
|
|
199
|
-
| `acceptedTokens` | `Address[]` | No | List of accepted token addresses. Defaults to `[USDp, USDC, sUSDp]` for known networks (see [Default tokens](#default-tokens)). |
|
|
200
|
+
| `acceptedTokens` | `Address[]` | No | List of accepted token addresses. Defaults to `[USDp, USDC, sUSDp]` for known networks (see [Default tokens](#default-tokens)). On an `upto` route it defaults to USDp alone, and any other token is rejected at construction: metered payments are USDp only. |
|
|
200
201
|
| `description` | `string` | No | Human-readable description of the resource, included in the 402 body. |
|
|
202
|
+
| `scheme` | `"exact" \| "upto"` | No | `exact` (default) charges `price` as invoiced. `upto` invoices `price` as a **cap** the payer signs; the handler names the actual amount to charge and the rest is refunded on-chain — see [Metered routes](#metered-routes-upto). |
|
|
203
|
+
| `meter` | `(ctx) => string \| bigint \| undefined` | No | `upto` routes only: reads the amount to charge from the handler's response, see [Reading the amount from the response](#reading-the-amount-from-the-response-meter). |
|
|
204
|
+
| `maxTimeoutSeconds` | `number` | No | Settlement window the payer signs for (default 300). Metered routes announce how long their handler may take; the facilitator caps it at 3600 (one hour). Express, Hono and Next.js derive the handler timeout from it; Fastify does not time handlers out, set the server's `connectionTimeout` / `requestTimeout` yourself. |
|
|
201
205
|
|
|
202
206
|
Route matching uses **longest prefix wins**: `/api/v1/users` will match a `/api/v1` route config before a `/api` one.
|
|
203
207
|
|
|
@@ -299,12 +303,12 @@ interface PaymentRequired {
|
|
|
299
303
|
mimeType: "application/json";
|
|
300
304
|
};
|
|
301
305
|
accepts: Array<{ // one entry per accepted token
|
|
302
|
-
scheme: "exact";
|
|
306
|
+
scheme: "exact" | "upto"; // the route's scheme ("exact" unless configured)
|
|
303
307
|
network: string; // EIP-155 format: "eip155:8453"
|
|
304
308
|
asset: string; // token address
|
|
305
|
-
amount: string; // smallest unit of `asset`, per its own decimals
|
|
309
|
+
amount: string; // smallest unit of `asset`, per its own decimals — the cap for "upto"
|
|
306
310
|
payTo: string;
|
|
307
|
-
maxTimeoutSeconds: 300
|
|
311
|
+
maxTimeoutSeconds: number; // the route's settlement window (default 300)
|
|
308
312
|
extra: { decimals: number }; // decimals of `asset` — payers need not guess
|
|
309
313
|
}>;
|
|
310
314
|
}
|
|
@@ -350,6 +354,7 @@ interface PaymentConfirmation {
|
|
|
350
354
|
chain: string; // "base"
|
|
351
355
|
gasSponsored: boolean;
|
|
352
356
|
networkId: string; // "eip155:8453"
|
|
357
|
+
amountSettled?: string; // metered routes: what was actually charged (smallest unit)
|
|
353
358
|
}
|
|
354
359
|
```
|
|
355
360
|
|
|
@@ -357,9 +362,176 @@ The `access-control-expose-headers` header is set automatically so browsers can
|
|
|
357
362
|
|
|
358
363
|
---
|
|
359
364
|
|
|
365
|
+
## Metered routes (`upto`)
|
|
366
|
+
|
|
367
|
+
Some resources only know their cost after the work is done — an LLM completion is priced by the tokens it generates. A route declared with `scheme: "upto"` invoices `price` as a **cap**: the payer signs an authorization for that cap, the handler serves the request and names the amount it actually charges, and the facilitator settles that amount through Parallel's escrow contract, refunding the remainder to the payer in the same transaction. Metered payments are made in USDp: `acceptedTokens` defaults to the network's USDp and may not list any other token.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
routes: {
|
|
371
|
+
"/v1/chat/completions": {
|
|
372
|
+
scheme: "upto",
|
|
373
|
+
price: "2", // the cap, in USDp
|
|
374
|
+
maxTimeoutSeconds: 600, // the handler may take up to ~9 minutes
|
|
375
|
+
network: "base",
|
|
376
|
+
payTo: "0xYourAddress",
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
The handler reports the amount to charge through the `payment-settle-amount` response header, in whole token units like `price`:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { SETTLE_AMOUNT_HEADER } from "@parallel-protocol/x402";
|
|
385
|
+
|
|
386
|
+
fastify.post("/v1/chat/completions", async (request, reply) => {
|
|
387
|
+
const completion = await runInference(request.body);
|
|
388
|
+
reply.header(SETTLE_AMOUNT_HEADER, completion.costInUsdp); // e.g. "0.0137"
|
|
389
|
+
return completion;
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The middleware strips the header before the response leaves, whatever the status code: a handler that prices the request and then fails never tells the client what it was about to charge. The value must follow the same format as `price` (a plain decimal such as `"0.0137"`, no leading zeros on the integer part, no exponent, no unit) and must not carry more decimals than the token has. Rules on a `2xx` response:
|
|
394
|
+
|
|
395
|
+
| Header value | What happens |
|
|
396
|
+
|---|---|
|
|
397
|
+
| absent | The cap is charged. |
|
|
398
|
+
| `"0"` (an explicit zero) | The request is served for free — nothing is settled, no `payment-response`. |
|
|
399
|
+
| a decimal up to `price` | That amount is settled; the receipt's `amountSettled` carries it. |
|
|
400
|
+
| malformed, more decimals than the token, or above `price` | The response is discarded and the client gets 402 `INVALID_SETTLE_AMOUNT` — a handler bug never charges the payer, and a positive amount the token cannot represent is never silently served for free. Round your cost to the token's precision before setting the header. |
|
|
401
|
+
|
|
402
|
+
### Reading the amount from the response: `meter`
|
|
403
|
+
|
|
404
|
+
Instead of a header the handler sets, a route may carry a `meter` function that reads the amount from the response itself, so the handler stays a plain handler. It receives `{ status, header(name), body, request }`: the response status and headers, its JSON body as the middleware parsed it (`undefined` when the response is not JSON), and the request as the price function saw it. It returns an amount in the same format as `price`, or nothing:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
"/v1/chat/completions": {
|
|
408
|
+
scheme: "upto",
|
|
409
|
+
price: "2",
|
|
410
|
+
network: "base",
|
|
411
|
+
payTo: "0xYourAddress",
|
|
412
|
+
meter: ({ body }) => (body as { usage?: { cost?: number } }).usage?.cost?.toFixed(6),
|
|
413
|
+
},
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
| `meter` result | What happens |
|
|
417
|
+
|---|---|
|
|
418
|
+
| an amount up to `price` | That amount is settled. |
|
|
419
|
+
| an amount above `price` | **The cap is settled**: an overrun is the merchant's loss on that request, not a refused response. Size the cap so it does not happen. |
|
|
420
|
+
| `"0"` | The request is served for free, nothing is settled. |
|
|
421
|
+
| `undefined` or `null` | The `payment-settle-amount` header decides, with the rules above (absent charges the cap). |
|
|
422
|
+
| it throws, or returns something that is not an amount | The response is discarded and the client gets 402 `INVALID_SETTLE_AMOUNT`. |
|
|
423
|
+
|
|
424
|
+
The response body is read only on routes that have a `meter` function, from a clone or from the buffered chunks the middleware already holds, so other routes are untouched. Reading it gets the handler's own time budget: a body that streams from a slow upstream and is not complete within the window is treated like a failing meter. Return a buffered body on metered routes. `meter` only applies to `upto` routes.
|
|
425
|
+
|
|
426
|
+
Timing: the payer's authorization expires after `maxTimeoutSeconds`, and settlement needs a minute at most. On Express, Hono and Next.js the handler timeout is raised to `maxTimeoutSeconds − 60 s` for metered routes; Fastify handlers are never timed out by the middleware, so size the server's own `connectionTimeout` and `requestTimeout` to the window. A handler that finishes after the window is served but cannot be settled: the client gets 402 `PAYMENT_SETTLEMENT_FAILED` and the merchant has done the work for nothing — keep a margin.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Per-request pricing
|
|
431
|
+
|
|
432
|
+
Some resources are priced by what is asked of them: an LLM completion by the messages sent and the `max_tokens` allowed. Give such a route a `price` function instead of a fixed amount. It receives the request and returns a price in the same format as a fixed `price`:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
routes: {
|
|
436
|
+
"/v1/chat/completions": {
|
|
437
|
+
scheme: "upto",
|
|
438
|
+
network: "base",
|
|
439
|
+
payTo: "0xYourAddress",
|
|
440
|
+
price: ({ body }) => {
|
|
441
|
+
const { max_tokens = 1024 } = body as { max_tokens?: number };
|
|
442
|
+
return (max_tokens * 0.000002).toFixed(6); // the cap for this request, in USDp
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
The function gets `{ method, path, url, header(name), body }`. `body` is the request body as the framework parsed it (`undefined` when there is none), so it is available where your framework parses it:
|
|
449
|
+
|
|
450
|
+
| Framework | `body` |
|
|
451
|
+
|-----------|--------|
|
|
452
|
+
| Fastify | Parsed by Fastify. Routes priced per request, or metered from their response, are gated at the `preHandler` stage (after parsing and schema validation); other routes keep gating at `onRequest`. |
|
|
453
|
+
| Express | `req.body`, so mount `express.json()` before `paymentMiddleware`. |
|
|
454
|
+
| Hono | Parsed with `c.req.json()` when the content type is JSON. Hono caches it, your handler can read it again. |
|
|
455
|
+
| Next.js | Parsed from a clone of the request when the content type is JSON; the handler keeps the original. |
|
|
456
|
+
|
|
457
|
+
The function runs twice per paid request: on the unpaid request, to build the 402, and on the paid retry, to size what the facilitator verifies. It must therefore be **pure**: same request, same price, no clock and no randomness. A retry whose body changed in between is priced again, and refused by the facilitator if the signed amount no longer covers the new price. It may be `async`.
|
|
458
|
+
|
|
459
|
+
A function that throws or returns something that is not a price is a merchant bug: the request fails with `X402ConfigError` rather than being served for free or mispriced. To refuse a request the route will not serve (an unsupported option, a resource that does not exist) throw `X402PriceRejectedError(code, message, status = 400)` instead: the client gets that status with `{ error: code, message }`, and nothing is invoiced or signed.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## LLM pricing (`@parallel-protocol/x402/llm`)
|
|
464
|
+
|
|
465
|
+
Everything a metered inference route needs, so the merchant writes no pricing or metering code: a price function that computes the cap from the request, a meter that reads the actual cost from the provider's response, and a relay to the provider.
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
import {
|
|
469
|
+
chatCompletionCap,
|
|
470
|
+
chatCompletionMeter,
|
|
471
|
+
forwardChatCompletion,
|
|
472
|
+
openRouterPrices,
|
|
473
|
+
} from "@parallel-protocol/x402/llm";
|
|
474
|
+
|
|
475
|
+
await app.register(
|
|
476
|
+
paymentMiddleware({
|
|
477
|
+
facilitator: { url: "https://agents.parallel.best" },
|
|
478
|
+
routes: {
|
|
479
|
+
"/v1/chat/completions": {
|
|
480
|
+
scheme: "upto",
|
|
481
|
+
network: "base",
|
|
482
|
+
payTo: "0xYourAddress",
|
|
483
|
+
maxTimeoutSeconds: 600,
|
|
484
|
+
price: chatCompletionCap({
|
|
485
|
+
prices: openRouterPrices(), // live prices, or a fixed table (below)
|
|
486
|
+
maxOutputTokens: 4096, // the largest completion this route pays for
|
|
487
|
+
models: ["deepseek/deepseek-chat", "anthropic/claude-sonnet-4.5"],
|
|
488
|
+
}),
|
|
489
|
+
meter: chatCompletionMeter(), // the provider's usage.cost, or tokens at the model's prices
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
}),
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
app.post("/v1/chat/completions", async (request, reply) => {
|
|
496
|
+
const upstream = await forwardChatCompletion(request.body, {
|
|
497
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
498
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
499
|
+
maxOutputTokens: 4096, // the same bound as the cap
|
|
500
|
+
});
|
|
501
|
+
return reply.status(upstream.status).send(upstream.body);
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
The same handler on the other frameworks: Express `res.status(upstream.status).json(upstream.body)`, Hono `c.json(upstream.body, upstream.status)`, Next.js `NextResponse.json(upstream.body, { status: upstream.status })`.
|
|
506
|
+
|
|
507
|
+
`forwardChatCompletion(body, { baseUrl, apiKey, maxOutputTokens, outputTokensField, timeout, headers, fetch, fields })` posts to `${baseUrl}/chat/completions` with only the fields the cap accounts for (`FORWARDED_FIELDS`: model, messages, tools, sampling parameters, reasoning options), `stream: false` and the completion bounded by `maxOutputTokens` (sent as `max_tokens`, or `max_completion_tokens` for providers that want it; a `reasoning.max_tokens` budget is bounded the same way), and returns the provider's `{ status, body }`: a 2xx and the statuses about the client's request (400, 404, 413, 422, 429 and the like) are handed back as they are, so nothing is settled on an error; a status about the merchant's account with the provider (401, 402, 403, 5xx) is folded into 502 `UPSTREAM_ERROR` with a fixed body, so the client never sees the merchant's provider details; a provider that cannot be reached answers 502 `UPSTREAM_UNAVAILABLE`, a timeout 504 `UPSTREAM_TIMEOUT` (five minutes by default).
|
|
508
|
+
|
|
509
|
+
`chatCompletionMeter({ prices? })` reads the amount from the response: `usage.cost` in dollars when the provider reports one (OpenRouter does), otherwise `usage.prompt_tokens` and `usage.completion_tokens` at the model's prices when `prices` is given, otherwise nothing, which charges the cap. So a provider that reports no cost (DeepSeek, most direct APIs) needs the same `prices` as the cap, or every request settles at the cap with nothing refunded and no error to show it. A zero cost serves the request for free. Cached input tokens are charged at the full input price in the token-count path.
|
|
510
|
+
|
|
511
|
+
The cap of a request is a bound on its input tokens at the model's input price, plus the largest completion the request may produce (`max_tokens`, bounded by `maxOutputTokens`, which also stands in for it when absent) at its output price. One USDp per dollar. The cap must cover the real count whichever tokenizer the provider uses, or the response is refused and the merchant has paid the provider for nothing, so the built-in bound counts one token per UTF-8 byte of the messages and tools, which no tokenizer exceeds. English prose runs at about four characters per token, so the cap is loose there: the payer only pays the provider's real count and the rest is refunded. A merchant who wants a tighter cap plugs a real tokenizer through `estimateInputTokens`.
|
|
512
|
+
|
|
513
|
+
| Option | Default | Description |
|
|
514
|
+
|--------|---------|-------------|
|
|
515
|
+
| `prices` | required | `openRouterPrices()` or a table `{ [model]: { input, output, maxOutputTokens? } }` in USD per million tokens. |
|
|
516
|
+
| `maxOutputTokens` | `4096` | Bound on the completion the cap pays for. **Forward the same bound to the provider**: a request may still ask for more. |
|
|
517
|
+
| `margin` | `0` | Extra margin on the input bound, for a merchant who replaced it with a tighter estimate. |
|
|
518
|
+
| `models` | all known | Models this route serves; others are refused with `MODEL_NOT_ALLOWED`. |
|
|
519
|
+
| `allowStreaming` | `false` | Streamed requests are refused unless set. A streamed response cannot carry the actual cost, so it is charged the cap. |
|
|
520
|
+
| `estimateInputTokens` | built-in | Replace the bound with a real tokenizer. |
|
|
521
|
+
|
|
522
|
+
A handler that does not use `forwardChatCompletion` should still forward only the fields the cap accounts for, never the raw body: options billed outside the token model are refused (`plugins`, `web_search_options`, `modalities` other than text) but a provider may add others. With `chatCompletionMeter`, a cost above the cap settles the cap; a handler that names more than the cap in `payment-settle-amount` instead has its response refused with `INVALID_SETTLE_AMOUNT`.
|
|
523
|
+
|
|
524
|
+
`openRouterPrices({ ttl: "24h", timeout: "10s" })` loads the public models listing (no API key) on first use, serves it from memory and refreshes it in the background once older than `ttl`, so no request waits for OpenRouter after the first one; a load is abandoned after `timeout`, a refresh that fails keeps the current list, and a failed load is not retried for five seconds. With no list at all, requests are refused with a 503 `PRICING_UNAVAILABLE`. A model the list does not know may have been released since the list was loaded: once the list is more than five minutes old, such a request refreshes it in the background and is refused with an `UNKNOWN_MODEL` message that says so, and the next request finds the model. A fixed table never touches the network: `prices: { "deepseek-flash": { input: 0.30, output: 1.20 } }`.
|
|
525
|
+
|
|
526
|
+
Requests the route cannot meter are refused before anything is invoiced, with a 400 and a JSON body: an unknown model (`UNKNOWN_MODEL`), a model outside `models` (`MODEL_NOT_ALLOWED`), more than one choice (`n`), non-text content, plugins, web search, image or audio output, or streaming (`UNSUPPORTED_OPTION`), a malformed body (`INVALID_REQUEST`). Live prices are the one accepted exception to the purity rule of price functions: a list refreshed between the unpaid request and the paid retry may re-price the retry, which is then refused and re-invoiced.
|
|
527
|
+
|
|
528
|
+
Without `meter`, what is charged after the response is the handler's call: set the `payment-settle-amount` header to the provider's cost, see [Metered routes](#metered-routes-upto).
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
360
532
|
## Payment routes
|
|
361
533
|
|
|
362
|
-
The facilitator supports
|
|
534
|
+
The facilitator supports 13 settlement routes. The route is selected automatically based on the signed payload:
|
|
363
535
|
|
|
364
536
|
| Route | Credential method(s) | tokenIn (agent pays) | tokenOut (merchant receives) | Gas est. | Notes |
|
|
365
537
|
|-------|--------------------------|---------------------|------------------------------|----------|-------|
|
|
@@ -375,6 +547,7 @@ The facilitator supports 12 settlement routes. The route is selected automatical
|
|
|
375
547
|
| **J** | `transferWithAuthorization` | sUSDp | sUSDp | ~65K | Direct sUSDp share transfer |
|
|
376
548
|
| **K** | `redeemWithAuthorization` + `swapExactOutputWithAuthorization` | sUSDp | non-USDC backing (exact) | ~180K | Same as I but for other Parallelizer collaterals (atomic multicall3) |
|
|
377
549
|
| **L** | `partialRedeemWithAuthorization` | sUSDp (partial) + USDp | USDp or backing collateral | ~180K | Partial sUSDp redeem combined with existing USDp; delivers to merchant (atomic multicall3) |
|
|
550
|
+
| **U** | `uptoWithAuthorization` | USDp (cap) | USDp (actual) | ~130K | Metered payment: the escrow pulls the signed cap, pays the merchant the settled amount and refunds the rest atomically (scheme `upto`) |
|
|
378
551
|
|
|
379
552
|
**Chain requirements:**
|
|
380
553
|
- Routes B, D, E, G, I, K, L require the **Parallelizer** to be deployed on the target chain (Ethereum, Base, Avalanche, HyperEVM).
|
|
@@ -393,6 +566,9 @@ import {
|
|
|
393
566
|
// Middleware (framework-agnostic)
|
|
394
567
|
createPaymentGate,
|
|
395
568
|
createPaymentMiddleware,
|
|
569
|
+
resolveCharge,
|
|
570
|
+
resolveSettleAmount,
|
|
571
|
+
SETTLE_AMOUNT_HEADER,
|
|
396
572
|
|
|
397
573
|
// Facilitator client
|
|
398
574
|
FacilitatorClient,
|
|
@@ -400,6 +576,8 @@ import {
|
|
|
400
576
|
// Errors
|
|
401
577
|
X402ConfigError,
|
|
402
578
|
X402RuntimeError,
|
|
579
|
+
X402PriceRejectedError,
|
|
580
|
+
isPriceRejection,
|
|
403
581
|
X402_ERROR_CODES,
|
|
404
582
|
type X402ErrorCode,
|
|
405
583
|
|
|
@@ -412,6 +590,8 @@ import {
|
|
|
412
590
|
type FacilitatorConfig,
|
|
413
591
|
type FacilitatorResponse,
|
|
414
592
|
type HTTPAdapter,
|
|
593
|
+
type MeterContext,
|
|
594
|
+
type MeterFunction,
|
|
415
595
|
type MiddlewareResult,
|
|
416
596
|
type PassResult,
|
|
417
597
|
type PaymentConfirmation,
|
|
@@ -420,6 +600,8 @@ import {
|
|
|
420
600
|
type PaymentMiddlewareConfig,
|
|
421
601
|
type PaymentRequired,
|
|
422
602
|
type PaymentRequirements,
|
|
603
|
+
type PriceContext,
|
|
604
|
+
type PriceFunction,
|
|
423
605
|
type ResourceInfo,
|
|
424
606
|
type RouteConfig,
|
|
425
607
|
type RunHandlerResult,
|
|
@@ -443,6 +625,33 @@ import {
|
|
|
443
625
|
|
|
444
626
|
Each subpath also exports its adapter class (`ExpressAdapter`, `NextAdapter`, `FastifyAdapter`, `HonoAdapter`) for use with [`createPaymentGate`](#advanced-createpaymentgate).
|
|
445
627
|
|
|
628
|
+
### LLM pricing (`@parallel-protocol/x402/llm`)
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
import {
|
|
632
|
+
chatCompletionCap, // the ready-made price function, see LLM pricing
|
|
633
|
+
chatCompletionMeter, // the ready-made meter: usage.cost, or tokens at the model's prices
|
|
634
|
+
forwardChatCompletion, // the relay to an OpenAI-compatible provider
|
|
635
|
+
FORWARDED_FIELDS, // the request fields the relay forwards
|
|
636
|
+
openRouterPrices, // live prices from OpenRouter, cached
|
|
637
|
+
staticPrices, // a fixed table in USD per million tokens
|
|
638
|
+
estimateInputTokens, // the built-in input token bound of a body
|
|
639
|
+
estimateTokens, // the bound of a piece of text
|
|
640
|
+
parseOpenRouterModels, // the models listing to a price table
|
|
641
|
+
parseDuration,
|
|
642
|
+
formatMicroUsd, // micro-dollars to an amount string
|
|
643
|
+
microFromUsd, // dollars to whole micro-dollars, rounded up
|
|
644
|
+
type ChatCompletionCapOptions,
|
|
645
|
+
type ChatCompletionMeterOptions,
|
|
646
|
+
type ChatCompletionBody,
|
|
647
|
+
type ForwardChatCompletionOptions,
|
|
648
|
+
type ForwardedResponse,
|
|
649
|
+
type ModelPrice,
|
|
650
|
+
type PriceSource,
|
|
651
|
+
type PriceTable,
|
|
652
|
+
} from "@parallel-protocol/x402/llm";
|
|
653
|
+
```
|
|
654
|
+
|
|
446
655
|
---
|
|
447
656
|
|
|
448
657
|
## Advanced: `createPaymentGate`
|
|
@@ -530,6 +739,17 @@ A single-call `client.pay(paymentHeader, requirements)` (`POST /x402/pay`) also
|
|
|
530
739
|
|-------------|------|
|
|
531
740
|
| `X402ConfigError` | Invalid config at construction (bad `payTo`, bad facilitator URL, empty `routes`, non-positive `price`) — plus, on the first request to a route, an unknown network with no `acceptedTokens`, or an accepted token whose decimals are neither in the catalog nor set via `decimals` |
|
|
532
741
|
| `X402RuntimeError` | Runtime failure; `.code` is one of the SDK codes below, or a facilitator error code propagated verbatim (see next table) |
|
|
742
|
+
| `X402PriceRejectedError` | Thrown by a price function to refuse a request before anything is invoiced; the client gets `.status` (400 by default) with `{ error: .code, message }`. `isPriceRejection(err)` recognises one by shape. |
|
|
743
|
+
|
|
744
|
+
Refusals `chatCompletionCap` makes, all before payment:
|
|
745
|
+
|
|
746
|
+
| `error` | Status | Meaning |
|
|
747
|
+
|---------|--------|---------|
|
|
748
|
+
| `INVALID_REQUEST` | 400 | No JSON object body, no `model`, no `messages`, or a `max_tokens` that is not a positive integer |
|
|
749
|
+
| `UNKNOWN_MODEL` | 400 | The price source has no price for this model |
|
|
750
|
+
| `MODEL_NOT_ALLOWED` | 400 | The model is outside the route's `models` list |
|
|
751
|
+
| `UNSUPPORTED_OPTION` | 400 | More than one choice, non-text content, plugins, web search, image or audio output, or streaming on a route that does not allow it |
|
|
752
|
+
| `PRICING_UNAVAILABLE` | 503 | Live prices could not be loaded yet; retry shortly |
|
|
533
753
|
|
|
534
754
|
| `X402RuntimeError.code` | Meaning |
|
|
535
755
|
|-------------------------|---------|
|