@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.cjs
CHANGED
|
@@ -604,7 +604,8 @@ function preflight(routeEntry, handler, deps, request) {
|
|
|
604
604
|
request,
|
|
605
605
|
meta,
|
|
606
606
|
pluginCtx,
|
|
607
|
-
report: createReporter(deps.plugin, pluginCtx, routeEntry.key)
|
|
607
|
+
report: createReporter(deps.plugin, pluginCtx, routeEntry.key),
|
|
608
|
+
query: void 0
|
|
608
609
|
};
|
|
609
610
|
}
|
|
610
611
|
function buildMeta(request, routeEntry) {
|
|
@@ -627,13 +628,19 @@ function buildMeta(request, routeEntry) {
|
|
|
627
628
|
var import_server = require("next/server");
|
|
628
629
|
|
|
629
630
|
// src/pipeline/body.ts
|
|
631
|
+
var MalformedJsonError = class extends Error {
|
|
632
|
+
constructor() {
|
|
633
|
+
super("Invalid JSON");
|
|
634
|
+
this.name = "MalformedJsonError";
|
|
635
|
+
}
|
|
636
|
+
};
|
|
630
637
|
async function bufferBody(request) {
|
|
631
638
|
const text = await request.text();
|
|
632
|
-
if (!text) return void 0;
|
|
639
|
+
if (!text.trim()) return void 0;
|
|
633
640
|
try {
|
|
634
641
|
return JSON.parse(text);
|
|
635
642
|
} catch {
|
|
636
|
-
|
|
643
|
+
throw new MalformedJsonError();
|
|
637
644
|
}
|
|
638
645
|
}
|
|
639
646
|
function validateBody(parsed, schema) {
|
|
@@ -714,7 +721,18 @@ function computeQuotaLevel(remaining, warn, critical) {
|
|
|
714
721
|
// src/pipeline/steps/parse-body.ts
|
|
715
722
|
async function parseBody(ctx, request = ctx.request) {
|
|
716
723
|
if (!ctx.routeEntry.bodySchema) return { ok: true, data: void 0 };
|
|
717
|
-
|
|
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
|
+
}
|
|
718
736
|
const result = validateBody(raw, ctx.routeEntry.bodySchema);
|
|
719
737
|
if (result.success) return { ok: true, data: result.data };
|
|
720
738
|
const response = import_server.NextResponse.json(
|
|
@@ -725,6 +743,22 @@ async function parseBody(ctx, request = ctx.request) {
|
|
|
725
743
|
return { ok: false, response };
|
|
726
744
|
}
|
|
727
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
|
+
|
|
728
762
|
// src/pipeline/steps/errors.ts
|
|
729
763
|
function errorStatus(error, fallback) {
|
|
730
764
|
const status = error?.status;
|
|
@@ -739,9 +773,9 @@ function handlerFailureError(response) {
|
|
|
739
773
|
}
|
|
740
774
|
|
|
741
775
|
// src/pipeline/steps/fail.ts
|
|
742
|
-
var
|
|
776
|
+
var import_server3 = require("next/server");
|
|
743
777
|
function fail(ctx, status, message, requestBody) {
|
|
744
|
-
const response =
|
|
778
|
+
const response = import_server3.NextResponse.json({ success: false, error: message }, { status });
|
|
745
779
|
firePluginResponse(ctx, response, requestBody);
|
|
746
780
|
return response;
|
|
747
781
|
}
|
|
@@ -758,7 +792,7 @@ async function runValidate(ctx, body) {
|
|
|
758
792
|
}
|
|
759
793
|
|
|
760
794
|
// src/pipeline/flows/static/static-invoke.ts
|
|
761
|
-
var
|
|
795
|
+
var import_server4 = require("next/server");
|
|
762
796
|
|
|
763
797
|
// src/types.ts
|
|
764
798
|
var HttpError = class extends Error {
|
|
@@ -769,14 +803,6 @@ var HttpError = class extends Error {
|
|
|
769
803
|
}
|
|
770
804
|
};
|
|
771
805
|
|
|
772
|
-
// src/pipeline/steps/parse-query.ts
|
|
773
|
-
function parseQuery(request, routeEntry) {
|
|
774
|
-
if (!routeEntry.querySchema) return void 0;
|
|
775
|
-
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
776
|
-
const result = routeEntry.querySchema.safeParse(params);
|
|
777
|
-
return result.success ? result.data : params;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
806
|
// src/pipeline/flows/static/static-invoke.ts
|
|
781
807
|
function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
782
808
|
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
|
|
@@ -787,7 +813,7 @@ function invokeUnauthed(ctx, wallet, account, body) {
|
|
|
787
813
|
function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
788
814
|
return {
|
|
789
815
|
body,
|
|
790
|
-
query:
|
|
816
|
+
query: ctx.query,
|
|
791
817
|
request: ctx.request,
|
|
792
818
|
requestId: ctx.meta.requestId,
|
|
793
819
|
route: ctx.routeEntry.key,
|
|
@@ -816,14 +842,14 @@ async function runHandler(ctx, handlerCtx) {
|
|
|
816
842
|
} catch (error) {
|
|
817
843
|
return errorResult(error);
|
|
818
844
|
}
|
|
819
|
-
const response = rawResult instanceof Response ? rawResult :
|
|
845
|
+
const response = rawResult instanceof Response ? rawResult : import_server4.NextResponse.json(rawResult);
|
|
820
846
|
return { response, rawResult };
|
|
821
847
|
}
|
|
822
848
|
function errorResult(error) {
|
|
823
849
|
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
824
850
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
825
851
|
return {
|
|
826
|
-
response:
|
|
852
|
+
response: import_server4.NextResponse.json({ success: false, error: message }, { status }),
|
|
827
853
|
rawResult: void 0,
|
|
828
854
|
handlerError: error
|
|
829
855
|
};
|
|
@@ -1236,9 +1262,10 @@ var DynamicPricing = class {
|
|
|
1236
1262
|
}
|
|
1237
1263
|
needsBody = true;
|
|
1238
1264
|
async quote(body) {
|
|
1265
|
+
let priced;
|
|
1239
1266
|
try {
|
|
1240
1267
|
const raw = await this.opts.fn(body);
|
|
1241
|
-
|
|
1268
|
+
priced = this.cap(raw, body);
|
|
1242
1269
|
} catch (err) {
|
|
1243
1270
|
if (err instanceof HttpError) throw err;
|
|
1244
1271
|
this.alert("error", `Pricing function failed: ${msg(err)}`, {
|
|
@@ -1251,6 +1278,13 @@ var DynamicPricing = class {
|
|
|
1251
1278
|
}
|
|
1252
1279
|
throw err;
|
|
1253
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;
|
|
1254
1288
|
}
|
|
1255
1289
|
challengeQuote(body) {
|
|
1256
1290
|
if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
|
|
@@ -1325,14 +1359,14 @@ var TieredPricing = class {
|
|
|
1325
1359
|
`Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
|
|
1326
1360
|
);
|
|
1327
1361
|
}
|
|
1328
|
-
challengeQuote(body) {
|
|
1362
|
+
async challengeQuote(body) {
|
|
1329
1363
|
if (body !== void 0) {
|
|
1330
1364
|
try {
|
|
1331
|
-
return this.quote(body);
|
|
1365
|
+
return await this.quote(body);
|
|
1332
1366
|
} catch {
|
|
1333
1367
|
}
|
|
1334
1368
|
}
|
|
1335
|
-
return
|
|
1369
|
+
return this.maxTierPrice();
|
|
1336
1370
|
}
|
|
1337
1371
|
describe() {
|
|
1338
1372
|
return {
|
|
@@ -2066,7 +2100,7 @@ async function settleX402Payment(server, payload, requirements, amountOverride)
|
|
|
2066
2100
|
};
|
|
2067
2101
|
}
|
|
2068
2102
|
function tagBareDecimalAsDollars(amount) {
|
|
2069
|
-
if (/^\d
|
|
2103
|
+
if (/^\d+(?:\.\d+)?$/.test(amount)) return `$${amount}`;
|
|
2070
2104
|
return amount;
|
|
2071
2105
|
}
|
|
2072
2106
|
|
|
@@ -2313,8 +2347,8 @@ function getAllowedStrategies(allowed) {
|
|
|
2313
2347
|
return allowed.map((name) => STRATEGIES[name]);
|
|
2314
2348
|
}
|
|
2315
2349
|
|
|
2316
|
-
// src/pipeline/flows/
|
|
2317
|
-
var
|
|
2350
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2351
|
+
var import_server5 = require("next/server");
|
|
2318
2352
|
|
|
2319
2353
|
// src/pipeline/challenge-extensions.ts
|
|
2320
2354
|
init_evm();
|
|
@@ -2330,20 +2364,17 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2330
2364
|
});
|
|
2331
2365
|
const inputSchema = routeEntry.bodySchema ? toJSON(routeEntry.bodySchema) : routeEntry.querySchema ? toJSON(routeEntry.querySchema) : void 0;
|
|
2332
2366
|
const outputSchema = routeEntry.outputSchema ? toJSON(routeEntry.outputSchema) : void 0;
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
}
|
|
2342
|
-
if (outputSchema && routeEntry.outputExample !== void 0) {
|
|
2343
|
-
config.output = { schema: outputSchema, example: routeEntry.outputExample };
|
|
2344
|
-
}
|
|
2345
|
-
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 };
|
|
2346
2376
|
}
|
|
2377
|
+
extensions = declareDiscoveryExtension(config);
|
|
2347
2378
|
} catch (err) {
|
|
2348
2379
|
ctx.report(
|
|
2349
2380
|
"warn",
|
|
@@ -2380,14 +2411,14 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2380
2411
|
return extensions;
|
|
2381
2412
|
}
|
|
2382
2413
|
|
|
2383
|
-
// src/pipeline/flows/
|
|
2384
|
-
async function
|
|
2414
|
+
// src/pipeline/flows/challenge-response.ts
|
|
2415
|
+
async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
2385
2416
|
let challengePrice;
|
|
2386
2417
|
try {
|
|
2387
2418
|
challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
|
|
2388
2419
|
} catch (err) {
|
|
2389
2420
|
const message = errorMessage(err, "Price calculation failed");
|
|
2390
|
-
const errorResponse =
|
|
2421
|
+
const errorResponse = import_server5.NextResponse.json(
|
|
2391
2422
|
{ success: false, error: message },
|
|
2392
2423
|
{ status: errorStatus(err, 500) }
|
|
2393
2424
|
);
|
|
@@ -2396,7 +2427,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2396
2427
|
}
|
|
2397
2428
|
const extensions = await buildChallengeExtensions(ctx);
|
|
2398
2429
|
const responseBody = failure ? JSON.stringify({ error: failure.message ?? null, reason: failure.reason }) : null;
|
|
2399
|
-
const response = new
|
|
2430
|
+
const response = new import_server5.NextResponse(responseBody, {
|
|
2400
2431
|
status: 402,
|
|
2401
2432
|
headers: {
|
|
2402
2433
|
"Content-Type": "application/json",
|
|
@@ -2423,7 +2454,7 @@ async function build402(ctx, pricing, body, failure) {
|
|
|
2423
2454
|
const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
|
|
2424
2455
|
ctx.report("critical", message);
|
|
2425
2456
|
if (strategy.protocol === "x402") {
|
|
2426
|
-
const errorResponse =
|
|
2457
|
+
const errorResponse = import_server5.NextResponse.json(
|
|
2427
2458
|
{ success: false, error: message },
|
|
2428
2459
|
{ status: 500 }
|
|
2429
2460
|
);
|
|
@@ -2475,7 +2506,7 @@ function surrogatePriceForSkippedBody(routeEntry) {
|
|
|
2475
2506
|
}
|
|
2476
2507
|
|
|
2477
2508
|
// src/pipeline/flows/dynamic/dynamic-channel-mgmt.ts
|
|
2478
|
-
var
|
|
2509
|
+
var import_server6 = require("next/server");
|
|
2479
2510
|
async function runDynamicChannelMgmtFlow(args) {
|
|
2480
2511
|
const { ctx, strategy, account, pricing, skipBody } = args;
|
|
2481
2512
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -2494,7 +2525,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2494
2525
|
if (verifyOutcome.kind === "config") {
|
|
2495
2526
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2496
2527
|
}
|
|
2497
|
-
return
|
|
2528
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2498
2529
|
}
|
|
2499
2530
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2500
2531
|
firePaymentVerified(ctx, {
|
|
@@ -2503,7 +2534,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2503
2534
|
amount: price,
|
|
2504
2535
|
network: verifyOutcome.payment.network
|
|
2505
2536
|
});
|
|
2506
|
-
const synthetic = new
|
|
2537
|
+
const synthetic = new import_server6.NextResponse(null, { status: 200 });
|
|
2507
2538
|
const settleScope = {
|
|
2508
2539
|
wallet: verifyOutcome.wallet,
|
|
2509
2540
|
account,
|
|
@@ -2565,11 +2596,11 @@ function createChargeContext(args) {
|
|
|
2565
2596
|
}
|
|
2566
2597
|
|
|
2567
2598
|
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2568
|
-
var
|
|
2599
|
+
var import_server7 = require("next/server");
|
|
2569
2600
|
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2570
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,
|
|
@@ -2581,14 +2612,14 @@ function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
2581
2612
|
};
|
|
2582
2613
|
}
|
|
2583
2614
|
function toResponse(rawResult) {
|
|
2584
|
-
return rawResult instanceof Response ? rawResult :
|
|
2615
|
+
return rawResult instanceof Response ? rawResult : import_server7.NextResponse.json(rawResult);
|
|
2585
2616
|
}
|
|
2586
2617
|
function errorResult2(error) {
|
|
2587
2618
|
const status = error instanceof HttpError ? error.status : typeof error?.status === "number" ? error.status : 500;
|
|
2588
2619
|
const message = error instanceof Error ? error.message : "Internal error";
|
|
2589
2620
|
return {
|
|
2590
2621
|
kind: "request",
|
|
2591
|
-
response:
|
|
2622
|
+
response: import_server7.NextResponse.json({ success: false, error: message }, { status }),
|
|
2592
2623
|
rawResult: void 0,
|
|
2593
2624
|
handlerError: error
|
|
2594
2625
|
};
|
|
@@ -2791,7 +2822,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2791
2822
|
if (!incomingStrategy) {
|
|
2792
2823
|
const initError = protocolInitError(routeEntry, deps);
|
|
2793
2824
|
if (initError) return fail(ctx, 500, initError);
|
|
2794
|
-
return
|
|
2825
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2795
2826
|
}
|
|
2796
2827
|
const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
|
|
2797
2828
|
if (skipHandler) {
|
|
@@ -2818,7 +2849,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2818
2849
|
if (verifyOutcome.kind === "config") {
|
|
2819
2850
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2820
2851
|
}
|
|
2821
|
-
return
|
|
2852
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2822
2853
|
}
|
|
2823
2854
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2824
2855
|
firePaymentVerified(ctx, {
|
|
@@ -2962,7 +2993,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2962
2993
|
if (!incomingStrategy) {
|
|
2963
2994
|
const initError = protocolInitError(routeEntry, deps);
|
|
2964
2995
|
if (initError) return fail(ctx, 500, initError);
|
|
2965
|
-
return
|
|
2996
|
+
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
2966
2997
|
}
|
|
2967
2998
|
const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
|
|
2968
2999
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
@@ -2979,7 +3010,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2979
3010
|
if (verifyOutcome.kind === "config") {
|
|
2980
3011
|
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
2981
3012
|
}
|
|
2982
|
-
return
|
|
3013
|
+
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2983
3014
|
}
|
|
2984
3015
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2985
3016
|
firePaymentVerified(ctx, {
|
|
@@ -3013,7 +3044,7 @@ async function runPaidFlow(ctx) {
|
|
|
3013
3044
|
}
|
|
3014
3045
|
|
|
3015
3046
|
// src/pipeline/flows/siwx-only.ts
|
|
3016
|
-
var
|
|
3047
|
+
var import_server8 = require("next/server");
|
|
3017
3048
|
|
|
3018
3049
|
// src/kv-store/client.ts
|
|
3019
3050
|
var BIGINT_SUFFIX = "#__bigint";
|
|
@@ -3247,7 +3278,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3247
3278
|
}
|
|
3248
3279
|
const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
|
|
3249
3280
|
if (!siwx.valid) {
|
|
3250
|
-
const response =
|
|
3281
|
+
const response = import_server8.NextResponse.json(
|
|
3251
3282
|
{ error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
|
|
3252
3283
|
{ status: 402 }
|
|
3253
3284
|
);
|
|
@@ -3308,7 +3339,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3308
3339
|
`SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3309
3340
|
);
|
|
3310
3341
|
}
|
|
3311
|
-
const response = new
|
|
3342
|
+
const response = new import_server8.NextResponse(JSON.stringify(paymentRequired), {
|
|
3312
3343
|
status: 402,
|
|
3313
3344
|
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
3314
3345
|
});
|
|
@@ -3354,6 +3385,9 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
3354
3385
|
return async (request) => {
|
|
3355
3386
|
await deps.initPromise;
|
|
3356
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;
|
|
3357
3391
|
if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
|
|
3358
3392
|
if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
|
|
3359
3393
|
if (routeEntry.pricing) return runPaidFlow(ctx);
|
|
@@ -3527,11 +3561,21 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3527
3561
|
}
|
|
3528
3562
|
}
|
|
3529
3563
|
}
|
|
3564
|
+
if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
|
|
3565
|
+
throw new Error(
|
|
3566
|
+
`route '${this.#s.key}': price '${pricing}' must be a positive decimal string`
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3530
3569
|
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3531
3570
|
throw new Error(
|
|
3532
3571
|
`route '${this.#s.key}': maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3533
3572
|
);
|
|
3534
3573
|
}
|
|
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
|
+
);
|
|
3578
|
+
}
|
|
3535
3579
|
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3536
3580
|
throw new Error(
|
|
3537
3581
|
`route '${this.#s.key}': tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
@@ -3979,7 +4023,7 @@ function normalizeMeteredArg(routeKey, options) {
|
|
|
3979
4023
|
}
|
|
3980
4024
|
|
|
3981
4025
|
// src/discovery/well-known.ts
|
|
3982
|
-
var
|
|
4026
|
+
var import_server9 = require("next/server");
|
|
3983
4027
|
|
|
3984
4028
|
// src/discovery/utils/guidance.ts
|
|
3985
4029
|
async function resolveGuidance(discovery) {
|
|
@@ -4023,7 +4067,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
4023
4067
|
if (instructions) {
|
|
4024
4068
|
body.instructions = instructions;
|
|
4025
4069
|
}
|
|
4026
|
-
return
|
|
4070
|
+
return import_server9.NextResponse.json(body, {
|
|
4027
4071
|
headers: {
|
|
4028
4072
|
"Access-Control-Allow-Origin": "*",
|
|
4029
4073
|
"Access-Control-Allow-Methods": "GET",
|
|
@@ -4040,14 +4084,14 @@ function toDiscoveryResource(method, url, mode) {
|
|
|
4040
4084
|
}
|
|
4041
4085
|
|
|
4042
4086
|
// src/discovery/openapi.ts
|
|
4043
|
-
var
|
|
4087
|
+
var import_server10 = require("next/server");
|
|
4044
4088
|
init_constants();
|
|
4045
4089
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
4046
4090
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
4047
4091
|
let cached = null;
|
|
4048
4092
|
let validated = false;
|
|
4049
4093
|
return async (_request) => {
|
|
4050
|
-
if (cached) return
|
|
4094
|
+
if (cached) return import_server10.NextResponse.json(cached);
|
|
4051
4095
|
if (!validated && pricesKeys) {
|
|
4052
4096
|
registry.validate(pricesKeys);
|
|
4053
4097
|
validated = true;
|
|
@@ -4110,7 +4154,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
4110
4154
|
paths
|
|
4111
4155
|
};
|
|
4112
4156
|
cached = createDocument(openApiDocument);
|
|
4113
|
-
return
|
|
4157
|
+
return import_server10.NextResponse.json(cached);
|
|
4114
4158
|
};
|
|
4115
4159
|
}
|
|
4116
4160
|
function deriveTag(routeKey) {
|
|
@@ -4246,11 +4290,11 @@ function tierExtrema(prices) {
|
|
|
4246
4290
|
}
|
|
4247
4291
|
|
|
4248
4292
|
// src/discovery/llms-txt.ts
|
|
4249
|
-
var
|
|
4293
|
+
var import_server11 = require("next/server");
|
|
4250
4294
|
function createLlmsTxtHandler(discovery) {
|
|
4251
4295
|
return async (_request) => {
|
|
4252
4296
|
const guidance = await resolveGuidance(discovery) ?? "";
|
|
4253
|
-
return new
|
|
4297
|
+
return new import_server11.NextResponse(guidance, {
|
|
4254
4298
|
headers: {
|
|
4255
4299
|
"Content-Type": "text/plain; charset=utf-8",
|
|
4256
4300
|
"Access-Control-Allow-Origin": "*"
|
|
@@ -4300,10 +4344,14 @@ var isPlaceholderEvm = (v) => ZERO_EVM_ADDRESS_RE.test(v);
|
|
|
4300
4344
|
var isSolanaAddress = (v) => SOLANA_ADDRESS_RE.test(v);
|
|
4301
4345
|
var isX402Network = (v) => v.startsWith("eip155:") || v.startsWith("solana:");
|
|
4302
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
|
+
}
|
|
4303
4351
|
function operatorAddressesCollide(opKey, fpKey) {
|
|
4304
|
-
|
|
4305
|
-
const
|
|
4306
|
-
|
|
4352
|
+
const op = evmAddressFromKey(opKey);
|
|
4353
|
+
const fp = evmAddressFromKey(fpKey);
|
|
4354
|
+
if (!op || !fp) return null;
|
|
4307
4355
|
return op === fp ? op : null;
|
|
4308
4356
|
}
|
|
4309
4357
|
function trimAll(raw) {
|
|
@@ -4465,7 +4513,7 @@ function getConfiguredX402Accepts2(config) {
|
|
|
4465
4513
|
}
|
|
4466
4514
|
];
|
|
4467
4515
|
}
|
|
4468
|
-
function validateX402Config(config, env
|
|
4516
|
+
function validateX402Config(config, env) {
|
|
4469
4517
|
const accepts = getConfiguredX402Accepts2(config);
|
|
4470
4518
|
const issues = [];
|
|
4471
4519
|
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
@@ -4507,23 +4555,21 @@ function validateX402Config(config, env, options) {
|
|
|
4507
4555
|
`x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4508
4556
|
);
|
|
4509
4557
|
}
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
);
|
|
4521
|
-
}
|
|
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
|
+
);
|
|
4522
4568
|
}
|
|
4523
4569
|
}
|
|
4524
4570
|
return issues;
|
|
4525
4571
|
}
|
|
4526
|
-
function validateMppConfig(config) {
|
|
4572
|
+
function validateMppConfig(config, env) {
|
|
4527
4573
|
const m = config.mpp;
|
|
4528
4574
|
if (!m) {
|
|
4529
4575
|
return [
|
|
@@ -4571,7 +4617,7 @@ function validateMppConfig(config) {
|
|
|
4571
4617
|
`MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4572
4618
|
);
|
|
4573
4619
|
}
|
|
4574
|
-
if (!m.rpcUrl) {
|
|
4620
|
+
if (!m.rpcUrl && !env.TEMPO_RPC_URL) {
|
|
4575
4621
|
push(
|
|
4576
4622
|
"missing_mpp_rpc_url",
|
|
4577
4623
|
"MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
@@ -4596,6 +4642,15 @@ function validateMppConfig(config) {
|
|
|
4596
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).`
|
|
4597
4643
|
);
|
|
4598
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
|
+
}
|
|
4599
4654
|
return issues;
|
|
4600
4655
|
}
|
|
4601
4656
|
function translateZodIssues(error) {
|
|
@@ -4724,8 +4779,8 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4724
4779
|
message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
|
|
4725
4780
|
});
|
|
4726
4781
|
}
|
|
4727
|
-
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env
|
|
4728
|
-
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));
|
|
4729
4784
|
return issues;
|
|
4730
4785
|
}
|
|
4731
4786
|
|
|
@@ -4810,9 +4865,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4810
4865
|
const getClient = async () => tempoClient;
|
|
4811
4866
|
const operatorAccount = config.mpp.operatorKey ? privateKeyToAccount2(config.mpp.operatorKey) : void 0;
|
|
4812
4867
|
const feePayerAccount = config.mpp.feePayerKey ? privateKeyToAccount2(config.mpp.feePayerKey) : void 0;
|
|
4813
|
-
if (config.mpp.session && operatorAccount) {
|
|
4814
|
-
assertOperatorMatchesRecipient(config, operatorAccount.address);
|
|
4815
|
-
}
|
|
4816
4868
|
const resolvedStore = kvStore ? await createKvMppStore(kvStore) : void 0;
|
|
4817
4869
|
const realm = new URL(resolvedBaseUrl).host;
|
|
4818
4870
|
const mppConfig = config.mpp;
|
|
@@ -4850,15 +4902,6 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
4850
4902
|
return { initError: err instanceof Error ? err.message : String(err) };
|
|
4851
4903
|
}
|
|
4852
4904
|
}
|
|
4853
|
-
function assertOperatorMatchesRecipient(config, operatorAddress) {
|
|
4854
|
-
const recipient = (config.mpp?.recipient ?? config.payeeAddress)?.toLowerCase();
|
|
4855
|
-
const opAddr = operatorAddress.toLowerCase();
|
|
4856
|
-
if (recipient && opAddr !== recipient) {
|
|
4857
|
-
throw new Error(
|
|
4858
|
-
`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}.`
|
|
4859
|
-
);
|
|
4860
|
-
}
|
|
4861
|
-
}
|
|
4862
4905
|
|
|
4863
4906
|
// src/index.ts
|
|
4864
4907
|
init_constants();
|
|
@@ -4869,10 +4912,7 @@ function createRouter(config) {
|
|
|
4869
4912
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
4870
4913
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
4871
4914
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
4872
|
-
const configIssues = getRouterConfigIssues(config, {
|
|
4873
|
-
env: process.env,
|
|
4874
|
-
requireCdpKeys: process.env.NODE_ENV === "production"
|
|
4875
|
-
});
|
|
4915
|
+
const configIssues = getRouterConfigIssues(config, { env: process.env });
|
|
4876
4916
|
const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
|
|
4877
4917
|
if (baseUrlIssue) throw new RouterConfigError([baseUrlIssue]);
|
|
4878
4918
|
const emptyProtocolsIssue = configIssues.find((issue) => issue.code === "empty_protocols");
|
|
@@ -4885,10 +4925,7 @@ function createRouter(config) {
|
|
|
4885
4925
|
const x402ConfigError = x402ConfigIssues.length > 0 ? formatRouterConfigIssues(x402ConfigIssues) : void 0;
|
|
4886
4926
|
const mppConfigError = mppConfigIssues.length > 0 ? formatRouterConfigIssues(mppConfigIssues) : void 0;
|
|
4887
4927
|
if (protocolConfigIssues.length > 0) {
|
|
4888
|
-
|
|
4889
|
-
if (process.env.NODE_ENV === "production") {
|
|
4890
|
-
throw new RouterConfigError(protocolConfigIssues);
|
|
4891
|
-
}
|
|
4928
|
+
throw new RouterConfigError(protocolConfigIssues);
|
|
4892
4929
|
}
|
|
4893
4930
|
const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
4894
4931
|
if (config.plugin?.init) {
|
package/dist/index.d.cts
CHANGED
|
@@ -381,7 +381,7 @@ interface DiscoveryConfig {
|
|
|
381
381
|
serverUrl?: string;
|
|
382
382
|
}
|
|
383
383
|
interface RouterConfig {
|
|
384
|
-
/** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via `.paid(
|
|
384
|
+
/** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via the `payTo` option on `.paid()` / `.upTo()` / `.metered()`. */
|
|
385
385
|
payeeAddress?: string;
|
|
386
386
|
/** Origin URL (required). Used as 402 realm, discovery base, OpenAPI server, and MPP memo prefix — must match the public domain or payment matching breaks. */
|
|
387
387
|
baseUrl: string;
|
|
@@ -789,7 +789,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
789
789
|
private register;
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
-
type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
|
|
792
|
+
type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'mpp_operator_recipient_mismatch' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
|
|
793
793
|
type RouterConfigIssueSeverity = 'error' | 'warning';
|
|
794
794
|
interface RouterConfigIssue {
|
|
795
795
|
code: RouterConfigIssueCode;
|
package/dist/index.d.ts
CHANGED
|
@@ -381,7 +381,7 @@ interface DiscoveryConfig {
|
|
|
381
381
|
serverUrl?: string;
|
|
382
382
|
}
|
|
383
383
|
interface RouterConfig {
|
|
384
|
-
/** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via `.paid(
|
|
384
|
+
/** Default payee for paid routes — populates `payTo` on the auto-generated x402 `exact` accept and acts as the MPP `recipient` fallback. Override per-protocol via `x402.accepts[i].payTo` / `mpp.recipient`, or per-route via the `payTo` option on `.paid()` / `.upTo()` / `.metered()`. */
|
|
385
385
|
payeeAddress?: string;
|
|
386
386
|
/** Origin URL (required). Used as 402 realm, discovery base, OpenAPI server, and MPP memo prefix — must match the public domain or payment matching breaks. */
|
|
387
387
|
baseUrl: string;
|
|
@@ -789,7 +789,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
789
789
|
private register;
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
-
type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
|
|
792
|
+
type RouterConfigIssueCode = 'missing_base_url' | 'invalid_base_url' | 'empty_protocols' | 'missing_x402_accepts' | 'missing_x402_network' | 'unsupported_x402_network' | 'missing_x402_asset' | 'invalid_x402_decimals' | 'missing_x402_payee' | 'invalid_x402_payee' | 'invalid_solana_payee' | 'invalid_solana_facilitator_url' | 'missing_cdp_keys' | 'placeholder_payee' | 'missing_mpp_config' | 'missing_mpp_secret_key' | 'missing_mpp_currency' | 'invalid_mpp_currency' | 'missing_mpp_recipient' | 'invalid_mpp_recipient' | 'missing_mpp_rpc_url' | 'invalid_mpp_rpc_url' | 'invalid_mpp_fee_payer_key' | 'invalid_mpp_operator_key' | 'mpp_operator_equals_fee_payer' | 'mpp_operator_recipient_mismatch' | 'missing_discovery_title' | 'missing_discovery_description' | 'missing_discovery_guidance' | 'invalid_server_url' | 'kv_url_without_token' | 'kv_token_without_url' | 'invalid_kv_url' | 'missing_kv_in_production';
|
|
793
793
|
type RouterConfigIssueSeverity = 'error' | 'warning';
|
|
794
794
|
interface RouterConfigIssue {
|
|
795
795
|
code: RouterConfigIssueCode;
|