@agentcash/router 1.6.0 → 1.7.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 +85 -0
- package/README.md +133 -550
- package/dist/index.cjs +567 -525
- package/dist/index.d.cts +90 -71
- package/dist/index.d.ts +90 -71
- package/dist/index.js +559 -506
- package/package.json +6 -14
- package/.claude/CLAUDE.md +0 -343
- package/.claude/skills/router-guide/SKILL.md +0 -585
package/dist/index.cjs
CHANGED
|
@@ -31,14 +31,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
33
|
// src/constants.ts
|
|
34
|
-
var
|
|
34
|
+
var BASE_MAINNET_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, ZERO_EVM_ADDRESS, DEFAULT_SOLANA_FACILITATOR_URL;
|
|
35
35
|
var init_constants = __esm({
|
|
36
36
|
"src/constants.ts"() {
|
|
37
37
|
"use strict";
|
|
38
|
-
|
|
38
|
+
BASE_MAINNET_NETWORK = "eip155:8453";
|
|
39
39
|
SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
40
|
-
|
|
40
|
+
TEMPO_USDC_ADDRESS = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
41
|
+
TEMPO_USDC_DECIMALS = 6;
|
|
42
|
+
BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
43
|
+
BASE_USDC_DECIMALS = 6;
|
|
41
44
|
ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
45
|
+
DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.corbits.dev";
|
|
42
46
|
}
|
|
43
47
|
});
|
|
44
48
|
|
|
@@ -55,7 +59,7 @@ function getConfiguredX402Accepts(config) {
|
|
|
55
59
|
return [
|
|
56
60
|
{
|
|
57
61
|
scheme: "exact",
|
|
58
|
-
network: config.network ??
|
|
62
|
+
network: config.network ?? BASE_MAINNET_NETWORK,
|
|
59
63
|
payTo: config.payeeAddress
|
|
60
64
|
}
|
|
61
65
|
];
|
|
@@ -235,7 +239,10 @@ async function getAcceptsHeadersForFacilitator(facilitator) {
|
|
|
235
239
|
return {};
|
|
236
240
|
}
|
|
237
241
|
function resolveX402FacilitatorTarget(config, network, defaultEvmFacilitator) {
|
|
238
|
-
|
|
242
|
+
if (isSolanaNetwork(network)) {
|
|
243
|
+
return config.x402?.facilitators?.solana ?? DEFAULT_SOLANA_FACILITATOR_URL;
|
|
244
|
+
}
|
|
245
|
+
return defaultEvmFacilitator;
|
|
239
246
|
}
|
|
240
247
|
function normalizeFacilitatorTarget(target) {
|
|
241
248
|
return typeof target === "string" ? { url: target } : target;
|
|
@@ -248,19 +255,18 @@ function getNetworkFamily(network) {
|
|
|
248
255
|
function sameFacilitatorConfig(a, b) {
|
|
249
256
|
return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders && a.createAcceptsHeaders === b.createAcceptsHeaders;
|
|
250
257
|
}
|
|
251
|
-
var DEFAULT_SOLANA_FACILITATOR_URL;
|
|
252
258
|
var init_facilitators = __esm({
|
|
253
259
|
"src/protocols/x402/facilitators.ts"() {
|
|
254
260
|
"use strict";
|
|
255
261
|
init_evm();
|
|
256
262
|
init_solana();
|
|
257
|
-
|
|
263
|
+
init_constants();
|
|
258
264
|
}
|
|
259
265
|
});
|
|
260
266
|
|
|
261
|
-
// src/server.ts
|
|
262
|
-
var
|
|
263
|
-
__export(
|
|
267
|
+
// src/init/x402-server.ts
|
|
268
|
+
var x402_server_exports = {};
|
|
269
|
+
__export(x402_server_exports, {
|
|
264
270
|
createX402Server: () => createX402Server
|
|
265
271
|
});
|
|
266
272
|
async function createX402Server(config) {
|
|
@@ -337,8 +343,8 @@ function buildSupportedKinds(group) {
|
|
|
337
343
|
return [exactKind, uptoKind];
|
|
338
344
|
});
|
|
339
345
|
}
|
|
340
|
-
var
|
|
341
|
-
"src/server.ts"() {
|
|
346
|
+
var init_x402_server = __esm({
|
|
347
|
+
"src/init/x402-server.ts"() {
|
|
342
348
|
"use strict";
|
|
343
349
|
init_evm();
|
|
344
350
|
init_solana();
|
|
@@ -350,30 +356,19 @@ var init_server = __esm({
|
|
|
350
356
|
// src/index.ts
|
|
351
357
|
var index_exports = {};
|
|
352
358
|
__export(index_exports, {
|
|
353
|
-
|
|
359
|
+
BASE_MAINNET_NETWORK: () => BASE_MAINNET_NETWORK,
|
|
360
|
+
BASE_USDC_ADDRESS: () => BASE_USDC_ADDRESS,
|
|
361
|
+
BASE_USDC_DECIMALS: () => BASE_USDC_DECIMALS,
|
|
362
|
+
DEFAULT_SOLANA_FACILITATOR_URL: () => DEFAULT_SOLANA_FACILITATOR_URL,
|
|
354
363
|
HttpError: () => HttpError,
|
|
355
|
-
MemoryEntitlementStore: () => MemoryEntitlementStore,
|
|
356
|
-
MemoryNonceStore: () => MemoryNonceStore,
|
|
357
|
-
RouteBuilder: () => RouteBuilder,
|
|
358
|
-
RouteRegistry: () => RouteRegistry,
|
|
359
364
|
RouterConfigError: () => RouterConfigError,
|
|
360
|
-
SIWX_CHALLENGE_EXPIRY_MS: () => SIWX_CHALLENGE_EXPIRY_MS,
|
|
361
|
-
SIWX_ERROR_MESSAGES: () => SIWX_ERROR_MESSAGES,
|
|
362
365
|
SOLANA_MAINNET_NETWORK: () => SOLANA_MAINNET_NETWORK,
|
|
363
|
-
|
|
366
|
+
TEMPO_USDC_ADDRESS: () => TEMPO_USDC_ADDRESS,
|
|
367
|
+
TEMPO_USDC_DECIMALS: () => TEMPO_USDC_DECIMALS,
|
|
364
368
|
ZERO_EVM_ADDRESS: () => ZERO_EVM_ADDRESS,
|
|
365
|
-
consolePlugin: () => consolePlugin,
|
|
366
|
-
createKvEntitlementStore: () => createKvEntitlementStore,
|
|
367
|
-
createKvMppStore: () => createKvMppStore,
|
|
368
|
-
createKvNonceStore: () => createKvNonceStore,
|
|
369
369
|
createRouter: () => createRouter,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
mppFromEnv: () => mppFromEnv,
|
|
373
|
-
paidOptionsForProtocols: () => paidOptionsForProtocols,
|
|
374
|
-
validateRouterConfig: () => validateRouterConfig,
|
|
375
|
-
withPrefix: () => withPrefix,
|
|
376
|
-
x402AcceptsFromEnv: () => x402AcceptsFromEnv
|
|
370
|
+
createRouterFromEnv: () => createRouterFromEnv,
|
|
371
|
+
routerConfigFromEnv: () => routerConfigFromEnv
|
|
377
372
|
});
|
|
378
373
|
module.exports = __toCommonJS(index_exports);
|
|
379
374
|
|
|
@@ -441,7 +436,7 @@ var AUTH_SCHEME = {
|
|
|
441
436
|
MPP_PAYMENT: "Payment "
|
|
442
437
|
};
|
|
443
438
|
|
|
444
|
-
// src/plugin.ts
|
|
439
|
+
// src/plugin/index.ts
|
|
445
440
|
function createDefaultContext(meta) {
|
|
446
441
|
const ctx = {
|
|
447
442
|
requestId: meta.requestId,
|
|
@@ -477,46 +472,8 @@ function firePluginHook(plugin, method, ...args) {
|
|
|
477
472
|
return void 0;
|
|
478
473
|
}
|
|
479
474
|
}
|
|
480
|
-
function consolePlugin() {
|
|
481
|
-
return {
|
|
482
|
-
onRequest(meta) {
|
|
483
|
-
const ctx = createDefaultContext(meta);
|
|
484
|
-
return ctx;
|
|
485
|
-
},
|
|
486
|
-
onAuthVerified(_ctx, auth) {
|
|
487
|
-
const wallet = auth.wallet ? ` wallet=${auth.wallet}` : "";
|
|
488
|
-
console.log(`[router] AUTH ${auth.authMode} ${auth.route}${wallet}`);
|
|
489
|
-
},
|
|
490
|
-
onPaymentVerified(_ctx, payment) {
|
|
491
|
-
console.log(`[router] VERIFIED ${payment.protocol} ${payment.payer} ${payment.amount}`);
|
|
492
|
-
},
|
|
493
|
-
onPaymentSettled(_ctx, settlement) {
|
|
494
|
-
console.log(`[router] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);
|
|
495
|
-
},
|
|
496
|
-
onResponse(ctx, response) {
|
|
497
|
-
const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : "";
|
|
498
|
-
console.log(
|
|
499
|
-
`[router] ${ctx.route} \u2192 ${response.statusCode} (${response.duration}ms)${wallet}`
|
|
500
|
-
);
|
|
501
|
-
},
|
|
502
|
-
onError(_ctx, error) {
|
|
503
|
-
console.error(`[router] ERROR ${error.status}: ${error.message}`);
|
|
504
|
-
},
|
|
505
|
-
onAlert(_ctx, alert) {
|
|
506
|
-
const logFn = alert.level === "critical" || alert.level === "error" ? console.error : alert.level === "warn" ? console.warn : console.log;
|
|
507
|
-
logFn(
|
|
508
|
-
`[router] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
509
|
-
alert.meta ?? ""
|
|
510
|
-
);
|
|
511
|
-
},
|
|
512
|
-
onProviderQuota(_ctx, event) {
|
|
513
|
-
const logFn = event.level === "critical" ? console.error : event.level === "warn" ? console.warn : console.log;
|
|
514
|
-
logFn(`[router] QUOTA ${event.level.toUpperCase()} ${event.provider}: ${event.message}`);
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
475
|
|
|
519
|
-
// src/
|
|
476
|
+
// src/plugin/reporter.ts
|
|
520
477
|
function createReporter(plugin, pluginCtx, route) {
|
|
521
478
|
return (level, message, meta) => {
|
|
522
479
|
firePluginHook(plugin, "onAlert", pluginCtx, {
|
|
@@ -528,7 +485,7 @@ function createReporter(plugin, pluginCtx, route) {
|
|
|
528
485
|
};
|
|
529
486
|
}
|
|
530
487
|
|
|
531
|
-
// src/pipeline/
|
|
488
|
+
// src/pipeline/steps/preflight.ts
|
|
532
489
|
function preflight(routeEntry, handler, deps, request) {
|
|
533
490
|
const meta = buildMeta(request, routeEntry);
|
|
534
491
|
const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
|
|
@@ -558,10 +515,10 @@ function buildMeta(request, routeEntry) {
|
|
|
558
515
|
};
|
|
559
516
|
}
|
|
560
517
|
|
|
561
|
-
// src/pipeline/
|
|
518
|
+
// src/pipeline/steps/parse-body.ts
|
|
562
519
|
var import_server = require("next/server");
|
|
563
520
|
|
|
564
|
-
// src/body.ts
|
|
521
|
+
// src/pipeline/body.ts
|
|
565
522
|
async function bufferBody(request) {
|
|
566
523
|
const text = await request.text();
|
|
567
524
|
if (!text) return void 0;
|
|
@@ -585,7 +542,19 @@ function validateBody(parsed, schema) {
|
|
|
585
542
|
};
|
|
586
543
|
}
|
|
587
544
|
|
|
588
|
-
// src/
|
|
545
|
+
// src/plugin/events.ts
|
|
546
|
+
function fireAuthVerified(ctx, event) {
|
|
547
|
+
firePluginHook(ctx.deps.plugin, "onAuthVerified", ctx.pluginCtx, {
|
|
548
|
+
...event,
|
|
549
|
+
route: ctx.routeEntry.key
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function firePaymentVerified(ctx, event) {
|
|
553
|
+
firePluginHook(ctx.deps.plugin, "onPaymentVerified", ctx.pluginCtx, event);
|
|
554
|
+
}
|
|
555
|
+
function firePaymentSettled(ctx, event) {
|
|
556
|
+
firePluginHook(ctx.deps.plugin, "onPaymentSettled", ctx.pluginCtx, event);
|
|
557
|
+
}
|
|
589
558
|
function firePluginResponse(ctx, response, requestBody, responseBody) {
|
|
590
559
|
firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
|
|
591
560
|
statusCode: response.status,
|
|
@@ -604,8 +573,37 @@ function firePluginResponse(ctx, response, requestBody, responseBody) {
|
|
|
604
573
|
});
|
|
605
574
|
}
|
|
606
575
|
}
|
|
576
|
+
function fireProviderQuota(ctx, response, handlerResult) {
|
|
577
|
+
const { providerName, providerConfig } = ctx.routeEntry;
|
|
578
|
+
if (!providerName || !providerConfig?.extractQuota) return;
|
|
579
|
+
if (response.status >= 400) return;
|
|
580
|
+
try {
|
|
581
|
+
const quota = providerConfig.extractQuota(handlerResult, response.headers);
|
|
582
|
+
if (!quota) return;
|
|
583
|
+
const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
|
|
584
|
+
const overage = providerConfig.overage ?? "same-rate";
|
|
585
|
+
const event = {
|
|
586
|
+
provider: providerName,
|
|
587
|
+
route: ctx.routeEntry.key,
|
|
588
|
+
remaining: quota.remaining,
|
|
589
|
+
limit: quota.limit,
|
|
590
|
+
spend: quota.spend,
|
|
591
|
+
level,
|
|
592
|
+
overage,
|
|
593
|
+
message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
|
|
594
|
+
};
|
|
595
|
+
firePluginHook(ctx.deps.plugin, "onProviderQuota", ctx.pluginCtx, event);
|
|
596
|
+
} catch {
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function computeQuotaLevel(remaining, warn, critical) {
|
|
600
|
+
if (remaining === null) return "healthy";
|
|
601
|
+
if (critical !== void 0 && remaining <= critical) return "critical";
|
|
602
|
+
if (warn !== void 0 && remaining <= warn) return "warn";
|
|
603
|
+
return "healthy";
|
|
604
|
+
}
|
|
607
605
|
|
|
608
|
-
// src/pipeline/
|
|
606
|
+
// src/pipeline/steps/parse-body.ts
|
|
609
607
|
async function parseBody(ctx, request = ctx.request) {
|
|
610
608
|
if (!ctx.routeEntry.bodySchema) return { ok: true, data: void 0 };
|
|
611
609
|
const raw = await bufferBody(request);
|
|
@@ -619,15 +617,7 @@ async function parseBody(ctx, request = ctx.request) {
|
|
|
619
617
|
return { ok: false, response };
|
|
620
618
|
}
|
|
621
619
|
|
|
622
|
-
// src/pipeline/
|
|
623
|
-
function parseQuery(request, routeEntry) {
|
|
624
|
-
if (!routeEntry.querySchema) return void 0;
|
|
625
|
-
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
626
|
-
const result = routeEntry.querySchema.safeParse(params);
|
|
627
|
-
return result.success ? result.data : params;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// src/pipeline/context/errors.ts
|
|
620
|
+
// src/pipeline/steps/errors.ts
|
|
631
621
|
function errorStatus(error, fallback) {
|
|
632
622
|
const status = error?.status;
|
|
633
623
|
return typeof status === "number" ? status : fallback;
|
|
@@ -640,7 +630,7 @@ function handlerFailureError(response) {
|
|
|
640
630
|
return Object.assign(new Error(message), { status: response.status });
|
|
641
631
|
}
|
|
642
632
|
|
|
643
|
-
// src/pipeline/
|
|
633
|
+
// src/pipeline/steps/fail.ts
|
|
644
634
|
var import_server2 = require("next/server");
|
|
645
635
|
function fail(ctx, status, message, requestBody) {
|
|
646
636
|
const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
|
|
@@ -648,7 +638,7 @@ function fail(ctx, status, message, requestBody) {
|
|
|
648
638
|
return response;
|
|
649
639
|
}
|
|
650
640
|
|
|
651
|
-
// src/pipeline/
|
|
641
|
+
// src/pipeline/steps/run-validate.ts
|
|
652
642
|
async function runValidate(ctx, body) {
|
|
653
643
|
if (!ctx.routeEntry.validateFn) return null;
|
|
654
644
|
try {
|
|
@@ -671,6 +661,14 @@ var HttpError = class extends Error {
|
|
|
671
661
|
}
|
|
672
662
|
};
|
|
673
663
|
|
|
664
|
+
// src/pipeline/steps/parse-query.ts
|
|
665
|
+
function parseQuery(request, routeEntry) {
|
|
666
|
+
if (!routeEntry.querySchema) return void 0;
|
|
667
|
+
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
668
|
+
const result = routeEntry.querySchema.safeParse(params);
|
|
669
|
+
return result.success ? result.data : params;
|
|
670
|
+
}
|
|
671
|
+
|
|
674
672
|
// src/pipeline/flows/static/static-invoke.ts
|
|
675
673
|
function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
676
674
|
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
|
|
@@ -688,14 +686,7 @@ function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
688
686
|
wallet,
|
|
689
687
|
payment,
|
|
690
688
|
account,
|
|
691
|
-
alert
|
|
692
|
-
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
693
|
-
level,
|
|
694
|
-
message,
|
|
695
|
-
route: ctx.routeEntry.key,
|
|
696
|
-
meta: alertMeta
|
|
697
|
-
});
|
|
698
|
-
},
|
|
689
|
+
alert: ctx.report,
|
|
699
690
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
700
691
|
};
|
|
701
692
|
}
|
|
@@ -739,45 +730,14 @@ function isThenable(value) {
|
|
|
739
730
|
return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
740
731
|
}
|
|
741
732
|
|
|
742
|
-
// src/pipeline/
|
|
743
|
-
function fireProviderQuota(ctx, response, handlerResult) {
|
|
744
|
-
const { providerName, providerConfig } = ctx.routeEntry;
|
|
745
|
-
if (!providerName || !providerConfig?.extractQuota) return;
|
|
746
|
-
if (response.status >= 400) return;
|
|
747
|
-
try {
|
|
748
|
-
const quota = providerConfig.extractQuota(handlerResult, response.headers);
|
|
749
|
-
if (!quota) return;
|
|
750
|
-
const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
|
|
751
|
-
const overage = providerConfig.overage ?? "same-rate";
|
|
752
|
-
const event = {
|
|
753
|
-
provider: providerName,
|
|
754
|
-
route: ctx.routeEntry.key,
|
|
755
|
-
remaining: quota.remaining,
|
|
756
|
-
limit: quota.limit,
|
|
757
|
-
spend: quota.spend,
|
|
758
|
-
level,
|
|
759
|
-
overage,
|
|
760
|
-
message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
|
|
761
|
-
};
|
|
762
|
-
firePluginHook(ctx.deps.plugin, "onProviderQuota", ctx.pluginCtx, event);
|
|
763
|
-
} catch {
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
function computeQuotaLevel(remaining, warn, critical) {
|
|
767
|
-
if (remaining === null) return "healthy";
|
|
768
|
-
if (critical !== void 0 && remaining <= critical) return "critical";
|
|
769
|
-
if (warn !== void 0 && remaining <= warn) return "warn";
|
|
770
|
-
return "healthy";
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// src/pipeline/context/finalize/response.ts
|
|
733
|
+
// src/pipeline/steps/finalize/response.ts
|
|
774
734
|
function finalize(ctx, response, rawResult, requestBody) {
|
|
775
735
|
fireProviderQuota(ctx, response, rawResult);
|
|
776
736
|
firePluginResponse(ctx, response, requestBody, rawResult);
|
|
777
737
|
return response;
|
|
778
738
|
}
|
|
779
739
|
|
|
780
|
-
// src/pipeline/
|
|
740
|
+
// src/pipeline/steps/grant-entitlement.ts
|
|
781
741
|
async function grantEntitlementIfSiwx(ctx, wallet) {
|
|
782
742
|
if (!ctx.routeEntry.siwxEnabled) return;
|
|
783
743
|
try {
|
|
@@ -790,7 +750,7 @@ async function grantEntitlementIfSiwx(ctx, wallet) {
|
|
|
790
750
|
}
|
|
791
751
|
}
|
|
792
752
|
|
|
793
|
-
// src/pipeline/
|
|
753
|
+
// src/pipeline/steps/settlement-context.ts
|
|
794
754
|
function settlementContext(ctx, scope) {
|
|
795
755
|
return {
|
|
796
756
|
route: ctx.routeEntry.key,
|
|
@@ -804,7 +764,7 @@ function settlementContext(ctx, scope) {
|
|
|
804
764
|
};
|
|
805
765
|
}
|
|
806
766
|
|
|
807
|
-
// src/pipeline/
|
|
767
|
+
// src/pipeline/steps/run-settlement-error.ts
|
|
808
768
|
async function runSettlementError(ctx, scope, error, phase) {
|
|
809
769
|
const hook = ctx.routeEntry.settlement?.onSettlementError;
|
|
810
770
|
if (!hook) return;
|
|
@@ -816,7 +776,7 @@ async function runSettlementError(ctx, scope, error, phase) {
|
|
|
816
776
|
}
|
|
817
777
|
}
|
|
818
778
|
|
|
819
|
-
// src/pipeline/
|
|
779
|
+
// src/pipeline/steps/run-after-settle.ts
|
|
820
780
|
async function runAfterSettle(ctx, scope) {
|
|
821
781
|
const hook = ctx.routeEntry.settlement?.afterSettle;
|
|
822
782
|
if (!hook) return;
|
|
@@ -829,11 +789,11 @@ async function runAfterSettle(ctx, scope) {
|
|
|
829
789
|
}
|
|
830
790
|
}
|
|
831
791
|
|
|
832
|
-
// src/pipeline/
|
|
792
|
+
// src/pipeline/steps/finalize/epilogue.ts
|
|
833
793
|
async function runPostSettleEpilogue(args) {
|
|
834
794
|
const { ctx, strategy, wallet, settle, afterSettleScope, rawResult, body } = args;
|
|
835
795
|
await grantEntitlementIfSiwx(ctx, wallet);
|
|
836
|
-
|
|
796
|
+
firePaymentSettled(ctx, {
|
|
837
797
|
protocol: strategy.protocol,
|
|
838
798
|
payer: wallet,
|
|
839
799
|
transaction: settle.settledPayment.transaction ?? "",
|
|
@@ -843,7 +803,7 @@ async function runPostSettleEpilogue(args) {
|
|
|
843
803
|
return finalize(ctx, settle.response, rawResult, body);
|
|
844
804
|
}
|
|
845
805
|
|
|
846
|
-
// src/pipeline/
|
|
806
|
+
// src/pipeline/steps/finalize/request.ts
|
|
847
807
|
async function settleAndFinalizeRequest(args) {
|
|
848
808
|
const { ctx, strategy, verifyOutcome, scope, rawResult, body, billedAmount, onSettleError } = args;
|
|
849
809
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -876,7 +836,7 @@ async function settleAndFinalizeRequest(args) {
|
|
|
876
836
|
});
|
|
877
837
|
}
|
|
878
838
|
|
|
879
|
-
// src/pipeline/
|
|
839
|
+
// src/pipeline/steps/finalize/stream.ts
|
|
880
840
|
async function settleAndFinalizeStream(args) {
|
|
881
841
|
const { ctx, strategy, verifyOutcome, source, account, body, bindChannelCharge } = args;
|
|
882
842
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -914,7 +874,7 @@ async function settleAndFinalizeStream(args) {
|
|
|
914
874
|
});
|
|
915
875
|
}
|
|
916
876
|
|
|
917
|
-
// src/pipeline/
|
|
877
|
+
// src/pipeline/steps/run-handler-only.ts
|
|
918
878
|
async function runHandlerOnly(ctx, wallet, account) {
|
|
919
879
|
const body = await parseBody(ctx);
|
|
920
880
|
if (!body.ok) return body.response;
|
|
@@ -924,7 +884,7 @@ async function runHandlerOnly(ctx, wallet, account) {
|
|
|
924
884
|
return finalize(ctx, result.response, result.rawResult, body.data);
|
|
925
885
|
}
|
|
926
886
|
|
|
927
|
-
// src/pipeline/
|
|
887
|
+
// src/pipeline/steps/run-before-settle.ts
|
|
928
888
|
async function runBeforeSettle(ctx, scope) {
|
|
929
889
|
const hook = ctx.routeEntry.settlement?.beforeSettle;
|
|
930
890
|
if (!hook) return null;
|
|
@@ -941,7 +901,7 @@ async function runBeforeSettle(ctx, scope) {
|
|
|
941
901
|
}
|
|
942
902
|
}
|
|
943
903
|
|
|
944
|
-
// src/pipeline/
|
|
904
|
+
// src/pipeline/steps/run-settled-handler-error.ts
|
|
945
905
|
async function runSettledHandlerError(ctx, scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
|
|
946
906
|
const hook = ctx.routeEntry.settlement?.onSettledHandlerError;
|
|
947
907
|
if (!hook) return;
|
|
@@ -1015,7 +975,7 @@ async function buildSIWXExtension() {
|
|
|
1015
975
|
return declareSIWxExtension();
|
|
1016
976
|
}
|
|
1017
977
|
|
|
1018
|
-
// src/pipeline/
|
|
978
|
+
// src/pipeline/steps/try-siwx-fast-path.ts
|
|
1019
979
|
async function trySiwxFastPath(ctx, account) {
|
|
1020
980
|
const { request, routeEntry, deps } = ctx;
|
|
1021
981
|
if (!routeEntry.siwxEnabled) return null;
|
|
@@ -1027,22 +987,18 @@ async function trySiwxFastPath(ctx, account) {
|
|
|
1027
987
|
ctx.pluginCtx.setVerifiedWallet(wallet);
|
|
1028
988
|
const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
|
|
1029
989
|
if (!entitled) return null;
|
|
1030
|
-
|
|
1031
|
-
authMode: "siwx",
|
|
1032
|
-
wallet,
|
|
1033
|
-
route: routeEntry.key
|
|
1034
|
-
});
|
|
990
|
+
fireAuthVerified(ctx, { authMode: "siwx", wallet });
|
|
1035
991
|
return runHandlerOnly(ctx, wallet, account);
|
|
1036
992
|
}
|
|
1037
993
|
|
|
1038
|
-
// src/pipeline/
|
|
994
|
+
// src/pipeline/steps/should-parse-body-early.ts
|
|
1039
995
|
function shouldParseBodyEarly(incomingStrategy, routeEntry, pricing) {
|
|
1040
996
|
if (incomingStrategy) return false;
|
|
1041
997
|
if (!routeEntry.bodySchema) return false;
|
|
1042
998
|
return (pricing?.needsBody ?? false) || !!routeEntry.validateFn;
|
|
1043
999
|
}
|
|
1044
1000
|
|
|
1045
|
-
// src/pipeline/
|
|
1001
|
+
// src/pipeline/steps/resolve-early-body.ts
|
|
1046
1002
|
async function resolveEarlyBody(args) {
|
|
1047
1003
|
const { ctx, pricing, incomingStrategy } = args;
|
|
1048
1004
|
if (!shouldParseBodyEarly(incomingStrategy, ctx.routeEntry, pricing)) {
|
|
@@ -1074,24 +1030,19 @@ function extractBearerToken(header) {
|
|
|
1074
1030
|
return null;
|
|
1075
1031
|
}
|
|
1076
1032
|
|
|
1077
|
-
// src/pipeline/
|
|
1033
|
+
// src/pipeline/steps/run-api-key-gate.ts
|
|
1078
1034
|
async function runApiKeyGate(ctx) {
|
|
1079
|
-
const { request, routeEntry
|
|
1035
|
+
const { request, routeEntry } = ctx;
|
|
1080
1036
|
if (!routeEntry.apiKeyResolver) return { ok: true, account: void 0 };
|
|
1081
1037
|
const apiKeyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
|
|
1082
1038
|
if (!apiKeyResult.valid) {
|
|
1083
1039
|
return { ok: false, response: fail(ctx, 401, "Invalid or missing API key") };
|
|
1084
1040
|
}
|
|
1085
|
-
|
|
1086
|
-
authMode: "apiKey",
|
|
1087
|
-
wallet: null,
|
|
1088
|
-
route: routeEntry.key,
|
|
1089
|
-
account: apiKeyResult.account
|
|
1090
|
-
});
|
|
1041
|
+
fireAuthVerified(ctx, { authMode: "apiKey", wallet: null, account: apiKeyResult.account });
|
|
1091
1042
|
return { ok: true, account: apiKeyResult.account };
|
|
1092
1043
|
}
|
|
1093
1044
|
|
|
1094
|
-
// src/pipeline/
|
|
1045
|
+
// src/pipeline/steps/protocol-init-error.ts
|
|
1095
1046
|
function protocolInitError(routeEntry, deps) {
|
|
1096
1047
|
if (!routeEntry.pricing) return null;
|
|
1097
1048
|
const errors = [];
|
|
@@ -1114,12 +1065,7 @@ async function runApiKeyOnlyFlow(ctx) {
|
|
|
1114
1065
|
}
|
|
1115
1066
|
const result = await verifyApiKey(ctx.request, ctx.routeEntry.apiKeyResolver);
|
|
1116
1067
|
if (!result.valid) return fail(ctx, 401, "Invalid or missing API key");
|
|
1117
|
-
|
|
1118
|
-
authMode: "apiKey",
|
|
1119
|
-
wallet: null,
|
|
1120
|
-
route: ctx.routeEntry.key,
|
|
1121
|
-
account: result.account
|
|
1122
|
-
});
|
|
1068
|
+
fireAuthVerified(ctx, { authMode: "apiKey", wallet: null, account: result.account });
|
|
1123
1069
|
return runHandlerOnly(ctx, null, result.account);
|
|
1124
1070
|
}
|
|
1125
1071
|
|
|
@@ -2209,21 +2155,6 @@ function reportSettleFailure(report, err, network) {
|
|
|
2209
2155
|
});
|
|
2210
2156
|
}
|
|
2211
2157
|
|
|
2212
|
-
// src/protocols/detect.ts
|
|
2213
|
-
function detectProtocol(request) {
|
|
2214
|
-
if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
|
|
2215
|
-
return "x402";
|
|
2216
|
-
}
|
|
2217
|
-
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
2218
|
-
if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
|
|
2219
|
-
return "mpp";
|
|
2220
|
-
}
|
|
2221
|
-
if (request.headers.get(HEADERS.SIWX)) {
|
|
2222
|
-
return "siwx";
|
|
2223
|
-
}
|
|
2224
|
-
return null;
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
2158
|
// src/protocols/index.ts
|
|
2228
2159
|
var STRATEGIES = {
|
|
2229
2160
|
x402: x402Strategy,
|
|
@@ -2248,9 +2179,9 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2248
2179
|
const { routeEntry } = ctx;
|
|
2249
2180
|
let extensions;
|
|
2250
2181
|
try {
|
|
2251
|
-
const { z } = await import("zod");
|
|
2182
|
+
const { z: z2 } = await import("zod");
|
|
2252
2183
|
const { declareDiscoveryExtension } = await import("@x402/extensions/bazaar");
|
|
2253
|
-
const toJSON = (schema) =>
|
|
2184
|
+
const toJSON = (schema) => z2.toJSONSchema(schema, {
|
|
2254
2185
|
target: "draft-2020-12",
|
|
2255
2186
|
unrepresentable: "any"
|
|
2256
2187
|
});
|
|
@@ -2271,11 +2202,10 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2271
2202
|
extensions = declareDiscoveryExtension(config);
|
|
2272
2203
|
}
|
|
2273
2204
|
} catch (err) {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
});
|
|
2205
|
+
ctx.report(
|
|
2206
|
+
"warn",
|
|
2207
|
+
`Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2208
|
+
);
|
|
2279
2209
|
}
|
|
2280
2210
|
if (routeEntry.siwxEnabled) {
|
|
2281
2211
|
try {
|
|
@@ -2409,7 +2339,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2409
2339
|
return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2410
2340
|
}
|
|
2411
2341
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2412
|
-
|
|
2342
|
+
firePaymentVerified(ctx, {
|
|
2413
2343
|
protocol: strategy.protocol,
|
|
2414
2344
|
payer: verifyOutcome.wallet,
|
|
2415
2345
|
amount: price,
|
|
@@ -2517,14 +2447,7 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2517
2447
|
wallet,
|
|
2518
2448
|
payment,
|
|
2519
2449
|
account,
|
|
2520
|
-
alert
|
|
2521
|
-
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
2522
|
-
level,
|
|
2523
|
-
message,
|
|
2524
|
-
route: ctx.routeEntry.key,
|
|
2525
|
-
meta: alertMeta
|
|
2526
|
-
});
|
|
2527
|
-
},
|
|
2450
|
+
alert: ctx.report,
|
|
2528
2451
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2529
2452
|
};
|
|
2530
2453
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
@@ -2589,7 +2512,7 @@ function resolveDynamicPreflight(strategy, request, routeEntry) {
|
|
|
2589
2512
|
// src/pipeline/flows/dynamic/dynamic-request.ts
|
|
2590
2513
|
async function runDynamicRequestFlow(args) {
|
|
2591
2514
|
const { ctx, strategy, verifyOutcome, account, body, result } = args;
|
|
2592
|
-
const {
|
|
2515
|
+
const { routeEntry } = ctx;
|
|
2593
2516
|
const settleScope = {
|
|
2594
2517
|
wallet: verifyOutcome.wallet,
|
|
2595
2518
|
account,
|
|
@@ -2615,11 +2538,10 @@ async function runDynamicRequestFlow(args) {
|
|
|
2615
2538
|
billedAmount,
|
|
2616
2539
|
onSettleError: async (error, failMessage) => {
|
|
2617
2540
|
await runSettlementError(ctx, settleScope, error, "settle");
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
});
|
|
2541
|
+
ctx.report(
|
|
2542
|
+
"critical",
|
|
2543
|
+
`${strategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`
|
|
2544
|
+
);
|
|
2623
2545
|
}
|
|
2624
2546
|
});
|
|
2625
2547
|
}
|
|
@@ -2689,7 +2611,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2689
2611
|
return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2690
2612
|
}
|
|
2691
2613
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2692
|
-
|
|
2614
|
+
firePaymentVerified(ctx, {
|
|
2693
2615
|
protocol: incomingStrategy.protocol,
|
|
2694
2616
|
payer: verifyOutcome.wallet,
|
|
2695
2617
|
amount: price,
|
|
@@ -2755,7 +2677,6 @@ async function resolveStaticBodyAndPrice(args) {
|
|
|
2755
2677
|
// src/pipeline/flows/static/static-request.ts
|
|
2756
2678
|
async function runStaticRequestFlow(args) {
|
|
2757
2679
|
const { ctx, strategy, verifyOutcome, account, body, price, result } = args;
|
|
2758
|
-
const { deps, routeEntry } = ctx;
|
|
2759
2680
|
const settleScope = {
|
|
2760
2681
|
wallet: verifyOutcome.wallet,
|
|
2761
2682
|
account,
|
|
@@ -2796,11 +2717,10 @@ async function runStaticRequestFlow(args) {
|
|
|
2796
2717
|
billedAmount: price,
|
|
2797
2718
|
onSettleError: async (error, failMessage) => {
|
|
2798
2719
|
await runSettlementError(ctx, settleScope, error, "settle");
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
});
|
|
2720
|
+
ctx.report(
|
|
2721
|
+
"critical",
|
|
2722
|
+
`${strategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`
|
|
2723
|
+
);
|
|
2804
2724
|
}
|
|
2805
2725
|
});
|
|
2806
2726
|
}
|
|
@@ -2846,7 +2766,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2846
2766
|
return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2847
2767
|
}
|
|
2848
2768
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2849
|
-
|
|
2769
|
+
firePaymentVerified(ctx, {
|
|
2850
2770
|
protocol: incomingStrategy.protocol,
|
|
2851
2771
|
payer: verifyOutcome.wallet,
|
|
2852
2772
|
amount: price,
|
|
@@ -3030,6 +2950,21 @@ async function createKvMppStore(kv, options) {
|
|
|
3030
2950
|
});
|
|
3031
2951
|
}
|
|
3032
2952
|
|
|
2953
|
+
// src/protocols/detect.ts
|
|
2954
|
+
function detectProtocol(request) {
|
|
2955
|
+
if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
|
|
2956
|
+
return "x402";
|
|
2957
|
+
}
|
|
2958
|
+
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
2959
|
+
if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
|
|
2960
|
+
return "mpp";
|
|
2961
|
+
}
|
|
2962
|
+
if (request.headers.get(HEADERS.SIWX)) {
|
|
2963
|
+
return "siwx";
|
|
2964
|
+
}
|
|
2965
|
+
return null;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
3033
2968
|
// src/protocols/mpp/siwx-mode.ts
|
|
3034
2969
|
var import_mppx3 = require("mppx");
|
|
3035
2970
|
async function verifyMppSiwx(request, mppx) {
|
|
@@ -3069,11 +3004,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3069
3004
|
}
|
|
3070
3005
|
if (mppSiwxResult.valid) {
|
|
3071
3006
|
ctx.pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
|
|
3072
|
-
|
|
3073
|
-
authMode: "siwx",
|
|
3074
|
-
wallet: mppSiwxResult.wallet,
|
|
3075
|
-
route: routeEntry.key
|
|
3076
|
-
});
|
|
3007
|
+
fireAuthVerified(ctx, { authMode: "siwx", wallet: mppSiwxResult.wallet });
|
|
3077
3008
|
const authResponse = await runHandlerOnly(ctx, mppSiwxResult.wallet, void 0);
|
|
3078
3009
|
if (authResponse.status < 400) {
|
|
3079
3010
|
return mppSiwxResult.withReceipt(authResponse);
|
|
@@ -3095,11 +3026,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3095
3026
|
}
|
|
3096
3027
|
const wallet = normalizeWalletAddress(siwx.wallet);
|
|
3097
3028
|
ctx.pluginCtx.setVerifiedWallet(wallet);
|
|
3098
|
-
|
|
3099
|
-
authMode: "siwx",
|
|
3100
|
-
wallet,
|
|
3101
|
-
route: routeEntry.key
|
|
3102
|
-
});
|
|
3029
|
+
fireAuthVerified(ctx, { authMode: "siwx", wallet });
|
|
3103
3030
|
return runHandlerOnly(ctx, wallet, void 0);
|
|
3104
3031
|
}
|
|
3105
3032
|
async function buildSiwxChallenge(ctx) {
|
|
@@ -3192,7 +3119,7 @@ async function runUnprotectedFlow(ctx) {
|
|
|
3192
3119
|
return runHandlerOnly(ctx, null, void 0);
|
|
3193
3120
|
}
|
|
3194
3121
|
|
|
3195
|
-
// src/orchestrate.ts
|
|
3122
|
+
// src/pipeline/orchestrate.ts
|
|
3196
3123
|
function createRequestHandler(routeEntry, handler, deps) {
|
|
3197
3124
|
return async (request) => {
|
|
3198
3125
|
await deps.initPromise;
|
|
@@ -3967,7 +3894,7 @@ function toProtocolObject(protocol, mppInfo) {
|
|
|
3967
3894
|
mpp: {
|
|
3968
3895
|
method: mppInfo?.method ?? "tempo",
|
|
3969
3896
|
intent: mppInfo?.intent ?? "charge",
|
|
3970
|
-
currency: mppInfo?.currency ??
|
|
3897
|
+
currency: mppInfo?.currency ?? TEMPO_USDC_ADDRESS
|
|
3971
3898
|
}
|
|
3972
3899
|
};
|
|
3973
3900
|
}
|
|
@@ -4047,142 +3974,256 @@ function formatRouterConfigIssues(issues) {
|
|
|
4047
3974
|
return issues.map((issue) => issue.message).join("\n");
|
|
4048
3975
|
}
|
|
4049
3976
|
|
|
4050
|
-
// src/config/
|
|
4051
|
-
|
|
3977
|
+
// src/config/schema.ts
|
|
3978
|
+
var import_zod = require("zod");
|
|
3979
|
+
init_constants();
|
|
4052
3980
|
|
|
4053
|
-
// src/config/
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
3981
|
+
// src/config/utils.ts
|
|
3982
|
+
var import_accounts = require("viem/accounts");
|
|
3983
|
+
var EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
3984
|
+
var EVM_PRIVATE_KEY_RE = /^0x[a-fA-F0-9]{64}$/;
|
|
3985
|
+
var SOLANA_ADDRESS_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
3986
|
+
var ZERO_EVM_ADDRESS_RE = /^0x0{40}$/i;
|
|
3987
|
+
function isUrl(value) {
|
|
3988
|
+
try {
|
|
3989
|
+
new URL(value);
|
|
3990
|
+
return true;
|
|
3991
|
+
} catch {
|
|
3992
|
+
return false;
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
var isEvmAddress = (v) => EVM_ADDRESS_RE.test(v);
|
|
3996
|
+
var isEvmPrivateKey = (v) => EVM_PRIVATE_KEY_RE.test(v);
|
|
3997
|
+
var isPlaceholderEvm = (v) => ZERO_EVM_ADDRESS_RE.test(v);
|
|
3998
|
+
var isSolanaAddress = (v) => SOLANA_ADDRESS_RE.test(v);
|
|
3999
|
+
var isX402Network = (v) => v.startsWith("eip155:") || v.startsWith("solana:");
|
|
4000
|
+
var canonicalizeEvm = (addr) => addr.toLowerCase();
|
|
4001
|
+
function operatorAddressesCollide(opKey, fpKey) {
|
|
4002
|
+
if (!opKey || !fpKey || !isEvmPrivateKey(opKey) || !isEvmPrivateKey(fpKey)) return null;
|
|
4003
|
+
const op = (0, import_accounts.privateKeyToAccount)(opKey).address.toLowerCase();
|
|
4004
|
+
const fp = (0, import_accounts.privateKeyToAccount)(fpKey).address.toLowerCase();
|
|
4005
|
+
return op === fp ? op : null;
|
|
4006
|
+
}
|
|
4007
|
+
function trimAll(raw) {
|
|
4008
|
+
const out = {};
|
|
4009
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
4010
|
+
if (typeof v !== "string") {
|
|
4011
|
+
out[k] = void 0;
|
|
4012
|
+
continue;
|
|
4013
|
+
}
|
|
4014
|
+
const trimmed = v.trim();
|
|
4015
|
+
out[k] = trimmed.length > 0 ? trimmed : void 0;
|
|
4016
|
+
}
|
|
4017
|
+
return out;
|
|
4073
4018
|
}
|
|
4074
4019
|
|
|
4075
|
-
// src/config/
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4020
|
+
// src/config/schema.ts
|
|
4021
|
+
function addIssue(ctx, params, message, path = []) {
|
|
4022
|
+
ctx.addIssue({ code: "custom", path, params, message });
|
|
4023
|
+
}
|
|
4024
|
+
var x402 = { protocol: "x402" };
|
|
4025
|
+
var mpp = { protocol: "mpp" };
|
|
4026
|
+
var envShape = {
|
|
4027
|
+
BASE_URL: import_zod.z.string().refine(isUrl, {
|
|
4028
|
+
params: { code: "invalid_base_url" },
|
|
4029
|
+
message: "BASE_URL must be a valid URL \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Must match the public domain."
|
|
4030
|
+
}).optional(),
|
|
4031
|
+
EVM_PAYEE_ADDRESS: import_zod.z.string().refine(isEvmAddress, {
|
|
4032
|
+
params: { code: "invalid_x402_payee", ...x402 },
|
|
4033
|
+
message: "EVM_PAYEE_ADDRESS must be a 0x-prefixed 20-byte EVM address \u2014 the wallet that receives x402 and MPP payments."
|
|
4034
|
+
}).refine((v) => !isPlaceholderEvm(v), {
|
|
4035
|
+
params: { code: "placeholder_payee", ...x402 },
|
|
4036
|
+
message: "EVM_PAYEE_ADDRESS is the zero address (0x000\u2026000) \u2014 payments to this address are unrecoverable. Set it to a wallet you control."
|
|
4037
|
+
}).optional(),
|
|
4038
|
+
CDP_API_KEY_ID: import_zod.z.string().optional(),
|
|
4039
|
+
CDP_API_KEY_SECRET: import_zod.z.string().optional(),
|
|
4040
|
+
SOLANA_PAYEE_ADDRESS: import_zod.z.string().refine(isSolanaAddress, {
|
|
4041
|
+
params: { code: "invalid_solana_payee", ...x402 },
|
|
4042
|
+
message: "SOLANA_PAYEE_ADDRESS must be a base58 Solana address (32\u201344 chars). When set, the router also accepts Solana payments."
|
|
4043
|
+
}).optional(),
|
|
4044
|
+
SOLANA_FACILITATOR_URL: import_zod.z.string().refine(isUrl, {
|
|
4045
|
+
params: { code: "invalid_solana_facilitator_url", ...x402 },
|
|
4046
|
+
message: "SOLANA_FACILITATOR_URL must be a valid URL \u2014 override for the Solana x402 facilitator. Defaults to DEFAULT_SOLANA_FACILITATOR_URL."
|
|
4047
|
+
}).optional(),
|
|
4048
|
+
MPP_SECRET_KEY: import_zod.z.string().optional(),
|
|
4049
|
+
MPP_CURRENCY: import_zod.z.string().refine(isEvmAddress, {
|
|
4050
|
+
params: { code: "invalid_mpp_currency", ...mpp },
|
|
4051
|
+
message: "MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address \u2014 the token contract MPP charges in. Use TEMPO_USDC_ADDRESS for Tempo USDC."
|
|
4052
|
+
}).optional(),
|
|
4053
|
+
TEMPO_RPC_URL: import_zod.z.string().refine(isUrl, {
|
|
4054
|
+
params: { code: "invalid_mpp_rpc_url", ...mpp },
|
|
4055
|
+
message: "TEMPO_RPC_URL must be a valid URL \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401."
|
|
4056
|
+
}).optional(),
|
|
4057
|
+
MPP_OPERATOR_KEY: import_zod.z.string().refine(isEvmPrivateKey, {
|
|
4058
|
+
params: { code: "invalid_mpp_operator_key", ...mpp },
|
|
4059
|
+
message: "MPP_OPERATOR_KEY must be a 0x-prefixed 32-byte EVM private key \u2014 signs server-side close/settle; presence enables MPP session mode."
|
|
4060
|
+
}).optional(),
|
|
4061
|
+
MPP_FEE_PAYER_KEY: import_zod.z.string().refine(isEvmPrivateKey, {
|
|
4062
|
+
params: { code: "invalid_mpp_fee_payer_key", ...mpp },
|
|
4063
|
+
message: "MPP_FEE_PAYER_KEY must be a 0x-prefixed 32-byte EVM private key \u2014 sponsors client gas for channel open/topUp. Must resolve to a different address than MPP_OPERATOR_KEY."
|
|
4064
|
+
}).optional(),
|
|
4065
|
+
KV_REST_API_URL: import_zod.z.string().optional(),
|
|
4066
|
+
KV_REST_API_TOKEN: import_zod.z.string().optional(),
|
|
4067
|
+
NODE_ENV: import_zod.z.string().optional()
|
|
4068
|
+
};
|
|
4069
|
+
var ENV_KEYS = Object.keys(envShape);
|
|
4070
|
+
var EnvInputSchema = import_zod.z.object(envShape).passthrough().superRefine((env, ctx) => {
|
|
4071
|
+
if (env.BASE_URL === void 0) {
|
|
4072
|
+
addIssue(
|
|
4073
|
+
ctx,
|
|
4074
|
+
{ code: "missing_base_url" },
|
|
4075
|
+
"BASE_URL is required \u2014 the public origin used as the 402 realm, OpenAPI server URL, and MPP memo prefix. Set it to your production domain.",
|
|
4076
|
+
["BASE_URL"]
|
|
4077
|
+
);
|
|
4078
|
+
}
|
|
4079
|
+
if (env.EVM_PAYEE_ADDRESS === void 0) {
|
|
4080
|
+
addIssue(
|
|
4081
|
+
ctx,
|
|
4082
|
+
{ code: "missing_x402_payee", ...x402 },
|
|
4083
|
+
"EVM_PAYEE_ADDRESS is required \u2014 the EVM address that receives x402 and MPP payments.",
|
|
4084
|
+
["EVM_PAYEE_ADDRESS"]
|
|
4085
|
+
);
|
|
4086
|
+
}
|
|
4087
|
+
if (env.MPP_SECRET_KEY) {
|
|
4088
|
+
if (env.MPP_CURRENCY === void 0) {
|
|
4089
|
+
addIssue(
|
|
4090
|
+
ctx,
|
|
4091
|
+
{ code: "missing_mpp_currency", ...mpp },
|
|
4092
|
+
"MPP_CURRENCY is required when MPP is enabled \u2014 the Tempo currency address MPP charges in. Use TEMPO_USDC_ADDRESS for Tempo USDC.",
|
|
4093
|
+
["MPP_CURRENCY"]
|
|
4094
|
+
);
|
|
4095
|
+
}
|
|
4096
|
+
if (env.TEMPO_RPC_URL === void 0) {
|
|
4097
|
+
addIssue(
|
|
4098
|
+
ctx,
|
|
4099
|
+
{ code: "missing_mpp_rpc_url", ...mpp },
|
|
4100
|
+
"TEMPO_RPC_URL is required when MPP is enabled \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401.",
|
|
4101
|
+
["TEMPO_RPC_URL"]
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
const collision = operatorAddressesCollide(env.MPP_OPERATOR_KEY, env.MPP_FEE_PAYER_KEY);
|
|
4106
|
+
if (collision) {
|
|
4107
|
+
addIssue(
|
|
4108
|
+
ctx,
|
|
4109
|
+
{ code: "mpp_operator_equals_fee_payer", ...mpp },
|
|
4110
|
+
`MPP_OPERATOR_KEY and MPP_FEE_PAYER_KEY resolve to the same address (${collision}). Tempo rejects fee-delegated txs with sender === feePayer. Use two distinct wallets, or unset MPP_FEE_PAYER_KEY to let clients pay their own gas.`,
|
|
4111
|
+
["MPP_FEE_PAYER_KEY"]
|
|
4112
|
+
);
|
|
4113
|
+
}
|
|
4114
|
+
});
|
|
4115
|
+
function collectKvWarnings(env, kvStoreOptionProvided) {
|
|
4116
|
+
if (kvStoreOptionProvided) return [];
|
|
4117
|
+
const warn = (code, message) => ({
|
|
4118
|
+
code,
|
|
4119
|
+
severity: "warning",
|
|
4120
|
+
message
|
|
4121
|
+
});
|
|
4122
|
+
if (env.KV_REST_API_URL && !env.KV_REST_API_TOKEN) {
|
|
4088
4123
|
return [
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
}
|
|
4124
|
+
warn(
|
|
4125
|
+
"kv_url_without_token",
|
|
4126
|
+
"KV_REST_API_URL is set but KV_REST_API_TOKEN is missing \u2014 falling back to in-memory KV (unsafe in serverless production)."
|
|
4127
|
+
)
|
|
4094
4128
|
];
|
|
4095
4129
|
}
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
}
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4130
|
+
if (env.KV_REST_API_TOKEN && !env.KV_REST_API_URL) {
|
|
4131
|
+
return [
|
|
4132
|
+
warn(
|
|
4133
|
+
"kv_token_without_url",
|
|
4134
|
+
"KV_REST_API_TOKEN is set but KV_REST_API_URL is missing \u2014 falling back to in-memory KV (unsafe in serverless production)."
|
|
4135
|
+
)
|
|
4136
|
+
];
|
|
4137
|
+
}
|
|
4138
|
+
if (env.KV_REST_API_URL && env.KV_REST_API_TOKEN && !isUrl(env.KV_REST_API_URL)) {
|
|
4139
|
+
return [
|
|
4140
|
+
warn(
|
|
4141
|
+
"invalid_kv_url",
|
|
4142
|
+
`KV_REST_API_URL is not a valid URL \u2014 KV calls will fail at request time. Got: ${env.KV_REST_API_URL}`
|
|
4143
|
+
)
|
|
4144
|
+
];
|
|
4145
|
+
}
|
|
4146
|
+
if (!env.KV_REST_API_URL && !env.KV_REST_API_TOKEN && env.NODE_ENV === "production") {
|
|
4147
|
+
return [
|
|
4148
|
+
warn(
|
|
4149
|
+
"missing_kv_in_production",
|
|
4150
|
+
"No KV_REST_API_URL/KV_REST_API_TOKEN set in production \u2014 using the in-memory KV store. SIWX nonce, SIWX entitlement, and MPP replay state will be lost across instances. Configure Upstash/Vercel KV or pass a custom kvStore."
|
|
4151
|
+
)
|
|
4152
|
+
];
|
|
4153
|
+
}
|
|
4154
|
+
return [];
|
|
4121
4155
|
}
|
|
4122
|
-
function
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
};
|
|
4156
|
+
function getConfiguredX402Accepts2(config) {
|
|
4157
|
+
if (config.x402?.accepts?.length) return [...config.x402.accepts];
|
|
4158
|
+
return [
|
|
4159
|
+
{
|
|
4160
|
+
scheme: "exact",
|
|
4161
|
+
network: config.network ?? BASE_MAINNET_NETWORK,
|
|
4162
|
+
payTo: config.payeeAddress
|
|
4163
|
+
}
|
|
4164
|
+
];
|
|
4132
4165
|
}
|
|
4133
|
-
function
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4166
|
+
function validateX402Config(config, env, options) {
|
|
4167
|
+
const accepts = getConfiguredX402Accepts2(config);
|
|
4168
|
+
const issues = [];
|
|
4169
|
+
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
4170
|
+
if (accepts.length === 0) {
|
|
4171
|
+
push("missing_x402_accepts", "x402 requires at least one accept configuration.");
|
|
4172
|
+
return issues;
|
|
4173
|
+
}
|
|
4174
|
+
if (accepts.some((a) => !a.network)) {
|
|
4175
|
+
push("missing_x402_network", "x402 accepts require a network.");
|
|
4176
|
+
}
|
|
4177
|
+
const unsupported = accepts.find((a) => a.network && !isX402Network(a.network));
|
|
4178
|
+
if (unsupported) {
|
|
4179
|
+
push(
|
|
4180
|
+
"unsupported_x402_network",
|
|
4181
|
+
`unsupported x402 network '${unsupported.network}'. Use eip155:* or solana:*.`
|
|
4182
|
+
);
|
|
4183
|
+
}
|
|
4184
|
+
if (accepts.some((a) => (a.scheme ?? "exact") !== "exact" && !a.asset)) {
|
|
4185
|
+
push("missing_x402_asset", "non-exact x402 accepts require an asset.");
|
|
4186
|
+
}
|
|
4187
|
+
if (accepts.some(
|
|
4188
|
+
(a) => a.decimals !== void 0 && (!Number.isInteger(a.decimals) || a.decimals < 0)
|
|
4189
|
+
)) {
|
|
4190
|
+
push("invalid_x402_decimals", "x402 accept decimals must be a non-negative integer.");
|
|
4191
|
+
}
|
|
4192
|
+
if (!config.payeeAddress && accepts.some((a) => !a.payTo)) {
|
|
4193
|
+
push(
|
|
4194
|
+
"missing_x402_payee",
|
|
4195
|
+
"x402 requires payeeAddress in router config or payTo on every x402 accept."
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
const placeholder = [
|
|
4146
4199
|
config.payeeAddress,
|
|
4147
|
-
...accepts.map((
|
|
4148
|
-
]);
|
|
4149
|
-
if (
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
}
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
}
|
|
4200
|
+
...accepts.map((a) => typeof a.payTo === "string" ? a.payTo : void 0)
|
|
4201
|
+
].find((v) => v !== void 0 && isPlaceholderEvm(v));
|
|
4202
|
+
if (placeholder) {
|
|
4203
|
+
push(
|
|
4204
|
+
"placeholder_payee",
|
|
4205
|
+
`x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4206
|
+
);
|
|
4207
|
+
}
|
|
4208
|
+
if (options.requireCdpKeys !== false) {
|
|
4209
|
+
const hasEvm = accepts.some(
|
|
4210
|
+
(a) => typeof a.network === "string" && a.network.startsWith("eip155:")
|
|
4211
|
+
);
|
|
4212
|
+
if (hasEvm) {
|
|
4213
|
+
const missing = ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"].filter((k) => !env[k]);
|
|
4214
|
+
if (missing.length > 0) {
|
|
4215
|
+
push(
|
|
4216
|
+
"missing_cdp_keys",
|
|
4217
|
+
`x402 EVM facilitator (Coinbase) requires ${missing.join(" and ")}.`
|
|
4218
|
+
);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
return issues;
|
|
4169
4223
|
}
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
var CHECKS2 = [
|
|
4174
|
-
checkSecretKey,
|
|
4175
|
-
checkCurrency,
|
|
4176
|
-
checkRecipient,
|
|
4177
|
-
checkPlaceholderRecipient,
|
|
4178
|
-
checkRpcUrl,
|
|
4179
|
-
checkFeePayerKey,
|
|
4180
|
-
checkOperatorKey,
|
|
4181
|
-
checkOperatorMatchesFeePayer
|
|
4182
|
-
];
|
|
4183
|
-
function validateMppConfig(config, env) {
|
|
4184
|
-
const mpp = config.mpp;
|
|
4185
|
-
if (!mpp) {
|
|
4224
|
+
function validateMppConfig(config) {
|
|
4225
|
+
const m = config.mpp;
|
|
4226
|
+
if (!m) {
|
|
4186
4227
|
return [
|
|
4187
4228
|
{
|
|
4188
4229
|
code: "missing_mpp_config",
|
|
@@ -4191,109 +4232,184 @@ function validateMppConfig(config, env) {
|
|
|
4191
4232
|
}
|
|
4192
4233
|
];
|
|
4193
4234
|
}
|
|
4194
|
-
const
|
|
4195
|
-
|
|
4196
|
-
|
|
4235
|
+
const issues = [];
|
|
4236
|
+
const push = (code, message) => issues.push({ code, protocol: "mpp", message });
|
|
4237
|
+
if (!m.secretKey) {
|
|
4238
|
+
push(
|
|
4239
|
+
"missing_mpp_secret_key",
|
|
4240
|
+
"MPP requires secretKey. Set MPP_SECRET_KEY or pass mpp.secretKey."
|
|
4241
|
+
);
|
|
4242
|
+
}
|
|
4243
|
+
if (!m.currency) {
|
|
4244
|
+
push("missing_mpp_currency", "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency.");
|
|
4245
|
+
} else if (!isEvmAddress(m.currency)) {
|
|
4246
|
+
push(
|
|
4247
|
+
"invalid_mpp_currency",
|
|
4248
|
+
"MPP currency must be a 0x-prefixed 20-byte Tempo currency address. Use TEMPO_USDC_ADDRESS for Tempo USDC."
|
|
4249
|
+
);
|
|
4250
|
+
}
|
|
4251
|
+
const recipient = m.recipient ?? config.payeeAddress;
|
|
4252
|
+
if (!recipient) {
|
|
4253
|
+
push(
|
|
4254
|
+
"missing_mpp_recipient",
|
|
4255
|
+
"MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config."
|
|
4256
|
+
);
|
|
4257
|
+
} else if (!isEvmAddress(recipient)) {
|
|
4258
|
+
push(
|
|
4259
|
+
"invalid_mpp_recipient",
|
|
4260
|
+
"MPP recipient must be a 0x-prefixed EVM address. Solana recipients require x402."
|
|
4261
|
+
);
|
|
4262
|
+
}
|
|
4263
|
+
const placeholder = [m.recipient, config.payeeAddress].find(
|
|
4264
|
+
(v) => typeof v === "string" && isPlaceholderEvm(v)
|
|
4197
4265
|
);
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
protocol: "mpp",
|
|
4204
|
-
message: "MPP requires secretKey. Set MPP_SECRET_KEY or pass mpp.secretKey."
|
|
4205
|
-
};
|
|
4206
|
-
}
|
|
4207
|
-
function checkCurrency({ mpp }) {
|
|
4208
|
-
if (!mpp.currency) {
|
|
4209
|
-
return {
|
|
4210
|
-
code: "missing_mpp_currency",
|
|
4211
|
-
protocol: "mpp",
|
|
4212
|
-
message: "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency."
|
|
4213
|
-
};
|
|
4266
|
+
if (placeholder) {
|
|
4267
|
+
push(
|
|
4268
|
+
"placeholder_payee",
|
|
4269
|
+
`MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4270
|
+
);
|
|
4214
4271
|
}
|
|
4215
|
-
if (!
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
};
|
|
4272
|
+
if (!m.rpcUrl) {
|
|
4273
|
+
push(
|
|
4274
|
+
"missing_mpp_rpc_url",
|
|
4275
|
+
"MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
4276
|
+
);
|
|
4221
4277
|
}
|
|
4222
|
-
|
|
4278
|
+
if (m.feePayerKey && !isEvmPrivateKey(m.feePayerKey)) {
|
|
4279
|
+
push(
|
|
4280
|
+
"invalid_mpp_fee_payer_key",
|
|
4281
|
+
"MPP feePayerKey must be a 0x-prefixed 32-byte EVM private key."
|
|
4282
|
+
);
|
|
4283
|
+
}
|
|
4284
|
+
if (m.operatorKey && !isEvmPrivateKey(m.operatorKey)) {
|
|
4285
|
+
push(
|
|
4286
|
+
"invalid_mpp_operator_key",
|
|
4287
|
+
"MPP operatorKey must be a 0x-prefixed 32-byte EVM private key."
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
const collision = operatorAddressesCollide(m.operatorKey, m.feePayerKey);
|
|
4291
|
+
if (collision) {
|
|
4292
|
+
push(
|
|
4293
|
+
"mpp_operator_equals_fee_payer",
|
|
4294
|
+
`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).`
|
|
4295
|
+
);
|
|
4296
|
+
}
|
|
4297
|
+
return issues;
|
|
4223
4298
|
}
|
|
4224
|
-
function
|
|
4225
|
-
|
|
4226
|
-
|
|
4299
|
+
function translateZodIssues(error) {
|
|
4300
|
+
return error.issues.map((issue) => {
|
|
4301
|
+
const params = issue.params;
|
|
4302
|
+
if (!params?.code) {
|
|
4303
|
+
throw new Error(
|
|
4304
|
+
`[router] schema issue missing params.code (path=${issue.path.join(".")}, message=${issue.message}). Every refinement / addIssue call must set params.code.`
|
|
4305
|
+
);
|
|
4306
|
+
}
|
|
4227
4307
|
return {
|
|
4228
|
-
code:
|
|
4229
|
-
|
|
4230
|
-
|
|
4308
|
+
code: params.code,
|
|
4309
|
+
message: issue.message,
|
|
4310
|
+
...params.protocol ? { protocol: params.protocol } : {},
|
|
4311
|
+
...params.severity ? { severity: params.severity } : {}
|
|
4231
4312
|
};
|
|
4313
|
+
});
|
|
4314
|
+
}
|
|
4315
|
+
function routerConfigFromEnv(options) {
|
|
4316
|
+
const rawEnv = options.env ?? process.env;
|
|
4317
|
+
const env = trimAll(rawEnv);
|
|
4318
|
+
const optionIssues = [];
|
|
4319
|
+
if (!options.title?.trim()) {
|
|
4320
|
+
optionIssues.push({
|
|
4321
|
+
code: "missing_discovery_title",
|
|
4322
|
+
message: "discovery `title` is required. Pass a short product name."
|
|
4323
|
+
});
|
|
4232
4324
|
}
|
|
4233
|
-
if (!
|
|
4234
|
-
|
|
4235
|
-
code: "
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
};
|
|
4325
|
+
if (!options.description?.trim()) {
|
|
4326
|
+
optionIssues.push({
|
|
4327
|
+
code: "missing_discovery_description",
|
|
4328
|
+
message: "discovery `description` is required. One sentence is enough."
|
|
4329
|
+
});
|
|
4239
4330
|
}
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
}
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
}
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
const
|
|
4279
|
-
const
|
|
4280
|
-
|
|
4331
|
+
if (options.guidance === void 0) {
|
|
4332
|
+
optionIssues.push({
|
|
4333
|
+
code: "missing_discovery_guidance",
|
|
4334
|
+
message: "discovery `guidance` is required. Provide an empty string to opt out of `/llms.txt`."
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4337
|
+
if (options.serverUrl !== void 0 && !isUrl(options.serverUrl)) {
|
|
4338
|
+
optionIssues.push({
|
|
4339
|
+
code: "invalid_server_url",
|
|
4340
|
+
message: `discovery \`serverUrl\` must be a valid URL. Got: ${options.serverUrl}`
|
|
4341
|
+
});
|
|
4342
|
+
}
|
|
4343
|
+
const parsed = EnvInputSchema.safeParse(env);
|
|
4344
|
+
const envIssues = parsed.success ? [] : translateZodIssues(parsed.error);
|
|
4345
|
+
const issues = [...envIssues, ...optionIssues];
|
|
4346
|
+
if (issues.length > 0) throw new RouterConfigError(issues);
|
|
4347
|
+
for (const warning of collectKvWarnings(env, options.kvStore !== void 0)) {
|
|
4348
|
+
console.warn(`[router] ${warning.message}`);
|
|
4349
|
+
}
|
|
4350
|
+
const payeeAddress = canonicalizeEvm(env.EVM_PAYEE_ADDRESS);
|
|
4351
|
+
const accepts = [
|
|
4352
|
+
{ scheme: "exact", network: BASE_MAINNET_NETWORK, payTo: payeeAddress },
|
|
4353
|
+
{
|
|
4354
|
+
scheme: "upto",
|
|
4355
|
+
network: BASE_MAINNET_NETWORK,
|
|
4356
|
+
payTo: payeeAddress,
|
|
4357
|
+
asset: BASE_USDC_ADDRESS,
|
|
4358
|
+
decimals: BASE_USDC_DECIMALS
|
|
4359
|
+
}
|
|
4360
|
+
];
|
|
4361
|
+
if (env.SOLANA_PAYEE_ADDRESS) {
|
|
4362
|
+
accepts.push({
|
|
4363
|
+
scheme: "exact",
|
|
4364
|
+
network: SOLANA_MAINNET_NETWORK,
|
|
4365
|
+
payTo: env.SOLANA_PAYEE_ADDRESS
|
|
4366
|
+
});
|
|
4367
|
+
}
|
|
4368
|
+
const configuredSolanaFacilitator = options.x402Facilitators?.solana;
|
|
4369
|
+
const solanaFacilitator = typeof configuredSolanaFacilitator === "string" ? configuredSolanaFacilitator : configuredSolanaFacilitator ?? env.SOLANA_FACILITATOR_URL ?? DEFAULT_SOLANA_FACILITATOR_URL;
|
|
4370
|
+
const mppEnabled = options.protocols?.includes("mpp") ?? Boolean(env.MPP_SECRET_KEY);
|
|
4371
|
+
const protocols = options.protocols ? [...options.protocols] : mppEnabled ? ["x402", "mpp"] : ["x402"];
|
|
4372
|
+
const mppConfig = mppEnabled ? {
|
|
4373
|
+
secretKey: env.MPP_SECRET_KEY,
|
|
4374
|
+
currency: canonicalizeEvm(env.MPP_CURRENCY),
|
|
4375
|
+
rpcUrl: env.TEMPO_RPC_URL,
|
|
4376
|
+
recipient: payeeAddress,
|
|
4377
|
+
...env.MPP_FEE_PAYER_KEY ? { feePayerKey: env.MPP_FEE_PAYER_KEY } : {},
|
|
4378
|
+
...env.MPP_OPERATOR_KEY ? { operatorKey: env.MPP_OPERATOR_KEY, session: {} } : {}
|
|
4379
|
+
} : void 0;
|
|
4281
4380
|
return {
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4381
|
+
payeeAddress,
|
|
4382
|
+
baseUrl: env.BASE_URL,
|
|
4383
|
+
network: BASE_MAINNET_NETWORK,
|
|
4384
|
+
protocols,
|
|
4385
|
+
x402: {
|
|
4386
|
+
accepts,
|
|
4387
|
+
facilitators: {
|
|
4388
|
+
...options.x402Facilitators,
|
|
4389
|
+
solana: solanaFacilitator
|
|
4390
|
+
}
|
|
4391
|
+
},
|
|
4392
|
+
...mppConfig ? { mpp: mppConfig } : {},
|
|
4393
|
+
discovery: {
|
|
4394
|
+
title: options.title,
|
|
4395
|
+
version: options.version ?? "1.0.0",
|
|
4396
|
+
description: options.description,
|
|
4397
|
+
guidance: options.guidance,
|
|
4398
|
+
...options.contact ? { contact: options.contact } : {},
|
|
4399
|
+
...options.ownershipProofs ? { ownershipProofs: options.ownershipProofs } : {},
|
|
4400
|
+
...options.methodHints ? { methodHints: options.methodHints } : {},
|
|
4401
|
+
...options.serverUrl ? { serverUrl: options.serverUrl } : {}
|
|
4402
|
+
},
|
|
4403
|
+
...options.prices ? { prices: options.prices } : {},
|
|
4404
|
+
...options.plugin ? { plugin: options.plugin } : {},
|
|
4405
|
+
...options.kvStore ? { kvStore: options.kvStore } : {},
|
|
4406
|
+
strictRoutes: options.strictRoutes ?? false
|
|
4285
4407
|
};
|
|
4286
4408
|
}
|
|
4287
|
-
|
|
4288
|
-
// src/config/validate.ts
|
|
4289
|
-
function validateRouterConfig(config, options = {}) {
|
|
4290
|
-
const issues = getRouterConfigIssues(config, options);
|
|
4291
|
-
if (issues.length > 0) throw new RouterConfigError(issues);
|
|
4292
|
-
}
|
|
4293
4409
|
function getRouterConfigIssues(config, options = {}) {
|
|
4294
|
-
const env = options.env ??
|
|
4295
|
-
const issues = [];
|
|
4410
|
+
const env = options.env ?? {};
|
|
4296
4411
|
const protocols = config.protocols ?? ["x402"];
|
|
4412
|
+
const issues = [];
|
|
4297
4413
|
if (!config.baseUrl) {
|
|
4298
4414
|
issues.push({
|
|
4299
4415
|
code: "missing_base_url",
|
|
@@ -4306,83 +4422,16 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4306
4422
|
message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
|
|
4307
4423
|
});
|
|
4308
4424
|
}
|
|
4309
|
-
if (protocols.includes("x402"))
|
|
4310
|
-
|
|
4311
|
-
}
|
|
4312
|
-
if (protocols.includes("mpp")) {
|
|
4313
|
-
issues.push(...validateMppConfig(config, env));
|
|
4314
|
-
}
|
|
4425
|
+
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env, options));
|
|
4426
|
+
if (protocols.includes("mpp")) issues.push(...validateMppConfig(config));
|
|
4315
4427
|
return issues;
|
|
4316
4428
|
}
|
|
4317
4429
|
|
|
4318
|
-
// src/config/env.ts
|
|
4319
|
-
init_constants();
|
|
4320
|
-
function mppFromEnv(env, options = {}) {
|
|
4321
|
-
const secretKey = env.MPP_SECRET_KEY;
|
|
4322
|
-
const currency = env.MPP_CURRENCY;
|
|
4323
|
-
const rpcUrl = env.TEMPO_RPC_URL;
|
|
4324
|
-
const feePayerKey = options.feePayerKey ?? env.MPP_FEE_PAYER_KEY;
|
|
4325
|
-
const feePayerKeySource = options.feePayerKey !== void 0 ? "feePayerKey" : "MPP_FEE_PAYER_KEY";
|
|
4326
|
-
const hasAnyMppEnv = Boolean(secretKey || currency || rpcUrl || options.require);
|
|
4327
|
-
if (!hasAnyMppEnv) return void 0;
|
|
4328
|
-
const missing = [
|
|
4329
|
-
secretKey ? null : "MPP_SECRET_KEY",
|
|
4330
|
-
currency ? null : "MPP_CURRENCY",
|
|
4331
|
-
rpcUrl ? null : "TEMPO_RPC_URL"
|
|
4332
|
-
].filter(Boolean);
|
|
4333
|
-
if (missing.length > 0) {
|
|
4334
|
-
throw new Error(`MPP env is incomplete. Missing: ${missing.join(", ")}`);
|
|
4335
|
-
}
|
|
4336
|
-
if (!isEvmAddress(currency)) {
|
|
4337
|
-
throw new Error("MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address");
|
|
4338
|
-
}
|
|
4339
|
-
if (options.recipient && !isEvmAddress(options.recipient)) {
|
|
4340
|
-
throw new Error("MPP recipient must be a 0x-prefixed EVM address");
|
|
4341
|
-
}
|
|
4342
|
-
if (feePayerKey && !isEvmPrivateKey(feePayerKey)) {
|
|
4343
|
-
throw new Error(`${feePayerKeySource} must be a 0x-prefixed 32-byte EVM private key`);
|
|
4344
|
-
}
|
|
4345
|
-
return {
|
|
4346
|
-
secretKey,
|
|
4347
|
-
currency,
|
|
4348
|
-
rpcUrl,
|
|
4349
|
-
...options.recipient ? { recipient: options.recipient } : {},
|
|
4350
|
-
...feePayerKey ? { feePayerKey } : {}
|
|
4351
|
-
};
|
|
4352
|
-
}
|
|
4353
|
-
function x402AcceptsFromEnv(env, options = {}) {
|
|
4354
|
-
const payeeEnv = options.payeeEnv ?? "X402_WALLET_ADDRESS";
|
|
4355
|
-
const solanaPayeeEnv = options.solanaPayeeEnv ?? "SOLANA_PAYEE_ADDRESS";
|
|
4356
|
-
const payeeAddress = options.payeeAddress ?? env[payeeEnv];
|
|
4357
|
-
if (!payeeAddress) {
|
|
4358
|
-
throw new Error(`${payeeEnv} is required to build x402 accepts`);
|
|
4359
|
-
}
|
|
4360
|
-
const accepts = [
|
|
4361
|
-
{
|
|
4362
|
-
scheme: "exact",
|
|
4363
|
-
network: options.network ?? BASE_NETWORK,
|
|
4364
|
-
payTo: payeeAddress
|
|
4365
|
-
}
|
|
4366
|
-
];
|
|
4367
|
-
const solanaPayeeAddress = options.solanaPayeeAddress ?? env[solanaPayeeEnv];
|
|
4368
|
-
if (solanaPayeeAddress) {
|
|
4369
|
-
accepts.push({
|
|
4370
|
-
scheme: "exact",
|
|
4371
|
-
network: SOLANA_MAINNET_NETWORK,
|
|
4372
|
-
payTo: solanaPayeeAddress
|
|
4373
|
-
});
|
|
4374
|
-
}
|
|
4375
|
-
return accepts;
|
|
4376
|
-
}
|
|
4377
|
-
function paidOptionsForProtocols(protocols) {
|
|
4378
|
-
return { protocols: [...protocols] };
|
|
4379
|
-
}
|
|
4380
|
-
|
|
4381
4430
|
// src/init/x402.ts
|
|
4382
4431
|
async function initX402(config, configError) {
|
|
4383
4432
|
if (configError) return { initError: configError };
|
|
4384
4433
|
try {
|
|
4385
|
-
const { createX402Server: createX402Server2 } = await Promise.resolve().then(() => (
|
|
4434
|
+
const { createX402Server: createX402Server2 } = await Promise.resolve().then(() => (init_x402_server(), x402_server_exports));
|
|
4386
4435
|
const result = await createX402Server2(config);
|
|
4387
4436
|
await result.initPromise;
|
|
4388
4437
|
return {
|
|
@@ -4394,7 +4443,7 @@ async function initX402(config, configError) {
|
|
|
4394
4443
|
}
|
|
4395
4444
|
}
|
|
4396
4445
|
|
|
4397
|
-
// src/mppx
|
|
4446
|
+
// src/init/mppx.ts
|
|
4398
4447
|
function getMppxRequestContext(args) {
|
|
4399
4448
|
const {
|
|
4400
4449
|
Mppx,
|
|
@@ -4516,9 +4565,10 @@ function createRouter(config) {
|
|
|
4516
4565
|
const kvStore = resolveKvStore(config.kvStore);
|
|
4517
4566
|
const nonceStore = kvStore ? createKvNonceStore(kvStore) : new MemoryNonceStore();
|
|
4518
4567
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
4519
|
-
const network = config.network ??
|
|
4568
|
+
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
4520
4569
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
4521
4570
|
const configIssues = getRouterConfigIssues(config, {
|
|
4571
|
+
env: process.env,
|
|
4522
4572
|
requireCdpKeys: process.env.NODE_ENV === "production"
|
|
4523
4573
|
});
|
|
4524
4574
|
const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
|
|
@@ -4641,30 +4691,22 @@ function normalizePath(path) {
|
|
|
4641
4691
|
normalized = normalized.replace(/^api\/+/, "");
|
|
4642
4692
|
return normalized.replace(/\/+$/, "");
|
|
4643
4693
|
}
|
|
4694
|
+
function createRouterFromEnv(options) {
|
|
4695
|
+
return createRouter(routerConfigFromEnv(options));
|
|
4696
|
+
}
|
|
4644
4697
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4645
4698
|
0 && (module.exports = {
|
|
4646
|
-
|
|
4699
|
+
BASE_MAINNET_NETWORK,
|
|
4700
|
+
BASE_USDC_ADDRESS,
|
|
4701
|
+
BASE_USDC_DECIMALS,
|
|
4702
|
+
DEFAULT_SOLANA_FACILITATOR_URL,
|
|
4647
4703
|
HttpError,
|
|
4648
|
-
MemoryEntitlementStore,
|
|
4649
|
-
MemoryNonceStore,
|
|
4650
|
-
RouteBuilder,
|
|
4651
|
-
RouteRegistry,
|
|
4652
4704
|
RouterConfigError,
|
|
4653
|
-
SIWX_CHALLENGE_EXPIRY_MS,
|
|
4654
|
-
SIWX_ERROR_MESSAGES,
|
|
4655
4705
|
SOLANA_MAINNET_NETWORK,
|
|
4656
|
-
|
|
4706
|
+
TEMPO_USDC_ADDRESS,
|
|
4707
|
+
TEMPO_USDC_DECIMALS,
|
|
4657
4708
|
ZERO_EVM_ADDRESS,
|
|
4658
|
-
consolePlugin,
|
|
4659
|
-
createKvEntitlementStore,
|
|
4660
|
-
createKvMppStore,
|
|
4661
|
-
createKvNonceStore,
|
|
4662
4709
|
createRouter,
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
mppFromEnv,
|
|
4666
|
-
paidOptionsForProtocols,
|
|
4667
|
-
validateRouterConfig,
|
|
4668
|
-
withPrefix,
|
|
4669
|
-
x402AcceptsFromEnv
|
|
4710
|
+
createRouterFromEnv,
|
|
4711
|
+
routerConfigFromEnv
|
|
4670
4712
|
});
|