@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.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) => ({
|
|
@@ -362,9 +365,18 @@ function withScopedKinds(client, kinds) {
|
|
|
362
365
|
return {
|
|
363
366
|
verify: client.verify.bind(client),
|
|
364
367
|
settle: client.settle.bind(client),
|
|
365
|
-
getSupported: async () =>
|
|
368
|
+
getSupported: async () => {
|
|
369
|
+
const live = await client.getSupported();
|
|
370
|
+
return { ...live, kinds: mergeKindExtras(kinds, live.kinds) };
|
|
371
|
+
}
|
|
366
372
|
};
|
|
367
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
|
+
}
|
|
368
380
|
function buildSupportedKinds(group) {
|
|
369
381
|
return group.networks.flatMap((network) => {
|
|
370
382
|
if (group.family === "solana") {
|
|
@@ -592,7 +604,8 @@ function preflight(routeEntry, handler, deps, request) {
|
|
|
592
604
|
request,
|
|
593
605
|
meta,
|
|
594
606
|
pluginCtx,
|
|
595
|
-
report: createReporter(deps.plugin, pluginCtx, routeEntry.key)
|
|
607
|
+
report: createReporter(deps.plugin, pluginCtx, routeEntry.key),
|
|
608
|
+
query: void 0
|
|
596
609
|
};
|
|
597
610
|
}
|
|
598
611
|
function buildMeta(request, routeEntry) {
|
|
@@ -615,13 +628,19 @@ function buildMeta(request, routeEntry) {
|
|
|
615
628
|
var import_server = require("next/server");
|
|
616
629
|
|
|
617
630
|
// src/pipeline/body.ts
|
|
631
|
+
var MalformedJsonError = class extends Error {
|
|
632
|
+
constructor() {
|
|
633
|
+
super("Invalid JSON");
|
|
634
|
+
this.name = "MalformedJsonError";
|
|
635
|
+
}
|
|
636
|
+
};
|
|
618
637
|
async function bufferBody(request) {
|
|
619
638
|
const text = await request.text();
|
|
620
|
-
if (!text) return void 0;
|
|
639
|
+
if (!text.trim()) return void 0;
|
|
621
640
|
try {
|
|
622
641
|
return JSON.parse(text);
|
|
623
642
|
} catch {
|
|
624
|
-
|
|
643
|
+
throw new MalformedJsonError();
|
|
625
644
|
}
|
|
626
645
|
}
|
|
627
646
|
function validateBody(parsed, schema) {
|
|
@@ -702,7 +721,18 @@ function computeQuotaLevel(remaining, warn, critical) {
|
|
|
702
721
|
// src/pipeline/steps/parse-body.ts
|
|
703
722
|
async function parseBody(ctx, request = ctx.request) {
|
|
704
723
|
if (!ctx.routeEntry.bodySchema) return { ok: true, data: void 0 };
|
|
705
|
-
|
|
724
|
+
let raw;
|
|
725
|
+
try {
|
|
726
|
+
raw = await bufferBody(request);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
if (!(err instanceof MalformedJsonError)) throw err;
|
|
729
|
+
const response2 = import_server.NextResponse.json(
|
|
730
|
+
{ success: false, error: "Invalid JSON", issues: [] },
|
|
731
|
+
{ status: 400 }
|
|
732
|
+
);
|
|
733
|
+
firePluginResponse(ctx, response2);
|
|
734
|
+
return { ok: false, response: response2 };
|
|
735
|
+
}
|
|
706
736
|
const result = validateBody(raw, ctx.routeEntry.bodySchema);
|
|
707
737
|
if (result.success) return { ok: true, data: result.data };
|
|
708
738
|
const response = import_server.NextResponse.json(
|
|
@@ -713,6 +743,22 @@ async function parseBody(ctx, request = ctx.request) {
|
|
|
713
743
|
return { ok: false, response };
|
|
714
744
|
}
|
|
715
745
|
|
|
746
|
+
// src/pipeline/steps/parse-query.ts
|
|
747
|
+
var import_server2 = require("next/server");
|
|
748
|
+
function validateQuery(ctx) {
|
|
749
|
+
const { querySchema } = ctx.routeEntry;
|
|
750
|
+
if (!querySchema) return { ok: true, data: void 0 };
|
|
751
|
+
const params = Object.fromEntries(ctx.request.nextUrl.searchParams.entries());
|
|
752
|
+
const result = validateBody(params, querySchema);
|
|
753
|
+
if (result.success) return { ok: true, data: result.data };
|
|
754
|
+
const response = import_server2.NextResponse.json(
|
|
755
|
+
{ success: false, error: result.error, issues: result.issues },
|
|
756
|
+
{ status: 400 }
|
|
757
|
+
);
|
|
758
|
+
firePluginResponse(ctx, response);
|
|
759
|
+
return { ok: false, response };
|
|
760
|
+
}
|
|
761
|
+
|
|
716
762
|
// src/pipeline/steps/errors.ts
|
|
717
763
|
function errorStatus(error, fallback) {
|
|
718
764
|
const status = error?.status;
|
|
@@ -727,9 +773,9 @@ function handlerFailureError(response) {
|
|
|
727
773
|
}
|
|
728
774
|
|
|
729
775
|
// src/pipeline/steps/fail.ts
|
|
730
|
-
var
|
|
776
|
+
var import_server3 = require("next/server");
|
|
731
777
|
function fail(ctx, status, message, requestBody) {
|
|
732
|
-
const response =
|
|
778
|
+
const response = import_server3.NextResponse.json({ success: false, error: message }, { status });
|
|
733
779
|
firePluginResponse(ctx, response, requestBody);
|
|
734
780
|
return response;
|
|
735
781
|
}
|
|
@@ -746,7 +792,7 @@ async function runValidate(ctx, body) {
|
|
|
746
792
|
}
|
|
747
793
|
|
|
748
794
|
// src/pipeline/flows/static/static-invoke.ts
|
|
749
|
-
var
|
|
795
|
+
var import_server4 = require("next/server");
|
|
750
796
|
|
|
751
797
|
// src/types.ts
|
|
752
798
|
var HttpError = class extends Error {
|
|
@@ -757,14 +803,6 @@ var HttpError = class extends Error {
|
|
|
757
803
|
}
|
|
758
804
|
};
|
|
759
805
|
|
|
760
|
-
// src/pipeline/steps/parse-query.ts
|
|
761
|
-
function parseQuery(request, routeEntry) {
|
|
762
|
-
if (!routeEntry.querySchema) return void 0;
|
|
763
|
-
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
764
|
-
const result = routeEntry.querySchema.safeParse(params);
|
|
765
|
-
return result.success ? result.data : params;
|
|
766
|
-
}
|
|
767
|
-
|
|
768
806
|
// src/pipeline/flows/static/static-invoke.ts
|
|
769
807
|
function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
770
808
|
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
|
|
@@ -775,7 +813,7 @@ function invokeUnauthed(ctx, wallet, account, body) {
|
|
|
775
813
|
function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
776
814
|
return {
|
|
777
815
|
body,
|
|
778
|
-
query:
|
|
816
|
+
query: ctx.query,
|
|
779
817
|
request: ctx.request,
|
|
780
818
|
requestId: ctx.meta.requestId,
|
|
781
819
|
route: ctx.routeEntry.key,
|
|
@@ -795,10 +833,7 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
795
833
|
}
|
|
796
834
|
if (isAsyncIterable(returned) && !isThenable(returned)) {
|
|
797
835
|
return errorResult(
|
|
798
|
-
new HttpError(
|
|
799
|
-
`route '${ctx.routeEntry.key}': streaming handlers require .paid({ dynamic: true })`,
|
|
800
|
-
500
|
|
801
|
-
)
|
|
836
|
+
new HttpError(`route '${ctx.routeEntry.key}': streaming handlers require .metered()`, 500)
|
|
802
837
|
);
|
|
803
838
|
}
|
|
804
839
|
let rawResult;
|
|
@@ -807,14 +842,14 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
807
842
|
} catch (error) {
|
|
808
843
|
return errorResult(error);
|
|
809
844
|
}
|
|
810
|
-
const response = rawResult instanceof Response ? rawResult :
|
|
845
|
+
const response = rawResult instanceof Response ? rawResult : import_server4.NextResponse.json(rawResult);
|
|
811
846
|
return { response, rawResult };
|
|
812
847
|
}
|
|
813
848
|
function errorResult(error) {
|
|
814
849
|
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
815
850
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
816
851
|
return {
|
|
817
|
-
response:
|
|
852
|
+
response: import_server4.NextResponse.json({ success: false, error: message }, { status }),
|
|
818
853
|
rawResult: void 0,
|
|
819
854
|
handlerError: error
|
|
820
855
|
};
|
|
@@ -1227,10 +1262,12 @@ var DynamicPricing = class {
|
|
|
1227
1262
|
}
|
|
1228
1263
|
needsBody = true;
|
|
1229
1264
|
async quote(body) {
|
|
1265
|
+
let priced;
|
|
1230
1266
|
try {
|
|
1231
1267
|
const raw = await this.opts.fn(body);
|
|
1232
|
-
|
|
1268
|
+
priced = this.cap(raw, body);
|
|
1233
1269
|
} catch (err) {
|
|
1270
|
+
if (err instanceof HttpError) throw err;
|
|
1234
1271
|
this.alert("error", `Pricing function failed: ${msg(err)}`, {
|
|
1235
1272
|
error: err instanceof Error ? err.stack : String(err),
|
|
1236
1273
|
body
|
|
@@ -1241,6 +1278,13 @@ var DynamicPricing = class {
|
|
|
1241
1278
|
}
|
|
1242
1279
|
throw err;
|
|
1243
1280
|
}
|
|
1281
|
+
if (!isPositiveDecimal(priced)) {
|
|
1282
|
+
throw new HttpError(
|
|
1283
|
+
`route '${this.opts.route ?? "unknown"}': dynamic pricing returned an invalid amount '${priced}'`,
|
|
1284
|
+
500
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
return priced;
|
|
1244
1288
|
}
|
|
1245
1289
|
challengeQuote(body) {
|
|
1246
1290
|
if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
|
|
@@ -1315,14 +1359,14 @@ var TieredPricing = class {
|
|
|
1315
1359
|
`Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
|
|
1316
1360
|
);
|
|
1317
1361
|
}
|
|
1318
|
-
challengeQuote(body) {
|
|
1362
|
+
async challengeQuote(body) {
|
|
1319
1363
|
if (body !== void 0) {
|
|
1320
1364
|
try {
|
|
1321
|
-
return this.quote(body);
|
|
1365
|
+
return await this.quote(body);
|
|
1322
1366
|
} catch {
|
|
1323
1367
|
}
|
|
1324
1368
|
}
|
|
1325
|
-
return
|
|
1369
|
+
return this.maxTierPrice();
|
|
1326
1370
|
}
|
|
1327
1371
|
describe() {
|
|
1328
1372
|
return {
|
|
@@ -1753,7 +1797,7 @@ var mppStrategy = {
|
|
|
1753
1797
|
async verify(args) {
|
|
1754
1798
|
const info = readMppCredential(args.request);
|
|
1755
1799
|
if (!info) return { ok: false, kind: "invalid" };
|
|
1756
|
-
if (args.routeEntry.
|
|
1800
|
+
if (args.routeEntry.billing === "metered") {
|
|
1757
1801
|
if (!info.sessionAction) return { ok: false, kind: "invalid" };
|
|
1758
1802
|
return verifySessionMode(args, info);
|
|
1759
1803
|
}
|
|
@@ -1804,7 +1848,7 @@ var mppStrategy = {
|
|
|
1804
1848
|
async buildChallenge(args) {
|
|
1805
1849
|
if (!args.deps.mppx) return {};
|
|
1806
1850
|
const sessionsConfigured = args.deps.mppSessionConfig && (args.deps.mppx.sessionRequest || args.deps.mppx.sessionStream);
|
|
1807
|
-
if (args.routeEntry.
|
|
1851
|
+
if (args.routeEntry.billing === "metered" && sessionsConfigured) {
|
|
1808
1852
|
const tickCost = args.routeEntry.tickCost;
|
|
1809
1853
|
const computedDeposit = tickCost !== void 0 ? multiplyDecimal(tickCost, args.deps.mppSessionConfig.depositMultiplier) : void 0;
|
|
1810
1854
|
const suggestedDeposit = args.routeEntry.maxPrice ?? computedDeposit ?? args.price;
|
|
@@ -2056,12 +2100,12 @@ async function settleX402Payment(server, payload, requirements, amountOverride)
|
|
|
2056
2100
|
};
|
|
2057
2101
|
}
|
|
2058
2102
|
function tagBareDecimalAsDollars(amount) {
|
|
2059
|
-
if (/^\d
|
|
2103
|
+
if (/^\d+(?:\.\d+)?$/.test(amount)) return `$${amount}`;
|
|
2060
2104
|
return amount;
|
|
2061
2105
|
}
|
|
2062
2106
|
|
|
2063
2107
|
// src/protocols/x402/verify.ts
|
|
2064
|
-
var
|
|
2108
|
+
var import_types3 = require("@x402/core/types");
|
|
2065
2109
|
async function verifyX402Payment(opts) {
|
|
2066
2110
|
const { server, request, price, accepts, report } = opts;
|
|
2067
2111
|
const payload = await readPaymentPayload(request);
|
|
@@ -2080,7 +2124,7 @@ async function verifyX402Payment(opts) {
|
|
|
2080
2124
|
try {
|
|
2081
2125
|
verify = await server.verifyPayment(payload, matching);
|
|
2082
2126
|
} catch (err) {
|
|
2083
|
-
if (err instanceof
|
|
2127
|
+
if (err instanceof import_types3.VerifyError && err.statusCode >= 400 && err.statusCode < 500) {
|
|
2084
2128
|
return invalidPaymentVerification({
|
|
2085
2129
|
reason: err.invalidReason ?? "verify_error",
|
|
2086
2130
|
...err.invalidMessage ? { message: err.invalidMessage } : {},
|
|
@@ -2181,7 +2225,7 @@ async function verifyX402(args) {
|
|
|
2181
2225
|
const accepts = await resolveX402Accepts(
|
|
2182
2226
|
request,
|
|
2183
2227
|
routeEntry,
|
|
2184
|
-
deps.x402Accepts,
|
|
2228
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2185
2229
|
deps.payeeAddress,
|
|
2186
2230
|
body
|
|
2187
2231
|
);
|
|
@@ -2229,7 +2273,7 @@ async function verifyX402(args) {
|
|
|
2229
2273
|
async function settleX402(args) {
|
|
2230
2274
|
const { response, payment, token, deps, routeEntry, billedAmount, report } = args;
|
|
2231
2275
|
const { payload, requirements } = token;
|
|
2232
|
-
const override = routeEntry.
|
|
2276
|
+
const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
|
|
2233
2277
|
try {
|
|
2234
2278
|
const settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2235
2279
|
if (!settle.result?.success) {
|
|
@@ -2259,7 +2303,7 @@ async function buildX402ChallengeContribution(args) {
|
|
|
2259
2303
|
const accepts = await resolveX402Accepts(
|
|
2260
2304
|
request,
|
|
2261
2305
|
routeEntry,
|
|
2262
|
-
deps.x402Accepts,
|
|
2306
|
+
selectRouteAccepts(deps.x402Accepts, routeEntry),
|
|
2263
2307
|
deps.payeeAddress,
|
|
2264
2308
|
body
|
|
2265
2309
|
);
|
|
@@ -2303,8 +2347,8 @@ function getAllowedStrategies(allowed) {
|
|
|
2303
2347
|
return allowed.map((name) => STRATEGIES[name]);
|
|
2304
2348
|
}
|
|
2305
2349
|
|
|
2306
|
-
// src/pipeline/flows/
|
|
2307
|
-
var
|
|
2350
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2351
|
+
var import_server5 = require("next/server");
|
|
2308
2352
|
|
|
2309
2353
|
// src/pipeline/challenge-extensions.ts
|
|
2310
2354
|
init_evm();
|
|
@@ -2320,20 +2364,17 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2320
2364
|
});
|
|
2321
2365
|
const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
|
|
2322
2366
|
const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2332
|
-
if (outputSchema && routeEntry.outputExample !== void 0) {
|
|
2333
|
-
config.output = { schema: outputSchema, example: routeEntry.outputExample };
|
|
2334
|
-
}
|
|
2335
|
-
extensions = declareDiscoveryExtension(config);
|
|
2367
|
+
const isBodyMethod = routeEntry.method === "POST" || routeEntry.method === "PUT" || routeEntry.method === "PATCH";
|
|
2368
|
+
const config = { method: routeEntry.method };
|
|
2369
|
+
if (isBodyMethod) config.bodyType = "json";
|
|
2370
|
+
if (inputSchema) config.inputSchema = inputSchema;
|
|
2371
|
+
if (routeEntry.inputExample !== void 0) {
|
|
2372
|
+
config.input = routeEntry.inputExample;
|
|
2373
|
+
}
|
|
2374
|
+
if (outputSchema && routeEntry.outputExample !== void 0) {
|
|
2375
|
+
config.output = { schema: outputSchema, example: routeEntry.outputExample };
|
|
2336
2376
|
}
|
|
2377
|
+
extensions = declareDiscoveryExtension(config);
|
|
2337
2378
|
} catch (err) {
|
|
2338
2379
|
ctx.report(
|
|
2339
2380
|
"warn",
|
|
@@ -2352,9 +2393,7 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2352
2393
|
} catch {
|
|
2353
2394
|
}
|
|
2354
2395
|
}
|
|
2355
|
-
const hasEvmUpto = ctx.deps.x402Accepts.some(
|
|
2356
|
-
(accept) => accept.scheme === "upto" && isEvmNetwork(accept.network)
|
|
2357
|
-
);
|
|
2396
|
+
const hasEvmUpto = ctx.routeEntry.billing === "upto" && ctx.deps.x402Accepts.some((accept) => accept.scheme === "upto" && isEvmNetwork(accept.network));
|
|
2358
2397
|
if (hasEvmUpto) {
|
|
2359
2398
|
try {
|
|
2360
2399
|
const { declareEip2612GasSponsoringExtension } = await import("@x402/extensions");
|
|
@@ -2372,14 +2411,14 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2372
2411
|
return extensions;
|
|
2373
2412
|
}
|
|
2374
2413
|
|
|
2375
|
-
// src/pipeline/flows/
|
|
2376
|
-
async function
|
|
2414
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2415
|
+
async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
2377
2416
|
let challengePrice;
|
|
2378
2417
|
try {
|
|
2379
2418
|
challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
|
|
2380
2419
|
} catch (err) {
|
|
2381
2420
|
const message = errorMessage(err, "Price calculation failed");
|
|
2382
|
-
const errorResponse =
|
|
2421
|
+
const errorResponse = import_server5.NextResponse.json(
|
|
2383
2422
|
{ success: false, error: message },
|
|
2384
2423
|
{ status: errorStatus(err, 500) }
|
|
2385
2424
|
);
|
|
@@ -2388,7 +2427,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2388
2427
|
}
|
|
2389
2428
|
const extensions = await buildChallengeExtensions(ctx);
|
|
2390
2429
|
const responseBody = failure ? JSON.stringify({ error: failure.message ?? null, reason: failure.reason }) : null;
|
|
2391
|
-
const response = new
|
|
2430
|
+
const response = new import_server5.NextResponse(responseBody, {
|
|
2392
2431
|
status: 402,
|
|
2393
2432
|
headers: {
|
|
2394
2433
|
"Content-Type": "application/json",
|
|
@@ -2415,7 +2454,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2415
2454
|
const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
|
|
2416
2455
|
ctx.report("critical", message);
|
|
2417
2456
|
if (strategy.protocol === "x402") {
|
|
2418
|
-
const errorResponse =
|
|
2457
|
+
const errorResponse = import_server5.NextResponse.json(
|
|
2419
2458
|
{ success: false, error: message },
|
|
2420
2459
|
{ status: 500 }
|
|
2421
2460
|
);
|
|
@@ -2467,7 +2506,7 @@ function surrogatePriceForSkippedBody(routeEntry) {
|
|
|
2467
2506
|
}
|
|
2468
2507
|
|
|
2469
2508
|
// src/pipeline/flows/dynamic/dynamic-channel-mgmt.ts
|
|
2470
|
-
var
|
|
2509
|
+
var import_server6 = require("next/server");
|
|
2471
2510
|
async function runDynamicChannelMgmtFlow(args) {
|
|
2472
2511
|
const { ctx, strategy, account, pricing, skipBody } = args;
|
|
2473
2512
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -2486,7 +2525,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2486
2525
|
if (verifyOutcome.kind === "config") {
|
|
2487
2526
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2488
2527
|
}
|
|
2489
|
-
return
|
|
2528
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2490
2529
|
}
|
|
2491
2530
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2492
2531
|
firePaymentVerified(ctx, {
|
|
@@ -2495,7 +2534,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2495
2534
|
amount: price,
|
|
2496
2535
|
network: verifyOutcome.payment.network
|
|
2497
2536
|
});
|
|
2498
|
-
const synthetic = new
|
|
2537
|
+
const synthetic = new import_server6.NextResponse(null, { status: 200 });
|
|
2499
2538
|
const settleScope = {
|
|
2500
2539
|
wallet: verifyOutcome.wallet,
|
|
2501
2540
|
account,
|
|
@@ -2521,10 +2560,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2521
2560
|
});
|
|
2522
2561
|
}
|
|
2523
2562
|
|
|
2524
|
-
// src/
|
|
2525
|
-
var import_server6 = require("next/server");
|
|
2526
|
-
|
|
2527
|
-
// src/pricing/charge-context.ts
|
|
2563
|
+
// src/pricing/metered-charge.ts
|
|
2528
2564
|
function createChargeContext(args) {
|
|
2529
2565
|
const { tickCost, maxPrice, route } = args;
|
|
2530
2566
|
const tickAtomic = decimalToAtomic(tickCost);
|
|
@@ -2559,17 +2595,12 @@ function createChargeContext(args) {
|
|
|
2559
2595
|
};
|
|
2560
2596
|
}
|
|
2561
2597
|
|
|
2562
|
-
// src/pipeline/flows/dynamic/dynamic-invoke.ts
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
tickCost: ctx.routeEntry.tickCost,
|
|
2567
|
-
maxPrice: ctx.routeEntry.maxPrice,
|
|
2568
|
-
route: ctx.routeEntry.key
|
|
2569
|
-
}) : null;
|
|
2570
|
-
const baseHandlerCtx = {
|
|
2598
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2599
|
+
var import_server7 = require("next/server");
|
|
2600
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2601
|
+
return {
|
|
2571
2602
|
body,
|
|
2572
|
-
query:
|
|
2603
|
+
query: ctx.query,
|
|
2573
2604
|
request: ctx.request,
|
|
2574
2605
|
requestId: ctx.meta.requestId,
|
|
2575
2606
|
route: ctx.routeEntry.key,
|
|
@@ -2579,12 +2610,41 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2579
2610
|
alert: ctx.report,
|
|
2580
2611
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2581
2612
|
};
|
|
2613
|
+
}
|
|
2614
|
+
function toResponse(rawResult) {
|
|
2615
|
+
return rawResult instanceof Response ? rawResult : import_server7.NextResponse.json(rawResult);
|
|
2616
|
+
}
|
|
2617
|
+
function errorResult2(error) {
|
|
2618
|
+
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
2619
|
+
const message = error instanceof Error ? error.message : "Internal error";
|
|
2620
|
+
return {
|
|
2621
|
+
kind: "request",
|
|
2622
|
+
response: import_server7.NextResponse.json({ success: false, error: message }, { status }),
|
|
2623
|
+
rawResult: void 0,
|
|
2624
|
+
handlerError: error
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2627
|
+
function isAsyncIterable2(value) {
|
|
2628
|
+
return value != null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
2629
|
+
}
|
|
2630
|
+
function isThenable2(value) {
|
|
2631
|
+
return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2635
|
+
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2636
|
+
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2637
|
+
tickCost: ctx.routeEntry.tickCost,
|
|
2638
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2639
|
+
route: ctx.routeEntry.key
|
|
2640
|
+
}) : null;
|
|
2641
|
+
const baseHandlerCtx = buildBaseHandlerCtx(ctx, wallet, account, body, payment);
|
|
2582
2642
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2583
2643
|
let returned;
|
|
2584
2644
|
try {
|
|
2585
2645
|
returned = ctx.handler(handlerCtx);
|
|
2586
2646
|
} catch (error) {
|
|
2587
|
-
return errorResult2(error
|
|
2647
|
+
return errorResult2(error);
|
|
2588
2648
|
}
|
|
2589
2649
|
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2590
2650
|
if (!chargeContext) {
|
|
@@ -2592,41 +2652,80 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2592
2652
|
new HttpError(
|
|
2593
2653
|
"route returned an async iterable from a non-streaming handler \u2014 use .stream(async function*(...)) instead of .handler() to opt into streaming",
|
|
2594
2654
|
500
|
|
2595
|
-
)
|
|
2596
|
-
null
|
|
2655
|
+
)
|
|
2597
2656
|
);
|
|
2598
2657
|
}
|
|
2599
|
-
return {
|
|
2600
|
-
kind: "stream",
|
|
2601
|
-
source: returned,
|
|
2602
|
-
chargeContext
|
|
2603
|
-
};
|
|
2658
|
+
return { kind: "stream", source: returned, chargeContext };
|
|
2604
2659
|
}
|
|
2605
2660
|
let rawResult;
|
|
2606
2661
|
try {
|
|
2607
2662
|
rawResult = await returned;
|
|
2608
2663
|
} catch (error) {
|
|
2609
|
-
return errorResult2(error
|
|
2664
|
+
return errorResult2(error);
|
|
2610
2665
|
}
|
|
2611
|
-
|
|
2612
|
-
return { kind: "request", response, rawResult };
|
|
2666
|
+
return { kind: "request", response: toResponse(rawResult), rawResult };
|
|
2613
2667
|
}
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2668
|
+
|
|
2669
|
+
// src/pricing/upto-charge.ts
|
|
2670
|
+
function createUptoChargeContext(args) {
|
|
2671
|
+
const { maxPrice, route } = args;
|
|
2672
|
+
const capAtomic = decimalToAtomic(maxPrice);
|
|
2673
|
+
if (capAtomic <= 0n) {
|
|
2674
|
+
throw new Error(`route '${route}': maxPrice '${maxPrice}' must be a positive decimal string`);
|
|
2675
|
+
}
|
|
2676
|
+
let calls = 0;
|
|
2677
|
+
let atomic = 0n;
|
|
2678
|
+
const charge = async (amount) => {
|
|
2679
|
+
const nextAtomic = atomic + decimalToAtomic(amount);
|
|
2680
|
+
if (nextAtomic > capAtomic) {
|
|
2681
|
+
throw Object.assign(
|
|
2682
|
+
new Error(
|
|
2683
|
+
`route '${route}': charge() running total ($${atomicToDecimal(nextAtomic)}) exceeds maxPrice ($${atomicToDecimal(capAtomic)})`
|
|
2684
|
+
),
|
|
2685
|
+
{ status: 400, code: "CHARGE_OVER_CAP" }
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
calls += 1;
|
|
2689
|
+
atomic = nextAtomic;
|
|
2690
|
+
};
|
|
2618
2691
|
return {
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
handlerError: error
|
|
2692
|
+
charge,
|
|
2693
|
+
callCount: () => calls,
|
|
2694
|
+
atomicTotal: () => atomic
|
|
2623
2695
|
};
|
|
2624
2696
|
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2697
|
+
|
|
2698
|
+
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2699
|
+
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2700
|
+
const uptoCtx = createUptoChargeContext({
|
|
2701
|
+
maxPrice: ctx.routeEntry.maxPrice,
|
|
2702
|
+
route: ctx.routeEntry.key
|
|
2703
|
+
});
|
|
2704
|
+
const handlerCtx = {
|
|
2705
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2706
|
+
charge: uptoCtx.charge
|
|
2707
|
+
};
|
|
2708
|
+
let returned;
|
|
2709
|
+
try {
|
|
2710
|
+
returned = ctx.handler(handlerCtx);
|
|
2711
|
+
} catch (error) {
|
|
2712
|
+
return errorResult2(error);
|
|
2713
|
+
}
|
|
2714
|
+
if (isAsyncIterable2(returned) && !isThenable2(returned)) {
|
|
2715
|
+
return errorResult2(
|
|
2716
|
+
new HttpError(
|
|
2717
|
+
"streaming is not supported on .upTo() routes \u2014 return a value from .handler() instead",
|
|
2718
|
+
500
|
|
2719
|
+
)
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
let rawResult;
|
|
2723
|
+
try {
|
|
2724
|
+
rawResult = await returned;
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
return errorResult2(error);
|
|
2727
|
+
}
|
|
2728
|
+
return { kind: "request", response: toResponse(rawResult), rawResult, uptoContext: uptoCtx };
|
|
2630
2729
|
}
|
|
2631
2730
|
|
|
2632
2731
|
// src/pipeline/flows/dynamic/dynamic-preflight.ts
|
|
@@ -2656,7 +2755,7 @@ async function runDynamicRequestFlow(args) {
|
|
|
2656
2755
|
}
|
|
2657
2756
|
const beforeErr = await runBeforeSettle(ctx, settleScope);
|
|
2658
2757
|
if (beforeErr) return beforeErr;
|
|
2659
|
-
const billedAmount = routeEntry
|
|
2758
|
+
const billedAmount = computeBilledAmount(routeEntry, result);
|
|
2660
2759
|
return settleAndFinalizeRequest({
|
|
2661
2760
|
ctx,
|
|
2662
2761
|
strategy,
|
|
@@ -2674,6 +2773,19 @@ async function runDynamicRequestFlow(args) {
|
|
|
2674
2773
|
}
|
|
2675
2774
|
});
|
|
2676
2775
|
}
|
|
2776
|
+
function computeBilledAmount(routeEntry, result) {
|
|
2777
|
+
if (routeEntry.billing === "upto") {
|
|
2778
|
+
const total = result.uptoContext?.atomicTotal() ?? 0n;
|
|
2779
|
+
if (total <= 0n) {
|
|
2780
|
+
throw new HttpError(
|
|
2781
|
+
`route '${routeEntry.key}': handler did not call charge(amount) \u2014 upto routes must accumulate a non-zero billed amount`,
|
|
2782
|
+
500
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
return atomicToDecimal(total);
|
|
2786
|
+
}
|
|
2787
|
+
return routeEntry.tickCost;
|
|
2788
|
+
}
|
|
2677
2789
|
|
|
2678
2790
|
// src/pipeline/flows/dynamic/dynamic-stream.ts
|
|
2679
2791
|
async function runDynamicStreamFlow(args) {
|
|
@@ -2710,7 +2822,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2710
2822
|
if (!incomingStrategy) {
|
|
2711
2823
|
const initError = protocolInitError(routeEntry, deps);
|
|
2712
2824
|
if (initError) return fail(ctx, 500, initError);
|
|
2713
|
-
return
|
|
2825
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2714
2826
|
}
|
|
2715
2827
|
const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
|
|
2716
2828
|
if (skipHandler) {
|
|
@@ -2737,7 +2849,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2737
2849
|
if (verifyOutcome.kind === "config") {
|
|
2738
2850
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2739
2851
|
}
|
|
2740
|
-
return
|
|
2852
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2741
2853
|
}
|
|
2742
2854
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2743
2855
|
firePaymentVerified(ctx, {
|
|
@@ -2746,13 +2858,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2746
2858
|
amount: price,
|
|
2747
2859
|
network: verifyOutcome.payment.network
|
|
2748
2860
|
});
|
|
2749
|
-
const result = await invokeDynamic(
|
|
2750
|
-
ctx,
|
|
2751
|
-
verifyOutcome.wallet,
|
|
2752
|
-
account,
|
|
2753
|
-
parsedBody,
|
|
2754
|
-
verifyOutcome.payment
|
|
2755
|
-
);
|
|
2861
|
+
const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
|
|
2756
2862
|
switch (result.kind) {
|
|
2757
2863
|
case "stream":
|
|
2758
2864
|
return runDynamicStreamFlow({
|
|
@@ -2774,6 +2880,18 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2774
2880
|
});
|
|
2775
2881
|
}
|
|
2776
2882
|
}
|
|
2883
|
+
async function invokeDynamic(ctx, verifyOutcome, account, parsedBody) {
|
|
2884
|
+
switch (ctx.routeEntry.billing) {
|
|
2885
|
+
case "upto":
|
|
2886
|
+
return invokeUpto(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2887
|
+
case "metered":
|
|
2888
|
+
return invokeMetered(ctx, verifyOutcome.wallet, account, parsedBody, verifyOutcome.payment);
|
|
2889
|
+
case "exact":
|
|
2890
|
+
throw new Error(
|
|
2891
|
+
`route '${ctx.routeEntry.key}': exact billing must not reach the dynamic paid flow`
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2777
2895
|
|
|
2778
2896
|
// src/pipeline/flows/static/static-body-and-price.ts
|
|
2779
2897
|
async function resolveStaticBodyAndPrice(args) {
|
|
@@ -2875,7 +2993,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2875
2993
|
if (!incomingStrategy) {
|
|
2876
2994
|
const initError = protocolInitError(routeEntry, deps);
|
|
2877
2995
|
if (initError) return fail(ctx, 500, initError);
|
|
2878
|
-
return
|
|
2996
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2879
2997
|
}
|
|
2880
2998
|
const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
|
|
2881
2999
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
@@ -2892,7 +3010,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2892
3010
|
if (verifyOutcome.kind === "config") {
|
|
2893
3011
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2894
3012
|
}
|
|
2895
|
-
return
|
|
3013
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2896
3014
|
}
|
|
2897
3015
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2898
3016
|
firePaymentVerified(ctx, {
|
|
@@ -2921,17 +3039,12 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2921
3039
|
|
|
2922
3040
|
// src/pipeline/flows/paid.ts
|
|
2923
3041
|
async function runPaidFlow(ctx) {
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
case true:
|
|
2927
|
-
return runDynamicPaidFlow(ctx);
|
|
2928
|
-
case false:
|
|
2929
|
-
return runStaticPaidFlow(ctx);
|
|
2930
|
-
}
|
|
3042
|
+
const handlerCharged = ctx.routeEntry.billing !== "exact";
|
|
3043
|
+
return handlerCharged ? runDynamicPaidFlow(ctx) : runStaticPaidFlow(ctx);
|
|
2931
3044
|
}
|
|
2932
3045
|
|
|
2933
3046
|
// src/pipeline/flows/siwx-only.ts
|
|
2934
|
-
var
|
|
3047
|
+
var import_server8 = require("next/server");
|
|
2935
3048
|
|
|
2936
3049
|
// src/kv-store/client.ts
|
|
2937
3050
|
var BIGINT_SUFFIX = "#__bigint";
|
|
@@ -3165,7 +3278,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3165
3278
|
}
|
|
3166
3279
|
const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
|
|
3167
3280
|
if (!siwx.valid) {
|
|
3168
|
-
const response =
|
|
3281
|
+
const response = import_server8.NextResponse.json(
|
|
3169
3282
|
{ error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
|
|
3170
3283
|
{ status: 402 }
|
|
3171
3284
|
);
|
|
@@ -3226,7 +3339,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3226
3339
|
`SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3227
3340
|
);
|
|
3228
3341
|
}
|
|
3229
|
-
const response = new
|
|
3342
|
+
const response = new import_server8.NextResponse(JSON.stringify(paymentRequired), {
|
|
3230
3343
|
status: 402,
|
|
3231
3344
|
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
3232
3345
|
});
|
|
@@ -3272,6 +3385,9 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
3272
3385
|
return async (request) => {
|
|
3273
3386
|
await deps.initPromise;
|
|
3274
3387
|
const ctx = preflight(routeEntry, handler, deps, request);
|
|
3388
|
+
const query = validateQuery(ctx);
|
|
3389
|
+
if (!query.ok) return query.response;
|
|
3390
|
+
ctx.query = query.data;
|
|
3275
3391
|
if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
|
|
3276
3392
|
if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
|
|
3277
3393
|
if (routeEntry.pricing) return runPaidFlow(ctx);
|
|
@@ -3325,7 +3441,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3325
3441
|
protocols: defaults?.protocols ? [...defaults.protocols] : ["x402"],
|
|
3326
3442
|
maxPrice: void 0,
|
|
3327
3443
|
minPrice: void 0,
|
|
3328
|
-
|
|
3444
|
+
billing: "exact",
|
|
3329
3445
|
tickCost: void 0,
|
|
3330
3446
|
unitType: void 0,
|
|
3331
3447
|
payTo: void 0,
|
|
@@ -3356,39 +3472,84 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3356
3472
|
next.#s = { ...this.#s, protocols: [...this.#s.protocols] };
|
|
3357
3473
|
return next;
|
|
3358
3474
|
}
|
|
3359
|
-
paid(
|
|
3360
|
-
|
|
3475
|
+
paid(arg, options) {
|
|
3476
|
+
return this.applyPaid(normalizePaidArg(this.#s.key, arg, options), "paid");
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* x402-only handler-computed billing. The handler receives `charge(amount)`
|
|
3480
|
+
* and the request settles once for the accumulated total, capped at
|
|
3481
|
+
* `maxPrice`. Requires an `'upto'` accept on at least one configured network.
|
|
3482
|
+
* Pass a bare string as sugar for `{ maxPrice }`.
|
|
3483
|
+
*
|
|
3484
|
+
* @example
|
|
3485
|
+
* ```ts
|
|
3486
|
+
* router.route('llm')
|
|
3487
|
+
* .upTo('0.05')
|
|
3488
|
+
* .body(schema)
|
|
3489
|
+
* .handler(async ({ body, charge }) => { await charge('0.001'); ... });
|
|
3490
|
+
* ```
|
|
3491
|
+
*/
|
|
3492
|
+
upTo(arg) {
|
|
3493
|
+
return this.applyPaid(normalizeUpToArg(this.#s.key, arg), "upTo");
|
|
3494
|
+
}
|
|
3495
|
+
/**
|
|
3496
|
+
* MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
|
|
3497
|
+
* `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
|
|
3498
|
+
* `maxPrice`. Requires `RouterConfig.mpp.session`.
|
|
3499
|
+
*
|
|
3500
|
+
* @example
|
|
3501
|
+
* ```ts
|
|
3502
|
+
* router.route('llm/stream')
|
|
3503
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3504
|
+
* .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
|
|
3505
|
+
* ```
|
|
3506
|
+
*/
|
|
3507
|
+
metered(options) {
|
|
3508
|
+
return this.applyPaid(normalizeMeteredArg(this.#s.key, options), "metered");
|
|
3509
|
+
}
|
|
3510
|
+
applyPaid(normalized, method) {
|
|
3511
|
+
const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
|
|
3361
3512
|
if (this.#s.authMode === "unprotected") {
|
|
3362
3513
|
throw new Error(
|
|
3363
|
-
`route '${this.#s.key}': Cannot combine .unprotected() and
|
|
3514
|
+
`route '${this.#s.key}': Cannot combine .unprotected() and .${method}() on the same route.`
|
|
3364
3515
|
);
|
|
3365
3516
|
}
|
|
3366
3517
|
if (this.#s.pricing !== void 0) {
|
|
3367
3518
|
throw new Error(
|
|
3368
|
-
`route '${this.#s.key}': Cannot
|
|
3519
|
+
`route '${this.#s.key}': Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
|
|
3369
3520
|
);
|
|
3370
3521
|
}
|
|
3371
3522
|
const next = this.fork();
|
|
3372
3523
|
next.#s.authMode = "paid";
|
|
3373
3524
|
next.#s.pricing = pricing;
|
|
3374
|
-
if (
|
|
3525
|
+
if (billing === "upto") {
|
|
3526
|
+
if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
|
|
3527
|
+
throw new Error(
|
|
3528
|
+
`route '${this.#s.key}': .upTo() is x402-only \u2014 remove the conflicting protocols override.`
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
next.#s.protocols = ["x402"];
|
|
3532
|
+
} else if (billing === "metered") {
|
|
3533
|
+
if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
|
|
3534
|
+
throw new Error(
|
|
3535
|
+
`route '${this.#s.key}': .metered() is MPP-only \u2014 remove the conflicting protocols override.`
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
next.#s.protocols = ["mpp"];
|
|
3539
|
+
} else if (resolvedOptions.protocols) {
|
|
3375
3540
|
next.#s.protocols = [...resolvedOptions.protocols];
|
|
3376
3541
|
} else if (next.#s.protocols.length === 0) {
|
|
3377
3542
|
next.#s.protocols = ["x402"];
|
|
3378
3543
|
}
|
|
3379
|
-
if (resolvedOptions
|
|
3380
|
-
if (
|
|
3381
|
-
if (resolvedOptions
|
|
3382
|
-
if (resolvedOptions
|
|
3383
|
-
if (resolvedOptions
|
|
3384
|
-
|
|
3385
|
-
if (
|
|
3544
|
+
if (resolvedOptions.maxPrice) next.#s.maxPrice = resolvedOptions.maxPrice;
|
|
3545
|
+
if (maxPrice) next.#s.maxPrice = maxPrice;
|
|
3546
|
+
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3547
|
+
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3548
|
+
if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
|
|
3549
|
+
next.#s.billing = billing;
|
|
3550
|
+
if (tickCost) next.#s.tickCost = tickCost;
|
|
3551
|
+
if (unitType) next.#s.unitType = unitType;
|
|
3386
3552
|
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3387
|
-
if (next.#s.dynamicPrice) {
|
|
3388
|
-
throw new Error(
|
|
3389
|
-
`route '${this.#s.key}': .paid({ dynamic: true }) is incompatible with tiered pricing`
|
|
3390
|
-
);
|
|
3391
|
-
}
|
|
3392
3553
|
for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
|
|
3393
3554
|
if (!tierKey) {
|
|
3394
3555
|
throw new Error(`route '${this.#s.key}': tier key cannot be empty`);
|
|
@@ -3400,21 +3561,25 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3400
3561
|
}
|
|
3401
3562
|
}
|
|
3402
3563
|
}
|
|
3403
|
-
if (
|
|
3564
|
+
if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
|
|
3404
3565
|
throw new Error(
|
|
3405
|
-
`route '${this.#s.key}':
|
|
3566
|
+
`route '${this.#s.key}': price '${pricing}' must be a positive decimal string`
|
|
3406
3567
|
);
|
|
3407
3568
|
}
|
|
3408
|
-
if (
|
|
3569
|
+
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3409
3570
|
throw new Error(
|
|
3410
|
-
`route '${this.#s.key}':
|
|
3571
|
+
`route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3411
3572
|
);
|
|
3412
3573
|
}
|
|
3413
|
-
if (next.#s.
|
|
3414
|
-
throw new Error(
|
|
3574
|
+
if (next.#s.minPrice !== void 0 && !isPositiveDecimal(next.#s.minPrice)) {
|
|
3575
|
+
throw new Error(
|
|
3576
|
+
`route '${this.#s.key}': minPrice '${next.#s.minPrice}' must be a positive decimal string`
|
|
3577
|
+
);
|
|
3415
3578
|
}
|
|
3416
|
-
if (next.#s.
|
|
3417
|
-
throw new Error(
|
|
3579
|
+
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3580
|
+
throw new Error(
|
|
3581
|
+
`route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
3582
|
+
);
|
|
3418
3583
|
}
|
|
3419
3584
|
return next;
|
|
3420
3585
|
}
|
|
@@ -3697,13 +3862,13 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3697
3862
|
/**
|
|
3698
3863
|
* Register a streaming handler (`async function*`) and return the Next.js
|
|
3699
3864
|
* route function. Each `charge()` call bills one tick (`tickCost` USDC) up
|
|
3700
|
-
* to `maxPrice`; requires `.
|
|
3865
|
+
* to `maxPrice`; requires `.metered({ ... })` and MPP session mode.
|
|
3701
3866
|
*
|
|
3702
3867
|
* @example
|
|
3703
3868
|
* ```ts
|
|
3704
3869
|
* export const POST = router
|
|
3705
3870
|
* .route('llm/stream')
|
|
3706
|
-
* .
|
|
3871
|
+
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3707
3872
|
* .body(schema)
|
|
3708
3873
|
* .stream(async function* ({ body, charge }) {
|
|
3709
3874
|
* for await (const token of streamLLM(body.prompt)) {
|
|
@@ -3719,7 +3884,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3719
3884
|
register(handlerFn, streaming) {
|
|
3720
3885
|
if (!this.#s.authMode) {
|
|
3721
3886
|
throw new Error(
|
|
3722
|
-
`route '${this.#s.key}': Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
3887
|
+
`route '${this.#s.key}': Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
3723
3888
|
);
|
|
3724
3889
|
}
|
|
3725
3890
|
if (this.#s.validateFn && !this.#s.bodySchema) {
|
|
@@ -3730,24 +3895,34 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3730
3895
|
if (this.#s.settlement && !this.#s.pricing) {
|
|
3731
3896
|
throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
|
|
3732
3897
|
}
|
|
3733
|
-
if (this.#s.
|
|
3898
|
+
if (this.#s.billing === "upto") {
|
|
3734
3899
|
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
3735
3900
|
if (!hasUpto) {
|
|
3736
3901
|
throw new Error(
|
|
3737
|
-
`route '${this.#s.key}': .
|
|
3902
|
+
`route '${this.#s.key}': .upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
|
|
3738
3903
|
);
|
|
3739
3904
|
}
|
|
3740
3905
|
}
|
|
3741
|
-
if (this.#s.
|
|
3906
|
+
if (this.#s.pricing !== void 0 && this.#s.billing === "exact" && this.#s.protocols.includes("x402")) {
|
|
3907
|
+
const hasExact = this.#s.deps.x402Accepts.some(
|
|
3908
|
+
(accept) => (accept.scheme ?? "exact") !== "upto"
|
|
3909
|
+
);
|
|
3910
|
+
if (!hasExact) {
|
|
3911
|
+
throw new Error(
|
|
3912
|
+
`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.`
|
|
3913
|
+
);
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
if (this.#s.billing === "metered") {
|
|
3742
3917
|
if (!this.#s.deps.mppSessionConfig) {
|
|
3743
3918
|
throw new Error(
|
|
3744
|
-
`route '${this.#s.key}': .
|
|
3919
|
+
`route '${this.#s.key}': .metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
|
|
3745
3920
|
);
|
|
3746
3921
|
}
|
|
3747
3922
|
}
|
|
3748
|
-
if (streaming &&
|
|
3923
|
+
if (streaming && this.#s.billing !== "metered") {
|
|
3749
3924
|
throw new Error(
|
|
3750
|
-
`route '${this.#s.key}': .stream() requires .
|
|
3925
|
+
`route '${this.#s.key}': .stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
3751
3926
|
);
|
|
3752
3927
|
}
|
|
3753
3928
|
validateExamples(
|
|
@@ -3765,7 +3940,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3765
3940
|
authMode: this.#s.authMode,
|
|
3766
3941
|
siwxEnabled: this.#s.siwxEnabled,
|
|
3767
3942
|
pricing: this.#s.pricing,
|
|
3768
|
-
|
|
3943
|
+
billing: this.#s.billing,
|
|
3769
3944
|
streaming: streaming ? true : void 0,
|
|
3770
3945
|
protocols: this.#s.protocols,
|
|
3771
3946
|
bodySchema: this.#s.bodySchema,
|
|
@@ -3792,20 +3967,63 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3792
3967
|
return createRequestHandler(entry, handlerFn, this.#s.deps);
|
|
3793
3968
|
}
|
|
3794
3969
|
};
|
|
3795
|
-
function
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
const opts = pricingOrOptions;
|
|
3799
|
-
if (!opts.maxPrice) {
|
|
3800
|
-
throw new Error(`route '${routeKey}': .paid({ dynamic: true }) requires maxPrice`);
|
|
3801
|
-
}
|
|
3802
|
-
return { pricing: opts.maxPrice, resolvedOptions: opts };
|
|
3970
|
+
function normalizePaidArg(routeKey, arg, options) {
|
|
3971
|
+
if (typeof arg === "string") {
|
|
3972
|
+
return { pricing: arg, resolvedOptions: options ?? {}, billing: "exact" };
|
|
3803
3973
|
}
|
|
3804
|
-
|
|
3974
|
+
if (typeof arg === "function") {
|
|
3975
|
+
return {
|
|
3976
|
+
pricing: arg,
|
|
3977
|
+
resolvedOptions: options ?? {},
|
|
3978
|
+
billing: "exact"
|
|
3979
|
+
};
|
|
3980
|
+
}
|
|
3981
|
+
if ("tiers" in arg && "field" in arg) {
|
|
3982
|
+
return {
|
|
3983
|
+
pricing: { field: arg.field, tiers: arg.tiers, default: arg.default },
|
|
3984
|
+
resolvedOptions: arg,
|
|
3985
|
+
billing: "exact"
|
|
3986
|
+
};
|
|
3987
|
+
}
|
|
3988
|
+
if ("price" in arg && typeof arg.price === "string") {
|
|
3989
|
+
return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
|
|
3990
|
+
}
|
|
3991
|
+
throw new Error(
|
|
3992
|
+
`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().`
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
function normalizeUpToArg(routeKey, arg) {
|
|
3996
|
+
const options = typeof arg === "string" ? { maxPrice: arg } : arg;
|
|
3997
|
+
if (!options.maxPrice) {
|
|
3998
|
+
throw new Error(`route '${routeKey}': .upTo() requires maxPrice`);
|
|
3999
|
+
}
|
|
4000
|
+
return {
|
|
4001
|
+
pricing: options.maxPrice,
|
|
4002
|
+
resolvedOptions: options,
|
|
4003
|
+
billing: "upto",
|
|
4004
|
+
unitType: options.unitType,
|
|
4005
|
+
maxPrice: options.maxPrice
|
|
4006
|
+
};
|
|
4007
|
+
}
|
|
4008
|
+
function normalizeMeteredArg(routeKey, options) {
|
|
4009
|
+
if (!options.maxPrice) {
|
|
4010
|
+
throw new Error(`route '${routeKey}': .metered() requires maxPrice`);
|
|
4011
|
+
}
|
|
4012
|
+
if (!options.tickCost) {
|
|
4013
|
+
throw new Error(`route '${routeKey}': .metered() requires tickCost`);
|
|
4014
|
+
}
|
|
4015
|
+
return {
|
|
4016
|
+
pricing: options.maxPrice,
|
|
4017
|
+
resolvedOptions: options,
|
|
4018
|
+
billing: "metered",
|
|
4019
|
+
tickCost: options.tickCost,
|
|
4020
|
+
unitType: options.unitType,
|
|
4021
|
+
maxPrice: options.maxPrice
|
|
4022
|
+
};
|
|
3805
4023
|
}
|
|
3806
4024
|
|
|
3807
4025
|
// src/discovery/well-known.ts
|
|
3808
|
-
var
|
|
4026
|
+
var import_server9 = require("next/server");
|
|
3809
4027
|
|
|
3810
4028
|
// src/discovery/utils/guidance.ts
|
|
3811
4029
|
async function resolveGuidance(discovery) {
|
|
@@ -3849,7 +4067,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
3849
4067
|
if (instructions) {
|
|
3850
4068
|
body.instructions = instructions;
|
|
3851
4069
|
}
|
|
3852
|
-
return
|
|
4070
|
+
return import_server9.NextResponse.json(body, {
|
|
3853
4071
|
headers: {
|
|
3854
4072
|
"Access-Control-Allow-Origin": "*",
|
|
3855
4073
|
"Access-Control-Allow-Methods": "GET",
|
|
@@ -3866,14 +4084,14 @@ function toDiscoveryResource(method, url, mode) {
|
|
|
3866
4084
|
}
|
|
3867
4085
|
|
|
3868
4086
|
// src/discovery/openapi.ts
|
|
3869
|
-
var
|
|
4087
|
+
var import_server10 = require("next/server");
|
|
3870
4088
|
init_constants();
|
|
3871
4089
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
3872
4090
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
3873
4091
|
let cached = null;
|
|
3874
4092
|
let validated = false;
|
|
3875
4093
|
return async (_request) => {
|
|
3876
|
-
if (cached) return
|
|
4094
|
+
if (cached) return import_server10.NextResponse.json(cached);
|
|
3877
4095
|
if (!validated && pricesKeys) {
|
|
3878
4096
|
registry.validate(pricesKeys);
|
|
3879
4097
|
validated = true;
|
|
@@ -3936,7 +4154,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
3936
4154
|
paths
|
|
3937
4155
|
};
|
|
3938
4156
|
cached = createDocument(openApiDocument);
|
|
3939
|
-
return
|
|
4157
|
+
return import_server10.NextResponse.json(cached);
|
|
3940
4158
|
};
|
|
3941
4159
|
}
|
|
3942
4160
|
function deriveTag(routeKey) {
|
|
@@ -4072,11 +4290,11 @@ function tierExtrema(prices) {
|
|
|
4072
4290
|
}
|
|
4073
4291
|
|
|
4074
4292
|
// src/discovery/llms-txt.ts
|
|
4075
|
-
var
|
|
4293
|
+
var import_server11 = require("next/server");
|
|
4076
4294
|
function createLlmsTxtHandler(discovery) {
|
|
4077
4295
|
return async (_request) => {
|
|
4078
4296
|
const guidance = await resolveGuidance(discovery) ?? "";
|
|
4079
|
-
return new
|
|
4297
|
+
return new import_server11.NextResponse(guidance, {
|
|
4080
4298
|
headers: {
|
|
4081
4299
|
"Content-Type": "text/plain; charset=utf-8",
|
|
4082
4300
|
"Access-Control-Allow-Origin": "*"
|
|
@@ -4126,10 +4344,14 @@ var isPlaceholderEvm = (v) => ZERO_EVM_ADDRESS_RE.test(v);
|
|
|
4126
4344
|
var isSolanaAddress = (v) => SOLANA_ADDRESS_RE.test(v);
|
|
4127
4345
|
var isX402Network = (v) => v.startsWith("eip155:") || v.startsWith("solana:");
|
|
4128
4346
|
var canonicalizeEvm = (addr) => addr.toLowerCase();
|
|
4347
|
+
function evmAddressFromKey(key) {
|
|
4348
|
+
if (!key || !isEvmPrivateKey(key)) return null;
|
|
4349
|
+
return (0, import_accounts.privateKeyToAccount)(key).address.toLowerCase();
|
|
4350
|
+
}
|
|
4129
4351
|
function operatorAddressesCollide(opKey, fpKey) {
|
|
4130
|
-
|
|
4131
|
-
const
|
|
4132
|
-
|
|
4352
|
+
const op = evmAddressFromKey(opKey);
|
|
4353
|
+
const fp = evmAddressFromKey(fpKey);
|
|
4354
|
+
if (!op || !fp) return null;
|
|
4133
4355
|
return op === fp ? op : null;
|
|
4134
4356
|
}
|
|
4135
4357
|
function trimAll(raw) {
|
|
@@ -4291,7 +4513,7 @@ function getConfiguredX402Accepts2(config) {
|
|
|
4291
4513
|
}
|
|
4292
4514
|
];
|
|
4293
4515
|
}
|
|
4294
|
-
function validateX402Config(config, env
|
|
4516
|
+
function validateX402Config(config, env) {
|
|
4295
4517
|
const accepts = getConfiguredX402Accepts2(config);
|
|
4296
4518
|
const issues = [];
|
|
4297
4519
|
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
@@ -4333,23 +4555,21 @@ function validateX402Config(config, env, options) {
|
|
|
4333
4555
|
`x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4334
4556
|
);
|
|
4335
4557
|
}
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
);
|
|
4347
|
-
}
|
|
4558
|
+
const hasEvm = accepts.some(
|
|
4559
|
+
(a) => typeof a.network === "string" && a.network.startsWith("eip155:")
|
|
4560
|
+
);
|
|
4561
|
+
if (hasEvm) {
|
|
4562
|
+
const missing = ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"].filter((k) => !env[k]);
|
|
4563
|
+
if (missing.length > 0) {
|
|
4564
|
+
push(
|
|
4565
|
+
"missing_cdp_keys",
|
|
4566
|
+
`x402 EVM facilitator (Coinbase) requires ${missing.join(" and ")}. Create an API key at https://portal.cdp.coinbase.com and set it via env.`
|
|
4567
|
+
);
|
|
4348
4568
|
}
|
|
4349
4569
|
}
|
|
4350
4570
|
return issues;
|
|
4351
4571
|
}
|
|
4352
|
-
function validateMppConfig(config) {
|
|
4572
|
+
function validateMppConfig(config, env) {
|
|
4353
4573
|
const m = config.mpp;
|
|
4354
4574
|
if (!m) {
|
|
4355
4575
|
return [
|
|
@@ -4397,7 +4617,7 @@ function validateMppConfig(config) {
|
|
|
4397
4617
|
`MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4398
4618
|
);
|
|
4399
4619
|
}
|
|
4400
|
-
if (!m.rpcUrl) {
|
|
4620
|
+
if (!m.rpcUrl && !env.TEMPO_RPC_URL) {
|
|
4401
4621
|
push(
|
|
4402
4622
|
"missing_mpp_rpc_url",
|
|
4403
4623
|
"MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
@@ -4422,6 +4642,15 @@ function validateMppConfig(config) {
|
|
|
4422
4642
|
`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).`
|
|
4423
4643
|
);
|
|
4424
4644
|
}
|
|
4645
|
+
if (m.session && recipient && isEvmAddress(recipient)) {
|
|
4646
|
+
const operatorAddress = evmAddressFromKey(m.operatorKey);
|
|
4647
|
+
if (operatorAddress && operatorAddress !== recipient.toLowerCase()) {
|
|
4648
|
+
push(
|
|
4649
|
+
"mpp_operator_recipient_mismatch",
|
|
4650
|
+
`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.`
|
|
4651
|
+
);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4425
4654
|
return issues;
|
|
4426
4655
|
}
|
|
4427
4656
|
function translateZodIssues(error) {
|
|
@@ -4550,8 +4779,8 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4550
4779
|
message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
|
|
4551
4780
|
});
|
|
4552
4781
|
}
|
|
4553
|
-
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env
|
|
4554
|
-
if (protocols.includes("mpp")) issues.push(...validateMppConfig(config));
|
|
4782
|
+
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env));
|
|
4783
|
+
if (protocols.includes("mpp")) issues.push(...validateMppConfig(config, env));
|
|
4555
4784
|
return issues;
|
|
4556
4785
|
}
|
|
4557
4786
|
|
|
@@ -4636,9 +4865,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4636
4865
|
const getClient = async () => tempoClient;
|
|
4637
4866
|
const operatorAccount = config.mpp.operatorKey ? privateKeyToAccount2(config.mpp.operatorKey) : void 0;
|
|
4638
4867
|
const feePayerAccount = config.mpp.feePayerKey ? privateKeyToAccount2(config.mpp.feePayerKey) : void 0;
|
|
4639
|
-
if (config.mpp.session && operatorAccount) {
|
|
4640
|
-
assertOperatorMatchesRecipient(config, operatorAccount.address);
|
|
4641
|
-
}
|
|
4642
4868
|
const resolvedStore = kvStore ? await createKvMppStore(kvStore) : void 0;
|
|
4643
4869
|
const realm = new URL(resolvedBaseUrl).host;
|
|
4644
4870
|
const mppConfig = config.mpp;
|
|
@@ -4676,15 +4902,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4676
4902
|
return { initError: err instanceof Error ? err.message : String(err) };
|
|
4677
4903
|
}
|
|
4678
4904
|
}
|
|
4679
|
-
function assertOperatorMatchesRecipient(config, operatorAddress) {
|
|
4680
|
-
const recipient = (config.mpp?.recipient ?? config.payeeAddress)?.toLowerCase();
|
|
4681
|
-
const opAddr = operatorAddress.toLowerCase();
|
|
4682
|
-
if (recipient && opAddr !== recipient) {
|
|
4683
|
-
throw new Error(
|
|
4684
|
-
`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}.`
|
|
4685
|
-
);
|
|
4686
|
-
}
|
|
4687
|
-
}
|
|
4688
4905
|
|
|
4689
4906
|
// src/index.ts
|
|
4690
4907
|
init_constants();
|
|
@@ -4695,10 +4912,7 @@ function createRouter(config) {
|
|
|
4695
4912
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
4696
4913
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
4697
4914
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
4698
|
-
const configIssues = getRouterConfigIssues(config, {
|
|
4699
|
-
env: process.env,
|
|
4700
|
-
requireCdpKeys: process.env.NODE_ENV === "production"
|
|
4701
|
-
});
|
|
4915
|
+
const configIssues = getRouterConfigIssues(config, { env: process.env });
|
|
4702
4916
|
const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
|
|
4703
4917
|
if (baseUrlIssue) throw new RouterConfigError([baseUrlIssue]);
|
|
4704
4918
|
const emptyProtocolsIssue = configIssues.find((issue) => issue.code === "empty_protocols");
|
|
@@ -4711,10 +4925,7 @@ function createRouter(config) {
|
|
|
4711
4925
|
const x402ConfigError = x402ConfigIssues.length > 0 ? formatRouterConfigIssues(x402ConfigIssues) : void 0;
|
|
4712
4926
|
const mppConfigError = mppConfigIssues.length > 0 ? formatRouterConfigIssues(mppConfigIssues) : void 0;
|
|
4713
4927
|
if (protocolConfigIssues.length > 0) {
|
|
4714
|
-
|
|
4715
|
-
if (process.env.NODE_ENV === "production") {
|
|
4716
|
-
throw new RouterConfigError(protocolConfigIssues);
|
|
4717
|
-
}
|
|
4928
|
+
throw new RouterConfigError(protocolConfigIssues);
|
|
4718
4929
|
}
|
|
4719
4930
|
const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
4720
4931
|
if (config.plugin?.init) {
|
|
@@ -4740,7 +4951,7 @@ function createRouter(config) {
|
|
|
4740
4951
|
x402Accepts,
|
|
4741
4952
|
mppx: null,
|
|
4742
4953
|
tempoClient: null,
|
|
4743
|
-
mppSessionConfig: config.mpp?.session ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4954
|
+
mppSessionConfig: config.mpp?.session && config.mpp.operatorKey ? { depositMultiplier: config.mpp.session.depositMultiplier ?? 10 } : null
|
|
4744
4955
|
};
|
|
4745
4956
|
deps.initPromise = (async () => {
|
|
4746
4957
|
const x402Result = await initX402(config, kvStore, x402ConfigError);
|