@agentcash/router 1.12.0 → 1.13.0

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
@@ -473,6 +473,7 @@ __export(index_exports, {
473
473
  DEFAULT_SOLANA_FACILITATOR_URL: () => DEFAULT_SOLANA_FACILITATOR_URL,
474
474
  DEFAULT_TEMPO_RPC_URL: () => DEFAULT_TEMPO_RPC_URL,
475
475
  HttpError: () => HttpError,
476
+ RouteDefinitionError: () => RouteDefinitionError,
476
477
  RouterConfigError: () => RouterConfigError,
477
478
  SOLANA_MAINNET_NETWORK: () => SOLANA_MAINNET_NETWORK,
478
479
  TEMPO_USDC_ADDRESS: () => TEMPO_USDC_ADDRESS,
@@ -873,6 +874,13 @@ var HttpError = class extends Error {
873
874
  this.name = "HttpError";
874
875
  }
875
876
  };
877
+ var RouteDefinitionError = class extends Error {
878
+ constructor(route, detail) {
879
+ super(`route '${route}': ${detail}`);
880
+ this.route = route;
881
+ this.name = "RouteDefinitionError";
882
+ }
883
+ };
876
884
 
877
885
  // src/auth/agent-identity.ts
878
886
  var PROTOCOL_VERSION = 1;
@@ -1400,6 +1408,23 @@ function multiplyDecimal(decimal, factor) {
1400
1408
  return fracPart ? `${intPart}.${fracPart}` : intPart;
1401
1409
  }
1402
1410
 
1411
+ // src/protocols/detect.ts
1412
+ function hasX402Payment(request) {
1413
+ return Boolean(
1414
+ request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
1415
+ );
1416
+ }
1417
+ function hasMppPayment(request) {
1418
+ const auth = request.headers.get(HEADERS.AUTHORIZATION);
1419
+ return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
1420
+ }
1421
+ function detectProtocol(request) {
1422
+ if (hasX402Payment(request)) return "x402";
1423
+ if (hasMppPayment(request)) return "mpp";
1424
+ if (request.headers.get(HEADERS.SIWX)) return "siwx";
1425
+ return null;
1426
+ }
1427
+
1403
1428
  // src/protocols/mpp/credential.ts
1404
1429
  var import_mppx = require("mppx");
1405
1430
  var import_viem = require("viem");
@@ -1766,10 +1791,7 @@ function settleHashMode(args) {
1766
1791
  // src/protocols/mpp/strategy.ts
1767
1792
  var mppStrategy = {
1768
1793
  protocol: "mpp",
1769
- detects(request) {
1770
- const auth = request.headers.get(HEADERS.AUTHORIZATION);
1771
- return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
1772
- },
1794
+ detects: hasMppPayment,
1773
1795
  preflight(request, _routeEntry) {
1774
1796
  const info = readMppCredential(request);
1775
1797
  if (!info?.sessionAction) return null;
@@ -2204,11 +2226,7 @@ function formatVerifyFailureMessage(failure) {
2204
2226
  }
2205
2227
  var x402Strategy = {
2206
2228
  protocol: "x402",
2207
- detects(request) {
2208
- return Boolean(
2209
- request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
2210
- );
2211
- },
2229
+ detects: hasX402Payment,
2212
2230
  verify: (args) => verifyX402(args),
2213
2231
  settle: (args) => settleX402(args),
2214
2232
  buildChallenge: (args) => buildX402ChallengeContribution(args)
@@ -2355,21 +2373,6 @@ function getAllowedStrategies(allowed) {
2355
2373
  return allowed.map((name) => STRATEGIES[name]);
2356
2374
  }
2357
2375
 
2358
- // src/protocols/detect.ts
2359
- function detectProtocol(request) {
2360
- if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
2361
- return "x402";
2362
- }
2363
- const auth = request.headers.get(HEADERS.AUTHORIZATION);
2364
- if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
2365
- return "mpp";
2366
- }
2367
- if (request.headers.get(HEADERS.SIWX)) {
2368
- return "siwx";
2369
- }
2370
- return null;
2371
- }
2372
-
2373
2376
  // src/pipeline/flows/api-key-only.ts
2374
2377
  async function runApiKeyOnlyFlow(ctx) {
2375
2378
  if (!ctx.routeEntry.apiKeyResolver) {
@@ -2692,6 +2695,61 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2692
2695
  return response;
2693
2696
  }
2694
2697
 
2698
+ // src/pipeline/flows/paid-preamble.ts
2699
+ async function runPaidPreamble(ctx) {
2700
+ const { request, routeEntry, deps, report } = ctx;
2701
+ const apiKeyGate = await runApiKeyGate(ctx);
2702
+ if (!apiKeyGate.ok) return { done: true, response: apiKeyGate.response };
2703
+ const { account } = apiKeyGate;
2704
+ const pricing = selectPricing(routeEntry.pricing, {
2705
+ alert: report,
2706
+ maxPrice: routeEntry.maxPrice,
2707
+ minPrice: routeEntry.minPrice,
2708
+ route: routeEntry.key
2709
+ });
2710
+ const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
2711
+ const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
2712
+ if (!earlyResolution.ok) return { done: true, response: earlyResolution.response };
2713
+ const { earlyBody } = earlyResolution;
2714
+ const siwxFastPath = await trySiwxFastPath(ctx, account);
2715
+ if (siwxFastPath) return { done: true, response: siwxFastPath };
2716
+ if (!incomingStrategy) {
2717
+ const initError = protocolInitError(routeEntry, deps);
2718
+ if (initError) return { done: true, response: fail(ctx, 500, initError) };
2719
+ return { done: true, response: await buildChallengeResponse(ctx, pricing, earlyBody) };
2720
+ }
2721
+ return { done: false, account, pricing, incomingStrategy, earlyBody };
2722
+ }
2723
+ async function runPaidVerify(args) {
2724
+ const { ctx, strategy, pricing, parsedBody, price } = args;
2725
+ const { request, routeEntry, deps, report } = ctx;
2726
+ const verifyOutcome = await strategy.verify({
2727
+ request,
2728
+ body: parsedBody,
2729
+ price,
2730
+ routeEntry,
2731
+ deps,
2732
+ report
2733
+ });
2734
+ if (verifyOutcome.ok === false) {
2735
+ if (verifyOutcome.kind === "config") {
2736
+ return { ok: false, response: fail(ctx, 500, verifyOutcome.message, parsedBody) };
2737
+ }
2738
+ return {
2739
+ ok: false,
2740
+ response: await buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure)
2741
+ };
2742
+ }
2743
+ ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
2744
+ firePaymentVerified(ctx, {
2745
+ protocol: strategy.protocol,
2746
+ payer: verifyOutcome.wallet,
2747
+ amount: price,
2748
+ network: verifyOutcome.payment.network
2749
+ });
2750
+ return { ok: true, verifyOutcome };
2751
+ }
2752
+
2695
2753
  // src/pipeline/flows/dynamic/dynamic-body-and-price.ts
2696
2754
  async function resolveDynamicBodyAndPrice(args) {
2697
2755
  const { ctx, pricing, skipBody } = args;
@@ -3058,27 +3116,10 @@ async function runDynamicStreamFlow(args) {
3058
3116
 
3059
3117
  // src/pipeline/flows/dynamic/dynamic-paid.ts
3060
3118
  async function runDynamicPaidFlow(ctx) {
3061
- const { request, routeEntry, deps, report } = ctx;
3062
- const apiKeyGate = await runApiKeyGate(ctx);
3063
- if (!apiKeyGate.ok) return apiKeyGate.response;
3064
- const { account } = apiKeyGate;
3065
- const pricing = selectPricing(routeEntry.pricing, {
3066
- alert: report,
3067
- maxPrice: routeEntry.maxPrice,
3068
- minPrice: routeEntry.minPrice,
3069
- route: routeEntry.key
3070
- });
3071
- const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
3072
- const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
3073
- if (!earlyResolution.ok) return earlyResolution.response;
3074
- const { earlyBody } = earlyResolution;
3075
- const siwxFastPath = await trySiwxFastPath(ctx, account);
3076
- if (siwxFastPath) return siwxFastPath;
3077
- if (!incomingStrategy) {
3078
- const initError = protocolInitError(routeEntry, deps);
3079
- if (initError) return fail(ctx, 500, initError);
3080
- return buildChallengeResponse(ctx, pricing, earlyBody);
3081
- }
3119
+ const { request, routeEntry } = ctx;
3120
+ const preamble = await runPaidPreamble(ctx);
3121
+ if (preamble.done) return preamble.response;
3122
+ const { account, pricing, incomingStrategy } = preamble;
3082
3123
  const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
3083
3124
  if (skipHandler) {
3084
3125
  return runDynamicChannelMgmtFlow({
@@ -3092,27 +3133,15 @@ async function runDynamicPaidFlow(ctx) {
3092
3133
  const bodyAndPrice = await resolveDynamicBodyAndPrice({ ctx, pricing, skipBody });
3093
3134
  if (!bodyAndPrice.ok) return bodyAndPrice.response;
3094
3135
  const { parsedBody, price } = bodyAndPrice;
3095
- const verifyOutcome = await incomingStrategy.verify({
3096
- request,
3097
- body: parsedBody,
3098
- price,
3099
- routeEntry,
3100
- deps,
3101
- report
3102
- });
3103
- if (verifyOutcome.ok === false) {
3104
- if (verifyOutcome.kind === "config") {
3105
- return fail(ctx, 500, verifyOutcome.message, parsedBody);
3106
- }
3107
- return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
3108
- }
3109
- ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
3110
- firePaymentVerified(ctx, {
3111
- protocol: incomingStrategy.protocol,
3112
- payer: verifyOutcome.wallet,
3113
- amount: price,
3114
- network: verifyOutcome.payment.network
3136
+ const verify = await runPaidVerify({
3137
+ ctx,
3138
+ strategy: incomingStrategy,
3139
+ pricing,
3140
+ parsedBody,
3141
+ price
3115
3142
  });
3143
+ if (!verify.ok) return verify.response;
3144
+ const { verifyOutcome } = verify;
3116
3145
  const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
3117
3146
  switch (result.kind) {
3118
3147
  case "stream":
@@ -3244,51 +3273,21 @@ function failureFromCause2(cause) {
3244
3273
 
3245
3274
  // src/pipeline/flows/static/static-paid.ts
3246
3275
  async function runStaticPaidFlow(ctx) {
3247
- const { request, routeEntry, deps, report } = ctx;
3248
- const apiKeyGate = await runApiKeyGate(ctx);
3249
- if (!apiKeyGate.ok) return apiKeyGate.response;
3250
- const { account } = apiKeyGate;
3251
- const pricing = selectPricing(routeEntry.pricing, {
3252
- alert: report,
3253
- maxPrice: routeEntry.maxPrice,
3254
- minPrice: routeEntry.minPrice,
3255
- route: routeEntry.key
3256
- });
3257
- const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
3258
- const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
3259
- if (!earlyResolution.ok) return earlyResolution.response;
3260
- const { earlyBody } = earlyResolution;
3261
- const siwxFastPath = await trySiwxFastPath(ctx, account);
3262
- if (siwxFastPath) return siwxFastPath;
3263
- if (!incomingStrategy) {
3264
- const initError = protocolInitError(routeEntry, deps);
3265
- if (initError) return fail(ctx, 500, initError);
3266
- return buildChallengeResponse(ctx, pricing, earlyBody);
3267
- }
3276
+ const preamble = await runPaidPreamble(ctx);
3277
+ if (preamble.done) return preamble.response;
3278
+ const { account, pricing, incomingStrategy } = preamble;
3268
3279
  const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
3269
3280
  if (!bodyAndPrice.ok) return bodyAndPrice.response;
3270
3281
  const { parsedBody, price } = bodyAndPrice;
3271
- const verifyOutcome = await incomingStrategy.verify({
3272
- request,
3273
- body: parsedBody,
3274
- price,
3275
- routeEntry,
3276
- deps,
3277
- report
3278
- });
3279
- if (verifyOutcome.ok === false) {
3280
- if (verifyOutcome.kind === "config") {
3281
- return fail(ctx, 500, verifyOutcome.message, parsedBody);
3282
- }
3283
- return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
3284
- }
3285
- ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
3286
- firePaymentVerified(ctx, {
3287
- protocol: incomingStrategy.protocol,
3288
- payer: verifyOutcome.wallet,
3289
- amount: price,
3290
- network: verifyOutcome.payment.network
3282
+ const verify = await runPaidVerify({
3283
+ ctx,
3284
+ strategy: incomingStrategy,
3285
+ pricing,
3286
+ parsedBody,
3287
+ price
3291
3288
  });
3289
+ if (!verify.ok) return verify.response;
3290
+ const { verifyOutcome } = verify;
3292
3291
  const result = await invokePaidStatic(
3293
3292
  ctx,
3294
3293
  verifyOutcome.wallet,
@@ -3780,55 +3779,35 @@ var RouteBuilder = class _RouteBuilder {
3780
3779
  return next;
3781
3780
  }
3782
3781
  paid(arg, options) {
3783
- return this.applyPaid(normalizePaidArg(this.#s.key, arg, options), "paid");
3782
+ const self = this;
3783
+ return self.applyPaid(normalizePaidArg(self.#s.key, arg, options), "paid");
3784
3784
  }
3785
- /**
3786
- * x402-only handler-computed billing. The handler receives `charge(amount)`
3787
- * and the request settles once for the accumulated total, capped at
3788
- * `maxPrice`. Requires an `'upto'` accept on at least one configured network.
3789
- * Pass a bare string as sugar for `{ maxPrice }`.
3790
- *
3791
- * @example
3792
- * ```ts
3793
- * router.route('llm')
3794
- * .upTo('0.05')
3795
- * .body(schema)
3796
- * .handler(async ({ body, charge }) => { await charge('0.001'); ... });
3797
- * ```
3798
- */
3799
3785
  upTo(arg) {
3800
- return this.applyPaid(normalizeUpToArg(this.#s.key, arg), "upTo");
3786
+ const self = this;
3787
+ return self.applyPaid(normalizeUpToArg(self.#s.key, arg), "upTo");
3801
3788
  }
3802
- /**
3803
- * MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
3804
- * `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
3805
- * `maxPrice`. Requires `RouterConfig.mpp.session`.
3806
- *
3807
- * @example
3808
- * ```ts
3809
- * router.route('llm/stream')
3810
- * .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
3811
- * .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
3812
- * ```
3813
- */
3814
3789
  metered(options) {
3815
- return this.applyPaid(normalizeMeteredArg(this.#s.key, options), "metered");
3790
+ const self = this;
3791
+ return self.applyPaid(normalizeMeteredArg(self.#s.key, options), "metered");
3816
3792
  }
3817
3793
  applyPaid(normalized, method) {
3818
3794
  const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
3819
3795
  if (this.#s.authMode === "unprotected") {
3820
- throw new Error(
3821
- `route '${this.#s.key}': Cannot combine .unprotected() and .${method}() on the same route.`
3796
+ throw new RouteDefinitionError(
3797
+ this.#s.key,
3798
+ `Cannot combine .unprotected() and .${method}() on the same route.`
3822
3799
  );
3823
3800
  }
3824
3801
  if (this.#s.pricing !== void 0) {
3825
- throw new Error(
3826
- `route '${this.#s.key}': Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
3802
+ throw new RouteDefinitionError(
3803
+ this.#s.key,
3804
+ `Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
3827
3805
  );
3828
3806
  }
3829
3807
  if (this.#s.siwxEnabled && billing === "metered") {
3830
- throw new Error(
3831
- `route '${this.#s.key}': Cannot combine .siwx() and .metered() \u2014 per-tick MPP billing has no entitlement model. Use .paid() or .upTo() with .siwx(), or drop .siwx() for metered routes.`
3808
+ throw new RouteDefinitionError(
3809
+ this.#s.key,
3810
+ `Cannot combine .siwx() and .metered() \u2014 per-tick MPP billing has no entitlement model. Use .paid() or .upTo() with .siwx(), or drop .siwx() for metered routes.`
3832
3811
  );
3833
3812
  }
3834
3813
  const next = this.fork();
@@ -3836,15 +3815,17 @@ var RouteBuilder = class _RouteBuilder {
3836
3815
  next.#s.pricing = pricing;
3837
3816
  if (billing === "upto") {
3838
3817
  if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
3839
- throw new Error(
3840
- `route '${this.#s.key}': .upTo() is x402-only \u2014 remove the conflicting protocols override.`
3818
+ throw new RouteDefinitionError(
3819
+ this.#s.key,
3820
+ `.upTo() is x402-only \u2014 remove the conflicting protocols override.`
3841
3821
  );
3842
3822
  }
3843
3823
  next.#s.protocols = ["x402"];
3844
3824
  } else if (billing === "metered") {
3845
3825
  if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
3846
- throw new Error(
3847
- `route '${this.#s.key}': .metered() is MPP-only \u2014 remove the conflicting protocols override.`
3826
+ throw new RouteDefinitionError(
3827
+ this.#s.key,
3828
+ `.metered() is MPP-only \u2014 remove the conflicting protocols override.`
3848
3829
  );
3849
3830
  }
3850
3831
  next.#s.protocols = ["mpp"];
@@ -3868,73 +3849,69 @@ var RouteBuilder = class _RouteBuilder {
3868
3849
  if (typeof pricing === "object" && "tiers" in pricing) {
3869
3850
  for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
3870
3851
  if (!tierKey) {
3871
- throw new Error(`route '${this.#s.key}': tier key cannot be empty`);
3852
+ throw new RouteDefinitionError(this.#s.key, `tier key cannot be empty`);
3872
3853
  }
3873
3854
  if (!isPositiveDecimal(tierConfig.price)) {
3874
- throw new Error(
3875
- `route '${this.#s.key}': tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
3855
+ throw new RouteDefinitionError(
3856
+ this.#s.key,
3857
+ `tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
3876
3858
  );
3877
3859
  }
3878
3860
  }
3879
3861
  }
3880
3862
  if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
3881
- throw new Error(
3882
- `route '${this.#s.key}': price '${pricing}' must be a positive decimal string`
3863
+ throw new RouteDefinitionError(
3864
+ this.#s.key,
3865
+ `price '${pricing}' must be a positive decimal string`
3883
3866
  );
3884
3867
  }
3885
3868
  if (typeof pricing === "function" && next.#s.maxPrice === void 0) {
3886
- throw new Error(
3887
- `route '${this.#s.key}': dynamic pricing requires maxPrice \u2014 without it, bare probes would advertise a $0 challenge`
3869
+ throw new RouteDefinitionError(
3870
+ this.#s.key,
3871
+ `dynamic pricing requires maxPrice \u2014 without it, bare probes would advertise a $0 challenge`
3888
3872
  );
3889
3873
  }
3890
3874
  if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
3891
- throw new Error(
3892
- `route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
3875
+ throw new RouteDefinitionError(
3876
+ this.#s.key,
3877
+ `maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
3893
3878
  );
3894
3879
  }
3895
3880
  if (next.#s.minPrice !== void 0 && !isPositiveDecimal(next.#s.minPrice)) {
3896
- throw new Error(
3897
- `route '${this.#s.key}': minPrice '${next.#s.minPrice}' must be a positive decimal string`
3881
+ throw new RouteDefinitionError(
3882
+ this.#s.key,
3883
+ `minPrice '${next.#s.minPrice}' must be a positive decimal string`
3898
3884
  );
3899
3885
  }
3900
3886
  if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
3901
- throw new Error(
3902
- `route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
3887
+ throw new RouteDefinitionError(
3888
+ this.#s.key,
3889
+ `tickCost '${next.#s.tickCost}' must be a positive decimal string`
3903
3890
  );
3904
3891
  }
3905
3892
  return next;
3906
3893
  }
3907
- /**
3908
- * Require Sign-In-with-X wallet identity on this route — clients prove
3909
- * control of a wallet via a signed challenge. Composes with `.paid()` and
3910
- * `.upTo()` for pay-once-then-replay: the first request settles normally,
3911
- * subsequent requests with a valid SIWX signature for the same wallet skip
3912
- * payment (on `.upTo()`, `charge(amount)` becomes a no-op on the replay).
3913
- * Mutually exclusive with `.metered()`.
3914
- *
3915
- * @example
3916
- * ```ts
3917
- * router.route('profile').siwx().handler(async ({ wallet }) => getProfile(wallet));
3918
- * router.route('inbox').paid('0.01').siwx().handler(async ({ wallet }) => getInbox(wallet));
3919
- * ```
3920
- */
3921
3894
  siwx() {
3922
- if (this.#s.authMode === "unprotected") {
3923
- throw new Error(
3924
- `route '${this.#s.key}': Cannot combine .unprotected() and .siwx() on the same route.`
3895
+ const self = this;
3896
+ if (self.#s.authMode === "unprotected") {
3897
+ throw new RouteDefinitionError(
3898
+ self.#s.key,
3899
+ `Cannot combine .unprotected() and .siwx() on the same route.`
3925
3900
  );
3926
3901
  }
3927
- if (this.#s.apiKeyResolver) {
3928
- throw new Error(
3929
- `route '${this.#s.key}': Combining .siwx() and .apiKey() is not supported on the same route.`
3902
+ if (self.#s.apiKeyResolver) {
3903
+ throw new RouteDefinitionError(
3904
+ self.#s.key,
3905
+ `Combining .siwx() and .apiKey() is not supported on the same route.`
3930
3906
  );
3931
3907
  }
3932
- if (this.#s.billing === "metered") {
3933
- throw new Error(
3934
- `route '${this.#s.key}': Cannot combine .metered() and .siwx() \u2014 per-tick MPP billing has no entitlement model. Use .paid() or .upTo() with .siwx(), or drop .siwx() for metered routes.`
3908
+ if (self.#s.billing === "metered") {
3909
+ throw new RouteDefinitionError(
3910
+ self.#s.key,
3911
+ `Cannot combine .metered() and .siwx() \u2014 per-tick MPP billing has no entitlement model. Use .paid() or .upTo() with .siwx(), or drop .siwx() for metered routes.`
3935
3912
  );
3936
3913
  }
3937
- const next = this.fork();
3914
+ const next = self.fork();
3938
3915
  next.#s.siwxEnabled = true;
3939
3916
  if (next.#s.authMode === "paid" || next.#s.pricing) {
3940
3917
  next.#s.authMode = "paid";
@@ -3945,59 +3922,48 @@ var RouteBuilder = class _RouteBuilder {
3945
3922
  next.#s.protocols = [];
3946
3923
  return next;
3947
3924
  }
3948
- /**
3949
- * Require an `X-API-Key` header (or `Authorization: Bearer <key>`); the
3950
- * resolver returns the account record, or `null` for 401. Composes with
3951
- * `.paid()` — key is checked first, payment second.
3952
- *
3953
- * @example
3954
- * ```ts
3955
- * router
3956
- * .route('admin/users')
3957
- * .apiKey(async (key) => db.admin.findByKey(key))
3958
- * .handler(async ({ account }) => db.user.list(account.orgId));
3959
- * ```
3960
- */
3961
3925
  apiKey(resolver) {
3962
- if (this.#s.siwxEnabled) {
3963
- throw new Error(
3964
- `route '${this.#s.key}': Combining .apiKey() and .siwx() is not supported on the same route.`
3926
+ const self = this;
3927
+ if (self.#s.authMode === "unprotected") {
3928
+ throw new RouteDefinitionError(
3929
+ self.#s.key,
3930
+ `Cannot combine .unprotected() and .apiKey() on the same route.`
3965
3931
  );
3966
3932
  }
3967
- const next = this.fork();
3933
+ if (self.#s.siwxEnabled) {
3934
+ throw new RouteDefinitionError(
3935
+ self.#s.key,
3936
+ `Combining .apiKey() and .siwx() is not supported on the same route.`
3937
+ );
3938
+ }
3939
+ const next = self.fork();
3968
3940
  next.#s.authMode = "apiKey";
3969
3941
  next.#s.apiKeyResolver = resolver;
3970
3942
  return next;
3971
3943
  }
3972
- /**
3973
- * Mark the route as public — no auth, no payment, no SIWX. The handler
3974
- * receives `null` for `wallet`, `payment`, and `account`.
3975
- *
3976
- * @example
3977
- * ```ts
3978
- * router.route('health').unprotected().handler(async () => ({ status: 'ok' }));
3979
- * ```
3980
- */
3981
3944
  unprotected() {
3982
- if (this.#s.authMode && this.#s.authMode !== "unprotected") {
3983
- throw new Error(
3984
- `route '${this.#s.key}': Cannot combine .unprotected() and .${this.#s.authMode}() on the same route.`
3945
+ const self = this;
3946
+ if (self.#s.authMode && self.#s.authMode !== "unprotected") {
3947
+ throw new RouteDefinitionError(
3948
+ self.#s.key,
3949
+ `Cannot combine .unprotected() and .${self.#s.authMode}() on the same route.`
3985
3950
  );
3986
3951
  }
3987
- if (this.#s.pricing) {
3988
- throw new Error(
3989
- `route '${this.#s.key}': Cannot combine .unprotected() and .paid() on the same route.`
3952
+ if (self.#s.pricing) {
3953
+ throw new RouteDefinitionError(
3954
+ self.#s.key,
3955
+ `Cannot combine .unprotected() and .paid() on the same route.`
3990
3956
  );
3991
3957
  }
3992
- const next = this.fork();
3958
+ const next = self.fork();
3993
3959
  next.#s.authMode = "unprotected";
3994
3960
  next.#s.protocols = [];
3995
3961
  return next;
3996
3962
  }
3997
3963
  /**
3998
3964
  * Tag the route with an upstream provider for discovery and provider-side
3999
- * monitoring. The provider name and config surface in `well-known` and
4000
- * OpenAPI output.
3965
+ * monitoring. The provider name and config surface in OpenAPI discovery
3966
+ * output.
4001
3967
  *
4002
3968
  * @example
4003
3969
  * ```ts
@@ -4094,8 +4060,8 @@ var RouteBuilder = class _RouteBuilder {
4094
4060
  return next;
4095
4061
  }
4096
4062
  /**
4097
- * Set a human-readable summary of the route. Surfaces in OpenAPI,
4098
- * `well-known`, and `llms.txt` discovery output.
4063
+ * Set a human-readable summary of the route. Surfaces in OpenAPI and
4064
+ * `llms.txt` discovery output.
4099
4065
  *
4100
4066
  * @example
4101
4067
  * ```ts
@@ -4231,38 +4197,46 @@ var RouteBuilder = class _RouteBuilder {
4231
4197
  }
4232
4198
  register(handlerFn, streaming) {
4233
4199
  if (!this.#s.authMode) {
4234
- throw new Error(
4235
- `route '${this.#s.key}': Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
4200
+ throw new RouteDefinitionError(
4201
+ this.#s.key,
4202
+ `Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
4236
4203
  );
4237
4204
  }
4238
4205
  if (this.#s.validateFn && !this.#s.bodySchema) {
4239
- throw new Error(
4240
- `route '${this.#s.key}': .validate() requires .body() \u2014 validation runs on parsed body`
4206
+ throw new RouteDefinitionError(
4207
+ this.#s.key,
4208
+ `.validate() requires .body() \u2014 validation runs on parsed body`
4241
4209
  );
4242
4210
  }
4243
4211
  if (this.#s.settlement && !this.#s.pricing) {
4244
- throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
4212
+ throw new RouteDefinitionError(this.#s.key, `.settlement() requires a paid route`);
4245
4213
  }
4246
4214
  if (this.#s.mppInfo?.settleBeforeHandler) {
4247
4215
  if (!this.#s.pricing) {
4248
- throw new Error(`route '${this.#s.key}': mpp.settleBeforeHandler requires a paid route`);
4216
+ throw new RouteDefinitionError(
4217
+ this.#s.key,
4218
+ `mpp.settleBeforeHandler requires a paid route`
4219
+ );
4249
4220
  }
4250
4221
  if (this.#s.billing !== "exact") {
4251
- throw new Error(
4252
- `route '${this.#s.key}': mpp.settleBeforeHandler is only supported on .paid() routes`
4222
+ throw new RouteDefinitionError(
4223
+ this.#s.key,
4224
+ `mpp.settleBeforeHandler is only supported on .paid() routes`
4253
4225
  );
4254
4226
  }
4255
4227
  if (this.#s.settlement?.beforeSettle) {
4256
- throw new Error(
4257
- `route '${this.#s.key}': mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
4228
+ throw new RouteDefinitionError(
4229
+ this.#s.key,
4230
+ `mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
4258
4231
  );
4259
4232
  }
4260
4233
  }
4261
4234
  if (this.#s.billing === "upto") {
4262
4235
  const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
4263
4236
  if (!hasUpto) {
4264
- throw new Error(
4265
- `route '${this.#s.key}': .upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
4237
+ throw new RouteDefinitionError(
4238
+ this.#s.key,
4239
+ `.upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
4266
4240
  );
4267
4241
  }
4268
4242
  }
@@ -4271,26 +4245,30 @@ var RouteBuilder = class _RouteBuilder {
4271
4245
  (accept) => (accept.scheme ?? "exact") !== "upto"
4272
4246
  );
4273
4247
  if (!hasExact) {
4274
- throw new Error(
4275
- `route '${this.#s.key}': .paid() needs a non-'upto' x402 accept \u2014 an 'upto'-only accept list cannot serve a fixed-price route. Add { scheme: 'exact', network } to RouterConfig.x402.accepts, or use .upTo() for handler-computed billing.`
4248
+ throw new RouteDefinitionError(
4249
+ this.#s.key,
4250
+ `.paid() needs a non-'upto' x402 accept \u2014 an 'upto'-only accept list cannot serve a fixed-price route. Add { scheme: 'exact', network } to RouterConfig.x402.accepts, or use .upTo() for handler-computed billing.`
4276
4251
  );
4277
4252
  }
4278
4253
  }
4279
4254
  if (this.#s.billing === "metered") {
4280
4255
  if (!this.#s.deps.mppSessionConfig) {
4281
- throw new Error(
4282
- `route '${this.#s.key}': .metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
4256
+ throw new RouteDefinitionError(
4257
+ this.#s.key,
4258
+ `.metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
4283
4259
  );
4284
4260
  }
4285
4261
  }
4286
4262
  if (streaming && this.#s.billing !== "metered") {
4287
- throw new Error(
4288
- `route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
4263
+ throw new RouteDefinitionError(
4264
+ this.#s.key,
4265
+ `.stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
4289
4266
  );
4290
4267
  }
4291
4268
  if (this.#s.description !== void 0 && this.#s.description.length > MAX_X402_DESCRIPTION_LENGTH && this.#s.pricing !== void 0 && this.#s.protocols.includes("x402")) {
4292
- throw new Error(
4293
- `route '${this.#s.key}': .description() is ${this.#s.description.length} chars; must be \u2264 ${MAX_X402_DESCRIPTION_LENGTH} chars \u2014 the CDP x402 facilitator rejects payments whose 402 challenge resource.description exceeds ~500 chars.`
4269
+ throw new RouteDefinitionError(
4270
+ this.#s.key,
4271
+ `.description() is ${this.#s.description.length} chars; must be \u2264 ${MAX_X402_DESCRIPTION_LENGTH} chars \u2014 the CDP x402 facilitator rejects payments whose 402 challenge resource.description exceeds ~500 chars.`
4294
4272
  );
4295
4273
  }
4296
4274
  validateExamples(
@@ -4358,14 +4336,15 @@ function normalizePaidArg(routeKey, arg, options) {
4358
4336
  if ("price" in arg && typeof arg.price === "string") {
4359
4337
  return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
4360
4338
  }
4361
- throw new Error(
4362
- `route '${routeKey}': .paid() requires one of: a price string, a (body) => string function, { price }, or { field, tiers }. For handler-computed billing use .upTo(); for per-tick billing use .metered().`
4339
+ throw new RouteDefinitionError(
4340
+ routeKey,
4341
+ `.paid() requires one of: a price string, a (body) => string function, { price }, or { field, tiers }. For handler-computed billing use .upTo(); for per-tick billing use .metered().`
4363
4342
  );
4364
4343
  }
4365
4344
  function normalizeUpToArg(routeKey, arg) {
4366
4345
  const options = typeof arg === "string" ? { maxPrice: arg } : arg;
4367
4346
  if (!options.maxPrice) {
4368
- throw new Error(`route '${routeKey}': .upTo() requires maxPrice`);
4347
+ throw new RouteDefinitionError(routeKey, `.upTo() requires maxPrice`);
4369
4348
  }
4370
4349
  return {
4371
4350
  pricing: options.maxPrice,
@@ -4377,10 +4356,10 @@ function normalizeUpToArg(routeKey, arg) {
4377
4356
  }
4378
4357
  function normalizeMeteredArg(routeKey, options) {
4379
4358
  if (!options.maxPrice) {
4380
- throw new Error(`route '${routeKey}': .metered() requires maxPrice`);
4359
+ throw new RouteDefinitionError(routeKey, `.metered() requires maxPrice`);
4381
4360
  }
4382
4361
  if (!options.tickCost) {
4383
- throw new Error(`route '${routeKey}': .metered() requires tickCost`);
4362
+ throw new RouteDefinitionError(routeKey, `.metered() requires tickCost`);
4384
4363
  }
4385
4364
  return {
4386
4365
  pricing: options.maxPrice,
@@ -4687,9 +4666,9 @@ function createNotFoundHandler(baseUrl) {
4687
4666
  code: "route_not_found",
4688
4667
  error: "Route not found. Rediscover this origin and retry with the current discovery document.",
4689
4668
  requestedUrl: request.url,
4669
+ // The deprecated /.well-known/x402 surface is intentionally not advertised.
4690
4670
  discovery: {
4691
4671
  openapi: `${normalizedBase}/openapi.json`,
4692
- wellKnown: `${normalizedBase}/.well-known/x402`,
4693
4672
  llmsTxt: `${normalizedBase}/llms.txt`
4694
4673
  }
4695
4674
  },
@@ -4723,6 +4702,7 @@ function formatRouterConfigIssues(issues) {
4723
4702
 
4724
4703
  // src/config/schema.ts
4725
4704
  var import_zod = require("zod");
4705
+ init_accepts();
4726
4706
  init_constants();
4727
4707
 
4728
4708
  // src/config/utils.ts
@@ -4911,18 +4891,8 @@ function deriveBaseUrlEnv(env) {
4911
4891
  BASE_URL: vercelBaseUrl
4912
4892
  };
4913
4893
  }
4914
- function getConfiguredX402Accepts2(config) {
4915
- if (config.x402?.accepts?.length) return [...config.x402.accepts];
4916
- return [
4917
- {
4918
- scheme: "exact",
4919
- network: config.network ?? BASE_MAINNET_NETWORK,
4920
- payTo: config.payeeAddress
4921
- }
4922
- ];
4923
- }
4924
4894
  function validateX402Config(config, env) {
4925
- const accepts = getConfiguredX402Accepts2(config);
4895
+ const accepts = getConfiguredX402Accepts(config);
4926
4896
  const issues = [];
4927
4897
  const push = (code, message) => issues.push({ code, protocol: "x402", message });
4928
4898
  if (accepts.length === 0) {
@@ -5223,7 +5193,8 @@ function getMppxRequestContext(args) {
5223
5193
  recipient: mppConfig.recipient ?? payeeAddress,
5224
5194
  getClient,
5225
5195
  ...feePayerAccount ? { feePayer: feePayerAccount } : {},
5226
- ...resolvedStore ? { store: resolvedStore } : {}
5196
+ ...resolvedStore ? { store: resolvedStore } : {},
5197
+ ...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {}
5227
5198
  }),
5228
5199
  ...sessionEnabled ? [
5229
5200
  tempo.session({
@@ -5261,7 +5232,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5261
5232
  try {
5262
5233
  const { Mppx, tempo } = await import("mppx/server");
5263
5234
  const { createClient, http } = await import("viem");
5264
- const { tempo: tempoChain } = await import("viem/chains");
5235
+ const { tempo: tempoChain } = await import("viem/tempo/chains");
5265
5236
  const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
5266
5237
  const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL;
5267
5238
  const tempoClient = createClient({ chain: tempoChain, transport: http(rpcUrl) });
@@ -5279,7 +5250,9 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5279
5250
  getClient,
5280
5251
  ...operatorAccount ? { account: operatorAccount } : {},
5281
5252
  ...feePayerAccount ? { feePayer: feePayerAccount } : {},
5282
- ...resolvedStore ? { store: resolvedStore } : {}
5253
+ ...resolvedStore ? { store: resolvedStore } : {},
5254
+ ...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {},
5255
+ ...mppConfig.session?.settlementSchedule ? { settlementSchedule: mppConfig.session.settlementSchedule } : {}
5283
5256
  };
5284
5257
  const mppxArgs = {
5285
5258
  Mppx,
@@ -5295,6 +5268,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5295
5268
  };
5296
5269
  const primary = getMppxRequestContext(mppxArgs);
5297
5270
  const streaming = getMppxStreamingContext(mppxArgs);
5271
+ subscribePaymentFailedAlerts(config.plugin, [primary, streaming]);
5298
5272
  const mppx = {
5299
5273
  charge: primary.charge,
5300
5274
  ...primary.session ? { sessionRequest: primary.session } : {},
@@ -5305,6 +5279,41 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5305
5279
  return { initError: err instanceof Error ? err.message : String(err) };
5306
5280
  }
5307
5281
  }
5282
+ function subscribePaymentFailedAlerts(plugin, instances) {
5283
+ if (!plugin?.onAlert) return;
5284
+ const ctx = {
5285
+ requestId: "mppx",
5286
+ route: "mpp",
5287
+ walletAddress: null,
5288
+ clientId: null,
5289
+ sessionId: null,
5290
+ verifiedWallet: null,
5291
+ setVerifiedWallet() {
5292
+ }
5293
+ };
5294
+ for (const instance of instances) {
5295
+ const events = instance;
5296
+ if (typeof events?.onPaymentFailed !== "function") continue;
5297
+ events.onPaymentFailed((payload) => {
5298
+ try {
5299
+ const error = payload?.error ?? {};
5300
+ firePluginHook(plugin, "onAlert", ctx, {
5301
+ level: (error.status ?? 402) >= 500 ? "error" : "warn",
5302
+ message: `MPP payment failed: ${error.message ?? error.name ?? "unknown error"}`,
5303
+ route: "mpp",
5304
+ meta: {
5305
+ ...payload?.method ? { method: `${payload.method.name}/${payload.method.intent}` } : {},
5306
+ ...error.type ? { errorType: error.type } : {},
5307
+ ...error.status !== void 0 ? { status: error.status } : {},
5308
+ ...error.hint ? { hint: error.hint } : {},
5309
+ ...payload?.credential?.source ? { payer: payload.credential.source } : {}
5310
+ }
5311
+ });
5312
+ } catch {
5313
+ }
5314
+ });
5315
+ }
5316
+ }
5308
5317
 
5309
5318
  // src/index.ts
5310
5319
  init_constants();
@@ -5448,6 +5457,7 @@ function createRouterFromEnv(options) {
5448
5457
  DEFAULT_SOLANA_FACILITATOR_URL,
5449
5458
  DEFAULT_TEMPO_RPC_URL,
5450
5459
  HttpError,
5460
+ RouteDefinitionError,
5451
5461
  RouterConfigError,
5452
5462
  SOLANA_MAINNET_NETWORK,
5453
5463
  TEMPO_USDC_ADDRESS,