@key0ai/key0 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +29 -14
  2. package/dist/__tests__/x402-http-middleware.test.js +4 -207
  3. package/dist/__tests__/x402-http-middleware.test.js.map +1 -1
  4. package/dist/core/__tests__/storage-postgres.test.js +0 -1
  5. package/dist/core/__tests__/storage-postgres.test.js.map +1 -1
  6. package/dist/core/challenge-engine.js +1 -1
  7. package/dist/core/challenge-engine.js.map +1 -1
  8. package/dist/integrations/express.d.ts +6 -4
  9. package/dist/integrations/express.d.ts.map +1 -1
  10. package/dist/integrations/express.js +21 -14
  11. package/dist/integrations/express.js.map +1 -1
  12. package/dist/integrations/fastify.d.ts +5 -1
  13. package/dist/integrations/fastify.d.ts.map +1 -1
  14. package/dist/integrations/fastify.js +116 -8
  15. package/dist/integrations/fastify.js.map +1 -1
  16. package/dist/integrations/hono.d.ts +8 -2
  17. package/dist/integrations/hono.d.ts.map +1 -1
  18. package/dist/integrations/hono.js +115 -12
  19. package/dist/integrations/hono.js.map +1 -1
  20. package/dist/integrations/settlement.d.ts.map +1 -1
  21. package/dist/integrations/settlement.js +1 -3
  22. package/dist/integrations/settlement.js.map +1 -1
  23. package/package.json +2 -1
  24. package/src/__tests__/x402-http-middleware.test.ts +4 -256
  25. package/src/core/__tests__/storage-postgres.test.ts +0 -2
  26. package/src/core/challenge-engine.ts +1 -1
  27. package/src/integrations/express.ts +233 -232
  28. package/src/integrations/fastify.ts +159 -8
  29. package/src/integrations/hono.ts +164 -12
  30. package/src/integrations/settlement.ts +1 -3
  31. package/dist/integrations/x402-http-middleware.d.ts +0 -15
  32. package/dist/integrations/x402-http-middleware.d.ts.map +0 -1
  33. package/dist/integrations/x402-http-middleware.js +0 -171
  34. package/dist/integrations/x402-http-middleware.js.map +0 -1
  35. package/src/integrations/x402-http-middleware.ts +0 -246
@@ -1,10 +1,5 @@
1
1
  import { AGENT_CARD_PATH } from "@a2a-js/sdk";
2
- import {
3
- agentCardHandler,
4
- jsonRpcHandler,
5
- restHandler,
6
- UserBuilder,
7
- } from "@a2a-js/sdk/server/express";
2
+ import { agentCardHandler, jsonRpcHandler, UserBuilder } from "@a2a-js/sdk/server/express";
8
3
  import { type NextFunction, type Request, type Response, Router } from "express";
9
4
  import { createKey0, type Key0Config } from "../factory.js";
10
5
  import type { ValidateAccessTokenConfig } from "../middleware.js";
@@ -15,21 +10,22 @@ import { mountMcpRoutes } from "./mcp.js";
15
10
  import {
16
11
  buildDiscoveryResponse,
17
12
  buildHttpPaymentRequirements,
18
- createX402HttpMiddleware,
19
13
  decodePaymentSignature,
20
14
  settlePayment,
21
- } from "./x402-http-middleware.js";
15
+ } from "./settlement.js";
22
16
 
23
17
  /**
24
- * Create an Express router that serves the agent card and A2A endpoint.
18
+ * Create an Express router that serves the agent card and the unified x402 endpoint.
25
19
  *
26
20
  * Usage:
27
21
  * app.use(key0Router({ config, adapter }));
28
22
  *
29
23
  * This auto-serves:
30
- * GET /.well-known/agent.json
31
- * POST {config.basePath}/jsonrpc (A2A JSON-RPC)
32
- * POST {config.basePath}/access (Simple x402 HTTP)
24
+ * GET /.well-known/agent.json — A2A agent card (discovery)
25
+ * POST /x402/access — unified endpoint
26
+ * • X-A2A-Extensions header present delegates to A2A JSON-RPC handler
27
+ * • No header → x402 HTTP flow (discovery / challenge / settle)
28
+ * POST /mcp — MCP Streamable HTTP (when mcp: true)
33
29
  */
34
30
  export function key0Router(opts: Key0Config): Router {
35
31
  const { requestHandler, engine } = createKey0(opts);
@@ -40,256 +36,261 @@ export function key0Router(opts: Key0Config): Router {
40
36
  router.use(`/${AGENT_CARD_PATH}`, agentCardHandler({ agentCardProvider: requestHandler }));
41
37
  router.use("/.well-known/agent.json", agentCardHandler({ agentCardProvider: requestHandler }));
42
38
 
43
- // A2A endpoint with x402 middleware
44
- const basePath = opts.config.basePath ?? "/a2a";
45
- router.use(
46
- `${basePath}/jsonrpc`,
47
- createX402HttpMiddleware(engine, opts.config), // x402 HTTP middleware (before A2A handler)
48
- jsonRpcHandler({ requestHandler, userBuilder: UserBuilder.noAuthentication }),
49
- );
50
- router.use(
51
- `${basePath}/rest`,
52
- restHandler({ requestHandler, userBuilder: UserBuilder.noAuthentication }),
53
- );
39
+ // Unified x402 endpoint with A2A JSON-RPC fallback
40
+ // When X-A2A-Extensions header is present, the request is delegated to the
41
+ // A2A JSON-RPC handler (executor). Otherwise, it is handled as plain x402 HTTP.
42
+ router.post(
43
+ `/x402/access`,
44
+ async (req: Request, res: Response, next: NextFunction) => {
45
+ // ===== A2A-native clients: delegate to JSON-RPC handler =====
46
+ if (req.headers["x-a2a-extensions"]) {
47
+ console.log(
48
+ "[x402-access] X-A2A-Extensions header detected → delegating to JSON-RPC handler",
49
+ );
50
+ return next();
51
+ }
54
52
 
55
- // Simple x402 HTTP endpoint (no JSON-RPC wrapping)
56
- router.post(`/x402/access`, async (req: Request, res: Response) => {
57
- const startTime = Date.now();
58
- try {
59
- console.log("\n[x402-access] ========== NEW REQUEST ==========");
60
- console.log(`[x402-access] Timestamp: ${new Date().toISOString()}`);
61
- console.log(`[x402-access] Method: ${req.method}`);
62
- console.log(`[x402-access] URL: ${req.url}`);
63
- console.log("[x402-access] Body:", JSON.stringify(req.body, null, 2));
64
- console.log("[x402-access] Headers:", JSON.stringify(req.headers, null, 2));
65
-
66
- // Parse body (allow empty for discovery)
67
- const body = req.body || {};
68
- let { planId, resourceId = "default" } = body;
69
- let { requestId } = body;
70
-
71
- // Check for PAYMENT-SIGNATURE header
72
- const paymentSignature = req.headers["payment-signature"] as string | undefined;
73
- console.log(`[x402-access] PAYMENT-SIGNATURE present: ${!!paymentSignature}`);
74
-
75
- // ===== x402 shortcut: extract planId from PAYMENT-SIGNATURE if not in body =====
76
- // Standard x402 clients replay the same request with PAYMENT-SIGNATURE header.
77
- // The planId is embedded in accepted.extra.planId within the signed payload.
78
- if (!planId && paymentSignature) {
79
- try {
80
- const decoded = decodePaymentSignature(paymentSignature);
81
- const sigTierId = decoded.accepted?.extra?.["planId"] as string | undefined;
82
- if (sigTierId) {
53
+ const startTime = Date.now();
54
+ try {
55
+ console.log("\n[x402-access] ========== NEW REQUEST ==========");
56
+ console.log(`[x402-access] Timestamp: ${new Date().toISOString()}`);
57
+ console.log(`[x402-access] Method: ${req.method}`);
58
+ console.log(`[x402-access] URL: ${req.url}`);
59
+ console.log("[x402-access] Body:", JSON.stringify(req.body, null, 2));
60
+ console.log("[x402-access] Headers:", JSON.stringify(req.headers, null, 2));
61
+
62
+ // Parse body (allow empty for discovery)
63
+ const body = req.body || {};
64
+ let { planId, resourceId = "default" } = body;
65
+ let { requestId } = body;
66
+
67
+ // Check for PAYMENT-SIGNATURE header
68
+ const paymentSignature = req.headers["payment-signature"] as string | undefined;
69
+ console.log(`[x402-access] PAYMENT-SIGNATURE present: ${!!paymentSignature}`);
70
+
71
+ // ===== x402 shortcut: extract planId from PAYMENT-SIGNATURE if not in body =====
72
+ // Standard x402 clients replay the same request with PAYMENT-SIGNATURE header.
73
+ // The planId is embedded in accepted.extra.planId within the signed payload.
74
+ if (!planId && paymentSignature) {
75
+ try {
76
+ const decoded = decodePaymentSignature(paymentSignature);
77
+ const sigTierId = decoded.accepted?.extra?.["planId"] as string | undefined;
78
+ if (sigTierId) {
79
+ console.log(
80
+ `[x402-access] ✓ Extracted planId="${sigTierId}" from PAYMENT-SIGNATURE (x402 replay)`,
81
+ );
82
+ planId = sigTierId;
83
+ }
84
+ } catch (err) {
83
85
  console.log(
84
- `[x402-access] Extracted planId="${sigTierId}" from PAYMENT-SIGNATURE (x402 replay)`,
86
+ `[x402-access] Failed to decode PAYMENT-SIGNATURE for planId extraction: ${err}`,
85
87
  );
86
- planId = sigTierId;
88
+ // Fall through to discovery — the signature might be malformed
87
89
  }
88
- } catch (err) {
89
- console.log(
90
- `[x402-access] ⚠ Failed to decode PAYMENT-SIGNATURE for planId extraction: ${err}`,
91
- );
92
- // Fall through to discovery — the signature might be malformed
93
90
  }
94
- }
95
91
 
96
- // ===== CASE 1: No planId → Discovery (402 with all tiers, no PENDING record) =====
97
- if (!planId) {
98
- console.log("[x402-access] → CASE 1: No planId provided, returning discovery 402");
92
+ // ===== CASE 1: No planId → Discovery (402 with all tiers, no PENDING record) =====
93
+ if (!planId) {
94
+ console.log("[x402-access] → CASE 1: No planId provided, returning discovery 402");
99
95
 
100
- const discoveryResponse = buildDiscoveryResponse(opts.config, networkConfig);
101
- console.log(
102
- "[x402-access] Discovery response:",
103
- JSON.stringify(discoveryResponse, null, 2),
104
- );
105
-
106
- // Encode as base64 for PAYMENT-REQUIRED header
107
- const encoded = Buffer.from(JSON.stringify(discoveryResponse)).toString("base64");
108
- res.setHeader("payment-required", encoded);
96
+ const discoveryResponse = buildDiscoveryResponse(opts.config, networkConfig);
97
+ console.log(
98
+ "[x402-access] Discovery response:",
99
+ JSON.stringify(discoveryResponse, null, 2),
100
+ );
109
101
 
110
- // Add www-authenticate header for HTTP spec compliance
111
- const authHeader = `Payment realm="${opts.config.agentUrl}", accept="exact"`;
112
- res.setHeader("www-authenticate", authHeader);
102
+ // Encode as base64 for PAYMENT-REQUIRED header
103
+ const encoded = Buffer.from(JSON.stringify(discoveryResponse)).toString("base64");
104
+ res.setHeader("payment-required", encoded);
113
105
 
114
- console.log(`[x402-access] payment-required header set (${encoded.length} bytes)`);
115
- console.log(`[x402-access] www-authenticate header set`);
116
- console.log("[x402-access] → Returning HTTP 402 Payment Required (Discovery)");
106
+ // Add www-authenticate header for HTTP spec compliance
107
+ const authHeader = `Payment realm="${opts.config.agentUrl}", accept="exact"`;
108
+ res.setHeader("www-authenticate", authHeader);
117
109
 
118
- return res.status(402).json({
119
- ...discoveryResponse,
120
- error: "Payment required",
121
- });
122
- }
110
+ console.log(`[x402-access] payment-required header set (${encoded.length} bytes)`);
111
+ console.log(`[x402-access] www-authenticate header set`);
112
+ console.log("[x402-access] → Returning HTTP 402 Payment Required (Discovery)");
123
113
 
124
- // Auto-generate requestId if not provided (for simplicity)
125
- if (!requestId) {
126
- requestId = `http-${crypto.randomUUID()}`;
127
- console.log(`[x402-access] Auto-generated requestId: ${requestId}`);
128
- }
114
+ return res.status(402).json({
115
+ ...discoveryResponse,
116
+ error: "Payment required",
117
+ });
118
+ }
129
119
 
130
- console.log(
131
- `[x402-access] Parsed request - planId: ${planId}, requestId: ${requestId}, resourceId: ${resourceId}`,
132
- );
120
+ // Auto-generate requestId if not provided (for simplicity)
121
+ if (!requestId) {
122
+ requestId = `http-${crypto.randomUUID()}`;
123
+ console.log(`[x402-access] Auto-generated requestId: ${requestId}`);
124
+ }
133
125
 
134
- // ===== CASE 2: planId present, no PAYMENT-SIGNATURE → Challenge (402 + PENDING record) =====
135
- if (!paymentSignature) {
136
126
  console.log(
137
- "[x402-access] CASE 2: planId provided, no PAYMENT-SIGNATURE, issuing 402 challenge",
127
+ `[x402-access] Parsed request - planId: ${planId}, requestId: ${requestId}, resourceId: ${resourceId}`,
138
128
  );
139
- console.log(`[x402-access] Creating PENDING record for requestId: ${requestId}`);
140
129
 
141
- // Create PENDING record via engine (handles tier/resource validation and idempotency)
142
- const { challengeId } = await engine.requestHttpAccess(requestId, planId, resourceId);
143
- console.log(`[x402-access] ✓ PENDING record created, challengeId=${challengeId}`);
144
-
145
- // Build payment requirements with schema
146
- console.log(`[x402-access] Building payment requirements for tier: ${planId}`);
147
- const requirements: X402PaymentRequiredResponse = buildHttpPaymentRequirements(
148
- planId,
149
- resourceId,
150
- opts.config,
151
- networkConfig,
152
- {
153
- inputSchema: {
154
- type: "object",
155
- properties: {
156
- planId: {
157
- type: "string",
158
- description: `Tier to purchase. Must be '${planId}'`,
159
- },
160
- requestId: {
161
- type: "string",
162
- description: "Client-generated UUID for idempotency (auto-generated if omitted)",
163
- },
164
- resourceId: {
165
- type: "string",
166
- description: "Optional: Specific resource identifier (defaults to 'default')",
130
+ // ===== CASE 2: planId present, no PAYMENT-SIGNATURE → Challenge (402 + PENDING record) =====
131
+ if (!paymentSignature) {
132
+ console.log(
133
+ "[x402-access] → CASE 2: planId provided, no PAYMENT-SIGNATURE, issuing 402 challenge",
134
+ );
135
+ console.log(`[x402-access] Creating PENDING record for requestId: ${requestId}`);
136
+
137
+ // Create PENDING record via engine (handles tier/resource validation and idempotency)
138
+ const { challengeId } = await engine.requestHttpAccess(requestId, planId, resourceId);
139
+ console.log(`[x402-access] ✓ PENDING record created, challengeId=${challengeId}`);
140
+
141
+ // Build payment requirements with schema
142
+ console.log(`[x402-access] Building payment requirements for tier: ${planId}`);
143
+ const requirements: X402PaymentRequiredResponse = buildHttpPaymentRequirements(
144
+ planId,
145
+ resourceId,
146
+ opts.config,
147
+ networkConfig,
148
+ {
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {
152
+ planId: {
153
+ type: "string",
154
+ description: `Tier to purchase. Must be '${planId}'`,
155
+ },
156
+ requestId: {
157
+ type: "string",
158
+ description:
159
+ "Client-generated UUID for idempotency (auto-generated if omitted)",
160
+ },
161
+ resourceId: {
162
+ type: "string",
163
+ description: "Optional: Specific resource identifier (defaults to 'default')",
164
+ },
167
165
  },
166
+ required: ["planId"],
168
167
  },
169
- required: ["planId"],
170
- },
171
- outputSchema: {
172
- type: "object",
173
- properties: {
174
- accessToken: { type: "string", description: "JWT token for API access" },
175
- tokenType: { type: "string", description: "Token type (usually 'Bearer')" },
176
- expiresAt: { type: "string", description: "ISO 8601 expiration timestamp" },
177
- resourceEndpoint: {
178
- type: "string",
179
- description: "URL to access the protected resource",
168
+ outputSchema: {
169
+ type: "object",
170
+ properties: {
171
+ accessToken: { type: "string", description: "JWT token for API access" },
172
+ tokenType: { type: "string", description: "Token type (usually 'Bearer')" },
173
+ expiresAt: { type: "string", description: "ISO 8601 expiration timestamp" },
174
+ resourceEndpoint: {
175
+ type: "string",
176
+ description: "URL to access the protected resource",
177
+ },
178
+ txHash: { type: "string", description: "On-chain transaction hash" },
179
+ explorerUrl: { type: "string", description: "Blockchain explorer URL" },
180
180
  },
181
- txHash: { type: "string", description: "On-chain transaction hash" },
182
- explorerUrl: { type: "string", description: "Blockchain explorer URL" },
183
181
  },
182
+ description: `Access to ${resourceId} via ${planId} tier`,
184
183
  },
185
- description: `Access to ${resourceId} via ${planId} tier`,
186
- },
187
- );
188
- console.log(`[x402-access] Payment requirements:`, JSON.stringify(requirements, null, 2));
184
+ );
185
+ console.log(`[x402-access] Payment requirements:`, JSON.stringify(requirements, null, 2));
186
+
187
+ // Encode as base64 for payment-required header
188
+ const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
189
+ res.setHeader("payment-required", encoded);
190
+
191
+ // Add www-authenticate header
192
+ const authHeader = `Payment realm="${opts.config.agentUrl}", accept="exact", challenge="${challengeId}"`;
193
+ res.setHeader("www-authenticate", authHeader);
194
+
195
+ console.log(`[x402-access] payment-required header set (${encoded.length} bytes)`);
196
+ console.log(`[x402-access] www-authenticate header set`);
197
+ console.log("[x402-access] → Returning HTTP 402 Payment Required (Challenge)");
198
+
199
+ return res.status(402).json({
200
+ ...requirements,
201
+ challengeId,
202
+ requestId,
203
+ error: "Payment required",
204
+ });
205
+ }
189
206
 
190
- // Encode as base64 for payment-required header
191
- const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
192
- res.setHeader("payment-required", encoded);
207
+ // ===== CASE 3: planId + PAYMENT-SIGNATURE → Settle and return access grant =====
208
+ console.log("[x402-access] CASE 3: Processing payment");
209
+ console.log(
210
+ `[x402-access] PAYMENT-SIGNATURE header received (${paymentSignature.length} bytes)`,
211
+ );
193
212
 
194
- // Add www-authenticate header
195
- const authHeader = `Payment realm="${opts.config.agentUrl}", accept="exact", challenge="${challengeId}"`;
196
- res.setHeader("www-authenticate", authHeader);
213
+ // Pre-settlement check: avoid burning USDC if already delivered/expired/cancelled
214
+ console.log("[x402-access] Pre-settlement check for requestId:", requestId);
215
+ const existingGrant = await engine.preSettlementCheck(requestId);
216
+ if (existingGrant) {
217
+ console.log("[x402-access] ✓ Already delivered, returning cached grant");
218
+ return res.status(200).json(existingGrant);
219
+ }
197
220
 
198
- console.log(`[x402-access] payment-required header set (${encoded.length} bytes)`);
199
- console.log(`[x402-access] www-authenticate header set`);
200
- console.log("[x402-access] Returning HTTP 402 Payment Required (Challenge)");
221
+ // Decode header then settle via shared settlement layer
222
+ console.log("[x402-access] Decoding payment signature...");
223
+ const paymentPayload = decodePaymentSignature(paymentSignature);
224
+ console.log(
225
+ "[x402-access] Payment payload decoded:",
226
+ JSON.stringify(paymentPayload, null, 2),
227
+ );
201
228
 
202
- return res.status(402).json({
203
- ...requirements,
204
- challengeId,
205
- error: "Payment required",
206
- });
207
- }
229
+ console.log("[x402-access] Settling payment on-chain...");
230
+ const { txHash, settleResponse, payer } = await settlePayment(
231
+ paymentPayload,
232
+ opts.config,
233
+ networkConfig,
234
+ );
208
235
 
209
- // ===== CASE 3: planId + PAYMENT-SIGNATURE Settle and return access grant =====
210
- console.log("[x402-access] CASE 3: Processing payment");
211
- console.log(
212
- `[x402-access] PAYMENT-SIGNATURE header received (${paymentSignature.length} bytes)`,
213
- );
214
-
215
- // Pre-settlement check: avoid burning USDC if already delivered/expired/cancelled
216
- console.log("[x402-access] Pre-settlement check for requestId:", requestId);
217
- const existingGrant = await engine.preSettlementCheck(requestId);
218
- if (existingGrant) {
219
- console.log("[x402-access] ✓ Already delivered, returning cached grant");
220
- return res.status(200).json(existingGrant);
221
- }
236
+ console.log(`[x402-access] Payment settled successfully`);
237
+ console.log(`[x402-access] - Transaction Hash: ${txHash}`);
238
+ console.log(`[x402-access] - Payer: ${payer}`);
239
+ console.log(`[x402-access] - Settle Response:`, JSON.stringify(settleResponse, null, 2));
222
240
 
223
- // Decode header then settle via shared settlement layer
224
- console.log("[x402-access] Decoding payment signature...");
225
- const paymentPayload = decodePaymentSignature(paymentSignature);
226
- console.log(
227
- "[x402-access] Payment payload decoded:",
228
- JSON.stringify(paymentPayload, null, 2),
229
- );
230
-
231
- console.log("[x402-access] Settling payment on-chain...");
232
- const { txHash, settleResponse, payer } = await settlePayment(
233
- paymentPayload,
234
- opts.config,
235
- networkConfig,
236
- );
237
-
238
- console.log(`[x402-access] Payment settled successfully`);
239
- console.log(`[x402-access] - Transaction Hash: ${txHash}`);
240
- console.log(`[x402-access] - Payer: ${payer}`);
241
- console.log(`[x402-access] - Settle Response:`, JSON.stringify(settleResponse, null, 2));
242
-
243
- // Process payment with full lifecycle tracking (PENDING PAID → DELIVERED)
244
- console.log(`[x402-access] Processing payment for requestId: ${requestId}`);
245
- const grant = await engine.processHttpPayment(
246
- requestId,
247
- planId,
248
- resourceId,
249
- txHash,
250
- payer as `0x${string}` | undefined,
251
- );
252
- console.log("[x402-access] ✓ Access grant issued successfully");
253
- console.log("[x402-access] Grant details:", JSON.stringify(grant, null, 2));
254
-
255
- // Set payment-response header
256
- const paymentResponse = Buffer.from(JSON.stringify(settleResponse)).toString("base64");
257
- res.setHeader("payment-response", paymentResponse);
258
- console.log(`[x402-access] payment-response header set (${paymentResponse.length} bytes)`);
259
-
260
- console.log("[x402-access] → Returning HTTP 200 OK with access grant");
261
- return res.status(200).json(grant);
262
- } catch (err: unknown) {
263
- const elapsed = Date.now() - startTime;
264
- console.error(`[x402-access] ✗ Error occurred after ${elapsed}ms`);
265
- console.error("[x402-access] Error type:", err?.constructor?.name || typeof err);
266
- console.error("[x402-access] Error details:", err);
267
-
268
- if (err instanceof Error) {
269
- console.error("[x402-access] Error message:", err.message);
270
- console.error("[x402-access] Error stack:", err.stack);
271
- }
241
+ // Process payment with full lifecycle tracking (PENDING → PAID → DELIVERED)
242
+ console.log(`[x402-access] Processing payment for requestId: ${requestId}`);
243
+ const grant = await engine.processHttpPayment(
244
+ requestId,
245
+ planId,
246
+ resourceId,
247
+ txHash,
248
+ payer as `0x${string}` | undefined,
249
+ );
250
+ console.log("[x402-access] Access grant issued successfully");
251
+ console.log("[x402-access] Grant details:", JSON.stringify(grant, null, 2));
252
+
253
+ // Set payment-response header
254
+ const paymentResponse = Buffer.from(JSON.stringify(settleResponse)).toString("base64");
255
+ res.setHeader("payment-response", paymentResponse);
256
+ console.log(`[x402-access] payment-response header set (${paymentResponse.length} bytes)`);
257
+
258
+ console.log("[x402-access] Returning HTTP 200 OK with access grant");
259
+ return res.status(200).json(grant);
260
+ } catch (err: unknown) {
261
+ const elapsed = Date.now() - startTime;
262
+ console.error(`[x402-access] Error occurred after ${elapsed}ms`);
263
+ console.error("[x402-access] Error type:", err?.constructor?.name || typeof err);
264
+ console.error("[x402-access] Error details:", err);
265
+
266
+ if (err instanceof Error) {
267
+ console.error("[x402-access] Error message:", err.message);
268
+ console.error("[x402-access] Error stack:", err.stack);
269
+ }
272
270
 
273
- if (err instanceof Key0Error) {
274
- console.error(`[x402-access] Key0 error: ${err.code} (HTTP ${err.httpStatus})`);
275
- // Return the grant directly for PROOF_ALREADY_REDEEMED (status 200)
276
- if (err.code === "PROOF_ALREADY_REDEEMED" && err.details?.["grant"]) {
277
- return res.status(200).json(err.details["grant"]);
271
+ if (err instanceof Key0Error) {
272
+ console.error(`[x402-access] Key0 error: ${err.code} (HTTP ${err.httpStatus})`);
273
+ // Return the grant directly for PROOF_ALREADY_REDEEMED (status 200)
274
+ if (err.code === "PROOF_ALREADY_REDEEMED" && err.details?.["grant"]) {
275
+ return res.status(200).json(err.details["grant"]);
276
+ }
277
+ return res.status(err.httpStatus).json(err.toJSON());
278
278
  }
279
- return res.status(err.httpStatus).json(err.toJSON());
280
- }
281
279
 
282
- console.error("[x402-access] Returning 500 Internal Server Error");
283
- return res.status(500).json({
284
- error: "INTERNAL_ERROR",
285
- message: err instanceof Error ? err.message : "Internal server error",
286
- });
287
- } finally {
288
- const elapsed = Date.now() - startTime;
289
- console.log(`[x402-access] Request completed in ${elapsed}ms`);
290
- console.log("[x402-access] ========== REQUEST COMPLETE ==========\n");
291
- }
292
- });
280
+ console.error("[x402-access] Returning 500 Internal Server Error");
281
+ return res.status(500).json({
282
+ error: "INTERNAL_ERROR",
283
+ message: err instanceof Error ? err.message : "Internal server error",
284
+ });
285
+ } finally {
286
+ const elapsed = Date.now() - startTime;
287
+ console.log(`[x402-access] Request completed in ${elapsed}ms`);
288
+ console.log("[x402-access] ========== REQUEST COMPLETE ==========\n");
289
+ }
290
+ },
291
+ // A2A JSON-RPC fallback: reached when X-A2A-Extensions header is present
292
+ jsonRpcHandler({ requestHandler, userBuilder: UserBuilder.noAuthentication }),
293
+ );
293
294
 
294
295
  // MCP routes (when mcp: true)
295
296
  if (opts.config.mcp) {