@agentcash/router 1.5.1 → 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 /^0x/i.test(address) ? 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,106 +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, options = {}) {
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
- options.onError?.(error);
645
- const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
646
- const message = error instanceof Error ? error.message : "Internal error";
647
- return import_server.NextResponse.json({ success: false, error: message }, { status });
648
- }
649
- }
650
-
651
595
  // src/body.ts
652
596
  async function bufferBody(request) {
653
597
  const text = await request.text();
@@ -672,299 +616,329 @@ function validateBody(parsed, schema) {
672
616
  };
673
617
  }
674
618
 
675
- // src/pricing.ts
676
- async function resolvePrice(pricing, body) {
677
- if (typeof pricing === "string") {
678
- return pricing;
679
- }
680
- if (typeof pricing === "function") {
681
- return pricing(body);
682
- }
683
- const { field, tiers, default: defaultTier } = pricing;
684
- const tierKey = body != null ? String(body[field] ?? "") : "";
685
- if (tierKey && tiers[tierKey]) {
686
- return tiers[tierKey].price;
687
- }
688
- if (defaultTier && tiers[defaultTier]) {
689
- return tiers[defaultTier].price;
690
- }
691
- if (!tierKey) {
692
- throw Object.assign(new Error(`Missing required field '${field}' for tier pricing`), {
693
- status: 400
694
- });
695
- }
696
- throw Object.assign(
697
- new Error(
698
- `Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
699
- ),
700
- { status: 400 }
701
- );
702
- }
703
- function resolveMaxPrice(pricing) {
704
- if (typeof pricing === "string") return pricing;
705
- if (typeof pricing === "function") {
706
- throw new Error("Dynamic pricing requires maxPrice \u2014 this should be caught at registration");
707
- }
708
- const { tiers } = pricing;
709
- let max = "0";
710
- for (const tier of Object.values(tiers)) {
711
- if (parseFloat(tier.price) > parseFloat(max)) {
712
- max = tier.price;
713
- }
714
- }
715
- return max;
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
+ };
716
632
  }
717
633
 
718
- // src/orchestrate.ts
719
- var import_mppx2 = require("mppx");
720
- var import_actions = require("viem/actions");
721
- var import_tempo = require("viem/tempo");
722
- var import_viem2 = require("viem");
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;
640
+ }
723
641
 
724
- // src/protocols/x402.ts
725
- init_x402_facilitators();
726
- init_evm();
727
- init_solana();
728
- async function buildX402Challenge(opts) {
729
- const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
730
- const { encodePaymentRequiredHeader } = await import("@x402/core/http");
731
- const resource = buildChallengeResource(request, routeEntry);
732
- const requirements = await buildChallengeRequirements(
733
- server,
734
- request,
735
- price,
736
- accepts,
737
- resource,
738
- facilitatorsByNetwork
739
- );
740
- const paymentRequired = await server.createPaymentRequiredResponse(
741
- requirements,
742
- resource,
743
- void 0,
744
- extensions
745
- );
746
- const encoded = encodePaymentRequiredHeader(paymentRequired);
747
- return { encoded, requirements };
642
+ // src/pipeline/context/errors.ts
643
+ function errorStatus(error, fallback) {
644
+ const status = error?.status;
645
+ return typeof status === "number" ? status : fallback;
748
646
  }
749
- async function verifyX402Payment(opts) {
750
- const { server, request, price, accepts } = opts;
751
- const payload = await readPaymentPayload(request);
752
- if (!payload) return null;
753
- const requirements = await buildExpectedRequirements(server, request, price, accepts);
754
- const matching = findVerifiableRequirements(server, requirements, payload);
755
- if (!matching) {
756
- return invalidPaymentVerification();
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
+ }
654
+
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
+ });
757
675
  }
758
- 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;
759
688
  try {
760
- verify = await server.verifyPayment(payload, matching);
689
+ await ctx.routeEntry.validateFn(body);
690
+ return null;
761
691
  } catch (err) {
762
- const sc = err.statusCode;
763
- if (sc && sc >= 400 && sc < 500) return invalidPaymentVerification();
764
- throw err;
765
- }
766
- if (!verify.isValid) return invalidPaymentVerification();
767
- if (typeof verify.payer !== "string" || verify.payer.length === 0) {
768
- throw new Error("x402 verification succeeded without a payer address");
692
+ return fail(ctx, errorStatus(err, 400), errorMessage(err, "Validation failed"), body);
769
693
  }
770
- return {
771
- valid: true,
772
- payer: verify.payer,
773
- payload,
774
- requirements: matching
775
- };
776
694
  }
777
- function findVerifiableRequirements(server, requirements, payload) {
778
- const strictMatch = server.findMatchingRequirements(requirements, payload);
779
- if (strictMatch) {
780
- 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";
781
705
  }
782
- if (payload.x402Version !== 2) {
783
- 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 });
784
719
  }
785
- const stableMatch = requirements.find(
786
- (requirement) => matchesStableFields(requirement, payload.accepted)
787
- );
788
- return stableMatch ? payload.accepted : null;
789
- }
790
- function matchesStableFields(requirement, accepted) {
791
- 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;
792
- }
793
- async function buildExpectedRequirements(server, request, price, accepts) {
794
- const exactRequirements = await buildExactRequirements(server, request, price, accepts);
795
- const customRequirements = buildCustomRequirements(price, accepts);
796
- return [...exactRequirements, ...customRequirements];
797
720
  }
798
- async function buildExactRequirements(server, request, price, accepts) {
799
- const exactGroups = [
800
- buildEvmExactOptions(accepts, price),
801
- buildSolanaExactOptions(accepts, price)
802
- ].filter((options) => options.length > 0);
803
- if (exactGroups.length === 0) return [];
804
- const requirements = [];
805
- const failures = [];
806
- for (const options of exactGroups) {
807
- try {
808
- requirements.push(
809
- ...await server.buildPaymentRequirementsFromOptions(options, { request })
810
- );
811
- } catch (error) {
812
- const err = error instanceof Error ? error : new Error(String(error));
813
- failures.push(err);
814
- if (exactGroups.length === 1) {
815
- 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;
816
754
  }
817
- console.warn(
818
- `[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
819
- );
820
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 {
821
782
  }
822
- if (requirements.length > 0) {
823
- return requirements;
824
- }
825
- throw failures[0] ?? new Error("Failed to build x402 exact requirements");
826
783
  }
827
- function buildCustomRequirements(price, accepts) {
828
- 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";
829
789
  }
830
- async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork) {
831
- const requirements = await buildExpectedRequirements(server, request, price, accepts);
832
- if (!needsFacilitatorEnrichment(accepts)) return requirements;
833
- 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;
834
796
  }
835
- function needsFacilitatorEnrichment(accepts) {
836
- 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);
837
809
  }
838
- async function enrichGroup(group, resource) {
839
- const accepted = await enrichRequirementsWithFacilitatorAccepts(
840
- group.facilitator,
841
- resource,
842
- group.items.map(({ requirement }) => requirement)
843
- );
844
- if (accepted.length !== group.items.length) {
845
- throw new Error(
846
- `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
847
838
  );
848
839
  }
849
- return accepted;
850
840
  }
851
- async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork) {
852
- const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
853
- if (groups.length === 0) return requirements;
854
- const results = await Promise.all(
855
- groups.map(async (group) => {
856
- try {
857
- return { success: true, group, accepted: await enrichGroup(group, resource) };
858
- } catch (err) {
859
- const label = group.facilitator.url ?? group.facilitator.network;
860
- const reason = err instanceof Error ? err.message : String(err);
861
- console.warn(
862
- `[router] ${label} /accepts failed, dropping ${group.items.length} requirement(s): ${reason}`
863
- );
864
- return { success: false, group };
865
- }
866
- })
867
- );
868
- const enriched = [...requirements];
869
- results.filter((r) => r.success).forEach(({ group, accepted }) => {
870
- accepted.forEach((req, offset) => {
871
- const index = group.items[offset]?.index;
872
- 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
873
855
  });
874
- });
875
- const failedIndices = new Set(
876
- results.filter((r) => !r.success).flatMap(({ group }) => group.items.map(({ index }) => index))
877
- );
878
- const remaining = enriched.filter((_, i) => !failedIndices.has(i));
879
- if (remaining.length === 0) {
880
- throw new Error(
881
- "All facilitator enrichments failed; no payment requirements remain for challenge"
882
- );
883
856
  }
884
- return remaining;
885
857
  }
886
- function collectEnrichmentGroups(requirements, facilitatorsByNetwork) {
887
- const groups = [];
888
- requirements.forEach((requirement, index) => {
889
- if (!requiresFacilitatorEnrichment(requirement)) return;
890
- const facilitator = getRequiredFacilitator(requirement, facilitatorsByNetwork);
891
- const existing = groups.find(
892
- (group) => sameResolvedX402Facilitator(group.facilitator, facilitator)
893
- );
894
- if (existing) {
895
- existing.items.push({ index, requirement });
896
- return;
897
- }
898
- groups.push({
899
- facilitator,
900
- 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
901
872
  });
902
- });
903
- return groups;
873
+ await runSettlementError(ctx, scope, error, "afterSettle");
874
+ }
904
875
  }
905
- function getRequiredFacilitator(requirement, facilitatorsByNetwork) {
906
- const facilitator = getFacilitatorForRequirement(facilitatorsByNetwork, requirement);
907
- if (!facilitator) {
908
- throw new Error(
909
- `Missing x402 facilitator for ${requirement.scheme} requirement on ${requirement.network}`
910
- );
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
+ });
911
891
  }
912
- return facilitator;
913
- }
914
- function requiresFacilitatorEnrichment(requirement) {
915
- return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
916
892
  }
917
- function buildCustomRequirement(price, accept) {
918
- if (!accept.asset) {
919
- throw new Error(
920
- `Custom x402 accept '${accept.scheme}' on '${accept.network}' is missing asset`
921
- );
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
+ });
922
905
  }
923
- return {
924
- scheme: accept.scheme,
925
- network: accept.network,
926
- amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
927
- asset: accept.asset,
928
- payTo: accept.payTo,
929
- maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
930
- extra: accept.extra ?? {}
931
- };
932
- }
933
- function buildChallengeResource(request, routeEntry) {
934
- return {
935
- url: request.url,
936
- method: routeEntry.method,
937
- description: routeEntry.description,
938
- mimeType: "application/json"
939
- };
940
- }
941
- async function readPaymentPayload(request) {
942
- const paymentHeader = request.headers.get("PAYMENT-SIGNATURE") ?? request.headers.get("X-PAYMENT");
943
- if (!paymentHeader) return null;
944
- const { decodePaymentSignatureHeader } = await import("@x402/core/http");
945
- return decodePaymentSignatureHeader(paymentHeader);
946
- }
947
- function invalidPaymentVerification() {
948
- return { valid: false, payload: null, requirements: null, payer: null };
949
906
  }
950
- function decimalToAtomicUnits(amount, decimals) {
951
- const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
952
- if (!match?.groups) {
953
- throw new Error(`Invalid decimal amount '${amount}'`);
954
- }
955
- const whole = match.groups.whole;
956
- const fraction = match.groups.fraction ?? "";
957
- if (fraction.length > decimals) {
958
- throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
959
- }
960
- const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
961
- 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);
962
937
  }
963
- async function settleX402Payment(server, payload, requirements) {
964
- const { encodePaymentResponseHeader } = await import("@x402/core/http");
965
- const result = await server.settlePayment(payload, requirements);
966
- const encoded = encodePaymentResponseHeader(result);
967
- return { encoded, result };
938
+
939
+ // src/auth/normalize-wallet.ts
940
+ function normalizeWalletAddress(address) {
941
+ return /^0x/i.test(address) ? address.toLowerCase() : address;
968
942
  }
969
943
 
970
944
  // src/auth/siwx.ts
@@ -988,7 +962,7 @@ function categorizeValidationError(error) {
988
962
  }
989
963
  async function verifySIWX(request, _routeEntry, nonceStore) {
990
964
  const { parseSIWxHeader, validateSIWxMessage, verifySIWxSignature } = await import("@x402/extensions/sign-in-with-x");
991
- const header = request.headers.get("SIGN-IN-WITH-X");
965
+ const header = request.headers.get(HEADERS.SIWX);
992
966
  if (!header) {
993
967
  return { valid: false, wallet: null, code: "siwx_missing_header" };
994
968
  }
@@ -1017,25 +991,52 @@ async function buildSIWXExtension() {
1017
991
  return declareSIWxExtension();
1018
992
  }
1019
993
 
1020
- // src/auth/mpp-siwx.ts
1021
- var import_mppx = require("mppx");
1022
- var import_viem = require("viem");
1023
- async function verifyMppSiwx(request, mppx) {
1024
- const result = await mppx.charge({ amount: "0" })(request);
1025
- if (result.status === 402) {
1026
- 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
+ }
1027
1032
  }
1028
- const credential = import_mppx.Credential.fromRequest(request);
1029
- const rawSource = credential?.source ?? "";
1030
- const didParts = rawSource.split(":");
1031
- const lastPart = didParts[didParts.length - 1];
1032
- const wallet = normalizeWalletAddress((0, import_viem.isAddress)(lastPart) ? (0, import_viem.getAddress)(lastPart) : rawSource);
1033
- return { valid: true, wallet, withReceipt: result.withReceipt };
1033
+ if (errors.length === 0) return null;
1034
+ return `Payment protocol initialization failed. ${errors.join("; ")}`;
1034
1035
  }
1035
1036
 
1036
1037
  // src/auth/api-key.ts
1037
1038
  async function verifyApiKey(request, resolver) {
1038
- 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));
1039
1040
  if (!apiKey) {
1040
1041
  return { valid: false, account: null };
1041
1042
  }
@@ -1047,947 +1048,873 @@ async function verifyApiKey(request, resolver) {
1047
1048
  }
1048
1049
  function extractBearerToken(header) {
1049
1050
  if (!header) return null;
1050
- if (header.startsWith("Bearer ")) return header.slice(7);
1051
+ if (header.startsWith(AUTH_SCHEME.BEARER)) return header.slice(AUTH_SCHEME.BEARER.length);
1051
1052
  return null;
1052
1053
  }
1053
1054
 
1054
- // src/orchestrate.ts
1055
- init_x402_config();
1056
- function getRequirementNetwork(requirements, fallback) {
1057
- const network = requirements?.network;
1058
- return typeof network === "string" ? network : fallback;
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);
1059
1069
  }
1060
- function getRequirementRecipient(requirements) {
1061
- const payTo = requirements?.payTo;
1062
- return typeof payTo === "string" ? payTo : void 0;
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;
1091
+ }
1092
+ }
1093
+ challengeQuote(body) {
1094
+ if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
1095
+ return this.quote(body);
1096
+ }
1097
+ describe() {
1098
+ return {
1099
+ mode: "dynamic",
1100
+ min: this.opts.minPrice ?? "0",
1101
+ max: this.opts.maxPrice ?? "0"
1102
+ };
1103
+ }
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
1113
+ });
1114
+ return this.opts.maxPrice;
1115
+ }
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);
1063
1124
  }
1064
- function errorStatus(error, fallback) {
1065
- const status = error?.status;
1066
- return typeof status === "number" ? status : fallback;
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) {
1164
+ try {
1165
+ return this.quote(body);
1166
+ } catch {
1167
+ }
1168
+ }
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 });
1067
1192
  }
1068
- function errorMessage(error, fallback) {
1069
- return error instanceof Error ? error.message : fallback;
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)}`);
1070
1219
  }
1071
- function handlerFailureError(response) {
1072
- const message = response.statusText || `Handler returned HTTP ${response.status}`;
1073
- return Object.assign(new Error(message), { status: response.status });
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 };
1074
1231
  }
1075
- function siwxSignatureType(network) {
1076
- return network.startsWith("solana:") ? "ed25519" : "eip191";
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);
1077
1236
  }
1078
- function getSupportedChains(x402Accepts, fallbackNetwork) {
1079
- const seen = /* @__PURE__ */ new Set();
1080
- const chains = [];
1081
- for (const accept of x402Accepts) {
1082
- if (accept.network && !seen.has(accept.network)) {
1083
- seen.add(accept.network);
1084
- chains.push({ chainId: accept.network, type: siwxSignatureType(accept.network) });
1085
- }
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 "";
1086
1250
  }
1087
- if (chains.length === 0) {
1088
- chains.push({ chainId: fallbackNetwork, type: siwxSignatureType(fallbackNetwork) });
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 "";
1089
1260
  }
1090
- return chains;
1091
1261
  }
1092
- function createRequestHandler(routeEntry, handler, deps) {
1093
- async function invoke(request, meta, pluginCtx, wallet, account, parsedBody, payment) {
1094
- const ctx = {
1095
- body: parsedBody,
1096
- query: parseQuery(request, routeEntry),
1097
- request,
1098
- requestId: meta.requestId,
1099
- route: routeEntry.key,
1100
- wallet,
1101
- payment,
1102
- account,
1103
- alert(level, message, alertMeta) {
1104
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1105
- level,
1106
- message,
1107
- route: routeEntry.key,
1108
- meta: alertMeta
1109
- });
1110
- },
1111
- setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
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"
1112
1271
  };
1113
- let rawResult;
1114
- let handlerError;
1115
- const response = await safeCallHandler(
1116
- async (c) => {
1117
- rawResult = await handler(c);
1118
- return rawResult;
1119
- },
1120
- ctx,
1121
- {
1122
- onError(error) {
1123
- handlerError = error;
1124
- }
1125
- }
1126
- );
1127
- return { response, rawResult, handlerError };
1128
1272
  }
1129
- function finalize(response, rawResult, meta, pluginCtx, requestBody) {
1130
- fireProviderQuota(routeEntry, response, rawResult, deps, pluginCtx);
1131
- firePluginResponse(deps, pluginCtx, meta, response, requestBody, rawResult);
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
+ };
1132
1312
  }
1133
- function fail(status, message, meta, pluginCtx, requestBody) {
1134
- const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
1135
- firePluginResponse(deps, pluginCtx, meta, response, requestBody);
1136
- return response;
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
+ };
1137
1325
  }
1138
- function settlementContext(scope) {
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}`);
1139
1336
  return {
1140
- route: routeEntry.key,
1141
- request: scope.request,
1142
- body: scope.parsedBody,
1143
- wallet: scope.wallet,
1144
- account: scope.account,
1145
- payment: scope.payment,
1146
- response: scope.response,
1147
- result: scope.rawResult
1337
+ ok: false,
1338
+ error: settlementError,
1339
+ failMessage: `MPP payment failed: ${detail}`,
1340
+ failStatus: 500
1148
1341
  };
1149
1342
  }
1150
- async function runBeforeSettle(scope) {
1151
- const hook = routeEntry.settlement?.beforeSettle;
1152
- if (!hook) return null;
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 {};
1153
1435
  try {
1154
- await hook(settlementContext(scope));
1155
- return null;
1156
- } catch (error) {
1157
- return fail(
1158
- errorStatus(error, 500),
1159
- errorMessage(error, "Pre-settlement validation failed"),
1160
- scope.meta,
1161
- scope.pluginCtx,
1162
- scope.parsedBody
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
+ }
1441
+ } catch (err) {
1442
+ console.warn(
1443
+ `[router] MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`
1163
1444
  );
1445
+ throw err;
1164
1446
  }
1447
+ return {};
1165
1448
  }
1166
- async function runSettlementError(scope, error, phase) {
1167
- const hook = routeEntry.settlement?.onSettlementError;
1168
- if (!hook) return;
1169
- try {
1170
- await hook({
1171
- ...settlementContext(scope),
1172
- error,
1173
- phase
1174
- });
1175
- } catch (hookError) {
1176
- const message = errorMessage(hookError, "Settlement error hook failed");
1177
- console.error(`[router] ${routeEntry.key}: onSettlementError failed: ${message}`);
1178
- firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
1179
- level: "error",
1180
- message: `Settlement error hook failed: ${message}`,
1181
- route: routeEntry.key
1182
- });
1183
- }
1184
- }
1185
- async function runAfterSettle(scope) {
1186
- const hook = routeEntry.settlement?.afterSettle;
1187
- if (!hook) return;
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) {
1188
1475
  try {
1189
- await hook(settlementContext(scope));
1476
+ requirements.push(
1477
+ ...await server.buildPaymentRequirementsFromOptions(options, { request })
1478
+ );
1190
1479
  } catch (error) {
1191
- const message = errorMessage(error, "Post-settlement hook failed");
1192
- console.error(`[router] ${routeEntry.key}: afterSettle failed: ${message}`);
1193
- firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
1194
- level: "error",
1195
- message: `Post-settlement hook failed: ${message}`,
1196
- route: routeEntry.key
1197
- });
1198
- await runSettlementError(scope, error, "afterSettle");
1199
- }
1200
- }
1201
- async function runSettledHandlerError(scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
1202
- const hook = routeEntry.settlement?.onSettledHandlerError;
1203
- if (!hook) return;
1204
- try {
1205
- await hook({
1206
- ...settlementContext(scope),
1207
- error
1208
- });
1209
- } catch (hookError) {
1210
- const message = errorMessage(hookError, "Settled handler error hook failed");
1211
- console.error(`[router] ${routeEntry.key}: onSettledHandlerError failed: ${message}`);
1212
- firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
1213
- level: "error",
1214
- message: `Settled handler error hook failed: ${message}`,
1215
- route: routeEntry.key
1216
- });
1217
- }
1218
- }
1219
- return async (request) => {
1220
- await deps.initPromise;
1221
- const meta = buildMeta(request, routeEntry);
1222
- const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
1223
- async function handleAuth(wallet, account2) {
1224
- const body2 = await parseBody(request, routeEntry);
1225
- if (!body2.ok) {
1226
- firePluginResponse(deps, pluginCtx, meta, body2.response);
1227
- return body2.response;
1228
- }
1229
- if (routeEntry.validateFn) {
1230
- try {
1231
- await routeEntry.validateFn(body2.data);
1232
- } catch (err) {
1233
- const status = err.status ?? 400;
1234
- const message = err instanceof Error ? err.message : "Validation failed";
1235
- return fail(status, message, meta, pluginCtx, body2.data);
1236
- }
1237
- }
1238
- const { response, rawResult } = await invoke(
1239
- request,
1240
- meta,
1241
- pluginCtx,
1242
- wallet,
1243
- account2,
1244
- body2.data,
1245
- null
1246
- );
1247
- finalize(response, rawResult, meta, pluginCtx, body2.data);
1248
- return response;
1249
- }
1250
- if (routeEntry.authMode === "unprotected") {
1251
- return handleAuth(null, void 0);
1252
- }
1253
- let account;
1254
- if (routeEntry.authMode === "apiKey" || routeEntry.apiKeyResolver) {
1255
- if (!routeEntry.apiKeyResolver) {
1256
- return fail(401, "API key resolver not configured", meta, pluginCtx);
1257
- }
1258
- const keyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
1259
- if (!keyResult.valid) {
1260
- return fail(401, "Invalid or missing API key", meta, pluginCtx);
1261
- }
1262
- account = keyResult.account;
1263
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1264
- authMode: "apiKey",
1265
- wallet: null,
1266
- route: routeEntry.key,
1267
- account
1268
- });
1269
- if (routeEntry.authMode === "apiKey" && !routeEntry.pricing) {
1270
- return handleAuth(null, account);
1271
- }
1272
- }
1273
- const protocol = detectProtocol(request);
1274
- let earlyBodyData;
1275
- const pricingNeedsBody = routeEntry.pricing != null && typeof routeEntry.pricing !== "string";
1276
- const needsEarlyParse = !protocol && routeEntry.bodySchema && (pricingNeedsBody || routeEntry.validateFn);
1277
- if (needsEarlyParse) {
1278
- const requestForPricing = request.clone();
1279
- const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
1280
- if (earlyBodyResult.ok) {
1281
- earlyBodyData = earlyBodyResult.data;
1282
- if (routeEntry.validateFn) {
1283
- try {
1284
- await routeEntry.validateFn(earlyBodyData);
1285
- } catch (err) {
1286
- const status = err.status ?? 400;
1287
- const message = err instanceof Error ? err.message : "Validation failed";
1288
- return fail(status, message, meta, pluginCtx, earlyBodyData);
1289
- }
1290
- }
1291
- } else {
1292
- firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
1293
- return earlyBodyResult.response;
1294
- }
1295
- }
1296
- if (routeEntry.authMode === "siwx" || routeEntry.siwxEnabled) {
1297
- if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get("SIGN-IN-WITH-X")) {
1298
- const requestForValidation = request.clone();
1299
- const earlyBodyResult = await parseBody(requestForValidation, routeEntry);
1300
- if (earlyBodyResult.ok) {
1301
- try {
1302
- await routeEntry.validateFn(earlyBodyResult.data);
1303
- } catch (err) {
1304
- const status = err.status ?? 400;
1305
- const message = err instanceof Error ? err.message : "Validation failed";
1306
- return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
1307
- }
1308
- }
1309
- }
1310
- const siwxHeader = request.headers.get("SIGN-IN-WITH-X");
1311
- if (!siwxHeader && protocol === "mpp" && routeEntry.authMode === "siwx" && deps.mppx) {
1312
- let mppSiwxResult;
1313
- try {
1314
- mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
1315
- } catch (err) {
1316
- const message = err instanceof Error ? err.message : String(err);
1317
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1318
- level: "critical",
1319
- message: `MPP SIWX verification failed: ${message}`,
1320
- route: routeEntry.key
1321
- });
1322
- return fail(500, `MPP SIWX verification failed: ${message}`, meta, pluginCtx);
1323
- }
1324
- if (mppSiwxResult.valid) {
1325
- pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
1326
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1327
- authMode: "siwx",
1328
- wallet: mppSiwxResult.wallet,
1329
- route: routeEntry.key
1330
- });
1331
- const authResponse = await handleAuth(mppSiwxResult.wallet, void 0);
1332
- if (authResponse.status < 400) {
1333
- return mppSiwxResult.withReceipt(authResponse);
1334
- }
1335
- return authResponse;
1336
- }
1337
- }
1338
- if (!siwxHeader && routeEntry.authMode === "siwx") {
1339
- const url = new URL(request.url);
1340
- const nonce = crypto.randomUUID().replace(/-/g, "");
1341
- const supportedChains = getSupportedChains(deps.x402Accepts, deps.network);
1342
- const primaryChain = supportedChains[0];
1343
- const siwxInfo = {
1344
- domain: url.hostname,
1345
- uri: request.url,
1346
- version: "1",
1347
- chainId: primaryChain.chainId,
1348
- type: primaryChain.type,
1349
- nonce,
1350
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1351
- expirationTime: new Date(Date.now() + SIWX_CHALLENGE_EXPIRY_MS).toISOString(),
1352
- statement: "Sign in to verify your wallet identity"
1353
- };
1354
- let siwxSchema;
1355
- try {
1356
- siwxSchema = await buildSIWXExtension();
1357
- } catch {
1358
- }
1359
- const paymentRequired = {
1360
- x402Version: 2,
1361
- error: "SIWX authentication required",
1362
- resource: {
1363
- url: request.url,
1364
- description: routeEntry.description ?? "SIWX-protected endpoint",
1365
- mimeType: "application/json"
1366
- },
1367
- accepts: [],
1368
- extensions: {
1369
- "sign-in-with-x": {
1370
- info: siwxInfo,
1371
- // supportedChains at top level required by MCP tools for chain detection
1372
- supportedChains,
1373
- ...siwxSchema ? { schema: siwxSchema } : {}
1374
- }
1375
- }
1376
- };
1377
- let encoded;
1378
- try {
1379
- const { encodePaymentRequiredHeader } = await import("@x402/core/http");
1380
- encoded = encodePaymentRequiredHeader(paymentRequired);
1381
- } catch (err) {
1382
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1383
- level: "warn",
1384
- message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
1385
- route: routeEntry.key
1386
- });
1387
- }
1388
- const response = new import_server2.NextResponse(JSON.stringify(paymentRequired), {
1389
- status: 402,
1390
- headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
1391
- });
1392
- if (encoded) response.headers.set("PAYMENT-REQUIRED", encoded);
1393
- if (deps.mppx) {
1394
- try {
1395
- const mppChallenge = await deps.mppx.charge({ amount: "0" })(request);
1396
- if (mppChallenge.status === 402) {
1397
- const wwwAuth = mppChallenge.challenge.headers.get("WWW-Authenticate");
1398
- if (wwwAuth) response.headers.set("WWW-Authenticate", wwwAuth);
1399
- }
1400
- } catch {
1401
- }
1402
- }
1403
- firePluginResponse(deps, pluginCtx, meta, response);
1404
- return response;
1405
- }
1406
- if (siwxHeader) {
1407
- const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
1408
- if (!siwx.valid) {
1409
- if (routeEntry.authMode === "siwx") {
1410
- const response = import_server2.NextResponse.json(
1411
- { error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
1412
- { status: 402 }
1413
- );
1414
- firePluginResponse(deps, pluginCtx, meta, response);
1415
- return response;
1416
- }
1417
- } else {
1418
- const wallet = normalizeWalletAddress(siwx.wallet);
1419
- pluginCtx.setVerifiedWallet(wallet);
1420
- if (routeEntry.authMode === "siwx") {
1421
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1422
- authMode: "siwx",
1423
- wallet,
1424
- route: routeEntry.key
1425
- });
1426
- return handleAuth(wallet, void 0);
1427
- }
1428
- if (routeEntry.siwxEnabled && routeEntry.pricing) {
1429
- const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
1430
- if (entitled) {
1431
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1432
- authMode: "siwx",
1433
- wallet,
1434
- route: routeEntry.key
1435
- });
1436
- return handleAuth(wallet, account);
1437
- }
1438
- }
1439
- }
1440
- }
1441
- }
1442
- if (!protocol || protocol === "siwx") {
1443
- if (routeEntry.pricing) {
1444
- const initErrors = routeEntry.protocols.map((p) => {
1445
- if (p === "x402" && deps.x402InitError) return `x402: ${deps.x402InitError}`;
1446
- if (p === "mpp" && deps.mppInitError) return `mpp: ${deps.mppInitError}`;
1447
- return null;
1448
- }).filter(Boolean);
1449
- if (initErrors.length > 0) {
1450
- return fail(
1451
- 500,
1452
- `Payment protocol initialization failed. ${initErrors.join("; ")}`,
1453
- meta,
1454
- pluginCtx
1455
- );
1456
- }
1457
- }
1458
- return await build402(request, routeEntry, deps, meta, pluginCtx, earlyBodyData);
1459
- }
1460
- const body = await parseBody(request, routeEntry);
1461
- if (!body.ok) {
1462
- firePluginResponse(deps, pluginCtx, meta, body.response);
1463
- return body.response;
1464
- }
1465
- if (routeEntry.validateFn) {
1466
- try {
1467
- await routeEntry.validateFn(body.data);
1468
- } catch (err) {
1469
- const status = err.status ?? 400;
1470
- const message = err instanceof Error ? err.message : "Validation failed";
1471
- return fail(status, message, meta, pluginCtx, body.data);
1480
+ const err = error instanceof Error ? error : new Error(String(error));
1481
+ failures.push(err);
1482
+ if (exactGroups.length === 1) {
1483
+ throw err;
1472
1484
  }
1473
- }
1474
- const priceResult = await resolveDynamicPrice(body.data, routeEntry, deps, pluginCtx, meta);
1475
- if ("error" in priceResult) return priceResult.error;
1476
- const price = priceResult.price;
1477
- if (!routeEntry.protocols.includes(protocol)) {
1478
- const accepted = routeEntry.protocols.join(", ") || "none";
1479
1485
  console.warn(
1480
- `[router] ${routeEntry.key}: received ${protocol} payment but route accepts [${accepted}]`
1481
- );
1482
- return fail(
1483
- 400,
1484
- `This route does not accept ${protocol} payments. Accepted protocols: ${accepted}`,
1485
- meta,
1486
- pluginCtx,
1487
- body.data
1486
+ `[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
1488
1487
  );
1489
1488
  }
1490
- if (protocol === "x402") {
1491
- if (!deps.x402Server) {
1492
- 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";
1493
- console.error(`[router] ${routeEntry.key}: ${reason}`);
1494
- return fail(500, reason, meta, pluginCtx, body.data);
1495
- }
1496
- const accepts = await resolveX402Accepts(
1497
- request,
1498
- routeEntry,
1499
- deps.x402Accepts,
1500
- deps.payeeAddress,
1501
- body.data
1502
- );
1503
- const verify = await verifyX402Payment({
1504
- server: deps.x402Server,
1505
- request,
1506
- price,
1507
- accepts
1508
- });
1509
- if (!verify?.valid)
1510
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1511
- const { payload: verifyPayload, requirements: verifyRequirements } = verify;
1512
- const matchedNetwork = getRequirementNetwork(verifyRequirements, deps.network);
1513
- const matchedRecipient = getRequirementRecipient(verifyRequirements);
1514
- const wallet = normalizeWalletAddress(verify.payer);
1515
- const payment = {
1516
- protocol: "x402",
1517
- status: "verified",
1518
- payer: wallet,
1519
- amount: price,
1520
- network: matchedNetwork,
1521
- ...matchedRecipient ? { recipient: matchedRecipient } : {}
1522
- };
1523
- pluginCtx.setVerifiedWallet(wallet);
1524
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1525
- protocol: "x402",
1526
- payer: wallet,
1527
- amount: price,
1528
- network: matchedNetwork
1529
- });
1530
- const { response, rawResult, handlerError } = await invoke(
1531
- request,
1532
- meta,
1533
- pluginCtx,
1534
- wallet,
1535
- account,
1536
- body.data,
1537
- payment
1538
- );
1539
- const settleScope = {
1540
- request,
1541
- meta,
1542
- pluginCtx,
1543
- wallet,
1544
- account,
1545
- parsedBody: body.data,
1546
- payment,
1547
- response,
1548
- rawResult,
1549
- handlerError
1550
- };
1551
- if (response.status < 400) {
1552
- const validationFailure = await runBeforeSettle(settleScope);
1553
- if (validationFailure) return validationFailure;
1554
- try {
1555
- const settle = await settleX402Payment(
1556
- deps.x402Server,
1557
- verifyPayload,
1558
- verifyRequirements
1559
- );
1560
- if (!settle.result?.success) {
1561
- const reason = settle.result?.errorReason || "x402 settlement returned success=false";
1562
- const error = new Error(reason);
1563
- error.errorReason = reason;
1564
- throw error;
1565
- }
1566
- if (routeEntry.siwxEnabled) {
1567
- try {
1568
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1569
- } catch (error) {
1570
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1571
- level: "warn",
1572
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1573
- route: routeEntry.key
1574
- });
1575
- }
1576
- }
1577
- response.headers.set("PAYMENT-RESPONSE", settle.encoded);
1578
- response.headers.set("Cache-Control", "private");
1579
- const transaction = String(settle.result?.transaction ?? "");
1580
- const settledPayment = {
1581
- ...payment,
1582
- status: "settled",
1583
- ...transaction ? { transaction } : {}
1584
- };
1585
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1586
- protocol: "x402",
1587
- payer: wallet,
1588
- transaction,
1589
- network: matchedNetwork
1590
- });
1591
- await runAfterSettle({ ...settleScope, payment: settledPayment });
1592
- } catch (err) {
1593
- await runSettlementError(settleScope, err, "settle");
1594
- const errObj = err;
1595
- console.error("Settlement failed", {
1596
- message: err instanceof Error ? err.message : String(err),
1597
- route: routeEntry.key,
1598
- network: matchedNetwork,
1599
- errorReason: errObj.errorReason,
1600
- facilitatorStatus: errObj.response?.status,
1601
- facilitatorBody: errObj.response?.data ?? errObj.response?.body
1602
- });
1603
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1604
- level: "critical",
1605
- message: `Settlement failed: ${err instanceof Error ? err.message : String(err)}`,
1606
- route: routeEntry.key
1607
- });
1608
- return fail(500, "Settlement failed", meta, pluginCtx, body.data);
1609
- }
1610
- }
1611
- finalize(response, rawResult, meta, pluginCtx, body.data);
1612
- return response;
1613
- }
1614
- if (protocol === "mpp") {
1615
- if (!deps.mppx) {
1616
- 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";
1617
- console.error(`[router] ${routeEntry.key}: ${reason}`);
1618
- return fail(500, reason, meta, pluginCtx, body.data);
1619
- }
1620
- const mppCredential = import_mppx2.Credential.fromRequest(request);
1621
- const rawSource = mppCredential?.source ?? "";
1622
- const didParts = rawSource.split(":");
1623
- const lastPart = didParts[didParts.length - 1];
1624
- const wallet = normalizeWalletAddress((0, import_viem2.isAddress)(lastPart) ? (0, import_viem2.getAddress)(lastPart) : rawSource);
1625
- const payloadType = mppCredential?.payload?.type;
1626
- if (payloadType === "transaction" && deps.tempoClient) {
1627
- try {
1628
- const serializedTx = mppCredential.payload.signature;
1629
- const transaction = import_tempo.Transaction.deserialize(serializedTx);
1630
- await (0, import_actions.call)(deps.tempoClient, {
1631
- ...transaction,
1632
- account: transaction.from,
1633
- calls: transaction.calls ?? []
1634
- });
1635
- } catch (err) {
1636
- const message = err instanceof Error ? err.message : String(err);
1637
- console.warn(`[router] ${routeEntry.key}: MPP simulation failed \u2014 ${message}`);
1638
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1639
- level: "warn",
1640
- message: `MPP simulation failed: ${message}`,
1641
- route: routeEntry.key
1642
- });
1643
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1644
- }
1645
- pluginCtx.setVerifiedWallet(wallet);
1646
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1647
- protocol: "mpp",
1648
- payer: wallet,
1649
- amount: price,
1650
- network: "tempo:4217"
1651
- });
1652
- const mppRecipient2 = deps.mppRecipient ?? deps.payeeAddress;
1653
- const payment2 = {
1654
- protocol: "mpp",
1655
- status: "verified",
1656
- payer: wallet,
1657
- amount: price,
1658
- network: "tempo:4217",
1659
- ...mppRecipient2 ? { recipient: mppRecipient2 } : {}
1660
- };
1661
- const { response: response2, rawResult: rawResult2, handlerError: handlerError2 } = await invoke(
1662
- request,
1663
- meta,
1664
- pluginCtx,
1665
- wallet,
1666
- account,
1667
- body.data,
1668
- payment2
1669
- );
1670
- const settleScope2 = {
1671
- request,
1672
- meta,
1673
- pluginCtx,
1674
- wallet,
1675
- account,
1676
- parsedBody: body.data,
1677
- payment: payment2,
1678
- response: response2,
1679
- rawResult: rawResult2,
1680
- handlerError: handlerError2
1681
- };
1682
- if (response2.status < 400) {
1683
- const validationFailure = await runBeforeSettle(settleScope2);
1684
- if (validationFailure) return validationFailure;
1685
- let mppResult2;
1686
- try {
1687
- mppResult2 = await deps.mppx.charge({ amount: price })(request);
1688
- } catch (err) {
1689
- await runSettlementError(settleScope2, err, "settle");
1690
- const message = err instanceof Error ? err.message : String(err);
1691
- console.error(
1692
- `[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`
1693
- );
1694
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1695
- level: "critical",
1696
- message: `MPP broadcast failed after handler: ${message}`,
1697
- route: routeEntry.key
1698
- });
1699
- return fail(
1700
- 500,
1701
- `MPP payment processing failed: ${message}`,
1702
- meta,
1703
- pluginCtx,
1704
- body.data
1705
- );
1706
- }
1707
- if (mppResult2.status === 402) {
1708
- let rejectReason = "";
1709
- try {
1710
- const problemBody = await mppResult2.challenge.clone().text();
1711
- if (problemBody) {
1712
- const problem = JSON.parse(problemBody);
1713
- rejectReason = problem.detail || problem.title || "";
1714
- }
1715
- } catch {
1716
- }
1717
- const detail = rejectReason || "transaction reverted on-chain after handler execution";
1718
- const settlementError = Object.assign(new Error(detail), {
1719
- status: 402,
1720
- detail,
1721
- mppResult: mppResult2,
1722
- challenge: mppResult2.challenge
1723
- });
1724
- await runSettlementError(settleScope2, settlementError, "settle");
1725
- console.error(
1726
- `[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`
1727
- );
1728
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1729
- level: "critical",
1730
- message: `MPP payment failed after handler: ${detail}`,
1731
- route: routeEntry.key
1732
- });
1733
- return fail(500, `MPP payment failed: ${detail}`, meta, pluginCtx, body.data);
1734
- }
1735
- const receiptResponse = mppResult2.withReceipt(response2);
1736
- receiptResponse.headers.set("Cache-Control", "private");
1737
- let txHash2 = "";
1738
- const receiptHeader2 = receiptResponse.headers.get("Payment-Receipt");
1739
- if (receiptHeader2) {
1740
- try {
1741
- txHash2 = import_mppx2.Receipt.deserialize(receiptHeader2).reference;
1742
- } catch {
1743
- }
1744
- }
1745
- if (routeEntry.siwxEnabled) {
1746
- try {
1747
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1748
- } catch (error) {
1749
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1750
- level: "warn",
1751
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1752
- route: routeEntry.key
1753
- });
1754
- }
1755
- }
1756
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1757
- protocol: "mpp",
1758
- payer: wallet,
1759
- transaction: txHash2,
1760
- network: "tempo:4217"
1761
- });
1762
- const settledPayment = {
1763
- ...payment2,
1764
- status: "settled",
1765
- ...txHash2 ? { transaction: txHash2 } : {},
1766
- ...receiptHeader2 ? { receipt: receiptHeader2 } : {}
1767
- };
1768
- await runAfterSettle({
1769
- ...settleScope2,
1770
- payment: settledPayment,
1771
- response: receiptResponse
1772
- });
1773
- finalize(receiptResponse, rawResult2, meta, pluginCtx, body.data);
1774
- return receiptResponse;
1775
- }
1776
- finalize(response2, rawResult2, meta, pluginCtx, body.data);
1777
- return response2;
1778
- }
1779
- let mppResult;
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) => {
1780
1576
  try {
1781
- mppResult = await deps.mppx.charge({ amount: price })(request);
1577
+ return { success: true, group, accepted: await enrichGroup(group, resource) };
1782
1578
  } catch (err) {
1783
- const message = err instanceof Error ? err.message : String(err);
1784
- console.error(`[router] ${routeEntry.key}: MPP charge failed: ${message}`);
1785
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1786
- level: "critical",
1787
- message: `MPP charge failed: ${message}`,
1788
- route: routeEntry.key
1789
- });
1790
- 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 };
1791
1585
  }
1792
- if (mppResult.status === 402) {
1793
- let rejectReason = "";
1794
- try {
1795
- const problemBody = await mppResult.challenge.clone().text();
1796
- if (problemBody) {
1797
- const problem = JSON.parse(problemBody);
1798
- rejectReason = problem.detail || problem.title || "";
1799
- }
1800
- } catch {
1801
- }
1802
- const detail = rejectReason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
1803
- console.warn(`[router] ${routeEntry.key}: MPP credential rejected \u2014 ${detail}`);
1804
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1805
- level: "warn",
1806
- message: `MPP payment rejected: ${detail}`,
1807
- route: routeEntry.key
1808
- });
1809
- 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
1810
1756
  }
1811
- let txHash = "";
1812
- const receiptHeader = mppResult.withReceipt(new Response()).headers.get(
1813
- "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
1814
1767
  );
1815
- if (receiptHeader) {
1816
- try {
1817
- txHash = import_mppx2.Receipt.deserialize(receiptHeader).reference;
1818
- } catch {
1819
- }
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;
1820
1773
  }
1821
- pluginCtx.setVerifiedWallet(wallet);
1822
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1823
- protocol: "mpp",
1824
- payer: wallet,
1825
- amount: price,
1826
- network: "tempo:4217"
1827
- });
1828
- const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
1829
- const payment = {
1830
- protocol: "mpp",
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,
1831
1779
  status: "settled",
1832
- payer: wallet,
1833
- amount: price,
1834
- network: "tempo:4217",
1835
- ...mppRecipient ? { recipient: mppRecipient } : {},
1836
- ...txHash ? { transaction: txHash } : {},
1837
- ...receiptHeader ? { receipt: receiptHeader } : {}
1838
- };
1839
- const { response, rawResult, handlerError } = await invoke(
1840
- request,
1841
- meta,
1842
- pluginCtx,
1843
- wallet,
1844
- account,
1845
- body.data,
1846
- payment
1847
- );
1848
- const settleScope = {
1849
- request,
1850
- meta,
1851
- pluginCtx,
1852
- wallet,
1853
- account,
1854
- parsedBody: body.data,
1855
- payment,
1856
- response,
1857
- rawResult,
1858
- handlerError
1780
+ ...transaction ? { transaction } : {}
1859
1781
  };
1860
- if (response.status < 400) {
1861
- if (routeEntry.siwxEnabled) {
1862
- try {
1863
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1864
- } catch (error) {
1865
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1866
- level: "warn",
1867
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1868
- route: routeEntry.key
1869
- });
1870
- }
1871
- }
1872
- const receiptResponse = mppResult.withReceipt(response);
1873
- receiptResponse.headers.set("Cache-Control", "private");
1874
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1875
- protocol: "mpp",
1876
- payer: wallet,
1877
- transaction: txHash,
1878
- network: "tempo:4217"
1879
- });
1880
- await runAfterSettle({ ...settleScope, response: receiptResponse });
1881
- finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
1882
- return receiptResponse;
1883
- }
1884
- await runSettledHandlerError(settleScope);
1885
- finalize(response, rawResult, meta, pluginCtx, body.data);
1886
- return response;
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
1792
+ });
1793
+ return { ok: false, error: err, failMessage: "Settlement failed" };
1887
1794
  }
1888
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1889
- };
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;
1890
1821
  }
1891
- async function parseBody(request, routeEntry) {
1892
- if (!routeEntry.bodySchema) return { ok: true, data: void 0 };
1893
- const raw = await bufferBody(request);
1894
- const result = validateBody(raw, routeEntry.bodySchema);
1895
- if (result.success) return { ok: true, data: result.data };
1896
- return {
1897
- ok: false,
1898
- response: import_server2.NextResponse.json(
1899
- { success: false, error: result.error, issues: result.issues },
1900
- { status: 400 }
1901
- )
1902
- };
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;
1903
1840
  }
1904
- function buildMeta(request, routeEntry) {
1905
- return {
1906
- requestId: crypto.randomUUID(),
1907
- method: request.method,
1908
- route: routeEntry.key,
1909
- origin: request.headers.get("origin") ?? new URL(request.url).origin,
1910
- referer: request.headers.get("referer"),
1911
- walletAddress: request.headers.get("X-Wallet-Address"),
1912
- clientId: request.headers.get("X-Client-ID"),
1913
- sessionId: request.headers.get("X-Session-ID"),
1914
- contentType: request.headers.get("content-type"),
1915
- headers: Object.fromEntries(request.headers.entries()),
1916
- startTime: Date.now()
1917
- };
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;
1918
1853
  }
1919
- function parseQuery(request, routeEntry) {
1920
- if (!routeEntry.querySchema) return void 0;
1921
- const params = Object.fromEntries(request.nextUrl.searchParams.entries());
1922
- const result = routeEntry.querySchema.safeParse(params);
1923
- return result.success ? result.data : params;
1854
+ function getAllowedStrategies(allowed) {
1855
+ return allowed.map((name) => STRATEGIES[name]);
1924
1856
  }
1925
- async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta) {
1857
+
1858
+ // src/pipeline/challenge.ts
1859
+ var import_server4 = require("next/server");
1860
+ async function build402(ctx, pricing, body) {
1861
+ let challengePrice;
1926
1862
  try {
1927
- let price = await resolvePrice(routeEntry.pricing, bodyData);
1928
- if (routeEntry.maxPrice) {
1929
- const calculated = parseFloat(price);
1930
- const max = parseFloat(routeEntry.maxPrice);
1931
- if (!Number.isFinite(calculated) || calculated > max) {
1932
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1933
- level: "warn",
1934
- message: `Price ${price} exceeds maxPrice ${routeEntry.maxPrice}, capping`,
1935
- route: routeEntry.key,
1936
- meta: { calculated: price, maxPrice: routeEntry.maxPrice, body: bodyData }
1937
- });
1938
- price = routeEntry.maxPrice;
1939
- }
1940
- }
1941
- return { price };
1863
+ challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
1942
1864
  } catch (err) {
1943
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1944
- level: "error",
1945
- message: `Pricing function failed: ${err instanceof Error ? err.message : String(err)}`,
1946
- route: routeEntry.key,
1947
- meta: { error: err instanceof Error ? err.stack : String(err), body: bodyData }
1948
- });
1949
- if (routeEntry.maxPrice) {
1950
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1951
- level: "warn",
1952
- message: `Using maxPrice ${routeEntry.maxPrice} as fallback after pricing error`,
1953
- route: routeEntry.key
1954
- });
1955
- return { price: routeEntry.maxPrice };
1956
- } else {
1957
- const message = errorMessage(err, "Price calculation failed");
1958
- const errorResponse = import_server2.NextResponse.json(
1959
- { success: false, error: message },
1960
- { status: errorStatus(err, 500) }
1961
- );
1962
- firePluginResponse(deps, pluginCtx, meta, errorResponse);
1963
- return { error: errorResponse };
1964
- }
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;
1965
1872
  }
1966
- }
1967
- async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1968
- const response = new import_server2.NextResponse(null, {
1873
+ const extensions = await buildChallengeExtensions(ctx);
1874
+ const response = new import_server4.NextResponse(null, {
1969
1875
  status: 402,
1970
1876
  headers: {
1971
1877
  "Content-Type": "application/json",
1972
1878
  "Cache-Control": "no-store"
1973
1879
  }
1974
1880
  });
1975
- let challengePrice;
1976
- if (bodyData !== void 0 && typeof routeEntry.pricing !== "string" && routeEntry.pricing != null) {
1977
- const result = await resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta);
1978
- if ("error" in result) return result.error;
1979
- challengePrice = result.price;
1980
- } else if (routeEntry.maxPrice) {
1981
- challengePrice = routeEntry.maxPrice;
1982
- } else if (routeEntry.pricing) {
1881
+ for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
1983
1882
  try {
1984
- challengePrice = resolveMaxPrice(routeEntry.pricing);
1985
- } catch {
1986
- challengePrice = "0";
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);
1894
+ }
1895
+ }
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
+ }
1987
1911
  }
1988
- } else {
1989
- challengePrice = "0";
1990
1912
  }
1913
+ firePluginResponse(ctx, response);
1914
+ return response;
1915
+ }
1916
+ async function buildChallengeExtensions(ctx) {
1917
+ const { routeEntry } = ctx;
1991
1918
  let extensions;
1992
1919
  try {
1993
1920
  const { z } = await import("zod");
@@ -2013,7 +1940,7 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
2013
1940
  extensions = declareDiscoveryExtension(config);
2014
1941
  }
2015
1942
  } catch (err) {
2016
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1943
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
2017
1944
  level: "warn",
2018
1945
  message: `Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`,
2019
1946
  route: routeEntry.key
@@ -2031,101 +1958,390 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
2031
1958
  } catch {
2032
1959
  }
2033
1960
  }
2034
- if (routeEntry.protocols.includes("x402") && deps.x402Server) {
2035
- try {
2036
- const accepts = await resolveX402Accepts(
2037
- request,
2038
- routeEntry,
2039
- deps.x402Accepts,
2040
- deps.payeeAddress,
2041
- bodyData
2042
- );
2043
- const { encoded } = await buildX402Challenge({
2044
- server: deps.x402Server,
2045
- routeEntry,
2046
- request,
2047
- price: challengePrice,
2048
- accepts,
2049
- facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
2050
- extensions
2051
- });
2052
- response.headers.set("PAYMENT-REQUIRED", encoded);
2053
- } catch (err) {
2054
- const message = `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`;
2055
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
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
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, {
2056
2095
  level: "critical",
2057
- message,
2096
+ message: `${incomingStrategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`,
2058
2097
  route: routeEntry.key
2059
2098
  });
2060
- const errorResponse = import_server2.NextResponse.json({ success: false, error: message }, { status: 500 });
2061
- firePluginResponse(deps, pluginCtx, meta, errorResponse);
2062
- return errorResponse;
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);
2063
2120
  }
2064
2121
  }
2065
- if (routeEntry.protocols.includes("mpp") && deps.mppx) {
2066
- try {
2067
- const result = await deps.mppx.charge({ amount: challengePrice })(request);
2068
- if (result.status === 402) {
2069
- const wwwAuth = result.challenge.headers.get("WWW-Authenticate");
2070
- if (wwwAuth) response.headers.set("WWW-Authenticate", wwwAuth);
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
+ );
2142
+ }
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);
2147
+ return {
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;
2071
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
+ }
2162
+ };
2163
+ }
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 };
2176
+ }
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;
2187
+ } else {
2188
+ firePluginResponse(ctx, earlyBody.response);
2189
+ return earlyBody.response;
2190
+ }
2191
+ }
2192
+ const siwxHeader = request.headers.get(HEADERS.SIWX);
2193
+ const protocol = detectProtocol(request);
2194
+ if (!siwxHeader && protocol === "mpp" && deps.mppx) {
2195
+ let mppSiwxResult;
2196
+ try {
2197
+ mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
2072
2198
  } catch (err) {
2073
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
2199
+ const message = err instanceof Error ? err.message : String(err);
2200
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
2074
2201
  level: "critical",
2075
- message: `MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
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,
2076
2212
  route: routeEntry.key
2077
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;
2078
2219
  }
2079
2220
  }
2080
- firePluginResponse(deps, pluginCtx, meta, response);
2081
- return response;
2082
- }
2083
- function firePluginResponse(deps, pluginCtx, meta, response, requestBody, responseBody) {
2084
- firePluginHook(deps.plugin, "onResponse", pluginCtx, {
2085
- statusCode: response.status,
2086
- statusText: response.statusText,
2087
- duration: Date.now() - meta.startTime,
2088
- contentType: response.headers.get("content-type"),
2089
- headers: Object.fromEntries(response.headers.entries()),
2090
- requestBody,
2091
- responseBody
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
2092
2239
  });
2093
- if (response.status >= 400 && response.status !== 402) {
2094
- firePluginHook(deps.plugin, "onError", pluginCtx, {
2095
- status: response.status,
2096
- message: response.statusText || `HTTP ${response.status}`,
2097
- settled: false
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;
2260
+ try {
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 } : {}
2279
+ }
2280
+ }
2281
+ };
2282
+ let encoded;
2283
+ try {
2284
+ const { encodePaymentRequiredHeader } = await import("@x402/core/http");
2285
+ encoded = encodePaymentRequiredHeader(paymentRequired);
2286
+ } catch (err) {
2287
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
2288
+ level: "warn",
2289
+ message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
2290
+ route: routeEntry.key
2098
2291
  });
2099
2292
  }
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) {
2299
+ try {
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);
2304
+ }
2305
+ } catch {
2306
+ }
2307
+ }
2308
+ firePluginResponse(ctx, response);
2309
+ return response;
2100
2310
  }
2101
- function computeQuotaLevel(remaining, warn, critical) {
2102
- if (remaining === null) return "healthy";
2103
- if (critical !== void 0 && remaining <= critical) return "critical";
2104
- if (warn !== void 0 && remaining <= warn) return "warn";
2105
- return "healthy";
2311
+ function siwxSignatureType(network) {
2312
+ return network.startsWith("solana:") ? "ed25519" : "eip191";
2106
2313
  }
2107
- function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx) {
2108
- const { providerName, providerConfig } = routeEntry;
2109
- if (!providerName || !providerConfig?.extractQuota) return;
2110
- if (response.status >= 400) return;
2111
- try {
2112
- const quota = providerConfig.extractQuota(handlerResult, response.headers);
2113
- if (!quota) return;
2114
- const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
2115
- const overage = providerConfig.overage ?? "same-rate";
2116
- const event = {
2117
- provider: providerName,
2118
- route: routeEntry.key,
2119
- remaining: quota.remaining,
2120
- limit: quota.limit,
2121
- spend: quota.spend,
2122
- level,
2123
- overage,
2124
- message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
2125
- };
2126
- firePluginHook(deps.plugin, "onProviderQuota", pluginCtx, event);
2127
- } catch {
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
+ }
2128
2322
  }
2323
+ if (chains.length === 0) {
2324
+ chains.push({ chainId: fallbackNetwork, type: siwxSignatureType(fallbackNetwork) });
2325
+ }
2326
+ return chains;
2327
+ }
2328
+
2329
+ // src/pipeline/flows/unprotected.ts
2330
+ async function runUnprotectedFlow(ctx) {
2331
+ return runHandlerOnly(ctx, null, void 0);
2332
+ }
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
+ };
2129
2345
  }
2130
2346
 
2131
2347
  // src/validate-examples.ts
@@ -2600,7 +2816,7 @@ function createRedisEntitlementStore(client, options) {
2600
2816
  }
2601
2817
 
2602
2818
  // src/discovery/well-known.ts
2603
- var import_server3 = require("next/server");
2819
+ var import_server6 = require("next/server");
2604
2820
 
2605
2821
  // src/discovery/utils/guidance.ts
2606
2822
  async function resolveGuidance(discovery) {
@@ -2644,7 +2860,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
2644
2860
  if (instructions) {
2645
2861
  body.instructions = instructions;
2646
2862
  }
2647
- return import_server3.NextResponse.json(body, {
2863
+ return import_server6.NextResponse.json(body, {
2648
2864
  headers: {
2649
2865
  "Access-Control-Allow-Origin": "*",
2650
2866
  "Access-Control-Allow-Methods": "GET",
@@ -2661,14 +2877,14 @@ function toDiscoveryResource(method, url, mode) {
2661
2877
  }
2662
2878
 
2663
2879
  // src/discovery/openapi.ts
2664
- var import_server4 = require("next/server");
2880
+ var import_server7 = require("next/server");
2665
2881
  init_constants();
2666
2882
  function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2667
2883
  const normalizedBase = baseUrl.replace(/\/+$/, "");
2668
2884
  let cached = null;
2669
2885
  let validated = false;
2670
2886
  return async (_request) => {
2671
- if (cached) return import_server4.NextResponse.json(cached);
2887
+ if (cached) return import_server7.NextResponse.json(cached);
2672
2888
  if (!validated && pricesKeys) {
2673
2889
  registry.validate(pricesKeys);
2674
2890
  validated = true;
@@ -2693,14 +2909,14 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2693
2909
  securitySchemes.siwx = {
2694
2910
  type: "apiKey",
2695
2911
  in: "header",
2696
- name: "SIGN-IN-WITH-X"
2912
+ name: HEADERS.SIWX
2697
2913
  };
2698
2914
  }
2699
2915
  if (requiresApiKeyScheme) {
2700
2916
  securitySchemes.apiKey = {
2701
2917
  type: "apiKey",
2702
2918
  in: "header",
2703
- name: "X-API-Key"
2919
+ name: HEADERS.API_KEY
2704
2920
  };
2705
2921
  }
2706
2922
  const discoveryMetadata = {};
@@ -2731,7 +2947,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2731
2947
  paths
2732
2948
  };
2733
2949
  cached = createDocument(openApiDocument);
2734
- return import_server4.NextResponse.json(cached);
2950
+ return import_server7.NextResponse.json(cached);
2735
2951
  };
2736
2952
  }
2737
2953
  function deriveTag(routeKey) {
@@ -2854,11 +3070,11 @@ function buildPricingInfo(entry) {
2854
3070
  }
2855
3071
 
2856
3072
  // src/discovery/llms-txt.ts
2857
- var import_server5 = require("next/server");
3073
+ var import_server8 = require("next/server");
2858
3074
  function createLlmsTxtHandler(discovery) {
2859
3075
  return async (_request) => {
2860
3076
  const guidance = await resolveGuidance(discovery) ?? "";
2861
- return new import_server5.NextResponse(guidance, {
3077
+ return new import_server8.NextResponse(guidance, {
2862
3078
  headers: {
2863
3079
  "Content-Type": "text/plain; charset=utf-8",
2864
3080
  "Access-Control-Allow-Origin": "*"
@@ -2868,14 +3084,14 @@ function createLlmsTxtHandler(discovery) {
2868
3084
  }
2869
3085
 
2870
3086
  // src/index.ts
2871
- init_x402_config();
3087
+ init_accepts();
2872
3088
  init_constants();
2873
3089
 
2874
3090
  // src/config.ts
2875
3091
  init_constants();
2876
3092
  init_evm();
2877
3093
  init_solana();
2878
- init_x402_config();
3094
+ init_accepts();
2879
3095
  var RouterConfigError = class extends Error {
2880
3096
  issues;
2881
3097
  constructor(issues) {