@agentcash/router 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,7 +8,65 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
- // src/protocols/evm.ts
11
+ // src/constants.ts
12
+ var BASE_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_CURRENCY, ZERO_EVM_ADDRESS;
13
+ var init_constants = __esm({
14
+ "src/constants.ts"() {
15
+ "use strict";
16
+ BASE_NETWORK = "eip155:8453";
17
+ SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
18
+ TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50";
19
+ ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
20
+ }
21
+ });
22
+
23
+ // src/protocols/x402/accepts.ts
24
+ async function resolvePayToValue(payTo, request, fallback, body) {
25
+ if (!payTo) return fallback;
26
+ if (typeof payTo === "string") return payTo;
27
+ return payTo(request, body);
28
+ }
29
+ function getConfiguredX402Accepts(config) {
30
+ if (config.x402?.accepts?.length) {
31
+ return [...config.x402.accepts];
32
+ }
33
+ return [
34
+ {
35
+ scheme: "exact",
36
+ network: config.network ?? BASE_NETWORK,
37
+ payTo: config.payeeAddress
38
+ }
39
+ ];
40
+ }
41
+ function getConfiguredX402Networks(config) {
42
+ return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
43
+ }
44
+ async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
45
+ return Promise.all(
46
+ accepts.map(async (accept) => ({
47
+ network: accept.network,
48
+ scheme: accept.scheme ?? "exact",
49
+ payTo: await resolvePayToValue(
50
+ routeEntry.payTo ?? accept.payTo,
51
+ request,
52
+ fallbackPayTo,
53
+ body
54
+ ),
55
+ ...accept.asset ? { asset: accept.asset } : {},
56
+ ...accept.decimals !== void 0 ? { decimals: accept.decimals } : {},
57
+ ...accept.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: accept.maxTimeoutSeconds } : {},
58
+ ...accept.extra ? { extra: accept.extra } : {}
59
+ }))
60
+ );
61
+ }
62
+ var init_accepts = __esm({
63
+ "src/protocols/x402/accepts.ts"() {
64
+ "use strict";
65
+ init_constants();
66
+ }
67
+ });
68
+
69
+ // src/protocols/x402/evm.ts
12
70
  function isEvmNetwork(network) {
13
71
  return network.startsWith("eip155:");
14
72
  }
@@ -26,12 +84,12 @@ function buildEvmExactOptions(accepts, price) {
26
84
  }));
27
85
  }
28
86
  var init_evm = __esm({
29
- "src/protocols/evm.ts"() {
87
+ "src/protocols/x402/evm.ts"() {
30
88
  "use strict";
31
89
  }
32
90
  });
33
91
 
34
- // src/protocols/solana.ts
92
+ // src/protocols/x402/solana.ts
35
93
  function isSolanaNetwork(network) {
36
94
  return network.startsWith("solana:");
37
95
  }
@@ -81,13 +139,13 @@ function isSolanaRequirement(requirement) {
81
139
  return isSolanaNetwork(requirement.network);
82
140
  }
83
141
  var init_solana = __esm({
84
- "src/protocols/solana.ts"() {
142
+ "src/protocols/x402/solana.ts"() {
85
143
  "use strict";
86
- init_x402_facilitators();
144
+ init_facilitators();
87
145
  }
88
146
  });
89
147
 
90
- // src/x402-facilitators.ts
148
+ // src/protocols/x402/facilitators.ts
91
149
  function getResolvedX402Facilitator(config, network, defaultEvmFacilitator) {
92
150
  const family = getNetworkFamily(network);
93
151
  if (!family) return null;
@@ -157,8 +215,8 @@ function sameFacilitatorConfig(a, b) {
157
215
  return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders && a.createAcceptsHeaders === b.createAcceptsHeaders;
158
216
  }
159
217
  var DEFAULT_SOLANA_FACILITATOR_URL;
160
- var init_x402_facilitators = __esm({
161
- "src/x402-facilitators.ts"() {
218
+ var init_facilitators = __esm({
219
+ "src/protocols/x402/facilitators.ts"() {
162
220
  "use strict";
163
221
  init_evm();
164
222
  init_solana();
@@ -166,64 +224,6 @@ var init_x402_facilitators = __esm({
166
224
  }
167
225
  });
168
226
 
169
- // src/constants.ts
170
- var BASE_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_CURRENCY, ZERO_EVM_ADDRESS;
171
- var init_constants = __esm({
172
- "src/constants.ts"() {
173
- "use strict";
174
- BASE_NETWORK = "eip155:8453";
175
- SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
176
- TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50";
177
- ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
178
- }
179
- });
180
-
181
- // src/x402-config.ts
182
- async function resolvePayToValue(payTo, request, fallback, body) {
183
- if (!payTo) return fallback;
184
- if (typeof payTo === "string") return payTo;
185
- return payTo(request, body);
186
- }
187
- function getConfiguredX402Accepts(config) {
188
- if (config.x402?.accepts?.length) {
189
- return [...config.x402.accepts];
190
- }
191
- return [
192
- {
193
- scheme: "exact",
194
- network: config.network ?? BASE_NETWORK,
195
- payTo: config.payeeAddress
196
- }
197
- ];
198
- }
199
- function getConfiguredX402Networks(config) {
200
- return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
201
- }
202
- async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
203
- return Promise.all(
204
- accepts.map(async (accept) => ({
205
- network: accept.network,
206
- scheme: accept.scheme ?? "exact",
207
- payTo: await resolvePayToValue(
208
- routeEntry.payTo ?? accept.payTo,
209
- request,
210
- fallbackPayTo,
211
- body
212
- ),
213
- ...accept.asset ? { asset: accept.asset } : {},
214
- ...accept.decimals !== void 0 ? { decimals: accept.decimals } : {},
215
- ...accept.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: accept.maxTimeoutSeconds } : {},
216
- ...accept.extra ? { extra: accept.extra } : {}
217
- }))
218
- );
219
- }
220
- var init_x402_config = __esm({
221
- "src/x402-config.ts"() {
222
- "use strict";
223
- init_constants();
224
- }
225
- });
226
-
227
227
  // src/server.ts
228
228
  var server_exports = {};
229
229
  __export(server_exports, {
@@ -308,8 +308,8 @@ var init_server = __esm({
308
308
  "use strict";
309
309
  init_evm();
310
310
  init_solana();
311
- init_x402_facilitators();
312
- init_x402_config();
311
+ init_facilitators();
312
+ init_accepts();
313
313
  }
314
314
  });
315
315
 
@@ -415,13 +415,32 @@ var RouteRegistry = class {
415
415
  }
416
416
  };
417
417
 
418
- // src/orchestrate.ts
419
- import { NextResponse as NextResponse2 } from "next/server";
420
-
421
- // src/auth/normalize-wallet.ts
422
- function normalizeWalletAddress(address) {
423
- return address.startsWith("0x") ? address.toLowerCase() : address;
424
- }
418
+ // src/headers.ts
419
+ var HEADERS = {
420
+ // ---- Standard HTTP ----
421
+ AUTHORIZATION: "Authorization",
422
+ WWW_AUTHENTICATE: "WWW-Authenticate",
423
+ // ---- Auth ----
424
+ API_KEY: "X-API-Key",
425
+ // ---- Request meta (used by plugin/observability) ----
426
+ WALLET_ADDRESS: "X-Wallet-Address",
427
+ CLIENT_ID: "X-Client-ID",
428
+ SESSION_ID: "X-Session-ID",
429
+ // ---- SIWX ----
430
+ SIWX: "SIGN-IN-WITH-X",
431
+ // ---- x402 (payment) ----
432
+ X402_PAYMENT_SIGNATURE: "PAYMENT-SIGNATURE",
433
+ /** Legacy x402 payment header — accepted alongside PAYMENT-SIGNATURE. */
434
+ X402_PAYMENT_LEGACY: "X-PAYMENT",
435
+ X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
436
+ X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
437
+ // ---- MPP (payment) ----
438
+ MPP_PAYMENT_RECEIPT: "Payment-Receipt"
439
+ };
440
+ var AUTH_SCHEME = {
441
+ BEARER: "Bearer ",
442
+ MPP_PAYMENT: "Payment "
443
+ };
425
444
 
426
445
  // src/plugin.ts
427
446
  function createDefaultContext(meta) {
@@ -498,105 +517,31 @@ function consolePlugin() {
498
517
  };
499
518
  }
500
519
 
501
- // src/auth/nonce.ts
502
- var SIWX_CHALLENGE_EXPIRY_MS = 5 * 60 * 1e3;
503
- var MemoryNonceStore = class {
504
- seen = /* @__PURE__ */ new Map();
505
- async check(nonce) {
506
- this.evict();
507
- if (this.seen.has(nonce)) return false;
508
- this.seen.set(nonce, Date.now() + SIWX_CHALLENGE_EXPIRY_MS);
509
- return true;
510
- }
511
- evict() {
512
- const now = Date.now();
513
- for (const [n, exp] of this.seen) {
514
- if (exp < now) this.seen.delete(n);
515
- }
516
- }
517
- };
518
- function detectRedisClientType(client) {
519
- if (!client || typeof client !== "object") {
520
- throw new Error(
521
- "createRedisNonceStore requires a Redis client. Supported: @upstash/redis, ioredis. Pass your Redis client instance as the first argument."
522
- );
523
- }
524
- if ("options" in client && "status" in client) {
525
- return "ioredis";
526
- }
527
- const constructor = client.constructor?.name;
528
- if (constructor === "Redis" && "url" in client) {
529
- return "upstash";
530
- }
531
- if (typeof client.set === "function") {
532
- return "upstash";
533
- }
534
- throw new Error(
535
- "Unrecognized Redis client. Supported: @upstash/redis, ioredis. If using a different client, implement NonceStore interface directly."
536
- );
520
+ // src/pipeline/context/preflight.ts
521
+ function preflight(routeEntry, handler, deps, request) {
522
+ const meta = buildMeta(request, routeEntry);
523
+ const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
524
+ return { routeEntry, handler, deps, request, meta, pluginCtx };
537
525
  }
538
- function createRedisNonceStore(client, opts) {
539
- const prefix = opts?.prefix ?? "siwx:nonce:";
540
- const ttlSeconds = Math.ceil((opts?.ttlMs ?? SIWX_CHALLENGE_EXPIRY_MS) / 1e3);
541
- const clientType = detectRedisClientType(client);
526
+ function buildMeta(request, routeEntry) {
542
527
  return {
543
- async check(nonce) {
544
- const key = `${prefix}${nonce}`;
545
- if (clientType === "upstash") {
546
- const redis = client;
547
- const result = await redis.set(key, "1", { ex: ttlSeconds, nx: true });
548
- return result !== null;
549
- }
550
- if (clientType === "ioredis") {
551
- const redis = client;
552
- const result = await redis.set(key, "1", "EX", ttlSeconds, "NX");
553
- return result === "OK";
554
- }
555
- throw new Error("Unknown Redis client type");
556
- }
528
+ requestId: crypto.randomUUID(),
529
+ method: request.method,
530
+ route: routeEntry.key,
531
+ origin: request.headers.get("origin") ?? new URL(request.url).origin,
532
+ referer: request.headers.get("referer"),
533
+ walletAddress: request.headers.get(HEADERS.WALLET_ADDRESS),
534
+ clientId: request.headers.get(HEADERS.CLIENT_ID),
535
+ sessionId: request.headers.get(HEADERS.SESSION_ID),
536
+ contentType: request.headers.get("content-type"),
537
+ headers: Object.fromEntries(request.headers.entries()),
538
+ startTime: Date.now()
557
539
  };
558
540
  }
559
541
 
560
- // src/protocols/detect.ts
561
- function detectProtocol(request) {
562
- if (request.headers.get("PAYMENT-SIGNATURE") || request.headers.get("X-PAYMENT")) {
563
- return "x402";
564
- }
565
- const auth = request.headers.get("Authorization");
566
- if (auth && auth.startsWith("Payment ")) {
567
- return "mpp";
568
- }
569
- if (request.headers.get("SIGN-IN-WITH-X")) {
570
- return "siwx";
571
- }
572
- return null;
573
- }
574
-
575
- // src/handler.ts
542
+ // src/pipeline/context/parse-body.ts
576
543
  import { NextResponse } from "next/server";
577
544
 
578
- // src/types.ts
579
- var HttpError = class extends Error {
580
- constructor(message, status) {
581
- super(message);
582
- this.status = status;
583
- this.name = "HttpError";
584
- }
585
- };
586
-
587
- // src/handler.ts
588
- async function safeCallHandler(handler, ctx) {
589
- try {
590
- const result = await handler(ctx);
591
- if (result instanceof Response) return result;
592
- return NextResponse.json(result);
593
- } catch (error) {
594
- const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
595
- const message = error instanceof Error ? error.message : "Internal error";
596
- return NextResponse.json({ success: false, error: message }, { status });
597
- }
598
- }
599
-
600
545
  // src/body.ts
601
546
  async function bufferBody(request) {
602
547
  const text = await request.text();
@@ -621,296 +566,329 @@ function validateBody(parsed, schema) {
621
566
  };
622
567
  }
623
568
 
624
- // src/pricing.ts
625
- async function resolvePrice(pricing, body) {
626
- if (typeof pricing === "string") {
627
- return pricing;
628
- }
629
- if (typeof pricing === "function") {
630
- return pricing(body);
631
- }
632
- const { field, tiers, default: defaultTier } = pricing;
633
- const tierKey = body != null ? String(body[field] ?? "") : "";
634
- if (tierKey && tiers[tierKey]) {
635
- return tiers[tierKey].price;
636
- }
637
- if (defaultTier && tiers[defaultTier]) {
638
- return tiers[defaultTier].price;
639
- }
640
- if (!tierKey) {
641
- throw Object.assign(new Error(`Missing required field '${field}' for tier pricing`), {
642
- status: 400
643
- });
644
- }
645
- throw Object.assign(
646
- new Error(
647
- `Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
648
- ),
649
- { status: 400 }
650
- );
569
+ // src/pipeline/context/parse-body.ts
570
+ async function parseBody(request, routeEntry) {
571
+ if (!routeEntry.bodySchema) return { ok: true, data: void 0 };
572
+ const raw = await bufferBody(request);
573
+ const result = validateBody(raw, routeEntry.bodySchema);
574
+ if (result.success) return { ok: true, data: result.data };
575
+ return {
576
+ ok: false,
577
+ response: NextResponse.json(
578
+ { success: false, error: result.error, issues: result.issues },
579
+ { status: 400 }
580
+ )
581
+ };
651
582
  }
652
- function resolveMaxPrice(pricing) {
653
- if (typeof pricing === "string") return pricing;
654
- if (typeof pricing === "function") {
655
- throw new Error("Dynamic pricing requires maxPrice \u2014 this should be caught at registration");
656
- }
657
- const { tiers } = pricing;
658
- let max = "0";
659
- for (const tier of Object.values(tiers)) {
660
- if (parseFloat(tier.price) > parseFloat(max)) {
661
- max = tier.price;
662
- }
663
- }
664
- return max;
583
+
584
+ // src/pipeline/context/parse-query.ts
585
+ function parseQuery(request, routeEntry) {
586
+ if (!routeEntry.querySchema) return void 0;
587
+ const params = Object.fromEntries(request.nextUrl.searchParams.entries());
588
+ const result = routeEntry.querySchema.safeParse(params);
589
+ return result.success ? result.data : params;
665
590
  }
666
591
 
667
- // src/orchestrate.ts
668
- import { Credential as Credential2, Receipt } from "mppx";
669
- import { call as viemCall } from "viem/actions";
670
- import { Transaction as TempoTransaction } from "viem/tempo";
671
- import { isAddress as isAddress2, getAddress as getAddress2 } from "viem";
592
+ // src/pipeline/context/errors.ts
593
+ function errorStatus(error, fallback) {
594
+ const status = error?.status;
595
+ return typeof status === "number" ? status : fallback;
596
+ }
597
+ function errorMessage(error, fallback) {
598
+ return error instanceof Error ? error.message : fallback;
599
+ }
600
+ function handlerFailureError(response) {
601
+ const message = response.statusText || `Handler returned HTTP ${response.status}`;
602
+ return Object.assign(new Error(message), { status: response.status });
603
+ }
672
604
 
673
- // src/protocols/x402.ts
674
- init_x402_facilitators();
675
- init_evm();
676
- init_solana();
677
- async function buildX402Challenge(opts) {
678
- const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
679
- const { encodePaymentRequiredHeader } = await import("@x402/core/http");
680
- const resource = buildChallengeResource(request, routeEntry);
681
- const requirements = await buildChallengeRequirements(
682
- server,
683
- request,
684
- price,
685
- accepts,
686
- resource,
687
- facilitatorsByNetwork
688
- );
689
- const paymentRequired = await server.createPaymentRequiredResponse(
690
- requirements,
691
- resource,
692
- void 0,
693
- extensions
694
- );
695
- const encoded = encodePaymentRequiredHeader(paymentRequired);
696
- return { encoded, requirements };
697
- }
698
- async function verifyX402Payment(opts) {
699
- const { server, request, price, accepts } = opts;
700
- const payload = await readPaymentPayload(request);
701
- if (!payload) return null;
702
- const requirements = await buildExpectedRequirements(server, request, price, accepts);
703
- const matching = findVerifiableRequirements(server, requirements, payload);
704
- if (!matching) {
705
- return invalidPaymentVerification();
605
+ // src/pipeline/context/fail.ts
606
+ import { NextResponse as NextResponse2 } from "next/server";
607
+
608
+ // src/pipeline/context/fire-plugin-response.ts
609
+ function firePluginResponse(ctx, response, requestBody, responseBody) {
610
+ firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
611
+ statusCode: response.status,
612
+ statusText: response.statusText,
613
+ duration: Date.now() - ctx.meta.startTime,
614
+ contentType: response.headers.get("content-type"),
615
+ headers: Object.fromEntries(response.headers.entries()),
616
+ requestBody,
617
+ responseBody
618
+ });
619
+ if (response.status >= 400 && response.status !== 402) {
620
+ firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, {
621
+ status: response.status,
622
+ message: response.statusText || `HTTP ${response.status}`,
623
+ settled: false
624
+ });
706
625
  }
707
- let verify;
626
+ }
627
+
628
+ // src/pipeline/context/fail.ts
629
+ function fail(ctx, status, message, requestBody) {
630
+ const response = NextResponse2.json({ success: false, error: message }, { status });
631
+ firePluginResponse(ctx, response, requestBody);
632
+ return response;
633
+ }
634
+
635
+ // src/pipeline/context/run-validate.ts
636
+ async function runValidate(ctx, body) {
637
+ if (!ctx.routeEntry.validateFn) return null;
708
638
  try {
709
- verify = await server.verifyPayment(payload, matching);
639
+ await ctx.routeEntry.validateFn(body);
640
+ return null;
710
641
  } catch (err) {
711
- const sc = err.statusCode;
712
- if (sc && sc >= 400 && sc < 500) return invalidPaymentVerification();
713
- throw err;
642
+ return fail(ctx, errorStatus(err, 400), errorMessage(err, "Validation failed"), body);
714
643
  }
715
- if (!verify.isValid) return invalidPaymentVerification();
716
- return {
717
- valid: true,
718
- payer: verify.payer,
719
- payload,
720
- requirements: matching
721
- };
722
644
  }
723
- function findVerifiableRequirements(server, requirements, payload) {
724
- const strictMatch = server.findMatchingRequirements(requirements, payload);
725
- if (strictMatch) {
726
- return payload.x402Version === 2 ? payload.accepted : strictMatch;
645
+
646
+ // src/handler.ts
647
+ import { NextResponse as NextResponse3 } from "next/server";
648
+
649
+ // src/types.ts
650
+ var HttpError = class extends Error {
651
+ constructor(message, status) {
652
+ super(message);
653
+ this.status = status;
654
+ this.name = "HttpError";
727
655
  }
728
- if (payload.x402Version !== 2) {
729
- return null;
656
+ };
657
+
658
+ // src/handler.ts
659
+ async function safeCallHandler(handler, ctx, options = {}) {
660
+ try {
661
+ const result = await handler(ctx);
662
+ if (result instanceof Response) return result;
663
+ return NextResponse3.json(result);
664
+ } catch (error) {
665
+ options.onError?.(error);
666
+ const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
667
+ const message = error instanceof Error ? error.message : "Internal error";
668
+ return NextResponse3.json({ success: false, error: message }, { status });
730
669
  }
731
- const stableMatch = requirements.find(
732
- (requirement) => matchesStableFields(requirement, payload.accepted)
733
- );
734
- return stableMatch ? payload.accepted : null;
735
- }
736
- function matchesStableFields(requirement, accepted) {
737
- 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;
738
- }
739
- async function buildExpectedRequirements(server, request, price, accepts) {
740
- const exactRequirements = await buildExactRequirements(server, request, price, accepts);
741
- const customRequirements = buildCustomRequirements(price, accepts);
742
- return [...exactRequirements, ...customRequirements];
743
670
  }
744
- async function buildExactRequirements(server, request, price, accepts) {
745
- const exactGroups = [
746
- buildEvmExactOptions(accepts, price),
747
- buildSolanaExactOptions(accepts, price)
748
- ].filter((options) => options.length > 0);
749
- if (exactGroups.length === 0) return [];
750
- const requirements = [];
751
- const failures = [];
752
- for (const options of exactGroups) {
753
- try {
754
- requirements.push(
755
- ...await server.buildPaymentRequirementsFromOptions(options, { request })
756
- );
757
- } catch (error) {
758
- const err = error instanceof Error ? error : new Error(String(error));
759
- failures.push(err);
760
- if (exactGroups.length === 1) {
761
- throw err;
671
+
672
+ // src/pipeline/context/invoke.ts
673
+ async function invoke(ctx, wallet, account, body, payment) {
674
+ const handlerCtx = {
675
+ body,
676
+ query: parseQuery(ctx.request, ctx.routeEntry),
677
+ request: ctx.request,
678
+ requestId: ctx.meta.requestId,
679
+ route: ctx.routeEntry.key,
680
+ wallet,
681
+ payment,
682
+ account,
683
+ alert(level, message, alertMeta) {
684
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
685
+ level,
686
+ message,
687
+ route: ctx.routeEntry.key,
688
+ meta: alertMeta
689
+ });
690
+ },
691
+ setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
692
+ };
693
+ let rawResult;
694
+ let handlerError;
695
+ const response = await safeCallHandler(
696
+ async (c) => {
697
+ rawResult = await ctx.handler(c);
698
+ return rawResult;
699
+ },
700
+ handlerCtx,
701
+ {
702
+ onError(error) {
703
+ handlerError = error;
762
704
  }
763
- console.warn(
764
- `[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
765
- );
766
705
  }
706
+ );
707
+ return { response, rawResult, handlerError };
708
+ }
709
+
710
+ // src/pipeline/context/fire-provider-quota.ts
711
+ function fireProviderQuota(ctx, response, handlerResult) {
712
+ const { providerName, providerConfig } = ctx.routeEntry;
713
+ if (!providerName || !providerConfig?.extractQuota) return;
714
+ if (response.status >= 400) return;
715
+ try {
716
+ const quota = providerConfig.extractQuota(handlerResult, response.headers);
717
+ if (!quota) return;
718
+ const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
719
+ const overage = providerConfig.overage ?? "same-rate";
720
+ const event = {
721
+ provider: providerName,
722
+ route: ctx.routeEntry.key,
723
+ remaining: quota.remaining,
724
+ limit: quota.limit,
725
+ spend: quota.spend,
726
+ level,
727
+ overage,
728
+ message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
729
+ };
730
+ firePluginHook(ctx.deps.plugin, "onProviderQuota", ctx.pluginCtx, event);
731
+ } catch {
767
732
  }
768
- if (requirements.length > 0) {
769
- return requirements;
770
- }
771
- throw failures[0] ?? new Error("Failed to build x402 exact requirements");
772
733
  }
773
- function buildCustomRequirements(price, accepts) {
774
- return accepts.filter((accept) => accept.scheme !== "exact").map((accept) => buildCustomRequirement(price, accept));
734
+ function computeQuotaLevel(remaining, warn, critical) {
735
+ if (remaining === null) return "healthy";
736
+ if (critical !== void 0 && remaining <= critical) return "critical";
737
+ if (warn !== void 0 && remaining <= warn) return "warn";
738
+ return "healthy";
775
739
  }
776
- async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork) {
777
- const requirements = await buildExpectedRequirements(server, request, price, accepts);
778
- if (!needsFacilitatorEnrichment(accepts)) return requirements;
779
- return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork);
740
+
741
+ // src/pipeline/context/finalize.ts
742
+ function finalize(ctx, response, rawResult, requestBody) {
743
+ fireProviderQuota(ctx, response, rawResult);
744
+ firePluginResponse(ctx, response, requestBody, rawResult);
745
+ return response;
780
746
  }
781
- function needsFacilitatorEnrichment(accepts) {
782
- return accepts.some((accept) => accept.scheme !== "exact") || hasSolanaAccepts(accepts);
747
+
748
+ // src/pipeline/context/run-handler-only.ts
749
+ async function runHandlerOnly(ctx, wallet, account) {
750
+ const body = await parseBody(ctx.request, ctx.routeEntry);
751
+ if (!body.ok) {
752
+ firePluginResponse(ctx, body.response);
753
+ return body.response;
754
+ }
755
+ const validateErr = await runValidate(ctx, body.data);
756
+ if (validateErr) return validateErr;
757
+ const result = await invoke(ctx, wallet, account, body.data, null);
758
+ return finalize(ctx, result.response, result.rawResult, body.data);
783
759
  }
784
- async function enrichGroup(group, resource) {
785
- const accepted = await enrichRequirementsWithFacilitatorAccepts(
786
- group.facilitator,
787
- resource,
788
- group.items.map(({ requirement }) => requirement)
789
- );
790
- if (accepted.length !== group.items.length) {
791
- throw new Error(
792
- `Facilitator /accepts returned ${accepted.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
760
+
761
+ // src/pipeline/context/settlement-context.ts
762
+ function settlementContext(ctx, scope) {
763
+ return {
764
+ route: ctx.routeEntry.key,
765
+ request: ctx.request,
766
+ body: scope.body,
767
+ wallet: scope.wallet,
768
+ account: scope.account,
769
+ payment: scope.payment,
770
+ response: scope.response,
771
+ result: scope.rawResult
772
+ };
773
+ }
774
+
775
+ // src/pipeline/context/run-before-settle.ts
776
+ async function runBeforeSettle(ctx, scope) {
777
+ const hook = ctx.routeEntry.settlement?.beforeSettle;
778
+ if (!hook) return null;
779
+ try {
780
+ await hook(settlementContext(ctx, scope));
781
+ return null;
782
+ } catch (error) {
783
+ return fail(
784
+ ctx,
785
+ errorStatus(error, 500),
786
+ errorMessage(error, "Pre-settlement validation failed"),
787
+ scope.body
793
788
  );
794
789
  }
795
- return accepted;
796
790
  }
797
- async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork) {
798
- const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
799
- if (groups.length === 0) return requirements;
800
- const results = await Promise.all(
801
- groups.map(async (group) => {
802
- try {
803
- return { success: true, group, accepted: await enrichGroup(group, resource) };
804
- } catch (err) {
805
- const label = group.facilitator.url ?? group.facilitator.network;
806
- const reason = err instanceof Error ? err.message : String(err);
807
- console.warn(
808
- `[router] ${label} /accepts failed, dropping ${group.items.length} requirement(s): ${reason}`
809
- );
810
- return { success: false, group };
811
- }
812
- })
813
- );
814
- const enriched = [...requirements];
815
- results.filter((r) => r.success).forEach(({ group, accepted }) => {
816
- accepted.forEach((req, offset) => {
817
- const index = group.items[offset]?.index;
818
- if (index !== void 0) enriched[index] = req;
791
+
792
+ // src/pipeline/context/run-settlement-error.ts
793
+ async function runSettlementError(ctx, scope, error, phase) {
794
+ const hook = ctx.routeEntry.settlement?.onSettlementError;
795
+ if (!hook) return;
796
+ try {
797
+ await hook({ ...settlementContext(ctx, scope), error, phase });
798
+ } catch (hookError) {
799
+ const message = errorMessage(hookError, "Settlement error hook failed");
800
+ console.error(`[router] ${ctx.routeEntry.key}: onSettlementError failed: ${message}`);
801
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
802
+ level: "error",
803
+ message: `Settlement error hook failed: ${message}`,
804
+ route: ctx.routeEntry.key
819
805
  });
820
- });
821
- const failedIndices = new Set(
822
- results.filter((r) => !r.success).flatMap(({ group }) => group.items.map(({ index }) => index))
823
- );
824
- const remaining = enriched.filter((_, i) => !failedIndices.has(i));
825
- if (remaining.length === 0) {
826
- throw new Error(
827
- "All facilitator enrichments failed; no payment requirements remain for challenge"
828
- );
829
806
  }
830
- return remaining;
831
807
  }
832
- function collectEnrichmentGroups(requirements, facilitatorsByNetwork) {
833
- const groups = [];
834
- requirements.forEach((requirement, index) => {
835
- if (!requiresFacilitatorEnrichment(requirement)) return;
836
- const facilitator = getRequiredFacilitator(requirement, facilitatorsByNetwork);
837
- const existing = groups.find(
838
- (group) => sameResolvedX402Facilitator(group.facilitator, facilitator)
839
- );
840
- if (existing) {
841
- existing.items.push({ index, requirement });
842
- return;
843
- }
844
- groups.push({
845
- facilitator,
846
- items: [{ index, requirement }]
808
+
809
+ // src/pipeline/context/run-after-settle.ts
810
+ async function runAfterSettle(ctx, scope) {
811
+ const hook = ctx.routeEntry.settlement?.afterSettle;
812
+ if (!hook) return;
813
+ try {
814
+ await hook(settlementContext(ctx, scope));
815
+ } catch (error) {
816
+ const message = errorMessage(error, "Post-settlement hook failed");
817
+ console.error(`[router] ${ctx.routeEntry.key}: afterSettle failed: ${message}`);
818
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
819
+ level: "error",
820
+ message: `Post-settlement hook failed: ${message}`,
821
+ route: ctx.routeEntry.key
847
822
  });
848
- });
849
- return groups;
823
+ await runSettlementError(ctx, scope, error, "afterSettle");
824
+ }
850
825
  }
851
- function getRequiredFacilitator(requirement, facilitatorsByNetwork) {
852
- const facilitator = getFacilitatorForRequirement(facilitatorsByNetwork, requirement);
853
- if (!facilitator) {
854
- throw new Error(
855
- `Missing x402 facilitator for ${requirement.scheme} requirement on ${requirement.network}`
856
- );
826
+
827
+ // src/pipeline/context/run-settled-handler-error.ts
828
+ async function runSettledHandlerError(ctx, scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
829
+ const hook = ctx.routeEntry.settlement?.onSettledHandlerError;
830
+ if (!hook) return;
831
+ try {
832
+ await hook({ ...settlementContext(ctx, scope), error });
833
+ } catch (hookError) {
834
+ const message = errorMessage(hookError, "Settled handler error hook failed");
835
+ console.error(`[router] ${ctx.routeEntry.key}: onSettledHandlerError failed: ${message}`);
836
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
837
+ level: "error",
838
+ message: `Settled handler error hook failed: ${message}`,
839
+ route: ctx.routeEntry.key
840
+ });
857
841
  }
858
- return facilitator;
859
842
  }
860
- function requiresFacilitatorEnrichment(requirement) {
861
- return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
843
+
844
+ // src/pipeline/context/grant-entitlement.ts
845
+ async function grantEntitlementIfSiwx(ctx, wallet) {
846
+ if (!ctx.routeEntry.siwxEnabled) return;
847
+ try {
848
+ await ctx.deps.entitlementStore.grant(ctx.routeEntry.key, wallet);
849
+ } catch (error) {
850
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
851
+ level: "warn",
852
+ message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
853
+ route: ctx.routeEntry.key
854
+ });
855
+ }
862
856
  }
863
- function buildCustomRequirement(price, accept) {
864
- if (!accept.asset) {
865
- throw new Error(
866
- `Custom x402 accept '${accept.scheme}' on '${accept.network}' is missing asset`
867
- );
868
- }
869
- return {
870
- scheme: accept.scheme,
871
- network: accept.network,
872
- amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
873
- asset: accept.asset,
874
- payTo: accept.payTo,
875
- maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
876
- extra: accept.extra ?? {}
877
- };
878
- }
879
- function buildChallengeResource(request, routeEntry) {
880
- return {
881
- url: request.url,
882
- method: routeEntry.method,
883
- description: routeEntry.description,
884
- mimeType: "application/json"
885
- };
886
- }
887
- async function readPaymentPayload(request) {
888
- const paymentHeader = request.headers.get("PAYMENT-SIGNATURE") ?? request.headers.get("X-PAYMENT");
889
- if (!paymentHeader) return null;
890
- const { decodePaymentSignatureHeader } = await import("@x402/core/http");
891
- return decodePaymentSignatureHeader(paymentHeader);
892
- }
893
- function invalidPaymentVerification() {
894
- return { valid: false, payload: null, requirements: null, payer: null };
895
- }
896
- function decimalToAtomicUnits(amount, decimals) {
897
- const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
898
- if (!match?.groups) {
899
- throw new Error(`Invalid decimal amount '${amount}'`);
900
- }
901
- const whole = match.groups.whole;
902
- const fraction = match.groups.fraction ?? "";
903
- if (fraction.length > decimals) {
904
- throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
905
- }
906
- const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
907
- return normalized === "" ? "0" : normalized;
857
+
858
+ // src/pipeline/context/settle-and-finalize.ts
859
+ async function settleAndFinalize(args) {
860
+ const { ctx, strategy, verifyOutcome, scope, rawResult, body, onSettleError } = args;
861
+ const { request, routeEntry, deps } = ctx;
862
+ const settle = await strategy.settle({
863
+ request,
864
+ response: scope.response,
865
+ payment: verifyOutcome.payment,
866
+ token: verifyOutcome.token,
867
+ routeEntry,
868
+ deps
869
+ });
870
+ if (!settle.ok) {
871
+ if (onSettleError) await onSettleError(settle.error, settle.failMessage);
872
+ return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
873
+ }
874
+ await grantEntitlementIfSiwx(ctx, verifyOutcome.wallet);
875
+ firePluginHook(deps.plugin, "onPaymentSettled", ctx.pluginCtx, {
876
+ protocol: strategy.protocol,
877
+ payer: verifyOutcome.wallet,
878
+ transaction: settle.settledPayment.transaction ?? "",
879
+ network: settle.settledPayment.network
880
+ });
881
+ await runAfterSettle(ctx, {
882
+ ...scope,
883
+ payment: settle.settledPayment,
884
+ response: settle.response
885
+ });
886
+ return finalize(ctx, settle.response, rawResult, body);
908
887
  }
909
- async function settleX402Payment(server, payload, requirements) {
910
- const { encodePaymentResponseHeader } = await import("@x402/core/http");
911
- const result = await server.settlePayment(payload, requirements);
912
- const encoded = encodePaymentResponseHeader(result);
913
- return { encoded, result };
888
+
889
+ // src/auth/normalize-wallet.ts
890
+ function normalizeWalletAddress(address) {
891
+ return /^0x/i.test(address) ? address.toLowerCase() : address;
914
892
  }
915
893
 
916
894
  // src/auth/siwx.ts
@@ -934,7 +912,7 @@ function categorizeValidationError(error) {
934
912
  }
935
913
  async function verifySIWX(request, _routeEntry, nonceStore) {
936
914
  const { parseSIWxHeader, validateSIWxMessage, verifySIWxSignature } = await import("@x402/extensions/sign-in-with-x");
937
- const header = request.headers.get("SIGN-IN-WITH-X");
915
+ const header = request.headers.get(HEADERS.SIWX);
938
916
  if (!header) {
939
917
  return { valid: false, wallet: null, code: "siwx_missing_header" };
940
918
  }
@@ -963,25 +941,52 @@ async function buildSIWXExtension() {
963
941
  return declareSIWxExtension();
964
942
  }
965
943
 
966
- // src/auth/mpp-siwx.ts
967
- import { Credential } from "mppx";
968
- import { isAddress, getAddress } from "viem";
969
- async function verifyMppSiwx(request, mppx) {
970
- const result = await mppx.charge({ amount: "0" })(request);
971
- if (result.status === 402) {
972
- return { valid: false, challenge: result.challenge };
944
+ // src/pipeline/context/try-siwx-fast-path.ts
945
+ async function trySiwxFastPath(ctx, account) {
946
+ const { request, routeEntry, deps } = ctx;
947
+ if (!routeEntry.siwxEnabled) return null;
948
+ const siwxHeader = request.headers.get(HEADERS.SIWX);
949
+ if (!siwxHeader) return null;
950
+ const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
951
+ if (!siwx.valid) return null;
952
+ const wallet = normalizeWalletAddress(siwx.wallet);
953
+ ctx.pluginCtx.setVerifiedWallet(wallet);
954
+ const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
955
+ if (!entitled) return null;
956
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
957
+ authMode: "siwx",
958
+ wallet,
959
+ route: routeEntry.key
960
+ });
961
+ return runHandlerOnly(ctx, wallet, account);
962
+ }
963
+
964
+ // src/pipeline/context/should-parse-body-early.ts
965
+ function shouldParseBodyEarly(incomingStrategy, routeEntry, pricing) {
966
+ if (incomingStrategy) return false;
967
+ if (!routeEntry.bodySchema) return false;
968
+ return (pricing?.needsBody ?? false) || !!routeEntry.validateFn;
969
+ }
970
+
971
+ // src/pipeline/context/protocol-init-error.ts
972
+ function protocolInitError(routeEntry, deps) {
973
+ if (!routeEntry.pricing) return null;
974
+ const errors = [];
975
+ for (const protocol of routeEntry.protocols) {
976
+ if (protocol === "x402" && deps.x402InitError) {
977
+ errors.push(`x402: ${deps.x402InitError}`);
978
+ }
979
+ if (protocol === "mpp" && deps.mppInitError) {
980
+ errors.push(`mpp: ${deps.mppInitError}`);
981
+ }
973
982
  }
974
- const credential = Credential.fromRequest(request);
975
- const rawSource = credential?.source ?? "";
976
- const didParts = rawSource.split(":");
977
- const lastPart = didParts[didParts.length - 1];
978
- const wallet = normalizeWalletAddress(isAddress(lastPart) ? getAddress(lastPart) : rawSource);
979
- return { valid: true, wallet, withReceipt: result.withReceipt };
983
+ if (errors.length === 0) return null;
984
+ return `Payment protocol initialization failed. ${errors.join("; ")}`;
980
985
  }
981
986
 
982
987
  // src/auth/api-key.ts
983
988
  async function verifyApiKey(request, resolver) {
984
- const apiKey = request.headers.get("X-API-Key") ?? extractBearerToken(request.headers.get("Authorization"));
989
+ const apiKey = request.headers.get(HEADERS.API_KEY) ?? extractBearerToken(request.headers.get(HEADERS.AUTHORIZATION));
985
990
  if (!apiKey) {
986
991
  return { valid: false, account: null };
987
992
  }
@@ -993,939 +998,1311 @@ async function verifyApiKey(request, resolver) {
993
998
  }
994
999
  function extractBearerToken(header) {
995
1000
  if (!header) return null;
996
- if (header.startsWith("Bearer ")) return header.slice(7);
1001
+ if (header.startsWith(AUTH_SCHEME.BEARER)) return header.slice(AUTH_SCHEME.BEARER.length);
997
1002
  return null;
998
1003
  }
999
1004
 
1000
- // src/orchestrate.ts
1001
- init_x402_config();
1002
- function getRequirementNetwork(requirements, fallback) {
1003
- const network = requirements?.network;
1004
- return typeof network === "string" ? network : fallback;
1005
- }
1006
- function getRequirementRecipient(requirements) {
1007
- const payTo = requirements?.payTo;
1008
- return typeof payTo === "string" ? payTo : void 0;
1009
- }
1010
- function siwxSignatureType(network) {
1011
- return network.startsWith("solana:") ? "ed25519" : "eip191";
1005
+ // src/pipeline/flows/api-key-only.ts
1006
+ async function runApiKeyOnlyFlow(ctx) {
1007
+ if (!ctx.routeEntry.apiKeyResolver) {
1008
+ return fail(ctx, 401, "API key resolver not configured");
1009
+ }
1010
+ const result = await verifyApiKey(ctx.request, ctx.routeEntry.apiKeyResolver);
1011
+ if (!result.valid) return fail(ctx, 401, "Invalid or missing API key");
1012
+ firePluginHook(ctx.deps.plugin, "onAuthVerified", ctx.pluginCtx, {
1013
+ authMode: "apiKey",
1014
+ wallet: null,
1015
+ route: ctx.routeEntry.key,
1016
+ account: result.account
1017
+ });
1018
+ return runHandlerOnly(ctx, null, result.account);
1012
1019
  }
1013
- function getSupportedChains(x402Accepts, fallbackNetwork) {
1014
- const seen = /* @__PURE__ */ new Set();
1015
- const chains = [];
1016
- for (const accept of x402Accepts) {
1017
- if (accept.network && !seen.has(accept.network)) {
1018
- seen.add(accept.network);
1019
- chains.push({ chainId: accept.network, type: siwxSignatureType(accept.network) });
1020
+
1021
+ // src/pricing/dynamic.ts
1022
+ var DynamicPricing = class {
1023
+ constructor(opts) {
1024
+ this.opts = opts;
1025
+ }
1026
+ needsBody = true;
1027
+ async quote(body) {
1028
+ try {
1029
+ const raw = await this.opts.fn(body);
1030
+ return this.cap(raw, body);
1031
+ } catch (err) {
1032
+ this.alert("error", `Pricing function failed: ${msg(err)}`, {
1033
+ error: err instanceof Error ? err.stack : String(err),
1034
+ body
1035
+ });
1036
+ if (this.opts.maxPrice) {
1037
+ this.alert("warn", `Using maxPrice ${this.opts.maxPrice} as fallback after pricing error`);
1038
+ return this.opts.maxPrice;
1039
+ }
1040
+ throw err;
1020
1041
  }
1021
1042
  }
1022
- if (chains.length === 0) {
1023
- chains.push({ chainId: fallbackNetwork, type: siwxSignatureType(fallbackNetwork) });
1043
+ challengeQuote(body) {
1044
+ if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
1045
+ return this.quote(body);
1024
1046
  }
1025
- return chains;
1026
- }
1027
- function createRequestHandler(routeEntry, handler, deps) {
1028
- async function invoke(request, meta, pluginCtx, wallet, account, parsedBody, payment) {
1029
- const ctx = {
1030
- body: parsedBody,
1031
- query: parseQuery(request, routeEntry),
1032
- request,
1033
- requestId: meta.requestId,
1034
- route: routeEntry.key,
1035
- wallet,
1036
- payment,
1037
- account,
1038
- alert(level, message, alertMeta) {
1039
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1040
- level,
1041
- message,
1042
- route: routeEntry.key,
1043
- meta: alertMeta
1044
- });
1045
- },
1046
- setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
1047
+ describe() {
1048
+ return {
1049
+ mode: "dynamic",
1050
+ min: this.opts.minPrice ?? "0",
1051
+ max: this.opts.maxPrice ?? "0"
1047
1052
  };
1048
- let rawResult;
1049
- const response = await safeCallHandler(async (c) => {
1050
- rawResult = await handler(c);
1051
- return rawResult;
1052
- }, ctx);
1053
- return { response, rawResult };
1054
1053
  }
1055
- function finalize(response, rawResult, meta, pluginCtx, requestBody) {
1056
- fireProviderQuota(routeEntry, response, rawResult, deps, pluginCtx);
1057
- firePluginResponse(deps, pluginCtx, meta, response, requestBody, rawResult);
1058
- }
1059
- function fail(status, message, meta, pluginCtx, requestBody) {
1060
- const response = NextResponse2.json({ success: false, error: message }, { status });
1061
- firePluginResponse(deps, pluginCtx, meta, response, requestBody);
1062
- return response;
1063
- }
1064
- return async (request) => {
1065
- await deps.initPromise;
1066
- const meta = buildMeta(request, routeEntry);
1067
- const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
1068
- async function handleAuth(wallet, account2) {
1069
- const body2 = await parseBody(request, routeEntry);
1070
- if (!body2.ok) {
1071
- firePluginResponse(deps, pluginCtx, meta, body2.response);
1072
- return body2.response;
1073
- }
1074
- if (routeEntry.validateFn) {
1075
- try {
1076
- await routeEntry.validateFn(body2.data);
1077
- } catch (err) {
1078
- const status = err.status ?? 400;
1079
- const message = err instanceof Error ? err.message : "Validation failed";
1080
- return fail(status, message, meta, pluginCtx, body2.data);
1081
- }
1082
- }
1083
- const { response, rawResult } = await invoke(
1084
- request,
1085
- meta,
1086
- pluginCtx,
1087
- wallet,
1088
- account2,
1089
- body2.data,
1090
- null
1091
- );
1092
- finalize(response, rawResult, meta, pluginCtx, body2.data);
1093
- return response;
1094
- }
1095
- if (routeEntry.authMode === "unprotected") {
1096
- return handleAuth(null, void 0);
1097
- }
1098
- let account;
1099
- if (routeEntry.authMode === "apiKey" || routeEntry.apiKeyResolver) {
1100
- if (!routeEntry.apiKeyResolver) {
1101
- return fail(401, "API key resolver not configured", meta, pluginCtx);
1102
- }
1103
- const keyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
1104
- if (!keyResult.valid) {
1105
- return fail(401, "Invalid or missing API key", meta, pluginCtx);
1106
- }
1107
- account = keyResult.account;
1108
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1109
- authMode: "apiKey",
1110
- wallet: null,
1111
- route: routeEntry.key,
1112
- account
1054
+ cap(raw, body) {
1055
+ if (!this.opts.maxPrice) return raw;
1056
+ const n = parseFloat(raw);
1057
+ const max = parseFloat(this.opts.maxPrice);
1058
+ if (!Number.isFinite(n) || n > max) {
1059
+ this.alert("warn", `Price ${raw} exceeds maxPrice ${this.opts.maxPrice}, capping`, {
1060
+ calculated: raw,
1061
+ maxPrice: this.opts.maxPrice,
1062
+ body
1113
1063
  });
1114
- if (routeEntry.authMode === "apiKey" && !routeEntry.pricing) {
1115
- return handleAuth(null, account);
1116
- }
1117
- }
1118
- const protocol = detectProtocol(request);
1119
- let earlyBodyData;
1120
- const pricingNeedsBody = routeEntry.pricing != null && typeof routeEntry.pricing !== "string";
1121
- const needsEarlyParse = !protocol && routeEntry.bodySchema && (pricingNeedsBody || routeEntry.validateFn);
1122
- if (needsEarlyParse) {
1123
- const requestForPricing = request.clone();
1124
- const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
1125
- if (earlyBodyResult.ok) {
1126
- earlyBodyData = earlyBodyResult.data;
1127
- if (routeEntry.validateFn) {
1128
- try {
1129
- await routeEntry.validateFn(earlyBodyData);
1130
- } catch (err) {
1131
- const status = err.status ?? 400;
1132
- const message = err instanceof Error ? err.message : "Validation failed";
1133
- return fail(status, message, meta, pluginCtx, earlyBodyData);
1134
- }
1135
- }
1136
- } else {
1137
- firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
1138
- return earlyBodyResult.response;
1139
- }
1064
+ return this.opts.maxPrice;
1140
1065
  }
1141
- if (routeEntry.authMode === "siwx" || routeEntry.siwxEnabled) {
1142
- if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get("SIGN-IN-WITH-X")) {
1143
- const requestForValidation = request.clone();
1144
- const earlyBodyResult = await parseBody(requestForValidation, routeEntry);
1145
- if (earlyBodyResult.ok) {
1146
- try {
1147
- await routeEntry.validateFn(earlyBodyResult.data);
1148
- } catch (err) {
1149
- const status = err.status ?? 400;
1150
- const message = err instanceof Error ? err.message : "Validation failed";
1151
- return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
1152
- }
1153
- }
1154
- }
1155
- const siwxHeader = request.headers.get("SIGN-IN-WITH-X");
1156
- if (!siwxHeader && protocol === "mpp" && routeEntry.authMode === "siwx" && deps.mppx) {
1157
- let mppSiwxResult;
1158
- try {
1159
- mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
1160
- } catch (err) {
1161
- const message = err instanceof Error ? err.message : String(err);
1162
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1163
- level: "critical",
1164
- message: `MPP SIWX verification failed: ${message}`,
1165
- route: routeEntry.key
1166
- });
1167
- return fail(500, `MPP SIWX verification failed: ${message}`, meta, pluginCtx);
1168
- }
1169
- if (mppSiwxResult.valid) {
1170
- pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
1171
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1172
- authMode: "siwx",
1173
- wallet: mppSiwxResult.wallet,
1174
- route: routeEntry.key
1175
- });
1176
- const authResponse = await handleAuth(mppSiwxResult.wallet, void 0);
1177
- if (authResponse.status < 400) {
1178
- return mppSiwxResult.withReceipt(authResponse);
1179
- }
1180
- return authResponse;
1181
- }
1182
- }
1183
- if (!siwxHeader && routeEntry.authMode === "siwx") {
1184
- const url = new URL(request.url);
1185
- const nonce = crypto.randomUUID().replace(/-/g, "");
1186
- const supportedChains = getSupportedChains(deps.x402Accepts, deps.network);
1187
- const primaryChain = supportedChains[0];
1188
- const siwxInfo = {
1189
- domain: url.hostname,
1190
- uri: request.url,
1191
- version: "1",
1192
- chainId: primaryChain.chainId,
1193
- type: primaryChain.type,
1194
- nonce,
1195
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1196
- expirationTime: new Date(Date.now() + SIWX_CHALLENGE_EXPIRY_MS).toISOString(),
1197
- statement: "Sign in to verify your wallet identity"
1198
- };
1199
- let siwxSchema;
1200
- try {
1201
- siwxSchema = await buildSIWXExtension();
1202
- } catch {
1203
- }
1204
- const paymentRequired = {
1205
- x402Version: 2,
1206
- error: "SIWX authentication required",
1207
- resource: {
1208
- url: request.url,
1209
- description: routeEntry.description ?? "SIWX-protected endpoint",
1210
- mimeType: "application/json"
1211
- },
1212
- accepts: [],
1213
- extensions: {
1214
- "sign-in-with-x": {
1215
- info: siwxInfo,
1216
- // supportedChains at top level required by MCP tools for chain detection
1217
- supportedChains,
1218
- ...siwxSchema ? { schema: siwxSchema } : {}
1219
- }
1220
- }
1221
- };
1222
- let encoded;
1223
- try {
1224
- const { encodePaymentRequiredHeader } = await import("@x402/core/http");
1225
- encoded = encodePaymentRequiredHeader(paymentRequired);
1226
- } catch (err) {
1227
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1228
- level: "warn",
1229
- message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
1230
- route: routeEntry.key
1231
- });
1232
- }
1233
- const response = new NextResponse2(JSON.stringify(paymentRequired), {
1234
- status: 402,
1235
- headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
1236
- });
1237
- if (encoded) response.headers.set("PAYMENT-REQUIRED", encoded);
1238
- if (deps.mppx) {
1239
- try {
1240
- const mppChallenge = await deps.mppx.charge({ amount: "0" })(request);
1241
- if (mppChallenge.status === 402) {
1242
- const wwwAuth = mppChallenge.challenge.headers.get("WWW-Authenticate");
1243
- if (wwwAuth) response.headers.set("WWW-Authenticate", wwwAuth);
1244
- }
1245
- } catch {
1246
- }
1247
- }
1248
- firePluginResponse(deps, pluginCtx, meta, response);
1249
- return response;
1250
- }
1251
- if (siwxHeader) {
1252
- const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
1253
- if (!siwx.valid) {
1254
- if (routeEntry.authMode === "siwx") {
1255
- const response = NextResponse2.json(
1256
- { error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
1257
- { status: 402 }
1258
- );
1259
- firePluginResponse(deps, pluginCtx, meta, response);
1260
- return response;
1261
- }
1262
- } else {
1263
- const wallet = normalizeWalletAddress(siwx.wallet);
1264
- pluginCtx.setVerifiedWallet(wallet);
1265
- if (routeEntry.authMode === "siwx") {
1266
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1267
- authMode: "siwx",
1268
- wallet,
1269
- route: routeEntry.key
1270
- });
1271
- return handleAuth(wallet, void 0);
1272
- }
1273
- if (routeEntry.siwxEnabled && routeEntry.pricing) {
1274
- const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
1275
- if (entitled) {
1276
- firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
1277
- authMode: "siwx",
1278
- wallet,
1279
- route: routeEntry.key
1280
- });
1281
- return handleAuth(wallet, account);
1282
- }
1283
- }
1284
- }
1285
- }
1286
- }
1287
- if (!protocol || protocol === "siwx") {
1288
- if (routeEntry.pricing) {
1289
- const initErrors = routeEntry.protocols.map((p) => {
1290
- if (p === "x402" && deps.x402InitError) return `x402: ${deps.x402InitError}`;
1291
- if (p === "mpp" && deps.mppInitError) return `mpp: ${deps.mppInitError}`;
1292
- return null;
1293
- }).filter(Boolean);
1294
- if (initErrors.length > 0) {
1295
- return fail(
1296
- 500,
1297
- `Payment protocol initialization failed. ${initErrors.join("; ")}`,
1298
- meta,
1299
- pluginCtx
1300
- );
1301
- }
1302
- }
1303
- return await build402(request, routeEntry, deps, meta, pluginCtx, earlyBodyData);
1304
- }
1305
- const body = await parseBody(request, routeEntry);
1306
- if (!body.ok) {
1307
- firePluginResponse(deps, pluginCtx, meta, body.response);
1308
- return body.response;
1309
- }
1310
- if (routeEntry.validateFn) {
1066
+ return raw;
1067
+ }
1068
+ alert(level, message, meta) {
1069
+ this.opts.alert?.(level, message, meta);
1070
+ }
1071
+ };
1072
+ function msg(err) {
1073
+ return err instanceof Error ? err.message : String(err);
1074
+ }
1075
+
1076
+ // src/pricing/fixed.ts
1077
+ var FixedPricing = class {
1078
+ constructor(price) {
1079
+ this.price = price;
1080
+ }
1081
+ needsBody = false;
1082
+ quote() {
1083
+ return Promise.resolve(this.price);
1084
+ }
1085
+ challengeQuote() {
1086
+ return Promise.resolve(this.price);
1087
+ }
1088
+ describe() {
1089
+ return { mode: "fixed", amount: this.price };
1090
+ }
1091
+ };
1092
+
1093
+ // src/pricing/tiered.ts
1094
+ var TieredPricing = class {
1095
+ constructor(opts) {
1096
+ this.opts = opts;
1097
+ }
1098
+ needsBody = true;
1099
+ async quote(body) {
1100
+ const { field, tiers, default: defaultTier } = this.opts;
1101
+ const tierKey = body != null ? String(body[field] ?? "") : "";
1102
+ if (tierKey && tiers[tierKey]) return tiers[tierKey].price;
1103
+ if (defaultTier && tiers[defaultTier]) return tiers[defaultTier].price;
1104
+ if (!tierKey) {
1105
+ throw httpError(400, `Missing required field '${field}' for tier pricing`);
1106
+ }
1107
+ throw httpError(
1108
+ 400,
1109
+ `Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
1110
+ );
1111
+ }
1112
+ challengeQuote(body) {
1113
+ if (body !== void 0) {
1311
1114
  try {
1312
- await routeEntry.validateFn(body.data);
1313
- } catch (err) {
1314
- const status = err.status ?? 400;
1315
- const message = err instanceof Error ? err.message : "Validation failed";
1316
- return fail(status, message, meta, pluginCtx, body.data);
1115
+ return this.quote(body);
1116
+ } catch {
1317
1117
  }
1318
1118
  }
1319
- let price;
1119
+ return Promise.resolve(this.maxTierPrice());
1120
+ }
1121
+ describe() {
1122
+ return {
1123
+ mode: "tiered",
1124
+ tiers: Object.entries(this.opts.tiers).map(([key, tier]) => ({
1125
+ key,
1126
+ price: tier.price,
1127
+ ...tier.label !== void 0 ? { label: tier.label } : {}
1128
+ })),
1129
+ ...this.opts.default !== void 0 ? { default: this.opts.default } : {}
1130
+ };
1131
+ }
1132
+ maxTierPrice() {
1133
+ let max = "0";
1134
+ for (const tier of Object.values(this.opts.tiers)) {
1135
+ if (parseFloat(tier.price) > parseFloat(max)) max = tier.price;
1136
+ }
1137
+ return max;
1138
+ }
1139
+ };
1140
+ function httpError(status, message) {
1141
+ return Object.assign(new Error(message), { status });
1142
+ }
1143
+
1144
+ // src/pricing/index.ts
1145
+ function selectPricing(raw, deps = {}) {
1146
+ if (raw == null) return null;
1147
+ if (typeof raw === "string") {
1148
+ return new FixedPricing(raw);
1149
+ }
1150
+ if (typeof raw === "function") {
1151
+ return new DynamicPricing({
1152
+ fn: raw,
1153
+ maxPrice: deps.maxPrice,
1154
+ minPrice: deps.minPrice,
1155
+ route: deps.route,
1156
+ alert: deps.alert
1157
+ });
1158
+ }
1159
+ if (typeof raw === "object" && "tiers" in raw) {
1160
+ return new TieredPricing({
1161
+ field: raw.field,
1162
+ tiers: raw.tiers,
1163
+ default: raw.default,
1164
+ maxPrice: deps.maxPrice,
1165
+ minPrice: deps.minPrice
1166
+ });
1167
+ }
1168
+ throw new Error(`Unknown pricing config: ${JSON.stringify(raw)}`);
1169
+ }
1170
+
1171
+ // src/protocols/mpp/credential.ts
1172
+ import { Credential } from "mppx";
1173
+ import { getAddress, isAddress } from "viem";
1174
+ function readMppCredential(request) {
1175
+ const credential = Credential.fromRequest(request);
1176
+ if (!credential) return null;
1177
+ const wallet = walletFromDid(credential.source ?? "");
1178
+ const rawType = credential.payload?.type;
1179
+ const payloadType = rawType === "transaction" ? "transaction" : rawType === "hash" ? "hash" : "unknown";
1180
+ return { credential, wallet, payloadType };
1181
+ }
1182
+ function walletFromDid(rawSource) {
1183
+ const parts = rawSource.split(":");
1184
+ const last = parts[parts.length - 1];
1185
+ return normalizeWalletAddress(isAddress(last) ? getAddress(last) : rawSource);
1186
+ }
1187
+
1188
+ // src/protocols/mpp/transaction-mode.ts
1189
+ import { Transaction as TempoTransaction } from "viem/tempo";
1190
+ import { call as viemCall } from "viem/actions";
1191
+
1192
+ // src/protocols/mpp/receipt.ts
1193
+ import { Receipt } from "mppx";
1194
+ function extractTxHash(receiptHeader) {
1195
+ if (!receiptHeader) return "";
1196
+ try {
1197
+ return Receipt.deserialize(receiptHeader).reference;
1198
+ } catch {
1199
+ return "";
1200
+ }
1201
+ }
1202
+ async function readChallengeReason(challenge) {
1203
+ try {
1204
+ const text = await challenge.clone().text();
1205
+ if (!text) return "";
1206
+ const problem = JSON.parse(text);
1207
+ return problem.detail || problem.title || "";
1208
+ } catch {
1209
+ return "";
1210
+ }
1211
+ }
1212
+
1213
+ // src/protocols/mpp/transaction-mode.ts
1214
+ async function verifyTxMode(args, info) {
1215
+ const { deps, price, routeEntry } = args;
1216
+ if (!deps.tempoClient) {
1217
+ return {
1218
+ ok: false,
1219
+ kind: "config",
1220
+ message: "tempoClient not configured for MPP transaction-payload mode"
1221
+ };
1222
+ }
1223
+ try {
1224
+ const serializedTx = info.credential.payload.signature;
1225
+ const transaction = TempoTransaction.deserialize(serializedTx);
1226
+ await viemCall(deps.tempoClient, {
1227
+ ...transaction,
1228
+ account: transaction.from,
1229
+ calls: transaction.calls ?? []
1230
+ });
1231
+ } catch (err) {
1232
+ const message = err instanceof Error ? err.message : String(err);
1233
+ console.warn(`[router] ${routeEntry.key}: MPP simulation failed \u2014 ${message}`);
1234
+ return { ok: false, kind: "invalid" };
1235
+ }
1236
+ const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
1237
+ const payment = {
1238
+ protocol: "mpp",
1239
+ status: "verified",
1240
+ payer: info.wallet,
1241
+ amount: price,
1242
+ network: "tempo:4217",
1243
+ ...mppRecipient ? { recipient: mppRecipient } : {}
1244
+ };
1245
+ return {
1246
+ ok: true,
1247
+ wallet: info.wallet,
1248
+ payment,
1249
+ token: { mode: "transaction", credential: info.credential },
1250
+ alreadySettled: false
1251
+ };
1252
+ }
1253
+ async function settleTxMode(args) {
1254
+ const { request, response, payment, deps, routeEntry } = args;
1255
+ if (!deps.mppx) {
1256
+ return {
1257
+ ok: false,
1258
+ error: new Error("mppx unavailable"),
1259
+ failMessage: "MPP not initialized",
1260
+ failStatus: 500
1261
+ };
1262
+ }
1263
+ let result;
1264
+ try {
1265
+ result = await deps.mppx.charge({ amount: payment.amount })(request);
1266
+ } catch (err) {
1267
+ const message = err instanceof Error ? err.message : String(err);
1268
+ console.error(`[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`);
1269
+ return {
1270
+ ok: false,
1271
+ error: err,
1272
+ failMessage: `MPP payment processing failed: ${message}`,
1273
+ failStatus: 500
1274
+ };
1275
+ }
1276
+ if (result.status === 402) {
1277
+ const reason = await readChallengeReason(result.challenge);
1278
+ const detail = reason || "transaction reverted on-chain after handler execution";
1279
+ const settlementError = Object.assign(new Error(detail), {
1280
+ status: 402,
1281
+ detail,
1282
+ mppResult: result,
1283
+ challenge: result.challenge
1284
+ });
1285
+ console.error(`[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`);
1286
+ return {
1287
+ ok: false,
1288
+ error: settlementError,
1289
+ failMessage: `MPP payment failed: ${detail}`,
1290
+ failStatus: 500
1291
+ };
1292
+ }
1293
+ const receiptResponse = result.withReceipt(response);
1294
+ receiptResponse.headers.set("Cache-Control", "private");
1295
+ const receiptHeader = receiptResponse.headers.get(HEADERS.MPP_PAYMENT_RECEIPT) ?? void 0;
1296
+ const txHash = extractTxHash(receiptHeader);
1297
+ const settledPayment = {
1298
+ ...payment,
1299
+ status: "settled",
1300
+ ...txHash ? { transaction: txHash } : {},
1301
+ ...receiptHeader ? { receipt: receiptHeader } : {}
1302
+ };
1303
+ return { ok: true, response: receiptResponse, settledPayment };
1304
+ }
1305
+
1306
+ // src/protocols/mpp/hash-mode.ts
1307
+ async function verifyHashMode(args, info) {
1308
+ const { deps, price, routeEntry, request } = args;
1309
+ if (!deps.mppx) {
1310
+ 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";
1311
+ console.error(`[router] ${routeEntry.key}: ${reason}`);
1312
+ return { ok: false, kind: "config", message: reason };
1313
+ }
1314
+ let chargeResult;
1315
+ try {
1316
+ chargeResult = await deps.mppx.charge({ amount: price })(request);
1317
+ } catch (err) {
1318
+ const message = err instanceof Error ? err.message : String(err);
1319
+ console.error(`[router] ${routeEntry.key}: MPP charge failed: ${message}`);
1320
+ return { ok: false, kind: "config", message: `MPP payment processing failed: ${message}` };
1321
+ }
1322
+ if (chargeResult.status === 402) {
1323
+ const reason = await readChallengeReason(chargeResult.challenge);
1324
+ const detail = reason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
1325
+ console.warn(`[router] ${routeEntry.key}: MPP credential rejected \u2014 ${detail}`);
1326
+ return { ok: false, kind: "invalid" };
1327
+ }
1328
+ const receiptHeader = chargeResult.withReceipt(new Response()).headers.get(
1329
+ HEADERS.MPP_PAYMENT_RECEIPT
1330
+ );
1331
+ const txHash = extractTxHash(receiptHeader);
1332
+ const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
1333
+ const payment = {
1334
+ protocol: "mpp",
1335
+ status: "settled",
1336
+ payer: info.wallet,
1337
+ amount: price,
1338
+ network: "tempo:4217",
1339
+ ...mppRecipient ? { recipient: mppRecipient } : {},
1340
+ ...txHash ? { transaction: txHash } : {},
1341
+ ...receiptHeader ? { receipt: receiptHeader } : {}
1342
+ };
1343
+ return {
1344
+ ok: true,
1345
+ wallet: info.wallet,
1346
+ payment,
1347
+ token: { mode: "hash", charge: chargeResult },
1348
+ alreadySettled: true
1349
+ };
1350
+ }
1351
+ function settleHashMode(args) {
1352
+ const { response, payment, token } = args;
1353
+ const hashToken = token;
1354
+ const receiptResponse = hashToken.charge.withReceipt(response);
1355
+ receiptResponse.headers.set("Cache-Control", "private");
1356
+ return {
1357
+ ok: true,
1358
+ response: receiptResponse,
1359
+ settledPayment: payment
1360
+ };
1361
+ }
1362
+
1363
+ // src/protocols/mpp/strategy.ts
1364
+ var mppStrategy = {
1365
+ protocol: "mpp",
1366
+ detects(request) {
1367
+ const auth = request.headers.get(HEADERS.AUTHORIZATION);
1368
+ return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
1369
+ },
1370
+ async verify(args) {
1371
+ const info = readMppCredential(args.request);
1372
+ if (!info) return { ok: false, kind: "invalid" };
1373
+ if (info.payloadType === "transaction" && args.deps.tempoClient) {
1374
+ return verifyTxMode(args, info);
1375
+ }
1376
+ return verifyHashMode(args, info);
1377
+ },
1378
+ async settle(args) {
1379
+ const token = args.token;
1380
+ if (token.mode === "transaction") return settleTxMode(args);
1381
+ return settleHashMode(args);
1382
+ },
1383
+ async buildChallenge(args) {
1384
+ if (!args.deps.mppx) return {};
1320
1385
  try {
1321
- price = await resolvePrice(routeEntry.pricing, body.data);
1386
+ const result = await args.deps.mppx.charge({ amount: args.price })(args.request);
1387
+ if (result.status === 402) {
1388
+ const wwwAuth = result.challenge.headers.get(HEADERS.WWW_AUTHENTICATE);
1389
+ if (wwwAuth) return { headers: { [HEADERS.WWW_AUTHENTICATE]: wwwAuth } };
1390
+ }
1322
1391
  } catch (err) {
1323
- return fail(
1324
- err.status ?? 500,
1325
- err instanceof Error ? err.message : "Price resolution failed",
1326
- meta,
1327
- pluginCtx,
1328
- body.data
1329
- );
1330
- }
1331
- if (!routeEntry.protocols.includes(protocol)) {
1332
- const accepted = routeEntry.protocols.join(", ") || "none";
1333
1392
  console.warn(
1334
- `[router] ${routeEntry.key}: received ${protocol} payment but route accepts [${accepted}]`
1335
- );
1336
- return fail(
1337
- 400,
1338
- `This route does not accept ${protocol} payments. Accepted protocols: ${accepted}`,
1339
- meta,
1340
- pluginCtx,
1341
- body.data
1393
+ `[router] MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`
1342
1394
  );
1395
+ throw err;
1343
1396
  }
1344
- if (protocol === "x402") {
1345
- if (!deps.x402Server) {
1346
- 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";
1347
- console.error(`[router] ${routeEntry.key}: ${reason}`);
1348
- return fail(500, reason, meta, pluginCtx, body.data);
1349
- }
1350
- const accepts = await resolveX402Accepts(
1351
- request,
1352
- routeEntry,
1353
- deps.x402Accepts,
1354
- deps.payeeAddress,
1355
- body.data
1356
- );
1357
- const verify = await verifyX402Payment({
1358
- server: deps.x402Server,
1359
- request,
1360
- price,
1361
- accepts
1362
- });
1363
- if (!verify?.valid)
1364
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1365
- const { payload: verifyPayload, requirements: verifyRequirements } = verify;
1366
- const matchedNetwork = getRequirementNetwork(verifyRequirements, deps.network);
1367
- const matchedRecipient = getRequirementRecipient(verifyRequirements);
1368
- const wallet = normalizeWalletAddress(verify.payer);
1369
- const payment = {
1370
- protocol: "x402",
1371
- status: "verified",
1372
- payer: wallet,
1373
- amount: price,
1374
- network: matchedNetwork,
1375
- ...matchedRecipient ? { recipient: matchedRecipient } : {}
1376
- };
1377
- pluginCtx.setVerifiedWallet(wallet);
1378
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1379
- protocol: "x402",
1380
- payer: wallet,
1381
- amount: price,
1382
- network: matchedNetwork
1383
- });
1384
- const { response, rawResult } = await invoke(
1385
- request,
1386
- meta,
1387
- pluginCtx,
1388
- wallet,
1389
- account,
1390
- body.data,
1391
- payment
1397
+ return {};
1398
+ }
1399
+ };
1400
+
1401
+ // src/protocols/x402/strategy.ts
1402
+ init_accepts();
1403
+
1404
+ // src/protocols/x402/challenge.ts
1405
+ init_facilitators();
1406
+ init_solana();
1407
+
1408
+ // src/protocols/x402/requirements.ts
1409
+ init_evm();
1410
+ init_solana();
1411
+ async function buildExpectedRequirements(server, request, price, accepts) {
1412
+ const exactRequirements = await buildExactRequirements(server, request, price, accepts);
1413
+ const customRequirements = buildCustomRequirements(price, accepts);
1414
+ return [...exactRequirements, ...customRequirements];
1415
+ }
1416
+ async function buildExactRequirements(server, request, price, accepts) {
1417
+ const exactGroups = [
1418
+ buildEvmExactOptions(accepts, price),
1419
+ buildSolanaExactOptions(accepts, price)
1420
+ ].filter((options) => options.length > 0);
1421
+ if (exactGroups.length === 0) return [];
1422
+ const requirements = [];
1423
+ const failures = [];
1424
+ for (const options of exactGroups) {
1425
+ try {
1426
+ requirements.push(
1427
+ ...await server.buildPaymentRequirementsFromOptions(options, { request })
1392
1428
  );
1393
- if (response.status < 400) {
1394
- try {
1395
- const settle = await settleX402Payment(
1396
- deps.x402Server,
1397
- verifyPayload,
1398
- verifyRequirements
1399
- );
1400
- if (!settle.result?.success) {
1401
- const reason = settle.result?.errorReason || "x402 settlement returned success=false";
1402
- const error = new Error(reason);
1403
- error.errorReason = reason;
1404
- throw error;
1405
- }
1406
- if (routeEntry.siwxEnabled) {
1407
- try {
1408
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1409
- } catch (error) {
1410
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1411
- level: "warn",
1412
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1413
- route: routeEntry.key
1414
- });
1415
- }
1416
- }
1417
- response.headers.set("PAYMENT-RESPONSE", settle.encoded);
1418
- response.headers.set("Cache-Control", "private");
1419
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1420
- protocol: "x402",
1421
- payer: verify.payer,
1422
- transaction: String(settle.result?.transaction ?? ""),
1423
- network: matchedNetwork
1424
- });
1425
- } catch (err) {
1426
- const errObj = err;
1427
- console.error("Settlement failed", {
1428
- message: err instanceof Error ? err.message : String(err),
1429
- route: routeEntry.key,
1430
- network: matchedNetwork,
1431
- errorReason: errObj.errorReason,
1432
- facilitatorStatus: errObj.response?.status,
1433
- facilitatorBody: errObj.response?.data ?? errObj.response?.body
1434
- });
1435
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1436
- level: "critical",
1437
- message: `Settlement failed: ${err instanceof Error ? err.message : String(err)}`,
1438
- route: routeEntry.key
1439
- });
1440
- return fail(500, "Settlement failed", meta, pluginCtx, body.data);
1441
- }
1442
- }
1443
- finalize(response, rawResult, meta, pluginCtx, body.data);
1444
- return response;
1445
- }
1446
- if (protocol === "mpp") {
1447
- if (!deps.mppx) {
1448
- 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";
1449
- console.error(`[router] ${routeEntry.key}: ${reason}`);
1450
- return fail(500, reason, meta, pluginCtx, body.data);
1451
- }
1452
- const mppCredential = Credential2.fromRequest(request);
1453
- const rawSource = mppCredential?.source ?? "";
1454
- const didParts = rawSource.split(":");
1455
- const lastPart = didParts[didParts.length - 1];
1456
- const wallet = normalizeWalletAddress(isAddress2(lastPart) ? getAddress2(lastPart) : rawSource);
1457
- const payloadType = mppCredential?.payload?.type;
1458
- if (payloadType === "transaction" && deps.tempoClient) {
1459
- try {
1460
- const serializedTx = mppCredential.payload.signature;
1461
- const transaction = TempoTransaction.deserialize(serializedTx);
1462
- await viemCall(deps.tempoClient, {
1463
- ...transaction,
1464
- account: transaction.from,
1465
- calls: transaction.calls ?? []
1466
- });
1467
- } catch (err) {
1468
- const message = err instanceof Error ? err.message : String(err);
1469
- console.warn(`[router] ${routeEntry.key}: MPP simulation failed \u2014 ${message}`);
1470
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1471
- level: "warn",
1472
- message: `MPP simulation failed: ${message}`,
1473
- route: routeEntry.key
1474
- });
1475
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1476
- }
1477
- pluginCtx.setVerifiedWallet(wallet);
1478
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1479
- protocol: "mpp",
1480
- payer: wallet,
1481
- amount: price,
1482
- network: "tempo:4217"
1483
- });
1484
- const { response: response2, rawResult: rawResult2 } = await invoke(
1485
- request,
1486
- meta,
1487
- pluginCtx,
1488
- wallet,
1489
- account,
1490
- body.data,
1491
- {
1492
- protocol: "mpp",
1493
- status: "verified",
1494
- payer: wallet,
1495
- amount: price,
1496
- network: "tempo:4217",
1497
- recipient: deps.payeeAddress
1498
- }
1499
- );
1500
- if (response2.status < 400) {
1501
- let mppResult2;
1502
- try {
1503
- mppResult2 = await deps.mppx.charge({ amount: price })(request);
1504
- } catch (err) {
1505
- const message = err instanceof Error ? err.message : String(err);
1506
- console.error(
1507
- `[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`
1508
- );
1509
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1510
- level: "critical",
1511
- message: `MPP broadcast failed after handler: ${message}`,
1512
- route: routeEntry.key
1513
- });
1514
- return fail(
1515
- 500,
1516
- `MPP payment processing failed: ${message}`,
1517
- meta,
1518
- pluginCtx,
1519
- body.data
1520
- );
1521
- }
1522
- if (mppResult2.status === 402) {
1523
- let rejectReason = "";
1524
- try {
1525
- const problemBody = await mppResult2.challenge.clone().text();
1526
- if (problemBody) {
1527
- const problem = JSON.parse(problemBody);
1528
- rejectReason = problem.detail || problem.title || "";
1529
- }
1530
- } catch {
1531
- }
1532
- const detail = rejectReason || "transaction reverted on-chain after handler execution";
1533
- console.error(
1534
- `[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`
1535
- );
1536
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1537
- level: "critical",
1538
- message: `MPP payment failed after handler: ${detail}`,
1539
- route: routeEntry.key
1540
- });
1541
- return fail(500, `MPP payment failed: ${detail}`, meta, pluginCtx, body.data);
1542
- }
1543
- const receiptResponse = mppResult2.withReceipt(response2);
1544
- receiptResponse.headers.set("Cache-Control", "private");
1545
- let txHash2 = "";
1546
- const receiptHeader2 = receiptResponse.headers.get("Payment-Receipt");
1547
- if (receiptHeader2) {
1548
- try {
1549
- txHash2 = Receipt.deserialize(receiptHeader2).reference;
1550
- } catch {
1551
- }
1552
- }
1553
- if (routeEntry.siwxEnabled) {
1554
- try {
1555
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1556
- } catch (error) {
1557
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1558
- level: "warn",
1559
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1560
- route: routeEntry.key
1561
- });
1562
- }
1563
- }
1564
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1565
- protocol: "mpp",
1566
- payer: wallet,
1567
- transaction: txHash2,
1568
- network: "tempo:4217"
1569
- });
1570
- finalize(receiptResponse, rawResult2, meta, pluginCtx, body.data);
1571
- return receiptResponse;
1572
- }
1573
- finalize(response2, rawResult2, meta, pluginCtx, body.data);
1574
- return response2;
1429
+ } catch (error) {
1430
+ const err = error instanceof Error ? error : new Error(String(error));
1431
+ failures.push(err);
1432
+ if (exactGroups.length === 1) {
1433
+ throw err;
1575
1434
  }
1576
- let mppResult;
1435
+ console.warn(
1436
+ `[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
1437
+ );
1438
+ }
1439
+ }
1440
+ if (requirements.length > 0) {
1441
+ return requirements;
1442
+ }
1443
+ throw failures[0] ?? new Error("Failed to build x402 exact requirements");
1444
+ }
1445
+ function buildCustomRequirements(price, accepts) {
1446
+ return accepts.filter((accept) => accept.scheme !== "exact").map((accept) => buildCustomRequirement(price, accept));
1447
+ }
1448
+ function buildCustomRequirement(price, accept) {
1449
+ if (!accept.asset) {
1450
+ throw new Error(
1451
+ `Custom x402 accept '${accept.scheme}' on '${accept.network}' is missing asset`
1452
+ );
1453
+ }
1454
+ return {
1455
+ scheme: accept.scheme,
1456
+ network: accept.network,
1457
+ amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
1458
+ asset: accept.asset,
1459
+ payTo: accept.payTo,
1460
+ maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
1461
+ extra: accept.extra ?? {}
1462
+ };
1463
+ }
1464
+ function decimalToAtomicUnits(amount, decimals) {
1465
+ const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
1466
+ if (!match?.groups) {
1467
+ throw new Error(`Invalid decimal amount '${amount}'`);
1468
+ }
1469
+ const whole = match.groups.whole;
1470
+ const fraction = match.groups.fraction ?? "";
1471
+ if (fraction.length > decimals) {
1472
+ throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
1473
+ }
1474
+ const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
1475
+ return normalized === "" ? "0" : normalized;
1476
+ }
1477
+
1478
+ // src/protocols/x402/challenge.ts
1479
+ async function buildX402Challenge(opts) {
1480
+ const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
1481
+ const { encodePaymentRequiredHeader } = await import("@x402/core/http");
1482
+ const resource = buildChallengeResource(request, routeEntry);
1483
+ const requirements = await buildChallengeRequirements(
1484
+ server,
1485
+ request,
1486
+ price,
1487
+ accepts,
1488
+ resource,
1489
+ facilitatorsByNetwork
1490
+ );
1491
+ const paymentRequired = await server.createPaymentRequiredResponse(
1492
+ requirements,
1493
+ resource,
1494
+ void 0,
1495
+ extensions
1496
+ );
1497
+ const encoded = encodePaymentRequiredHeader(paymentRequired);
1498
+ return { encoded, requirements };
1499
+ }
1500
+ async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork) {
1501
+ const requirements = await buildExpectedRequirements(server, request, price, accepts);
1502
+ if (!needsFacilitatorEnrichment(accepts)) return requirements;
1503
+ return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork);
1504
+ }
1505
+ function needsFacilitatorEnrichment(accepts) {
1506
+ return accepts.some((accept) => accept.scheme !== "exact") || hasSolanaAccepts(accepts);
1507
+ }
1508
+ async function enrichGroup(group, resource) {
1509
+ const accepted = await enrichRequirementsWithFacilitatorAccepts(
1510
+ group.facilitator,
1511
+ resource,
1512
+ group.items.map(({ requirement }) => requirement)
1513
+ );
1514
+ if (accepted.length !== group.items.length) {
1515
+ throw new Error(
1516
+ `Facilitator /accepts returned ${accepted.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
1517
+ );
1518
+ }
1519
+ return accepted;
1520
+ }
1521
+ async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork) {
1522
+ const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
1523
+ if (groups.length === 0) return requirements;
1524
+ const results = await Promise.all(
1525
+ groups.map(async (group) => {
1577
1526
  try {
1578
- mppResult = await deps.mppx.charge({ amount: price })(request);
1527
+ return { success: true, group, accepted: await enrichGroup(group, resource) };
1579
1528
  } catch (err) {
1580
- const message = err instanceof Error ? err.message : String(err);
1581
- console.error(`[router] ${routeEntry.key}: MPP charge failed: ${message}`);
1582
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1583
- level: "critical",
1584
- message: `MPP charge failed: ${message}`,
1585
- route: routeEntry.key
1586
- });
1587
- return fail(500, `MPP payment processing failed: ${message}`, meta, pluginCtx, body.data);
1529
+ const label = group.facilitator.url ?? group.facilitator.network;
1530
+ const reason = err instanceof Error ? err.message : String(err);
1531
+ console.warn(
1532
+ `[router] ${label} /accepts failed, dropping ${group.items.length} requirement(s): ${reason}`
1533
+ );
1534
+ return { success: false, group };
1588
1535
  }
1589
- if (mppResult.status === 402) {
1590
- let rejectReason = "";
1591
- try {
1592
- const problemBody = await mppResult.challenge.clone().text();
1593
- if (problemBody) {
1594
- const problem = JSON.parse(problemBody);
1595
- rejectReason = problem.detail || problem.title || "";
1596
- }
1597
- } catch {
1598
- }
1599
- const detail = rejectReason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
1600
- console.warn(`[router] ${routeEntry.key}: MPP credential rejected \u2014 ${detail}`);
1601
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1602
- level: "warn",
1603
- message: `MPP payment rejected: ${detail}`,
1604
- route: routeEntry.key
1605
- });
1606
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1536
+ })
1537
+ );
1538
+ const enriched = [...requirements];
1539
+ results.filter((r) => r.success).forEach(({ group, accepted }) => {
1540
+ accepted.forEach((req, offset) => {
1541
+ const index = group.items[offset]?.index;
1542
+ if (index !== void 0) enriched[index] = req;
1543
+ });
1544
+ });
1545
+ const failedIndices = new Set(
1546
+ results.filter((r) => !r.success).flatMap(({ group }) => group.items.map(({ index }) => index))
1547
+ );
1548
+ const remaining = enriched.filter((_, i) => !failedIndices.has(i));
1549
+ if (remaining.length === 0) {
1550
+ throw new Error(
1551
+ "All facilitator enrichments failed; no payment requirements remain for challenge"
1552
+ );
1553
+ }
1554
+ return remaining;
1555
+ }
1556
+ function collectEnrichmentGroups(requirements, facilitatorsByNetwork) {
1557
+ const groups = [];
1558
+ requirements.forEach((requirement, index) => {
1559
+ if (!requiresFacilitatorEnrichment(requirement)) return;
1560
+ const facilitator = getRequiredFacilitator(requirement, facilitatorsByNetwork);
1561
+ const existing = groups.find(
1562
+ (group) => sameResolvedX402Facilitator(group.facilitator, facilitator)
1563
+ );
1564
+ if (existing) {
1565
+ existing.items.push({ index, requirement });
1566
+ return;
1567
+ }
1568
+ groups.push({
1569
+ facilitator,
1570
+ items: [{ index, requirement }]
1571
+ });
1572
+ });
1573
+ return groups;
1574
+ }
1575
+ function getRequiredFacilitator(requirement, facilitatorsByNetwork) {
1576
+ const facilitator = getFacilitatorForRequirement(facilitatorsByNetwork, requirement);
1577
+ if (!facilitator) {
1578
+ throw new Error(
1579
+ `Missing x402 facilitator for ${requirement.scheme} requirement on ${requirement.network}`
1580
+ );
1581
+ }
1582
+ return facilitator;
1583
+ }
1584
+ function requiresFacilitatorEnrichment(requirement) {
1585
+ return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
1586
+ }
1587
+ function buildChallengeResource(request, routeEntry) {
1588
+ return {
1589
+ url: request.url,
1590
+ method: routeEntry.method,
1591
+ description: routeEntry.description,
1592
+ mimeType: "application/json"
1593
+ };
1594
+ }
1595
+
1596
+ // src/protocols/x402/settle.ts
1597
+ async function settleX402Payment(server, payload, requirements) {
1598
+ const { encodePaymentResponseHeader } = await import("@x402/core/http");
1599
+ const result = await server.settlePayment(payload, requirements);
1600
+ const encoded = encodePaymentResponseHeader(result);
1601
+ return { encoded, result };
1602
+ }
1603
+
1604
+ // src/protocols/x402/verify.ts
1605
+ async function verifyX402Payment(opts) {
1606
+ const { server, request, price, accepts } = opts;
1607
+ const payload = await readPaymentPayload(request);
1608
+ if (!payload) return null;
1609
+ const requirements = await buildExpectedRequirements(server, request, price, accepts);
1610
+ const matching = findVerifiableRequirements(server, requirements, payload);
1611
+ if (!matching) {
1612
+ return invalidPaymentVerification();
1613
+ }
1614
+ let verify;
1615
+ try {
1616
+ verify = await server.verifyPayment(payload, matching);
1617
+ } catch (err) {
1618
+ const sc = err.statusCode;
1619
+ if (sc && sc >= 400 && sc < 500) return invalidPaymentVerification();
1620
+ throw err;
1621
+ }
1622
+ if (!verify.isValid) return invalidPaymentVerification();
1623
+ if (typeof verify.payer !== "string" || verify.payer.length === 0) {
1624
+ throw new Error("x402 verification succeeded without a payer address");
1625
+ }
1626
+ return {
1627
+ valid: true,
1628
+ payer: verify.payer,
1629
+ payload,
1630
+ requirements: matching
1631
+ };
1632
+ }
1633
+ function findVerifiableRequirements(server, requirements, payload) {
1634
+ const strictMatch = server.findMatchingRequirements(requirements, payload);
1635
+ if (strictMatch) {
1636
+ return payload.x402Version === 2 ? payload.accepted : strictMatch;
1637
+ }
1638
+ if (payload.x402Version !== 2) {
1639
+ return null;
1640
+ }
1641
+ const stableMatch = requirements.find(
1642
+ (requirement) => matchesStableFields(requirement, payload.accepted)
1643
+ );
1644
+ return stableMatch ? payload.accepted : null;
1645
+ }
1646
+ function matchesStableFields(requirement, accepted) {
1647
+ 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;
1648
+ }
1649
+ async function readPaymentPayload(request) {
1650
+ const paymentHeader = request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY);
1651
+ if (!paymentHeader) return null;
1652
+ const { decodePaymentSignatureHeader } = await import("@x402/core/http");
1653
+ return decodePaymentSignatureHeader(paymentHeader);
1654
+ }
1655
+ function invalidPaymentVerification() {
1656
+ return { valid: false, payload: null, requirements: null, payer: null };
1657
+ }
1658
+
1659
+ // src/protocols/x402/strategy.ts
1660
+ var x402Strategy = {
1661
+ protocol: "x402",
1662
+ detects(request) {
1663
+ return Boolean(
1664
+ request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
1665
+ );
1666
+ },
1667
+ async verify(args) {
1668
+ const { request, body, price, routeEntry, deps } = args;
1669
+ if (!deps.x402Server) {
1670
+ 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";
1671
+ console.error(`[router] ${routeEntry.key}: ${reason}`);
1672
+ return { ok: false, kind: "config", message: reason };
1673
+ }
1674
+ const accepts = await resolveX402Accepts(
1675
+ request,
1676
+ routeEntry,
1677
+ deps.x402Accepts,
1678
+ deps.payeeAddress,
1679
+ body
1680
+ );
1681
+ const verifyResult = await verifyX402Payment({
1682
+ server: deps.x402Server,
1683
+ request,
1684
+ price,
1685
+ accepts
1686
+ });
1687
+ if (!verifyResult?.valid) return { ok: false, kind: "invalid" };
1688
+ const wallet = normalizeWalletAddress(verifyResult.payer);
1689
+ const matchedNetwork = getRequirementNetwork(verifyResult.requirements, deps.network);
1690
+ const matchedRecipient = getRequirementRecipient(verifyResult.requirements);
1691
+ const payment = {
1692
+ protocol: "x402",
1693
+ status: "verified",
1694
+ payer: wallet,
1695
+ amount: price,
1696
+ network: matchedNetwork,
1697
+ ...matchedRecipient ? { recipient: matchedRecipient } : {}
1698
+ };
1699
+ return {
1700
+ ok: true,
1701
+ wallet,
1702
+ payment,
1703
+ token: {
1704
+ payload: verifyResult.payload,
1705
+ requirements: verifyResult.requirements
1607
1706
  }
1608
- let txHash = "";
1609
- const receiptHeader = mppResult.withReceipt(new Response()).headers.get(
1610
- "Payment-Receipt"
1707
+ };
1708
+ },
1709
+ async settle(args) {
1710
+ const { response, payment, token, deps } = args;
1711
+ const x402Token = token;
1712
+ try {
1713
+ const settle = await settleX402Payment(
1714
+ deps.x402Server,
1715
+ x402Token.payload,
1716
+ x402Token.requirements
1611
1717
  );
1612
- if (receiptHeader) {
1613
- try {
1614
- txHash = Receipt.deserialize(receiptHeader).reference;
1615
- } catch {
1616
- }
1718
+ if (!settle.result?.success) {
1719
+ const reason = settle.result?.errorReason || "x402 settlement returned success=false";
1720
+ const error = new Error(reason);
1721
+ error.errorReason = reason;
1722
+ throw error;
1617
1723
  }
1618
- pluginCtx.setVerifiedWallet(wallet);
1619
- firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1620
- protocol: "mpp",
1621
- payer: wallet,
1622
- amount: price,
1623
- network: "tempo:4217"
1724
+ response.headers.set(HEADERS.X402_PAYMENT_RESPONSE, settle.encoded);
1725
+ response.headers.set("Cache-Control", "private");
1726
+ const transaction = String(settle.result?.transaction ?? "");
1727
+ const settledPayment = {
1728
+ ...payment,
1729
+ status: "settled",
1730
+ ...transaction ? { transaction } : {}
1731
+ };
1732
+ return { ok: true, response, settledPayment };
1733
+ } catch (err) {
1734
+ const errObj = err;
1735
+ console.error("Settlement failed", {
1736
+ message: err instanceof Error ? err.message : String(err),
1737
+ route: args.routeEntry.key,
1738
+ network: payment.network,
1739
+ errorReason: errObj.errorReason,
1740
+ facilitatorStatus: errObj.response?.status,
1741
+ facilitatorBody: errObj.response?.data ?? errObj.response?.body
1624
1742
  });
1625
- const { response, rawResult } = await invoke(
1626
- request,
1627
- meta,
1628
- pluginCtx,
1629
- wallet,
1630
- account,
1631
- body.data,
1632
- {
1633
- protocol: "mpp",
1634
- status: "settled",
1635
- payer: wallet,
1636
- amount: price,
1637
- network: "tempo:4217",
1638
- recipient: deps.payeeAddress,
1639
- ...txHash ? { transaction: txHash } : {},
1640
- ...receiptHeader ? { receipt: receiptHeader } : {}
1641
- }
1642
- );
1643
- if (response.status < 400) {
1644
- if (routeEntry.siwxEnabled) {
1645
- try {
1646
- await deps.entitlementStore.grant(routeEntry.key, wallet);
1647
- } catch (error) {
1648
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1649
- level: "warn",
1650
- message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
1651
- route: routeEntry.key
1652
- });
1653
- }
1743
+ return { ok: false, error: err, failMessage: "Settlement failed" };
1744
+ }
1745
+ },
1746
+ async buildChallenge(args) {
1747
+ const { request, routeEntry, body, price, extensions, deps } = args;
1748
+ if (!deps.x402Server) return {};
1749
+ const accepts = await resolveX402Accepts(
1750
+ request,
1751
+ routeEntry,
1752
+ deps.x402Accepts,
1753
+ deps.payeeAddress,
1754
+ body
1755
+ );
1756
+ const { encoded } = await buildX402Challenge({
1757
+ server: deps.x402Server,
1758
+ routeEntry,
1759
+ request,
1760
+ price,
1761
+ accepts,
1762
+ facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
1763
+ extensions
1764
+ });
1765
+ return { headers: { [HEADERS.X402_PAYMENT_REQUIRED]: encoded } };
1766
+ }
1767
+ };
1768
+ function getRequirementNetwork(requirements, fallback) {
1769
+ const network = requirements?.network;
1770
+ return typeof network === "string" ? network : fallback;
1771
+ }
1772
+ function getRequirementRecipient(requirements) {
1773
+ const payTo = requirements?.payTo;
1774
+ return typeof payTo === "string" ? payTo : void 0;
1775
+ }
1776
+
1777
+ // src/protocols/detect.ts
1778
+ function detectProtocol(request) {
1779
+ if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
1780
+ return "x402";
1781
+ }
1782
+ const auth = request.headers.get(HEADERS.AUTHORIZATION);
1783
+ if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
1784
+ return "mpp";
1785
+ }
1786
+ if (request.headers.get(HEADERS.SIWX)) {
1787
+ return "siwx";
1788
+ }
1789
+ return null;
1790
+ }
1791
+
1792
+ // src/protocols/index.ts
1793
+ var STRATEGIES = {
1794
+ x402: x402Strategy,
1795
+ mpp: mppStrategy
1796
+ };
1797
+ function selectIncomingStrategy(request, allowed) {
1798
+ for (const name of allowed) {
1799
+ const strategy = STRATEGIES[name];
1800
+ if (strategy.detects(request)) return strategy;
1801
+ }
1802
+ return null;
1803
+ }
1804
+ function getAllowedStrategies(allowed) {
1805
+ return allowed.map((name) => STRATEGIES[name]);
1806
+ }
1807
+
1808
+ // src/pipeline/challenge.ts
1809
+ import { NextResponse as NextResponse4 } from "next/server";
1810
+ async function build402(ctx, pricing, body) {
1811
+ let challengePrice;
1812
+ try {
1813
+ challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
1814
+ } catch (err) {
1815
+ const message = errorMessage(err, "Price calculation failed");
1816
+ const errorResponse = NextResponse4.json(
1817
+ { success: false, error: message },
1818
+ { status: errorStatus(err, 500) }
1819
+ );
1820
+ firePluginResponse(ctx, errorResponse);
1821
+ return errorResponse;
1822
+ }
1823
+ const extensions = await buildChallengeExtensions(ctx);
1824
+ const response = new NextResponse4(null, {
1825
+ status: 402,
1826
+ headers: {
1827
+ "Content-Type": "application/json",
1828
+ "Cache-Control": "no-store"
1829
+ }
1830
+ });
1831
+ for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
1832
+ try {
1833
+ const contribution = await strategy.buildChallenge({
1834
+ request: ctx.request,
1835
+ routeEntry: ctx.routeEntry,
1836
+ body,
1837
+ price: challengePrice,
1838
+ extensions,
1839
+ deps: ctx.deps
1840
+ });
1841
+ if (contribution.headers) {
1842
+ for (const [name, value] of Object.entries(contribution.headers)) {
1843
+ response.headers.set(name, value);
1654
1844
  }
1655
- const receiptResponse = mppResult.withReceipt(response);
1656
- receiptResponse.headers.set("Cache-Control", "private");
1657
- firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1658
- protocol: "mpp",
1659
- payer: wallet,
1660
- transaction: txHash,
1661
- network: "tempo:4217"
1662
- });
1663
- finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
1664
- return receiptResponse;
1665
1845
  }
1666
- finalize(response, rawResult, meta, pluginCtx, body.data);
1667
- return response;
1846
+ } catch (err) {
1847
+ const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
1848
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
1849
+ level: "critical",
1850
+ message,
1851
+ route: ctx.routeEntry.key
1852
+ });
1853
+ if (strategy.protocol === "x402") {
1854
+ const errorResponse = NextResponse4.json(
1855
+ { success: false, error: message },
1856
+ { status: 500 }
1857
+ );
1858
+ firePluginResponse(ctx, errorResponse);
1859
+ return errorResponse;
1860
+ }
1668
1861
  }
1669
- return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1670
- };
1862
+ }
1863
+ firePluginResponse(ctx, response);
1864
+ return response;
1671
1865
  }
1672
- async function parseBody(request, routeEntry) {
1673
- if (!routeEntry.bodySchema) return { ok: true, data: void 0 };
1674
- const raw = await bufferBody(request);
1675
- const result = validateBody(raw, routeEntry.bodySchema);
1676
- if (result.success) return { ok: true, data: result.data };
1677
- return {
1678
- ok: false,
1679
- response: NextResponse2.json(
1680
- { success: false, error: result.error, issues: result.issues },
1681
- { status: 400 }
1682
- )
1866
+ async function buildChallengeExtensions(ctx) {
1867
+ const { routeEntry } = ctx;
1868
+ let extensions;
1869
+ try {
1870
+ const { z } = await import("zod");
1871
+ const { declareDiscoveryExtension } = await import("@x402/extensions/bazaar");
1872
+ const toJSON = (schema) => z.toJSONSchema(schema, {
1873
+ target: "draft-2020-12",
1874
+ unrepresentable: "any"
1875
+ });
1876
+ const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
1877
+ const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1878
+ if (inputSchema) {
1879
+ const config = {
1880
+ method: routeEntry.method,
1881
+ bodyType: routeEntry.bodySchema ? "json" : void 0,
1882
+ inputSchema
1883
+ };
1884
+ if (routeEntry.inputExample !== void 0) {
1885
+ config.input = routeEntry.inputExample;
1886
+ }
1887
+ if (outputSchema && routeEntry.outputExample !== void 0) {
1888
+ config.output = { schema: outputSchema, example: routeEntry.outputExample };
1889
+ }
1890
+ extensions = declareDiscoveryExtension(config);
1891
+ }
1892
+ } catch (err) {
1893
+ firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
1894
+ level: "warn",
1895
+ message: `Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`,
1896
+ route: routeEntry.key
1897
+ });
1898
+ }
1899
+ if (routeEntry.siwxEnabled) {
1900
+ try {
1901
+ const siwxExtension = await buildSIWXExtension();
1902
+ if (siwxExtension && typeof siwxExtension === "object" && !Array.isArray(siwxExtension)) {
1903
+ extensions = {
1904
+ ...extensions ?? {},
1905
+ ...siwxExtension
1906
+ };
1907
+ }
1908
+ } catch {
1909
+ }
1910
+ }
1911
+ return extensions;
1912
+ }
1913
+
1914
+ // src/pipeline/flows/paid.ts
1915
+ async function runPaidFlow(ctx) {
1916
+ const { request, routeEntry, deps } = ctx;
1917
+ let account = void 0;
1918
+ if (routeEntry.apiKeyResolver) {
1919
+ const apiKeyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
1920
+ if (!apiKeyResult.valid) return fail(ctx, 401, "Invalid or missing API key");
1921
+ account = apiKeyResult.account;
1922
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
1923
+ authMode: "apiKey",
1924
+ wallet: null,
1925
+ route: routeEntry.key,
1926
+ account
1927
+ });
1928
+ }
1929
+ const alertFn = (level, message, meta) => {
1930
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
1931
+ level,
1932
+ message,
1933
+ route: routeEntry.key,
1934
+ meta
1935
+ });
1683
1936
  };
1937
+ const pricing = selectPricing(routeEntry.pricing, {
1938
+ alert: alertFn,
1939
+ maxPrice: routeEntry.maxPrice,
1940
+ minPrice: routeEntry.minPrice,
1941
+ route: routeEntry.key
1942
+ });
1943
+ const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
1944
+ let earlyBody = void 0;
1945
+ if (shouldParseBodyEarly(incomingStrategy, routeEntry, pricing)) {
1946
+ const earlyClone = request.clone();
1947
+ const earlyResult = await parseBody(earlyClone, routeEntry);
1948
+ if (earlyResult.ok) {
1949
+ earlyBody = earlyResult.data;
1950
+ const validateErr2 = await runValidate(ctx, earlyBody);
1951
+ if (validateErr2) return validateErr2;
1952
+ } else {
1953
+ firePluginResponse(ctx, earlyResult.response);
1954
+ return earlyResult.response;
1955
+ }
1956
+ }
1957
+ const siwxFastPath = await trySiwxFastPath(ctx, account);
1958
+ if (siwxFastPath) return siwxFastPath;
1959
+ if (!incomingStrategy) {
1960
+ const initError = protocolInitError(routeEntry, deps);
1961
+ if (initError) return fail(ctx, 500, initError);
1962
+ return build402(ctx, pricing, earlyBody);
1963
+ }
1964
+ const body = await parseBody(request, routeEntry);
1965
+ if (!body.ok) {
1966
+ firePluginResponse(ctx, body.response);
1967
+ return body.response;
1968
+ }
1969
+ const validateErr = await runValidate(ctx, body.data);
1970
+ if (validateErr) return validateErr;
1971
+ if (!pricing) {
1972
+ return fail(ctx, 500, "Pricing not configured", body.data);
1973
+ }
1974
+ let price;
1975
+ try {
1976
+ price = await pricing.quote(body.data);
1977
+ } catch (err) {
1978
+ return fail(
1979
+ ctx,
1980
+ errorStatus(err, 500),
1981
+ errorMessage(err, "Price calculation failed"),
1982
+ body.data
1983
+ );
1984
+ }
1985
+ const verifyOutcome = await incomingStrategy.verify({
1986
+ request,
1987
+ body: body.data,
1988
+ price,
1989
+ routeEntry,
1990
+ deps
1991
+ });
1992
+ if (verifyOutcome.ok === false) {
1993
+ if (verifyOutcome.kind === "config") {
1994
+ return fail(ctx, 500, verifyOutcome.message, body.data);
1995
+ }
1996
+ return build402(ctx, pricing, body.data);
1997
+ }
1998
+ ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
1999
+ firePluginHook(deps.plugin, "onPaymentVerified", ctx.pluginCtx, {
2000
+ protocol: incomingStrategy.protocol,
2001
+ payer: verifyOutcome.wallet,
2002
+ amount: price,
2003
+ network: verifyOutcome.payment.network
2004
+ });
2005
+ const result = await invoke(ctx, verifyOutcome.wallet, account, body.data, verifyOutcome.payment);
2006
+ const settleScope = {
2007
+ wallet: verifyOutcome.wallet,
2008
+ account,
2009
+ body: body.data,
2010
+ payment: verifyOutcome.payment,
2011
+ response: result.response,
2012
+ rawResult: result.rawResult,
2013
+ handlerError: result.handlerError
2014
+ };
2015
+ if (verifyOutcome.alreadySettled) {
2016
+ if (result.response.status >= 400) {
2017
+ const settledScope = settleScope;
2018
+ await runSettledHandlerError(ctx, settledScope);
2019
+ return finalize(ctx, result.response, result.rawResult, body.data);
2020
+ }
2021
+ return settleAndFinalize({
2022
+ ctx,
2023
+ strategy: incomingStrategy,
2024
+ verifyOutcome,
2025
+ scope: settleScope,
2026
+ rawResult: result.rawResult,
2027
+ body: body.data
2028
+ });
2029
+ }
2030
+ if (result.response.status >= 400) {
2031
+ return finalize(ctx, result.response, result.rawResult, body.data);
2032
+ }
2033
+ const beforeErr = await runBeforeSettle(ctx, settleScope);
2034
+ if (beforeErr) return beforeErr;
2035
+ return settleAndFinalize({
2036
+ ctx,
2037
+ strategy: incomingStrategy,
2038
+ verifyOutcome,
2039
+ scope: settleScope,
2040
+ rawResult: result.rawResult,
2041
+ body: body.data,
2042
+ onSettleError: async (error, failMessage) => {
2043
+ await runSettlementError(ctx, settleScope, error, "settle");
2044
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
2045
+ level: "critical",
2046
+ message: `${incomingStrategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`,
2047
+ route: routeEntry.key
2048
+ });
2049
+ }
2050
+ });
2051
+ }
2052
+
2053
+ // src/pipeline/flows/siwx-only.ts
2054
+ import { NextResponse as NextResponse5 } from "next/server";
2055
+
2056
+ // src/auth/nonce.ts
2057
+ var SIWX_CHALLENGE_EXPIRY_MS = 5 * 60 * 1e3;
2058
+ var MemoryNonceStore = class {
2059
+ seen = /* @__PURE__ */ new Map();
2060
+ async check(nonce) {
2061
+ this.evict();
2062
+ if (this.seen.has(nonce)) return false;
2063
+ this.seen.set(nonce, Date.now() + SIWX_CHALLENGE_EXPIRY_MS);
2064
+ return true;
2065
+ }
2066
+ evict() {
2067
+ const now = Date.now();
2068
+ for (const [n, exp] of this.seen) {
2069
+ if (exp < now) this.seen.delete(n);
2070
+ }
2071
+ }
2072
+ };
2073
+ function detectRedisClientType(client) {
2074
+ if (!client || typeof client !== "object") {
2075
+ throw new Error(
2076
+ "createRedisNonceStore requires a Redis client. Supported: @upstash/redis, ioredis. Pass your Redis client instance as the first argument."
2077
+ );
2078
+ }
2079
+ if ("options" in client && "status" in client) {
2080
+ return "ioredis";
2081
+ }
2082
+ const constructor = client.constructor?.name;
2083
+ if (constructor === "Redis" && "url" in client) {
2084
+ return "upstash";
2085
+ }
2086
+ if (typeof client.set === "function") {
2087
+ return "upstash";
2088
+ }
2089
+ throw new Error(
2090
+ "Unrecognized Redis client. Supported: @upstash/redis, ioredis. If using a different client, implement NonceStore interface directly."
2091
+ );
1684
2092
  }
1685
- function buildMeta(request, routeEntry) {
2093
+ function createRedisNonceStore(client, opts) {
2094
+ const prefix = opts?.prefix ?? "siwx:nonce:";
2095
+ const ttlSeconds = Math.ceil((opts?.ttlMs ?? SIWX_CHALLENGE_EXPIRY_MS) / 1e3);
2096
+ const clientType = detectRedisClientType(client);
1686
2097
  return {
1687
- requestId: crypto.randomUUID(),
1688
- method: request.method,
1689
- route: routeEntry.key,
1690
- origin: request.headers.get("origin") ?? new URL(request.url).origin,
1691
- referer: request.headers.get("referer"),
1692
- walletAddress: request.headers.get("X-Wallet-Address"),
1693
- clientId: request.headers.get("X-Client-ID"),
1694
- sessionId: request.headers.get("X-Session-ID"),
1695
- contentType: request.headers.get("content-type"),
1696
- headers: Object.fromEntries(request.headers.entries()),
1697
- startTime: Date.now()
2098
+ async check(nonce) {
2099
+ const key = `${prefix}${nonce}`;
2100
+ if (clientType === "upstash") {
2101
+ const redis = client;
2102
+ const result = await redis.set(key, "1", { ex: ttlSeconds, nx: true });
2103
+ return result !== null;
2104
+ }
2105
+ if (clientType === "ioredis") {
2106
+ const redis = client;
2107
+ const result = await redis.set(key, "1", "EX", ttlSeconds, "NX");
2108
+ return result === "OK";
2109
+ }
2110
+ throw new Error("Unknown Redis client type");
2111
+ }
1698
2112
  };
1699
2113
  }
1700
- function parseQuery(request, routeEntry) {
1701
- if (!routeEntry.querySchema) return void 0;
1702
- const params = Object.fromEntries(request.nextUrl.searchParams.entries());
1703
- const result = routeEntry.querySchema.safeParse(params);
1704
- return result.success ? result.data : params;
2114
+
2115
+ // src/protocols/mpp/siwx-mode.ts
2116
+ import { Credential as Credential2 } from "mppx";
2117
+ async function verifyMppSiwx(request, mppx) {
2118
+ const result = await mppx.charge({ amount: "0" })(request);
2119
+ if (result.status === 402) {
2120
+ return { valid: false, challenge: result.challenge };
2121
+ }
2122
+ const credential = Credential2.fromRequest(request);
2123
+ const rawSource = credential?.source ?? "";
2124
+ const wallet = walletFromDid(rawSource);
2125
+ return { valid: true, wallet, withReceipt: result.withReceipt };
1705
2126
  }
1706
- async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta) {
1707
- try {
1708
- let price = await resolvePrice(routeEntry.pricing, bodyData);
1709
- if (routeEntry.maxPrice) {
1710
- const calculated = parseFloat(price);
1711
- const max = parseFloat(routeEntry.maxPrice);
1712
- if (!Number.isFinite(calculated) || calculated > max) {
1713
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1714
- level: "warn",
1715
- message: `Price ${price} exceeds maxPrice ${routeEntry.maxPrice}, capping`,
1716
- route: routeEntry.key,
1717
- meta: { calculated: price, maxPrice: routeEntry.maxPrice, body: bodyData }
1718
- });
1719
- price = routeEntry.maxPrice;
1720
- }
1721
- }
1722
- return { price };
1723
- } catch (err) {
1724
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1725
- level: "error",
1726
- message: `Pricing function failed: ${err instanceof Error ? err.message : String(err)}`,
1727
- route: routeEntry.key,
1728
- meta: { error: err instanceof Error ? err.stack : String(err), body: bodyData }
1729
- });
1730
- if (routeEntry.maxPrice) {
1731
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1732
- level: "warn",
1733
- message: `Using maxPrice ${routeEntry.maxPrice} as fallback after pricing error`,
1734
- route: routeEntry.key
1735
- });
1736
- return { price: routeEntry.maxPrice };
2127
+
2128
+ // src/pipeline/flows/siwx-only.ts
2129
+ async function runSiwxOnlyFlow(ctx) {
2130
+ const { request, routeEntry, deps } = ctx;
2131
+ if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get(HEADERS.SIWX)) {
2132
+ const earlyClone = request.clone();
2133
+ const earlyBody = await parseBody(earlyClone, routeEntry);
2134
+ if (earlyBody.ok) {
2135
+ const validateErr = await runValidate(ctx, earlyBody.data);
2136
+ if (validateErr) return validateErr;
1737
2137
  } else {
1738
- const errorResponse = NextResponse2.json(
1739
- { success: false, error: "Price calculation failed" },
1740
- { status: 500 }
1741
- );
1742
- firePluginResponse(deps, pluginCtx, meta, errorResponse);
1743
- return { error: errorResponse };
2138
+ firePluginResponse(ctx, earlyBody.response);
2139
+ return earlyBody.response;
1744
2140
  }
1745
2141
  }
1746
- }
1747
- async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1748
- const response = new NextResponse2(null, {
1749
- status: 402,
1750
- headers: {
1751
- "Content-Type": "application/json",
1752
- "Cache-Control": "no-store"
1753
- }
1754
- });
1755
- let challengePrice;
1756
- if (bodyData !== void 0 && typeof routeEntry.pricing !== "string" && routeEntry.pricing != null) {
1757
- const result = await resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta);
1758
- if ("error" in result) return result.error;
1759
- challengePrice = result.price;
1760
- } else if (routeEntry.maxPrice) {
1761
- challengePrice = routeEntry.maxPrice;
1762
- } else if (routeEntry.pricing) {
2142
+ const siwxHeader = request.headers.get(HEADERS.SIWX);
2143
+ const protocol = detectProtocol(request);
2144
+ if (!siwxHeader && protocol === "mpp" && deps.mppx) {
2145
+ let mppSiwxResult;
1763
2146
  try {
1764
- challengePrice = resolveMaxPrice(routeEntry.pricing);
1765
- } catch {
1766
- challengePrice = "0";
2147
+ mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
2148
+ } catch (err) {
2149
+ const message = err instanceof Error ? err.message : String(err);
2150
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
2151
+ level: "critical",
2152
+ message: `MPP SIWX verification failed: ${message}`,
2153
+ route: routeEntry.key
2154
+ });
2155
+ return fail(ctx, 500, `MPP SIWX verification failed: ${message}`);
2156
+ }
2157
+ if (mppSiwxResult.valid) {
2158
+ ctx.pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
2159
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
2160
+ authMode: "siwx",
2161
+ wallet: mppSiwxResult.wallet,
2162
+ route: routeEntry.key
2163
+ });
2164
+ const authResponse = await runHandlerOnly(ctx, mppSiwxResult.wallet, void 0);
2165
+ if (authResponse.status < 400) {
2166
+ return mppSiwxResult.withReceipt(authResponse);
2167
+ }
2168
+ return authResponse;
1767
2169
  }
1768
- } else {
1769
- challengePrice = "0";
1770
2170
  }
1771
- let extensions;
2171
+ if (!siwxHeader) {
2172
+ return buildSiwxChallenge(ctx);
2173
+ }
2174
+ const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
2175
+ if (!siwx.valid) {
2176
+ const response = NextResponse5.json(
2177
+ { error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
2178
+ { status: 402 }
2179
+ );
2180
+ firePluginResponse(ctx, response);
2181
+ return response;
2182
+ }
2183
+ const wallet = normalizeWalletAddress(siwx.wallet);
2184
+ ctx.pluginCtx.setVerifiedWallet(wallet);
2185
+ firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
2186
+ authMode: "siwx",
2187
+ wallet,
2188
+ route: routeEntry.key
2189
+ });
2190
+ return runHandlerOnly(ctx, wallet, void 0);
2191
+ }
2192
+ async function buildSiwxChallenge(ctx) {
2193
+ const { request, routeEntry, deps } = ctx;
2194
+ const url = new URL(request.url);
2195
+ const nonce = crypto.randomUUID().replace(/-/g, "");
2196
+ const supportedChains = getSupportedChains(deps.x402Accepts, deps.network);
2197
+ const primaryChain = supportedChains[0];
2198
+ const siwxInfo = {
2199
+ domain: url.hostname,
2200
+ uri: request.url,
2201
+ version: "1",
2202
+ chainId: primaryChain.chainId,
2203
+ type: primaryChain.type,
2204
+ nonce,
2205
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
2206
+ expirationTime: new Date(Date.now() + SIWX_CHALLENGE_EXPIRY_MS).toISOString(),
2207
+ statement: "Sign in to verify your wallet identity"
2208
+ };
2209
+ let siwxSchema;
1772
2210
  try {
1773
- const { z } = await import("zod");
1774
- const { declareDiscoveryExtension } = await import("@x402/extensions/bazaar");
1775
- const toJSON = (schema) => z.toJSONSchema(schema, {
1776
- target: "draft-2020-12",
1777
- unrepresentable: "any"
1778
- });
1779
- const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
1780
- const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
1781
- if (inputSchema) {
1782
- const config = {
1783
- method: routeEntry.method,
1784
- bodyType: routeEntry.bodySchema ? "json" : void 0,
1785
- inputSchema
1786
- };
1787
- if (routeEntry.inputExample !== void 0) {
1788
- config.input = routeEntry.inputExample;
1789
- }
1790
- if (outputSchema && routeEntry.outputExample !== void 0) {
1791
- config.output = { schema: outputSchema, example: routeEntry.outputExample };
2211
+ siwxSchema = await buildSIWXExtension();
2212
+ } catch {
2213
+ }
2214
+ const paymentRequired = {
2215
+ x402Version: 2,
2216
+ error: "SIWX authentication required",
2217
+ resource: {
2218
+ url: request.url,
2219
+ description: routeEntry.description ?? "SIWX-protected endpoint",
2220
+ mimeType: "application/json"
2221
+ },
2222
+ accepts: [],
2223
+ extensions: {
2224
+ "sign-in-with-x": {
2225
+ info: siwxInfo,
2226
+ // Required by MCP tools at the top level for chain detection.
2227
+ supportedChains,
2228
+ ...siwxSchema ? { schema: siwxSchema } : {}
1792
2229
  }
1793
- extensions = declareDiscoveryExtension(config);
1794
2230
  }
2231
+ };
2232
+ let encoded;
2233
+ try {
2234
+ const { encodePaymentRequiredHeader } = await import("@x402/core/http");
2235
+ encoded = encodePaymentRequiredHeader(paymentRequired);
1795
2236
  } catch (err) {
1796
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
2237
+ firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
1797
2238
  level: "warn",
1798
- message: `Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`,
2239
+ message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
1799
2240
  route: routeEntry.key
1800
2241
  });
1801
2242
  }
1802
- if (routeEntry.siwxEnabled) {
2243
+ const response = new NextResponse5(JSON.stringify(paymentRequired), {
2244
+ status: 402,
2245
+ headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
2246
+ });
2247
+ if (encoded) response.headers.set(HEADERS.X402_PAYMENT_REQUIRED, encoded);
2248
+ if (deps.mppx) {
1803
2249
  try {
1804
- const siwxExtension = await buildSIWXExtension();
1805
- if (siwxExtension && typeof siwxExtension === "object" && !Array.isArray(siwxExtension)) {
1806
- extensions = {
1807
- ...extensions ?? {},
1808
- ...siwxExtension
1809
- };
2250
+ const mppChallenge = await deps.mppx.charge({ amount: "0" })(request);
2251
+ if (mppChallenge.status === 402) {
2252
+ const wwwAuth = mppChallenge.challenge.headers.get(HEADERS.WWW_AUTHENTICATE);
2253
+ if (wwwAuth) response.headers.set(HEADERS.WWW_AUTHENTICATE, wwwAuth);
1810
2254
  }
1811
2255
  } catch {
1812
2256
  }
1813
2257
  }
1814
- if (routeEntry.protocols.includes("x402") && deps.x402Server) {
1815
- try {
1816
- const accepts = await resolveX402Accepts(
1817
- request,
1818
- routeEntry,
1819
- deps.x402Accepts,
1820
- deps.payeeAddress,
1821
- bodyData
1822
- );
1823
- const { encoded } = await buildX402Challenge({
1824
- server: deps.x402Server,
1825
- routeEntry,
1826
- request,
1827
- price: challengePrice,
1828
- accepts,
1829
- facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
1830
- extensions
1831
- });
1832
- response.headers.set("PAYMENT-REQUIRED", encoded);
1833
- } catch (err) {
1834
- const message = `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`;
1835
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1836
- level: "critical",
1837
- message,
1838
- route: routeEntry.key
1839
- });
1840
- const errorResponse = NextResponse2.json({ success: false, error: message }, { status: 500 });
1841
- firePluginResponse(deps, pluginCtx, meta, errorResponse);
1842
- return errorResponse;
1843
- }
1844
- }
1845
- if (routeEntry.protocols.includes("mpp") && deps.mppx) {
1846
- try {
1847
- const result = await deps.mppx.charge({ amount: challengePrice })(request);
1848
- if (result.status === 402) {
1849
- const wwwAuth = result.challenge.headers.get("WWW-Authenticate");
1850
- if (wwwAuth) response.headers.set("WWW-Authenticate", wwwAuth);
1851
- }
1852
- } catch (err) {
1853
- firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1854
- level: "critical",
1855
- message: `MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
1856
- route: routeEntry.key
1857
- });
1858
- }
1859
- }
1860
- firePluginResponse(deps, pluginCtx, meta, response);
2258
+ firePluginResponse(ctx, response);
1861
2259
  return response;
1862
2260
  }
1863
- function firePluginResponse(deps, pluginCtx, meta, response, requestBody, responseBody) {
1864
- firePluginHook(deps.plugin, "onResponse", pluginCtx, {
1865
- statusCode: response.status,
1866
- statusText: response.statusText,
1867
- duration: Date.now() - meta.startTime,
1868
- contentType: response.headers.get("content-type"),
1869
- headers: Object.fromEntries(response.headers.entries()),
1870
- requestBody,
1871
- responseBody
1872
- });
1873
- if (response.status >= 400 && response.status !== 402) {
1874
- firePluginHook(deps.plugin, "onError", pluginCtx, {
1875
- status: response.status,
1876
- message: response.statusText || `HTTP ${response.status}`,
1877
- settled: false
1878
- });
2261
+ function siwxSignatureType(network) {
2262
+ return network.startsWith("solana:") ? "ed25519" : "eip191";
2263
+ }
2264
+ function getSupportedChains(x402Accepts, fallbackNetwork) {
2265
+ const seen = /* @__PURE__ */ new Set();
2266
+ const chains = [];
2267
+ for (const accept of x402Accepts) {
2268
+ if (accept.network && !seen.has(accept.network)) {
2269
+ seen.add(accept.network);
2270
+ chains.push({ chainId: accept.network, type: siwxSignatureType(accept.network) });
2271
+ }
1879
2272
  }
2273
+ if (chains.length === 0) {
2274
+ chains.push({ chainId: fallbackNetwork, type: siwxSignatureType(fallbackNetwork) });
2275
+ }
2276
+ return chains;
1880
2277
  }
1881
- function computeQuotaLevel(remaining, warn, critical) {
1882
- if (remaining === null) return "healthy";
1883
- if (critical !== void 0 && remaining <= critical) return "critical";
1884
- if (warn !== void 0 && remaining <= warn) return "warn";
1885
- return "healthy";
2278
+
2279
+ // src/pipeline/flows/unprotected.ts
2280
+ async function runUnprotectedFlow(ctx) {
2281
+ return runHandlerOnly(ctx, null, void 0);
1886
2282
  }
1887
- function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx) {
1888
- const { providerName, providerConfig } = routeEntry;
1889
- if (!providerName || !providerConfig?.extractQuota) return;
1890
- if (response.status >= 400) return;
1891
- try {
1892
- const quota = providerConfig.extractQuota(handlerResult, response.headers);
1893
- if (!quota) return;
1894
- const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
1895
- const overage = providerConfig.overage ?? "same-rate";
1896
- const event = {
1897
- provider: providerName,
1898
- route: routeEntry.key,
1899
- remaining: quota.remaining,
1900
- limit: quota.limit,
1901
- spend: quota.spend,
1902
- level,
1903
- overage,
1904
- message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
1905
- };
1906
- firePluginHook(deps.plugin, "onProviderQuota", pluginCtx, event);
1907
- } catch {
1908
- }
2283
+
2284
+ // src/orchestrate.ts
2285
+ function createRequestHandler(routeEntry, handler, deps) {
2286
+ return async (request) => {
2287
+ await deps.initPromise;
2288
+ const ctx = preflight(routeEntry, handler, deps, request);
2289
+ if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
2290
+ if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
2291
+ if (routeEntry.pricing) return runPaidFlow(ctx);
2292
+ if (routeEntry.apiKeyResolver) return runApiKeyOnlyFlow(ctx);
2293
+ return runUnprotectedFlow(ctx);
2294
+ };
1909
2295
  }
1910
2296
 
1911
2297
  // src/validate-examples.ts
1912
2298
  function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
1913
- if (bodySchema && !hasInputExample) {
1914
- throw new Error(
1915
- `route '${key}': .body() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample body to advertise.`
1916
- );
1917
- }
1918
- if (querySchema && !hasInputExample) {
1919
- throw new Error(
1920
- `route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
1921
- );
2299
+ const inputSchema = bodySchema ?? querySchema;
2300
+ if (hasInputExample && !inputSchema) {
2301
+ throw new Error(`route '${key}': .inputExample() requires .body() or .query()`);
1922
2302
  }
1923
- if (outputSchema && !hasOutputExample) {
1924
- throw new Error(
1925
- `route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
1926
- );
2303
+ if (hasOutputExample && !outputSchema) {
2304
+ throw new Error(`route '${key}': .outputExample() requires .output()`);
1927
2305
  }
1928
- const inputSchema = bodySchema ?? querySchema;
1929
2306
  if (inputSchema && hasInputExample) {
1930
2307
  const result = inputSchema.safeParse(inputExample);
1931
2308
  if (!result.success) {
@@ -1999,6 +2376,8 @@ var RouteBuilder = class {
1999
2376
  /** @internal */
2000
2377
  _validateFn;
2001
2378
  /** @internal */
2379
+ _settlement;
2380
+ /** @internal */
2002
2381
  _mppInfo;
2003
2382
  constructor(key, registry, deps) {
2004
2383
  this._key = key;
@@ -2012,11 +2391,21 @@ var RouteBuilder = class {
2012
2391
  return next;
2013
2392
  }
2014
2393
  paid(pricing, options) {
2394
+ if (this._authMode === "unprotected") {
2395
+ throw new Error(
2396
+ `route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
2397
+ );
2398
+ }
2399
+ if (this._pricing !== void 0) {
2400
+ throw new Error(
2401
+ `route '${this._key}': Cannot call .paid() more than once on the same route.`
2402
+ );
2403
+ }
2015
2404
  const next = this.fork();
2016
2405
  next._authMode = "paid";
2017
2406
  next._pricing = pricing;
2018
2407
  if (options?.protocols) {
2019
- next._protocols = options.protocols;
2408
+ next._protocols = [...options.protocols];
2020
2409
  } else if (next._protocols.length === 0) {
2021
2410
  next._protocols = ["x402"];
2022
2411
  }
@@ -2081,6 +2470,16 @@ var RouteBuilder = class {
2081
2470
  return next;
2082
2471
  }
2083
2472
  unprotected() {
2473
+ if (this._authMode && this._authMode !== "unprotected") {
2474
+ throw new Error(
2475
+ `route '${this._key}': Cannot combine .unprotected() and .${this._authMode}() on the same route.`
2476
+ );
2477
+ }
2478
+ if (this._pricing) {
2479
+ throw new Error(
2480
+ `route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
2481
+ );
2482
+ }
2084
2483
  const next = this.fork();
2085
2484
  next._authMode = "unprotected";
2086
2485
  next._protocols = [];
@@ -2095,32 +2494,43 @@ var RouteBuilder = class {
2095
2494
  next._providerConfig = config ?? {};
2096
2495
  return next;
2097
2496
  }
2098
- // -------------------------------------------------------------------------
2099
- // Schema methods
2100
- // -------------------------------------------------------------------------
2101
- body(schema) {
2497
+ body(schema, example) {
2102
2498
  const next = this.fork();
2103
2499
  next._bodySchema = schema;
2500
+ if (example !== void 0) {
2501
+ next._inputExample = example;
2502
+ next._hasInputExample = true;
2503
+ }
2104
2504
  return next;
2105
2505
  }
2106
- query(schema) {
2506
+ query(schema, example) {
2107
2507
  const next = this.fork();
2108
2508
  next._querySchema = schema;
2509
+ if (example !== void 0) {
2510
+ next._inputExample = example;
2511
+ next._hasInputExample = true;
2512
+ }
2109
2513
  next._method = "GET";
2110
2514
  return next;
2111
2515
  }
2112
- output(schema) {
2516
+ output(schema, example) {
2113
2517
  const next = this.fork();
2114
2518
  next._outputSchema = schema;
2519
+ if (example !== void 0) {
2520
+ next._outputExample = example;
2521
+ next._hasOutputExample = true;
2522
+ }
2115
2523
  return next;
2116
2524
  }
2117
2525
  /**
2118
2526
  * Provide a conforming example of the request input (body or query params).
2119
2527
  *
2120
- * **Required** whenever `.body()` or `.query()` is set enforced at compile time via
2121
- * `.handler()` overloads, and at route-registration time via Zod validation of the
2122
- * example against the schema. The example is embedded in the bazaar discovery extension
2123
- * so indexers can advertise a working sample call.
2528
+ * Optional. When provided, the example is validated against the request schema
2529
+ * at route registration and embedded in the bazaar discovery extension so
2530
+ * indexers can advertise a working sample call.
2531
+ *
2532
+ * For the common case, pass the example directly to `.body(schema, example)` or
2533
+ * `.query(schema, example)` instead.
2124
2534
  *
2125
2535
  * @example
2126
2536
  * ```ts
@@ -2140,10 +2550,11 @@ var RouteBuilder = class {
2140
2550
  /**
2141
2551
  * Provide a conforming example of the response output.
2142
2552
  *
2143
- * **Required** whenever `.output()` is set enforced at compile time via `.handler()`
2144
- * overloads, and at route-registration time via Zod validation of the example against
2145
- * the schema. The example is embedded in the bazaar discovery extension so indexers
2146
- * can advertise the response shape.
2553
+ * Optional. When provided, the example is validated against the output schema
2554
+ * at route registration and embedded in the bazaar discovery extension so
2555
+ * indexers can advertise the response shape.
2556
+ *
2557
+ * For the common case, pass the example directly to `.output(schema, example)` instead.
2147
2558
  *
2148
2559
  * Accepts any JSON value (objects, arrays, or primitives) — top-level array
2149
2560
  * or primitive responses (e.g. `z.array(...)`) are supported alongside the
@@ -2216,15 +2627,39 @@ var RouteBuilder = class {
2216
2627
  return next;
2217
2628
  }
2218
2629
  // -------------------------------------------------------------------------
2630
+ // Settlement lifecycle
2631
+ // -------------------------------------------------------------------------
2632
+ /**
2633
+ * Add route-specific settlement hooks.
2634
+ *
2635
+ * `beforeSettle` runs after a successful handler response but before
2636
+ * router-controlled settlement/broadcast, so it can still prevent the charge
2637
+ * for x402 and MPP transaction-payload flows. `afterSettle` runs after
2638
+ * settlement and is intended for durable ledgers or app-owned refund queues.
2639
+ */
2640
+ settlement(lifecycle) {
2641
+ const next = this.fork();
2642
+ next._settlement = lifecycle;
2643
+ return next;
2644
+ }
2645
+ // -------------------------------------------------------------------------
2219
2646
  // Terminal method
2220
2647
  // -------------------------------------------------------------------------
2221
2648
  handler(fn) {
2222
2649
  const handlerFn = fn;
2650
+ if (!this._authMode) {
2651
+ throw new Error(
2652
+ `route '${this._key}': Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()`
2653
+ );
2654
+ }
2223
2655
  if (this._validateFn && !this._bodySchema) {
2224
2656
  throw new Error(
2225
2657
  `route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
2226
2658
  );
2227
2659
  }
2660
+ if (this._settlement && !this._pricing) {
2661
+ throw new Error(`route '${this._key}': .settlement() requires a paid route`);
2662
+ }
2228
2663
  validateExamples(
2229
2664
  this._key,
2230
2665
  this._bodySchema,
@@ -2256,6 +2691,7 @@ var RouteBuilder = class {
2256
2691
  providerName: this._providerName,
2257
2692
  providerConfig: this._providerConfig,
2258
2693
  validateFn: this._validateFn,
2694
+ settlement: this._settlement,
2259
2695
  mppInfo: this._mppInfo
2260
2696
  };
2261
2697
  this._registry.register(entry);
@@ -2330,7 +2766,7 @@ function createRedisEntitlementStore(client, options) {
2330
2766
  }
2331
2767
 
2332
2768
  // src/discovery/well-known.ts
2333
- import { NextResponse as NextResponse3 } from "next/server";
2769
+ import { NextResponse as NextResponse6 } from "next/server";
2334
2770
 
2335
2771
  // src/discovery/utils/guidance.ts
2336
2772
  async function resolveGuidance(discovery) {
@@ -2374,7 +2810,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
2374
2810
  if (instructions) {
2375
2811
  body.instructions = instructions;
2376
2812
  }
2377
- return NextResponse3.json(body, {
2813
+ return NextResponse6.json(body, {
2378
2814
  headers: {
2379
2815
  "Access-Control-Allow-Origin": "*",
2380
2816
  "Access-Control-Allow-Methods": "GET",
@@ -2391,13 +2827,14 @@ function toDiscoveryResource(method, url, mode) {
2391
2827
  }
2392
2828
 
2393
2829
  // src/discovery/openapi.ts
2394
- import { NextResponse as NextResponse4 } from "next/server";
2830
+ init_constants();
2831
+ import { NextResponse as NextResponse7 } from "next/server";
2395
2832
  function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2396
2833
  const normalizedBase = baseUrl.replace(/\/+$/, "");
2397
2834
  let cached = null;
2398
2835
  let validated = false;
2399
2836
  return async (_request) => {
2400
- if (cached) return NextResponse4.json(cached);
2837
+ if (cached) return NextResponse7.json(cached);
2401
2838
  if (!validated && pricesKeys) {
2402
2839
  registry.validate(pricesKeys);
2403
2840
  validated = true;
@@ -2422,14 +2859,14 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2422
2859
  securitySchemes.siwx = {
2423
2860
  type: "apiKey",
2424
2861
  in: "header",
2425
- name: "SIGN-IN-WITH-X"
2862
+ name: HEADERS.SIWX
2426
2863
  };
2427
2864
  }
2428
2865
  if (requiresApiKeyScheme) {
2429
2866
  securitySchemes.apiKey = {
2430
2867
  type: "apiKey",
2431
2868
  in: "header",
2432
- name: "X-API-Key"
2869
+ name: HEADERS.API_KEY
2433
2870
  };
2434
2871
  }
2435
2872
  const discoveryMetadata = {};
@@ -2460,7 +2897,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2460
2897
  paths
2461
2898
  };
2462
2899
  cached = createDocument(openApiDocument);
2463
- return NextResponse4.json(cached);
2900
+ return NextResponse7.json(cached);
2464
2901
  };
2465
2902
  }
2466
2903
  function deriveTag(routeKey) {
@@ -2533,7 +2970,7 @@ function toProtocolObject(protocol, mppInfo) {
2533
2970
  mpp: {
2534
2971
  method: mppInfo?.method ?? "tempo",
2535
2972
  intent: mppInfo?.intent ?? "charge",
2536
- currency: mppInfo?.currency ?? "0x20c0000000000000000000000000000000000001"
2973
+ currency: mppInfo?.currency ?? TEMPO_USDC_CURRENCY
2537
2974
  }
2538
2975
  };
2539
2976
  }
@@ -2583,11 +3020,11 @@ function buildPricingInfo(entry) {
2583
3020
  }
2584
3021
 
2585
3022
  // src/discovery/llms-txt.ts
2586
- import { NextResponse as NextResponse5 } from "next/server";
3023
+ import { NextResponse as NextResponse8 } from "next/server";
2587
3024
  function createLlmsTxtHandler(discovery) {
2588
3025
  return async (_request) => {
2589
3026
  const guidance = await resolveGuidance(discovery) ?? "";
2590
- return new NextResponse5(guidance, {
3027
+ return new NextResponse8(guidance, {
2591
3028
  headers: {
2592
3029
  "Content-Type": "text/plain; charset=utf-8",
2593
3030
  "Access-Control-Allow-Origin": "*"
@@ -2597,14 +3034,14 @@ function createLlmsTxtHandler(discovery) {
2597
3034
  }
2598
3035
 
2599
3036
  // src/index.ts
2600
- init_x402_config();
3037
+ init_accepts();
2601
3038
  init_constants();
2602
3039
 
2603
3040
  // src/config.ts
2604
3041
  init_constants();
2605
3042
  init_evm();
2606
3043
  init_solana();
2607
- init_x402_config();
3044
+ init_accepts();
2608
3045
  var RouterConfigError = class extends Error {
2609
3046
  issues;
2610
3047
  constructor(issues) {
@@ -2648,6 +3085,8 @@ function mppFromEnv(env, options = {}) {
2648
3085
  const secretKey = env.MPP_SECRET_KEY;
2649
3086
  const currency = env.MPP_CURRENCY;
2650
3087
  const rpcUrl = env.TEMPO_RPC_URL;
3088
+ const feePayerKey = options.feePayerKey ?? env.MPP_FEE_PAYER_KEY;
3089
+ const feePayerKeySource = options.feePayerKey !== void 0 ? "feePayerKey" : "MPP_FEE_PAYER_KEY";
2651
3090
  const hasAnyMppEnv = Boolean(secretKey || currency || rpcUrl || options.require);
2652
3091
  if (!hasAnyMppEnv) return void 0;
2653
3092
  const missing = [
@@ -2658,12 +3097,21 @@ function mppFromEnv(env, options = {}) {
2658
3097
  if (missing.length > 0) {
2659
3098
  throw new Error(`MPP env is incomplete. Missing: ${missing.join(", ")}`);
2660
3099
  }
3100
+ if (!isEvmAddress(currency)) {
3101
+ throw new Error("MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address");
3102
+ }
3103
+ if (options.recipient && !isEvmAddress(options.recipient)) {
3104
+ throw new Error("MPP recipient must be a 0x-prefixed EVM address");
3105
+ }
3106
+ if (feePayerKey && !isEvmPrivateKey(feePayerKey)) {
3107
+ throw new Error(`${feePayerKeySource} must be a 0x-prefixed 32-byte EVM private key`);
3108
+ }
2661
3109
  return {
2662
3110
  secretKey,
2663
3111
  currency,
2664
3112
  rpcUrl,
2665
3113
  ...options.recipient ? { recipient: options.recipient } : {},
2666
- ...options.feePayerKey ? { feePayerKey: options.feePayerKey } : {},
3114
+ ...feePayerKey ? { feePayerKey } : {},
2667
3115
  ...options.useDefaultStore !== void 0 ? { useDefaultStore: options.useDefaultStore } : {}
2668
3116
  };
2669
3117
  }
@@ -2801,13 +3249,26 @@ function validateMppConfig(config, env) {
2801
3249
  protocol: "mpp",
2802
3250
  message: "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency."
2803
3251
  });
3252
+ } else if (!isEvmAddress(mpp.currency)) {
3253
+ issues.push({
3254
+ code: "invalid_mpp_currency",
3255
+ protocol: "mpp",
3256
+ message: "MPP currency must be a 0x-prefixed 20-byte Tempo currency address. Use TEMPO_USDC_CURRENCY for Tempo USDC."
3257
+ });
2804
3258
  }
2805
- if (!mpp.recipient && !config.payeeAddress) {
3259
+ const mppRecipient = mpp.recipient ?? config.payeeAddress;
3260
+ if (!mppRecipient) {
2806
3261
  issues.push({
2807
3262
  code: "missing_mpp_recipient",
2808
3263
  protocol: "mpp",
2809
3264
  message: "MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config."
2810
3265
  });
3266
+ } else if (!isEvmAddress(mppRecipient)) {
3267
+ issues.push({
3268
+ code: "invalid_mpp_recipient",
3269
+ protocol: "mpp",
3270
+ message: "MPP recipient must be a 0x-prefixed EVM address. Solana recipients require x402."
3271
+ });
2811
3272
  }
2812
3273
  const placeholder = findPlaceholderPayee([mpp.recipient, config.payeeAddress]);
2813
3274
  if (placeholder) {
@@ -2824,6 +3285,13 @@ function validateMppConfig(config, env) {
2824
3285
  message: "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
2825
3286
  });
2826
3287
  }
3288
+ if (mpp.feePayerKey && !isEvmPrivateKey(mpp.feePayerKey)) {
3289
+ issues.push({
3290
+ code: "invalid_mpp_fee_payer_key",
3291
+ protocol: "mpp",
3292
+ message: "MPP feePayerKey must be a 0x-prefixed 32-byte EVM private key."
3293
+ });
3294
+ }
2827
3295
  if (mpp.useDefaultStore && !mpp.store && (!env.KV_REST_API_URL || !env.KV_REST_API_TOKEN)) {
2828
3296
  issues.push({
2829
3297
  code: "missing_mpp_default_store_env",
@@ -2841,8 +3309,14 @@ function usesDefaultEvmFacilitator(config) {
2841
3309
  function isSupportedX402Network(network) {
2842
3310
  return isEvmNetwork(network) || isSolanaNetwork(network);
2843
3311
  }
3312
+ function isEvmAddress(value) {
3313
+ return /^0x[a-fA-F0-9]{40}$/.test(value);
3314
+ }
3315
+ function isEvmPrivateKey(value) {
3316
+ return /^0x[a-fA-F0-9]{64}$/.test(value);
3317
+ }
2844
3318
  function findPlaceholderPayee(values) {
2845
- return values.find((value) => value?.toLowerCase() === ZERO_EVM_ADDRESS) ?? null;
3319
+ return values.find((value) => value !== void 0 && /^0x0{40}$/i.test(value)) ?? null;
2846
3320
  }
2847
3321
 
2848
3322
  // src/index.ts
@@ -2891,6 +3365,7 @@ function createRouter(config) {
2891
3365
  nonceStore,
2892
3366
  entitlementStore,
2893
3367
  payeeAddress: config.payeeAddress ?? "",
3368
+ mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
2894
3369
  network,
2895
3370
  x402FacilitatorsByNetwork: void 0,
2896
3371
  x402Accepts,