@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.
- package/README.md +29 -14
- package/dist/__tests__/x402-http-middleware.test.js +4 -207
- package/dist/__tests__/x402-http-middleware.test.js.map +1 -1
- package/dist/core/__tests__/storage-postgres.test.js +0 -1
- package/dist/core/__tests__/storage-postgres.test.js.map +1 -1
- package/dist/core/challenge-engine.js +1 -1
- package/dist/core/challenge-engine.js.map +1 -1
- package/dist/integrations/express.d.ts +6 -4
- package/dist/integrations/express.d.ts.map +1 -1
- package/dist/integrations/express.js +21 -14
- package/dist/integrations/express.js.map +1 -1
- package/dist/integrations/fastify.d.ts +5 -1
- package/dist/integrations/fastify.d.ts.map +1 -1
- package/dist/integrations/fastify.js +116 -8
- package/dist/integrations/fastify.js.map +1 -1
- package/dist/integrations/hono.d.ts +8 -2
- package/dist/integrations/hono.d.ts.map +1 -1
- package/dist/integrations/hono.js +115 -12
- package/dist/integrations/hono.js.map +1 -1
- package/dist/integrations/settlement.d.ts.map +1 -1
- package/dist/integrations/settlement.js +1 -3
- package/dist/integrations/settlement.js.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/x402-http-middleware.test.ts +4 -256
- package/src/core/__tests__/storage-postgres.test.ts +0 -2
- package/src/core/challenge-engine.ts +1 -1
- package/src/integrations/express.ts +233 -232
- package/src/integrations/fastify.ts +159 -8
- package/src/integrations/hono.ts +164 -12
- package/src/integrations/settlement.ts +1 -3
- package/dist/integrations/x402-http-middleware.d.ts +0 -15
- package/dist/integrations/x402-http-middleware.d.ts.map +0 -1
- package/dist/integrations/x402-http-middleware.js +0 -171
- package/dist/integrations/x402-http-middleware.js.map +0 -1
- 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 "./
|
|
15
|
+
} from "./settlement.js";
|
|
22
16
|
|
|
23
17
|
/**
|
|
24
|
-
* Create an Express router that serves the agent card and
|
|
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
|
|
32
|
-
*
|
|
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
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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]
|
|
86
|
+
`[x402-access] ⚠ Failed to decode PAYMENT-SIGNATURE for planId extraction: ${err}`,
|
|
85
87
|
);
|
|
86
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
return res.status(402).json({
|
|
115
|
+
...discoveryResponse,
|
|
116
|
+
error: "Payment required",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
resourceId
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
console.log(
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
`[x402-access]
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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) {
|