@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 +115 -93
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +115 -93
- package/package.json +2 -2
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
|
|
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 {
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
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'",
|