@agentcash/router 1.14.1 → 1.15.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
@@ -131,6 +131,75 @@ var init_evm = __esm({
131
131
  }
132
132
  });
133
133
 
134
+ // src/kv-store/facilitator-supported.ts
135
+ function createCachedSupportedFetch(fetchLive, options = {}) {
136
+ const { kv, cacheKey, ttlSeconds = FACILITATOR_SUPPORTED_TTL_SECONDS, fallback } = options;
137
+ const kvKey = kv && cacheKey ? `${FACILITATOR_SUPPORTED_KV_PREFIX}${cacheKey}` : void 0;
138
+ let inflight;
139
+ return () => {
140
+ if (inflight) return inflight;
141
+ const attempt = fetchSupported(fetchLive, kv, kvKey, ttlSeconds, fallback);
142
+ inflight = attempt;
143
+ attempt.catch(() => {
144
+ if (inflight === attempt) inflight = void 0;
145
+ });
146
+ return attempt;
147
+ };
148
+ }
149
+ function withCachedSupported(inner, options = {}) {
150
+ return {
151
+ verify: inner.verify.bind(inner),
152
+ settle: inner.settle.bind(inner),
153
+ getSupported: createCachedSupportedFetch(() => inner.getSupported(), options)
154
+ };
155
+ }
156
+ async function fetchSupported(fetchLive, kv, kvKey, ttlSeconds, fallback) {
157
+ if (kv && kvKey) {
158
+ const cached = await readKvCache(kv, kvKey);
159
+ if (cached) return cached;
160
+ }
161
+ const fresh = await tryFetchLive(fetchLive, fallback);
162
+ if (fresh === null) return fallback();
163
+ if (kv && kvKey) await writeKvCache(kv, kvKey, fresh, ttlSeconds);
164
+ return fresh;
165
+ }
166
+ async function tryFetchLive(fetchLive, fallback) {
167
+ try {
168
+ return await fetchLive();
169
+ } catch (err) {
170
+ if (!fallback) throw err;
171
+ console.warn(
172
+ `[x402] facilitator /supported failed, using hardcoded baseline: ${err instanceof Error ? err.message : String(err)}`
173
+ );
174
+ return null;
175
+ }
176
+ }
177
+ async function readKvCache(kv, key) {
178
+ try {
179
+ const cached = await kv.get(key);
180
+ return isKindsResponse(cached) ? cached : void 0;
181
+ } catch {
182
+ return void 0;
183
+ }
184
+ }
185
+ async function writeKvCache(kv, key, value, ttlSeconds) {
186
+ try {
187
+ await kv.setNxEx(key, value, ttlSeconds);
188
+ } catch {
189
+ }
190
+ }
191
+ function isKindsResponse(value) {
192
+ return typeof value === "object" && value !== null && Array.isArray(value.kinds);
193
+ }
194
+ var FACILITATOR_SUPPORTED_TTL_SECONDS, FACILITATOR_SUPPORTED_KV_PREFIX;
195
+ var init_facilitator_supported = __esm({
196
+ "src/kv-store/facilitator-supported.ts"() {
197
+ "use strict";
198
+ FACILITATOR_SUPPORTED_TTL_SECONDS = 60 * 60;
199
+ FACILITATOR_SUPPORTED_KV_PREFIX = "x402:facilitator-supported:";
200
+ }
201
+ });
202
+
134
203
  // src/protocols/x402/solana.ts
135
204
  function isSolanaNetwork(network) {
136
205
  return network.startsWith("solana:");
@@ -164,7 +233,8 @@ function matchesSupportedKind(requirement, kind) {
164
233
  async function fetchFacilitatorSupported(facilitator) {
165
234
  const authHeaders = await getSupportedHeadersForFacilitator(facilitator);
166
235
  const response = await fetch(`${facilitatorBaseUrl(facilitator)}/supported`, {
167
- headers: authHeaders
236
+ headers: authHeaders,
237
+ signal: AbortSignal.timeout(SUPPORTED_FETCH_TIMEOUT_MS)
168
238
  });
169
239
  if (!response.ok) {
170
240
  throw new Error(`Facilitator /supported failed with status ${response.status}`);
@@ -173,7 +243,18 @@ async function fetchFacilitatorSupported(facilitator) {
173
243
  if (!Array.isArray(body.kinds)) {
174
244
  throw new Error("Facilitator /supported response did not include kinds");
175
245
  }
176
- return body.kinds;
246
+ return { kinds: body.kinds };
247
+ }
248
+ function getCachedSupportedFetch(facilitator, kv) {
249
+ let fetcher = supportedFetchers.get(facilitator);
250
+ if (!fetcher) {
251
+ fetcher = createCachedSupportedFetch(() => fetchFacilitatorSupported(facilitator), {
252
+ kv,
253
+ cacheKey: facilitator.url
254
+ });
255
+ supportedFetchers.set(facilitator, fetcher);
256
+ }
257
+ return fetcher;
177
258
  }
178
259
  function enrichRequirementFromKind(requirement, kinds) {
179
260
  const kind = kinds.find((candidate) => matchesSupportedKind(requirement, candidate));
@@ -192,31 +273,32 @@ function enrichRequirementFromKind(requirement, kinds) {
192
273
  const kindExtra = kind.extra ?? {};
193
274
  const requirementFeatures = requirementExtra.features ?? {};
194
275
  const kindFeatures = kindExtra.features ?? {};
276
+ const features = { ...requirementFeatures, ...kindFeatures };
195
277
  return {
196
278
  ...requirement,
197
279
  ...kind.asset && !requirement.asset ? { asset: kind.asset } : {},
198
280
  extra: {
199
281
  ...requirementExtra,
200
282
  ...kindExtra,
201
- features: {
202
- ...requirementFeatures,
203
- ...kindFeatures,
204
- xSettlementAccountSupported: true
205
- }
283
+ ...Object.keys(features).length > 0 ? { features } : {}
206
284
  }
207
285
  };
208
286
  }
209
- async function enrichRequirementsFromFacilitatorSupported(facilitator, requirements) {
210
- const kinds = await fetchFacilitatorSupported(facilitator);
287
+ async function enrichRequirementsFromFacilitatorSupported(facilitator, requirements, kv) {
288
+ const { kinds } = await getCachedSupportedFetch(facilitator, kv)();
211
289
  return requirements.map((requirement) => enrichRequirementFromKind(requirement, kinds));
212
290
  }
213
291
  function isSolanaRequirement(requirement) {
214
292
  return isSolanaNetwork(requirement.network);
215
293
  }
294
+ var SUPPORTED_FETCH_TIMEOUT_MS, supportedFetchers;
216
295
  var init_solana = __esm({
217
296
  "src/protocols/x402/solana.ts"() {
218
297
  "use strict";
298
+ init_facilitator_supported();
219
299
  init_facilitators();
300
+ SUPPORTED_FETCH_TIMEOUT_MS = 1e4;
301
+ supportedFetchers = /* @__PURE__ */ new WeakMap();
220
302
  }
221
303
  });
222
304
 
@@ -298,72 +380,6 @@ var init_facilitators = __esm({
298
380
  }
299
381
  });
300
382
 
301
- // src/kv-store/facilitator-supported.ts
302
- function withCachedSupported(inner, options = {}) {
303
- const { kv, cacheKey, ttlSeconds = FACILITATOR_SUPPORTED_TTL_SECONDS, fallback } = options;
304
- const kvKey = kv && cacheKey ? `${FACILITATOR_SUPPORTED_KV_PREFIX}${cacheKey}` : void 0;
305
- let inflight;
306
- return {
307
- verify: inner.verify.bind(inner),
308
- settle: inner.settle.bind(inner),
309
- getSupported: () => {
310
- if (inflight) return inflight;
311
- const attempt = fetchSupported(inner, kv, kvKey, ttlSeconds, fallback);
312
- inflight = attempt;
313
- attempt.catch(() => {
314
- if (inflight === attempt) inflight = void 0;
315
- });
316
- return attempt;
317
- }
318
- };
319
- }
320
- async function fetchSupported(inner, kv, kvKey, ttlSeconds, fallback) {
321
- if (kv && kvKey) {
322
- const cached = await readKvCache(kv, kvKey);
323
- if (cached) return cached;
324
- }
325
- const fresh = await tryFetchLive(inner, fallback);
326
- if (fresh === null) return fallback();
327
- if (kv && kvKey) await writeKvCache(kv, kvKey, fresh, ttlSeconds);
328
- return fresh;
329
- }
330
- async function tryFetchLive(inner, fallback) {
331
- try {
332
- return await inner.getSupported();
333
- } catch (err) {
334
- if (!fallback) throw err;
335
- console.warn(
336
- `[x402] facilitator /supported failed, using hardcoded baseline: ${err instanceof Error ? err.message : String(err)}`
337
- );
338
- return null;
339
- }
340
- }
341
- async function readKvCache(kv, key) {
342
- try {
343
- const cached = await kv.get(key);
344
- return isSupportedResponse(cached) ? cached : void 0;
345
- } catch {
346
- return void 0;
347
- }
348
- }
349
- async function writeKvCache(kv, key, value, ttlSeconds) {
350
- try {
351
- await kv.setNxEx(key, value, ttlSeconds);
352
- } catch {
353
- }
354
- }
355
- function isSupportedResponse(value) {
356
- return typeof value === "object" && value !== null && Array.isArray(value.kinds);
357
- }
358
- var FACILITATOR_SUPPORTED_TTL_SECONDS, FACILITATOR_SUPPORTED_KV_PREFIX;
359
- var init_facilitator_supported = __esm({
360
- "src/kv-store/facilitator-supported.ts"() {
361
- "use strict";
362
- FACILITATOR_SUPPORTED_TTL_SECONDS = 60 * 60;
363
- FACILITATOR_SUPPORTED_KV_PREFIX = "x402:facilitator-supported:";
364
- }
365
- });
366
-
367
383
  // src/protocols/x402/facilitator-clients.ts
368
384
  function createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient, kvStore) {
369
385
  return getResolvedX402FacilitatorGroups(facilitatorsByNetwork).map((group) => {
@@ -411,14 +427,7 @@ function mergeKindExtras(scoped, live) {
411
427
  function buildSupportedKinds(group) {
412
428
  return group.networks.flatMap((network) => {
413
429
  if (group.family === "solana") {
414
- return [
415
- {
416
- x402Version: 2,
417
- scheme: "exact",
418
- network,
419
- extra: { features: { xSettlementAccountSupported: true } }
420
- }
421
- ];
430
+ return [{ x402Version: 2, scheme: "exact", network }];
422
431
  }
423
432
  return [
424
433
  { x402Version: 2, scheme: "exact", network },
@@ -1997,7 +2006,17 @@ function buildCustomRequirement(price, accept) {
1997
2006
 
1998
2007
  // src/protocols/x402/challenge.ts
1999
2008
  async function buildX402Challenge(opts) {
2000
- const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions, report } = opts;
2009
+ const {
2010
+ server,
2011
+ routeEntry,
2012
+ request,
2013
+ price,
2014
+ accepts,
2015
+ facilitatorsByNetwork,
2016
+ extensions,
2017
+ report,
2018
+ kvStore
2019
+ } = opts;
2001
2020
  const { encodePaymentRequiredHeader } = await import("@x402/core/http");
2002
2021
  const resource = buildChallengeResource(request, routeEntry);
2003
2022
  const requirements = await buildChallengeRequirements(
@@ -2005,9 +2024,9 @@ async function buildX402Challenge(opts) {
2005
2024
  request,
2006
2025
  price,
2007
2026
  accepts,
2008
- resource,
2009
2027
  facilitatorsByNetwork,
2010
- report
2028
+ report,
2029
+ kvStore
2011
2030
  );
2012
2031
  const paymentRequired = await server.createPaymentRequiredResponse(
2013
2032
  requirements,
@@ -2018,18 +2037,19 @@ async function buildX402Challenge(opts) {
2018
2037
  const encoded = encodePaymentRequiredHeader(paymentRequired);
2019
2038
  return { encoded, requirements };
2020
2039
  }
2021
- async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork, report) {
2040
+ async function buildChallengeRequirements(server, request, price, accepts, facilitatorsByNetwork, report, kvStore) {
2022
2041
  const requirements = await buildExpectedRequirements(server, request, price, accepts, report);
2023
2042
  if (!needsFacilitatorEnrichment(accepts)) return requirements;
2024
- return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork, report);
2043
+ return enrichChallengeRequirements(requirements, facilitatorsByNetwork, report, kvStore);
2025
2044
  }
2026
2045
  function needsFacilitatorEnrichment(accepts) {
2027
2046
  return hasSolanaAccepts(accepts);
2028
2047
  }
2029
- async function enrichGroup(group) {
2048
+ async function enrichGroup(group, kvStore) {
2030
2049
  const accepted = await enrichRequirementsFromFacilitatorSupported(
2031
2050
  group.facilitator,
2032
- group.items.map(({ requirement }) => requirement)
2051
+ group.items.map(({ requirement }) => requirement),
2052
+ kvStore
2033
2053
  );
2034
2054
  if (accepted.length !== group.items.length) {
2035
2055
  throw new Error(
@@ -2038,13 +2058,13 @@ async function enrichGroup(group) {
2038
2058
  }
2039
2059
  return accepted;
2040
2060
  }
2041
- async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork, report) {
2061
+ async function enrichChallengeRequirements(requirements, facilitatorsByNetwork, report, kvStore) {
2042
2062
  const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
2043
2063
  if (groups.length === 0) return requirements;
2044
2064
  const results = await Promise.all(
2045
2065
  groups.map(async (group) => {
2046
2066
  try {
2047
- return { success: true, group, accepted: await enrichGroup(group) };
2067
+ return { success: true, group, accepted: await enrichGroup(group, kvStore) };
2048
2068
  } catch (err) {
2049
2069
  const label = group.facilitator.url ?? group.facilitator.network;
2050
2070
  const reason = err instanceof Error ? err.message : String(err);
@@ -2370,7 +2390,8 @@ async function buildX402ChallengeContribution(args) {
2370
2390
  accepts,
2371
2391
  facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
2372
2392
  extensions,
2373
- report
2393
+ report,
2394
+ kvStore: deps.kvStore
2374
2395
  });
2375
2396
  return { headers: { [HEADERS.X402_PAYMENT_REQUIRED]: encoded } };
2376
2397
  }
@@ -5392,6 +5413,7 @@ function createRouter(config) {
5392
5413
  network,
5393
5414
  x402FacilitatorsByNetwork: void 0,
5394
5415
  x402Accepts,
5416
+ kvStore,
5395
5417
  mppx: null,
5396
5418
  tempoClient: null,
5397
5419
  mppSessionConfig: config.mpp?.session && config.mpp.operatorKey ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
package/dist/index.d.cts CHANGED
@@ -545,6 +545,7 @@ interface RouterDeps {
545
545
  network: string;
546
546
  x402FacilitatorsByNetwork?: Record<string, ResolvedX402Facilitator>;
547
547
  x402Accepts: X402AcceptConfig[];
548
+ kvStore?: KvStore;
548
549
  mppx?: {
549
550
  charge: MppxMiddleware<{
550
551
  amount: string;
@@ -1024,9 +1025,6 @@ declare function createRouter<const C extends RouterConfig>(config: C): ServiceR
1024
1025
  * });
1025
1026
  * ```
1026
1027
  */
1027
- type ExtractEnvPriceKeys<O> = [O] extends [{
1028
- prices: infer P extends Record<string, string>;
1029
- }] ? Extract<keyof P, string> : never;
1030
- declare function createRouterFromEnv<const O extends CreateRouterFromEnvOptions>(options: O): ServiceRouter<ExtractEnvPriceKeys<O>>;
1028
+ declare function createRouterFromEnv<const O extends CreateRouterFromEnvOptions>(options: O): ServiceRouter<ExtractPriceKeys<O>>;
1031
1029
 
1032
1030
  export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CheckoutSessionContext, type CheckoutSessionFn, type CheckoutSessionResponse, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type MppProtocolInfo, type PaidOptions, type ProtocolType, RouteDefinitionError, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
package/dist/index.d.ts CHANGED
@@ -545,6 +545,7 @@ interface RouterDeps {
545
545
  network: string;
546
546
  x402FacilitatorsByNetwork?: Record<string, ResolvedX402Facilitator>;
547
547
  x402Accepts: X402AcceptConfig[];
548
+ kvStore?: KvStore;
548
549
  mppx?: {
549
550
  charge: MppxMiddleware<{
550
551
  amount: string;
@@ -1024,9 +1025,6 @@ declare function createRouter<const C extends RouterConfig>(config: C): ServiceR
1024
1025
  * });
1025
1026
  * ```
1026
1027
  */
1027
- type ExtractEnvPriceKeys<O> = [O] extends [{
1028
- prices: infer P extends Record<string, string>;
1029
- }] ? Extract<keyof P, string> : never;
1030
- declare function createRouterFromEnv<const O extends CreateRouterFromEnvOptions>(options: O): ServiceRouter<ExtractEnvPriceKeys<O>>;
1028
+ declare function createRouterFromEnv<const O extends CreateRouterFromEnvOptions>(options: O): ServiceRouter<ExtractPriceKeys<O>>;
1031
1029
 
1032
1030
  export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CheckoutSessionContext, type CheckoutSessionFn, type CheckoutSessionResponse, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type MppProtocolInfo, type PaidOptions, type ProtocolType, RouteDefinitionError, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
package/dist/index.js CHANGED
@@ -109,6 +109,75 @@ var init_evm = __esm({
109
109
  }
110
110
  });
111
111
 
112
+ // src/kv-store/facilitator-supported.ts
113
+ function createCachedSupportedFetch(fetchLive, options = {}) {
114
+ const { kv, cacheKey, ttlSeconds = FACILITATOR_SUPPORTED_TTL_SECONDS, fallback } = options;
115
+ const kvKey = kv && cacheKey ? `${FACILITATOR_SUPPORTED_KV_PREFIX}${cacheKey}` : void 0;
116
+ let inflight;
117
+ return () => {
118
+ if (inflight) return inflight;
119
+ const attempt = fetchSupported(fetchLive, kv, kvKey, ttlSeconds, fallback);
120
+ inflight = attempt;
121
+ attempt.catch(() => {
122
+ if (inflight === attempt) inflight = void 0;
123
+ });
124
+ return attempt;
125
+ };
126
+ }
127
+ function withCachedSupported(inner, options = {}) {
128
+ return {
129
+ verify: inner.verify.bind(inner),
130
+ settle: inner.settle.bind(inner),
131
+ getSupported: createCachedSupportedFetch(() => inner.getSupported(), options)
132
+ };
133
+ }
134
+ async function fetchSupported(fetchLive, kv, kvKey, ttlSeconds, fallback) {
135
+ if (kv && kvKey) {
136
+ const cached = await readKvCache(kv, kvKey);
137
+ if (cached) return cached;
138
+ }
139
+ const fresh = await tryFetchLive(fetchLive, fallback);
140
+ if (fresh === null) return fallback();
141
+ if (kv && kvKey) await writeKvCache(kv, kvKey, fresh, ttlSeconds);
142
+ return fresh;
143
+ }
144
+ async function tryFetchLive(fetchLive, fallback) {
145
+ try {
146
+ return await fetchLive();
147
+ } catch (err) {
148
+ if (!fallback) throw err;
149
+ console.warn(
150
+ `[x402] facilitator /supported failed, using hardcoded baseline: ${err instanceof Error ? err.message : String(err)}`
151
+ );
152
+ return null;
153
+ }
154
+ }
155
+ async function readKvCache(kv, key) {
156
+ try {
157
+ const cached = await kv.get(key);
158
+ return isKindsResponse(cached) ? cached : void 0;
159
+ } catch {
160
+ return void 0;
161
+ }
162
+ }
163
+ async function writeKvCache(kv, key, value, ttlSeconds) {
164
+ try {
165
+ await kv.setNxEx(key, value, ttlSeconds);
166
+ } catch {
167
+ }
168
+ }
169
+ function isKindsResponse(value) {
170
+ return typeof value === "object" && value !== null && Array.isArray(value.kinds);
171
+ }
172
+ var FACILITATOR_SUPPORTED_TTL_SECONDS, FACILITATOR_SUPPORTED_KV_PREFIX;
173
+ var init_facilitator_supported = __esm({
174
+ "src/kv-store/facilitator-supported.ts"() {
175
+ "use strict";
176
+ FACILITATOR_SUPPORTED_TTL_SECONDS = 60 * 60;
177
+ FACILITATOR_SUPPORTED_KV_PREFIX = "x402:facilitator-supported:";
178
+ }
179
+ });
180
+
112
181
  // src/protocols/x402/solana.ts
113
182
  function isSolanaNetwork(network) {
114
183
  return network.startsWith("solana:");
@@ -142,7 +211,8 @@ function matchesSupportedKind(requirement, kind) {
142
211
  async function fetchFacilitatorSupported(facilitator) {
143
212
  const authHeaders = await getSupportedHeadersForFacilitator(facilitator);
144
213
  const response = await fetch(`${facilitatorBaseUrl(facilitator)}/supported`, {
145
- headers: authHeaders
214
+ headers: authHeaders,
215
+ signal: AbortSignal.timeout(SUPPORTED_FETCH_TIMEOUT_MS)
146
216
  });
147
217
  if (!response.ok) {
148
218
  throw new Error(`Facilitator /supported failed with status ${response.status}`);
@@ -151,7 +221,18 @@ async function fetchFacilitatorSupported(facilitator) {
151
221
  if (!Array.isArray(body.kinds)) {
152
222
  throw new Error("Facilitator /supported response did not include kinds");
153
223
  }
154
- return body.kinds;
224
+ return { kinds: body.kinds };
225
+ }
226
+ function getCachedSupportedFetch(facilitator, kv) {
227
+ let fetcher = supportedFetchers.get(facilitator);
228
+ if (!fetcher) {
229
+ fetcher = createCachedSupportedFetch(() => fetchFacilitatorSupported(facilitator), {
230
+ kv,
231
+ cacheKey: facilitator.url
232
+ });
233
+ supportedFetchers.set(facilitator, fetcher);
234
+ }
235
+ return fetcher;
155
236
  }
156
237
  function enrichRequirementFromKind(requirement, kinds) {
157
238
  const kind = kinds.find((candidate) => matchesSupportedKind(requirement, candidate));
@@ -170,31 +251,32 @@ function enrichRequirementFromKind(requirement, kinds) {
170
251
  const kindExtra = kind.extra ?? {};
171
252
  const requirementFeatures = requirementExtra.features ?? {};
172
253
  const kindFeatures = kindExtra.features ?? {};
254
+ const features = { ...requirementFeatures, ...kindFeatures };
173
255
  return {
174
256
  ...requirement,
175
257
  ...kind.asset && !requirement.asset ? { asset: kind.asset } : {},
176
258
  extra: {
177
259
  ...requirementExtra,
178
260
  ...kindExtra,
179
- features: {
180
- ...requirementFeatures,
181
- ...kindFeatures,
182
- xSettlementAccountSupported: true
183
- }
261
+ ...Object.keys(features).length > 0 ? { features } : {}
184
262
  }
185
263
  };
186
264
  }
187
- async function enrichRequirementsFromFacilitatorSupported(facilitator, requirements) {
188
- const kinds = await fetchFacilitatorSupported(facilitator);
265
+ async function enrichRequirementsFromFacilitatorSupported(facilitator, requirements, kv) {
266
+ const { kinds } = await getCachedSupportedFetch(facilitator, kv)();
189
267
  return requirements.map((requirement) => enrichRequirementFromKind(requirement, kinds));
190
268
  }
191
269
  function isSolanaRequirement(requirement) {
192
270
  return isSolanaNetwork(requirement.network);
193
271
  }
272
+ var SUPPORTED_FETCH_TIMEOUT_MS, supportedFetchers;
194
273
  var init_solana = __esm({
195
274
  "src/protocols/x402/solana.ts"() {
196
275
  "use strict";
276
+ init_facilitator_supported();
197
277
  init_facilitators();
278
+ SUPPORTED_FETCH_TIMEOUT_MS = 1e4;
279
+ supportedFetchers = /* @__PURE__ */ new WeakMap();
198
280
  }
199
281
  });
200
282
 
@@ -276,72 +358,6 @@ var init_facilitators = __esm({
276
358
  }
277
359
  });
278
360
 
279
- // src/kv-store/facilitator-supported.ts
280
- function withCachedSupported(inner, options = {}) {
281
- const { kv, cacheKey, ttlSeconds = FACILITATOR_SUPPORTED_TTL_SECONDS, fallback } = options;
282
- const kvKey = kv && cacheKey ? `${FACILITATOR_SUPPORTED_KV_PREFIX}${cacheKey}` : void 0;
283
- let inflight;
284
- return {
285
- verify: inner.verify.bind(inner),
286
- settle: inner.settle.bind(inner),
287
- getSupported: () => {
288
- if (inflight) return inflight;
289
- const attempt = fetchSupported(inner, kv, kvKey, ttlSeconds, fallback);
290
- inflight = attempt;
291
- attempt.catch(() => {
292
- if (inflight === attempt) inflight = void 0;
293
- });
294
- return attempt;
295
- }
296
- };
297
- }
298
- async function fetchSupported(inner, kv, kvKey, ttlSeconds, fallback) {
299
- if (kv && kvKey) {
300
- const cached = await readKvCache(kv, kvKey);
301
- if (cached) return cached;
302
- }
303
- const fresh = await tryFetchLive(inner, fallback);
304
- if (fresh === null) return fallback();
305
- if (kv && kvKey) await writeKvCache(kv, kvKey, fresh, ttlSeconds);
306
- return fresh;
307
- }
308
- async function tryFetchLive(inner, fallback) {
309
- try {
310
- return await inner.getSupported();
311
- } catch (err) {
312
- if (!fallback) throw err;
313
- console.warn(
314
- `[x402] facilitator /supported failed, using hardcoded baseline: ${err instanceof Error ? err.message : String(err)}`
315
- );
316
- return null;
317
- }
318
- }
319
- async function readKvCache(kv, key) {
320
- try {
321
- const cached = await kv.get(key);
322
- return isSupportedResponse(cached) ? cached : void 0;
323
- } catch {
324
- return void 0;
325
- }
326
- }
327
- async function writeKvCache(kv, key, value, ttlSeconds) {
328
- try {
329
- await kv.setNxEx(key, value, ttlSeconds);
330
- } catch {
331
- }
332
- }
333
- function isSupportedResponse(value) {
334
- return typeof value === "object" && value !== null && Array.isArray(value.kinds);
335
- }
336
- var FACILITATOR_SUPPORTED_TTL_SECONDS, FACILITATOR_SUPPORTED_KV_PREFIX;
337
- var init_facilitator_supported = __esm({
338
- "src/kv-store/facilitator-supported.ts"() {
339
- "use strict";
340
- FACILITATOR_SUPPORTED_TTL_SECONDS = 60 * 60;
341
- FACILITATOR_SUPPORTED_KV_PREFIX = "x402:facilitator-supported:";
342
- }
343
- });
344
-
345
361
  // src/protocols/x402/facilitator-clients.ts
346
362
  function createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient, kvStore) {
347
363
  return getResolvedX402FacilitatorGroups(facilitatorsByNetwork).map((group) => {
@@ -389,14 +405,7 @@ function mergeKindExtras(scoped, live) {
389
405
  function buildSupportedKinds(group) {
390
406
  return group.networks.flatMap((network) => {
391
407
  if (group.family === "solana") {
392
- return [
393
- {
394
- x402Version: 2,
395
- scheme: "exact",
396
- network,
397
- extra: { features: { xSettlementAccountSupported: true } }
398
- }
399
- ];
408
+ return [{ x402Version: 2, scheme: "exact", network }];
400
409
  }
401
410
  return [
402
411
  { x402Version: 2, scheme: "exact", network },
@@ -1958,7 +1967,17 @@ function buildCustomRequirement(price, accept) {
1958
1967
 
1959
1968
  // src/protocols/x402/challenge.ts
1960
1969
  async function buildX402Challenge(opts) {
1961
- const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions, report } = opts;
1970
+ const {
1971
+ server,
1972
+ routeEntry,
1973
+ request,
1974
+ price,
1975
+ accepts,
1976
+ facilitatorsByNetwork,
1977
+ extensions,
1978
+ report,
1979
+ kvStore
1980
+ } = opts;
1962
1981
  const { encodePaymentRequiredHeader } = await import("@x402/core/http");
1963
1982
  const resource = buildChallengeResource(request, routeEntry);
1964
1983
  const requirements = await buildChallengeRequirements(
@@ -1966,9 +1985,9 @@ async function buildX402Challenge(opts) {
1966
1985
  request,
1967
1986
  price,
1968
1987
  accepts,
1969
- resource,
1970
1988
  facilitatorsByNetwork,
1971
- report
1989
+ report,
1990
+ kvStore
1972
1991
  );
1973
1992
  const paymentRequired = await server.createPaymentRequiredResponse(
1974
1993
  requirements,
@@ -1979,18 +1998,19 @@ async function buildX402Challenge(opts) {
1979
1998
  const encoded = encodePaymentRequiredHeader(paymentRequired);
1980
1999
  return { encoded, requirements };
1981
2000
  }
1982
- async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork, report) {
2001
+ async function buildChallengeRequirements(server, request, price, accepts, facilitatorsByNetwork, report, kvStore) {
1983
2002
  const requirements = await buildExpectedRequirements(server, request, price, accepts, report);
1984
2003
  if (!needsFacilitatorEnrichment(accepts)) return requirements;
1985
- return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork, report);
2004
+ return enrichChallengeRequirements(requirements, facilitatorsByNetwork, report, kvStore);
1986
2005
  }
1987
2006
  function needsFacilitatorEnrichment(accepts) {
1988
2007
  return hasSolanaAccepts(accepts);
1989
2008
  }
1990
- async function enrichGroup(group) {
2009
+ async function enrichGroup(group, kvStore) {
1991
2010
  const accepted = await enrichRequirementsFromFacilitatorSupported(
1992
2011
  group.facilitator,
1993
- group.items.map(({ requirement }) => requirement)
2012
+ group.items.map(({ requirement }) => requirement),
2013
+ kvStore
1994
2014
  );
1995
2015
  if (accepted.length !== group.items.length) {
1996
2016
  throw new Error(
@@ -1999,13 +2019,13 @@ async function enrichGroup(group) {
1999
2019
  }
2000
2020
  return accepted;
2001
2021
  }
2002
- async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork, report) {
2022
+ async function enrichChallengeRequirements(requirements, facilitatorsByNetwork, report, kvStore) {
2003
2023
  const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
2004
2024
  if (groups.length === 0) return requirements;
2005
2025
  const results = await Promise.all(
2006
2026
  groups.map(async (group) => {
2007
2027
  try {
2008
- return { success: true, group, accepted: await enrichGroup(group) };
2028
+ return { success: true, group, accepted: await enrichGroup(group, kvStore) };
2009
2029
  } catch (err) {
2010
2030
  const label = group.facilitator.url ?? group.facilitator.network;
2011
2031
  const reason = err instanceof Error ? err.message : String(err);
@@ -2331,7 +2351,8 @@ async function buildX402ChallengeContribution(args) {
2331
2351
  accepts,
2332
2352
  facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
2333
2353
  extensions,
2334
- report
2354
+ report,
2355
+ kvStore: deps.kvStore
2335
2356
  });
2336
2357
  return { headers: { [HEADERS.X402_PAYMENT_REQUIRED]: encoded } };
2337
2358
  }
@@ -5353,6 +5374,7 @@ function createRouter(config) {
5353
5374
  network,
5354
5375
  x402FacilitatorsByNetwork: void 0,
5355
5376
  x402Accepts,
5377
+ kvStore,
5356
5378
  mppx: null,
5357
5379
  tempoClient: null,
5358
5380
  mppSessionConfig: config.mpp?.session && config.mpp.operatorKey ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/router",
3
- "version": "1.14.1",
3
+ "version": "1.15.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": {
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",
68
- "typecheck": "tsc --noEmit && tsc -p tests/tsconfig.json --noEmit",
68
+ "typecheck": "tsc --noEmit",
69
69
  "lint": "eslint src/ tests/ --max-warnings 0",
70
70
  "lint:fix": "eslint src/ tests/ --fix",
71
71
  "format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts' 'examples/fortune/**/*.ts' 'examples/fortune/**/*.md' '*.json' '*.mjs'",