@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.js CHANGED
@@ -835,6 +835,13 @@ var HttpError = class extends Error {
835
835
  this.name = "HttpError";
836
836
  }
837
837
  };
838
+ var RouteDefinitionError = class extends Error {
839
+ constructor(route, detail) {
840
+ super(`route '${route}': ${detail}`);
841
+ this.route = route;
842
+ this.name = "RouteDefinitionError";
843
+ }
844
+ };
838
845
 
839
846
  // src/auth/agent-identity.ts
840
847
  var PROTOCOL_VERSION = 1;
@@ -1362,6 +1369,23 @@ function multiplyDecimal(decimal, factor) {
1362
1369
  return fracPart ? `${intPart}.${fracPart}` : intPart;
1363
1370
  }
1364
1371
 
1372
+ // src/protocols/detect.ts
1373
+ function hasX402Payment(request) {
1374
+ return Boolean(
1375
+ request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
1376
+ );
1377
+ }
1378
+ function hasMppPayment(request) {
1379
+ const auth = request.headers.get(HEADERS.AUTHORIZATION);
1380
+ return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
1381
+ }
1382
+ function detectProtocol(request) {
1383
+ if (hasX402Payment(request)) return "x402";
1384
+ if (hasMppPayment(request)) return "mpp";
1385
+ if (request.headers.get(HEADERS.SIWX)) return "siwx";
1386
+ return null;
1387
+ }
1388
+
1365
1389
  // src/protocols/mpp/credential.ts
1366
1390
  import { Credential } from "mppx";
1367
1391
  import { getAddress, isAddress } from "viem";
@@ -1728,10 +1752,7 @@ function settleHashMode(args) {
1728
1752
  // src/protocols/mpp/strategy.ts
1729
1753
  var mppStrategy = {
1730
1754
  protocol: "mpp",
1731
- detects(request) {
1732
- const auth = request.headers.get(HEADERS.AUTHORIZATION);
1733
- return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
1734
- },
1755
+ detects: hasMppPayment,
1735
1756
  preflight(request, _routeEntry) {
1736
1757
  const info = readMppCredential(request);
1737
1758
  if (!info?.sessionAction) return null;
@@ -2166,11 +2187,7 @@ function formatVerifyFailureMessage(failure) {
2166
2187
  }
2167
2188
  var x402Strategy = {
2168
2189
  protocol: "x402",
2169
- detects(request) {
2170
- return Boolean(
2171
- request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
2172
- );
2173
- },
2190
+ detects: hasX402Payment,
2174
2191
  verify: (args) => verifyX402(args),
2175
2192
  settle: (args) => settleX402(args),
2176
2193
  buildChallenge: (args) => buildX402ChallengeContribution(args)
@@ -2317,21 +2334,6 @@ function getAllowedStrategies(allowed) {
2317
2334
  return allowed.map((name) => STRATEGIES[name]);
2318
2335
  }
2319
2336
 
2320
- // src/protocols/detect.ts
2321
- function detectProtocol(request) {
2322
- if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
2323
- return "x402";
2324
- }
2325
- const auth = request.headers.get(HEADERS.AUTHORIZATION);
2326
- if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
2327
- return "mpp";
2328
- }
2329
- if (request.headers.get(HEADERS.SIWX)) {
2330
- return "siwx";
2331
- }
2332
- return null;
2333
- }
2334
-
2335
2337
  // src/pipeline/flows/api-key-only.ts
2336
2338
  async function runApiKeyOnlyFlow(ctx) {
2337
2339
  if (!ctx.routeEntry.apiKeyResolver) {
@@ -2654,6 +2656,61 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
2654
2656
  return response;
2655
2657
  }
2656
2658
 
2659
+ // src/pipeline/flows/paid-preamble.ts
2660
+ async function runPaidPreamble(ctx) {
2661
+ const { request, routeEntry, deps, report } = ctx;
2662
+ const apiKeyGate = await runApiKeyGate(ctx);
2663
+ if (!apiKeyGate.ok) return { done: true, response: apiKeyGate.response };
2664
+ const { account } = apiKeyGate;
2665
+ const pricing = selectPricing(routeEntry.pricing, {
2666
+ alert: report,
2667
+ maxPrice: routeEntry.maxPrice,
2668
+ minPrice: routeEntry.minPrice,
2669
+ route: routeEntry.key
2670
+ });
2671
+ const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
2672
+ const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
2673
+ if (!earlyResolution.ok) return { done: true, response: earlyResolution.response };
2674
+ const { earlyBody } = earlyResolution;
2675
+ const siwxFastPath = await trySiwxFastPath(ctx, account);
2676
+ if (siwxFastPath) return { done: true, response: siwxFastPath };
2677
+ if (!incomingStrategy) {
2678
+ const initError = protocolInitError(routeEntry, deps);
2679
+ if (initError) return { done: true, response: fail(ctx, 500, initError) };
2680
+ return { done: true, response: await buildChallengeResponse(ctx, pricing, earlyBody) };
2681
+ }
2682
+ return { done: false, account, pricing, incomingStrategy, earlyBody };
2683
+ }
2684
+ async function runPaidVerify(args) {
2685
+ const { ctx, strategy, pricing, parsedBody, price } = args;
2686
+ const { request, routeEntry, deps, report } = ctx;
2687
+ const verifyOutcome = await strategy.verify({
2688
+ request,
2689
+ body: parsedBody,
2690
+ price,
2691
+ routeEntry,
2692
+ deps,
2693
+ report
2694
+ });
2695
+ if (verifyOutcome.ok === false) {
2696
+ if (verifyOutcome.kind === "config") {
2697
+ return { ok: false, response: fail(ctx, 500, verifyOutcome.message, parsedBody) };
2698
+ }
2699
+ return {
2700
+ ok: false,
2701
+ response: await buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure)
2702
+ };
2703
+ }
2704
+ ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
2705
+ firePaymentVerified(ctx, {
2706
+ protocol: strategy.protocol,
2707
+ payer: verifyOutcome.wallet,
2708
+ amount: price,
2709
+ network: verifyOutcome.payment.network
2710
+ });
2711
+ return { ok: true, verifyOutcome };
2712
+ }
2713
+
2657
2714
  // src/pipeline/flows/dynamic/dynamic-body-and-price.ts
2658
2715
  async function resolveDynamicBodyAndPrice(args) {
2659
2716
  const { ctx, pricing, skipBody } = args;
@@ -3020,27 +3077,10 @@ async function runDynamicStreamFlow(args) {
3020
3077
 
3021
3078
  // src/pipeline/flows/dynamic/dynamic-paid.ts
3022
3079
  async function runDynamicPaidFlow(ctx) {
3023
- const { request, routeEntry, deps, report } = ctx;
3024
- const apiKeyGate = await runApiKeyGate(ctx);
3025
- if (!apiKeyGate.ok) return apiKeyGate.response;
3026
- const { account } = apiKeyGate;
3027
- const pricing = selectPricing(routeEntry.pricing, {
3028
- alert: report,
3029
- maxPrice: routeEntry.maxPrice,
3030
- minPrice: routeEntry.minPrice,
3031
- route: routeEntry.key
3032
- });
3033
- const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
3034
- const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
3035
- if (!earlyResolution.ok) return earlyResolution.response;
3036
- const { earlyBody } = earlyResolution;
3037
- const siwxFastPath = await trySiwxFastPath(ctx, account);
3038
- if (siwxFastPath) return siwxFastPath;
3039
- if (!incomingStrategy) {
3040
- const initError = protocolInitError(routeEntry, deps);
3041
- if (initError) return fail(ctx, 500, initError);
3042
- return buildChallengeResponse(ctx, pricing, earlyBody);
3043
- }
3080
+ const { request, routeEntry } = ctx;
3081
+ const preamble = await runPaidPreamble(ctx);
3082
+ if (preamble.done) return preamble.response;
3083
+ const { account, pricing, incomingStrategy } = preamble;
3044
3084
  const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
3045
3085
  if (skipHandler) {
3046
3086
  return runDynamicChannelMgmtFlow({
@@ -3054,27 +3094,15 @@ async function runDynamicPaidFlow(ctx) {
3054
3094
  const bodyAndPrice = await resolveDynamicBodyAndPrice({ ctx, pricing, skipBody });
3055
3095
  if (!bodyAndPrice.ok) return bodyAndPrice.response;
3056
3096
  const { parsedBody, price } = bodyAndPrice;
3057
- const verifyOutcome = await incomingStrategy.verify({
3058
- request,
3059
- body: parsedBody,
3060
- price,
3061
- routeEntry,
3062
- deps,
3063
- report
3064
- });
3065
- if (verifyOutcome.ok === false) {
3066
- if (verifyOutcome.kind === "config") {
3067
- return fail(ctx, 500, verifyOutcome.message, parsedBody);
3068
- }
3069
- return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
3070
- }
3071
- ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
3072
- firePaymentVerified(ctx, {
3073
- protocol: incomingStrategy.protocol,
3074
- payer: verifyOutcome.wallet,
3075
- amount: price,
3076
- network: verifyOutcome.payment.network
3097
+ const verify = await runPaidVerify({
3098
+ ctx,
3099
+ strategy: incomingStrategy,
3100
+ pricing,
3101
+ parsedBody,
3102
+ price
3077
3103
  });
3104
+ if (!verify.ok) return verify.response;
3105
+ const { verifyOutcome } = verify;
3078
3106
  const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
3079
3107
  switch (result.kind) {
3080
3108
  case "stream":
@@ -3206,51 +3234,21 @@ function failureFromCause2(cause) {
3206
3234
 
3207
3235
  // src/pipeline/flows/static/static-paid.ts
3208
3236
  async function runStaticPaidFlow(ctx) {
3209
- const { request, routeEntry, deps, report } = ctx;
3210
- const apiKeyGate = await runApiKeyGate(ctx);
3211
- if (!apiKeyGate.ok) return apiKeyGate.response;
3212
- const { account } = apiKeyGate;
3213
- const pricing = selectPricing(routeEntry.pricing, {
3214
- alert: report,
3215
- maxPrice: routeEntry.maxPrice,
3216
- minPrice: routeEntry.minPrice,
3217
- route: routeEntry.key
3218
- });
3219
- const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
3220
- const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
3221
- if (!earlyResolution.ok) return earlyResolution.response;
3222
- const { earlyBody } = earlyResolution;
3223
- const siwxFastPath = await trySiwxFastPath(ctx, account);
3224
- if (siwxFastPath) return siwxFastPath;
3225
- if (!incomingStrategy) {
3226
- const initError = protocolInitError(routeEntry, deps);
3227
- if (initError) return fail(ctx, 500, initError);
3228
- return buildChallengeResponse(ctx, pricing, earlyBody);
3229
- }
3237
+ const preamble = await runPaidPreamble(ctx);
3238
+ if (preamble.done) return preamble.response;
3239
+ const { account, pricing, incomingStrategy } = preamble;
3230
3240
  const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
3231
3241
  if (!bodyAndPrice.ok) return bodyAndPrice.response;
3232
3242
  const { parsedBody, price } = bodyAndPrice;
3233
- const verifyOutcome = await incomingStrategy.verify({
3234
- request,
3235
- body: parsedBody,
3236
- price,
3237
- routeEntry,
3238
- deps,
3239
- report
3240
- });
3241
- if (verifyOutcome.ok === false) {
3242
- if (verifyOutcome.kind === "config") {
3243
- return fail(ctx, 500, verifyOutcome.message, parsedBody);
3244
- }
3245
- return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
3246
- }
3247
- ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
3248
- firePaymentVerified(ctx, {
3249
- protocol: incomingStrategy.protocol,
3250
- payer: verifyOutcome.wallet,
3251
- amount: price,
3252
- network: verifyOutcome.payment.network
3243
+ const verify = await runPaidVerify({
3244
+ ctx,
3245
+ strategy: incomingStrategy,
3246
+ pricing,
3247
+ parsedBody,
3248
+ price
3253
3249
  });
3250
+ if (!verify.ok) return verify.response;
3251
+ const { verifyOutcome } = verify;
3254
3252
  const result = await invokePaidStatic(
3255
3253
  ctx,
3256
3254
  verifyOutcome.wallet,
@@ -3742,55 +3740,35 @@ var RouteBuilder = class _RouteBuilder {
3742
3740
  return next;
3743
3741
  }
3744
3742
  paid(arg, options) {
3745
- return this.applyPaid(normalizePaidArg(this.#s.key, arg, options), "paid");
3743
+ const self = this;
3744
+ return self.applyPaid(normalizePaidArg(self.#s.key, arg, options), "paid");
3746
3745
  }
3747
- /**
3748
- * x402-only handler-computed billing. The handler receives `charge(amount)`
3749
- * and the request settles once for the accumulated total, capped at
3750
- * `maxPrice`. Requires an `'upto'` accept on at least one configured network.
3751
- * Pass a bare string as sugar for `{ maxPrice }`.
3752
- *
3753
- * @example
3754
- * ```ts
3755
- * router.route('llm')
3756
- * .upTo('0.05')
3757
- * .body(schema)
3758
- * .handler(async ({ body, charge }) => { await charge('0.001'); ... });
3759
- * ```
3760
- */
3761
3746
  upTo(arg) {
3762
- return this.applyPaid(normalizeUpToArg(this.#s.key, arg), "upTo");
3747
+ const self = this;
3748
+ return self.applyPaid(normalizeUpToArg(self.#s.key, arg), "upTo");
3763
3749
  }
3764
- /**
3765
- * MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
3766
- * `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
3767
- * `maxPrice`. Requires `RouterConfig.mpp.session`.
3768
- *
3769
- * @example
3770
- * ```ts
3771
- * router.route('llm/stream')
3772
- * .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
3773
- * .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
3774
- * ```
3775
- */
3776
3750
  metered(options) {
3777
- return this.applyPaid(normalizeMeteredArg(this.#s.key, options), "metered");
3751
+ const self = this;
3752
+ return self.applyPaid(normalizeMeteredArg(self.#s.key, options), "metered");
3778
3753
  }
3779
3754
  applyPaid(normalized, method) {
3780
3755
  const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
3781
3756
  if (this.#s.authMode === "unprotected") {
3782
- throw new Error(
3783
- `route '${this.#s.key}': Cannot combine .unprotected() and .${method}() on the same route.`
3757
+ throw new RouteDefinitionError(
3758
+ this.#s.key,
3759
+ `Cannot combine .unprotected() and .${method}() on the same route.`
3784
3760
  );
3785
3761
  }
3786
3762
  if (this.#s.pricing !== void 0) {
3787
- throw new Error(
3788
- `route '${this.#s.key}': Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
3763
+ throw new RouteDefinitionError(
3764
+ this.#s.key,
3765
+ `Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
3789
3766
  );
3790
3767
  }
3791
3768
  if (this.#s.siwxEnabled && billing === "metered") {
3792
- throw new Error(
3793
- `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.`
3769
+ throw new RouteDefinitionError(
3770
+ this.#s.key,
3771
+ `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.`
3794
3772
  );
3795
3773
  }
3796
3774
  const next = this.fork();
@@ -3798,15 +3776,17 @@ var RouteBuilder = class _RouteBuilder {
3798
3776
  next.#s.pricing = pricing;
3799
3777
  if (billing === "upto") {
3800
3778
  if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
3801
- throw new Error(
3802
- `route '${this.#s.key}': .upTo() is x402-only \u2014 remove the conflicting protocols override.`
3779
+ throw new RouteDefinitionError(
3780
+ this.#s.key,
3781
+ `.upTo() is x402-only \u2014 remove the conflicting protocols override.`
3803
3782
  );
3804
3783
  }
3805
3784
  next.#s.protocols = ["x402"];
3806
3785
  } else if (billing === "metered") {
3807
3786
  if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
3808
- throw new Error(
3809
- `route '${this.#s.key}': .metered() is MPP-only \u2014 remove the conflicting protocols override.`
3787
+ throw new RouteDefinitionError(
3788
+ this.#s.key,
3789
+ `.metered() is MPP-only \u2014 remove the conflicting protocols override.`
3810
3790
  );
3811
3791
  }
3812
3792
  next.#s.protocols = ["mpp"];
@@ -3830,73 +3810,69 @@ var RouteBuilder = class _RouteBuilder {
3830
3810
  if (typeof pricing === "object" && "tiers" in pricing) {
3831
3811
  for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
3832
3812
  if (!tierKey) {
3833
- throw new Error(`route '${this.#s.key}': tier key cannot be empty`);
3813
+ throw new RouteDefinitionError(this.#s.key, `tier key cannot be empty`);
3834
3814
  }
3835
3815
  if (!isPositiveDecimal(tierConfig.price)) {
3836
- throw new Error(
3837
- `route '${this.#s.key}': tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
3816
+ throw new RouteDefinitionError(
3817
+ this.#s.key,
3818
+ `tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
3838
3819
  );
3839
3820
  }
3840
3821
  }
3841
3822
  }
3842
3823
  if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
3843
- throw new Error(
3844
- `route '${this.#s.key}': price '${pricing}' must be a positive decimal string`
3824
+ throw new RouteDefinitionError(
3825
+ this.#s.key,
3826
+ `price '${pricing}' must be a positive decimal string`
3845
3827
  );
3846
3828
  }
3847
3829
  if (typeof pricing === "function" && next.#s.maxPrice === void 0) {
3848
- throw new Error(
3849
- `route '${this.#s.key}': dynamic pricing requires maxPrice \u2014 without it, bare probes would advertise a $0 challenge`
3830
+ throw new RouteDefinitionError(
3831
+ this.#s.key,
3832
+ `dynamic pricing requires maxPrice \u2014 without it, bare probes would advertise a $0 challenge`
3850
3833
  );
3851
3834
  }
3852
3835
  if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
3853
- throw new Error(
3854
- `route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
3836
+ throw new RouteDefinitionError(
3837
+ this.#s.key,
3838
+ `maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
3855
3839
  );
3856
3840
  }
3857
3841
  if (next.#s.minPrice !== void 0 && !isPositiveDecimal(next.#s.minPrice)) {
3858
- throw new Error(
3859
- `route '${this.#s.key}': minPrice '${next.#s.minPrice}' must be a positive decimal string`
3842
+ throw new RouteDefinitionError(
3843
+ this.#s.key,
3844
+ `minPrice '${next.#s.minPrice}' must be a positive decimal string`
3860
3845
  );
3861
3846
  }
3862
3847
  if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
3863
- throw new Error(
3864
- `route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
3848
+ throw new RouteDefinitionError(
3849
+ this.#s.key,
3850
+ `tickCost '${next.#s.tickCost}' must be a positive decimal string`
3865
3851
  );
3866
3852
  }
3867
3853
  return next;
3868
3854
  }
3869
- /**
3870
- * Require Sign-In-with-X wallet identity on this route — clients prove
3871
- * control of a wallet via a signed challenge. Composes with `.paid()` and
3872
- * `.upTo()` for pay-once-then-replay: the first request settles normally,
3873
- * subsequent requests with a valid SIWX signature for the same wallet skip
3874
- * payment (on `.upTo()`, `charge(amount)` becomes a no-op on the replay).
3875
- * Mutually exclusive with `.metered()`.
3876
- *
3877
- * @example
3878
- * ```ts
3879
- * router.route('profile').siwx().handler(async ({ wallet }) => getProfile(wallet));
3880
- * router.route('inbox').paid('0.01').siwx().handler(async ({ wallet }) => getInbox(wallet));
3881
- * ```
3882
- */
3883
3855
  siwx() {
3884
- if (this.#s.authMode === "unprotected") {
3885
- throw new Error(
3886
- `route '${this.#s.key}': Cannot combine .unprotected() and .siwx() on the same route.`
3856
+ const self = this;
3857
+ if (self.#s.authMode === "unprotected") {
3858
+ throw new RouteDefinitionError(
3859
+ self.#s.key,
3860
+ `Cannot combine .unprotected() and .siwx() on the same route.`
3887
3861
  );
3888
3862
  }
3889
- if (this.#s.apiKeyResolver) {
3890
- throw new Error(
3891
- `route '${this.#s.key}': Combining .siwx() and .apiKey() is not supported on the same route.`
3863
+ if (self.#s.apiKeyResolver) {
3864
+ throw new RouteDefinitionError(
3865
+ self.#s.key,
3866
+ `Combining .siwx() and .apiKey() is not supported on the same route.`
3892
3867
  );
3893
3868
  }
3894
- if (this.#s.billing === "metered") {
3895
- throw new Error(
3896
- `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.`
3869
+ if (self.#s.billing === "metered") {
3870
+ throw new RouteDefinitionError(
3871
+ self.#s.key,
3872
+ `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.`
3897
3873
  );
3898
3874
  }
3899
- const next = this.fork();
3875
+ const next = self.fork();
3900
3876
  next.#s.siwxEnabled = true;
3901
3877
  if (next.#s.authMode === "paid" || next.#s.pricing) {
3902
3878
  next.#s.authMode = "paid";
@@ -3907,59 +3883,48 @@ var RouteBuilder = class _RouteBuilder {
3907
3883
  next.#s.protocols = [];
3908
3884
  return next;
3909
3885
  }
3910
- /**
3911
- * Require an `X-API-Key` header (or `Authorization: Bearer <key>`); the
3912
- * resolver returns the account record, or `null` for 401. Composes with
3913
- * `.paid()` — key is checked first, payment second.
3914
- *
3915
- * @example
3916
- * ```ts
3917
- * router
3918
- * .route('admin/users')
3919
- * .apiKey(async (key) => db.admin.findByKey(key))
3920
- * .handler(async ({ account }) => db.user.list(account.orgId));
3921
- * ```
3922
- */
3923
3886
  apiKey(resolver) {
3924
- if (this.#s.siwxEnabled) {
3925
- throw new Error(
3926
- `route '${this.#s.key}': Combining .apiKey() and .siwx() is not supported on the same route.`
3887
+ const self = this;
3888
+ if (self.#s.authMode === "unprotected") {
3889
+ throw new RouteDefinitionError(
3890
+ self.#s.key,
3891
+ `Cannot combine .unprotected() and .apiKey() on the same route.`
3927
3892
  );
3928
3893
  }
3929
- const next = this.fork();
3894
+ if (self.#s.siwxEnabled) {
3895
+ throw new RouteDefinitionError(
3896
+ self.#s.key,
3897
+ `Combining .apiKey() and .siwx() is not supported on the same route.`
3898
+ );
3899
+ }
3900
+ const next = self.fork();
3930
3901
  next.#s.authMode = "apiKey";
3931
3902
  next.#s.apiKeyResolver = resolver;
3932
3903
  return next;
3933
3904
  }
3934
- /**
3935
- * Mark the route as public — no auth, no payment, no SIWX. The handler
3936
- * receives `null` for `wallet`, `payment`, and `account`.
3937
- *
3938
- * @example
3939
- * ```ts
3940
- * router.route('health').unprotected().handler(async () => ({ status: 'ok' }));
3941
- * ```
3942
- */
3943
3905
  unprotected() {
3944
- if (this.#s.authMode && this.#s.authMode !== "unprotected") {
3945
- throw new Error(
3946
- `route '${this.#s.key}': Cannot combine .unprotected() and .${this.#s.authMode}() on the same route.`
3906
+ const self = this;
3907
+ if (self.#s.authMode && self.#s.authMode !== "unprotected") {
3908
+ throw new RouteDefinitionError(
3909
+ self.#s.key,
3910
+ `Cannot combine .unprotected() and .${self.#s.authMode}() on the same route.`
3947
3911
  );
3948
3912
  }
3949
- if (this.#s.pricing) {
3950
- throw new Error(
3951
- `route '${this.#s.key}': Cannot combine .unprotected() and .paid() on the same route.`
3913
+ if (self.#s.pricing) {
3914
+ throw new RouteDefinitionError(
3915
+ self.#s.key,
3916
+ `Cannot combine .unprotected() and .paid() on the same route.`
3952
3917
  );
3953
3918
  }
3954
- const next = this.fork();
3919
+ const next = self.fork();
3955
3920
  next.#s.authMode = "unprotected";
3956
3921
  next.#s.protocols = [];
3957
3922
  return next;
3958
3923
  }
3959
3924
  /**
3960
3925
  * Tag the route with an upstream provider for discovery and provider-side
3961
- * monitoring. The provider name and config surface in `well-known` and
3962
- * OpenAPI output.
3926
+ * monitoring. The provider name and config surface in OpenAPI discovery
3927
+ * output.
3963
3928
  *
3964
3929
  * @example
3965
3930
  * ```ts
@@ -4056,8 +4021,8 @@ var RouteBuilder = class _RouteBuilder {
4056
4021
  return next;
4057
4022
  }
4058
4023
  /**
4059
- * Set a human-readable summary of the route. Surfaces in OpenAPI,
4060
- * `well-known`, and `llms.txt` discovery output.
4024
+ * Set a human-readable summary of the route. Surfaces in OpenAPI and
4025
+ * `llms.txt` discovery output.
4061
4026
  *
4062
4027
  * @example
4063
4028
  * ```ts
@@ -4193,38 +4158,46 @@ var RouteBuilder = class _RouteBuilder {
4193
4158
  }
4194
4159
  register(handlerFn, streaming) {
4195
4160
  if (!this.#s.authMode) {
4196
- throw new Error(
4197
- `route '${this.#s.key}': Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
4161
+ throw new RouteDefinitionError(
4162
+ this.#s.key,
4163
+ `Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
4198
4164
  );
4199
4165
  }
4200
4166
  if (this.#s.validateFn && !this.#s.bodySchema) {
4201
- throw new Error(
4202
- `route '${this.#s.key}': .validate() requires .body() \u2014 validation runs on parsed body`
4167
+ throw new RouteDefinitionError(
4168
+ this.#s.key,
4169
+ `.validate() requires .body() \u2014 validation runs on parsed body`
4203
4170
  );
4204
4171
  }
4205
4172
  if (this.#s.settlement && !this.#s.pricing) {
4206
- throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
4173
+ throw new RouteDefinitionError(this.#s.key, `.settlement() requires a paid route`);
4207
4174
  }
4208
4175
  if (this.#s.mppInfo?.settleBeforeHandler) {
4209
4176
  if (!this.#s.pricing) {
4210
- throw new Error(`route '${this.#s.key}': mpp.settleBeforeHandler requires a paid route`);
4177
+ throw new RouteDefinitionError(
4178
+ this.#s.key,
4179
+ `mpp.settleBeforeHandler requires a paid route`
4180
+ );
4211
4181
  }
4212
4182
  if (this.#s.billing !== "exact") {
4213
- throw new Error(
4214
- `route '${this.#s.key}': mpp.settleBeforeHandler is only supported on .paid() routes`
4183
+ throw new RouteDefinitionError(
4184
+ this.#s.key,
4185
+ `mpp.settleBeforeHandler is only supported on .paid() routes`
4215
4186
  );
4216
4187
  }
4217
4188
  if (this.#s.settlement?.beforeSettle) {
4218
- throw new Error(
4219
- `route '${this.#s.key}': mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
4189
+ throw new RouteDefinitionError(
4190
+ this.#s.key,
4191
+ `mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
4220
4192
  );
4221
4193
  }
4222
4194
  }
4223
4195
  if (this.#s.billing === "upto") {
4224
4196
  const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
4225
4197
  if (!hasUpto) {
4226
- throw new Error(
4227
- `route '${this.#s.key}': .upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
4198
+ throw new RouteDefinitionError(
4199
+ this.#s.key,
4200
+ `.upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
4228
4201
  );
4229
4202
  }
4230
4203
  }
@@ -4233,26 +4206,30 @@ var RouteBuilder = class _RouteBuilder {
4233
4206
  (accept) => (accept.scheme ?? "exact") !== "upto"
4234
4207
  );
4235
4208
  if (!hasExact) {
4236
- throw new Error(
4237
- `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.`
4209
+ throw new RouteDefinitionError(
4210
+ this.#s.key,
4211
+ `.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.`
4238
4212
  );
4239
4213
  }
4240
4214
  }
4241
4215
  if (this.#s.billing === "metered") {
4242
4216
  if (!this.#s.deps.mppSessionConfig) {
4243
- throw new Error(
4244
- `route '${this.#s.key}': .metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
4217
+ throw new RouteDefinitionError(
4218
+ this.#s.key,
4219
+ `.metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
4245
4220
  );
4246
4221
  }
4247
4222
  }
4248
4223
  if (streaming && this.#s.billing !== "metered") {
4249
- throw new Error(
4250
- `route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
4224
+ throw new RouteDefinitionError(
4225
+ this.#s.key,
4226
+ `.stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
4251
4227
  );
4252
4228
  }
4253
4229
  if (this.#s.description !== void 0 && this.#s.description.length > MAX_X402_DESCRIPTION_LENGTH && this.#s.pricing !== void 0 && this.#s.protocols.includes("x402")) {
4254
- throw new Error(
4255
- `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.`
4230
+ throw new RouteDefinitionError(
4231
+ this.#s.key,
4232
+ `.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.`
4256
4233
  );
4257
4234
  }
4258
4235
  validateExamples(
@@ -4320,14 +4297,15 @@ function normalizePaidArg(routeKey, arg, options) {
4320
4297
  if ("price" in arg && typeof arg.price === "string") {
4321
4298
  return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
4322
4299
  }
4323
- throw new Error(
4324
- `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().`
4300
+ throw new RouteDefinitionError(
4301
+ routeKey,
4302
+ `.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().`
4325
4303
  );
4326
4304
  }
4327
4305
  function normalizeUpToArg(routeKey, arg) {
4328
4306
  const options = typeof arg === "string" ? { maxPrice: arg } : arg;
4329
4307
  if (!options.maxPrice) {
4330
- throw new Error(`route '${routeKey}': .upTo() requires maxPrice`);
4308
+ throw new RouteDefinitionError(routeKey, `.upTo() requires maxPrice`);
4331
4309
  }
4332
4310
  return {
4333
4311
  pricing: options.maxPrice,
@@ -4339,10 +4317,10 @@ function normalizeUpToArg(routeKey, arg) {
4339
4317
  }
4340
4318
  function normalizeMeteredArg(routeKey, options) {
4341
4319
  if (!options.maxPrice) {
4342
- throw new Error(`route '${routeKey}': .metered() requires maxPrice`);
4320
+ throw new RouteDefinitionError(routeKey, `.metered() requires maxPrice`);
4343
4321
  }
4344
4322
  if (!options.tickCost) {
4345
- throw new Error(`route '${routeKey}': .metered() requires tickCost`);
4323
+ throw new RouteDefinitionError(routeKey, `.metered() requires tickCost`);
4346
4324
  }
4347
4325
  return {
4348
4326
  pricing: options.maxPrice,
@@ -4649,9 +4627,9 @@ function createNotFoundHandler(baseUrl) {
4649
4627
  code: "route_not_found",
4650
4628
  error: "Route not found. Rediscover this origin and retry with the current discovery document.",
4651
4629
  requestedUrl: request.url,
4630
+ // The deprecated /.well-known/x402 surface is intentionally not advertised.
4652
4631
  discovery: {
4653
4632
  openapi: `${normalizedBase}/openapi.json`,
4654
- wellKnown: `${normalizedBase}/.well-known/x402`,
4655
4633
  llmsTxt: `${normalizedBase}/llms.txt`
4656
4634
  }
4657
4635
  },
@@ -4684,6 +4662,7 @@ function formatRouterConfigIssues(issues) {
4684
4662
  }
4685
4663
 
4686
4664
  // src/config/schema.ts
4665
+ init_accepts();
4687
4666
  init_constants();
4688
4667
  import { z } from "zod";
4689
4668
 
@@ -4873,18 +4852,8 @@ function deriveBaseUrlEnv(env) {
4873
4852
  BASE_URL: vercelBaseUrl
4874
4853
  };
4875
4854
  }
4876
- function getConfiguredX402Accepts2(config) {
4877
- if (config.x402?.accepts?.length) return [...config.x402.accepts];
4878
- return [
4879
- {
4880
- scheme: "exact",
4881
- network: config.network ?? BASE_MAINNET_NETWORK,
4882
- payTo: config.payeeAddress
4883
- }
4884
- ];
4885
- }
4886
4855
  function validateX402Config(config, env) {
4887
- const accepts = getConfiguredX402Accepts2(config);
4856
+ const accepts = getConfiguredX402Accepts(config);
4888
4857
  const issues = [];
4889
4858
  const push = (code, message) => issues.push({ code, protocol: "x402", message });
4890
4859
  if (accepts.length === 0) {
@@ -5185,7 +5154,8 @@ function getMppxRequestContext(args) {
5185
5154
  recipient: mppConfig.recipient ?? payeeAddress,
5186
5155
  getClient,
5187
5156
  ...feePayerAccount ? { feePayer: feePayerAccount } : {},
5188
- ...resolvedStore ? { store: resolvedStore } : {}
5157
+ ...resolvedStore ? { store: resolvedStore } : {},
5158
+ ...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {}
5189
5159
  }),
5190
5160
  ...sessionEnabled ? [
5191
5161
  tempo.session({
@@ -5223,7 +5193,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5223
5193
  try {
5224
5194
  const { Mppx, tempo } = await import("mppx/server");
5225
5195
  const { createClient, http } = await import("viem");
5226
- const { tempo: tempoChain } = await import("viem/chains");
5196
+ const { tempo: tempoChain } = await import("viem/tempo/chains");
5227
5197
  const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
5228
5198
  const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL;
5229
5199
  const tempoClient = createClient({ chain: tempoChain, transport: http(rpcUrl) });
@@ -5241,7 +5211,9 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5241
5211
  getClient,
5242
5212
  ...operatorAccount ? { account: operatorAccount } : {},
5243
5213
  ...feePayerAccount ? { feePayer: feePayerAccount } : {},
5244
- ...resolvedStore ? { store: resolvedStore } : {}
5214
+ ...resolvedStore ? { store: resolvedStore } : {},
5215
+ ...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {},
5216
+ ...mppConfig.session?.settlementSchedule ? { settlementSchedule: mppConfig.session.settlementSchedule } : {}
5245
5217
  };
5246
5218
  const mppxArgs = {
5247
5219
  Mppx,
@@ -5257,6 +5229,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5257
5229
  };
5258
5230
  const primary = getMppxRequestContext(mppxArgs);
5259
5231
  const streaming = getMppxStreamingContext(mppxArgs);
5232
+ subscribePaymentFailedAlerts(config.plugin, [primary, streaming]);
5260
5233
  const mppx = {
5261
5234
  charge: primary.charge,
5262
5235
  ...primary.session ? { sessionRequest: primary.session } : {},
@@ -5267,6 +5240,41 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
5267
5240
  return { initError: err instanceof Error ? err.message : String(err) };
5268
5241
  }
5269
5242
  }
5243
+ function subscribePaymentFailedAlerts(plugin, instances) {
5244
+ if (!plugin?.onAlert) return;
5245
+ const ctx = {
5246
+ requestId: "mppx",
5247
+ route: "mpp",
5248
+ walletAddress: null,
5249
+ clientId: null,
5250
+ sessionId: null,
5251
+ verifiedWallet: null,
5252
+ setVerifiedWallet() {
5253
+ }
5254
+ };
5255
+ for (const instance of instances) {
5256
+ const events = instance;
5257
+ if (typeof events?.onPaymentFailed !== "function") continue;
5258
+ events.onPaymentFailed((payload) => {
5259
+ try {
5260
+ const error = payload?.error ?? {};
5261
+ firePluginHook(plugin, "onAlert", ctx, {
5262
+ level: (error.status ?? 402) >= 500 ? "error" : "warn",
5263
+ message: `MPP payment failed: ${error.message ?? error.name ?? "unknown error"}`,
5264
+ route: "mpp",
5265
+ meta: {
5266
+ ...payload?.method ? { method: `${payload.method.name}/${payload.method.intent}` } : {},
5267
+ ...error.type ? { errorType: error.type } : {},
5268
+ ...error.status !== void 0 ? { status: error.status } : {},
5269
+ ...error.hint ? { hint: error.hint } : {},
5270
+ ...payload?.credential?.source ? { payer: payload.credential.source } : {}
5271
+ }
5272
+ });
5273
+ } catch {
5274
+ }
5275
+ });
5276
+ }
5277
+ }
5270
5278
 
5271
5279
  // src/index.ts
5272
5280
  init_constants();
@@ -5409,6 +5417,7 @@ export {
5409
5417
  DEFAULT_SOLANA_FACILITATOR_URL,
5410
5418
  DEFAULT_TEMPO_RPC_URL,
5411
5419
  HttpError,
5420
+ RouteDefinitionError,
5412
5421
  RouterConfigError,
5413
5422
  SOLANA_MAINNET_NETWORK,
5414
5423
  TEMPO_USDC_ADDRESS,