@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.cjs
CHANGED
|
@@ -67,6 +67,9 @@ function getConfiguredX402Accepts(config) {
|
|
|
67
67
|
function getConfiguredX402Networks(config) {
|
|
68
68
|
return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
|
|
69
69
|
}
|
|
70
|
+
function selectRouteAccepts(accepts, routeEntry) {
|
|
71
|
+
return routeEntry.billing === "upto" ? accepts.filter((accept) => accept.scheme === "upto") : accepts.filter((accept) => (accept.scheme ?? "exact") !== "upto");
|
|
72
|
+
}
|
|
70
73
|
async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
|
|
71
74
|
return Promise.all(
|
|
72
75
|
accepts.map(async (accept) => ({
|
|
@@ -264,12 +267,148 @@ var init_facilitators = __esm({
|
|
|
264
267
|
}
|
|
265
268
|
});
|
|
266
269
|
|
|
270
|
+
// src/kv-store/facilitator-supported.ts
|
|
271
|
+
function withCachedSupported(inner, options = {}) {
|
|
272
|
+
const { kv, cacheKey, ttlSeconds = FACILITATOR_SUPPORTED_TTL_SECONDS, fallback } = options;
|
|
273
|
+
const kvKey = kv && cacheKey ? `${FACILITATOR_SUPPORTED_KV_PREFIX}${cacheKey}` : void 0;
|
|
274
|
+
let inflight;
|
|
275
|
+
return {
|
|
276
|
+
verify: inner.verify.bind(inner),
|
|
277
|
+
settle: inner.settle.bind(inner),
|
|
278
|
+
getSupported: () => {
|
|
279
|
+
if (inflight) return inflight;
|
|
280
|
+
const attempt = fetchSupported(inner, kv, kvKey, ttlSeconds, fallback);
|
|
281
|
+
inflight = attempt;
|
|
282
|
+
attempt.catch(() => {
|
|
283
|
+
if (inflight === attempt) inflight = void 0;
|
|
284
|
+
});
|
|
285
|
+
return attempt;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async function fetchSupported(inner, kv, kvKey, ttlSeconds, fallback) {
|
|
290
|
+
if (kv && kvKey) {
|
|
291
|
+
const cached = await readKvCache(kv, kvKey);
|
|
292
|
+
if (cached) return cached;
|
|
293
|
+
}
|
|
294
|
+
const fresh = await tryFetchLive(inner, fallback);
|
|
295
|
+
if (fresh === null) return fallback();
|
|
296
|
+
if (kv && kvKey) await writeKvCache(kv, kvKey, fresh, ttlSeconds);
|
|
297
|
+
return fresh;
|
|
298
|
+
}
|
|
299
|
+
async function tryFetchLive(inner, fallback) {
|
|
300
|
+
try {
|
|
301
|
+
return await inner.getSupported();
|
|
302
|
+
} catch (err) {
|
|
303
|
+
if (!fallback) throw err;
|
|
304
|
+
console.warn(
|
|
305
|
+
`[x402] facilitator /supported failed, using hardcoded baseline: ${err instanceof Error ? err.message : String(err)}`
|
|
306
|
+
);
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function readKvCache(kv, key) {
|
|
311
|
+
try {
|
|
312
|
+
const cached = await kv.get(key);
|
|
313
|
+
return isSupportedResponse(cached) ? cached : void 0;
|
|
314
|
+
} catch {
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function writeKvCache(kv, key, value, ttlSeconds) {
|
|
319
|
+
try {
|
|
320
|
+
await kv.setNxEx(key, value, ttlSeconds);
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function isSupportedResponse(value) {
|
|
325
|
+
return typeof value === "object" && value !== null && Array.isArray(value.kinds);
|
|
326
|
+
}
|
|
327
|
+
var FACILITATOR_SUPPORTED_TTL_SECONDS, FACILITATOR_SUPPORTED_KV_PREFIX;
|
|
328
|
+
var init_facilitator_supported = __esm({
|
|
329
|
+
"src/kv-store/facilitator-supported.ts"() {
|
|
330
|
+
"use strict";
|
|
331
|
+
FACILITATOR_SUPPORTED_TTL_SECONDS = 60 * 60;
|
|
332
|
+
FACILITATOR_SUPPORTED_KV_PREFIX = "x402:facilitator-supported:";
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// src/protocols/x402/facilitator-clients.ts
|
|
337
|
+
function createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient, kvStore) {
|
|
338
|
+
return getResolvedX402FacilitatorGroups(facilitatorsByNetwork).map((group) => {
|
|
339
|
+
const inner = new HTTPFacilitatorClient(group.config);
|
|
340
|
+
const kinds = buildSupportedKinds(group);
|
|
341
|
+
const baseline = () => ({
|
|
342
|
+
kinds,
|
|
343
|
+
extensions: [],
|
|
344
|
+
signers: {}
|
|
345
|
+
});
|
|
346
|
+
if (group.family === "solana") {
|
|
347
|
+
return hardcodedSupportedClient(inner, baseline);
|
|
348
|
+
}
|
|
349
|
+
const cached = withCachedSupported(inner, {
|
|
350
|
+
kv: kvStore,
|
|
351
|
+
cacheKey: group.config.url,
|
|
352
|
+
fallback: baseline
|
|
353
|
+
});
|
|
354
|
+
return withScopedKinds(cached, kinds);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
function hardcodedSupportedClient(inner, build) {
|
|
358
|
+
return {
|
|
359
|
+
verify: inner.verify.bind(inner),
|
|
360
|
+
settle: inner.settle.bind(inner),
|
|
361
|
+
getSupported: async () => build()
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function withScopedKinds(client, kinds) {
|
|
365
|
+
return {
|
|
366
|
+
verify: client.verify.bind(client),
|
|
367
|
+
settle: client.settle.bind(client),
|
|
368
|
+
getSupported: async () => {
|
|
369
|
+
const live = await client.getSupported();
|
|
370
|
+
return { ...live, kinds: mergeKindExtras(kinds, live.kinds) };
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function mergeKindExtras(scoped, live) {
|
|
375
|
+
return scoped.map((kind) => {
|
|
376
|
+
const match = live.find((l) => l.scheme === kind.scheme && l.network === kind.network);
|
|
377
|
+
return match?.extra ? { ...kind, extra: { ...kind.extra, ...match.extra } } : kind;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function buildSupportedKinds(group) {
|
|
381
|
+
return group.networks.flatMap((network) => {
|
|
382
|
+
if (group.family === "solana") {
|
|
383
|
+
return [
|
|
384
|
+
{
|
|
385
|
+
x402Version: 2,
|
|
386
|
+
scheme: "exact",
|
|
387
|
+
network,
|
|
388
|
+
extra: { features: { xSettlementAccountSupported: true } }
|
|
389
|
+
}
|
|
390
|
+
];
|
|
391
|
+
}
|
|
392
|
+
return [
|
|
393
|
+
{ x402Version: 2, scheme: "exact", network },
|
|
394
|
+
{ x402Version: 2, scheme: "upto", network }
|
|
395
|
+
];
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
var init_facilitator_clients = __esm({
|
|
399
|
+
"src/protocols/x402/facilitator-clients.ts"() {
|
|
400
|
+
"use strict";
|
|
401
|
+
init_facilitator_supported();
|
|
402
|
+
init_facilitators();
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
267
406
|
// src/init/x402-server.ts
|
|
268
407
|
var x402_server_exports = {};
|
|
269
408
|
__export(x402_server_exports, {
|
|
270
409
|
createX402Server: () => createX402Server
|
|
271
410
|
});
|
|
272
|
-
async function createX402Server(config) {
|
|
411
|
+
async function createX402Server(config, kvStore) {
|
|
273
412
|
const { x402ResourceServer, HTTPFacilitatorClient } = await import("@x402/core/server");
|
|
274
413
|
const { registerExactEvmScheme } = await import("@x402/evm/exact/server");
|
|
275
414
|
const { bazaarResourceServerExtension } = await import("@x402/extensions/bazaar");
|
|
@@ -283,7 +422,11 @@ async function createX402Server(config) {
|
|
|
283
422
|
);
|
|
284
423
|
const evmNetworks = filterEvmNetworks(configuredNetworks);
|
|
285
424
|
const svmNetworks = filterSolanaNetworks(configuredNetworks);
|
|
286
|
-
const facilitatorClients = createFacilitatorClients(
|
|
425
|
+
const facilitatorClients = createFacilitatorClients(
|
|
426
|
+
facilitatorsByNetwork,
|
|
427
|
+
HTTPFacilitatorClient,
|
|
428
|
+
kvStore
|
|
429
|
+
);
|
|
287
430
|
const server = new x402ResourceServer(
|
|
288
431
|
facilitatorClients.length === 1 ? facilitatorClients[0] : facilitatorClients
|
|
289
432
|
);
|
|
@@ -307,48 +450,13 @@ async function createX402Server(config) {
|
|
|
307
450
|
facilitatorsByNetwork
|
|
308
451
|
};
|
|
309
452
|
}
|
|
310
|
-
function createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient) {
|
|
311
|
-
const groups = getResolvedX402FacilitatorGroups(facilitatorsByNetwork);
|
|
312
|
-
return groups.map((group) => {
|
|
313
|
-
const inner = new HTTPFacilitatorClient(group.config);
|
|
314
|
-
const kinds = buildSupportedKinds(group);
|
|
315
|
-
return hardcodedSupportedClient(inner, kinds);
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
function hardcodedSupportedClient(inner, kinds) {
|
|
319
|
-
return {
|
|
320
|
-
verify: inner.verify.bind(inner),
|
|
321
|
-
settle: inner.settle.bind(inner),
|
|
322
|
-
getSupported: async () => ({ kinds, extensions: [], signers: {} })
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
function buildSupportedKinds(group) {
|
|
326
|
-
return group.networks.flatMap((network) => {
|
|
327
|
-
const exactKind = {
|
|
328
|
-
x402Version: 2,
|
|
329
|
-
scheme: "exact",
|
|
330
|
-
network,
|
|
331
|
-
...group.family === "solana" ? {
|
|
332
|
-
extra: {
|
|
333
|
-
features: {
|
|
334
|
-
xSettlementAccountSupported: true
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
} : {}
|
|
338
|
-
};
|
|
339
|
-
const uptoKind = { x402Version: 2, scheme: "upto", network };
|
|
340
|
-
if (group.family === "evm") {
|
|
341
|
-
return [exactKind, uptoKind];
|
|
342
|
-
}
|
|
343
|
-
return [exactKind, uptoKind];
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
453
|
var init_x402_server = __esm({
|
|
347
454
|
"src/init/x402-server.ts"() {
|
|
348
455
|
"use strict";
|
|
349
456
|
init_evm();
|
|
350
457
|
init_solana();
|
|
351
458
|
init_facilitators();
|
|
459
|
+
init_facilitator_clients();
|
|
352
460
|
init_accepts();
|
|
353
461
|
}
|
|
354
462
|
});
|
|
@@ -699,10 +807,7 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
699
807
|
}
|
|
700
808
|
if (isAsyncIterable(returned) && !isThenable(returned)) {
|
|
701
809
|
return errorResult(
|
|
702
|
-
new HttpError(
|
|
703
|
-
`route '${ctx.routeEntry.key}': streaming handlers require .paid({ dynamic: true })`,
|
|
704
|
-
500
|
|
705
|
-
)
|
|
810
|
+
new HttpError(`route '${ctx.routeEntry.key}': streaming handlers require .metered()`, 500)
|
|
706
811
|
);
|
|
707
812
|
}
|
|
708
813
|
let rawResult;
|
|
@@ -1069,6 +1174,61 @@ async function runApiKeyOnlyFlow(ctx) {
|
|
|
1069
1174
|
return runHandlerOnly(ctx, null, result.account);
|
|
1070
1175
|
}
|
|
1071
1176
|
|
|
1177
|
+
// src/pricing/format.ts
|
|
1178
|
+
var USDC_DECIMALS = 6;
|
|
1179
|
+
var DECIMAL_RE = /^(\d+)(?:\.(\d+))?$/;
|
|
1180
|
+
function badDecimal(amount) {
|
|
1181
|
+
return Object.assign(new Error(`'${amount}' is not a valid decimal-dollar string`), {
|
|
1182
|
+
status: 400
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
function decimalToAtomic(amount, decimals = USDC_DECIMALS) {
|
|
1186
|
+
const match = DECIMAL_RE.exec(amount.trim());
|
|
1187
|
+
if (!match) throw badDecimal(amount);
|
|
1188
|
+
const whole = match[1];
|
|
1189
|
+
const fraction = match[2] ?? "";
|
|
1190
|
+
if (fraction.length > decimals) {
|
|
1191
|
+
throw Object.assign(new Error(`Amount '${amount}' exceeds ${decimals} decimal places`), {
|
|
1192
|
+
status: 400
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
|
|
1196
|
+
return BigInt(normalized || "0");
|
|
1197
|
+
}
|
|
1198
|
+
function atomicToDecimal(atomic, decimals = USDC_DECIMALS) {
|
|
1199
|
+
const divisor = 10n ** BigInt(decimals);
|
|
1200
|
+
const whole = atomic / divisor;
|
|
1201
|
+
const fraction = atomic % divisor;
|
|
1202
|
+
if (fraction === 0n) return whole.toString();
|
|
1203
|
+
const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
1204
|
+
return `${whole}.${fractionStr}`;
|
|
1205
|
+
}
|
|
1206
|
+
function compareDecimals(a, b) {
|
|
1207
|
+
const av = decimalToAtomic(a);
|
|
1208
|
+
const bv = decimalToAtomic(b);
|
|
1209
|
+
if (av < bv) return -1;
|
|
1210
|
+
if (av > bv) return 1;
|
|
1211
|
+
return 0;
|
|
1212
|
+
}
|
|
1213
|
+
function isPositiveDecimal(value) {
|
|
1214
|
+
try {
|
|
1215
|
+
return decimalToAtomic(value) > 0n;
|
|
1216
|
+
} catch {
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
function multiplyDecimal(decimal, factor) {
|
|
1221
|
+
if (!Number.isFinite(factor) || factor <= 0) return decimal;
|
|
1222
|
+
const [whole, fraction = ""] = decimal.split(".");
|
|
1223
|
+
const scaled = (BigInt(whole + fraction) * BigInt(factor)).toString();
|
|
1224
|
+
const decimals = fraction.length;
|
|
1225
|
+
if (decimals === 0) return scaled;
|
|
1226
|
+
const padded = scaled.padStart(decimals + 1, "0");
|
|
1227
|
+
const intPart = padded.slice(0, padded.length - decimals);
|
|
1228
|
+
const fracPart = padded.slice(padded.length - decimals).replace(/0+$/, "");
|
|
1229
|
+
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1072
1232
|
// src/pricing/dynamic.ts
|
|
1073
1233
|
var DynamicPricing = class {
|
|
1074
1234
|
constructor(opts) {
|
|
@@ -1080,6 +1240,7 @@ var DynamicPricing = class {
|
|
|
1080
1240
|
const raw = await this.opts.fn(body);
|
|
1081
1241
|
return this.cap(raw, body);
|
|
1082
1242
|
} catch (err) {
|
|
1243
|
+
if (err instanceof HttpError) throw err;
|
|
1083
1244
|
this.alert("error", `Pricing function failed: ${msg(err)}`, {
|
|
1084
1245
|
error: err instanceof Error ? err.stack : String(err),
|
|
1085
1246
|
body
|
|
@@ -1104,9 +1265,13 @@ var DynamicPricing = class {
|
|
|
1104
1265
|
}
|
|
1105
1266
|
cap(raw, body) {
|
|
1106
1267
|
if (!this.opts.maxPrice) return raw;
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1268
|
+
let overCap;
|
|
1269
|
+
try {
|
|
1270
|
+
overCap = compareDecimals(raw, this.opts.maxPrice) > 0;
|
|
1271
|
+
} catch {
|
|
1272
|
+
overCap = true;
|
|
1273
|
+
}
|
|
1274
|
+
if (overCap) {
|
|
1110
1275
|
this.alert("warn", `Price ${raw} exceeds maxPrice ${this.opts.maxPrice}, capping`, {
|
|
1111
1276
|
calculated: raw,
|
|
1112
1277
|
maxPrice: this.opts.maxPrice,
|
|
@@ -1183,7 +1348,7 @@ var TieredPricing = class {
|
|
|
1183
1348
|
maxTierPrice() {
|
|
1184
1349
|
let max = "0";
|
|
1185
1350
|
for (const tier of Object.values(this.opts.tiers)) {
|
|
1186
|
-
if (
|
|
1351
|
+
if (compareDecimals(tier.price, max) > 0) max = tier.price;
|
|
1187
1352
|
}
|
|
1188
1353
|
return max;
|
|
1189
1354
|
}
|
|
@@ -1598,7 +1763,7 @@ var mppStrategy = {
|
|
|
1598
1763
|
async verify(args) {
|
|
1599
1764
|
const info = readMppCredential(args.request);
|
|
1600
1765
|
if (!info) return { ok: false, kind: "invalid" };
|
|
1601
|
-
if (args.routeEntry.
|
|
1766
|
+
if (args.routeEntry.billing === "metered") {
|
|
1602
1767
|
if (!info.sessionAction) return { ok: false, kind: "invalid" };
|
|
1603
1768
|
return verifySessionMode(args, info);
|
|
1604
1769
|
}
|
|
@@ -1649,7 +1814,7 @@ var mppStrategy = {
|
|
|
1649
1814
|
async buildChallenge(args) {
|
|
1650
1815
|
if (!args.deps.mppx) return {};
|
|
1651
1816
|
const sessionsConfigured = args.deps.mppSessionConfig && (args.deps.mppx.sessionRequest || args.deps.mppx.sessionStream);
|
|
1652
|
-
if (args.routeEntry.
|
|
1817
|
+
if (args.routeEntry.billing === "metered" && sessionsConfigured) {
|
|
1653
1818
|
const tickCost = args.routeEntry.tickCost;
|
|
1654
1819
|
const computedDeposit = tickCost !== void 0 ? multiplyDecimal(tickCost, args.deps.mppSessionConfig.depositMultiplier) : void 0;
|
|
1655
1820
|
const suggestedDeposit = args.routeEntry.maxPrice ?? computedDeposit ?? args.price;
|
|
@@ -1661,17 +1826,6 @@ var mppStrategy = {
|
|
|
1661
1826
|
return buildChargeChallenge(args);
|
|
1662
1827
|
}
|
|
1663
1828
|
};
|
|
1664
|
-
function multiplyDecimal(decimal, factor) {
|
|
1665
|
-
if (!Number.isFinite(factor) || factor <= 0) return decimal;
|
|
1666
|
-
const [whole, fraction = ""] = decimal.split(".");
|
|
1667
|
-
const scaled = (BigInt(whole + fraction) * BigInt(factor)).toString();
|
|
1668
|
-
const decimals = fraction.length;
|
|
1669
|
-
if (decimals === 0) return scaled;
|
|
1670
|
-
const padded = scaled.padStart(decimals + 1, "0");
|
|
1671
|
-
const intPart = padded.slice(0, padded.length - decimals);
|
|
1672
|
-
const fracPart = padded.slice(padded.length - decimals).replace(/0+$/, "");
|
|
1673
|
-
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
1674
|
-
}
|
|
1675
1829
|
async function buildChargeChallenge(args) {
|
|
1676
1830
|
if (!args.deps.mppx) return {};
|
|
1677
1831
|
try {
|
|
@@ -1764,26 +1918,13 @@ function buildCustomRequirement(price, accept) {
|
|
|
1764
1918
|
return {
|
|
1765
1919
|
scheme: accept.scheme,
|
|
1766
1920
|
network: accept.network,
|
|
1767
|
-
amount:
|
|
1921
|
+
amount: decimalToAtomic(price, accept.decimals ?? 6).toString(),
|
|
1768
1922
|
asset: accept.asset,
|
|
1769
1923
|
payTo: accept.payTo,
|
|
1770
1924
|
maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
|
|
1771
1925
|
extra: accept.extra ?? {}
|
|
1772
1926
|
};
|
|
1773
1927
|
}
|
|
1774
|
-
function decimalToAtomicUnits(amount, decimals) {
|
|
1775
|
-
const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
|
|
1776
|
-
if (!match?.groups) {
|
|
1777
|
-
throw new Error(`Invalid decimal amount '${amount}'`);
|
|
1778
|
-
}
|
|
1779
|
-
const whole = match.groups.whole;
|
|
1780
|
-
const fraction = match.groups.fraction ?? "";
|
|
1781
|
-
if (fraction.length > decimals) {
|
|
1782
|
-
throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
|
|
1783
|
-
}
|
|
1784
|
-
const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
|
|
1785
|
-
return normalized === "" ? "0" : normalized;
|
|
1786
|
-
}
|
|
1787
1928
|
|
|
1788
1929
|
// src/protocols/x402/challenge.ts
|
|
1789
1930
|
async function buildX402Challenge(opts) {
|
|
@@ -1930,7 +2071,7 @@ function tagBareDecimalAsDollars(amount) {
|
|
|
1930
2071
|
}
|
|
1931
2072
|
|
|
1932
2073
|
// src/protocols/x402/verify.ts
|
|
1933
|
-
var
|
|
2074
|
+
var import_types3 = require("@x402/core/types");
|
|
1934
2075
|
async function verifyX402Payment(opts) {
|
|
1935
2076
|
const { server, request, price, accepts, report } = opts;
|
|
1936
2077
|
const payload = await readPaymentPayload(request);
|
|
@@ -1949,7 +2090,7 @@ async function verifyX402Payment(opts) {
|
|
|
1949
2090
|
try {
|
|
1950
2091
|
verify = await server.verifyPayment(payload, matching);
|
|
1951
2092
|
} catch (err) {
|
|
1952
|
-
if (err instanceof
|
|
2093
|
+
if (err instanceof import_types3.VerifyError && err.statusCode >= 400 && err.statusCode < 500) {
|
|
1953
2094
|
return invalidPaymentVerification({
|
|
1954
2095
|
reason: err.invalidReason ?? "verify_error",
|
|
1955
2096
|
...err.invalidMessage ? { message: err.invalidMessage } : {},
|
|
@@ -2050,7 +2191,7 @@ async function verifyX402(args) {
|
|
|
2050
2191
|
const accepts = await resolveX402Accepts(
|
|
2051
2192
|
request,
|
|
2052
2193
|
routeEntry,
|
|
2053
|
-
deps.x402Accepts,
|
|
2194
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2054
2195
|
deps.payeeAddress,
|
|
2055
2196
|
body
|
|
2056
2197
|
);
|
|
@@ -2098,7 +2239,7 @@ async function verifyX402(args) {
|
|
|
2098
2239
|
async function settleX402(args) {
|
|
2099
2240
|
const { response, payment, token, deps, routeEntry, billedAmount, report } = args;
|
|
2100
2241
|
const { payload, requirements } = token;
|
|
2101
|
-
const override = routeEntry.
|
|
2242
|
+
const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
|
|
2102
2243
|
try {
|
|
2103
2244
|
const settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2104
2245
|
if (!settle.result?.success) {
|
|
@@ -2128,7 +2269,7 @@ async function buildX402ChallengeContribution(args) {
|
|
|
2128
2269
|
const accepts = await resolveX402Accepts(
|
|
2129
2270
|
request,
|
|
2130
2271
|
routeEntry,
|
|
2131
|
-
deps.x402Accepts,
|
|
2272
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2132
2273
|
deps.payeeAddress,
|
|
2133
2274
|
body
|
|
2134
2275
|
);
|
|
@@ -2146,13 +2287,14 @@ async function buildX402ChallengeContribution(args) {
|
|
|
2146
2287
|
}
|
|
2147
2288
|
function reportSettleFailure(report, err, network) {
|
|
2148
2289
|
const facilitator = err ?? {};
|
|
2149
|
-
|
|
2290
|
+
const meta = {
|
|
2150
2291
|
error: err instanceof Error ? err.message : String(err),
|
|
2151
2292
|
network,
|
|
2152
2293
|
errorReason: facilitator.errorReason,
|
|
2153
2294
|
facilitatorStatus: facilitator.response?.status,
|
|
2154
2295
|
facilitatorBody: facilitator.response?.data ?? facilitator.response?.body
|
|
2155
|
-
}
|
|
2296
|
+
};
|
|
2297
|
+
report("error", "Settlement failed", meta);
|
|
2156
2298
|
}
|
|
2157
2299
|
|
|
2158
2300
|
// src/protocols/index.ts
|
|
@@ -2175,6 +2317,7 @@ function getAllowedStrategies(allowed) {
|
|
|
2175
2317
|
var import_server4 = require("next/server");
|
|
2176
2318
|
|
|
2177
2319
|
// src/pipeline/challenge-extensions.ts
|
|
2320
|
+
init_evm();
|
|
2178
2321
|
async function buildChallengeExtensions(ctx) {
|
|
2179
2322
|
const { routeEntry } = ctx;
|
|
2180
2323
|
let extensions;
|
|
@@ -2219,6 +2362,21 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2219
2362
|
} catch {
|
|
2220
2363
|
}
|
|
2221
2364
|
}
|
|
2365
|
+
const hasEvmUpto = ctx.routeEntry.billing === "upto" && ctx.deps.x402Accepts.some((accept) => accept.scheme === "upto" && isEvmNetwork(accept.network));
|
|
2366
|
+
if (hasEvmUpto) {
|
|
2367
|
+
try {
|
|
2368
|
+
const { declareEip2612GasSponsoringExtension } = await import("@x402/extensions");
|
|
2369
|
+
extensions = {
|
|
2370
|
+
...extensions ?? {},
|
|
2371
|
+
...declareEip2612GasSponsoringExtension()
|
|
2372
|
+
};
|
|
2373
|
+
} catch (err) {
|
|
2374
|
+
ctx.report(
|
|
2375
|
+
"warn",
|
|
2376
|
+
`EIP-2612 gas-sponsoring declaration failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2222
2380
|
return extensions;
|
|
2223
2381
|
}
|
|
2224
2382
|
|
|
@@ -2371,31 +2529,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2371
2529
|
});
|
|
2372
2530
|
}
|
|
2373
2531
|
|
|
2374
|
-
// src/
|
|
2375
|
-
var import_server6 = require("next/server");
|
|
2376
|
-
|
|
2377
|
-
// src/pricing/atomic.ts
|
|
2378
|
-
var USDC_DECIMALS = 6;
|
|
2379
|
-
function decimalToAtomic(amount) {
|
|
2380
|
-
const m = /^(\d+)(?:\.(\d+))?$/.exec(amount.trim());
|
|
2381
|
-
if (!m) {
|
|
2382
|
-
throw Object.assign(new Error(`'${amount}' is not a valid decimal-dollar string`), {
|
|
2383
|
-
status: 400
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
const whole = m[1];
|
|
2387
|
-
const fraction = (m[2] ?? "").slice(0, USDC_DECIMALS).padEnd(USDC_DECIMALS, "0");
|
|
2388
|
-
return BigInt(`${whole}${fraction}`.replace(/^0+(?=\d)/, "") || "0");
|
|
2389
|
-
}
|
|
2390
|
-
function atomicToDecimal(atomic) {
|
|
2391
|
-
const whole = atomic / 10n ** BigInt(USDC_DECIMALS);
|
|
2392
|
-
const fraction = atomic % 10n ** BigInt(USDC_DECIMALS);
|
|
2393
|
-
if (fraction === 0n) return whole.toString();
|
|
2394
|
-
const fractionStr = fraction.toString().padStart(USDC_DECIMALS, "0").replace(/0+$/, "");
|
|
2395
|
-
return `${whole}.${fractionStr}`;
|
|
2396
|
-
}
|
|
2397
|
-
|
|
2398
|
-
// src/pricing/charge-context.ts
|
|
2532
|
+
// src/pricing/metered-charge.ts
|
|
2399
2533
|
function createChargeContext(args) {
|
|
2400
2534
|
const { tickCost, maxPrice, route } = args;
|
|
2401
2535
|
const tickAtomic = decimalToAtomic(tickCost);
|
|
@@ -2430,15 +2564,10 @@ function createChargeContext(args) {
|
|
|
2430
2564
|
};
|
|
2431
2565
|
}
|
|
2432
2566
|
|
|
2433
|
-
// src/pipeline/flows/dynamic/dynamic-invoke.ts
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
tickCost: ctx.routeEntry.tickCost,
|
|
2438
|
-
maxPrice: ctx.routeEntry.maxPrice,
|
|
2439
|
-
route: ctx.routeEntry.key
|
|
2440
|
-
}) : null;
|
|
2441
|
-
const baseHandlerCtx = {
|
|
2567
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2568
|
+
var import_server6 = require("next/server");
|
|
2569
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2570
|
+
return {
|
|
2442
2571
|
body,
|
|
2443
2572
|
query: parseQuery(ctx.request, ctx.routeEntry),
|
|
2444
2573
|
request: ctx.request,
|
|
@@ -2450,12 +2579,41 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2450
2579
|
alert: ctx.report,
|
|
2451
2580
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2452
2581
|
};
|
|
2582
|
+
}
|
|
2583
|
+
function toResponse(rawResult) {
|
|
2584
|
+
return rawResult instanceof Response ? rawResult : import_server6.NextResponse.json(rawResult);
|
|
2585
|
+
}
|
|
2586
|
+
function errorResult2(error) {
|
|
2587
|
+
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
2588
|
+
const message = error instanceof Error ? error.message : "Internal error";
|
|
2589
|
+
return {
|
|
2590
|
+
kind: "request",
|
|
2591
|
+
response: import_server6.NextResponse.json({ success: false, error: message }, { status }),
|
|
2592
|
+
rawResult: void 0,
|
|
2593
|
+
handlerError: error
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
function isAsyncIterable2(value) {
|
|
2597
|
+
return value != null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
2598
|
+
}
|
|
2599
|
+
function isThenable2(value) {
|
|
2600
|
+
return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2604
|
+
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2605
|
+
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2606
|
+
tickCost: ctx.routeEntry.tickCost,
|
|
2607
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2608
|
+
route: ctx.routeEntry.key
|
|
2609
|
+
}) : null;
|
|
2610
|
+
const baseHandlerCtx = buildBaseHandlerCtx(ctx, wallet, account, body, payment);
|
|
2453
2611
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2454
2612
|
let returned;
|
|
2455
2613
|
try {
|
|
2456
2614
|
returned = ctx.handler(handlerCtx);
|
|
2457
2615
|
} catch (error) {
|
|
2458
|
-
return errorResult2(error
|
|
2616
|
+
return errorResult2(error);
|
|
2459
2617
|
}
|
|
2460
2618
|
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2461
2619
|
if (!chargeContext) {
|
|
@@ -2463,41 +2621,80 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2463
2621
|
new HttpError(
|
|
2464
2622
|
"route returned an async iterable from a non-streaming handler \u2014 use .stream(async function*(...)) instead of .handler() to opt into streaming",
|
|
2465
2623
|
500
|
|
2466
|
-
)
|
|
2467
|
-
null
|
|
2624
|
+
)
|
|
2468
2625
|
);
|
|
2469
2626
|
}
|
|
2470
|
-
return {
|
|
2471
|
-
kind: "stream",
|
|
2472
|
-
source: returned,
|
|
2473
|
-
chargeContext
|
|
2474
|
-
};
|
|
2627
|
+
return { kind: "stream", source: returned, chargeContext };
|
|
2475
2628
|
}
|
|
2476
2629
|
let rawResult;
|
|
2477
2630
|
try {
|
|
2478
2631
|
rawResult = await returned;
|
|
2479
2632
|
} catch (error) {
|
|
2480
|
-
return errorResult2(error
|
|
2633
|
+
return errorResult2(error);
|
|
2481
2634
|
}
|
|
2482
|
-
|
|
2483
|
-
return { kind: "request", response, rawResult };
|
|
2635
|
+
return { kind: "request", response: toResponse(rawResult), rawResult };
|
|
2484
2636
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2637
|
+
|
|
2638
|
+
// src/pricing/upto-charge.ts
|
|
2639
|
+
function createUptoChargeContext(args) {
|
|
2640
|
+
const { maxPrice, route } = args;
|
|
2641
|
+
const capAtomic = decimalToAtomic(maxPrice);
|
|
2642
|
+
if (capAtomic <= 0n) {
|
|
2643
|
+
throw new Error(`route '${route}': maxPrice '${maxPrice}' must be a positive decimal string`);
|
|
2644
|
+
}
|
|
2645
|
+
let calls = 0;
|
|
2646
|
+
let atomic = 0n;
|
|
2647
|
+
const charge = async (amount) => {
|
|
2648
|
+
const nextAtomic = atomic + decimalToAtomic(amount);
|
|
2649
|
+
if (nextAtomic > capAtomic) {
|
|
2650
|
+
throw Object.assign(
|
|
2651
|
+
new Error(
|
|
2652
|
+
`route '${route}': charge() running total ($${atomicToDecimal(nextAtomic)}) exceeds maxPrice ($${atomicToDecimal(capAtomic)})`
|
|
2653
|
+
),
|
|
2654
|
+
{ status: 400, code: "CHARGE_OVER_CAP" }
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
calls += 1;
|
|
2658
|
+
atomic = nextAtomic;
|
|
2659
|
+
};
|
|
2489
2660
|
return {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
handlerError: error
|
|
2661
|
+
charge,
|
|
2662
|
+
callCount: () => calls,
|
|
2663
|
+
atomicTotal: () => atomic
|
|
2494
2664
|
};
|
|
2495
2665
|
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2666
|
+
|
|
2667
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2668
|
+
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2669
|
+
const uptoCtx = createUptoChargeContext({
|
|
2670
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2671
|
+
route: ctx.routeEntry.key
|
|
2672
|
+
});
|
|
2673
|
+
const handlerCtx = {
|
|
2674
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2675
|
+
charge: uptoCtx.charge
|
|
2676
|
+
};
|
|
2677
|
+
let returned;
|
|
2678
|
+
try {
|
|
2679
|
+
returned = ctx.handler(handlerCtx);
|
|
2680
|
+
} catch (error) {
|
|
2681
|
+
return errorResult2(error);
|
|
2682
|
+
}
|
|
2683
|
+
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2684
|
+
return errorResult2(
|
|
2685
|
+
new HttpError(
|
|
2686
|
+
"streaming is not supported on .upTo() routes \u2014 return a value from .handler() instead",
|
|
2687
|
+
500
|
|
2688
|
+
)
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2691
|
+
let rawResult;
|
|
2692
|
+
try {
|
|
2693
|
+
rawResult = await returned;
|
|
2694
|
+
} catch (error) {
|
|
2695
|
+
return errorResult2(error);
|
|
2696
|
+
}
|
|
2697
|
+
return { kind: "request", response: toResponse(rawResult), rawResult, uptoContext: uptoCtx };
|
|
2501
2698
|
}
|
|
2502
2699
|
|
|
2503
2700
|
// src/pipeline/flows/dynamic/dynamic-preflight.ts
|
|
@@ -2527,7 +2724,7 @@ async function runDynamicRequestFlow(args) {
|
|
|
2527
2724
|
}
|
|
2528
2725
|
const beforeErr = await runBeforeSettle(ctx, settleScope);
|
|
2529
2726
|
if (beforeErr) return beforeErr;
|
|
2530
|
-
const billedAmount = routeEntry
|
|
2727
|
+
const billedAmount = computeBilledAmount(routeEntry, result);
|
|
2531
2728
|
return settleAndFinalizeRequest({
|
|
2532
2729
|
ctx,
|
|
2533
2730
|
strategy,
|
|
@@ -2545,6 +2742,19 @@ async function runDynamicRequestFlow(args) {
|
|
|
2545
2742
|
}
|
|
2546
2743
|
});
|
|
2547
2744
|
}
|
|
2745
|
+
function computeBilledAmount(routeEntry, result) {
|
|
2746
|
+
if (routeEntry.billing === "upto") {
|
|
2747
|
+
const total = result.uptoContext?.atomicTotal() ?? 0n;
|
|
2748
|
+
if (total <= 0n) {
|
|
2749
|
+
throw new HttpError(
|
|
2750
|
+
`route '${routeEntry.key}': handler did not call charge(amount) \u2014 upto routes must accumulate a non-zero billed amount`,
|
|
2751
|
+
500
|
|
2752
|
+
);
|
|
2753
|
+
}
|
|
2754
|
+
return atomicToDecimal(total);
|
|
2755
|
+
}
|
|
2756
|
+
return routeEntry.tickCost;
|
|
2757
|
+
}
|
|
2548
2758
|
|
|
2549
2759
|
// src/pipeline/flows/dynamic/dynamic-stream.ts
|
|
2550
2760
|
async function runDynamicStreamFlow(args) {
|
|
@@ -2617,13 +2827,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2617
2827
|
amount: price,
|
|
2618
2828
|
network: verifyOutcome.payment.network
|
|
2619
2829
|
});
|
|
2620
|
-
const result = await invokeDynamic(
|
|
2621
|
-
ctx,
|
|
2622
|
-
verifyOutcome.wallet,
|
|
2623
|
-
account,
|
|
2624
|
-
parsedBody,
|
|
2625
|
-
verifyOutcome.payment
|
|
2626
|
-
);
|
|
2830
|
+
const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
|
|
2627
2831
|
switch (result.kind) {
|
|
2628
2832
|
case "stream":
|
|
2629
2833
|
return runDynamicStreamFlow({
|
|
@@ -2645,6 +2849,18 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2645
2849
|
});
|
|
2646
2850
|
}
|
|
2647
2851
|
}
|
|
2852
|
+
async function invokeDynamic(ctx, verifyOutcome, account, parsedBody) {
|
|
2853
|
+
switch (ctx.routeEntry.billing) {
|
|
2854
|
+
case "upto":
|
|
2855
|
+
return invokeUpto(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2856
|
+
case "metered":
|
|
2857
|
+
return invokeMetered(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2858
|
+
case "exact":
|
|
2859
|
+
throw new Error(
|
|
2860
|
+
`route '${ctx.routeEntry.key}': exact billing must not reach the dynamic paid flow`
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2648
2864
|
|
|
2649
2865
|
// src/pipeline/flows/static/static-body-and-price.ts
|
|
2650
2866
|
async function resolveStaticBodyAndPrice(args) {
|
|
@@ -2792,19 +3008,27 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2792
3008
|
|
|
2793
3009
|
// src/pipeline/flows/paid.ts
|
|
2794
3010
|
async function runPaidFlow(ctx) {
|
|
2795
|
-
const
|
|
2796
|
-
|
|
2797
|
-
case true:
|
|
2798
|
-
return runDynamicPaidFlow(ctx);
|
|
2799
|
-
case false:
|
|
2800
|
-
return runStaticPaidFlow(ctx);
|
|
2801
|
-
}
|
|
3011
|
+
const handlerCharged = ctx.routeEntry.billing !== "exact";
|
|
3012
|
+
return handlerCharged ? runDynamicPaidFlow(ctx) : runStaticPaidFlow(ctx);
|
|
2802
3013
|
}
|
|
2803
3014
|
|
|
2804
3015
|
// src/pipeline/flows/siwx-only.ts
|
|
2805
3016
|
var import_server7 = require("next/server");
|
|
2806
3017
|
|
|
2807
3018
|
// src/kv-store/client.ts
|
|
3019
|
+
var BIGINT_SUFFIX = "#__bigint";
|
|
3020
|
+
function stringifyValue(value) {
|
|
3021
|
+
return JSON.stringify(
|
|
3022
|
+
value,
|
|
3023
|
+
(_key, v) => typeof v === "bigint" ? `${v.toString()}${BIGINT_SUFFIX}` : v
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
function parseValue(raw) {
|
|
3027
|
+
return JSON.parse(
|
|
3028
|
+
raw,
|
|
3029
|
+
(_key, v) => typeof v === "string" && v.endsWith(BIGINT_SUFFIX) ? BigInt(v.slice(0, -BIGINT_SUFFIX.length)) : v
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
2808
3032
|
function restKvStore(url, token) {
|
|
2809
3033
|
const base = url.replace(/\/+$/, "");
|
|
2810
3034
|
const authHeader = { Authorization: `Bearer ${token}` };
|
|
@@ -2826,16 +3050,22 @@ function restKvStore(url, token) {
|
|
|
2826
3050
|
const res = await fetch(`${base}/get/${encodeURIComponent(key)}`, { headers: authHeader });
|
|
2827
3051
|
if (!res.ok) throw new Error(`[kv-store] GET ${key}: ${res.status}`);
|
|
2828
3052
|
const { result } = await res.json();
|
|
2829
|
-
|
|
3053
|
+
if (result == null) return null;
|
|
3054
|
+
if (typeof result !== "string") return result;
|
|
3055
|
+
try {
|
|
3056
|
+
return parseValue(result);
|
|
3057
|
+
} catch {
|
|
3058
|
+
return result;
|
|
3059
|
+
}
|
|
2830
3060
|
}
|
|
2831
3061
|
async function set(key, value) {
|
|
2832
|
-
await exec(["SET", key,
|
|
3062
|
+
await exec(["SET", key, stringifyValue(value)]);
|
|
2833
3063
|
}
|
|
2834
3064
|
async function del(key) {
|
|
2835
3065
|
await exec(["DEL", key]);
|
|
2836
3066
|
}
|
|
2837
3067
|
async function setNxEx(key, value, ttlSeconds) {
|
|
2838
|
-
const result = await exec(["SET", key,
|
|
3068
|
+
const result = await exec(["SET", key, stringifyValue(value), "EX", ttlSeconds, "NX"]);
|
|
2839
3069
|
return result === "OK";
|
|
2840
3070
|
}
|
|
2841
3071
|
async function sadd(key, member) {
|
|
@@ -3164,142 +3394,148 @@ ${issues}`
|
|
|
3164
3394
|
}
|
|
3165
3395
|
|
|
3166
3396
|
// src/builder.ts
|
|
3167
|
-
var RouteBuilder = class {
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
_inputExample = void 0;
|
|
3202
|
-
/** @internal */
|
|
3203
|
-
_hasInputExample = false;
|
|
3204
|
-
/** @internal */
|
|
3205
|
-
_outputExample = void 0;
|
|
3206
|
-
/** @internal */
|
|
3207
|
-
_hasOutputExample = false;
|
|
3208
|
-
/** @internal */
|
|
3209
|
-
_description;
|
|
3210
|
-
/** @internal */
|
|
3211
|
-
_path;
|
|
3212
|
-
/** @internal */
|
|
3213
|
-
_method = "POST";
|
|
3214
|
-
/** @internal */
|
|
3215
|
-
_apiKeyResolver;
|
|
3216
|
-
/** @internal */
|
|
3217
|
-
_providerName;
|
|
3218
|
-
/** @internal */
|
|
3219
|
-
_providerConfig;
|
|
3220
|
-
/** @internal */
|
|
3221
|
-
_validateFn;
|
|
3222
|
-
/** @internal */
|
|
3223
|
-
_settlement;
|
|
3224
|
-
/** @internal */
|
|
3225
|
-
_mppInfo;
|
|
3226
|
-
constructor(key, registry, deps) {
|
|
3227
|
-
this._key = key;
|
|
3228
|
-
this._registry = registry;
|
|
3229
|
-
this._deps = deps;
|
|
3397
|
+
var RouteBuilder = class _RouteBuilder {
|
|
3398
|
+
#s;
|
|
3399
|
+
constructor(key, registry, deps, defaults) {
|
|
3400
|
+
this.#s = {
|
|
3401
|
+
key,
|
|
3402
|
+
registry,
|
|
3403
|
+
deps,
|
|
3404
|
+
authMode: null,
|
|
3405
|
+
pricing: void 0,
|
|
3406
|
+
siwxEnabled: false,
|
|
3407
|
+
protocols: defaults?.protocols ? [...defaults.protocols] : ["x402"],
|
|
3408
|
+
maxPrice: void 0,
|
|
3409
|
+
minPrice: void 0,
|
|
3410
|
+
billing: "exact",
|
|
3411
|
+
tickCost: void 0,
|
|
3412
|
+
unitType: void 0,
|
|
3413
|
+
payTo: void 0,
|
|
3414
|
+
bodySchema: void 0,
|
|
3415
|
+
querySchema: void 0,
|
|
3416
|
+
outputSchema: void 0,
|
|
3417
|
+
inputExample: void 0,
|
|
3418
|
+
hasInputExample: false,
|
|
3419
|
+
outputExample: void 0,
|
|
3420
|
+
hasOutputExample: false,
|
|
3421
|
+
description: void 0,
|
|
3422
|
+
path: void 0,
|
|
3423
|
+
method: "POST",
|
|
3424
|
+
apiKeyResolver: void 0,
|
|
3425
|
+
providerName: void 0,
|
|
3426
|
+
providerConfig: void 0,
|
|
3427
|
+
validateFn: void 0,
|
|
3428
|
+
settlement: void 0,
|
|
3429
|
+
mppInfo: void 0
|
|
3430
|
+
};
|
|
3230
3431
|
}
|
|
3231
3432
|
fork() {
|
|
3232
|
-
const next =
|
|
3233
|
-
|
|
3234
|
-
|
|
3433
|
+
const next = new _RouteBuilder(
|
|
3434
|
+
this.#s.key,
|
|
3435
|
+
this.#s.registry,
|
|
3436
|
+
this.#s.deps
|
|
3437
|
+
);
|
|
3438
|
+
next.#s = { ...this.#s, protocols: [...this.#s.protocols] };
|
|
3235
3439
|
return next;
|
|
3236
3440
|
}
|
|
3237
|
-
paid(
|
|
3238
|
-
|
|
3239
|
-
|
|
3441
|
+
paid(arg, options) {
|
|
3442
|
+
return this.applyPaid(normalizePaidArg(this.#s.key, arg, options), "paid");
|
|
3443
|
+
}
|
|
3444
|
+
/**
|
|
3445
|
+
* x402-only handler-computed billing. The handler receives `charge(amount)`
|
|
3446
|
+
* and the request settles once for the accumulated total, capped at
|
|
3447
|
+
* `maxPrice`. Requires an `'upto'` accept on at least one configured network.
|
|
3448
|
+
* Pass a bare string as sugar for `{ maxPrice }`.
|
|
3449
|
+
*
|
|
3450
|
+
* @example
|
|
3451
|
+
* ```ts
|
|
3452
|
+
* router.route('llm')
|
|
3453
|
+
* .upTo('0.05')
|
|
3454
|
+
* .body(schema)
|
|
3455
|
+
* .handler(async ({ body, charge }) => { await charge('0.001'); ... });
|
|
3456
|
+
* ```
|
|
3457
|
+
*/
|
|
3458
|
+
upTo(arg) {
|
|
3459
|
+
return this.applyPaid(normalizeUpToArg(this.#s.key, arg), "upTo");
|
|
3460
|
+
}
|
|
3461
|
+
/**
|
|
3462
|
+
* MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
|
|
3463
|
+
* `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
|
|
3464
|
+
* `maxPrice`. Requires `RouterConfig.mpp.session`.
|
|
3465
|
+
*
|
|
3466
|
+
* @example
|
|
3467
|
+
* ```ts
|
|
3468
|
+
* router.route('llm/stream')
|
|
3469
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3470
|
+
* .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
|
|
3471
|
+
* ```
|
|
3472
|
+
*/
|
|
3473
|
+
metered(options) {
|
|
3474
|
+
return this.applyPaid(normalizeMeteredArg(this.#s.key, options), "metered");
|
|
3475
|
+
}
|
|
3476
|
+
applyPaid(normalized, method) {
|
|
3477
|
+
const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
|
|
3478
|
+
if (this.#s.authMode === "unprotected") {
|
|
3240
3479
|
throw new Error(
|
|
3241
|
-
`route '${this.
|
|
3480
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .${method}() on the same route.`
|
|
3242
3481
|
);
|
|
3243
3482
|
}
|
|
3244
|
-
if (this.
|
|
3483
|
+
if (this.#s.pricing !== void 0) {
|
|
3245
3484
|
throw new Error(
|
|
3246
|
-
`route '${this.
|
|
3485
|
+
`route '${this.#s.key}': Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
|
|
3247
3486
|
);
|
|
3248
3487
|
}
|
|
3249
3488
|
const next = this.fork();
|
|
3250
|
-
next.
|
|
3251
|
-
next.
|
|
3252
|
-
if (
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
if (
|
|
3260
|
-
|
|
3261
|
-
if (resolvedOptions?.dynamic) next._dynamicPrice = true;
|
|
3262
|
-
if (resolvedOptions?.tickCost) next._tickCost = resolvedOptions.tickCost;
|
|
3263
|
-
if (resolvedOptions?.unitType) next._unitType = resolvedOptions.unitType;
|
|
3264
|
-
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3265
|
-
if (next._dynamicPrice) {
|
|
3489
|
+
next.#s.authMode = "paid";
|
|
3490
|
+
next.#s.pricing = pricing;
|
|
3491
|
+
if (billing === "upto") {
|
|
3492
|
+
if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
|
|
3493
|
+
throw new Error(
|
|
3494
|
+
`route '${this.#s.key}': .upTo() is x402-only \u2014 remove the conflicting protocols override.`
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
next.#s.protocols = ["x402"];
|
|
3498
|
+
} else if (billing === "metered") {
|
|
3499
|
+
if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
|
|
3266
3500
|
throw new Error(
|
|
3267
|
-
`route '${this.
|
|
3501
|
+
`route '${this.#s.key}': .metered() is MPP-only \u2014 remove the conflicting protocols override.`
|
|
3268
3502
|
);
|
|
3269
3503
|
}
|
|
3504
|
+
next.#s.protocols = ["mpp"];
|
|
3505
|
+
} else if (resolvedOptions.protocols) {
|
|
3506
|
+
next.#s.protocols = [...resolvedOptions.protocols];
|
|
3507
|
+
} else if (next.#s.protocols.length === 0) {
|
|
3508
|
+
next.#s.protocols = ["x402"];
|
|
3509
|
+
}
|
|
3510
|
+
if (resolvedOptions.maxPrice) next.#s.maxPrice = resolvedOptions.maxPrice;
|
|
3511
|
+
if (maxPrice) next.#s.maxPrice = maxPrice;
|
|
3512
|
+
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3513
|
+
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3514
|
+
if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
|
|
3515
|
+
next.#s.billing = billing;
|
|
3516
|
+
if (tickCost) next.#s.tickCost = tickCost;
|
|
3517
|
+
if (unitType) next.#s.unitType = unitType;
|
|
3518
|
+
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3270
3519
|
for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
|
|
3271
3520
|
if (!tierKey) {
|
|
3272
|
-
throw new Error(`route '${this.
|
|
3521
|
+
throw new Error(`route '${this.#s.key}': tier key cannot be empty`);
|
|
3273
3522
|
}
|
|
3274
|
-
|
|
3275
|
-
if (isNaN(tierPrice) || tierPrice <= 0) {
|
|
3523
|
+
if (!isPositiveDecimal(tierConfig.price)) {
|
|
3276
3524
|
throw new Error(
|
|
3277
|
-
`route '${this.
|
|
3525
|
+
`route '${this.#s.key}': tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
|
|
3278
3526
|
);
|
|
3279
3527
|
}
|
|
3280
3528
|
}
|
|
3281
3529
|
}
|
|
3282
|
-
if (
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
`route '${this._key}': maxPrice '${resolvedOptions.maxPrice}' must be a positive decimal string`
|
|
3287
|
-
);
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
if (resolvedOptions?.tickCost !== void 0) {
|
|
3291
|
-
const parsed = parseFloat(resolvedOptions.tickCost);
|
|
3292
|
-
if (isNaN(parsed) || parsed <= 0) {
|
|
3293
|
-
throw new Error(
|
|
3294
|
-
`route '${this._key}': tickCost '${resolvedOptions.tickCost}' must be a positive decimal string`
|
|
3295
|
-
);
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
if (next._dynamicPrice && !next._maxPrice) {
|
|
3299
|
-
throw new Error(`route '${this._key}': .paid({ dynamic: true }) requires maxPrice`);
|
|
3530
|
+
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3531
|
+
throw new Error(
|
|
3532
|
+
`route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3533
|
+
);
|
|
3300
3534
|
}
|
|
3301
|
-
if (next.
|
|
3302
|
-
throw new Error(
|
|
3535
|
+
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3536
|
+
throw new Error(
|
|
3537
|
+
`route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
3538
|
+
);
|
|
3303
3539
|
}
|
|
3304
3540
|
return next;
|
|
3305
3541
|
}
|
|
@@ -3314,25 +3550,25 @@ var RouteBuilder = class {
|
|
|
3314
3550
|
* ```
|
|
3315
3551
|
*/
|
|
3316
3552
|
siwx() {
|
|
3317
|
-
if (this.
|
|
3553
|
+
if (this.#s.authMode === "unprotected") {
|
|
3318
3554
|
throw new Error(
|
|
3319
|
-
`route '${this.
|
|
3555
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .siwx() on the same route.`
|
|
3320
3556
|
);
|
|
3321
3557
|
}
|
|
3322
|
-
if (this.
|
|
3558
|
+
if (this.#s.apiKeyResolver) {
|
|
3323
3559
|
throw new Error(
|
|
3324
|
-
`route '${this.
|
|
3560
|
+
`route '${this.#s.key}': Combining .siwx() and .apiKey() is not supported on the same route.`
|
|
3325
3561
|
);
|
|
3326
3562
|
}
|
|
3327
3563
|
const next = this.fork();
|
|
3328
|
-
next.
|
|
3329
|
-
if (next.
|
|
3330
|
-
next.
|
|
3331
|
-
if (next.
|
|
3564
|
+
next.#s.siwxEnabled = true;
|
|
3565
|
+
if (next.#s.authMode === "paid" || next.#s.pricing) {
|
|
3566
|
+
next.#s.authMode = "paid";
|
|
3567
|
+
if (next.#s.protocols.length === 0) next.#s.protocols = ["x402"];
|
|
3332
3568
|
return next;
|
|
3333
3569
|
}
|
|
3334
|
-
next.
|
|
3335
|
-
next.
|
|
3570
|
+
next.#s.authMode = "siwx";
|
|
3571
|
+
next.#s.protocols = [];
|
|
3336
3572
|
return next;
|
|
3337
3573
|
}
|
|
3338
3574
|
/**
|
|
@@ -3349,14 +3585,14 @@ var RouteBuilder = class {
|
|
|
3349
3585
|
* ```
|
|
3350
3586
|
*/
|
|
3351
3587
|
apiKey(resolver) {
|
|
3352
|
-
if (this.
|
|
3588
|
+
if (this.#s.siwxEnabled) {
|
|
3353
3589
|
throw new Error(
|
|
3354
|
-
`route '${this.
|
|
3590
|
+
`route '${this.#s.key}': Combining .apiKey() and .siwx() is not supported on the same route.`
|
|
3355
3591
|
);
|
|
3356
3592
|
}
|
|
3357
3593
|
const next = this.fork();
|
|
3358
|
-
next.
|
|
3359
|
-
next.
|
|
3594
|
+
next.#s.authMode = "apiKey";
|
|
3595
|
+
next.#s.apiKeyResolver = resolver;
|
|
3360
3596
|
return next;
|
|
3361
3597
|
}
|
|
3362
3598
|
/**
|
|
@@ -3369,19 +3605,19 @@ var RouteBuilder = class {
|
|
|
3369
3605
|
* ```
|
|
3370
3606
|
*/
|
|
3371
3607
|
unprotected() {
|
|
3372
|
-
if (this.
|
|
3608
|
+
if (this.#s.authMode && this.#s.authMode !== "unprotected") {
|
|
3373
3609
|
throw new Error(
|
|
3374
|
-
`route '${this.
|
|
3610
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .${this.#s.authMode}() on the same route.`
|
|
3375
3611
|
);
|
|
3376
3612
|
}
|
|
3377
|
-
if (this.
|
|
3613
|
+
if (this.#s.pricing) {
|
|
3378
3614
|
throw new Error(
|
|
3379
|
-
`route '${this.
|
|
3615
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .paid() on the same route.`
|
|
3380
3616
|
);
|
|
3381
3617
|
}
|
|
3382
3618
|
const next = this.fork();
|
|
3383
|
-
next.
|
|
3384
|
-
next.
|
|
3619
|
+
next.#s.authMode = "unprotected";
|
|
3620
|
+
next.#s.protocols = [];
|
|
3385
3621
|
return next;
|
|
3386
3622
|
}
|
|
3387
3623
|
/**
|
|
@@ -3400,8 +3636,8 @@ var RouteBuilder = class {
|
|
|
3400
3636
|
*/
|
|
3401
3637
|
provider(name, config) {
|
|
3402
3638
|
const next = this.fork();
|
|
3403
|
-
next.
|
|
3404
|
-
next.
|
|
3639
|
+
next.#s.providerName = name;
|
|
3640
|
+
next.#s.providerConfig = config ?? {};
|
|
3405
3641
|
return next;
|
|
3406
3642
|
}
|
|
3407
3643
|
/**
|
|
@@ -3416,7 +3652,7 @@ var RouteBuilder = class {
|
|
|
3416
3652
|
*/
|
|
3417
3653
|
body(schema) {
|
|
3418
3654
|
const next = this.fork();
|
|
3419
|
-
next.
|
|
3655
|
+
next.#s.bodySchema = schema;
|
|
3420
3656
|
return next;
|
|
3421
3657
|
}
|
|
3422
3658
|
/**
|
|
@@ -3432,8 +3668,8 @@ var RouteBuilder = class {
|
|
|
3432
3668
|
*/
|
|
3433
3669
|
query(schema) {
|
|
3434
3670
|
const next = this.fork();
|
|
3435
|
-
next.
|
|
3436
|
-
next.
|
|
3671
|
+
next.#s.querySchema = schema;
|
|
3672
|
+
next.#s.method = "GET";
|
|
3437
3673
|
return next;
|
|
3438
3674
|
}
|
|
3439
3675
|
/**
|
|
@@ -3450,7 +3686,7 @@ var RouteBuilder = class {
|
|
|
3450
3686
|
*/
|
|
3451
3687
|
output(schema) {
|
|
3452
3688
|
const next = this.fork();
|
|
3453
|
-
next.
|
|
3689
|
+
next.#s.outputSchema = schema;
|
|
3454
3690
|
return next;
|
|
3455
3691
|
}
|
|
3456
3692
|
/**
|
|
@@ -3464,8 +3700,8 @@ var RouteBuilder = class {
|
|
|
3464
3700
|
*/
|
|
3465
3701
|
inputExample(example) {
|
|
3466
3702
|
const next = this.fork();
|
|
3467
|
-
next.
|
|
3468
|
-
next.
|
|
3703
|
+
next.#s.inputExample = example;
|
|
3704
|
+
next.#s.hasInputExample = true;
|
|
3469
3705
|
return next;
|
|
3470
3706
|
}
|
|
3471
3707
|
/**
|
|
@@ -3479,8 +3715,8 @@ var RouteBuilder = class {
|
|
|
3479
3715
|
*/
|
|
3480
3716
|
outputExample(example) {
|
|
3481
3717
|
const next = this.fork();
|
|
3482
|
-
next.
|
|
3483
|
-
next.
|
|
3718
|
+
next.#s.outputExample = example;
|
|
3719
|
+
next.#s.hasOutputExample = true;
|
|
3484
3720
|
return next;
|
|
3485
3721
|
}
|
|
3486
3722
|
/**
|
|
@@ -3494,7 +3730,7 @@ var RouteBuilder = class {
|
|
|
3494
3730
|
*/
|
|
3495
3731
|
description(text) {
|
|
3496
3732
|
const next = this.fork();
|
|
3497
|
-
next.
|
|
3733
|
+
next.#s.description = text;
|
|
3498
3734
|
return next;
|
|
3499
3735
|
}
|
|
3500
3736
|
/**
|
|
@@ -3508,7 +3744,7 @@ var RouteBuilder = class {
|
|
|
3508
3744
|
*/
|
|
3509
3745
|
path(p) {
|
|
3510
3746
|
const next = this.fork();
|
|
3511
|
-
next.
|
|
3747
|
+
next.#s.path = p;
|
|
3512
3748
|
return next;
|
|
3513
3749
|
}
|
|
3514
3750
|
/**
|
|
@@ -3522,7 +3758,7 @@ var RouteBuilder = class {
|
|
|
3522
3758
|
*/
|
|
3523
3759
|
method(m) {
|
|
3524
3760
|
const next = this.fork();
|
|
3525
|
-
next.
|
|
3761
|
+
next.#s.method = m;
|
|
3526
3762
|
return next;
|
|
3527
3763
|
}
|
|
3528
3764
|
/**
|
|
@@ -3541,7 +3777,7 @@ var RouteBuilder = class {
|
|
|
3541
3777
|
*/
|
|
3542
3778
|
validate(fn) {
|
|
3543
3779
|
const next = this.fork();
|
|
3544
|
-
next.
|
|
3780
|
+
next.#s.validateFn = fn;
|
|
3545
3781
|
return next;
|
|
3546
3782
|
}
|
|
3547
3783
|
/**
|
|
@@ -3559,7 +3795,7 @@ var RouteBuilder = class {
|
|
|
3559
3795
|
*/
|
|
3560
3796
|
settlement(lifecycle) {
|
|
3561
3797
|
const next = this.fork();
|
|
3562
|
-
next.
|
|
3798
|
+
next.#s.settlement = lifecycle;
|
|
3563
3799
|
return next;
|
|
3564
3800
|
}
|
|
3565
3801
|
/**
|
|
@@ -3582,13 +3818,13 @@ var RouteBuilder = class {
|
|
|
3582
3818
|
/**
|
|
3583
3819
|
* Register a streaming handler (`async function*`) and return the Next.js
|
|
3584
3820
|
* route function. Each `charge()` call bills one tick (`tickCost` USDC) up
|
|
3585
|
-
* to `maxPrice`; requires `.
|
|
3821
|
+
* to `maxPrice`; requires `.metered({ ... })` and MPP session mode.
|
|
3586
3822
|
*
|
|
3587
3823
|
* @example
|
|
3588
3824
|
* ```ts
|
|
3589
3825
|
* export const POST = router
|
|
3590
3826
|
* .route('llm/stream')
|
|
3591
|
-
* .
|
|
3827
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3592
3828
|
* .body(schema)
|
|
3593
3829
|
* .stream(async function* ({ body, charge }) {
|
|
3594
3830
|
* for await (const token of streamLLM(body.prompt)) {
|
|
@@ -3602,91 +3838,144 @@ var RouteBuilder = class {
|
|
|
3602
3838
|
return this.register(fn, true);
|
|
3603
3839
|
}
|
|
3604
3840
|
register(handlerFn, streaming) {
|
|
3605
|
-
if (!this.
|
|
3841
|
+
if (!this.#s.authMode) {
|
|
3606
3842
|
throw new Error(
|
|
3607
|
-
`route '${this.
|
|
3843
|
+
`route '${this.#s.key}': Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
3608
3844
|
);
|
|
3609
3845
|
}
|
|
3610
|
-
if (this.
|
|
3846
|
+
if (this.#s.validateFn && !this.#s.bodySchema) {
|
|
3611
3847
|
throw new Error(
|
|
3612
|
-
`route '${this.
|
|
3848
|
+
`route '${this.#s.key}': .validate() requires .body() \u2014 validation runs on parsed body`
|
|
3613
3849
|
);
|
|
3614
3850
|
}
|
|
3615
|
-
if (this.
|
|
3616
|
-
throw new Error(`route '${this.
|
|
3851
|
+
if (this.#s.settlement && !this.#s.pricing) {
|
|
3852
|
+
throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
|
|
3617
3853
|
}
|
|
3618
|
-
if (this.
|
|
3619
|
-
const hasUpto = this.
|
|
3854
|
+
if (this.#s.billing === "upto") {
|
|
3855
|
+
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
3620
3856
|
if (!hasUpto) {
|
|
3621
3857
|
throw new Error(
|
|
3622
|
-
`route '${this.
|
|
3858
|
+
`route '${this.#s.key}': .upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
|
|
3859
|
+
);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
if (this.#s.pricing !== void 0 && this.#s.billing === "exact" && this.#s.protocols.includes("x402")) {
|
|
3863
|
+
const hasExact = this.#s.deps.x402Accepts.some(
|
|
3864
|
+
(accept) => (accept.scheme ?? "exact") !== "upto"
|
|
3865
|
+
);
|
|
3866
|
+
if (!hasExact) {
|
|
3867
|
+
throw new Error(
|
|
3868
|
+
`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.`
|
|
3623
3869
|
);
|
|
3624
3870
|
}
|
|
3625
3871
|
}
|
|
3626
|
-
if (this.
|
|
3627
|
-
if (!this.
|
|
3872
|
+
if (this.#s.billing === "metered") {
|
|
3873
|
+
if (!this.#s.deps.mppSessionConfig) {
|
|
3628
3874
|
throw new Error(
|
|
3629
|
-
`route '${this.
|
|
3875
|
+
`route '${this.#s.key}': .metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
|
|
3630
3876
|
);
|
|
3631
3877
|
}
|
|
3632
3878
|
}
|
|
3633
|
-
if (streaming &&
|
|
3879
|
+
if (streaming && this.#s.billing !== "metered") {
|
|
3634
3880
|
throw new Error(
|
|
3635
|
-
`route '${this.
|
|
3881
|
+
`route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
3636
3882
|
);
|
|
3637
3883
|
}
|
|
3638
3884
|
validateExamples(
|
|
3639
|
-
this.
|
|
3640
|
-
this.
|
|
3641
|
-
this.
|
|
3642
|
-
this.
|
|
3643
|
-
this.
|
|
3644
|
-
this.
|
|
3645
|
-
this.
|
|
3646
|
-
this.
|
|
3885
|
+
this.#s.key,
|
|
3886
|
+
this.#s.bodySchema,
|
|
3887
|
+
this.#s.querySchema,
|
|
3888
|
+
this.#s.outputSchema,
|
|
3889
|
+
this.#s.inputExample,
|
|
3890
|
+
this.#s.hasInputExample,
|
|
3891
|
+
this.#s.outputExample,
|
|
3892
|
+
this.#s.hasOutputExample
|
|
3647
3893
|
);
|
|
3648
3894
|
const entry = {
|
|
3649
|
-
key: this.
|
|
3650
|
-
authMode: this.
|
|
3651
|
-
siwxEnabled: this.
|
|
3652
|
-
pricing: this.
|
|
3653
|
-
|
|
3895
|
+
key: this.#s.key,
|
|
3896
|
+
authMode: this.#s.authMode,
|
|
3897
|
+
siwxEnabled: this.#s.siwxEnabled,
|
|
3898
|
+
pricing: this.#s.pricing,
|
|
3899
|
+
billing: this.#s.billing,
|
|
3654
3900
|
streaming: streaming ? true : void 0,
|
|
3655
|
-
protocols: this.
|
|
3656
|
-
bodySchema: this.
|
|
3657
|
-
querySchema: this.
|
|
3658
|
-
outputSchema: this.
|
|
3659
|
-
inputExample: this.
|
|
3660
|
-
outputExample: this.
|
|
3661
|
-
description: this.
|
|
3662
|
-
path: this.
|
|
3663
|
-
method: this.
|
|
3664
|
-
maxPrice: this.
|
|
3665
|
-
minPrice: this.
|
|
3666
|
-
payTo: this.
|
|
3667
|
-
apiKeyResolver: this.
|
|
3668
|
-
providerName: this.
|
|
3669
|
-
providerConfig: this.
|
|
3670
|
-
validateFn: this.
|
|
3671
|
-
settlement: this.
|
|
3672
|
-
mppInfo: this.
|
|
3673
|
-
tickCost: this.
|
|
3674
|
-
unitType: this.
|
|
3901
|
+
protocols: this.#s.protocols,
|
|
3902
|
+
bodySchema: this.#s.bodySchema,
|
|
3903
|
+
querySchema: this.#s.querySchema,
|
|
3904
|
+
outputSchema: this.#s.outputSchema,
|
|
3905
|
+
inputExample: this.#s.hasInputExample ? this.#s.inputExample : void 0,
|
|
3906
|
+
outputExample: this.#s.hasOutputExample ? this.#s.outputExample : void 0,
|
|
3907
|
+
description: this.#s.description,
|
|
3908
|
+
path: this.#s.path,
|
|
3909
|
+
method: this.#s.method,
|
|
3910
|
+
maxPrice: this.#s.maxPrice,
|
|
3911
|
+
minPrice: this.#s.minPrice,
|
|
3912
|
+
payTo: this.#s.payTo,
|
|
3913
|
+
apiKeyResolver: this.#s.apiKeyResolver,
|
|
3914
|
+
providerName: this.#s.providerName,
|
|
3915
|
+
providerConfig: this.#s.providerConfig,
|
|
3916
|
+
validateFn: this.#s.validateFn,
|
|
3917
|
+
settlement: this.#s.settlement,
|
|
3918
|
+
mppInfo: this.#s.mppInfo,
|
|
3919
|
+
tickCost: this.#s.tickCost,
|
|
3920
|
+
unitType: this.#s.unitType
|
|
3675
3921
|
};
|
|
3676
|
-
this.
|
|
3677
|
-
return createRequestHandler(entry, handlerFn, this.
|
|
3922
|
+
this.#s.registry.register(entry);
|
|
3923
|
+
return createRequestHandler(entry, handlerFn, this.#s.deps);
|
|
3678
3924
|
}
|
|
3679
3925
|
};
|
|
3680
|
-
function
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3926
|
+
function normalizePaidArg(routeKey, arg, options) {
|
|
3927
|
+
if (typeof arg === "string") {
|
|
3928
|
+
return { pricing: arg, resolvedOptions: options ?? {}, billing: "exact" };
|
|
3929
|
+
}
|
|
3930
|
+
if (typeof arg === "function") {
|
|
3931
|
+
return {
|
|
3932
|
+
pricing: arg,
|
|
3933
|
+
resolvedOptions: options ?? {},
|
|
3934
|
+
billing: "exact"
|
|
3935
|
+
};
|
|
3936
|
+
}
|
|
3937
|
+
if ("tiers" in arg && "field" in arg) {
|
|
3938
|
+
return {
|
|
3939
|
+
pricing: { field: arg.field, tiers: arg.tiers, default: arg.default },
|
|
3940
|
+
resolvedOptions: arg,
|
|
3941
|
+
billing: "exact"
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
if ("price" in arg && typeof arg.price === "string") {
|
|
3945
|
+
return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
|
|
3946
|
+
}
|
|
3947
|
+
throw new Error(
|
|
3948
|
+
`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().`
|
|
3949
|
+
);
|
|
3950
|
+
}
|
|
3951
|
+
function normalizeUpToArg(routeKey, arg) {
|
|
3952
|
+
const options = typeof arg === "string" ? { maxPrice: arg } : arg;
|
|
3953
|
+
if (!options.maxPrice) {
|
|
3954
|
+
throw new Error(`route '${routeKey}': .upTo() requires maxPrice`);
|
|
3955
|
+
}
|
|
3956
|
+
return {
|
|
3957
|
+
pricing: options.maxPrice,
|
|
3958
|
+
resolvedOptions: options,
|
|
3959
|
+
billing: "upto",
|
|
3960
|
+
unitType: options.unitType,
|
|
3961
|
+
maxPrice: options.maxPrice
|
|
3962
|
+
};
|
|
3963
|
+
}
|
|
3964
|
+
function normalizeMeteredArg(routeKey, options) {
|
|
3965
|
+
if (!options.maxPrice) {
|
|
3966
|
+
throw new Error(`route '${routeKey}': .metered() requires maxPrice`);
|
|
3967
|
+
}
|
|
3968
|
+
if (!options.tickCost) {
|
|
3969
|
+
throw new Error(`route '${routeKey}': .metered() requires tickCost`);
|
|
3688
3970
|
}
|
|
3689
|
-
return {
|
|
3971
|
+
return {
|
|
3972
|
+
pricing: options.maxPrice,
|
|
3973
|
+
resolvedOptions: options,
|
|
3974
|
+
billing: "metered",
|
|
3975
|
+
tickCost: options.tickCost,
|
|
3976
|
+
unitType: options.unitType,
|
|
3977
|
+
maxPrice: options.maxPrice
|
|
3978
|
+
};
|
|
3690
3979
|
}
|
|
3691
3980
|
|
|
3692
3981
|
// src/discovery/well-known.ts
|
|
@@ -3918,17 +4207,16 @@ function buildPricingInfo(entry) {
|
|
|
3918
4207
|
};
|
|
3919
4208
|
}
|
|
3920
4209
|
if ("tiers" in entry.pricing) {
|
|
3921
|
-
const tierPrices = Object.values(entry.pricing.tiers).map((tier) =>
|
|
3922
|
-
const
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
if (min === max) {
|
|
4210
|
+
const tierPrices = Object.values(entry.pricing.tiers).map((tier) => tier.price);
|
|
4211
|
+
const extrema = tierExtrema(tierPrices);
|
|
4212
|
+
if (extrema) {
|
|
4213
|
+
if (extrema.min === extrema.max) {
|
|
3926
4214
|
return {
|
|
3927
|
-
price: { mode: "fixed", currency: "USD", amount:
|
|
4215
|
+
price: { mode: "fixed", currency: "USD", amount: extrema.min }
|
|
3928
4216
|
};
|
|
3929
4217
|
}
|
|
3930
4218
|
return {
|
|
3931
|
-
price: { mode: "dynamic", currency: "USD", min:
|
|
4219
|
+
price: { mode: "dynamic", currency: "USD", min: extrema.min, max: extrema.max }
|
|
3932
4220
|
};
|
|
3933
4221
|
}
|
|
3934
4222
|
return {
|
|
@@ -3942,6 +4230,20 @@ function buildPricingInfo(entry) {
|
|
|
3942
4230
|
}
|
|
3943
4231
|
return void 0;
|
|
3944
4232
|
}
|
|
4233
|
+
function tierExtrema(prices) {
|
|
4234
|
+
if (prices.length === 0) return null;
|
|
4235
|
+
let min = prices[0];
|
|
4236
|
+
let max = prices[0];
|
|
4237
|
+
try {
|
|
4238
|
+
for (const price of prices.slice(1)) {
|
|
4239
|
+
if (compareDecimals(price, min) < 0) min = price;
|
|
4240
|
+
if (compareDecimals(price, max) > 0) max = price;
|
|
4241
|
+
}
|
|
4242
|
+
} catch {
|
|
4243
|
+
return null;
|
|
4244
|
+
}
|
|
4245
|
+
return { min, max };
|
|
4246
|
+
}
|
|
3945
4247
|
|
|
3946
4248
|
// src/discovery/llms-txt.ts
|
|
3947
4249
|
var import_server10 = require("next/server");
|
|
@@ -4428,11 +4730,11 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4428
4730
|
}
|
|
4429
4731
|
|
|
4430
4732
|
// src/init/x402.ts
|
|
4431
|
-
async function initX402(config, configError) {
|
|
4733
|
+
async function initX402(config, kvStore, configError) {
|
|
4432
4734
|
if (configError) return { initError: configError };
|
|
4433
4735
|
try {
|
|
4434
4736
|
const { createX402Server: createX402Server2 } = await Promise.resolve().then(() => (init_x402_server(), x402_server_exports));
|
|
4435
|
-
const result = await createX402Server2(config);
|
|
4737
|
+
const result = await createX402Server2(config, kvStore);
|
|
4436
4738
|
await result.initPromise;
|
|
4437
4739
|
return {
|
|
4438
4740
|
server: result.server,
|
|
@@ -4612,10 +4914,10 @@ function createRouter(config) {
|
|
|
4612
4914
|
x402Accepts,
|
|
4613
4915
|
mppx: null,
|
|
4614
4916
|
tempoClient: null,
|
|
4615
|
-
mppSessionConfig: config.mpp?.session ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4917
|
+
mppSessionConfig: config.mpp?.session && config.mpp.operatorKey ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4616
4918
|
};
|
|
4617
4919
|
deps.initPromise = (async () => {
|
|
4618
|
-
const x402Result = await initX402(config, x402ConfigError);
|
|
4920
|
+
const x402Result = await initX402(config, kvStore, x402ConfigError);
|
|
4619
4921
|
deps.x402Server = x402Result.server ?? null;
|
|
4620
4922
|
deps.x402FacilitatorsByNetwork = x402Result.facilitatorsByNetwork;
|
|
4621
4923
|
if (x402Result.initError) deps.x402InitError = x402Result.initError;
|
|
@@ -4644,11 +4946,10 @@ function createRouter(config) {
|
|
|
4644
4946
|
`[router] strictRoutes=true forbids key/path divergence for route '${definition.path}'. Remove custom \`key\` or make it equal to \`path\`.`
|
|
4645
4947
|
);
|
|
4646
4948
|
}
|
|
4647
|
-
let builder = new RouteBuilder(key, registry, deps
|
|
4949
|
+
let builder = new RouteBuilder(key, registry, deps, {
|
|
4950
|
+
protocols: config.protocols
|
|
4951
|
+
});
|
|
4648
4952
|
builder = builder.path(normalizedPath);
|
|
4649
|
-
if (config.protocols) {
|
|
4650
|
-
builder._protocols = [...config.protocols];
|
|
4651
|
-
}
|
|
4652
4953
|
if (definition.method) {
|
|
4653
4954
|
builder = builder.method(definition.method);
|
|
4654
4955
|
}
|