@agentcash/router 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1177 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/server.ts
34
+ var server_exports = {};
35
+ __export(server_exports, {
36
+ createX402Server: () => createX402Server
37
+ });
38
+ function createX402Server(config) {
39
+ const { x402ResourceServer, HTTPFacilitatorClient } = require("@x402/core/server");
40
+ const { registerExactEvmScheme } = require("@x402/evm/exact/server");
41
+ const { bazaarResourceServerExtension } = require("@x402/extensions/bazaar");
42
+ const { siwxResourceServerExtension } = require("@x402/extensions/sign-in-with-x");
43
+ const { facilitator: defaultFacilitator } = require("@coinbase/x402");
44
+ const facilitatorUrl = config.facilitatorUrl ?? defaultFacilitator;
45
+ const client = new HTTPFacilitatorClient(facilitatorUrl);
46
+ const server = new x402ResourceServer(client);
47
+ registerExactEvmScheme(server);
48
+ server.registerExtension(bazaarResourceServerExtension);
49
+ server.registerExtension(siwxResourceServerExtension);
50
+ const initPromise = retryInit(server);
51
+ return { server, initPromise };
52
+ }
53
+ async function retryInit(server, maxAttempts = 3, backoff = [1e3, 2e3, 4e3]) {
54
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
55
+ try {
56
+ await server.init();
57
+ return;
58
+ } catch (err) {
59
+ const is429 = err instanceof Error && (err.message.includes("429") || err.message.includes("rate limit"));
60
+ if (!is429 || attempt === maxAttempts - 1) throw err;
61
+ await new Promise((r) => setTimeout(r, backoff[attempt] ?? 4e3));
62
+ }
63
+ }
64
+ }
65
+ var init_server = __esm({
66
+ "src/server.ts"() {
67
+ "use strict";
68
+ }
69
+ });
70
+
71
+ // src/index.ts
72
+ var index_exports = {};
73
+ __export(index_exports, {
74
+ HttpError: () => HttpError,
75
+ MemoryNonceStore: () => MemoryNonceStore,
76
+ RouteBuilder: () => RouteBuilder,
77
+ RouteRegistry: () => RouteRegistry,
78
+ consolePlugin: () => consolePlugin,
79
+ createRouter: () => createRouter
80
+ });
81
+ module.exports = __toCommonJS(index_exports);
82
+
83
+ // src/registry.ts
84
+ var RouteRegistry = class {
85
+ routes = /* @__PURE__ */ new Map();
86
+ register(entry) {
87
+ if (this.routes.has(entry.key)) {
88
+ throw new Error(`route '${entry.key}': already registered (duplicate route key)`);
89
+ }
90
+ this.routes.set(entry.key, entry);
91
+ }
92
+ get(key) {
93
+ return this.routes.get(key);
94
+ }
95
+ entries() {
96
+ return this.routes.entries();
97
+ }
98
+ has(key) {
99
+ return this.routes.has(key);
100
+ }
101
+ get size() {
102
+ return this.routes.size;
103
+ }
104
+ validate(expectedKeys) {
105
+ if (!expectedKeys) return;
106
+ const missing = expectedKeys.filter((k) => !this.routes.has(k));
107
+ if (missing.length > 0) {
108
+ throw new Error(
109
+ `route${missing.length > 1 ? "s" : ""} ${missing.map((k) => `'${k}'`).join(", ")} in prices map but not registered \u2014 add to barrel imports`
110
+ );
111
+ }
112
+ }
113
+ };
114
+
115
+ // src/orchestrate.ts
116
+ var import_server2 = require("next/server");
117
+
118
+ // src/plugin.ts
119
+ function createDefaultContext(meta) {
120
+ const ctx = {
121
+ requestId: meta.requestId,
122
+ route: meta.route,
123
+ walletAddress: meta.walletAddress,
124
+ clientId: meta.clientId,
125
+ sessionId: meta.sessionId,
126
+ verifiedWallet: null,
127
+ setVerifiedWallet(address) {
128
+ ctx.verifiedWallet = address;
129
+ }
130
+ };
131
+ return ctx;
132
+ }
133
+ function firePluginHook(plugin, method, ...args) {
134
+ if (!plugin) return void 0;
135
+ const fn = plugin[method];
136
+ if (typeof fn !== "function") return void 0;
137
+ try {
138
+ const result = fn.apply(plugin, args);
139
+ if (result && typeof result.catch === "function") {
140
+ result.catch(() => {
141
+ });
142
+ }
143
+ return result;
144
+ } catch {
145
+ return void 0;
146
+ }
147
+ }
148
+ function consolePlugin() {
149
+ return {
150
+ onRequest(meta) {
151
+ const ctx = createDefaultContext(meta);
152
+ return ctx;
153
+ },
154
+ onPaymentVerified(_ctx, payment) {
155
+ console.log(`[router] VERIFIED ${payment.protocol} ${payment.payer} ${payment.amount}`);
156
+ },
157
+ onPaymentSettled(_ctx, settlement) {
158
+ console.log(`[router] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);
159
+ },
160
+ onResponse(ctx, response) {
161
+ const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : "";
162
+ console.log(
163
+ `[router] ${ctx.route} \u2192 ${response.statusCode} (${response.duration}ms)${wallet}`
164
+ );
165
+ },
166
+ onError(_ctx, error) {
167
+ console.error(`[router] ERROR ${error.status}: ${error.message}`);
168
+ },
169
+ onAlert(_ctx, alert) {
170
+ const logFn = alert.level === "critical" || alert.level === "error" ? console.error : alert.level === "warn" ? console.warn : console.log;
171
+ logFn(
172
+ `[router] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
173
+ alert.meta ?? ""
174
+ );
175
+ },
176
+ onProviderQuota(_ctx, event) {
177
+ const logFn = event.level === "critical" ? console.error : event.level === "warn" ? console.warn : console.log;
178
+ logFn(`[router] QUOTA ${event.level.toUpperCase()} ${event.provider}: ${event.message}`);
179
+ }
180
+ };
181
+ }
182
+
183
+ // src/protocols/detect.ts
184
+ function detectProtocol(request) {
185
+ if (request.headers.get("PAYMENT-SIGNATURE") || request.headers.get("X-PAYMENT")) {
186
+ return "x402";
187
+ }
188
+ const auth = request.headers.get("Authorization");
189
+ if (auth && auth.startsWith("Payment ")) {
190
+ return "mpp";
191
+ }
192
+ if (request.headers.get("SIGN-IN-WITH-X")) {
193
+ return "siwx";
194
+ }
195
+ return null;
196
+ }
197
+
198
+ // src/handler.ts
199
+ var import_server = require("next/server");
200
+
201
+ // src/types.ts
202
+ var HttpError = class extends Error {
203
+ constructor(message, status) {
204
+ super(message);
205
+ this.status = status;
206
+ this.name = "HttpError";
207
+ }
208
+ };
209
+
210
+ // src/handler.ts
211
+ async function safeCallHandler(handler, ctx) {
212
+ try {
213
+ const result = await handler(ctx);
214
+ if (result instanceof Response) return result;
215
+ return import_server.NextResponse.json(result);
216
+ } catch (error) {
217
+ const status = error instanceof HttpError ? error.status : 500;
218
+ const message = error instanceof Error ? error.message : "Internal error";
219
+ return import_server.NextResponse.json({ success: false, error: message }, { status });
220
+ }
221
+ }
222
+
223
+ // src/body.ts
224
+ async function bufferBody(request) {
225
+ const text = await request.text();
226
+ if (!text) return void 0;
227
+ try {
228
+ return JSON.parse(text);
229
+ } catch {
230
+ return void 0;
231
+ }
232
+ }
233
+ function validateBody(parsed, schema) {
234
+ const result = schema.safeParse(parsed);
235
+ if (result.success) {
236
+ return { success: true, data: result.data };
237
+ }
238
+ const err = result.error;
239
+ const issues = "issues" in err ? err.issues : [{ message: String(err.message ?? "Validation failed") }];
240
+ return {
241
+ success: false,
242
+ error: "Validation failed",
243
+ issues
244
+ };
245
+ }
246
+
247
+ // src/pricing.ts
248
+ async function resolvePrice(pricing, body) {
249
+ if (typeof pricing === "string") {
250
+ return pricing;
251
+ }
252
+ if (typeof pricing === "function") {
253
+ return pricing(body);
254
+ }
255
+ const { field, tiers, default: defaultTier } = pricing;
256
+ const tierKey = body != null ? String(body[field] ?? "") : "";
257
+ if (tierKey && tiers[tierKey]) {
258
+ return tiers[tierKey].price;
259
+ }
260
+ if (defaultTier && tiers[defaultTier]) {
261
+ return tiers[defaultTier].price;
262
+ }
263
+ if (!tierKey) {
264
+ throw Object.assign(new Error(`Missing required field '${field}' for tier pricing`), {
265
+ status: 400
266
+ });
267
+ }
268
+ throw Object.assign(
269
+ new Error(
270
+ `Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
271
+ ),
272
+ { status: 400 }
273
+ );
274
+ }
275
+ function resolveMaxPrice(pricing) {
276
+ if (typeof pricing === "string") return pricing;
277
+ if (typeof pricing === "function") {
278
+ throw new Error("Dynamic pricing requires maxPrice \u2014 this should be caught at registration");
279
+ }
280
+ const { tiers } = pricing;
281
+ let max = "0";
282
+ for (const tier of Object.values(tiers)) {
283
+ if (parseFloat(tier.price) > parseFloat(max)) {
284
+ max = tier.price;
285
+ }
286
+ }
287
+ return max;
288
+ }
289
+
290
+ // src/protocols/x402.ts
291
+ function buildX402Challenge(server, routeEntry, request, price, payeeAddress, network, extensions) {
292
+ const { encodePaymentRequiredHeader } = require("@x402/core/http");
293
+ const options = {
294
+ scheme: "exact",
295
+ network,
296
+ price,
297
+ payTo: payeeAddress
298
+ };
299
+ const resource = {
300
+ url: request.url,
301
+ method: routeEntry.method,
302
+ description: routeEntry.description
303
+ };
304
+ const requirements = server.buildPaymentRequirementsFromOptions(options, {
305
+ request
306
+ });
307
+ const paymentRequired = server.createPaymentRequiredResponse(
308
+ requirements,
309
+ resource,
310
+ null,
311
+ extensions
312
+ );
313
+ const encoded = encodePaymentRequiredHeader(paymentRequired);
314
+ return { encoded, requirements };
315
+ }
316
+ async function verifyX402Payment(server, request, routeEntry, price, payeeAddress, network) {
317
+ const { decodePaymentSignatureHeader } = require("@x402/core/http");
318
+ const paymentHeader = request.headers.get("PAYMENT-SIGNATURE") ?? request.headers.get("X-PAYMENT");
319
+ if (!paymentHeader) return null;
320
+ const payload = decodePaymentSignatureHeader(paymentHeader);
321
+ const options = {
322
+ scheme: "exact",
323
+ network,
324
+ price,
325
+ payTo: payeeAddress
326
+ };
327
+ const requirements = server.buildPaymentRequirementsFromOptions(options, {
328
+ request
329
+ });
330
+ const matching = server.findMatchingRequirements(requirements, payload);
331
+ const verify = await server.verifyPayment(payload, matching);
332
+ if (!verify.isValid) {
333
+ return { valid: false, payload: null, requirements: null, payer: null };
334
+ }
335
+ return {
336
+ valid: true,
337
+ payer: verify.payer,
338
+ payload,
339
+ requirements: matching
340
+ };
341
+ }
342
+ async function settleX402Payment(server, payload, requirements) {
343
+ const { encodePaymentResponseHeader } = require("@x402/core/http");
344
+ const result = await server.settlePayment(payload, requirements);
345
+ const encoded = encodePaymentResponseHeader(result);
346
+ return { encoded, result };
347
+ }
348
+
349
+ // src/protocols/mpp.ts
350
+ var mpayLoaded = false;
351
+ var Challenge;
352
+ var Credential;
353
+ var Receipt;
354
+ var tempo;
355
+ function ensureMpay() {
356
+ if (mpayLoaded) return;
357
+ try {
358
+ const mpay = require("mpay");
359
+ Challenge = mpay.Challenge;
360
+ Credential = mpay.Credential;
361
+ Receipt = mpay.Receipt;
362
+ const mpayServer = require("mpay/server");
363
+ tempo = mpayServer.tempo;
364
+ mpayLoaded = true;
365
+ } catch {
366
+ throw new Error("mpay package is required for MPP protocol support. Install it: pnpm add mpay");
367
+ }
368
+ }
369
+ function buildMPPChallenge(routeEntry, request, mppConfig, price) {
370
+ ensureMpay();
371
+ const intent = {
372
+ amount: price,
373
+ currency: mppConfig.currency,
374
+ recipient: mppConfig.recipient ?? ""
375
+ };
376
+ const challenge = Challenge.fromIntent(intent, {
377
+ secretKey: mppConfig.secretKey,
378
+ realm: new URL(request.url).origin,
379
+ request
380
+ });
381
+ return Challenge.serialize(challenge);
382
+ }
383
+ async function verifyMPPCredential(request, _routeEntry, mppConfig, price) {
384
+ ensureMpay();
385
+ const credential = Credential.fromRequest(request);
386
+ if (!credential) return null;
387
+ const isValid = Challenge.verify(credential, { secretKey: mppConfig.secretKey });
388
+ if (!isValid) {
389
+ return { valid: false, payer: null };
390
+ }
391
+ const chargeConfig = {
392
+ amount: price,
393
+ currency: mppConfig.currency,
394
+ recipient: mppConfig.recipient ?? ""
395
+ };
396
+ const verifyResult = await tempo.charge(chargeConfig).verify(credential);
397
+ if (!verifyResult?.valid) {
398
+ return { valid: false, payer: null };
399
+ }
400
+ return {
401
+ valid: true,
402
+ payer: verifyResult.payer
403
+ };
404
+ }
405
+ function buildMPPReceipt(reference) {
406
+ ensureMpay();
407
+ const receipt = Receipt.from({
408
+ method: "tempo",
409
+ status: "success",
410
+ reference,
411
+ timestamp: Date.now()
412
+ });
413
+ return Receipt.serialize(receipt);
414
+ }
415
+
416
+ // src/auth/siwx.ts
417
+ async function verifySIWX(request, _routeEntry, nonceStore) {
418
+ const {
419
+ parseSIWxHeader,
420
+ validateSIWxMessage,
421
+ verifySIWxSignature
422
+ } = require("@x402/extensions/sign-in-with-x");
423
+ const header = request.headers.get("SIGN-IN-WITH-X");
424
+ if (!header) return { valid: false, wallet: null };
425
+ const payload = parseSIWxHeader(header);
426
+ const uri = request.url;
427
+ const validation = await validateSIWxMessage(payload, uri, {
428
+ checkNonce: (nonce) => nonceStore.check(nonce)
429
+ });
430
+ if (!validation.isValid) {
431
+ return { valid: false, wallet: null };
432
+ }
433
+ const verified = await verifySIWxSignature(payload);
434
+ if (!verified?.isValid) {
435
+ return { valid: false, wallet: null };
436
+ }
437
+ return { valid: true, wallet: verified.address };
438
+ }
439
+ function buildSIWXExtension() {
440
+ const { declareSIWxExtension } = require("@x402/extensions/sign-in-with-x");
441
+ return declareSIWxExtension();
442
+ }
443
+
444
+ // src/auth/api-key.ts
445
+ async function verifyApiKey(request, resolver) {
446
+ const apiKey = request.headers.get("X-API-Key") ?? extractBearerToken(request.headers.get("Authorization"));
447
+ if (!apiKey) {
448
+ return { valid: false, account: null };
449
+ }
450
+ const account = await resolver(apiKey);
451
+ if (account == null) {
452
+ return { valid: false, account: null };
453
+ }
454
+ return { valid: true, account };
455
+ }
456
+ function extractBearerToken(header) {
457
+ if (!header) return null;
458
+ if (header.startsWith("Bearer ")) return header.slice(7);
459
+ return null;
460
+ }
461
+
462
+ // src/orchestrate.ts
463
+ function createRequestHandler(routeEntry, handler, deps) {
464
+ async function invoke(request, meta, pluginCtx, wallet, account, parsedBody) {
465
+ const ctx = {
466
+ body: parsedBody,
467
+ query: parseQuery(request, routeEntry),
468
+ request,
469
+ requestId: meta.requestId,
470
+ route: routeEntry.key,
471
+ wallet,
472
+ account,
473
+ alert(level, message, alertMeta) {
474
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
475
+ level,
476
+ message,
477
+ route: routeEntry.key,
478
+ meta: alertMeta
479
+ });
480
+ },
481
+ setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
482
+ };
483
+ let rawResult;
484
+ const response = await safeCallHandler(async (c) => {
485
+ rawResult = await handler(c);
486
+ return rawResult;
487
+ }, ctx);
488
+ return { response, rawResult };
489
+ }
490
+ function finalize(response, rawResult, meta, pluginCtx) {
491
+ fireProviderQuota(routeEntry, response, rawResult, deps, pluginCtx);
492
+ firePluginResponse(deps, pluginCtx, meta, response);
493
+ }
494
+ function fail(status, message, meta, pluginCtx) {
495
+ const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
496
+ firePluginResponse(deps, pluginCtx, meta, response);
497
+ return response;
498
+ }
499
+ return async (request) => {
500
+ await deps.initPromise;
501
+ const meta = buildMeta(request, routeEntry);
502
+ const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
503
+ async function handleAuth(wallet, account2) {
504
+ const body2 = await parseBody(request, routeEntry);
505
+ if (!body2.ok) {
506
+ firePluginResponse(deps, pluginCtx, meta, body2.response);
507
+ return body2.response;
508
+ }
509
+ const { response, rawResult } = await invoke(
510
+ request,
511
+ meta,
512
+ pluginCtx,
513
+ wallet,
514
+ account2,
515
+ body2.data
516
+ );
517
+ finalize(response, rawResult, meta, pluginCtx);
518
+ return response;
519
+ }
520
+ if (routeEntry.authMode === "unprotected") {
521
+ return handleAuth(null, void 0);
522
+ }
523
+ let account = void 0;
524
+ if (routeEntry.authMode === "apiKey" || routeEntry.apiKeyResolver) {
525
+ if (!routeEntry.apiKeyResolver) {
526
+ return fail(401, "API key resolver not configured", meta, pluginCtx);
527
+ }
528
+ const keyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
529
+ if (!keyResult.valid) {
530
+ return fail(401, "Invalid or missing API key", meta, pluginCtx);
531
+ }
532
+ account = keyResult.account;
533
+ if (routeEntry.authMode === "apiKey" && !routeEntry.pricing) {
534
+ return handleAuth(null, account);
535
+ }
536
+ }
537
+ const protocol = detectProtocol(request);
538
+ if (routeEntry.authMode === "siwx") {
539
+ if (!request.headers.get("SIGN-IN-WITH-X")) {
540
+ const response = new import_server2.NextResponse(null, { status: 402 });
541
+ try {
542
+ if (buildSIWXExtension()) response.headers.set("X-SIWX-REQUIRED", "true");
543
+ } catch {
544
+ }
545
+ firePluginResponse(deps, pluginCtx, meta, response);
546
+ return response;
547
+ }
548
+ const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
549
+ if (!siwx.valid) {
550
+ return fail(
551
+ 402,
552
+ "SIWX verification failed \u2014 signature invalid, message expired, or nonce already used",
553
+ meta,
554
+ pluginCtx
555
+ );
556
+ }
557
+ pluginCtx.setVerifiedWallet(siwx.wallet);
558
+ return handleAuth(siwx.wallet, void 0);
559
+ }
560
+ if (!protocol || protocol === "siwx") {
561
+ return build402(request, routeEntry, deps, meta, pluginCtx);
562
+ }
563
+ const body = await parseBody(request, routeEntry);
564
+ if (!body.ok) {
565
+ firePluginResponse(deps, pluginCtx, meta, body.response);
566
+ return body.response;
567
+ }
568
+ let price;
569
+ try {
570
+ price = await resolvePrice(routeEntry.pricing, body.data);
571
+ } catch (err) {
572
+ return fail(
573
+ err.status ?? 500,
574
+ err instanceof Error ? err.message : "Price resolution failed",
575
+ meta,
576
+ pluginCtx
577
+ );
578
+ }
579
+ if (protocol === "x402") {
580
+ if (!deps.x402Server) {
581
+ 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";
582
+ return fail(500, reason, meta, pluginCtx);
583
+ }
584
+ const verify = await verifyX402Payment(
585
+ deps.x402Server,
586
+ request,
587
+ routeEntry,
588
+ price,
589
+ deps.payeeAddress,
590
+ deps.network
591
+ );
592
+ if (!verify?.valid) return build402(request, routeEntry, deps, meta, pluginCtx);
593
+ pluginCtx.setVerifiedWallet(verify.payer);
594
+ firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
595
+ protocol: "x402",
596
+ payer: verify.payer,
597
+ amount: price,
598
+ network: deps.network
599
+ });
600
+ const { response, rawResult } = await invoke(
601
+ request,
602
+ meta,
603
+ pluginCtx,
604
+ verify.payer,
605
+ account,
606
+ body.data
607
+ );
608
+ if (response.status < 400) {
609
+ try {
610
+ const settle = await settleX402Payment(
611
+ deps.x402Server,
612
+ verify.payload,
613
+ verify.requirements
614
+ );
615
+ response.headers.set("PAYMENT-RESPONSE", settle.encoded);
616
+ firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
617
+ protocol: "x402",
618
+ payer: verify.payer,
619
+ transaction: String(settle.result?.transaction ?? ""),
620
+ network: deps.network
621
+ });
622
+ } catch (err) {
623
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
624
+ level: "critical",
625
+ message: `Settlement failed: ${err instanceof Error ? err.message : String(err)}`,
626
+ route: routeEntry.key
627
+ });
628
+ }
629
+ }
630
+ finalize(response, rawResult, meta, pluginCtx);
631
+ return response;
632
+ }
633
+ if (protocol === "mpp") {
634
+ if (!deps.mppConfig) return build402(request, routeEntry, deps, meta, pluginCtx);
635
+ const verify = await verifyMPPCredential(request, routeEntry, deps.mppConfig, price);
636
+ if (!verify?.valid) return build402(request, routeEntry, deps, meta, pluginCtx);
637
+ const wallet = verify.payer;
638
+ pluginCtx.setVerifiedWallet(wallet);
639
+ firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
640
+ protocol: "mpp",
641
+ payer: wallet,
642
+ amount: price,
643
+ network: "tempo:42431"
644
+ });
645
+ const { response, rawResult } = await invoke(
646
+ request,
647
+ meta,
648
+ pluginCtx,
649
+ wallet,
650
+ account,
651
+ body.data
652
+ );
653
+ if (response.status < 400) {
654
+ try {
655
+ response.headers.set("Payment-Receipt", buildMPPReceipt(crypto.randomUUID()));
656
+ } catch {
657
+ }
658
+ }
659
+ finalize(response, rawResult, meta, pluginCtx);
660
+ return response;
661
+ }
662
+ return build402(request, routeEntry, deps, meta, pluginCtx);
663
+ };
664
+ }
665
+ async function parseBody(request, routeEntry) {
666
+ if (!routeEntry.bodySchema) return { ok: true, data: void 0 };
667
+ const raw = await bufferBody(request);
668
+ const result = validateBody(raw, routeEntry.bodySchema);
669
+ if (result.success) return { ok: true, data: result.data };
670
+ return {
671
+ ok: false,
672
+ response: import_server2.NextResponse.json(
673
+ { success: false, error: result.error, issues: result.issues },
674
+ { status: 400 }
675
+ )
676
+ };
677
+ }
678
+ function buildMeta(request, routeEntry) {
679
+ return {
680
+ requestId: crypto.randomUUID(),
681
+ method: request.method,
682
+ route: routeEntry.key,
683
+ origin: request.headers.get("origin") ?? new URL(request.url).origin,
684
+ referer: request.headers.get("referer"),
685
+ walletAddress: request.headers.get("X-Wallet-Address"),
686
+ clientId: request.headers.get("X-Client-ID"),
687
+ sessionId: request.headers.get("X-Session-ID"),
688
+ contentType: request.headers.get("content-type"),
689
+ headers: Object.fromEntries(request.headers.entries()),
690
+ startTime: Date.now()
691
+ };
692
+ }
693
+ function parseQuery(request, routeEntry) {
694
+ if (!routeEntry.querySchema) return void 0;
695
+ const params = Object.fromEntries(request.nextUrl.searchParams.entries());
696
+ const result = routeEntry.querySchema.safeParse(params);
697
+ return result.success ? result.data : params;
698
+ }
699
+ function build402(request, routeEntry, deps, meta, pluginCtx) {
700
+ const response = new import_server2.NextResponse(null, { status: 402 });
701
+ let challengePrice;
702
+ if (routeEntry.maxPrice) {
703
+ challengePrice = routeEntry.maxPrice;
704
+ } else if (routeEntry.pricing) {
705
+ try {
706
+ challengePrice = resolveMaxPrice(routeEntry.pricing);
707
+ } catch {
708
+ challengePrice = "0";
709
+ }
710
+ } else {
711
+ challengePrice = "0";
712
+ }
713
+ let extensions;
714
+ try {
715
+ const { z } = require("zod");
716
+ const { declareDiscoveryExtension } = require("@x402/extensions/bazaar");
717
+ const inputSchema = routeEntry.bodySchema ? z.toJSONSchema(routeEntry.bodySchema, { target: "draft-2020-12" }) : routeEntry.querySchema ? z.toJSONSchema(routeEntry.querySchema, { target: "draft-2020-12" }) : void 0;
718
+ const outputSchema = routeEntry.outputSchema ? z.toJSONSchema(routeEntry.outputSchema, { target: "draft-2020-12" }) : void 0;
719
+ if (inputSchema) {
720
+ const config = {
721
+ bodyType: routeEntry.bodySchema ? "json" : void 0,
722
+ inputSchema
723
+ };
724
+ if (outputSchema) config.output = { schema: outputSchema, example: {} };
725
+ extensions = declareDiscoveryExtension(config);
726
+ }
727
+ } catch {
728
+ }
729
+ if (routeEntry.protocols.includes("x402") && deps.x402Server) {
730
+ try {
731
+ const { encoded } = buildX402Challenge(
732
+ deps.x402Server,
733
+ routeEntry,
734
+ request,
735
+ challengePrice,
736
+ deps.payeeAddress,
737
+ deps.network,
738
+ extensions
739
+ );
740
+ response.headers.set("PAYMENT-REQUIRED", encoded);
741
+ } catch {
742
+ }
743
+ }
744
+ if (routeEntry.protocols.includes("mpp") && deps.mppConfig) {
745
+ try {
746
+ response.headers.set(
747
+ "WWW-Authenticate",
748
+ buildMPPChallenge(routeEntry, request, deps.mppConfig, challengePrice)
749
+ );
750
+ } catch {
751
+ }
752
+ }
753
+ firePluginResponse(deps, pluginCtx, meta, response);
754
+ return response;
755
+ }
756
+ function firePluginResponse(deps, pluginCtx, meta, response) {
757
+ firePluginHook(deps.plugin, "onResponse", pluginCtx, {
758
+ statusCode: response.status,
759
+ statusText: response.statusText,
760
+ duration: Date.now() - meta.startTime,
761
+ contentType: response.headers.get("content-type"),
762
+ headers: Object.fromEntries(response.headers.entries())
763
+ });
764
+ if (response.status >= 400 && response.status !== 402) {
765
+ firePluginHook(deps.plugin, "onError", pluginCtx, {
766
+ status: response.status,
767
+ message: response.statusText || `HTTP ${response.status}`,
768
+ settled: false
769
+ });
770
+ }
771
+ }
772
+ function computeQuotaLevel(remaining, warn, critical) {
773
+ if (remaining === null) return "healthy";
774
+ if (critical !== void 0 && remaining <= critical) return "critical";
775
+ if (warn !== void 0 && remaining <= warn) return "warn";
776
+ return "healthy";
777
+ }
778
+ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx) {
779
+ const { providerName, providerConfig } = routeEntry;
780
+ if (!providerName || !providerConfig?.extractQuota) return;
781
+ if (response.status >= 400) return;
782
+ try {
783
+ const quota = providerConfig.extractQuota(handlerResult, response.headers);
784
+ if (!quota) return;
785
+ const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
786
+ const overage = providerConfig.overage ?? "same-rate";
787
+ const event = {
788
+ provider: providerName,
789
+ route: routeEntry.key,
790
+ remaining: quota.remaining,
791
+ limit: quota.limit,
792
+ spend: quota.spend,
793
+ level,
794
+ overage,
795
+ message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
796
+ };
797
+ firePluginHook(deps.plugin, "onProviderQuota", pluginCtx, event);
798
+ } catch {
799
+ }
800
+ }
801
+
802
+ // src/builder.ts
803
+ var RouteBuilder = class {
804
+ /** @internal */
805
+ _key;
806
+ /** @internal */
807
+ _registry;
808
+ /** @internal */
809
+ _deps;
810
+ /** @internal */
811
+ _authMode = null;
812
+ /** @internal */
813
+ _pricing;
814
+ /** @internal */
815
+ _protocols = ["x402"];
816
+ /** @internal */
817
+ _maxPrice;
818
+ /** @internal */
819
+ _bodySchema;
820
+ /** @internal */
821
+ _querySchema;
822
+ /** @internal */
823
+ _outputSchema;
824
+ /** @internal */
825
+ _description;
826
+ /** @internal */
827
+ _path;
828
+ /** @internal */
829
+ _method = "POST";
830
+ /** @internal */
831
+ _apiKeyResolver;
832
+ /** @internal */
833
+ _providerName;
834
+ /** @internal */
835
+ _providerConfig;
836
+ constructor(key, registry, deps) {
837
+ this._key = key;
838
+ this._registry = registry;
839
+ this._deps = deps;
840
+ }
841
+ fork() {
842
+ const next = Object.create(Object.getPrototypeOf(this));
843
+ Object.assign(next, this);
844
+ next._protocols = [...this._protocols];
845
+ return next;
846
+ }
847
+ paid(pricing, options) {
848
+ const next = this.fork();
849
+ next._authMode = "paid";
850
+ next._pricing = pricing;
851
+ if (options?.protocols) next._protocols = options.protocols;
852
+ if (options?.maxPrice) next._maxPrice = options.maxPrice;
853
+ if (typeof pricing === "function" && !options?.maxPrice) {
854
+ throw new Error(`route '${this._key}': dynamic pricing requires maxPrice option`);
855
+ }
856
+ if (typeof pricing === "object" && "tiers" in pricing) {
857
+ for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
858
+ if (!tierKey) {
859
+ throw new Error(`route '${this._key}': tier key cannot be empty`);
860
+ }
861
+ const tierPrice = parseFloat(tierConfig.price);
862
+ if (isNaN(tierPrice) || tierPrice <= 0) {
863
+ throw new Error(
864
+ `route '${this._key}': tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
865
+ );
866
+ }
867
+ }
868
+ }
869
+ if (options?.maxPrice !== void 0) {
870
+ const parsed = parseFloat(options.maxPrice);
871
+ if (isNaN(parsed) || parsed <= 0) {
872
+ throw new Error(
873
+ `route '${this._key}': maxPrice '${options.maxPrice}' must be a positive decimal string`
874
+ );
875
+ }
876
+ }
877
+ return next;
878
+ }
879
+ siwx() {
880
+ const next = this.fork();
881
+ next._authMode = "siwx";
882
+ next._protocols = [];
883
+ return next;
884
+ }
885
+ apiKey(resolver) {
886
+ const next = this.fork();
887
+ next._authMode = "apiKey";
888
+ next._apiKeyResolver = resolver;
889
+ return next;
890
+ }
891
+ unprotected() {
892
+ const next = this.fork();
893
+ next._authMode = "unprotected";
894
+ next._protocols = [];
895
+ return next;
896
+ }
897
+ // -------------------------------------------------------------------------
898
+ // Provider monitoring
899
+ // -------------------------------------------------------------------------
900
+ provider(name, config) {
901
+ const next = this.fork();
902
+ next._providerName = name;
903
+ next._providerConfig = config ?? {};
904
+ return next;
905
+ }
906
+ // -------------------------------------------------------------------------
907
+ // Schema methods
908
+ // -------------------------------------------------------------------------
909
+ body(schema) {
910
+ const next = this.fork();
911
+ next._bodySchema = schema;
912
+ return next;
913
+ }
914
+ query(schema) {
915
+ const next = this.fork();
916
+ next._querySchema = schema;
917
+ next._method = "GET";
918
+ return next;
919
+ }
920
+ output(schema) {
921
+ const next = this.fork();
922
+ next._outputSchema = schema;
923
+ return next;
924
+ }
925
+ description(text) {
926
+ const next = this.fork();
927
+ next._description = text;
928
+ return next;
929
+ }
930
+ path(p) {
931
+ const next = this.fork();
932
+ next._path = p;
933
+ return next;
934
+ }
935
+ handler(fn) {
936
+ const entry = {
937
+ key: this._key,
938
+ authMode: this._authMode,
939
+ pricing: this._pricing,
940
+ protocols: this._protocols,
941
+ bodySchema: this._bodySchema,
942
+ querySchema: this._querySchema,
943
+ outputSchema: this._outputSchema,
944
+ description: this._description,
945
+ path: this._path,
946
+ method: this._method,
947
+ maxPrice: this._maxPrice,
948
+ apiKeyResolver: this._apiKeyResolver,
949
+ providerName: this._providerName,
950
+ providerConfig: this._providerConfig
951
+ };
952
+ this._registry.register(entry);
953
+ return createRequestHandler(entry, fn, this._deps);
954
+ }
955
+ };
956
+
957
+ // src/auth/nonce.ts
958
+ var MemoryNonceStore = class {
959
+ seen = /* @__PURE__ */ new Map();
960
+ async check(nonce) {
961
+ this.evict();
962
+ if (this.seen.has(nonce)) return false;
963
+ this.seen.set(nonce, Date.now() + 5 * 60 * 1e3);
964
+ return true;
965
+ }
966
+ evict() {
967
+ const now = Date.now();
968
+ for (const [n, exp] of this.seen) {
969
+ if (exp < now) this.seen.delete(n);
970
+ }
971
+ }
972
+ };
973
+
974
+ // src/discovery/well-known.ts
975
+ var import_server3 = require("next/server");
976
+ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
977
+ let validated = false;
978
+ return async (_request) => {
979
+ if (!validated && pricesKeys) {
980
+ registry.validate(pricesKeys);
981
+ validated = true;
982
+ }
983
+ const x402Resources = [];
984
+ const mppResources = [];
985
+ for (const [key, entry] of registry.entries()) {
986
+ const url = `${baseUrl}/api/${entry.path ?? key}`;
987
+ if (entry.protocols.includes("x402")) x402Resources.push(url);
988
+ if (entry.protocols.includes("mpp")) mppResources.push(url);
989
+ }
990
+ let instructions;
991
+ if (typeof options.instructions === "function") {
992
+ instructions = await options.instructions();
993
+ } else if (typeof options.instructions === "string") {
994
+ instructions = options.instructions;
995
+ }
996
+ const body = {
997
+ version: 1,
998
+ resources: x402Resources
999
+ };
1000
+ if (mppResources.length > 0) {
1001
+ body.mppResources = mppResources;
1002
+ }
1003
+ if (options.ownershipProofs) {
1004
+ body.ownershipProofs = options.ownershipProofs;
1005
+ }
1006
+ if (instructions) {
1007
+ body.instructions = instructions;
1008
+ }
1009
+ return import_server3.NextResponse.json(body);
1010
+ };
1011
+ }
1012
+
1013
+ // src/discovery/openapi.ts
1014
+ var import_server4 = require("next/server");
1015
+ function createOpenAPIHandler(registry, baseUrl, pricesKeys, options) {
1016
+ let cached = null;
1017
+ let validated = false;
1018
+ return async (_request) => {
1019
+ if (cached) return import_server4.NextResponse.json(cached);
1020
+ if (!validated && pricesKeys) {
1021
+ registry.validate(pricesKeys);
1022
+ validated = true;
1023
+ }
1024
+ const { createDocument } = await import("zod-openapi");
1025
+ const paths = {};
1026
+ const tagSet = /* @__PURE__ */ new Set();
1027
+ for (const [key, entry] of registry.entries()) {
1028
+ const apiPath = `/api/${entry.path ?? key}`;
1029
+ const method = entry.method.toLowerCase();
1030
+ const tag = deriveTag(key);
1031
+ tagSet.add(tag);
1032
+ paths[apiPath] = {
1033
+ [method]: buildOperation(key, entry, tag)
1034
+ };
1035
+ }
1036
+ cached = createDocument({
1037
+ openapi: "3.1.0",
1038
+ info: {
1039
+ title: options.title,
1040
+ description: options.description,
1041
+ version: options.version,
1042
+ ...options.contact && { contact: options.contact }
1043
+ },
1044
+ servers: [{ url: options.baseUrl ?? baseUrl }],
1045
+ tags: Array.from(tagSet).sort().map((name) => ({ name })),
1046
+ paths
1047
+ });
1048
+ return import_server4.NextResponse.json(cached);
1049
+ };
1050
+ }
1051
+ function deriveTag(routeKey) {
1052
+ return routeKey.split("/")[0].split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
1053
+ }
1054
+ function buildOperation(routeKey, entry, tag) {
1055
+ const protocols = entry.protocols.length > 0 ? entry.protocols : void 0;
1056
+ let price;
1057
+ if (typeof entry.pricing === "string") {
1058
+ price = parseFloat(entry.pricing);
1059
+ } else if (entry.maxPrice) {
1060
+ price = parseFloat(entry.maxPrice);
1061
+ }
1062
+ const operation = {
1063
+ operationId: routeKey.replace(/\//g, "_"),
1064
+ summary: entry.description ?? routeKey,
1065
+ tags: [tag],
1066
+ responses: {
1067
+ "200": {
1068
+ description: "Successful response",
1069
+ ...entry.outputSchema && {
1070
+ content: {
1071
+ "application/json": { schema: entry.outputSchema }
1072
+ }
1073
+ }
1074
+ },
1075
+ ...entry.authMode === "paid" && {
1076
+ "402": {
1077
+ description: "Payment Required"
1078
+ }
1079
+ }
1080
+ }
1081
+ };
1082
+ if (price !== void 0 || protocols) {
1083
+ operation["x-payment-info"] = {
1084
+ ...price !== void 0 && { price },
1085
+ ...protocols && { protocols }
1086
+ };
1087
+ }
1088
+ if (entry.bodySchema) {
1089
+ operation.requestBody = {
1090
+ required: true,
1091
+ content: {
1092
+ "application/json": { schema: entry.bodySchema }
1093
+ }
1094
+ };
1095
+ }
1096
+ if (entry.querySchema) {
1097
+ operation.requestParams = {
1098
+ query: entry.querySchema
1099
+ };
1100
+ }
1101
+ return operation;
1102
+ }
1103
+
1104
+ // src/index.ts
1105
+ function createRouter(config) {
1106
+ const registry = new RouteRegistry();
1107
+ const nonceStore = config.siwx?.nonceStore ?? new MemoryNonceStore();
1108
+ const network = config.network ?? "eip155:8453";
1109
+ const baseUrl = typeof globalThis.process !== "undefined" ? process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000" : "http://localhost:3000";
1110
+ if (config.plugin?.init) {
1111
+ try {
1112
+ config.plugin.init({ origin: baseUrl });
1113
+ } catch {
1114
+ }
1115
+ }
1116
+ const deps = {
1117
+ x402Server: null,
1118
+ initPromise: Promise.resolve(),
1119
+ plugin: config.plugin,
1120
+ nonceStore,
1121
+ payeeAddress: config.payeeAddress,
1122
+ network,
1123
+ mppConfig: config.mpp
1124
+ };
1125
+ try {
1126
+ const { createX402Server: createX402Server2 } = (init_server(), __toCommonJS(server_exports));
1127
+ const result = createX402Server2(config);
1128
+ deps.x402Server = result.server;
1129
+ deps.initPromise = result.initPromise.catch((err) => {
1130
+ deps.x402Server = null;
1131
+ deps.x402InitError = err instanceof Error ? err.message : String(err);
1132
+ });
1133
+ } catch {
1134
+ }
1135
+ const pricesKeys = config.prices ? Object.keys(config.prices) : void 0;
1136
+ return {
1137
+ route(key) {
1138
+ const builder = new RouteBuilder(key, registry, deps);
1139
+ if (config.prices && key in config.prices) {
1140
+ return builder.paid(config.prices[key]);
1141
+ }
1142
+ return builder;
1143
+ },
1144
+ wellKnown(options) {
1145
+ return createWellKnownHandler(registry, baseUrl, pricesKeys, options);
1146
+ },
1147
+ openapi(options) {
1148
+ return createOpenAPIHandler(registry, baseUrl, pricesKeys, options);
1149
+ },
1150
+ monitors() {
1151
+ const result = [];
1152
+ for (const [, entry] of registry.entries()) {
1153
+ if (entry.providerName && entry.providerConfig?.monitor) {
1154
+ result.push({
1155
+ provider: entry.providerName,
1156
+ route: entry.key,
1157
+ monitor: entry.providerConfig.monitor,
1158
+ overage: entry.providerConfig.overage ?? "same-rate",
1159
+ warn: entry.providerConfig.warn,
1160
+ critical: entry.providerConfig.critical
1161
+ });
1162
+ }
1163
+ }
1164
+ return result;
1165
+ },
1166
+ registry
1167
+ };
1168
+ }
1169
+ // Annotate the CommonJS export names for ESM import in node:
1170
+ 0 && (module.exports = {
1171
+ HttpError,
1172
+ MemoryNonceStore,
1173
+ RouteBuilder,
1174
+ RouteRegistry,
1175
+ consolePlugin,
1176
+ createRouter
1177
+ });