@agentcash/router 1.5.2 → 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/dist/index.d.cts CHANGED
@@ -1,78 +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 { Store } from 'mppx';
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';
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;
7
45
 
8
46
  interface EntitlementStore {
9
47
  has(route: string, wallet: string): Promise<boolean>;
10
48
  grant(route: string, wallet: string): Promise<void>;
11
49
  }
12
- /**
13
- * In-memory SIWX entitlement store.
14
- *
15
- * Suitable for development and tests. Not durable across server restarts.
16
- */
17
50
  declare class MemoryEntitlementStore implements EntitlementStore {
18
51
  private readonly routeToWallets;
19
52
  has(route: string, wallet: string): Promise<boolean>;
20
53
  grant(route: string, wallet: string): Promise<void>;
21
54
  }
22
- interface RedisEntitlementStoreOptions {
23
- /** Key prefix. Default: 'siwx:entitlement:' */
55
+ interface KvEntitlementStoreOptions {
24
56
  prefix?: string;
25
57
  }
26
- /**
27
- * Redis-backed entitlement store for paid+SIWX acceleration.
28
- */
29
- declare function createRedisEntitlementStore(client: unknown, options?: RedisEntitlementStoreOptions): EntitlementStore;
58
+ declare function createKvEntitlementStore(kv: KvStore, options?: KvEntitlementStoreOptions): EntitlementStore;
30
59
 
31
- /**
32
- * SIWX challenge expiry in milliseconds.
33
- * Currently not configurable per-route — this is a known limitation.
34
- * Future versions may add `siwx: { expiryMs }` to RouterConfig.
35
- */
36
- declare const SIWX_CHALLENGE_EXPIRY_MS: number;
37
- interface NonceStore {
38
- check(nonce: string): Promise<boolean>;
39
- }
40
- /**
41
- * In-memory nonce store for development and testing.
42
- * NOT suitable for production serverless environments (Vercel, etc.)
43
- * where each function invocation gets fresh memory.
44
- *
45
- * For production, use `createRedisNonceStore()` with Upstash or ioredis.
46
- */
47
- declare class MemoryNonceStore implements NonceStore {
48
- private seen;
49
- check(nonce: string): Promise<boolean>;
50
- private evict;
51
- }
52
- interface RedisNonceStoreOptions {
53
- /** Key prefix for nonce storage. Default: 'siwx:nonce:' */
60
+ type MppStore = Store.Store;
61
+ interface KvMppStoreOptions {
54
62
  prefix?: string;
55
- /** TTL in milliseconds. Default: SIWX_CHALLENGE_EXPIRY_MS (5 minutes) */
56
- ttlMs?: number;
57
63
  }
58
- /**
59
- * Create a Redis-backed nonce store for production SIWX replay protection.
60
- * Auto-detects client type (Upstash or ioredis) and uses appropriate API.
61
- *
62
- * @example
63
- * ```ts
64
- * // Upstash (Vercel)
65
- * import { Redis } from '@upstash/redis';
66
- * const redis = new Redis({ url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN });
67
- * const nonceStore = createRedisNonceStore(redis);
68
- *
69
- * // ioredis
70
- * import Redis from 'ioredis';
71
- * const redis = new Redis(process.env.REDIS_URL);
72
- * const nonceStore = createRedisNonceStore(redis);
73
- * ```
74
- */
75
- declare function createRedisNonceStore(client: unknown, opts?: RedisNonceStoreOptions): NonceStore;
64
+ declare function createKvMppStore(kv: KvStore, options?: KvMppStoreOptions): Promise<MppStore>;
76
65
 
77
66
  interface RequestMeta {
78
67
  requestId: string;
@@ -167,14 +156,19 @@ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
167
156
  type JsonObject = {
168
157
  [key: string]: JsonValue;
169
158
  };
170
-
171
159
  interface X402Server {
172
160
  initialize(): Promise<void>;
173
161
  buildPaymentRequirementsFromOptions(options: Array<{
174
162
  scheme: string;
175
163
  network: string;
176
- price: string;
164
+ price: string | {
165
+ asset: string;
166
+ amount: string;
167
+ extra?: Record<string, unknown>;
168
+ };
177
169
  payTo: string;
170
+ maxTimeoutSeconds?: number;
171
+ extra?: Record<string, unknown>;
178
172
  }>, context: {
179
173
  request: Request;
180
174
  }): Promise<PaymentRequirements[]>;
@@ -184,32 +178,20 @@ interface X402Server {
184
178
  description?: string;
185
179
  }, error?: string, extensions?: Record<string, unknown>): Promise<PaymentRequired>;
186
180
  findMatchingRequirements(requirements: PaymentRequirements[], payload: unknown): PaymentRequirements;
187
- verifyPayment(payload: unknown, requirements: PaymentRequirements): Promise<{
188
- isValid: boolean;
189
- payer?: string;
190
- }>;
191
- 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>;
192
185
  }
193
186
  type ProtocolType = 'x402' | 'mpp';
194
187
  type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
195
188
  type RouteMethod = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
196
189
  interface RouteDefinition<K extends string = string> {
197
- /**
198
- * Public API path segment (without `/api/` prefix).
199
- * Example: `flightaware/airports/id/flights/arrivals`
200
- */
190
+ /** Public API path segment without the `/api/` prefix (e.g. `flightaware/airports/id/flights/arrivals`). */
201
191
  path: K;
202
- /**
203
- * Internal route ID for pricing maps / analytics. Defaults to `path`.
204
- *
205
- * In `strictRoutes` mode, custom keys are disallowed to prevent discovery
206
- * drift between internal IDs and advertised paths.
207
- */
192
+ /** Internal route ID for pricing maps / analytics. Defaults to `path`. Disallowed under `strictRoutes` to prevent discovery drift. */
208
193
  key?: string;
209
- /**
210
- * Optional explicit method. If omitted, defaults to builder behavior
211
- * (`POST`, or `GET` when `.query()` is used).
212
- */
194
+ /** Explicit HTTP method. Defaults to `POST`, or `GET` when `.query()` is used. */
213
195
  method?: RouteMethod;
214
196
  }
215
197
  interface TierConfig {
@@ -223,14 +205,21 @@ type PricingConfig<TBody = unknown> = string | ((body: TBody) => string | Promis
223
205
  };
224
206
  type PayToConfig = string | ((request: Request, body?: unknown) => string | Promise<string>);
225
207
  interface X402AcceptBase {
208
+ /** Chain identifier (e.g. `base`, `base-sepolia`, `solana-mainnet`). */
226
209
  network: string;
210
+ /** Token contract address (EVM) or mint (Solana). Defaults to USDC for the network. */
227
211
  asset?: string;
212
+ /** Token decimals. Defaults to USDC's 6. */
228
213
  decimals?: number;
214
+ /** Max payment-proof age the facilitator will accept, in seconds. */
229
215
  maxTimeoutSeconds?: number;
216
+ /** Extra fields passed through to the x402 PaymentRequirements `extra` block. */
230
217
  extra?: Record<string, unknown>;
231
218
  }
232
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' */
233
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`. */
234
223
  payTo?: PayToConfig;
235
224
  }
236
225
  interface X402ResolvedAccept extends X402AcceptBase {
@@ -238,11 +227,15 @@ interface X402ResolvedAccept extends X402AcceptBase {
238
227
  payTo: string;
239
228
  }
240
229
  interface X402RouterFacilitatorConfig extends FacilitatorConfig {
230
+ /** Async header builder invoked per facilitator call. Use for short-lived auth tokens (e.g. CDP signed headers). */
241
231
  createAcceptsHeaders?: () => Promise<Record<string, string>>;
242
232
  }
233
+ /** A facilitator URL or a full `FacilitatorConfig` (URL + auth header builders). */
243
234
  type X402FacilitatorTarget = string | X402RouterFacilitatorConfig;
244
235
  interface X402FacilitatorsConfig {
236
+ /** Facilitator for EVM chains (Base, etc.). Defaults to the Coinbase facilitator using `CDP_API_KEY_ID`/`CDP_API_KEY_SECRET`. */
245
237
  evm?: X402FacilitatorTarget;
238
+ /** Facilitator for Solana. Required to accept Solana payments — there's no default. */
246
239
  solana?: X402FacilitatorTarget;
247
240
  }
248
241
  interface MppProtocolInfo {
@@ -258,6 +251,12 @@ interface PaidOptions {
258
251
  payTo?: PayToConfig;
259
252
  /** Override MPP protocol metadata in x-payment-info discovery. */
260
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;
261
260
  }
262
261
  type PaymentStatus = 'verified' | 'settled';
263
262
  interface HandlerPaymentContext {
@@ -293,26 +292,16 @@ interface SettledHandlerErrorContext<TBody = unknown> extends SettlementSettledC
293
292
  error: unknown;
294
293
  }
295
294
  interface SettlementLifecycle<TBody = unknown> {
296
- /**
297
- * Runs after the handler returns a successful response, before router-controlled
298
- * settlement/broadcast. Throw with `.status` to return a specific error and
299
- * skip settlement when the protocol flow has not already settled.
300
- */
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). */
301
296
  beforeSettle?: (ctx: SettlementLifecycleContext<TBody>) => void | Promise<void>;
302
- /**
303
- * Runs after successful settlement. Use for durable ledgers and audit rows.
304
- * Errors are alerted and do not change the already-settled response.
305
- */
297
+ /** Runs after successful settlement; for durable ledgers and audit rows. Errors are alerted but don't change the already-settled response. */
306
298
  afterSettle?: (ctx: SettlementSettledContext<TBody>) => void | Promise<void>;
307
- /**
308
- * Runs when the router has already observed a settled payment, then the
309
- * handler returns an error response. Use for app-owned refund or
310
- * compensation queues.
311
- */
299
+ /** Runs when payment was settled but the handler then returned an error response. Use for app-owned refund / compensation queues. */
312
300
  onSettledHandlerError?: (ctx: SettledHandlerErrorContext<TBody>) => void | Promise<void>;
313
301
  /** Runs when router-controlled settlement fails after the handler succeeded. */
314
302
  onSettlementError?: (ctx: SettlementErrorContext<TBody>) => void | Promise<void>;
315
303
  }
304
+ type ChargeFn = () => Promise<void>;
316
305
  interface HandlerContext<TBody = undefined, TQuery = undefined> {
317
306
  body: TBody;
318
307
  query: TQuery;
@@ -325,6 +314,10 @@ interface HandlerContext<TBody = undefined, TQuery = undefined> {
325
314
  alert: AlertFn;
326
315
  setVerifiedWallet: (addr: string) => void;
327
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
+ }
328
321
  type OveragePolicy = 'same-rate' | 'increased-rate' | 'hard-stop';
329
322
  type QuotaLevel = 'healthy' | 'warn' | 'critical';
330
323
  interface QuotaInfo {
@@ -358,28 +351,17 @@ interface RouteEntry {
358
351
  */
359
352
  siwxEnabled?: boolean;
360
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;
361
358
  protocols: ProtocolType[];
362
359
  bodySchema?: ZodType;
363
360
  querySchema?: ZodType;
364
361
  outputSchema?: ZodType;
365
- /**
366
- * Optional conforming example for the request input (body for body routes, query params for query routes).
367
- * When present, it must satisfy the corresponding schema and is validated at route registration.
368
- *
369
- * Emitted in the bazaar discovery extension so indexers can advertise a working sample call.
370
- */
362
+ /** Optional conforming example for the request input (body or query). Validated against the schema at registration. Emitted in the bazaar discovery extension. */
371
363
  inputExample?: JsonObject;
372
- /**
373
- * Optional conforming example for the response output. When present, it must
374
- * satisfy `outputSchema` and is validated at route registration.
375
- *
376
- * Accepts any JSON value (object, array, or primitive) to support top-level array or
377
- * primitive response schemas.
378
- *
379
- * Emitted in the bazaar discovery extension. Without it the `output` block is
380
- * dropped from the declaration entirely (the output schema alone cannot be
381
- * exposed in bazaar without an example).
382
- */
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). */
383
365
  outputExample?: JsonValue;
384
366
  description?: string;
385
367
  path?: string;
@@ -393,6 +375,10 @@ interface RouteEntry {
393
375
  validateFn?: (body: unknown) => void | Promise<void>;
394
376
  settlement?: SettlementLifecycle;
395
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;
396
382
  }
397
383
  interface DiscoveryConfig {
398
384
  title: string;
@@ -410,93 +396,53 @@ interface DiscoveryConfig {
410
396
  serverUrl?: string;
411
397
  }
412
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 })`. */
413
400
  payeeAddress?: string;
414
- /**
415
- * Origin URL (e.g. `https://myapp.com`).
416
- * Used for 402 challenge realm, discovery URLs, OpenAPI servers, and MPP memo indexing.
417
- *
418
- * **Required.** No auto-detection — the realm is load-bearing for payment matching,
419
- * so it must be explicitly set by the consuming app.
420
- */
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. */
421
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' */
422
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`). */
423
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. */
424
408
  accepts?: X402AcceptConfig[];
409
+ /** Per-chain facilitator overrides (`evm`/`solana`). Defaults to the Coinbase facilitator on EVM; set `solana` to accept Solana payments. */
425
410
  facilitators?: X402FacilitatorsConfig;
426
411
  };
412
+ /** Observability hook receiving request/auth/payment/settlement events. Use `consolePlugin` for dev, or implement `RouterPlugin` for structured logs/analytics. */
427
413
  plugin?: RouterPlugin;
428
- siwx?: {
429
- nonceStore?: NonceStore;
430
- entitlementStore?: EntitlementStore;
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;
431
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. */
432
420
  prices?: Record<string, string>;
421
+ /** MPP (Tempo) payment-channel config. Required when `protocols` includes `'mpp'`. */
433
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`. */
434
424
  secretKey: string;
425
+ /** Tempo currency contract address (0x-prefixed). Use `TEMPO_USDC_CURRENCY` for USDC on Tempo. */
435
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. */
436
428
  recipient?: string;
437
- /** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL env var. */
429
+ /** Tempo RPC URL for on-chain verification. Falls back to `TEMPO_RPC_URL`. */
438
430
  rpcUrl?: string;
439
- /**
440
- * Private key of the account that sponsors transaction fees.
441
- * When set, clients don't need gas tokensthe server pays fees on their behalf.
442
- * Must be a hex-encoded private key (e.g. `0xabc123...`).
443
- */
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. */
444
434
  feePayerKey?: string;
445
- /**
446
- * Persistent store for transaction hash replay protection.
447
- *
448
- * Without this, mppx defaults to `Store.memory()` which is wiped on every cold start —
449
- * unsafe on Vercel or any multi-instance deployment. Pass `Store.upstash(redis)` or
450
- * `Store.cloudflare(kv)` for a shared persistent store.
451
- *
452
- * @example
453
- * import { Store } from 'mppx'
454
- * store: Store.upstash({ get, set, del })
455
- * store: Store.cloudflare(env.MY_KV_NAMESPACE)
456
- */
457
- store?: Store.Store;
458
- /**
459
- * When `true`, auto-configures an Upstash-backed persistent store from Vercel KV
460
- * environment variables (`KV_REST_API_URL` + `KV_REST_API_TOKEN`).
461
- *
462
- * Uses raw `fetch` against the Upstash REST API — no extra npm dependencies.
463
- * Ignored when `store` is explicitly provided.
464
- *
465
- * @example
466
- * createRouter({
467
- * mpp: {
468
- * secretKey: process.env.MPP_SECRET_KEY!,
469
- * currency: TEMPO_USDC_CURRENCY,
470
- * useDefaultStore: true,
471
- * }
472
- * })
473
- */
474
- 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
+ };
475
440
  };
476
- /**
477
- * Payment protocols to accept on paid routes unless a route overrides them.
478
- *
479
- * @default ['x402']
480
- *
481
- * @example
482
- * // Accept both x402 and MPP payments
483
- * createRouter({
484
- * protocols: ['x402', 'mpp'],
485
- * mpp: { secretKey, currency: TEMPO_USDC_CURRENCY, recipient },
486
- * prices: { 'exa/search': '0.01' }
487
- * })
488
- */
441
+ /** Payment protocols to accept on paid routes unless overridden per route. @default ['x402'] */
489
442
  protocols?: ProtocolType[];
490
- /**
491
- * Enforce explicit, path-first route definitions.
492
- *
493
- * When enabled:
494
- * - `.route('key')` is rejected; use `.route({ path })`.
495
- * - custom `key` differing from `path` is rejected.
496
- *
497
- * This prevents discovery/openapi drift caused by shorthand internal keys.
498
- */
443
+ /** When true, `.route('key')` is rejected (use `.route({ path })`) and custom `key !== path` is rejected. Prevents discovery/openapi drift. */
499
444
  strictRoutes?: boolean;
445
+ /** Static metadata for auto-generated discovery surfaces — `/.well-known/x402`, OpenAPI (`/api/openapi`), and `/llms.txt`. */
500
446
  discovery: DiscoveryConfig;
501
447
  }
502
448
 
@@ -519,6 +465,14 @@ interface ResolvedX402Facilitator {
519
465
  config: X402RouterFacilitatorConfig;
520
466
  }
521
467
 
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>>;
522
476
  interface RouterDeps {
523
477
  x402Server: X402Server | null;
524
478
  initPromise: Promise<void>;
@@ -533,15 +487,22 @@ interface RouterDeps {
533
487
  x402FacilitatorsByNetwork?: Record<string, ResolvedX402Facilitator>;
534
488
  x402Accepts: X402AcceptConfig[];
535
489
  mppx?: {
536
- charge: (options: {
490
+ charge: MppxMiddleware<{
537
491
  amount: string;
538
- }) => (input: Request) => Promise<{
539
- status: 402;
540
- challenge: Response;
541
- } | {
542
- status: 200;
543
- withReceipt: (response: Response) => Response;
544
- }>;
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;
545
506
  } | null;
546
507
  tempoClient?: viem.Client | null;
547
508
  }
@@ -551,29 +512,22 @@ type OrchestrateDeps = RouterDeps;
551
512
 
552
513
  type True = true;
553
514
  type False = false;
554
- /**
555
- * Active request-input type at a builder position. Resolves to `TBody` when
556
- * `.body()` has been called, `TQuery` when `.query()` has been called, and
557
- * `never` when neither — making `.inputExample()` unusable before a schema
558
- * is set (the literal won't assign to `never`).
559
- */
560
515
  type InputTypeFor<TBody, TQuery> = [TBody] extends [undefined] ? [TQuery] extends [undefined] ? never : TQuery : TBody;
561
- /**
562
- * The handler argument type. Narrows to the real handler signature when the
563
- * builder state is valid, and to a descriptive error object when it isn't —
564
- * the mismatch surfaces as a TS type error at the `.handler(...)` call site
565
- * with the `__missing` string as the contextual hint.
566
- *
567
- * Encoded as a conditional argument rather than overload `this:` constraints
568
- * because TypeScript doesn't reliably gate overload selection on `this` for
569
- * generic classes (structurally identical instance types collapse).
570
- */
516
+ type RequestHandlerFn<TBody, TQuery> = (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown>;
517
+ type StreamingHandlerFn<TBody, TQuery> = (ctx: StreamingHandlerContext<TBody, TQuery>) => AsyncIterable<unknown>;
571
518
  type HandlerArg<TBody, TQuery, HasAuth extends boolean, NeedsBody extends boolean, HasBody extends boolean> = HasAuth extends true ? [NeedsBody, HasBody] extends [true, false] ? {
572
519
  __missing: 'Call .body(schema) — dynamic/tiered pricing requires a body schema to resolve the price against';
573
- } : (ctx: HandlerContext<TBody, TQuery>) => Promise<unknown> : {
520
+ } : RequestHandlerFn<TBody, TQuery> : {
574
521
  __missing: 'Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()';
575
522
  };
576
- declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = undefined, HasAuth extends boolean = false, NeedsBody extends boolean = false, HasBody extends boolean = false> {
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> {
577
531
  /** @internal */ readonly _key: string;
578
532
  /** @internal */ readonly _registry: RouteRegistry;
579
533
  /** @internal */ readonly _deps: OrchestrateDeps;
@@ -583,6 +537,9 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
583
537
  /** @internal */ _protocols: ProtocolType[];
584
538
  /** @internal */ _maxPrice: string | undefined;
585
539
  /** @internal */ _minPrice: string | undefined;
540
+ /** @internal */ _dynamicPrice: boolean;
541
+ /** @internal */ _tickCost: string | undefined;
542
+ /** @internal */ _unitType: string | undefined;
586
543
  /** @internal */ _payTo: PayToConfig | undefined;
587
544
  /** @internal */ _bodySchema: ZodType | undefined;
588
545
  /** @internal */ _querySchema: ZodType | undefined;
@@ -602,10 +559,63 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
602
559
  /** @internal */ _mppInfo: MppProtocolInfo | undefined;
603
560
  constructor(key: string, registry: RouteRegistry, deps: OrchestrateDeps);
604
561
  private fork;
605
- paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody>;
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
+ */
606
602
  paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
607
603
  maxPrice?: string;
608
- }): 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
+ */
609
619
  paid(pricing: {
610
620
  field: string;
611
621
  tiers: Record<string, {
@@ -613,102 +623,208 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
613
623
  label?: string;
614
624
  }>;
615
625
  default?: string;
616
- }, options?: PaidOptions): RouteBuilder<TBody, TQuery, TOutput, True, True, HasBody>;
617
- siwx(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody>;
618
- apiKey(resolver: (key: string) => unknown | Promise<unknown>): RouteBuilder<TBody, TQuery, TOutput, True, NeedsBody, HasBody>;
619
- unprotected(): RouteBuilder<TBody, TQuery, TOutput, True, False, HasBody>;
620
- provider(name: string, config?: ProviderConfig): this;
621
- body<T>(schema: ZodType<T>): RouteBuilder<T, TQuery, TOutput, HasAuth, NeedsBody, True>;
622
- body<T>(schema: ZodType<T>, example: T & JsonObject): RouteBuilder<T, TQuery, TOutput, HasAuth, NeedsBody, True>;
623
- query<T>(schema: ZodType<T>): RouteBuilder<TBody, T, TOutput, HasAuth, NeedsBody, HasBody>;
624
- query<T>(schema: ZodType<T>, example: T & JsonObject): RouteBuilder<TBody, T, TOutput, HasAuth, NeedsBody, HasBody>;
625
- output<T>(schema: ZodType<T>): RouteBuilder<TBody, TQuery, T, HasAuth, NeedsBody, HasBody>;
626
- 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>;
627
627
  /**
628
- * Provide a conforming example of the request input (body or query params).
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.
629
631
  *
630
- * Optional. When provided, the example is validated against the request schema
631
- * at route registration and embedded in the bazaar discovery extension so
632
- * indexers can advertise a working sample call.
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.
633
642
  *
634
- * For the common case, pass the example directly to `.body(schema, example)` or
635
- * `.query(schema, example)` instead.
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`.
636
655
  *
637
656
  * @example
638
657
  * ```ts
639
- * router.route('search')
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.
666
+ *
667
+ * @example
668
+ * ```ts
669
+ * router
670
+ * .route('search')
640
671
  * .paid('0.01')
641
- * .body(z.object({ q: z.string() }))
642
- * .inputExample({ q: 'hello world' })
643
- * .handler(async ({ body }) => { ... });
672
+ * .provider('exa', { quotaPerMonth: 1000 })
673
+ * .handler(handler);
644
674
  * ```
645
675
  */
646
- inputExample(example: InputTypeFor<TBody, TQuery> & JsonObject): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody>;
676
+ provider(name: string, config?: ProviderConfig): this;
647
677
  /**
648
- * Provide a conforming example of the response output.
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.
649
680
  *
650
- * Optional. When provided, the example is validated against the output schema
651
- * at route registration and embedded in the bazaar discovery extension so
652
- * indexers can advertise the response shape.
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.
653
692
  *
654
- * For the common case, pass the example directly to `.output(schema, example)` instead.
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.
655
705
  *
656
- * Accepts any JSON value (objects, arrays, or primitives) — top-level array
657
- * or primitive responses (e.g. `z.array(...)`) are supported alongside the
658
- * common object case.
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.
659
716
  *
660
717
  * @example
661
718
  * ```ts
662
- * router.route('search')
663
- * .paid('0.01')
664
- * .output(z.object({ results: z.array(z.string()) }))
665
- * .outputExample({ results: ['a', 'b'] })
666
- * .handler(async () => { ... });
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.
667
726
  *
668
- * // Top-level array response
669
- * router.route('chains')
670
- * .paid('0.01')
671
- * .output(z.array(z.object({ name: z.string() })))
672
- * .outputExample([{ name: 'Ethereum' }])
673
- * .handler(async () => { ... });
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');
674
740
  * ```
675
741
  */
676
- outputExample(example: TOutput & JsonValue): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody>;
677
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
+ */
678
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
+ */
679
762
  method(m: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'): this;
680
763
  /**
681
- * Add pre-payment validation that runs after body parsing but before the 402
682
- * challenge is shown. Use this for async business logic like "is this resource
683
- * available?" or "has this user hit their rate limit?".
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.
684
767
  *
685
- * Requires `.body()` — call `.body()` before `.validate()` for type inference.
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).
686
782
  *
687
783
  * @example
688
- * ```typescript
689
- * router
690
- * .route('domain/register')
691
- * .paid(calculatePrice)
692
- * .body(RegisterSchema) // .body() first for type inference
693
- * .validate(async (body) => {
694
- * if (await isDomainTaken(body.domain)) {
695
- * throw Object.assign(new Error('Domain taken'), { status: 409 });
696
- * }
697
- * })
698
- * .handler(async ({ body }) => { ... });
784
+ * ```ts
785
+ * .settlement({
786
+ * beforeSettle: ({ result }) => (result.refund ? 'skip' : 'continue'),
787
+ * afterSettle: ({ tx }) => analytics.track('settled', { tx }),
788
+ * });
699
789
  * ```
700
790
  */
701
- validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody>;
791
+ settlement(lifecycle: SettlementLifecycle<TBody>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, IsDynamic>;
702
792
  /**
703
- * Add route-specific settlement hooks.
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.
704
796
  *
705
- * `beforeSettle` runs after a successful handler response but before
706
- * router-controlled settlement/broadcast, so it can still prevent the charge
707
- * for x402 and MPP transaction-payload flows. `afterSettle` runs after
708
- * settlement and is intended for durable ledgers or app-owned refund queues.
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
+ * ```
709
805
  */
710
- settlement(lifecycle: SettlementLifecycle<TBody>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody>;
711
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;
712
828
  }
713
829
 
714
830
  declare const BASE_NETWORK = "eip155:8453";
@@ -717,7 +833,7 @@ declare const TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50"
717
833
  declare const ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
718
834
 
719
835
  type RouterEnv = Record<string, string | undefined>;
720
- 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' | 'missing_mpp_default_store_env';
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';
721
837
  interface RouterConfigIssue {
722
838
  code: RouterConfigIssueCode;
723
839
  message: string;
@@ -727,17 +843,19 @@ interface RouterConfigValidationOptions {
727
843
  env?: RouterEnv;
728
844
  requireCdpKeys?: boolean;
729
845
  }
846
+
730
847
  declare class RouterConfigError extends Error {
731
848
  readonly issues: RouterConfigIssue[];
732
849
  constructor(issues: RouterConfigIssue[]);
733
850
  }
851
+ declare function formatRouterConfigIssues(issues: readonly RouterConfigIssue[]): string;
852
+
734
853
  declare function validateRouterConfig(config: RouterConfig, options?: RouterConfigValidationOptions): void;
735
854
  declare function getRouterConfigIssues(config: RouterConfig, options?: RouterConfigValidationOptions): RouterConfigIssue[];
736
- declare function formatRouterConfigIssues(issues: readonly RouterConfigIssue[]): string;
855
+
737
856
  declare function mppFromEnv(env: RouterEnv, options?: {
738
857
  recipient?: string;
739
858
  require?: boolean;
740
- useDefaultStore?: boolean;
741
859
  feePayerKey?: string;
742
860
  }): RouterConfig['mpp'] | undefined;
743
861
  declare function x402AcceptsFromEnv(env: RouterEnv, options?: {
@@ -749,10 +867,7 @@ declare function x402AcceptsFromEnv(env: RouterEnv, options?: {
749
867
  }): X402AcceptConfig[];
750
868
  declare function paidOptionsForProtocols(protocols: readonly ProtocolType[]): PaidOptions;
751
869
 
752
- /**
753
- * SIWX verification error codes.
754
- * Enables clients to auto-retry transient failures (e.g., expired challenge).
755
- */
870
+ /** SIWX verification error codes. */
756
871
  type SiwxErrorCode = 'siwx_missing_header' | 'siwx_malformed' | 'siwx_expired' | 'siwx_nonce_used' | 'siwx_invalid_signature';
757
872
  /** Human-readable error messages for each SIWX error code. */
758
873
  declare const SIWX_ERROR_MESSAGES: Record<SiwxErrorCode, string>;
@@ -777,4 +892,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
777
892
  prices?: P;
778
893
  }): ServiceRouter<Extract<keyof P, string>>;
779
894
 
780
- 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 RedisEntitlementStoreOptions, type RedisNonceStoreOptions, 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, TEMPO_USDC_CURRENCY, type TierConfig, type X402AcceptConfig, type X402FacilitatorTarget, type X402FacilitatorsConfig, type X402ResolvedAccept, type X402RouterFacilitatorConfig, type X402Server, ZERO_EVM_ADDRESS, consolePlugin, createRedisEntitlementStore, createRedisNonceStore, createRouter, formatRouterConfigIssues, getRouterConfigIssues, mppFromEnv, paidOptionsForProtocols, validateRouterConfig, x402AcceptsFromEnv };
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 };