@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.js
CHANGED
|
@@ -9,14 +9,18 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/constants.ts
|
|
12
|
-
var
|
|
12
|
+
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;
|
|
13
13
|
var init_constants = __esm({
|
|
14
14
|
"src/constants.ts"() {
|
|
15
15
|
"use strict";
|
|
16
|
-
|
|
16
|
+
BASE_MAINNET_NETWORK = "eip155:8453";
|
|
17
17
|
SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
18
|
-
|
|
18
|
+
TEMPO_USDC_ADDRESS = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
19
|
+
TEMPO_USDC_DECIMALS = 6;
|
|
20
|
+
BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
21
|
+
BASE_USDC_DECIMALS = 6;
|
|
19
22
|
ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
23
|
+
DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.corbits.dev";
|
|
20
24
|
}
|
|
21
25
|
});
|
|
22
26
|
|
|
@@ -33,7 +37,7 @@ function getConfiguredX402Accepts(config) {
|
|
|
33
37
|
return [
|
|
34
38
|
{
|
|
35
39
|
scheme: "exact",
|
|
36
|
-
network: config.network ??
|
|
40
|
+
network: config.network ?? BASE_MAINNET_NETWORK,
|
|
37
41
|
payTo: config.payeeAddress
|
|
38
42
|
}
|
|
39
43
|
];
|
|
@@ -213,7 +217,10 @@ async function getAcceptsHeadersForFacilitator(facilitator) {
|
|
|
213
217
|
return {};
|
|
214
218
|
}
|
|
215
219
|
function resolveX402FacilitatorTarget(config, network, defaultEvmFacilitator) {
|
|
216
|
-
|
|
220
|
+
if (isSolanaNetwork(network)) {
|
|
221
|
+
return config.x402?.facilitators?.solana ?? DEFAULT_SOLANA_FACILITATOR_URL;
|
|
222
|
+
}
|
|
223
|
+
return defaultEvmFacilitator;
|
|
217
224
|
}
|
|
218
225
|
function normalizeFacilitatorTarget(target) {
|
|
219
226
|
return typeof target === "string" ? { url: target } : target;
|
|
@@ -226,19 +233,18 @@ function getNetworkFamily(network) {
|
|
|
226
233
|
function sameFacilitatorConfig(a, b) {
|
|
227
234
|
return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders && a.createAcceptsHeaders === b.createAcceptsHeaders;
|
|
228
235
|
}
|
|
229
|
-
var DEFAULT_SOLANA_FACILITATOR_URL;
|
|
230
236
|
var init_facilitators = __esm({
|
|
231
237
|
"src/protocols/x402/facilitators.ts"() {
|
|
232
238
|
"use strict";
|
|
233
239
|
init_evm();
|
|
234
240
|
init_solana();
|
|
235
|
-
|
|
241
|
+
init_constants();
|
|
236
242
|
}
|
|
237
243
|
});
|
|
238
244
|
|
|
239
|
-
// src/server.ts
|
|
240
|
-
var
|
|
241
|
-
__export(
|
|
245
|
+
// src/init/x402-server.ts
|
|
246
|
+
var x402_server_exports = {};
|
|
247
|
+
__export(x402_server_exports, {
|
|
242
248
|
createX402Server: () => createX402Server
|
|
243
249
|
});
|
|
244
250
|
async function createX402Server(config) {
|
|
@@ -315,8 +321,8 @@ function buildSupportedKinds(group) {
|
|
|
315
321
|
return [exactKind, uptoKind];
|
|
316
322
|
});
|
|
317
323
|
}
|
|
318
|
-
var
|
|
319
|
-
"src/server.ts"() {
|
|
324
|
+
var init_x402_server = __esm({
|
|
325
|
+
"src/init/x402-server.ts"() {
|
|
320
326
|
"use strict";
|
|
321
327
|
init_evm();
|
|
322
328
|
init_solana();
|
|
@@ -389,7 +395,7 @@ var AUTH_SCHEME = {
|
|
|
389
395
|
MPP_PAYMENT: "Payment "
|
|
390
396
|
};
|
|
391
397
|
|
|
392
|
-
// src/plugin.ts
|
|
398
|
+
// src/plugin/index.ts
|
|
393
399
|
function createDefaultContext(meta) {
|
|
394
400
|
const ctx = {
|
|
395
401
|
requestId: meta.requestId,
|
|
@@ -425,46 +431,8 @@ function firePluginHook(plugin, method, ...args) {
|
|
|
425
431
|
return void 0;
|
|
426
432
|
}
|
|
427
433
|
}
|
|
428
|
-
function consolePlugin() {
|
|
429
|
-
return {
|
|
430
|
-
onRequest(meta) {
|
|
431
|
-
const ctx = createDefaultContext(meta);
|
|
432
|
-
return ctx;
|
|
433
|
-
},
|
|
434
|
-
onAuthVerified(_ctx, auth) {
|
|
435
|
-
const wallet = auth.wallet ? ` wallet=${auth.wallet}` : "";
|
|
436
|
-
console.log(`[router] AUTH ${auth.authMode} ${auth.route}${wallet}`);
|
|
437
|
-
},
|
|
438
|
-
onPaymentVerified(_ctx, payment) {
|
|
439
|
-
console.log(`[router] VERIFIED ${payment.protocol} ${payment.payer} ${payment.amount}`);
|
|
440
|
-
},
|
|
441
|
-
onPaymentSettled(_ctx, settlement) {
|
|
442
|
-
console.log(`[router] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);
|
|
443
|
-
},
|
|
444
|
-
onResponse(ctx, response) {
|
|
445
|
-
const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : "";
|
|
446
|
-
console.log(
|
|
447
|
-
`[router] ${ctx.route} \u2192 ${response.statusCode} (${response.duration}ms)${wallet}`
|
|
448
|
-
);
|
|
449
|
-
},
|
|
450
|
-
onError(_ctx, error) {
|
|
451
|
-
console.error(`[router] ERROR ${error.status}: ${error.message}`);
|
|
452
|
-
},
|
|
453
|
-
onAlert(_ctx, alert) {
|
|
454
|
-
const logFn = alert.level === "critical" || alert.level === "error" ? console.error : alert.level === "warn" ? console.warn : console.log;
|
|
455
|
-
logFn(
|
|
456
|
-
`[router] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
457
|
-
alert.meta ?? ""
|
|
458
|
-
);
|
|
459
|
-
},
|
|
460
|
-
onProviderQuota(_ctx, event) {
|
|
461
|
-
const logFn = event.level === "critical" ? console.error : event.level === "warn" ? console.warn : console.log;
|
|
462
|
-
logFn(`[router] QUOTA ${event.level.toUpperCase()} ${event.provider}: ${event.message}`);
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
434
|
|
|
467
|
-
// src/
|
|
435
|
+
// src/plugin/reporter.ts
|
|
468
436
|
function createReporter(plugin, pluginCtx, route) {
|
|
469
437
|
return (level, message, meta) => {
|
|
470
438
|
firePluginHook(plugin, "onAlert", pluginCtx, {
|
|
@@ -476,7 +444,7 @@ function createReporter(plugin, pluginCtx, route) {
|
|
|
476
444
|
};
|
|
477
445
|
}
|
|
478
446
|
|
|
479
|
-
// src/pipeline/
|
|
447
|
+
// src/pipeline/steps/preflight.ts
|
|
480
448
|
function preflight(routeEntry, handler, deps, request) {
|
|
481
449
|
const meta = buildMeta(request, routeEntry);
|
|
482
450
|
const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
|
|
@@ -506,10 +474,10 @@ function buildMeta(request, routeEntry) {
|
|
|
506
474
|
};
|
|
507
475
|
}
|
|
508
476
|
|
|
509
|
-
// src/pipeline/
|
|
477
|
+
// src/pipeline/steps/parse-body.ts
|
|
510
478
|
import { NextResponse } from "next/server";
|
|
511
479
|
|
|
512
|
-
// src/body.ts
|
|
480
|
+
// src/pipeline/body.ts
|
|
513
481
|
async function bufferBody(request) {
|
|
514
482
|
const text = await request.text();
|
|
515
483
|
if (!text) return void 0;
|
|
@@ -533,7 +501,19 @@ function validateBody(parsed, schema) {
|
|
|
533
501
|
};
|
|
534
502
|
}
|
|
535
503
|
|
|
536
|
-
// src/
|
|
504
|
+
// src/plugin/events.ts
|
|
505
|
+
function fireAuthVerified(ctx, event) {
|
|
506
|
+
firePluginHook(ctx.deps.plugin, "onAuthVerified", ctx.pluginCtx, {
|
|
507
|
+
...event,
|
|
508
|
+
route: ctx.routeEntry.key
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function firePaymentVerified(ctx, event) {
|
|
512
|
+
firePluginHook(ctx.deps.plugin, "onPaymentVerified", ctx.pluginCtx, event);
|
|
513
|
+
}
|
|
514
|
+
function firePaymentSettled(ctx, event) {
|
|
515
|
+
firePluginHook(ctx.deps.plugin, "onPaymentSettled", ctx.pluginCtx, event);
|
|
516
|
+
}
|
|
537
517
|
function firePluginResponse(ctx, response, requestBody, responseBody) {
|
|
538
518
|
firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
|
|
539
519
|
statusCode: response.status,
|
|
@@ -552,8 +532,37 @@ function firePluginResponse(ctx, response, requestBody, responseBody) {
|
|
|
552
532
|
});
|
|
553
533
|
}
|
|
554
534
|
}
|
|
535
|
+
function fireProviderQuota(ctx, response, handlerResult) {
|
|
536
|
+
const { providerName, providerConfig } = ctx.routeEntry;
|
|
537
|
+
if (!providerName || !providerConfig?.extractQuota) return;
|
|
538
|
+
if (response.status >= 400) return;
|
|
539
|
+
try {
|
|
540
|
+
const quota = providerConfig.extractQuota(handlerResult, response.headers);
|
|
541
|
+
if (!quota) return;
|
|
542
|
+
const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
|
|
543
|
+
const overage = providerConfig.overage ?? "same-rate";
|
|
544
|
+
const event = {
|
|
545
|
+
provider: providerName,
|
|
546
|
+
route: ctx.routeEntry.key,
|
|
547
|
+
remaining: quota.remaining,
|
|
548
|
+
limit: quota.limit,
|
|
549
|
+
spend: quota.spend,
|
|
550
|
+
level,
|
|
551
|
+
overage,
|
|
552
|
+
message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
|
|
553
|
+
};
|
|
554
|
+
firePluginHook(ctx.deps.plugin, "onProviderQuota", ctx.pluginCtx, event);
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function computeQuotaLevel(remaining, warn, critical) {
|
|
559
|
+
if (remaining === null) return "healthy";
|
|
560
|
+
if (critical !== void 0 && remaining <= critical) return "critical";
|
|
561
|
+
if (warn !== void 0 && remaining <= warn) return "warn";
|
|
562
|
+
return "healthy";
|
|
563
|
+
}
|
|
555
564
|
|
|
556
|
-
// src/pipeline/
|
|
565
|
+
// src/pipeline/steps/parse-body.ts
|
|
557
566
|
async function parseBody(ctx, request = ctx.request) {
|
|
558
567
|
if (!ctx.routeEntry.bodySchema) return { ok: true, data: void 0 };
|
|
559
568
|
const raw = await bufferBody(request);
|
|
@@ -567,15 +576,7 @@ async function parseBody(ctx, request = ctx.request) {
|
|
|
567
576
|
return { ok: false, response };
|
|
568
577
|
}
|
|
569
578
|
|
|
570
|
-
// src/pipeline/
|
|
571
|
-
function parseQuery(request, routeEntry) {
|
|
572
|
-
if (!routeEntry.querySchema) return void 0;
|
|
573
|
-
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
574
|
-
const result = routeEntry.querySchema.safeParse(params);
|
|
575
|
-
return result.success ? result.data : params;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// src/pipeline/context/errors.ts
|
|
579
|
+
// src/pipeline/steps/errors.ts
|
|
579
580
|
function errorStatus(error, fallback) {
|
|
580
581
|
const status = error?.status;
|
|
581
582
|
return typeof status === "number" ? status : fallback;
|
|
@@ -588,7 +589,7 @@ function handlerFailureError(response) {
|
|
|
588
589
|
return Object.assign(new Error(message), { status: response.status });
|
|
589
590
|
}
|
|
590
591
|
|
|
591
|
-
// src/pipeline/
|
|
592
|
+
// src/pipeline/steps/fail.ts
|
|
592
593
|
import { NextResponse as NextResponse2 } from "next/server";
|
|
593
594
|
function fail(ctx, status, message, requestBody) {
|
|
594
595
|
const response = NextResponse2.json({ success: false, error: message }, { status });
|
|
@@ -596,7 +597,7 @@ function fail(ctx, status, message, requestBody) {
|
|
|
596
597
|
return response;
|
|
597
598
|
}
|
|
598
599
|
|
|
599
|
-
// src/pipeline/
|
|
600
|
+
// src/pipeline/steps/run-validate.ts
|
|
600
601
|
async function runValidate(ctx, body) {
|
|
601
602
|
if (!ctx.routeEntry.validateFn) return null;
|
|
602
603
|
try {
|
|
@@ -619,6 +620,14 @@ var HttpError = class extends Error {
|
|
|
619
620
|
}
|
|
620
621
|
};
|
|
621
622
|
|
|
623
|
+
// src/pipeline/steps/parse-query.ts
|
|
624
|
+
function parseQuery(request, routeEntry) {
|
|
625
|
+
if (!routeEntry.querySchema) return void 0;
|
|
626
|
+
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
627
|
+
const result = routeEntry.querySchema.safeParse(params);
|
|
628
|
+
return result.success ? result.data : params;
|
|
629
|
+
}
|
|
630
|
+
|
|
622
631
|
// src/pipeline/flows/static/static-invoke.ts
|
|
623
632
|
function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
624
633
|
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
|
|
@@ -636,14 +645,7 @@ function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
636
645
|
wallet,
|
|
637
646
|
payment,
|
|
638
647
|
account,
|
|
639
|
-
alert
|
|
640
|
-
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
641
|
-
level,
|
|
642
|
-
message,
|
|
643
|
-
route: ctx.routeEntry.key,
|
|
644
|
-
meta: alertMeta
|
|
645
|
-
});
|
|
646
|
-
},
|
|
648
|
+
alert: ctx.report,
|
|
647
649
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
648
650
|
};
|
|
649
651
|
}
|
|
@@ -687,45 +689,14 @@ function isThenable(value) {
|
|
|
687
689
|
return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
688
690
|
}
|
|
689
691
|
|
|
690
|
-
// src/pipeline/
|
|
691
|
-
function fireProviderQuota(ctx, response, handlerResult) {
|
|
692
|
-
const { providerName, providerConfig } = ctx.routeEntry;
|
|
693
|
-
if (!providerName || !providerConfig?.extractQuota) return;
|
|
694
|
-
if (response.status >= 400) return;
|
|
695
|
-
try {
|
|
696
|
-
const quota = providerConfig.extractQuota(handlerResult, response.headers);
|
|
697
|
-
if (!quota) return;
|
|
698
|
-
const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
|
|
699
|
-
const overage = providerConfig.overage ?? "same-rate";
|
|
700
|
-
const event = {
|
|
701
|
-
provider: providerName,
|
|
702
|
-
route: ctx.routeEntry.key,
|
|
703
|
-
remaining: quota.remaining,
|
|
704
|
-
limit: quota.limit,
|
|
705
|
-
spend: quota.spend,
|
|
706
|
-
level,
|
|
707
|
-
overage,
|
|
708
|
-
message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
|
|
709
|
-
};
|
|
710
|
-
firePluginHook(ctx.deps.plugin, "onProviderQuota", ctx.pluginCtx, event);
|
|
711
|
-
} catch {
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
function computeQuotaLevel(remaining, warn, critical) {
|
|
715
|
-
if (remaining === null) return "healthy";
|
|
716
|
-
if (critical !== void 0 && remaining <= critical) return "critical";
|
|
717
|
-
if (warn !== void 0 && remaining <= warn) return "warn";
|
|
718
|
-
return "healthy";
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// src/pipeline/context/finalize/response.ts
|
|
692
|
+
// src/pipeline/steps/finalize/response.ts
|
|
722
693
|
function finalize(ctx, response, rawResult, requestBody) {
|
|
723
694
|
fireProviderQuota(ctx, response, rawResult);
|
|
724
695
|
firePluginResponse(ctx, response, requestBody, rawResult);
|
|
725
696
|
return response;
|
|
726
697
|
}
|
|
727
698
|
|
|
728
|
-
// src/pipeline/
|
|
699
|
+
// src/pipeline/steps/grant-entitlement.ts
|
|
729
700
|
async function grantEntitlementIfSiwx(ctx, wallet) {
|
|
730
701
|
if (!ctx.routeEntry.siwxEnabled) return;
|
|
731
702
|
try {
|
|
@@ -738,7 +709,7 @@ async function grantEntitlementIfSiwx(ctx, wallet) {
|
|
|
738
709
|
}
|
|
739
710
|
}
|
|
740
711
|
|
|
741
|
-
// src/pipeline/
|
|
712
|
+
// src/pipeline/steps/settlement-context.ts
|
|
742
713
|
function settlementContext(ctx, scope) {
|
|
743
714
|
return {
|
|
744
715
|
route: ctx.routeEntry.key,
|
|
@@ -752,7 +723,7 @@ function settlementContext(ctx, scope) {
|
|
|
752
723
|
};
|
|
753
724
|
}
|
|
754
725
|
|
|
755
|
-
// src/pipeline/
|
|
726
|
+
// src/pipeline/steps/run-settlement-error.ts
|
|
756
727
|
async function runSettlementError(ctx, scope, error, phase) {
|
|
757
728
|
const hook = ctx.routeEntry.settlement?.onSettlementError;
|
|
758
729
|
if (!hook) return;
|
|
@@ -764,7 +735,7 @@ async function runSettlementError(ctx, scope, error, phase) {
|
|
|
764
735
|
}
|
|
765
736
|
}
|
|
766
737
|
|
|
767
|
-
// src/pipeline/
|
|
738
|
+
// src/pipeline/steps/run-after-settle.ts
|
|
768
739
|
async function runAfterSettle(ctx, scope) {
|
|
769
740
|
const hook = ctx.routeEntry.settlement?.afterSettle;
|
|
770
741
|
if (!hook) return;
|
|
@@ -777,11 +748,11 @@ async function runAfterSettle(ctx, scope) {
|
|
|
777
748
|
}
|
|
778
749
|
}
|
|
779
750
|
|
|
780
|
-
// src/pipeline/
|
|
751
|
+
// src/pipeline/steps/finalize/epilogue.ts
|
|
781
752
|
async function runPostSettleEpilogue(args) {
|
|
782
753
|
const { ctx, strategy, wallet, settle, afterSettleScope, rawResult, body } = args;
|
|
783
754
|
await grantEntitlementIfSiwx(ctx, wallet);
|
|
784
|
-
|
|
755
|
+
firePaymentSettled(ctx, {
|
|
785
756
|
protocol: strategy.protocol,
|
|
786
757
|
payer: wallet,
|
|
787
758
|
transaction: settle.settledPayment.transaction ?? "",
|
|
@@ -791,7 +762,7 @@ async function runPostSettleEpilogue(args) {
|
|
|
791
762
|
return finalize(ctx, settle.response, rawResult, body);
|
|
792
763
|
}
|
|
793
764
|
|
|
794
|
-
// src/pipeline/
|
|
765
|
+
// src/pipeline/steps/finalize/request.ts
|
|
795
766
|
async function settleAndFinalizeRequest(args) {
|
|
796
767
|
const { ctx, strategy, verifyOutcome, scope, rawResult, body, billedAmount, onSettleError } = args;
|
|
797
768
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -824,7 +795,7 @@ async function settleAndFinalizeRequest(args) {
|
|
|
824
795
|
});
|
|
825
796
|
}
|
|
826
797
|
|
|
827
|
-
// src/pipeline/
|
|
798
|
+
// src/pipeline/steps/finalize/stream.ts
|
|
828
799
|
async function settleAndFinalizeStream(args) {
|
|
829
800
|
const { ctx, strategy, verifyOutcome, source, account, body, bindChannelCharge } = args;
|
|
830
801
|
const { request, routeEntry, deps, report } = ctx;
|
|
@@ -862,7 +833,7 @@ async function settleAndFinalizeStream(args) {
|
|
|
862
833
|
});
|
|
863
834
|
}
|
|
864
835
|
|
|
865
|
-
// src/pipeline/
|
|
836
|
+
// src/pipeline/steps/run-handler-only.ts
|
|
866
837
|
async function runHandlerOnly(ctx, wallet, account) {
|
|
867
838
|
const body = await parseBody(ctx);
|
|
868
839
|
if (!body.ok) return body.response;
|
|
@@ -872,7 +843,7 @@ async function runHandlerOnly(ctx, wallet, account) {
|
|
|
872
843
|
return finalize(ctx, result.response, result.rawResult, body.data);
|
|
873
844
|
}
|
|
874
845
|
|
|
875
|
-
// src/pipeline/
|
|
846
|
+
// src/pipeline/steps/run-before-settle.ts
|
|
876
847
|
async function runBeforeSettle(ctx, scope) {
|
|
877
848
|
const hook = ctx.routeEntry.settlement?.beforeSettle;
|
|
878
849
|
if (!hook) return null;
|
|
@@ -889,7 +860,7 @@ async function runBeforeSettle(ctx, scope) {
|
|
|
889
860
|
}
|
|
890
861
|
}
|
|
891
862
|
|
|
892
|
-
// src/pipeline/
|
|
863
|
+
// src/pipeline/steps/run-settled-handler-error.ts
|
|
893
864
|
async function runSettledHandlerError(ctx, scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
|
|
894
865
|
const hook = ctx.routeEntry.settlement?.onSettledHandlerError;
|
|
895
866
|
if (!hook) return;
|
|
@@ -963,7 +934,7 @@ async function buildSIWXExtension() {
|
|
|
963
934
|
return declareSIWxExtension();
|
|
964
935
|
}
|
|
965
936
|
|
|
966
|
-
// src/pipeline/
|
|
937
|
+
// src/pipeline/steps/try-siwx-fast-path.ts
|
|
967
938
|
async function trySiwxFastPath(ctx, account) {
|
|
968
939
|
const { request, routeEntry, deps } = ctx;
|
|
969
940
|
if (!routeEntry.siwxEnabled) return null;
|
|
@@ -975,22 +946,18 @@ async function trySiwxFastPath(ctx, account) {
|
|
|
975
946
|
ctx.pluginCtx.setVerifiedWallet(wallet);
|
|
976
947
|
const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
|
|
977
948
|
if (!entitled) return null;
|
|
978
|
-
|
|
979
|
-
authMode: "siwx",
|
|
980
|
-
wallet,
|
|
981
|
-
route: routeEntry.key
|
|
982
|
-
});
|
|
949
|
+
fireAuthVerified(ctx, { authMode: "siwx", wallet });
|
|
983
950
|
return runHandlerOnly(ctx, wallet, account);
|
|
984
951
|
}
|
|
985
952
|
|
|
986
|
-
// src/pipeline/
|
|
953
|
+
// src/pipeline/steps/should-parse-body-early.ts
|
|
987
954
|
function shouldParseBodyEarly(incomingStrategy, routeEntry, pricing) {
|
|
988
955
|
if (incomingStrategy) return false;
|
|
989
956
|
if (!routeEntry.bodySchema) return false;
|
|
990
957
|
return (pricing?.needsBody ?? false) || !!routeEntry.validateFn;
|
|
991
958
|
}
|
|
992
959
|
|
|
993
|
-
// src/pipeline/
|
|
960
|
+
// src/pipeline/steps/resolve-early-body.ts
|
|
994
961
|
async function resolveEarlyBody(args) {
|
|
995
962
|
const { ctx, pricing, incomingStrategy } = args;
|
|
996
963
|
if (!shouldParseBodyEarly(incomingStrategy, ctx.routeEntry, pricing)) {
|
|
@@ -1022,24 +989,19 @@ function extractBearerToken(header) {
|
|
|
1022
989
|
return null;
|
|
1023
990
|
}
|
|
1024
991
|
|
|
1025
|
-
// src/pipeline/
|
|
992
|
+
// src/pipeline/steps/run-api-key-gate.ts
|
|
1026
993
|
async function runApiKeyGate(ctx) {
|
|
1027
|
-
const { request, routeEntry
|
|
994
|
+
const { request, routeEntry } = ctx;
|
|
1028
995
|
if (!routeEntry.apiKeyResolver) return { ok: true, account: void 0 };
|
|
1029
996
|
const apiKeyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
|
|
1030
997
|
if (!apiKeyResult.valid) {
|
|
1031
998
|
return { ok: false, response: fail(ctx, 401, "Invalid or missing API key") };
|
|
1032
999
|
}
|
|
1033
|
-
|
|
1034
|
-
authMode: "apiKey",
|
|
1035
|
-
wallet: null,
|
|
1036
|
-
route: routeEntry.key,
|
|
1037
|
-
account: apiKeyResult.account
|
|
1038
|
-
});
|
|
1000
|
+
fireAuthVerified(ctx, { authMode: "apiKey", wallet: null, account: apiKeyResult.account });
|
|
1039
1001
|
return { ok: true, account: apiKeyResult.account };
|
|
1040
1002
|
}
|
|
1041
1003
|
|
|
1042
|
-
// src/pipeline/
|
|
1004
|
+
// src/pipeline/steps/protocol-init-error.ts
|
|
1043
1005
|
function protocolInitError(routeEntry, deps) {
|
|
1044
1006
|
if (!routeEntry.pricing) return null;
|
|
1045
1007
|
const errors = [];
|
|
@@ -1062,12 +1024,7 @@ async function runApiKeyOnlyFlow(ctx) {
|
|
|
1062
1024
|
}
|
|
1063
1025
|
const result = await verifyApiKey(ctx.request, ctx.routeEntry.apiKeyResolver);
|
|
1064
1026
|
if (!result.valid) return fail(ctx, 401, "Invalid or missing API key");
|
|
1065
|
-
|
|
1066
|
-
authMode: "apiKey",
|
|
1067
|
-
wallet: null,
|
|
1068
|
-
route: ctx.routeEntry.key,
|
|
1069
|
-
account: result.account
|
|
1070
|
-
});
|
|
1027
|
+
fireAuthVerified(ctx, { authMode: "apiKey", wallet: null, account: result.account });
|
|
1071
1028
|
return runHandlerOnly(ctx, null, result.account);
|
|
1072
1029
|
}
|
|
1073
1030
|
|
|
@@ -2157,21 +2114,6 @@ function reportSettleFailure(report, err, network) {
|
|
|
2157
2114
|
});
|
|
2158
2115
|
}
|
|
2159
2116
|
|
|
2160
|
-
// src/protocols/detect.ts
|
|
2161
|
-
function detectProtocol(request) {
|
|
2162
|
-
if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
|
|
2163
|
-
return "x402";
|
|
2164
|
-
}
|
|
2165
|
-
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
2166
|
-
if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
|
|
2167
|
-
return "mpp";
|
|
2168
|
-
}
|
|
2169
|
-
if (request.headers.get(HEADERS.SIWX)) {
|
|
2170
|
-
return "siwx";
|
|
2171
|
-
}
|
|
2172
|
-
return null;
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
2117
|
// src/protocols/index.ts
|
|
2176
2118
|
var STRATEGIES = {
|
|
2177
2119
|
x402: x402Strategy,
|
|
@@ -2196,9 +2138,9 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2196
2138
|
const { routeEntry } = ctx;
|
|
2197
2139
|
let extensions;
|
|
2198
2140
|
try {
|
|
2199
|
-
const { z } = await import("zod");
|
|
2141
|
+
const { z: z2 } = await import("zod");
|
|
2200
2142
|
const { declareDiscoveryExtension } = await import("@x402/extensions/bazaar");
|
|
2201
|
-
const toJSON = (schema) =>
|
|
2143
|
+
const toJSON = (schema) => z2.toJSONSchema(schema, {
|
|
2202
2144
|
target: "draft-2020-12",
|
|
2203
2145
|
unrepresentable: "any"
|
|
2204
2146
|
});
|
|
@@ -2219,11 +2161,10 @@ async function buildChallengeExtensions(ctx) {
|
|
|
2219
2161
|
extensions = declareDiscoveryExtension(config);
|
|
2220
2162
|
}
|
|
2221
2163
|
} catch (err) {
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
});
|
|
2164
|
+
ctx.report(
|
|
2165
|
+
"warn",
|
|
2166
|
+
`Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2167
|
+
);
|
|
2227
2168
|
}
|
|
2228
2169
|
if (routeEntry.siwxEnabled) {
|
|
2229
2170
|
try {
|
|
@@ -2357,7 +2298,7 @@ async function runDynamicChannelMgmtFlow(args) {
|
|
|
2357
2298
|
return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2358
2299
|
}
|
|
2359
2300
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2360
|
-
|
|
2301
|
+
firePaymentVerified(ctx, {
|
|
2361
2302
|
protocol: strategy.protocol,
|
|
2362
2303
|
payer: verifyOutcome.wallet,
|
|
2363
2304
|
amount: price,
|
|
@@ -2465,14 +2406,7 @@ async function invokeDynamic(ctx, wallet, account, body, payment) {
|
|
|
2465
2406
|
wallet,
|
|
2466
2407
|
payment,
|
|
2467
2408
|
account,
|
|
2468
|
-
alert
|
|
2469
|
-
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
2470
|
-
level,
|
|
2471
|
-
message,
|
|
2472
|
-
route: ctx.routeEntry.key,
|
|
2473
|
-
meta: alertMeta
|
|
2474
|
-
});
|
|
2475
|
-
},
|
|
2409
|
+
alert: ctx.report,
|
|
2476
2410
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2477
2411
|
};
|
|
2478
2412
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
@@ -2537,7 +2471,7 @@ function resolveDynamicPreflight(strategy, request, routeEntry) {
|
|
|
2537
2471
|
// src/pipeline/flows/dynamic/dynamic-request.ts
|
|
2538
2472
|
async function runDynamicRequestFlow(args) {
|
|
2539
2473
|
const { ctx, strategy, verifyOutcome, account, body, result } = args;
|
|
2540
|
-
const {
|
|
2474
|
+
const { routeEntry } = ctx;
|
|
2541
2475
|
const settleScope = {
|
|
2542
2476
|
wallet: verifyOutcome.wallet,
|
|
2543
2477
|
account,
|
|
@@ -2563,11 +2497,10 @@ async function runDynamicRequestFlow(args) {
|
|
|
2563
2497
|
billedAmount,
|
|
2564
2498
|
onSettleError: async (error, failMessage) => {
|
|
2565
2499
|
await runSettlementError(ctx, settleScope, error, "settle");
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
});
|
|
2500
|
+
ctx.report(
|
|
2501
|
+
"critical",
|
|
2502
|
+
`${strategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`
|
|
2503
|
+
);
|
|
2571
2504
|
}
|
|
2572
2505
|
});
|
|
2573
2506
|
}
|
|
@@ -2637,7 +2570,7 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
2637
2570
|
return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2638
2571
|
}
|
|
2639
2572
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2640
|
-
|
|
2573
|
+
firePaymentVerified(ctx, {
|
|
2641
2574
|
protocol: incomingStrategy.protocol,
|
|
2642
2575
|
payer: verifyOutcome.wallet,
|
|
2643
2576
|
amount: price,
|
|
@@ -2703,7 +2636,6 @@ async function resolveStaticBodyAndPrice(args) {
|
|
|
2703
2636
|
// src/pipeline/flows/static/static-request.ts
|
|
2704
2637
|
async function runStaticRequestFlow(args) {
|
|
2705
2638
|
const { ctx, strategy, verifyOutcome, account, body, price, result } = args;
|
|
2706
|
-
const { deps, routeEntry } = ctx;
|
|
2707
2639
|
const settleScope = {
|
|
2708
2640
|
wallet: verifyOutcome.wallet,
|
|
2709
2641
|
account,
|
|
@@ -2744,11 +2676,10 @@ async function runStaticRequestFlow(args) {
|
|
|
2744
2676
|
billedAmount: price,
|
|
2745
2677
|
onSettleError: async (error, failMessage) => {
|
|
2746
2678
|
await runSettlementError(ctx, settleScope, error, "settle");
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
});
|
|
2679
|
+
ctx.report(
|
|
2680
|
+
"critical",
|
|
2681
|
+
`${strategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`
|
|
2682
|
+
);
|
|
2752
2683
|
}
|
|
2753
2684
|
});
|
|
2754
2685
|
}
|
|
@@ -2794,7 +2725,7 @@ async function runStaticPaidFlow(ctx) {
|
|
|
2794
2725
|
return build402(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
2795
2726
|
}
|
|
2796
2727
|
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2797
|
-
|
|
2728
|
+
firePaymentVerified(ctx, {
|
|
2798
2729
|
protocol: incomingStrategy.protocol,
|
|
2799
2730
|
payer: verifyOutcome.wallet,
|
|
2800
2731
|
amount: price,
|
|
@@ -2978,6 +2909,21 @@ async function createKvMppStore(kv, options) {
|
|
|
2978
2909
|
});
|
|
2979
2910
|
}
|
|
2980
2911
|
|
|
2912
|
+
// src/protocols/detect.ts
|
|
2913
|
+
function detectProtocol(request) {
|
|
2914
|
+
if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
|
|
2915
|
+
return "x402";
|
|
2916
|
+
}
|
|
2917
|
+
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
2918
|
+
if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
|
|
2919
|
+
return "mpp";
|
|
2920
|
+
}
|
|
2921
|
+
if (request.headers.get(HEADERS.SIWX)) {
|
|
2922
|
+
return "siwx";
|
|
2923
|
+
}
|
|
2924
|
+
return null;
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2981
2927
|
// src/protocols/mpp/siwx-mode.ts
|
|
2982
2928
|
import { Credential as Credential2 } from "mppx";
|
|
2983
2929
|
async function verifyMppSiwx(request, mppx) {
|
|
@@ -3017,11 +2963,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3017
2963
|
}
|
|
3018
2964
|
if (mppSiwxResult.valid) {
|
|
3019
2965
|
ctx.pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
|
|
3020
|
-
|
|
3021
|
-
authMode: "siwx",
|
|
3022
|
-
wallet: mppSiwxResult.wallet,
|
|
3023
|
-
route: routeEntry.key
|
|
3024
|
-
});
|
|
2966
|
+
fireAuthVerified(ctx, { authMode: "siwx", wallet: mppSiwxResult.wallet });
|
|
3025
2967
|
const authResponse = await runHandlerOnly(ctx, mppSiwxResult.wallet, void 0);
|
|
3026
2968
|
if (authResponse.status < 400) {
|
|
3027
2969
|
return mppSiwxResult.withReceipt(authResponse);
|
|
@@ -3043,11 +2985,7 @@ async function runSiwxOnlyFlow(ctx) {
|
|
|
3043
2985
|
}
|
|
3044
2986
|
const wallet = normalizeWalletAddress(siwx.wallet);
|
|
3045
2987
|
ctx.pluginCtx.setVerifiedWallet(wallet);
|
|
3046
|
-
|
|
3047
|
-
authMode: "siwx",
|
|
3048
|
-
wallet,
|
|
3049
|
-
route: routeEntry.key
|
|
3050
|
-
});
|
|
2988
|
+
fireAuthVerified(ctx, { authMode: "siwx", wallet });
|
|
3051
2989
|
return runHandlerOnly(ctx, wallet, void 0);
|
|
3052
2990
|
}
|
|
3053
2991
|
async function buildSiwxChallenge(ctx) {
|
|
@@ -3140,7 +3078,7 @@ async function runUnprotectedFlow(ctx) {
|
|
|
3140
3078
|
return runHandlerOnly(ctx, null, void 0);
|
|
3141
3079
|
}
|
|
3142
3080
|
|
|
3143
|
-
// src/orchestrate.ts
|
|
3081
|
+
// src/pipeline/orchestrate.ts
|
|
3144
3082
|
function createRequestHandler(routeEntry, handler, deps) {
|
|
3145
3083
|
return async (request) => {
|
|
3146
3084
|
await deps.initPromise;
|
|
@@ -3915,7 +3853,7 @@ function toProtocolObject(protocol, mppInfo) {
|
|
|
3915
3853
|
mpp: {
|
|
3916
3854
|
method: mppInfo?.method ?? "tempo",
|
|
3917
3855
|
intent: mppInfo?.intent ?? "charge",
|
|
3918
|
-
currency: mppInfo?.currency ??
|
|
3856
|
+
currency: mppInfo?.currency ?? TEMPO_USDC_ADDRESS
|
|
3919
3857
|
}
|
|
3920
3858
|
};
|
|
3921
3859
|
}
|
|
@@ -3995,142 +3933,256 @@ function formatRouterConfigIssues(issues) {
|
|
|
3995
3933
|
return issues.map((issue) => issue.message).join("\n");
|
|
3996
3934
|
}
|
|
3997
3935
|
|
|
3998
|
-
// src/config/
|
|
3999
|
-
|
|
3936
|
+
// src/config/schema.ts
|
|
3937
|
+
init_constants();
|
|
3938
|
+
import { z } from "zod";
|
|
4000
3939
|
|
|
4001
|
-
// src/config/
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
}
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
3940
|
+
// src/config/utils.ts
|
|
3941
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
3942
|
+
var EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
3943
|
+
var EVM_PRIVATE_KEY_RE = /^0x[a-fA-F0-9]{64}$/;
|
|
3944
|
+
var SOLANA_ADDRESS_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
3945
|
+
var ZERO_EVM_ADDRESS_RE = /^0x0{40}$/i;
|
|
3946
|
+
function isUrl(value) {
|
|
3947
|
+
try {
|
|
3948
|
+
new URL(value);
|
|
3949
|
+
return true;
|
|
3950
|
+
} catch {
|
|
3951
|
+
return false;
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
var isEvmAddress = (v) => EVM_ADDRESS_RE.test(v);
|
|
3955
|
+
var isEvmPrivateKey = (v) => EVM_PRIVATE_KEY_RE.test(v);
|
|
3956
|
+
var isPlaceholderEvm = (v) => ZERO_EVM_ADDRESS_RE.test(v);
|
|
3957
|
+
var isSolanaAddress = (v) => SOLANA_ADDRESS_RE.test(v);
|
|
3958
|
+
var isX402Network = (v) => v.startsWith("eip155:") || v.startsWith("solana:");
|
|
3959
|
+
var canonicalizeEvm = (addr) => addr.toLowerCase();
|
|
3960
|
+
function operatorAddressesCollide(opKey, fpKey) {
|
|
3961
|
+
if (!opKey || !fpKey || !isEvmPrivateKey(opKey) || !isEvmPrivateKey(fpKey)) return null;
|
|
3962
|
+
const op = privateKeyToAccount(opKey).address.toLowerCase();
|
|
3963
|
+
const fp = privateKeyToAccount(fpKey).address.toLowerCase();
|
|
3964
|
+
return op === fp ? op : null;
|
|
3965
|
+
}
|
|
3966
|
+
function trimAll(raw) {
|
|
3967
|
+
const out = {};
|
|
3968
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
3969
|
+
if (typeof v !== "string") {
|
|
3970
|
+
out[k] = void 0;
|
|
3971
|
+
continue;
|
|
3972
|
+
}
|
|
3973
|
+
const trimmed = v.trim();
|
|
3974
|
+
out[k] = trimmed.length > 0 ? trimmed : void 0;
|
|
3975
|
+
}
|
|
3976
|
+
return out;
|
|
4021
3977
|
}
|
|
4022
3978
|
|
|
4023
|
-
// src/config/
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
3979
|
+
// src/config/schema.ts
|
|
3980
|
+
function addIssue(ctx, params, message, path = []) {
|
|
3981
|
+
ctx.addIssue({ code: "custom", path, params, message });
|
|
3982
|
+
}
|
|
3983
|
+
var x402 = { protocol: "x402" };
|
|
3984
|
+
var mpp = { protocol: "mpp" };
|
|
3985
|
+
var envShape = {
|
|
3986
|
+
BASE_URL: z.string().refine(isUrl, {
|
|
3987
|
+
params: { code: "invalid_base_url" },
|
|
3988
|
+
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."
|
|
3989
|
+
}).optional(),
|
|
3990
|
+
EVM_PAYEE_ADDRESS: z.string().refine(isEvmAddress, {
|
|
3991
|
+
params: { code: "invalid_x402_payee", ...x402 },
|
|
3992
|
+
message: "EVM_PAYEE_ADDRESS must be a 0x-prefixed 20-byte EVM address \u2014 the wallet that receives x402 and MPP payments."
|
|
3993
|
+
}).refine((v) => !isPlaceholderEvm(v), {
|
|
3994
|
+
params: { code: "placeholder_payee", ...x402 },
|
|
3995
|
+
message: "EVM_PAYEE_ADDRESS is the zero address (0x000\u2026000) \u2014 payments to this address are unrecoverable. Set it to a wallet you control."
|
|
3996
|
+
}).optional(),
|
|
3997
|
+
CDP_API_KEY_ID: z.string().optional(),
|
|
3998
|
+
CDP_API_KEY_SECRET: z.string().optional(),
|
|
3999
|
+
SOLANA_PAYEE_ADDRESS: z.string().refine(isSolanaAddress, {
|
|
4000
|
+
params: { code: "invalid_solana_payee", ...x402 },
|
|
4001
|
+
message: "SOLANA_PAYEE_ADDRESS must be a base58 Solana address (32\u201344 chars). When set, the router also accepts Solana payments."
|
|
4002
|
+
}).optional(),
|
|
4003
|
+
SOLANA_FACILITATOR_URL: z.string().refine(isUrl, {
|
|
4004
|
+
params: { code: "invalid_solana_facilitator_url", ...x402 },
|
|
4005
|
+
message: "SOLANA_FACILITATOR_URL must be a valid URL \u2014 override for the Solana x402 facilitator. Defaults to DEFAULT_SOLANA_FACILITATOR_URL."
|
|
4006
|
+
}).optional(),
|
|
4007
|
+
MPP_SECRET_KEY: z.string().optional(),
|
|
4008
|
+
MPP_CURRENCY: z.string().refine(isEvmAddress, {
|
|
4009
|
+
params: { code: "invalid_mpp_currency", ...mpp },
|
|
4010
|
+
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."
|
|
4011
|
+
}).optional(),
|
|
4012
|
+
TEMPO_RPC_URL: z.string().refine(isUrl, {
|
|
4013
|
+
params: { code: "invalid_mpp_rpc_url", ...mpp },
|
|
4014
|
+
message: "TEMPO_RPC_URL must be a valid URL \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401."
|
|
4015
|
+
}).optional(),
|
|
4016
|
+
MPP_OPERATOR_KEY: z.string().refine(isEvmPrivateKey, {
|
|
4017
|
+
params: { code: "invalid_mpp_operator_key", ...mpp },
|
|
4018
|
+
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."
|
|
4019
|
+
}).optional(),
|
|
4020
|
+
MPP_FEE_PAYER_KEY: z.string().refine(isEvmPrivateKey, {
|
|
4021
|
+
params: { code: "invalid_mpp_fee_payer_key", ...mpp },
|
|
4022
|
+
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."
|
|
4023
|
+
}).optional(),
|
|
4024
|
+
KV_REST_API_URL: z.string().optional(),
|
|
4025
|
+
KV_REST_API_TOKEN: z.string().optional(),
|
|
4026
|
+
NODE_ENV: z.string().optional()
|
|
4027
|
+
};
|
|
4028
|
+
var ENV_KEYS = Object.keys(envShape);
|
|
4029
|
+
var EnvInputSchema = z.object(envShape).passthrough().superRefine((env, ctx) => {
|
|
4030
|
+
if (env.BASE_URL === void 0) {
|
|
4031
|
+
addIssue(
|
|
4032
|
+
ctx,
|
|
4033
|
+
{ code: "missing_base_url" },
|
|
4034
|
+
"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.",
|
|
4035
|
+
["BASE_URL"]
|
|
4036
|
+
);
|
|
4037
|
+
}
|
|
4038
|
+
if (env.EVM_PAYEE_ADDRESS === void 0) {
|
|
4039
|
+
addIssue(
|
|
4040
|
+
ctx,
|
|
4041
|
+
{ code: "missing_x402_payee", ...x402 },
|
|
4042
|
+
"EVM_PAYEE_ADDRESS is required \u2014 the EVM address that receives x402 and MPP payments.",
|
|
4043
|
+
["EVM_PAYEE_ADDRESS"]
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
4046
|
+
if (env.MPP_SECRET_KEY) {
|
|
4047
|
+
if (env.MPP_CURRENCY === void 0) {
|
|
4048
|
+
addIssue(
|
|
4049
|
+
ctx,
|
|
4050
|
+
{ code: "missing_mpp_currency", ...mpp },
|
|
4051
|
+
"MPP_CURRENCY is required when MPP is enabled \u2014 the Tempo currency address MPP charges in. Use TEMPO_USDC_ADDRESS for Tempo USDC.",
|
|
4052
|
+
["MPP_CURRENCY"]
|
|
4053
|
+
);
|
|
4054
|
+
}
|
|
4055
|
+
if (env.TEMPO_RPC_URL === void 0) {
|
|
4056
|
+
addIssue(
|
|
4057
|
+
ctx,
|
|
4058
|
+
{ code: "missing_mpp_rpc_url", ...mpp },
|
|
4059
|
+
"TEMPO_RPC_URL is required when MPP is enabled \u2014 authenticated Tempo JSON-RPC endpoint. Public rpc.tempo.xyz returns 401.",
|
|
4060
|
+
["TEMPO_RPC_URL"]
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
const collision = operatorAddressesCollide(env.MPP_OPERATOR_KEY, env.MPP_FEE_PAYER_KEY);
|
|
4065
|
+
if (collision) {
|
|
4066
|
+
addIssue(
|
|
4067
|
+
ctx,
|
|
4068
|
+
{ code: "mpp_operator_equals_fee_payer", ...mpp },
|
|
4069
|
+
`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.`,
|
|
4070
|
+
["MPP_FEE_PAYER_KEY"]
|
|
4071
|
+
);
|
|
4072
|
+
}
|
|
4073
|
+
});
|
|
4074
|
+
function collectKvWarnings(env, kvStoreOptionProvided) {
|
|
4075
|
+
if (kvStoreOptionProvided) return [];
|
|
4076
|
+
const warn = (code, message) => ({
|
|
4077
|
+
code,
|
|
4078
|
+
severity: "warning",
|
|
4079
|
+
message
|
|
4080
|
+
});
|
|
4081
|
+
if (env.KV_REST_API_URL && !env.KV_REST_API_TOKEN) {
|
|
4036
4082
|
return [
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
}
|
|
4083
|
+
warn(
|
|
4084
|
+
"kv_url_without_token",
|
|
4085
|
+
"KV_REST_API_URL is set but KV_REST_API_TOKEN is missing \u2014 falling back to in-memory KV (unsafe in serverless production)."
|
|
4086
|
+
)
|
|
4042
4087
|
];
|
|
4043
4088
|
}
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
}
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4089
|
+
if (env.KV_REST_API_TOKEN && !env.KV_REST_API_URL) {
|
|
4090
|
+
return [
|
|
4091
|
+
warn(
|
|
4092
|
+
"kv_token_without_url",
|
|
4093
|
+
"KV_REST_API_TOKEN is set but KV_REST_API_URL is missing \u2014 falling back to in-memory KV (unsafe in serverless production)."
|
|
4094
|
+
)
|
|
4095
|
+
];
|
|
4096
|
+
}
|
|
4097
|
+
if (env.KV_REST_API_URL && env.KV_REST_API_TOKEN && !isUrl(env.KV_REST_API_URL)) {
|
|
4098
|
+
return [
|
|
4099
|
+
warn(
|
|
4100
|
+
"invalid_kv_url",
|
|
4101
|
+
`KV_REST_API_URL is not a valid URL \u2014 KV calls will fail at request time. Got: ${env.KV_REST_API_URL}`
|
|
4102
|
+
)
|
|
4103
|
+
];
|
|
4104
|
+
}
|
|
4105
|
+
if (!env.KV_REST_API_URL && !env.KV_REST_API_TOKEN && env.NODE_ENV === "production") {
|
|
4106
|
+
return [
|
|
4107
|
+
warn(
|
|
4108
|
+
"missing_kv_in_production",
|
|
4109
|
+
"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."
|
|
4110
|
+
)
|
|
4111
|
+
];
|
|
4112
|
+
}
|
|
4113
|
+
return [];
|
|
4069
4114
|
}
|
|
4070
|
-
function
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
};
|
|
4115
|
+
function getConfiguredX402Accepts2(config) {
|
|
4116
|
+
if (config.x402?.accepts?.length) return [...config.x402.accepts];
|
|
4117
|
+
return [
|
|
4118
|
+
{
|
|
4119
|
+
scheme: "exact",
|
|
4120
|
+
network: config.network ?? BASE_MAINNET_NETWORK,
|
|
4121
|
+
payTo: config.payeeAddress
|
|
4122
|
+
}
|
|
4123
|
+
];
|
|
4080
4124
|
}
|
|
4081
|
-
function
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4125
|
+
function validateX402Config(config, env, options) {
|
|
4126
|
+
const accepts = getConfiguredX402Accepts2(config);
|
|
4127
|
+
const issues = [];
|
|
4128
|
+
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
4129
|
+
if (accepts.length === 0) {
|
|
4130
|
+
push("missing_x402_accepts", "x402 requires at least one accept configuration.");
|
|
4131
|
+
return issues;
|
|
4132
|
+
}
|
|
4133
|
+
if (accepts.some((a) => !a.network)) {
|
|
4134
|
+
push("missing_x402_network", "x402 accepts require a network.");
|
|
4135
|
+
}
|
|
4136
|
+
const unsupported = accepts.find((a) => a.network && !isX402Network(a.network));
|
|
4137
|
+
if (unsupported) {
|
|
4138
|
+
push(
|
|
4139
|
+
"unsupported_x402_network",
|
|
4140
|
+
`unsupported x402 network '${unsupported.network}'. Use eip155:* or solana:*.`
|
|
4141
|
+
);
|
|
4142
|
+
}
|
|
4143
|
+
if (accepts.some((a) => (a.scheme ?? "exact") !== "exact" && !a.asset)) {
|
|
4144
|
+
push("missing_x402_asset", "non-exact x402 accepts require an asset.");
|
|
4145
|
+
}
|
|
4146
|
+
if (accepts.some(
|
|
4147
|
+
(a) => a.decimals !== void 0 && (!Number.isInteger(a.decimals) || a.decimals < 0)
|
|
4148
|
+
)) {
|
|
4149
|
+
push("invalid_x402_decimals", "x402 accept decimals must be a non-negative integer.");
|
|
4150
|
+
}
|
|
4151
|
+
if (!config.payeeAddress && accepts.some((a) => !a.payTo)) {
|
|
4152
|
+
push(
|
|
4153
|
+
"missing_x402_payee",
|
|
4154
|
+
"x402 requires payeeAddress in router config or payTo on every x402 accept."
|
|
4155
|
+
);
|
|
4156
|
+
}
|
|
4157
|
+
const placeholder = [
|
|
4094
4158
|
config.payeeAddress,
|
|
4095
|
-
...accepts.map((
|
|
4096
|
-
]);
|
|
4097
|
-
if (
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
}
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
}
|
|
4159
|
+
...accepts.map((a) => typeof a.payTo === "string" ? a.payTo : void 0)
|
|
4160
|
+
].find((v) => v !== void 0 && isPlaceholderEvm(v));
|
|
4161
|
+
if (placeholder) {
|
|
4162
|
+
push(
|
|
4163
|
+
"placeholder_payee",
|
|
4164
|
+
`x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4165
|
+
);
|
|
4166
|
+
}
|
|
4167
|
+
if (options.requireCdpKeys !== false) {
|
|
4168
|
+
const hasEvm = accepts.some(
|
|
4169
|
+
(a) => typeof a.network === "string" && a.network.startsWith("eip155:")
|
|
4170
|
+
);
|
|
4171
|
+
if (hasEvm) {
|
|
4172
|
+
const missing = ["CDP_API_KEY_ID", "CDP_API_KEY_SECRET"].filter((k) => !env[k]);
|
|
4173
|
+
if (missing.length > 0) {
|
|
4174
|
+
push(
|
|
4175
|
+
"missing_cdp_keys",
|
|
4176
|
+
`x402 EVM facilitator (Coinbase) requires ${missing.join(" and ")}.`
|
|
4177
|
+
);
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
return issues;
|
|
4117
4182
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
var CHECKS2 = [
|
|
4122
|
-
checkSecretKey,
|
|
4123
|
-
checkCurrency,
|
|
4124
|
-
checkRecipient,
|
|
4125
|
-
checkPlaceholderRecipient,
|
|
4126
|
-
checkRpcUrl,
|
|
4127
|
-
checkFeePayerKey,
|
|
4128
|
-
checkOperatorKey,
|
|
4129
|
-
checkOperatorMatchesFeePayer
|
|
4130
|
-
];
|
|
4131
|
-
function validateMppConfig(config, env) {
|
|
4132
|
-
const mpp = config.mpp;
|
|
4133
|
-
if (!mpp) {
|
|
4183
|
+
function validateMppConfig(config) {
|
|
4184
|
+
const m = config.mpp;
|
|
4185
|
+
if (!m) {
|
|
4134
4186
|
return [
|
|
4135
4187
|
{
|
|
4136
4188
|
code: "missing_mpp_config",
|
|
@@ -4139,109 +4191,184 @@ function validateMppConfig(config, env) {
|
|
|
4139
4191
|
}
|
|
4140
4192
|
];
|
|
4141
4193
|
}
|
|
4142
|
-
const
|
|
4143
|
-
|
|
4144
|
-
|
|
4194
|
+
const issues = [];
|
|
4195
|
+
const push = (code, message) => issues.push({ code, protocol: "mpp", message });
|
|
4196
|
+
if (!m.secretKey) {
|
|
4197
|
+
push(
|
|
4198
|
+
"missing_mpp_secret_key",
|
|
4199
|
+
"MPP requires secretKey. Set MPP_SECRET_KEY or pass mpp.secretKey."
|
|
4200
|
+
);
|
|
4201
|
+
}
|
|
4202
|
+
if (!m.currency) {
|
|
4203
|
+
push("missing_mpp_currency", "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency.");
|
|
4204
|
+
} else if (!isEvmAddress(m.currency)) {
|
|
4205
|
+
push(
|
|
4206
|
+
"invalid_mpp_currency",
|
|
4207
|
+
"MPP currency must be a 0x-prefixed 20-byte Tempo currency address. Use TEMPO_USDC_ADDRESS for Tempo USDC."
|
|
4208
|
+
);
|
|
4209
|
+
}
|
|
4210
|
+
const recipient = m.recipient ?? config.payeeAddress;
|
|
4211
|
+
if (!recipient) {
|
|
4212
|
+
push(
|
|
4213
|
+
"missing_mpp_recipient",
|
|
4214
|
+
"MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config."
|
|
4215
|
+
);
|
|
4216
|
+
} else if (!isEvmAddress(recipient)) {
|
|
4217
|
+
push(
|
|
4218
|
+
"invalid_mpp_recipient",
|
|
4219
|
+
"MPP recipient must be a 0x-prefixed EVM address. Solana recipients require x402."
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
const placeholder = [m.recipient, config.payeeAddress].find(
|
|
4223
|
+
(v) => typeof v === "string" && isPlaceholderEvm(v)
|
|
4145
4224
|
);
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
protocol: "mpp",
|
|
4152
|
-
message: "MPP requires secretKey. Set MPP_SECRET_KEY or pass mpp.secretKey."
|
|
4153
|
-
};
|
|
4154
|
-
}
|
|
4155
|
-
function checkCurrency({ mpp }) {
|
|
4156
|
-
if (!mpp.currency) {
|
|
4157
|
-
return {
|
|
4158
|
-
code: "missing_mpp_currency",
|
|
4159
|
-
protocol: "mpp",
|
|
4160
|
-
message: "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency."
|
|
4161
|
-
};
|
|
4225
|
+
if (placeholder) {
|
|
4226
|
+
push(
|
|
4227
|
+
"placeholder_payee",
|
|
4228
|
+
`MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
|
|
4229
|
+
);
|
|
4162
4230
|
}
|
|
4163
|
-
if (!
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
};
|
|
4231
|
+
if (!m.rpcUrl) {
|
|
4232
|
+
push(
|
|
4233
|
+
"missing_mpp_rpc_url",
|
|
4234
|
+
"MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
|
|
4235
|
+
);
|
|
4169
4236
|
}
|
|
4170
|
-
|
|
4237
|
+
if (m.feePayerKey && !isEvmPrivateKey(m.feePayerKey)) {
|
|
4238
|
+
push(
|
|
4239
|
+
"invalid_mpp_fee_payer_key",
|
|
4240
|
+
"MPP feePayerKey must be a 0x-prefixed 32-byte EVM private key."
|
|
4241
|
+
);
|
|
4242
|
+
}
|
|
4243
|
+
if (m.operatorKey && !isEvmPrivateKey(m.operatorKey)) {
|
|
4244
|
+
push(
|
|
4245
|
+
"invalid_mpp_operator_key",
|
|
4246
|
+
"MPP operatorKey must be a 0x-prefixed 32-byte EVM private key."
|
|
4247
|
+
);
|
|
4248
|
+
}
|
|
4249
|
+
const collision = operatorAddressesCollide(m.operatorKey, m.feePayerKey);
|
|
4250
|
+
if (collision) {
|
|
4251
|
+
push(
|
|
4252
|
+
"mpp_operator_equals_fee_payer",
|
|
4253
|
+
`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).`
|
|
4254
|
+
);
|
|
4255
|
+
}
|
|
4256
|
+
return issues;
|
|
4171
4257
|
}
|
|
4172
|
-
function
|
|
4173
|
-
|
|
4174
|
-
|
|
4258
|
+
function translateZodIssues(error) {
|
|
4259
|
+
return error.issues.map((issue) => {
|
|
4260
|
+
const params = issue.params;
|
|
4261
|
+
if (!params?.code) {
|
|
4262
|
+
throw new Error(
|
|
4263
|
+
`[router] schema issue missing params.code (path=${issue.path.join(".")}, message=${issue.message}). Every refinement / addIssue call must set params.code.`
|
|
4264
|
+
);
|
|
4265
|
+
}
|
|
4175
4266
|
return {
|
|
4176
|
-
code:
|
|
4177
|
-
|
|
4178
|
-
|
|
4267
|
+
code: params.code,
|
|
4268
|
+
message: issue.message,
|
|
4269
|
+
...params.protocol ? { protocol: params.protocol } : {},
|
|
4270
|
+
...params.severity ? { severity: params.severity } : {}
|
|
4179
4271
|
};
|
|
4272
|
+
});
|
|
4273
|
+
}
|
|
4274
|
+
function routerConfigFromEnv(options) {
|
|
4275
|
+
const rawEnv = options.env ?? process.env;
|
|
4276
|
+
const env = trimAll(rawEnv);
|
|
4277
|
+
const optionIssues = [];
|
|
4278
|
+
if (!options.title?.trim()) {
|
|
4279
|
+
optionIssues.push({
|
|
4280
|
+
code: "missing_discovery_title",
|
|
4281
|
+
message: "discovery `title` is required. Pass a short product name."
|
|
4282
|
+
});
|
|
4180
4283
|
}
|
|
4181
|
-
if (!
|
|
4182
|
-
|
|
4183
|
-
code: "
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
};
|
|
4284
|
+
if (!options.description?.trim()) {
|
|
4285
|
+
optionIssues.push({
|
|
4286
|
+
code: "missing_discovery_description",
|
|
4287
|
+
message: "discovery `description` is required. One sentence is enough."
|
|
4288
|
+
});
|
|
4187
4289
|
}
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
}
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
}
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
const
|
|
4227
|
-
const
|
|
4228
|
-
|
|
4290
|
+
if (options.guidance === void 0) {
|
|
4291
|
+
optionIssues.push({
|
|
4292
|
+
code: "missing_discovery_guidance",
|
|
4293
|
+
message: "discovery `guidance` is required. Provide an empty string to opt out of `/llms.txt`."
|
|
4294
|
+
});
|
|
4295
|
+
}
|
|
4296
|
+
if (options.serverUrl !== void 0 && !isUrl(options.serverUrl)) {
|
|
4297
|
+
optionIssues.push({
|
|
4298
|
+
code: "invalid_server_url",
|
|
4299
|
+
message: `discovery \`serverUrl\` must be a valid URL. Got: ${options.serverUrl}`
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4302
|
+
const parsed = EnvInputSchema.safeParse(env);
|
|
4303
|
+
const envIssues = parsed.success ? [] : translateZodIssues(parsed.error);
|
|
4304
|
+
const issues = [...envIssues, ...optionIssues];
|
|
4305
|
+
if (issues.length > 0) throw new RouterConfigError(issues);
|
|
4306
|
+
for (const warning of collectKvWarnings(env, options.kvStore !== void 0)) {
|
|
4307
|
+
console.warn(`[router] ${warning.message}`);
|
|
4308
|
+
}
|
|
4309
|
+
const payeeAddress = canonicalizeEvm(env.EVM_PAYEE_ADDRESS);
|
|
4310
|
+
const accepts = [
|
|
4311
|
+
{ scheme: "exact", network: BASE_MAINNET_NETWORK, payTo: payeeAddress },
|
|
4312
|
+
{
|
|
4313
|
+
scheme: "upto",
|
|
4314
|
+
network: BASE_MAINNET_NETWORK,
|
|
4315
|
+
payTo: payeeAddress,
|
|
4316
|
+
asset: BASE_USDC_ADDRESS,
|
|
4317
|
+
decimals: BASE_USDC_DECIMALS
|
|
4318
|
+
}
|
|
4319
|
+
];
|
|
4320
|
+
if (env.SOLANA_PAYEE_ADDRESS) {
|
|
4321
|
+
accepts.push({
|
|
4322
|
+
scheme: "exact",
|
|
4323
|
+
network: SOLANA_MAINNET_NETWORK,
|
|
4324
|
+
payTo: env.SOLANA_PAYEE_ADDRESS
|
|
4325
|
+
});
|
|
4326
|
+
}
|
|
4327
|
+
const configuredSolanaFacilitator = options.x402Facilitators?.solana;
|
|
4328
|
+
const solanaFacilitator = typeof configuredSolanaFacilitator === "string" ? configuredSolanaFacilitator : configuredSolanaFacilitator ?? env.SOLANA_FACILITATOR_URL ?? DEFAULT_SOLANA_FACILITATOR_URL;
|
|
4329
|
+
const mppEnabled = options.protocols?.includes("mpp") ?? Boolean(env.MPP_SECRET_KEY);
|
|
4330
|
+
const protocols = options.protocols ? [...options.protocols] : mppEnabled ? ["x402", "mpp"] : ["x402"];
|
|
4331
|
+
const mppConfig = mppEnabled ? {
|
|
4332
|
+
secretKey: env.MPP_SECRET_KEY,
|
|
4333
|
+
currency: canonicalizeEvm(env.MPP_CURRENCY),
|
|
4334
|
+
rpcUrl: env.TEMPO_RPC_URL,
|
|
4335
|
+
recipient: payeeAddress,
|
|
4336
|
+
...env.MPP_FEE_PAYER_KEY ? { feePayerKey: env.MPP_FEE_PAYER_KEY } : {},
|
|
4337
|
+
...env.MPP_OPERATOR_KEY ? { operatorKey: env.MPP_OPERATOR_KEY, session: {} } : {}
|
|
4338
|
+
} : void 0;
|
|
4229
4339
|
return {
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4340
|
+
payeeAddress,
|
|
4341
|
+
baseUrl: env.BASE_URL,
|
|
4342
|
+
network: BASE_MAINNET_NETWORK,
|
|
4343
|
+
protocols,
|
|
4344
|
+
x402: {
|
|
4345
|
+
accepts,
|
|
4346
|
+
facilitators: {
|
|
4347
|
+
...options.x402Facilitators,
|
|
4348
|
+
solana: solanaFacilitator
|
|
4349
|
+
}
|
|
4350
|
+
},
|
|
4351
|
+
...mppConfig ? { mpp: mppConfig } : {},
|
|
4352
|
+
discovery: {
|
|
4353
|
+
title: options.title,
|
|
4354
|
+
version: options.version ?? "1.0.0",
|
|
4355
|
+
description: options.description,
|
|
4356
|
+
guidance: options.guidance,
|
|
4357
|
+
...options.contact ? { contact: options.contact } : {},
|
|
4358
|
+
...options.ownershipProofs ? { ownershipProofs: options.ownershipProofs } : {},
|
|
4359
|
+
...options.methodHints ? { methodHints: options.methodHints } : {},
|
|
4360
|
+
...options.serverUrl ? { serverUrl: options.serverUrl } : {}
|
|
4361
|
+
},
|
|
4362
|
+
...options.prices ? { prices: options.prices } : {},
|
|
4363
|
+
...options.plugin ? { plugin: options.plugin } : {},
|
|
4364
|
+
...options.kvStore ? { kvStore: options.kvStore } : {},
|
|
4365
|
+
strictRoutes: options.strictRoutes ?? false
|
|
4233
4366
|
};
|
|
4234
4367
|
}
|
|
4235
|
-
|
|
4236
|
-
// src/config/validate.ts
|
|
4237
|
-
function validateRouterConfig(config, options = {}) {
|
|
4238
|
-
const issues = getRouterConfigIssues(config, options);
|
|
4239
|
-
if (issues.length > 0) throw new RouterConfigError(issues);
|
|
4240
|
-
}
|
|
4241
4368
|
function getRouterConfigIssues(config, options = {}) {
|
|
4242
|
-
const env = options.env ??
|
|
4243
|
-
const issues = [];
|
|
4369
|
+
const env = options.env ?? {};
|
|
4244
4370
|
const protocols = config.protocols ?? ["x402"];
|
|
4371
|
+
const issues = [];
|
|
4245
4372
|
if (!config.baseUrl) {
|
|
4246
4373
|
issues.push({
|
|
4247
4374
|
code: "missing_base_url",
|
|
@@ -4254,83 +4381,16 @@ function getRouterConfigIssues(config, options = {}) {
|
|
|
4254
4381
|
message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
|
|
4255
4382
|
});
|
|
4256
4383
|
}
|
|
4257
|
-
if (protocols.includes("x402"))
|
|
4258
|
-
|
|
4259
|
-
}
|
|
4260
|
-
if (protocols.includes("mpp")) {
|
|
4261
|
-
issues.push(...validateMppConfig(config, env));
|
|
4262
|
-
}
|
|
4384
|
+
if (protocols.includes("x402")) issues.push(...validateX402Config(config, env, options));
|
|
4385
|
+
if (protocols.includes("mpp")) issues.push(...validateMppConfig(config));
|
|
4263
4386
|
return issues;
|
|
4264
4387
|
}
|
|
4265
4388
|
|
|
4266
|
-
// src/config/env.ts
|
|
4267
|
-
init_constants();
|
|
4268
|
-
function mppFromEnv(env, options = {}) {
|
|
4269
|
-
const secretKey = env.MPP_SECRET_KEY;
|
|
4270
|
-
const currency = env.MPP_CURRENCY;
|
|
4271
|
-
const rpcUrl = env.TEMPO_RPC_URL;
|
|
4272
|
-
const feePayerKey = options.feePayerKey ?? env.MPP_FEE_PAYER_KEY;
|
|
4273
|
-
const feePayerKeySource = options.feePayerKey !== void 0 ? "feePayerKey" : "MPP_FEE_PAYER_KEY";
|
|
4274
|
-
const hasAnyMppEnv = Boolean(secretKey || currency || rpcUrl || options.require);
|
|
4275
|
-
if (!hasAnyMppEnv) return void 0;
|
|
4276
|
-
const missing = [
|
|
4277
|
-
secretKey ? null : "MPP_SECRET_KEY",
|
|
4278
|
-
currency ? null : "MPP_CURRENCY",
|
|
4279
|
-
rpcUrl ? null : "TEMPO_RPC_URL"
|
|
4280
|
-
].filter(Boolean);
|
|
4281
|
-
if (missing.length > 0) {
|
|
4282
|
-
throw new Error(`MPP env is incomplete. Missing: ${missing.join(", ")}`);
|
|
4283
|
-
}
|
|
4284
|
-
if (!isEvmAddress(currency)) {
|
|
4285
|
-
throw new Error("MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address");
|
|
4286
|
-
}
|
|
4287
|
-
if (options.recipient && !isEvmAddress(options.recipient)) {
|
|
4288
|
-
throw new Error("MPP recipient must be a 0x-prefixed EVM address");
|
|
4289
|
-
}
|
|
4290
|
-
if (feePayerKey && !isEvmPrivateKey(feePayerKey)) {
|
|
4291
|
-
throw new Error(`${feePayerKeySource} must be a 0x-prefixed 32-byte EVM private key`);
|
|
4292
|
-
}
|
|
4293
|
-
return {
|
|
4294
|
-
secretKey,
|
|
4295
|
-
currency,
|
|
4296
|
-
rpcUrl,
|
|
4297
|
-
...options.recipient ? { recipient: options.recipient } : {},
|
|
4298
|
-
...feePayerKey ? { feePayerKey } : {}
|
|
4299
|
-
};
|
|
4300
|
-
}
|
|
4301
|
-
function x402AcceptsFromEnv(env, options = {}) {
|
|
4302
|
-
const payeeEnv = options.payeeEnv ?? "X402_WALLET_ADDRESS";
|
|
4303
|
-
const solanaPayeeEnv = options.solanaPayeeEnv ?? "SOLANA_PAYEE_ADDRESS";
|
|
4304
|
-
const payeeAddress = options.payeeAddress ?? env[payeeEnv];
|
|
4305
|
-
if (!payeeAddress) {
|
|
4306
|
-
throw new Error(`${payeeEnv} is required to build x402 accepts`);
|
|
4307
|
-
}
|
|
4308
|
-
const accepts = [
|
|
4309
|
-
{
|
|
4310
|
-
scheme: "exact",
|
|
4311
|
-
network: options.network ?? BASE_NETWORK,
|
|
4312
|
-
payTo: payeeAddress
|
|
4313
|
-
}
|
|
4314
|
-
];
|
|
4315
|
-
const solanaPayeeAddress = options.solanaPayeeAddress ?? env[solanaPayeeEnv];
|
|
4316
|
-
if (solanaPayeeAddress) {
|
|
4317
|
-
accepts.push({
|
|
4318
|
-
scheme: "exact",
|
|
4319
|
-
network: SOLANA_MAINNET_NETWORK,
|
|
4320
|
-
payTo: solanaPayeeAddress
|
|
4321
|
-
});
|
|
4322
|
-
}
|
|
4323
|
-
return accepts;
|
|
4324
|
-
}
|
|
4325
|
-
function paidOptionsForProtocols(protocols) {
|
|
4326
|
-
return { protocols: [...protocols] };
|
|
4327
|
-
}
|
|
4328
|
-
|
|
4329
4389
|
// src/init/x402.ts
|
|
4330
4390
|
async function initX402(config, configError) {
|
|
4331
4391
|
if (configError) return { initError: configError };
|
|
4332
4392
|
try {
|
|
4333
|
-
const { createX402Server: createX402Server2 } = await Promise.resolve().then(() => (
|
|
4393
|
+
const { createX402Server: createX402Server2 } = await Promise.resolve().then(() => (init_x402_server(), x402_server_exports));
|
|
4334
4394
|
const result = await createX402Server2(config);
|
|
4335
4395
|
await result.initPromise;
|
|
4336
4396
|
return {
|
|
@@ -4342,7 +4402,7 @@ async function initX402(config, configError) {
|
|
|
4342
4402
|
}
|
|
4343
4403
|
}
|
|
4344
4404
|
|
|
4345
|
-
// src/mppx
|
|
4405
|
+
// src/init/mppx.ts
|
|
4346
4406
|
function getMppxRequestContext(args) {
|
|
4347
4407
|
const {
|
|
4348
4408
|
Mppx,
|
|
@@ -4464,9 +4524,10 @@ function createRouter(config) {
|
|
|
4464
4524
|
const kvStore = resolveKvStore(config.kvStore);
|
|
4465
4525
|
const nonceStore = kvStore ? createKvNonceStore(kvStore) : new MemoryNonceStore();
|
|
4466
4526
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
4467
|
-
const network = config.network ??
|
|
4527
|
+
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
4468
4528
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
4469
4529
|
const configIssues = getRouterConfigIssues(config, {
|
|
4530
|
+
env: process.env,
|
|
4470
4531
|
requireCdpKeys: process.env.NODE_ENV === "production"
|
|
4471
4532
|
});
|
|
4472
4533
|
const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
|
|
@@ -4589,29 +4650,21 @@ function normalizePath(path) {
|
|
|
4589
4650
|
normalized = normalized.replace(/^api\/+/, "");
|
|
4590
4651
|
return normalized.replace(/\/+$/, "");
|
|
4591
4652
|
}
|
|
4653
|
+
function createRouterFromEnv(options) {
|
|
4654
|
+
return createRouter(routerConfigFromEnv(options));
|
|
4655
|
+
}
|
|
4592
4656
|
export {
|
|
4593
|
-
|
|
4657
|
+
BASE_MAINNET_NETWORK,
|
|
4658
|
+
BASE_USDC_ADDRESS,
|
|
4659
|
+
BASE_USDC_DECIMALS,
|
|
4660
|
+
DEFAULT_SOLANA_FACILITATOR_URL,
|
|
4594
4661
|
HttpError,
|
|
4595
|
-
MemoryEntitlementStore,
|
|
4596
|
-
MemoryNonceStore,
|
|
4597
|
-
RouteBuilder,
|
|
4598
|
-
RouteRegistry,
|
|
4599
4662
|
RouterConfigError,
|
|
4600
|
-
SIWX_CHALLENGE_EXPIRY_MS,
|
|
4601
|
-
SIWX_ERROR_MESSAGES,
|
|
4602
4663
|
SOLANA_MAINNET_NETWORK,
|
|
4603
|
-
|
|
4664
|
+
TEMPO_USDC_ADDRESS,
|
|
4665
|
+
TEMPO_USDC_DECIMALS,
|
|
4604
4666
|
ZERO_EVM_ADDRESS,
|
|
4605
|
-
consolePlugin,
|
|
4606
|
-
createKvEntitlementStore,
|
|
4607
|
-
createKvMppStore,
|
|
4608
|
-
createKvNonceStore,
|
|
4609
4667
|
createRouter,
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
mppFromEnv,
|
|
4613
|
-
paidOptionsForProtocols,
|
|
4614
|
-
validateRouterConfig,
|
|
4615
|
-
withPrefix,
|
|
4616
|
-
x402AcceptsFromEnv
|
|
4668
|
+
createRouterFromEnv,
|
|
4669
|
+
routerConfigFromEnv
|
|
4617
4670
|
};
|