@agentcash/router 1.8.0 → 1.9.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/dist/index.cjs CHANGED
@@ -604,7 +604,8 @@ function preflight(routeEntry, handler, deps, request) {
604
604
  request,
605
605
  meta,
606
606
  pluginCtx,
607
- report: createReporter(deps.plugin, pluginCtx, routeEntry.key)
607
+ report: createReporter(deps.plugin, pluginCtx, routeEntry.key),
608
+ query: void 0
608
609
  };
609
610
  }
610
611
  function buildMeta(request, routeEntry) {
@@ -627,13 +628,19 @@ function buildMeta(request, routeEntry) {
627
628
  var import_server = require("next/server");
628
629
 
629
630
  // src/pipeline/body.ts
631
+ var MalformedJsonError = class extends Error {
632
+ constructor() {
633
+ super("Invalid JSON");
634
+ this.name = "MalformedJsonError";
635
+ }
636
+ };
630
637
  async function bufferBody(request) {
631
638
  const text = await request.text();
632
- if (!text) return void 0;
639
+ if (!text.trim()) return void 0;
633
640
  try {
634
641
  return JSON.parse(text);
635
642
  } catch {
636
- return void 0;
643
+ throw new MalformedJsonError();
637
644
  }
638
645
  }
639
646
  function validateBody(parsed, schema) {
@@ -714,7 +721,18 @@ function computeQuotaLevel(remaining, warn, critical) {
714
721
  // src/pipeline/steps/parse-body.ts
715
722
  async function parseBody(ctx, request = ctx.request) {
716
723
  if (!ctx.routeEntry.bodySchema) return { ok: true, data: void 0 };
717
- const raw = await bufferBody(request);
724
+ let raw;
725
+ try {
726
+ raw = await bufferBody(request);
727
+ } catch (err) {
728
+ if (!(err instanceof MalformedJsonError)) throw err;
729
+ const response2 = import_server.NextResponse.json(
730
+ { success: false, error: "Invalid JSON", issues: [] },
731
+ { status: 400 }
732
+ );
733
+ firePluginResponse(ctx, response2);
734
+ return { ok: false, response: response2 };
735
+ }
718
736
  const result = validateBody(raw, ctx.routeEntry.bodySchema);
719
737
  if (result.success) return { ok: true, data: result.data };
720
738
  const response = import_server.NextResponse.json(
@@ -725,6 +743,22 @@ async function parseBody(ctx, request = ctx.request) {
725
743
  return { ok: false, response };
726
744
  }
727
745
 
746
+ // src/pipeline/steps/parse-query.ts
747
+ var import_server2 = require("next/server");
748
+ function validateQuery(ctx) {
749
+ const { querySchema } = ctx.routeEntry;
750
+ if (!querySchema) return { ok: true, data: void 0 };
751
+ const params = Object.fromEntries(ctx.request.nextUrl.searchParams.entries());
752
+ const result = validateBody(params, querySchema);
753
+ if (result.success) return { ok: true, data: result.data };
754
+ const response = import_server2.NextResponse.json(
755
+ { success: false, error: result.error, issues: result.issues },
756
+ { status: 400 }
757
+ );
758
+ firePluginResponse(ctx, response);
759
+ return { ok: false, response };
760
+ }
761
+
728
762
  // src/pipeline/steps/errors.ts
729
763
  function errorStatus(error, fallback) {
730
764
  const status = error?.status;
@@ -739,9 +773,9 @@ function handlerFailureError(response) {
739
773
  }
740
774
 
741
775
  // src/pipeline/steps/fail.ts
742
- var import_server2 = require("next/server");
776
+ var import_server3 = require("next/server");
743
777
  function fail(ctx, status, message, requestBody) {
744
- const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
778
+ const response = import_server3.NextResponse.json({ success: false, error: message }, { status });
745
779
  firePluginResponse(ctx, response, requestBody);
746
780
  return response;
747
781
  }
@@ -758,7 +792,7 @@ async function runValidate(ctx, body) {
758
792
  }
759
793
 
760
794
  // src/pipeline/flows/static/static-invoke.ts
761
- var import_server3 = require("next/server");
795
+ var import_server4 = require("next/server");
762
796
 
763
797
  // src/types.ts
764
798
  var HttpError = class extends Error {
@@ -769,14 +803,6 @@ var HttpError = class extends Error {
769
803
  }
770
804
  };
771
805
 
772
- // src/pipeline/steps/parse-query.ts
773
- function parseQuery(request, routeEntry) {
774
- if (!routeEntry.querySchema) return void 0;
775
- const params = Object.fromEntries(request.nextUrl.searchParams.entries());
776
- const result = routeEntry.querySchema.safeParse(params);
777
- return result.success ? result.data : params;
778
- }
779
-
780
806
  // src/pipeline/flows/static/static-invoke.ts
781
807
  function invokePaidStatic(ctx, wallet, account, body, payment) {
782
808
  return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
@@ -787,7 +813,7 @@ function invokeUnauthed(ctx, wallet, account, body) {
787
813
  function buildHandlerCtx(ctx, wallet, account, body, payment) {
788
814
  return {
789
815
  body,
790
- query: parseQuery(ctx.request, ctx.routeEntry),
816
+ query: ctx.query,
791
817
  request: ctx.request,
792
818
  requestId: ctx.meta.requestId,
793
819
  route: ctx.routeEntry.key,
@@ -816,14 +842,14 @@ async function runHandler(ctx, handlerCtx) {
816
842
  } catch (error) {
817
843
  return errorResult(error);
818
844
  }
819
- const response = rawResult instanceof Response ? rawResult : import_server3.NextResponse.json(rawResult);
845
+ const response = rawResult instanceof Response ? rawResult : import_server4.NextResponse.json(rawResult);
820
846
  return { response, rawResult };
821
847
  }
822
848
  function errorResult(error) {
823
849
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
824
850
  const message = error instanceof Error ? error.message : "Internal error";
825
851
  return {
826
- response: import_server3.NextResponse.json({ success: false, error: message }, { status }),
852
+ response: import_server4.NextResponse.json({ success: false, error: message }, { status }),
827
853
  rawResult: void 0,
828
854
  handlerError: error
829
855
  };
@@ -1236,9 +1262,10 @@ var DynamicPricing = class {
1236
1262
  }
1237
1263
  needsBody = true;
1238
1264
  async quote(body) {
1265
+ let priced;
1239
1266
  try {
1240
1267
  const raw = await this.opts.fn(body);
1241
- return this.cap(raw, body);
1268
+ priced = this.cap(raw, body);
1242
1269
  } catch (err) {
1243
1270
  if (err instanceof HttpError) throw err;
1244
1271
  this.alert("error", `Pricing function failed: ${msg(err)}`, {
@@ -1251,6 +1278,13 @@ var DynamicPricing = class {
1251
1278
  }
1252
1279
  throw err;
1253
1280
  }
1281
+ if (!isPositiveDecimal(priced)) {
1282
+ throw new HttpError(
1283
+ `route '${this.opts.route ?? "unknown"}': dynamic pricing returned an invalid amount '${priced}'`,
1284
+ 500
1285
+ );
1286
+ }
1287
+ return priced;
1254
1288
  }
1255
1289
  challengeQuote(body) {
1256
1290
  if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
@@ -1325,14 +1359,14 @@ var TieredPricing = class {
1325
1359
  `Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
1326
1360
  );
1327
1361
  }
1328
- challengeQuote(body) {
1362
+ async challengeQuote(body) {
1329
1363
  if (body !== void 0) {
1330
1364
  try {
1331
- return this.quote(body);
1365
+ return await this.quote(body);
1332
1366
  } catch {
1333
1367
  }
1334
1368
  }
1335
- return Promise.resolve(this.maxTierPrice());
1369
+ return this.maxTierPrice();
1336
1370
  }
1337
1371
  describe() {
1338
1372
  return {
@@ -2066,7 +2100,7 @@ async function settleX402Payment(server, payload, requirements, amountOverride)
2066
2100
  };
2067
2101
  }
2068
2102
  function tagBareDecimalAsDollars(amount) {
2069
- if (/^\d+\.\d+$/.test(amount)) return `$${amount}`;
2103
+ if (/^\d+(?:\.\d+)?$/.test(amount)) return `$${amount}`;
2070
2104
  return amount;
2071
2105
  }
2072
2106
 
@@ -2313,8 +2347,8 @@ function getAllowedStrategies(allowed) {
2313
2347
  return allowed.map((name) => STRATEGIES[name]);
2314
2348
  }
2315
2349
 
2316
- // src/pipeline/flows/build402.ts
2317
- var import_server4 = require("next/server");
2350
+ // src/pipeline/flows/challenge-response.ts
2351
+ var import_server5 = require("next/server");
2318
2352
 
2319
2353
  // src/pipeline/challenge-extensions.ts
2320
2354
  init_evm();
@@ -2330,20 +2364,17 @@ async function buildChallengeExtensions(ctx) {
2330
2364
  });
2331
2365
  const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
2332
2366
  const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
2333
- if (inputSchema) {
2334
- const config = {
2335
- method: routeEntry.method,
2336
- bodyType: routeEntry.bodySchema ? "json" : void 0,
2337
- inputSchema
2338
- };
2339
- if (routeEntry.inputExample !== void 0) {
2340
- config.input = routeEntry.inputExample;
2341
- }
2342
- if (outputSchema && routeEntry.outputExample !== void 0) {
2343
- config.output = { schema: outputSchema, example: routeEntry.outputExample };
2344
- }
2345
- extensions = declareDiscoveryExtension(config);
2367
+ const isBodyMethod = routeEntry.method === "POST" || routeEntry.method === "PUT" || routeEntry.method === "PATCH";
2368
+ const config = { method: routeEntry.method };
2369
+ if (isBodyMethod) config.bodyType = "json";
2370
+ if (inputSchema) config.inputSchema = inputSchema;
2371
+ if (routeEntry.inputExample !== void 0) {
2372
+ config.input = routeEntry.inputExample;
2373
+ }
2374
+ if (outputSchema && routeEntry.outputExample !== void 0) {
2375
+ config.output = { schema: outputSchema, example: routeEntry.outputExample };
2346
2376
  }
2377
+ extensions = declareDiscoveryExtension(config);
2347
2378
  } catch (err) {
2348
2379
  ctx.report(
2349
2380
  "warn",
@@ -2380,14 +2411,14 @@ async function buildChallengeExtensions(ctx) {
2380
2411
  return extensions;
2381
2412
  }
2382
2413
 
2383
- // src/pipeline/flows/build402.ts
2384
- async function build402(ctx, pricing, body, failure) {
2414
+ // src/pipeline/flows/challenge-response.ts
2415
+ async function buildChallengeResponse(ctx, pricing, body, failure) {
2385
2416
  let challengePrice;
2386
2417
  try {
2387
2418
  challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
2388
2419
  } catch (err) {
2389
2420
  const message = errorMessage(err, "Price calculation failed");
2390
- const errorResponse = import_server4.NextResponse.json(
2421
+ const errorResponse = import_server5.NextResponse.json(
2391
2422
  { success: false, error: message },
2392
2423
  { status: errorStatus(err, 500) }
2393
2424
  );
@@ -2396,7 +2427,7 @@ async function build402(ctx, pricing, body, failure) {
2396
2427
  }
2397
2428
  const extensions = await buildChallengeExtensions(ctx);
2398
2429
  const responseBody = failure ? JSON.stringify({ error: failure.message ?? null, reason: failure.reason }) : null;
2399
- const response = new import_server4.NextResponse(responseBody, {
2430
+ const response = new import_server5.NextResponse(responseBody, {
2400
2431
  status: 402,
2401
2432
  headers: {
2402
2433
  "Content-Type": "application/json",
@@ -2423,7 +2454,7 @@ async function build402(ctx, pricing, body, failure) {
2423
2454
  const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
2424
2455
  ctx.report("critical", message);
2425
2456
  if (strategy.protocol === "x402") {
2426
- const errorResponse = import_server4.NextResponse.json(
2457
+ const errorResponse = import_server5.NextResponse.json(
2427
2458
  { success: false, error: message },
2428
2459
  { status: 500 }
2429
2460
  );
@@ -2475,7 +2506,7 @@ function surrogatePriceForSkippedBody(routeEntry) {
2475
2506
  }
2476
2507
 
2477
2508
  // src/pipeline/flows/dynamic/dynamic-channel-mgmt.ts
2478
- var import_server5 = require("next/server");
2509
+ var import_server6 = require("next/server");
2479
2510
  async function runDynamicChannelMgmtFlow(args) {
2480
2511
  const { ctx, strategy, account, pricing, skipBody } = args;
2481
2512
  const { request, routeEntry, deps, report } = ctx;
@@ -2494,7 +2525,7 @@ async function runDynamicChannelMgmtFlow(args) {
2494
2525
  if (verifyOutcome.kind === "config") {
2495
2526
  return fail(ctx, 500, verifyOutcome.message, parsedBody);
2496
2527
  }
2497
- return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
2528
+ return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
2498
2529
  }
2499
2530
  ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
2500
2531
  firePaymentVerified(ctx, {
@@ -2503,7 +2534,7 @@ async function runDynamicChannelMgmtFlow(args) {
2503
2534
  amount: price,
2504
2535
  network: verifyOutcome.payment.network
2505
2536
  });
2506
- const synthetic = new import_server5.NextResponse(null, { status: 200 });
2537
+ const synthetic = new import_server6.NextResponse(null, { status: 200 });
2507
2538
  const settleScope = {
2508
2539
  wallet: verifyOutcome.wallet,
2509
2540
  account,
@@ -2565,11 +2596,11 @@ function createChargeContext(args) {
2565
2596
  }
2566
2597
 
2567
2598
  // src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
2568
- var import_server6 = require("next/server");
2599
+ var import_server7 = require("next/server");
2569
2600
  function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
2570
2601
  return {
2571
2602
  body,
2572
- query: parseQuery(ctx.request, ctx.routeEntry),
2603
+ query: ctx.query,
2573
2604
  request: ctx.request,
2574
2605
  requestId: ctx.meta.requestId,
2575
2606
  route: ctx.routeEntry.key,
@@ -2581,14 +2612,14 @@ function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
2581
2612
  };
2582
2613
  }
2583
2614
  function toResponse(rawResult) {
2584
- return rawResult instanceof Response ? rawResult : import_server6.NextResponse.json(rawResult);
2615
+ return rawResult instanceof Response ? rawResult : import_server7.NextResponse.json(rawResult);
2585
2616
  }
2586
2617
  function errorResult2(error) {
2587
2618
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
2588
2619
  const message = error instanceof Error ? error.message : "Internal error";
2589
2620
  return {
2590
2621
  kind: "request",
2591
- response: import_server6.NextResponse.json({ success: false, error: message }, { status }),
2622
+ response: import_server7.NextResponse.json({ success: false, error: message }, { status }),
2592
2623
  rawResult: void 0,
2593
2624
  handlerError: error
2594
2625
  };
@@ -2791,7 +2822,7 @@ async function runDynamicPaidFlow(ctx) {
2791
2822
  if (!incomingStrategy) {
2792
2823
  const initError = protocolInitError(routeEntry, deps);
2793
2824
  if (initError) return fail(ctx, 500, initError);
2794
- return build402(ctx, pricing, earlyBody);
2825
+ return buildChallengeResponse(ctx, pricing, earlyBody);
2795
2826
  }
2796
2827
  const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
2797
2828
  if (skipHandler) {
@@ -2818,7 +2849,7 @@ async function runDynamicPaidFlow(ctx) {
2818
2849
  if (verifyOutcome.kind === "config") {
2819
2850
  return fail(ctx, 500, verifyOutcome.message, parsedBody);
2820
2851
  }
2821
- return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
2852
+ return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
2822
2853
  }
2823
2854
  ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
2824
2855
  firePaymentVerified(ctx, {
@@ -2962,7 +2993,7 @@ async function runStaticPaidFlow(ctx) {
2962
2993
  if (!incomingStrategy) {
2963
2994
  const initError = protocolInitError(routeEntry, deps);
2964
2995
  if (initError) return fail(ctx, 500, initError);
2965
- return build402(ctx, pricing, earlyBody);
2996
+ return buildChallengeResponse(ctx, pricing, earlyBody);
2966
2997
  }
2967
2998
  const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
2968
2999
  if (!bodyAndPrice.ok) return bodyAndPrice.response;
@@ -2979,7 +3010,7 @@ async function runStaticPaidFlow(ctx) {
2979
3010
  if (verifyOutcome.kind === "config") {
2980
3011
  return fail(ctx, 500, verifyOutcome.message, parsedBody);
2981
3012
  }
2982
- return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
3013
+ return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
2983
3014
  }
2984
3015
  ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
2985
3016
  firePaymentVerified(ctx, {
@@ -3013,7 +3044,7 @@ async function runPaidFlow(ctx) {
3013
3044
  }
3014
3045
 
3015
3046
  // src/pipeline/flows/siwx-only.ts
3016
- var import_server7 = require("next/server");
3047
+ var import_server8 = require("next/server");
3017
3048
 
3018
3049
  // src/kv-store/client.ts
3019
3050
  var BIGINT_SUFFIX = "#__bigint";
@@ -3247,7 +3278,7 @@ async function runSiwxOnlyFlow(ctx) {
3247
3278
  }
3248
3279
  const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
3249
3280
  if (!siwx.valid) {
3250
- const response = import_server7.NextResponse.json(
3281
+ const response = import_server8.NextResponse.json(
3251
3282
  { error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
3252
3283
  { status: 402 }
3253
3284
  );
@@ -3308,7 +3339,7 @@ async function buildSiwxChallenge(ctx) {
3308
3339
  `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`
3309
3340
  );
3310
3341
  }
3311
- const response = new import_server7.NextResponse(JSON.stringify(paymentRequired), {
3342
+ const response = new import_server8.NextResponse(JSON.stringify(paymentRequired), {
3312
3343
  status: 402,
3313
3344
  headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
3314
3345
  });
@@ -3354,6 +3385,9 @@ function createRequestHandler(routeEntry, handler, deps) {
3354
3385
  return async (request) => {
3355
3386
  await deps.initPromise;
3356
3387
  const ctx = preflight(routeEntry, handler, deps, request);
3388
+ const query = validateQuery(ctx);
3389
+ if (!query.ok) return query.response;
3390
+ ctx.query = query.data;
3357
3391
  if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
3358
3392
  if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
3359
3393
  if (routeEntry.pricing) return runPaidFlow(ctx);
@@ -3527,11 +3561,21 @@ var RouteBuilder = class _RouteBuilder {
3527
3561
  }
3528
3562
  }
3529
3563
  }
3564
+ if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
3565
+ throw new Error(
3566
+ `route '${this.#s.key}': price '${pricing}' must be a positive decimal string`
3567
+ );
3568
+ }
3530
3569
  if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
3531
3570
  throw new Error(
3532
3571
  `route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
3533
3572
  );
3534
3573
  }
3574
+ if (next.#s.minPrice !== void 0 && !isPositiveDecimal(next.#s.minPrice)) {
3575
+ throw new Error(
3576
+ `route '${this.#s.key}': minPrice '${next.#s.minPrice}' must be a positive decimal string`
3577
+ );
3578
+ }
3535
3579
  if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
3536
3580
  throw new Error(
3537
3581
  `route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
@@ -3979,7 +4023,7 @@ function normalizeMeteredArg(routeKey, options) {
3979
4023
  }
3980
4024
 
3981
4025
  // src/discovery/well-known.ts
3982
- var import_server8 = require("next/server");
4026
+ var import_server9 = require("next/server");
3983
4027
 
3984
4028
  // src/discovery/utils/guidance.ts
3985
4029
  async function resolveGuidance(discovery) {
@@ -4023,7 +4067,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
4023
4067
  if (instructions) {
4024
4068
  body.instructions = instructions;
4025
4069
  }
4026
- return import_server8.NextResponse.json(body, {
4070
+ return import_server9.NextResponse.json(body, {
4027
4071
  headers: {
4028
4072
  "Access-Control-Allow-Origin": "*",
4029
4073
  "Access-Control-Allow-Methods": "GET",
@@ -4040,14 +4084,14 @@ function toDiscoveryResource(method, url, mode) {
4040
4084
  }
4041
4085
 
4042
4086
  // src/discovery/openapi.ts
4043
- var import_server9 = require("next/server");
4087
+ var import_server10 = require("next/server");
4044
4088
  init_constants();
4045
4089
  function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
4046
4090
  const normalizedBase = baseUrl.replace(/\/+$/, "");
4047
4091
  let cached = null;
4048
4092
  let validated = false;
4049
4093
  return async (_request) => {
4050
- if (cached) return import_server9.NextResponse.json(cached);
4094
+ if (cached) return import_server10.NextResponse.json(cached);
4051
4095
  if (!validated && pricesKeys) {
4052
4096
  registry.validate(pricesKeys);
4053
4097
  validated = true;
@@ -4110,7 +4154,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
4110
4154
  paths
4111
4155
  };
4112
4156
  cached = createDocument(openApiDocument);
4113
- return import_server9.NextResponse.json(cached);
4157
+ return import_server10.NextResponse.json(cached);
4114
4158
  };
4115
4159
  }
4116
4160
  function deriveTag(routeKey) {
@@ -4246,11 +4290,11 @@ function tierExtrema(prices) {
4246
4290
  }
4247
4291
 
4248
4292
  // src/discovery/llms-txt.ts
4249
- var import_server10 = require("next/server");
4293
+ var import_server11 = require("next/server");
4250
4294
  function createLlmsTxtHandler(discovery) {
4251
4295
  return async (_request) => {
4252
4296
  const guidance = await resolveGuidance(discovery) ?? "";
4253
- return new import_server10.NextResponse(guidance, {
4297
+ return new import_server11.NextResponse(guidance, {
4254
4298
  headers: {
4255
4299
  "Content-Type": "text/plain; charset=utf-8",
4256
4300
  "Access-Control-Allow-Origin": "*"
@@ -4300,10 +4344,14 @@ var isPlaceholderEvm = (v) => ZERO_EVM_ADDRESS_RE.test(v);
4300
4344
  var isSolanaAddress = (v) => SOLANA_ADDRESS_RE.test(v);
4301
4345
  var isX402Network = (v) => v.startsWith("eip155:") || v.startsWith("solana:");
4302
4346
  var canonicalizeEvm = (addr) => addr.toLowerCase();
4347
+ function evmAddressFromKey(key) {
4348
+ if (!key || !isEvmPrivateKey(key)) return null;
4349
+ return (0, import_accounts.privateKeyToAccount)(key).address.toLowerCase();
4350
+ }
4303
4351
  function operatorAddressesCollide(opKey, fpKey) {
4304
- if (!opKey || !fpKey || !isEvmPrivateKey(opKey) || !isEvmPrivateKey(fpKey)) return null;
4305
- const op = (0, import_accounts.privateKeyToAccount)(opKey).address.toLowerCase();
4306
- const fp = (0, import_accounts.privateKeyToAccount)(fpKey).address.toLowerCase();
4352
+ const op = evmAddressFromKey(opKey);
4353
+ const fp = evmAddressFromKey(fpKey);
4354
+ if (!op || !fp) return null;
4307
4355
  return op === fp ? op : null;
4308
4356
  }
4309
4357
  function trimAll(raw) {
@@ -4465,7 +4513,7 @@ function getConfiguredX402Accepts2(config) {
4465
4513
  }
4466
4514
  ];
4467
4515
  }
4468
- function validateX402Config(config, env, options) {
4516
+ function validateX402Config(config, env) {
4469
4517
  const accepts = getConfiguredX402Accepts2(config);
4470
4518
  const issues = [];
4471
4519
  const push = (code, message) => issues.push({ code, protocol: "x402", message });
@@ -4507,23 +4555,21 @@ function validateX402Config(config, env, options) {
4507
4555
  `x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
4508
4556
  );
4509
4557
  }
4510
- if (options.requireCdpKeys !== false) {
4511
- const hasEvm = accepts.some(
4512
- (a) => typeof a.network === "string" && a.network.startsWith("eip155:")
4513
- );
4514
- if (hasEvm) {
4515
- const missing = ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"].filter((k) => !env[k]);
4516
- if (missing.length > 0) {
4517
- push(
4518
- "missing_cdp_keys",
4519
- `x402 EVM facilitator (Coinbase) requires ${missing.join(" and ")}.`
4520
- );
4521
- }
4558
+ const hasEvm = accepts.some(
4559
+ (a) => typeof a.network === "string" && a.network.startsWith("eip155:")
4560
+ );
4561
+ if (hasEvm) {
4562
+ const missing = ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"].filter((k) => !env[k]);
4563
+ if (missing.length > 0) {
4564
+ push(
4565
+ "missing_cdp_keys",
4566
+ `x402 EVM facilitator (Coinbase) requires ${missing.join(" and ")}. Create an API key at https://portal.cdp.coinbase.com and set it via env.`
4567
+ );
4522
4568
  }
4523
4569
  }
4524
4570
  return issues;
4525
4571
  }
4526
- function validateMppConfig(config) {
4572
+ function validateMppConfig(config, env) {
4527
4573
  const m = config.mpp;
4528
4574
  if (!m) {
4529
4575
  return [
@@ -4571,7 +4617,7 @@ function validateMppConfig(config) {
4571
4617
  `MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
4572
4618
  );
4573
4619
  }
4574
- if (!m.rpcUrl) {
4620
+ if (!m.rpcUrl && !env.TEMPO_RPC_URL) {
4575
4621
  push(
4576
4622
  "missing_mpp_rpc_url",
4577
4623
  "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
@@ -4596,6 +4642,15 @@ function validateMppConfig(config) {
4596
4642
  `MPP operatorKey and feePayerKey resolve to the same address (${collision}). Tempo rejects fee-delegated txs with sender === feePayer, so channel close/settle would fail at runtime. Either use two distinct wallets, or omit feePayerKey to disable gas sponsorship (clients then pay their own gas).`
4597
4643
  );
4598
4644
  }
4645
+ if (m.session && recipient && isEvmAddress(recipient)) {
4646
+ const operatorAddress = evmAddressFromKey(m.operatorKey);
4647
+ if (operatorAddress && operatorAddress !== recipient.toLowerCase()) {
4648
+ push(
4649
+ "mpp_operator_recipient_mismatch",
4650
+ `MPP session operatorKey resolves to ${operatorAddress}, which must equal the recipient/payee ${recipient.toLowerCase()}. mppx's channel-close handler asserts sender === payee. Set mpp.operatorKey to the recipient\u2019s private key, or set mpp.recipient/payeeAddress to the operator address.`
4651
+ );
4652
+ }
4653
+ }
4599
4654
  return issues;
4600
4655
  }
4601
4656
  function translateZodIssues(error) {
@@ -4724,8 +4779,8 @@ function getRouterConfigIssues(config, options = {}) {
4724
4779
  message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
4725
4780
  });
4726
4781
  }
4727
- if (protocols.includes("x402")) issues.push(...validateX402Config(config, env, options));
4728
- if (protocols.includes("mpp")) issues.push(...validateMppConfig(config));
4782
+ if (protocols.includes("x402")) issues.push(...validateX402Config(config, env));
4783
+ if (protocols.includes("mpp")) issues.push(...validateMppConfig(config, env));
4729
4784
  return issues;
4730
4785
  }
4731
4786
 
@@ -4810,9 +4865,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
4810
4865
  const getClient = async () => tempoClient;
4811
4866
  const operatorAccount = config.mpp.operatorKey ? privateKeyToAccount2(config.mpp.operatorKey) : void 0;
4812
4867
  const feePayerAccount = config.mpp.feePayerKey ? privateKeyToAccount2(config.mpp.feePayerKey) : void 0;
4813
- if (config.mpp.session && operatorAccount) {
4814
- assertOperatorMatchesRecipient(config, operatorAccount.address);
4815
- }
4816
4868
  const resolvedStore = kvStore ? await createKvMppStore(kvStore) : void 0;
4817
4869
  const realm = new URL(resolvedBaseUrl).host;
4818
4870
  const mppConfig = config.mpp;
@@ -4850,15 +4902,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
4850
4902
  return { initError: err instanceof Error ? err.message : String(err) };
4851
4903
  }
4852
4904
  }
4853
- function assertOperatorMatchesRecipient(config, operatorAddress) {
4854
- const recipient = (config.mpp?.recipient ?? config.payeeAddress)?.toLowerCase();
4855
- const opAddr = operatorAddress.toLowerCase();
4856
- if (recipient && opAddr !== recipient) {
4857
- throw new Error(
4858
- `MPP session config mismatch: operator address ${operatorAddress} must equal recipient/payee ${recipient}. mppx's channel-close handler asserts sender === payee. Set mpp.operatorKey to the private key for ${recipient}, or set mpp.recipient/payeeAddress to ${operatorAddress}.`
4859
- );
4860
- }
4861
- }
4862
4905
 
4863
4906
  // src/index.ts
4864
4907
  init_constants();
@@ -4869,10 +4912,7 @@ function createRouter(config) {
4869
4912
  const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
4870
4913
  const network = config.network ?? BASE_MAINNET_NETWORK;
4871
4914
  const x402Accepts = getConfiguredX402Accepts(config);
4872
- const configIssues = getRouterConfigIssues(config, {
4873
- env: process.env,
4874
- requireCdpKeys: process.env.NODE_ENV === "production"
4875
- });
4915
+ const configIssues = getRouterConfigIssues(config, { env: process.env });
4876
4916
  const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
4877
4917
  if (baseUrlIssue) throw new RouterConfigError([baseUrlIssue]);
4878
4918
  const emptyProtocolsIssue = configIssues.find((issue) => issue.code === "empty_protocols");
@@ -4885,10 +4925,7 @@ function createRouter(config) {
4885
4925
  const x402ConfigError = x402ConfigIssues.length > 0 ? formatRouterConfigIssues(x402ConfigIssues) : void 0;
4886
4926
  const mppConfigError = mppConfigIssues.length > 0 ? formatRouterConfigIssues(mppConfigIssues) : void 0;
4887
4927
  if (protocolConfigIssues.length > 0) {
4888
- for (const issue of protocolConfigIssues) console.error(`[router] ${issue.message}`);
4889
- if (process.env.NODE_ENV === "production") {
4890
- throw new RouterConfigError(protocolConfigIssues);
4891
- }
4928
+ throw new RouterConfigError(protocolConfigIssues);
4892
4929
  }
4893
4930
  const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
4894
4931
  if (config.plugin?.init) {
package/dist/index.d.cts CHANGED
@@ -381,7 +381,7 @@ interface DiscoveryConfig {
381
381
  serverUrl?: string;
382
382
  }
383
383
  interface RouterConfig {
384
- /** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via `.paid({ payTo })`. */
384
+ /** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via the `payTo` option on `.paid()` / `.upTo()` / `.metered()`. */
385
385
  payeeAddress?: string;
386
386
  /** Origin URL (required). Used as 402 realm, discovery base, OpenAPI server, and MPP memo prefix — must match the public domain or payment matching breaks. */
387
387
  baseUrl: string;
@@ -789,7 +789,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
789
789
  private register;
790
790
  }
791
791
 
792
- type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
792
+ type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'mpp_operator_recipient_mismatch' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
793
793
  type RouterConfigIssueSeverity = 'error' | 'warning';
794
794
  interface RouterConfigIssue {
795
795
  code: RouterConfigIssueCode;
package/dist/index.d.ts CHANGED
@@ -381,7 +381,7 @@ interface DiscoveryConfig {
381
381
  serverUrl?: string;
382
382
  }
383
383
  interface RouterConfig {
384
- /** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via `.paid({ payTo })`. */
384
+ /** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via the `payTo` option on `.paid()` / `.upTo()` / `.metered()`. */
385
385
  payeeAddress?: string;
386
386
  /** Origin URL (required). Used as 402 realm, discovery base, OpenAPI server, and MPP memo prefix — must match the public domain or payment matching breaks. */
387
387
  baseUrl: string;
@@ -789,7 +789,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
789
789
  private register;
790
790
  }
791
791
 
792
- type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
792
+ type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'mpp_operator_recipient_mismatch' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
793
793
  type RouterConfigIssueSeverity = 'error' | 'warning';
794
794
  interface RouterConfigIssue {
795
795
  code: RouterConfigIssueCode;