@agentcash/router 1.8.0 → 1.9.1
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 +61 -16
- package/dist/index.cjs +141 -104
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +141 -104
- package/package.json +9 -19
package/dist/index.js
CHANGED
|
@@ -563,7 +563,8 @@ function preflight(routeEntry, handler, deps, request) {
|
|
|
563
563
|
request,
|
|
564
564
|
meta,
|
|
565
565
|
pluginCtx,
|
|
566
|
-
report: createReporter(deps.plugin, pluginCtx, routeEntry.key)
|
|
566
|
+
report: createReporter(deps.plugin, pluginCtx, routeEntry.key),
|
|
567
|
+
query: void 0
|
|
567
568
|
};
|
|
568
569
|
}
|
|
569
570
|
function buildMeta(request, routeEntry) {
|
|
@@ -586,13 +587,19 @@ function buildMeta(request, routeEntry) {
|
|
|
586
587
|
import { NextResponse } from "next/server";
|
|
587
588
|
|
|
588
589
|
// src/pipeline/body.ts
|
|
590
|
+
var MalformedJsonError = class extends Error {
|
|
591
|
+
constructor() {
|
|
592
|
+
super("Invalid JSON");
|
|
593
|
+
this.name = "MalformedJsonError";
|
|
594
|
+
}
|
|
595
|
+
};
|
|
589
596
|
async function bufferBody(request) {
|
|
590
597
|
const text = await request.text();
|
|
591
|
-
if (!text) return void 0;
|
|
598
|
+
if (!text.trim()) return void 0;
|
|
592
599
|
try {
|
|
593
600
|
return JSON.parse(text);
|
|
594
601
|
} catch {
|
|
595
|
-
|
|
602
|
+
throw new MalformedJsonError();
|
|
596
603
|
}
|
|
597
604
|
}
|
|
598
605
|
function validateBody(parsed, schema) {
|
|
@@ -673,7 +680,18 @@ function computeQuotaLevel(remaining, warn, critical) {
|
|
|
673
680
|
// src/pipeline/steps/parse-body.ts
|
|
674
681
|
async function parseBody(ctx, request = ctx.request) {
|
|
675
682
|
if (!ctx.routeEntry.bodySchema) return { ok: true, data: void 0 };
|
|
676
|
-
|
|
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
|
+
}
|
|
677
695
|
const result = validateBody(raw, ctx.routeEntry.bodySchema);
|
|
678
696
|
if (result.success) return { ok: true, data: result.data };
|
|
679
697
|
const response = NextResponse.json(
|
|
@@ -684,6 +702,22 @@ async function parseBody(ctx, request = ctx.request) {
|
|
|
684
702
|
return { ok: false, response };
|
|
685
703
|
}
|
|
686
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
|
+
|
|
687
721
|
// src/pipeline/steps/errors.ts
|
|
688
722
|
function errorStatus(error, fallback) {
|
|
689
723
|
const status = error?.status;
|
|
@@ -698,9 +732,9 @@ function handlerFailureError(response) {
|
|
|
698
732
|
}
|
|
699
733
|
|
|
700
734
|
// src/pipeline/steps/fail.ts
|
|
701
|
-
import { NextResponse as
|
|
735
|
+
import { NextResponse as NextResponse3 } from "next/server";
|
|
702
736
|
function fail(ctx, status, message, requestBody) {
|
|
703
|
-
const response =
|
|
737
|
+
const response = NextResponse3.json({ success: false, error: message }, { status });
|
|
704
738
|
firePluginResponse(ctx, response, requestBody);
|
|
705
739
|
return response;
|
|
706
740
|
}
|
|
@@ -717,7 +751,7 @@ async function runValidate(ctx, body) {
|
|
|
717
751
|
}
|
|
718
752
|
|
|
719
753
|
// src/pipeline/flows/static/static-invoke.ts
|
|
720
|
-
import { NextResponse as
|
|
754
|
+
import { NextResponse as NextResponse4 } from "next/server";
|
|
721
755
|
|
|
722
756
|
// src/types.ts
|
|
723
757
|
var HttpError = class extends Error {
|
|
@@ -728,14 +762,6 @@ var HttpError = class extends Error {
|
|
|
728
762
|
}
|
|
729
763
|
};
|
|
730
764
|
|
|
731
|
-
// src/pipeline/steps/parse-query.ts
|
|
732
|
-
function parseQuery(request, routeEntry) {
|
|
733
|
-
if (!routeEntry.querySchema) return void 0;
|
|
734
|
-
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
735
|
-
const result = routeEntry.querySchema.safeParse(params);
|
|
736
|
-
return result.success ? result.data : params;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
765
|
// src/pipeline/flows/static/static-invoke.ts
|
|
740
766
|
function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
741
767
|
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
|
|
@@ -746,7 +772,7 @@ function invokeUnauthed(ctx, wallet, account, body) {
|
|
|
746
772
|
function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
747
773
|
return {
|
|
748
774
|
body,
|
|
749
|
-
query:
|
|
775
|
+
query: ctx.query,
|
|
750
776
|
request: ctx.request,
|
|
751
777
|
requestId: ctx.meta.requestId,
|
|
752
778
|
route: ctx.routeEntry.key,
|
|
@@ -775,14 +801,14 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
775
801
|
} catch (error) {
|
|
776
802
|
return errorResult(error);
|
|
777
803
|
}
|
|
778
|
-
const response = rawResult instanceof Response ? rawResult :
|
|
804
|
+
const response = rawResult instanceof Response ? rawResult : NextResponse4.json(rawResult);
|
|
779
805
|
return { response, rawResult };
|
|
780
806
|
}
|
|
781
807
|
function errorResult(error) {
|
|
782
808
|
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
783
809
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
784
810
|
return {
|
|
785
|
-
response:
|
|
811
|
+
response: NextResponse4.json({ success: false, error: message }, { status }),
|
|
786
812
|
rawResult: void 0,
|
|
787
813
|
handlerError: error
|
|
788
814
|
};
|
|
@@ -1195,9 +1221,10 @@ var DynamicPricing = class {
|
|
|
1195
1221
|
}
|
|
1196
1222
|
needsBody = true;
|
|
1197
1223
|
async quote(body) {
|
|
1224
|
+
let priced;
|
|
1198
1225
|
try {
|
|
1199
1226
|
const raw = await this.opts.fn(body);
|
|
1200
|
-
|
|
1227
|
+
priced = this.cap(raw, body);
|
|
1201
1228
|
} catch (err) {
|
|
1202
1229
|
if (err instanceof HttpError) throw err;
|
|
1203
1230
|
this.alert("error", `Pricing function failed: ${msg(err)}`, {
|
|
@@ -1210,6 +1237,13 @@ var DynamicPricing = class {
|
|
|
1210
1237
|
}
|
|
1211
1238
|
throw err;
|
|
1212
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;
|
|
1213
1247
|
}
|
|
1214
1248
|
challengeQuote(body) {
|
|
1215
1249
|
if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
|
|
@@ -1284,14 +1318,14 @@ var TieredPricing = class {
|
|
|
1284
1318
|
`Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
|
|
1285
1319
|
);
|
|
1286
1320
|
}
|
|
1287
|
-
challengeQuote(body) {
|
|
1321
|
+
async challengeQuote(body) {
|
|
1288
1322
|
if (body !== void 0) {
|
|
1289
1323
|
try {
|
|
1290
|
-
return this.quote(body);
|
|
1324
|
+
return await this.quote(body);
|
|
1291
1325
|
} catch {
|
|
1292
1326
|
}
|
|
1293
1327
|
}
|
|
1294
|
-
return
|
|
1328
|
+
return this.maxTierPrice();
|
|
1295
1329
|
}
|
|
1296
1330
|
describe() {
|
|
1297
1331
|
return {
|
|
@@ -2025,7 +2059,7 @@ async function settleX402Payment(server, payload, requirements, amountOverride)
|
|
|
2025
2059
|
};
|
|
2026
2060
|
}
|
|
2027
2061
|
function tagBareDecimalAsDollars(amount) {
|
|
2028
|
-
if (/^\d
|
|
2062
|
+
if (/^\d+(?:\.\d+)?$/.test(amount)) return `$${amount}`;
|
|
2029
2063
|
return amount;
|
|
2030
2064
|
}
|
|
2031
2065
|
|
|
@@ -2272,8 +2306,8 @@ function getAllowedStrategies(allowed) {
|
|
|
2272
2306
|
return allowed.map((name) => STRATEGIES[name]);
|
|
2273
2307
|
}
|
|
2274
2308
|
|
|
2275
|
-
// src/pipeline/flows/
|
|
2276
|
-
import { NextResponse as
|
|
2309
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2310
|
+
import { NextResponse as NextResponse5 } from "next/server";
|
|
2277
2311
|
|
|
2278
2312
|
// src/pipeline/challenge-extensions.ts
|
|
2279
2313
|
init_evm();
|
|
@@ -2289,20 +2323,17 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2289
2323
|
});
|
|
2290
2324
|
const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
|
|
2291
2325
|
const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
}
|
|
2301
|
-
if (outputSchema && routeEntry.outputExample !== void 0) {
|
|
2302
|
-
config.output = { schema: outputSchema, example: routeEntry.outputExample };
|
|
2303
|
-
}
|
|
2304
|
-
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 };
|
|
2305
2335
|
}
|
|
2336
|
+
extensions = declareDiscoveryExtension(config);
|
|
2306
2337
|
} catch (err) {
|
|
2307
2338
|
ctx.report(
|
|
2308
2339
|
"warn",
|
|
@@ -2339,14 +2370,14 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2339
2370
|
return extensions;
|
|
2340
2371
|
}
|
|
2341
2372
|
|
|
2342
|
-
// src/pipeline/flows/
|
|
2343
|
-
async function
|
|
2373
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2374
|
+
async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
2344
2375
|
let challengePrice;
|
|
2345
2376
|
try {
|
|
2346
2377
|
challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
|
|
2347
2378
|
} catch (err) {
|
|
2348
2379
|
const message = errorMessage(err, "Price calculation failed");
|
|
2349
|
-
const errorResponse =
|
|
2380
|
+
const errorResponse = NextResponse5.json(
|
|
2350
2381
|
{ success: false, error: message },
|
|
2351
2382
|
{ status: errorStatus(err, 500) }
|
|
2352
2383
|
);
|
|
@@ -2355,7 +2386,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2355
2386
|
}
|
|
2356
2387
|
const extensions = await buildChallengeExtensions(ctx);
|
|
2357
2388
|
const responseBody = failure ? JSON.stringify({ error: failure.message ?? null, reason: failure.reason }) : null;
|
|
2358
|
-
const response = new
|
|
2389
|
+
const response = new NextResponse5(responseBody, {
|
|
2359
2390
|
status: 402,
|
|
2360
2391
|
headers: {
|
|
2361
2392
|
"Content-Type": "application/json",
|
|
@@ -2382,7 +2413,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2382
2413
|
const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
|
|
2383
2414
|
ctx.report("critical", message);
|
|
2384
2415
|
if (strategy.protocol === "x402") {
|
|
2385
|
-
const errorResponse =
|
|
2416
|
+
const errorResponse = NextResponse5.json(
|
|
2386
2417
|
{ success: false, error: message },
|
|
2387
2418
|
{ status: 500 }
|
|
2388
2419
|
);
|
|
@@ -2434,7 +2465,7 @@ function surrogatePriceForSkippedBody(routeEntry) {
|
|
|
2434
2465
|
}
|
|
2435
2466
|
|
|
2436
2467
|
// src/pipeline/flows/dynamic/dynamic-channel-mgmt.ts
|
|
2437
|
-
import { NextResponse as
|
|
2468
|
+
import { NextResponse as NextResponse6 } from "next/server";
|
|
2438
2469
|
async function runDynamicChannelMgmtFlow(args) {
|
|
2439
2470
|
const { ctx, strategy, account, pricing, skipBody } = args;
|
|
2440
2471
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -2453,7 +2484,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2453
2484
|
if (verifyOutcome.kind === "config") {
|
|
2454
2485
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2455
2486
|
}
|
|
2456
|
-
return
|
|
2487
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2457
2488
|
}
|
|
2458
2489
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2459
2490
|
firePaymentVerified(ctx, {
|
|
@@ -2462,7 +2493,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2462
2493
|
amount: price,
|
|
2463
2494
|
network: verifyOutcome.payment.network
|
|
2464
2495
|
});
|
|
2465
|
-
const synthetic = new
|
|
2496
|
+
const synthetic = new NextResponse6(null, { status: 200 });
|
|
2466
2497
|
const settleScope = {
|
|
2467
2498
|
wallet: verifyOutcome.wallet,
|
|
2468
2499
|
account,
|
|
@@ -2524,11 +2555,11 @@ function createChargeContext(args) {
|
|
|
2524
2555
|
}
|
|
2525
2556
|
|
|
2526
2557
|
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2527
|
-
import { NextResponse as
|
|
2558
|
+
import { NextResponse as NextResponse7 } from "next/server";
|
|
2528
2559
|
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2529
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,
|
|
@@ -2540,14 +2571,14 @@ function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
2540
2571
|
};
|
|
2541
2572
|
}
|
|
2542
2573
|
function toResponse(rawResult) {
|
|
2543
|
-
return rawResult instanceof Response ? rawResult :
|
|
2574
|
+
return rawResult instanceof Response ? rawResult : NextResponse7.json(rawResult);
|
|
2544
2575
|
}
|
|
2545
2576
|
function errorResult2(error) {
|
|
2546
2577
|
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
2547
2578
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
2548
2579
|
return {
|
|
2549
2580
|
kind: "request",
|
|
2550
|
-
response:
|
|
2581
|
+
response: NextResponse7.json({ success: false, error: message }, { status }),
|
|
2551
2582
|
rawResult: void 0,
|
|
2552
2583
|
handlerError: error
|
|
2553
2584
|
};
|
|
@@ -2750,7 +2781,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2750
2781
|
if (!incomingStrategy) {
|
|
2751
2782
|
const initError = protocolInitError(routeEntry, deps);
|
|
2752
2783
|
if (initError) return fail(ctx, 500, initError);
|
|
2753
|
-
return
|
|
2784
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2754
2785
|
}
|
|
2755
2786
|
const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
|
|
2756
2787
|
if (skipHandler) {
|
|
@@ -2777,7 +2808,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2777
2808
|
if (verifyOutcome.kind === "config") {
|
|
2778
2809
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2779
2810
|
}
|
|
2780
|
-
return
|
|
2811
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2781
2812
|
}
|
|
2782
2813
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2783
2814
|
firePaymentVerified(ctx, {
|
|
@@ -2921,7 +2952,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2921
2952
|
if (!incomingStrategy) {
|
|
2922
2953
|
const initError = protocolInitError(routeEntry, deps);
|
|
2923
2954
|
if (initError) return fail(ctx, 500, initError);
|
|
2924
|
-
return
|
|
2955
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2925
2956
|
}
|
|
2926
2957
|
const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
|
|
2927
2958
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
@@ -2938,7 +2969,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2938
2969
|
if (verifyOutcome.kind === "config") {
|
|
2939
2970
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2940
2971
|
}
|
|
2941
|
-
return
|
|
2972
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2942
2973
|
}
|
|
2943
2974
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2944
2975
|
firePaymentVerified(ctx, {
|
|
@@ -2972,7 +3003,7 @@ async function runPaidFlow(ctx) {
|
|
|
2972
3003
|
}
|
|
2973
3004
|
|
|
2974
3005
|
// src/pipeline/flows/siwx-only.ts
|
|
2975
|
-
import { NextResponse as
|
|
3006
|
+
import { NextResponse as NextResponse8 } from "next/server";
|
|
2976
3007
|
|
|
2977
3008
|
// src/kv-store/client.ts
|
|
2978
3009
|
var BIGINT_SUFFIX = "#__bigint";
|
|
@@ -3206,7 +3237,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3206
3237
|
}
|
|
3207
3238
|
const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
|
|
3208
3239
|
if (!siwx.valid) {
|
|
3209
|
-
const response =
|
|
3240
|
+
const response = NextResponse8.json(
|
|
3210
3241
|
{ error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
|
|
3211
3242
|
{ status: 402 }
|
|
3212
3243
|
);
|
|
@@ -3267,7 +3298,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3267
3298
|
`SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3268
3299
|
);
|
|
3269
3300
|
}
|
|
3270
|
-
const response = new
|
|
3301
|
+
const response = new NextResponse8(JSON.stringify(paymentRequired), {
|
|
3271
3302
|
status: 402,
|
|
3272
3303
|
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
3273
3304
|
});
|
|
@@ -3313,6 +3344,9 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
3313
3344
|
return async (request) => {
|
|
3314
3345
|
await deps.initPromise;
|
|
3315
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;
|
|
3316
3350
|
if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
|
|
3317
3351
|
if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
|
|
3318
3352
|
if (routeEntry.pricing) return runPaidFlow(ctx);
|
|
@@ -3486,11 +3520,21 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3486
3520
|
}
|
|
3487
3521
|
}
|
|
3488
3522
|
}
|
|
3523
|
+
if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
|
|
3524
|
+
throw new Error(
|
|
3525
|
+
`route '${this.#s.key}': price '${pricing}' must be a positive decimal string`
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3489
3528
|
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3490
3529
|
throw new Error(
|
|
3491
3530
|
`route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3492
3531
|
);
|
|
3493
3532
|
}
|
|
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
|
+
);
|
|
3537
|
+
}
|
|
3494
3538
|
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3495
3539
|
throw new Error(
|
|
3496
3540
|
`route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
@@ -3938,7 +3982,7 @@ function normalizeMeteredArg(routeKey, options) {
|
|
|
3938
3982
|
}
|
|
3939
3983
|
|
|
3940
3984
|
// src/discovery/well-known.ts
|
|
3941
|
-
import { NextResponse as
|
|
3985
|
+
import { NextResponse as NextResponse9 } from "next/server";
|
|
3942
3986
|
|
|
3943
3987
|
// src/discovery/utils/guidance.ts
|
|
3944
3988
|
async function resolveGuidance(discovery) {
|
|
@@ -3982,7 +4026,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
3982
4026
|
if (instructions) {
|
|
3983
4027
|
body.instructions = instructions;
|
|
3984
4028
|
}
|
|
3985
|
-
return
|
|
4029
|
+
return NextResponse9.json(body, {
|
|
3986
4030
|
headers: {
|
|
3987
4031
|
"Access-Control-Allow-Origin": "*",
|
|
3988
4032
|
"Access-Control-Allow-Methods": "GET",
|
|
@@ -4000,13 +4044,13 @@ function toDiscoveryResource(method, url, mode) {
|
|
|
4000
4044
|
|
|
4001
4045
|
// src/discovery/openapi.ts
|
|
4002
4046
|
init_constants();
|
|
4003
|
-
import { NextResponse as
|
|
4047
|
+
import { NextResponse as NextResponse10 } from "next/server";
|
|
4004
4048
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
4005
4049
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
4006
4050
|
let cached = null;
|
|
4007
4051
|
let validated = false;
|
|
4008
4052
|
return async (_request) => {
|
|
4009
|
-
if (cached) return
|
|
4053
|
+
if (cached) return NextResponse10.json(cached);
|
|
4010
4054
|
if (!validated && pricesKeys) {
|
|
4011
4055
|
registry.validate(pricesKeys);
|
|
4012
4056
|
validated = true;
|
|
@@ -4069,7 +4113,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
4069
4113
|
paths
|
|
4070
4114
|
};
|
|
4071
4115
|
cached = createDocument(openApiDocument);
|
|
4072
|
-
return
|
|
4116
|
+
return NextResponse10.json(cached);
|
|
4073
4117
|
};
|
|
4074
4118
|
}
|
|
4075
4119
|
function deriveTag(routeKey) {
|
|
@@ -4205,11 +4249,11 @@ function tierExtrema(prices) {
|
|
|
4205
4249
|
}
|
|
4206
4250
|
|
|
4207
4251
|
// src/discovery/llms-txt.ts
|
|
4208
|
-
import { NextResponse as
|
|
4252
|
+
import { NextResponse as NextResponse11 } from "next/server";
|
|
4209
4253
|
function createLlmsTxtHandler(discovery) {
|
|
4210
4254
|
return async (_request) => {
|
|
4211
4255
|
const guidance = await resolveGuidance(discovery) ?? "";
|
|
4212
|
-
return new
|
|
4256
|
+
return new NextResponse11(guidance, {
|
|
4213
4257
|
headers: {
|
|
4214
4258
|
"Content-Type": "text/plain; charset=utf-8",
|
|
4215
4259
|
"Access-Control-Allow-Origin": "*"
|
|
@@ -4259,10 +4303,14 @@ var isPlaceholderEvm = (v) => ZERO_EVM_ADDRESS_RE.test(v);
|
|
|
4259
4303
|
var isSolanaAddress = (v) => SOLANA_ADDRESS_RE.test(v);
|
|
4260
4304
|
var isX402Network = (v) => v.startsWith("eip155:") || v.startsWith("solana:");
|
|
4261
4305
|
var canonicalizeEvm = (addr) => addr.toLowerCase();
|
|
4306
|
+
function evmAddressFromKey(key) {
|
|
4307
|
+
if (!key || !isEvmPrivateKey(key)) return null;
|
|
4308
|
+
return privateKeyToAccount(key).address.toLowerCase();
|
|
4309
|
+
}
|
|
4262
4310
|
function operatorAddressesCollide(opKey, fpKey) {
|
|
4263
|
-
|
|
4264
|
-
const
|
|
4265
|
-
|
|
4311
|
+
const op = evmAddressFromKey(opKey);
|
|
4312
|
+
const fp = evmAddressFromKey(fpKey);
|
|
4313
|
+
if (!op || !fp) return null;
|
|
4266
4314
|
return op === fp ? op : null;
|
|
4267
4315
|
}
|
|
4268
4316
|
function trimAll(raw) {
|
|
@@ -4424,7 +4472,7 @@ function getConfiguredX402Accepts2(config) {
|
|
|
4424
4472
|
}
|
|
4425
4473
|
];
|
|
4426
4474
|
}
|
|
4427
|
-
function validateX402Config(config, env
|
|
4475
|
+
function validateX402Config(config, env) {
|
|
4428
4476
|
const accepts = getConfiguredX402Accepts2(config);
|
|
4429
4477
|
const issues = [];
|
|
4430
4478
|
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
@@ -4466,23 +4514,21 @@ function validateX402Config(config, env, options) {
|
|
|
4466
4514
|
`x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4467
4515
|
);
|
|
4468
4516
|
}
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
);
|
|
4480
|
-
}
|
|
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
|
+
);
|
|
4481
4527
|
}
|
|
4482
4528
|
}
|
|
4483
4529
|
return issues;
|
|
4484
4530
|
}
|
|
4485
|
-
function validateMppConfig(config) {
|
|
4531
|
+
function validateMppConfig(config, env) {
|
|
4486
4532
|
const m = config.mpp;
|
|
4487
4533
|
if (!m) {
|
|
4488
4534
|
return [
|
|
@@ -4530,7 +4576,7 @@ function validateMppConfig(config) {
|
|
|
4530
4576
|
`MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4531
4577
|
);
|
|
4532
4578
|
}
|
|
4533
|
-
if (!m.rpcUrl) {
|
|
4579
|
+
if (!m.rpcUrl && !env.TEMPO_RPC_URL) {
|
|
4534
4580
|
push(
|
|
4535
4581
|
"missing_mpp_rpc_url",
|
|
4536
4582
|
"MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
@@ -4555,6 +4601,15 @@ function validateMppConfig(config) {
|
|
|
4555
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).`
|
|
4556
4602
|
);
|
|
4557
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
|
+
}
|
|
4558
4613
|
return issues;
|
|
4559
4614
|
}
|
|
4560
4615
|
function translateZodIssues(error) {
|
|
@@ -4683,8 +4738,8 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4683
4738
|
message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
|
|
4684
4739
|
});
|
|
4685
4740
|
}
|
|
4686
|
-
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env
|
|
4687
|
-
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));
|
|
4688
4743
|
return issues;
|
|
4689
4744
|
}
|
|
4690
4745
|
|
|
@@ -4769,9 +4824,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4769
4824
|
const getClient = async () => tempoClient;
|
|
4770
4825
|
const operatorAccount = config.mpp.operatorKey ? privateKeyToAccount2(config.mpp.operatorKey) : void 0;
|
|
4771
4826
|
const feePayerAccount = config.mpp.feePayerKey ? privateKeyToAccount2(config.mpp.feePayerKey) : void 0;
|
|
4772
|
-
if (config.mpp.session && operatorAccount) {
|
|
4773
|
-
assertOperatorMatchesRecipient(config, operatorAccount.address);
|
|
4774
|
-
}
|
|
4775
4827
|
const resolvedStore = kvStore ? await createKvMppStore(kvStore) : void 0;
|
|
4776
4828
|
const realm = new URL(resolvedBaseUrl).host;
|
|
4777
4829
|
const mppConfig = config.mpp;
|
|
@@ -4809,15 +4861,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4809
4861
|
return { initError: err instanceof Error ? err.message : String(err) };
|
|
4810
4862
|
}
|
|
4811
4863
|
}
|
|
4812
|
-
function assertOperatorMatchesRecipient(config, operatorAddress) {
|
|
4813
|
-
const recipient = (config.mpp?.recipient ?? config.payeeAddress)?.toLowerCase();
|
|
4814
|
-
const opAddr = operatorAddress.toLowerCase();
|
|
4815
|
-
if (recipient && opAddr !== recipient) {
|
|
4816
|
-
throw new Error(
|
|
4817
|
-
`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}.`
|
|
4818
|
-
);
|
|
4819
|
-
}
|
|
4820
|
-
}
|
|
4821
4864
|
|
|
4822
4865
|
// src/index.ts
|
|
4823
4866
|
init_constants();
|
|
@@ -4828,10 +4871,7 @@ function createRouter(config) {
|
|
|
4828
4871
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
4829
4872
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
4830
4873
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
4831
|
-
const configIssues = getRouterConfigIssues(config, {
|
|
4832
|
-
env: process.env,
|
|
4833
|
-
requireCdpKeys: process.env.NODE_ENV === "production"
|
|
4834
|
-
});
|
|
4874
|
+
const configIssues = getRouterConfigIssues(config, { env: process.env });
|
|
4835
4875
|
const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
|
|
4836
4876
|
if (baseUrlIssue) throw new RouterConfigError([baseUrlIssue]);
|
|
4837
4877
|
const emptyProtocolsIssue = configIssues.find((issue) => issue.code === "empty_protocols");
|
|
@@ -4844,10 +4884,7 @@ function createRouter(config) {
|
|
|
4844
4884
|
const x402ConfigError = x402ConfigIssues.length > 0 ? formatRouterConfigIssues(x402ConfigIssues) : void 0;
|
|
4845
4885
|
const mppConfigError = mppConfigIssues.length > 0 ? formatRouterConfigIssues(mppConfigIssues) : void 0;
|
|
4846
4886
|
if (protocolConfigIssues.length > 0) {
|
|
4847
|
-
|
|
4848
|
-
if (process.env.NODE_ENV === "production") {
|
|
4849
|
-
throw new RouterConfigError(protocolConfigIssues);
|
|
4850
|
-
}
|
|
4887
|
+
throw new RouterConfigError(protocolConfigIssues);
|
|
4851
4888
|
}
|
|
4852
4889
|
const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
4853
4890
|
if (config.plugin?.init) {
|