@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,246 +0,0 @@
|
|
|
1
|
-
import type { NextFunction, Request, Response } from "express";
|
|
2
|
-
import type { ChallengeEngine } from "../core/index.js";
|
|
3
|
-
import { CHAIN_CONFIGS } from "../types/config-shared.js";
|
|
4
|
-
import type {
|
|
5
|
-
AccessGrant,
|
|
6
|
-
AccessRequest,
|
|
7
|
-
SellerConfig,
|
|
8
|
-
X402SettleResponse,
|
|
9
|
-
} from "../types/index.js";
|
|
10
|
-
import { Key0Error } from "../types/index.js";
|
|
11
|
-
import {
|
|
12
|
-
buildDiscoveryResponse as buildDiscoveryResponseImpl,
|
|
13
|
-
buildHttpPaymentRequirements,
|
|
14
|
-
decodePaymentSignature,
|
|
15
|
-
settlePayment,
|
|
16
|
-
} from "./settlement.js";
|
|
17
|
-
|
|
18
|
-
// x402 v2 headers
|
|
19
|
-
const PAYMENT_SIGNATURE_HEADER = "payment-signature";
|
|
20
|
-
const PAYMENT_REQUIRED_HEADER = "payment-required";
|
|
21
|
-
const PAYMENT_RESPONSE_HEADER = "payment-response";
|
|
22
|
-
const X_A2A_EXTENSIONS_HEADER = "x-a2a-extensions";
|
|
23
|
-
|
|
24
|
-
// Re-export shared settlement utilities so callers can import from a single place
|
|
25
|
-
export {
|
|
26
|
-
buildDiscoveryResponse,
|
|
27
|
-
buildHttpPaymentRequirements,
|
|
28
|
-
decodePaymentSignature,
|
|
29
|
-
settlePayment,
|
|
30
|
-
settleViaFacilitator,
|
|
31
|
-
settleViaGasWallet,
|
|
32
|
-
} from "./settlement.js";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Express middleware that intercepts AccessRequest calls on the JSON-RPC endpoint
|
|
36
|
-
* and implements the x402 HTTP flow for clients that do NOT send X-A2A-Extensions.
|
|
37
|
-
*
|
|
38
|
-
* Routing:
|
|
39
|
-
* X-A2A-Extensions present → A2A-native client → pass through to A2A JSON-RPC handler
|
|
40
|
-
* message/send + no payment-signature → HTTP 402 with PaymentRequirements
|
|
41
|
-
* message/send + payment-signature → settle → HTTP 200 with AccessGrant
|
|
42
|
-
*/
|
|
43
|
-
export function createX402HttpMiddleware(engine: ChallengeEngine, config: SellerConfig) {
|
|
44
|
-
const networkConfig = CHAIN_CONFIGS[config.network];
|
|
45
|
-
|
|
46
|
-
return async (req: Request, res: Response, next: NextFunction) => {
|
|
47
|
-
console.log("\n[x402-http-middleware] ========== NEW REQUEST ==========");
|
|
48
|
-
console.log("[x402-http-middleware] Method:", req.method);
|
|
49
|
-
console.log("[x402-http-middleware] Path:", req.path);
|
|
50
|
-
console.log("[x402-http-middleware] Headers:", JSON.stringify(req.headers, null, 2));
|
|
51
|
-
|
|
52
|
-
// Intercept response to log the body
|
|
53
|
-
const originalJson = res.json.bind(res);
|
|
54
|
-
const originalSend = res.send.bind(res);
|
|
55
|
-
|
|
56
|
-
res.json = (body: any) => {
|
|
57
|
-
console.log("[x402-http-middleware] Response Status:", res.statusCode);
|
|
58
|
-
console.log("[x402-http-middleware] Response Body:", JSON.stringify(body, null, 2));
|
|
59
|
-
return originalJson(body);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
res.send = (body: any) => {
|
|
63
|
-
console.log("[x402-http-middleware] Response Status:", res.statusCode);
|
|
64
|
-
console.log(
|
|
65
|
-
"[x402-http-middleware] Response Body:",
|
|
66
|
-
typeof body === "string" ? body : JSON.stringify(body, null, 2),
|
|
67
|
-
);
|
|
68
|
-
return originalSend(body);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
// 1. If X-A2A-Extensions header present, this is an A2A client → pass through
|
|
73
|
-
const hasA2AExtensions = req.headers[X_A2A_EXTENSIONS_HEADER];
|
|
74
|
-
console.log(`[x402-http-middleware] X-A2A-Extensions header present: ${!!hasA2AExtensions}`);
|
|
75
|
-
if (hasA2AExtensions) {
|
|
76
|
-
console.log("[x402-http-middleware] → Passing through to A2A JSON-RPC handler");
|
|
77
|
-
return next();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 2. Parse JSON-RPC body
|
|
81
|
-
const body = req.body;
|
|
82
|
-
console.log("[x402-http-middleware] Body:", JSON.stringify(body, null, 2));
|
|
83
|
-
if (!body || typeof body !== "object") {
|
|
84
|
-
console.log("[x402-http-middleware] → No valid body, passing through");
|
|
85
|
-
return next();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 3. Only intercept message/send
|
|
89
|
-
console.log(`[x402-http-middleware] Method in body: ${body.method}`);
|
|
90
|
-
if (body.method !== "message/send") {
|
|
91
|
-
console.log("[x402-http-middleware] → Not a message/send call, passing through");
|
|
92
|
-
return next();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 4. Extract AccessRequest from message parts
|
|
96
|
-
// Route on tierId presence, not on type field
|
|
97
|
-
const params = body.params;
|
|
98
|
-
if (!params || !params.message || !params.message.parts) {
|
|
99
|
-
console.log("[x402-http-middleware] → No valid message parts, passing through");
|
|
100
|
-
return next();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let accessRequest: AccessRequest | null = null;
|
|
104
|
-
|
|
105
|
-
console.log(`[x402-http-middleware] Parsing ${params.message.parts.length} message parts...`);
|
|
106
|
-
for (const part of params.message.parts) {
|
|
107
|
-
console.log(`[x402-http-middleware] - Part kind: ${part.kind}`);
|
|
108
|
-
if (part.kind === "data" && part.data && part.data.type === "AccessRequest") {
|
|
109
|
-
accessRequest = part.data as AccessRequest;
|
|
110
|
-
console.log("[x402-http-middleware] ✓ Found AccessRequest data part");
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
if (part.kind === "text") {
|
|
114
|
-
try {
|
|
115
|
-
const parsed = JSON.parse(part.text);
|
|
116
|
-
if (parsed.type === "AccessRequest") {
|
|
117
|
-
accessRequest = parsed as AccessRequest;
|
|
118
|
-
console.log("[x402-http-middleware] ✓ Found AccessRequest in text part");
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!accessRequest) {
|
|
128
|
-
console.log("[x402-http-middleware] → No data found in message parts, passing through");
|
|
129
|
-
return next();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const resourceId = accessRequest.resourceId || "default";
|
|
133
|
-
const planId = accessRequest.planId;
|
|
134
|
-
const requestId = accessRequest.requestId || `http-${crypto.randomUUID()}`;
|
|
135
|
-
console.log(
|
|
136
|
-
`[x402-http-middleware] AccessRequest: planId=${planId}, resourceId=${resourceId}, requestId=${requestId}`,
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
// 5a. Discovery case: no planId → return 402 with full product catalog
|
|
140
|
-
if (!planId) {
|
|
141
|
-
console.log("[x402-http-middleware] → No planId provided, returning discovery 402");
|
|
142
|
-
const discoveryResponse = buildDiscoveryResponseImpl(config, networkConfig);
|
|
143
|
-
const encoded = Buffer.from(JSON.stringify(discoveryResponse)).toString("base64");
|
|
144
|
-
res.setHeader(PAYMENT_REQUIRED_HEADER, encoded);
|
|
145
|
-
return res.status(402).json({
|
|
146
|
-
...discoveryResponse,
|
|
147
|
-
error: "Payment required",
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 5b. Check for PAYMENT-SIGNATURE header
|
|
152
|
-
const paymentSignatureRaw = req.headers[PAYMENT_SIGNATURE_HEADER] as string | undefined;
|
|
153
|
-
console.log(
|
|
154
|
-
`[x402-http-middleware] PAYMENT-SIGNATURE header present: ${!!paymentSignatureRaw}`,
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
if (!paymentSignatureRaw) {
|
|
158
|
-
// ===== STEP 1: No payment → create PENDING record and return HTTP 402 =====
|
|
159
|
-
console.log("[x402-http-middleware] → STEP 1: Issuing 402 challenge");
|
|
160
|
-
|
|
161
|
-
const { challengeId } = await engine.requestHttpAccess(requestId, planId, resourceId);
|
|
162
|
-
console.log(`[x402-http-middleware] ✓ PENDING record created, challengeId=${challengeId}`);
|
|
163
|
-
|
|
164
|
-
const requirements = buildHttpPaymentRequirements(
|
|
165
|
-
planId,
|
|
166
|
-
resourceId,
|
|
167
|
-
config,
|
|
168
|
-
networkConfig,
|
|
169
|
-
);
|
|
170
|
-
console.log(
|
|
171
|
-
"[x402-http-middleware] Payment requirements:",
|
|
172
|
-
JSON.stringify(requirements, null, 2),
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
const base64Requirements = Buffer.from(JSON.stringify(requirements)).toString("base64");
|
|
176
|
-
res.setHeader(PAYMENT_REQUIRED_HEADER, base64Requirements);
|
|
177
|
-
|
|
178
|
-
return res.status(402).json({
|
|
179
|
-
...requirements,
|
|
180
|
-
challengeId,
|
|
181
|
-
error: "PAYMENT-SIGNATURE header is required",
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ===== STEP 2: Has PAYMENT-SIGNATURE → settle and return access grant =====
|
|
186
|
-
console.log("[x402-http-middleware] → STEP 2: Processing payment");
|
|
187
|
-
console.log(
|
|
188
|
-
`[x402-http-middleware] PAYMENT-SIGNATURE (first 50 chars): ${paymentSignatureRaw.substring(0, 50)}...`,
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
// Pre-settlement check: avoid burning USDC if already delivered/expired/cancelled
|
|
192
|
-
const existingGrant = await engine.preSettlementCheck(requestId);
|
|
193
|
-
if (existingGrant) {
|
|
194
|
-
console.log("[x402-http-middleware] ✓ Already delivered, returning cached grant");
|
|
195
|
-
return res.status(200).json(existingGrant);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Decode the header then settle via shared settlement layer
|
|
199
|
-
const paymentPayload = decodePaymentSignature(paymentSignatureRaw);
|
|
200
|
-
const {
|
|
201
|
-
txHash,
|
|
202
|
-
settleResponse: _settleResponse,
|
|
203
|
-
payer,
|
|
204
|
-
} = await settlePayment(paymentPayload, config, networkConfig);
|
|
205
|
-
|
|
206
|
-
console.log(`[x402-http-middleware] ✓ Payment settled, txHash: ${txHash}`);
|
|
207
|
-
|
|
208
|
-
const grant: AccessGrant = await engine.processHttpPayment(
|
|
209
|
-
requestId,
|
|
210
|
-
planId,
|
|
211
|
-
resourceId,
|
|
212
|
-
txHash,
|
|
213
|
-
payer as `0x${string}` | undefined,
|
|
214
|
-
);
|
|
215
|
-
console.log("[x402-http-middleware] ✓ Access grant issued:", JSON.stringify(grant, null, 2));
|
|
216
|
-
|
|
217
|
-
const settlementResponse: X402SettleResponse = {
|
|
218
|
-
success: true,
|
|
219
|
-
transaction: txHash,
|
|
220
|
-
network: `eip155:${networkConfig.chainId}`,
|
|
221
|
-
...(payer && { payer }),
|
|
222
|
-
};
|
|
223
|
-
res.setHeader(
|
|
224
|
-
PAYMENT_RESPONSE_HEADER,
|
|
225
|
-
Buffer.from(JSON.stringify(settlementResponse)).toString("base64"),
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
return res.status(200).json(grant);
|
|
229
|
-
} catch (err: unknown) {
|
|
230
|
-
console.error("[x402-http-middleware] ✗ ERROR:", err);
|
|
231
|
-
if (err instanceof Key0Error) {
|
|
232
|
-
// Return the grant directly for PROOF_ALREADY_REDEEMED (status 200)
|
|
233
|
-
if (err.code === "PROOF_ALREADY_REDEEMED" && err.details?.["grant"]) {
|
|
234
|
-
return res.status(200).json(err.details["grant"]);
|
|
235
|
-
}
|
|
236
|
-
return res.status(err.httpStatus).json(err.toJSON());
|
|
237
|
-
}
|
|
238
|
-
return res.status(500).json({
|
|
239
|
-
error: "INTERNAL_ERROR",
|
|
240
|
-
message: err instanceof Error ? err.message : "Internal server error",
|
|
241
|
-
});
|
|
242
|
-
} finally {
|
|
243
|
-
console.log("[x402-http-middleware] ========== REQUEST COMPLETE ==========\n");
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
}
|