@agentcash/router 1.7.0 → 1.8.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/README.md +2 -0
- package/dist/index.cjs +680 -379
- package/dist/index.d.cts +111 -114
- package/dist/index.d.ts +111 -114
- package/dist/index.js +678 -377
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -45,6 +45,9 @@ function getConfiguredX402Accepts(config) {
|
|
|
45
45
|
function getConfiguredX402Networks(config) {
|
|
46
46
|
return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
|
|
47
47
|
}
|
|
48
|
+
function selectRouteAccepts(accepts, routeEntry) {
|
|
49
|
+
return routeEntry.billing === "upto" ? accepts.filter((accept) => accept.scheme === "upto") : accepts.filter((accept) => (accept.scheme ?? "exact") !== "upto");
|
|
50
|
+
}
|
|
48
51
|
async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
|
|
49
52
|
return Promise.all(
|
|
50
53
|
accepts.map(async (accept) => ({
|
|
@@ -242,12 +245,148 @@ var init_facilitators = __esm({
|
|
|
242
245
|
}
|
|
243
246
|
});
|
|
244
247
|
|
|
248
|
+
// src/kv-store/facilitator-supported.ts
|
|
249
|
+
function withCachedSupported(inner, options = {}) {
|
|
250
|
+
const { kv, cacheKey, ttlSeconds = FACILITATOR_SUPPORTED_TTL_SECONDS, fallback } = options;
|
|
251
|
+
const kvKey = kv && cacheKey ? `${FACILITATOR_SUPPORTED_KV_PREFIX}${cacheKey}` : void 0;
|
|
252
|
+
let inflight;
|
|
253
|
+
return {
|
|
254
|
+
verify: inner.verify.bind(inner),
|
|
255
|
+
settle: inner.settle.bind(inner),
|
|
256
|
+
getSupported: () => {
|
|
257
|
+
if (inflight) return inflight;
|
|
258
|
+
const attempt = fetchSupported(inner, kv, kvKey, ttlSeconds, fallback);
|
|
259
|
+
inflight = attempt;
|
|
260
|
+
attempt.catch(() => {
|
|
261
|
+
if (inflight === attempt) inflight = void 0;
|
|
262
|
+
});
|
|
263
|
+
return attempt;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async function fetchSupported(inner, kv, kvKey, ttlSeconds, fallback) {
|
|
268
|
+
if (kv && kvKey) {
|
|
269
|
+
const cached = await readKvCache(kv, kvKey);
|
|
270
|
+
if (cached) return cached;
|
|
271
|
+
}
|
|
272
|
+
const fresh = await tryFetchLive(inner, fallback);
|
|
273
|
+
if (fresh === null) return fallback();
|
|
274
|
+
if (kv && kvKey) await writeKvCache(kv, kvKey, fresh, ttlSeconds);
|
|
275
|
+
return fresh;
|
|
276
|
+
}
|
|
277
|
+
async function tryFetchLive(inner, fallback) {
|
|
278
|
+
try {
|
|
279
|
+
return await inner.getSupported();
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (!fallback) throw err;
|
|
282
|
+
console.warn(
|
|
283
|
+
`[x402] facilitator /supported failed, using hardcoded baseline: ${err instanceof Error ? err.message : String(err)}`
|
|
284
|
+
);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function readKvCache(kv, key) {
|
|
289
|
+
try {
|
|
290
|
+
const cached = await kv.get(key);
|
|
291
|
+
return isSupportedResponse(cached) ? cached : void 0;
|
|
292
|
+
} catch {
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function writeKvCache(kv, key, value, ttlSeconds) {
|
|
297
|
+
try {
|
|
298
|
+
await kv.setNxEx(key, value, ttlSeconds);
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function isSupportedResponse(value) {
|
|
303
|
+
return typeof value === "object" && value !== null && Array.isArray(value.kinds);
|
|
304
|
+
}
|
|
305
|
+
var FACILITATOR_SUPPORTED_TTL_SECONDS, FACILITATOR_SUPPORTED_KV_PREFIX;
|
|
306
|
+
var init_facilitator_supported = __esm({
|
|
307
|
+
"src/kv-store/facilitator-supported.ts"() {
|
|
308
|
+
"use strict";
|
|
309
|
+
FACILITATOR_SUPPORTED_TTL_SECONDS = 60 * 60;
|
|
310
|
+
FACILITATOR_SUPPORTED_KV_PREFIX = "x402:facilitator-supported:";
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// src/protocols/x402/facilitator-clients.ts
|
|
315
|
+
function createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient, kvStore) {
|
|
316
|
+
return getResolvedX402FacilitatorGroups(facilitatorsByNetwork).map((group) => {
|
|
317
|
+
const inner = new HTTPFacilitatorClient(group.config);
|
|
318
|
+
const kinds = buildSupportedKinds(group);
|
|
319
|
+
const baseline = () => ({
|
|
320
|
+
kinds,
|
|
321
|
+
extensions: [],
|
|
322
|
+
signers: {}
|
|
323
|
+
});
|
|
324
|
+
if (group.family === "solana") {
|
|
325
|
+
return hardcodedSupportedClient(inner, baseline);
|
|
326
|
+
}
|
|
327
|
+
const cached = withCachedSupported(inner, {
|
|
328
|
+
kv: kvStore,
|
|
329
|
+
cacheKey: group.config.url,
|
|
330
|
+
fallback: baseline
|
|
331
|
+
});
|
|
332
|
+
return withScopedKinds(cached, kinds);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
function hardcodedSupportedClient(inner, build) {
|
|
336
|
+
return {
|
|
337
|
+
verify: inner.verify.bind(inner),
|
|
338
|
+
settle: inner.settle.bind(inner),
|
|
339
|
+
getSupported: async () => build()
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function withScopedKinds(client, kinds) {
|
|
343
|
+
return {
|
|
344
|
+
verify: client.verify.bind(client),
|
|
345
|
+
settle: client.settle.bind(client),
|
|
346
|
+
getSupported: async () => {
|
|
347
|
+
const live = await client.getSupported();
|
|
348
|
+
return { ...live, kinds: mergeKindExtras(kinds, live.kinds) };
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function mergeKindExtras(scoped, live) {
|
|
353
|
+
return scoped.map((kind) => {
|
|
354
|
+
const match = live.find((l) => l.scheme === kind.scheme && l.network === kind.network);
|
|
355
|
+
return match?.extra ? { ...kind, extra: { ...kind.extra, ...match.extra } } : kind;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function buildSupportedKinds(group) {
|
|
359
|
+
return group.networks.flatMap((network) => {
|
|
360
|
+
if (group.family === "solana") {
|
|
361
|
+
return [
|
|
362
|
+
{
|
|
363
|
+
x402Version: 2,
|
|
364
|
+
scheme: "exact",
|
|
365
|
+
network,
|
|
366
|
+
extra: { features: { xSettlementAccountSupported: true } }
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
}
|
|
370
|
+
return [
|
|
371
|
+
{ x402Version: 2, scheme: "exact", network },
|
|
372
|
+
{ x402Version: 2, scheme: "upto", network }
|
|
373
|
+
];
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
var init_facilitator_clients = __esm({
|
|
377
|
+
"src/protocols/x402/facilitator-clients.ts"() {
|
|
378
|
+
"use strict";
|
|
379
|
+
init_facilitator_supported();
|
|
380
|
+
init_facilitators();
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
245
384
|
// src/init/x402-server.ts
|
|
246
385
|
var x402_server_exports = {};
|
|
247
386
|
__export(x402_server_exports, {
|
|
248
387
|
createX402Server: () => createX402Server
|
|
249
388
|
});
|
|
250
|
-
async function createX402Server(config) {
|
|
389
|
+
async function createX402Server(config, kvStore) {
|
|
251
390
|
const { x402ResourceServer, HTTPFacilitatorClient } = await import("@x402/core/server");
|
|
252
391
|
const { registerExactEvmScheme } = await import("@x402/evm/exact/server");
|
|
253
392
|
const { bazaarResourceServerExtension } = await import("@x402/extensions/bazaar");
|
|
@@ -261,7 +400,11 @@ async function createX402Server(config) {
|
|
|
261
400
|
);
|
|
262
401
|
const evmNetworks = filterEvmNetworks(configuredNetworks);
|
|
263
402
|
const svmNetworks = filterSolanaNetworks(configuredNetworks);
|
|
264
|
-
const facilitatorClients = createFacilitatorClients(
|
|
403
|
+
const facilitatorClients = createFacilitatorClients(
|
|
404
|
+
facilitatorsByNetwork,
|
|
405
|
+
HTTPFacilitatorClient,
|
|
406
|
+
kvStore
|
|
407
|
+
);
|
|
265
408
|
const server = new x402ResourceServer(
|
|
266
409
|
facilitatorClients.length === 1 ? facilitatorClients[0] : facilitatorClients
|
|
267
410
|
);
|
|
@@ -285,48 +428,13 @@ async function createX402Server(config) {
|
|
|
285
428
|
facilitatorsByNetwork
|
|
286
429
|
};
|
|
287
430
|
}
|
|
288
|
-
function createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient) {
|
|
289
|
-
const groups = getResolvedX402FacilitatorGroups(facilitatorsByNetwork);
|
|
290
|
-
return groups.map((group) => {
|
|
291
|
-
const inner = new HTTPFacilitatorClient(group.config);
|
|
292
|
-
const kinds = buildSupportedKinds(group);
|
|
293
|
-
return hardcodedSupportedClient(inner, kinds);
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
function hardcodedSupportedClient(inner, kinds) {
|
|
297
|
-
return {
|
|
298
|
-
verify: inner.verify.bind(inner),
|
|
299
|
-
settle: inner.settle.bind(inner),
|
|
300
|
-
getSupported: async () => ({ kinds, extensions: [], signers: {} })
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
function buildSupportedKinds(group) {
|
|
304
|
-
return group.networks.flatMap((network) => {
|
|
305
|
-
const exactKind = {
|
|
306
|
-
x402Version: 2,
|
|
307
|
-
scheme: "exact",
|
|
308
|
-
network,
|
|
309
|
-
...group.family === "solana" ? {
|
|
310
|
-
extra: {
|
|
311
|
-
features: {
|
|
312
|
-
xSettlementAccountSupported: true
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
} : {}
|
|
316
|
-
};
|
|
317
|
-
const uptoKind = { x402Version: 2, scheme: "upto", network };
|
|
318
|
-
if (group.family === "evm") {
|
|
319
|
-
return [exactKind, uptoKind];
|
|
320
|
-
}
|
|
321
|
-
return [exactKind, uptoKind];
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
431
|
var init_x402_server = __esm({
|
|
325
432
|
"src/init/x402-server.ts"() {
|
|
326
433
|
"use strict";
|
|
327
434
|
init_evm();
|
|
328
435
|
init_solana();
|
|
329
436
|
init_facilitators();
|
|
437
|
+
init_facilitator_clients();
|
|
330
438
|
init_accepts();
|
|
331
439
|
}
|
|
332
440
|
});
|
|
@@ -658,10 +766,7 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
658
766
|
}
|
|
659
767
|
if (isAsyncIterable(returned) && !isThenable(returned)) {
|
|
660
768
|
return errorResult(
|
|
661
|
-
new HttpError(
|
|
662
|
-
`route '${ctx.routeEntry.key}': streaming handlers require .paid({ dynamic: true })`,
|
|
663
|
-
500
|
|
664
|
-
)
|
|
769
|
+
new HttpError(`route '${ctx.routeEntry.key}': streaming handlers require .metered()`, 500)
|
|
665
770
|
);
|
|
666
771
|
}
|
|
667
772
|
let rawResult;
|
|
@@ -1028,6 +1133,61 @@ async function runApiKeyOnlyFlow(ctx) {
|
|
|
1028
1133
|
return runHandlerOnly(ctx, null, result.account);
|
|
1029
1134
|
}
|
|
1030
1135
|
|
|
1136
|
+
// src/pricing/format.ts
|
|
1137
|
+
var USDC_DECIMALS = 6;
|
|
1138
|
+
var DECIMAL_RE = /^(\d+)(?:\.(\d+))?$/;
|
|
1139
|
+
function badDecimal(amount) {
|
|
1140
|
+
return Object.assign(new Error(`'${amount}' is not a valid decimal-dollar string`), {
|
|
1141
|
+
status: 400
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
function decimalToAtomic(amount, decimals = USDC_DECIMALS) {
|
|
1145
|
+
const match = DECIMAL_RE.exec(amount.trim());
|
|
1146
|
+
if (!match) throw badDecimal(amount);
|
|
1147
|
+
const whole = match[1];
|
|
1148
|
+
const fraction = match[2] ?? "";
|
|
1149
|
+
if (fraction.length > decimals) {
|
|
1150
|
+
throw Object.assign(new Error(`Amount '${amount}' exceeds ${decimals} decimal places`), {
|
|
1151
|
+
status: 400
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
|
|
1155
|
+
return BigInt(normalized || "0");
|
|
1156
|
+
}
|
|
1157
|
+
function atomicToDecimal(atomic, decimals = USDC_DECIMALS) {
|
|
1158
|
+
const divisor = 10n ** BigInt(decimals);
|
|
1159
|
+
const whole = atomic / divisor;
|
|
1160
|
+
const fraction = atomic % divisor;
|
|
1161
|
+
if (fraction === 0n) return whole.toString();
|
|
1162
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
1163
|
+
return `${whole}.${fractionStr}`;
|
|
1164
|
+
}
|
|
1165
|
+
function compareDecimals(a, b) {
|
|
1166
|
+
const av = decimalToAtomic(a);
|
|
1167
|
+
const bv = decimalToAtomic(b);
|
|
1168
|
+
if (av < bv) return -1;
|
|
1169
|
+
if (av > bv) return 1;
|
|
1170
|
+
return 0;
|
|
1171
|
+
}
|
|
1172
|
+
function isPositiveDecimal(value) {
|
|
1173
|
+
try {
|
|
1174
|
+
return decimalToAtomic(value) > 0n;
|
|
1175
|
+
} catch {
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function multiplyDecimal(decimal, factor) {
|
|
1180
|
+
if (!Number.isFinite(factor) || factor <= 0) return decimal;
|
|
1181
|
+
const [whole, fraction = ""] = decimal.split(".");
|
|
1182
|
+
const scaled = (BigInt(whole + fraction) * BigInt(factor)).toString();
|
|
1183
|
+
const decimals = fraction.length;
|
|
1184
|
+
if (decimals === 0) return scaled;
|
|
1185
|
+
const padded = scaled.padStart(decimals + 1, "0");
|
|
1186
|
+
const intPart = padded.slice(0, padded.length - decimals);
|
|
1187
|
+
const fracPart = padded.slice(padded.length - decimals).replace(/0+$/, "");
|
|
1188
|
+
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1031
1191
|
// src/pricing/dynamic.ts
|
|
1032
1192
|
var DynamicPricing = class {
|
|
1033
1193
|
constructor(opts) {
|
|
@@ -1039,6 +1199,7 @@ var DynamicPricing = class {
|
|
|
1039
1199
|
const raw = await this.opts.fn(body);
|
|
1040
1200
|
return this.cap(raw, body);
|
|
1041
1201
|
} catch (err) {
|
|
1202
|
+
if (err instanceof HttpError) throw err;
|
|
1042
1203
|
this.alert("error", `Pricing function failed: ${msg(err)}`, {
|
|
1043
1204
|
error: err instanceof Error ? err.stack : String(err),
|
|
1044
1205
|
body
|
|
@@ -1063,9 +1224,13 @@ var DynamicPricing = class {
|
|
|
1063
1224
|
}
|
|
1064
1225
|
cap(raw, body) {
|
|
1065
1226
|
if (!this.opts.maxPrice) return raw;
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1227
|
+
let overCap;
|
|
1228
|
+
try {
|
|
1229
|
+
overCap = compareDecimals(raw, this.opts.maxPrice) > 0;
|
|
1230
|
+
} catch {
|
|
1231
|
+
overCap = true;
|
|
1232
|
+
}
|
|
1233
|
+
if (overCap) {
|
|
1069
1234
|
this.alert("warn", `Price ${raw} exceeds maxPrice ${this.opts.maxPrice}, capping`, {
|
|
1070
1235
|
calculated: raw,
|
|
1071
1236
|
maxPrice: this.opts.maxPrice,
|
|
@@ -1142,7 +1307,7 @@ var TieredPricing = class {
|
|
|
1142
1307
|
maxTierPrice() {
|
|
1143
1308
|
let max = "0";
|
|
1144
1309
|
for (const tier of Object.values(this.opts.tiers)) {
|
|
1145
|
-
if (
|
|
1310
|
+
if (compareDecimals(tier.price, max) > 0) max = tier.price;
|
|
1146
1311
|
}
|
|
1147
1312
|
return max;
|
|
1148
1313
|
}
|
|
@@ -1557,7 +1722,7 @@ var mppStrategy = {
|
|
|
1557
1722
|
async verify(args) {
|
|
1558
1723
|
const info = readMppCredential(args.request);
|
|
1559
1724
|
if (!info) return { ok: false, kind: "invalid" };
|
|
1560
|
-
if (args.routeEntry.
|
|
1725
|
+
if (args.routeEntry.billing === "metered") {
|
|
1561
1726
|
if (!info.sessionAction) return { ok: false, kind: "invalid" };
|
|
1562
1727
|
return verifySessionMode(args, info);
|
|
1563
1728
|
}
|
|
@@ -1608,7 +1773,7 @@ var mppStrategy = {
|
|
|
1608
1773
|
async buildChallenge(args) {
|
|
1609
1774
|
if (!args.deps.mppx) return {};
|
|
1610
1775
|
const sessionsConfigured = args.deps.mppSessionConfig && (args.deps.mppx.sessionRequest || args.deps.mppx.sessionStream);
|
|
1611
|
-
if (args.routeEntry.
|
|
1776
|
+
if (args.routeEntry.billing === "metered" && sessionsConfigured) {
|
|
1612
1777
|
const tickCost = args.routeEntry.tickCost;
|
|
1613
1778
|
const computedDeposit = tickCost !== void 0 ? multiplyDecimal(tickCost, args.deps.mppSessionConfig.depositMultiplier) : void 0;
|
|
1614
1779
|
const suggestedDeposit = args.routeEntry.maxPrice ?? computedDeposit ?? args.price;
|
|
@@ -1620,17 +1785,6 @@ var mppStrategy = {
|
|
|
1620
1785
|
return buildChargeChallenge(args);
|
|
1621
1786
|
}
|
|
1622
1787
|
};
|
|
1623
|
-
function multiplyDecimal(decimal, factor) {
|
|
1624
|
-
if (!Number.isFinite(factor) || factor <= 0) return decimal;
|
|
1625
|
-
const [whole, fraction = ""] = decimal.split(".");
|
|
1626
|
-
const scaled = (BigInt(whole + fraction) * BigInt(factor)).toString();
|
|
1627
|
-
const decimals = fraction.length;
|
|
1628
|
-
if (decimals === 0) return scaled;
|
|
1629
|
-
const padded = scaled.padStart(decimals + 1, "0");
|
|
1630
|
-
const intPart = padded.slice(0, padded.length - decimals);
|
|
1631
|
-
const fracPart = padded.slice(padded.length - decimals).replace(/0+$/, "");
|
|
1632
|
-
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
1633
|
-
}
|
|
1634
1788
|
async function buildChargeChallenge(args) {
|
|
1635
1789
|
if (!args.deps.mppx) return {};
|
|
1636
1790
|
try {
|
|
@@ -1723,26 +1877,13 @@ function buildCustomRequirement(price, accept) {
|
|
|
1723
1877
|
return {
|
|
1724
1878
|
scheme: accept.scheme,
|
|
1725
1879
|
network: accept.network,
|
|
1726
|
-
amount:
|
|
1880
|
+
amount: decimalToAtomic(price, accept.decimals ?? 6).toString(),
|
|
1727
1881
|
asset: accept.asset,
|
|
1728
1882
|
payTo: accept.payTo,
|
|
1729
1883
|
maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
|
|
1730
1884
|
extra: accept.extra ?? {}
|
|
1731
1885
|
};
|
|
1732
1886
|
}
|
|
1733
|
-
function decimalToAtomicUnits(amount, decimals) {
|
|
1734
|
-
const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
|
|
1735
|
-
if (!match?.groups) {
|
|
1736
|
-
throw new Error(`Invalid decimal amount '${amount}'`);
|
|
1737
|
-
}
|
|
1738
|
-
const whole = match.groups.whole;
|
|
1739
|
-
const fraction = match.groups.fraction ?? "";
|
|
1740
|
-
if (fraction.length > decimals) {
|
|
1741
|
-
throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
|
|
1742
|
-
}
|
|
1743
|
-
const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
|
|
1744
|
-
return normalized === "" ? "0" : normalized;
|
|
1745
|
-
}
|
|
1746
1887
|
|
|
1747
1888
|
// src/protocols/x402/challenge.ts
|
|
1748
1889
|
async function buildX402Challenge(opts) {
|
|
@@ -2009,7 +2150,7 @@ async function verifyX402(args) {
|
|
|
2009
2150
|
const accepts = await resolveX402Accepts(
|
|
2010
2151
|
request,
|
|
2011
2152
|
routeEntry,
|
|
2012
|
-
deps.x402Accepts,
|
|
2153
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2013
2154
|
deps.payeeAddress,
|
|
2014
2155
|
body
|
|
2015
2156
|
);
|
|
@@ -2057,7 +2198,7 @@ async function verifyX402(args) {
|
|
|
2057
2198
|
async function settleX402(args) {
|
|
2058
2199
|
const { response, payment, token, deps, routeEntry, billedAmount, report } = args;
|
|
2059
2200
|
const { payload, requirements } = token;
|
|
2060
|
-
const override = routeEntry.
|
|
2201
|
+
const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
|
|
2061
2202
|
try {
|
|
2062
2203
|
const settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2063
2204
|
if (!settle.result?.success) {
|
|
@@ -2087,7 +2228,7 @@ async function buildX402ChallengeContribution(args) {
|
|
|
2087
2228
|
const accepts = await resolveX402Accepts(
|
|
2088
2229
|
request,
|
|
2089
2230
|
routeEntry,
|
|
2090
|
-
deps.x402Accepts,
|
|
2231
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2091
2232
|
deps.payeeAddress,
|
|
2092
2233
|
body
|
|
2093
2234
|
);
|
|
@@ -2105,13 +2246,14 @@ async function buildX402ChallengeContribution(args) {
|
|
|
2105
2246
|
}
|
|
2106
2247
|
function reportSettleFailure(report, err, network) {
|
|
2107
2248
|
const facilitator = err ?? {};
|
|
2108
|
-
|
|
2249
|
+
const meta = {
|
|
2109
2250
|
error: err instanceof Error ? err.message : String(err),
|
|
2110
2251
|
network,
|
|
2111
2252
|
errorReason: facilitator.errorReason,
|
|
2112
2253
|
facilitatorStatus: facilitator.response?.status,
|
|
2113
2254
|
facilitatorBody: facilitator.response?.data ?? facilitator.response?.body
|
|
2114
|
-
}
|
|
2255
|
+
};
|
|
2256
|
+
report("error", "Settlement failed", meta);
|
|
2115
2257
|
}
|
|
2116
2258
|
|
|
2117
2259
|
// src/protocols/index.ts
|
|
@@ -2134,6 +2276,7 @@ function getAllowedStrategies(allowed) {
|
|
|
2134
2276
|
import { NextResponse as NextResponse4 } from "next/server";
|
|
2135
2277
|
|
|
2136
2278
|
// src/pipeline/challenge-extensions.ts
|
|
2279
|
+
init_evm();
|
|
2137
2280
|
async function buildChallengeExtensions(ctx) {
|
|
2138
2281
|
const { routeEntry } = ctx;
|
|
2139
2282
|
let extensions;
|
|
@@ -2178,6 +2321,21 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2178
2321
|
} catch {
|
|
2179
2322
|
}
|
|
2180
2323
|
}
|
|
2324
|
+
const hasEvmUpto = ctx.routeEntry.billing === "upto" && ctx.deps.x402Accepts.some((accept) => accept.scheme === "upto" && isEvmNetwork(accept.network));
|
|
2325
|
+
if (hasEvmUpto) {
|
|
2326
|
+
try {
|
|
2327
|
+
const { declareEip2612GasSponsoringExtension } = await import("@x402/extensions");
|
|
2328
|
+
extensions = {
|
|
2329
|
+
...extensions ?? {},
|
|
2330
|
+
...declareEip2612GasSponsoringExtension()
|
|
2331
|
+
};
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
ctx.report(
|
|
2334
|
+
"warn",
|
|
2335
|
+
`EIP-2612 gas-sponsoring declaration failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2181
2339
|
return extensions;
|
|
2182
2340
|
}
|
|
2183
2341
|
|
|
@@ -2330,31 +2488,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2330
2488
|
});
|
|
2331
2489
|
}
|
|
2332
2490
|
|
|
2333
|
-
// src/
|
|
2334
|
-
import { NextResponse as NextResponse6 } from "next/server";
|
|
2335
|
-
|
|
2336
|
-
// src/pricing/atomic.ts
|
|
2337
|
-
var USDC_DECIMALS = 6;
|
|
2338
|
-
function decimalToAtomic(amount) {
|
|
2339
|
-
const m = /^(\d+)(?:\.(\d+))?$/.exec(amount.trim());
|
|
2340
|
-
if (!m) {
|
|
2341
|
-
throw Object.assign(new Error(`'${amount}' is not a valid decimal-dollar string`), {
|
|
2342
|
-
status: 400
|
|
2343
|
-
});
|
|
2344
|
-
}
|
|
2345
|
-
const whole = m[1];
|
|
2346
|
-
const fraction = (m[2] ?? "").slice(0, USDC_DECIMALS).padEnd(USDC_DECIMALS, "0");
|
|
2347
|
-
return BigInt(`${whole}${fraction}`.replace(/^0+(?=\d)/, "") || "0");
|
|
2348
|
-
}
|
|
2349
|
-
function atomicToDecimal(atomic) {
|
|
2350
|
-
const whole = atomic / 10n ** BigInt(USDC_DECIMALS);
|
|
2351
|
-
const fraction = atomic % 10n ** BigInt(USDC_DECIMALS);
|
|
2352
|
-
if (fraction === 0n) return whole.toString();
|
|
2353
|
-
const fractionStr = fraction.toString().padStart(USDC_DECIMALS, "0").replace(/0+$/, "");
|
|
2354
|
-
return `${whole}.${fractionStr}`;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
// src/pricing/charge-context.ts
|
|
2491
|
+
// src/pricing/metered-charge.ts
|
|
2358
2492
|
function createChargeContext(args) {
|
|
2359
2493
|
const { tickCost, maxPrice, route } = args;
|
|
2360
2494
|
const tickAtomic = decimalToAtomic(tickCost);
|
|
@@ -2389,15 +2523,10 @@ function createChargeContext(args) {
|
|
|
2389
2523
|
};
|
|
2390
2524
|
}
|
|
2391
2525
|
|
|
2392
|
-
// src/pipeline/flows/dynamic/dynamic-invoke.ts
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
tickCost: ctx.routeEntry.tickCost,
|
|
2397
|
-
maxPrice: ctx.routeEntry.maxPrice,
|
|
2398
|
-
route: ctx.routeEntry.key
|
|
2399
|
-
}) : null;
|
|
2400
|
-
const baseHandlerCtx = {
|
|
2526
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2527
|
+
import { NextResponse as NextResponse6 } from "next/server";
|
|
2528
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2529
|
+
return {
|
|
2401
2530
|
body,
|
|
2402
2531
|
query: parseQuery(ctx.request, ctx.routeEntry),
|
|
2403
2532
|
request: ctx.request,
|
|
@@ -2409,12 +2538,41 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2409
2538
|
alert: ctx.report,
|
|
2410
2539
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2411
2540
|
};
|
|
2541
|
+
}
|
|
2542
|
+
function toResponse(rawResult) {
|
|
2543
|
+
return rawResult instanceof Response ? rawResult : NextResponse6.json(rawResult);
|
|
2544
|
+
}
|
|
2545
|
+
function errorResult2(error) {
|
|
2546
|
+
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
2547
|
+
const message = error instanceof Error ? error.message : "Internal error";
|
|
2548
|
+
return {
|
|
2549
|
+
kind: "request",
|
|
2550
|
+
response: NextResponse6.json({ success: false, error: message }, { status }),
|
|
2551
|
+
rawResult: void 0,
|
|
2552
|
+
handlerError: error
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
function isAsyncIterable2(value) {
|
|
2556
|
+
return value != null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
2557
|
+
}
|
|
2558
|
+
function isThenable2(value) {
|
|
2559
|
+
return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2563
|
+
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2564
|
+
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2565
|
+
tickCost: ctx.routeEntry.tickCost,
|
|
2566
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2567
|
+
route: ctx.routeEntry.key
|
|
2568
|
+
}) : null;
|
|
2569
|
+
const baseHandlerCtx = buildBaseHandlerCtx(ctx, wallet, account, body, payment);
|
|
2412
2570
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2413
2571
|
let returned;
|
|
2414
2572
|
try {
|
|
2415
2573
|
returned = ctx.handler(handlerCtx);
|
|
2416
2574
|
} catch (error) {
|
|
2417
|
-
return errorResult2(error
|
|
2575
|
+
return errorResult2(error);
|
|
2418
2576
|
}
|
|
2419
2577
|
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2420
2578
|
if (!chargeContext) {
|
|
@@ -2422,41 +2580,80 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2422
2580
|
new HttpError(
|
|
2423
2581
|
"route returned an async iterable from a non-streaming handler \u2014 use .stream(async function*(...)) instead of .handler() to opt into streaming",
|
|
2424
2582
|
500
|
|
2425
|
-
)
|
|
2426
|
-
null
|
|
2583
|
+
)
|
|
2427
2584
|
);
|
|
2428
2585
|
}
|
|
2429
|
-
return {
|
|
2430
|
-
kind: "stream",
|
|
2431
|
-
source: returned,
|
|
2432
|
-
chargeContext
|
|
2433
|
-
};
|
|
2586
|
+
return { kind: "stream", source: returned, chargeContext };
|
|
2434
2587
|
}
|
|
2435
2588
|
let rawResult;
|
|
2436
2589
|
try {
|
|
2437
2590
|
rawResult = await returned;
|
|
2438
2591
|
} catch (error) {
|
|
2439
|
-
return errorResult2(error
|
|
2592
|
+
return errorResult2(error);
|
|
2440
2593
|
}
|
|
2441
|
-
|
|
2442
|
-
return { kind: "request", response, rawResult };
|
|
2594
|
+
return { kind: "request", response: toResponse(rawResult), rawResult };
|
|
2443
2595
|
}
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2596
|
+
|
|
2597
|
+
// src/pricing/upto-charge.ts
|
|
2598
|
+
function createUptoChargeContext(args) {
|
|
2599
|
+
const { maxPrice, route } = args;
|
|
2600
|
+
const capAtomic = decimalToAtomic(maxPrice);
|
|
2601
|
+
if (capAtomic <= 0n) {
|
|
2602
|
+
throw new Error(`route '${route}': maxPrice '${maxPrice}' must be a positive decimal string`);
|
|
2603
|
+
}
|
|
2604
|
+
let calls = 0;
|
|
2605
|
+
let atomic = 0n;
|
|
2606
|
+
const charge = async (amount) => {
|
|
2607
|
+
const nextAtomic = atomic + decimalToAtomic(amount);
|
|
2608
|
+
if (nextAtomic > capAtomic) {
|
|
2609
|
+
throw Object.assign(
|
|
2610
|
+
new Error(
|
|
2611
|
+
`route '${route}': charge() running total ($${atomicToDecimal(nextAtomic)}) exceeds maxPrice ($${atomicToDecimal(capAtomic)})`
|
|
2612
|
+
),
|
|
2613
|
+
{ status: 400, code: "CHARGE_OVER_CAP" }
|
|
2614
|
+
);
|
|
2615
|
+
}
|
|
2616
|
+
calls += 1;
|
|
2617
|
+
atomic = nextAtomic;
|
|
2618
|
+
};
|
|
2448
2619
|
return {
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
handlerError: error
|
|
2620
|
+
charge,
|
|
2621
|
+
callCount: () => calls,
|
|
2622
|
+
atomicTotal: () => atomic
|
|
2453
2623
|
};
|
|
2454
2624
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2625
|
+
|
|
2626
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2627
|
+
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2628
|
+
const uptoCtx = createUptoChargeContext({
|
|
2629
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2630
|
+
route: ctx.routeEntry.key
|
|
2631
|
+
});
|
|
2632
|
+
const handlerCtx = {
|
|
2633
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2634
|
+
charge: uptoCtx.charge
|
|
2635
|
+
};
|
|
2636
|
+
let returned;
|
|
2637
|
+
try {
|
|
2638
|
+
returned = ctx.handler(handlerCtx);
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
return errorResult2(error);
|
|
2641
|
+
}
|
|
2642
|
+
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2643
|
+
return errorResult2(
|
|
2644
|
+
new HttpError(
|
|
2645
|
+
"streaming is not supported on .upTo() routes \u2014 return a value from .handler() instead",
|
|
2646
|
+
500
|
|
2647
|
+
)
|
|
2648
|
+
);
|
|
2649
|
+
}
|
|
2650
|
+
let rawResult;
|
|
2651
|
+
try {
|
|
2652
|
+
rawResult = await returned;
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
return errorResult2(error);
|
|
2655
|
+
}
|
|
2656
|
+
return { kind: "request", response: toResponse(rawResult), rawResult, uptoContext: uptoCtx };
|
|
2460
2657
|
}
|
|
2461
2658
|
|
|
2462
2659
|
// src/pipeline/flows/dynamic/dynamic-preflight.ts
|
|
@@ -2486,7 +2683,7 @@ async function runDynamicRequestFlow(args) {
|
|
|
2486
2683
|
}
|
|
2487
2684
|
const beforeErr = await runBeforeSettle(ctx, settleScope);
|
|
2488
2685
|
if (beforeErr) return beforeErr;
|
|
2489
|
-
const billedAmount = routeEntry
|
|
2686
|
+
const billedAmount = computeBilledAmount(routeEntry, result);
|
|
2490
2687
|
return settleAndFinalizeRequest({
|
|
2491
2688
|
ctx,
|
|
2492
2689
|
strategy,
|
|
@@ -2504,6 +2701,19 @@ async function runDynamicRequestFlow(args) {
|
|
|
2504
2701
|
}
|
|
2505
2702
|
});
|
|
2506
2703
|
}
|
|
2704
|
+
function computeBilledAmount(routeEntry, result) {
|
|
2705
|
+
if (routeEntry.billing === "upto") {
|
|
2706
|
+
const total = result.uptoContext?.atomicTotal() ?? 0n;
|
|
2707
|
+
if (total <= 0n) {
|
|
2708
|
+
throw new HttpError(
|
|
2709
|
+
`route '${routeEntry.key}': handler did not call charge(amount) \u2014 upto routes must accumulate a non-zero billed amount`,
|
|
2710
|
+
500
|
|
2711
|
+
);
|
|
2712
|
+
}
|
|
2713
|
+
return atomicToDecimal(total);
|
|
2714
|
+
}
|
|
2715
|
+
return routeEntry.tickCost;
|
|
2716
|
+
}
|
|
2507
2717
|
|
|
2508
2718
|
// src/pipeline/flows/dynamic/dynamic-stream.ts
|
|
2509
2719
|
async function runDynamicStreamFlow(args) {
|
|
@@ -2576,13 +2786,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2576
2786
|
amount: price,
|
|
2577
2787
|
network: verifyOutcome.payment.network
|
|
2578
2788
|
});
|
|
2579
|
-
const result = await invokeDynamic(
|
|
2580
|
-
ctx,
|
|
2581
|
-
verifyOutcome.wallet,
|
|
2582
|
-
account,
|
|
2583
|
-
parsedBody,
|
|
2584
|
-
verifyOutcome.payment
|
|
2585
|
-
);
|
|
2789
|
+
const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
|
|
2586
2790
|
switch (result.kind) {
|
|
2587
2791
|
case "stream":
|
|
2588
2792
|
return runDynamicStreamFlow({
|
|
@@ -2604,6 +2808,18 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2604
2808
|
});
|
|
2605
2809
|
}
|
|
2606
2810
|
}
|
|
2811
|
+
async function invokeDynamic(ctx, verifyOutcome, account, parsedBody) {
|
|
2812
|
+
switch (ctx.routeEntry.billing) {
|
|
2813
|
+
case "upto":
|
|
2814
|
+
return invokeUpto(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2815
|
+
case "metered":
|
|
2816
|
+
return invokeMetered(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2817
|
+
case "exact":
|
|
2818
|
+
throw new Error(
|
|
2819
|
+
`route '${ctx.routeEntry.key}': exact billing must not reach the dynamic paid flow`
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2607
2823
|
|
|
2608
2824
|
// src/pipeline/flows/static/static-body-and-price.ts
|
|
2609
2825
|
async function resolveStaticBodyAndPrice(args) {
|
|
@@ -2751,19 +2967,27 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2751
2967
|
|
|
2752
2968
|
// src/pipeline/flows/paid.ts
|
|
2753
2969
|
async function runPaidFlow(ctx) {
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2756
|
-
case true:
|
|
2757
|
-
return runDynamicPaidFlow(ctx);
|
|
2758
|
-
case false:
|
|
2759
|
-
return runStaticPaidFlow(ctx);
|
|
2760
|
-
}
|
|
2970
|
+
const handlerCharged = ctx.routeEntry.billing !== "exact";
|
|
2971
|
+
return handlerCharged ? runDynamicPaidFlow(ctx) : runStaticPaidFlow(ctx);
|
|
2761
2972
|
}
|
|
2762
2973
|
|
|
2763
2974
|
// src/pipeline/flows/siwx-only.ts
|
|
2764
2975
|
import { NextResponse as NextResponse7 } from "next/server";
|
|
2765
2976
|
|
|
2766
2977
|
// src/kv-store/client.ts
|
|
2978
|
+
var BIGINT_SUFFIX = "#__bigint";
|
|
2979
|
+
function stringifyValue(value) {
|
|
2980
|
+
return JSON.stringify(
|
|
2981
|
+
value,
|
|
2982
|
+
(_key, v) => typeof v === "bigint" ? `${v.toString()}${BIGINT_SUFFIX}` : v
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
function parseValue(raw) {
|
|
2986
|
+
return JSON.parse(
|
|
2987
|
+
raw,
|
|
2988
|
+
(_key, v) => typeof v === "string" && v.endsWith(BIGINT_SUFFIX) ? BigInt(v.slice(0, -BIGINT_SUFFIX.length)) : v
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2767
2991
|
function restKvStore(url, token) {
|
|
2768
2992
|
const base = url.replace(/\/+$/, "");
|
|
2769
2993
|
const authHeader = { Authorization: `Bearer ${token}` };
|
|
@@ -2785,16 +3009,22 @@ function restKvStore(url, token) {
|
|
|
2785
3009
|
const res = await fetch(`${base}/get/${encodeURIComponent(key)}`, { headers: authHeader });
|
|
2786
3010
|
if (!res.ok) throw new Error(`[kv-store] GET ${key}: ${res.status}`);
|
|
2787
3011
|
const { result } = await res.json();
|
|
2788
|
-
|
|
3012
|
+
if (result == null) return null;
|
|
3013
|
+
if (typeof result !== "string") return result;
|
|
3014
|
+
try {
|
|
3015
|
+
return parseValue(result);
|
|
3016
|
+
} catch {
|
|
3017
|
+
return result;
|
|
3018
|
+
}
|
|
2789
3019
|
}
|
|
2790
3020
|
async function set(key, value) {
|
|
2791
|
-
await exec(["SET", key,
|
|
3021
|
+
await exec(["SET", key, stringifyValue(value)]);
|
|
2792
3022
|
}
|
|
2793
3023
|
async function del(key) {
|
|
2794
3024
|
await exec(["DEL", key]);
|
|
2795
3025
|
}
|
|
2796
3026
|
async function setNxEx(key, value, ttlSeconds) {
|
|
2797
|
-
const result = await exec(["SET", key,
|
|
3027
|
+
const result = await exec(["SET", key, stringifyValue(value), "EX", ttlSeconds, "NX"]);
|
|
2798
3028
|
return result === "OK";
|
|
2799
3029
|
}
|
|
2800
3030
|
async function sadd(key, member) {
|
|
@@ -3123,142 +3353,148 @@ ${issues}`
|
|
|
3123
3353
|
}
|
|
3124
3354
|
|
|
3125
3355
|
// src/builder.ts
|
|
3126
|
-
var RouteBuilder = class {
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
_inputExample = void 0;
|
|
3161
|
-
/** @internal */
|
|
3162
|
-
_hasInputExample = false;
|
|
3163
|
-
/** @internal */
|
|
3164
|
-
_outputExample = void 0;
|
|
3165
|
-
/** @internal */
|
|
3166
|
-
_hasOutputExample = false;
|
|
3167
|
-
/** @internal */
|
|
3168
|
-
_description;
|
|
3169
|
-
/** @internal */
|
|
3170
|
-
_path;
|
|
3171
|
-
/** @internal */
|
|
3172
|
-
_method = "POST";
|
|
3173
|
-
/** @internal */
|
|
3174
|
-
_apiKeyResolver;
|
|
3175
|
-
/** @internal */
|
|
3176
|
-
_providerName;
|
|
3177
|
-
/** @internal */
|
|
3178
|
-
_providerConfig;
|
|
3179
|
-
/** @internal */
|
|
3180
|
-
_validateFn;
|
|
3181
|
-
/** @internal */
|
|
3182
|
-
_settlement;
|
|
3183
|
-
/** @internal */
|
|
3184
|
-
_mppInfo;
|
|
3185
|
-
constructor(key, registry, deps) {
|
|
3186
|
-
this._key = key;
|
|
3187
|
-
this._registry = registry;
|
|
3188
|
-
this._deps = deps;
|
|
3356
|
+
var RouteBuilder = class _RouteBuilder {
|
|
3357
|
+
#s;
|
|
3358
|
+
constructor(key, registry, deps, defaults) {
|
|
3359
|
+
this.#s = {
|
|
3360
|
+
key,
|
|
3361
|
+
registry,
|
|
3362
|
+
deps,
|
|
3363
|
+
authMode: null,
|
|
3364
|
+
pricing: void 0,
|
|
3365
|
+
siwxEnabled: false,
|
|
3366
|
+
protocols: defaults?.protocols ? [...defaults.protocols] : ["x402"],
|
|
3367
|
+
maxPrice: void 0,
|
|
3368
|
+
minPrice: void 0,
|
|
3369
|
+
billing: "exact",
|
|
3370
|
+
tickCost: void 0,
|
|
3371
|
+
unitType: void 0,
|
|
3372
|
+
payTo: void 0,
|
|
3373
|
+
bodySchema: void 0,
|
|
3374
|
+
querySchema: void 0,
|
|
3375
|
+
outputSchema: void 0,
|
|
3376
|
+
inputExample: void 0,
|
|
3377
|
+
hasInputExample: false,
|
|
3378
|
+
outputExample: void 0,
|
|
3379
|
+
hasOutputExample: false,
|
|
3380
|
+
description: void 0,
|
|
3381
|
+
path: void 0,
|
|
3382
|
+
method: "POST",
|
|
3383
|
+
apiKeyResolver: void 0,
|
|
3384
|
+
providerName: void 0,
|
|
3385
|
+
providerConfig: void 0,
|
|
3386
|
+
validateFn: void 0,
|
|
3387
|
+
settlement: void 0,
|
|
3388
|
+
mppInfo: void 0
|
|
3389
|
+
};
|
|
3189
3390
|
}
|
|
3190
3391
|
fork() {
|
|
3191
|
-
const next =
|
|
3192
|
-
|
|
3193
|
-
|
|
3392
|
+
const next = new _RouteBuilder(
|
|
3393
|
+
this.#s.key,
|
|
3394
|
+
this.#s.registry,
|
|
3395
|
+
this.#s.deps
|
|
3396
|
+
);
|
|
3397
|
+
next.#s = { ...this.#s, protocols: [...this.#s.protocols] };
|
|
3194
3398
|
return next;
|
|
3195
3399
|
}
|
|
3196
|
-
paid(
|
|
3197
|
-
|
|
3198
|
-
|
|
3400
|
+
paid(arg, options) {
|
|
3401
|
+
return this.applyPaid(normalizePaidArg(this.#s.key, arg, options), "paid");
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* x402-only handler-computed billing. The handler receives `charge(amount)`
|
|
3405
|
+
* and the request settles once for the accumulated total, capped at
|
|
3406
|
+
* `maxPrice`. Requires an `'upto'` accept on at least one configured network.
|
|
3407
|
+
* Pass a bare string as sugar for `{ maxPrice }`.
|
|
3408
|
+
*
|
|
3409
|
+
* @example
|
|
3410
|
+
* ```ts
|
|
3411
|
+
* router.route('llm')
|
|
3412
|
+
* .upTo('0.05')
|
|
3413
|
+
* .body(schema)
|
|
3414
|
+
* .handler(async ({ body, charge }) => { await charge('0.001'); ... });
|
|
3415
|
+
* ```
|
|
3416
|
+
*/
|
|
3417
|
+
upTo(arg) {
|
|
3418
|
+
return this.applyPaid(normalizeUpToArg(this.#s.key, arg), "upTo");
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
|
|
3422
|
+
* `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
|
|
3423
|
+
* `maxPrice`. Requires `RouterConfig.mpp.session`.
|
|
3424
|
+
*
|
|
3425
|
+
* @example
|
|
3426
|
+
* ```ts
|
|
3427
|
+
* router.route('llm/stream')
|
|
3428
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3429
|
+
* .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
|
|
3430
|
+
* ```
|
|
3431
|
+
*/
|
|
3432
|
+
metered(options) {
|
|
3433
|
+
return this.applyPaid(normalizeMeteredArg(this.#s.key, options), "metered");
|
|
3434
|
+
}
|
|
3435
|
+
applyPaid(normalized, method) {
|
|
3436
|
+
const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
|
|
3437
|
+
if (this.#s.authMode === "unprotected") {
|
|
3199
3438
|
throw new Error(
|
|
3200
|
-
`route '${this.
|
|
3439
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .${method}() on the same route.`
|
|
3201
3440
|
);
|
|
3202
3441
|
}
|
|
3203
|
-
if (this.
|
|
3442
|
+
if (this.#s.pricing !== void 0) {
|
|
3204
3443
|
throw new Error(
|
|
3205
|
-
`route '${this.
|
|
3444
|
+
`route '${this.#s.key}': Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
|
|
3206
3445
|
);
|
|
3207
3446
|
}
|
|
3208
3447
|
const next = this.fork();
|
|
3209
|
-
next.
|
|
3210
|
-
next.
|
|
3211
|
-
if (
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
if (
|
|
3219
|
-
|
|
3220
|
-
if (resolvedOptions?.dynamic) next._dynamicPrice = true;
|
|
3221
|
-
if (resolvedOptions?.tickCost) next._tickCost = resolvedOptions.tickCost;
|
|
3222
|
-
if (resolvedOptions?.unitType) next._unitType = resolvedOptions.unitType;
|
|
3223
|
-
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3224
|
-
if (next._dynamicPrice) {
|
|
3448
|
+
next.#s.authMode = "paid";
|
|
3449
|
+
next.#s.pricing = pricing;
|
|
3450
|
+
if (billing === "upto") {
|
|
3451
|
+
if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
|
|
3452
|
+
throw new Error(
|
|
3453
|
+
`route '${this.#s.key}': .upTo() is x402-only \u2014 remove the conflicting protocols override.`
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
next.#s.protocols = ["x402"];
|
|
3457
|
+
} else if (billing === "metered") {
|
|
3458
|
+
if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
|
|
3225
3459
|
throw new Error(
|
|
3226
|
-
`route '${this.
|
|
3460
|
+
`route '${this.#s.key}': .metered() is MPP-only \u2014 remove the conflicting protocols override.`
|
|
3227
3461
|
);
|
|
3228
3462
|
}
|
|
3463
|
+
next.#s.protocols = ["mpp"];
|
|
3464
|
+
} else if (resolvedOptions.protocols) {
|
|
3465
|
+
next.#s.protocols = [...resolvedOptions.protocols];
|
|
3466
|
+
} else if (next.#s.protocols.length === 0) {
|
|
3467
|
+
next.#s.protocols = ["x402"];
|
|
3468
|
+
}
|
|
3469
|
+
if (resolvedOptions.maxPrice) next.#s.maxPrice = resolvedOptions.maxPrice;
|
|
3470
|
+
if (maxPrice) next.#s.maxPrice = maxPrice;
|
|
3471
|
+
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3472
|
+
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3473
|
+
if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
|
|
3474
|
+
next.#s.billing = billing;
|
|
3475
|
+
if (tickCost) next.#s.tickCost = tickCost;
|
|
3476
|
+
if (unitType) next.#s.unitType = unitType;
|
|
3477
|
+
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3229
3478
|
for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
|
|
3230
3479
|
if (!tierKey) {
|
|
3231
|
-
throw new Error(`route '${this.
|
|
3480
|
+
throw new Error(`route '${this.#s.key}': tier key cannot be empty`);
|
|
3232
3481
|
}
|
|
3233
|
-
|
|
3234
|
-
if (isNaN(tierPrice) || tierPrice <= 0) {
|
|
3482
|
+
if (!isPositiveDecimal(tierConfig.price)) {
|
|
3235
3483
|
throw new Error(
|
|
3236
|
-
`route '${this.
|
|
3484
|
+
`route '${this.#s.key}': tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
|
|
3237
3485
|
);
|
|
3238
3486
|
}
|
|
3239
3487
|
}
|
|
3240
3488
|
}
|
|
3241
|
-
if (
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
`route '${this._key}': maxPrice '${resolvedOptions.maxPrice}' must be a positive decimal string`
|
|
3246
|
-
);
|
|
3247
|
-
}
|
|
3248
|
-
}
|
|
3249
|
-
if (resolvedOptions?.tickCost !== void 0) {
|
|
3250
|
-
const parsed = parseFloat(resolvedOptions.tickCost);
|
|
3251
|
-
if (isNaN(parsed) || parsed <= 0) {
|
|
3252
|
-
throw new Error(
|
|
3253
|
-
`route '${this._key}': tickCost '${resolvedOptions.tickCost}' must be a positive decimal string`
|
|
3254
|
-
);
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
if (next._dynamicPrice && !next._maxPrice) {
|
|
3258
|
-
throw new Error(`route '${this._key}': .paid({ dynamic: true }) requires maxPrice`);
|
|
3489
|
+
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3490
|
+
throw new Error(
|
|
3491
|
+
`route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3492
|
+
);
|
|
3259
3493
|
}
|
|
3260
|
-
if (next.
|
|
3261
|
-
throw new Error(
|
|
3494
|
+
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3495
|
+
throw new Error(
|
|
3496
|
+
`route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
3497
|
+
);
|
|
3262
3498
|
}
|
|
3263
3499
|
return next;
|
|
3264
3500
|
}
|
|
@@ -3273,25 +3509,25 @@ var RouteBuilder = class {
|
|
|
3273
3509
|
* ```
|
|
3274
3510
|
*/
|
|
3275
3511
|
siwx() {
|
|
3276
|
-
if (this.
|
|
3512
|
+
if (this.#s.authMode === "unprotected") {
|
|
3277
3513
|
throw new Error(
|
|
3278
|
-
`route '${this.
|
|
3514
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .siwx() on the same route.`
|
|
3279
3515
|
);
|
|
3280
3516
|
}
|
|
3281
|
-
if (this.
|
|
3517
|
+
if (this.#s.apiKeyResolver) {
|
|
3282
3518
|
throw new Error(
|
|
3283
|
-
`route '${this.
|
|
3519
|
+
`route '${this.#s.key}': Combining .siwx() and .apiKey() is not supported on the same route.`
|
|
3284
3520
|
);
|
|
3285
3521
|
}
|
|
3286
3522
|
const next = this.fork();
|
|
3287
|
-
next.
|
|
3288
|
-
if (next.
|
|
3289
|
-
next.
|
|
3290
|
-
if (next.
|
|
3523
|
+
next.#s.siwxEnabled = true;
|
|
3524
|
+
if (next.#s.authMode === "paid" || next.#s.pricing) {
|
|
3525
|
+
next.#s.authMode = "paid";
|
|
3526
|
+
if (next.#s.protocols.length === 0) next.#s.protocols = ["x402"];
|
|
3291
3527
|
return next;
|
|
3292
3528
|
}
|
|
3293
|
-
next.
|
|
3294
|
-
next.
|
|
3529
|
+
next.#s.authMode = "siwx";
|
|
3530
|
+
next.#s.protocols = [];
|
|
3295
3531
|
return next;
|
|
3296
3532
|
}
|
|
3297
3533
|
/**
|
|
@@ -3308,14 +3544,14 @@ var RouteBuilder = class {
|
|
|
3308
3544
|
* ```
|
|
3309
3545
|
*/
|
|
3310
3546
|
apiKey(resolver) {
|
|
3311
|
-
if (this.
|
|
3547
|
+
if (this.#s.siwxEnabled) {
|
|
3312
3548
|
throw new Error(
|
|
3313
|
-
`route '${this.
|
|
3549
|
+
`route '${this.#s.key}': Combining .apiKey() and .siwx() is not supported on the same route.`
|
|
3314
3550
|
);
|
|
3315
3551
|
}
|
|
3316
3552
|
const next = this.fork();
|
|
3317
|
-
next.
|
|
3318
|
-
next.
|
|
3553
|
+
next.#s.authMode = "apiKey";
|
|
3554
|
+
next.#s.apiKeyResolver = resolver;
|
|
3319
3555
|
return next;
|
|
3320
3556
|
}
|
|
3321
3557
|
/**
|
|
@@ -3328,19 +3564,19 @@ var RouteBuilder = class {
|
|
|
3328
3564
|
* ```
|
|
3329
3565
|
*/
|
|
3330
3566
|
unprotected() {
|
|
3331
|
-
if (this.
|
|
3567
|
+
if (this.#s.authMode && this.#s.authMode !== "unprotected") {
|
|
3332
3568
|
throw new Error(
|
|
3333
|
-
`route '${this.
|
|
3569
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .${this.#s.authMode}() on the same route.`
|
|
3334
3570
|
);
|
|
3335
3571
|
}
|
|
3336
|
-
if (this.
|
|
3572
|
+
if (this.#s.pricing) {
|
|
3337
3573
|
throw new Error(
|
|
3338
|
-
`route '${this.
|
|
3574
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .paid() on the same route.`
|
|
3339
3575
|
);
|
|
3340
3576
|
}
|
|
3341
3577
|
const next = this.fork();
|
|
3342
|
-
next.
|
|
3343
|
-
next.
|
|
3578
|
+
next.#s.authMode = "unprotected";
|
|
3579
|
+
next.#s.protocols = [];
|
|
3344
3580
|
return next;
|
|
3345
3581
|
}
|
|
3346
3582
|
/**
|
|
@@ -3359,8 +3595,8 @@ var RouteBuilder = class {
|
|
|
3359
3595
|
*/
|
|
3360
3596
|
provider(name, config) {
|
|
3361
3597
|
const next = this.fork();
|
|
3362
|
-
next.
|
|
3363
|
-
next.
|
|
3598
|
+
next.#s.providerName = name;
|
|
3599
|
+
next.#s.providerConfig = config ?? {};
|
|
3364
3600
|
return next;
|
|
3365
3601
|
}
|
|
3366
3602
|
/**
|
|
@@ -3375,7 +3611,7 @@ var RouteBuilder = class {
|
|
|
3375
3611
|
*/
|
|
3376
3612
|
body(schema) {
|
|
3377
3613
|
const next = this.fork();
|
|
3378
|
-
next.
|
|
3614
|
+
next.#s.bodySchema = schema;
|
|
3379
3615
|
return next;
|
|
3380
3616
|
}
|
|
3381
3617
|
/**
|
|
@@ -3391,8 +3627,8 @@ var RouteBuilder = class {
|
|
|
3391
3627
|
*/
|
|
3392
3628
|
query(schema) {
|
|
3393
3629
|
const next = this.fork();
|
|
3394
|
-
next.
|
|
3395
|
-
next.
|
|
3630
|
+
next.#s.querySchema = schema;
|
|
3631
|
+
next.#s.method = "GET";
|
|
3396
3632
|
return next;
|
|
3397
3633
|
}
|
|
3398
3634
|
/**
|
|
@@ -3409,7 +3645,7 @@ var RouteBuilder = class {
|
|
|
3409
3645
|
*/
|
|
3410
3646
|
output(schema) {
|
|
3411
3647
|
const next = this.fork();
|
|
3412
|
-
next.
|
|
3648
|
+
next.#s.outputSchema = schema;
|
|
3413
3649
|
return next;
|
|
3414
3650
|
}
|
|
3415
3651
|
/**
|
|
@@ -3423,8 +3659,8 @@ var RouteBuilder = class {
|
|
|
3423
3659
|
*/
|
|
3424
3660
|
inputExample(example) {
|
|
3425
3661
|
const next = this.fork();
|
|
3426
|
-
next.
|
|
3427
|
-
next.
|
|
3662
|
+
next.#s.inputExample = example;
|
|
3663
|
+
next.#s.hasInputExample = true;
|
|
3428
3664
|
return next;
|
|
3429
3665
|
}
|
|
3430
3666
|
/**
|
|
@@ -3438,8 +3674,8 @@ var RouteBuilder = class {
|
|
|
3438
3674
|
*/
|
|
3439
3675
|
outputExample(example) {
|
|
3440
3676
|
const next = this.fork();
|
|
3441
|
-
next.
|
|
3442
|
-
next.
|
|
3677
|
+
next.#s.outputExample = example;
|
|
3678
|
+
next.#s.hasOutputExample = true;
|
|
3443
3679
|
return next;
|
|
3444
3680
|
}
|
|
3445
3681
|
/**
|
|
@@ -3453,7 +3689,7 @@ var RouteBuilder = class {
|
|
|
3453
3689
|
*/
|
|
3454
3690
|
description(text) {
|
|
3455
3691
|
const next = this.fork();
|
|
3456
|
-
next.
|
|
3692
|
+
next.#s.description = text;
|
|
3457
3693
|
return next;
|
|
3458
3694
|
}
|
|
3459
3695
|
/**
|
|
@@ -3467,7 +3703,7 @@ var RouteBuilder = class {
|
|
|
3467
3703
|
*/
|
|
3468
3704
|
path(p) {
|
|
3469
3705
|
const next = this.fork();
|
|
3470
|
-
next.
|
|
3706
|
+
next.#s.path = p;
|
|
3471
3707
|
return next;
|
|
3472
3708
|
}
|
|
3473
3709
|
/**
|
|
@@ -3481,7 +3717,7 @@ var RouteBuilder = class {
|
|
|
3481
3717
|
*/
|
|
3482
3718
|
method(m) {
|
|
3483
3719
|
const next = this.fork();
|
|
3484
|
-
next.
|
|
3720
|
+
next.#s.method = m;
|
|
3485
3721
|
return next;
|
|
3486
3722
|
}
|
|
3487
3723
|
/**
|
|
@@ -3500,7 +3736,7 @@ var RouteBuilder = class {
|
|
|
3500
3736
|
*/
|
|
3501
3737
|
validate(fn) {
|
|
3502
3738
|
const next = this.fork();
|
|
3503
|
-
next.
|
|
3739
|
+
next.#s.validateFn = fn;
|
|
3504
3740
|
return next;
|
|
3505
3741
|
}
|
|
3506
3742
|
/**
|
|
@@ -3518,7 +3754,7 @@ var RouteBuilder = class {
|
|
|
3518
3754
|
*/
|
|
3519
3755
|
settlement(lifecycle) {
|
|
3520
3756
|
const next = this.fork();
|
|
3521
|
-
next.
|
|
3757
|
+
next.#s.settlement = lifecycle;
|
|
3522
3758
|
return next;
|
|
3523
3759
|
}
|
|
3524
3760
|
/**
|
|
@@ -3541,13 +3777,13 @@ var RouteBuilder = class {
|
|
|
3541
3777
|
/**
|
|
3542
3778
|
* Register a streaming handler (`async function*`) and return the Next.js
|
|
3543
3779
|
* route function. Each `charge()` call bills one tick (`tickCost` USDC) up
|
|
3544
|
-
* to `maxPrice`; requires `.
|
|
3780
|
+
* to `maxPrice`; requires `.metered({ ... })` and MPP session mode.
|
|
3545
3781
|
*
|
|
3546
3782
|
* @example
|
|
3547
3783
|
* ```ts
|
|
3548
3784
|
* export const POST = router
|
|
3549
3785
|
* .route('llm/stream')
|
|
3550
|
-
* .
|
|
3786
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3551
3787
|
* .body(schema)
|
|
3552
3788
|
* .stream(async function* ({ body, charge }) {
|
|
3553
3789
|
* for await (const token of streamLLM(body.prompt)) {
|
|
@@ -3561,91 +3797,144 @@ var RouteBuilder = class {
|
|
|
3561
3797
|
return this.register(fn, true);
|
|
3562
3798
|
}
|
|
3563
3799
|
register(handlerFn, streaming) {
|
|
3564
|
-
if (!this.
|
|
3800
|
+
if (!this.#s.authMode) {
|
|
3565
3801
|
throw new Error(
|
|
3566
|
-
`route '${this.
|
|
3802
|
+
`route '${this.#s.key}': Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
3567
3803
|
);
|
|
3568
3804
|
}
|
|
3569
|
-
if (this.
|
|
3805
|
+
if (this.#s.validateFn && !this.#s.bodySchema) {
|
|
3570
3806
|
throw new Error(
|
|
3571
|
-
`route '${this.
|
|
3807
|
+
`route '${this.#s.key}': .validate() requires .body() \u2014 validation runs on parsed body`
|
|
3572
3808
|
);
|
|
3573
3809
|
}
|
|
3574
|
-
if (this.
|
|
3575
|
-
throw new Error(`route '${this.
|
|
3810
|
+
if (this.#s.settlement && !this.#s.pricing) {
|
|
3811
|
+
throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
|
|
3576
3812
|
}
|
|
3577
|
-
if (this.
|
|
3578
|
-
const hasUpto = this.
|
|
3813
|
+
if (this.#s.billing === "upto") {
|
|
3814
|
+
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
3579
3815
|
if (!hasUpto) {
|
|
3580
3816
|
throw new Error(
|
|
3581
|
-
`route '${this.
|
|
3817
|
+
`route '${this.#s.key}': .upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
|
|
3818
|
+
);
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
if (this.#s.pricing !== void 0 && this.#s.billing === "exact" && this.#s.protocols.includes("x402")) {
|
|
3822
|
+
const hasExact = this.#s.deps.x402Accepts.some(
|
|
3823
|
+
(accept) => (accept.scheme ?? "exact") !== "upto"
|
|
3824
|
+
);
|
|
3825
|
+
if (!hasExact) {
|
|
3826
|
+
throw new Error(
|
|
3827
|
+
`route '${this.#s.key}': .paid() needs a non-'upto' x402 accept \u2014 an 'upto'-only accept list cannot serve a fixed-price route. Add { scheme: 'exact', network } to RouterConfig.x402.accepts, or use .upTo() for handler-computed billing.`
|
|
3582
3828
|
);
|
|
3583
3829
|
}
|
|
3584
3830
|
}
|
|
3585
|
-
if (this.
|
|
3586
|
-
if (!this.
|
|
3831
|
+
if (this.#s.billing === "metered") {
|
|
3832
|
+
if (!this.#s.deps.mppSessionConfig) {
|
|
3587
3833
|
throw new Error(
|
|
3588
|
-
`route '${this.
|
|
3834
|
+
`route '${this.#s.key}': .metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
|
|
3589
3835
|
);
|
|
3590
3836
|
}
|
|
3591
3837
|
}
|
|
3592
|
-
if (streaming &&
|
|
3838
|
+
if (streaming && this.#s.billing !== "metered") {
|
|
3593
3839
|
throw new Error(
|
|
3594
|
-
`route '${this.
|
|
3840
|
+
`route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
3595
3841
|
);
|
|
3596
3842
|
}
|
|
3597
3843
|
validateExamples(
|
|
3598
|
-
this.
|
|
3599
|
-
this.
|
|
3600
|
-
this.
|
|
3601
|
-
this.
|
|
3602
|
-
this.
|
|
3603
|
-
this.
|
|
3604
|
-
this.
|
|
3605
|
-
this.
|
|
3844
|
+
this.#s.key,
|
|
3845
|
+
this.#s.bodySchema,
|
|
3846
|
+
this.#s.querySchema,
|
|
3847
|
+
this.#s.outputSchema,
|
|
3848
|
+
this.#s.inputExample,
|
|
3849
|
+
this.#s.hasInputExample,
|
|
3850
|
+
this.#s.outputExample,
|
|
3851
|
+
this.#s.hasOutputExample
|
|
3606
3852
|
);
|
|
3607
3853
|
const entry = {
|
|
3608
|
-
key: this.
|
|
3609
|
-
authMode: this.
|
|
3610
|
-
siwxEnabled: this.
|
|
3611
|
-
pricing: this.
|
|
3612
|
-
|
|
3854
|
+
key: this.#s.key,
|
|
3855
|
+
authMode: this.#s.authMode,
|
|
3856
|
+
siwxEnabled: this.#s.siwxEnabled,
|
|
3857
|
+
pricing: this.#s.pricing,
|
|
3858
|
+
billing: this.#s.billing,
|
|
3613
3859
|
streaming: streaming ? true : void 0,
|
|
3614
|
-
protocols: this.
|
|
3615
|
-
bodySchema: this.
|
|
3616
|
-
querySchema: this.
|
|
3617
|
-
outputSchema: this.
|
|
3618
|
-
inputExample: this.
|
|
3619
|
-
outputExample: this.
|
|
3620
|
-
description: this.
|
|
3621
|
-
path: this.
|
|
3622
|
-
method: this.
|
|
3623
|
-
maxPrice: this.
|
|
3624
|
-
minPrice: this.
|
|
3625
|
-
payTo: this.
|
|
3626
|
-
apiKeyResolver: this.
|
|
3627
|
-
providerName: this.
|
|
3628
|
-
providerConfig: this.
|
|
3629
|
-
validateFn: this.
|
|
3630
|
-
settlement: this.
|
|
3631
|
-
mppInfo: this.
|
|
3632
|
-
tickCost: this.
|
|
3633
|
-
unitType: this.
|
|
3860
|
+
protocols: this.#s.protocols,
|
|
3861
|
+
bodySchema: this.#s.bodySchema,
|
|
3862
|
+
querySchema: this.#s.querySchema,
|
|
3863
|
+
outputSchema: this.#s.outputSchema,
|
|
3864
|
+
inputExample: this.#s.hasInputExample ? this.#s.inputExample : void 0,
|
|
3865
|
+
outputExample: this.#s.hasOutputExample ? this.#s.outputExample : void 0,
|
|
3866
|
+
description: this.#s.description,
|
|
3867
|
+
path: this.#s.path,
|
|
3868
|
+
method: this.#s.method,
|
|
3869
|
+
maxPrice: this.#s.maxPrice,
|
|
3870
|
+
minPrice: this.#s.minPrice,
|
|
3871
|
+
payTo: this.#s.payTo,
|
|
3872
|
+
apiKeyResolver: this.#s.apiKeyResolver,
|
|
3873
|
+
providerName: this.#s.providerName,
|
|
3874
|
+
providerConfig: this.#s.providerConfig,
|
|
3875
|
+
validateFn: this.#s.validateFn,
|
|
3876
|
+
settlement: this.#s.settlement,
|
|
3877
|
+
mppInfo: this.#s.mppInfo,
|
|
3878
|
+
tickCost: this.#s.tickCost,
|
|
3879
|
+
unitType: this.#s.unitType
|
|
3634
3880
|
};
|
|
3635
|
-
this.
|
|
3636
|
-
return createRequestHandler(entry, handlerFn, this.
|
|
3881
|
+
this.#s.registry.register(entry);
|
|
3882
|
+
return createRequestHandler(entry, handlerFn, this.#s.deps);
|
|
3637
3883
|
}
|
|
3638
3884
|
};
|
|
3639
|
-
function
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3885
|
+
function normalizePaidArg(routeKey, arg, options) {
|
|
3886
|
+
if (typeof arg === "string") {
|
|
3887
|
+
return { pricing: arg, resolvedOptions: options ?? {}, billing: "exact" };
|
|
3888
|
+
}
|
|
3889
|
+
if (typeof arg === "function") {
|
|
3890
|
+
return {
|
|
3891
|
+
pricing: arg,
|
|
3892
|
+
resolvedOptions: options ?? {},
|
|
3893
|
+
billing: "exact"
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
if ("tiers" in arg && "field" in arg) {
|
|
3897
|
+
return {
|
|
3898
|
+
pricing: { field: arg.field, tiers: arg.tiers, default: arg.default },
|
|
3899
|
+
resolvedOptions: arg,
|
|
3900
|
+
billing: "exact"
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3903
|
+
if ("price" in arg && typeof arg.price === "string") {
|
|
3904
|
+
return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
|
|
3905
|
+
}
|
|
3906
|
+
throw new Error(
|
|
3907
|
+
`route '${routeKey}': .paid() requires one of: a price string, a (body) => string function, { price }, or { field, tiers }. For handler-computed billing use .upTo(); for per-tick billing use .metered().`
|
|
3908
|
+
);
|
|
3909
|
+
}
|
|
3910
|
+
function normalizeUpToArg(routeKey, arg) {
|
|
3911
|
+
const options = typeof arg === "string" ? { maxPrice: arg } : arg;
|
|
3912
|
+
if (!options.maxPrice) {
|
|
3913
|
+
throw new Error(`route '${routeKey}': .upTo() requires maxPrice`);
|
|
3914
|
+
}
|
|
3915
|
+
return {
|
|
3916
|
+
pricing: options.maxPrice,
|
|
3917
|
+
resolvedOptions: options,
|
|
3918
|
+
billing: "upto",
|
|
3919
|
+
unitType: options.unitType,
|
|
3920
|
+
maxPrice: options.maxPrice
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
function normalizeMeteredArg(routeKey, options) {
|
|
3924
|
+
if (!options.maxPrice) {
|
|
3925
|
+
throw new Error(`route '${routeKey}': .metered() requires maxPrice`);
|
|
3926
|
+
}
|
|
3927
|
+
if (!options.tickCost) {
|
|
3928
|
+
throw new Error(`route '${routeKey}': .metered() requires tickCost`);
|
|
3647
3929
|
}
|
|
3648
|
-
return {
|
|
3930
|
+
return {
|
|
3931
|
+
pricing: options.maxPrice,
|
|
3932
|
+
resolvedOptions: options,
|
|
3933
|
+
billing: "metered",
|
|
3934
|
+
tickCost: options.tickCost,
|
|
3935
|
+
unitType: options.unitType,
|
|
3936
|
+
maxPrice: options.maxPrice
|
|
3937
|
+
};
|
|
3649
3938
|
}
|
|
3650
3939
|
|
|
3651
3940
|
// src/discovery/well-known.ts
|
|
@@ -3877,17 +4166,16 @@ function buildPricingInfo(entry) {
|
|
|
3877
4166
|
};
|
|
3878
4167
|
}
|
|
3879
4168
|
if ("tiers" in entry.pricing) {
|
|
3880
|
-
const tierPrices = Object.values(entry.pricing.tiers).map((tier) =>
|
|
3881
|
-
const
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
if (min === max) {
|
|
4169
|
+
const tierPrices = Object.values(entry.pricing.tiers).map((tier) => tier.price);
|
|
4170
|
+
const extrema = tierExtrema(tierPrices);
|
|
4171
|
+
if (extrema) {
|
|
4172
|
+
if (extrema.min === extrema.max) {
|
|
3885
4173
|
return {
|
|
3886
|
-
price: { mode: "fixed", currency: "USD", amount:
|
|
4174
|
+
price: { mode: "fixed", currency: "USD", amount: extrema.min }
|
|
3887
4175
|
};
|
|
3888
4176
|
}
|
|
3889
4177
|
return {
|
|
3890
|
-
price: { mode: "dynamic", currency: "USD", min:
|
|
4178
|
+
price: { mode: "dynamic", currency: "USD", min: extrema.min, max: extrema.max }
|
|
3891
4179
|
};
|
|
3892
4180
|
}
|
|
3893
4181
|
return {
|
|
@@ -3901,6 +4189,20 @@ function buildPricingInfo(entry) {
|
|
|
3901
4189
|
}
|
|
3902
4190
|
return void 0;
|
|
3903
4191
|
}
|
|
4192
|
+
function tierExtrema(prices) {
|
|
4193
|
+
if (prices.length === 0) return null;
|
|
4194
|
+
let min = prices[0];
|
|
4195
|
+
let max = prices[0];
|
|
4196
|
+
try {
|
|
4197
|
+
for (const price of prices.slice(1)) {
|
|
4198
|
+
if (compareDecimals(price, min) < 0) min = price;
|
|
4199
|
+
if (compareDecimals(price, max) > 0) max = price;
|
|
4200
|
+
}
|
|
4201
|
+
} catch {
|
|
4202
|
+
return null;
|
|
4203
|
+
}
|
|
4204
|
+
return { min, max };
|
|
4205
|
+
}
|
|
3904
4206
|
|
|
3905
4207
|
// src/discovery/llms-txt.ts
|
|
3906
4208
|
import { NextResponse as NextResponse10 } from "next/server";
|
|
@@ -4387,11 +4689,11 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4387
4689
|
}
|
|
4388
4690
|
|
|
4389
4691
|
// src/init/x402.ts
|
|
4390
|
-
async function initX402(config, configError) {
|
|
4692
|
+
async function initX402(config, kvStore, configError) {
|
|
4391
4693
|
if (configError) return { initError: configError };
|
|
4392
4694
|
try {
|
|
4393
4695
|
const { createX402Server: createX402Server2 } = await Promise.resolve().then(() => (init_x402_server(), x402_server_exports));
|
|
4394
|
-
const result = await createX402Server2(config);
|
|
4696
|
+
const result = await createX402Server2(config, kvStore);
|
|
4395
4697
|
await result.initPromise;
|
|
4396
4698
|
return {
|
|
4397
4699
|
server: result.server,
|
|
@@ -4571,10 +4873,10 @@ function createRouter(config) {
|
|
|
4571
4873
|
x402Accepts,
|
|
4572
4874
|
mppx: null,
|
|
4573
4875
|
tempoClient: null,
|
|
4574
|
-
mppSessionConfig: config.mpp?.session ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4876
|
+
mppSessionConfig: config.mpp?.session && config.mpp.operatorKey ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4575
4877
|
};
|
|
4576
4878
|
deps.initPromise = (async () => {
|
|
4577
|
-
const x402Result = await initX402(config, x402ConfigError);
|
|
4879
|
+
const x402Result = await initX402(config, kvStore, x402ConfigError);
|
|
4578
4880
|
deps.x402Server = x402Result.server ?? null;
|
|
4579
4881
|
deps.x402FacilitatorsByNetwork = x402Result.facilitatorsByNetwork;
|
|
4580
4882
|
if (x402Result.initError) deps.x402InitError = x402Result.initError;
|
|
@@ -4603,11 +4905,10 @@ function createRouter(config) {
|
|
|
4603
4905
|
`[router] strictRoutes=true forbids key/path divergence for route '${definition.path}'. Remove custom \`key\` or make it equal to \`path\`.`
|
|
4604
4906
|
);
|
|
4605
4907
|
}
|
|
4606
|
-
let builder = new RouteBuilder(key, registry, deps
|
|
4908
|
+
let builder = new RouteBuilder(key, registry, deps, {
|
|
4909
|
+
protocols: config.protocols
|
|
4910
|
+
});
|
|
4607
4911
|
builder = builder.path(normalizedPath);
|
|
4608
|
-
if (config.protocols) {
|
|
4609
|
-
builder._protocols = [...config.protocols];
|
|
4610
|
-
}
|
|
4611
4912
|
if (definition.method) {
|
|
4612
4913
|
builder = builder.method(definition.method);
|
|
4613
4914
|
}
|