@agentcash/router 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -41,8 +41,9 @@ async function createX402Server(config) {
41
41
  const { bazaarResourceServerExtension } = await import("@x402/extensions/bazaar");
42
42
  const { siwxResourceServerExtension } = await import("@x402/extensions/sign-in-with-x");
43
43
  const { facilitator: defaultFacilitator } = await import("@coinbase/x402");
44
- const facilitatorUrl = config.facilitatorUrl ?? defaultFacilitator;
45
- const client = new HTTPFacilitatorClient(facilitatorUrl);
44
+ const raw = config.facilitatorUrl ?? defaultFacilitator;
45
+ const facilitatorConfig = typeof raw === "string" ? { url: raw } : raw;
46
+ const client = new HTTPFacilitatorClient(facilitatorConfig);
46
47
  const server = new x402ResourceServer(client);
47
48
  registerExactEvmScheme(server);
48
49
  server.registerExtension(bazaarResourceServerExtension);
@@ -53,7 +54,7 @@ async function createX402Server(config) {
53
54
  async function retryInit(server, maxAttempts = 3, backoff = [1e3, 2e3, 4e3]) {
54
55
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
55
56
  try {
56
- await server.init();
57
+ await server.initialize();
57
58
  return;
58
59
  } catch (err) {
59
60
  const is429 = err instanceof Error && (err.message.includes("429") || err.message.includes("rate limit"));
@@ -83,9 +84,15 @@ module.exports = __toCommonJS(index_exports);
83
84
  // src/registry.ts
84
85
  var RouteRegistry = class {
85
86
  routes = /* @__PURE__ */ new Map();
87
+ // Silently overwrites on duplicate key. Next.js module loading order is
88
+ // non-deterministic during build — discovery stubs and real handlers may
89
+ // register the same route key in either order. Last writer wins.
90
+ // Prior art: ElysiaJS uses the same pattern (silent overwrite in router.history).
86
91
  register(entry) {
87
- if (this.routes.has(entry.key)) {
88
- throw new Error(`route '${entry.key}': already registered (duplicate route key)`);
92
+ if (this.routes.has(entry.key) && typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
93
+ console.warn(
94
+ `[agentcash/router] route '${entry.key}' registered twice \u2014 overwriting (this is expected for discovery stubs during next build)`
95
+ );
89
96
  }
90
97
  this.routes.set(entry.key, entry);
91
98
  }
@@ -301,10 +308,10 @@ async function buildX402Challenge(server, routeEntry, request, price, payeeAddre
301
308
  method: routeEntry.method,
302
309
  description: routeEntry.description
303
310
  };
304
- const requirements = server.buildPaymentRequirementsFromOptions(options, {
311
+ const requirements = await server.buildPaymentRequirementsFromOptions([options], {
305
312
  request
306
313
  });
307
- const paymentRequired = server.createPaymentRequiredResponse(
314
+ const paymentRequired = await server.createPaymentRequiredResponse(
308
315
  requirements,
309
316
  resource,
310
317
  null,
@@ -324,7 +331,7 @@ async function verifyX402Payment(server, request, routeEntry, price, payeeAddres
324
331
  price,
325
332
  payTo: payeeAddress
326
333
  };
327
- const requirements = server.buildPaymentRequirementsFromOptions(options, {
334
+ const requirements = await server.buildPaymentRequirementsFromOptions([options], {
328
335
  request
329
336
  });
330
337
  const matching = server.findMatchingRequirements(requirements, payload);
@@ -368,12 +375,12 @@ async function ensureMpay() {
368
375
  }
369
376
  async function buildMPPChallenge(routeEntry, request, mppConfig, price) {
370
377
  await ensureMpay();
371
- const intent = {
378
+ const methodIntent = tempo.charge({
372
379
  amount: price,
373
380
  currency: mppConfig.currency,
374
381
  recipient: mppConfig.recipient ?? ""
375
- };
376
- const challenge = Challenge.fromIntent(intent, {
382
+ });
383
+ const challenge = Challenge.fromIntent(methodIntent, {
377
384
  secretKey: mppConfig.secretKey,
378
385
  realm: new URL(request.url).origin,
379
386
  request
@@ -384,7 +391,7 @@ async function verifyMPPCredential(request, _routeEntry, mppConfig, price) {
384
391
  await ensureMpay();
385
392
  const credential = Credential.fromRequest(request);
386
393
  if (!credential) return null;
387
- const isValid = Challenge.verify(credential, { secretKey: mppConfig.secretKey });
394
+ const isValid = Challenge.verify(credential.challenge, { secretKey: mppConfig.secretKey });
388
395
  if (!isValid) {
389
396
  return { valid: false, payer: null };
390
397
  }
@@ -408,7 +415,7 @@ async function buildMPPReceipt(reference) {
408
415
  method: "tempo",
409
416
  status: "success",
410
417
  reference,
411
- timestamp: Date.now()
418
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
412
419
  });
413
420
  return Receipt.serialize(receipt);
414
421
  }
@@ -571,7 +578,12 @@ function createRequestHandler(routeEntry, handler, deps) {
571
578
  try {
572
579
  const { encodePaymentRequiredHeader } = await import("@x402/core/http");
573
580
  encoded = encodePaymentRequiredHeader(paymentRequired);
574
- } catch {
581
+ } catch (err) {
582
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
583
+ level: "warn",
584
+ message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
585
+ route: routeEntry.key
586
+ });
575
587
  }
576
588
  const response = new import_server2.NextResponse(JSON.stringify(paymentRequired), {
577
589
  status: 402,
@@ -774,7 +786,12 @@ async function build402(request, routeEntry, deps, meta, pluginCtx) {
774
786
  extensions
775
787
  );
776
788
  response.headers.set("PAYMENT-REQUIRED", encoded);
777
- } catch {
789
+ } catch (err) {
790
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
791
+ level: "critical",
792
+ message: `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
793
+ route: routeEntry.key
794
+ });
778
795
  }
779
796
  }
780
797
  if (routeEntry.protocols.includes("mpp") && deps.mppConfig) {
@@ -783,7 +800,12 @@ async function build402(request, routeEntry, deps, meta, pluginCtx) {
783
800
  "WWW-Authenticate",
784
801
  await buildMPPChallenge(routeEntry, request, deps.mppConfig, challengePrice)
785
802
  );
786
- } catch {
803
+ } catch (err) {
804
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
805
+ level: "critical",
806
+ message: `MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
807
+ route: routeEntry.key
808
+ });
787
809
  }
788
810
  }
789
811
  firePluginResponse(deps, pluginCtx, meta, response);
@@ -1158,7 +1180,11 @@ function createRouter(config) {
1158
1180
  const baseUrl = typeof globalThis.process !== "undefined" ? process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000" : "http://localhost:3000";
1159
1181
  if (config.plugin?.init) {
1160
1182
  try {
1161
- config.plugin.init({ origin: baseUrl });
1183
+ const result = config.plugin.init({ origin: baseUrl });
1184
+ if (result && typeof result.catch === "function") {
1185
+ result.catch(() => {
1186
+ });
1187
+ }
1162
1188
  } catch {
1163
1189
  }
1164
1190
  }
package/dist/index.d.cts CHANGED
@@ -82,6 +82,28 @@ interface AlertEvent {
82
82
  meta?: Record<string, unknown>;
83
83
  }
84
84
  type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
85
+ interface X402Server {
86
+ initialize(): Promise<void>;
87
+ buildPaymentRequirementsFromOptions(options: Array<{
88
+ scheme: string;
89
+ network: string;
90
+ price: string;
91
+ payTo: string;
92
+ }>, context: {
93
+ request: Request;
94
+ }): Promise<unknown[]>;
95
+ createPaymentRequiredResponse(requirements: unknown[], resource: {
96
+ url: string;
97
+ method: string;
98
+ description?: string;
99
+ }, error: string | null, extensions?: Record<string, unknown>): Promise<unknown>;
100
+ findMatchingRequirements(requirements: unknown[], payload: unknown): unknown;
101
+ verifyPayment(payload: unknown, requirements: unknown): Promise<{
102
+ isValid: boolean;
103
+ payer?: string;
104
+ }>;
105
+ settlePayment(payload: unknown, requirements: unknown): Promise<unknown>;
106
+ }
85
107
  type ProtocolType = 'x402' | 'mpp';
86
108
  type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
87
109
  interface TierConfig {
@@ -192,7 +214,7 @@ interface OpenAPIOptions {
192
214
  }
193
215
 
194
216
  interface OrchestrateDeps {
195
- x402Server: Record<string, Function> | null;
217
+ x402Server: X402Server | null;
196
218
  initPromise: Promise<void>;
197
219
  x402InitError?: string;
198
220
  plugin?: RouterPlugin;
@@ -272,4 +294,4 @@ interface ServiceRouter {
272
294
  }
273
295
  declare function createRouter(config: RouterConfig): ServiceRouter;
274
296
 
275
- export { type AlertEvent, type AlertFn, type AlertLevel, type AuthMode, type ErrorEvent, type HandlerContext, HttpError, MemoryNonceStore, type MonitorEntry, type NonceStore, type OveragePolicy, type PaidOptions, type PaymentEvent, type PluginContext, type PricingConfig, type ProtocolType, type ProviderConfig, type ProviderQuotaEvent, type QuotaInfo, type QuotaLevel, type RequestMeta, type ResponseMeta, RouteBuilder, type RouteEntry, RouteRegistry, type RouterConfig, type RouterPlugin, type ServiceRouter, type SettlementEvent, type TierConfig, consolePlugin, createRouter };
297
+ export { type AlertEvent, type AlertFn, type AlertLevel, type AuthMode, type ErrorEvent, type HandlerContext, HttpError, MemoryNonceStore, type MonitorEntry, type NonceStore, type OveragePolicy, type PaidOptions, type PaymentEvent, type PluginContext, type PricingConfig, type ProtocolType, type ProviderConfig, type ProviderQuotaEvent, type QuotaInfo, type QuotaLevel, type RequestMeta, type ResponseMeta, RouteBuilder, type RouteEntry, RouteRegistry, type RouterConfig, type RouterPlugin, type ServiceRouter, type SettlementEvent, type TierConfig, type X402Server, consolePlugin, createRouter };
package/dist/index.d.ts CHANGED
@@ -82,6 +82,28 @@ interface AlertEvent {
82
82
  meta?: Record<string, unknown>;
83
83
  }
84
84
  type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
85
+ interface X402Server {
86
+ initialize(): Promise<void>;
87
+ buildPaymentRequirementsFromOptions(options: Array<{
88
+ scheme: string;
89
+ network: string;
90
+ price: string;
91
+ payTo: string;
92
+ }>, context: {
93
+ request: Request;
94
+ }): Promise<unknown[]>;
95
+ createPaymentRequiredResponse(requirements: unknown[], resource: {
96
+ url: string;
97
+ method: string;
98
+ description?: string;
99
+ }, error: string | null, extensions?: Record<string, unknown>): Promise<unknown>;
100
+ findMatchingRequirements(requirements: unknown[], payload: unknown): unknown;
101
+ verifyPayment(payload: unknown, requirements: unknown): Promise<{
102
+ isValid: boolean;
103
+ payer?: string;
104
+ }>;
105
+ settlePayment(payload: unknown, requirements: unknown): Promise<unknown>;
106
+ }
85
107
  type ProtocolType = 'x402' | 'mpp';
86
108
  type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
87
109
  interface TierConfig {
@@ -192,7 +214,7 @@ interface OpenAPIOptions {
192
214
  }
193
215
 
194
216
  interface OrchestrateDeps {
195
- x402Server: Record<string, Function> | null;
217
+ x402Server: X402Server | null;
196
218
  initPromise: Promise<void>;
197
219
  x402InitError?: string;
198
220
  plugin?: RouterPlugin;
@@ -272,4 +294,4 @@ interface ServiceRouter {
272
294
  }
273
295
  declare function createRouter(config: RouterConfig): ServiceRouter;
274
296
 
275
- export { type AlertEvent, type AlertFn, type AlertLevel, type AuthMode, type ErrorEvent, type HandlerContext, HttpError, MemoryNonceStore, type MonitorEntry, type NonceStore, type OveragePolicy, type PaidOptions, type PaymentEvent, type PluginContext, type PricingConfig, type ProtocolType, type ProviderConfig, type ProviderQuotaEvent, type QuotaInfo, type QuotaLevel, type RequestMeta, type ResponseMeta, RouteBuilder, type RouteEntry, RouteRegistry, type RouterConfig, type RouterPlugin, type ServiceRouter, type SettlementEvent, type TierConfig, consolePlugin, createRouter };
297
+ export { type AlertEvent, type AlertFn, type AlertLevel, type AuthMode, type ErrorEvent, type HandlerContext, HttpError, MemoryNonceStore, type MonitorEntry, type NonceStore, type OveragePolicy, type PaidOptions, type PaymentEvent, type PluginContext, type PricingConfig, type ProtocolType, type ProviderConfig, type ProviderQuotaEvent, type QuotaInfo, type QuotaLevel, type RequestMeta, type ResponseMeta, RouteBuilder, type RouteEntry, RouteRegistry, type RouterConfig, type RouterPlugin, type ServiceRouter, type SettlementEvent, type TierConfig, type X402Server, consolePlugin, createRouter };
package/dist/index.js CHANGED
@@ -19,8 +19,9 @@ async function createX402Server(config) {
19
19
  const { bazaarResourceServerExtension } = await import("@x402/extensions/bazaar");
20
20
  const { siwxResourceServerExtension } = await import("@x402/extensions/sign-in-with-x");
21
21
  const { facilitator: defaultFacilitator } = await import("@coinbase/x402");
22
- const facilitatorUrl = config.facilitatorUrl ?? defaultFacilitator;
23
- const client = new HTTPFacilitatorClient(facilitatorUrl);
22
+ const raw = config.facilitatorUrl ?? defaultFacilitator;
23
+ const facilitatorConfig = typeof raw === "string" ? { url: raw } : raw;
24
+ const client = new HTTPFacilitatorClient(facilitatorConfig);
24
25
  const server = new x402ResourceServer(client);
25
26
  registerExactEvmScheme(server);
26
27
  server.registerExtension(bazaarResourceServerExtension);
@@ -31,7 +32,7 @@ async function createX402Server(config) {
31
32
  async function retryInit(server, maxAttempts = 3, backoff = [1e3, 2e3, 4e3]) {
32
33
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
33
34
  try {
34
- await server.init();
35
+ await server.initialize();
35
36
  return;
36
37
  } catch (err) {
37
38
  const is429 = err instanceof Error && (err.message.includes("429") || err.message.includes("rate limit"));
@@ -49,9 +50,15 @@ var init_server = __esm({
49
50
  // src/registry.ts
50
51
  var RouteRegistry = class {
51
52
  routes = /* @__PURE__ */ new Map();
53
+ // Silently overwrites on duplicate key. Next.js module loading order is
54
+ // non-deterministic during build — discovery stubs and real handlers may
55
+ // register the same route key in either order. Last writer wins.
56
+ // Prior art: ElysiaJS uses the same pattern (silent overwrite in router.history).
52
57
  register(entry) {
53
- if (this.routes.has(entry.key)) {
54
- throw new Error(`route '${entry.key}': already registered (duplicate route key)`);
58
+ if (this.routes.has(entry.key) && typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production") {
59
+ console.warn(
60
+ `[agentcash/router] route '${entry.key}' registered twice \u2014 overwriting (this is expected for discovery stubs during next build)`
61
+ );
55
62
  }
56
63
  this.routes.set(entry.key, entry);
57
64
  }
@@ -267,10 +274,10 @@ async function buildX402Challenge(server, routeEntry, request, price, payeeAddre
267
274
  method: routeEntry.method,
268
275
  description: routeEntry.description
269
276
  };
270
- const requirements = server.buildPaymentRequirementsFromOptions(options, {
277
+ const requirements = await server.buildPaymentRequirementsFromOptions([options], {
271
278
  request
272
279
  });
273
- const paymentRequired = server.createPaymentRequiredResponse(
280
+ const paymentRequired = await server.createPaymentRequiredResponse(
274
281
  requirements,
275
282
  resource,
276
283
  null,
@@ -290,7 +297,7 @@ async function verifyX402Payment(server, request, routeEntry, price, payeeAddres
290
297
  price,
291
298
  payTo: payeeAddress
292
299
  };
293
- const requirements = server.buildPaymentRequirementsFromOptions(options, {
300
+ const requirements = await server.buildPaymentRequirementsFromOptions([options], {
294
301
  request
295
302
  });
296
303
  const matching = server.findMatchingRequirements(requirements, payload);
@@ -334,12 +341,12 @@ async function ensureMpay() {
334
341
  }
335
342
  async function buildMPPChallenge(routeEntry, request, mppConfig, price) {
336
343
  await ensureMpay();
337
- const intent = {
344
+ const methodIntent = tempo.charge({
338
345
  amount: price,
339
346
  currency: mppConfig.currency,
340
347
  recipient: mppConfig.recipient ?? ""
341
- };
342
- const challenge = Challenge.fromIntent(intent, {
348
+ });
349
+ const challenge = Challenge.fromIntent(methodIntent, {
343
350
  secretKey: mppConfig.secretKey,
344
351
  realm: new URL(request.url).origin,
345
352
  request
@@ -350,7 +357,7 @@ async function verifyMPPCredential(request, _routeEntry, mppConfig, price) {
350
357
  await ensureMpay();
351
358
  const credential = Credential.fromRequest(request);
352
359
  if (!credential) return null;
353
- const isValid = Challenge.verify(credential, { secretKey: mppConfig.secretKey });
360
+ const isValid = Challenge.verify(credential.challenge, { secretKey: mppConfig.secretKey });
354
361
  if (!isValid) {
355
362
  return { valid: false, payer: null };
356
363
  }
@@ -374,7 +381,7 @@ async function buildMPPReceipt(reference) {
374
381
  method: "tempo",
375
382
  status: "success",
376
383
  reference,
377
- timestamp: Date.now()
384
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
378
385
  });
379
386
  return Receipt.serialize(receipt);
380
387
  }
@@ -537,7 +544,12 @@ function createRequestHandler(routeEntry, handler, deps) {
537
544
  try {
538
545
  const { encodePaymentRequiredHeader } = await import("@x402/core/http");
539
546
  encoded = encodePaymentRequiredHeader(paymentRequired);
540
- } catch {
547
+ } catch (err) {
548
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
549
+ level: "warn",
550
+ message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
551
+ route: routeEntry.key
552
+ });
541
553
  }
542
554
  const response = new NextResponse2(JSON.stringify(paymentRequired), {
543
555
  status: 402,
@@ -740,7 +752,12 @@ async function build402(request, routeEntry, deps, meta, pluginCtx) {
740
752
  extensions
741
753
  );
742
754
  response.headers.set("PAYMENT-REQUIRED", encoded);
743
- } catch {
755
+ } catch (err) {
756
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
757
+ level: "critical",
758
+ message: `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
759
+ route: routeEntry.key
760
+ });
744
761
  }
745
762
  }
746
763
  if (routeEntry.protocols.includes("mpp") && deps.mppConfig) {
@@ -749,7 +766,12 @@ async function build402(request, routeEntry, deps, meta, pluginCtx) {
749
766
  "WWW-Authenticate",
750
767
  await buildMPPChallenge(routeEntry, request, deps.mppConfig, challengePrice)
751
768
  );
752
- } catch {
769
+ } catch (err) {
770
+ firePluginHook(deps.plugin, "onAlert", pluginCtx, {
771
+ level: "critical",
772
+ message: `MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
773
+ route: routeEntry.key
774
+ });
753
775
  }
754
776
  }
755
777
  firePluginResponse(deps, pluginCtx, meta, response);
@@ -1124,7 +1146,11 @@ function createRouter(config) {
1124
1146
  const baseUrl = typeof globalThis.process !== "undefined" ? process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000" : "http://localhost:3000";
1125
1147
  if (config.plugin?.init) {
1126
1148
  try {
1127
- config.plugin.init({ origin: baseUrl });
1149
+ const result = config.plugin.init({ origin: baseUrl });
1150
+ if (result && typeof result.catch === "function") {
1151
+ result.catch(() => {
1152
+ });
1153
+ }
1128
1154
  } catch {
1129
1155
  }
1130
1156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Unified route builder for Next.js App Router APIs with x402, MPP, SIWX, and API key auth",
5
5
  "type": "module",
6
6
  "exports": {
@@ -70,6 +70,6 @@
70
70
  "license": "MIT",
71
71
  "repository": {
72
72
  "type": "git",
73
- "url": "https://github.com/merit-systems/agentcash-router"
73
+ "url": "git+https://github.com/merit-systems/agentcash-router.git"
74
74
  }
75
75
  }