@agentcash/router 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,8 +47,10 @@ export const env = createEnv({
47
47
  ```
48
48
 
49
49
  Without these keys, x402 routes that use the default EVM facilitator will fail to initialize.
50
- Run `validateRouterConfig(config)` during startup if you want missing facilitator, MPP, or store
51
- configuration to throw immediately in every environment.
50
+ `createRouter(config)` always validates the base URL and protocol list, throws protocol config
51
+ errors in production, and keeps protocol-specific init errors as request-time JSON 500s in
52
+ development. Call `validateRouterConfig(config)` before `createRouter(config)` when you want
53
+ missing facilitator, MPP, or store configuration to throw immediately in every environment.
52
54
 
53
55
  ### Recommended strict setup
54
56
 
@@ -61,17 +63,18 @@ import {
61
63
  type ProtocolType,
62
64
  } from '@agentcash/router';
63
65
 
64
- const accepts = x402AcceptsFromEnv(process.env);
66
+ const payeeAddress = process.env.X402_WALLET_ADDRESS ?? process.env.X402_PAYEE_ADDRESS;
67
+ const accepts = x402AcceptsFromEnv(process.env, { payeeAddress });
65
68
  const protocols: ProtocolType[] = process.env.MPP_SECRET_KEY ? ['x402', 'mpp'] : ['x402'];
66
69
 
67
70
  const config = {
68
- payeeAddress: process.env.X402_WALLET_ADDRESS!,
71
+ payeeAddress: payeeAddress!,
69
72
  baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
70
73
  strictRoutes: true,
71
74
  protocols,
72
75
  x402: { accepts },
73
76
  mpp: mppFromEnv(process.env, {
74
- recipient: process.env.X402_WALLET_ADDRESS,
77
+ recipient: payeeAddress,
75
78
  useDefaultStore: true,
76
79
  }),
77
80
  discovery: {
@@ -86,11 +89,18 @@ export const router = createRouter(config);
86
89
 
87
90
  `x402AcceptsFromEnv()` always adds Base (`BASE_NETWORK`) and also adds Solana
88
91
  mainnet (`SOLANA_MAINNET_NETWORK`) when `SOLANA_PAYEE_ADDRESS` is set. Solana
89
- addresses are case-sensitive and are preserved as-is.
92
+ addresses are case-sensitive and are preserved as-is. It reads
93
+ `X402_WALLET_ADDRESS` by default; older services that still use
94
+ `X402_PAYEE_ADDRESS` can pass `{ payeeEnv: 'X402_PAYEE_ADDRESS' }`.
90
95
 
91
96
  `mppFromEnv()` returns `undefined` when no MPP env vars are present. If any MPP
92
97
  env var is present, the full trio is required: `MPP_SECRET_KEY`, `MPP_CURRENCY`,
93
- and `TEMPO_RPC_URL`.
98
+ and `TEMPO_RPC_URL`. `MPP_CURRENCY` must be the Tempo currency address; for
99
+ Tempo USDC use `TEMPO_USDC_CURRENCY`. Optional `MPP_FEE_PAYER_KEY` is included
100
+ when present and validated as a 32-byte EVM private key. `mppFromEnv()` only
101
+ builds config; call `validateRouterConfig(config)` before `createRouter(config)`
102
+ to fail fast on `mpp.useDefaultStore` store env (`KV_REST_API_URL` and
103
+ `KV_REST_API_TOKEN`) when you use the default store.
94
104
 
95
105
  ## Quick Start
96
106
 
@@ -101,7 +111,7 @@ and `TEMPO_RPC_URL`.
101
111
  import { createRouter } from '@agentcash/router';
102
112
 
103
113
  export const router = createRouter({
104
- payeeAddress: process.env.X402_PAYEE_ADDRESS!,
114
+ payeeAddress: process.env.X402_WALLET_ADDRESS!,
105
115
  baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
106
116
  strictRoutes: true, // recommended
107
117
  discovery: {
@@ -187,8 +197,8 @@ Creates a `ServiceRouter` instance.
187
197
  | `plugin` | `RouterPlugin` | `undefined` | Observability plugin |
188
198
  | `prices` | `Record<string, string>` | `undefined` | Central pricing map (auto-applied) |
189
199
  | `siwx.nonceStore` | `NonceStore` | `MemoryNonceStore` | Custom nonce store |
190
- | `mpp` | `{ secretKey, currency, recipient?, rpcUrl?, useDefaultStore? }` | `undefined` | MPP config |
191
- | `protocols` | `('x402' \| 'mpp')[]` | `['x402']` | Default protocols for auto-priced routes |
200
+ | `mpp` | `{ secretKey, currency, recipient?, rpcUrl?, feePayerKey?, useDefaultStore? }` | `undefined` | MPP config |
201
+ | `protocols` | `('x402' \| 'mpp')[]` | `['x402']` | Default protocols for paid routes |
192
202
  | `strictRoutes` | `boolean` | `false` | Enforce `route({ path })` and prevent key/path divergence |
193
203
 
194
204
  ### Config validation helpers
@@ -207,7 +217,9 @@ import {
207
217
  ```
208
218
 
209
219
  - `validateRouterConfig(config)` throws `RouterConfigError` with structured
210
- issues. Use it when you want invalid env/config to fail at startup.
220
+ issues. Use it when you want invalid env/config to fail at startup in every
221
+ environment; `createRouter(config)` still performs its own validation and
222
+ keeps deferred protocol init errors in development for request-time feedback.
211
223
  - `getRouterConfigIssues(config)` returns the same structured issues without
212
224
  throwing.
213
225
  - `x402AcceptsFromEnv(env)` builds Base and optional Solana x402 accepts from
@@ -216,6 +228,8 @@ import {
216
228
  partial MPP env.
217
229
  - `paidOptionsForProtocols(protocols)` copies a protocol array into a
218
230
  route-level `PaidOptions` object.
231
+ - Manual `.paid(price)` routes inherit `createRouter({ protocols })` unless
232
+ the route passes its own `options.protocols`.
219
233
 
220
234
  ### Path-First Routing
221
235
 
@@ -241,11 +255,13 @@ The fluent builder ensures compile-time safety:
241
255
  - `.siwx()` - SIWX wallet auth
242
256
  - `.apiKey(resolver)` - API key auth (composable with `.paid()`)
243
257
  - `.unprotected()` - No auth
244
- - `.body(zodSchema)` - Request body validation
245
- - `.query(zodSchema)` - Query parameter validation
246
- - `.output(zodSchema)` - Response schema (for OpenAPI)
258
+ - `.body(zodSchema, example?)` - Request body validation with optional discovery example
259
+ - `.query(zodSchema, example?)` - Query parameter validation with optional discovery example
260
+ - `.output(zodSchema, example?)` - Response schema with optional discovery example
261
+ - `.inputExample(sample)` / `.outputExample(sample)` - Optional examples when a separate call reads better
247
262
  - `.description(text)` - Route description (for OpenAPI)
248
263
  - `.provider(name, config?)` - Provider monitoring (see [Provider Monitoring](#provider-monitoring))
264
+ - `.settlement({ beforeSettle, afterSettle, onSettledHandlerError, onSettlementError })` - Payment lifecycle hooks
249
265
  - `.handler(fn)` - Terminal method, returns Next.js handler
250
266
 
251
267
  ### Pricing Modes
@@ -343,6 +359,38 @@ and best-effort recipient/transaction/receipt metadata when the protocol
343
359
  provides it. x402 handlers currently see `status: 'verified'` because settlement
344
360
  happens after a successful handler response.
345
361
 
362
+ ### Settlement Lifecycle
363
+
364
+ For paid routes, use `.settlement()` when final checks belong after handler work
365
+ but before router-controlled settlement:
366
+
367
+ ```typescript
368
+ router.route('render')
369
+ .paid('0.10')
370
+ .body(schema, { prompt: 'city at dusk' })
371
+ .settlement({
372
+ beforeSettle: async ({ result }) => {
373
+ if (!isUsableResult(result)) {
374
+ throw Object.assign(new Error('Render failed validation'), { status: 502 });
375
+ }
376
+ },
377
+ afterSettle: async ({ payment, result }) => {
378
+ await ledger.record({ tx: payment.transaction, result });
379
+ },
380
+ onSettledHandlerError: async ({ payment, error }) => {
381
+ await compensationQueue.enqueue({ receipt: payment.receipt, error });
382
+ },
383
+ })
384
+ .handler(async ({ body }) => render(body));
385
+ ```
386
+
387
+ `beforeSettle` can still prevent the charge for x402 and MPP
388
+ transaction-payload flows. `afterSettle` is for durable ledgers, analytics, and
389
+ post-settlement bookkeeping. `onSettledHandlerError` covers already-settled
390
+ MPP requests whose handler returns an error response, which is the right place
391
+ to enqueue app-owned refund or compensation work. The router cannot generically
392
+ refund protocol payments because it does not hold merchant signing keys.
393
+
346
394
  ### RouterPlugin
347
395
 
348
396
  Pluggable observability. All hooks are optional and fire-and-forget.
@@ -361,8 +409,13 @@ const myPlugin: RouterPlugin = {
361
409
  };
362
410
 
363
411
  export const router = createRouter({
364
- payeeAddress: process.env.X402_PAYEE_ADDRESS!,
412
+ payeeAddress: process.env.X402_WALLET_ADDRESS!,
413
+ baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
365
414
  plugin: myPlugin,
415
+ discovery: {
416
+ title: 'My API',
417
+ version: '1.0.0',
418
+ },
366
419
  });
367
420
  ```
368
421
 
@@ -372,8 +425,13 @@ Built-in `consolePlugin()` logs lifecycle events:
372
425
  import { createRouter, consolePlugin } from '@agentcash/router';
373
426
 
374
427
  export const router = createRouter({
375
- payeeAddress: process.env.X402_PAYEE_ADDRESS!,
428
+ payeeAddress: process.env.X402_WALLET_ADDRESS!,
429
+ baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
376
430
  plugin: consolePlugin(),
431
+ discovery: {
432
+ title: 'My API',
433
+ version: '1.0.0',
434
+ },
377
435
  });
378
436
  ```
379
437
 
@@ -383,7 +441,12 @@ For services with many static-priced routes:
383
441
 
384
442
  ```typescript
385
443
  const router = createRouter({
386
- payeeAddress: process.env.X402_PAYEE_ADDRESS!,
444
+ payeeAddress: process.env.X402_WALLET_ADDRESS!,
445
+ baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
446
+ discovery: {
447
+ title: 'My API',
448
+ version: '1.0.0',
449
+ },
387
450
  prices: {
388
451
  'search': '0.02',
389
452
  'lookup': '0.05',