@agentcash/router 1.4.1 → 1.5.1
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 +150 -9
- package/dist/index.cjs +658 -112
- package/dist/index.d.cts +148 -41
- package/dist/index.d.ts +148 -41
- package/dist/index.js +646 -111
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -46,7 +46,61 @@ export const env = createEnv({
|
|
|
46
46
|
});
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Without these keys, x402 routes
|
|
49
|
+
Without these keys, x402 routes that use the default EVM facilitator will fail to initialize.
|
|
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.
|
|
54
|
+
|
|
55
|
+
### Recommended strict setup
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import {
|
|
59
|
+
createRouter,
|
|
60
|
+
mppFromEnv,
|
|
61
|
+
validateRouterConfig,
|
|
62
|
+
x402AcceptsFromEnv,
|
|
63
|
+
type ProtocolType,
|
|
64
|
+
} from '@agentcash/router';
|
|
65
|
+
|
|
66
|
+
const payeeAddress = process.env.X402_WALLET_ADDRESS ?? process.env.X402_PAYEE_ADDRESS;
|
|
67
|
+
const accepts = x402AcceptsFromEnv(process.env, { payeeAddress });
|
|
68
|
+
const protocols: ProtocolType[] = process.env.MPP_SECRET_KEY ? ['x402', 'mpp'] : ['x402'];
|
|
69
|
+
|
|
70
|
+
const config = {
|
|
71
|
+
payeeAddress: payeeAddress!,
|
|
72
|
+
baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
73
|
+
strictRoutes: true,
|
|
74
|
+
protocols,
|
|
75
|
+
x402: { accepts },
|
|
76
|
+
mpp: mppFromEnv(process.env, {
|
|
77
|
+
recipient: payeeAddress,
|
|
78
|
+
useDefaultStore: true,
|
|
79
|
+
}),
|
|
80
|
+
discovery: {
|
|
81
|
+
title: 'My API',
|
|
82
|
+
version: '1.0.0',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
validateRouterConfig(config);
|
|
87
|
+
export const router = createRouter(config);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`x402AcceptsFromEnv()` always adds Base (`BASE_NETWORK`) and also adds Solana
|
|
91
|
+
mainnet (`SOLANA_MAINNET_NETWORK`) when `SOLANA_PAYEE_ADDRESS` is set. Solana
|
|
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' }`.
|
|
95
|
+
|
|
96
|
+
`mppFromEnv()` returns `undefined` when no MPP env vars are present. If any MPP
|
|
97
|
+
env var is present, the full trio is required: `MPP_SECRET_KEY`, `MPP_CURRENCY`,
|
|
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.
|
|
50
104
|
|
|
51
105
|
## Quick Start
|
|
52
106
|
|
|
@@ -57,7 +111,7 @@ Without these keys, x402 routes will fail to initialize (empty 402 responses, no
|
|
|
57
111
|
import { createRouter } from '@agentcash/router';
|
|
58
112
|
|
|
59
113
|
export const router = createRouter({
|
|
60
|
-
payeeAddress: process.env.
|
|
114
|
+
payeeAddress: process.env.X402_WALLET_ADDRESS!,
|
|
61
115
|
baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
62
116
|
strictRoutes: true, // recommended
|
|
63
117
|
discovery: {
|
|
@@ -143,9 +197,40 @@ Creates a `ServiceRouter` instance.
|
|
|
143
197
|
| `plugin` | `RouterPlugin` | `undefined` | Observability plugin |
|
|
144
198
|
| `prices` | `Record<string, string>` | `undefined` | Central pricing map (auto-applied) |
|
|
145
199
|
| `siwx.nonceStore` | `NonceStore` | `MemoryNonceStore` | Custom nonce store |
|
|
146
|
-
| `mpp` | `{ secretKey, currency, recipient? }` | `undefined` | MPP config |
|
|
200
|
+
| `mpp` | `{ secretKey, currency, recipient?, rpcUrl?, feePayerKey?, useDefaultStore? }` | `undefined` | MPP config |
|
|
201
|
+
| `protocols` | `('x402' \| 'mpp')[]` | `['x402']` | Default protocols for paid routes |
|
|
147
202
|
| `strictRoutes` | `boolean` | `false` | Enforce `route({ path })` and prevent key/path divergence |
|
|
148
203
|
|
|
204
|
+
### Config validation helpers
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import {
|
|
208
|
+
BASE_NETWORK,
|
|
209
|
+
SOLANA_MAINNET_NETWORK,
|
|
210
|
+
TEMPO_USDC_CURRENCY,
|
|
211
|
+
getRouterConfigIssues,
|
|
212
|
+
mppFromEnv,
|
|
213
|
+
paidOptionsForProtocols,
|
|
214
|
+
validateRouterConfig,
|
|
215
|
+
x402AcceptsFromEnv,
|
|
216
|
+
} from '@agentcash/router';
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- `validateRouterConfig(config)` throws `RouterConfigError` with structured
|
|
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.
|
|
223
|
+
- `getRouterConfigIssues(config)` returns the same structured issues without
|
|
224
|
+
throwing.
|
|
225
|
+
- `x402AcceptsFromEnv(env)` builds Base and optional Solana x402 accepts from
|
|
226
|
+
`X402_WALLET_ADDRESS` and `SOLANA_PAYEE_ADDRESS`.
|
|
227
|
+
- `mppFromEnv(env)` builds MPP config only when MPP env is present, and rejects
|
|
228
|
+
partial MPP env.
|
|
229
|
+
- `paidOptionsForProtocols(protocols)` copies a protocol array into a
|
|
230
|
+
route-level `PaidOptions` object.
|
|
231
|
+
- Manual `.paid(price)` routes inherit `createRouter({ protocols })` unless
|
|
232
|
+
the route passes its own `options.protocols`.
|
|
233
|
+
|
|
149
234
|
### Path-First Routing
|
|
150
235
|
|
|
151
236
|
Use path-first route definitions to keep runtime, OpenAPI, and discovery aligned:
|
|
@@ -170,11 +255,13 @@ The fluent builder ensures compile-time safety:
|
|
|
170
255
|
- `.siwx()` - SIWX wallet auth
|
|
171
256
|
- `.apiKey(resolver)` - API key auth (composable with `.paid()`)
|
|
172
257
|
- `.unprotected()` - No auth
|
|
173
|
-
- `.body(zodSchema)` - Request body validation
|
|
174
|
-
- `.query(zodSchema)` - Query parameter validation
|
|
175
|
-
- `.output(zodSchema)` - Response schema
|
|
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
|
|
176
262
|
- `.description(text)` - Route description (for OpenAPI)
|
|
177
263
|
- `.provider(name, config?)` - Provider monitoring (see [Provider Monitoring](#provider-monitoring))
|
|
264
|
+
- `.settlement({ beforeSettle, afterSettle, onSettledHandlerError, onSettlementError })` - Payment lifecycle hooks
|
|
178
265
|
- `.handler(fn)` - Terminal method, returns Next.js handler
|
|
179
266
|
|
|
180
267
|
### Pricing Modes
|
|
@@ -259,12 +346,51 @@ interface HandlerContext<TBody, TQuery> {
|
|
|
259
346
|
query: TQuery; // Parsed + validated
|
|
260
347
|
request: NextRequest; // Raw request
|
|
261
348
|
wallet: string | null; // Verified wallet address
|
|
349
|
+
payment: HandlerPaymentContext | null; // Payment metadata for this request
|
|
262
350
|
account: unknown; // From .apiKey() resolver
|
|
263
351
|
alert: AlertFn; // Fire observability alerts
|
|
264
352
|
setVerifiedWallet: (addr: string) => void;
|
|
265
353
|
}
|
|
266
354
|
```
|
|
267
355
|
|
|
356
|
+
`payment` is `null` for unprotected, API-key-only, and SIWX-only requests. For
|
|
357
|
+
paid requests it includes `protocol`, `status`, `payer`, `amount`, `network`,
|
|
358
|
+
and best-effort recipient/transaction/receipt metadata when the protocol
|
|
359
|
+
provides it. x402 handlers currently see `status: 'verified'` because settlement
|
|
360
|
+
happens after a successful handler response.
|
|
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
|
+
|
|
268
394
|
### RouterPlugin
|
|
269
395
|
|
|
270
396
|
Pluggable observability. All hooks are optional and fire-and-forget.
|
|
@@ -283,8 +409,13 @@ const myPlugin: RouterPlugin = {
|
|
|
283
409
|
};
|
|
284
410
|
|
|
285
411
|
export const router = createRouter({
|
|
286
|
-
payeeAddress: process.env.
|
|
412
|
+
payeeAddress: process.env.X402_WALLET_ADDRESS!,
|
|
413
|
+
baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
287
414
|
plugin: myPlugin,
|
|
415
|
+
discovery: {
|
|
416
|
+
title: 'My API',
|
|
417
|
+
version: '1.0.0',
|
|
418
|
+
},
|
|
288
419
|
});
|
|
289
420
|
```
|
|
290
421
|
|
|
@@ -294,8 +425,13 @@ Built-in `consolePlugin()` logs lifecycle events:
|
|
|
294
425
|
import { createRouter, consolePlugin } from '@agentcash/router';
|
|
295
426
|
|
|
296
427
|
export const router = createRouter({
|
|
297
|
-
payeeAddress: process.env.
|
|
428
|
+
payeeAddress: process.env.X402_WALLET_ADDRESS!,
|
|
429
|
+
baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
298
430
|
plugin: consolePlugin(),
|
|
431
|
+
discovery: {
|
|
432
|
+
title: 'My API',
|
|
433
|
+
version: '1.0.0',
|
|
434
|
+
},
|
|
299
435
|
});
|
|
300
436
|
```
|
|
301
437
|
|
|
@@ -305,7 +441,12 @@ For services with many static-priced routes:
|
|
|
305
441
|
|
|
306
442
|
```typescript
|
|
307
443
|
const router = createRouter({
|
|
308
|
-
payeeAddress: process.env.
|
|
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
|
+
},
|
|
309
450
|
prices: {
|
|
310
451
|
'search': '0.02',
|
|
311
452
|
'lookup': '0.05',
|