@agentcash/router 1.5.1 → 1.6.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/.claude/CLAUDE.md +116 -2
- package/README.md +34 -5
- package/dist/index.cjs +3319 -2009
- package/dist/index.d.cts +399 -274
- package/dist/index.d.ts +399 -274
- package/dist/index.js +3316 -2008
- package/package.json +11 -11
- package/dist/client/index.cjs +0 -94
- package/dist/client/index.d.cts +0 -86
- package/dist/client/index.d.ts +0 -86
- package/dist/client/index.js +0 -56
- package/dist/siwx-BMlja_nt.d.cts +0 -9
- package/dist/siwx-BMlja_nt.d.ts +0 -9
package/dist/index.d.cts
CHANGED
|
@@ -1,79 +1,67 @@
|
|
|
1
1
|
import { FacilitatorConfig } from '@x402/core/http';
|
|
2
2
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
3
|
import { ZodType } from 'zod';
|
|
4
|
-
import {
|
|
5
|
-
import { PaymentRequirements, PaymentRequired, SettleResponse, Network } from '@x402/core/types';
|
|
4
|
+
import { PaymentRequirements, PaymentRequired, VerifyResponse, SettleResponse, Network } from '@x402/core/types';
|
|
6
5
|
import * as viem from 'viem';
|
|
7
|
-
|
|
6
|
+
import { Transport } from 'mppx/server';
|
|
7
|
+
import { Store } from 'mppx';
|
|
8
|
+
|
|
9
|
+
interface KvStore {
|
|
10
|
+
get(key: string): Promise<unknown>;
|
|
11
|
+
set(key: string, value: unknown): Promise<void>;
|
|
12
|
+
del(key: string): Promise<void>;
|
|
13
|
+
setNxEx(key: string, value: unknown, ttlSeconds: number): Promise<boolean>;
|
|
14
|
+
sadd(key: string, member: string): Promise<void>;
|
|
15
|
+
sismember(key: string, member: string): Promise<boolean>;
|
|
16
|
+
update<R>(key: string, fn: (current: unknown) => KvChange<R>): Promise<R>;
|
|
17
|
+
}
|
|
18
|
+
type KvChange<R> = {
|
|
19
|
+
op: 'noop';
|
|
20
|
+
result: R;
|
|
21
|
+
} | {
|
|
22
|
+
op: 'set';
|
|
23
|
+
value: unknown;
|
|
24
|
+
result: R;
|
|
25
|
+
} | {
|
|
26
|
+
op: 'delete';
|
|
27
|
+
result: R;
|
|
28
|
+
};
|
|
29
|
+
declare function withPrefix(kv: KvStore, prefix: string): KvStore;
|
|
30
|
+
|
|
31
|
+
declare const SIWX_CHALLENGE_EXPIRY_MS: number;
|
|
32
|
+
interface NonceStore {
|
|
33
|
+
check(nonce: string): Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
declare class MemoryNonceStore implements NonceStore {
|
|
36
|
+
private seen;
|
|
37
|
+
check(nonce: string): Promise<boolean>;
|
|
38
|
+
private evict;
|
|
39
|
+
}
|
|
40
|
+
interface KvNonceStoreOptions {
|
|
41
|
+
prefix?: string;
|
|
42
|
+
ttlMs?: number;
|
|
43
|
+
}
|
|
44
|
+
declare function createKvNonceStore(kv: KvStore, options?: KvNonceStoreOptions): NonceStore;
|
|
8
45
|
|
|
9
46
|
interface EntitlementStore {
|
|
10
47
|
has(route: string, wallet: string): Promise<boolean>;
|
|
11
48
|
grant(route: string, wallet: string): Promise<void>;
|
|
12
49
|
}
|
|
13
|
-
/**
|
|
14
|
-
* In-memory SIWX entitlement store.
|
|
15
|
-
*
|
|
16
|
-
* Suitable for development and tests. Not durable across server restarts.
|
|
17
|
-
*/
|
|
18
50
|
declare class MemoryEntitlementStore implements EntitlementStore {
|
|
19
51
|
private readonly routeToWallets;
|
|
20
52
|
has(route: string, wallet: string): Promise<boolean>;
|
|
21
53
|
grant(route: string, wallet: string): Promise<void>;
|
|
22
54
|
}
|
|
23
|
-
interface
|
|
24
|
-
/** Key prefix. Default: 'siwx:entitlement:' */
|
|
55
|
+
interface KvEntitlementStoreOptions {
|
|
25
56
|
prefix?: string;
|
|
26
57
|
}
|
|
27
|
-
|
|
28
|
-
* Redis-backed entitlement store for paid+SIWX acceleration.
|
|
29
|
-
*/
|
|
30
|
-
declare function createRedisEntitlementStore(client: unknown, options?: RedisEntitlementStoreOptions): EntitlementStore;
|
|
58
|
+
declare function createKvEntitlementStore(kv: KvStore, options?: KvEntitlementStoreOptions): EntitlementStore;
|
|
31
59
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* Currently not configurable per-route — this is a known limitation.
|
|
35
|
-
* Future versions may add `siwx: { expiryMs }` to RouterConfig.
|
|
36
|
-
*/
|
|
37
|
-
declare const SIWX_CHALLENGE_EXPIRY_MS: number;
|
|
38
|
-
interface NonceStore {
|
|
39
|
-
check(nonce: string): Promise<boolean>;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* In-memory nonce store for development and testing.
|
|
43
|
-
* NOT suitable for production serverless environments (Vercel, etc.)
|
|
44
|
-
* where each function invocation gets fresh memory.
|
|
45
|
-
*
|
|
46
|
-
* For production, use `createRedisNonceStore()` with Upstash or ioredis.
|
|
47
|
-
*/
|
|
48
|
-
declare class MemoryNonceStore implements NonceStore {
|
|
49
|
-
private seen;
|
|
50
|
-
check(nonce: string): Promise<boolean>;
|
|
51
|
-
private evict;
|
|
52
|
-
}
|
|
53
|
-
interface RedisNonceStoreOptions {
|
|
54
|
-
/** Key prefix for nonce storage. Default: 'siwx:nonce:' */
|
|
60
|
+
type MppStore = Store.Store;
|
|
61
|
+
interface KvMppStoreOptions {
|
|
55
62
|
prefix?: string;
|
|
56
|
-
/** TTL in milliseconds. Default: SIWX_CHALLENGE_EXPIRY_MS (5 minutes) */
|
|
57
|
-
ttlMs?: number;
|
|
58
63
|
}
|
|
59
|
-
|
|
60
|
-
* Create a Redis-backed nonce store for production SIWX replay protection.
|
|
61
|
-
* Auto-detects client type (Upstash or ioredis) and uses appropriate API.
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```ts
|
|
65
|
-
* // Upstash (Vercel)
|
|
66
|
-
* import { Redis } from '@upstash/redis';
|
|
67
|
-
* const redis = new Redis({ url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN });
|
|
68
|
-
* const nonceStore = createRedisNonceStore(redis);
|
|
69
|
-
*
|
|
70
|
-
* // ioredis
|
|
71
|
-
* import Redis from 'ioredis';
|
|
72
|
-
* const redis = new Redis(process.env.REDIS_URL);
|
|
73
|
-
* const nonceStore = createRedisNonceStore(redis);
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
76
|
-
declare function createRedisNonceStore(client: unknown, opts?: RedisNonceStoreOptions): NonceStore;
|
|
64
|
+
declare function createKvMppStore(kv: KvStore, options?: KvMppStoreOptions): Promise<MppStore>;
|
|
77
65
|
|
|
78
66
|
interface RequestMeta {
|
|
79
67
|
requestId: string;
|
|
@@ -168,14 +156,19 @@ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
|
168
156
|
type JsonObject = {
|
|
169
157
|
[key: string]: JsonValue;
|
|
170
158
|
};
|
|
171
|
-
|
|
172
159
|
interface X402Server {
|
|
173
160
|
initialize(): Promise<void>;
|
|
174
161
|
buildPaymentRequirementsFromOptions(options: Array<{
|
|
175
162
|
scheme: string;
|
|
176
163
|
network: string;
|
|
177
|
-
price: string
|
|
164
|
+
price: string | {
|
|
165
|
+
asset: string;
|
|
166
|
+
amount: string;
|
|
167
|
+
extra?: Record<string, unknown>;
|
|
168
|
+
};
|
|
178
169
|
payTo: string;
|
|
170
|
+
maxTimeoutSeconds?: number;
|
|
171
|
+
extra?: Record<string, unknown>;
|
|
179
172
|
}>, context: {
|
|
180
173
|
request: Request;
|
|
181
174
|
}): Promise<PaymentRequirements[]>;
|
|
@@ -185,32 +178,20 @@ interface X402Server {
|
|
|
185
178
|
description?: string;
|
|
186
179
|
}, error?: string, extensions?: Record<string, unknown>): Promise<PaymentRequired>;
|
|
187
180
|
findMatchingRequirements(requirements: PaymentRequirements[], payload: unknown): PaymentRequirements;
|
|
188
|
-
verifyPayment(payload: unknown, requirements: PaymentRequirements): Promise<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}>;
|
|
192
|
-
settlePayment(payload: unknown, requirements: PaymentRequirements): Promise<SettleResponse>;
|
|
181
|
+
verifyPayment(payload: unknown, requirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
182
|
+
settlePayment(payload: unknown, requirements: PaymentRequirements, declaredExtensions?: Record<string, unknown>, transportContext?: unknown, settlementOverrides?: {
|
|
183
|
+
amount?: string;
|
|
184
|
+
}): Promise<SettleResponse>;
|
|
193
185
|
}
|
|
194
186
|
type ProtocolType = 'x402' | 'mpp';
|
|
195
187
|
type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
|
|
196
188
|
type RouteMethod = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
|
|
197
189
|
interface RouteDefinition<K extends string = string> {
|
|
198
|
-
/**
|
|
199
|
-
* Public API path segment (without `/api/` prefix).
|
|
200
|
-
* Example: `flightaware/airports/id/flights/arrivals`
|
|
201
|
-
*/
|
|
190
|
+
/** Public API path segment without the `/api/` prefix (e.g. `flightaware/airports/id/flights/arrivals`). */
|
|
202
191
|
path: K;
|
|
203
|
-
/**
|
|
204
|
-
* Internal route ID for pricing maps / analytics. Defaults to `path`.
|
|
205
|
-
*
|
|
206
|
-
* In `strictRoutes` mode, custom keys are disallowed to prevent discovery
|
|
207
|
-
* drift between internal IDs and advertised paths.
|
|
208
|
-
*/
|
|
192
|
+
/** Internal route ID for pricing maps / analytics. Defaults to `path`. Disallowed under `strictRoutes` to prevent discovery drift. */
|
|
209
193
|
key?: string;
|
|
210
|
-
/**
|
|
211
|
-
* Optional explicit method. If omitted, defaults to builder behavior
|
|
212
|
-
* (`POST`, or `GET` when `.query()` is used).
|
|
213
|
-
*/
|
|
194
|
+
/** Explicit HTTP method. Defaults to `POST`, or `GET` when `.query()` is used. */
|
|
214
195
|
method?: RouteMethod;
|
|
215
196
|
}
|
|
216
197
|
interface TierConfig {
|
|
@@ -224,14 +205,21 @@ type PricingConfig<TBody = unknown> = string | ((body: TBody) => string | Promis
|
|
|
224
205
|
};
|
|
225
206
|
type PayToConfig = string | ((request: Request, body?: unknown) => string | Promise<string>);
|
|
226
207
|
interface X402AcceptBase {
|
|
208
|
+
/** Chain identifier (e.g. `base`, `base-sepolia`, `solana-mainnet`). */
|
|
227
209
|
network: string;
|
|
210
|
+
/** Token contract address (EVM) or mint (Solana). Defaults to USDC for the network. */
|
|
228
211
|
asset?: string;
|
|
212
|
+
/** Token decimals. Defaults to USDC's 6. */
|
|
229
213
|
decimals?: number;
|
|
214
|
+
/** Max payment-proof age the facilitator will accept, in seconds. */
|
|
230
215
|
maxTimeoutSeconds?: number;
|
|
216
|
+
/** Extra fields passed through to the x402 PaymentRequirements `extra` block. */
|
|
231
217
|
extra?: Record<string, unknown>;
|
|
232
218
|
}
|
|
233
219
|
interface X402AcceptConfig extends X402AcceptBase {
|
|
220
|
+
/** `'exact'` for fixed-price one-shot payments; `'upto'` for settle-≤-cap (required for `.paid({ dynamic: true })` on x402). @default 'exact' */
|
|
234
221
|
scheme?: string;
|
|
222
|
+
/** Per-accept payee override. Function form receives the request and parsed body for dynamic recipient routing. Falls back to `RouterConfig.payeeAddress`. */
|
|
235
223
|
payTo?: PayToConfig;
|
|
236
224
|
}
|
|
237
225
|
interface X402ResolvedAccept extends X402AcceptBase {
|
|
@@ -239,11 +227,15 @@ interface X402ResolvedAccept extends X402AcceptBase {
|
|
|
239
227
|
payTo: string;
|
|
240
228
|
}
|
|
241
229
|
interface X402RouterFacilitatorConfig extends FacilitatorConfig {
|
|
230
|
+
/** Async header builder invoked per facilitator call. Use for short-lived auth tokens (e.g. CDP signed headers). */
|
|
242
231
|
createAcceptsHeaders?: () => Promise<Record<string, string>>;
|
|
243
232
|
}
|
|
233
|
+
/** A facilitator URL or a full `FacilitatorConfig` (URL + auth header builders). */
|
|
244
234
|
type X402FacilitatorTarget = string | X402RouterFacilitatorConfig;
|
|
245
235
|
interface X402FacilitatorsConfig {
|
|
236
|
+
/** Facilitator for EVM chains (Base, etc.). Defaults to the Coinbase facilitator using `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET`. */
|
|
246
237
|
evm?: X402FacilitatorTarget;
|
|
238
|
+
/** Facilitator for Solana. Required to accept Solana payments — there's no default. */
|
|
247
239
|
solana?: X402FacilitatorTarget;
|
|
248
240
|
}
|
|
249
241
|
interface MppProtocolInfo {
|
|
@@ -259,6 +251,12 @@ interface PaidOptions {
|
|
|
259
251
|
payTo?: PayToConfig;
|
|
260
252
|
/** Override MPP protocol metadata in x-payment-info discovery. */
|
|
261
253
|
mpp?: MppProtocolInfo;
|
|
254
|
+
/** Handler-driven dynamic pricing: handler calls `charge()` per tick, total billed is `tickCost × calls` capped at `maxPrice`. Requires `maxPrice`. Incompatible with tiered pricing. On x402 needs an `upto` accept; on MPP needs `RouterConfig.mpp.session`. */
|
|
255
|
+
dynamic?: boolean;
|
|
256
|
+
/** Per-tick cost (positive decimal-dollar string). Required for `.paid({ dynamic: true })`. Also the voucher-headroom granularity for MPP sessions. */
|
|
257
|
+
tickCost?: string;
|
|
258
|
+
/** Cosmetic unit label for 402 challenges / UIs (e.g. `'token'`, `'byte'`). Does not affect billing. */
|
|
259
|
+
unitType?: string;
|
|
262
260
|
}
|
|
263
261
|
type PaymentStatus = 'verified' | 'settled';
|
|
264
262
|
interface HandlerPaymentContext {
|
|
@@ -294,26 +292,16 @@ interface SettledHandlerErrorContext<TBody = unknown> extends SettlementSettledC
|
|
|
294
292
|
error: unknown;
|
|
295
293
|
}
|
|
296
294
|
interface SettlementLifecycle<TBody = unknown> {
|
|
297
|
-
/**
|
|
298
|
-
* Runs after the handler returns a successful response, before router-controlled
|
|
299
|
-
* settlement/broadcast. Throw with `.status` to return a specific error and
|
|
300
|
-
* skip settlement when the protocol flow has not already settled.
|
|
301
|
-
*/
|
|
295
|
+
/** Runs after a successful handler response, before router-controlled settlement/broadcast. Throw with `.status` to fail the request and skip settlement (when not already settled). */
|
|
302
296
|
beforeSettle?: (ctx: SettlementLifecycleContext<TBody>) => void | Promise<void>;
|
|
303
|
-
/**
|
|
304
|
-
* Runs after successful settlement. Use for durable ledgers and audit rows.
|
|
305
|
-
* Errors are alerted and do not change the already-settled response.
|
|
306
|
-
*/
|
|
297
|
+
/** Runs after successful settlement; for durable ledgers and audit rows. Errors are alerted but don't change the already-settled response. */
|
|
307
298
|
afterSettle?: (ctx: SettlementSettledContext<TBody>) => void | Promise<void>;
|
|
308
|
-
/**
|
|
309
|
-
* Runs when the router has already observed a settled payment, then the
|
|
310
|
-
* handler returns an error response. Use for app-owned refund or
|
|
311
|
-
* compensation queues.
|
|
312
|
-
*/
|
|
299
|
+
/** Runs when payment was settled but the handler then returned an error response. Use for app-owned refund / compensation queues. */
|
|
313
300
|
onSettledHandlerError?: (ctx: SettledHandlerErrorContext<TBody>) => void | Promise<void>;
|
|
314
301
|
/** Runs when router-controlled settlement fails after the handler succeeded. */
|
|
315
302
|
onSettlementError?: (ctx: SettlementErrorContext<TBody>) => void | Promise<void>;
|
|
316
303
|
}
|
|
304
|
+
type ChargeFn = () => Promise<void>;
|
|
317
305
|
interface HandlerContext<TBody = undefined, TQuery = undefined> {
|
|
318
306
|
body: TBody;
|
|
319
307
|
query: TQuery;
|
|
@@ -326,6 +314,10 @@ interface HandlerContext<TBody = undefined, TQuery = undefined> {
|
|
|
326
314
|
alert: AlertFn;
|
|
327
315
|
setVerifiedWallet: (addr: string) => void;
|
|
328
316
|
}
|
|
317
|
+
/** Handler context for streaming `.paid({ dynamic: true })` handlers (async generators). Call `charge()` once per billable unit. */
|
|
318
|
+
interface StreamingHandlerContext<TBody = undefined, TQuery = undefined> extends HandlerContext<TBody, TQuery> {
|
|
319
|
+
charge: ChargeFn;
|
|
320
|
+
}
|
|
329
321
|
type OveragePolicy = 'same-rate' | 'increased-rate' | 'hard-stop';
|
|
330
322
|
type QuotaLevel = 'healthy' | 'warn' | 'critical';
|
|
331
323
|
interface QuotaInfo {
|
|
@@ -359,28 +351,17 @@ interface RouteEntry {
|
|
|
359
351
|
*/
|
|
360
352
|
siwxEnabled?: boolean;
|
|
361
353
|
pricing?: PricingConfig;
|
|
354
|
+
/** When true the route is dynamic-priced; bills `tickCost` per request (request-mode) or per `charge()` call (streaming). */
|
|
355
|
+
dynamicPrice?: boolean;
|
|
356
|
+
/** True iff handler is an async generator. Streaming handlers settle per-tick over SSE; non-streaming dynamic handlers bill exactly `tickCost` per request. Set by the builder at `.handler(fn)` time. */
|
|
357
|
+
streaming?: boolean;
|
|
362
358
|
protocols: ProtocolType[];
|
|
363
359
|
bodySchema?: ZodType;
|
|
364
360
|
querySchema?: ZodType;
|
|
365
361
|
outputSchema?: ZodType;
|
|
366
|
-
/**
|
|
367
|
-
* Optional conforming example for the request input (body for body routes, query params for query routes).
|
|
368
|
-
* When present, it must satisfy the corresponding schema and is validated at route registration.
|
|
369
|
-
*
|
|
370
|
-
* Emitted in the bazaar discovery extension so indexers can advertise a working sample call.
|
|
371
|
-
*/
|
|
362
|
+
/** Optional conforming example for the request input (body or query). Validated against the schema at registration. Emitted in the bazaar discovery extension. */
|
|
372
363
|
inputExample?: JsonObject;
|
|
373
|
-
/**
|
|
374
|
-
* Optional conforming example for the response output. When present, it must
|
|
375
|
-
* satisfy `outputSchema` and is validated at route registration.
|
|
376
|
-
*
|
|
377
|
-
* Accepts any JSON value (object, array, or primitive) to support top-level array or
|
|
378
|
-
* primitive response schemas.
|
|
379
|
-
*
|
|
380
|
-
* Emitted in the bazaar discovery extension. Without it the `output` block is
|
|
381
|
-
* dropped from the declaration entirely (the output schema alone cannot be
|
|
382
|
-
* exposed in bazaar without an example).
|
|
383
|
-
*/
|
|
364
|
+
/** Optional conforming example for the response output (any JSON value). Validated against `outputSchema` at registration. Without it, the bazaar `output` block is omitted (schema alone can't be exposed). */
|
|
384
365
|
outputExample?: JsonValue;
|
|
385
366
|
description?: string;
|
|
386
367
|
path?: string;
|
|
@@ -394,6 +375,10 @@ interface RouteEntry {
|
|
|
394
375
|
validateFn?: (body: unknown) => void | Promise<void>;
|
|
395
376
|
settlement?: SettlementLifecycle;
|
|
396
377
|
mppInfo?: MppProtocolInfo;
|
|
378
|
+
/** Per-tick cost (decimal-dollar). Required when `dynamicPrice` is true. */
|
|
379
|
+
tickCost?: string;
|
|
380
|
+
/** Cosmetic unit label for 402 challenges and client UIs. */
|
|
381
|
+
unitType?: string;
|
|
397
382
|
}
|
|
398
383
|
interface DiscoveryConfig {
|
|
399
384
|
title: string;
|
|
@@ -411,93 +396,53 @@ interface DiscoveryConfig {
|
|
|
411
396
|
serverUrl?: string;
|
|
412
397
|
}
|
|
413
398
|
interface RouterConfig {
|
|
399
|
+
/** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via `.paid({ payTo })`. */
|
|
414
400
|
payeeAddress?: string;
|
|
415
|
-
/**
|
|
416
|
-
* Origin URL (e.g. `https://myapp.com`).
|
|
417
|
-
* Used for 402 challenge realm, discovery URLs, OpenAPI servers, and MPP memo indexing.
|
|
418
|
-
*
|
|
419
|
-
* **Required.** No auto-detection — the realm is load-bearing for payment matching,
|
|
420
|
-
* so it must be explicitly set by the consuming app.
|
|
421
|
-
*/
|
|
401
|
+
/** Origin URL (required). Used as 402 realm, discovery base, OpenAPI server, and MPP memo prefix — must match the public domain or payment matching breaks. */
|
|
422
402
|
baseUrl: string;
|
|
403
|
+
/** Default chain for the auto-generated x402 `exact` accept (e.g. `base`, `base-sepolia`). Ignored when `x402.accepts` is set. @default 'base' */
|
|
423
404
|
network?: string;
|
|
405
|
+
/** x402 protocol settings. Omit to default to a single `exact`/USDC accept on `network` paid to `payeeAddress`, verified via the Coinbase default facilitator (requires `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET`). */
|
|
424
406
|
x402?: {
|
|
407
|
+
/** Explicit accepts list (scheme + network + asset). Overrides the auto-generated default. Add an `upto` accept here to enable `.paid({ dynamic: true })` on x402. */
|
|
425
408
|
accepts?: X402AcceptConfig[];
|
|
409
|
+
/** Per-chain facilitator overrides (`evm`/`solana`). Defaults to the Coinbase facilitator on EVM; set `solana` to accept Solana payments. */
|
|
426
410
|
facilitators?: X402FacilitatorsConfig;
|
|
427
411
|
};
|
|
412
|
+
/** Observability hook receiving request/auth/payment/settlement events. Use `consolePlugin` for dev, or implement `RouterPlugin` for structured logs/analytics. */
|
|
428
413
|
plugin?: RouterPlugin;
|
|
429
|
-
siwx
|
|
430
|
-
|
|
431
|
-
|
|
414
|
+
/** Single KV cache for SIWX nonce, SIWX entitlement, and MPP tx-hash replay (prefixed `siwx:nonce:`, `siwx:ent:`, `mpp:`). Pass `{ url, token }` for an Upstash-compatible REST endpoint (Upstash, Vercel KV), or a custom `KvStore` implementation. Omitted: auto-bootstraps from `KV_REST_API_URL` + `KV_REST_API_TOKEN`; falls back to in-memory when missing (unsafe in serverless). */
|
|
415
|
+
kvStore?: KvStore | {
|
|
416
|
+
url: string;
|
|
417
|
+
token: string;
|
|
432
418
|
};
|
|
419
|
+
/** Centralized price map keyed by route ID. `.route(key)` auto-applies `.paid(prices[key])` when `key` is listed; per-route `.paid()` still works for keys not in the map. */
|
|
433
420
|
prices?: Record<string, string>;
|
|
421
|
+
/** MPP (Tempo) payment-channel config. Required when `protocols` includes `'mpp'`. */
|
|
434
422
|
mpp?: {
|
|
423
|
+
/** HMAC key for signing/verifying MPP challenge nonces. Persist across deploys — rotating invalidates outstanding 402 challenges. Falls back to `MPP_SECRET_KEY`. */
|
|
435
424
|
secretKey: string;
|
|
425
|
+
/** Tempo currency contract address (0x-prefixed). Use `TEMPO_USDC_CURRENCY` for USDC on Tempo. */
|
|
436
426
|
currency: string;
|
|
427
|
+
/** MPP payee address (EVM). Overrides `payeeAddress` for MPP only. Required when `payeeAddress` is unset. MUST equal `operatorKey`'s derived address when `session` is enabled. */
|
|
437
428
|
recipient?: string;
|
|
438
|
-
/** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL
|
|
429
|
+
/** Tempo RPC URL for on-chain verification. Falls back to `TEMPO_RPC_URL`. */
|
|
439
430
|
rpcUrl?: string;
|
|
440
|
-
/**
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
* Must be a hex-encoded private key (e.g. `0xabc123...`).
|
|
444
|
-
*/
|
|
431
|
+
/** Hex private key. Signs channel close/settle; required for `session`. Address MUST equal `recipient`/payee — mppx asserts sender===payee on settle. Validated at init. */
|
|
432
|
+
operatorKey?: string;
|
|
433
|
+
/** Hex private key. Sponsors gas for client channel open/topUp. MUST resolve to a different address than `operatorKey` — Tempo rejects sender===feePayer. Validated at init. Omit to make clients pay their own gas. */
|
|
445
434
|
feePayerKey?: string;
|
|
446
|
-
/**
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
* `Store.cloudflare(kv)` for a shared persistent store.
|
|
452
|
-
*
|
|
453
|
-
* @example
|
|
454
|
-
* import { Store } from 'mppx'
|
|
455
|
-
* store: Store.upstash({ get, set, del })
|
|
456
|
-
* store: Store.cloudflare(env.MY_KV_NAMESPACE)
|
|
457
|
-
*/
|
|
458
|
-
store?: Store.Store;
|
|
459
|
-
/**
|
|
460
|
-
* When `true`, auto-configures an Upstash-backed persistent store from Vercel KV
|
|
461
|
-
* environment variables (`KV_REST_API_URL` + `KV_REST_API_TOKEN`).
|
|
462
|
-
*
|
|
463
|
-
* Uses raw `fetch` against the Upstash REST API — no extra npm dependencies.
|
|
464
|
-
* Ignored when `store` is explicitly provided.
|
|
465
|
-
*
|
|
466
|
-
* @example
|
|
467
|
-
* createRouter({
|
|
468
|
-
* mpp: {
|
|
469
|
-
* secretKey: process.env.MPP_SECRET_KEY!,
|
|
470
|
-
* currency: TEMPO_USDC_CURRENCY,
|
|
471
|
-
* useDefaultStore: true,
|
|
472
|
-
* }
|
|
473
|
-
* })
|
|
474
|
-
*/
|
|
475
|
-
useDefaultStore?: boolean;
|
|
435
|
+
/** Enables MPP payment-channel sessions for `.paid({ dynamic: true })` routes (registers both request and SSE session middleware). Also requires `mpp.operatorKey`. */
|
|
436
|
+
session?: {
|
|
437
|
+
/** Suggested deposit on the 402 challenge = `tickCost × depositMultiplier` USDC. Route `maxPrice` overrides. @default 10 */
|
|
438
|
+
depositMultiplier?: number;
|
|
439
|
+
};
|
|
476
440
|
};
|
|
477
|
-
/**
|
|
478
|
-
* Payment protocols to accept on paid routes unless a route overrides them.
|
|
479
|
-
*
|
|
480
|
-
* @default ['x402']
|
|
481
|
-
*
|
|
482
|
-
* @example
|
|
483
|
-
* // Accept both x402 and MPP payments
|
|
484
|
-
* createRouter({
|
|
485
|
-
* protocols: ['x402', 'mpp'],
|
|
486
|
-
* mpp: { secretKey, currency: TEMPO_USDC_CURRENCY, recipient },
|
|
487
|
-
* prices: { 'exa/search': '0.01' }
|
|
488
|
-
* })
|
|
489
|
-
*/
|
|
441
|
+
/** Payment protocols to accept on paid routes unless overridden per route. @default ['x402'] */
|
|
490
442
|
protocols?: ProtocolType[];
|
|
491
|
-
/**
|
|
492
|
-
* Enforce explicit, path-first route definitions.
|
|
493
|
-
*
|
|
494
|
-
* When enabled:
|
|
495
|
-
* - `.route('key')` is rejected; use `.route({ path })`.
|
|
496
|
-
* - custom `key` differing from `path` is rejected.
|
|
497
|
-
*
|
|
498
|
-
* This prevents discovery/openapi drift caused by shorthand internal keys.
|
|
499
|
-
*/
|
|
443
|
+
/** When true, `.route('key')` is rejected (use `.route({ path })`) and custom `key !== path` is rejected. Prevents discovery/openapi drift. */
|
|
500
444
|
strictRoutes?: boolean;
|
|
445
|
+
/** Static metadata for auto-generated discovery surfaces — `/.well-known/x402`, OpenAPI (`/api/openapi`), and `/llms.txt`. */
|
|
501
446
|
discovery: DiscoveryConfig;
|
|
502
447
|
}
|
|
503
448
|
|
|
@@ -520,7 +465,15 @@ interface ResolvedX402Facilitator {
|
|
|
520
465
|
config: X402RouterFacilitatorConfig;
|
|
521
466
|
}
|
|
522
467
|
|
|
523
|
-
|
|
468
|
+
type MppxMiddlewareResponse<T extends Transport.AnyTransport> = {
|
|
469
|
+
status: 402;
|
|
470
|
+
challenge: Transport.ChallengeOutputOf<T>;
|
|
471
|
+
} | {
|
|
472
|
+
status: 200;
|
|
473
|
+
withReceipt: Transport.WithReceipt<T>;
|
|
474
|
+
};
|
|
475
|
+
type MppxMiddleware<TOptions, T extends Transport.AnyTransport> = (options: TOptions) => (input: Request) => Promise<MppxMiddlewareResponse<T>>;
|
|
476
|
+
interface RouterDeps {
|
|
524
477
|
x402Server: X402Server | null;
|
|
525
478
|
initPromise: Promise<void>;
|
|
526
479
|
x402InitError?: string;
|
|
@@ -534,44 +487,47 @@ interface OrchestrateDeps {
|
|
|
534
487
|
x402FacilitatorsByNetwork?: Record<string, ResolvedX402Facilitator>;
|
|
535
488
|
x402Accepts: X402AcceptConfig[];
|
|
536
489
|
mppx?: {
|
|
537
|
-
charge:
|
|
490
|
+
charge: MppxMiddleware<{
|
|
538
491
|
amount: string;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
492
|
+
}, Transport.Http>;
|
|
493
|
+
sessionRequest?: MppxMiddleware<{
|
|
494
|
+
amount: string;
|
|
495
|
+
unitType?: string;
|
|
496
|
+
suggestedDeposit?: string;
|
|
497
|
+
}, Transport.Http>;
|
|
498
|
+
sessionStream?: MppxMiddleware<{
|
|
499
|
+
amount: string;
|
|
500
|
+
unitType?: string;
|
|
501
|
+
suggestedDeposit?: string;
|
|
502
|
+
}, Transport.Sse>;
|
|
503
|
+
} | null;
|
|
504
|
+
mppSessionConfig?: {
|
|
505
|
+
depositMultiplier: number;
|
|
546
506
|
} | null;
|
|
547
507
|
tempoClient?: viem.Client | null;
|
|
548
508
|
}
|
|
549
509
|
|
|
510
|
+
/** @deprecated alias kept for downstream consumers; use `RouterDeps`. */
|
|
511
|
+
type OrchestrateDeps = RouterDeps;
|
|
512
|
+
|
|
550
513
|
type True = true;
|
|
551
514
|
type False = false;
|
|
552
|
-
/**
|
|
553
|
-
* Active request-input type at a builder position. Resolves to `TBody` when
|
|
554
|
-
* `.body()` has been called, `TQuery` when `.query()` has been called, and
|
|
555
|
-
* `never` when neither — making `.inputExample()` unusable before a schema
|
|
556
|
-
* is set (the literal won't assign to `never`).
|
|
557
|
-
*/
|
|
558
515
|
type InputTypeFor<TBody, TQuery> = [TBody] extends [undefined] ? [TQuery] extends [undefined] ? never : TQuery : TBody;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
* builder state is valid, and to a descriptive error object when it isn't —
|
|
562
|
-
* the mismatch surfaces as a TS type error at the `.handler(...)` call site
|
|
563
|
-
* with the `__missing` string as the contextual hint.
|
|
564
|
-
*
|
|
565
|
-
* Encoded as a conditional argument rather than overload `this:` constraints
|
|
566
|
-
* because TypeScript doesn't reliably gate overload selection on `this` for
|
|
567
|
-
* generic classes (structurally identical instance types collapse).
|
|
568
|
-
*/
|
|
516
|
+
type RequestHandlerFn<TBody, TQuery> = (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown>;
|
|
517
|
+
type StreamingHandlerFn<TBody, TQuery> = (ctx: StreamingHandlerContext<TBody, TQuery>) => AsyncIterable<unknown>;
|
|
569
518
|
type HandlerArg<TBody, TQuery, HasAuth extends boolean, NeedsBody extends boolean, HasBody extends boolean> = HasAuth extends true ? [NeedsBody, HasBody] extends [true, false] ? {
|
|
570
519
|
__missing: 'Call .body(schema) — dynamic/tiered pricing requires a body schema to resolve the price against';
|
|
571
|
-
} :
|
|
520
|
+
} : RequestHandlerFn<TBody, TQuery> : {
|
|
572
521
|
__missing: 'Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()';
|
|
573
522
|
};
|
|
574
|
-
|
|
523
|
+
type StreamArg<TBody, TQuery, HasAuth extends boolean, NeedsBody extends boolean, HasBody extends boolean, IsDynamic extends boolean> = HasAuth extends true ? IsDynamic extends true ? [NeedsBody, HasBody] extends [true, false] ? {
|
|
524
|
+
__missing: 'Call .body(schema) — dynamic pricing requires a body schema to resolve the price against';
|
|
525
|
+
} : StreamingHandlerFn<TBody, TQuery> : {
|
|
526
|
+
__missing: 'Streaming handlers require .paid({ dynamic: true, tickCost, unitType, maxPrice }) — static/free routes cannot meter per-chunk billing';
|
|
527
|
+
} : {
|
|
528
|
+
__missing: 'Select an auth mode: .paid({ dynamic: true, ... }) — streaming requires handler-driven dynamic pricing';
|
|
529
|
+
};
|
|
530
|
+
declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = undefined, HasAuth extends boolean = false, NeedsBody extends boolean = false, HasBody extends boolean = false, IsDynamic extends boolean = false> {
|
|
575
531
|
/** @internal */ readonly _key: string;
|
|
576
532
|
/** @internal */ readonly _registry: RouteRegistry;
|
|
577
533
|
/** @internal */ readonly _deps: OrchestrateDeps;
|
|
@@ -581,6 +537,9 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
581
537
|
/** @internal */ _protocols: ProtocolType[];
|
|
582
538
|
/** @internal */ _maxPrice: string | undefined;
|
|
583
539
|
/** @internal */ _minPrice: string | undefined;
|
|
540
|
+
/** @internal */ _dynamicPrice: boolean;
|
|
541
|
+
/** @internal */ _tickCost: string | undefined;
|
|
542
|
+
/** @internal */ _unitType: string | undefined;
|
|
584
543
|
/** @internal */ _payTo: PayToConfig | undefined;
|
|
585
544
|
/** @internal */ _bodySchema: ZodType | undefined;
|
|
586
545
|
/** @internal */ _querySchema: ZodType | undefined;
|
|
@@ -600,10 +559,63 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
600
559
|
/** @internal */ _mppInfo: MppProtocolInfo | undefined;
|
|
601
560
|
constructor(key: string, registry: RouteRegistry, deps: OrchestrateDeps);
|
|
602
561
|
private fork;
|
|
603
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Charge a fixed price per request, denominated in USDC as a decimal string.
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```ts
|
|
567
|
+
* router.route('search').paid('0.01').handler(handler);
|
|
568
|
+
* ```
|
|
569
|
+
*/
|
|
570
|
+
paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, IsDynamic>;
|
|
571
|
+
/**
|
|
572
|
+
* Configure handler-driven dynamic pricing — each tick costs `tickCost` USDC,
|
|
573
|
+
* capped at `maxPrice`. Pair with `.handler()` for one-tick-per-request
|
|
574
|
+
* billing, or with `.stream()` for per-yield metering.
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```ts
|
|
578
|
+
* router
|
|
579
|
+
* .route('llm/stream')
|
|
580
|
+
* .paid({ dynamic: true, tickCost: '0.0001', unitType: 'token', maxPrice: '0.05' })
|
|
581
|
+
* .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
|
|
582
|
+
* ```
|
|
583
|
+
*/
|
|
584
|
+
paid(options: PaidOptions & {
|
|
585
|
+
dynamic: true;
|
|
586
|
+
maxPrice: string;
|
|
587
|
+
}): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, True>;
|
|
588
|
+
/**
|
|
589
|
+
* Compute the price from the parsed body before issuing the 402 challenge.
|
|
590
|
+
* Throw an `HttpError` from the pricing function to reject the request before
|
|
591
|
+
* payment is requested.
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```ts
|
|
595
|
+
* router
|
|
596
|
+
* .route('llm')
|
|
597
|
+
* .paid((body) => `${body.tokens * 0.0001}`, { maxPrice: '5.00' })
|
|
598
|
+
* .body(schema)
|
|
599
|
+
* .handler(handler);
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
604
602
|
paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
|
|
605
603
|
maxPrice?: string;
|
|
606
|
-
}): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody>;
|
|
604
|
+
}): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody, IsDynamic>;
|
|
605
|
+
/**
|
|
606
|
+
* Select a price tier from `body[field]`, optionally falling back to the
|
|
607
|
+
* `default` tier when the value is missing. The 402 challenge advertises the
|
|
608
|
+
* highest tier price.
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```ts
|
|
612
|
+
* router
|
|
613
|
+
* .route('upload')
|
|
614
|
+
* .paid({ field: 'size', tiers: { sm: { price: '0.01' }, lg: { price: '0.10' } } })
|
|
615
|
+
* .body(schema)
|
|
616
|
+
* .handler(handler);
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
607
619
|
paid(pricing: {
|
|
608
620
|
field: string;
|
|
609
621
|
tiers: Record<string, {
|
|
@@ -611,102 +623,208 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
611
623
|
label?: string;
|
|
612
624
|
}>;
|
|
613
625
|
default?: string;
|
|
614
|
-
}, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody>;
|
|
615
|
-
siwx(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody>;
|
|
616
|
-
apiKey(resolver: (key: string) => unknown | Promise<unknown>): RouteBuilder<TBody, TQuery, TOutput, True, NeedsBody, HasBody>;
|
|
617
|
-
unprotected(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody>;
|
|
618
|
-
provider(name: string, config?: ProviderConfig): this;
|
|
619
|
-
body<T>(schema: ZodType<T>): RouteBuilder<T, TQuery, TOutput, HasAuth, NeedsBody, True>;
|
|
620
|
-
body<T>(schema: ZodType<T>, example: T & JsonObject): RouteBuilder<T, TQuery, TOutput, HasAuth, NeedsBody, True>;
|
|
621
|
-
query<T>(schema: ZodType<T>): RouteBuilder<TBody, T, TOutput, HasAuth, NeedsBody, HasBody>;
|
|
622
|
-
query<T>(schema: ZodType<T>, example: T & JsonObject): RouteBuilder<TBody, T, TOutput, HasAuth, NeedsBody, HasBody>;
|
|
623
|
-
output<T>(schema: ZodType<T>): RouteBuilder<TBody, TQuery, T, HasAuth, NeedsBody, HasBody>;
|
|
624
|
-
output<T>(schema: ZodType<T>, example: T & JsonValue): RouteBuilder<TBody, TQuery, T, HasAuth, NeedsBody, HasBody>;
|
|
626
|
+
}, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody, IsDynamic>;
|
|
625
627
|
/**
|
|
626
|
-
*
|
|
628
|
+
* Require Sign-In-with-X wallet identity on this route — clients prove
|
|
629
|
+
* control of a wallet via a signed challenge. Combine with `.paid()` to gate
|
|
630
|
+
* a paid route on a verified wallet identity.
|
|
627
631
|
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* router.route('profile').siwx().handler(async ({ wallet }) => getProfile(wallet));
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
siwx(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, IsDynamic>;
|
|
638
|
+
/**
|
|
639
|
+
* Require an `X-API-Key` header (or `Authorization: Bearer <key>`); the
|
|
640
|
+
* resolver returns the account record, or `null` for 401. Composes with
|
|
641
|
+
* `.paid()` — key is checked first, payment second.
|
|
631
642
|
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
643
|
+
* @example
|
|
644
|
+
* ```ts
|
|
645
|
+
* router
|
|
646
|
+
* .route('admin/users')
|
|
647
|
+
* .apiKey(async (key) => db.admin.findByKey(key))
|
|
648
|
+
* .handler(async ({ account }) => db.user.list(account.orgId));
|
|
649
|
+
* ```
|
|
650
|
+
*/
|
|
651
|
+
apiKey(resolver: (key: string) => unknown | Promise<unknown>): RouteBuilder<TBody, TQuery, TOutput, True, NeedsBody, HasBody, IsDynamic>;
|
|
652
|
+
/**
|
|
653
|
+
* Mark the route as public — no auth, no payment, no SIWX. The handler
|
|
654
|
+
* receives `null` for `wallet`, `payment`, and `account`.
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* ```ts
|
|
658
|
+
* router.route('health').unprotected().handler(async () => ({ status: 'ok' }));
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
661
|
+
unprotected(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody, IsDynamic>;
|
|
662
|
+
/**
|
|
663
|
+
* Tag the route with an upstream provider for discovery and provider-side
|
|
664
|
+
* monitoring. The provider name and config surface in `well-known` and
|
|
665
|
+
* OpenAPI output.
|
|
634
666
|
*
|
|
635
667
|
* @example
|
|
636
668
|
* ```ts
|
|
637
|
-
* router
|
|
669
|
+
* router
|
|
670
|
+
* .route('search')
|
|
638
671
|
* .paid('0.01')
|
|
639
|
-
* .
|
|
640
|
-
* .
|
|
641
|
-
* .handler(async ({ body }) => { ... });
|
|
672
|
+
* .provider('exa', { quotaPerMonth: 1000 })
|
|
673
|
+
* .handler(handler);
|
|
642
674
|
* ```
|
|
643
675
|
*/
|
|
644
|
-
|
|
676
|
+
provider(name: string, config?: ProviderConfig): this;
|
|
645
677
|
/**
|
|
646
|
-
*
|
|
678
|
+
* Declare the request body's Zod schema. Parsed body is typed as `ctx.body`
|
|
679
|
+
* in the handler. Use `.inputExample()` to attach a discovery example.
|
|
647
680
|
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
681
|
+
* @example
|
|
682
|
+
* ```ts
|
|
683
|
+
* .body(z.object({ query: z.string() }))
|
|
684
|
+
* .handler(async ({ body }) => search(body.query));
|
|
685
|
+
* ```
|
|
686
|
+
*/
|
|
687
|
+
body<T>(schema: ZodType<T>): RouteBuilder<T, TQuery, TOutput, HasAuth, NeedsBody, True, IsDynamic>;
|
|
688
|
+
/**
|
|
689
|
+
* Declare a query-string Zod schema and switch the route to `GET`. Parsed
|
|
690
|
+
* query is typed as `ctx.query` in the handler. Use `.inputExample()` to
|
|
691
|
+
* attach a discovery example.
|
|
651
692
|
*
|
|
652
|
-
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```ts
|
|
695
|
+
* .query(z.object({ id: z.string() }))
|
|
696
|
+
* .handler(async ({ query }) => getById(query.id));
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
query<T>(schema: ZodType<T>): RouteBuilder<TBody, T, TOutput, HasAuth, NeedsBody, HasBody, IsDynamic>;
|
|
700
|
+
/**
|
|
701
|
+
* Declare the response output's Zod schema for OpenAPI generation. The
|
|
702
|
+
* runtime does not validate handler return values — use Zod's `.parse()`
|
|
703
|
+
* inside the handler if strict output validation is required. Use
|
|
704
|
+
* `.outputExample()` to attach a discovery example.
|
|
653
705
|
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
706
|
+
* @example
|
|
707
|
+
* ```ts
|
|
708
|
+
* .output(z.object({ result: z.string() }))
|
|
709
|
+
* .handler(async () => ({ result: 'ok' }));
|
|
710
|
+
* ```
|
|
711
|
+
*/
|
|
712
|
+
output<T>(schema: ZodType<T>): RouteBuilder<TBody, TQuery, T, HasAuth, NeedsBody, HasBody, IsDynamic>;
|
|
713
|
+
/**
|
|
714
|
+
* Attach an example of the request body or query for discovery output,
|
|
715
|
+
* validated against the registered schema at registration.
|
|
657
716
|
*
|
|
658
717
|
* @example
|
|
659
718
|
* ```ts
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
719
|
+
* .body(searchSchema).inputExample({ query: 'cats' });
|
|
720
|
+
* ```
|
|
721
|
+
*/
|
|
722
|
+
inputExample(example: InputTypeFor<TBody, TQuery> & JsonObject): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, IsDynamic>;
|
|
723
|
+
/**
|
|
724
|
+
* Attach an example response for discovery output, validated against the
|
|
725
|
+
* registered output schema at registration.
|
|
665
726
|
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
|
|
671
|
-
|
|
727
|
+
* @example
|
|
728
|
+
* ```ts
|
|
729
|
+
* .output(resultSchema).outputExample({ result: 'ok' });
|
|
730
|
+
* ```
|
|
731
|
+
*/
|
|
732
|
+
outputExample(example: TOutput & JsonValue): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, IsDynamic>;
|
|
733
|
+
/**
|
|
734
|
+
* Set a human-readable summary of the route. Surfaces in OpenAPI,
|
|
735
|
+
* `well-known`, and `llms.txt` discovery output.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* .description('Search indexed web pages by full-text query');
|
|
672
740
|
* ```
|
|
673
741
|
*/
|
|
674
|
-
outputExample(example: TOutput & JsonValue): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody>;
|
|
675
742
|
description(text: string): this;
|
|
743
|
+
/**
|
|
744
|
+
* Override the URL path advertised in discovery output. Defaults to the
|
|
745
|
+
* registry key passed to `.route()`.
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* ```ts
|
|
749
|
+
* router.route('search').path('/v2/search').handler(handler);
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
676
752
|
path(p: string): this;
|
|
753
|
+
/**
|
|
754
|
+
* Override the HTTP method advertised in discovery. Defaults to `POST`, or
|
|
755
|
+
* `GET` when `.query()` has been called.
|
|
756
|
+
*
|
|
757
|
+
* @example
|
|
758
|
+
* ```ts
|
|
759
|
+
* router.route('items/delete').method('DELETE').handler(handler);
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
677
762
|
method(m: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'): this;
|
|
678
763
|
/**
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
764
|
+
* Run validation against the parsed body before the 402 challenge. Throw
|
|
765
|
+
* `Object.assign(new Error('...'), { status })` to reject with a custom
|
|
766
|
+
* status code; defaults to 400. Requires `.body()` to be called first.
|
|
682
767
|
*
|
|
683
|
-
*
|
|
768
|
+
* @example
|
|
769
|
+
* ```ts
|
|
770
|
+
* .body(RegisterSchema).validate(async (body) => {
|
|
771
|
+
* if (await isTaken(body.name)) {
|
|
772
|
+
* throw Object.assign(new Error('taken'), { status: 409 });
|
|
773
|
+
* }
|
|
774
|
+
* });
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, IsDynamic>;
|
|
778
|
+
/**
|
|
779
|
+
* Hook into the settlement lifecycle. `beforeSettle` runs after the handler
|
|
780
|
+
* succeeds but before on-chain settlement and can cancel the charge;
|
|
781
|
+
* `afterSettle` runs after settlement completes (success or failure).
|
|
684
782
|
*
|
|
685
783
|
* @example
|
|
686
|
-
* ```
|
|
687
|
-
*
|
|
688
|
-
* .
|
|
689
|
-
* .
|
|
690
|
-
*
|
|
691
|
-
* .validate(async (body) => {
|
|
692
|
-
* if (await isDomainTaken(body.domain)) {
|
|
693
|
-
* throw Object.assign(new Error('Domain taken'), { status: 409 });
|
|
694
|
-
* }
|
|
695
|
-
* })
|
|
696
|
-
* .handler(async ({ body }) => { ... });
|
|
784
|
+
* ```ts
|
|
785
|
+
* .settlement({
|
|
786
|
+
* beforeSettle: ({ result }) => (result.refund ? 'skip' : 'continue'),
|
|
787
|
+
* afterSettle: ({ tx }) => analytics.track('settled', { tx }),
|
|
788
|
+
* });
|
|
697
789
|
* ```
|
|
698
790
|
*/
|
|
699
|
-
|
|
791
|
+
settlement(lifecycle: SettlementLifecycle<TBody>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, IsDynamic>;
|
|
700
792
|
/**
|
|
701
|
-
*
|
|
793
|
+
* Register the request handler and return the Next.js route function. The
|
|
794
|
+
* handler receives a typed context and may return a value (serialized to
|
|
795
|
+
* JSON), a raw `Response`, or throw an `HttpError` for a non-2xx status.
|
|
702
796
|
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
797
|
+
* @example
|
|
798
|
+
* ```ts
|
|
799
|
+
* export const POST = router
|
|
800
|
+
* .route('search')
|
|
801
|
+
* .paid('0.01')
|
|
802
|
+
* .body(schema)
|
|
803
|
+
* .handler(async ({ body, wallet }) => searchService(body, wallet));
|
|
804
|
+
* ```
|
|
707
805
|
*/
|
|
708
|
-
settlement(lifecycle: SettlementLifecycle<TBody>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody>;
|
|
709
806
|
handler(fn: HandlerArg<TBody, TQuery, HasAuth, NeedsBody, HasBody>): (request: NextRequest) => Promise<Response>;
|
|
807
|
+
/**
|
|
808
|
+
* Register a streaming handler (`async function*`) and return the Next.js
|
|
809
|
+
* route function. Each `charge()` call bills one tick (`tickCost` USDC) up
|
|
810
|
+
* to `maxPrice`; requires `.paid({ dynamic: true, ... })` and MPP session mode.
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* ```ts
|
|
814
|
+
* export const POST = router
|
|
815
|
+
* .route('llm/stream')
|
|
816
|
+
* .paid({ dynamic: true, tickCost: '0.0001', unitType: 'token', maxPrice: '0.05' })
|
|
817
|
+
* .body(schema)
|
|
818
|
+
* .stream(async function* ({ body, charge }) {
|
|
819
|
+
* for await (const token of streamLLM(body.prompt)) {
|
|
820
|
+
* await charge();
|
|
821
|
+
* yield token;
|
|
822
|
+
* }
|
|
823
|
+
* });
|
|
824
|
+
* ```
|
|
825
|
+
*/
|
|
826
|
+
stream(fn: StreamArg<TBody, TQuery, HasAuth, NeedsBody, HasBody, IsDynamic>): (request: NextRequest) => Promise<Response>;
|
|
827
|
+
private register;
|
|
710
828
|
}
|
|
711
829
|
|
|
712
830
|
declare const BASE_NETWORK = "eip155:8453";
|
|
@@ -715,7 +833,7 @@ declare const TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50"
|
|
|
715
833
|
declare const ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
716
834
|
|
|
717
835
|
type RouterEnv = Record<string, string | undefined>;
|
|
718
|
-
type RouterConfigIssueCode = 'missing_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | '
|
|
836
|
+
type RouterConfigIssueCode = 'missing_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer';
|
|
719
837
|
interface RouterConfigIssue {
|
|
720
838
|
code: RouterConfigIssueCode;
|
|
721
839
|
message: string;
|
|
@@ -725,17 +843,19 @@ interface RouterConfigValidationOptions {
|
|
|
725
843
|
env?: RouterEnv;
|
|
726
844
|
requireCdpKeys?: boolean;
|
|
727
845
|
}
|
|
846
|
+
|
|
728
847
|
declare class RouterConfigError extends Error {
|
|
729
848
|
readonly issues: RouterConfigIssue[];
|
|
730
849
|
constructor(issues: RouterConfigIssue[]);
|
|
731
850
|
}
|
|
851
|
+
declare function formatRouterConfigIssues(issues: readonly RouterConfigIssue[]): string;
|
|
852
|
+
|
|
732
853
|
declare function validateRouterConfig(config: RouterConfig, options?: RouterConfigValidationOptions): void;
|
|
733
854
|
declare function getRouterConfigIssues(config: RouterConfig, options?: RouterConfigValidationOptions): RouterConfigIssue[];
|
|
734
|
-
|
|
855
|
+
|
|
735
856
|
declare function mppFromEnv(env: RouterEnv, options?: {
|
|
736
857
|
recipient?: string;
|
|
737
858
|
require?: boolean;
|
|
738
|
-
useDefaultStore?: boolean;
|
|
739
859
|
feePayerKey?: string;
|
|
740
860
|
}): RouterConfig['mpp'] | undefined;
|
|
741
861
|
declare function x402AcceptsFromEnv(env: RouterEnv, options?: {
|
|
@@ -747,6 +867,11 @@ declare function x402AcceptsFromEnv(env: RouterEnv, options?: {
|
|
|
747
867
|
}): X402AcceptConfig[];
|
|
748
868
|
declare function paidOptionsForProtocols(protocols: readonly ProtocolType[]): PaidOptions;
|
|
749
869
|
|
|
870
|
+
/** SIWX verification error codes. */
|
|
871
|
+
type SiwxErrorCode = 'siwx_missing_header' | 'siwx_malformed' | 'siwx_expired' | 'siwx_nonce_used' | 'siwx_invalid_signature';
|
|
872
|
+
/** Human-readable error messages for each SIWX error code. */
|
|
873
|
+
declare const SIWX_ERROR_MESSAGES: Record<SiwxErrorCode, string>;
|
|
874
|
+
|
|
750
875
|
interface MonitorEntry {
|
|
751
876
|
provider: string;
|
|
752
877
|
route: string;
|
|
@@ -767,4 +892,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
|
|
|
767
892
|
prices?: P;
|
|
768
893
|
}): ServiceRouter<Extract<keyof P, string>>;
|
|
769
894
|
|
|
770
|
-
export { type AlertEvent, type AlertFn, type AlertLevel, type AuthEvent, type AuthMode, BASE_NETWORK, type DiscoveryConfig, type EntitlementStore, type ErrorEvent, type HandlerContext, type HandlerPaymentContext, HttpError, MemoryEntitlementStore, MemoryNonceStore, type MonitorEntry, type MppProtocolInfo, type NonceStore, type OveragePolicy, type PaidOptions, type PayToConfig, type PaymentEvent, type PaymentStatus, type PluginContext, type PricingConfig, type ProtocolType, type ProviderConfig, type ProviderQuotaEvent, type QuotaInfo, type QuotaLevel, type
|
|
895
|
+
export { type AlertEvent, type AlertFn, type AlertLevel, type AuthEvent, type AuthMode, BASE_NETWORK, type DiscoveryConfig, type EntitlementStore, type ErrorEvent, type HandlerContext, type HandlerPaymentContext, HttpError, type KvChange, type KvEntitlementStoreOptions, type KvMppStoreOptions, type KvNonceStoreOptions, type KvStore, MemoryEntitlementStore, MemoryNonceStore, type MonitorEntry, type MppProtocolInfo, type NonceStore, type OveragePolicy, type PaidOptions, type PayToConfig, type PaymentEvent, type PaymentStatus, type PluginContext, type PricingConfig, type ProtocolType, type ProviderConfig, type ProviderQuotaEvent, type QuotaInfo, type QuotaLevel, type RequestMeta, type ResponseMeta, RouteBuilder, type RouteEntry, RouteRegistry, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigValidationOptions, type RouterEnv, type RouterPlugin, SIWX_CHALLENGE_EXPIRY_MS, SIWX_ERROR_MESSAGES, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettledHandlerErrorContext, type SettlementErrorContext, type SettlementEvent, type SettlementLifecycle, type SettlementLifecycleContext, type SettlementSettledContext, type SiwxErrorCode, type StreamingHandlerContext, TEMPO_USDC_CURRENCY, type TierConfig, type X402AcceptConfig, type X402FacilitatorTarget, type X402FacilitatorsConfig, type X402ResolvedAccept, type X402RouterFacilitatorConfig, type X402Server, ZERO_EVM_ADDRESS, consolePlugin, createKvEntitlementStore, createKvMppStore, createKvNonceStore, createRouter, formatRouterConfigIssues, getRouterConfigIssues, mppFromEnv, paidOptionsForProtocols, validateRouterConfig, withPrefix, x402AcceptsFromEnv };
|