@agentcash/router 1.1.3 → 1.1.5
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/skills/router-guide/SKILL.md +7 -1
- package/README.md +16 -9
- package/dist/index.cjs +17 -7
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +17 -7
- package/package.json +4 -4
|
@@ -368,6 +368,8 @@ for (const entry of router.monitors()) {
|
|
|
368
368
|
|
|
369
369
|
## Discovery Setup
|
|
370
370
|
|
|
371
|
+
Discovery metadata (`title`, `version`, `description`, `guidance`) is configured once in `createRouter({ discovery })`. The discovery handlers are zero-arg:
|
|
372
|
+
|
|
371
373
|
```typescript
|
|
372
374
|
// app/.well-known/x402/route.ts
|
|
373
375
|
import '@/lib/routes/barrel'; // barrel import FIRST to register all routes
|
|
@@ -377,7 +379,11 @@ export const GET = router.wellKnown();
|
|
|
377
379
|
// app/openapi.json/route.ts
|
|
378
380
|
import '@/lib/routes/barrel';
|
|
379
381
|
import { router } from '@/lib/routes';
|
|
380
|
-
export const GET = router.openapi(
|
|
382
|
+
export const GET = router.openapi();
|
|
383
|
+
|
|
384
|
+
// app/llms.txt/route.ts
|
|
385
|
+
import { router } from '@/lib/routes';
|
|
386
|
+
export const GET = router.llmsTxt();
|
|
381
387
|
```
|
|
382
388
|
|
|
383
389
|
**Barrel import must come first.** Without it, Next.js lazy-loads route modules, so discovery endpoints hit before routes register → `route 'X' in prices map but not registered` error.
|
package/README.md
CHANGED
|
@@ -60,6 +60,11 @@ export const router = createRouter({
|
|
|
60
60
|
payeeAddress: process.env.X402_PAYEE_ADDRESS!,
|
|
61
61
|
baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
62
62
|
strictRoutes: true, // recommended
|
|
63
|
+
discovery: {
|
|
64
|
+
title: 'My API',
|
|
65
|
+
version: '1.0.0',
|
|
66
|
+
description: 'Pay-per-call API',
|
|
67
|
+
},
|
|
63
68
|
});
|
|
64
69
|
```
|
|
65
70
|
|
|
@@ -99,28 +104,29 @@ export const GET = router.route({ path: 'health' })
|
|
|
99
104
|
|
|
100
105
|
### 3. Auto-discovery
|
|
101
106
|
|
|
107
|
+
Discovery metadata (`title`, `version`, `description`, `guidance`) is configured once in `createRouter({ discovery })`. The discovery handlers are zero-arg:
|
|
108
|
+
|
|
102
109
|
```typescript
|
|
103
110
|
// app/.well-known/x402/route.ts
|
|
104
111
|
import { router } from '@/lib/routes';
|
|
105
112
|
import '@/lib/routes/barrel'; // ensures all routes are imported
|
|
106
|
-
export const GET = router.wellKnown(
|
|
113
|
+
export const GET = router.wellKnown();
|
|
107
114
|
|
|
108
115
|
// app/openapi.json/route.ts
|
|
109
116
|
import { router } from '@/lib/routes';
|
|
110
117
|
import '@/lib/routes/barrel';
|
|
111
|
-
export const GET = router.openapi(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
118
|
+
export const GET = router.openapi();
|
|
119
|
+
|
|
120
|
+
// app/llms.txt/route.ts
|
|
121
|
+
import { router } from '@/lib/routes';
|
|
122
|
+
export const GET = router.llmsTxt();
|
|
117
123
|
```
|
|
118
124
|
|
|
119
125
|
OpenAPI output follows the discovery contract:
|
|
120
126
|
|
|
121
127
|
- Paid signaling via `responses.402` + `x-payment-info`
|
|
122
128
|
- Auth signaling via `security` + `components.securitySchemes`
|
|
123
|
-
- Optional top-level metadata via `x-discovery` (`
|
|
129
|
+
- Optional top-level metadata via `x-discovery` (`ownershipProofs`)
|
|
124
130
|
|
|
125
131
|
## API
|
|
126
132
|
|
|
@@ -130,8 +136,9 @@ Creates a `ServiceRouter` instance.
|
|
|
130
136
|
|
|
131
137
|
| Option | Type | Default | Description |
|
|
132
138
|
|--------|------|---------|-------------|
|
|
133
|
-
| `payeeAddress` | `string` |
|
|
139
|
+
| `payeeAddress` | `string` | — | Wallet address to receive payments |
|
|
134
140
|
| `baseUrl` | `string` | **required** | Service origin used for discovery/OpenAPI/realm |
|
|
141
|
+
| `discovery` | `DiscoveryConfig` | **required** | Title, version, description, guidance for OpenAPI/well-known/llms.txt |
|
|
135
142
|
| `network` | `string` | `'eip155:8453'` | Blockchain network |
|
|
136
143
|
| `plugin` | `RouterPlugin` | `undefined` | Observability plugin |
|
|
137
144
|
| `prices` | `Record<string, string>` | `undefined` | Central pricing map (auto-applied) |
|
package/dist/index.cjs
CHANGED
|
@@ -1108,7 +1108,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1108
1108
|
}
|
|
1109
1109
|
const response = new import_server2.NextResponse(JSON.stringify(paymentRequired), {
|
|
1110
1110
|
status: 402,
|
|
1111
|
-
headers: { "Content-Type": "application/json" }
|
|
1111
|
+
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
1112
1112
|
});
|
|
1113
1113
|
if (encoded) response.headers.set("PAYMENT-REQUIRED", encoded);
|
|
1114
1114
|
firePluginResponse(deps, pluginCtx, meta, response);
|
|
@@ -1269,6 +1269,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1269
1269
|
}
|
|
1270
1270
|
}
|
|
1271
1271
|
response.headers.set("PAYMENT-RESPONSE", settle.encoded);
|
|
1272
|
+
response.headers.set("Cache-Control", "private");
|
|
1272
1273
|
firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
|
|
1273
1274
|
protocol: "x402",
|
|
1274
1275
|
payer: verify.payer,
|
|
@@ -1359,6 +1360,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1359
1360
|
}
|
|
1360
1361
|
}
|
|
1361
1362
|
const receiptResponse = mppResult.withReceipt(response);
|
|
1363
|
+
receiptResponse.headers.set("Cache-Control", "private");
|
|
1362
1364
|
finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
|
|
1363
1365
|
return receiptResponse;
|
|
1364
1366
|
}
|
|
@@ -1447,7 +1449,8 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
|
|
|
1447
1449
|
const response = new import_server2.NextResponse(null, {
|
|
1448
1450
|
status: 402,
|
|
1449
1451
|
headers: {
|
|
1450
|
-
"Content-Type": "application/json"
|
|
1452
|
+
"Content-Type": "application/json",
|
|
1453
|
+
"Cache-Control": "no-store"
|
|
1451
1454
|
}
|
|
1452
1455
|
});
|
|
1453
1456
|
let challengePrice;
|
|
@@ -2233,16 +2236,23 @@ function createRouter(config) {
|
|
|
2233
2236
|
try {
|
|
2234
2237
|
const { Mppx, tempo } = await import("mppx/server");
|
|
2235
2238
|
const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL;
|
|
2239
|
+
const getClient = async () => {
|
|
2240
|
+
const { createClient, http } = await import("viem");
|
|
2241
|
+
const { tempo: tempoChain } = await import("viem/chains");
|
|
2242
|
+
return createClient({ chain: tempoChain, transport: http(rpcUrl) });
|
|
2243
|
+
};
|
|
2244
|
+
let feePayerAccount;
|
|
2245
|
+
if (config.mpp.feePayerKey) {
|
|
2246
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
2247
|
+
feePayerAccount = privateKeyToAccount(config.mpp.feePayerKey);
|
|
2248
|
+
}
|
|
2236
2249
|
deps.mppx = Mppx.create({
|
|
2237
2250
|
methods: [
|
|
2238
2251
|
tempo.charge({
|
|
2239
2252
|
currency: config.mpp.currency,
|
|
2240
2253
|
recipient: config.mpp.recipient ?? config.payeeAddress,
|
|
2241
|
-
getClient
|
|
2242
|
-
|
|
2243
|
-
const { tempo: tempoChain } = await import("viem/chains");
|
|
2244
|
-
return createClient({ chain: tempoChain, transport: http(rpcUrl) });
|
|
2245
|
-
}
|
|
2254
|
+
getClient,
|
|
2255
|
+
...feePayerAccount ? { feePayer: feePayerAccount } : {}
|
|
2246
2256
|
})
|
|
2247
2257
|
],
|
|
2248
2258
|
secretKey: config.mpp.secretKey,
|
package/dist/index.d.cts
CHANGED
|
@@ -347,6 +347,12 @@ interface RouterConfig {
|
|
|
347
347
|
recipient?: string;
|
|
348
348
|
/** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL env var. */
|
|
349
349
|
rpcUrl?: string;
|
|
350
|
+
/**
|
|
351
|
+
* Private key of the account that sponsors transaction fees.
|
|
352
|
+
* When set, clients don't need gas tokens — the server pays fees on their behalf.
|
|
353
|
+
* Must be a hex-encoded private key (e.g. `0xabc123...`).
|
|
354
|
+
*/
|
|
355
|
+
feePayerKey?: string;
|
|
350
356
|
};
|
|
351
357
|
/**
|
|
352
358
|
* Payment protocols to accept on auto-priced routes (those using the `prices` config).
|
package/dist/index.d.ts
CHANGED
|
@@ -347,6 +347,12 @@ interface RouterConfig {
|
|
|
347
347
|
recipient?: string;
|
|
348
348
|
/** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL env var. */
|
|
349
349
|
rpcUrl?: string;
|
|
350
|
+
/**
|
|
351
|
+
* Private key of the account that sponsors transaction fees.
|
|
352
|
+
* When set, clients don't need gas tokens — the server pays fees on their behalf.
|
|
353
|
+
* Must be a hex-encoded private key (e.g. `0xabc123...`).
|
|
354
|
+
*/
|
|
355
|
+
feePayerKey?: string;
|
|
350
356
|
};
|
|
351
357
|
/**
|
|
352
358
|
* Payment protocols to accept on auto-priced routes (those using the `prices` config).
|
package/dist/index.js
CHANGED
|
@@ -1069,7 +1069,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1069
1069
|
}
|
|
1070
1070
|
const response = new NextResponse2(JSON.stringify(paymentRequired), {
|
|
1071
1071
|
status: 402,
|
|
1072
|
-
headers: { "Content-Type": "application/json" }
|
|
1072
|
+
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
1073
1073
|
});
|
|
1074
1074
|
if (encoded) response.headers.set("PAYMENT-REQUIRED", encoded);
|
|
1075
1075
|
firePluginResponse(deps, pluginCtx, meta, response);
|
|
@@ -1230,6 +1230,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1230
1230
|
}
|
|
1231
1231
|
}
|
|
1232
1232
|
response.headers.set("PAYMENT-RESPONSE", settle.encoded);
|
|
1233
|
+
response.headers.set("Cache-Control", "private");
|
|
1233
1234
|
firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
|
|
1234
1235
|
protocol: "x402",
|
|
1235
1236
|
payer: verify.payer,
|
|
@@ -1320,6 +1321,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
1320
1321
|
}
|
|
1321
1322
|
}
|
|
1322
1323
|
const receiptResponse = mppResult.withReceipt(response);
|
|
1324
|
+
receiptResponse.headers.set("Cache-Control", "private");
|
|
1323
1325
|
finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
|
|
1324
1326
|
return receiptResponse;
|
|
1325
1327
|
}
|
|
@@ -1408,7 +1410,8 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
|
|
|
1408
1410
|
const response = new NextResponse2(null, {
|
|
1409
1411
|
status: 402,
|
|
1410
1412
|
headers: {
|
|
1411
|
-
"Content-Type": "application/json"
|
|
1413
|
+
"Content-Type": "application/json",
|
|
1414
|
+
"Cache-Control": "no-store"
|
|
1412
1415
|
}
|
|
1413
1416
|
});
|
|
1414
1417
|
let challengePrice;
|
|
@@ -2194,16 +2197,23 @@ function createRouter(config) {
|
|
|
2194
2197
|
try {
|
|
2195
2198
|
const { Mppx, tempo } = await import("mppx/server");
|
|
2196
2199
|
const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL;
|
|
2200
|
+
const getClient = async () => {
|
|
2201
|
+
const { createClient, http } = await import("viem");
|
|
2202
|
+
const { tempo: tempoChain } = await import("viem/chains");
|
|
2203
|
+
return createClient({ chain: tempoChain, transport: http(rpcUrl) });
|
|
2204
|
+
};
|
|
2205
|
+
let feePayerAccount;
|
|
2206
|
+
if (config.mpp.feePayerKey) {
|
|
2207
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
2208
|
+
feePayerAccount = privateKeyToAccount(config.mpp.feePayerKey);
|
|
2209
|
+
}
|
|
2197
2210
|
deps.mppx = Mppx.create({
|
|
2198
2211
|
methods: [
|
|
2199
2212
|
tempo.charge({
|
|
2200
2213
|
currency: config.mpp.currency,
|
|
2201
2214
|
recipient: config.mpp.recipient ?? config.payeeAddress,
|
|
2202
|
-
getClient
|
|
2203
|
-
|
|
2204
|
-
const { tempo: tempoChain } = await import("viem/chains");
|
|
2205
|
-
return createClient({ chain: tempoChain, transport: http(rpcUrl) });
|
|
2206
|
-
}
|
|
2215
|
+
getClient,
|
|
2216
|
+
...feePayerAccount ? { feePayer: feePayerAccount } : {}
|
|
2207
2217
|
})
|
|
2208
2218
|
],
|
|
2209
2219
|
secretKey: config.mpp.secretKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/router",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Unified route builder for Next.js App Router APIs with x402, MPP, SIWX, and API key auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@x402/evm": "^2.3.0",
|
|
30
30
|
"@x402/extensions": "^2.3.0",
|
|
31
31
|
"@x402/svm": "2.3.0",
|
|
32
|
-
"mppx": "^0.
|
|
32
|
+
"mppx": "^0.4.2",
|
|
33
33
|
"next": ">=15.0.0",
|
|
34
34
|
"zod": "^4.0.0",
|
|
35
35
|
"zod-openapi": "^5.0.0"
|
|
@@ -61,14 +61,14 @@
|
|
|
61
61
|
"@x402/extensions": "^2.3.0",
|
|
62
62
|
"@x402/svm": "2.3.0",
|
|
63
63
|
"eslint": "^10.0.0",
|
|
64
|
-
"mppx": "^0.
|
|
64
|
+
"mppx": "^0.4.2",
|
|
65
65
|
"next": "^15.0.0",
|
|
66
66
|
"prettier": "^3.8.1",
|
|
67
67
|
"react": "^19.0.0",
|
|
68
68
|
"tsup": "^8.0.0",
|
|
69
69
|
"typescript": "^5.8.0",
|
|
70
70
|
"typescript-eslint": "^8.55.0",
|
|
71
|
-
"viem": "^2.
|
|
71
|
+
"viem": "^2.47.2",
|
|
72
72
|
"vitest": "^3.0.0",
|
|
73
73
|
"zod": "^4.0.0",
|
|
74
74
|
"zod-openapi": "^5.0.0"
|