@agentcash/router 0.6.3 → 0.6.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/CLAUDE.md CHANGED
@@ -131,6 +131,20 @@ router
131
131
 
132
132
  ## Environment Variables
133
133
 
134
+ ### Base URL Resolution
135
+
136
+ `baseUrl` is auto-detected — most consumers don't need to pass it:
137
+
138
+ 1. **`config.baseUrl`** — Explicit override (for non-Vercel deployments)
139
+ 2. **`VERCEL_URL`** — Auto-detected on Vercel (set by the platform on every build and deployment)
140
+ 3. **`localhost:PORT`** — Fallback for local dev (`PORT` env var, defaults to 3000)
141
+
142
+ In production on a non-Vercel host, pass `baseUrl` explicitly. On Vercel, it just works. In dev, it just works. No custom env vars needed.
143
+
144
+ **Do NOT use `NEXT_PUBLIC_BASE_URL`.** It was removed in 0.6.5. The library handles base URL resolution internally.
145
+
146
+ ### CDP API Keys
147
+
134
148
  The router uses the default facilitator from `@coinbase/x402`, which requires CDP API keys in `process.env`:
135
149
 
136
150
  - `CDP_API_KEY_ID` — Coinbase Developer Platform API key ID
package/dist/index.cjs CHANGED
@@ -382,7 +382,8 @@ async function buildX402Challenge(server, routeEntry, request, price, payeeAddre
382
382
  const resource = {
383
383
  url: request.url,
384
384
  method: routeEntry.method,
385
- description: routeEntry.description
385
+ description: routeEntry.description,
386
+ mimeType: "application/json"
386
387
  };
387
388
  const requirements = await server.buildPaymentRequirementsFromOptions([options], {
388
389
  request
@@ -605,18 +606,16 @@ function createRequestHandler(routeEntry, handler, deps) {
605
606
  if (needsEarlyParse) {
606
607
  const requestForPricing = request.clone();
607
608
  const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
608
- if (!earlyBodyResult.ok) {
609
- firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
610
- return earlyBodyResult.response;
611
- }
612
- earlyBodyData = earlyBodyResult.data;
613
- if (routeEntry.validateFn) {
614
- try {
615
- await routeEntry.validateFn(earlyBodyData);
616
- } catch (err) {
617
- const status = err.status ?? 400;
618
- const message = err instanceof Error ? err.message : "Validation failed";
619
- return fail(status, message, meta, pluginCtx, earlyBodyData);
609
+ if (earlyBodyResult.ok) {
610
+ earlyBodyData = earlyBodyResult.data;
611
+ if (routeEntry.validateFn) {
612
+ try {
613
+ await routeEntry.validateFn(earlyBodyData);
614
+ } catch (err) {
615
+ const status = err.status ?? 400;
616
+ const message = err instanceof Error ? err.message : "Validation failed";
617
+ return fail(status, message, meta, pluginCtx, earlyBodyData);
618
+ }
620
619
  }
621
620
  }
622
621
  }
@@ -624,16 +623,14 @@ function createRequestHandler(routeEntry, handler, deps) {
624
623
  if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get("SIGN-IN-WITH-X")) {
625
624
  const requestForValidation = request.clone();
626
625
  const earlyBodyResult = await parseBody(requestForValidation, routeEntry);
627
- if (!earlyBodyResult.ok) {
628
- firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
629
- return earlyBodyResult.response;
630
- }
631
- try {
632
- await routeEntry.validateFn(earlyBodyResult.data);
633
- } catch (err) {
634
- const status = err.status ?? 400;
635
- const message = err instanceof Error ? err.message : "Validation failed";
636
- return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
626
+ if (earlyBodyResult.ok) {
627
+ try {
628
+ await routeEntry.validateFn(earlyBodyResult.data);
629
+ } catch (err) {
630
+ const status = err.status ?? 400;
631
+ const message = err instanceof Error ? err.message : "Validation failed";
632
+ return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
633
+ }
637
634
  }
638
635
  }
639
636
  if (!request.headers.get("SIGN-IN-WITH-X")) {
@@ -1470,20 +1467,21 @@ function createRouter(config) {
1470
1467
  const registry = new RouteRegistry();
1471
1468
  const nonceStore = config.siwx?.nonceStore ?? new MemoryNonceStore();
1472
1469
  const network = config.network ?? "eip155:8453";
1473
- const baseUrl = config.baseUrl ?? (typeof globalThis.process !== "undefined" ? process.env.NEXT_PUBLIC_BASE_URL : void 0);
1470
+ const baseUrl = config.baseUrl ?? (typeof globalThis.process !== "undefined" && process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : void 0);
1474
1471
  if (config.protocols && config.protocols.length === 0) {
1475
1472
  throw new Error(
1476
1473
  "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
1477
1474
  );
1478
1475
  }
1479
- if (!baseUrl) {
1480
- const msg = "baseUrl is required. Pass it in RouterConfig or set NEXT_PUBLIC_BASE_URL (e.g. https://myapp.com). It is used for discovery URLs, OpenAPI servers, and MPP realm.";
1481
- if (process.env.NODE_ENV === "production") {
1482
- throw new Error(msg);
1483
- }
1484
- console.warn(`[router] ${msg}`);
1476
+ if (!baseUrl && process.env.NODE_ENV === "production") {
1477
+ console.warn(
1478
+ "[router] baseUrl was not provided and VERCEL_URL is not set. Falling back to localhost. Pass baseUrl in RouterConfig for non-Vercel deployments."
1479
+ );
1485
1480
  }
1486
- const resolvedBaseUrl = (baseUrl ?? "http://localhost:3000").replace(/\/+$/, "");
1481
+ const resolvedBaseUrl = (baseUrl ?? `http://localhost:${process.env.PORT ?? 3e3}`).replace(
1482
+ /\/+$/,
1483
+ ""
1484
+ );
1487
1485
  let x402ConfigError;
1488
1486
  let mppConfigError;
1489
1487
  if ((!config.protocols || config.protocols.includes("x402")) && !config.payeeAddress) {
package/dist/index.d.cts CHANGED
@@ -235,11 +235,11 @@ interface RouteEntry {
235
235
  interface RouterConfig {
236
236
  payeeAddress: string;
237
237
  /**
238
- * Production origin URL (e.g. `https://myapp.com`).
238
+ * Origin URL (e.g. `https://myapp.com`).
239
239
  * Used for discovery URLs, OpenAPI servers, and MPP realm.
240
240
  *
241
- * Falls back to `NEXT_PUBLIC_BASE_URL` env var.
242
- * Required in production `next build` will fail without it.
241
+ * Auto-detected on Vercel via `VERCEL_URL`. Falls back to `localhost:PORT` in dev.
242
+ * Only needed for non-Vercel production deployments.
243
243
  */
244
244
  baseUrl?: string;
245
245
  network?: string;
package/dist/index.d.ts CHANGED
@@ -235,11 +235,11 @@ interface RouteEntry {
235
235
  interface RouterConfig {
236
236
  payeeAddress: string;
237
237
  /**
238
- * Production origin URL (e.g. `https://myapp.com`).
238
+ * Origin URL (e.g. `https://myapp.com`).
239
239
  * Used for discovery URLs, OpenAPI servers, and MPP realm.
240
240
  *
241
- * Falls back to `NEXT_PUBLIC_BASE_URL` env var.
242
- * Required in production `next build` will fail without it.
241
+ * Auto-detected on Vercel via `VERCEL_URL`. Falls back to `localhost:PORT` in dev.
242
+ * Only needed for non-Vercel production deployments.
243
243
  */
244
244
  baseUrl?: string;
245
245
  network?: string;
package/dist/index.js CHANGED
@@ -345,7 +345,8 @@ async function buildX402Challenge(server, routeEntry, request, price, payeeAddre
345
345
  const resource = {
346
346
  url: request.url,
347
347
  method: routeEntry.method,
348
- description: routeEntry.description
348
+ description: routeEntry.description,
349
+ mimeType: "application/json"
349
350
  };
350
351
  const requirements = await server.buildPaymentRequirementsFromOptions([options], {
351
352
  request
@@ -568,18 +569,16 @@ function createRequestHandler(routeEntry, handler, deps) {
568
569
  if (needsEarlyParse) {
569
570
  const requestForPricing = request.clone();
570
571
  const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
571
- if (!earlyBodyResult.ok) {
572
- firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
573
- return earlyBodyResult.response;
574
- }
575
- earlyBodyData = earlyBodyResult.data;
576
- if (routeEntry.validateFn) {
577
- try {
578
- await routeEntry.validateFn(earlyBodyData);
579
- } catch (err) {
580
- const status = err.status ?? 400;
581
- const message = err instanceof Error ? err.message : "Validation failed";
582
- return fail(status, message, meta, pluginCtx, earlyBodyData);
572
+ if (earlyBodyResult.ok) {
573
+ earlyBodyData = earlyBodyResult.data;
574
+ if (routeEntry.validateFn) {
575
+ try {
576
+ await routeEntry.validateFn(earlyBodyData);
577
+ } catch (err) {
578
+ const status = err.status ?? 400;
579
+ const message = err instanceof Error ? err.message : "Validation failed";
580
+ return fail(status, message, meta, pluginCtx, earlyBodyData);
581
+ }
583
582
  }
584
583
  }
585
584
  }
@@ -587,16 +586,14 @@ function createRequestHandler(routeEntry, handler, deps) {
587
586
  if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get("SIGN-IN-WITH-X")) {
588
587
  const requestForValidation = request.clone();
589
588
  const earlyBodyResult = await parseBody(requestForValidation, routeEntry);
590
- if (!earlyBodyResult.ok) {
591
- firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
592
- return earlyBodyResult.response;
593
- }
594
- try {
595
- await routeEntry.validateFn(earlyBodyResult.data);
596
- } catch (err) {
597
- const status = err.status ?? 400;
598
- const message = err instanceof Error ? err.message : "Validation failed";
599
- return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
589
+ if (earlyBodyResult.ok) {
590
+ try {
591
+ await routeEntry.validateFn(earlyBodyResult.data);
592
+ } catch (err) {
593
+ const status = err.status ?? 400;
594
+ const message = err instanceof Error ? err.message : "Validation failed";
595
+ return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
596
+ }
600
597
  }
601
598
  }
602
599
  if (!request.headers.get("SIGN-IN-WITH-X")) {
@@ -1433,20 +1430,21 @@ function createRouter(config) {
1433
1430
  const registry = new RouteRegistry();
1434
1431
  const nonceStore = config.siwx?.nonceStore ?? new MemoryNonceStore();
1435
1432
  const network = config.network ?? "eip155:8453";
1436
- const baseUrl = config.baseUrl ?? (typeof globalThis.process !== "undefined" ? process.env.NEXT_PUBLIC_BASE_URL : void 0);
1433
+ const baseUrl = config.baseUrl ?? (typeof globalThis.process !== "undefined" && process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : void 0);
1437
1434
  if (config.protocols && config.protocols.length === 0) {
1438
1435
  throw new Error(
1439
1436
  "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
1440
1437
  );
1441
1438
  }
1442
- if (!baseUrl) {
1443
- const msg = "baseUrl is required. Pass it in RouterConfig or set NEXT_PUBLIC_BASE_URL (e.g. https://myapp.com). It is used for discovery URLs, OpenAPI servers, and MPP realm.";
1444
- if (process.env.NODE_ENV === "production") {
1445
- throw new Error(msg);
1446
- }
1447
- console.warn(`[router] ${msg}`);
1439
+ if (!baseUrl && process.env.NODE_ENV === "production") {
1440
+ console.warn(
1441
+ "[router] baseUrl was not provided and VERCEL_URL is not set. Falling back to localhost. Pass baseUrl in RouterConfig for non-Vercel deployments."
1442
+ );
1448
1443
  }
1449
- const resolvedBaseUrl = (baseUrl ?? "http://localhost:3000").replace(/\/+$/, "");
1444
+ const resolvedBaseUrl = (baseUrl ?? `http://localhost:${process.env.PORT ?? 3e3}`).replace(
1445
+ /\/+$/,
1446
+ ""
1447
+ );
1450
1448
  let x402ConfigError;
1451
1449
  let mppConfigError;
1452
1450
  if ((!config.protocols || config.protocols.includes("x402")) && !config.payeeAddress) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "0.6.3",
3
+ "version": "0.6.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": {