@agentcash/router 1.5.0 → 1.5.2

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
@@ -30,7 +30,65 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
- // src/protocols/evm.ts
33
+ // src/constants.ts
34
+ var BASE_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_CURRENCY, ZERO_EVM_ADDRESS;
35
+ var init_constants = __esm({
36
+ "src/constants.ts"() {
37
+ "use strict";
38
+ BASE_NETWORK = "eip155:8453";
39
+ SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
40
+ TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50";
41
+ ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
42
+ }
43
+ });
44
+
45
+ // src/protocols/x402/accepts.ts
46
+ async function resolvePayToValue(payTo, request, fallback, body) {
47
+ if (!payTo) return fallback;
48
+ if (typeof payTo === "string") return payTo;
49
+ return payTo(request, body);
50
+ }
51
+ function getConfiguredX402Accepts(config) {
52
+ if (config.x402?.accepts?.length) {
53
+ return [...config.x402.accepts];
54
+ }
55
+ return [
56
+ {
57
+ scheme: "exact",
58
+ network: config.network ?? BASE_NETWORK,
59
+ payTo: config.payeeAddress
60
+ }
61
+ ];
62
+ }
63
+ function getConfiguredX402Networks(config) {
64
+ return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
65
+ }
66
+ async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
67
+ return Promise.all(
68
+ accepts.map(async (accept) => ({
69
+ network: accept.network,
70
+ scheme: accept.scheme ?? "exact",
71
+ payTo: await resolvePayToValue(
72
+ routeEntry.payTo ?? accept.payTo,
73
+ request,
74
+ fallbackPayTo,
75
+ body
76
+ ),
77
+ ...accept.asset ? { asset: accept.asset } : {},
78
+ ...accept.decimals !== void 0 ? { decimals: accept.decimals } : {},
79
+ ...accept.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: accept.maxTimeoutSeconds } : {},
80
+ ...accept.extra ? { extra: accept.extra } : {}
81
+ }))
82
+ );
83
+ }
84
+ var init_accepts = __esm({
85
+ "src/protocols/x402/accepts.ts"() {
86
+ "use strict";
87
+ init_constants();
88
+ }
89
+ });
90
+
91
+ // src/protocols/x402/evm.ts
34
92
  function isEvmNetwork(network) {
35
93
  return network.startsWith("eip155:");
36
94
  }
@@ -48,12 +106,12 @@ function buildEvmExactOptions(accepts, price) {
48
106
  }));
49
107
  }
50
108
  var init_evm = __esm({
51
- "src/protocols/evm.ts"() {
109
+ "src/protocols/x402/evm.ts"() {
52
110
  "use strict";
53
111
  }
54
112
  });
55
113
 
56
- // src/protocols/solana.ts
114
+ // src/protocols/x402/solana.ts
57
115
  function isSolanaNetwork(network) {
58
116
  return network.startsWith("solana:");
59
117
  }
@@ -103,13 +161,13 @@ function isSolanaRequirement(requirement) {
103
161
  return isSolanaNetwork(requirement.network);
104
162
  }
105
163
  var init_solana = __esm({
106
- "src/protocols/solana.ts"() {
164
+ "src/protocols/x402/solana.ts"() {
107
165
  "use strict";
108
- init_x402_facilitators();
166
+ init_facilitators();
109
167
  }
110
168
  });
111
169
 
112
- // src/x402-facilitators.ts
170
+ // src/protocols/x402/facilitators.ts
113
171
  function getResolvedX402Facilitator(config, network, defaultEvmFacilitator) {
114
172
  const family = getNetworkFamily(network);
115
173
  if (!family) return null;
@@ -179,8 +237,8 @@ function sameFacilitatorConfig(a, b) {
179
237
  return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders && a.createAcceptsHeaders === b.createAcceptsHeaders;
180
238
  }
181
239
  var DEFAULT_SOLANA_FACILITATOR_URL;
182
- var init_x402_facilitators = __esm({
183
- "src/x402-facilitators.ts"() {
240
+ var init_facilitators = __esm({
241
+ "src/protocols/x402/facilitators.ts"() {
184
242
  "use strict";
185
243
  init_evm();
186
244
  init_solana();
@@ -188,64 +246,6 @@ var init_x402_facilitators = __esm({
188
246
  }
189
247
  });
190
248
 
191
- // src/constants.ts
192
- var BASE_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_CURRENCY, ZERO_EVM_ADDRESS;
193
- var init_constants = __esm({
194
- "src/constants.ts"() {
195
- "use strict";
196
- BASE_NETWORK = "eip155:8453";
197
- SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
198
- TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50";
199
- ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
200
- }
201
- });
202
-
203
- // src/x402-config.ts
204
- async function resolvePayToValue(payTo, request, fallback, body) {
205
- if (!payTo) return fallback;
206
- if (typeof payTo === "string") return payTo;
207
- return payTo(request, body);
208
- }
209
- function getConfiguredX402Accepts(config) {
210
- if (config.x402?.accepts?.length) {
211
- return [...config.x402.accepts];
212
- }
213
- return [
214
- {
215
- scheme: "exact",
216
- network: config.network ?? BASE_NETWORK,
217
- payTo: config.payeeAddress
218
- }
219
- ];
220
- }
221
- function getConfiguredX402Networks(config) {
222
- return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
223
- }
224
- async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
225
- return Promise.all(
226
- accepts.map(async (accept) => ({
227
- network: accept.network,
228
- scheme: accept.scheme ?? "exact",
229
- payTo: await resolvePayToValue(
230
- routeEntry.payTo ?? accept.payTo,
231
- request,
232
- fallbackPayTo,
233
- body
234
- ),
235
- ...accept.asset ? { asset: accept.asset } : {},
236
- ...accept.decimals !== void 0 ? { decimals: accept.decimals } : {},
237
- ...accept.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: accept.maxTimeoutSeconds } : {},
238
- ...accept.extra ? { extra: accept.extra } : {}
239
- }))
240
- );
241
- }
242
- var init_x402_config = __esm({
243
- "src/x402-config.ts"() {
244
- "use strict";
245
- init_constants();
246
- }
247
- });
248
-
249
249
  // src/server.ts
250
250
  var server_exports = {};
251
251
  __export(server_exports, {
@@ -330,8 +330,8 @@ var init_server = __esm({
330
330
  "use strict";
331
331
  init_evm();
332
332
  init_solana();
333
- init_x402_facilitators();
334
- init_x402_config();
333
+ init_facilitators();
334
+ init_accepts();
335
335
  }
336
336
  });
337
337
 
@@ -465,13 +465,32 @@ var RouteRegistry = class {
465
465
  }
466
466
  };
467
467
 
468
- // src/orchestrate.ts
469
- var import_server2 = require("next/server");
470
-
471
- // src/auth/normalize-wallet.ts
472
- function normalizeWalletAddress(address) {
473
- return address.startsWith("0x") ? address.toLowerCase() : address;
474
- }
468
+ // src/headers.ts
469
+ var HEADERS = {
470
+ // ---- Standard HTTP ----
471
+ AUTHORIZATION: "Authorization",
472
+ WWW_AUTHENTICATE: "WWW-Authenticate",
473
+ // ---- Auth ----
474
+ API_KEY: "X-API-Key",
475
+ // ---- Request meta (used by plugin/observability) ----
476
+ WALLET_ADDRESS: "X-Wallet-Address",
477
+ CLIENT_ID: "X-Client-ID",
478
+ SESSION_ID: "X-Session-ID",
479
+ // ---- SIWX ----
480
+ SIWX: "SIGN-IN-WITH-X",
481
+ // ---- x402 (payment) ----
482
+ X402_PAYMENT_SIGNATURE: "PAYMENT-SIGNATURE",
483
+ /** Legacy x402 payment header — accepted alongside PAYMENT-SIGNATURE. */
484
+ X402_PAYMENT_LEGACY: "X-PAYMENT",
485
+ X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
486
+ X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
487
+ // ---- MPP (payment) ----
488
+ MPP_PAYMENT_RECEIPT: "Payment-Receipt"
489
+ };
490
+ var AUTH_SCHEME = {
491
+ BEARER: "Bearer ",
492
+ MPP_PAYMENT: "Payment "
493
+ };
475
494
 
476
495
  // src/plugin.ts
477
496
  function createDefaultContext(meta) {
@@ -548,105 +567,31 @@ function consolePlugin() {
548
567
  };
549
568
  }
550
569
 
551
- // src/auth/nonce.ts
552
- var SIWX_CHALLENGE_EXPIRY_MS = 5 * 60 * 1e3;
553
- var MemoryNonceStore = class {
554
- seen = /* @__PURE__ */ new Map();
555
- async check(nonce) {
556
- this.evict();
557
- if (this.seen.has(nonce)) return false;
558
- this.seen.set(nonce, Date.now() + SIWX_CHALLENGE_EXPIRY_MS);
559
- return true;
560
- }
561
- evict() {
562
- const now = Date.now();
563
- for (const [n, exp] of this.seen) {
564
- if (exp < now) this.seen.delete(n);
565
- }
566
- }
567
- };
568
- function detectRedisClientType(client) {
569
- if (!client || typeof client !== "object") {
570
- throw new Error(
571
- "createRedisNonceStore requires a Redis client. Supported: @upstash/redis, ioredis. Pass your Redis client instance as the first argument."
572
- );
573
- }
574
- if ("options" in client && "status" in client) {
575
- return "ioredis";
576
- }
577
- const constructor = client.constructor?.name;
578
- if (constructor === "Redis" && "url" in client) {
579
- return "upstash";
580
- }
581
- if (typeof client.set === "function") {
582
- return "upstash";
583
- }
584
- throw new Error(
585
- "Unrecognized Redis client. Supported: @upstash/redis, ioredis. If using a different client, implement NonceStore interface directly."
586
- );
570
+ // src/pipeline/context/preflight.ts
571
+ function preflight(routeEntry, handler, deps, request) {
572
+ const meta = buildMeta(request, routeEntry);
573
+ const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
574
+ return { routeEntry, handler, deps, request, meta, pluginCtx };
587
575
  }
588
- function createRedisNonceStore(client, opts) {
589
- const prefix = opts?.prefix ?? "siwx:nonce:";
590
- const ttlSeconds = Math.ceil((opts?.ttlMs ?? SIWX_CHALLENGE_EXPIRY_MS) / 1e3);
591
- const clientType = detectRedisClientType(client);
576
+ function buildMeta(request, routeEntry) {
592
577
  return {
593
- async check(nonce) {
594
- const key = `${prefix}${nonce}`;
595
- if (clientType === "upstash") {
596
- const redis = client;
597
- const result = await redis.set(key, "1", { ex: ttlSeconds, nx: true });
598
- return result !== null;
599
- }
600
- if (clientType === "ioredis") {
601
- const redis = client;
602
- const result = await redis.set(key, "1", "EX", ttlSeconds, "NX");
603
- return result === "OK";
604
- }
605
- throw new Error("Unknown Redis client type");
606
- }
578
+ requestId: crypto.randomUUID(),
579
+ method: request.method,
580
+ route: routeEntry.key,
581
+ origin: request.headers.get("origin") ?? new URL(request.url).origin,
582
+ referer: request.headers.get("referer"),
583
+ walletAddress: request.headers.get(HEADERS.WALLET_ADDRESS),
584
+ clientId: request.headers.get(HEADERS.CLIENT_ID),
585
+ sessionId: request.headers.get(HEADERS.SESSION_ID),
586
+ contentType: request.headers.get("content-type"),
587
+ headers: Object.fromEntries(request.headers.entries()),
588
+ startTime: Date.now()
607
589
  };
608
590
  }
609
591
 
610
- // src/protocols/detect.ts
611
- function detectProtocol(request) {
612
- if (request.headers.get("PAYMENT-SIGNATURE") || request.headers.get("X-PAYMENT")) {
613
- return "x402";
614
- }
615
- const auth = request.headers.get("Authorization");
616
- if (auth && auth.startsWith("Payment ")) {
617
- return "mpp";
618
- }
619
- if (request.headers.get("SIGN-IN-WITH-X")) {
620
- return "siwx";
621
- }
622
- return null;
623
- }
624
-
625
- // src/handler.ts
592
+ // src/pipeline/context/parse-body.ts
626
593
  var import_server = require("next/server");
627
594
 
628
- // src/types.ts
629
- var HttpError = class extends Error {
630
- constructor(message, status) {
631
- super(message);
632
- this.status = status;
633
- this.name = "HttpError";
634
- }
635
- };
636
-
637
- // src/handler.ts
638
- async function safeCallHandler(handler, ctx) {
639
- try {
640
- const result = await handler(ctx);
641
- if (result instanceof Response) return result;
642
- return import_server.NextResponse.json(result);
643
- } catch (error) {
644
- const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
645
- const message = error instanceof Error ? error.message : "Internal error";
646
- return import_server.NextResponse.json({ success: false, error: message }, { status });
647
- }
648
- }
649
-
650
595
  // src/body.ts
651
596
  async function bufferBody(request) {
652
597
  const text = await request.text();
@@ -671,296 +616,329 @@ function validateBody(parsed, schema) {
671
616
  };
672
617
  }
673
618
 
674
- // src/pricing.ts
675
- async function resolvePrice(pricing, body) {
676
- if (typeof pricing === "string") {
677
- return pricing;
678
- }
679
- if (typeof pricing === "function") {
680
- return pricing(body);
681
- }
682
- const { field, tiers, default: defaultTier } = pricing;
683
- const tierKey = body != null ? String(body[field] ?? "") : "";
684
- if (tierKey && tiers[tierKey]) {
685
- return tiers[tierKey].price;
686
- }
687
- if (defaultTier && tiers[defaultTier]) {
688
- return tiers[defaultTier].price;
689
- }
690
- if (!tierKey) {
691
- throw Object.assign(new Error(`Missing required field '${field}' for tier pricing`), {
692
- status: 400
693
- });
694
- }
695
- throw Object.assign(
696
- new Error(
697
- `Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
698
- ),
699
- { status: 400 }
700
- );
619
+ // src/pipeline/context/parse-body.ts
620
+ async function parseBody(request, routeEntry) {
621
+ if (!routeEntry.bodySchema) return { ok: true, data: void 0 };
622
+ const raw = await bufferBody(request);
623
+ const result = validateBody(raw, routeEntry.bodySchema);
624
+ if (result.success) return { ok: true, data: result.data };
625
+ return {
626
+ ok: false,
627
+ response: import_server.NextResponse.json(
628
+ { success: false, error: result.error, issues: result.issues },
629
+ { status: 400 }
630
+ )
631
+ };
701
632
  }
702
- function resolveMaxPrice(pricing) {
703
- if (typeof pricing === "string") return pricing;
704
- if (typeof pricing === "function") {
705
- throw new Error("Dynamic pricing requires maxPrice \u2014 this should be caught at registration");
706
- }
707
- const { tiers } = pricing;
708
- let max = "0";
709
- for (const tier of Object.values(tiers)) {
710
- if (parseFloat(tier.price) > parseFloat(max)) {
711
- max = tier.price;
712
- }
713
- }
714
- return max;
633
+
634
+ // src/pipeline/context/parse-query.ts
635
+ function parseQuery(request, routeEntry) {
636
+ if (!routeEntry.querySchema) return void 0;
637
+ const params = Object.fromEntries(request.nextUrl.searchParams.entries());
638
+ const result = routeEntry.querySchema.safeParse(params);
639
+ return result.success ? result.data : params;
715
640
  }
716
641
 
717
- // src/orchestrate.ts
718
- var import_mppx2 = require("mppx");
719
- var import_actions = require("viem/actions");
720
- var import_tempo = require("viem/tempo");
721
- var import_viem2 = require("viem");
642
+ // src/pipeline/context/errors.ts
643
+ function errorStatus(error, fallback) {
644
+ const status = error?.status;
645
+ return typeof status === "number" ? status : fallback;
646
+ }
647
+ function errorMessage(error, fallback) {
648
+ return error instanceof Error ? error.message : fallback;
649
+ }
650
+ function handlerFailureError(response) {
651
+ const message = response.statusText || `Handler returned HTTP ${response.status}`;
652
+ return Object.assign(new Error(message), { status: response.status });
653
+ }
722
654
 
723
- // src/protocols/x402.ts
724
- init_x402_facilitators();
725
- init_evm();
726
- init_solana();
727
- async function buildX402Challenge(opts) {
728
- const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
729
- const { encodePaymentRequiredHeader } = await import("@x402/core/http");
730
- const resource = buildChallengeResource(request, routeEntry);
731
- const requirements = await buildChallengeRequirements(
732
- server,
733
- request,
734
- price,
735
- accepts,
736
- resource,
737
- facilitatorsByNetwork
738
- );
739
- const paymentRequired = await server.createPaymentRequiredResponse(
740
- requirements,
741
- resource,
742
- void 0,
743
- extensions
744
- );
745
- const encoded = encodePaymentRequiredHeader(paymentRequired);
746
- return { encoded, requirements };
747
- }
748
- async function verifyX402Payment(opts) {
749
- const { server, request, price, accepts } = opts;
750
- const payload = await readPaymentPayload(request);
751
- if (!payload) return null;
752
- const requirements = await buildExpectedRequirements(server, request, price, accepts);
753
- const matching = findVerifiableRequirements(server, requirements, payload);
754
- if (!matching) {
755
- return invalidPaymentVerification();
655
+ // src/pipeline/context/fail.ts
656
+ var import_server2 = require("next/server");
657
+
658
+ // src/pipeline/context/fire-plugin-response.ts
659
+ function firePluginResponse(ctx, response, requestBody, responseBody) {
660
+ firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
661
+ statusCode: response.status,
662
+ statusText: response.statusText,
663
+ duration: Date.now() - ctx.meta.startTime,
664
+ contentType: response.headers.get("content-type"),
665
+ headers: Object.fromEntries(response.headers.entries()),
666
+ requestBody,
667
+ responseBody
668
+ });
669
+ if (response.status >= 400 && response.status !== 402) {
670
+ firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, {
671
+ status: response.status,
672
+ message: response.statusText || `HTTP ${response.status}`,
673
+ settled: false
674
+ });
756
675
  }
757
- let verify;
676
+ }
677
+
678
+ // src/pipeline/context/fail.ts
679
+ function fail(ctx, status, message, requestBody) {
680
+ const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
681
+ firePluginResponse(ctx, response, requestBody);
682
+ return response;
683
+ }
684
+
685
+ // src/pipeline/context/run-validate.ts
686
+ async function runValidate(ctx, body) {
687
+ if (!ctx.routeEntry.validateFn) return null;
758
688
  try {
759
- verify = await server.verifyPayment(payload, matching);
689
+ await ctx.routeEntry.validateFn(body);
690
+ return null;
760
691
  } catch (err) {
761
- const sc = err.statusCode;
762
- if (sc && sc >= 400 && sc < 500) return invalidPaymentVerification();
763
- throw err;
692
+ return fail(ctx, errorStatus(err, 400), errorMessage(err, "Validation failed"), body);
764
693
  }
765
- if (!verify.isValid) return invalidPaymentVerification();
766
- return {
767
- valid: true,
768
- payer: verify.payer,
769
- payload,
770
- requirements: matching
771
- };
772
694
  }
773
- function findVerifiableRequirements(server, requirements, payload) {
774
- const strictMatch = server.findMatchingRequirements(requirements, payload);
775
- if (strictMatch) {
776
- return payload.x402Version === 2 ? payload.accepted : strictMatch;
695
+
696
+ // src/handler.ts
697
+ var import_server3 = require("next/server");
698
+
699
+ // src/types.ts
700
+ var HttpError = class extends Error {
701
+ constructor(message, status) {
702
+ super(message);
703
+ this.status = status;
704
+ this.name = "HttpError";
777
705
  }
778
- if (payload.x402Version !== 2) {
779
- return null;
706
+ };
707
+
708
+ // src/handler.ts
709
+ async function safeCallHandler(handler, ctx, options = {}) {
710
+ try {
711
+ const result = await handler(ctx);
712
+ if (result instanceof Response) return result;
713
+ return import_server3.NextResponse.json(result);
714
+ } catch (error) {
715
+ options.onError?.(error);
716
+ const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
717
+ const message = error instanceof Error ? error.message : "Internal error";
718
+ return import_server3.NextResponse.json({ success: false, error: message }, { status });
780
719
  }
781
- const stableMatch = requirements.find(
782
- (requirement) => matchesStableFields(requirement, payload.accepted)
783
- );
784
- return stableMatch ? payload.accepted : null;
785
- }
786
- function matchesStableFields(requirement, accepted) {
787
- return requirement.scheme === accepted.scheme && requirement.network === accepted.network && requirement.payTo === accepted.payTo && requirement.asset === accepted.asset && requirement.amount === accepted.amount && requirement.maxTimeoutSeconds === accepted.maxTimeoutSeconds;
788
- }
789
- async function buildExpectedRequirements(server, request, price, accepts) {
790
- const exactRequirements = await buildExactRequirements(server, request, price, accepts);
791
- const customRequirements = buildCustomRequirements(price, accepts);
792
- return [...exactRequirements, ...customRequirements];
793
720
  }
794
- async function buildExactRequirements(server, request, price, accepts) {
795
- const exactGroups = [
796
- buildEvmExactOptions(accepts, price),
797
- buildSolanaExactOptions(accepts, price)
798
- ].filter((options) => options.length > 0);
799
- if (exactGroups.length === 0) return [];
800
- const requirements = [];
801
- const failures = [];
802
- for (const options of exactGroups) {
803
- try {
804
- requirements.push(
805
- ...await server.buildPaymentRequirementsFromOptions(options, { request })
806
- );
807
- } catch (error) {
808
- const err = error instanceof Error ? error : new Error(String(error));
809
- failures.push(err);
810
- if (exactGroups.length === 1) {
811
- throw err;
721
+
722
+ // src/pipeline/context/invoke.ts
723
+ async function invoke(ctx, wallet, account, body, payment) {
724
+ const handlerCtx = {
725
+ body,
726
+ query: parseQuery(ctx.request, ctx.routeEntry),
727
+ request: ctx.request,
728
+ requestId: ctx.meta.requestId,
729
+ route: ctx.routeEntry.key,
730
+ wallet,
731
+ payment,
732
+ account,
733
+ alert(level, message, alertMeta) {
734
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
735
+ level,
736
+ message,
737
+ route: ctx.routeEntry.key,
738
+ meta: alertMeta
739
+ });
740
+ },
741
+ setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
742
+ };
743
+ let rawResult;
744
+ let handlerError;
745
+ const response = await safeCallHandler(
746
+ async (c) => {
747
+ rawResult = await ctx.handler(c);
748
+ return rawResult;
749
+ },
750
+ handlerCtx,
751
+ {
752
+ onError(error) {
753
+ handlerError = error;
812
754
  }
813
- console.warn(
814
- `[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
815
- );
816
755
  }
756
+ );
757
+ return { response, rawResult, handlerError };
758
+ }
759
+
760
+ // src/pipeline/context/fire-provider-quota.ts
761
+ function fireProviderQuota(ctx, response, handlerResult) {
762
+ const { providerName, providerConfig } = ctx.routeEntry;
763
+ if (!providerName || !providerConfig?.extractQuota) return;
764
+ if (response.status >= 400) return;
765
+ try {
766
+ const quota = providerConfig.extractQuota(handlerResult, response.headers);
767
+ if (!quota) return;
768
+ const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
769
+ const overage = providerConfig.overage ?? "same-rate";
770
+ const event = {
771
+ provider: providerName,
772
+ route: ctx.routeEntry.key,
773
+ remaining: quota.remaining,
774
+ limit: quota.limit,
775
+ spend: quota.spend,
776
+ level,
777
+ overage,
778
+ message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
779
+ };
780
+ firePluginHook(ctx.deps.plugin, "onProviderQuota", ctx.pluginCtx, event);
781
+ } catch {
817
782
  }
818
- if (requirements.length > 0) {
819
- return requirements;
820
- }
821
- throw failures[0] ?? new Error("Failed to build x402 exact requirements");
822
783
  }
823
- function buildCustomRequirements(price, accepts) {
824
- return accepts.filter((accept) => accept.scheme !== "exact").map((accept) => buildCustomRequirement(price, accept));
784
+ function computeQuotaLevel(remaining, warn, critical) {
785
+ if (remaining === null) return "healthy";
786
+ if (critical !== void 0 && remaining <= critical) return "critical";
787
+ if (warn !== void 0 && remaining <= warn) return "warn";
788
+ return "healthy";
825
789
  }
826
- async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork) {
827
- const requirements = await buildExpectedRequirements(server, request, price, accepts);
828
- if (!needsFacilitatorEnrichment(accepts)) return requirements;
829
- return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork);
790
+
791
+ // src/pipeline/context/finalize.ts
792
+ function finalize(ctx, response, rawResult, requestBody) {
793
+ fireProviderQuota(ctx, response, rawResult);
794
+ firePluginResponse(ctx, response, requestBody, rawResult);
795
+ return response;
830
796
  }
831
- function needsFacilitatorEnrichment(accepts) {
832
- return accepts.some((accept) => accept.scheme !== "exact") || hasSolanaAccepts(accepts);
797
+
798
+ // src/pipeline/context/run-handler-only.ts
799
+ async function runHandlerOnly(ctx, wallet, account) {
800
+ const body = await parseBody(ctx.request, ctx.routeEntry);
801
+ if (!body.ok) {
802
+ firePluginResponse(ctx, body.response);
803
+ return body.response;
804
+ }
805
+ const validateErr = await runValidate(ctx, body.data);
806
+ if (validateErr) return validateErr;
807
+ const result = await invoke(ctx, wallet, account, body.data, null);
808
+ return finalize(ctx, result.response, result.rawResult, body.data);
833
809
  }
834
- async function enrichGroup(group, resource) {
835
- const accepted = await enrichRequirementsWithFacilitatorAccepts(
836
- group.facilitator,
837
- resource,
838
- group.items.map(({ requirement }) => requirement)
839
- );
840
- if (accepted.length !== group.items.length) {
841
- throw new Error(
842
- `Facilitator /accepts returned ${accepted.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
810
+
811
+ // src/pipeline/context/settlement-context.ts
812
+ function settlementContext(ctx, scope) {
813
+ return {
814
+ route: ctx.routeEntry.key,
815
+ request: ctx.request,
816
+ body: scope.body,
817
+ wallet: scope.wallet,
818
+ account: scope.account,
819
+ payment: scope.payment,
820
+ response: scope.response,
821
+ result: scope.rawResult
822
+ };
823
+ }
824
+
825
+ // src/pipeline/context/run-before-settle.ts
826
+ async function runBeforeSettle(ctx, scope) {
827
+ const hook = ctx.routeEntry.settlement?.beforeSettle;
828
+ if (!hook) return null;
829
+ try {
830
+ await hook(settlementContext(ctx, scope));
831
+ return null;
832
+ } catch (error) {
833
+ return fail(
834
+ ctx,
835
+ errorStatus(error, 500),
836
+ errorMessage(error, "Pre-settlement validation failed"),
837
+ scope.body
843
838
  );
844
839
  }
845
- return accepted;
846
840
  }
847
- async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork) {
848
- const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
849
- if (groups.length === 0) return requirements;
850
- const results = await Promise.all(
851
- groups.map(async (group) => {
852
- try {
853
- return { success: true, group, accepted: await enrichGroup(group, resource) };
854
- } catch (err) {
855
- const label = group.facilitator.url ?? group.facilitator.network;
856
- const reason = err instanceof Error ? err.message : String(err);
857
- console.warn(
858
- `[router] ${label} /accepts failed, dropping ${group.items.length} requirement(s): ${reason}`
859
- );
860
- return { success: false, group };
861
- }
862
- })
863
- );
864
- const enriched = [...requirements];
865
- results.filter((r) => r.success).forEach(({ group, accepted }) => {
866
- accepted.forEach((req, offset) => {
867
- const index = group.items[offset]?.index;
868
- if (index !== void 0) enriched[index] = req;
841
+
842
+ // src/pipeline/context/run-settlement-error.ts
843
+ async function runSettlementError(ctx, scope, error, phase) {
844
+ const hook = ctx.routeEntry.settlement?.onSettlementError;
845
+ if (!hook) return;
846
+ try {
847
+ await hook({ ...settlementContext(ctx, scope), error, phase });
848
+ } catch (hookError) {
849
+ const message = errorMessage(hookError, "Settlement error hook failed");
850
+ console.error(`[router] ${ctx.routeEntry.key}: onSettlementError failed: ${message}`);
851
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
852
+ level: "error",
853
+ message: `Settlement error hook failed: ${message}`,
854
+ route: ctx.routeEntry.key
869
855
  });
870
- });
871
- const failedIndices = new Set(
872
- results.filter((r) => !r.success).flatMap(({ group }) => group.items.map(({ index }) => index))
873
- );
874
- const remaining = enriched.filter((_, i) => !failedIndices.has(i));
875
- if (remaining.length === 0) {
876
- throw new Error(
877
- "All facilitator enrichments failed; no payment requirements remain for challenge"
878
- );
879
856
  }
880
- return remaining;
881
857
  }
882
- function collectEnrichmentGroups(requirements, facilitatorsByNetwork) {
883
- const groups = [];
884
- requirements.forEach((requirement, index) => {
885
- if (!requiresFacilitatorEnrichment(requirement)) return;
886
- const facilitator = getRequiredFacilitator(requirement, facilitatorsByNetwork);
887
- const existing = groups.find(
888
- (group) => sameResolvedX402Facilitator(group.facilitator, facilitator)
889
- );
890
- if (existing) {
891
- existing.items.push({ index, requirement });
892
- return;
893
- }
894
- groups.push({
895
- facilitator,
896
- items: [{ index, requirement }]
858
+
859
+ // src/pipeline/context/run-after-settle.ts
860
+ async function runAfterSettle(ctx, scope) {
861
+ const hook = ctx.routeEntry.settlement?.afterSettle;
862
+ if (!hook) return;
863
+ try {
864
+ await hook(settlementContext(ctx, scope));
865
+ } catch (error) {
866
+ const message = errorMessage(error, "Post-settlement hook failed");
867
+ console.error(`[router] ${ctx.routeEntry.key}: afterSettle failed: ${message}`);
868
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
869
+ level: "error",
870
+ message: `Post-settlement hook failed: ${message}`,
871
+ route: ctx.routeEntry.key
897
872
  });
898
- });
899
- return groups;
873
+ await runSettlementError(ctx, scope, error, "afterSettle");
874
+ }
900
875
  }
901
- function getRequiredFacilitator(requirement, facilitatorsByNetwork) {
902
- const facilitator = getFacilitatorForRequirement(facilitatorsByNetwork, requirement);
903
- if (!facilitator) {
904
- throw new Error(
905
- `Missing x402 facilitator for ${requirement.scheme} requirement on ${requirement.network}`
906
- );
876
+
877
+ // src/pipeline/context/run-settled-handler-error.ts
878
+ async function runSettledHandlerError(ctx, scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
879
+ const hook = ctx.routeEntry.settlement?.onSettledHandlerError;
880
+ if (!hook) return;
881
+ try {
882
+ await hook({ ...settlementContext(ctx, scope), error });
883
+ } catch (hookError) {
884
+ const message = errorMessage(hookError, "Settled handler error hook failed");
885
+ console.error(`[router] ${ctx.routeEntry.key}: onSettledHandlerError failed: ${message}`);
886
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
887
+ level: "error",
888
+ message: `Settled handler error hook failed: ${message}`,
889
+ route: ctx.routeEntry.key
890
+ });
907
891
  }
908
- return facilitator;
909
892
  }
910
- function requiresFacilitatorEnrichment(requirement) {
911
- return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
893
+
894
+ // src/pipeline/context/grant-entitlement.ts
895
+ async function grantEntitlementIfSiwx(ctx, wallet) {
896
+ if (!ctx.routeEntry.siwxEnabled) return;
897
+ try {
898
+ await ctx.deps.entitlementStore.grant(ctx.routeEntry.key, wallet);
899
+ } catch (error) {
900
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
901
+ level: "warn",
902
+ message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
903
+ route: ctx.routeEntry.key
904
+ });
905
+ }
912
906
  }
913
- function buildCustomRequirement(price, accept) {
914
- if (!accept.asset) {
915
- throw new Error(
916
- `Custom x402 accept '${accept.scheme}' on '${accept.network}' is missing asset`
917
- );
918
- }
919
- return {
920
- scheme: accept.scheme,
921
- network: accept.network,
922
- amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
923
- asset: accept.asset,
924
- payTo: accept.payTo,
925
- maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
926
- extra: accept.extra ?? {}
927
- };
928
- }
929
- function buildChallengeResource(request, routeEntry) {
930
- return {
931
- url: request.url,
932
- method: routeEntry.method,
933
- description: routeEntry.description,
934
- mimeType: "application/json"
935
- };
936
- }
937
- async function readPaymentPayload(request) {
938
- const paymentHeader = request.headers.get("PAYMENT-SIGNATURE") ?? request.headers.get("X-PAYMENT");
939
- if (!paymentHeader) return null;
940
- const { decodePaymentSignatureHeader } = await import("@x402/core/http");
941
- return decodePaymentSignatureHeader(paymentHeader);
942
- }
943
- function invalidPaymentVerification() {
944
- return { valid: false, payload: null, requirements: null, payer: null };
945
- }
946
- function decimalToAtomicUnits(amount, decimals) {
947
- const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
948
- if (!match?.groups) {
949
- throw new Error(`Invalid decimal amount '${amount}'`);
950
- }
951
- const whole = match.groups.whole;
952
- const fraction = match.groups.fraction ?? "";
953
- if (fraction.length > decimals) {
954
- throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
955
- }
956
- const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
957
- return normalized === "" ? "0" : normalized;
907
+
908
+ // src/pipeline/context/settle-and-finalize.ts
909
+ async function settleAndFinalize(args) {
910
+ const { ctx, strategy, verifyOutcome, scope, rawResult, body, onSettleError } = args;
911
+ const { request, routeEntry, deps } = ctx;
912
+ const settle = await strategy.settle({
913
+ request,
914
+ response: scope.response,
915
+ payment: verifyOutcome.payment,
916
+ token: verifyOutcome.token,
917
+ routeEntry,
918
+ deps
919
+ });
920
+ if (!settle.ok) {
921
+ if (onSettleError) await onSettleError(settle.error, settle.failMessage);
922
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
923
+ }
924
+ await grantEntitlementIfSiwx(ctx, verifyOutcome.wallet);
925
+ firePluginHook(deps.plugin, "onPaymentSettled", ctx.pluginCtx, {
926
+ protocol: strategy.protocol,
927
+ payer: verifyOutcome.wallet,
928
+ transaction: settle.settledPayment.transaction ?? "",
929
+ network: settle.settledPayment.network
930
+ });
931
+ await runAfterSettle(ctx, {
932
+ ...scope,
933
+ payment: settle.settledPayment,
934
+ response: settle.response
935
+ });
936
+ return finalize(ctx, settle.response, rawResult, body);
958
937
  }
959
- async function settleX402Payment(server, payload, requirements) {
960
- const { encodePaymentResponseHeader } = await import("@x402/core/http");
961
- const result = await server.settlePayment(payload, requirements);
962
- const encoded = encodePaymentResponseHeader(result);
963
- return { encoded, result };
938
+
939
+ // src/auth/normalize-wallet.ts
940
+ function normalizeWalletAddress(address) {
941
+ return /^0x/i.test(address) ? address.toLowerCase() : address;
964
942
  }
965
943
 
966
944
  // src/auth/siwx.ts
@@ -984,7 +962,7 @@ function categorizeValidationError(error) {
984
962
  }
985
963
  async function verifySIWX(request, _routeEntry, nonceStore) {
986
964
  const { parseSIWxHeader, validateSIWxMessage, verifySIWxSignature } = await import("@x402/extensions/sign-in-with-x");
987
- const header = request.headers.get("SIGN-IN-WITH-X");
965
+ const header = request.headers.get(HEADERS.SIWX);
988
966
  if (!header) {
989
967
  return { valid: false, wallet: null, code: "siwx_missing_header" };
990
968
  }
@@ -1013,25 +991,52 @@ async function buildSIWXExtension() {
1013
991
  return declareSIWxExtension();
1014
992
  }
1015
993
 
1016
- // src/auth/mpp-siwx.ts
1017
- var import_mppx = require("mppx");
1018
- var import_viem = require("viem");
1019
- async function verifyMppSiwx(request, mppx) {
1020
- const result = await mppx.charge({ amount: "0" })(request);
1021
- if (result.status === 402) {
1022
- return { valid: false, challenge: result.challenge };
994
+ // src/pipeline/context/try-siwx-fast-path.ts
995
+ async function trySiwxFastPath(ctx, account) {
996
+ const { request, routeEntry, deps } = ctx;
997
+ if (!routeEntry.siwxEnabled) return null;
998
+ const siwxHeader = request.headers.get(HEADERS.SIWX);
999
+ if (!siwxHeader) return null;
1000
+ const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
1001
+ if (!siwx.valid) return null;
1002
+ const wallet = normalizeWalletAddress(siwx.wallet);
1003
+ ctx.pluginCtx.setVerifiedWallet(wallet);
1004
+ const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
1005
+ if (!entitled) return null;
1006
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
1007
+ authMode: "siwx",
1008
+ wallet,
1009
+ route: routeEntry.key
1010
+ });
1011
+ return runHandlerOnly(ctx, wallet, account);
1012
+ }
1013
+
1014
+ // src/pipeline/context/should-parse-body-early.ts
1015
+ function shouldParseBodyEarly(incomingStrategy, routeEntry, pricing) {
1016
+ if (incomingStrategy) return false;
1017
+ if (!routeEntry.bodySchema) return false;
1018
+ return (pricing?.needsBody ?? false) || !!routeEntry.validateFn;
1019
+ }
1020
+
1021
+ // src/pipeline/context/protocol-init-error.ts
1022
+ function protocolInitError(routeEntry, deps) {
1023
+ if (!routeEntry.pricing) return null;
1024
+ const errors = [];
1025
+ for (const protocol of routeEntry.protocols) {
1026
+ if (protocol === "x402" && deps.x402InitError) {
1027
+ errors.push(`x402: ${deps.x402InitError}`);
1028
+ }
1029
+ if (protocol === "mpp" && deps.mppInitError) {
1030
+ errors.push(`mpp: ${deps.mppInitError}`);
1031
+ }
1023
1032
  }
1024
- const credential = import_mppx.Credential.fromRequest(request);
1025
- const rawSource = credential?.source ?? "";
1026
- const didParts = rawSource.split(":");
1027
- const lastPart = didParts[didParts.length - 1];
1028
- const wallet = normalizeWalletAddress((0, import_viem.isAddress)(lastPart) ? (0, import_viem.getAddress)(lastPart) : rawSource);
1029
- return { valid: true, wallet, withReceipt: result.withReceipt };
1033
+ if (errors.length === 0) return null;
1034
+ return `Payment protocol initialization failed. ${errors.join("; ")}`;
1030
1035
  }
1031
1036
 
1032
1037
  // src/auth/api-key.ts
1033
1038
  async function verifyApiKey(request, resolver) {
1034
- const apiKey = request.headers.get("X-API-Key") ?? extractBearerToken(request.headers.get("Authorization"));
1039
+ const apiKey = request.headers.get(HEADERS.API_KEY) ?? extractBearerToken(request.headers.get(HEADERS.AUTHORIZATION));
1035
1040
  if (!apiKey) {
1036
1041
  return { valid: false, account: null };
1037
1042
  }
@@ -1043,939 +1048,1311 @@ async function verifyApiKey(request, resolver) {
1043
1048
  }
1044
1049
  function extractBearerToken(header) {
1045
1050
  if (!header) return null;
1046
- if (header.startsWith("Bearer ")) return header.slice(7);
1051
+ if (header.startsWith(AUTH_SCHEME.BEARER)) return header.slice(AUTH_SCHEME.BEARER.length);
1047
1052
  return null;
1048
1053
  }
1049
1054
 
1050
- // src/orchestrate.ts
1051
- init_x402_config();
1052
- function getRequirementNetwork(requirements, fallback) {
1053
- const network = requirements?.network;
1054
- return typeof network === "string" ? network : fallback;
1055
- }
1056
- function getRequirementRecipient(requirements) {
1057
- const payTo = requirements?.payTo;
1058
- return typeof payTo === "string" ? payTo : void 0;
1059
- }
1060
- function siwxSignatureType(network) {
1061
- return network.startsWith("solana:") ? "ed25519" : "eip191";
1055
+ // src/pipeline/flows/api-key-only.ts
1056
+ async function runApiKeyOnlyFlow(ctx) {
1057
+ if (!ctx.routeEntry.apiKeyResolver) {
1058
+ return fail(ctx, 401, "API key resolver not configured");
1059
+ }
1060
+ const result = await verifyApiKey(ctx.request, ctx.routeEntry.apiKeyResolver);
1061
+ if (!result.valid) return fail(ctx, 401, "Invalid or missing API key");
1062
+ firePluginHook(ctx.deps.plugin, "onAuthVerified", ctx.pluginCtx, {
1063
+ authMode: "apiKey",
1064
+ wallet: null,
1065
+ route: ctx.routeEntry.key,
1066
+ account: result.account
1067
+ });
1068
+ return runHandlerOnly(ctx, null, result.account);
1062
1069
  }
1063
- function getSupportedChains(x402Accepts, fallbackNetwork) {
1064
- const seen = /* @__PURE__ */ new Set();
1065
- const chains = [];
1066
- for (const accept of x402Accepts) {
1067
- if (accept.network && !seen.has(accept.network)) {
1068
- seen.add(accept.network);
1069
- chains.push({ chainId: accept.network, type: siwxSignatureType(accept.network) });
1070
+
1071
+ // src/pricing/dynamic.ts
1072
+ var DynamicPricing = class {
1073
+ constructor(opts) {
1074
+ this.opts = opts;
1075
+ }
1076
+ needsBody = true;
1077
+ async quote(body) {
1078
+ try {
1079
+ const raw = await this.opts.fn(body);
1080
+ return this.cap(raw, body);
1081
+ } catch (err) {
1082
+ this.alert("error", `Pricing function failed: ${msg(err)}`, {
1083
+ error: err instanceof Error ? err.stack : String(err),
1084
+ body
1085
+ });
1086
+ if (this.opts.maxPrice) {
1087
+ this.alert("warn", `Using maxPrice ${this.opts.maxPrice} as fallback after pricing error`);
1088
+ return this.opts.maxPrice;
1089
+ }
1090
+ throw err;
1070
1091
  }
1071
1092
  }
1072
- if (chains.length === 0) {
1073
- chains.push({ chainId: fallbackNetwork, type: siwxSignatureType(fallbackNetwork) });
1093
+ challengeQuote(body) {
1094
+ if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
1095
+ return this.quote(body);
1074
1096
  }
1075
- return chains;
1076
- }
1077
- function createRequestHandler(routeEntry, handler, deps) {
1078
- async function invoke(request, meta, pluginCtx, wallet, account, parsedBody, payment) {
1079
- const ctx = {
1080
- body: parsedBody,
1081
- query: parseQuery(request, routeEntry),
1082
- request,
1083
- requestId: meta.requestId,
1084
- route: routeEntry.key,
1085
- wallet,
1086
- payment,
1087
- account,
1088
- alert(level, message, alertMeta) {
1089
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1090
- level,
1091
- message,
1092
- route: routeEntry.key,
1093
- meta: alertMeta
1094
- });
1095
- },
1096
- setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
1097
+ describe() {
1098
+ return {
1099
+ mode: "dynamic",
1100
+ min: this.opts.minPrice ?? "0",
1101
+ max: this.opts.maxPrice ?? "0"
1097
1102
  };
1098
- let rawResult;
1099
- const response = await safeCallHandler(async (c) => {
1100
- rawResult = await handler(c);
1101
- return rawResult;
1102
- }, ctx);
1103
- return { response, rawResult };
1104
- }
1105
- function finalize(response, rawResult, meta, pluginCtx, requestBody) {
1106
- fireProviderQuota(routeEntry, response, rawResult, deps, pluginCtx);
1107
- firePluginResponse(deps, pluginCtx, meta, response, requestBody, rawResult);
1108
- }
1109
- function fail(status, message, meta, pluginCtx, requestBody) {
1110
- const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
1111
- firePluginResponse(deps, pluginCtx, meta, response, requestBody);
1112
- return response;
1113
1103
  }
1114
- return async (request) => {
1115
- await deps.initPromise;
1116
- const meta = buildMeta(request, routeEntry);
1117
- const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
1118
- async function handleAuth(wallet, account2) {
1119
- const body2 = await parseBody(request, routeEntry);
1120
- if (!body2.ok) {
1121
- firePluginResponse(deps, pluginCtx, meta, body2.response);
1122
- return body2.response;
1123
- }
1124
- if (routeEntry.validateFn) {
1125
- try {
1126
- await routeEntry.validateFn(body2.data);
1127
- } catch (err) {
1128
- const status = err.status ?? 400;
1129
- const message = err instanceof Error ? err.message : "Validation failed";
1130
- return fail(status, message, meta, pluginCtx, body2.data);
1131
- }
1132
- }
1133
- const { response, rawResult } = await invoke(
1134
- request,
1135
- meta,
1136
- pluginCtx,
1137
- wallet,
1138
- account2,
1139
- body2.data,
1140
- null
1141
- );
1142
- finalize(response, rawResult, meta, pluginCtx, body2.data);
1143
- return response;
1144
- }
1145
- if (routeEntry.authMode === "unprotected") {
1146
- return handleAuth(null, void 0);
1147
- }
1148
- let account;
1149
- if (routeEntry.authMode === "apiKey" || routeEntry.apiKeyResolver) {
1150
- if (!routeEntry.apiKeyResolver) {
1151
- return fail(401, "API key resolver not configured", meta, pluginCtx);
1152
- }
1153
- const keyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
1154
- if (!keyResult.valid) {
1155
- return fail(401, "Invalid or missing API key", meta, pluginCtx);
1156
- }
1157
- account = keyResult.account;
1158
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1159
- authMode: "apiKey",
1160
- wallet: null,
1161
- route: routeEntry.key,
1162
- account
1104
+ cap(raw, body) {
1105
+ if (!this.opts.maxPrice) return raw;
1106
+ const n = parseFloat(raw);
1107
+ const max = parseFloat(this.opts.maxPrice);
1108
+ if (!Number.isFinite(n) || n > max) {
1109
+ this.alert("warn", `Price ${raw} exceeds maxPrice ${this.opts.maxPrice}, capping`, {
1110
+ calculated: raw,
1111
+ maxPrice: this.opts.maxPrice,
1112
+ body
1163
1113
  });
1164
- if (routeEntry.authMode === "apiKey" && !routeEntry.pricing) {
1165
- return handleAuth(null, account);
1166
- }
1167
- }
1168
- const protocol = detectProtocol(request);
1169
- let earlyBodyData;
1170
- const pricingNeedsBody = routeEntry.pricing != null && typeof routeEntry.pricing !== "string";
1171
- const needsEarlyParse = !protocol && routeEntry.bodySchema && (pricingNeedsBody || routeEntry.validateFn);
1172
- if (needsEarlyParse) {
1173
- const requestForPricing = request.clone();
1174
- const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
1175
- if (earlyBodyResult.ok) {
1176
- earlyBodyData = earlyBodyResult.data;
1177
- if (routeEntry.validateFn) {
1178
- try {
1179
- await routeEntry.validateFn(earlyBodyData);
1180
- } catch (err) {
1181
- const status = err.status ?? 400;
1182
- const message = err instanceof Error ? err.message : "Validation failed";
1183
- return fail(status, message, meta, pluginCtx, earlyBodyData);
1184
- }
1185
- }
1186
- } else {
1187
- firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
1188
- return earlyBodyResult.response;
1189
- }
1190
- }
1191
- if (routeEntry.authMode === "siwx" || routeEntry.siwxEnabled) {
1192
- if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get("SIGN-IN-WITH-X")) {
1193
- const requestForValidation = request.clone();
1194
- const earlyBodyResult = await parseBody(requestForValidation, routeEntry);
1195
- if (earlyBodyResult.ok) {
1196
- try {
1197
- await routeEntry.validateFn(earlyBodyResult.data);
1198
- } catch (err) {
1199
- const status = err.status ?? 400;
1200
- const message = err instanceof Error ? err.message : "Validation failed";
1201
- return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
1202
- }
1203
- }
1204
- }
1205
- const siwxHeader = request.headers.get("SIGN-IN-WITH-X");
1206
- if (!siwxHeader && protocol === "mpp" && routeEntry.authMode === "siwx" && deps.mppx) {
1207
- let mppSiwxResult;
1208
- try {
1209
- mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
1210
- } catch (err) {
1211
- const message = err instanceof Error ? err.message : String(err);
1212
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1213
- level: "critical",
1214
- message: `MPP SIWX verification failed: ${message}`,
1215
- route: routeEntry.key
1216
- });
1217
- return fail(500, `MPP SIWX verification failed: ${message}`, meta, pluginCtx);
1218
- }
1219
- if (mppSiwxResult.valid) {
1220
- pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
1221
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1222
- authMode: "siwx",
1223
- wallet: mppSiwxResult.wallet,
1224
- route: routeEntry.key
1225
- });
1226
- const authResponse = await handleAuth(mppSiwxResult.wallet, void 0);
1227
- if (authResponse.status < 400) {
1228
- return mppSiwxResult.withReceipt(authResponse);
1229
- }
1230
- return authResponse;
1231
- }
1232
- }
1233
- if (!siwxHeader && routeEntry.authMode === "siwx") {
1234
- const url = new URL(request.url);
1235
- const nonce = crypto.randomUUID().replace(/-/g, "");
1236
- const supportedChains = getSupportedChains(deps.x402Accepts, deps.network);
1237
- const primaryChain = supportedChains[0];
1238
- const siwxInfo = {
1239
- domain: url.hostname,
1240
- uri: request.url,
1241
- version: "1",
1242
- chainId: primaryChain.chainId,
1243
- type: primaryChain.type,
1244
- nonce,
1245
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1246
- expirationTime: new Date(Date.now() + SIWX_CHALLENGE_EXPIRY_MS).toISOString(),
1247
- statement: "Sign in to verify your wallet identity"
1248
- };
1249
- let siwxSchema;
1250
- try {
1251
- siwxSchema = await buildSIWXExtension();
1252
- } catch {
1253
- }
1254
- const paymentRequired = {
1255
- x402Version: 2,
1256
- error: "SIWX authentication required",
1257
- resource: {
1258
- url: request.url,
1259
- description: routeEntry.description ?? "SIWX-protected endpoint",
1260
- mimeType: "application/json"
1261
- },
1262
- accepts: [],
1263
- extensions: {
1264
- "sign-in-with-x": {
1265
- info: siwxInfo,
1266
- // supportedChains at top level required by MCP tools for chain detection
1267
- supportedChains,
1268
- ...siwxSchema ? { schema: siwxSchema } : {}
1269
- }
1270
- }
1271
- };
1272
- let encoded;
1273
- try {
1274
- const { encodePaymentRequiredHeader } = await import("@x402/core/http");
1275
- encoded = encodePaymentRequiredHeader(paymentRequired);
1276
- } catch (err) {
1277
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1278
- level: "warn",
1279
- message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
1280
- route: routeEntry.key
1281
- });
1282
- }
1283
- const response = new import_server2.NextResponse(JSON.stringify(paymentRequired), {
1284
- status: 402,
1285
- headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
1286
- });
1287
- if (encoded) response.headers.set("PAYMENT-REQUIRED", encoded);
1288
- if (deps.mppx) {
1289
- try {
1290
- const mppChallenge = await deps.mppx.charge({ amount: "0" })(request);
1291
- if (mppChallenge.status === 402) {
1292
- const wwwAuth = mppChallenge.challenge.headers.get("WWW-Authenticate");
1293
- if (wwwAuth) response.headers.set("WWW-Authenticate", wwwAuth);
1294
- }
1295
- } catch {
1296
- }
1297
- }
1298
- firePluginResponse(deps, pluginCtx, meta, response);
1299
- return response;
1300
- }
1301
- if (siwxHeader) {
1302
- const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
1303
- if (!siwx.valid) {
1304
- if (routeEntry.authMode === "siwx") {
1305
- const response = import_server2.NextResponse.json(
1306
- { error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
1307
- { status: 402 }
1308
- );
1309
- firePluginResponse(deps, pluginCtx, meta, response);
1310
- return response;
1311
- }
1312
- } else {
1313
- const wallet = normalizeWalletAddress(siwx.wallet);
1314
- pluginCtx.setVerifiedWallet(wallet);
1315
- if (routeEntry.authMode === "siwx") {
1316
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1317
- authMode: "siwx",
1318
- wallet,
1319
- route: routeEntry.key
1320
- });
1321
- return handleAuth(wallet, void 0);
1322
- }
1323
- if (routeEntry.siwxEnabled && routeEntry.pricing) {
1324
- const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
1325
- if (entitled) {
1326
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1327
- authMode: "siwx",
1328
- wallet,
1329
- route: routeEntry.key
1330
- });
1331
- return handleAuth(wallet, account);
1332
- }
1333
- }
1334
- }
1335
- }
1336
- }
1337
- if (!protocol || protocol === "siwx") {
1338
- if (routeEntry.pricing) {
1339
- const initErrors = routeEntry.protocols.map((p) => {
1340
- if (p === "x402" && deps.x402InitError) return `x402: ${deps.x402InitError}`;
1341
- if (p === "mpp" && deps.mppInitError) return `mpp: ${deps.mppInitError}`;
1342
- return null;
1343
- }).filter(Boolean);
1344
- if (initErrors.length > 0) {
1345
- return fail(
1346
- 500,
1347
- `Payment protocol initialization failed. ${initErrors.join("; ")}`,
1348
- meta,
1349
- pluginCtx
1350
- );
1351
- }
1352
- }
1353
- return await build402(request, routeEntry, deps, meta, pluginCtx, earlyBodyData);
1354
- }
1355
- const body = await parseBody(request, routeEntry);
1356
- if (!body.ok) {
1357
- firePluginResponse(deps, pluginCtx, meta, body.response);
1358
- return body.response;
1114
+ return this.opts.maxPrice;
1359
1115
  }
1360
- if (routeEntry.validateFn) {
1116
+ return raw;
1117
+ }
1118
+ alert(level, message, meta) {
1119
+ this.opts.alert?.(level, message, meta);
1120
+ }
1121
+ };
1122
+ function msg(err) {
1123
+ return err instanceof Error ? err.message : String(err);
1124
+ }
1125
+
1126
+ // src/pricing/fixed.ts
1127
+ var FixedPricing = class {
1128
+ constructor(price) {
1129
+ this.price = price;
1130
+ }
1131
+ needsBody = false;
1132
+ quote() {
1133
+ return Promise.resolve(this.price);
1134
+ }
1135
+ challengeQuote() {
1136
+ return Promise.resolve(this.price);
1137
+ }
1138
+ describe() {
1139
+ return { mode: "fixed", amount: this.price };
1140
+ }
1141
+ };
1142
+
1143
+ // src/pricing/tiered.ts
1144
+ var TieredPricing = class {
1145
+ constructor(opts) {
1146
+ this.opts = opts;
1147
+ }
1148
+ needsBody = true;
1149
+ async quote(body) {
1150
+ const { field, tiers, default: defaultTier } = this.opts;
1151
+ const tierKey = body != null ? String(body[field] ?? "") : "";
1152
+ if (tierKey && tiers[tierKey]) return tiers[tierKey].price;
1153
+ if (defaultTier && tiers[defaultTier]) return tiers[defaultTier].price;
1154
+ if (!tierKey) {
1155
+ throw httpError(400, `Missing required field '${field}' for tier pricing`);
1156
+ }
1157
+ throw httpError(
1158
+ 400,
1159
+ `Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
1160
+ );
1161
+ }
1162
+ challengeQuote(body) {
1163
+ if (body !== void 0) {
1361
1164
  try {
1362
- await routeEntry.validateFn(body.data);
1363
- } catch (err) {
1364
- const status = err.status ?? 400;
1365
- const message = err instanceof Error ? err.message : "Validation failed";
1366
- return fail(status, message, meta, pluginCtx, body.data);
1165
+ return this.quote(body);
1166
+ } catch {
1367
1167
  }
1368
1168
  }
1369
- let price;
1169
+ return Promise.resolve(this.maxTierPrice());
1170
+ }
1171
+ describe() {
1172
+ return {
1173
+ mode: "tiered",
1174
+ tiers: Object.entries(this.opts.tiers).map(([key, tier]) => ({
1175
+ key,
1176
+ price: tier.price,
1177
+ ...tier.label !== void 0 ? { label: tier.label } : {}
1178
+ })),
1179
+ ...this.opts.default !== void 0 ? { default: this.opts.default } : {}
1180
+ };
1181
+ }
1182
+ maxTierPrice() {
1183
+ let max = "0";
1184
+ for (const tier of Object.values(this.opts.tiers)) {
1185
+ if (parseFloat(tier.price) > parseFloat(max)) max = tier.price;
1186
+ }
1187
+ return max;
1188
+ }
1189
+ };
1190
+ function httpError(status, message) {
1191
+ return Object.assign(new Error(message), { status });
1192
+ }
1193
+
1194
+ // src/pricing/index.ts
1195
+ function selectPricing(raw, deps = {}) {
1196
+ if (raw == null) return null;
1197
+ if (typeof raw === "string") {
1198
+ return new FixedPricing(raw);
1199
+ }
1200
+ if (typeof raw === "function") {
1201
+ return new DynamicPricing({
1202
+ fn: raw,
1203
+ maxPrice: deps.maxPrice,
1204
+ minPrice: deps.minPrice,
1205
+ route: deps.route,
1206
+ alert: deps.alert
1207
+ });
1208
+ }
1209
+ if (typeof raw === "object" && "tiers" in raw) {
1210
+ return new TieredPricing({
1211
+ field: raw.field,
1212
+ tiers: raw.tiers,
1213
+ default: raw.default,
1214
+ maxPrice: deps.maxPrice,
1215
+ minPrice: deps.minPrice
1216
+ });
1217
+ }
1218
+ throw new Error(`Unknown pricing config: ${JSON.stringify(raw)}`);
1219
+ }
1220
+
1221
+ // src/protocols/mpp/credential.ts
1222
+ var import_mppx = require("mppx");
1223
+ var import_viem = require("viem");
1224
+ function readMppCredential(request) {
1225
+ const credential = import_mppx.Credential.fromRequest(request);
1226
+ if (!credential) return null;
1227
+ const wallet = walletFromDid(credential.source ?? "");
1228
+ const rawType = credential.payload?.type;
1229
+ const payloadType = rawType === "transaction" ? "transaction" : rawType === "hash" ? "hash" : "unknown";
1230
+ return { credential, wallet, payloadType };
1231
+ }
1232
+ function walletFromDid(rawSource) {
1233
+ const parts = rawSource.split(":");
1234
+ const last = parts[parts.length - 1];
1235
+ return normalizeWalletAddress((0, import_viem.isAddress)(last) ? (0, import_viem.getAddress)(last) : rawSource);
1236
+ }
1237
+
1238
+ // src/protocols/mpp/transaction-mode.ts
1239
+ var import_tempo = require("viem/tempo");
1240
+ var import_actions = require("viem/actions");
1241
+
1242
+ // src/protocols/mpp/receipt.ts
1243
+ var import_mppx2 = require("mppx");
1244
+ function extractTxHash(receiptHeader) {
1245
+ if (!receiptHeader) return "";
1246
+ try {
1247
+ return import_mppx2.Receipt.deserialize(receiptHeader).reference;
1248
+ } catch {
1249
+ return "";
1250
+ }
1251
+ }
1252
+ async function readChallengeReason(challenge) {
1253
+ try {
1254
+ const text = await challenge.clone().text();
1255
+ if (!text) return "";
1256
+ const problem = JSON.parse(text);
1257
+ return problem.detail || problem.title || "";
1258
+ } catch {
1259
+ return "";
1260
+ }
1261
+ }
1262
+
1263
+ // src/protocols/mpp/transaction-mode.ts
1264
+ async function verifyTxMode(args, info) {
1265
+ const { deps, price, routeEntry } = args;
1266
+ if (!deps.tempoClient) {
1267
+ return {
1268
+ ok: false,
1269
+ kind: "config",
1270
+ message: "tempoClient not configured for MPP transaction-payload mode"
1271
+ };
1272
+ }
1273
+ try {
1274
+ const serializedTx = info.credential.payload.signature;
1275
+ const transaction = import_tempo.Transaction.deserialize(serializedTx);
1276
+ await (0, import_actions.call)(deps.tempoClient, {
1277
+ ...transaction,
1278
+ account: transaction.from,
1279
+ calls: transaction.calls ?? []
1280
+ });
1281
+ } catch (err) {
1282
+ const message = err instanceof Error ? err.message : String(err);
1283
+ console.warn(`[router] ${routeEntry.key}: MPP simulation failed \u2014 ${message}`);
1284
+ return { ok: false, kind: "invalid" };
1285
+ }
1286
+ const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
1287
+ const payment = {
1288
+ protocol: "mpp",
1289
+ status: "verified",
1290
+ payer: info.wallet,
1291
+ amount: price,
1292
+ network: "tempo:4217",
1293
+ ...mppRecipient ? { recipient: mppRecipient } : {}
1294
+ };
1295
+ return {
1296
+ ok: true,
1297
+ wallet: info.wallet,
1298
+ payment,
1299
+ token: { mode: "transaction", credential: info.credential },
1300
+ alreadySettled: false
1301
+ };
1302
+ }
1303
+ async function settleTxMode(args) {
1304
+ const { request, response, payment, deps, routeEntry } = args;
1305
+ if (!deps.mppx) {
1306
+ return {
1307
+ ok: false,
1308
+ error: new Error("mppx unavailable"),
1309
+ failMessage: "MPP not initialized",
1310
+ failStatus: 500
1311
+ };
1312
+ }
1313
+ let result;
1314
+ try {
1315
+ result = await deps.mppx.charge({ amount: payment.amount })(request);
1316
+ } catch (err) {
1317
+ const message = err instanceof Error ? err.message : String(err);
1318
+ console.error(`[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`);
1319
+ return {
1320
+ ok: false,
1321
+ error: err,
1322
+ failMessage: `MPP payment processing failed: ${message}`,
1323
+ failStatus: 500
1324
+ };
1325
+ }
1326
+ if (result.status === 402) {
1327
+ const reason = await readChallengeReason(result.challenge);
1328
+ const detail = reason || "transaction reverted on-chain after handler execution";
1329
+ const settlementError = Object.assign(new Error(detail), {
1330
+ status: 402,
1331
+ detail,
1332
+ mppResult: result,
1333
+ challenge: result.challenge
1334
+ });
1335
+ console.error(`[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`);
1336
+ return {
1337
+ ok: false,
1338
+ error: settlementError,
1339
+ failMessage: `MPP payment failed: ${detail}`,
1340
+ failStatus: 500
1341
+ };
1342
+ }
1343
+ const receiptResponse = result.withReceipt(response);
1344
+ receiptResponse.headers.set("Cache-Control", "private");
1345
+ const receiptHeader = receiptResponse.headers.get(HEADERS.MPP_PAYMENT_RECEIPT) ?? void 0;
1346
+ const txHash = extractTxHash(receiptHeader);
1347
+ const settledPayment = {
1348
+ ...payment,
1349
+ status: "settled",
1350
+ ...txHash ? { transaction: txHash } : {},
1351
+ ...receiptHeader ? { receipt: receiptHeader } : {}
1352
+ };
1353
+ return { ok: true, response: receiptResponse, settledPayment };
1354
+ }
1355
+
1356
+ // src/protocols/mpp/hash-mode.ts
1357
+ async function verifyHashMode(args, info) {
1358
+ const { deps, price, routeEntry, request } = args;
1359
+ if (!deps.mppx) {
1360
+ const reason = deps.mppInitError ? `MPP initialization failed: ${deps.mppInitError}` : "MPP not initialized \u2014 ensure mppx is installed and mpp config (secretKey, currency, recipient) is correct";
1361
+ console.error(`[router] ${routeEntry.key}: ${reason}`);
1362
+ return { ok: false, kind: "config", message: reason };
1363
+ }
1364
+ let chargeResult;
1365
+ try {
1366
+ chargeResult = await deps.mppx.charge({ amount: price })(request);
1367
+ } catch (err) {
1368
+ const message = err instanceof Error ? err.message : String(err);
1369
+ console.error(`[router] ${routeEntry.key}: MPP charge failed: ${message}`);
1370
+ return { ok: false, kind: "config", message: `MPP payment processing failed: ${message}` };
1371
+ }
1372
+ if (chargeResult.status === 402) {
1373
+ const reason = await readChallengeReason(chargeResult.challenge);
1374
+ const detail = reason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
1375
+ console.warn(`[router] ${routeEntry.key}: MPP credential rejected \u2014 ${detail}`);
1376
+ return { ok: false, kind: "invalid" };
1377
+ }
1378
+ const receiptHeader = chargeResult.withReceipt(new Response()).headers.get(
1379
+ HEADERS.MPP_PAYMENT_RECEIPT
1380
+ );
1381
+ const txHash = extractTxHash(receiptHeader);
1382
+ const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
1383
+ const payment = {
1384
+ protocol: "mpp",
1385
+ status: "settled",
1386
+ payer: info.wallet,
1387
+ amount: price,
1388
+ network: "tempo:4217",
1389
+ ...mppRecipient ? { recipient: mppRecipient } : {},
1390
+ ...txHash ? { transaction: txHash } : {},
1391
+ ...receiptHeader ? { receipt: receiptHeader } : {}
1392
+ };
1393
+ return {
1394
+ ok: true,
1395
+ wallet: info.wallet,
1396
+ payment,
1397
+ token: { mode: "hash", charge: chargeResult },
1398
+ alreadySettled: true
1399
+ };
1400
+ }
1401
+ function settleHashMode(args) {
1402
+ const { response, payment, token } = args;
1403
+ const hashToken = token;
1404
+ const receiptResponse = hashToken.charge.withReceipt(response);
1405
+ receiptResponse.headers.set("Cache-Control", "private");
1406
+ return {
1407
+ ok: true,
1408
+ response: receiptResponse,
1409
+ settledPayment: payment
1410
+ };
1411
+ }
1412
+
1413
+ // src/protocols/mpp/strategy.ts
1414
+ var mppStrategy = {
1415
+ protocol: "mpp",
1416
+ detects(request) {
1417
+ const auth = request.headers.get(HEADERS.AUTHORIZATION);
1418
+ return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
1419
+ },
1420
+ async verify(args) {
1421
+ const info = readMppCredential(args.request);
1422
+ if (!info) return { ok: false, kind: "invalid" };
1423
+ if (info.payloadType === "transaction" && args.deps.tempoClient) {
1424
+ return verifyTxMode(args, info);
1425
+ }
1426
+ return verifyHashMode(args, info);
1427
+ },
1428
+ async settle(args) {
1429
+ const token = args.token;
1430
+ if (token.mode === "transaction") return settleTxMode(args);
1431
+ return settleHashMode(args);
1432
+ },
1433
+ async buildChallenge(args) {
1434
+ if (!args.deps.mppx) return {};
1370
1435
  try {
1371
- price = await resolvePrice(routeEntry.pricing, body.data);
1436
+ const result = await args.deps.mppx.charge({ amount: args.price })(args.request);
1437
+ if (result.status === 402) {
1438
+ const wwwAuth = result.challenge.headers.get(HEADERS.WWW_AUTHENTICATE);
1439
+ if (wwwAuth) return { headers: { [HEADERS.WWW_AUTHENTICATE]: wwwAuth } };
1440
+ }
1372
1441
  } catch (err) {
1373
- return fail(
1374
- err.status ?? 500,
1375
- err instanceof Error ? err.message : "Price resolution failed",
1376
- meta,
1377
- pluginCtx,
1378
- body.data
1379
- );
1380
- }
1381
- if (!routeEntry.protocols.includes(protocol)) {
1382
- const accepted = routeEntry.protocols.join(", ") || "none";
1383
1442
  console.warn(
1384
- `[router] ${routeEntry.key}: received ${protocol} payment but route accepts [${accepted}]`
1385
- );
1386
- return fail(
1387
- 400,
1388
- `This route does not accept ${protocol} payments. Accepted protocols: ${accepted}`,
1389
- meta,
1390
- pluginCtx,
1391
- body.data
1443
+ `[router] MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`
1392
1444
  );
1445
+ throw err;
1393
1446
  }
1394
- if (protocol === "x402") {
1395
- if (!deps.x402Server) {
1396
- const reason = deps.x402InitError ? `x402 facilitator initialization failed: ${deps.x402InitError}` : "x402 server not initialized \u2014 ensure @x402/core, @x402/evm, and @coinbase/x402 are installed";
1397
- console.error(`[router] ${routeEntry.key}: ${reason}`);
1398
- return fail(500, reason, meta, pluginCtx, body.data);
1399
- }
1400
- const accepts = await resolveX402Accepts(
1401
- request,
1402
- routeEntry,
1403
- deps.x402Accepts,
1404
- deps.payeeAddress,
1405
- body.data
1406
- );
1407
- const verify = await verifyX402Payment({
1408
- server: deps.x402Server,
1409
- request,
1410
- price,
1411
- accepts
1412
- });
1413
- if (!verify?.valid)
1414
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1415
- const { payload: verifyPayload, requirements: verifyRequirements } = verify;
1416
- const matchedNetwork = getRequirementNetwork(verifyRequirements, deps.network);
1417
- const matchedRecipient = getRequirementRecipient(verifyRequirements);
1418
- const wallet = normalizeWalletAddress(verify.payer);
1419
- const payment = {
1420
- protocol: "x402",
1421
- status: "verified",
1422
- payer: wallet,
1423
- amount: price,
1424
- network: matchedNetwork,
1425
- ...matchedRecipient ? { recipient: matchedRecipient } : {}
1426
- };
1427
- pluginCtx.setVerifiedWallet(wallet);
1428
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1429
- protocol: "x402",
1430
- payer: wallet,
1431
- amount: price,
1432
- network: matchedNetwork
1433
- });
1434
- const { response, rawResult } = await invoke(
1435
- request,
1436
- meta,
1437
- pluginCtx,
1438
- wallet,
1439
- account,
1440
- body.data,
1441
- payment
1447
+ return {};
1448
+ }
1449
+ };
1450
+
1451
+ // src/protocols/x402/strategy.ts
1452
+ init_accepts();
1453
+
1454
+ // src/protocols/x402/challenge.ts
1455
+ init_facilitators();
1456
+ init_solana();
1457
+
1458
+ // src/protocols/x402/requirements.ts
1459
+ init_evm();
1460
+ init_solana();
1461
+ async function buildExpectedRequirements(server, request, price, accepts) {
1462
+ const exactRequirements = await buildExactRequirements(server, request, price, accepts);
1463
+ const customRequirements = buildCustomRequirements(price, accepts);
1464
+ return [...exactRequirements, ...customRequirements];
1465
+ }
1466
+ async function buildExactRequirements(server, request, price, accepts) {
1467
+ const exactGroups = [
1468
+ buildEvmExactOptions(accepts, price),
1469
+ buildSolanaExactOptions(accepts, price)
1470
+ ].filter((options) => options.length > 0);
1471
+ if (exactGroups.length === 0) return [];
1472
+ const requirements = [];
1473
+ const failures = [];
1474
+ for (const options of exactGroups) {
1475
+ try {
1476
+ requirements.push(
1477
+ ...await server.buildPaymentRequirementsFromOptions(options, { request })
1442
1478
  );
1443
- if (response.status < 400) {
1444
- try {
1445
- const settle = await settleX402Payment(
1446
- deps.x402Server,
1447
- verifyPayload,
1448
- verifyRequirements
1449
- );
1450
- if (!settle.result?.success) {
1451
- const reason = settle.result?.errorReason || "x402 settlement returned success=false";
1452
- const error = new Error(reason);
1453
- error.errorReason = reason;
1454
- throw error;
1455
- }
1456
- if (routeEntry.siwxEnabled) {
1457
- try {
1458
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1459
- } catch (error) {
1460
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1461
- level: "warn",
1462
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1463
- route: routeEntry.key
1464
- });
1465
- }
1466
- }
1467
- response.headers.set("PAYMENT-RESPONSE", settle.encoded);
1468
- response.headers.set("Cache-Control", "private");
1469
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1470
- protocol: "x402",
1471
- payer: verify.payer,
1472
- transaction: String(settle.result?.transaction ?? ""),
1473
- network: matchedNetwork
1474
- });
1475
- } catch (err) {
1476
- const errObj = err;
1477
- console.error("Settlement failed", {
1478
- message: err instanceof Error ? err.message : String(err),
1479
- route: routeEntry.key,
1480
- network: matchedNetwork,
1481
- errorReason: errObj.errorReason,
1482
- facilitatorStatus: errObj.response?.status,
1483
- facilitatorBody: errObj.response?.data ?? errObj.response?.body
1484
- });
1485
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1486
- level: "critical",
1487
- message: `Settlement failed: ${err instanceof Error ? err.message : String(err)}`,
1488
- route: routeEntry.key
1489
- });
1490
- return fail(500, "Settlement failed", meta, pluginCtx, body.data);
1491
- }
1492
- }
1493
- finalize(response, rawResult, meta, pluginCtx, body.data);
1494
- return response;
1495
- }
1496
- if (protocol === "mpp") {
1497
- if (!deps.mppx) {
1498
- const reason = deps.mppInitError ? `MPP initialization failed: ${deps.mppInitError}` : "MPP not initialized \u2014 ensure mppx is installed and mpp config (secretKey, currency, recipient) is correct";
1499
- console.error(`[router] ${routeEntry.key}: ${reason}`);
1500
- return fail(500, reason, meta, pluginCtx, body.data);
1501
- }
1502
- const mppCredential = import_mppx2.Credential.fromRequest(request);
1503
- const rawSource = mppCredential?.source ?? "";
1504
- const didParts = rawSource.split(":");
1505
- const lastPart = didParts[didParts.length - 1];
1506
- const wallet = normalizeWalletAddress((0, import_viem2.isAddress)(lastPart) ? (0, import_viem2.getAddress)(lastPart) : rawSource);
1507
- const payloadType = mppCredential?.payload?.type;
1508
- if (payloadType === "transaction" && deps.tempoClient) {
1509
- try {
1510
- const serializedTx = mppCredential.payload.signature;
1511
- const transaction = import_tempo.Transaction.deserialize(serializedTx);
1512
- await (0, import_actions.call)(deps.tempoClient, {
1513
- ...transaction,
1514
- account: transaction.from,
1515
- calls: transaction.calls ?? []
1516
- });
1517
- } catch (err) {
1518
- const message = err instanceof Error ? err.message : String(err);
1519
- console.warn(`[router] ${routeEntry.key}: MPP simulation failed \u2014 ${message}`);
1520
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1521
- level: "warn",
1522
- message: `MPP simulation failed: ${message}`,
1523
- route: routeEntry.key
1524
- });
1525
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1526
- }
1527
- pluginCtx.setVerifiedWallet(wallet);
1528
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1529
- protocol: "mpp",
1530
- payer: wallet,
1531
- amount: price,
1532
- network: "tempo:4217"
1533
- });
1534
- const { response: response2, rawResult: rawResult2 } = await invoke(
1535
- request,
1536
- meta,
1537
- pluginCtx,
1538
- wallet,
1539
- account,
1540
- body.data,
1541
- {
1542
- protocol: "mpp",
1543
- status: "verified",
1544
- payer: wallet,
1545
- amount: price,
1546
- network: "tempo:4217",
1547
- recipient: deps.payeeAddress
1548
- }
1549
- );
1550
- if (response2.status < 400) {
1551
- let mppResult2;
1552
- try {
1553
- mppResult2 = await deps.mppx.charge({ amount: price })(request);
1554
- } catch (err) {
1555
- const message = err instanceof Error ? err.message : String(err);
1556
- console.error(
1557
- `[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`
1558
- );
1559
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1560
- level: "critical",
1561
- message: `MPP broadcast failed after handler: ${message}`,
1562
- route: routeEntry.key
1563
- });
1564
- return fail(
1565
- 500,
1566
- `MPP payment processing failed: ${message}`,
1567
- meta,
1568
- pluginCtx,
1569
- body.data
1570
- );
1571
- }
1572
- if (mppResult2.status === 402) {
1573
- let rejectReason = "";
1574
- try {
1575
- const problemBody = await mppResult2.challenge.clone().text();
1576
- if (problemBody) {
1577
- const problem = JSON.parse(problemBody);
1578
- rejectReason = problem.detail || problem.title || "";
1579
- }
1580
- } catch {
1581
- }
1582
- const detail = rejectReason || "transaction reverted on-chain after handler execution";
1583
- console.error(
1584
- `[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`
1585
- );
1586
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1587
- level: "critical",
1588
- message: `MPP payment failed after handler: ${detail}`,
1589
- route: routeEntry.key
1590
- });
1591
- return fail(500, `MPP payment failed: ${detail}`, meta, pluginCtx, body.data);
1592
- }
1593
- const receiptResponse = mppResult2.withReceipt(response2);
1594
- receiptResponse.headers.set("Cache-Control", "private");
1595
- let txHash2 = "";
1596
- const receiptHeader2 = receiptResponse.headers.get("Payment-Receipt");
1597
- if (receiptHeader2) {
1598
- try {
1599
- txHash2 = import_mppx2.Receipt.deserialize(receiptHeader2).reference;
1600
- } catch {
1601
- }
1602
- }
1603
- if (routeEntry.siwxEnabled) {
1604
- try {
1605
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1606
- } catch (error) {
1607
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1608
- level: "warn",
1609
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1610
- route: routeEntry.key
1611
- });
1612
- }
1613
- }
1614
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1615
- protocol: "mpp",
1616
- payer: wallet,
1617
- transaction: txHash2,
1618
- network: "tempo:4217"
1619
- });
1620
- finalize(receiptResponse, rawResult2, meta, pluginCtx, body.data);
1621
- return receiptResponse;
1622
- }
1623
- finalize(response2, rawResult2, meta, pluginCtx, body.data);
1624
- return response2;
1479
+ } catch (error) {
1480
+ const err = error instanceof Error ? error : new Error(String(error));
1481
+ failures.push(err);
1482
+ if (exactGroups.length === 1) {
1483
+ throw err;
1625
1484
  }
1626
- let mppResult;
1485
+ console.warn(
1486
+ `[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
1487
+ );
1488
+ }
1489
+ }
1490
+ if (requirements.length > 0) {
1491
+ return requirements;
1492
+ }
1493
+ throw failures[0] ?? new Error("Failed to build x402 exact requirements");
1494
+ }
1495
+ function buildCustomRequirements(price, accepts) {
1496
+ return accepts.filter((accept) => accept.scheme !== "exact").map((accept) => buildCustomRequirement(price, accept));
1497
+ }
1498
+ function buildCustomRequirement(price, accept) {
1499
+ if (!accept.asset) {
1500
+ throw new Error(
1501
+ `Custom x402 accept '${accept.scheme}' on '${accept.network}' is missing asset`
1502
+ );
1503
+ }
1504
+ return {
1505
+ scheme: accept.scheme,
1506
+ network: accept.network,
1507
+ amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
1508
+ asset: accept.asset,
1509
+ payTo: accept.payTo,
1510
+ maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
1511
+ extra: accept.extra ?? {}
1512
+ };
1513
+ }
1514
+ function decimalToAtomicUnits(amount, decimals) {
1515
+ const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
1516
+ if (!match?.groups) {
1517
+ throw new Error(`Invalid decimal amount '${amount}'`);
1518
+ }
1519
+ const whole = match.groups.whole;
1520
+ const fraction = match.groups.fraction ?? "";
1521
+ if (fraction.length > decimals) {
1522
+ throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
1523
+ }
1524
+ const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
1525
+ return normalized === "" ? "0" : normalized;
1526
+ }
1527
+
1528
+ // src/protocols/x402/challenge.ts
1529
+ async function buildX402Challenge(opts) {
1530
+ const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
1531
+ const { encodePaymentRequiredHeader } = await import("@x402/core/http");
1532
+ const resource = buildChallengeResource(request, routeEntry);
1533
+ const requirements = await buildChallengeRequirements(
1534
+ server,
1535
+ request,
1536
+ price,
1537
+ accepts,
1538
+ resource,
1539
+ facilitatorsByNetwork
1540
+ );
1541
+ const paymentRequired = await server.createPaymentRequiredResponse(
1542
+ requirements,
1543
+ resource,
1544
+ void 0,
1545
+ extensions
1546
+ );
1547
+ const encoded = encodePaymentRequiredHeader(paymentRequired);
1548
+ return { encoded, requirements };
1549
+ }
1550
+ async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork) {
1551
+ const requirements = await buildExpectedRequirements(server, request, price, accepts);
1552
+ if (!needsFacilitatorEnrichment(accepts)) return requirements;
1553
+ return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork);
1554
+ }
1555
+ function needsFacilitatorEnrichment(accepts) {
1556
+ return accepts.some((accept) => accept.scheme !== "exact") || hasSolanaAccepts(accepts);
1557
+ }
1558
+ async function enrichGroup(group, resource) {
1559
+ const accepted = await enrichRequirementsWithFacilitatorAccepts(
1560
+ group.facilitator,
1561
+ resource,
1562
+ group.items.map(({ requirement }) => requirement)
1563
+ );
1564
+ if (accepted.length !== group.items.length) {
1565
+ throw new Error(
1566
+ `Facilitator /accepts returned ${accepted.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
1567
+ );
1568
+ }
1569
+ return accepted;
1570
+ }
1571
+ async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork) {
1572
+ const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
1573
+ if (groups.length === 0) return requirements;
1574
+ const results = await Promise.all(
1575
+ groups.map(async (group) => {
1627
1576
  try {
1628
- mppResult = await deps.mppx.charge({ amount: price })(request);
1577
+ return { success: true, group, accepted: await enrichGroup(group, resource) };
1629
1578
  } catch (err) {
1630
- const message = err instanceof Error ? err.message : String(err);
1631
- console.error(`[router] ${routeEntry.key}: MPP charge failed: ${message}`);
1632
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1633
- level: "critical",
1634
- message: `MPP charge failed: ${message}`,
1635
- route: routeEntry.key
1636
- });
1637
- return fail(500, `MPP payment processing failed: ${message}`, meta, pluginCtx, body.data);
1579
+ const label = group.facilitator.url ?? group.facilitator.network;
1580
+ const reason = err instanceof Error ? err.message : String(err);
1581
+ console.warn(
1582
+ `[router] ${label} /accepts failed, dropping ${group.items.length} requirement(s): ${reason}`
1583
+ );
1584
+ return { success: false, group };
1638
1585
  }
1639
- if (mppResult.status === 402) {
1640
- let rejectReason = "";
1641
- try {
1642
- const problemBody = await mppResult.challenge.clone().text();
1643
- if (problemBody) {
1644
- const problem = JSON.parse(problemBody);
1645
- rejectReason = problem.detail || problem.title || "";
1646
- }
1647
- } catch {
1648
- }
1649
- const detail = rejectReason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
1650
- console.warn(`[router] ${routeEntry.key}: MPP credential rejected \u2014 ${detail}`);
1651
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1652
- level: "warn",
1653
- message: `MPP payment rejected: ${detail}`,
1654
- route: routeEntry.key
1655
- });
1656
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1586
+ })
1587
+ );
1588
+ const enriched = [...requirements];
1589
+ results.filter((r) => r.success).forEach(({ group, accepted }) => {
1590
+ accepted.forEach((req, offset) => {
1591
+ const index = group.items[offset]?.index;
1592
+ if (index !== void 0) enriched[index] = req;
1593
+ });
1594
+ });
1595
+ const failedIndices = new Set(
1596
+ results.filter((r) => !r.success).flatMap(({ group }) => group.items.map(({ index }) => index))
1597
+ );
1598
+ const remaining = enriched.filter((_, i) => !failedIndices.has(i));
1599
+ if (remaining.length === 0) {
1600
+ throw new Error(
1601
+ "All facilitator enrichments failed; no payment requirements remain for challenge"
1602
+ );
1603
+ }
1604
+ return remaining;
1605
+ }
1606
+ function collectEnrichmentGroups(requirements, facilitatorsByNetwork) {
1607
+ const groups = [];
1608
+ requirements.forEach((requirement, index) => {
1609
+ if (!requiresFacilitatorEnrichment(requirement)) return;
1610
+ const facilitator = getRequiredFacilitator(requirement, facilitatorsByNetwork);
1611
+ const existing = groups.find(
1612
+ (group) => sameResolvedX402Facilitator(group.facilitator, facilitator)
1613
+ );
1614
+ if (existing) {
1615
+ existing.items.push({ index, requirement });
1616
+ return;
1617
+ }
1618
+ groups.push({
1619
+ facilitator,
1620
+ items: [{ index, requirement }]
1621
+ });
1622
+ });
1623
+ return groups;
1624
+ }
1625
+ function getRequiredFacilitator(requirement, facilitatorsByNetwork) {
1626
+ const facilitator = getFacilitatorForRequirement(facilitatorsByNetwork, requirement);
1627
+ if (!facilitator) {
1628
+ throw new Error(
1629
+ `Missing x402 facilitator for ${requirement.scheme} requirement on ${requirement.network}`
1630
+ );
1631
+ }
1632
+ return facilitator;
1633
+ }
1634
+ function requiresFacilitatorEnrichment(requirement) {
1635
+ return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
1636
+ }
1637
+ function buildChallengeResource(request, routeEntry) {
1638
+ return {
1639
+ url: request.url,
1640
+ method: routeEntry.method,
1641
+ description: routeEntry.description,
1642
+ mimeType: "application/json"
1643
+ };
1644
+ }
1645
+
1646
+ // src/protocols/x402/settle.ts
1647
+ async function settleX402Payment(server, payload, requirements) {
1648
+ const { encodePaymentResponseHeader } = await import("@x402/core/http");
1649
+ const result = await server.settlePayment(payload, requirements);
1650
+ const encoded = encodePaymentResponseHeader(result);
1651
+ return { encoded, result };
1652
+ }
1653
+
1654
+ // src/protocols/x402/verify.ts
1655
+ async function verifyX402Payment(opts) {
1656
+ const { server, request, price, accepts } = opts;
1657
+ const payload = await readPaymentPayload(request);
1658
+ if (!payload) return null;
1659
+ const requirements = await buildExpectedRequirements(server, request, price, accepts);
1660
+ const matching = findVerifiableRequirements(server, requirements, payload);
1661
+ if (!matching) {
1662
+ return invalidPaymentVerification();
1663
+ }
1664
+ let verify;
1665
+ try {
1666
+ verify = await server.verifyPayment(payload, matching);
1667
+ } catch (err) {
1668
+ const sc = err.statusCode;
1669
+ if (sc && sc >= 400 && sc < 500) return invalidPaymentVerification();
1670
+ throw err;
1671
+ }
1672
+ if (!verify.isValid) return invalidPaymentVerification();
1673
+ if (typeof verify.payer !== "string" || verify.payer.length === 0) {
1674
+ throw new Error("x402 verification succeeded without a payer address");
1675
+ }
1676
+ return {
1677
+ valid: true,
1678
+ payer: verify.payer,
1679
+ payload,
1680
+ requirements: matching
1681
+ };
1682
+ }
1683
+ function findVerifiableRequirements(server, requirements, payload) {
1684
+ const strictMatch = server.findMatchingRequirements(requirements, payload);
1685
+ if (strictMatch) {
1686
+ return payload.x402Version === 2 ? payload.accepted : strictMatch;
1687
+ }
1688
+ if (payload.x402Version !== 2) {
1689
+ return null;
1690
+ }
1691
+ const stableMatch = requirements.find(
1692
+ (requirement) => matchesStableFields(requirement, payload.accepted)
1693
+ );
1694
+ return stableMatch ? payload.accepted : null;
1695
+ }
1696
+ function matchesStableFields(requirement, accepted) {
1697
+ return requirement.scheme === accepted.scheme && requirement.network === accepted.network && requirement.payTo === accepted.payTo && requirement.asset === accepted.asset && requirement.amount === accepted.amount && requirement.maxTimeoutSeconds === accepted.maxTimeoutSeconds;
1698
+ }
1699
+ async function readPaymentPayload(request) {
1700
+ const paymentHeader = request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY);
1701
+ if (!paymentHeader) return null;
1702
+ const { decodePaymentSignatureHeader } = await import("@x402/core/http");
1703
+ return decodePaymentSignatureHeader(paymentHeader);
1704
+ }
1705
+ function invalidPaymentVerification() {
1706
+ return { valid: false, payload: null, requirements: null, payer: null };
1707
+ }
1708
+
1709
+ // src/protocols/x402/strategy.ts
1710
+ var x402Strategy = {
1711
+ protocol: "x402",
1712
+ detects(request) {
1713
+ return Boolean(
1714
+ request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
1715
+ );
1716
+ },
1717
+ async verify(args) {
1718
+ const { request, body, price, routeEntry, deps } = args;
1719
+ if (!deps.x402Server) {
1720
+ const reason = deps.x402InitError ? `x402 facilitator initialization failed: ${deps.x402InitError}` : "x402 server not initialized \u2014 ensure @x402/core, @x402/evm, and @coinbase/x402 are installed";
1721
+ console.error(`[router] ${routeEntry.key}: ${reason}`);
1722
+ return { ok: false, kind: "config", message: reason };
1723
+ }
1724
+ const accepts = await resolveX402Accepts(
1725
+ request,
1726
+ routeEntry,
1727
+ deps.x402Accepts,
1728
+ deps.payeeAddress,
1729
+ body
1730
+ );
1731
+ const verifyResult = await verifyX402Payment({
1732
+ server: deps.x402Server,
1733
+ request,
1734
+ price,
1735
+ accepts
1736
+ });
1737
+ if (!verifyResult?.valid) return { ok: false, kind: "invalid" };
1738
+ const wallet = normalizeWalletAddress(verifyResult.payer);
1739
+ const matchedNetwork = getRequirementNetwork(verifyResult.requirements, deps.network);
1740
+ const matchedRecipient = getRequirementRecipient(verifyResult.requirements);
1741
+ const payment = {
1742
+ protocol: "x402",
1743
+ status: "verified",
1744
+ payer: wallet,
1745
+ amount: price,
1746
+ network: matchedNetwork,
1747
+ ...matchedRecipient ? { recipient: matchedRecipient } : {}
1748
+ };
1749
+ return {
1750
+ ok: true,
1751
+ wallet,
1752
+ payment,
1753
+ token: {
1754
+ payload: verifyResult.payload,
1755
+ requirements: verifyResult.requirements
1657
1756
  }
1658
- let txHash = "";
1659
- const receiptHeader = mppResult.withReceipt(new Response()).headers.get(
1660
- "Payment-Receipt"
1757
+ };
1758
+ },
1759
+ async settle(args) {
1760
+ const { response, payment, token, deps } = args;
1761
+ const x402Token = token;
1762
+ try {
1763
+ const settle = await settleX402Payment(
1764
+ deps.x402Server,
1765
+ x402Token.payload,
1766
+ x402Token.requirements
1661
1767
  );
1662
- if (receiptHeader) {
1663
- try {
1664
- txHash = import_mppx2.Receipt.deserialize(receiptHeader).reference;
1665
- } catch {
1666
- }
1768
+ if (!settle.result?.success) {
1769
+ const reason = settle.result?.errorReason || "x402 settlement returned success=false";
1770
+ const error = new Error(reason);
1771
+ error.errorReason = reason;
1772
+ throw error;
1667
1773
  }
1668
- pluginCtx.setVerifiedWallet(wallet);
1669
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1670
- protocol: "mpp",
1671
- payer: wallet,
1672
- amount: price,
1673
- network: "tempo:4217"
1774
+ response.headers.set(HEADERS.X402_PAYMENT_RESPONSE, settle.encoded);
1775
+ response.headers.set("Cache-Control", "private");
1776
+ const transaction = String(settle.result?.transaction ?? "");
1777
+ const settledPayment = {
1778
+ ...payment,
1779
+ status: "settled",
1780
+ ...transaction ? { transaction } : {}
1781
+ };
1782
+ return { ok: true, response, settledPayment };
1783
+ } catch (err) {
1784
+ const errObj = err;
1785
+ console.error("Settlement failed", {
1786
+ message: err instanceof Error ? err.message : String(err),
1787
+ route: args.routeEntry.key,
1788
+ network: payment.network,
1789
+ errorReason: errObj.errorReason,
1790
+ facilitatorStatus: errObj.response?.status,
1791
+ facilitatorBody: errObj.response?.data ?? errObj.response?.body
1674
1792
  });
1675
- const { response, rawResult } = await invoke(
1676
- request,
1677
- meta,
1678
- pluginCtx,
1679
- wallet,
1680
- account,
1681
- body.data,
1682
- {
1683
- protocol: "mpp",
1684
- status: "settled",
1685
- payer: wallet,
1686
- amount: price,
1687
- network: "tempo:4217",
1688
- recipient: deps.payeeAddress,
1689
- ...txHash ? { transaction: txHash } : {},
1690
- ...receiptHeader ? { receipt: receiptHeader } : {}
1691
- }
1692
- );
1693
- if (response.status < 400) {
1694
- if (routeEntry.siwxEnabled) {
1695
- try {
1696
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1697
- } catch (error) {
1698
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1699
- level: "warn",
1700
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1701
- route: routeEntry.key
1702
- });
1703
- }
1793
+ return { ok: false, error: err, failMessage: "Settlement failed" };
1794
+ }
1795
+ },
1796
+ async buildChallenge(args) {
1797
+ const { request, routeEntry, body, price, extensions, deps } = args;
1798
+ if (!deps.x402Server) return {};
1799
+ const accepts = await resolveX402Accepts(
1800
+ request,
1801
+ routeEntry,
1802
+ deps.x402Accepts,
1803
+ deps.payeeAddress,
1804
+ body
1805
+ );
1806
+ const { encoded } = await buildX402Challenge({
1807
+ server: deps.x402Server,
1808
+ routeEntry,
1809
+ request,
1810
+ price,
1811
+ accepts,
1812
+ facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
1813
+ extensions
1814
+ });
1815
+ return { headers: { [HEADERS.X402_PAYMENT_REQUIRED]: encoded } };
1816
+ }
1817
+ };
1818
+ function getRequirementNetwork(requirements, fallback) {
1819
+ const network = requirements?.network;
1820
+ return typeof network === "string" ? network : fallback;
1821
+ }
1822
+ function getRequirementRecipient(requirements) {
1823
+ const payTo = requirements?.payTo;
1824
+ return typeof payTo === "string" ? payTo : void 0;
1825
+ }
1826
+
1827
+ // src/protocols/detect.ts
1828
+ function detectProtocol(request) {
1829
+ if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
1830
+ return "x402";
1831
+ }
1832
+ const auth = request.headers.get(HEADERS.AUTHORIZATION);
1833
+ if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
1834
+ return "mpp";
1835
+ }
1836
+ if (request.headers.get(HEADERS.SIWX)) {
1837
+ return "siwx";
1838
+ }
1839
+ return null;
1840
+ }
1841
+
1842
+ // src/protocols/index.ts
1843
+ var STRATEGIES = {
1844
+ x402: x402Strategy,
1845
+ mpp: mppStrategy
1846
+ };
1847
+ function selectIncomingStrategy(request, allowed) {
1848
+ for (const name of allowed) {
1849
+ const strategy = STRATEGIES[name];
1850
+ if (strategy.detects(request)) return strategy;
1851
+ }
1852
+ return null;
1853
+ }
1854
+ function getAllowedStrategies(allowed) {
1855
+ return allowed.map((name) => STRATEGIES[name]);
1856
+ }
1857
+
1858
+ // src/pipeline/challenge.ts
1859
+ var import_server4 = require("next/server");
1860
+ async function build402(ctx, pricing, body) {
1861
+ let challengePrice;
1862
+ try {
1863
+ challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
1864
+ } catch (err) {
1865
+ const message = errorMessage(err, "Price calculation failed");
1866
+ const errorResponse = import_server4.NextResponse.json(
1867
+ { success: false, error: message },
1868
+ { status: errorStatus(err, 500) }
1869
+ );
1870
+ firePluginResponse(ctx, errorResponse);
1871
+ return errorResponse;
1872
+ }
1873
+ const extensions = await buildChallengeExtensions(ctx);
1874
+ const response = new import_server4.NextResponse(null, {
1875
+ status: 402,
1876
+ headers: {
1877
+ "Content-Type": "application/json",
1878
+ "Cache-Control": "no-store"
1879
+ }
1880
+ });
1881
+ for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
1882
+ try {
1883
+ const contribution = await strategy.buildChallenge({
1884
+ request: ctx.request,
1885
+ routeEntry: ctx.routeEntry,
1886
+ body,
1887
+ price: challengePrice,
1888
+ extensions,
1889
+ deps: ctx.deps
1890
+ });
1891
+ if (contribution.headers) {
1892
+ for (const [name, value] of Object.entries(contribution.headers)) {
1893
+ response.headers.set(name, value);
1704
1894
  }
1705
- const receiptResponse = mppResult.withReceipt(response);
1706
- receiptResponse.headers.set("Cache-Control", "private");
1707
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1708
- protocol: "mpp",
1709
- payer: wallet,
1710
- transaction: txHash,
1711
- network: "tempo:4217"
1712
- });
1713
- finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
1714
- return receiptResponse;
1715
1895
  }
1716
- finalize(response, rawResult, meta, pluginCtx, body.data);
1717
- return response;
1896
+ } catch (err) {
1897
+ const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
1898
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
1899
+ level: "critical",
1900
+ message,
1901
+ route: ctx.routeEntry.key
1902
+ });
1903
+ if (strategy.protocol === "x402") {
1904
+ const errorResponse = import_server4.NextResponse.json(
1905
+ { success: false, error: message },
1906
+ { status: 500 }
1907
+ );
1908
+ firePluginResponse(ctx, errorResponse);
1909
+ return errorResponse;
1910
+ }
1718
1911
  }
1719
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1720
- };
1912
+ }
1913
+ firePluginResponse(ctx, response);
1914
+ return response;
1721
1915
  }
1722
- async function parseBody(request, routeEntry) {
1723
- if (!routeEntry.bodySchema) return { ok: true, data: void 0 };
1724
- const raw = await bufferBody(request);
1725
- const result = validateBody(raw, routeEntry.bodySchema);
1726
- if (result.success) return { ok: true, data: result.data };
1727
- return {
1728
- ok: false,
1729
- response: import_server2.NextResponse.json(
1730
- { success: false, error: result.error, issues: result.issues },
1731
- { status: 400 }
1732
- )
1916
+ async function buildChallengeExtensions(ctx) {
1917
+ const { routeEntry } = ctx;
1918
+ let extensions;
1919
+ try {
1920
+ const { z } = await import("zod");
1921
+ const { declareDiscoveryExtension } = await import("@x402/extensions/bazaar");
1922
+ const toJSON = (schema) => z.toJSONSchema(schema, {
1923
+ target: "draft-2020-12",
1924
+ unrepresentable: "any"
1925
+ });
1926
+ const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
1927
+ const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1928
+ if (inputSchema) {
1929
+ const config = {
1930
+ method: routeEntry.method,
1931
+ bodyType: routeEntry.bodySchema ? "json" : void 0,
1932
+ inputSchema
1933
+ };
1934
+ if (routeEntry.inputExample !== void 0) {
1935
+ config.input = routeEntry.inputExample;
1936
+ }
1937
+ if (outputSchema && routeEntry.outputExample !== void 0) {
1938
+ config.output = { schema: outputSchema, example: routeEntry.outputExample };
1939
+ }
1940
+ extensions = declareDiscoveryExtension(config);
1941
+ }
1942
+ } catch (err) {
1943
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
1944
+ level: "warn",
1945
+ message: `Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`,
1946
+ route: routeEntry.key
1947
+ });
1948
+ }
1949
+ if (routeEntry.siwxEnabled) {
1950
+ try {
1951
+ const siwxExtension = await buildSIWXExtension();
1952
+ if (siwxExtension && typeof siwxExtension === "object" && !Array.isArray(siwxExtension)) {
1953
+ extensions = {
1954
+ ...extensions ?? {},
1955
+ ...siwxExtension
1956
+ };
1957
+ }
1958
+ } catch {
1959
+ }
1960
+ }
1961
+ return extensions;
1962
+ }
1963
+
1964
+ // src/pipeline/flows/paid.ts
1965
+ async function runPaidFlow(ctx) {
1966
+ const { request, routeEntry, deps } = ctx;
1967
+ let account = void 0;
1968
+ if (routeEntry.apiKeyResolver) {
1969
+ const apiKeyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
1970
+ if (!apiKeyResult.valid) return fail(ctx, 401, "Invalid or missing API key");
1971
+ account = apiKeyResult.account;
1972
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
1973
+ authMode: "apiKey",
1974
+ wallet: null,
1975
+ route: routeEntry.key,
1976
+ account
1977
+ });
1978
+ }
1979
+ const alertFn = (level, message, meta) => {
1980
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
1981
+ level,
1982
+ message,
1983
+ route: routeEntry.key,
1984
+ meta
1985
+ });
1986
+ };
1987
+ const pricing = selectPricing(routeEntry.pricing, {
1988
+ alert: alertFn,
1989
+ maxPrice: routeEntry.maxPrice,
1990
+ minPrice: routeEntry.minPrice,
1991
+ route: routeEntry.key
1992
+ });
1993
+ const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
1994
+ let earlyBody = void 0;
1995
+ if (shouldParseBodyEarly(incomingStrategy, routeEntry, pricing)) {
1996
+ const earlyClone = request.clone();
1997
+ const earlyResult = await parseBody(earlyClone, routeEntry);
1998
+ if (earlyResult.ok) {
1999
+ earlyBody = earlyResult.data;
2000
+ const validateErr2 = await runValidate(ctx, earlyBody);
2001
+ if (validateErr2) return validateErr2;
2002
+ } else {
2003
+ firePluginResponse(ctx, earlyResult.response);
2004
+ return earlyResult.response;
2005
+ }
2006
+ }
2007
+ const siwxFastPath = await trySiwxFastPath(ctx, account);
2008
+ if (siwxFastPath) return siwxFastPath;
2009
+ if (!incomingStrategy) {
2010
+ const initError = protocolInitError(routeEntry, deps);
2011
+ if (initError) return fail(ctx, 500, initError);
2012
+ return build402(ctx, pricing, earlyBody);
2013
+ }
2014
+ const body = await parseBody(request, routeEntry);
2015
+ if (!body.ok) {
2016
+ firePluginResponse(ctx, body.response);
2017
+ return body.response;
2018
+ }
2019
+ const validateErr = await runValidate(ctx, body.data);
2020
+ if (validateErr) return validateErr;
2021
+ if (!pricing) {
2022
+ return fail(ctx, 500, "Pricing not configured", body.data);
2023
+ }
2024
+ let price;
2025
+ try {
2026
+ price = await pricing.quote(body.data);
2027
+ } catch (err) {
2028
+ return fail(
2029
+ ctx,
2030
+ errorStatus(err, 500),
2031
+ errorMessage(err, "Price calculation failed"),
2032
+ body.data
2033
+ );
2034
+ }
2035
+ const verifyOutcome = await incomingStrategy.verify({
2036
+ request,
2037
+ body: body.data,
2038
+ price,
2039
+ routeEntry,
2040
+ deps
2041
+ });
2042
+ if (verifyOutcome.ok === false) {
2043
+ if (verifyOutcome.kind === "config") {
2044
+ return fail(ctx, 500, verifyOutcome.message, body.data);
2045
+ }
2046
+ return build402(ctx, pricing, body.data);
2047
+ }
2048
+ ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
2049
+ firePluginHook(deps.plugin, "onPaymentVerified", ctx.pluginCtx, {
2050
+ protocol: incomingStrategy.protocol,
2051
+ payer: verifyOutcome.wallet,
2052
+ amount: price,
2053
+ network: verifyOutcome.payment.network
2054
+ });
2055
+ const result = await invoke(ctx, verifyOutcome.wallet, account, body.data, verifyOutcome.payment);
2056
+ const settleScope = {
2057
+ wallet: verifyOutcome.wallet,
2058
+ account,
2059
+ body: body.data,
2060
+ payment: verifyOutcome.payment,
2061
+ response: result.response,
2062
+ rawResult: result.rawResult,
2063
+ handlerError: result.handlerError
1733
2064
  };
2065
+ if (verifyOutcome.alreadySettled) {
2066
+ if (result.response.status >= 400) {
2067
+ const settledScope = settleScope;
2068
+ await runSettledHandlerError(ctx, settledScope);
2069
+ return finalize(ctx, result.response, result.rawResult, body.data);
2070
+ }
2071
+ return settleAndFinalize({
2072
+ ctx,
2073
+ strategy: incomingStrategy,
2074
+ verifyOutcome,
2075
+ scope: settleScope,
2076
+ rawResult: result.rawResult,
2077
+ body: body.data
2078
+ });
2079
+ }
2080
+ if (result.response.status >= 400) {
2081
+ return finalize(ctx, result.response, result.rawResult, body.data);
2082
+ }
2083
+ const beforeErr = await runBeforeSettle(ctx, settleScope);
2084
+ if (beforeErr) return beforeErr;
2085
+ return settleAndFinalize({
2086
+ ctx,
2087
+ strategy: incomingStrategy,
2088
+ verifyOutcome,
2089
+ scope: settleScope,
2090
+ rawResult: result.rawResult,
2091
+ body: body.data,
2092
+ onSettleError: async (error, failMessage) => {
2093
+ await runSettlementError(ctx, settleScope, error, "settle");
2094
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
2095
+ level: "critical",
2096
+ message: `${incomingStrategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`,
2097
+ route: routeEntry.key
2098
+ });
2099
+ }
2100
+ });
2101
+ }
2102
+
2103
+ // src/pipeline/flows/siwx-only.ts
2104
+ var import_server5 = require("next/server");
2105
+
2106
+ // src/auth/nonce.ts
2107
+ var SIWX_CHALLENGE_EXPIRY_MS = 5 * 60 * 1e3;
2108
+ var MemoryNonceStore = class {
2109
+ seen = /* @__PURE__ */ new Map();
2110
+ async check(nonce) {
2111
+ this.evict();
2112
+ if (this.seen.has(nonce)) return false;
2113
+ this.seen.set(nonce, Date.now() + SIWX_CHALLENGE_EXPIRY_MS);
2114
+ return true;
2115
+ }
2116
+ evict() {
2117
+ const now = Date.now();
2118
+ for (const [n, exp] of this.seen) {
2119
+ if (exp < now) this.seen.delete(n);
2120
+ }
2121
+ }
2122
+ };
2123
+ function detectRedisClientType(client) {
2124
+ if (!client || typeof client !== "object") {
2125
+ throw new Error(
2126
+ "createRedisNonceStore requires a Redis client. Supported: @upstash/redis, ioredis. Pass your Redis client instance as the first argument."
2127
+ );
2128
+ }
2129
+ if ("options" in client && "status" in client) {
2130
+ return "ioredis";
2131
+ }
2132
+ const constructor = client.constructor?.name;
2133
+ if (constructor === "Redis" && "url" in client) {
2134
+ return "upstash";
2135
+ }
2136
+ if (typeof client.set === "function") {
2137
+ return "upstash";
2138
+ }
2139
+ throw new Error(
2140
+ "Unrecognized Redis client. Supported: @upstash/redis, ioredis. If using a different client, implement NonceStore interface directly."
2141
+ );
1734
2142
  }
1735
- function buildMeta(request, routeEntry) {
2143
+ function createRedisNonceStore(client, opts) {
2144
+ const prefix = opts?.prefix ?? "siwx:nonce:";
2145
+ const ttlSeconds = Math.ceil((opts?.ttlMs ?? SIWX_CHALLENGE_EXPIRY_MS) / 1e3);
2146
+ const clientType = detectRedisClientType(client);
1736
2147
  return {
1737
- requestId: crypto.randomUUID(),
1738
- method: request.method,
1739
- route: routeEntry.key,
1740
- origin: request.headers.get("origin") ?? new URL(request.url).origin,
1741
- referer: request.headers.get("referer"),
1742
- walletAddress: request.headers.get("X-Wallet-Address"),
1743
- clientId: request.headers.get("X-Client-ID"),
1744
- sessionId: request.headers.get("X-Session-ID"),
1745
- contentType: request.headers.get("content-type"),
1746
- headers: Object.fromEntries(request.headers.entries()),
1747
- startTime: Date.now()
2148
+ async check(nonce) {
2149
+ const key = `${prefix}${nonce}`;
2150
+ if (clientType === "upstash") {
2151
+ const redis = client;
2152
+ const result = await redis.set(key, "1", { ex: ttlSeconds, nx: true });
2153
+ return result !== null;
2154
+ }
2155
+ if (clientType === "ioredis") {
2156
+ const redis = client;
2157
+ const result = await redis.set(key, "1", "EX", ttlSeconds, "NX");
2158
+ return result === "OK";
2159
+ }
2160
+ throw new Error("Unknown Redis client type");
2161
+ }
1748
2162
  };
1749
2163
  }
1750
- function parseQuery(request, routeEntry) {
1751
- if (!routeEntry.querySchema) return void 0;
1752
- const params = Object.fromEntries(request.nextUrl.searchParams.entries());
1753
- const result = routeEntry.querySchema.safeParse(params);
1754
- return result.success ? result.data : params;
2164
+
2165
+ // src/protocols/mpp/siwx-mode.ts
2166
+ var import_mppx3 = require("mppx");
2167
+ async function verifyMppSiwx(request, mppx) {
2168
+ const result = await mppx.charge({ amount: "0" })(request);
2169
+ if (result.status === 402) {
2170
+ return { valid: false, challenge: result.challenge };
2171
+ }
2172
+ const credential = import_mppx3.Credential.fromRequest(request);
2173
+ const rawSource = credential?.source ?? "";
2174
+ const wallet = walletFromDid(rawSource);
2175
+ return { valid: true, wallet, withReceipt: result.withReceipt };
1755
2176
  }
1756
- async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta) {
1757
- try {
1758
- let price = await resolvePrice(routeEntry.pricing, bodyData);
1759
- if (routeEntry.maxPrice) {
1760
- const calculated = parseFloat(price);
1761
- const max = parseFloat(routeEntry.maxPrice);
1762
- if (!Number.isFinite(calculated) || calculated > max) {
1763
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1764
- level: "warn",
1765
- message: `Price ${price} exceeds maxPrice ${routeEntry.maxPrice}, capping`,
1766
- route: routeEntry.key,
1767
- meta: { calculated: price, maxPrice: routeEntry.maxPrice, body: bodyData }
1768
- });
1769
- price = routeEntry.maxPrice;
1770
- }
1771
- }
1772
- return { price };
1773
- } catch (err) {
1774
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1775
- level: "error",
1776
- message: `Pricing function failed: ${err instanceof Error ? err.message : String(err)}`,
1777
- route: routeEntry.key,
1778
- meta: { error: err instanceof Error ? err.stack : String(err), body: bodyData }
1779
- });
1780
- if (routeEntry.maxPrice) {
1781
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1782
- level: "warn",
1783
- message: `Using maxPrice ${routeEntry.maxPrice} as fallback after pricing error`,
1784
- route: routeEntry.key
1785
- });
1786
- return { price: routeEntry.maxPrice };
2177
+
2178
+ // src/pipeline/flows/siwx-only.ts
2179
+ async function runSiwxOnlyFlow(ctx) {
2180
+ const { request, routeEntry, deps } = ctx;
2181
+ if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get(HEADERS.SIWX)) {
2182
+ const earlyClone = request.clone();
2183
+ const earlyBody = await parseBody(earlyClone, routeEntry);
2184
+ if (earlyBody.ok) {
2185
+ const validateErr = await runValidate(ctx, earlyBody.data);
2186
+ if (validateErr) return validateErr;
1787
2187
  } else {
1788
- const errorResponse = import_server2.NextResponse.json(
1789
- { success: false, error: "Price calculation failed" },
1790
- { status: 500 }
1791
- );
1792
- firePluginResponse(deps, pluginCtx, meta, errorResponse);
1793
- return { error: errorResponse };
2188
+ firePluginResponse(ctx, earlyBody.response);
2189
+ return earlyBody.response;
1794
2190
  }
1795
2191
  }
1796
- }
1797
- async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1798
- const response = new import_server2.NextResponse(null, {
1799
- status: 402,
1800
- headers: {
1801
- "Content-Type": "application/json",
1802
- "Cache-Control": "no-store"
1803
- }
1804
- });
1805
- let challengePrice;
1806
- if (bodyData !== void 0 && typeof routeEntry.pricing !== "string" && routeEntry.pricing != null) {
1807
- const result = await resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta);
1808
- if ("error" in result) return result.error;
1809
- challengePrice = result.price;
1810
- } else if (routeEntry.maxPrice) {
1811
- challengePrice = routeEntry.maxPrice;
1812
- } else if (routeEntry.pricing) {
2192
+ const siwxHeader = request.headers.get(HEADERS.SIWX);
2193
+ const protocol = detectProtocol(request);
2194
+ if (!siwxHeader && protocol === "mpp" && deps.mppx) {
2195
+ let mppSiwxResult;
1813
2196
  try {
1814
- challengePrice = resolveMaxPrice(routeEntry.pricing);
1815
- } catch {
1816
- challengePrice = "0";
2197
+ mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
2198
+ } catch (err) {
2199
+ const message = err instanceof Error ? err.message : String(err);
2200
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
2201
+ level: "critical",
2202
+ message: `MPP SIWX verification failed: ${message}`,
2203
+ route: routeEntry.key
2204
+ });
2205
+ return fail(ctx, 500, `MPP SIWX verification failed: ${message}`);
2206
+ }
2207
+ if (mppSiwxResult.valid) {
2208
+ ctx.pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
2209
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
2210
+ authMode: "siwx",
2211
+ wallet: mppSiwxResult.wallet,
2212
+ route: routeEntry.key
2213
+ });
2214
+ const authResponse = await runHandlerOnly(ctx, mppSiwxResult.wallet, void 0);
2215
+ if (authResponse.status < 400) {
2216
+ return mppSiwxResult.withReceipt(authResponse);
2217
+ }
2218
+ return authResponse;
1817
2219
  }
1818
- } else {
1819
- challengePrice = "0";
1820
2220
  }
1821
- let extensions;
2221
+ if (!siwxHeader) {
2222
+ return buildSiwxChallenge(ctx);
2223
+ }
2224
+ const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
2225
+ if (!siwx.valid) {
2226
+ const response = import_server5.NextResponse.json(
2227
+ { error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
2228
+ { status: 402 }
2229
+ );
2230
+ firePluginResponse(ctx, response);
2231
+ return response;
2232
+ }
2233
+ const wallet = normalizeWalletAddress(siwx.wallet);
2234
+ ctx.pluginCtx.setVerifiedWallet(wallet);
2235
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
2236
+ authMode: "siwx",
2237
+ wallet,
2238
+ route: routeEntry.key
2239
+ });
2240
+ return runHandlerOnly(ctx, wallet, void 0);
2241
+ }
2242
+ async function buildSiwxChallenge(ctx) {
2243
+ const { request, routeEntry, deps } = ctx;
2244
+ const url = new URL(request.url);
2245
+ const nonce = crypto.randomUUID().replace(/-/g, "");
2246
+ const supportedChains = getSupportedChains(deps.x402Accepts, deps.network);
2247
+ const primaryChain = supportedChains[0];
2248
+ const siwxInfo = {
2249
+ domain: url.hostname,
2250
+ uri: request.url,
2251
+ version: "1",
2252
+ chainId: primaryChain.chainId,
2253
+ type: primaryChain.type,
2254
+ nonce,
2255
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
2256
+ expirationTime: new Date(Date.now() + SIWX_CHALLENGE_EXPIRY_MS).toISOString(),
2257
+ statement: "Sign in to verify your wallet identity"
2258
+ };
2259
+ let siwxSchema;
1822
2260
  try {
1823
- const { z } = await import("zod");
1824
- const { declareDiscoveryExtension } = await import("@x402/extensions/bazaar");
1825
- const toJSON = (schema) => z.toJSONSchema(schema, {
1826
- target: "draft-2020-12",
1827
- unrepresentable: "any"
1828
- });
1829
- const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
1830
- const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1831
- if (inputSchema) {
1832
- const config = {
1833
- method: routeEntry.method,
1834
- bodyType: routeEntry.bodySchema ? "json" : void 0,
1835
- inputSchema
1836
- };
1837
- if (routeEntry.inputExample !== void 0) {
1838
- config.input = routeEntry.inputExample;
1839
- }
1840
- if (outputSchema && routeEntry.outputExample !== void 0) {
1841
- config.output = { schema: outputSchema, example: routeEntry.outputExample };
2261
+ siwxSchema = await buildSIWXExtension();
2262
+ } catch {
2263
+ }
2264
+ const paymentRequired = {
2265
+ x402Version: 2,
2266
+ error: "SIWX authentication required",
2267
+ resource: {
2268
+ url: request.url,
2269
+ description: routeEntry.description ?? "SIWX-protected endpoint",
2270
+ mimeType: "application/json"
2271
+ },
2272
+ accepts: [],
2273
+ extensions: {
2274
+ "sign-in-with-x": {
2275
+ info: siwxInfo,
2276
+ // Required by MCP tools at the top level for chain detection.
2277
+ supportedChains,
2278
+ ...siwxSchema ? { schema: siwxSchema } : {}
1842
2279
  }
1843
- extensions = declareDiscoveryExtension(config);
1844
2280
  }
2281
+ };
2282
+ let encoded;
2283
+ try {
2284
+ const { encodePaymentRequiredHeader } = await import("@x402/core/http");
2285
+ encoded = encodePaymentRequiredHeader(paymentRequired);
1845
2286
  } catch (err) {
1846
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
2287
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
1847
2288
  level: "warn",
1848
- message: `Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`,
2289
+ message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
1849
2290
  route: routeEntry.key
1850
2291
  });
1851
2292
  }
1852
- if (routeEntry.siwxEnabled) {
2293
+ const response = new import_server5.NextResponse(JSON.stringify(paymentRequired), {
2294
+ status: 402,
2295
+ headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
2296
+ });
2297
+ if (encoded) response.headers.set(HEADERS.X402_PAYMENT_REQUIRED, encoded);
2298
+ if (deps.mppx) {
1853
2299
  try {
1854
- const siwxExtension = await buildSIWXExtension();
1855
- if (siwxExtension && typeof siwxExtension === "object" && !Array.isArray(siwxExtension)) {
1856
- extensions = {
1857
- ...extensions ?? {},
1858
- ...siwxExtension
1859
- };
2300
+ const mppChallenge = await deps.mppx.charge({ amount: "0" })(request);
2301
+ if (mppChallenge.status === 402) {
2302
+ const wwwAuth = mppChallenge.challenge.headers.get(HEADERS.WWW_AUTHENTICATE);
2303
+ if (wwwAuth) response.headers.set(HEADERS.WWW_AUTHENTICATE, wwwAuth);
1860
2304
  }
1861
2305
  } catch {
1862
2306
  }
1863
2307
  }
1864
- if (routeEntry.protocols.includes("x402") && deps.x402Server) {
1865
- try {
1866
- const accepts = await resolveX402Accepts(
1867
- request,
1868
- routeEntry,
1869
- deps.x402Accepts,
1870
- deps.payeeAddress,
1871
- bodyData
1872
- );
1873
- const { encoded } = await buildX402Challenge({
1874
- server: deps.x402Server,
1875
- routeEntry,
1876
- request,
1877
- price: challengePrice,
1878
- accepts,
1879
- facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
1880
- extensions
1881
- });
1882
- response.headers.set("PAYMENT-REQUIRED", encoded);
1883
- } catch (err) {
1884
- const message = `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`;
1885
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1886
- level: "critical",
1887
- message,
1888
- route: routeEntry.key
1889
- });
1890
- const errorResponse = import_server2.NextResponse.json({ success: false, error: message }, { status: 500 });
1891
- firePluginResponse(deps, pluginCtx, meta, errorResponse);
1892
- return errorResponse;
1893
- }
1894
- }
1895
- if (routeEntry.protocols.includes("mpp") && deps.mppx) {
1896
- try {
1897
- const result = await deps.mppx.charge({ amount: challengePrice })(request);
1898
- if (result.status === 402) {
1899
- const wwwAuth = result.challenge.headers.get("WWW-Authenticate");
1900
- if (wwwAuth) response.headers.set("WWW-Authenticate", wwwAuth);
1901
- }
1902
- } catch (err) {
1903
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1904
- level: "critical",
1905
- message: `MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
1906
- route: routeEntry.key
1907
- });
1908
- }
1909
- }
1910
- firePluginResponse(deps, pluginCtx, meta, response);
2308
+ firePluginResponse(ctx, response);
1911
2309
  return response;
1912
2310
  }
1913
- function firePluginResponse(deps, pluginCtx, meta, response, requestBody, responseBody) {
1914
- firePluginHook(deps.plugin, "onResponse", pluginCtx, {
1915
- statusCode: response.status,
1916
- statusText: response.statusText,
1917
- duration: Date.now() - meta.startTime,
1918
- contentType: response.headers.get("content-type"),
1919
- headers: Object.fromEntries(response.headers.entries()),
1920
- requestBody,
1921
- responseBody
1922
- });
1923
- if (response.status >= 400 && response.status !== 402) {
1924
- firePluginHook(deps.plugin, "onError", pluginCtx, {
1925
- status: response.status,
1926
- message: response.statusText || `HTTP ${response.status}`,
1927
- settled: false
1928
- });
2311
+ function siwxSignatureType(network) {
2312
+ return network.startsWith("solana:") ? "ed25519" : "eip191";
2313
+ }
2314
+ function getSupportedChains(x402Accepts, fallbackNetwork) {
2315
+ const seen = /* @__PURE__ */ new Set();
2316
+ const chains = [];
2317
+ for (const accept of x402Accepts) {
2318
+ if (accept.network && !seen.has(accept.network)) {
2319
+ seen.add(accept.network);
2320
+ chains.push({ chainId: accept.network, type: siwxSignatureType(accept.network) });
2321
+ }
1929
2322
  }
2323
+ if (chains.length === 0) {
2324
+ chains.push({ chainId: fallbackNetwork, type: siwxSignatureType(fallbackNetwork) });
2325
+ }
2326
+ return chains;
1930
2327
  }
1931
- function computeQuotaLevel(remaining, warn, critical) {
1932
- if (remaining === null) return "healthy";
1933
- if (critical !== void 0 && remaining <= critical) return "critical";
1934
- if (warn !== void 0 && remaining <= warn) return "warn";
1935
- return "healthy";
2328
+
2329
+ // src/pipeline/flows/unprotected.ts
2330
+ async function runUnprotectedFlow(ctx) {
2331
+ return runHandlerOnly(ctx, null, void 0);
1936
2332
  }
1937
- function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx) {
1938
- const { providerName, providerConfig } = routeEntry;
1939
- if (!providerName || !providerConfig?.extractQuota) return;
1940
- if (response.status >= 400) return;
1941
- try {
1942
- const quota = providerConfig.extractQuota(handlerResult, response.headers);
1943
- if (!quota) return;
1944
- const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
1945
- const overage = providerConfig.overage ?? "same-rate";
1946
- const event = {
1947
- provider: providerName,
1948
- route: routeEntry.key,
1949
- remaining: quota.remaining,
1950
- limit: quota.limit,
1951
- spend: quota.spend,
1952
- level,
1953
- overage,
1954
- message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
1955
- };
1956
- firePluginHook(deps.plugin, "onProviderQuota", pluginCtx, event);
1957
- } catch {
1958
- }
2333
+
2334
+ // src/orchestrate.ts
2335
+ function createRequestHandler(routeEntry, handler, deps) {
2336
+ return async (request) => {
2337
+ await deps.initPromise;
2338
+ const ctx = preflight(routeEntry, handler, deps, request);
2339
+ if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
2340
+ if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
2341
+ if (routeEntry.pricing) return runPaidFlow(ctx);
2342
+ if (routeEntry.apiKeyResolver) return runApiKeyOnlyFlow(ctx);
2343
+ return runUnprotectedFlow(ctx);
2344
+ };
1959
2345
  }
1960
2346
 
1961
2347
  // src/validate-examples.ts
1962
2348
  function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
1963
- if (bodySchema && !hasInputExample) {
1964
- throw new Error(
1965
- `route '${key}': .body() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample body to advertise.`
1966
- );
1967
- }
1968
- if (querySchema && !hasInputExample) {
1969
- throw new Error(
1970
- `route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
1971
- );
2349
+ const inputSchema = bodySchema ?? querySchema;
2350
+ if (hasInputExample && !inputSchema) {
2351
+ throw new Error(`route '${key}': .inputExample() requires .body() or .query()`);
1972
2352
  }
1973
- if (outputSchema && !hasOutputExample) {
1974
- throw new Error(
1975
- `route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
1976
- );
2353
+ if (hasOutputExample && !outputSchema) {
2354
+ throw new Error(`route '${key}': .outputExample() requires .output()`);
1977
2355
  }
1978
- const inputSchema = bodySchema ?? querySchema;
1979
2356
  if (inputSchema && hasInputExample) {
1980
2357
  const result = inputSchema.safeParse(inputExample);
1981
2358
  if (!result.success) {
@@ -2049,6 +2426,8 @@ var RouteBuilder = class {
2049
2426
  /** @internal */
2050
2427
  _validateFn;
2051
2428
  /** @internal */
2429
+ _settlement;
2430
+ /** @internal */
2052
2431
  _mppInfo;
2053
2432
  constructor(key, registry, deps) {
2054
2433
  this._key = key;
@@ -2062,11 +2441,21 @@ var RouteBuilder = class {
2062
2441
  return next;
2063
2442
  }
2064
2443
  paid(pricing, options) {
2444
+ if (this._authMode === "unprotected") {
2445
+ throw new Error(
2446
+ `route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
2447
+ );
2448
+ }
2449
+ if (this._pricing !== void 0) {
2450
+ throw new Error(
2451
+ `route '${this._key}': Cannot call .paid() more than once on the same route.`
2452
+ );
2453
+ }
2065
2454
  const next = this.fork();
2066
2455
  next._authMode = "paid";
2067
2456
  next._pricing = pricing;
2068
2457
  if (options?.protocols) {
2069
- next._protocols = options.protocols;
2458
+ next._protocols = [...options.protocols];
2070
2459
  } else if (next._protocols.length === 0) {
2071
2460
  next._protocols = ["x402"];
2072
2461
  }
@@ -2131,6 +2520,16 @@ var RouteBuilder = class {
2131
2520
  return next;
2132
2521
  }
2133
2522
  unprotected() {
2523
+ if (this._authMode && this._authMode !== "unprotected") {
2524
+ throw new Error(
2525
+ `route '${this._key}': Cannot combine .unprotected() and .${this._authMode}() on the same route.`
2526
+ );
2527
+ }
2528
+ if (this._pricing) {
2529
+ throw new Error(
2530
+ `route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
2531
+ );
2532
+ }
2134
2533
  const next = this.fork();
2135
2534
  next._authMode = "unprotected";
2136
2535
  next._protocols = [];
@@ -2145,32 +2544,43 @@ var RouteBuilder = class {
2145
2544
  next._providerConfig = config ?? {};
2146
2545
  return next;
2147
2546
  }
2148
- // -------------------------------------------------------------------------
2149
- // Schema methods
2150
- // -------------------------------------------------------------------------
2151
- body(schema) {
2547
+ body(schema, example) {
2152
2548
  const next = this.fork();
2153
2549
  next._bodySchema = schema;
2550
+ if (example !== void 0) {
2551
+ next._inputExample = example;
2552
+ next._hasInputExample = true;
2553
+ }
2154
2554
  return next;
2155
2555
  }
2156
- query(schema) {
2556
+ query(schema, example) {
2157
2557
  const next = this.fork();
2158
2558
  next._querySchema = schema;
2559
+ if (example !== void 0) {
2560
+ next._inputExample = example;
2561
+ next._hasInputExample = true;
2562
+ }
2159
2563
  next._method = "GET";
2160
2564
  return next;
2161
2565
  }
2162
- output(schema) {
2566
+ output(schema, example) {
2163
2567
  const next = this.fork();
2164
2568
  next._outputSchema = schema;
2569
+ if (example !== void 0) {
2570
+ next._outputExample = example;
2571
+ next._hasOutputExample = true;
2572
+ }
2165
2573
  return next;
2166
2574
  }
2167
2575
  /**
2168
2576
  * Provide a conforming example of the request input (body or query params).
2169
2577
  *
2170
- * **Required** whenever `.body()` or `.query()` is set enforced at compile time via
2171
- * `.handler()` overloads, and at route-registration time via Zod validation of the
2172
- * example against the schema. The example is embedded in the bazaar discovery extension
2173
- * so indexers can advertise a working sample call.
2578
+ * Optional. When provided, the example is validated against the request schema
2579
+ * at route registration and embedded in the bazaar discovery extension so
2580
+ * indexers can advertise a working sample call.
2581
+ *
2582
+ * For the common case, pass the example directly to `.body(schema, example)` or
2583
+ * `.query(schema, example)` instead.
2174
2584
  *
2175
2585
  * @example
2176
2586
  * ```ts
@@ -2190,10 +2600,11 @@ var RouteBuilder = class {
2190
2600
  /**
2191
2601
  * Provide a conforming example of the response output.
2192
2602
  *
2193
- * **Required** whenever `.output()` is set enforced at compile time via `.handler()`
2194
- * overloads, and at route-registration time via Zod validation of the example against
2195
- * the schema. The example is embedded in the bazaar discovery extension so indexers
2196
- * can advertise the response shape.
2603
+ * Optional. When provided, the example is validated against the output schema
2604
+ * at route registration and embedded in the bazaar discovery extension so
2605
+ * indexers can advertise the response shape.
2606
+ *
2607
+ * For the common case, pass the example directly to `.output(schema, example)` instead.
2197
2608
  *
2198
2609
  * Accepts any JSON value (objects, arrays, or primitives) — top-level array
2199
2610
  * or primitive responses (e.g. `z.array(...)`) are supported alongside the
@@ -2266,15 +2677,39 @@ var RouteBuilder = class {
2266
2677
  return next;
2267
2678
  }
2268
2679
  // -------------------------------------------------------------------------
2680
+ // Settlement lifecycle
2681
+ // -------------------------------------------------------------------------
2682
+ /**
2683
+ * Add route-specific settlement hooks.
2684
+ *
2685
+ * `beforeSettle` runs after a successful handler response but before
2686
+ * router-controlled settlement/broadcast, so it can still prevent the charge
2687
+ * for x402 and MPP transaction-payload flows. `afterSettle` runs after
2688
+ * settlement and is intended for durable ledgers or app-owned refund queues.
2689
+ */
2690
+ settlement(lifecycle) {
2691
+ const next = this.fork();
2692
+ next._settlement = lifecycle;
2693
+ return next;
2694
+ }
2695
+ // -------------------------------------------------------------------------
2269
2696
  // Terminal method
2270
2697
  // -------------------------------------------------------------------------
2271
2698
  handler(fn) {
2272
2699
  const handlerFn = fn;
2700
+ if (!this._authMode) {
2701
+ throw new Error(
2702
+ `route '${this._key}': Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()`
2703
+ );
2704
+ }
2273
2705
  if (this._validateFn && !this._bodySchema) {
2274
2706
  throw new Error(
2275
2707
  `route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
2276
2708
  );
2277
2709
  }
2710
+ if (this._settlement && !this._pricing) {
2711
+ throw new Error(`route '${this._key}': .settlement() requires a paid route`);
2712
+ }
2278
2713
  validateExamples(
2279
2714
  this._key,
2280
2715
  this._bodySchema,
@@ -2306,6 +2741,7 @@ var RouteBuilder = class {
2306
2741
  providerName: this._providerName,
2307
2742
  providerConfig: this._providerConfig,
2308
2743
  validateFn: this._validateFn,
2744
+ settlement: this._settlement,
2309
2745
  mppInfo: this._mppInfo
2310
2746
  };
2311
2747
  this._registry.register(entry);
@@ -2380,7 +2816,7 @@ function createRedisEntitlementStore(client, options) {
2380
2816
  }
2381
2817
 
2382
2818
  // src/discovery/well-known.ts
2383
- var import_server3 = require("next/server");
2819
+ var import_server6 = require("next/server");
2384
2820
 
2385
2821
  // src/discovery/utils/guidance.ts
2386
2822
  async function resolveGuidance(discovery) {
@@ -2424,7 +2860,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
2424
2860
  if (instructions) {
2425
2861
  body.instructions = instructions;
2426
2862
  }
2427
- return import_server3.NextResponse.json(body, {
2863
+ return import_server6.NextResponse.json(body, {
2428
2864
  headers: {
2429
2865
  "Access-Control-Allow-Origin": "*",
2430
2866
  "Access-Control-Allow-Methods": "GET",
@@ -2441,13 +2877,14 @@ function toDiscoveryResource(method, url, mode) {
2441
2877
  }
2442
2878
 
2443
2879
  // src/discovery/openapi.ts
2444
- var import_server4 = require("next/server");
2880
+ var import_server7 = require("next/server");
2881
+ init_constants();
2445
2882
  function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2446
2883
  const normalizedBase = baseUrl.replace(/\/+$/, "");
2447
2884
  let cached = null;
2448
2885
  let validated = false;
2449
2886
  return async (_request) => {
2450
- if (cached) return import_server4.NextResponse.json(cached);
2887
+ if (cached) return import_server7.NextResponse.json(cached);
2451
2888
  if (!validated && pricesKeys) {
2452
2889
  registry.validate(pricesKeys);
2453
2890
  validated = true;
@@ -2472,14 +2909,14 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2472
2909
  securitySchemes.siwx = {
2473
2910
  type: "apiKey",
2474
2911
  in: "header",
2475
- name: "SIGN-IN-WITH-X"
2912
+ name: HEADERS.SIWX
2476
2913
  };
2477
2914
  }
2478
2915
  if (requiresApiKeyScheme) {
2479
2916
  securitySchemes.apiKey = {
2480
2917
  type: "apiKey",
2481
2918
  in: "header",
2482
- name: "X-API-Key"
2919
+ name: HEADERS.API_KEY
2483
2920
  };
2484
2921
  }
2485
2922
  const discoveryMetadata = {};
@@ -2510,7 +2947,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2510
2947
  paths
2511
2948
  };
2512
2949
  cached = createDocument(openApiDocument);
2513
- return import_server4.NextResponse.json(cached);
2950
+ return import_server7.NextResponse.json(cached);
2514
2951
  };
2515
2952
  }
2516
2953
  function deriveTag(routeKey) {
@@ -2583,7 +3020,7 @@ function toProtocolObject(protocol, mppInfo) {
2583
3020
  mpp: {
2584
3021
  method: mppInfo?.method ?? "tempo",
2585
3022
  intent: mppInfo?.intent ?? "charge",
2586
- currency: mppInfo?.currency ?? "0x20c0000000000000000000000000000000000001"
3023
+ currency: mppInfo?.currency ?? TEMPO_USDC_CURRENCY
2587
3024
  }
2588
3025
  };
2589
3026
  }
@@ -2633,11 +3070,11 @@ function buildPricingInfo(entry) {
2633
3070
  }
2634
3071
 
2635
3072
  // src/discovery/llms-txt.ts
2636
- var import_server5 = require("next/server");
3073
+ var import_server8 = require("next/server");
2637
3074
  function createLlmsTxtHandler(discovery) {
2638
3075
  return async (_request) => {
2639
3076
  const guidance = await resolveGuidance(discovery) ?? "";
2640
- return new import_server5.NextResponse(guidance, {
3077
+ return new import_server8.NextResponse(guidance, {
2641
3078
  headers: {
2642
3079
  "Content-Type": "text/plain; charset=utf-8",
2643
3080
  "Access-Control-Allow-Origin": "*"
@@ -2647,14 +3084,14 @@ function createLlmsTxtHandler(discovery) {
2647
3084
  }
2648
3085
 
2649
3086
  // src/index.ts
2650
- init_x402_config();
3087
+ init_accepts();
2651
3088
  init_constants();
2652
3089
 
2653
3090
  // src/config.ts
2654
3091
  init_constants();
2655
3092
  init_evm();
2656
3093
  init_solana();
2657
- init_x402_config();
3094
+ init_accepts();
2658
3095
  var RouterConfigError = class extends Error {
2659
3096
  issues;
2660
3097
  constructor(issues) {
@@ -2698,6 +3135,8 @@ function mppFromEnv(env, options = {}) {
2698
3135
  const secretKey = env.MPP_SECRET_KEY;
2699
3136
  const currency = env.MPP_CURRENCY;
2700
3137
  const rpcUrl = env.TEMPO_RPC_URL;
3138
+ const feePayerKey = options.feePayerKey ?? env.MPP_FEE_PAYER_KEY;
3139
+ const feePayerKeySource = options.feePayerKey !== void 0 ? "feePayerKey" : "MPP_FEE_PAYER_KEY";
2701
3140
  const hasAnyMppEnv = Boolean(secretKey || currency || rpcUrl || options.require);
2702
3141
  if (!hasAnyMppEnv) return void 0;
2703
3142
  const missing = [
@@ -2708,12 +3147,21 @@ function mppFromEnv(env, options = {}) {
2708
3147
  if (missing.length > 0) {
2709
3148
  throw new Error(`MPP env is incomplete. Missing: ${missing.join(", ")}`);
2710
3149
  }
3150
+ if (!isEvmAddress(currency)) {
3151
+ throw new Error("MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address");
3152
+ }
3153
+ if (options.recipient && !isEvmAddress(options.recipient)) {
3154
+ throw new Error("MPP recipient must be a 0x-prefixed EVM address");
3155
+ }
3156
+ if (feePayerKey && !isEvmPrivateKey(feePayerKey)) {
3157
+ throw new Error(`${feePayerKeySource} must be a 0x-prefixed 32-byte EVM private key`);
3158
+ }
2711
3159
  return {
2712
3160
  secretKey,
2713
3161
  currency,
2714
3162
  rpcUrl,
2715
3163
  ...options.recipient ? { recipient: options.recipient } : {},
2716
- ...options.feePayerKey ? { feePayerKey: options.feePayerKey } : {},
3164
+ ...feePayerKey ? { feePayerKey } : {},
2717
3165
  ...options.useDefaultStore !== void 0 ? { useDefaultStore: options.useDefaultStore } : {}
2718
3166
  };
2719
3167
  }
@@ -2851,13 +3299,26 @@ function validateMppConfig(config, env) {
2851
3299
  protocol: "mpp",
2852
3300
  message: "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency."
2853
3301
  });
3302
+ } else if (!isEvmAddress(mpp.currency)) {
3303
+ issues.push({
3304
+ code: "invalid_mpp_currency",
3305
+ protocol: "mpp",
3306
+ message: "MPP currency must be a 0x-prefixed 20-byte Tempo currency address. Use TEMPO_USDC_CURRENCY for Tempo USDC."
3307
+ });
2854
3308
  }
2855
- if (!mpp.recipient && !config.payeeAddress) {
3309
+ const mppRecipient = mpp.recipient ?? config.payeeAddress;
3310
+ if (!mppRecipient) {
2856
3311
  issues.push({
2857
3312
  code: "missing_mpp_recipient",
2858
3313
  protocol: "mpp",
2859
3314
  message: "MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config."
2860
3315
  });
3316
+ } else if (!isEvmAddress(mppRecipient)) {
3317
+ issues.push({
3318
+ code: "invalid_mpp_recipient",
3319
+ protocol: "mpp",
3320
+ message: "MPP recipient must be a 0x-prefixed EVM address. Solana recipients require x402."
3321
+ });
2861
3322
  }
2862
3323
  const placeholder = findPlaceholderPayee([mpp.recipient, config.payeeAddress]);
2863
3324
  if (placeholder) {
@@ -2874,6 +3335,13 @@ function validateMppConfig(config, env) {
2874
3335
  message: "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
2875
3336
  });
2876
3337
  }
3338
+ if (mpp.feePayerKey && !isEvmPrivateKey(mpp.feePayerKey)) {
3339
+ issues.push({
3340
+ code: "invalid_mpp_fee_payer_key",
3341
+ protocol: "mpp",
3342
+ message: "MPP feePayerKey must be a 0x-prefixed 32-byte EVM private key."
3343
+ });
3344
+ }
2877
3345
  if (mpp.useDefaultStore && !mpp.store && (!env.KV_REST_API_URL || !env.KV_REST_API_TOKEN)) {
2878
3346
  issues.push({
2879
3347
  code: "missing_mpp_default_store_env",
@@ -2891,8 +3359,14 @@ function usesDefaultEvmFacilitator(config) {
2891
3359
  function isSupportedX402Network(network) {
2892
3360
  return isEvmNetwork(network) || isSolanaNetwork(network);
2893
3361
  }
3362
+ function isEvmAddress(value) {
3363
+ return /^0x[a-fA-F0-9]{40}$/.test(value);
3364
+ }
3365
+ function isEvmPrivateKey(value) {
3366
+ return /^0x[a-fA-F0-9]{64}$/.test(value);
3367
+ }
2894
3368
  function findPlaceholderPayee(values) {
2895
- return values.find((value) => value?.toLowerCase() === ZERO_EVM_ADDRESS) ?? null;
3369
+ return values.find((value) => value !== void 0 && /^0x0{40}$/i.test(value)) ?? null;
2896
3370
  }
2897
3371
 
2898
3372
  // src/index.ts
@@ -2941,6 +3415,7 @@ function createRouter(config) {
2941
3415
  nonceStore,
2942
3416
  entitlementStore,
2943
3417
  payeeAddress: config.payeeAddress ?? "",
3418
+ mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
2944
3419
  network,
2945
3420
  x402FacilitatorsByNetwork: void 0,
2946
3421
  x402Accepts,