@agentcash/router 1.10.2 → 1.10.4

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 CHANGED
@@ -57,7 +57,7 @@ The recommended entry point reads its config from `process.env`. A copy-paste `.
57
57
  |-----|----------|---------|
58
58
  | `MPP_SECRET_KEY` | when MPP is enabled | Server-side MPP secret. Presence toggles MPP on. |
59
59
  | `MPP_CURRENCY` | when MPP is enabled | Tempo currency address. Use `TEMPO_USDC_ADDRESS` for Tempo USDC. |
60
- | `TEMPO_RPC_URL` | when MPP is enabled | Authenticated Tempo JSON-RPC endpoint. Public `rpc.tempo.xyz` returns 401. |
60
+ | `TEMPO_RPC_URL` | no | Tempo JSON-RPC endpoint for MPP on-chain verification. Defaults to the public `DEFAULT_TEMPO_RPC_URL` (`https://rpc.tempo.xyz`). Override only if you have a dedicated endpoint. |
61
61
  | `MPP_OPERATOR_KEY` | no | Signs server-side close/settle. When set, MPP session mode is enabled automatically (required for `.metered()`: both streaming and request-mode per-tick billing). Address must equal the payee. |
62
62
  | `MPP_FEE_PAYER_KEY` | no | Sponsors client gas for channel open/topUp. Must resolve to a different address than `MPP_OPERATOR_KEY` (Tempo rejects fee-delegated txs where `sender === feePayer`). |
63
63
 
package/dist/index.cjs CHANGED
@@ -31,7 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
33
  // src/constants.ts
34
- var BASE_MAINNET_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, ZERO_EVM_ADDRESS, DEFAULT_SOLANA_FACILITATOR_URL;
34
+ var BASE_MAINNET_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, ZERO_EVM_ADDRESS, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL;
35
35
  var init_constants = __esm({
36
36
  "src/constants.ts"() {
37
37
  "use strict";
@@ -43,6 +43,7 @@ var init_constants = __esm({
43
43
  BASE_USDC_DECIMALS = 6;
44
44
  ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
45
45
  DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.corbits.dev";
46
+ DEFAULT_TEMPO_RPC_URL = "https://rpc.tempo.xyz";
46
47
  }
47
48
  });
48
49
 
@@ -470,6 +471,7 @@ __export(index_exports, {
470
471
  BASE_USDC_ADDRESS: () => BASE_USDC_ADDRESS,
471
472
  BASE_USDC_DECIMALS: () => BASE_USDC_DECIMALS,
472
473
  DEFAULT_SOLANA_FACILITATOR_URL: () => DEFAULT_SOLANA_FACILITATOR_URL,
474
+ DEFAULT_TEMPO_RPC_URL: () => DEFAULT_TEMPO_RPC_URL,
473
475
  HttpError: () => HttpError,
474
476
  RouterConfigError: () => RouterConfigError,
475
477
  SOLANA_MAINNET_NETWORK: () => SOLANA_MAINNET_NETWORK,
@@ -539,7 +541,8 @@ var HEADERS = {
539
541
  X402_PAYMENT_LEGACY: "X-PAYMENT",
540
542
  X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
541
543
  X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
542
- MPP_PAYMENT_RECEIPT: "Payment-Receipt"
544
+ MPP_PAYMENT_RECEIPT: "Payment-Receipt",
545
+ REQUEST_ID: "X-Request-ID"
543
546
  };
544
547
  var AUTH_SCHEME = {
545
548
  BEARER: "Bearer ",
@@ -569,19 +572,19 @@ function firePluginHook(plugin, method, ...args) {
569
572
  const result = fn.apply(plugin, args);
570
573
  if (result && typeof result.catch === "function") {
571
574
  result.catch((error) => {
572
- console.error(
573
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
574
- );
575
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
575
576
  });
576
577
  }
577
578
  return result;
578
579
  } catch (error) {
579
- console.error(
580
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
581
- );
580
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
582
581
  return void 0;
583
582
  }
584
583
  }
584
+ function formatUnknownError(error) {
585
+ if (error instanceof Error) return error.stack ?? error.message;
586
+ return String(error);
587
+ }
585
588
 
586
589
  // src/plugin/reporter.ts
587
590
  function createReporter(plugin, pluginCtx, route) {
@@ -672,7 +675,8 @@ function firePaymentVerified(ctx, event) {
672
675
  function firePaymentSettled(ctx, event) {
673
676
  firePluginHook(ctx.deps.plugin, "onPaymentSettled", ctx.pluginCtx, event);
674
677
  }
675
- function firePluginResponse(ctx, response, requestBody, responseBody) {
678
+ function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
679
+ attachRequestId(response, ctx.meta.requestId);
676
680
  firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
677
681
  statusCode: response.status,
678
682
  statusText: response.statusText,
@@ -683,11 +687,9 @@ function firePluginResponse(ctx, response, requestBody, responseBody) {
683
687
  responseBody
684
688
  });
685
689
  if (response.status >= 400 && response.status !== 402) {
686
- firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, {
687
- status: response.status,
688
- message: response.statusText || `HTTP ${response.status}`,
689
- settled: false
690
- });
690
+ const error = buildErrorEvent(ctx, response, failure);
691
+ if (response.status >= 500) logRouterFailure(error);
692
+ firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, error);
691
693
  }
692
694
  }
693
695
  function fireProviderQuota(ctx, response, handlerResult) {
@@ -719,6 +721,67 @@ function computeQuotaLevel(remaining, warn, critical) {
719
721
  if (warn !== void 0 && remaining <= warn) return "warn";
720
722
  return "healthy";
721
723
  }
724
+ function attachRequestId(response, requestId) {
725
+ try {
726
+ if (!response.headers.has(HEADERS.REQUEST_ID)) {
727
+ response.headers.set(HEADERS.REQUEST_ID, requestId);
728
+ }
729
+ } catch {
730
+ }
731
+ }
732
+ function buildErrorEvent(ctx, response, failure) {
733
+ const error = errorDetails(failure?.cause);
734
+ const responseMessage = response.statusText || `HTTP ${response.status}`;
735
+ const message = failure?.message ?? error.message ?? responseMessage;
736
+ return {
737
+ status: response.status,
738
+ message,
739
+ settled: failure?.settled ?? false,
740
+ requestId: ctx.meta.requestId,
741
+ route: ctx.meta.route,
742
+ method: ctx.meta.method,
743
+ duration: Date.now() - ctx.meta.startTime,
744
+ walletAddress: ctx.meta.walletAddress,
745
+ verifiedWallet: ctx.pluginCtx.verifiedWallet,
746
+ clientId: ctx.meta.clientId,
747
+ sessionId: ctx.meta.sessionId,
748
+ errorName: error.name,
749
+ stack: error.stack,
750
+ cause: failure?.cause
751
+ };
752
+ }
753
+ function errorDetails(error) {
754
+ if (error instanceof Error) {
755
+ return {
756
+ message: error.message,
757
+ name: error.name,
758
+ stack: error.stack
759
+ };
760
+ }
761
+ if (typeof error === "object" && error !== null) {
762
+ const record = error;
763
+ return {
764
+ message: typeof record.message === "string" ? record.message : void 0,
765
+ name: typeof record.name === "string" ? record.name : void 0,
766
+ stack: typeof record.stack === "string" ? record.stack : void 0
767
+ };
768
+ }
769
+ if (typeof error === "string") return { message: error };
770
+ return {};
771
+ }
772
+ function logRouterFailure(error) {
773
+ console.error(`[router] ERROR ${error.route ?? "unknown"} ${error.status}: ${error.message}`, {
774
+ requestId: error.requestId,
775
+ method: error.method,
776
+ duration: error.duration,
777
+ walletAddress: error.walletAddress,
778
+ verifiedWallet: error.verifiedWallet,
779
+ clientId: error.clientId,
780
+ sessionId: error.sessionId,
781
+ errorName: error.errorName,
782
+ stack: error.stack
783
+ });
784
+ }
722
785
 
723
786
  // src/pipeline/steps/parse-body.ts
724
787
  async function parseBody(ctx, request = ctx.request) {
@@ -728,20 +791,19 @@ async function parseBody(ctx, request = ctx.request) {
728
791
  raw = await bufferBody(request);
729
792
  } catch (err) {
730
793
  if (!(err instanceof MalformedJsonError)) throw err;
731
- const response2 = import_server.NextResponse.json(
732
- { success: false, error: "Invalid JSON", issues: [] },
733
- { status: 400 }
734
- );
735
- firePluginResponse(ctx, response2);
794
+ const responseBody2 = { success: false, error: "Invalid JSON", issues: [] };
795
+ const response2 = import_server.NextResponse.json(responseBody2, { status: 400 });
796
+ firePluginResponse(ctx, response2, void 0, responseBody2, {
797
+ message: responseBody2.error,
798
+ cause: err
799
+ });
736
800
  return { ok: false, response: response2 };
737
801
  }
738
802
  const result = validateBody(raw, ctx.routeEntry.bodySchema);
739
803
  if (result.success) return { ok: true, data: result.data };
740
- const response = import_server.NextResponse.json(
741
- { success: false, error: result.error, issues: result.issues },
742
- { status: 400 }
743
- );
744
- firePluginResponse(ctx, response);
804
+ const responseBody = { success: false, error: result.error, issues: result.issues };
805
+ const response = import_server.NextResponse.json(responseBody, { status: 400 });
806
+ firePluginResponse(ctx, response, raw, responseBody, { message: result.error });
745
807
  return { ok: false, response };
746
808
  }
747
809
 
@@ -753,11 +815,9 @@ function validateQuery(ctx) {
753
815
  const params = Object.fromEntries(ctx.request.nextUrl.searchParams.entries());
754
816
  const result = validateBody(params, querySchema);
755
817
  if (result.success) return { ok: true, data: result.data };
756
- const response = import_server2.NextResponse.json(
757
- { success: false, error: result.error, issues: result.issues },
758
- { status: 400 }
759
- );
760
- firePluginResponse(ctx, response);
818
+ const responseBody = { success: false, error: result.error, issues: result.issues };
819
+ const response = import_server2.NextResponse.json(responseBody, { status: 400 });
820
+ firePluginResponse(ctx, response, params, responseBody, { message: result.error });
761
821
  return { ok: false, response };
762
822
  }
763
823
 
@@ -776,9 +836,13 @@ function handlerFailureError(response) {
776
836
 
777
837
  // src/pipeline/steps/fail.ts
778
838
  var import_server3 = require("next/server");
779
- function fail(ctx, status, message, requestBody) {
780
- const response = import_server3.NextResponse.json({ success: false, error: message }, { status });
781
- firePluginResponse(ctx, response, requestBody);
839
+ function fail(ctx, status, message, requestBody, failure) {
840
+ const responseBody = { success: false, error: message };
841
+ const response = import_server3.NextResponse.json(responseBody, { status });
842
+ firePluginResponse(ctx, response, requestBody, responseBody, {
843
+ ...failure,
844
+ message: failure?.message ?? message
845
+ });
782
846
  return response;
783
847
  }
784
848
 
@@ -854,9 +918,10 @@ async function runHandler(ctx, handlerCtx) {
854
918
  function errorResult(error) {
855
919
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
856
920
  const message = error instanceof Error ? error.message : "Internal error";
921
+ const responseBody = { success: false, error: message };
857
922
  return {
858
- response: import_server4.NextResponse.json({ success: false, error: message }, { status }),
859
- rawResult: void 0,
923
+ response: import_server4.NextResponse.json(responseBody, { status }),
924
+ rawResult: responseBody,
860
925
  handlerError: error
861
926
  };
862
927
  }
@@ -868,9 +933,9 @@ function isThenable(value) {
868
933
  }
869
934
 
870
935
  // src/pipeline/steps/finalize/response.ts
871
- function finalize(ctx, response, rawResult, requestBody) {
936
+ function finalize(ctx, response, rawResult, requestBody, failure) {
872
937
  fireProviderQuota(ctx, response, rawResult);
873
- firePluginResponse(ctx, response, requestBody, rawResult);
938
+ firePluginResponse(ctx, response, requestBody, rawResult, failure);
874
939
  return response;
875
940
  }
876
941
 
@@ -956,7 +1021,9 @@ async function settleAndFinalizeRequest(args) {
956
1021
  });
957
1022
  if (!settle.ok) {
958
1023
  if (onSettleError) await onSettleError(settle.error, settle.failMessage);
959
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
1024
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
1025
+ cause: settle.error
1026
+ });
960
1027
  }
961
1028
  return runPostSettleEpilogue({
962
1029
  ctx,
@@ -991,7 +1058,9 @@ async function settleAndFinalizeStream(args) {
991
1058
  report
992
1059
  });
993
1060
  if (!settle.ok) {
994
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
1061
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
1062
+ cause: settle.error
1063
+ });
995
1064
  }
996
1065
  return runPostSettleEpilogue({
997
1066
  ctx,
@@ -1018,7 +1087,13 @@ async function runHandlerOnly(ctx, wallet, account) {
1018
1087
  const validateErr = await runValidate(ctx, body.data);
1019
1088
  if (validateErr) return validateErr;
1020
1089
  const result = await invokeUnauthed(ctx, wallet, account, body.data);
1021
- return finalize(ctx, result.response, result.rawResult, body.data);
1090
+ return finalize(
1091
+ ctx,
1092
+ result.response,
1093
+ result.rawResult,
1094
+ body.data,
1095
+ result.handlerError === void 0 ? void 0 : { cause: result.handlerError }
1096
+ );
1022
1097
  }
1023
1098
 
1024
1099
  // src/pipeline/steps/run-before-settle.ts
@@ -1574,7 +1649,7 @@ async function verifyHashMode(args, info) {
1574
1649
  }
1575
1650
  if (chargeResult.status === 402) {
1576
1651
  const reason = await readChallengeReason(chargeResult.challenge);
1577
- const detail = reason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
1652
+ const detail = reason || "credential may be invalid, or check your TEMPO_RPC_URL endpoint";
1578
1653
  report("warn", `MPP credential rejected: ${detail}`);
1579
1654
  return { ok: false, kind: "invalid" };
1580
1655
  }
@@ -2457,11 +2532,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2457
2532
  challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
2458
2533
  } catch (err) {
2459
2534
  const message = errorMessage(err, "Price calculation failed");
2460
- const errorResponse = import_server5.NextResponse.json(
2461
- { success: false, error: message },
2462
- { status: errorStatus(err, 500) }
2463
- );
2464
- firePluginResponse(ctx, errorResponse);
2535
+ const responseBody2 = { success: false, error: message };
2536
+ const errorResponse = import_server5.NextResponse.json(responseBody2, { status: errorStatus(err, 500) });
2537
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2465
2538
  return errorResponse;
2466
2539
  }
2467
2540
  const extensions = await buildChallengeExtensions(ctx);
@@ -2493,11 +2566,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2493
2566
  const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
2494
2567
  ctx.report("critical", message);
2495
2568
  if (strategy.protocol === "x402") {
2496
- const errorResponse = import_server5.NextResponse.json(
2497
- { success: false, error: message },
2498
- { status: 500 }
2499
- );
2500
- firePluginResponse(ctx, errorResponse);
2569
+ const responseBody2 = { success: false, error: message };
2570
+ const errorResponse = import_server5.NextResponse.json(responseBody2, { status: 500 });
2571
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2501
2572
  return errorResponse;
2502
2573
  }
2503
2574
  }
@@ -2656,10 +2727,11 @@ function toResponse(rawResult) {
2656
2727
  function errorResult2(error) {
2657
2728
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
2658
2729
  const message = error instanceof Error ? error.message : "Internal error";
2730
+ const responseBody = { success: false, error: message };
2659
2731
  return {
2660
2732
  kind: "request",
2661
- response: import_server7.NextResponse.json({ success: false, error: message }, { status }),
2662
- rawResult: void 0,
2733
+ response: import_server7.NextResponse.json(responseBody, { status }),
2734
+ rawResult: responseBody,
2663
2735
  handlerError: error
2664
2736
  };
2665
2737
  }
@@ -2790,7 +2862,13 @@ async function runDynamicRequestFlow(args) {
2790
2862
  handlerError: result.handlerError
2791
2863
  };
2792
2864
  if (result.response.status >= 400) {
2793
- return finalize(ctx, result.response, result.rawResult, body);
2865
+ return finalize(
2866
+ ctx,
2867
+ result.response,
2868
+ result.rawResult,
2869
+ body,
2870
+ failureFromCause(result.handlerError)
2871
+ );
2794
2872
  }
2795
2873
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2796
2874
  if (beforeErr) return beforeErr;
@@ -2812,6 +2890,9 @@ async function runDynamicRequestFlow(args) {
2812
2890
  }
2813
2891
  });
2814
2892
  }
2893
+ function failureFromCause(cause) {
2894
+ return cause === void 0 ? void 0 : { cause };
2895
+ }
2815
2896
  function computeBilledAmount(routeEntry, result) {
2816
2897
  if (routeEntry.billing === "upto") {
2817
2898
  const total = result.uptoContext?.atomicTotal() ?? 0n;
@@ -2976,7 +3057,13 @@ async function runStaticRequestFlow(args) {
2976
3057
  if (result.response.status >= 400) {
2977
3058
  const settledScope = settleScope;
2978
3059
  await runSettledHandlerError(ctx, settledScope);
2979
- return finalize(ctx, result.response, result.rawResult, body);
3060
+ return finalize(
3061
+ ctx,
3062
+ result.response,
3063
+ result.rawResult,
3064
+ body,
3065
+ failureFromCause2(result.handlerError)
3066
+ );
2980
3067
  }
2981
3068
  return settleAndFinalizeRequest({
2982
3069
  ctx,
@@ -2989,7 +3076,13 @@ async function runStaticRequestFlow(args) {
2989
3076
  });
2990
3077
  }
2991
3078
  if (result.response.status >= 400) {
2992
- return finalize(ctx, result.response, result.rawResult, body);
3079
+ return finalize(
3080
+ ctx,
3081
+ result.response,
3082
+ result.rawResult,
3083
+ body,
3084
+ failureFromCause2(result.handlerError)
3085
+ );
2993
3086
  }
2994
3087
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2995
3088
  if (beforeErr) return beforeErr;
@@ -3010,6 +3103,9 @@ async function runStaticRequestFlow(args) {
3010
3103
  }
3011
3104
  });
3012
3105
  }
3106
+ function failureFromCause2(cause) {
3107
+ return cause === void 0 ? void 0 : { cause };
3108
+ }
3013
3109
 
3014
3110
  // src/pipeline/flows/static/static-paid.ts
3015
3111
  async function runStaticPaidFlow(ctx) {
@@ -4464,7 +4560,7 @@ var envShape = {
4464
4560
  }).optional(),
4465
4561
  TEMPO_RPC_URL: import_zod.z.string().refine(isUrl, {
4466
4562
  params: { code: "invalid_mpp_rpc_url", ...mpp },
4467
- message: "TEMPO_RPC_URL must be a valid URL \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401."
4563
+ message: "TEMPO_RPC_URL must be a valid URL \u2014 the Tempo JSON-RPC endpoint used for MPP on-chain verification. Optional; defaults to the public DEFAULT_TEMPO_RPC_URL."
4468
4564
  }).optional(),
4469
4565
  MPP_OPERATOR_KEY: import_zod.z.string().refine(isEvmPrivateKey, {
4470
4566
  params: { code: "invalid_mpp_operator_key", ...mpp },
@@ -4505,14 +4601,6 @@ var EnvInputSchema = import_zod.z.object(envShape).passthrough().superRefine((en
4505
4601
  ["MPP_CURRENCY"]
4506
4602
  );
4507
4603
  }
4508
- if (env.TEMPO_RPC_URL === void 0) {
4509
- addIssue(
4510
- ctx,
4511
- { code: "missing_mpp_rpc_url", ...mpp },
4512
- "TEMPO_RPC_URL is required when MPP is enabled \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401.",
4513
- ["TEMPO_RPC_URL"]
4514
- );
4515
- }
4516
4604
  }
4517
4605
  const collision = operatorAddressesCollide(env.MPP_OPERATOR_KEY, env.MPP_FEE_PAYER_KEY);
4518
4606
  if (collision) {
@@ -4644,7 +4732,7 @@ function validateX402Config(config, env) {
4644
4732
  }
4645
4733
  return issues;
4646
4734
  }
4647
- function validateMppConfig(config, env) {
4735
+ function validateMppConfig(config) {
4648
4736
  const m = config.mpp;
4649
4737
  if (!m) {
4650
4738
  return [
@@ -4692,12 +4780,6 @@ function validateMppConfig(config, env) {
4692
4780
  `MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
4693
4781
  );
4694
4782
  }
4695
- if (!m.rpcUrl && !env.TEMPO_RPC_URL) {
4696
- push(
4697
- "missing_mpp_rpc_url",
4698
- "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
4699
- );
4700
- }
4701
4783
  if (m.feePayerKey && !isEvmPrivateKey(m.feePayerKey)) {
4702
4784
  push(
4703
4785
  "invalid_mpp_fee_payer_key",
@@ -4804,7 +4886,7 @@ function routerConfigFromEnv(options) {
4804
4886
  const mppConfig = mppEnabled ? {
4805
4887
  secretKey: env.MPP_SECRET_KEY,
4806
4888
  currency: canonicalizeEvm(env.MPP_CURRENCY),
4807
- rpcUrl: env.TEMPO_RPC_URL,
4889
+ rpcUrl: env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL,
4808
4890
  recipient: payeeAddress,
4809
4891
  ...env.MPP_FEE_PAYER_KEY ? { feePayerKey: env.MPP_FEE_PAYER_KEY } : {},
4810
4892
  ...env.MPP_OPERATOR_KEY ? { operatorKey: env.MPP_OPERATOR_KEY, session: {} } : {}
@@ -4855,7 +4937,7 @@ function getRouterConfigIssues(config, options = {}) {
4855
4937
  });
4856
4938
  }
4857
4939
  if (protocols.includes("x402")) issues.push(...validateX402Config(config, env));
4858
- if (protocols.includes("mpp")) issues.push(...validateMppConfig(config, env));
4940
+ if (protocols.includes("mpp")) issues.push(...validateMppConfig(config));
4859
4941
  return issues;
4860
4942
  }
4861
4943
 
@@ -4927,6 +5009,7 @@ function getMppxStreamingContext(args) {
4927
5009
  }
4928
5010
 
4929
5011
  // src/init/mpp.ts
5012
+ init_constants();
4930
5013
  async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
4931
5014
  if (configError) return { initError: configError };
4932
5015
  if (!config.mpp) return {};
@@ -4935,7 +5018,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
4935
5018
  const { createClient, http } = await import("viem");
4936
5019
  const { tempo: tempoChain } = await import("viem/chains");
4937
5020
  const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
4938
- const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL;
5021
+ const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL;
4939
5022
  const tempoClient = createClient({ chain: tempoChain, transport: http(rpcUrl) });
4940
5023
  const getClient = async () => tempoClient;
4941
5024
  const operatorAccount = config.mpp.operatorKey ? privateKeyToAccount2(config.mpp.operatorKey) : void 0;
@@ -5113,6 +5196,7 @@ function createRouterFromEnv(options) {
5113
5196
  BASE_USDC_ADDRESS,
5114
5197
  BASE_USDC_DECIMALS,
5115
5198
  DEFAULT_SOLANA_FACILITATOR_URL,
5199
+ DEFAULT_TEMPO_RPC_URL,
5116
5200
  HttpError,
5117
5201
  RouterConfigError,
5118
5202
  SOLANA_MAINNET_NETWORK,
package/dist/index.d.cts CHANGED
@@ -77,13 +77,24 @@ interface ResponseMeta {
77
77
  headers: Record<string, string>;
78
78
  /** Parsed request body (when .body() was used). undefined when no body was parsed. */
79
79
  requestBody?: unknown;
80
- /** Handler return value. undefined for raw Response returns (streams) or error paths. */
80
+ /** Handler return value or structured router-generated error body. */
81
81
  responseBody?: unknown;
82
82
  }
83
83
  interface ErrorEvent {
84
84
  status: number;
85
85
  message: string;
86
86
  settled: boolean;
87
+ requestId?: string;
88
+ route?: string;
89
+ method?: string;
90
+ duration?: number;
91
+ walletAddress?: string | null;
92
+ verifiedWallet?: string | null;
93
+ clientId?: string | null;
94
+ sessionId?: string | null;
95
+ errorName?: string;
96
+ stack?: string;
97
+ cause?: unknown;
87
98
  }
88
99
  interface AuthEvent {
89
100
  /** Authentication mode that was verified */
@@ -411,7 +422,7 @@ interface RouterConfig {
411
422
  currency: string;
412
423
  /** MPP payee address (EVM). Overrides `payeeAddress` for MPP only. Required when `payeeAddress` is unset. MUST equal `operatorKey`'s derived address when `session` is enabled. */
413
424
  recipient?: string;
414
- /** Tempo RPC URL for on-chain verification. Falls back to `TEMPO_RPC_URL`. */
425
+ /** Tempo RPC URL for on-chain verification. Falls back to `TEMPO_RPC_URL`, then to the public `DEFAULT_TEMPO_RPC_URL`. */
415
426
  rpcUrl?: string;
416
427
  /** Hex private key. Signs channel close/settle; required for `session`. Address MUST equal `recipient`/payee — mppx asserts sender===payee on settle. Validated at init. */
417
428
  operatorKey?: string;
@@ -793,7 +804,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
793
804
  private register;
794
805
  }
795
806
 
796
- 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';
807
+ 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' | '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';
797
808
  type RouterConfigIssueSeverity = 'error' | 'warning';
798
809
  interface RouterConfigIssue {
799
810
  code: RouterConfigIssueCode;
@@ -870,6 +881,8 @@ declare const BASE_USDC_DECIMALS = 6;
870
881
  declare const ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
871
882
  /** Public Solana x402 facilitator. Override per-deployment via `SOLANA_FACILITATOR_URL`. */
872
883
  declare const DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.corbits.dev";
884
+ /** Public Tempo JSON-RPC endpoint used for MPP on-chain verification. Override per-deployment via `TEMPO_RPC_URL`. */
885
+ declare const DEFAULT_TEMPO_RPC_URL = "https://rpc.tempo.xyz";
873
886
 
874
887
  interface MonitorEntry {
875
888
  provider: string;
@@ -912,4 +925,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
912
925
  */
913
926
  declare function createRouterFromEnv<const P extends Record<string, string> = Record<never, string>>(options: CreateRouterFromEnvOptions<P>): ServiceRouter<Extract<keyof P, string>>;
914
927
 
915
- export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
928
+ export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
package/dist/index.d.ts CHANGED
@@ -77,13 +77,24 @@ interface ResponseMeta {
77
77
  headers: Record<string, string>;
78
78
  /** Parsed request body (when .body() was used). undefined when no body was parsed. */
79
79
  requestBody?: unknown;
80
- /** Handler return value. undefined for raw Response returns (streams) or error paths. */
80
+ /** Handler return value or structured router-generated error body. */
81
81
  responseBody?: unknown;
82
82
  }
83
83
  interface ErrorEvent {
84
84
  status: number;
85
85
  message: string;
86
86
  settled: boolean;
87
+ requestId?: string;
88
+ route?: string;
89
+ method?: string;
90
+ duration?: number;
91
+ walletAddress?: string | null;
92
+ verifiedWallet?: string | null;
93
+ clientId?: string | null;
94
+ sessionId?: string | null;
95
+ errorName?: string;
96
+ stack?: string;
97
+ cause?: unknown;
87
98
  }
88
99
  interface AuthEvent {
89
100
  /** Authentication mode that was verified */
@@ -411,7 +422,7 @@ interface RouterConfig {
411
422
  currency: string;
412
423
  /** MPP payee address (EVM). Overrides `payeeAddress` for MPP only. Required when `payeeAddress` is unset. MUST equal `operatorKey`'s derived address when `session` is enabled. */
413
424
  recipient?: string;
414
- /** Tempo RPC URL for on-chain verification. Falls back to `TEMPO_RPC_URL`. */
425
+ /** Tempo RPC URL for on-chain verification. Falls back to `TEMPO_RPC_URL`, then to the public `DEFAULT_TEMPO_RPC_URL`. */
415
426
  rpcUrl?: string;
416
427
  /** Hex private key. Signs channel close/settle; required for `session`. Address MUST equal `recipient`/payee — mppx asserts sender===payee on settle. Validated at init. */
417
428
  operatorKey?: string;
@@ -793,7 +804,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
793
804
  private register;
794
805
  }
795
806
 
796
- 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';
807
+ 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' | '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';
797
808
  type RouterConfigIssueSeverity = 'error' | 'warning';
798
809
  interface RouterConfigIssue {
799
810
  code: RouterConfigIssueCode;
@@ -870,6 +881,8 @@ declare const BASE_USDC_DECIMALS = 6;
870
881
  declare const ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
871
882
  /** Public Solana x402 facilitator. Override per-deployment via `SOLANA_FACILITATOR_URL`. */
872
883
  declare const DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.corbits.dev";
884
+ /** Public Tempo JSON-RPC endpoint used for MPP on-chain verification. Override per-deployment via `TEMPO_RPC_URL`. */
885
+ declare const DEFAULT_TEMPO_RPC_URL = "https://rpc.tempo.xyz";
873
886
 
874
887
  interface MonitorEntry {
875
888
  provider: string;
@@ -912,4 +925,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
912
925
  */
913
926
  declare function createRouterFromEnv<const P extends Record<string, string> = Record<never, string>>(options: CreateRouterFromEnvOptions<P>): ServiceRouter<Extract<keyof P, string>>;
914
927
 
915
- export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
928
+ export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/constants.ts
12
- var BASE_MAINNET_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, ZERO_EVM_ADDRESS, DEFAULT_SOLANA_FACILITATOR_URL;
12
+ var BASE_MAINNET_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, ZERO_EVM_ADDRESS, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL;
13
13
  var init_constants = __esm({
14
14
  "src/constants.ts"() {
15
15
  "use strict";
@@ -21,6 +21,7 @@ var init_constants = __esm({
21
21
  BASE_USDC_DECIMALS = 6;
22
22
  ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
23
23
  DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.corbits.dev";
24
+ DEFAULT_TEMPO_RPC_URL = "https://rpc.tempo.xyz";
24
25
  }
25
26
  });
26
27
 
@@ -498,7 +499,8 @@ var HEADERS = {
498
499
  X402_PAYMENT_LEGACY: "X-PAYMENT",
499
500
  X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
500
501
  X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
501
- MPP_PAYMENT_RECEIPT: "Payment-Receipt"
502
+ MPP_PAYMENT_RECEIPT: "Payment-Receipt",
503
+ REQUEST_ID: "X-Request-ID"
502
504
  };
503
505
  var AUTH_SCHEME = {
504
506
  BEARER: "Bearer ",
@@ -528,19 +530,19 @@ function firePluginHook(plugin, method, ...args) {
528
530
  const result = fn.apply(plugin, args);
529
531
  if (result && typeof result.catch === "function") {
530
532
  result.catch((error) => {
531
- console.error(
532
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
533
- );
533
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
534
534
  });
535
535
  }
536
536
  return result;
537
537
  } catch (error) {
538
- console.error(
539
- `[router] ERROR ${method}: ${error instanceof Error ? error.message : String(error)}`
540
- );
538
+ console.error(`[router] ERROR ${method}: ${formatUnknownError(error)}`);
541
539
  return void 0;
542
540
  }
543
541
  }
542
+ function formatUnknownError(error) {
543
+ if (error instanceof Error) return error.stack ?? error.message;
544
+ return String(error);
545
+ }
544
546
 
545
547
  // src/plugin/reporter.ts
546
548
  function createReporter(plugin, pluginCtx, route) {
@@ -631,7 +633,8 @@ function firePaymentVerified(ctx, event) {
631
633
  function firePaymentSettled(ctx, event) {
632
634
  firePluginHook(ctx.deps.plugin, "onPaymentSettled", ctx.pluginCtx, event);
633
635
  }
634
- function firePluginResponse(ctx, response, requestBody, responseBody) {
636
+ function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
637
+ attachRequestId(response, ctx.meta.requestId);
635
638
  firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
636
639
  statusCode: response.status,
637
640
  statusText: response.statusText,
@@ -642,11 +645,9 @@ function firePluginResponse(ctx, response, requestBody, responseBody) {
642
645
  responseBody
643
646
  });
644
647
  if (response.status >= 400 && response.status !== 402) {
645
- firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, {
646
- status: response.status,
647
- message: response.statusText || `HTTP ${response.status}`,
648
- settled: false
649
- });
648
+ const error = buildErrorEvent(ctx, response, failure);
649
+ if (response.status >= 500) logRouterFailure(error);
650
+ firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, error);
650
651
  }
651
652
  }
652
653
  function fireProviderQuota(ctx, response, handlerResult) {
@@ -678,6 +679,67 @@ function computeQuotaLevel(remaining, warn, critical) {
678
679
  if (warn !== void 0 && remaining <= warn) return "warn";
679
680
  return "healthy";
680
681
  }
682
+ function attachRequestId(response, requestId) {
683
+ try {
684
+ if (!response.headers.has(HEADERS.REQUEST_ID)) {
685
+ response.headers.set(HEADERS.REQUEST_ID, requestId);
686
+ }
687
+ } catch {
688
+ }
689
+ }
690
+ function buildErrorEvent(ctx, response, failure) {
691
+ const error = errorDetails(failure?.cause);
692
+ const responseMessage = response.statusText || `HTTP ${response.status}`;
693
+ const message = failure?.message ?? error.message ?? responseMessage;
694
+ return {
695
+ status: response.status,
696
+ message,
697
+ settled: failure?.settled ?? false,
698
+ requestId: ctx.meta.requestId,
699
+ route: ctx.meta.route,
700
+ method: ctx.meta.method,
701
+ duration: Date.now() - ctx.meta.startTime,
702
+ walletAddress: ctx.meta.walletAddress,
703
+ verifiedWallet: ctx.pluginCtx.verifiedWallet,
704
+ clientId: ctx.meta.clientId,
705
+ sessionId: ctx.meta.sessionId,
706
+ errorName: error.name,
707
+ stack: error.stack,
708
+ cause: failure?.cause
709
+ };
710
+ }
711
+ function errorDetails(error) {
712
+ if (error instanceof Error) {
713
+ return {
714
+ message: error.message,
715
+ name: error.name,
716
+ stack: error.stack
717
+ };
718
+ }
719
+ if (typeof error === "object" && error !== null) {
720
+ const record = error;
721
+ return {
722
+ message: typeof record.message === "string" ? record.message : void 0,
723
+ name: typeof record.name === "string" ? record.name : void 0,
724
+ stack: typeof record.stack === "string" ? record.stack : void 0
725
+ };
726
+ }
727
+ if (typeof error === "string") return { message: error };
728
+ return {};
729
+ }
730
+ function logRouterFailure(error) {
731
+ console.error(`[router] ERROR ${error.route ?? "unknown"} ${error.status}: ${error.message}`, {
732
+ requestId: error.requestId,
733
+ method: error.method,
734
+ duration: error.duration,
735
+ walletAddress: error.walletAddress,
736
+ verifiedWallet: error.verifiedWallet,
737
+ clientId: error.clientId,
738
+ sessionId: error.sessionId,
739
+ errorName: error.errorName,
740
+ stack: error.stack
741
+ });
742
+ }
681
743
 
682
744
  // src/pipeline/steps/parse-body.ts
683
745
  async function parseBody(ctx, request = ctx.request) {
@@ -687,20 +749,19 @@ async function parseBody(ctx, request = ctx.request) {
687
749
  raw = await bufferBody(request);
688
750
  } catch (err) {
689
751
  if (!(err instanceof MalformedJsonError)) throw err;
690
- const response2 = NextResponse.json(
691
- { success: false, error: "Invalid JSON", issues: [] },
692
- { status: 400 }
693
- );
694
- firePluginResponse(ctx, response2);
752
+ const responseBody2 = { success: false, error: "Invalid JSON", issues: [] };
753
+ const response2 = NextResponse.json(responseBody2, { status: 400 });
754
+ firePluginResponse(ctx, response2, void 0, responseBody2, {
755
+ message: responseBody2.error,
756
+ cause: err
757
+ });
695
758
  return { ok: false, response: response2 };
696
759
  }
697
760
  const result = validateBody(raw, ctx.routeEntry.bodySchema);
698
761
  if (result.success) return { ok: true, data: result.data };
699
- const response = NextResponse.json(
700
- { success: false, error: result.error, issues: result.issues },
701
- { status: 400 }
702
- );
703
- firePluginResponse(ctx, response);
762
+ const responseBody = { success: false, error: result.error, issues: result.issues };
763
+ const response = NextResponse.json(responseBody, { status: 400 });
764
+ firePluginResponse(ctx, response, raw, responseBody, { message: result.error });
704
765
  return { ok: false, response };
705
766
  }
706
767
 
@@ -712,11 +773,9 @@ function validateQuery(ctx) {
712
773
  const params = Object.fromEntries(ctx.request.nextUrl.searchParams.entries());
713
774
  const result = validateBody(params, querySchema);
714
775
  if (result.success) return { ok: true, data: result.data };
715
- const response = NextResponse2.json(
716
- { success: false, error: result.error, issues: result.issues },
717
- { status: 400 }
718
- );
719
- firePluginResponse(ctx, response);
776
+ const responseBody = { success: false, error: result.error, issues: result.issues };
777
+ const response = NextResponse2.json(responseBody, { status: 400 });
778
+ firePluginResponse(ctx, response, params, responseBody, { message: result.error });
720
779
  return { ok: false, response };
721
780
  }
722
781
 
@@ -735,9 +794,13 @@ function handlerFailureError(response) {
735
794
 
736
795
  // src/pipeline/steps/fail.ts
737
796
  import { NextResponse as NextResponse3 } from "next/server";
738
- function fail(ctx, status, message, requestBody) {
739
- const response = NextResponse3.json({ success: false, error: message }, { status });
740
- firePluginResponse(ctx, response, requestBody);
797
+ function fail(ctx, status, message, requestBody, failure) {
798
+ const responseBody = { success: false, error: message };
799
+ const response = NextResponse3.json(responseBody, { status });
800
+ firePluginResponse(ctx, response, requestBody, responseBody, {
801
+ ...failure,
802
+ message: failure?.message ?? message
803
+ });
741
804
  return response;
742
805
  }
743
806
 
@@ -813,9 +876,10 @@ async function runHandler(ctx, handlerCtx) {
813
876
  function errorResult(error) {
814
877
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
815
878
  const message = error instanceof Error ? error.message : "Internal error";
879
+ const responseBody = { success: false, error: message };
816
880
  return {
817
- response: NextResponse4.json({ success: false, error: message }, { status }),
818
- rawResult: void 0,
881
+ response: NextResponse4.json(responseBody, { status }),
882
+ rawResult: responseBody,
819
883
  handlerError: error
820
884
  };
821
885
  }
@@ -827,9 +891,9 @@ function isThenable(value) {
827
891
  }
828
892
 
829
893
  // src/pipeline/steps/finalize/response.ts
830
- function finalize(ctx, response, rawResult, requestBody) {
894
+ function finalize(ctx, response, rawResult, requestBody, failure) {
831
895
  fireProviderQuota(ctx, response, rawResult);
832
- firePluginResponse(ctx, response, requestBody, rawResult);
896
+ firePluginResponse(ctx, response, requestBody, rawResult, failure);
833
897
  return response;
834
898
  }
835
899
 
@@ -915,7 +979,9 @@ async function settleAndFinalizeRequest(args) {
915
979
  });
916
980
  if (!settle.ok) {
917
981
  if (onSettleError) await onSettleError(settle.error, settle.failMessage);
918
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
982
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
983
+ cause: settle.error
984
+ });
919
985
  }
920
986
  return runPostSettleEpilogue({
921
987
  ctx,
@@ -950,7 +1016,9 @@ async function settleAndFinalizeStream(args) {
950
1016
  report
951
1017
  });
952
1018
  if (!settle.ok) {
953
- return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
1019
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body, {
1020
+ cause: settle.error
1021
+ });
954
1022
  }
955
1023
  return runPostSettleEpilogue({
956
1024
  ctx,
@@ -977,7 +1045,13 @@ async function runHandlerOnly(ctx, wallet, account) {
977
1045
  const validateErr = await runValidate(ctx, body.data);
978
1046
  if (validateErr) return validateErr;
979
1047
  const result = await invokeUnauthed(ctx, wallet, account, body.data);
980
- return finalize(ctx, result.response, result.rawResult, body.data);
1048
+ return finalize(
1049
+ ctx,
1050
+ result.response,
1051
+ result.rawResult,
1052
+ body.data,
1053
+ result.handlerError === void 0 ? void 0 : { cause: result.handlerError }
1054
+ );
981
1055
  }
982
1056
 
983
1057
  // src/pipeline/steps/run-before-settle.ts
@@ -1533,7 +1607,7 @@ async function verifyHashMode(args, info) {
1533
1607
  }
1534
1608
  if (chargeResult.status === 402) {
1535
1609
  const reason = await readChallengeReason(chargeResult.challenge);
1536
- const detail = reason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
1610
+ const detail = reason || "credential may be invalid, or check your TEMPO_RPC_URL endpoint";
1537
1611
  report("warn", `MPP credential rejected: ${detail}`);
1538
1612
  return { ok: false, kind: "invalid" };
1539
1613
  }
@@ -2416,11 +2490,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2416
2490
  challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
2417
2491
  } catch (err) {
2418
2492
  const message = errorMessage(err, "Price calculation failed");
2419
- const errorResponse = NextResponse5.json(
2420
- { success: false, error: message },
2421
- { status: errorStatus(err, 500) }
2422
- );
2423
- firePluginResponse(ctx, errorResponse);
2493
+ const responseBody2 = { success: false, error: message };
2494
+ const errorResponse = NextResponse5.json(responseBody2, { status: errorStatus(err, 500) });
2495
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2424
2496
  return errorResponse;
2425
2497
  }
2426
2498
  const extensions = await buildChallengeExtensions(ctx);
@@ -2452,11 +2524,9 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2452
2524
  const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
2453
2525
  ctx.report("critical", message);
2454
2526
  if (strategy.protocol === "x402") {
2455
- const errorResponse = NextResponse5.json(
2456
- { success: false, error: message },
2457
- { status: 500 }
2458
- );
2459
- firePluginResponse(ctx, errorResponse);
2527
+ const responseBody2 = { success: false, error: message };
2528
+ const errorResponse = NextResponse5.json(responseBody2, { status: 500 });
2529
+ firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
2460
2530
  return errorResponse;
2461
2531
  }
2462
2532
  }
@@ -2615,10 +2685,11 @@ function toResponse(rawResult) {
2615
2685
  function errorResult2(error) {
2616
2686
  const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
2617
2687
  const message = error instanceof Error ? error.message : "Internal error";
2688
+ const responseBody = { success: false, error: message };
2618
2689
  return {
2619
2690
  kind: "request",
2620
- response: NextResponse7.json({ success: false, error: message }, { status }),
2621
- rawResult: void 0,
2691
+ response: NextResponse7.json(responseBody, { status }),
2692
+ rawResult: responseBody,
2622
2693
  handlerError: error
2623
2694
  };
2624
2695
  }
@@ -2749,7 +2820,13 @@ async function runDynamicRequestFlow(args) {
2749
2820
  handlerError: result.handlerError
2750
2821
  };
2751
2822
  if (result.response.status >= 400) {
2752
- return finalize(ctx, result.response, result.rawResult, body);
2823
+ return finalize(
2824
+ ctx,
2825
+ result.response,
2826
+ result.rawResult,
2827
+ body,
2828
+ failureFromCause(result.handlerError)
2829
+ );
2753
2830
  }
2754
2831
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2755
2832
  if (beforeErr) return beforeErr;
@@ -2771,6 +2848,9 @@ async function runDynamicRequestFlow(args) {
2771
2848
  }
2772
2849
  });
2773
2850
  }
2851
+ function failureFromCause(cause) {
2852
+ return cause === void 0 ? void 0 : { cause };
2853
+ }
2774
2854
  function computeBilledAmount(routeEntry, result) {
2775
2855
  if (routeEntry.billing === "upto") {
2776
2856
  const total = result.uptoContext?.atomicTotal() ?? 0n;
@@ -2935,7 +3015,13 @@ async function runStaticRequestFlow(args) {
2935
3015
  if (result.response.status >= 400) {
2936
3016
  const settledScope = settleScope;
2937
3017
  await runSettledHandlerError(ctx, settledScope);
2938
- return finalize(ctx, result.response, result.rawResult, body);
3018
+ return finalize(
3019
+ ctx,
3020
+ result.response,
3021
+ result.rawResult,
3022
+ body,
3023
+ failureFromCause2(result.handlerError)
3024
+ );
2939
3025
  }
2940
3026
  return settleAndFinalizeRequest({
2941
3027
  ctx,
@@ -2948,7 +3034,13 @@ async function runStaticRequestFlow(args) {
2948
3034
  });
2949
3035
  }
2950
3036
  if (result.response.status >= 400) {
2951
- return finalize(ctx, result.response, result.rawResult, body);
3037
+ return finalize(
3038
+ ctx,
3039
+ result.response,
3040
+ result.rawResult,
3041
+ body,
3042
+ failureFromCause2(result.handlerError)
3043
+ );
2952
3044
  }
2953
3045
  const beforeErr = await runBeforeSettle(ctx, settleScope);
2954
3046
  if (beforeErr) return beforeErr;
@@ -2969,6 +3061,9 @@ async function runStaticRequestFlow(args) {
2969
3061
  }
2970
3062
  });
2971
3063
  }
3064
+ function failureFromCause2(cause) {
3065
+ return cause === void 0 ? void 0 : { cause };
3066
+ }
2972
3067
 
2973
3068
  // src/pipeline/flows/static/static-paid.ts
2974
3069
  async function runStaticPaidFlow(ctx) {
@@ -4423,7 +4518,7 @@ var envShape = {
4423
4518
  }).optional(),
4424
4519
  TEMPO_RPC_URL: z.string().refine(isUrl, {
4425
4520
  params: { code: "invalid_mpp_rpc_url", ...mpp },
4426
- message: "TEMPO_RPC_URL must be a valid URL \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401."
4521
+ message: "TEMPO_RPC_URL must be a valid URL \u2014 the Tempo JSON-RPC endpoint used for MPP on-chain verification. Optional; defaults to the public DEFAULT_TEMPO_RPC_URL."
4427
4522
  }).optional(),
4428
4523
  MPP_OPERATOR_KEY: z.string().refine(isEvmPrivateKey, {
4429
4524
  params: { code: "invalid_mpp_operator_key", ...mpp },
@@ -4464,14 +4559,6 @@ var EnvInputSchema = z.object(envShape).passthrough().superRefine((env, ctx) =>
4464
4559
  ["MPP_CURRENCY"]
4465
4560
  );
4466
4561
  }
4467
- if (env.TEMPO_RPC_URL === void 0) {
4468
- addIssue(
4469
- ctx,
4470
- { code: "missing_mpp_rpc_url", ...mpp },
4471
- "TEMPO_RPC_URL is required when MPP is enabled \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401.",
4472
- ["TEMPO_RPC_URL"]
4473
- );
4474
- }
4475
4562
  }
4476
4563
  const collision = operatorAddressesCollide(env.MPP_OPERATOR_KEY, env.MPP_FEE_PAYER_KEY);
4477
4564
  if (collision) {
@@ -4603,7 +4690,7 @@ function validateX402Config(config, env) {
4603
4690
  }
4604
4691
  return issues;
4605
4692
  }
4606
- function validateMppConfig(config, env) {
4693
+ function validateMppConfig(config) {
4607
4694
  const m = config.mpp;
4608
4695
  if (!m) {
4609
4696
  return [
@@ -4651,12 +4738,6 @@ function validateMppConfig(config, env) {
4651
4738
  `MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
4652
4739
  );
4653
4740
  }
4654
- if (!m.rpcUrl && !env.TEMPO_RPC_URL) {
4655
- push(
4656
- "missing_mpp_rpc_url",
4657
- "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
4658
- );
4659
- }
4660
4741
  if (m.feePayerKey && !isEvmPrivateKey(m.feePayerKey)) {
4661
4742
  push(
4662
4743
  "invalid_mpp_fee_payer_key",
@@ -4763,7 +4844,7 @@ function routerConfigFromEnv(options) {
4763
4844
  const mppConfig = mppEnabled ? {
4764
4845
  secretKey: env.MPP_SECRET_KEY,
4765
4846
  currency: canonicalizeEvm(env.MPP_CURRENCY),
4766
- rpcUrl: env.TEMPO_RPC_URL,
4847
+ rpcUrl: env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL,
4767
4848
  recipient: payeeAddress,
4768
4849
  ...env.MPP_FEE_PAYER_KEY ? { feePayerKey: env.MPP_FEE_PAYER_KEY } : {},
4769
4850
  ...env.MPP_OPERATOR_KEY ? { operatorKey: env.MPP_OPERATOR_KEY, session: {} } : {}
@@ -4814,7 +4895,7 @@ function getRouterConfigIssues(config, options = {}) {
4814
4895
  });
4815
4896
  }
4816
4897
  if (protocols.includes("x402")) issues.push(...validateX402Config(config, env));
4817
- if (protocols.includes("mpp")) issues.push(...validateMppConfig(config, env));
4898
+ if (protocols.includes("mpp")) issues.push(...validateMppConfig(config));
4818
4899
  return issues;
4819
4900
  }
4820
4901
 
@@ -4886,6 +4967,7 @@ function getMppxStreamingContext(args) {
4886
4967
  }
4887
4968
 
4888
4969
  // src/init/mpp.ts
4970
+ init_constants();
4889
4971
  async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
4890
4972
  if (configError) return { initError: configError };
4891
4973
  if (!config.mpp) return {};
@@ -4894,7 +4976,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
4894
4976
  const { createClient, http } = await import("viem");
4895
4977
  const { tempo: tempoChain } = await import("viem/chains");
4896
4978
  const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
4897
- const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL;
4979
+ const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL;
4898
4980
  const tempoClient = createClient({ chain: tempoChain, transport: http(rpcUrl) });
4899
4981
  const getClient = async () => tempoClient;
4900
4982
  const operatorAccount = config.mpp.operatorKey ? privateKeyToAccount2(config.mpp.operatorKey) : void 0;
@@ -5071,6 +5153,7 @@ export {
5071
5153
  BASE_USDC_ADDRESS,
5072
5154
  BASE_USDC_DECIMALS,
5073
5155
  DEFAULT_SOLANA_FACILITATOR_URL,
5156
+ DEFAULT_TEMPO_RPC_URL,
5074
5157
  HttpError,
5075
5158
  RouterConfigError,
5076
5159
  SOLANA_MAINNET_NETWORK,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "1.10.2",
3
+ "version": "1.10.4",
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": {