@agentcash/router 0.5.1 → 0.6.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 CHANGED
@@ -43,25 +43,26 @@ async function createX402Server(config) {
43
43
  const { facilitator: defaultFacilitator } = await import("@coinbase/x402");
44
44
  const raw = config.facilitatorUrl ?? defaultFacilitator;
45
45
  const facilitatorConfig = typeof raw === "string" ? { url: raw } : raw;
46
- const client = new HTTPFacilitatorClient(facilitatorConfig);
46
+ const httpClient = new HTTPFacilitatorClient(facilitatorConfig);
47
+ const network = config.network ?? "eip155:8453";
48
+ const client = cachedClient(httpClient, network);
47
49
  const server = new x402ResourceServer(client);
48
50
  registerExactEvmScheme(server);
49
51
  server.registerExtension(bazaarResourceServerExtension);
50
52
  server.registerExtension(siwxResourceServerExtension);
51
- const initPromise = retryInit(server);
53
+ const initPromise = server.initialize();
52
54
  return { server, initPromise };
53
55
  }
54
- async function retryInit(server, maxAttempts = 3, backoff = [1e3, 2e3, 4e3]) {
55
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
56
- try {
57
- await server.initialize();
58
- return;
59
- } catch (err) {
60
- const is429 = err instanceof Error && (err.message.includes("429") || err.message.includes("rate limit"));
61
- if (!is429 || attempt === maxAttempts - 1) throw err;
62
- await new Promise((r) => setTimeout(r, backoff[attempt] ?? 4e3));
63
- }
64
- }
56
+ function cachedClient(inner, network) {
57
+ return {
58
+ verify: inner.verify.bind(inner),
59
+ settle: inner.settle.bind(inner),
60
+ getSupported: async () => ({
61
+ kinds: [{ x402Version: 2, scheme: "exact", network }],
62
+ extensions: [],
63
+ signers: {}
64
+ })
65
+ };
65
66
  }
66
67
  var init_server = __esm({
67
68
  "src/server.ts"() {
@@ -503,6 +504,11 @@ function extractBearerToken(header) {
503
504
  }
504
505
 
505
506
  // src/orchestrate.ts
507
+ async function resolvePayTo(routeEntry, request, fallback) {
508
+ if (!routeEntry.payTo) return fallback;
509
+ if (typeof routeEntry.payTo === "string") return routeEntry.payTo;
510
+ return routeEntry.payTo(request);
511
+ }
506
512
  function createRequestHandler(routeEntry, handler, deps) {
507
513
  async function invoke(request, meta, pluginCtx, wallet, account, parsedBody) {
508
514
  const ctx = {
@@ -765,12 +771,13 @@ function createRequestHandler(routeEntry, handler, deps) {
765
771
  console.error(`[router] ${routeEntry.key}: ${reason}`);
766
772
  return fail(500, reason, meta, pluginCtx);
767
773
  }
774
+ const payTo = await resolvePayTo(routeEntry, request, deps.payeeAddress);
768
775
  const verify = await verifyX402Payment(
769
776
  deps.x402Server,
770
777
  request,
771
778
  routeEntry,
772
779
  price,
773
- deps.payeeAddress,
780
+ payTo,
774
781
  deps.network
775
782
  );
776
783
  if (!verify?.valid) return await build402(request, routeEntry, deps, meta, pluginCtx);
@@ -1008,22 +1015,27 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1008
1015
  }
1009
1016
  if (routeEntry.protocols.includes("x402") && deps.x402Server) {
1010
1017
  try {
1018
+ const payTo = await resolvePayTo(routeEntry, request, deps.payeeAddress);
1011
1019
  const { encoded } = await buildX402Challenge(
1012
1020
  deps.x402Server,
1013
1021
  routeEntry,
1014
1022
  request,
1015
1023
  challengePrice,
1016
- deps.payeeAddress,
1024
+ payTo,
1017
1025
  deps.network,
1018
1026
  extensions
1019
1027
  );
1020
1028
  response.headers.set("PAYMENT-REQUIRED", encoded);
1021
1029
  } catch (err) {
1030
+ const message = `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`;
1022
1031
  firePluginHook(deps.plugin, "onAlert", pluginCtx, {
1023
1032
  level: "critical",
1024
- message: `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
1033
+ message,
1025
1034
  route: routeEntry.key
1026
1035
  });
1036
+ const errorResponse = import_server2.NextResponse.json({ success: false, error: message }, { status: 500 });
1037
+ firePluginResponse(deps, pluginCtx, meta, errorResponse);
1038
+ return errorResponse;
1027
1039
  }
1028
1040
  }
1029
1041
  if (routeEntry.protocols.includes("mpp") && deps.mppx) {
@@ -1109,6 +1121,8 @@ var RouteBuilder = class {
1109
1121
  /** @internal */
1110
1122
  _minPrice;
1111
1123
  /** @internal */
1124
+ _payTo;
1125
+ /** @internal */
1112
1126
  _bodySchema;
1113
1127
  /** @internal */
1114
1128
  _querySchema;
@@ -1151,6 +1165,7 @@ var RouteBuilder = class {
1151
1165
  if (options?.protocols) next._protocols = options.protocols;
1152
1166
  if (options?.maxPrice) next._maxPrice = options.maxPrice;
1153
1167
  if (options?.minPrice) next._minPrice = options.minPrice;
1168
+ if (options?.payTo) next._payTo = options.payTo;
1154
1169
  if (typeof pricing === "object" && "tiers" in pricing) {
1155
1170
  for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
1156
1171
  if (!tierKey) {
@@ -1288,6 +1303,7 @@ var RouteBuilder = class {
1288
1303
  method: this._method,
1289
1304
  maxPrice: this._maxPrice,
1290
1305
  minPrice: this._minPrice,
1306
+ payTo: this._payTo,
1291
1307
  apiKeyResolver: this._apiKeyResolver,
1292
1308
  providerName: this._providerName,
1293
1309
  providerConfig: this._providerConfig,
package/dist/index.d.cts CHANGED
@@ -171,6 +171,8 @@ interface PaidOptions {
171
171
  protocols?: ProtocolType[];
172
172
  maxPrice?: string;
173
173
  minPrice?: string;
174
+ /** Override the payment recipient. String for static, function for dynamic (receives the Request). */
175
+ payTo?: string | ((request: Request) => string | Promise<string>);
174
176
  }
175
177
  interface HandlerContext<TBody = undefined, TQuery = undefined> {
176
178
  body: TBody;
@@ -220,6 +222,7 @@ interface RouteEntry {
220
222
  method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
221
223
  maxPrice?: string;
222
224
  minPrice?: string;
225
+ payTo?: string | ((request: Request) => string | Promise<string>);
223
226
  apiKeyResolver?: (key: string) => unknown | Promise<unknown>;
224
227
  providerName?: string;
225
228
  providerConfig?: ProviderConfig;
@@ -325,6 +328,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
325
328
  /** @internal */ _protocols: ProtocolType[];
326
329
  /** @internal */ _maxPrice: string | undefined;
327
330
  /** @internal */ _minPrice: string | undefined;
331
+ /** @internal */ _payTo: string | ((request: Request) => string | Promise<string>) | undefined;
328
332
  /** @internal */ _bodySchema: ZodType | undefined;
329
333
  /** @internal */ _querySchema: ZodType | undefined;
330
334
  /** @internal */ _outputSchema: ZodType | undefined;
package/dist/index.d.ts CHANGED
@@ -171,6 +171,8 @@ interface PaidOptions {
171
171
  protocols?: ProtocolType[];
172
172
  maxPrice?: string;
173
173
  minPrice?: string;
174
+ /** Override the payment recipient. String for static, function for dynamic (receives the Request). */
175
+ payTo?: string | ((request: Request) => string | Promise<string>);
174
176
  }
175
177
  interface HandlerContext<TBody = undefined, TQuery = undefined> {
176
178
  body: TBody;
@@ -220,6 +222,7 @@ interface RouteEntry {
220
222
  method: 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH';
221
223
  maxPrice?: string;
222
224
  minPrice?: string;
225
+ payTo?: string | ((request: Request) => string | Promise<string>);
223
226
  apiKeyResolver?: (key: string) => unknown | Promise<unknown>;
224
227
  providerName?: string;
225
228
  providerConfig?: ProviderConfig;
@@ -325,6 +328,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
325
328
  /** @internal */ _protocols: ProtocolType[];
326
329
  /** @internal */ _maxPrice: string | undefined;
327
330
  /** @internal */ _minPrice: string | undefined;
331
+ /** @internal */ _payTo: string | ((request: Request) => string | Promise<string>) | undefined;
328
332
  /** @internal */ _bodySchema: ZodType | undefined;
329
333
  /** @internal */ _querySchema: ZodType | undefined;
330
334
  /** @internal */ _outputSchema: ZodType | undefined;
package/dist/index.js CHANGED
@@ -21,25 +21,26 @@ async function createX402Server(config) {
21
21
  const { facilitator: defaultFacilitator } = await import("@coinbase/x402");
22
22
  const raw = config.facilitatorUrl ?? defaultFacilitator;
23
23
  const facilitatorConfig = typeof raw === "string" ? { url: raw } : raw;
24
- const client = new HTTPFacilitatorClient(facilitatorConfig);
24
+ const httpClient = new HTTPFacilitatorClient(facilitatorConfig);
25
+ const network = config.network ?? "eip155:8453";
26
+ const client = cachedClient(httpClient, network);
25
27
  const server = new x402ResourceServer(client);
26
28
  registerExactEvmScheme(server);
27
29
  server.registerExtension(bazaarResourceServerExtension);
28
30
  server.registerExtension(siwxResourceServerExtension);
29
- const initPromise = retryInit(server);
31
+ const initPromise = server.initialize();
30
32
  return { server, initPromise };
31
33
  }
32
- async function retryInit(server, maxAttempts = 3, backoff = [1e3, 2e3, 4e3]) {
33
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
34
- try {
35
- await server.initialize();
36
- return;
37
- } catch (err) {
38
- const is429 = err instanceof Error && (err.message.includes("429") || err.message.includes("rate limit"));
39
- if (!is429 || attempt === maxAttempts - 1) throw err;
40
- await new Promise((r) => setTimeout(r, backoff[attempt] ?? 4e3));
41
- }
42
- }
34
+ function cachedClient(inner, network) {
35
+ return {
36
+ verify: inner.verify.bind(inner),
37
+ settle: inner.settle.bind(inner),
38
+ getSupported: async () => ({
39
+ kinds: [{ x402Version: 2, scheme: "exact", network }],
40
+ extensions: [],
41
+ signers: {}
42
+ })
43
+ };
43
44
  }
44
45
  var init_server = __esm({
45
46
  "src/server.ts"() {
@@ -466,6 +467,11 @@ function extractBearerToken(header) {
466
467
  }
467
468
 
468
469
  // src/orchestrate.ts
470
+ async function resolvePayTo(routeEntry, request, fallback) {
471
+ if (!routeEntry.payTo) return fallback;
472
+ if (typeof routeEntry.payTo === "string") return routeEntry.payTo;
473
+ return routeEntry.payTo(request);
474
+ }
469
475
  function createRequestHandler(routeEntry, handler, deps) {
470
476
  async function invoke(request, meta, pluginCtx, wallet, account, parsedBody) {
471
477
  const ctx = {
@@ -728,12 +734,13 @@ function createRequestHandler(routeEntry, handler, deps) {
728
734
  console.error(`[router] ${routeEntry.key}: ${reason}`);
729
735
  return fail(500, reason, meta, pluginCtx);
730
736
  }
737
+ const payTo = await resolvePayTo(routeEntry, request, deps.payeeAddress);
731
738
  const verify = await verifyX402Payment(
732
739
  deps.x402Server,
733
740
  request,
734
741
  routeEntry,
735
742
  price,
736
- deps.payeeAddress,
743
+ payTo,
737
744
  deps.network
738
745
  );
739
746
  if (!verify?.valid) return await build402(request, routeEntry, deps, meta, pluginCtx);
@@ -971,22 +978,27 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
971
978
  }
972
979
  if (routeEntry.protocols.includes("x402") && deps.x402Server) {
973
980
  try {
981
+ const payTo = await resolvePayTo(routeEntry, request, deps.payeeAddress);
974
982
  const { encoded } = await buildX402Challenge(
975
983
  deps.x402Server,
976
984
  routeEntry,
977
985
  request,
978
986
  challengePrice,
979
- deps.payeeAddress,
987
+ payTo,
980
988
  deps.network,
981
989
  extensions
982
990
  );
983
991
  response.headers.set("PAYMENT-REQUIRED", encoded);
984
992
  } catch (err) {
993
+ const message = `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`;
985
994
  firePluginHook(deps.plugin, "onAlert", pluginCtx, {
986
995
  level: "critical",
987
- message: `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`,
996
+ message,
988
997
  route: routeEntry.key
989
998
  });
999
+ const errorResponse = NextResponse2.json({ success: false, error: message }, { status: 500 });
1000
+ firePluginResponse(deps, pluginCtx, meta, errorResponse);
1001
+ return errorResponse;
990
1002
  }
991
1003
  }
992
1004
  if (routeEntry.protocols.includes("mpp") && deps.mppx) {
@@ -1072,6 +1084,8 @@ var RouteBuilder = class {
1072
1084
  /** @internal */
1073
1085
  _minPrice;
1074
1086
  /** @internal */
1087
+ _payTo;
1088
+ /** @internal */
1075
1089
  _bodySchema;
1076
1090
  /** @internal */
1077
1091
  _querySchema;
@@ -1114,6 +1128,7 @@ var RouteBuilder = class {
1114
1128
  if (options?.protocols) next._protocols = options.protocols;
1115
1129
  if (options?.maxPrice) next._maxPrice = options.maxPrice;
1116
1130
  if (options?.minPrice) next._minPrice = options.minPrice;
1131
+ if (options?.payTo) next._payTo = options.payTo;
1117
1132
  if (typeof pricing === "object" && "tiers" in pricing) {
1118
1133
  for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
1119
1134
  if (!tierKey) {
@@ -1251,6 +1266,7 @@ var RouteBuilder = class {
1251
1266
  method: this._method,
1252
1267
  maxPrice: this._maxPrice,
1253
1268
  minPrice: this._minPrice,
1269
+ payTo: this._payTo,
1254
1270
  apiKeyResolver: this._apiKeyResolver,
1255
1271
  providerName: this._providerName,
1256
1272
  providerConfig: this._providerConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
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": {