@key0ai/key0 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -21
- package/dist/__tests__/e2e.test.js +1 -1
- package/dist/__tests__/e2e.test.js.map +1 -1
- 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__/agent-card.test.js +14 -12
- package/dist/core/__tests__/agent-card.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/agent-card.d.ts.map +1 -1
- package/dist/core/agent-card.js +25 -10
- package/dist/core/agent-card.js.map +1 -1
- package/dist/core/challenge-engine.js +1 -1
- package/dist/core/challenge-engine.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.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 +27 -29
- 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 +117 -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 +116 -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/dist/types/agent-card.d.ts +16 -0
- package/dist/types/agent-card.d.ts.map +1 -1
- package/package.json +3 -1
- package/src/__tests__/e2e.test.ts +1 -1
- package/src/__tests__/x402-http-middleware.test.ts +4 -256
- package/src/core/__tests__/agent-card.test.ts +15 -12
- package/src/core/__tests__/storage-postgres.test.ts +0 -2
- package/src/core/agent-card.ts +26 -10
- package/src/core/challenge-engine.ts +1 -1
- package/src/core/index.ts +1 -1
- package/src/integrations/express.ts +221 -235
- package/src/integrations/fastify.ts +160 -8
- package/src/integrations/hono.ts +168 -12
- package/src/integrations/settlement.ts +1 -3
- package/src/types/agent-card.ts +13 -2
- package/src/types/config.ts +1 -1
- 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,255 +36,245 @@ 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
|
-
|
|
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");
|
|
99
|
-
|
|
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);
|
|
109
91
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return res.status(402).json({
|
|
119
|
-
...discoveryResponse,
|
|
120
|
-
error: "Payment required",
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
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
|
-
}
|
|
92
|
+
// ===== CASE 1: No planId → 400 pointing to GET /discovery =====
|
|
93
|
+
if (!planId) {
|
|
94
|
+
return res.status(400).json({
|
|
95
|
+
error:
|
|
96
|
+
"Please select a plan from the discovery API response to purchase access. Endpoint: GET /discovery",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
129
99
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
100
|
+
// Auto-generate requestId if not provided (for simplicity)
|
|
101
|
+
if (!requestId) {
|
|
102
|
+
requestId = `http-${crypto.randomUUID()}`;
|
|
103
|
+
console.log(`[x402-access] Auto-generated requestId: ${requestId}`);
|
|
104
|
+
}
|
|
133
105
|
|
|
134
|
-
// ===== CASE 2: planId present, no PAYMENT-SIGNATURE → Challenge (402 + PENDING record) =====
|
|
135
|
-
if (!paymentSignature) {
|
|
136
106
|
console.log(
|
|
137
|
-
|
|
107
|
+
`[x402-access] Parsed request - planId: ${planId}, requestId: ${requestId}, resourceId: ${resourceId}`,
|
|
138
108
|
);
|
|
139
|
-
console.log(`[x402-access] Creating PENDING record for requestId: ${requestId}`);
|
|
140
109
|
|
|
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
|
-
|
|
110
|
+
// ===== CASE 2: planId present, no PAYMENT-SIGNATURE → Challenge (402 + PENDING record) =====
|
|
111
|
+
if (!paymentSignature) {
|
|
112
|
+
console.log(
|
|
113
|
+
"[x402-access] → CASE 2: planId provided, no PAYMENT-SIGNATURE, issuing 402 challenge",
|
|
114
|
+
);
|
|
115
|
+
console.log(`[x402-access] Creating PENDING record for requestId: ${requestId}`);
|
|
116
|
+
|
|
117
|
+
// Create PENDING record via engine (handles tier/resource validation and idempotency)
|
|
118
|
+
const { challengeId } = await engine.requestHttpAccess(requestId, planId, resourceId);
|
|
119
|
+
console.log(`[x402-access] ✓ PENDING record created, challengeId=${challengeId}`);
|
|
120
|
+
|
|
121
|
+
// Build payment requirements with schema
|
|
122
|
+
console.log(`[x402-access] Building payment requirements for tier: ${planId}`);
|
|
123
|
+
const requirements: X402PaymentRequiredResponse = buildHttpPaymentRequirements(
|
|
124
|
+
planId,
|
|
125
|
+
resourceId,
|
|
126
|
+
opts.config,
|
|
127
|
+
networkConfig,
|
|
128
|
+
{
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
planId: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: `Tier to purchase. Must be '${planId}'`,
|
|
135
|
+
},
|
|
136
|
+
requestId: {
|
|
137
|
+
type: "string",
|
|
138
|
+
description:
|
|
139
|
+
"Client-generated UUID for idempotency (auto-generated if omitted)",
|
|
140
|
+
},
|
|
141
|
+
resourceId: {
|
|
142
|
+
type: "string",
|
|
143
|
+
description: "Optional: Specific resource identifier (defaults to 'default')",
|
|
144
|
+
},
|
|
167
145
|
},
|
|
146
|
+
required: ["planId"],
|
|
168
147
|
},
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
148
|
+
outputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
accessToken: { type: "string", description: "JWT token for API access" },
|
|
152
|
+
tokenType: { type: "string", description: "Token type (usually 'Bearer')" },
|
|
153
|
+
expiresAt: { type: "string", description: "ISO 8601 expiration timestamp" },
|
|
154
|
+
resourceEndpoint: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "URL to access the protected resource",
|
|
157
|
+
},
|
|
158
|
+
txHash: { type: "string", description: "On-chain transaction hash" },
|
|
159
|
+
explorerUrl: { type: "string", description: "Blockchain explorer URL" },
|
|
180
160
|
},
|
|
181
|
-
txHash: { type: "string", description: "On-chain transaction hash" },
|
|
182
|
-
explorerUrl: { type: "string", description: "Blockchain explorer URL" },
|
|
183
161
|
},
|
|
162
|
+
description: `Access to ${resourceId} via ${planId} tier`,
|
|
184
163
|
},
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
164
|
+
);
|
|
165
|
+
console.log(`[x402-access] Payment requirements:`, JSON.stringify(requirements, null, 2));
|
|
166
|
+
|
|
167
|
+
// Encode as base64 for payment-required header
|
|
168
|
+
const encoded = Buffer.from(JSON.stringify(requirements)).toString("base64");
|
|
169
|
+
res.setHeader("payment-required", encoded);
|
|
170
|
+
|
|
171
|
+
// Add www-authenticate header
|
|
172
|
+
const authHeader = `Payment realm="${opts.config.agentUrl}", accept="exact", challenge="${challengeId}"`;
|
|
173
|
+
res.setHeader("www-authenticate", authHeader);
|
|
174
|
+
|
|
175
|
+
console.log(`[x402-access] payment-required header set (${encoded.length} bytes)`);
|
|
176
|
+
console.log(`[x402-access] www-authenticate header set`);
|
|
177
|
+
console.log("[x402-access] → Returning HTTP 402 Payment Required (Challenge)");
|
|
178
|
+
|
|
179
|
+
return res.status(402).json({
|
|
180
|
+
...requirements,
|
|
181
|
+
challengeId,
|
|
182
|
+
requestId,
|
|
183
|
+
error: "Payment required",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
189
186
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
187
|
+
// ===== CASE 3: planId + PAYMENT-SIGNATURE → Settle and return access grant =====
|
|
188
|
+
console.log("[x402-access] → CASE 3: Processing payment");
|
|
189
|
+
console.log(
|
|
190
|
+
`[x402-access] PAYMENT-SIGNATURE header received (${paymentSignature.length} bytes)`,
|
|
191
|
+
);
|
|
193
192
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
// Pre-settlement check: avoid burning USDC if already delivered/expired/cancelled
|
|
194
|
+
console.log("[x402-access] Pre-settlement check for requestId:", requestId);
|
|
195
|
+
const existingGrant = await engine.preSettlementCheck(requestId);
|
|
196
|
+
if (existingGrant) {
|
|
197
|
+
console.log("[x402-access] ✓ Already delivered, returning cached grant");
|
|
198
|
+
return res.status(200).json(existingGrant);
|
|
199
|
+
}
|
|
197
200
|
|
|
198
|
-
|
|
199
|
-
console.log(
|
|
200
|
-
|
|
201
|
+
// Decode header then settle via shared settlement layer
|
|
202
|
+
console.log("[x402-access] Decoding payment signature...");
|
|
203
|
+
const paymentPayload = decodePaymentSignature(paymentSignature);
|
|
204
|
+
console.log(
|
|
205
|
+
"[x402-access] Payment payload decoded:",
|
|
206
|
+
JSON.stringify(paymentPayload, null, 2),
|
|
207
|
+
);
|
|
201
208
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
console.log("[x402-access] Settling payment on-chain...");
|
|
210
|
+
const { txHash, settleResponse, payer } = await settlePayment(
|
|
211
|
+
paymentPayload,
|
|
212
|
+
opts.config,
|
|
213
|
+
networkConfig,
|
|
214
|
+
);
|
|
208
215
|
|
|
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
|
-
}
|
|
216
|
+
console.log(`[x402-access] ✓ Payment settled successfully`);
|
|
217
|
+
console.log(`[x402-access] - Transaction Hash: ${txHash}`);
|
|
218
|
+
console.log(`[x402-access] - Payer: ${payer}`);
|
|
219
|
+
console.log(`[x402-access] - Settle Response:`, JSON.stringify(settleResponse, null, 2));
|
|
222
220
|
|
|
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
|
-
}
|
|
221
|
+
// Process payment with full lifecycle tracking (PENDING → PAID → DELIVERED)
|
|
222
|
+
console.log(`[x402-access] Processing payment for requestId: ${requestId}`);
|
|
223
|
+
const grant = await engine.processHttpPayment(
|
|
224
|
+
requestId,
|
|
225
|
+
planId,
|
|
226
|
+
resourceId,
|
|
227
|
+
txHash,
|
|
228
|
+
payer as `0x${string}` | undefined,
|
|
229
|
+
);
|
|
230
|
+
console.log("[x402-access] ✓ Access grant issued successfully");
|
|
231
|
+
console.log("[x402-access] Grant details:", JSON.stringify(grant, null, 2));
|
|
232
|
+
|
|
233
|
+
// Set payment-response header
|
|
234
|
+
const paymentResponse = Buffer.from(JSON.stringify(settleResponse)).toString("base64");
|
|
235
|
+
res.setHeader("payment-response", paymentResponse);
|
|
236
|
+
console.log(`[x402-access] payment-response header set (${paymentResponse.length} bytes)`);
|
|
237
|
+
|
|
238
|
+
console.log("[x402-access] → Returning HTTP 200 OK with access grant");
|
|
239
|
+
return res.status(200).json(grant);
|
|
240
|
+
} catch (err: unknown) {
|
|
241
|
+
const elapsed = Date.now() - startTime;
|
|
242
|
+
console.error(`[x402-access] ✗ Error occurred after ${elapsed}ms`);
|
|
243
|
+
console.error("[x402-access] Error type:", err?.constructor?.name || typeof err);
|
|
244
|
+
console.error("[x402-access] Error details:", err);
|
|
245
|
+
|
|
246
|
+
if (err instanceof Error) {
|
|
247
|
+
console.error("[x402-access] Error message:", err.message);
|
|
248
|
+
console.error("[x402-access] Error stack:", err.stack);
|
|
249
|
+
}
|
|
272
250
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
251
|
+
if (err instanceof Key0Error) {
|
|
252
|
+
console.error(`[x402-access] Key0 error: ${err.code} (HTTP ${err.httpStatus})`);
|
|
253
|
+
// Return the grant directly for PROOF_ALREADY_REDEEMED (status 200)
|
|
254
|
+
if (err.code === "PROOF_ALREADY_REDEEMED" && err.details?.["grant"]) {
|
|
255
|
+
return res.status(200).json(err.details["grant"]);
|
|
256
|
+
}
|
|
257
|
+
return res.status(err.httpStatus).json(err.toJSON());
|
|
278
258
|
}
|
|
279
|
-
|
|
259
|
+
|
|
260
|
+
console.error("[x402-access] Returning 500 Internal Server Error");
|
|
261
|
+
return res.status(500).json({
|
|
262
|
+
error: "INTERNAL_ERROR",
|
|
263
|
+
message: err instanceof Error ? err.message : "Internal server error",
|
|
264
|
+
});
|
|
265
|
+
} finally {
|
|
266
|
+
const elapsed = Date.now() - startTime;
|
|
267
|
+
console.log(`[x402-access] Request completed in ${elapsed}ms`);
|
|
268
|
+
console.log("[x402-access] ========== REQUEST COMPLETE ==========\n");
|
|
280
269
|
}
|
|
270
|
+
},
|
|
271
|
+
// A2A JSON-RPC fallback: reached when X-A2A-Extensions header is present
|
|
272
|
+
jsonRpcHandler({ requestHandler, userBuilder: UserBuilder.noAuthentication }),
|
|
273
|
+
);
|
|
281
274
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
}
|
|
275
|
+
router.get("/discovery", (_req: Request, res: Response) => {
|
|
276
|
+
const discoveryResponse = buildDiscoveryResponse(opts.config, networkConfig);
|
|
277
|
+
return res.status(200).json({ discoveryResponse });
|
|
292
278
|
});
|
|
293
279
|
|
|
294
280
|
// MCP routes (when mcp: true)
|