@agentcash/router 1.1.4 → 1.1.6

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.
@@ -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({ title: 'My API', version: '1.0.0' });
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({ methodHints: 'non-default' });
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
- title: 'My API',
113
- version: '1.0.0',
114
- llmsTxtUrl: 'https://my-api.dev/llms.txt',
115
- ownershipProofs: ['did:example:proof'],
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` (`llmsTxtUrl`, `ownershipProofs`)
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` | **required** | Wallet address to receive payments |
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: async () => {
2242
- const { createClient, http } = await import("viem");
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,
@@ -2274,12 +2284,14 @@ function createRouter(config) {
2274
2284
  }
2275
2285
  let builder = new RouteBuilder(key, registry, deps);
2276
2286
  builder = builder.path(normalizedPath);
2287
+ if (config.protocols) {
2288
+ builder._protocols = [...config.protocols];
2289
+ }
2277
2290
  if (definition.method) {
2278
2291
  builder = builder.method(definition.method);
2279
2292
  }
2280
2293
  if (config.prices && key in config.prices) {
2281
- const options = config.protocols ? { protocols: config.protocols } : void 0;
2282
- return builder.paid(config.prices[key], options);
2294
+ return builder.paid(config.prices[key]);
2283
2295
  }
2284
2296
  return builder;
2285
2297
  },
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: async () => {
2203
- const { createClient, http } = await import("viem");
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,
@@ -2235,12 +2245,14 @@ function createRouter(config) {
2235
2245
  }
2236
2246
  let builder = new RouteBuilder(key, registry, deps);
2237
2247
  builder = builder.path(normalizedPath);
2248
+ if (config.protocols) {
2249
+ builder._protocols = [...config.protocols];
2250
+ }
2238
2251
  if (definition.method) {
2239
2252
  builder = builder.method(definition.method);
2240
2253
  }
2241
2254
  if (config.prices && key in config.prices) {
2242
- const options = config.protocols ? { protocols: config.protocols } : void 0;
2243
- return builder.paid(config.prices[key], options);
2255
+ return builder.paid(config.prices[key]);
2244
2256
  }
2245
2257
  return builder;
2246
2258
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
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.3.2",
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,7 +61,7 @@
61
61
  "@x402/extensions": "^2.3.0",
62
62
  "@x402/svm": "2.3.0",
63
63
  "eslint": "^10.0.0",
64
- "mppx": "^0.4.1",
64
+ "mppx": "^0.4.2",
65
65
  "next": "^15.0.0",
66
66
  "prettier": "^3.8.1",
67
67
  "react": "^19.0.0",