@agentcash/router 1.5.1 → 1.5.2
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/dist/index.cjs +1636 -1420
- package/dist/index.d.cts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +1636 -1420
- package/package.json +1 -1
- package/dist/client/index.cjs +0 -94
- package/dist/client/index.d.cts +0 -86
- package/dist/client/index.d.ts +0 -86
- package/dist/client/index.js +0 -56
- package/dist/siwx-BMlja_nt.d.cts +0 -9
- package/dist/siwx-BMlja_nt.d.ts +0 -9
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,65 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
-
// src/
|
|
33
|
+
// src/constants.ts
|
|
34
|
+
var BASE_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_CURRENCY, ZERO_EVM_ADDRESS;
|
|
35
|
+
var init_constants = __esm({
|
|
36
|
+
"src/constants.ts"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
BASE_NETWORK = "eip155:8453";
|
|
39
|
+
SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
40
|
+
TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
41
|
+
ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// src/protocols/x402/accepts.ts
|
|
46
|
+
async function resolvePayToValue(payTo, request, fallback, body) {
|
|
47
|
+
if (!payTo) return fallback;
|
|
48
|
+
if (typeof payTo === "string") return payTo;
|
|
49
|
+
return payTo(request, body);
|
|
50
|
+
}
|
|
51
|
+
function getConfiguredX402Accepts(config) {
|
|
52
|
+
if (config.x402?.accepts?.length) {
|
|
53
|
+
return [...config.x402.accepts];
|
|
54
|
+
}
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
scheme: "exact",
|
|
58
|
+
network: config.network ?? BASE_NETWORK,
|
|
59
|
+
payTo: config.payeeAddress
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
function getConfiguredX402Networks(config) {
|
|
64
|
+
return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
|
|
65
|
+
}
|
|
66
|
+
async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
|
|
67
|
+
return Promise.all(
|
|
68
|
+
accepts.map(async (accept) => ({
|
|
69
|
+
network: accept.network,
|
|
70
|
+
scheme: accept.scheme ?? "exact",
|
|
71
|
+
payTo: await resolvePayToValue(
|
|
72
|
+
routeEntry.payTo ?? accept.payTo,
|
|
73
|
+
request,
|
|
74
|
+
fallbackPayTo,
|
|
75
|
+
body
|
|
76
|
+
),
|
|
77
|
+
...accept.asset ? { asset: accept.asset } : {},
|
|
78
|
+
...accept.decimals !== void 0 ? { decimals: accept.decimals } : {},
|
|
79
|
+
...accept.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: accept.maxTimeoutSeconds } : {},
|
|
80
|
+
...accept.extra ? { extra: accept.extra } : {}
|
|
81
|
+
}))
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
var init_accepts = __esm({
|
|
85
|
+
"src/protocols/x402/accepts.ts"() {
|
|
86
|
+
"use strict";
|
|
87
|
+
init_constants();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// src/protocols/x402/evm.ts
|
|
34
92
|
function isEvmNetwork(network) {
|
|
35
93
|
return network.startsWith("eip155:");
|
|
36
94
|
}
|
|
@@ -48,12 +106,12 @@ function buildEvmExactOptions(accepts, price) {
|
|
|
48
106
|
}));
|
|
49
107
|
}
|
|
50
108
|
var init_evm = __esm({
|
|
51
|
-
"src/protocols/evm.ts"() {
|
|
109
|
+
"src/protocols/x402/evm.ts"() {
|
|
52
110
|
"use strict";
|
|
53
111
|
}
|
|
54
112
|
});
|
|
55
113
|
|
|
56
|
-
// src/protocols/solana.ts
|
|
114
|
+
// src/protocols/x402/solana.ts
|
|
57
115
|
function isSolanaNetwork(network) {
|
|
58
116
|
return network.startsWith("solana:");
|
|
59
117
|
}
|
|
@@ -103,13 +161,13 @@ function isSolanaRequirement(requirement) {
|
|
|
103
161
|
return isSolanaNetwork(requirement.network);
|
|
104
162
|
}
|
|
105
163
|
var init_solana = __esm({
|
|
106
|
-
"src/protocols/solana.ts"() {
|
|
164
|
+
"src/protocols/x402/solana.ts"() {
|
|
107
165
|
"use strict";
|
|
108
|
-
|
|
166
|
+
init_facilitators();
|
|
109
167
|
}
|
|
110
168
|
});
|
|
111
169
|
|
|
112
|
-
// src/x402
|
|
170
|
+
// src/protocols/x402/facilitators.ts
|
|
113
171
|
function getResolvedX402Facilitator(config, network, defaultEvmFacilitator) {
|
|
114
172
|
const family = getNetworkFamily(network);
|
|
115
173
|
if (!family) return null;
|
|
@@ -179,8 +237,8 @@ function sameFacilitatorConfig(a, b) {
|
|
|
179
237
|
return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders && a.createAcceptsHeaders === b.createAcceptsHeaders;
|
|
180
238
|
}
|
|
181
239
|
var DEFAULT_SOLANA_FACILITATOR_URL;
|
|
182
|
-
var
|
|
183
|
-
"src/x402
|
|
240
|
+
var init_facilitators = __esm({
|
|
241
|
+
"src/protocols/x402/facilitators.ts"() {
|
|
184
242
|
"use strict";
|
|
185
243
|
init_evm();
|
|
186
244
|
init_solana();
|
|
@@ -188,64 +246,6 @@ var init_x402_facilitators = __esm({
|
|
|
188
246
|
}
|
|
189
247
|
});
|
|
190
248
|
|
|
191
|
-
// src/constants.ts
|
|
192
|
-
var BASE_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_CURRENCY, ZERO_EVM_ADDRESS;
|
|
193
|
-
var init_constants = __esm({
|
|
194
|
-
"src/constants.ts"() {
|
|
195
|
-
"use strict";
|
|
196
|
-
BASE_NETWORK = "eip155:8453";
|
|
197
|
-
SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
198
|
-
TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
199
|
-
ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// src/x402-config.ts
|
|
204
|
-
async function resolvePayToValue(payTo, request, fallback, body) {
|
|
205
|
-
if (!payTo) return fallback;
|
|
206
|
-
if (typeof payTo === "string") return payTo;
|
|
207
|
-
return payTo(request, body);
|
|
208
|
-
}
|
|
209
|
-
function getConfiguredX402Accepts(config) {
|
|
210
|
-
if (config.x402?.accepts?.length) {
|
|
211
|
-
return [...config.x402.accepts];
|
|
212
|
-
}
|
|
213
|
-
return [
|
|
214
|
-
{
|
|
215
|
-
scheme: "exact",
|
|
216
|
-
network: config.network ?? BASE_NETWORK,
|
|
217
|
-
payTo: config.payeeAddress
|
|
218
|
-
}
|
|
219
|
-
];
|
|
220
|
-
}
|
|
221
|
-
function getConfiguredX402Networks(config) {
|
|
222
|
-
return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
|
|
223
|
-
}
|
|
224
|
-
async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, body) {
|
|
225
|
-
return Promise.all(
|
|
226
|
-
accepts.map(async (accept) => ({
|
|
227
|
-
network: accept.network,
|
|
228
|
-
scheme: accept.scheme ?? "exact",
|
|
229
|
-
payTo: await resolvePayToValue(
|
|
230
|
-
routeEntry.payTo ?? accept.payTo,
|
|
231
|
-
request,
|
|
232
|
-
fallbackPayTo,
|
|
233
|
-
body
|
|
234
|
-
),
|
|
235
|
-
...accept.asset ? { asset: accept.asset } : {},
|
|
236
|
-
...accept.decimals !== void 0 ? { decimals: accept.decimals } : {},
|
|
237
|
-
...accept.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: accept.maxTimeoutSeconds } : {},
|
|
238
|
-
...accept.extra ? { extra: accept.extra } : {}
|
|
239
|
-
}))
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
var init_x402_config = __esm({
|
|
243
|
-
"src/x402-config.ts"() {
|
|
244
|
-
"use strict";
|
|
245
|
-
init_constants();
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
249
|
// src/server.ts
|
|
250
250
|
var server_exports = {};
|
|
251
251
|
__export(server_exports, {
|
|
@@ -330,8 +330,8 @@ var init_server = __esm({
|
|
|
330
330
|
"use strict";
|
|
331
331
|
init_evm();
|
|
332
332
|
init_solana();
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
init_facilitators();
|
|
334
|
+
init_accepts();
|
|
335
335
|
}
|
|
336
336
|
});
|
|
337
337
|
|
|
@@ -465,13 +465,32 @@ var RouteRegistry = class {
|
|
|
465
465
|
}
|
|
466
466
|
};
|
|
467
467
|
|
|
468
|
-
// src/
|
|
469
|
-
var
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
468
|
+
// src/headers.ts
|
|
469
|
+
var HEADERS = {
|
|
470
|
+
// ---- Standard HTTP ----
|
|
471
|
+
AUTHORIZATION: "Authorization",
|
|
472
|
+
WWW_AUTHENTICATE: "WWW-Authenticate",
|
|
473
|
+
// ---- Auth ----
|
|
474
|
+
API_KEY: "X-API-Key",
|
|
475
|
+
// ---- Request meta (used by plugin/observability) ----
|
|
476
|
+
WALLET_ADDRESS: "X-Wallet-Address",
|
|
477
|
+
CLIENT_ID: "X-Client-ID",
|
|
478
|
+
SESSION_ID: "X-Session-ID",
|
|
479
|
+
// ---- SIWX ----
|
|
480
|
+
SIWX: "SIGN-IN-WITH-X",
|
|
481
|
+
// ---- x402 (payment) ----
|
|
482
|
+
X402_PAYMENT_SIGNATURE: "PAYMENT-SIGNATURE",
|
|
483
|
+
/** Legacy x402 payment header — accepted alongside PAYMENT-SIGNATURE. */
|
|
484
|
+
X402_PAYMENT_LEGACY: "X-PAYMENT",
|
|
485
|
+
X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
|
|
486
|
+
X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
|
|
487
|
+
// ---- MPP (payment) ----
|
|
488
|
+
MPP_PAYMENT_RECEIPT: "Payment-Receipt"
|
|
489
|
+
};
|
|
490
|
+
var AUTH_SCHEME = {
|
|
491
|
+
BEARER: "Bearer ",
|
|
492
|
+
MPP_PAYMENT: "Payment "
|
|
493
|
+
};
|
|
475
494
|
|
|
476
495
|
// src/plugin.ts
|
|
477
496
|
function createDefaultContext(meta) {
|
|
@@ -548,106 +567,31 @@ function consolePlugin() {
|
|
|
548
567
|
};
|
|
549
568
|
}
|
|
550
569
|
|
|
551
|
-
// src/
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
this.evict();
|
|
557
|
-
if (this.seen.has(nonce)) return false;
|
|
558
|
-
this.seen.set(nonce, Date.now() + SIWX_CHALLENGE_EXPIRY_MS);
|
|
559
|
-
return true;
|
|
560
|
-
}
|
|
561
|
-
evict() {
|
|
562
|
-
const now = Date.now();
|
|
563
|
-
for (const [n, exp] of this.seen) {
|
|
564
|
-
if (exp < now) this.seen.delete(n);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
function detectRedisClientType(client) {
|
|
569
|
-
if (!client || typeof client !== "object") {
|
|
570
|
-
throw new Error(
|
|
571
|
-
"createRedisNonceStore requires a Redis client. Supported: @upstash/redis, ioredis. Pass your Redis client instance as the first argument."
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
if ("options" in client && "status" in client) {
|
|
575
|
-
return "ioredis";
|
|
576
|
-
}
|
|
577
|
-
const constructor = client.constructor?.name;
|
|
578
|
-
if (constructor === "Redis" && "url" in client) {
|
|
579
|
-
return "upstash";
|
|
580
|
-
}
|
|
581
|
-
if (typeof client.set === "function") {
|
|
582
|
-
return "upstash";
|
|
583
|
-
}
|
|
584
|
-
throw new Error(
|
|
585
|
-
"Unrecognized Redis client. Supported: @upstash/redis, ioredis. If using a different client, implement NonceStore interface directly."
|
|
586
|
-
);
|
|
570
|
+
// src/pipeline/context/preflight.ts
|
|
571
|
+
function preflight(routeEntry, handler, deps, request) {
|
|
572
|
+
const meta = buildMeta(request, routeEntry);
|
|
573
|
+
const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
|
|
574
|
+
return { routeEntry, handler, deps, request, meta, pluginCtx };
|
|
587
575
|
}
|
|
588
|
-
function
|
|
589
|
-
const prefix = opts?.prefix ?? "siwx:nonce:";
|
|
590
|
-
const ttlSeconds = Math.ceil((opts?.ttlMs ?? SIWX_CHALLENGE_EXPIRY_MS) / 1e3);
|
|
591
|
-
const clientType = detectRedisClientType(client);
|
|
576
|
+
function buildMeta(request, routeEntry) {
|
|
592
577
|
return {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
throw new Error("Unknown Redis client type");
|
|
606
|
-
}
|
|
578
|
+
requestId: crypto.randomUUID(),
|
|
579
|
+
method: request.method,
|
|
580
|
+
route: routeEntry.key,
|
|
581
|
+
origin: request.headers.get("origin") ?? new URL(request.url).origin,
|
|
582
|
+
referer: request.headers.get("referer"),
|
|
583
|
+
walletAddress: request.headers.get(HEADERS.WALLET_ADDRESS),
|
|
584
|
+
clientId: request.headers.get(HEADERS.CLIENT_ID),
|
|
585
|
+
sessionId: request.headers.get(HEADERS.SESSION_ID),
|
|
586
|
+
contentType: request.headers.get("content-type"),
|
|
587
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
588
|
+
startTime: Date.now()
|
|
607
589
|
};
|
|
608
590
|
}
|
|
609
591
|
|
|
610
|
-
// src/
|
|
611
|
-
function detectProtocol(request) {
|
|
612
|
-
if (request.headers.get("PAYMENT-SIGNATURE") || request.headers.get("X-PAYMENT")) {
|
|
613
|
-
return "x402";
|
|
614
|
-
}
|
|
615
|
-
const auth = request.headers.get("Authorization");
|
|
616
|
-
if (auth && auth.startsWith("Payment ")) {
|
|
617
|
-
return "mpp";
|
|
618
|
-
}
|
|
619
|
-
if (request.headers.get("SIGN-IN-WITH-X")) {
|
|
620
|
-
return "siwx";
|
|
621
|
-
}
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// src/handler.ts
|
|
592
|
+
// src/pipeline/context/parse-body.ts
|
|
626
593
|
var import_server = require("next/server");
|
|
627
594
|
|
|
628
|
-
// src/types.ts
|
|
629
|
-
var HttpError = class extends Error {
|
|
630
|
-
constructor(message, status) {
|
|
631
|
-
super(message);
|
|
632
|
-
this.status = status;
|
|
633
|
-
this.name = "HttpError";
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
// src/handler.ts
|
|
638
|
-
async function safeCallHandler(handler, ctx, options = {}) {
|
|
639
|
-
try {
|
|
640
|
-
const result = await handler(ctx);
|
|
641
|
-
if (result instanceof Response) return result;
|
|
642
|
-
return import_server.NextResponse.json(result);
|
|
643
|
-
} catch (error) {
|
|
644
|
-
options.onError?.(error);
|
|
645
|
-
const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
|
|
646
|
-
const message = error instanceof Error ? error.message : "Internal error";
|
|
647
|
-
return import_server.NextResponse.json({ success: false, error: message }, { status });
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
595
|
// src/body.ts
|
|
652
596
|
async function bufferBody(request) {
|
|
653
597
|
const text = await request.text();
|
|
@@ -672,299 +616,329 @@ function validateBody(parsed, schema) {
|
|
|
672
616
|
};
|
|
673
617
|
}
|
|
674
618
|
|
|
675
|
-
// src/
|
|
676
|
-
async function
|
|
677
|
-
if (
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
if (defaultTier && tiers[defaultTier]) {
|
|
689
|
-
return tiers[defaultTier].price;
|
|
690
|
-
}
|
|
691
|
-
if (!tierKey) {
|
|
692
|
-
throw Object.assign(new Error(`Missing required field '${field}' for tier pricing`), {
|
|
693
|
-
status: 400
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
throw Object.assign(
|
|
697
|
-
new Error(
|
|
698
|
-
`Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
|
|
699
|
-
),
|
|
700
|
-
{ status: 400 }
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
function resolveMaxPrice(pricing) {
|
|
704
|
-
if (typeof pricing === "string") return pricing;
|
|
705
|
-
if (typeof pricing === "function") {
|
|
706
|
-
throw new Error("Dynamic pricing requires maxPrice \u2014 this should be caught at registration");
|
|
707
|
-
}
|
|
708
|
-
const { tiers } = pricing;
|
|
709
|
-
let max = "0";
|
|
710
|
-
for (const tier of Object.values(tiers)) {
|
|
711
|
-
if (parseFloat(tier.price) > parseFloat(max)) {
|
|
712
|
-
max = tier.price;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
return max;
|
|
619
|
+
// src/pipeline/context/parse-body.ts
|
|
620
|
+
async function parseBody(request, routeEntry) {
|
|
621
|
+
if (!routeEntry.bodySchema) return { ok: true, data: void 0 };
|
|
622
|
+
const raw = await bufferBody(request);
|
|
623
|
+
const result = validateBody(raw, routeEntry.bodySchema);
|
|
624
|
+
if (result.success) return { ok: true, data: result.data };
|
|
625
|
+
return {
|
|
626
|
+
ok: false,
|
|
627
|
+
response: import_server.NextResponse.json(
|
|
628
|
+
{ success: false, error: result.error, issues: result.issues },
|
|
629
|
+
{ status: 400 }
|
|
630
|
+
)
|
|
631
|
+
};
|
|
716
632
|
}
|
|
717
633
|
|
|
718
|
-
// src/
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
634
|
+
// src/pipeline/context/parse-query.ts
|
|
635
|
+
function parseQuery(request, routeEntry) {
|
|
636
|
+
if (!routeEntry.querySchema) return void 0;
|
|
637
|
+
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
638
|
+
const result = routeEntry.querySchema.safeParse(params);
|
|
639
|
+
return result.success ? result.data : params;
|
|
640
|
+
}
|
|
723
641
|
|
|
724
|
-
// src/
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
async function buildX402Challenge(opts) {
|
|
729
|
-
const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
|
|
730
|
-
const { encodePaymentRequiredHeader } = await import("@x402/core/http");
|
|
731
|
-
const resource = buildChallengeResource(request, routeEntry);
|
|
732
|
-
const requirements = await buildChallengeRequirements(
|
|
733
|
-
server,
|
|
734
|
-
request,
|
|
735
|
-
price,
|
|
736
|
-
accepts,
|
|
737
|
-
resource,
|
|
738
|
-
facilitatorsByNetwork
|
|
739
|
-
);
|
|
740
|
-
const paymentRequired = await server.createPaymentRequiredResponse(
|
|
741
|
-
requirements,
|
|
742
|
-
resource,
|
|
743
|
-
void 0,
|
|
744
|
-
extensions
|
|
745
|
-
);
|
|
746
|
-
const encoded = encodePaymentRequiredHeader(paymentRequired);
|
|
747
|
-
return { encoded, requirements };
|
|
642
|
+
// src/pipeline/context/errors.ts
|
|
643
|
+
function errorStatus(error, fallback) {
|
|
644
|
+
const status = error?.status;
|
|
645
|
+
return typeof status === "number" ? status : fallback;
|
|
748
646
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
647
|
+
function errorMessage(error, fallback) {
|
|
648
|
+
return error instanceof Error ? error.message : fallback;
|
|
649
|
+
}
|
|
650
|
+
function handlerFailureError(response) {
|
|
651
|
+
const message = response.statusText || `Handler returned HTTP ${response.status}`;
|
|
652
|
+
return Object.assign(new Error(message), { status: response.status });
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/pipeline/context/fail.ts
|
|
656
|
+
var import_server2 = require("next/server");
|
|
657
|
+
|
|
658
|
+
// src/pipeline/context/fire-plugin-response.ts
|
|
659
|
+
function firePluginResponse(ctx, response, requestBody, responseBody) {
|
|
660
|
+
firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
|
|
661
|
+
statusCode: response.status,
|
|
662
|
+
statusText: response.statusText,
|
|
663
|
+
duration: Date.now() - ctx.meta.startTime,
|
|
664
|
+
contentType: response.headers.get("content-type"),
|
|
665
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
666
|
+
requestBody,
|
|
667
|
+
responseBody
|
|
668
|
+
});
|
|
669
|
+
if (response.status >= 400 && response.status !== 402) {
|
|
670
|
+
firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, {
|
|
671
|
+
status: response.status,
|
|
672
|
+
message: response.statusText || `HTTP ${response.status}`,
|
|
673
|
+
settled: false
|
|
674
|
+
});
|
|
757
675
|
}
|
|
758
|
-
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/pipeline/context/fail.ts
|
|
679
|
+
function fail(ctx, status, message, requestBody) {
|
|
680
|
+
const response = import_server2.NextResponse.json({ success: false, error: message }, { status });
|
|
681
|
+
firePluginResponse(ctx, response, requestBody);
|
|
682
|
+
return response;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/pipeline/context/run-validate.ts
|
|
686
|
+
async function runValidate(ctx, body) {
|
|
687
|
+
if (!ctx.routeEntry.validateFn) return null;
|
|
759
688
|
try {
|
|
760
|
-
|
|
689
|
+
await ctx.routeEntry.validateFn(body);
|
|
690
|
+
return null;
|
|
761
691
|
} catch (err) {
|
|
762
|
-
|
|
763
|
-
if (sc && sc >= 400 && sc < 500) return invalidPaymentVerification();
|
|
764
|
-
throw err;
|
|
765
|
-
}
|
|
766
|
-
if (!verify.isValid) return invalidPaymentVerification();
|
|
767
|
-
if (typeof verify.payer !== "string" || verify.payer.length === 0) {
|
|
768
|
-
throw new Error("x402 verification succeeded without a payer address");
|
|
692
|
+
return fail(ctx, errorStatus(err, 400), errorMessage(err, "Validation failed"), body);
|
|
769
693
|
}
|
|
770
|
-
return {
|
|
771
|
-
valid: true,
|
|
772
|
-
payer: verify.payer,
|
|
773
|
-
payload,
|
|
774
|
-
requirements: matching
|
|
775
|
-
};
|
|
776
694
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
695
|
+
|
|
696
|
+
// src/handler.ts
|
|
697
|
+
var import_server3 = require("next/server");
|
|
698
|
+
|
|
699
|
+
// src/types.ts
|
|
700
|
+
var HttpError = class extends Error {
|
|
701
|
+
constructor(message, status) {
|
|
702
|
+
super(message);
|
|
703
|
+
this.status = status;
|
|
704
|
+
this.name = "HttpError";
|
|
781
705
|
}
|
|
782
|
-
|
|
783
|
-
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// src/handler.ts
|
|
709
|
+
async function safeCallHandler(handler, ctx, options = {}) {
|
|
710
|
+
try {
|
|
711
|
+
const result = await handler(ctx);
|
|
712
|
+
if (result instanceof Response) return result;
|
|
713
|
+
return import_server3.NextResponse.json(result);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
options.onError?.(error);
|
|
716
|
+
const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
|
|
717
|
+
const message = error instanceof Error ? error.message : "Internal error";
|
|
718
|
+
return import_server3.NextResponse.json({ success: false, error: message }, { status });
|
|
784
719
|
}
|
|
785
|
-
const stableMatch = requirements.find(
|
|
786
|
-
(requirement) => matchesStableFields(requirement, payload.accepted)
|
|
787
|
-
);
|
|
788
|
-
return stableMatch ? payload.accepted : null;
|
|
789
|
-
}
|
|
790
|
-
function matchesStableFields(requirement, accepted) {
|
|
791
|
-
return requirement.scheme === accepted.scheme && requirement.network === accepted.network && requirement.payTo === accepted.payTo && requirement.asset === accepted.asset && requirement.amount === accepted.amount && requirement.maxTimeoutSeconds === accepted.maxTimeoutSeconds;
|
|
792
|
-
}
|
|
793
|
-
async function buildExpectedRequirements(server, request, price, accepts) {
|
|
794
|
-
const exactRequirements = await buildExactRequirements(server, request, price, accepts);
|
|
795
|
-
const customRequirements = buildCustomRequirements(price, accepts);
|
|
796
|
-
return [...exactRequirements, ...customRequirements];
|
|
797
720
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
721
|
+
|
|
722
|
+
// src/pipeline/context/invoke.ts
|
|
723
|
+
async function invoke(ctx, wallet, account, body, payment) {
|
|
724
|
+
const handlerCtx = {
|
|
725
|
+
body,
|
|
726
|
+
query: parseQuery(ctx.request, ctx.routeEntry),
|
|
727
|
+
request: ctx.request,
|
|
728
|
+
requestId: ctx.meta.requestId,
|
|
729
|
+
route: ctx.routeEntry.key,
|
|
730
|
+
wallet,
|
|
731
|
+
payment,
|
|
732
|
+
account,
|
|
733
|
+
alert(level, message, alertMeta) {
|
|
734
|
+
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
735
|
+
level,
|
|
736
|
+
message,
|
|
737
|
+
route: ctx.routeEntry.key,
|
|
738
|
+
meta: alertMeta
|
|
739
|
+
});
|
|
740
|
+
},
|
|
741
|
+
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
742
|
+
};
|
|
743
|
+
let rawResult;
|
|
744
|
+
let handlerError;
|
|
745
|
+
const response = await safeCallHandler(
|
|
746
|
+
async (c) => {
|
|
747
|
+
rawResult = await ctx.handler(c);
|
|
748
|
+
return rawResult;
|
|
749
|
+
},
|
|
750
|
+
handlerCtx,
|
|
751
|
+
{
|
|
752
|
+
onError(error) {
|
|
753
|
+
handlerError = error;
|
|
816
754
|
}
|
|
817
|
-
console.warn(
|
|
818
|
-
`[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
|
|
819
|
-
);
|
|
820
755
|
}
|
|
756
|
+
);
|
|
757
|
+
return { response, rawResult, handlerError };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/pipeline/context/fire-provider-quota.ts
|
|
761
|
+
function fireProviderQuota(ctx, response, handlerResult) {
|
|
762
|
+
const { providerName, providerConfig } = ctx.routeEntry;
|
|
763
|
+
if (!providerName || !providerConfig?.extractQuota) return;
|
|
764
|
+
if (response.status >= 400) return;
|
|
765
|
+
try {
|
|
766
|
+
const quota = providerConfig.extractQuota(handlerResult, response.headers);
|
|
767
|
+
if (!quota) return;
|
|
768
|
+
const level = computeQuotaLevel(quota.remaining, providerConfig.warn, providerConfig.critical);
|
|
769
|
+
const overage = providerConfig.overage ?? "same-rate";
|
|
770
|
+
const event = {
|
|
771
|
+
provider: providerName,
|
|
772
|
+
route: ctx.routeEntry.key,
|
|
773
|
+
remaining: quota.remaining,
|
|
774
|
+
limit: quota.limit,
|
|
775
|
+
spend: quota.spend,
|
|
776
|
+
level,
|
|
777
|
+
overage,
|
|
778
|
+
message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
|
|
779
|
+
};
|
|
780
|
+
firePluginHook(ctx.deps.plugin, "onProviderQuota", ctx.pluginCtx, event);
|
|
781
|
+
} catch {
|
|
821
782
|
}
|
|
822
|
-
if (requirements.length > 0) {
|
|
823
|
-
return requirements;
|
|
824
|
-
}
|
|
825
|
-
throw failures[0] ?? new Error("Failed to build x402 exact requirements");
|
|
826
783
|
}
|
|
827
|
-
function
|
|
828
|
-
|
|
784
|
+
function computeQuotaLevel(remaining, warn, critical) {
|
|
785
|
+
if (remaining === null) return "healthy";
|
|
786
|
+
if (critical !== void 0 && remaining <= critical) return "critical";
|
|
787
|
+
if (warn !== void 0 && remaining <= warn) return "warn";
|
|
788
|
+
return "healthy";
|
|
829
789
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
790
|
+
|
|
791
|
+
// src/pipeline/context/finalize.ts
|
|
792
|
+
function finalize(ctx, response, rawResult, requestBody) {
|
|
793
|
+
fireProviderQuota(ctx, response, rawResult);
|
|
794
|
+
firePluginResponse(ctx, response, requestBody, rawResult);
|
|
795
|
+
return response;
|
|
834
796
|
}
|
|
835
|
-
|
|
836
|
-
|
|
797
|
+
|
|
798
|
+
// src/pipeline/context/run-handler-only.ts
|
|
799
|
+
async function runHandlerOnly(ctx, wallet, account) {
|
|
800
|
+
const body = await parseBody(ctx.request, ctx.routeEntry);
|
|
801
|
+
if (!body.ok) {
|
|
802
|
+
firePluginResponse(ctx, body.response);
|
|
803
|
+
return body.response;
|
|
804
|
+
}
|
|
805
|
+
const validateErr = await runValidate(ctx, body.data);
|
|
806
|
+
if (validateErr) return validateErr;
|
|
807
|
+
const result = await invoke(ctx, wallet, account, body.data, null);
|
|
808
|
+
return finalize(ctx, result.response, result.rawResult, body.data);
|
|
837
809
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
810
|
+
|
|
811
|
+
// src/pipeline/context/settlement-context.ts
|
|
812
|
+
function settlementContext(ctx, scope) {
|
|
813
|
+
return {
|
|
814
|
+
route: ctx.routeEntry.key,
|
|
815
|
+
request: ctx.request,
|
|
816
|
+
body: scope.body,
|
|
817
|
+
wallet: scope.wallet,
|
|
818
|
+
account: scope.account,
|
|
819
|
+
payment: scope.payment,
|
|
820
|
+
response: scope.response,
|
|
821
|
+
result: scope.rawResult
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/pipeline/context/run-before-settle.ts
|
|
826
|
+
async function runBeforeSettle(ctx, scope) {
|
|
827
|
+
const hook = ctx.routeEntry.settlement?.beforeSettle;
|
|
828
|
+
if (!hook) return null;
|
|
829
|
+
try {
|
|
830
|
+
await hook(settlementContext(ctx, scope));
|
|
831
|
+
return null;
|
|
832
|
+
} catch (error) {
|
|
833
|
+
return fail(
|
|
834
|
+
ctx,
|
|
835
|
+
errorStatus(error, 500),
|
|
836
|
+
errorMessage(error, "Pre-settlement validation failed"),
|
|
837
|
+
scope.body
|
|
847
838
|
);
|
|
848
839
|
}
|
|
849
|
-
return accepted;
|
|
850
840
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
})
|
|
867
|
-
);
|
|
868
|
-
const enriched = [...requirements];
|
|
869
|
-
results.filter((r) => r.success).forEach(({ group, accepted }) => {
|
|
870
|
-
accepted.forEach((req, offset) => {
|
|
871
|
-
const index = group.items[offset]?.index;
|
|
872
|
-
if (index !== void 0) enriched[index] = req;
|
|
841
|
+
|
|
842
|
+
// src/pipeline/context/run-settlement-error.ts
|
|
843
|
+
async function runSettlementError(ctx, scope, error, phase) {
|
|
844
|
+
const hook = ctx.routeEntry.settlement?.onSettlementError;
|
|
845
|
+
if (!hook) return;
|
|
846
|
+
try {
|
|
847
|
+
await hook({ ...settlementContext(ctx, scope), error, phase });
|
|
848
|
+
} catch (hookError) {
|
|
849
|
+
const message = errorMessage(hookError, "Settlement error hook failed");
|
|
850
|
+
console.error(`[router] ${ctx.routeEntry.key}: onSettlementError failed: ${message}`);
|
|
851
|
+
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
852
|
+
level: "error",
|
|
853
|
+
message: `Settlement error hook failed: ${message}`,
|
|
854
|
+
route: ctx.routeEntry.key
|
|
873
855
|
});
|
|
874
|
-
});
|
|
875
|
-
const failedIndices = new Set(
|
|
876
|
-
results.filter((r) => !r.success).flatMap(({ group }) => group.items.map(({ index }) => index))
|
|
877
|
-
);
|
|
878
|
-
const remaining = enriched.filter((_, i) => !failedIndices.has(i));
|
|
879
|
-
if (remaining.length === 0) {
|
|
880
|
-
throw new Error(
|
|
881
|
-
"All facilitator enrichments failed; no payment requirements remain for challenge"
|
|
882
|
-
);
|
|
883
856
|
}
|
|
884
|
-
return remaining;
|
|
885
857
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
items: [{ index, requirement }]
|
|
858
|
+
|
|
859
|
+
// src/pipeline/context/run-after-settle.ts
|
|
860
|
+
async function runAfterSettle(ctx, scope) {
|
|
861
|
+
const hook = ctx.routeEntry.settlement?.afterSettle;
|
|
862
|
+
if (!hook) return;
|
|
863
|
+
try {
|
|
864
|
+
await hook(settlementContext(ctx, scope));
|
|
865
|
+
} catch (error) {
|
|
866
|
+
const message = errorMessage(error, "Post-settlement hook failed");
|
|
867
|
+
console.error(`[router] ${ctx.routeEntry.key}: afterSettle failed: ${message}`);
|
|
868
|
+
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
869
|
+
level: "error",
|
|
870
|
+
message: `Post-settlement hook failed: ${message}`,
|
|
871
|
+
route: ctx.routeEntry.key
|
|
901
872
|
});
|
|
902
|
-
|
|
903
|
-
|
|
873
|
+
await runSettlementError(ctx, scope, error, "afterSettle");
|
|
874
|
+
}
|
|
904
875
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
876
|
+
|
|
877
|
+
// src/pipeline/context/run-settled-handler-error.ts
|
|
878
|
+
async function runSettledHandlerError(ctx, scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
|
|
879
|
+
const hook = ctx.routeEntry.settlement?.onSettledHandlerError;
|
|
880
|
+
if (!hook) return;
|
|
881
|
+
try {
|
|
882
|
+
await hook({ ...settlementContext(ctx, scope), error });
|
|
883
|
+
} catch (hookError) {
|
|
884
|
+
const message = errorMessage(hookError, "Settled handler error hook failed");
|
|
885
|
+
console.error(`[router] ${ctx.routeEntry.key}: onSettledHandlerError failed: ${message}`);
|
|
886
|
+
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
887
|
+
level: "error",
|
|
888
|
+
message: `Settled handler error hook failed: ${message}`,
|
|
889
|
+
route: ctx.routeEntry.key
|
|
890
|
+
});
|
|
911
891
|
}
|
|
912
|
-
return facilitator;
|
|
913
|
-
}
|
|
914
|
-
function requiresFacilitatorEnrichment(requirement) {
|
|
915
|
-
return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
|
|
916
892
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
893
|
+
|
|
894
|
+
// src/pipeline/context/grant-entitlement.ts
|
|
895
|
+
async function grantEntitlementIfSiwx(ctx, wallet) {
|
|
896
|
+
if (!ctx.routeEntry.siwxEnabled) return;
|
|
897
|
+
try {
|
|
898
|
+
await ctx.deps.entitlementStore.grant(ctx.routeEntry.key, wallet);
|
|
899
|
+
} catch (error) {
|
|
900
|
+
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
901
|
+
level: "warn",
|
|
902
|
+
message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
903
|
+
route: ctx.routeEntry.key
|
|
904
|
+
});
|
|
922
905
|
}
|
|
923
|
-
return {
|
|
924
|
-
scheme: accept.scheme,
|
|
925
|
-
network: accept.network,
|
|
926
|
-
amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
|
|
927
|
-
asset: accept.asset,
|
|
928
|
-
payTo: accept.payTo,
|
|
929
|
-
maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
|
|
930
|
-
extra: accept.extra ?? {}
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
function buildChallengeResource(request, routeEntry) {
|
|
934
|
-
return {
|
|
935
|
-
url: request.url,
|
|
936
|
-
method: routeEntry.method,
|
|
937
|
-
description: routeEntry.description,
|
|
938
|
-
mimeType: "application/json"
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
async function readPaymentPayload(request) {
|
|
942
|
-
const paymentHeader = request.headers.get("PAYMENT-SIGNATURE") ?? request.headers.get("X-PAYMENT");
|
|
943
|
-
if (!paymentHeader) return null;
|
|
944
|
-
const { decodePaymentSignatureHeader } = await import("@x402/core/http");
|
|
945
|
-
return decodePaymentSignatureHeader(paymentHeader);
|
|
946
|
-
}
|
|
947
|
-
function invalidPaymentVerification() {
|
|
948
|
-
return { valid: false, payload: null, requirements: null, payer: null };
|
|
949
906
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
}
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
907
|
+
|
|
908
|
+
// src/pipeline/context/settle-and-finalize.ts
|
|
909
|
+
async function settleAndFinalize(args) {
|
|
910
|
+
const { ctx, strategy, verifyOutcome, scope, rawResult, body, onSettleError } = args;
|
|
911
|
+
const { request, routeEntry, deps } = ctx;
|
|
912
|
+
const settle = await strategy.settle({
|
|
913
|
+
request,
|
|
914
|
+
response: scope.response,
|
|
915
|
+
payment: verifyOutcome.payment,
|
|
916
|
+
token: verifyOutcome.token,
|
|
917
|
+
routeEntry,
|
|
918
|
+
deps
|
|
919
|
+
});
|
|
920
|
+
if (!settle.ok) {
|
|
921
|
+
if (onSettleError) await onSettleError(settle.error, settle.failMessage);
|
|
922
|
+
return fail(ctx, settle.failStatus ?? 500, settle.failMessage, body);
|
|
923
|
+
}
|
|
924
|
+
await grantEntitlementIfSiwx(ctx, verifyOutcome.wallet);
|
|
925
|
+
firePluginHook(deps.plugin, "onPaymentSettled", ctx.pluginCtx, {
|
|
926
|
+
protocol: strategy.protocol,
|
|
927
|
+
payer: verifyOutcome.wallet,
|
|
928
|
+
transaction: settle.settledPayment.transaction ?? "",
|
|
929
|
+
network: settle.settledPayment.network
|
|
930
|
+
});
|
|
931
|
+
await runAfterSettle(ctx, {
|
|
932
|
+
...scope,
|
|
933
|
+
payment: settle.settledPayment,
|
|
934
|
+
response: settle.response
|
|
935
|
+
});
|
|
936
|
+
return finalize(ctx, settle.response, rawResult, body);
|
|
962
937
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
return { encoded, result };
|
|
938
|
+
|
|
939
|
+
// src/auth/normalize-wallet.ts
|
|
940
|
+
function normalizeWalletAddress(address) {
|
|
941
|
+
return /^0x/i.test(address) ? address.toLowerCase() : address;
|
|
968
942
|
}
|
|
969
943
|
|
|
970
944
|
// src/auth/siwx.ts
|
|
@@ -988,7 +962,7 @@ function categorizeValidationError(error) {
|
|
|
988
962
|
}
|
|
989
963
|
async function verifySIWX(request, _routeEntry, nonceStore) {
|
|
990
964
|
const { parseSIWxHeader, validateSIWxMessage, verifySIWxSignature } = await import("@x402/extensions/sign-in-with-x");
|
|
991
|
-
const header = request.headers.get(
|
|
965
|
+
const header = request.headers.get(HEADERS.SIWX);
|
|
992
966
|
if (!header) {
|
|
993
967
|
return { valid: false, wallet: null, code: "siwx_missing_header" };
|
|
994
968
|
}
|
|
@@ -1017,25 +991,52 @@ async function buildSIWXExtension() {
|
|
|
1017
991
|
return declareSIWxExtension();
|
|
1018
992
|
}
|
|
1019
993
|
|
|
1020
|
-
// src/
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const
|
|
1025
|
-
if (
|
|
1026
|
-
|
|
994
|
+
// src/pipeline/context/try-siwx-fast-path.ts
|
|
995
|
+
async function trySiwxFastPath(ctx, account) {
|
|
996
|
+
const { request, routeEntry, deps } = ctx;
|
|
997
|
+
if (!routeEntry.siwxEnabled) return null;
|
|
998
|
+
const siwxHeader = request.headers.get(HEADERS.SIWX);
|
|
999
|
+
if (!siwxHeader) return null;
|
|
1000
|
+
const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
|
|
1001
|
+
if (!siwx.valid) return null;
|
|
1002
|
+
const wallet = normalizeWalletAddress(siwx.wallet);
|
|
1003
|
+
ctx.pluginCtx.setVerifiedWallet(wallet);
|
|
1004
|
+
const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
|
|
1005
|
+
if (!entitled) return null;
|
|
1006
|
+
firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
|
|
1007
|
+
authMode: "siwx",
|
|
1008
|
+
wallet,
|
|
1009
|
+
route: routeEntry.key
|
|
1010
|
+
});
|
|
1011
|
+
return runHandlerOnly(ctx, wallet, account);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/pipeline/context/should-parse-body-early.ts
|
|
1015
|
+
function shouldParseBodyEarly(incomingStrategy, routeEntry, pricing) {
|
|
1016
|
+
if (incomingStrategy) return false;
|
|
1017
|
+
if (!routeEntry.bodySchema) return false;
|
|
1018
|
+
return (pricing?.needsBody ?? false) || !!routeEntry.validateFn;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// src/pipeline/context/protocol-init-error.ts
|
|
1022
|
+
function protocolInitError(routeEntry, deps) {
|
|
1023
|
+
if (!routeEntry.pricing) return null;
|
|
1024
|
+
const errors = [];
|
|
1025
|
+
for (const protocol of routeEntry.protocols) {
|
|
1026
|
+
if (protocol === "x402" && deps.x402InitError) {
|
|
1027
|
+
errors.push(`x402: ${deps.x402InitError}`);
|
|
1028
|
+
}
|
|
1029
|
+
if (protocol === "mpp" && deps.mppInitError) {
|
|
1030
|
+
errors.push(`mpp: ${deps.mppInitError}`);
|
|
1031
|
+
}
|
|
1027
1032
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
const didParts = rawSource.split(":");
|
|
1031
|
-
const lastPart = didParts[didParts.length - 1];
|
|
1032
|
-
const wallet = normalizeWalletAddress((0, import_viem.isAddress)(lastPart) ? (0, import_viem.getAddress)(lastPart) : rawSource);
|
|
1033
|
-
return { valid: true, wallet, withReceipt: result.withReceipt };
|
|
1033
|
+
if (errors.length === 0) return null;
|
|
1034
|
+
return `Payment protocol initialization failed. ${errors.join("; ")}`;
|
|
1034
1035
|
}
|
|
1035
1036
|
|
|
1036
1037
|
// src/auth/api-key.ts
|
|
1037
1038
|
async function verifyApiKey(request, resolver) {
|
|
1038
|
-
const apiKey = request.headers.get(
|
|
1039
|
+
const apiKey = request.headers.get(HEADERS.API_KEY) ?? extractBearerToken(request.headers.get(HEADERS.AUTHORIZATION));
|
|
1039
1040
|
if (!apiKey) {
|
|
1040
1041
|
return { valid: false, account: null };
|
|
1041
1042
|
}
|
|
@@ -1047,947 +1048,873 @@ async function verifyApiKey(request, resolver) {
|
|
|
1047
1048
|
}
|
|
1048
1049
|
function extractBearerToken(header) {
|
|
1049
1050
|
if (!header) return null;
|
|
1050
|
-
if (header.startsWith(
|
|
1051
|
+
if (header.startsWith(AUTH_SCHEME.BEARER)) return header.slice(AUTH_SCHEME.BEARER.length);
|
|
1051
1052
|
return null;
|
|
1052
1053
|
}
|
|
1053
1054
|
|
|
1054
|
-
// src/
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1055
|
+
// src/pipeline/flows/api-key-only.ts
|
|
1056
|
+
async function runApiKeyOnlyFlow(ctx) {
|
|
1057
|
+
if (!ctx.routeEntry.apiKeyResolver) {
|
|
1058
|
+
return fail(ctx, 401, "API key resolver not configured");
|
|
1059
|
+
}
|
|
1060
|
+
const result = await verifyApiKey(ctx.request, ctx.routeEntry.apiKeyResolver);
|
|
1061
|
+
if (!result.valid) return fail(ctx, 401, "Invalid or missing API key");
|
|
1062
|
+
firePluginHook(ctx.deps.plugin, "onAuthVerified", ctx.pluginCtx, {
|
|
1063
|
+
authMode: "apiKey",
|
|
1064
|
+
wallet: null,
|
|
1065
|
+
route: ctx.routeEntry.key,
|
|
1066
|
+
account: result.account
|
|
1067
|
+
});
|
|
1068
|
+
return runHandlerOnly(ctx, null, result.account);
|
|
1059
1069
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1070
|
+
|
|
1071
|
+
// src/pricing/dynamic.ts
|
|
1072
|
+
var DynamicPricing = class {
|
|
1073
|
+
constructor(opts) {
|
|
1074
|
+
this.opts = opts;
|
|
1075
|
+
}
|
|
1076
|
+
needsBody = true;
|
|
1077
|
+
async quote(body) {
|
|
1078
|
+
try {
|
|
1079
|
+
const raw = await this.opts.fn(body);
|
|
1080
|
+
return this.cap(raw, body);
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
this.alert("error", `Pricing function failed: ${msg(err)}`, {
|
|
1083
|
+
error: err instanceof Error ? err.stack : String(err),
|
|
1084
|
+
body
|
|
1085
|
+
});
|
|
1086
|
+
if (this.opts.maxPrice) {
|
|
1087
|
+
this.alert("warn", `Using maxPrice ${this.opts.maxPrice} as fallback after pricing error`);
|
|
1088
|
+
return this.opts.maxPrice;
|
|
1089
|
+
}
|
|
1090
|
+
throw err;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
challengeQuote(body) {
|
|
1094
|
+
if (body === void 0) return Promise.resolve(this.opts.maxPrice ?? "0");
|
|
1095
|
+
return this.quote(body);
|
|
1096
|
+
}
|
|
1097
|
+
describe() {
|
|
1098
|
+
return {
|
|
1099
|
+
mode: "dynamic",
|
|
1100
|
+
min: this.opts.minPrice ?? "0",
|
|
1101
|
+
max: this.opts.maxPrice ?? "0"
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
cap(raw, body) {
|
|
1105
|
+
if (!this.opts.maxPrice) return raw;
|
|
1106
|
+
const n = parseFloat(raw);
|
|
1107
|
+
const max = parseFloat(this.opts.maxPrice);
|
|
1108
|
+
if (!Number.isFinite(n) || n > max) {
|
|
1109
|
+
this.alert("warn", `Price ${raw} exceeds maxPrice ${this.opts.maxPrice}, capping`, {
|
|
1110
|
+
calculated: raw,
|
|
1111
|
+
maxPrice: this.opts.maxPrice,
|
|
1112
|
+
body
|
|
1113
|
+
});
|
|
1114
|
+
return this.opts.maxPrice;
|
|
1115
|
+
}
|
|
1116
|
+
return raw;
|
|
1117
|
+
}
|
|
1118
|
+
alert(level, message, meta) {
|
|
1119
|
+
this.opts.alert?.(level, message, meta);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
function msg(err) {
|
|
1123
|
+
return err instanceof Error ? err.message : String(err);
|
|
1063
1124
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1125
|
+
|
|
1126
|
+
// src/pricing/fixed.ts
|
|
1127
|
+
var FixedPricing = class {
|
|
1128
|
+
constructor(price) {
|
|
1129
|
+
this.price = price;
|
|
1130
|
+
}
|
|
1131
|
+
needsBody = false;
|
|
1132
|
+
quote() {
|
|
1133
|
+
return Promise.resolve(this.price);
|
|
1134
|
+
}
|
|
1135
|
+
challengeQuote() {
|
|
1136
|
+
return Promise.resolve(this.price);
|
|
1137
|
+
}
|
|
1138
|
+
describe() {
|
|
1139
|
+
return { mode: "fixed", amount: this.price };
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
// src/pricing/tiered.ts
|
|
1144
|
+
var TieredPricing = class {
|
|
1145
|
+
constructor(opts) {
|
|
1146
|
+
this.opts = opts;
|
|
1147
|
+
}
|
|
1148
|
+
needsBody = true;
|
|
1149
|
+
async quote(body) {
|
|
1150
|
+
const { field, tiers, default: defaultTier } = this.opts;
|
|
1151
|
+
const tierKey = body != null ? String(body[field] ?? "") : "";
|
|
1152
|
+
if (tierKey && tiers[tierKey]) return tiers[tierKey].price;
|
|
1153
|
+
if (defaultTier && tiers[defaultTier]) return tiers[defaultTier].price;
|
|
1154
|
+
if (!tierKey) {
|
|
1155
|
+
throw httpError(400, `Missing required field '${field}' for tier pricing`);
|
|
1156
|
+
}
|
|
1157
|
+
throw httpError(
|
|
1158
|
+
400,
|
|
1159
|
+
`Unknown tier '${tierKey}' for field '${field}'. Valid tiers: ${Object.keys(tiers).join(", ")}`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
challengeQuote(body) {
|
|
1163
|
+
if (body !== void 0) {
|
|
1164
|
+
try {
|
|
1165
|
+
return this.quote(body);
|
|
1166
|
+
} catch {
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return Promise.resolve(this.maxTierPrice());
|
|
1170
|
+
}
|
|
1171
|
+
describe() {
|
|
1172
|
+
return {
|
|
1173
|
+
mode: "tiered",
|
|
1174
|
+
tiers: Object.entries(this.opts.tiers).map(([key, tier]) => ({
|
|
1175
|
+
key,
|
|
1176
|
+
price: tier.price,
|
|
1177
|
+
...tier.label !== void 0 ? { label: tier.label } : {}
|
|
1178
|
+
})),
|
|
1179
|
+
...this.opts.default !== void 0 ? { default: this.opts.default } : {}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
maxTierPrice() {
|
|
1183
|
+
let max = "0";
|
|
1184
|
+
for (const tier of Object.values(this.opts.tiers)) {
|
|
1185
|
+
if (parseFloat(tier.price) > parseFloat(max)) max = tier.price;
|
|
1186
|
+
}
|
|
1187
|
+
return max;
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
function httpError(status, message) {
|
|
1191
|
+
return Object.assign(new Error(message), { status });
|
|
1067
1192
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1193
|
+
|
|
1194
|
+
// src/pricing/index.ts
|
|
1195
|
+
function selectPricing(raw, deps = {}) {
|
|
1196
|
+
if (raw == null) return null;
|
|
1197
|
+
if (typeof raw === "string") {
|
|
1198
|
+
return new FixedPricing(raw);
|
|
1199
|
+
}
|
|
1200
|
+
if (typeof raw === "function") {
|
|
1201
|
+
return new DynamicPricing({
|
|
1202
|
+
fn: raw,
|
|
1203
|
+
maxPrice: deps.maxPrice,
|
|
1204
|
+
minPrice: deps.minPrice,
|
|
1205
|
+
route: deps.route,
|
|
1206
|
+
alert: deps.alert
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
if (typeof raw === "object" && "tiers" in raw) {
|
|
1210
|
+
return new TieredPricing({
|
|
1211
|
+
field: raw.field,
|
|
1212
|
+
tiers: raw.tiers,
|
|
1213
|
+
default: raw.default,
|
|
1214
|
+
maxPrice: deps.maxPrice,
|
|
1215
|
+
minPrice: deps.minPrice
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
throw new Error(`Unknown pricing config: ${JSON.stringify(raw)}`);
|
|
1070
1219
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1220
|
+
|
|
1221
|
+
// src/protocols/mpp/credential.ts
|
|
1222
|
+
var import_mppx = require("mppx");
|
|
1223
|
+
var import_viem = require("viem");
|
|
1224
|
+
function readMppCredential(request) {
|
|
1225
|
+
const credential = import_mppx.Credential.fromRequest(request);
|
|
1226
|
+
if (!credential) return null;
|
|
1227
|
+
const wallet = walletFromDid(credential.source ?? "");
|
|
1228
|
+
const rawType = credential.payload?.type;
|
|
1229
|
+
const payloadType = rawType === "transaction" ? "transaction" : rawType === "hash" ? "hash" : "unknown";
|
|
1230
|
+
return { credential, wallet, payloadType };
|
|
1074
1231
|
}
|
|
1075
|
-
function
|
|
1076
|
-
|
|
1232
|
+
function walletFromDid(rawSource) {
|
|
1233
|
+
const parts = rawSource.split(":");
|
|
1234
|
+
const last = parts[parts.length - 1];
|
|
1235
|
+
return normalizeWalletAddress((0, import_viem.isAddress)(last) ? (0, import_viem.getAddress)(last) : rawSource);
|
|
1077
1236
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1237
|
+
|
|
1238
|
+
// src/protocols/mpp/transaction-mode.ts
|
|
1239
|
+
var import_tempo = require("viem/tempo");
|
|
1240
|
+
var import_actions = require("viem/actions");
|
|
1241
|
+
|
|
1242
|
+
// src/protocols/mpp/receipt.ts
|
|
1243
|
+
var import_mppx2 = require("mppx");
|
|
1244
|
+
function extractTxHash(receiptHeader) {
|
|
1245
|
+
if (!receiptHeader) return "";
|
|
1246
|
+
try {
|
|
1247
|
+
return import_mppx2.Receipt.deserialize(receiptHeader).reference;
|
|
1248
|
+
} catch {
|
|
1249
|
+
return "";
|
|
1086
1250
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1251
|
+
}
|
|
1252
|
+
async function readChallengeReason(challenge) {
|
|
1253
|
+
try {
|
|
1254
|
+
const text = await challenge.clone().text();
|
|
1255
|
+
if (!text) return "";
|
|
1256
|
+
const problem = JSON.parse(text);
|
|
1257
|
+
return problem.detail || problem.title || "";
|
|
1258
|
+
} catch {
|
|
1259
|
+
return "";
|
|
1089
1260
|
}
|
|
1090
|
-
return chains;
|
|
1091
1261
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
payment,
|
|
1102
|
-
account,
|
|
1103
|
-
alert(level, message, alertMeta) {
|
|
1104
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1105
|
-
level,
|
|
1106
|
-
message,
|
|
1107
|
-
route: routeEntry.key,
|
|
1108
|
-
meta: alertMeta
|
|
1109
|
-
});
|
|
1110
|
-
},
|
|
1111
|
-
setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
|
|
1262
|
+
|
|
1263
|
+
// src/protocols/mpp/transaction-mode.ts
|
|
1264
|
+
async function verifyTxMode(args, info) {
|
|
1265
|
+
const { deps, price, routeEntry } = args;
|
|
1266
|
+
if (!deps.tempoClient) {
|
|
1267
|
+
return {
|
|
1268
|
+
ok: false,
|
|
1269
|
+
kind: "config",
|
|
1270
|
+
message: "tempoClient not configured for MPP transaction-payload mode"
|
|
1112
1271
|
};
|
|
1113
|
-
let rawResult;
|
|
1114
|
-
let handlerError;
|
|
1115
|
-
const response = await safeCallHandler(
|
|
1116
|
-
async (c) => {
|
|
1117
|
-
rawResult = await handler(c);
|
|
1118
|
-
return rawResult;
|
|
1119
|
-
},
|
|
1120
|
-
ctx,
|
|
1121
|
-
{
|
|
1122
|
-
onError(error) {
|
|
1123
|
-
handlerError = error;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
);
|
|
1127
|
-
return { response, rawResult, handlerError };
|
|
1128
1272
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1273
|
+
try {
|
|
1274
|
+
const serializedTx = info.credential.payload.signature;
|
|
1275
|
+
const transaction = import_tempo.Transaction.deserialize(serializedTx);
|
|
1276
|
+
await (0, import_actions.call)(deps.tempoClient, {
|
|
1277
|
+
...transaction,
|
|
1278
|
+
account: transaction.from,
|
|
1279
|
+
calls: transaction.calls ?? []
|
|
1280
|
+
});
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1283
|
+
console.warn(`[router] ${routeEntry.key}: MPP simulation failed \u2014 ${message}`);
|
|
1284
|
+
return { ok: false, kind: "invalid" };
|
|
1285
|
+
}
|
|
1286
|
+
const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
|
|
1287
|
+
const payment = {
|
|
1288
|
+
protocol: "mpp",
|
|
1289
|
+
status: "verified",
|
|
1290
|
+
payer: info.wallet,
|
|
1291
|
+
amount: price,
|
|
1292
|
+
network: "tempo:4217",
|
|
1293
|
+
...mppRecipient ? { recipient: mppRecipient } : {}
|
|
1294
|
+
};
|
|
1295
|
+
return {
|
|
1296
|
+
ok: true,
|
|
1297
|
+
wallet: info.wallet,
|
|
1298
|
+
payment,
|
|
1299
|
+
token: { mode: "transaction", credential: info.credential },
|
|
1300
|
+
alreadySettled: false
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
async function settleTxMode(args) {
|
|
1304
|
+
const { request, response, payment, deps, routeEntry } = args;
|
|
1305
|
+
if (!deps.mppx) {
|
|
1306
|
+
return {
|
|
1307
|
+
ok: false,
|
|
1308
|
+
error: new Error("mppx unavailable"),
|
|
1309
|
+
failMessage: "MPP not initialized",
|
|
1310
|
+
failStatus: 500
|
|
1311
|
+
};
|
|
1132
1312
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1313
|
+
let result;
|
|
1314
|
+
try {
|
|
1315
|
+
result = await deps.mppx.charge({ amount: payment.amount })(request);
|
|
1316
|
+
} catch (err) {
|
|
1317
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1318
|
+
console.error(`[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`);
|
|
1319
|
+
return {
|
|
1320
|
+
ok: false,
|
|
1321
|
+
error: err,
|
|
1322
|
+
failMessage: `MPP payment processing failed: ${message}`,
|
|
1323
|
+
failStatus: 500
|
|
1324
|
+
};
|
|
1137
1325
|
}
|
|
1138
|
-
|
|
1326
|
+
if (result.status === 402) {
|
|
1327
|
+
const reason = await readChallengeReason(result.challenge);
|
|
1328
|
+
const detail = reason || "transaction reverted on-chain after handler execution";
|
|
1329
|
+
const settlementError = Object.assign(new Error(detail), {
|
|
1330
|
+
status: 402,
|
|
1331
|
+
detail,
|
|
1332
|
+
mppResult: result,
|
|
1333
|
+
challenge: result.challenge
|
|
1334
|
+
});
|
|
1335
|
+
console.error(`[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`);
|
|
1139
1336
|
return {
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
account: scope.account,
|
|
1145
|
-
payment: scope.payment,
|
|
1146
|
-
response: scope.response,
|
|
1147
|
-
result: scope.rawResult
|
|
1337
|
+
ok: false,
|
|
1338
|
+
error: settlementError,
|
|
1339
|
+
failMessage: `MPP payment failed: ${detail}`,
|
|
1340
|
+
failStatus: 500
|
|
1148
1341
|
};
|
|
1149
1342
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1343
|
+
const receiptResponse = result.withReceipt(response);
|
|
1344
|
+
receiptResponse.headers.set("Cache-Control", "private");
|
|
1345
|
+
const receiptHeader = receiptResponse.headers.get(HEADERS.MPP_PAYMENT_RECEIPT) ?? void 0;
|
|
1346
|
+
const txHash = extractTxHash(receiptHeader);
|
|
1347
|
+
const settledPayment = {
|
|
1348
|
+
...payment,
|
|
1349
|
+
status: "settled",
|
|
1350
|
+
...txHash ? { transaction: txHash } : {},
|
|
1351
|
+
...receiptHeader ? { receipt: receiptHeader } : {}
|
|
1352
|
+
};
|
|
1353
|
+
return { ok: true, response: receiptResponse, settledPayment };
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// src/protocols/mpp/hash-mode.ts
|
|
1357
|
+
async function verifyHashMode(args, info) {
|
|
1358
|
+
const { deps, price, routeEntry, request } = args;
|
|
1359
|
+
if (!deps.mppx) {
|
|
1360
|
+
const reason = deps.mppInitError ? `MPP initialization failed: ${deps.mppInitError}` : "MPP not initialized \u2014 ensure mppx is installed and mpp config (secretKey, currency, recipient) is correct";
|
|
1361
|
+
console.error(`[router] ${routeEntry.key}: ${reason}`);
|
|
1362
|
+
return { ok: false, kind: "config", message: reason };
|
|
1363
|
+
}
|
|
1364
|
+
let chargeResult;
|
|
1365
|
+
try {
|
|
1366
|
+
chargeResult = await deps.mppx.charge({ amount: price })(request);
|
|
1367
|
+
} catch (err) {
|
|
1368
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1369
|
+
console.error(`[router] ${routeEntry.key}: MPP charge failed: ${message}`);
|
|
1370
|
+
return { ok: false, kind: "config", message: `MPP payment processing failed: ${message}` };
|
|
1371
|
+
}
|
|
1372
|
+
if (chargeResult.status === 402) {
|
|
1373
|
+
const reason = await readChallengeReason(chargeResult.challenge);
|
|
1374
|
+
const detail = reason || "credential may be invalid, or check TEMPO_RPC_URL configuration";
|
|
1375
|
+
console.warn(`[router] ${routeEntry.key}: MPP credential rejected \u2014 ${detail}`);
|
|
1376
|
+
return { ok: false, kind: "invalid" };
|
|
1377
|
+
}
|
|
1378
|
+
const receiptHeader = chargeResult.withReceipt(new Response()).headers.get(
|
|
1379
|
+
HEADERS.MPP_PAYMENT_RECEIPT
|
|
1380
|
+
);
|
|
1381
|
+
const txHash = extractTxHash(receiptHeader);
|
|
1382
|
+
const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
|
|
1383
|
+
const payment = {
|
|
1384
|
+
protocol: "mpp",
|
|
1385
|
+
status: "settled",
|
|
1386
|
+
payer: info.wallet,
|
|
1387
|
+
amount: price,
|
|
1388
|
+
network: "tempo:4217",
|
|
1389
|
+
...mppRecipient ? { recipient: mppRecipient } : {},
|
|
1390
|
+
...txHash ? { transaction: txHash } : {},
|
|
1391
|
+
...receiptHeader ? { receipt: receiptHeader } : {}
|
|
1392
|
+
};
|
|
1393
|
+
return {
|
|
1394
|
+
ok: true,
|
|
1395
|
+
wallet: info.wallet,
|
|
1396
|
+
payment,
|
|
1397
|
+
token: { mode: "hash", charge: chargeResult },
|
|
1398
|
+
alreadySettled: true
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
function settleHashMode(args) {
|
|
1402
|
+
const { response, payment, token } = args;
|
|
1403
|
+
const hashToken = token;
|
|
1404
|
+
const receiptResponse = hashToken.charge.withReceipt(response);
|
|
1405
|
+
receiptResponse.headers.set("Cache-Control", "private");
|
|
1406
|
+
return {
|
|
1407
|
+
ok: true,
|
|
1408
|
+
response: receiptResponse,
|
|
1409
|
+
settledPayment: payment
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/protocols/mpp/strategy.ts
|
|
1414
|
+
var mppStrategy = {
|
|
1415
|
+
protocol: "mpp",
|
|
1416
|
+
detects(request) {
|
|
1417
|
+
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
1418
|
+
return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
|
|
1419
|
+
},
|
|
1420
|
+
async verify(args) {
|
|
1421
|
+
const info = readMppCredential(args.request);
|
|
1422
|
+
if (!info) return { ok: false, kind: "invalid" };
|
|
1423
|
+
if (info.payloadType === "transaction" && args.deps.tempoClient) {
|
|
1424
|
+
return verifyTxMode(args, info);
|
|
1425
|
+
}
|
|
1426
|
+
return verifyHashMode(args, info);
|
|
1427
|
+
},
|
|
1428
|
+
async settle(args) {
|
|
1429
|
+
const token = args.token;
|
|
1430
|
+
if (token.mode === "transaction") return settleTxMode(args);
|
|
1431
|
+
return settleHashMode(args);
|
|
1432
|
+
},
|
|
1433
|
+
async buildChallenge(args) {
|
|
1434
|
+
if (!args.deps.mppx) return {};
|
|
1153
1435
|
try {
|
|
1154
|
-
await
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
scope.parsedBody
|
|
1436
|
+
const result = await args.deps.mppx.charge({ amount: args.price })(args.request);
|
|
1437
|
+
if (result.status === 402) {
|
|
1438
|
+
const wwwAuth = result.challenge.headers.get(HEADERS.WWW_AUTHENTICATE);
|
|
1439
|
+
if (wwwAuth) return { headers: { [HEADERS.WWW_AUTHENTICATE]: wwwAuth } };
|
|
1440
|
+
}
|
|
1441
|
+
} catch (err) {
|
|
1442
|
+
console.warn(
|
|
1443
|
+
`[router] MPP challenge build failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1163
1444
|
);
|
|
1445
|
+
throw err;
|
|
1164
1446
|
}
|
|
1447
|
+
return {};
|
|
1165
1448
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
// src/protocols/x402/strategy.ts
|
|
1452
|
+
init_accepts();
|
|
1453
|
+
|
|
1454
|
+
// src/protocols/x402/challenge.ts
|
|
1455
|
+
init_facilitators();
|
|
1456
|
+
init_solana();
|
|
1457
|
+
|
|
1458
|
+
// src/protocols/x402/requirements.ts
|
|
1459
|
+
init_evm();
|
|
1460
|
+
init_solana();
|
|
1461
|
+
async function buildExpectedRequirements(server, request, price, accepts) {
|
|
1462
|
+
const exactRequirements = await buildExactRequirements(server, request, price, accepts);
|
|
1463
|
+
const customRequirements = buildCustomRequirements(price, accepts);
|
|
1464
|
+
return [...exactRequirements, ...customRequirements];
|
|
1465
|
+
}
|
|
1466
|
+
async function buildExactRequirements(server, request, price, accepts) {
|
|
1467
|
+
const exactGroups = [
|
|
1468
|
+
buildEvmExactOptions(accepts, price),
|
|
1469
|
+
buildSolanaExactOptions(accepts, price)
|
|
1470
|
+
].filter((options) => options.length > 0);
|
|
1471
|
+
if (exactGroups.length === 0) return [];
|
|
1472
|
+
const requirements = [];
|
|
1473
|
+
const failures = [];
|
|
1474
|
+
for (const options of exactGroups) {
|
|
1188
1475
|
try {
|
|
1189
|
-
|
|
1476
|
+
requirements.push(
|
|
1477
|
+
...await server.buildPaymentRequirementsFromOptions(options, { request })
|
|
1478
|
+
);
|
|
1190
1479
|
} catch (error) {
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
message: `Post-settlement hook failed: ${message}`,
|
|
1196
|
-
route: routeEntry.key
|
|
1197
|
-
});
|
|
1198
|
-
await runSettlementError(scope, error, "afterSettle");
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
async function runSettledHandlerError(scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
|
|
1202
|
-
const hook = routeEntry.settlement?.onSettledHandlerError;
|
|
1203
|
-
if (!hook) return;
|
|
1204
|
-
try {
|
|
1205
|
-
await hook({
|
|
1206
|
-
...settlementContext(scope),
|
|
1207
|
-
error
|
|
1208
|
-
});
|
|
1209
|
-
} catch (hookError) {
|
|
1210
|
-
const message = errorMessage(hookError, "Settled handler error hook failed");
|
|
1211
|
-
console.error(`[router] ${routeEntry.key}: onSettledHandlerError failed: ${message}`);
|
|
1212
|
-
firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
|
|
1213
|
-
level: "error",
|
|
1214
|
-
message: `Settled handler error hook failed: ${message}`,
|
|
1215
|
-
route: routeEntry.key
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
return async (request) => {
|
|
1220
|
-
await deps.initPromise;
|
|
1221
|
-
const meta = buildMeta(request, routeEntry);
|
|
1222
|
-
const pluginCtx = firePluginHook(deps.plugin, "onRequest", meta) ?? createDefaultContext(meta);
|
|
1223
|
-
async function handleAuth(wallet, account2) {
|
|
1224
|
-
const body2 = await parseBody(request, routeEntry);
|
|
1225
|
-
if (!body2.ok) {
|
|
1226
|
-
firePluginResponse(deps, pluginCtx, meta, body2.response);
|
|
1227
|
-
return body2.response;
|
|
1228
|
-
}
|
|
1229
|
-
if (routeEntry.validateFn) {
|
|
1230
|
-
try {
|
|
1231
|
-
await routeEntry.validateFn(body2.data);
|
|
1232
|
-
} catch (err) {
|
|
1233
|
-
const status = err.status ?? 400;
|
|
1234
|
-
const message = err instanceof Error ? err.message : "Validation failed";
|
|
1235
|
-
return fail(status, message, meta, pluginCtx, body2.data);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
const { response, rawResult } = await invoke(
|
|
1239
|
-
request,
|
|
1240
|
-
meta,
|
|
1241
|
-
pluginCtx,
|
|
1242
|
-
wallet,
|
|
1243
|
-
account2,
|
|
1244
|
-
body2.data,
|
|
1245
|
-
null
|
|
1246
|
-
);
|
|
1247
|
-
finalize(response, rawResult, meta, pluginCtx, body2.data);
|
|
1248
|
-
return response;
|
|
1249
|
-
}
|
|
1250
|
-
if (routeEntry.authMode === "unprotected") {
|
|
1251
|
-
return handleAuth(null, void 0);
|
|
1252
|
-
}
|
|
1253
|
-
let account;
|
|
1254
|
-
if (routeEntry.authMode === "apiKey" || routeEntry.apiKeyResolver) {
|
|
1255
|
-
if (!routeEntry.apiKeyResolver) {
|
|
1256
|
-
return fail(401, "API key resolver not configured", meta, pluginCtx);
|
|
1257
|
-
}
|
|
1258
|
-
const keyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
|
|
1259
|
-
if (!keyResult.valid) {
|
|
1260
|
-
return fail(401, "Invalid or missing API key", meta, pluginCtx);
|
|
1261
|
-
}
|
|
1262
|
-
account = keyResult.account;
|
|
1263
|
-
firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
|
|
1264
|
-
authMode: "apiKey",
|
|
1265
|
-
wallet: null,
|
|
1266
|
-
route: routeEntry.key,
|
|
1267
|
-
account
|
|
1268
|
-
});
|
|
1269
|
-
if (routeEntry.authMode === "apiKey" && !routeEntry.pricing) {
|
|
1270
|
-
return handleAuth(null, account);
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
const protocol = detectProtocol(request);
|
|
1274
|
-
let earlyBodyData;
|
|
1275
|
-
const pricingNeedsBody = routeEntry.pricing != null && typeof routeEntry.pricing !== "string";
|
|
1276
|
-
const needsEarlyParse = !protocol && routeEntry.bodySchema && (pricingNeedsBody || routeEntry.validateFn);
|
|
1277
|
-
if (needsEarlyParse) {
|
|
1278
|
-
const requestForPricing = request.clone();
|
|
1279
|
-
const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
|
|
1280
|
-
if (earlyBodyResult.ok) {
|
|
1281
|
-
earlyBodyData = earlyBodyResult.data;
|
|
1282
|
-
if (routeEntry.validateFn) {
|
|
1283
|
-
try {
|
|
1284
|
-
await routeEntry.validateFn(earlyBodyData);
|
|
1285
|
-
} catch (err) {
|
|
1286
|
-
const status = err.status ?? 400;
|
|
1287
|
-
const message = err instanceof Error ? err.message : "Validation failed";
|
|
1288
|
-
return fail(status, message, meta, pluginCtx, earlyBodyData);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
} else {
|
|
1292
|
-
firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
|
|
1293
|
-
return earlyBodyResult.response;
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
if (routeEntry.authMode === "siwx" || routeEntry.siwxEnabled) {
|
|
1297
|
-
if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get("SIGN-IN-WITH-X")) {
|
|
1298
|
-
const requestForValidation = request.clone();
|
|
1299
|
-
const earlyBodyResult = await parseBody(requestForValidation, routeEntry);
|
|
1300
|
-
if (earlyBodyResult.ok) {
|
|
1301
|
-
try {
|
|
1302
|
-
await routeEntry.validateFn(earlyBodyResult.data);
|
|
1303
|
-
} catch (err) {
|
|
1304
|
-
const status = err.status ?? 400;
|
|
1305
|
-
const message = err instanceof Error ? err.message : "Validation failed";
|
|
1306
|
-
return fail(status, message, meta, pluginCtx, earlyBodyResult.data);
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
const siwxHeader = request.headers.get("SIGN-IN-WITH-X");
|
|
1311
|
-
if (!siwxHeader && protocol === "mpp" && routeEntry.authMode === "siwx" && deps.mppx) {
|
|
1312
|
-
let mppSiwxResult;
|
|
1313
|
-
try {
|
|
1314
|
-
mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
|
|
1315
|
-
} catch (err) {
|
|
1316
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1317
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1318
|
-
level: "critical",
|
|
1319
|
-
message: `MPP SIWX verification failed: ${message}`,
|
|
1320
|
-
route: routeEntry.key
|
|
1321
|
-
});
|
|
1322
|
-
return fail(500, `MPP SIWX verification failed: ${message}`, meta, pluginCtx);
|
|
1323
|
-
}
|
|
1324
|
-
if (mppSiwxResult.valid) {
|
|
1325
|
-
pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
|
|
1326
|
-
firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
|
|
1327
|
-
authMode: "siwx",
|
|
1328
|
-
wallet: mppSiwxResult.wallet,
|
|
1329
|
-
route: routeEntry.key
|
|
1330
|
-
});
|
|
1331
|
-
const authResponse = await handleAuth(mppSiwxResult.wallet, void 0);
|
|
1332
|
-
if (authResponse.status < 400) {
|
|
1333
|
-
return mppSiwxResult.withReceipt(authResponse);
|
|
1334
|
-
}
|
|
1335
|
-
return authResponse;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
if (!siwxHeader && routeEntry.authMode === "siwx") {
|
|
1339
|
-
const url = new URL(request.url);
|
|
1340
|
-
const nonce = crypto.randomUUID().replace(/-/g, "");
|
|
1341
|
-
const supportedChains = getSupportedChains(deps.x402Accepts, deps.network);
|
|
1342
|
-
const primaryChain = supportedChains[0];
|
|
1343
|
-
const siwxInfo = {
|
|
1344
|
-
domain: url.hostname,
|
|
1345
|
-
uri: request.url,
|
|
1346
|
-
version: "1",
|
|
1347
|
-
chainId: primaryChain.chainId,
|
|
1348
|
-
type: primaryChain.type,
|
|
1349
|
-
nonce,
|
|
1350
|
-
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1351
|
-
expirationTime: new Date(Date.now() + SIWX_CHALLENGE_EXPIRY_MS).toISOString(),
|
|
1352
|
-
statement: "Sign in to verify your wallet identity"
|
|
1353
|
-
};
|
|
1354
|
-
let siwxSchema;
|
|
1355
|
-
try {
|
|
1356
|
-
siwxSchema = await buildSIWXExtension();
|
|
1357
|
-
} catch {
|
|
1358
|
-
}
|
|
1359
|
-
const paymentRequired = {
|
|
1360
|
-
x402Version: 2,
|
|
1361
|
-
error: "SIWX authentication required",
|
|
1362
|
-
resource: {
|
|
1363
|
-
url: request.url,
|
|
1364
|
-
description: routeEntry.description ?? "SIWX-protected endpoint",
|
|
1365
|
-
mimeType: "application/json"
|
|
1366
|
-
},
|
|
1367
|
-
accepts: [],
|
|
1368
|
-
extensions: {
|
|
1369
|
-
"sign-in-with-x": {
|
|
1370
|
-
info: siwxInfo,
|
|
1371
|
-
// supportedChains at top level required by MCP tools for chain detection
|
|
1372
|
-
supportedChains,
|
|
1373
|
-
...siwxSchema ? { schema: siwxSchema } : {}
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
};
|
|
1377
|
-
let encoded;
|
|
1378
|
-
try {
|
|
1379
|
-
const { encodePaymentRequiredHeader } = await import("@x402/core/http");
|
|
1380
|
-
encoded = encodePaymentRequiredHeader(paymentRequired);
|
|
1381
|
-
} catch (err) {
|
|
1382
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1383
|
-
level: "warn",
|
|
1384
|
-
message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1385
|
-
route: routeEntry.key
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
const response = new import_server2.NextResponse(JSON.stringify(paymentRequired), {
|
|
1389
|
-
status: 402,
|
|
1390
|
-
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
1391
|
-
});
|
|
1392
|
-
if (encoded) response.headers.set("PAYMENT-REQUIRED", encoded);
|
|
1393
|
-
if (deps.mppx) {
|
|
1394
|
-
try {
|
|
1395
|
-
const mppChallenge = await deps.mppx.charge({ amount: "0" })(request);
|
|
1396
|
-
if (mppChallenge.status === 402) {
|
|
1397
|
-
const wwwAuth = mppChallenge.challenge.headers.get("WWW-Authenticate");
|
|
1398
|
-
if (wwwAuth) response.headers.set("WWW-Authenticate", wwwAuth);
|
|
1399
|
-
}
|
|
1400
|
-
} catch {
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
firePluginResponse(deps, pluginCtx, meta, response);
|
|
1404
|
-
return response;
|
|
1405
|
-
}
|
|
1406
|
-
if (siwxHeader) {
|
|
1407
|
-
const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
|
|
1408
|
-
if (!siwx.valid) {
|
|
1409
|
-
if (routeEntry.authMode === "siwx") {
|
|
1410
|
-
const response = import_server2.NextResponse.json(
|
|
1411
|
-
{ error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
|
|
1412
|
-
{ status: 402 }
|
|
1413
|
-
);
|
|
1414
|
-
firePluginResponse(deps, pluginCtx, meta, response);
|
|
1415
|
-
return response;
|
|
1416
|
-
}
|
|
1417
|
-
} else {
|
|
1418
|
-
const wallet = normalizeWalletAddress(siwx.wallet);
|
|
1419
|
-
pluginCtx.setVerifiedWallet(wallet);
|
|
1420
|
-
if (routeEntry.authMode === "siwx") {
|
|
1421
|
-
firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
|
|
1422
|
-
authMode: "siwx",
|
|
1423
|
-
wallet,
|
|
1424
|
-
route: routeEntry.key
|
|
1425
|
-
});
|
|
1426
|
-
return handleAuth(wallet, void 0);
|
|
1427
|
-
}
|
|
1428
|
-
if (routeEntry.siwxEnabled && routeEntry.pricing) {
|
|
1429
|
-
const entitled = await deps.entitlementStore.has(routeEntry.key, wallet);
|
|
1430
|
-
if (entitled) {
|
|
1431
|
-
firePluginHook(deps.plugin, "onAuthVerified", pluginCtx, {
|
|
1432
|
-
authMode: "siwx",
|
|
1433
|
-
wallet,
|
|
1434
|
-
route: routeEntry.key
|
|
1435
|
-
});
|
|
1436
|
-
return handleAuth(wallet, account);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
if (!protocol || protocol === "siwx") {
|
|
1443
|
-
if (routeEntry.pricing) {
|
|
1444
|
-
const initErrors = routeEntry.protocols.map((p) => {
|
|
1445
|
-
if (p === "x402" && deps.x402InitError) return `x402: ${deps.x402InitError}`;
|
|
1446
|
-
if (p === "mpp" && deps.mppInitError) return `mpp: ${deps.mppInitError}`;
|
|
1447
|
-
return null;
|
|
1448
|
-
}).filter(Boolean);
|
|
1449
|
-
if (initErrors.length > 0) {
|
|
1450
|
-
return fail(
|
|
1451
|
-
500,
|
|
1452
|
-
`Payment protocol initialization failed. ${initErrors.join("; ")}`,
|
|
1453
|
-
meta,
|
|
1454
|
-
pluginCtx
|
|
1455
|
-
);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
return await build402(request, routeEntry, deps, meta, pluginCtx, earlyBodyData);
|
|
1459
|
-
}
|
|
1460
|
-
const body = await parseBody(request, routeEntry);
|
|
1461
|
-
if (!body.ok) {
|
|
1462
|
-
firePluginResponse(deps, pluginCtx, meta, body.response);
|
|
1463
|
-
return body.response;
|
|
1464
|
-
}
|
|
1465
|
-
if (routeEntry.validateFn) {
|
|
1466
|
-
try {
|
|
1467
|
-
await routeEntry.validateFn(body.data);
|
|
1468
|
-
} catch (err) {
|
|
1469
|
-
const status = err.status ?? 400;
|
|
1470
|
-
const message = err instanceof Error ? err.message : "Validation failed";
|
|
1471
|
-
return fail(status, message, meta, pluginCtx, body.data);
|
|
1480
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1481
|
+
failures.push(err);
|
|
1482
|
+
if (exactGroups.length === 1) {
|
|
1483
|
+
throw err;
|
|
1472
1484
|
}
|
|
1473
|
-
}
|
|
1474
|
-
const priceResult = await resolveDynamicPrice(body.data, routeEntry, deps, pluginCtx, meta);
|
|
1475
|
-
if ("error" in priceResult) return priceResult.error;
|
|
1476
|
-
const price = priceResult.price;
|
|
1477
|
-
if (!routeEntry.protocols.includes(protocol)) {
|
|
1478
|
-
const accepted = routeEntry.protocols.join(", ") || "none";
|
|
1479
1485
|
console.warn(
|
|
1480
|
-
`[router]
|
|
1481
|
-
);
|
|
1482
|
-
return fail(
|
|
1483
|
-
400,
|
|
1484
|
-
`This route does not accept ${protocol} payments. Accepted protocols: ${accepted}`,
|
|
1485
|
-
meta,
|
|
1486
|
-
pluginCtx,
|
|
1487
|
-
body.data
|
|
1486
|
+
`[router] Failed to build x402 exact requirements for ${options[0]?.network}: ${err.message}`
|
|
1488
1487
|
);
|
|
1489
1488
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
response.headers.set("PAYMENT-RESPONSE", settle.encoded);
|
|
1578
|
-
response.headers.set("Cache-Control", "private");
|
|
1579
|
-
const transaction = String(settle.result?.transaction ?? "");
|
|
1580
|
-
const settledPayment = {
|
|
1581
|
-
...payment,
|
|
1582
|
-
status: "settled",
|
|
1583
|
-
...transaction ? { transaction } : {}
|
|
1584
|
-
};
|
|
1585
|
-
firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
|
|
1586
|
-
protocol: "x402",
|
|
1587
|
-
payer: wallet,
|
|
1588
|
-
transaction,
|
|
1589
|
-
network: matchedNetwork
|
|
1590
|
-
});
|
|
1591
|
-
await runAfterSettle({ ...settleScope, payment: settledPayment });
|
|
1592
|
-
} catch (err) {
|
|
1593
|
-
await runSettlementError(settleScope, err, "settle");
|
|
1594
|
-
const errObj = err;
|
|
1595
|
-
console.error("Settlement failed", {
|
|
1596
|
-
message: err instanceof Error ? err.message : String(err),
|
|
1597
|
-
route: routeEntry.key,
|
|
1598
|
-
network: matchedNetwork,
|
|
1599
|
-
errorReason: errObj.errorReason,
|
|
1600
|
-
facilitatorStatus: errObj.response?.status,
|
|
1601
|
-
facilitatorBody: errObj.response?.data ?? errObj.response?.body
|
|
1602
|
-
});
|
|
1603
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1604
|
-
level: "critical",
|
|
1605
|
-
message: `Settlement failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1606
|
-
route: routeEntry.key
|
|
1607
|
-
});
|
|
1608
|
-
return fail(500, "Settlement failed", meta, pluginCtx, body.data);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
finalize(response, rawResult, meta, pluginCtx, body.data);
|
|
1612
|
-
return response;
|
|
1613
|
-
}
|
|
1614
|
-
if (protocol === "mpp") {
|
|
1615
|
-
if (!deps.mppx) {
|
|
1616
|
-
const reason = deps.mppInitError ? `MPP initialization failed: ${deps.mppInitError}` : "MPP not initialized \u2014 ensure mppx is installed and mpp config (secretKey, currency, recipient) is correct";
|
|
1617
|
-
console.error(`[router] ${routeEntry.key}: ${reason}`);
|
|
1618
|
-
return fail(500, reason, meta, pluginCtx, body.data);
|
|
1619
|
-
}
|
|
1620
|
-
const mppCredential = import_mppx2.Credential.fromRequest(request);
|
|
1621
|
-
const rawSource = mppCredential?.source ?? "";
|
|
1622
|
-
const didParts = rawSource.split(":");
|
|
1623
|
-
const lastPart = didParts[didParts.length - 1];
|
|
1624
|
-
const wallet = normalizeWalletAddress((0, import_viem2.isAddress)(lastPart) ? (0, import_viem2.getAddress)(lastPart) : rawSource);
|
|
1625
|
-
const payloadType = mppCredential?.payload?.type;
|
|
1626
|
-
if (payloadType === "transaction" && deps.tempoClient) {
|
|
1627
|
-
try {
|
|
1628
|
-
const serializedTx = mppCredential.payload.signature;
|
|
1629
|
-
const transaction = import_tempo.Transaction.deserialize(serializedTx);
|
|
1630
|
-
await (0, import_actions.call)(deps.tempoClient, {
|
|
1631
|
-
...transaction,
|
|
1632
|
-
account: transaction.from,
|
|
1633
|
-
calls: transaction.calls ?? []
|
|
1634
|
-
});
|
|
1635
|
-
} catch (err) {
|
|
1636
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1637
|
-
console.warn(`[router] ${routeEntry.key}: MPP simulation failed \u2014 ${message}`);
|
|
1638
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1639
|
-
level: "warn",
|
|
1640
|
-
message: `MPP simulation failed: ${message}`,
|
|
1641
|
-
route: routeEntry.key
|
|
1642
|
-
});
|
|
1643
|
-
return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
|
|
1644
|
-
}
|
|
1645
|
-
pluginCtx.setVerifiedWallet(wallet);
|
|
1646
|
-
firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
|
|
1647
|
-
protocol: "mpp",
|
|
1648
|
-
payer: wallet,
|
|
1649
|
-
amount: price,
|
|
1650
|
-
network: "tempo:4217"
|
|
1651
|
-
});
|
|
1652
|
-
const mppRecipient2 = deps.mppRecipient ?? deps.payeeAddress;
|
|
1653
|
-
const payment2 = {
|
|
1654
|
-
protocol: "mpp",
|
|
1655
|
-
status: "verified",
|
|
1656
|
-
payer: wallet,
|
|
1657
|
-
amount: price,
|
|
1658
|
-
network: "tempo:4217",
|
|
1659
|
-
...mppRecipient2 ? { recipient: mppRecipient2 } : {}
|
|
1660
|
-
};
|
|
1661
|
-
const { response: response2, rawResult: rawResult2, handlerError: handlerError2 } = await invoke(
|
|
1662
|
-
request,
|
|
1663
|
-
meta,
|
|
1664
|
-
pluginCtx,
|
|
1665
|
-
wallet,
|
|
1666
|
-
account,
|
|
1667
|
-
body.data,
|
|
1668
|
-
payment2
|
|
1669
|
-
);
|
|
1670
|
-
const settleScope2 = {
|
|
1671
|
-
request,
|
|
1672
|
-
meta,
|
|
1673
|
-
pluginCtx,
|
|
1674
|
-
wallet,
|
|
1675
|
-
account,
|
|
1676
|
-
parsedBody: body.data,
|
|
1677
|
-
payment: payment2,
|
|
1678
|
-
response: response2,
|
|
1679
|
-
rawResult: rawResult2,
|
|
1680
|
-
handlerError: handlerError2
|
|
1681
|
-
};
|
|
1682
|
-
if (response2.status < 400) {
|
|
1683
|
-
const validationFailure = await runBeforeSettle(settleScope2);
|
|
1684
|
-
if (validationFailure) return validationFailure;
|
|
1685
|
-
let mppResult2;
|
|
1686
|
-
try {
|
|
1687
|
-
mppResult2 = await deps.mppx.charge({ amount: price })(request);
|
|
1688
|
-
} catch (err) {
|
|
1689
|
-
await runSettlementError(settleScope2, err, "settle");
|
|
1690
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1691
|
-
console.error(
|
|
1692
|
-
`[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`
|
|
1693
|
-
);
|
|
1694
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1695
|
-
level: "critical",
|
|
1696
|
-
message: `MPP broadcast failed after handler: ${message}`,
|
|
1697
|
-
route: routeEntry.key
|
|
1698
|
-
});
|
|
1699
|
-
return fail(
|
|
1700
|
-
500,
|
|
1701
|
-
`MPP payment processing failed: ${message}`,
|
|
1702
|
-
meta,
|
|
1703
|
-
pluginCtx,
|
|
1704
|
-
body.data
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
if (mppResult2.status === 402) {
|
|
1708
|
-
let rejectReason = "";
|
|
1709
|
-
try {
|
|
1710
|
-
const problemBody = await mppResult2.challenge.clone().text();
|
|
1711
|
-
if (problemBody) {
|
|
1712
|
-
const problem = JSON.parse(problemBody);
|
|
1713
|
-
rejectReason = problem.detail || problem.title || "";
|
|
1714
|
-
}
|
|
1715
|
-
} catch {
|
|
1716
|
-
}
|
|
1717
|
-
const detail = rejectReason || "transaction reverted on-chain after handler execution";
|
|
1718
|
-
const settlementError = Object.assign(new Error(detail), {
|
|
1719
|
-
status: 402,
|
|
1720
|
-
detail,
|
|
1721
|
-
mppResult: mppResult2,
|
|
1722
|
-
challenge: mppResult2.challenge
|
|
1723
|
-
});
|
|
1724
|
-
await runSettlementError(settleScope2, settlementError, "settle");
|
|
1725
|
-
console.error(
|
|
1726
|
-
`[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`
|
|
1727
|
-
);
|
|
1728
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1729
|
-
level: "critical",
|
|
1730
|
-
message: `MPP payment failed after handler: ${detail}`,
|
|
1731
|
-
route: routeEntry.key
|
|
1732
|
-
});
|
|
1733
|
-
return fail(500, `MPP payment failed: ${detail}`, meta, pluginCtx, body.data);
|
|
1734
|
-
}
|
|
1735
|
-
const receiptResponse = mppResult2.withReceipt(response2);
|
|
1736
|
-
receiptResponse.headers.set("Cache-Control", "private");
|
|
1737
|
-
let txHash2 = "";
|
|
1738
|
-
const receiptHeader2 = receiptResponse.headers.get("Payment-Receipt");
|
|
1739
|
-
if (receiptHeader2) {
|
|
1740
|
-
try {
|
|
1741
|
-
txHash2 = import_mppx2.Receipt.deserialize(receiptHeader2).reference;
|
|
1742
|
-
} catch {
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
if (routeEntry.siwxEnabled) {
|
|
1746
|
-
try {
|
|
1747
|
-
await deps.entitlementStore.grant(routeEntry.key, wallet);
|
|
1748
|
-
} catch (error) {
|
|
1749
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1750
|
-
level: "warn",
|
|
1751
|
-
message: `Entitlement grant failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1752
|
-
route: routeEntry.key
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
|
|
1757
|
-
protocol: "mpp",
|
|
1758
|
-
payer: wallet,
|
|
1759
|
-
transaction: txHash2,
|
|
1760
|
-
network: "tempo:4217"
|
|
1761
|
-
});
|
|
1762
|
-
const settledPayment = {
|
|
1763
|
-
...payment2,
|
|
1764
|
-
status: "settled",
|
|
1765
|
-
...txHash2 ? { transaction: txHash2 } : {},
|
|
1766
|
-
...receiptHeader2 ? { receipt: receiptHeader2 } : {}
|
|
1767
|
-
};
|
|
1768
|
-
await runAfterSettle({
|
|
1769
|
-
...settleScope2,
|
|
1770
|
-
payment: settledPayment,
|
|
1771
|
-
response: receiptResponse
|
|
1772
|
-
});
|
|
1773
|
-
finalize(receiptResponse, rawResult2, meta, pluginCtx, body.data);
|
|
1774
|
-
return receiptResponse;
|
|
1775
|
-
}
|
|
1776
|
-
finalize(response2, rawResult2, meta, pluginCtx, body.data);
|
|
1777
|
-
return response2;
|
|
1778
|
-
}
|
|
1779
|
-
let mppResult;
|
|
1489
|
+
}
|
|
1490
|
+
if (requirements.length > 0) {
|
|
1491
|
+
return requirements;
|
|
1492
|
+
}
|
|
1493
|
+
throw failures[0] ?? new Error("Failed to build x402 exact requirements");
|
|
1494
|
+
}
|
|
1495
|
+
function buildCustomRequirements(price, accepts) {
|
|
1496
|
+
return accepts.filter((accept) => accept.scheme !== "exact").map((accept) => buildCustomRequirement(price, accept));
|
|
1497
|
+
}
|
|
1498
|
+
function buildCustomRequirement(price, accept) {
|
|
1499
|
+
if (!accept.asset) {
|
|
1500
|
+
throw new Error(
|
|
1501
|
+
`Custom x402 accept '${accept.scheme}' on '${accept.network}' is missing asset`
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
return {
|
|
1505
|
+
scheme: accept.scheme,
|
|
1506
|
+
network: accept.network,
|
|
1507
|
+
amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
|
|
1508
|
+
asset: accept.asset,
|
|
1509
|
+
payTo: accept.payTo,
|
|
1510
|
+
maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
|
|
1511
|
+
extra: accept.extra ?? {}
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
function decimalToAtomicUnits(amount, decimals) {
|
|
1515
|
+
const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
|
|
1516
|
+
if (!match?.groups) {
|
|
1517
|
+
throw new Error(`Invalid decimal amount '${amount}'`);
|
|
1518
|
+
}
|
|
1519
|
+
const whole = match.groups.whole;
|
|
1520
|
+
const fraction = match.groups.fraction ?? "";
|
|
1521
|
+
if (fraction.length > decimals) {
|
|
1522
|
+
throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
|
|
1523
|
+
}
|
|
1524
|
+
const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
|
|
1525
|
+
return normalized === "" ? "0" : normalized;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// src/protocols/x402/challenge.ts
|
|
1529
|
+
async function buildX402Challenge(opts) {
|
|
1530
|
+
const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
|
|
1531
|
+
const { encodePaymentRequiredHeader } = await import("@x402/core/http");
|
|
1532
|
+
const resource = buildChallengeResource(request, routeEntry);
|
|
1533
|
+
const requirements = await buildChallengeRequirements(
|
|
1534
|
+
server,
|
|
1535
|
+
request,
|
|
1536
|
+
price,
|
|
1537
|
+
accepts,
|
|
1538
|
+
resource,
|
|
1539
|
+
facilitatorsByNetwork
|
|
1540
|
+
);
|
|
1541
|
+
const paymentRequired = await server.createPaymentRequiredResponse(
|
|
1542
|
+
requirements,
|
|
1543
|
+
resource,
|
|
1544
|
+
void 0,
|
|
1545
|
+
extensions
|
|
1546
|
+
);
|
|
1547
|
+
const encoded = encodePaymentRequiredHeader(paymentRequired);
|
|
1548
|
+
return { encoded, requirements };
|
|
1549
|
+
}
|
|
1550
|
+
async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork) {
|
|
1551
|
+
const requirements = await buildExpectedRequirements(server, request, price, accepts);
|
|
1552
|
+
if (!needsFacilitatorEnrichment(accepts)) return requirements;
|
|
1553
|
+
return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork);
|
|
1554
|
+
}
|
|
1555
|
+
function needsFacilitatorEnrichment(accepts) {
|
|
1556
|
+
return accepts.some((accept) => accept.scheme !== "exact") || hasSolanaAccepts(accepts);
|
|
1557
|
+
}
|
|
1558
|
+
async function enrichGroup(group, resource) {
|
|
1559
|
+
const accepted = await enrichRequirementsWithFacilitatorAccepts(
|
|
1560
|
+
group.facilitator,
|
|
1561
|
+
resource,
|
|
1562
|
+
group.items.map(({ requirement }) => requirement)
|
|
1563
|
+
);
|
|
1564
|
+
if (accepted.length !== group.items.length) {
|
|
1565
|
+
throw new Error(
|
|
1566
|
+
`Facilitator /accepts returned ${accepted.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
return accepted;
|
|
1570
|
+
}
|
|
1571
|
+
async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork) {
|
|
1572
|
+
const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
|
|
1573
|
+
if (groups.length === 0) return requirements;
|
|
1574
|
+
const results = await Promise.all(
|
|
1575
|
+
groups.map(async (group) => {
|
|
1780
1576
|
try {
|
|
1781
|
-
|
|
1577
|
+
return { success: true, group, accepted: await enrichGroup(group, resource) };
|
|
1782
1578
|
} catch (err) {
|
|
1783
|
-
const
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
});
|
|
1790
|
-
return fail(500, `MPP payment processing failed: ${message}`, meta, pluginCtx, body.data);
|
|
1579
|
+
const label = group.facilitator.url ?? group.facilitator.network;
|
|
1580
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1581
|
+
console.warn(
|
|
1582
|
+
`[router] ${label} /accepts failed, dropping ${group.items.length} requirement(s): ${reason}`
|
|
1583
|
+
);
|
|
1584
|
+
return { success: false, group };
|
|
1791
1585
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1586
|
+
})
|
|
1587
|
+
);
|
|
1588
|
+
const enriched = [...requirements];
|
|
1589
|
+
results.filter((r) => r.success).forEach(({ group, accepted }) => {
|
|
1590
|
+
accepted.forEach((req, offset) => {
|
|
1591
|
+
const index = group.items[offset]?.index;
|
|
1592
|
+
if (index !== void 0) enriched[index] = req;
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1595
|
+
const failedIndices = new Set(
|
|
1596
|
+
results.filter((r) => !r.success).flatMap(({ group }) => group.items.map(({ index }) => index))
|
|
1597
|
+
);
|
|
1598
|
+
const remaining = enriched.filter((_, i) => !failedIndices.has(i));
|
|
1599
|
+
if (remaining.length === 0) {
|
|
1600
|
+
throw new Error(
|
|
1601
|
+
"All facilitator enrichments failed; no payment requirements remain for challenge"
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
return remaining;
|
|
1605
|
+
}
|
|
1606
|
+
function collectEnrichmentGroups(requirements, facilitatorsByNetwork) {
|
|
1607
|
+
const groups = [];
|
|
1608
|
+
requirements.forEach((requirement, index) => {
|
|
1609
|
+
if (!requiresFacilitatorEnrichment(requirement)) return;
|
|
1610
|
+
const facilitator = getRequiredFacilitator(requirement, facilitatorsByNetwork);
|
|
1611
|
+
const existing = groups.find(
|
|
1612
|
+
(group) => sameResolvedX402Facilitator(group.facilitator, facilitator)
|
|
1613
|
+
);
|
|
1614
|
+
if (existing) {
|
|
1615
|
+
existing.items.push({ index, requirement });
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
groups.push({
|
|
1619
|
+
facilitator,
|
|
1620
|
+
items: [{ index, requirement }]
|
|
1621
|
+
});
|
|
1622
|
+
});
|
|
1623
|
+
return groups;
|
|
1624
|
+
}
|
|
1625
|
+
function getRequiredFacilitator(requirement, facilitatorsByNetwork) {
|
|
1626
|
+
const facilitator = getFacilitatorForRequirement(facilitatorsByNetwork, requirement);
|
|
1627
|
+
if (!facilitator) {
|
|
1628
|
+
throw new Error(
|
|
1629
|
+
`Missing x402 facilitator for ${requirement.scheme} requirement on ${requirement.network}`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
return facilitator;
|
|
1633
|
+
}
|
|
1634
|
+
function requiresFacilitatorEnrichment(requirement) {
|
|
1635
|
+
return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
|
|
1636
|
+
}
|
|
1637
|
+
function buildChallengeResource(request, routeEntry) {
|
|
1638
|
+
return {
|
|
1639
|
+
url: request.url,
|
|
1640
|
+
method: routeEntry.method,
|
|
1641
|
+
description: routeEntry.description,
|
|
1642
|
+
mimeType: "application/json"
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// src/protocols/x402/settle.ts
|
|
1647
|
+
async function settleX402Payment(server, payload, requirements) {
|
|
1648
|
+
const { encodePaymentResponseHeader } = await import("@x402/core/http");
|
|
1649
|
+
const result = await server.settlePayment(payload, requirements);
|
|
1650
|
+
const encoded = encodePaymentResponseHeader(result);
|
|
1651
|
+
return { encoded, result };
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/protocols/x402/verify.ts
|
|
1655
|
+
async function verifyX402Payment(opts) {
|
|
1656
|
+
const { server, request, price, accepts } = opts;
|
|
1657
|
+
const payload = await readPaymentPayload(request);
|
|
1658
|
+
if (!payload) return null;
|
|
1659
|
+
const requirements = await buildExpectedRequirements(server, request, price, accepts);
|
|
1660
|
+
const matching = findVerifiableRequirements(server, requirements, payload);
|
|
1661
|
+
if (!matching) {
|
|
1662
|
+
return invalidPaymentVerification();
|
|
1663
|
+
}
|
|
1664
|
+
let verify;
|
|
1665
|
+
try {
|
|
1666
|
+
verify = await server.verifyPayment(payload, matching);
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
const sc = err.statusCode;
|
|
1669
|
+
if (sc && sc >= 400 && sc < 500) return invalidPaymentVerification();
|
|
1670
|
+
throw err;
|
|
1671
|
+
}
|
|
1672
|
+
if (!verify.isValid) return invalidPaymentVerification();
|
|
1673
|
+
if (typeof verify.payer !== "string" || verify.payer.length === 0) {
|
|
1674
|
+
throw new Error("x402 verification succeeded without a payer address");
|
|
1675
|
+
}
|
|
1676
|
+
return {
|
|
1677
|
+
valid: true,
|
|
1678
|
+
payer: verify.payer,
|
|
1679
|
+
payload,
|
|
1680
|
+
requirements: matching
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
function findVerifiableRequirements(server, requirements, payload) {
|
|
1684
|
+
const strictMatch = server.findMatchingRequirements(requirements, payload);
|
|
1685
|
+
if (strictMatch) {
|
|
1686
|
+
return payload.x402Version === 2 ? payload.accepted : strictMatch;
|
|
1687
|
+
}
|
|
1688
|
+
if (payload.x402Version !== 2) {
|
|
1689
|
+
return null;
|
|
1690
|
+
}
|
|
1691
|
+
const stableMatch = requirements.find(
|
|
1692
|
+
(requirement) => matchesStableFields(requirement, payload.accepted)
|
|
1693
|
+
);
|
|
1694
|
+
return stableMatch ? payload.accepted : null;
|
|
1695
|
+
}
|
|
1696
|
+
function matchesStableFields(requirement, accepted) {
|
|
1697
|
+
return requirement.scheme === accepted.scheme && requirement.network === accepted.network && requirement.payTo === accepted.payTo && requirement.asset === accepted.asset && requirement.amount === accepted.amount && requirement.maxTimeoutSeconds === accepted.maxTimeoutSeconds;
|
|
1698
|
+
}
|
|
1699
|
+
async function readPaymentPayload(request) {
|
|
1700
|
+
const paymentHeader = request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY);
|
|
1701
|
+
if (!paymentHeader) return null;
|
|
1702
|
+
const { decodePaymentSignatureHeader } = await import("@x402/core/http");
|
|
1703
|
+
return decodePaymentSignatureHeader(paymentHeader);
|
|
1704
|
+
}
|
|
1705
|
+
function invalidPaymentVerification() {
|
|
1706
|
+
return { valid: false, payload: null, requirements: null, payer: null };
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/protocols/x402/strategy.ts
|
|
1710
|
+
var x402Strategy = {
|
|
1711
|
+
protocol: "x402",
|
|
1712
|
+
detects(request) {
|
|
1713
|
+
return Boolean(
|
|
1714
|
+
request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
|
|
1715
|
+
);
|
|
1716
|
+
},
|
|
1717
|
+
async verify(args) {
|
|
1718
|
+
const { request, body, price, routeEntry, deps } = args;
|
|
1719
|
+
if (!deps.x402Server) {
|
|
1720
|
+
const reason = deps.x402InitError ? `x402 facilitator initialization failed: ${deps.x402InitError}` : "x402 server not initialized \u2014 ensure @x402/core, @x402/evm, and @coinbase/x402 are installed";
|
|
1721
|
+
console.error(`[router] ${routeEntry.key}: ${reason}`);
|
|
1722
|
+
return { ok: false, kind: "config", message: reason };
|
|
1723
|
+
}
|
|
1724
|
+
const accepts = await resolveX402Accepts(
|
|
1725
|
+
request,
|
|
1726
|
+
routeEntry,
|
|
1727
|
+
deps.x402Accepts,
|
|
1728
|
+
deps.payeeAddress,
|
|
1729
|
+
body
|
|
1730
|
+
);
|
|
1731
|
+
const verifyResult = await verifyX402Payment({
|
|
1732
|
+
server: deps.x402Server,
|
|
1733
|
+
request,
|
|
1734
|
+
price,
|
|
1735
|
+
accepts
|
|
1736
|
+
});
|
|
1737
|
+
if (!verifyResult?.valid) return { ok: false, kind: "invalid" };
|
|
1738
|
+
const wallet = normalizeWalletAddress(verifyResult.payer);
|
|
1739
|
+
const matchedNetwork = getRequirementNetwork(verifyResult.requirements, deps.network);
|
|
1740
|
+
const matchedRecipient = getRequirementRecipient(verifyResult.requirements);
|
|
1741
|
+
const payment = {
|
|
1742
|
+
protocol: "x402",
|
|
1743
|
+
status: "verified",
|
|
1744
|
+
payer: wallet,
|
|
1745
|
+
amount: price,
|
|
1746
|
+
network: matchedNetwork,
|
|
1747
|
+
...matchedRecipient ? { recipient: matchedRecipient } : {}
|
|
1748
|
+
};
|
|
1749
|
+
return {
|
|
1750
|
+
ok: true,
|
|
1751
|
+
wallet,
|
|
1752
|
+
payment,
|
|
1753
|
+
token: {
|
|
1754
|
+
payload: verifyResult.payload,
|
|
1755
|
+
requirements: verifyResult.requirements
|
|
1810
1756
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1757
|
+
};
|
|
1758
|
+
},
|
|
1759
|
+
async settle(args) {
|
|
1760
|
+
const { response, payment, token, deps } = args;
|
|
1761
|
+
const x402Token = token;
|
|
1762
|
+
try {
|
|
1763
|
+
const settle = await settleX402Payment(
|
|
1764
|
+
deps.x402Server,
|
|
1765
|
+
x402Token.payload,
|
|
1766
|
+
x402Token.requirements
|
|
1814
1767
|
);
|
|
1815
|
-
if (
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1768
|
+
if (!settle.result?.success) {
|
|
1769
|
+
const reason = settle.result?.errorReason || "x402 settlement returned success=false";
|
|
1770
|
+
const error = new Error(reason);
|
|
1771
|
+
error.errorReason = reason;
|
|
1772
|
+
throw error;
|
|
1820
1773
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
network: "tempo:4217"
|
|
1827
|
-
});
|
|
1828
|
-
const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
|
|
1829
|
-
const payment = {
|
|
1830
|
-
protocol: "mpp",
|
|
1774
|
+
response.headers.set(HEADERS.X402_PAYMENT_RESPONSE, settle.encoded);
|
|
1775
|
+
response.headers.set("Cache-Control", "private");
|
|
1776
|
+
const transaction = String(settle.result?.transaction ?? "");
|
|
1777
|
+
const settledPayment = {
|
|
1778
|
+
...payment,
|
|
1831
1779
|
status: "settled",
|
|
1832
|
-
|
|
1833
|
-
amount: price,
|
|
1834
|
-
network: "tempo:4217",
|
|
1835
|
-
...mppRecipient ? { recipient: mppRecipient } : {},
|
|
1836
|
-
...txHash ? { transaction: txHash } : {},
|
|
1837
|
-
...receiptHeader ? { receipt: receiptHeader } : {}
|
|
1838
|
-
};
|
|
1839
|
-
const { response, rawResult, handlerError } = await invoke(
|
|
1840
|
-
request,
|
|
1841
|
-
meta,
|
|
1842
|
-
pluginCtx,
|
|
1843
|
-
wallet,
|
|
1844
|
-
account,
|
|
1845
|
-
body.data,
|
|
1846
|
-
payment
|
|
1847
|
-
);
|
|
1848
|
-
const settleScope = {
|
|
1849
|
-
request,
|
|
1850
|
-
meta,
|
|
1851
|
-
pluginCtx,
|
|
1852
|
-
wallet,
|
|
1853
|
-
account,
|
|
1854
|
-
parsedBody: body.data,
|
|
1855
|
-
payment,
|
|
1856
|
-
response,
|
|
1857
|
-
rawResult,
|
|
1858
|
-
handlerError
|
|
1780
|
+
...transaction ? { transaction } : {}
|
|
1859
1781
|
};
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
const receiptResponse = mppResult.withReceipt(response);
|
|
1873
|
-
receiptResponse.headers.set("Cache-Control", "private");
|
|
1874
|
-
firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
|
|
1875
|
-
protocol: "mpp",
|
|
1876
|
-
payer: wallet,
|
|
1877
|
-
transaction: txHash,
|
|
1878
|
-
network: "tempo:4217"
|
|
1879
|
-
});
|
|
1880
|
-
await runAfterSettle({ ...settleScope, response: receiptResponse });
|
|
1881
|
-
finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
|
|
1882
|
-
return receiptResponse;
|
|
1883
|
-
}
|
|
1884
|
-
await runSettledHandlerError(settleScope);
|
|
1885
|
-
finalize(response, rawResult, meta, pluginCtx, body.data);
|
|
1886
|
-
return response;
|
|
1782
|
+
return { ok: true, response, settledPayment };
|
|
1783
|
+
} catch (err) {
|
|
1784
|
+
const errObj = err;
|
|
1785
|
+
console.error("Settlement failed", {
|
|
1786
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1787
|
+
route: args.routeEntry.key,
|
|
1788
|
+
network: payment.network,
|
|
1789
|
+
errorReason: errObj.errorReason,
|
|
1790
|
+
facilitatorStatus: errObj.response?.status,
|
|
1791
|
+
facilitatorBody: errObj.response?.data ?? errObj.response?.body
|
|
1792
|
+
});
|
|
1793
|
+
return { ok: false, error: err, failMessage: "Settlement failed" };
|
|
1887
1794
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1795
|
+
},
|
|
1796
|
+
async buildChallenge(args) {
|
|
1797
|
+
const { request, routeEntry, body, price, extensions, deps } = args;
|
|
1798
|
+
if (!deps.x402Server) return {};
|
|
1799
|
+
const accepts = await resolveX402Accepts(
|
|
1800
|
+
request,
|
|
1801
|
+
routeEntry,
|
|
1802
|
+
deps.x402Accepts,
|
|
1803
|
+
deps.payeeAddress,
|
|
1804
|
+
body
|
|
1805
|
+
);
|
|
1806
|
+
const { encoded } = await buildX402Challenge({
|
|
1807
|
+
server: deps.x402Server,
|
|
1808
|
+
routeEntry,
|
|
1809
|
+
request,
|
|
1810
|
+
price,
|
|
1811
|
+
accepts,
|
|
1812
|
+
facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
|
|
1813
|
+
extensions
|
|
1814
|
+
});
|
|
1815
|
+
return { headers: { [HEADERS.X402_PAYMENT_REQUIRED]: encoded } };
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
function getRequirementNetwork(requirements, fallback) {
|
|
1819
|
+
const network = requirements?.network;
|
|
1820
|
+
return typeof network === "string" ? network : fallback;
|
|
1890
1821
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1822
|
+
function getRequirementRecipient(requirements) {
|
|
1823
|
+
const payTo = requirements?.payTo;
|
|
1824
|
+
return typeof payTo === "string" ? payTo : void 0;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// src/protocols/detect.ts
|
|
1828
|
+
function detectProtocol(request) {
|
|
1829
|
+
if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
|
|
1830
|
+
return "x402";
|
|
1831
|
+
}
|
|
1832
|
+
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
1833
|
+
if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
|
|
1834
|
+
return "mpp";
|
|
1835
|
+
}
|
|
1836
|
+
if (request.headers.get(HEADERS.SIWX)) {
|
|
1837
|
+
return "siwx";
|
|
1838
|
+
}
|
|
1839
|
+
return null;
|
|
1903
1840
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
startTime: Date.now()
|
|
1917
|
-
};
|
|
1841
|
+
|
|
1842
|
+
// src/protocols/index.ts
|
|
1843
|
+
var STRATEGIES = {
|
|
1844
|
+
x402: x402Strategy,
|
|
1845
|
+
mpp: mppStrategy
|
|
1846
|
+
};
|
|
1847
|
+
function selectIncomingStrategy(request, allowed) {
|
|
1848
|
+
for (const name of allowed) {
|
|
1849
|
+
const strategy = STRATEGIES[name];
|
|
1850
|
+
if (strategy.detects(request)) return strategy;
|
|
1851
|
+
}
|
|
1852
|
+
return null;
|
|
1918
1853
|
}
|
|
1919
|
-
function
|
|
1920
|
-
|
|
1921
|
-
const params = Object.fromEntries(request.nextUrl.searchParams.entries());
|
|
1922
|
-
const result = routeEntry.querySchema.safeParse(params);
|
|
1923
|
-
return result.success ? result.data : params;
|
|
1854
|
+
function getAllowedStrategies(allowed) {
|
|
1855
|
+
return allowed.map((name) => STRATEGIES[name]);
|
|
1924
1856
|
}
|
|
1925
|
-
|
|
1857
|
+
|
|
1858
|
+
// src/pipeline/challenge.ts
|
|
1859
|
+
var import_server4 = require("next/server");
|
|
1860
|
+
async function build402(ctx, pricing, body) {
|
|
1861
|
+
let challengePrice;
|
|
1926
1862
|
try {
|
|
1927
|
-
|
|
1928
|
-
if (routeEntry.maxPrice) {
|
|
1929
|
-
const calculated = parseFloat(price);
|
|
1930
|
-
const max = parseFloat(routeEntry.maxPrice);
|
|
1931
|
-
if (!Number.isFinite(calculated) || calculated > max) {
|
|
1932
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1933
|
-
level: "warn",
|
|
1934
|
-
message: `Price ${price} exceeds maxPrice ${routeEntry.maxPrice}, capping`,
|
|
1935
|
-
route: routeEntry.key,
|
|
1936
|
-
meta: { calculated: price, maxPrice: routeEntry.maxPrice, body: bodyData }
|
|
1937
|
-
});
|
|
1938
|
-
price = routeEntry.maxPrice;
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
return { price };
|
|
1863
|
+
challengePrice = pricing ? await pricing.challengeQuote(body) : "0";
|
|
1942
1864
|
} catch (err) {
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1951
|
-
level: "warn",
|
|
1952
|
-
message: `Using maxPrice ${routeEntry.maxPrice} as fallback after pricing error`,
|
|
1953
|
-
route: routeEntry.key
|
|
1954
|
-
});
|
|
1955
|
-
return { price: routeEntry.maxPrice };
|
|
1956
|
-
} else {
|
|
1957
|
-
const message = errorMessage(err, "Price calculation failed");
|
|
1958
|
-
const errorResponse = import_server2.NextResponse.json(
|
|
1959
|
-
{ success: false, error: message },
|
|
1960
|
-
{ status: errorStatus(err, 500) }
|
|
1961
|
-
);
|
|
1962
|
-
firePluginResponse(deps, pluginCtx, meta, errorResponse);
|
|
1963
|
-
return { error: errorResponse };
|
|
1964
|
-
}
|
|
1865
|
+
const message = errorMessage(err, "Price calculation failed");
|
|
1866
|
+
const errorResponse = import_server4.NextResponse.json(
|
|
1867
|
+
{ success: false, error: message },
|
|
1868
|
+
{ status: errorStatus(err, 500) }
|
|
1869
|
+
);
|
|
1870
|
+
firePluginResponse(ctx, errorResponse);
|
|
1871
|
+
return errorResponse;
|
|
1965
1872
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
const response = new import_server2.NextResponse(null, {
|
|
1873
|
+
const extensions = await buildChallengeExtensions(ctx);
|
|
1874
|
+
const response = new import_server4.NextResponse(null, {
|
|
1969
1875
|
status: 402,
|
|
1970
1876
|
headers: {
|
|
1971
1877
|
"Content-Type": "application/json",
|
|
1972
1878
|
"Cache-Control": "no-store"
|
|
1973
1879
|
}
|
|
1974
1880
|
});
|
|
1975
|
-
|
|
1976
|
-
if (bodyData !== void 0 && typeof routeEntry.pricing !== "string" && routeEntry.pricing != null) {
|
|
1977
|
-
const result = await resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta);
|
|
1978
|
-
if ("error" in result) return result.error;
|
|
1979
|
-
challengePrice = result.price;
|
|
1980
|
-
} else if (routeEntry.maxPrice) {
|
|
1981
|
-
challengePrice = routeEntry.maxPrice;
|
|
1982
|
-
} else if (routeEntry.pricing) {
|
|
1881
|
+
for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
|
|
1983
1882
|
try {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1883
|
+
const contribution = await strategy.buildChallenge({
|
|
1884
|
+
request: ctx.request,
|
|
1885
|
+
routeEntry: ctx.routeEntry,
|
|
1886
|
+
body,
|
|
1887
|
+
price: challengePrice,
|
|
1888
|
+
extensions,
|
|
1889
|
+
deps: ctx.deps
|
|
1890
|
+
});
|
|
1891
|
+
if (contribution.headers) {
|
|
1892
|
+
for (const [name, value] of Object.entries(contribution.headers)) {
|
|
1893
|
+
response.headers.set(name, value);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
|
|
1898
|
+
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
1899
|
+
level: "critical",
|
|
1900
|
+
message,
|
|
1901
|
+
route: ctx.routeEntry.key
|
|
1902
|
+
});
|
|
1903
|
+
if (strategy.protocol === "x402") {
|
|
1904
|
+
const errorResponse = import_server4.NextResponse.json(
|
|
1905
|
+
{ success: false, error: message },
|
|
1906
|
+
{ status: 500 }
|
|
1907
|
+
);
|
|
1908
|
+
firePluginResponse(ctx, errorResponse);
|
|
1909
|
+
return errorResponse;
|
|
1910
|
+
}
|
|
1987
1911
|
}
|
|
1988
|
-
} else {
|
|
1989
|
-
challengePrice = "0";
|
|
1990
1912
|
}
|
|
1913
|
+
firePluginResponse(ctx, response);
|
|
1914
|
+
return response;
|
|
1915
|
+
}
|
|
1916
|
+
async function buildChallengeExtensions(ctx) {
|
|
1917
|
+
const { routeEntry } = ctx;
|
|
1991
1918
|
let extensions;
|
|
1992
1919
|
try {
|
|
1993
1920
|
const { z } = await import("zod");
|
|
@@ -2013,7 +1940,7 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
|
|
|
2013
1940
|
extensions = declareDiscoveryExtension(config);
|
|
2014
1941
|
}
|
|
2015
1942
|
} catch (err) {
|
|
2016
|
-
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
1943
|
+
firePluginHook(ctx.deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
2017
1944
|
level: "warn",
|
|
2018
1945
|
message: `Bazaar schema generation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2019
1946
|
route: routeEntry.key
|
|
@@ -2031,101 +1958,390 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
|
|
|
2031
1958
|
} catch {
|
|
2032
1959
|
}
|
|
2033
1960
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
1961
|
+
return extensions;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// src/pipeline/flows/paid.ts
|
|
1965
|
+
async function runPaidFlow(ctx) {
|
|
1966
|
+
const { request, routeEntry, deps } = ctx;
|
|
1967
|
+
let account = void 0;
|
|
1968
|
+
if (routeEntry.apiKeyResolver) {
|
|
1969
|
+
const apiKeyResult = await verifyApiKey(request, routeEntry.apiKeyResolver);
|
|
1970
|
+
if (!apiKeyResult.valid) return fail(ctx, 401, "Invalid or missing API key");
|
|
1971
|
+
account = apiKeyResult.account;
|
|
1972
|
+
firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
|
|
1973
|
+
authMode: "apiKey",
|
|
1974
|
+
wallet: null,
|
|
1975
|
+
route: routeEntry.key,
|
|
1976
|
+
account
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
const alertFn = (level, message, meta) => {
|
|
1980
|
+
firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
1981
|
+
level,
|
|
1982
|
+
message,
|
|
1983
|
+
route: routeEntry.key,
|
|
1984
|
+
meta
|
|
1985
|
+
});
|
|
1986
|
+
};
|
|
1987
|
+
const pricing = selectPricing(routeEntry.pricing, {
|
|
1988
|
+
alert: alertFn,
|
|
1989
|
+
maxPrice: routeEntry.maxPrice,
|
|
1990
|
+
minPrice: routeEntry.minPrice,
|
|
1991
|
+
route: routeEntry.key
|
|
1992
|
+
});
|
|
1993
|
+
const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
|
|
1994
|
+
let earlyBody = void 0;
|
|
1995
|
+
if (shouldParseBodyEarly(incomingStrategy, routeEntry, pricing)) {
|
|
1996
|
+
const earlyClone = request.clone();
|
|
1997
|
+
const earlyResult = await parseBody(earlyClone, routeEntry);
|
|
1998
|
+
if (earlyResult.ok) {
|
|
1999
|
+
earlyBody = earlyResult.data;
|
|
2000
|
+
const validateErr2 = await runValidate(ctx, earlyBody);
|
|
2001
|
+
if (validateErr2) return validateErr2;
|
|
2002
|
+
} else {
|
|
2003
|
+
firePluginResponse(ctx, earlyResult.response);
|
|
2004
|
+
return earlyResult.response;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
const siwxFastPath = await trySiwxFastPath(ctx, account);
|
|
2008
|
+
if (siwxFastPath) return siwxFastPath;
|
|
2009
|
+
if (!incomingStrategy) {
|
|
2010
|
+
const initError = protocolInitError(routeEntry, deps);
|
|
2011
|
+
if (initError) return fail(ctx, 500, initError);
|
|
2012
|
+
return build402(ctx, pricing, earlyBody);
|
|
2013
|
+
}
|
|
2014
|
+
const body = await parseBody(request, routeEntry);
|
|
2015
|
+
if (!body.ok) {
|
|
2016
|
+
firePluginResponse(ctx, body.response);
|
|
2017
|
+
return body.response;
|
|
2018
|
+
}
|
|
2019
|
+
const validateErr = await runValidate(ctx, body.data);
|
|
2020
|
+
if (validateErr) return validateErr;
|
|
2021
|
+
if (!pricing) {
|
|
2022
|
+
return fail(ctx, 500, "Pricing not configured", body.data);
|
|
2023
|
+
}
|
|
2024
|
+
let price;
|
|
2025
|
+
try {
|
|
2026
|
+
price = await pricing.quote(body.data);
|
|
2027
|
+
} catch (err) {
|
|
2028
|
+
return fail(
|
|
2029
|
+
ctx,
|
|
2030
|
+
errorStatus(err, 500),
|
|
2031
|
+
errorMessage(err, "Price calculation failed"),
|
|
2032
|
+
body.data
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
const verifyOutcome = await incomingStrategy.verify({
|
|
2036
|
+
request,
|
|
2037
|
+
body: body.data,
|
|
2038
|
+
price,
|
|
2039
|
+
routeEntry,
|
|
2040
|
+
deps
|
|
2041
|
+
});
|
|
2042
|
+
if (verifyOutcome.ok === false) {
|
|
2043
|
+
if (verifyOutcome.kind === "config") {
|
|
2044
|
+
return fail(ctx, 500, verifyOutcome.message, body.data);
|
|
2045
|
+
}
|
|
2046
|
+
return build402(ctx, pricing, body.data);
|
|
2047
|
+
}
|
|
2048
|
+
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2049
|
+
firePluginHook(deps.plugin, "onPaymentVerified", ctx.pluginCtx, {
|
|
2050
|
+
protocol: incomingStrategy.protocol,
|
|
2051
|
+
payer: verifyOutcome.wallet,
|
|
2052
|
+
amount: price,
|
|
2053
|
+
network: verifyOutcome.payment.network
|
|
2054
|
+
});
|
|
2055
|
+
const result = await invoke(ctx, verifyOutcome.wallet, account, body.data, verifyOutcome.payment);
|
|
2056
|
+
const settleScope = {
|
|
2057
|
+
wallet: verifyOutcome.wallet,
|
|
2058
|
+
account,
|
|
2059
|
+
body: body.data,
|
|
2060
|
+
payment: verifyOutcome.payment,
|
|
2061
|
+
response: result.response,
|
|
2062
|
+
rawResult: result.rawResult,
|
|
2063
|
+
handlerError: result.handlerError
|
|
2064
|
+
};
|
|
2065
|
+
if (verifyOutcome.alreadySettled) {
|
|
2066
|
+
if (result.response.status >= 400) {
|
|
2067
|
+
const settledScope = settleScope;
|
|
2068
|
+
await runSettledHandlerError(ctx, settledScope);
|
|
2069
|
+
return finalize(ctx, result.response, result.rawResult, body.data);
|
|
2070
|
+
}
|
|
2071
|
+
return settleAndFinalize({
|
|
2072
|
+
ctx,
|
|
2073
|
+
strategy: incomingStrategy,
|
|
2074
|
+
verifyOutcome,
|
|
2075
|
+
scope: settleScope,
|
|
2076
|
+
rawResult: result.rawResult,
|
|
2077
|
+
body: body.data
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
if (result.response.status >= 400) {
|
|
2081
|
+
return finalize(ctx, result.response, result.rawResult, body.data);
|
|
2082
|
+
}
|
|
2083
|
+
const beforeErr = await runBeforeSettle(ctx, settleScope);
|
|
2084
|
+
if (beforeErr) return beforeErr;
|
|
2085
|
+
return settleAndFinalize({
|
|
2086
|
+
ctx,
|
|
2087
|
+
strategy: incomingStrategy,
|
|
2088
|
+
verifyOutcome,
|
|
2089
|
+
scope: settleScope,
|
|
2090
|
+
rawResult: result.rawResult,
|
|
2091
|
+
body: body.data,
|
|
2092
|
+
onSettleError: async (error, failMessage) => {
|
|
2093
|
+
await runSettlementError(ctx, settleScope, error, "settle");
|
|
2094
|
+
firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
2056
2095
|
level: "critical",
|
|
2057
|
-
message,
|
|
2096
|
+
message: `${incomingStrategy.protocol} ${failMessage}: ${errorMessage(error, "unknown")}`,
|
|
2058
2097
|
route: routeEntry.key
|
|
2059
2098
|
});
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// src/pipeline/flows/siwx-only.ts
|
|
2104
|
+
var import_server5 = require("next/server");
|
|
2105
|
+
|
|
2106
|
+
// src/auth/nonce.ts
|
|
2107
|
+
var SIWX_CHALLENGE_EXPIRY_MS = 5 * 60 * 1e3;
|
|
2108
|
+
var MemoryNonceStore = class {
|
|
2109
|
+
seen = /* @__PURE__ */ new Map();
|
|
2110
|
+
async check(nonce) {
|
|
2111
|
+
this.evict();
|
|
2112
|
+
if (this.seen.has(nonce)) return false;
|
|
2113
|
+
this.seen.set(nonce, Date.now() + SIWX_CHALLENGE_EXPIRY_MS);
|
|
2114
|
+
return true;
|
|
2115
|
+
}
|
|
2116
|
+
evict() {
|
|
2117
|
+
const now = Date.now();
|
|
2118
|
+
for (const [n, exp] of this.seen) {
|
|
2119
|
+
if (exp < now) this.seen.delete(n);
|
|
2063
2120
|
}
|
|
2064
2121
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2122
|
+
};
|
|
2123
|
+
function detectRedisClientType(client) {
|
|
2124
|
+
if (!client || typeof client !== "object") {
|
|
2125
|
+
throw new Error(
|
|
2126
|
+
"createRedisNonceStore requires a Redis client. Supported: @upstash/redis, ioredis. Pass your Redis client instance as the first argument."
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
if ("options" in client && "status" in client) {
|
|
2130
|
+
return "ioredis";
|
|
2131
|
+
}
|
|
2132
|
+
const constructor = client.constructor?.name;
|
|
2133
|
+
if (constructor === "Redis" && "url" in client) {
|
|
2134
|
+
return "upstash";
|
|
2135
|
+
}
|
|
2136
|
+
if (typeof client.set === "function") {
|
|
2137
|
+
return "upstash";
|
|
2138
|
+
}
|
|
2139
|
+
throw new Error(
|
|
2140
|
+
"Unrecognized Redis client. Supported: @upstash/redis, ioredis. If using a different client, implement NonceStore interface directly."
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
function createRedisNonceStore(client, opts) {
|
|
2144
|
+
const prefix = opts?.prefix ?? "siwx:nonce:";
|
|
2145
|
+
const ttlSeconds = Math.ceil((opts?.ttlMs ?? SIWX_CHALLENGE_EXPIRY_MS) / 1e3);
|
|
2146
|
+
const clientType = detectRedisClientType(client);
|
|
2147
|
+
return {
|
|
2148
|
+
async check(nonce) {
|
|
2149
|
+
const key = `${prefix}${nonce}`;
|
|
2150
|
+
if (clientType === "upstash") {
|
|
2151
|
+
const redis = client;
|
|
2152
|
+
const result = await redis.set(key, "1", { ex: ttlSeconds, nx: true });
|
|
2153
|
+
return result !== null;
|
|
2071
2154
|
}
|
|
2155
|
+
if (clientType === "ioredis") {
|
|
2156
|
+
const redis = client;
|
|
2157
|
+
const result = await redis.set(key, "1", "EX", ttlSeconds, "NX");
|
|
2158
|
+
return result === "OK";
|
|
2159
|
+
}
|
|
2160
|
+
throw new Error("Unknown Redis client type");
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// src/protocols/mpp/siwx-mode.ts
|
|
2166
|
+
var import_mppx3 = require("mppx");
|
|
2167
|
+
async function verifyMppSiwx(request, mppx) {
|
|
2168
|
+
const result = await mppx.charge({ amount: "0" })(request);
|
|
2169
|
+
if (result.status === 402) {
|
|
2170
|
+
return { valid: false, challenge: result.challenge };
|
|
2171
|
+
}
|
|
2172
|
+
const credential = import_mppx3.Credential.fromRequest(request);
|
|
2173
|
+
const rawSource = credential?.source ?? "";
|
|
2174
|
+
const wallet = walletFromDid(rawSource);
|
|
2175
|
+
return { valid: true, wallet, withReceipt: result.withReceipt };
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// src/pipeline/flows/siwx-only.ts
|
|
2179
|
+
async function runSiwxOnlyFlow(ctx) {
|
|
2180
|
+
const { request, routeEntry, deps } = ctx;
|
|
2181
|
+
if (routeEntry.validateFn && routeEntry.bodySchema && !request.headers.get(HEADERS.SIWX)) {
|
|
2182
|
+
const earlyClone = request.clone();
|
|
2183
|
+
const earlyBody = await parseBody(earlyClone, routeEntry);
|
|
2184
|
+
if (earlyBody.ok) {
|
|
2185
|
+
const validateErr = await runValidate(ctx, earlyBody.data);
|
|
2186
|
+
if (validateErr) return validateErr;
|
|
2187
|
+
} else {
|
|
2188
|
+
firePluginResponse(ctx, earlyBody.response);
|
|
2189
|
+
return earlyBody.response;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
const siwxHeader = request.headers.get(HEADERS.SIWX);
|
|
2193
|
+
const protocol = detectProtocol(request);
|
|
2194
|
+
if (!siwxHeader && protocol === "mpp" && deps.mppx) {
|
|
2195
|
+
let mppSiwxResult;
|
|
2196
|
+
try {
|
|
2197
|
+
mppSiwxResult = await verifyMppSiwx(request, deps.mppx);
|
|
2072
2198
|
} catch (err) {
|
|
2073
|
-
|
|
2199
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2200
|
+
firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
2074
2201
|
level: "critical",
|
|
2075
|
-
message: `MPP
|
|
2202
|
+
message: `MPP SIWX verification failed: ${message}`,
|
|
2203
|
+
route: routeEntry.key
|
|
2204
|
+
});
|
|
2205
|
+
return fail(ctx, 500, `MPP SIWX verification failed: ${message}`);
|
|
2206
|
+
}
|
|
2207
|
+
if (mppSiwxResult.valid) {
|
|
2208
|
+
ctx.pluginCtx.setVerifiedWallet(mppSiwxResult.wallet);
|
|
2209
|
+
firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
|
|
2210
|
+
authMode: "siwx",
|
|
2211
|
+
wallet: mppSiwxResult.wallet,
|
|
2076
2212
|
route: routeEntry.key
|
|
2077
2213
|
});
|
|
2214
|
+
const authResponse = await runHandlerOnly(ctx, mppSiwxResult.wallet, void 0);
|
|
2215
|
+
if (authResponse.status < 400) {
|
|
2216
|
+
return mppSiwxResult.withReceipt(authResponse);
|
|
2217
|
+
}
|
|
2218
|
+
return authResponse;
|
|
2078
2219
|
}
|
|
2079
2220
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2221
|
+
if (!siwxHeader) {
|
|
2222
|
+
return buildSiwxChallenge(ctx);
|
|
2223
|
+
}
|
|
2224
|
+
const siwx = await verifySIWX(request, routeEntry, deps.nonceStore);
|
|
2225
|
+
if (!siwx.valid) {
|
|
2226
|
+
const response = import_server5.NextResponse.json(
|
|
2227
|
+
{ error: siwx.code, message: SIWX_ERROR_MESSAGES[siwx.code] },
|
|
2228
|
+
{ status: 402 }
|
|
2229
|
+
);
|
|
2230
|
+
firePluginResponse(ctx, response);
|
|
2231
|
+
return response;
|
|
2232
|
+
}
|
|
2233
|
+
const wallet = normalizeWalletAddress(siwx.wallet);
|
|
2234
|
+
ctx.pluginCtx.setVerifiedWallet(wallet);
|
|
2235
|
+
firePluginHook(deps.plugin, "onAuthVerified", ctx.pluginCtx, {
|
|
2236
|
+
authMode: "siwx",
|
|
2237
|
+
wallet,
|
|
2238
|
+
route: routeEntry.key
|
|
2092
2239
|
});
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2240
|
+
return runHandlerOnly(ctx, wallet, void 0);
|
|
2241
|
+
}
|
|
2242
|
+
async function buildSiwxChallenge(ctx) {
|
|
2243
|
+
const { request, routeEntry, deps } = ctx;
|
|
2244
|
+
const url = new URL(request.url);
|
|
2245
|
+
const nonce = crypto.randomUUID().replace(/-/g, "");
|
|
2246
|
+
const supportedChains = getSupportedChains(deps.x402Accepts, deps.network);
|
|
2247
|
+
const primaryChain = supportedChains[0];
|
|
2248
|
+
const siwxInfo = {
|
|
2249
|
+
domain: url.hostname,
|
|
2250
|
+
uri: request.url,
|
|
2251
|
+
version: "1",
|
|
2252
|
+
chainId: primaryChain.chainId,
|
|
2253
|
+
type: primaryChain.type,
|
|
2254
|
+
nonce,
|
|
2255
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2256
|
+
expirationTime: new Date(Date.now() + SIWX_CHALLENGE_EXPIRY_MS).toISOString(),
|
|
2257
|
+
statement: "Sign in to verify your wallet identity"
|
|
2258
|
+
};
|
|
2259
|
+
let siwxSchema;
|
|
2260
|
+
try {
|
|
2261
|
+
siwxSchema = await buildSIWXExtension();
|
|
2262
|
+
} catch {
|
|
2263
|
+
}
|
|
2264
|
+
const paymentRequired = {
|
|
2265
|
+
x402Version: 2,
|
|
2266
|
+
error: "SIWX authentication required",
|
|
2267
|
+
resource: {
|
|
2268
|
+
url: request.url,
|
|
2269
|
+
description: routeEntry.description ?? "SIWX-protected endpoint",
|
|
2270
|
+
mimeType: "application/json"
|
|
2271
|
+
},
|
|
2272
|
+
accepts: [],
|
|
2273
|
+
extensions: {
|
|
2274
|
+
"sign-in-with-x": {
|
|
2275
|
+
info: siwxInfo,
|
|
2276
|
+
// Required by MCP tools at the top level for chain detection.
|
|
2277
|
+
supportedChains,
|
|
2278
|
+
...siwxSchema ? { schema: siwxSchema } : {}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
let encoded;
|
|
2283
|
+
try {
|
|
2284
|
+
const { encodePaymentRequiredHeader } = await import("@x402/core/http");
|
|
2285
|
+
encoded = encodePaymentRequiredHeader(paymentRequired);
|
|
2286
|
+
} catch (err) {
|
|
2287
|
+
firePluginHook(deps.plugin, "onAlert", ctx.pluginCtx, {
|
|
2288
|
+
level: "warn",
|
|
2289
|
+
message: `SIWX challenge header encoding failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2290
|
+
route: routeEntry.key
|
|
2098
2291
|
});
|
|
2099
2292
|
}
|
|
2293
|
+
const response = new import_server5.NextResponse(JSON.stringify(paymentRequired), {
|
|
2294
|
+
status: 402,
|
|
2295
|
+
headers: { "Content-Type": "application/json", "Cache-Control": "no-store" }
|
|
2296
|
+
});
|
|
2297
|
+
if (encoded) response.headers.set(HEADERS.X402_PAYMENT_REQUIRED, encoded);
|
|
2298
|
+
if (deps.mppx) {
|
|
2299
|
+
try {
|
|
2300
|
+
const mppChallenge = await deps.mppx.charge({ amount: "0" })(request);
|
|
2301
|
+
if (mppChallenge.status === 402) {
|
|
2302
|
+
const wwwAuth = mppChallenge.challenge.headers.get(HEADERS.WWW_AUTHENTICATE);
|
|
2303
|
+
if (wwwAuth) response.headers.set(HEADERS.WWW_AUTHENTICATE, wwwAuth);
|
|
2304
|
+
}
|
|
2305
|
+
} catch {
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
firePluginResponse(ctx, response);
|
|
2309
|
+
return response;
|
|
2100
2310
|
}
|
|
2101
|
-
function
|
|
2102
|
-
|
|
2103
|
-
if (critical !== void 0 && remaining <= critical) return "critical";
|
|
2104
|
-
if (warn !== void 0 && remaining <= warn) return "warn";
|
|
2105
|
-
return "healthy";
|
|
2311
|
+
function siwxSignatureType(network) {
|
|
2312
|
+
return network.startsWith("solana:") ? "ed25519" : "eip191";
|
|
2106
2313
|
}
|
|
2107
|
-
function
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
const overage = providerConfig.overage ?? "same-rate";
|
|
2116
|
-
const event = {
|
|
2117
|
-
provider: providerName,
|
|
2118
|
-
route: routeEntry.key,
|
|
2119
|
-
remaining: quota.remaining,
|
|
2120
|
-
limit: quota.limit,
|
|
2121
|
-
spend: quota.spend,
|
|
2122
|
-
level,
|
|
2123
|
-
overage,
|
|
2124
|
-
message: quota.remaining !== null ? `${providerName}: ${quota.remaining}${quota.limit ? `/${quota.limit}` : ""} remaining` : `${providerName}: quota info unavailable`
|
|
2125
|
-
};
|
|
2126
|
-
firePluginHook(deps.plugin, "onProviderQuota", pluginCtx, event);
|
|
2127
|
-
} catch {
|
|
2314
|
+
function getSupportedChains(x402Accepts, fallbackNetwork) {
|
|
2315
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2316
|
+
const chains = [];
|
|
2317
|
+
for (const accept of x402Accepts) {
|
|
2318
|
+
if (accept.network && !seen.has(accept.network)) {
|
|
2319
|
+
seen.add(accept.network);
|
|
2320
|
+
chains.push({ chainId: accept.network, type: siwxSignatureType(accept.network) });
|
|
2321
|
+
}
|
|
2128
2322
|
}
|
|
2323
|
+
if (chains.length === 0) {
|
|
2324
|
+
chains.push({ chainId: fallbackNetwork, type: siwxSignatureType(fallbackNetwork) });
|
|
2325
|
+
}
|
|
2326
|
+
return chains;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// src/pipeline/flows/unprotected.ts
|
|
2330
|
+
async function runUnprotectedFlow(ctx) {
|
|
2331
|
+
return runHandlerOnly(ctx, null, void 0);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
// src/orchestrate.ts
|
|
2335
|
+
function createRequestHandler(routeEntry, handler, deps) {
|
|
2336
|
+
return async (request) => {
|
|
2337
|
+
await deps.initPromise;
|
|
2338
|
+
const ctx = preflight(routeEntry, handler, deps, request);
|
|
2339
|
+
if (routeEntry.authMode === "unprotected") return runUnprotectedFlow(ctx);
|
|
2340
|
+
if (routeEntry.authMode === "siwx") return runSiwxOnlyFlow(ctx);
|
|
2341
|
+
if (routeEntry.pricing) return runPaidFlow(ctx);
|
|
2342
|
+
if (routeEntry.apiKeyResolver) return runApiKeyOnlyFlow(ctx);
|
|
2343
|
+
return runUnprotectedFlow(ctx);
|
|
2344
|
+
};
|
|
2129
2345
|
}
|
|
2130
2346
|
|
|
2131
2347
|
// src/validate-examples.ts
|
|
@@ -2600,7 +2816,7 @@ function createRedisEntitlementStore(client, options) {
|
|
|
2600
2816
|
}
|
|
2601
2817
|
|
|
2602
2818
|
// src/discovery/well-known.ts
|
|
2603
|
-
var
|
|
2819
|
+
var import_server6 = require("next/server");
|
|
2604
2820
|
|
|
2605
2821
|
// src/discovery/utils/guidance.ts
|
|
2606
2822
|
async function resolveGuidance(discovery) {
|
|
@@ -2644,7 +2860,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
2644
2860
|
if (instructions) {
|
|
2645
2861
|
body.instructions = instructions;
|
|
2646
2862
|
}
|
|
2647
|
-
return
|
|
2863
|
+
return import_server6.NextResponse.json(body, {
|
|
2648
2864
|
headers: {
|
|
2649
2865
|
"Access-Control-Allow-Origin": "*",
|
|
2650
2866
|
"Access-Control-Allow-Methods": "GET",
|
|
@@ -2661,14 +2877,14 @@ function toDiscoveryResource(method, url, mode) {
|
|
|
2661
2877
|
}
|
|
2662
2878
|
|
|
2663
2879
|
// src/discovery/openapi.ts
|
|
2664
|
-
var
|
|
2880
|
+
var import_server7 = require("next/server");
|
|
2665
2881
|
init_constants();
|
|
2666
2882
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
2667
2883
|
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
2668
2884
|
let cached = null;
|
|
2669
2885
|
let validated = false;
|
|
2670
2886
|
return async (_request) => {
|
|
2671
|
-
if (cached) return
|
|
2887
|
+
if (cached) return import_server7.NextResponse.json(cached);
|
|
2672
2888
|
if (!validated && pricesKeys) {
|
|
2673
2889
|
registry.validate(pricesKeys);
|
|
2674
2890
|
validated = true;
|
|
@@ -2693,14 +2909,14 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
2693
2909
|
securitySchemes.siwx = {
|
|
2694
2910
|
type: "apiKey",
|
|
2695
2911
|
in: "header",
|
|
2696
|
-
name:
|
|
2912
|
+
name: HEADERS.SIWX
|
|
2697
2913
|
};
|
|
2698
2914
|
}
|
|
2699
2915
|
if (requiresApiKeyScheme) {
|
|
2700
2916
|
securitySchemes.apiKey = {
|
|
2701
2917
|
type: "apiKey",
|
|
2702
2918
|
in: "header",
|
|
2703
|
-
name:
|
|
2919
|
+
name: HEADERS.API_KEY
|
|
2704
2920
|
};
|
|
2705
2921
|
}
|
|
2706
2922
|
const discoveryMetadata = {};
|
|
@@ -2731,7 +2947,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
|
|
|
2731
2947
|
paths
|
|
2732
2948
|
};
|
|
2733
2949
|
cached = createDocument(openApiDocument);
|
|
2734
|
-
return
|
|
2950
|
+
return import_server7.NextResponse.json(cached);
|
|
2735
2951
|
};
|
|
2736
2952
|
}
|
|
2737
2953
|
function deriveTag(routeKey) {
|
|
@@ -2854,11 +3070,11 @@ function buildPricingInfo(entry) {
|
|
|
2854
3070
|
}
|
|
2855
3071
|
|
|
2856
3072
|
// src/discovery/llms-txt.ts
|
|
2857
|
-
var
|
|
3073
|
+
var import_server8 = require("next/server");
|
|
2858
3074
|
function createLlmsTxtHandler(discovery) {
|
|
2859
3075
|
return async (_request) => {
|
|
2860
3076
|
const guidance = await resolveGuidance(discovery) ?? "";
|
|
2861
|
-
return new
|
|
3077
|
+
return new import_server8.NextResponse(guidance, {
|
|
2862
3078
|
headers: {
|
|
2863
3079
|
"Content-Type": "text/plain; charset=utf-8",
|
|
2864
3080
|
"Access-Control-Allow-Origin": "*"
|
|
@@ -2868,14 +3084,14 @@ function createLlmsTxtHandler(discovery) {
|
|
|
2868
3084
|
}
|
|
2869
3085
|
|
|
2870
3086
|
// src/index.ts
|
|
2871
|
-
|
|
3087
|
+
init_accepts();
|
|
2872
3088
|
init_constants();
|
|
2873
3089
|
|
|
2874
3090
|
// src/config.ts
|
|
2875
3091
|
init_constants();
|
|
2876
3092
|
init_evm();
|
|
2877
3093
|
init_solana();
|
|
2878
|
-
|
|
3094
|
+
init_accepts();
|
|
2879
3095
|
var RouterConfigError = class extends Error {
|
|
2880
3096
|
issues;
|
|
2881
3097
|
constructor(issues) {
|