@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/.claude/CLAUDE.md +116 -2
- package/README.md +34 -5
- package/dist/index.cjs +2173 -1079
- package/dist/index.d.cts +391 -276
- package/dist/index.d.ts +391 -276
- package/dist/index.js +2169 -1077
- package/package.json +11 -11
package/dist/index.d.ts
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
430
|
-
|
|
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
|
|
429
|
+
/** Tempo RPC URL for on-chain verification. Falls back to `TEMPO_RPC_URL`. */
|
|
438
430
|
rpcUrl?: string;
|
|
439
|
-
/**
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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:
|
|
490
|
+
charge: MppxMiddleware<{
|
|
537
491
|
amount: string;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
} :
|
|
520
|
+
} : RequestHandlerFn<TBody, TQuery> : {
|
|
574
521
|
__missing: 'Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()';
|
|
575
522
|
};
|
|
576
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
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
|
-
*
|
|
635
|
-
*
|
|
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('
|
|
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
|
-
* .
|
|
642
|
-
* .
|
|
643
|
-
* .handler(async ({ body }) => { ... });
|
|
672
|
+
* .provider('exa', { quotaPerMonth: 1000 })
|
|
673
|
+
* .handler(handler);
|
|
644
674
|
* ```
|
|
645
675
|
*/
|
|
646
|
-
|
|
676
|
+
provider(name: string, config?: ProviderConfig): this;
|
|
647
677
|
/**
|
|
648
|
-
*
|
|
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
|
-
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
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
|
-
*
|
|
663
|
-
*
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
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
|
-
*
|
|
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
|
-
* ```
|
|
689
|
-
*
|
|
690
|
-
* .
|
|
691
|
-
* .
|
|
692
|
-
*
|
|
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
|
-
|
|
791
|
+
settlement(lifecycle: SettlementLifecycle<TBody>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, IsDynamic>;
|
|
702
792
|
/**
|
|
703
|
-
*
|
|
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
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
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' | '
|
|
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
|
-
|
|
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
|
|
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 };
|