@agentcash/router 1.7.1 → 1.9.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/AGENTS.md +17 -11
- package/README.md +63 -16
- package/dist/index.cjs +425 -214
- package/dist/index.d.cts +108 -87
- package/dist/index.d.ts +108 -87
- package/dist/index.js +423 -212
- package/package.json +7 -4
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) => ({
|
|
@@ -340,9 +343,18 @@ function withScopedKinds(client, kinds) {
|
|
|
340
343
|
return {
|
|
341
344
|
verify: client.verify.bind(client),
|
|
342
345
|
settle: client.settle.bind(client),
|
|
343
|
-
getSupported: async () =>
|
|
346
|
+
getSupported: async () => {
|
|
347
|
+
const live = await client.getSupported();
|
|
348
|
+
return { ...live, kinds: mergeKindExtras(kinds, live.kinds) };
|
|
349
|
+
}
|
|
344
350
|
};
|
|
345
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
|
+
}
|
|
346
358
|
function buildSupportedKinds(group) {
|
|
347
359
|
return group.networks.flatMap((network) => {
|
|
348
360
|
if (group.family === "solana") {
|
|
@@ -551,7 +563,8 @@ function preflight(routeEntry, handler, deps, request) {
|
|
|
551
563
|
request,
|
|
552
564
|
meta,
|
|
553
565
|
pluginCtx,
|
|
554
|
-
report: createReporter(deps.plugin, pluginCtx, routeEntry.key)
|
|
566
|
+
report: createReporter(deps.plugin, pluginCtx, routeEntry.key),
|
|
567
|
+
query: void 0
|
|
555
568
|
};
|
|
556
569
|
}
|
|
557
570
|
function buildMeta(request, routeEntry) {
|
|
@@ -574,13 +587,19 @@ function buildMeta(request, routeEntry) {
|
|
|
574
587
|
import { NextResponse } from "next/server";
|
|
575
588
|
|
|
576
589
|
// src/pipeline/body.ts
|
|
590
|
+
var MalformedJsonError = class extends Error {
|
|
591
|
+
constructor() {
|
|
592
|
+
super("Invalid JSON");
|
|
593
|
+
this.name = "MalformedJsonError";
|
|
594
|
+
}
|
|
595
|
+
};
|
|
577
596
|
async function bufferBody(request) {
|
|
578
597
|
const text = await request.text();
|
|
579
|
-
if (!text) return void 0;
|
|
598
|
+
if (!text.trim()) return void 0;
|
|
580
599
|
try {
|
|
581
600
|
return JSON.parse(text);
|
|
582
601
|
} catch {
|
|
583
|
-
|
|
602
|
+
throw new MalformedJsonError();
|
|
584
603
|
}
|
|
585
604
|
}
|
|
586
605
|
function validateBody(parsed, schema) {
|
|
@@ -661,7 +680,18 @@ function computeQuotaLevel(remaining, warn, critical) {
|
|
|
661
680
|
// src/pipeline/steps/parse-body.ts
|
|
662
681
|
async function parseBody(ctx, request = ctx.request) {
|
|
663
682
|
if (!ctx.routeEntry.bodySchema) return { ok: true, data: void 0 };
|
|
664
|
-
|
|
683
|
+
let raw;
|
|
684
|
+
try {
|
|
685
|
+
raw = await bufferBody(request);
|
|
686
|
+
} catch (err) {
|
|
687
|
+
if (!(err instanceof MalformedJsonError)) throw err;
|
|
688
|
+
const response2 = NextResponse.json(
|
|
689
|
+
{ success: false, error: "Invalid JSON", issues: [] },
|
|
690
|
+
{ status: 400 }
|
|
691
|
+
);
|
|
692
|
+
firePluginResponse(ctx, response2);
|
|
693
|
+
return { ok: false, response: response2 };
|
|
694
|
+
}
|
|
665
695
|
const result = validateBody(raw, ctx.routeEntry.bodySchema);
|
|
666
696
|
if (result.success) return { ok: true, data: result.data };
|
|
667
697
|
const response = NextResponse.json(
|
|
@@ -672,6 +702,22 @@ async function parseBody(ctx, request = ctx.request) {
|
|
|
672
702
|
return { ok: false, response };
|
|
673
703
|
}
|
|
674
704
|
|
|
705
|
+
// src/pipeline/steps/parse-query.ts
|
|
706
|
+
import { NextResponse as NextResponse2 } from "next/server";
|
|
707
|
+
function validateQuery(ctx) {
|
|
708
|
+
const { querySchema } = ctx.routeEntry;
|
|
709
|
+
if (!querySchema) return { ok: true, data: void 0 };
|
|
710
|
+
const params = Object.fromEntries(ctx.request.nextUrl.searchParams.entries());
|
|
711
|
+
const result = validateBody(params, querySchema);
|
|
712
|
+
if (result.success) return { ok: true, data: result.data };
|
|
713
|
+
const response = NextResponse2.json(
|
|
714
|
+
{ success: false, error: result.error, issues: result.issues },
|
|
715
|
+
{ status: 400 }
|
|
716
|
+
);
|
|
717
|
+
firePluginResponse(ctx, response);
|
|
718
|
+
return { ok: false, response };
|
|
719
|
+
}
|
|
720
|
+
|
|
675
721
|
// src/pipeline/steps/errors.ts
|
|
676
722
|
function errorStatus(error, fallback) {
|
|
677
723
|
const status = error?.status;
|
|
@@ -686,9 +732,9 @@ function handlerFailureError(response) {
|
|
|
686
732
|
}
|
|
687
733
|
|
|
688
734
|
// src/pipeline/steps/fail.ts
|
|
689
|
-
import { NextResponse as
|
|
735
|
+
import { NextResponse as NextResponse3 } from "next/server";
|
|
690
736
|
function fail(ctx, status, message, requestBody) {
|
|
691
|
-
const response =
|
|
737
|
+
const response = NextResponse3.json({ success: false, error: message }, { status });
|
|
692
738
|
firePluginResponse(ctx, response, requestBody);
|
|
693
739
|
return response;
|
|
694
740
|
}
|
|
@@ -705,7 +751,7 @@ async function runValidate(ctx, body) {
|
|
|
705
751
|
}
|
|
706
752
|
|
|
707
753
|
// src/pipeline/flows/static/static-invoke.ts
|
|
708
|
-
import { NextResponse as
|
|
754
|
+
import { NextResponse as NextResponse4 } from "next/server";
|
|
709
755
|
|
|
710
756
|
// src/types.ts
|
|
711
757
|
var HttpError = class extends Error {
|
|
@@ -716,14 +762,6 @@ var HttpError = class extends Error {
|
|
|
716
762
|
}
|
|
717
763
|
};
|
|
718
764
|
|
|
719
|
-
// src/pipeline/steps/parse-query.ts
|
|
720
|
-
function parseQuery(request, routeEntry) {
|
|
721
|
-
if (!routeEntry.querySchema) return void 0;
|
|
722
|
-
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
723
|
-
const result = routeEntry.querySchema.safeParse(params);
|
|
724
|
-
return result.success ? result.data : params;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
765
|
// src/pipeline/flows/static/static-invoke.ts
|
|
728
766
|
function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
729
767
|
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
|
|
@@ -734,7 +772,7 @@ function invokeUnauthed(ctx, wallet, account, body) {
|
|
|
734
772
|
function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
735
773
|
return {
|
|
736
774
|
body,
|
|
737
|
-
query:
|
|
775
|
+
query: ctx.query,
|
|
738
776
|
request: ctx.request,
|
|
739
777
|
requestId: ctx.meta.requestId,
|
|
740
778
|
route: ctx.routeEntry.key,
|
|
@@ -754,10 +792,7 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
754
792
|
}
|
|
755
793
|
if (isAsyncIterable(returned) && !isThenable(returned)) {
|
|
756
794
|
return errorResult(
|
|
757
|
-
new HttpError(
|
|
758
|
-
`route '${ctx.routeEntry.key}': streaming handlers require .paid({ dynamic: true })`,
|
|
759
|
-
500
|
|
760
|
-
)
|
|
795
|
+
new HttpError(`route '${ctx.routeEntry.key}': streaming handlers require .metered()`, 500)
|
|
761
796
|
);
|
|
762
797
|
}
|
|
763
798
|
let rawResult;
|
|
@@ -766,14 +801,14 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
766
801
|
} catch (error) {
|
|
767
802
|
return errorResult(error);
|
|
768
803
|
}
|
|
769
|
-
const response = rawResult instanceof Response ? rawResult :
|
|
804
|
+
const response = rawResult instanceof Response ? rawResult : NextResponse4.json(rawResult);
|
|
770
805
|
return { response, rawResult };
|
|
771
806
|
}
|
|
772
807
|
function errorResult(error) {
|
|
773
808
|
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
774
809
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
775
810
|
return {
|
|
776
|
-
response:
|
|
811
|
+
response: NextResponse4.json({ success: false, error: message }, { status }),
|
|
777
812
|
rawResult: void 0,
|
|
778
813
|
handlerError: error
|
|
779
814
|
};
|
|
@@ -1186,10 +1221,12 @@ var DynamicPricing = class {
|
|
|
1186
1221
|
}
|
|
1187
1222
|
needsBody = true;
|
|
1188
1223
|
async quote(body) {
|
|
1224
|
+
let priced;
|
|
1189
1225
|
try {
|
|
1190
1226
|
const raw = await this.opts.fn(body);
|
|
1191
|
-
|
|
1227
|
+
priced = this.cap(raw, body);
|
|
1192
1228
|
} catch (err) {
|
|
1229
|
+
if (err instanceof HttpError) throw err;
|
|
1193
1230
|
this.alert("error", `Pricing function failed: ${msg(err)}`, {
|
|
1194
1231
|
error: err instanceof Error ? err.stack : String(err),
|
|
1195
1232
|
body
|
|
@@ -1200,6 +1237,13 @@ var DynamicPricing = class {
|
|
|
1200
1237
|
}
|
|
1201
1238
|
throw err;
|
|
1202
1239
|
}
|
|
1240
|
+
if (!isPositiveDecimal(priced)) {
|
|
1241
|
+
throw new HttpError(
|
|
1242
|
+
`route '${this.opts.route ?? "unknown"}': dynamic pricing returned an invalid amount '${priced}'`,
|
|
1243
|
+
500
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
return priced;
|
|
1203
1247
|
}
|
|
1204
1248
|
challengeQuote(body) {
|
|
1205
1249
|
if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
|
|
@@ -1274,14 +1318,14 @@ var TieredPricing = class {
|
|
|
1274
1318
|
`Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
|
|
1275
1319
|
);
|
|
1276
1320
|
}
|
|
1277
|
-
challengeQuote(body) {
|
|
1321
|
+
async challengeQuote(body) {
|
|
1278
1322
|
if (body !== void 0) {
|
|
1279
1323
|
try {
|
|
1280
|
-
return this.quote(body);
|
|
1324
|
+
return await this.quote(body);
|
|
1281
1325
|
} catch {
|
|
1282
1326
|
}
|
|
1283
1327
|
}
|
|
1284
|
-
return
|
|
1328
|
+
return this.maxTierPrice();
|
|
1285
1329
|
}
|
|
1286
1330
|
describe() {
|
|
1287
1331
|
return {
|
|
@@ -1712,7 +1756,7 @@ var mppStrategy = {
|
|
|
1712
1756
|
async verify(args) {
|
|
1713
1757
|
const info = readMppCredential(args.request);
|
|
1714
1758
|
if (!info) return { ok: false, kind: "invalid" };
|
|
1715
|
-
if (args.routeEntry.
|
|
1759
|
+
if (args.routeEntry.billing === "metered") {
|
|
1716
1760
|
if (!info.sessionAction) return { ok: false, kind: "invalid" };
|
|
1717
1761
|
return verifySessionMode(args, info);
|
|
1718
1762
|
}
|
|
@@ -1763,7 +1807,7 @@ var mppStrategy = {
|
|
|
1763
1807
|
async buildChallenge(args) {
|
|
1764
1808
|
if (!args.deps.mppx) return {};
|
|
1765
1809
|
const sessionsConfigured = args.deps.mppSessionConfig && (args.deps.mppx.sessionRequest || args.deps.mppx.sessionStream);
|
|
1766
|
-
if (args.routeEntry.
|
|
1810
|
+
if (args.routeEntry.billing === "metered" && sessionsConfigured) {
|
|
1767
1811
|
const tickCost = args.routeEntry.tickCost;
|
|
1768
1812
|
const computedDeposit = tickCost !== void 0 ? multiplyDecimal(tickCost, args.deps.mppSessionConfig.depositMultiplier) : void 0;
|
|
1769
1813
|
const suggestedDeposit = args.routeEntry.maxPrice ?? computedDeposit ?? args.price;
|
|
@@ -2015,7 +2059,7 @@ async function settleX402Payment(server, payload, requirements, amountOverride)
|
|
|
2015
2059
|
};
|
|
2016
2060
|
}
|
|
2017
2061
|
function tagBareDecimalAsDollars(amount) {
|
|
2018
|
-
if (/^\d
|
|
2062
|
+
if (/^\d+(?:\.\d+)?$/.test(amount)) return `$${amount}`;
|
|
2019
2063
|
return amount;
|
|
2020
2064
|
}
|
|
2021
2065
|
|
|
@@ -2140,7 +2184,7 @@ async function verifyX402(args) {
|
|
|
2140
2184
|
const accepts = await resolveX402Accepts(
|
|
2141
2185
|
request,
|
|
2142
2186
|
routeEntry,
|
|
2143
|
-
deps.x402Accepts,
|
|
2187
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2144
2188
|
deps.payeeAddress,
|
|
2145
2189
|
body
|
|
2146
2190
|
);
|
|
@@ -2188,7 +2232,7 @@ async function verifyX402(args) {
|
|
|
2188
2232
|
async function settleX402(args) {
|
|
2189
2233
|
const { response, payment, token, deps, routeEntry, billedAmount, report } = args;
|
|
2190
2234
|
const { payload, requirements } = token;
|
|
2191
|
-
const override = routeEntry.
|
|
2235
|
+
const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
|
|
2192
2236
|
try {
|
|
2193
2237
|
const settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2194
2238
|
if (!settle.result?.success) {
|
|
@@ -2218,7 +2262,7 @@ async function buildX402ChallengeContribution(args) {
|
|
|
2218
2262
|
const accepts = await resolveX402Accepts(
|
|
2219
2263
|
request,
|
|
2220
2264
|
routeEntry,
|
|
2221
|
-
deps.x402Accepts,
|
|
2265
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2222
2266
|
deps.payeeAddress,
|
|
2223
2267
|
body
|
|
2224
2268
|
);
|
|
@@ -2262,8 +2306,8 @@ function getAllowedStrategies(allowed) {
|
|
|
2262
2306
|
return allowed.map((name) => STRATEGIES[name]);
|
|
2263
2307
|
}
|
|
2264
2308
|
|
|
2265
|
-
// src/pipeline/flows/
|
|
2266
|
-
import { NextResponse as
|
|
2309
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2310
|
+
import { NextResponse as NextResponse5 } from "next/server";
|
|
2267
2311
|
|
|
2268
2312
|
// src/pipeline/challenge-extensions.ts
|
|
2269
2313
|
init_evm();
|
|
@@ -2279,20 +2323,17 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2279
2323
|
});
|
|
2280
2324
|
const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
|
|
2281
2325
|
const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
}
|
|
2291
|
-
if (outputSchema && routeEntry.outputExample !== void 0) {
|
|
2292
|
-
config.output = { schema: outputSchema, example: routeEntry.outputExample };
|
|
2293
|
-
}
|
|
2294
|
-
extensions = declareDiscoveryExtension(config);
|
|
2326
|
+
const isBodyMethod = routeEntry.method === "POST" || routeEntry.method === "PUT" || routeEntry.method === "PATCH";
|
|
2327
|
+
const config = { method: routeEntry.method };
|
|
2328
|
+
if (isBodyMethod) config.bodyType = "json";
|
|
2329
|
+
if (inputSchema) config.inputSchema = inputSchema;
|
|
2330
|
+
if (routeEntry.inputExample !== void 0) {
|
|
2331
|
+
config.input = routeEntry.inputExample;
|
|
2332
|
+
}
|
|
2333
|
+
if (outputSchema && routeEntry.outputExample !== void 0) {
|
|
2334
|
+
config.output = { schema: outputSchema, example: routeEntry.outputExample };
|
|
2295
2335
|
}
|
|
2336
|
+
extensions = declareDiscoveryExtension(config);
|
|
2296
2337
|
} catch (err) {
|
|
2297
2338
|
ctx.report(
|
|
2298
2339
|
"warn",
|
|
@@ -2311,9 +2352,7 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2311
2352
|
} catch {
|
|
2312
2353
|
}
|
|
2313
2354
|
}
|
|
2314
|
-
const hasEvmUpto = ctx.deps.x402Accepts.some(
|
|
2315
|
-
(accept) => accept.scheme === "upto" && isEvmNetwork(accept.network)
|
|
2316
|
-
);
|
|
2355
|
+
const hasEvmUpto = ctx.routeEntry.billing === "upto" && ctx.deps.x402Accepts.some((accept) => accept.scheme === "upto" && isEvmNetwork(accept.network));
|
|
2317
2356
|
if (hasEvmUpto) {
|
|
2318
2357
|
try {
|
|
2319
2358
|
const { declareEip2612GasSponsoringExtension } = await import("@x402/extensions");
|
|
@@ -2331,14 +2370,14 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2331
2370
|
return extensions;
|
|
2332
2371
|
}
|
|
2333
2372
|
|
|
2334
|
-
// src/pipeline/flows/
|
|
2335
|
-
async function
|
|
2373
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2374
|
+
async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
2336
2375
|
let challengePrice;
|
|
2337
2376
|
try {
|
|
2338
2377
|
challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
|
|
2339
2378
|
} catch (err) {
|
|
2340
2379
|
const message = errorMessage(err, "Price calculation failed");
|
|
2341
|
-
const errorResponse =
|
|
2380
|
+
const errorResponse = NextResponse5.json(
|
|
2342
2381
|
{ success: false, error: message },
|
|
2343
2382
|
{ status: errorStatus(err, 500) }
|
|
2344
2383
|
);
|
|
@@ -2347,7 +2386,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2347
2386
|
}
|
|
2348
2387
|
const extensions = await buildChallengeExtensions(ctx);
|
|
2349
2388
|
const responseBody = failure ? JSON.stringify({ error: failure.message ?? null, reason: failure.reason }) : null;
|
|
2350
|
-
const response = new
|
|
2389
|
+
const response = new NextResponse5(responseBody, {
|
|
2351
2390
|
status: 402,
|
|
2352
2391
|
headers: {
|
|
2353
2392
|
"Content-Type": "application/json",
|
|
@@ -2374,7 +2413,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2374
2413
|
const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
|
|
2375
2414
|
ctx.report("critical", message);
|
|
2376
2415
|
if (strategy.protocol === "x402") {
|
|
2377
|
-
const errorResponse =
|
|
2416
|
+
const errorResponse = NextResponse5.json(
|
|
2378
2417
|
{ success: false, error: message },
|
|
2379
2418
|
{ status: 500 }
|
|
2380
2419
|
);
|
|
@@ -2426,7 +2465,7 @@ function surrogatePriceForSkippedBody(routeEntry) {
|
|
|
2426
2465
|
}
|
|
2427
2466
|
|
|
2428
2467
|
// src/pipeline/flows/dynamic/dynamic-channel-mgmt.ts
|
|
2429
|
-
import { NextResponse as
|
|
2468
|
+
import { NextResponse as NextResponse6 } from "next/server";
|
|
2430
2469
|
async function runDynamicChannelMgmtFlow(args) {
|
|
2431
2470
|
const { ctx, strategy, account, pricing, skipBody } = args;
|
|
2432
2471
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -2445,7 +2484,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2445
2484
|
if (verifyOutcome.kind === "config") {
|
|
2446
2485
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2447
2486
|
}
|
|
2448
|
-
return
|
|
2487
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2449
2488
|
}
|
|
2450
2489
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2451
2490
|
firePaymentVerified(ctx, {
|
|
@@ -2454,7 +2493,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2454
2493
|
amount: price,
|
|
2455
2494
|
network: verifyOutcome.payment.network
|
|
2456
2495
|
});
|
|
2457
|
-
const synthetic = new
|
|
2496
|
+
const synthetic = new NextResponse6(null, { status: 200 });
|
|
2458
2497
|
const settleScope = {
|
|
2459
2498
|
wallet: verifyOutcome.wallet,
|
|
2460
2499
|
account,
|
|
@@ -2480,10 +2519,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2480
2519
|
});
|
|
2481
2520
|
}
|
|
2482
2521
|
|
|
2483
|
-
// src/
|
|
2484
|
-
import { NextResponse as NextResponse6 } from "next/server";
|
|
2485
|
-
|
|
2486
|
-
// src/pricing/charge-context.ts
|
|
2522
|
+
// src/pricing/metered-charge.ts
|
|
2487
2523
|
function createChargeContext(args) {
|
|
2488
2524
|
const { tickCost, maxPrice, route } = args;
|
|
2489
2525
|
const tickAtomic = decimalToAtomic(tickCost);
|
|
@@ -2518,17 +2554,12 @@ function createChargeContext(args) {
|
|
|
2518
2554
|
};
|
|
2519
2555
|
}
|
|
2520
2556
|
|
|
2521
|
-
// src/pipeline/flows/dynamic/dynamic-invoke.ts
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
tickCost: ctx.routeEntry.tickCost,
|
|
2526
|
-
maxPrice: ctx.routeEntry.maxPrice,
|
|
2527
|
-
route: ctx.routeEntry.key
|
|
2528
|
-
}) : null;
|
|
2529
|
-
const baseHandlerCtx = {
|
|
2557
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2558
|
+
import { NextResponse as NextResponse7 } from "next/server";
|
|
2559
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2560
|
+
return {
|
|
2530
2561
|
body,
|
|
2531
|
-
query:
|
|
2562
|
+
query: ctx.query,
|
|
2532
2563
|
request: ctx.request,
|
|
2533
2564
|
requestId: ctx.meta.requestId,
|
|
2534
2565
|
route: ctx.routeEntry.key,
|
|
@@ -2538,12 +2569,41 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2538
2569
|
alert: ctx.report,
|
|
2539
2570
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2540
2571
|
};
|
|
2572
|
+
}
|
|
2573
|
+
function toResponse(rawResult) {
|
|
2574
|
+
return rawResult instanceof Response ? rawResult : NextResponse7.json(rawResult);
|
|
2575
|
+
}
|
|
2576
|
+
function errorResult2(error) {
|
|
2577
|
+
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
2578
|
+
const message = error instanceof Error ? error.message : "Internal error";
|
|
2579
|
+
return {
|
|
2580
|
+
kind: "request",
|
|
2581
|
+
response: NextResponse7.json({ success: false, error: message }, { status }),
|
|
2582
|
+
rawResult: void 0,
|
|
2583
|
+
handlerError: error
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
function isAsyncIterable2(value) {
|
|
2587
|
+
return value != null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
2588
|
+
}
|
|
2589
|
+
function isThenable2(value) {
|
|
2590
|
+
return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2594
|
+
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2595
|
+
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2596
|
+
tickCost: ctx.routeEntry.tickCost,
|
|
2597
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2598
|
+
route: ctx.routeEntry.key
|
|
2599
|
+
}) : null;
|
|
2600
|
+
const baseHandlerCtx = buildBaseHandlerCtx(ctx, wallet, account, body, payment);
|
|
2541
2601
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2542
2602
|
let returned;
|
|
2543
2603
|
try {
|
|
2544
2604
|
returned = ctx.handler(handlerCtx);
|
|
2545
2605
|
} catch (error) {
|
|
2546
|
-
return errorResult2(error
|
|
2606
|
+
return errorResult2(error);
|
|
2547
2607
|
}
|
|
2548
2608
|
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2549
2609
|
if (!chargeContext) {
|
|
@@ -2551,41 +2611,80 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2551
2611
|
new HttpError(
|
|
2552
2612
|
"route returned an async iterable from a non-streaming handler \u2014 use .stream(async function*(...)) instead of .handler() to opt into streaming",
|
|
2553
2613
|
500
|
|
2554
|
-
)
|
|
2555
|
-
null
|
|
2614
|
+
)
|
|
2556
2615
|
);
|
|
2557
2616
|
}
|
|
2558
|
-
return {
|
|
2559
|
-
kind: "stream",
|
|
2560
|
-
source: returned,
|
|
2561
|
-
chargeContext
|
|
2562
|
-
};
|
|
2617
|
+
return { kind: "stream", source: returned, chargeContext };
|
|
2563
2618
|
}
|
|
2564
2619
|
let rawResult;
|
|
2565
2620
|
try {
|
|
2566
2621
|
rawResult = await returned;
|
|
2567
2622
|
} catch (error) {
|
|
2568
|
-
return errorResult2(error
|
|
2623
|
+
return errorResult2(error);
|
|
2569
2624
|
}
|
|
2570
|
-
|
|
2571
|
-
return { kind: "request", response, rawResult };
|
|
2625
|
+
return { kind: "request", response: toResponse(rawResult), rawResult };
|
|
2572
2626
|
}
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2627
|
+
|
|
2628
|
+
// src/pricing/upto-charge.ts
|
|
2629
|
+
function createUptoChargeContext(args) {
|
|
2630
|
+
const { maxPrice, route } = args;
|
|
2631
|
+
const capAtomic = decimalToAtomic(maxPrice);
|
|
2632
|
+
if (capAtomic <= 0n) {
|
|
2633
|
+
throw new Error(`route '${route}': maxPrice '${maxPrice}' must be a positive decimal string`);
|
|
2634
|
+
}
|
|
2635
|
+
let calls = 0;
|
|
2636
|
+
let atomic = 0n;
|
|
2637
|
+
const charge = async (amount) => {
|
|
2638
|
+
const nextAtomic = atomic + decimalToAtomic(amount);
|
|
2639
|
+
if (nextAtomic > capAtomic) {
|
|
2640
|
+
throw Object.assign(
|
|
2641
|
+
new Error(
|
|
2642
|
+
`route '${route}': charge() running total ($${atomicToDecimal(nextAtomic)}) exceeds maxPrice ($${atomicToDecimal(capAtomic)})`
|
|
2643
|
+
),
|
|
2644
|
+
{ status: 400, code: "CHARGE_OVER_CAP" }
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
calls += 1;
|
|
2648
|
+
atomic = nextAtomic;
|
|
2649
|
+
};
|
|
2577
2650
|
return {
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
handlerError: error
|
|
2651
|
+
charge,
|
|
2652
|
+
callCount: () => calls,
|
|
2653
|
+
atomicTotal: () => atomic
|
|
2582
2654
|
};
|
|
2583
2655
|
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2656
|
+
|
|
2657
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2658
|
+
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2659
|
+
const uptoCtx = createUptoChargeContext({
|
|
2660
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2661
|
+
route: ctx.routeEntry.key
|
|
2662
|
+
});
|
|
2663
|
+
const handlerCtx = {
|
|
2664
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2665
|
+
charge: uptoCtx.charge
|
|
2666
|
+
};
|
|
2667
|
+
let returned;
|
|
2668
|
+
try {
|
|
2669
|
+
returned = ctx.handler(handlerCtx);
|
|
2670
|
+
} catch (error) {
|
|
2671
|
+
return errorResult2(error);
|
|
2672
|
+
}
|
|
2673
|
+
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2674
|
+
return errorResult2(
|
|
2675
|
+
new HttpError(
|
|
2676
|
+
"streaming is not supported on .upTo() routes \u2014 return a value from .handler() instead",
|
|
2677
|
+
500
|
|
2678
|
+
)
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
let rawResult;
|
|
2682
|
+
try {
|
|
2683
|
+
rawResult = await returned;
|
|
2684
|
+
} catch (error) {
|
|
2685
|
+
return errorResult2(error);
|
|
2686
|
+
}
|
|
2687
|
+
return { kind: "request", response: toResponse(rawResult), rawResult, uptoContext: uptoCtx };
|
|
2589
2688
|
}
|
|
2590
2689
|
|
|
2591
2690
|
// src/pipeline/flows/dynamic/dynamic-preflight.ts
|
|
@@ -2615,7 +2714,7 @@ async function runDynamicRequestFlow(args) {
|
|
|
2615
2714
|
}
|
|
2616
2715
|
const beforeErr = await runBeforeSettle(ctx, settleScope);
|
|
2617
2716
|
if (beforeErr) return beforeErr;
|
|
2618
|
-
const billedAmount = routeEntry
|
|
2717
|
+
const billedAmount = computeBilledAmount(routeEntry, result);
|
|
2619
2718
|
return settleAndFinalizeRequest({
|
|
2620
2719
|
ctx,
|
|
2621
2720
|
strategy,
|
|
@@ -2633,6 +2732,19 @@ async function runDynamicRequestFlow(args) {
|
|
|
2633
2732
|
}
|
|
2634
2733
|
});
|
|
2635
2734
|
}
|
|
2735
|
+
function computeBilledAmount(routeEntry, result) {
|
|
2736
|
+
if (routeEntry.billing === "upto") {
|
|
2737
|
+
const total = result.uptoContext?.atomicTotal() ?? 0n;
|
|
2738
|
+
if (total <= 0n) {
|
|
2739
|
+
throw new HttpError(
|
|
2740
|
+
`route '${routeEntry.key}': handler did not call charge(amount) \u2014 upto routes must accumulate a non-zero billed amount`,
|
|
2741
|
+
500
|
|
2742
|
+
);
|
|
2743
|
+
}
|
|
2744
|
+
return atomicToDecimal(total);
|
|
2745
|
+
}
|
|
2746
|
+
return routeEntry.tickCost;
|
|
2747
|
+
}
|
|
2636
2748
|
|
|
2637
2749
|
// src/pipeline/flows/dynamic/dynamic-stream.ts
|
|
2638
2750
|
async function runDynamicStreamFlow(args) {
|
|
@@ -2669,7 +2781,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2669
2781
|
if (!incomingStrategy) {
|
|
2670
2782
|
const initError = protocolInitError(routeEntry, deps);
|
|
2671
2783
|
if (initError) return fail(ctx, 500, initError);
|
|
2672
|
-
return
|
|
2784
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2673
2785
|
}
|
|
2674
2786
|
const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
|
|
2675
2787
|
if (skipHandler) {
|
|
@@ -2696,7 +2808,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2696
2808
|
if (verifyOutcome.kind === "config") {
|
|
2697
2809
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2698
2810
|
}
|
|
2699
|
-
return
|
|
2811
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2700
2812
|
}
|
|
2701
2813
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2702
2814
|
firePaymentVerified(ctx, {
|
|
@@ -2705,13 +2817,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2705
2817
|
amount: price,
|
|
2706
2818
|
network: verifyOutcome.payment.network
|
|
2707
2819
|
});
|
|
2708
|
-
const result = await invokeDynamic(
|
|
2709
|
-
ctx,
|
|
2710
|
-
verifyOutcome.wallet,
|
|
2711
|
-
account,
|
|
2712
|
-
parsedBody,
|
|
2713
|
-
verifyOutcome.payment
|
|
2714
|
-
);
|
|
2820
|
+
const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
|
|
2715
2821
|
switch (result.kind) {
|
|
2716
2822
|
case "stream":
|
|
2717
2823
|
return runDynamicStreamFlow({
|
|
@@ -2733,6 +2839,18 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2733
2839
|
});
|
|
2734
2840
|
}
|
|
2735
2841
|
}
|
|
2842
|
+
async function invokeDynamic(ctx, verifyOutcome, account, parsedBody) {
|
|
2843
|
+
switch (ctx.routeEntry.billing) {
|
|
2844
|
+
case "upto":
|
|
2845
|
+
return invokeUpto(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2846
|
+
case "metered":
|
|
2847
|
+
return invokeMetered(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2848
|
+
case "exact":
|
|
2849
|
+
throw new Error(
|
|
2850
|
+
`route '${ctx.routeEntry.key}': exact billing must not reach the dynamic paid flow`
|
|
2851
|
+
);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2736
2854
|
|
|
2737
2855
|
// src/pipeline/flows/static/static-body-and-price.ts
|
|
2738
2856
|
async function resolveStaticBodyAndPrice(args) {
|
|
@@ -2834,7 +2952,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2834
2952
|
if (!incomingStrategy) {
|
|
2835
2953
|
const initError = protocolInitError(routeEntry, deps);
|
|
2836
2954
|
if (initError) return fail(ctx, 500, initError);
|
|
2837
|
-
return
|
|
2955
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2838
2956
|
}
|
|
2839
2957
|
const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
|
|
2840
2958
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
@@ -2851,7 +2969,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2851
2969
|
if (verifyOutcome.kind === "config") {
|
|
2852
2970
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2853
2971
|
}
|
|
2854
|
-
return
|
|
2972
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2855
2973
|
}
|
|
2856
2974
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2857
2975
|
firePaymentVerified(ctx, {
|
|
@@ -2880,17 +2998,12 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2880
2998
|
|
|
2881
2999
|
// src/pipeline/flows/paid.ts
|
|
2882
3000
|
async function runPaidFlow(ctx) {
|
|
2883
|
-
const
|
|
2884
|
-
|
|
2885
|
-
case true:
|
|
2886
|
-
return runDynamicPaidFlow(ctx);
|
|
2887
|
-
case false:
|
|
2888
|
-
return runStaticPaidFlow(ctx);
|
|
2889
|
-
}
|
|
3001
|
+
const handlerCharged = ctx.routeEntry.billing !== "exact";
|
|
3002
|
+
return handlerCharged ? runDynamicPaidFlow(ctx) : runStaticPaidFlow(ctx);
|
|
2890
3003
|
}
|
|
2891
3004
|
|
|
2892
3005
|
// src/pipeline/flows/siwx-only.ts
|
|
2893
|
-
import { NextResponse as
|
|
3006
|
+
import { NextResponse as NextResponse8 } from "next/server";
|
|
2894
3007
|
|
|
2895
3008
|
// src/kv-store/client.ts
|
|
2896
3009
|
var BIGINT_SUFFIX = "#__bigint";
|
|
@@ -3124,7 +3237,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3124
3237
|
}
|
|
3125
3238
|
const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
|
|
3126
3239
|
if (!siwx.valid) {
|
|
3127
|
-
const response =
|
|
3240
|
+
const response = NextResponse8.json(
|
|
3128
3241
|
{ error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
|
|
3129
3242
|
{ status: 402 }
|
|
3130
3243
|
);
|
|
@@ -3185,7 +3298,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3185
3298
|
`SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3186
3299
|
);
|
|
3187
3300
|
}
|
|
3188
|
-
const response = new
|
|
3301
|
+
const response = new NextResponse8(JSON.stringify(paymentRequired), {
|
|
3189
3302
|
status: 402,
|
|
3190
3303
|
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
3191
3304
|
});
|
|
@@ -3231,6 +3344,9 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
3231
3344
|
return async (request) => {
|
|
3232
3345
|
await deps.initPromise;
|
|
3233
3346
|
const ctx = preflight(routeEntry, handler, deps, request);
|
|
3347
|
+
const query = validateQuery(ctx);
|
|
3348
|
+
if (!query.ok) return query.response;
|
|
3349
|
+
ctx.query = query.data;
|
|
3234
3350
|
if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
|
|
3235
3351
|
if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
|
|
3236
3352
|
if (routeEntry.pricing) return runPaidFlow(ctx);
|
|
@@ -3284,7 +3400,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3284
3400
|
protocols: defaults?.protocols ? [...defaults.protocols] : ["x402"],
|
|
3285
3401
|
maxPrice: void 0,
|
|
3286
3402
|
minPrice: void 0,
|
|
3287
|
-
|
|
3403
|
+
billing: "exact",
|
|
3288
3404
|
tickCost: void 0,
|
|
3289
3405
|
unitType: void 0,
|
|
3290
3406
|
payTo: void 0,
|
|
@@ -3315,39 +3431,84 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3315
3431
|
next.#s = { ...this.#s, protocols: [...this.#s.protocols] };
|
|
3316
3432
|
return next;
|
|
3317
3433
|
}
|
|
3318
|
-
paid(
|
|
3319
|
-
|
|
3434
|
+
paid(arg, options) {
|
|
3435
|
+
return this.applyPaid(normalizePaidArg(this.#s.key, arg, options), "paid");
|
|
3436
|
+
}
|
|
3437
|
+
/**
|
|
3438
|
+
* x402-only handler-computed billing. The handler receives `charge(amount)`
|
|
3439
|
+
* and the request settles once for the accumulated total, capped at
|
|
3440
|
+
* `maxPrice`. Requires an `'upto'` accept on at least one configured network.
|
|
3441
|
+
* Pass a bare string as sugar for `{ maxPrice }`.
|
|
3442
|
+
*
|
|
3443
|
+
* @example
|
|
3444
|
+
* ```ts
|
|
3445
|
+
* router.route('llm')
|
|
3446
|
+
* .upTo('0.05')
|
|
3447
|
+
* .body(schema)
|
|
3448
|
+
* .handler(async ({ body, charge }) => { await charge('0.001'); ... });
|
|
3449
|
+
* ```
|
|
3450
|
+
*/
|
|
3451
|
+
upTo(arg) {
|
|
3452
|
+
return this.applyPaid(normalizeUpToArg(this.#s.key, arg), "upTo");
|
|
3453
|
+
}
|
|
3454
|
+
/**
|
|
3455
|
+
* MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
|
|
3456
|
+
* `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
|
|
3457
|
+
* `maxPrice`. Requires `RouterConfig.mpp.session`.
|
|
3458
|
+
*
|
|
3459
|
+
* @example
|
|
3460
|
+
* ```ts
|
|
3461
|
+
* router.route('llm/stream')
|
|
3462
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3463
|
+
* .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
|
|
3464
|
+
* ```
|
|
3465
|
+
*/
|
|
3466
|
+
metered(options) {
|
|
3467
|
+
return this.applyPaid(normalizeMeteredArg(this.#s.key, options), "metered");
|
|
3468
|
+
}
|
|
3469
|
+
applyPaid(normalized, method) {
|
|
3470
|
+
const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
|
|
3320
3471
|
if (this.#s.authMode === "unprotected") {
|
|
3321
3472
|
throw new Error(
|
|
3322
|
-
`route '${this.#s.key}': Cannot combine .unprotected() and
|
|
3473
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .${method}() on the same route.`
|
|
3323
3474
|
);
|
|
3324
3475
|
}
|
|
3325
3476
|
if (this.#s.pricing !== void 0) {
|
|
3326
3477
|
throw new Error(
|
|
3327
|
-
`route '${this.#s.key}': Cannot
|
|
3478
|
+
`route '${this.#s.key}': Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
|
|
3328
3479
|
);
|
|
3329
3480
|
}
|
|
3330
3481
|
const next = this.fork();
|
|
3331
3482
|
next.#s.authMode = "paid";
|
|
3332
3483
|
next.#s.pricing = pricing;
|
|
3333
|
-
if (
|
|
3484
|
+
if (billing === "upto") {
|
|
3485
|
+
if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
|
|
3486
|
+
throw new Error(
|
|
3487
|
+
`route '${this.#s.key}': .upTo() is x402-only \u2014 remove the conflicting protocols override.`
|
|
3488
|
+
);
|
|
3489
|
+
}
|
|
3490
|
+
next.#s.protocols = ["x402"];
|
|
3491
|
+
} else if (billing === "metered") {
|
|
3492
|
+
if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
|
|
3493
|
+
throw new Error(
|
|
3494
|
+
`route '${this.#s.key}': .metered() is MPP-only \u2014 remove the conflicting protocols override.`
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
next.#s.protocols = ["mpp"];
|
|
3498
|
+
} else if (resolvedOptions.protocols) {
|
|
3334
3499
|
next.#s.protocols = [...resolvedOptions.protocols];
|
|
3335
3500
|
} else if (next.#s.protocols.length === 0) {
|
|
3336
3501
|
next.#s.protocols = ["x402"];
|
|
3337
3502
|
}
|
|
3338
|
-
if (resolvedOptions
|
|
3339
|
-
if (
|
|
3340
|
-
if (resolvedOptions
|
|
3341
|
-
if (resolvedOptions
|
|
3342
|
-
if (resolvedOptions
|
|
3343
|
-
|
|
3344
|
-
if (
|
|
3503
|
+
if (resolvedOptions.maxPrice) next.#s.maxPrice = resolvedOptions.maxPrice;
|
|
3504
|
+
if (maxPrice) next.#s.maxPrice = maxPrice;
|
|
3505
|
+
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3506
|
+
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3507
|
+
if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
|
|
3508
|
+
next.#s.billing = billing;
|
|
3509
|
+
if (tickCost) next.#s.tickCost = tickCost;
|
|
3510
|
+
if (unitType) next.#s.unitType = unitType;
|
|
3345
3511
|
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3346
|
-
if (next.#s.dynamicPrice) {
|
|
3347
|
-
throw new Error(
|
|
3348
|
-
`route '${this.#s.key}': .paid({ dynamic: true }) is incompatible with tiered pricing`
|
|
3349
|
-
);
|
|
3350
|
-
}
|
|
3351
3512
|
for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
|
|
3352
3513
|
if (!tierKey) {
|
|
3353
3514
|
throw new Error(`route '${this.#s.key}': tier key cannot be empty`);
|
|
@@ -3359,21 +3520,25 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3359
3520
|
}
|
|
3360
3521
|
}
|
|
3361
3522
|
}
|
|
3362
|
-
if (
|
|
3523
|
+
if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
|
|
3363
3524
|
throw new Error(
|
|
3364
|
-
`route '${this.#s.key}':
|
|
3525
|
+
`route '${this.#s.key}': price '${pricing}' must be a positive decimal string`
|
|
3365
3526
|
);
|
|
3366
3527
|
}
|
|
3367
|
-
if (
|
|
3528
|
+
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3368
3529
|
throw new Error(
|
|
3369
|
-
`route '${this.#s.key}':
|
|
3530
|
+
`route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3370
3531
|
);
|
|
3371
3532
|
}
|
|
3372
|
-
if (next.#s.
|
|
3373
|
-
throw new Error(
|
|
3533
|
+
if (next.#s.minPrice !== void 0 && !isPositiveDecimal(next.#s.minPrice)) {
|
|
3534
|
+
throw new Error(
|
|
3535
|
+
`route '${this.#s.key}': minPrice '${next.#s.minPrice}' must be a positive decimal string`
|
|
3536
|
+
);
|
|
3374
3537
|
}
|
|
3375
|
-
if (next.#s.
|
|
3376
|
-
throw new Error(
|
|
3538
|
+
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3539
|
+
throw new Error(
|
|
3540
|
+
`route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
3541
|
+
);
|
|
3377
3542
|
}
|
|
3378
3543
|
return next;
|
|
3379
3544
|
}
|
|
@@ -3656,13 +3821,13 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3656
3821
|
/**
|
|
3657
3822
|
* Register a streaming handler (`async function*`) and return the Next.js
|
|
3658
3823
|
* route function. Each `charge()` call bills one tick (`tickCost` USDC) up
|
|
3659
|
-
* to `maxPrice`; requires `.
|
|
3824
|
+
* to `maxPrice`; requires `.metered({ ... })` and MPP session mode.
|
|
3660
3825
|
*
|
|
3661
3826
|
* @example
|
|
3662
3827
|
* ```ts
|
|
3663
3828
|
* export const POST = router
|
|
3664
3829
|
* .route('llm/stream')
|
|
3665
|
-
* .
|
|
3830
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3666
3831
|
* .body(schema)
|
|
3667
3832
|
* .stream(async function* ({ body, charge }) {
|
|
3668
3833
|
* for await (const token of streamLLM(body.prompt)) {
|
|
@@ -3678,7 +3843,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3678
3843
|
register(handlerFn, streaming) {
|
|
3679
3844
|
if (!this.#s.authMode) {
|
|
3680
3845
|
throw new Error(
|
|
3681
|
-
`route '${this.#s.key}': Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
3846
|
+
`route '${this.#s.key}': Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
3682
3847
|
);
|
|
3683
3848
|
}
|
|
3684
3849
|
if (this.#s.validateFn && !this.#s.bodySchema) {
|
|
@@ -3689,24 +3854,34 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3689
3854
|
if (this.#s.settlement && !this.#s.pricing) {
|
|
3690
3855
|
throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
|
|
3691
3856
|
}
|
|
3692
|
-
if (this.#s.
|
|
3857
|
+
if (this.#s.billing === "upto") {
|
|
3693
3858
|
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
3694
3859
|
if (!hasUpto) {
|
|
3695
3860
|
throw new Error(
|
|
3696
|
-
`route '${this.#s.key}': .
|
|
3861
|
+
`route '${this.#s.key}': .upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
|
|
3697
3862
|
);
|
|
3698
3863
|
}
|
|
3699
3864
|
}
|
|
3700
|
-
if (this.#s.
|
|
3865
|
+
if (this.#s.pricing !== void 0 && this.#s.billing === "exact" && this.#s.protocols.includes("x402")) {
|
|
3866
|
+
const hasExact = this.#s.deps.x402Accepts.some(
|
|
3867
|
+
(accept) => (accept.scheme ?? "exact") !== "upto"
|
|
3868
|
+
);
|
|
3869
|
+
if (!hasExact) {
|
|
3870
|
+
throw new Error(
|
|
3871
|
+
`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.`
|
|
3872
|
+
);
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
if (this.#s.billing === "metered") {
|
|
3701
3876
|
if (!this.#s.deps.mppSessionConfig) {
|
|
3702
3877
|
throw new Error(
|
|
3703
|
-
`route '${this.#s.key}': .
|
|
3878
|
+
`route '${this.#s.key}': .metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
|
|
3704
3879
|
);
|
|
3705
3880
|
}
|
|
3706
3881
|
}
|
|
3707
|
-
if (streaming &&
|
|
3882
|
+
if (streaming && this.#s.billing !== "metered") {
|
|
3708
3883
|
throw new Error(
|
|
3709
|
-
`route '${this.#s.key}': .stream() requires .
|
|
3884
|
+
`route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
3710
3885
|
);
|
|
3711
3886
|
}
|
|
3712
3887
|
validateExamples(
|
|
@@ -3724,7 +3899,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3724
3899
|
authMode: this.#s.authMode,
|
|
3725
3900
|
siwxEnabled: this.#s.siwxEnabled,
|
|
3726
3901
|
pricing: this.#s.pricing,
|
|
3727
|
-
|
|
3902
|
+
billing: this.#s.billing,
|
|
3728
3903
|
streaming: streaming ? true : void 0,
|
|
3729
3904
|
protocols: this.#s.protocols,
|
|
3730
3905
|
bodySchema: this.#s.bodySchema,
|
|
@@ -3751,20 +3926,63 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3751
3926
|
return createRequestHandler(entry, handlerFn, this.#s.deps);
|
|
3752
3927
|
}
|
|
3753
3928
|
};
|
|
3754
|
-
function
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
const opts = pricingOrOptions;
|
|
3758
|
-
if (!opts.maxPrice) {
|
|
3759
|
-
throw new Error(`route '${routeKey}': .paid({ dynamic: true }) requires maxPrice`);
|
|
3760
|
-
}
|
|
3761
|
-
return { pricing: opts.maxPrice, resolvedOptions: opts };
|
|
3929
|
+
function normalizePaidArg(routeKey, arg, options) {
|
|
3930
|
+
if (typeof arg === "string") {
|
|
3931
|
+
return { pricing: arg, resolvedOptions: options ?? {}, billing: "exact" };
|
|
3762
3932
|
}
|
|
3763
|
-
|
|
3933
|
+
if (typeof arg === "function") {
|
|
3934
|
+
return {
|
|
3935
|
+
pricing: arg,
|
|
3936
|
+
resolvedOptions: options ?? {},
|
|
3937
|
+
billing: "exact"
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
if ("tiers" in arg && "field" in arg) {
|
|
3941
|
+
return {
|
|
3942
|
+
pricing: { field: arg.field, tiers: arg.tiers, default: arg.default },
|
|
3943
|
+
resolvedOptions: arg,
|
|
3944
|
+
billing: "exact"
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3947
|
+
if ("price" in arg && typeof arg.price === "string") {
|
|
3948
|
+
return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
|
|
3949
|
+
}
|
|
3950
|
+
throw new Error(
|
|
3951
|
+
`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().`
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
function normalizeUpToArg(routeKey, arg) {
|
|
3955
|
+
const options = typeof arg === "string" ? { maxPrice: arg } : arg;
|
|
3956
|
+
if (!options.maxPrice) {
|
|
3957
|
+
throw new Error(`route '${routeKey}': .upTo() requires maxPrice`);
|
|
3958
|
+
}
|
|
3959
|
+
return {
|
|
3960
|
+
pricing: options.maxPrice,
|
|
3961
|
+
resolvedOptions: options,
|
|
3962
|
+
billing: "upto",
|
|
3963
|
+
unitType: options.unitType,
|
|
3964
|
+
maxPrice: options.maxPrice
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3967
|
+
function normalizeMeteredArg(routeKey, options) {
|
|
3968
|
+
if (!options.maxPrice) {
|
|
3969
|
+
throw new Error(`route '${routeKey}': .metered() requires maxPrice`);
|
|
3970
|
+
}
|
|
3971
|
+
if (!options.tickCost) {
|
|
3972
|
+
throw new Error(`route '${routeKey}': .metered() requires tickCost`);
|
|
3973
|
+
}
|
|
3974
|
+
return {
|
|
3975
|
+
pricing: options.maxPrice,
|
|
3976
|
+
resolvedOptions: options,
|
|
3977
|
+
billing: "metered",
|
|
3978
|
+
tickCost: options.tickCost,
|
|
3979
|
+
unitType: options.unitType,
|
|
3980
|
+
maxPrice: options.maxPrice
|
|
3981
|
+
};
|
|
3764
3982
|
}
|
|
3765
3983
|
|
|
3766
3984
|
// src/discovery/well-known.ts
|
|
3767
|
-
import { NextResponse as
|
|
3985
|
+
import { NextResponse as NextResponse9 } from "next/server";
|
|
3768
3986
|
|
|
3769
3987
|
// src/discovery/utils/guidance.ts
|
|
3770
3988
|
async function resolveGuidance(discovery) {
|
|
@@ -3808,7 +4026,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
3808
4026
|
if (instructions) {
|
|
3809
4027
|
body.instructions = instructions;
|
|
3810
4028
|
}
|
|
3811
|
-
return
|
|
4029
|
+
return NextResponse9.json(body, {
|
|
3812
4030
|
headers: {
|
|
3813
4031
|
"Access-Control-Allow-Origin": "*",
|
|
3814
4032
|
"Access-Control-Allow-Methods": "GET",
|
|
@@ -3826,13 +4044,13 @@ function toDiscoveryResource(method, url, mode) {
|
|
|
3826
4044
|
|
|
3827
4045
|
// src/discovery/openapi.ts
|
|
3828
4046
|
init_constants();
|
|
3829
|
-
import { NextResponse as
|
|
4047
|
+
import { NextResponse as NextResponse10 } from "next/server";
|
|
3830
4048
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
3831
4049
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
3832
4050
|
let cached = null;
|
|
3833
4051
|
let validated = false;
|
|
3834
4052
|
return async (_request) => {
|
|
3835
|
-
if (cached) return
|
|
4053
|
+
if (cached) return NextResponse10.json(cached);
|
|
3836
4054
|
if (!validated && pricesKeys) {
|
|
3837
4055
|
registry.validate(pricesKeys);
|
|
3838
4056
|
validated = true;
|
|
@@ -3895,7 +4113,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
3895
4113
|
paths
|
|
3896
4114
|
};
|
|
3897
4115
|
cached = createDocument(openApiDocument);
|
|
3898
|
-
return
|
|
4116
|
+
return NextResponse10.json(cached);
|
|
3899
4117
|
};
|
|
3900
4118
|
}
|
|
3901
4119
|
function deriveTag(routeKey) {
|
|
@@ -4031,11 +4249,11 @@ function tierExtrema(prices) {
|
|
|
4031
4249
|
}
|
|
4032
4250
|
|
|
4033
4251
|
// src/discovery/llms-txt.ts
|
|
4034
|
-
import { NextResponse as
|
|
4252
|
+
import { NextResponse as NextResponse11 } from "next/server";
|
|
4035
4253
|
function createLlmsTxtHandler(discovery) {
|
|
4036
4254
|
return async (_request) => {
|
|
4037
4255
|
const guidance = await resolveGuidance(discovery) ?? "";
|
|
4038
|
-
return new
|
|
4256
|
+
return new NextResponse11(guidance, {
|
|
4039
4257
|
headers: {
|
|
4040
4258
|
"Content-Type": "text/plain; charset=utf-8",
|
|
4041
4259
|
"Access-Control-Allow-Origin": "*"
|
|
@@ -4085,10 +4303,14 @@ var isPlaceholderEvm = (v) => ZERO_EVM_ADDRESS_RE.test(v);
|
|
|
4085
4303
|
var isSolanaAddress = (v) => SOLANA_ADDRESS_RE.test(v);
|
|
4086
4304
|
var isX402Network = (v) => v.startsWith("eip155:") || v.startsWith("solana:");
|
|
4087
4305
|
var canonicalizeEvm = (addr) => addr.toLowerCase();
|
|
4306
|
+
function evmAddressFromKey(key) {
|
|
4307
|
+
if (!key || !isEvmPrivateKey(key)) return null;
|
|
4308
|
+
return privateKeyToAccount(key).address.toLowerCase();
|
|
4309
|
+
}
|
|
4088
4310
|
function operatorAddressesCollide(opKey, fpKey) {
|
|
4089
|
-
|
|
4090
|
-
const
|
|
4091
|
-
|
|
4311
|
+
const op = evmAddressFromKey(opKey);
|
|
4312
|
+
const fp = evmAddressFromKey(fpKey);
|
|
4313
|
+
if (!op || !fp) return null;
|
|
4092
4314
|
return op === fp ? op : null;
|
|
4093
4315
|
}
|
|
4094
4316
|
function trimAll(raw) {
|
|
@@ -4250,7 +4472,7 @@ function getConfiguredX402Accepts2(config) {
|
|
|
4250
4472
|
}
|
|
4251
4473
|
];
|
|
4252
4474
|
}
|
|
4253
|
-
function validateX402Config(config, env
|
|
4475
|
+
function validateX402Config(config, env) {
|
|
4254
4476
|
const accepts = getConfiguredX402Accepts2(config);
|
|
4255
4477
|
const issues = [];
|
|
4256
4478
|
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
@@ -4292,23 +4514,21 @@ function validateX402Config(config, env, options) {
|
|
|
4292
4514
|
`x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4293
4515
|
);
|
|
4294
4516
|
}
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
);
|
|
4306
|
-
}
|
|
4517
|
+
const hasEvm = accepts.some(
|
|
4518
|
+
(a) => typeof a.network === "string" && a.network.startsWith("eip155:")
|
|
4519
|
+
);
|
|
4520
|
+
if (hasEvm) {
|
|
4521
|
+
const missing = ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"].filter((k) => !env[k]);
|
|
4522
|
+
if (missing.length > 0) {
|
|
4523
|
+
push(
|
|
4524
|
+
"missing_cdp_keys",
|
|
4525
|
+
`x402 EVM facilitator (Coinbase) requires ${missing.join(" and ")}. Create an API key at https://portal.cdp.coinbase.com and set it via env.`
|
|
4526
|
+
);
|
|
4307
4527
|
}
|
|
4308
4528
|
}
|
|
4309
4529
|
return issues;
|
|
4310
4530
|
}
|
|
4311
|
-
function validateMppConfig(config) {
|
|
4531
|
+
function validateMppConfig(config, env) {
|
|
4312
4532
|
const m = config.mpp;
|
|
4313
4533
|
if (!m) {
|
|
4314
4534
|
return [
|
|
@@ -4356,7 +4576,7 @@ function validateMppConfig(config) {
|
|
|
4356
4576
|
`MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4357
4577
|
);
|
|
4358
4578
|
}
|
|
4359
|
-
if (!m.rpcUrl) {
|
|
4579
|
+
if (!m.rpcUrl && !env.TEMPO_RPC_URL) {
|
|
4360
4580
|
push(
|
|
4361
4581
|
"missing_mpp_rpc_url",
|
|
4362
4582
|
"MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
@@ -4381,6 +4601,15 @@ function validateMppConfig(config) {
|
|
|
4381
4601
|
`MPP operatorKey and feePayerKey resolve to the same address (${collision}). Tempo rejects fee-delegated txs with sender === feePayer, so channel close/settle would fail at runtime. Either use two distinct wallets, or omit feePayerKey to disable gas sponsorship (clients then pay their own gas).`
|
|
4382
4602
|
);
|
|
4383
4603
|
}
|
|
4604
|
+
if (m.session && recipient && isEvmAddress(recipient)) {
|
|
4605
|
+
const operatorAddress = evmAddressFromKey(m.operatorKey);
|
|
4606
|
+
if (operatorAddress && operatorAddress !== recipient.toLowerCase()) {
|
|
4607
|
+
push(
|
|
4608
|
+
"mpp_operator_recipient_mismatch",
|
|
4609
|
+
`MPP session operatorKey resolves to ${operatorAddress}, which must equal the recipient/payee ${recipient.toLowerCase()}. mppx's channel-close handler asserts sender === payee. Set mpp.operatorKey to the recipient\u2019s private key, or set mpp.recipient/payeeAddress to the operator address.`
|
|
4610
|
+
);
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4384
4613
|
return issues;
|
|
4385
4614
|
}
|
|
4386
4615
|
function translateZodIssues(error) {
|
|
@@ -4509,8 +4738,8 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4509
4738
|
message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
|
|
4510
4739
|
});
|
|
4511
4740
|
}
|
|
4512
|
-
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env
|
|
4513
|
-
if (protocols.includes("mpp")) issues.push(...validateMppConfig(config));
|
|
4741
|
+
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env));
|
|
4742
|
+
if (protocols.includes("mpp")) issues.push(...validateMppConfig(config, env));
|
|
4514
4743
|
return issues;
|
|
4515
4744
|
}
|
|
4516
4745
|
|
|
@@ -4595,9 +4824,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4595
4824
|
const getClient = async () => tempoClient;
|
|
4596
4825
|
const operatorAccount = config.mpp.operatorKey ? privateKeyToAccount2(config.mpp.operatorKey) : void 0;
|
|
4597
4826
|
const feePayerAccount = config.mpp.feePayerKey ? privateKeyToAccount2(config.mpp.feePayerKey) : void 0;
|
|
4598
|
-
if (config.mpp.session && operatorAccount) {
|
|
4599
|
-
assertOperatorMatchesRecipient(config, operatorAccount.address);
|
|
4600
|
-
}
|
|
4601
4827
|
const resolvedStore = kvStore ? await createKvMppStore(kvStore) : void 0;
|
|
4602
4828
|
const realm = new URL(resolvedBaseUrl).host;
|
|
4603
4829
|
const mppConfig = config.mpp;
|
|
@@ -4635,15 +4861,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4635
4861
|
return { initError: err instanceof Error ? err.message : String(err) };
|
|
4636
4862
|
}
|
|
4637
4863
|
}
|
|
4638
|
-
function assertOperatorMatchesRecipient(config, operatorAddress) {
|
|
4639
|
-
const recipient = (config.mpp?.recipient ?? config.payeeAddress)?.toLowerCase();
|
|
4640
|
-
const opAddr = operatorAddress.toLowerCase();
|
|
4641
|
-
if (recipient && opAddr !== recipient) {
|
|
4642
|
-
throw new Error(
|
|
4643
|
-
`MPP session config mismatch: operator address ${operatorAddress} must equal recipient/payee ${recipient}. mppx's channel-close handler asserts sender === payee. Set mpp.operatorKey to the private key for ${recipient}, or set mpp.recipient/payeeAddress to ${operatorAddress}.`
|
|
4644
|
-
);
|
|
4645
|
-
}
|
|
4646
|
-
}
|
|
4647
4864
|
|
|
4648
4865
|
// src/index.ts
|
|
4649
4866
|
init_constants();
|
|
@@ -4654,10 +4871,7 @@ function createRouter(config) {
|
|
|
4654
4871
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
4655
4872
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
4656
4873
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
4657
|
-
const configIssues = getRouterConfigIssues(config, {
|
|
4658
|
-
env: process.env,
|
|
4659
|
-
requireCdpKeys: process.env.NODE_ENV === "production"
|
|
4660
|
-
});
|
|
4874
|
+
const configIssues = getRouterConfigIssues(config, { env: process.env });
|
|
4661
4875
|
const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
|
|
4662
4876
|
if (baseUrlIssue) throw new RouterConfigError([baseUrlIssue]);
|
|
4663
4877
|
const emptyProtocolsIssue = configIssues.find((issue) => issue.code === "empty_protocols");
|
|
@@ -4670,10 +4884,7 @@ function createRouter(config) {
|
|
|
4670
4884
|
const x402ConfigError = x402ConfigIssues.length > 0 ? formatRouterConfigIssues(x402ConfigIssues) : void 0;
|
|
4671
4885
|
const mppConfigError = mppConfigIssues.length > 0 ? formatRouterConfigIssues(mppConfigIssues) : void 0;
|
|
4672
4886
|
if (protocolConfigIssues.length > 0) {
|
|
4673
|
-
|
|
4674
|
-
if (process.env.NODE_ENV === "production") {
|
|
4675
|
-
throw new RouterConfigError(protocolConfigIssues);
|
|
4676
|
-
}
|
|
4887
|
+
throw new RouterConfigError(protocolConfigIssues);
|
|
4677
4888
|
}
|
|
4678
4889
|
const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
4679
4890
|
if (config.plugin?.init) {
|
|
@@ -4699,7 +4910,7 @@ function createRouter(config) {
|
|
|
4699
4910
|
x402Accepts,
|
|
4700
4911
|
mppx: null,
|
|
4701
4912
|
tempoClient: null,
|
|
4702
|
-
mppSessionConfig: config.mpp?.session ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4913
|
+
mppSessionConfig: config.mpp?.session && config.mpp.operatorKey ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4703
4914
|
};
|
|
4704
4915
|
deps.initPromise = (async () => {
|
|
4705
4916
|
const x402Result = await initX402(config, kvStore, x402ConfigError);
|