@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,13 +1,11 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import type { NextFunction, Request, Response } from "express";
|
|
3
2
|
import { AccessTokenIssuer } from "../core/access-token.js";
|
|
4
3
|
import { ChallengeEngine } from "../core/challenge-engine.js";
|
|
5
4
|
import {
|
|
6
5
|
buildHttpPaymentRequirements,
|
|
7
|
-
createX402HttpMiddleware,
|
|
8
6
|
decodePaymentSignature,
|
|
9
7
|
settleViaFacilitator,
|
|
10
|
-
} from "../integrations/
|
|
8
|
+
} from "../integrations/settlement.js";
|
|
11
9
|
import { MockPaymentAdapter } from "../test-utils/index.js";
|
|
12
10
|
import { TestChallengeStore, TestSeenTxStore } from "../test-utils/stores.js";
|
|
13
11
|
import { CHAIN_CONFIGS } from "../types/config-shared.js";
|
|
@@ -49,61 +47,7 @@ function makeConfig(): SellerConfig {
|
|
|
49
47
|
};
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
body,
|
|
55
|
-
headers: headers as any,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function createMockResponse(): {
|
|
60
|
-
res: Partial<Response>;
|
|
61
|
-
statusCode: number;
|
|
62
|
-
jsonData: any;
|
|
63
|
-
nextCalled: boolean;
|
|
64
|
-
headers: Record<string, string>;
|
|
65
|
-
} {
|
|
66
|
-
let statusCode = 200;
|
|
67
|
-
let jsonData: any = null;
|
|
68
|
-
const nextCalled = false;
|
|
69
|
-
const headers: Record<string, string> = {};
|
|
70
|
-
|
|
71
|
-
const res = {
|
|
72
|
-
status: function (code: number) {
|
|
73
|
-
statusCode = code;
|
|
74
|
-
return this;
|
|
75
|
-
},
|
|
76
|
-
json: function (data: any) {
|
|
77
|
-
jsonData = data;
|
|
78
|
-
return this;
|
|
79
|
-
},
|
|
80
|
-
send: function (_data: any) {
|
|
81
|
-
return this;
|
|
82
|
-
},
|
|
83
|
-
setHeader: function (name: string, value: string) {
|
|
84
|
-
headers[name] = value;
|
|
85
|
-
return this;
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
res: res as Partial<Response>,
|
|
91
|
-
get statusCode() {
|
|
92
|
-
return statusCode;
|
|
93
|
-
},
|
|
94
|
-
get jsonData() {
|
|
95
|
-
return jsonData;
|
|
96
|
-
},
|
|
97
|
-
get headers() {
|
|
98
|
-
return headers;
|
|
99
|
-
},
|
|
100
|
-
get nextCalled() {
|
|
101
|
-
return nextCalled;
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
describe("x402-http-middleware", () => {
|
|
50
|
+
describe("x402 settlement helpers", () => {
|
|
107
51
|
describe("buildHttpPaymentRequirements", () => {
|
|
108
52
|
test("should build correct payment requirements", () => {
|
|
109
53
|
const config = makeConfig();
|
|
@@ -114,7 +58,7 @@ describe("x402-http-middleware", () => {
|
|
|
114
58
|
// v2 response structure
|
|
115
59
|
expect(requirements.x402Version).toBe(2);
|
|
116
60
|
expect(requirements.resource).toBeDefined();
|
|
117
|
-
expect(requirements.resource.url).toBe("https://agent.example.com/
|
|
61
|
+
expect(requirements.resource.url).toBe("https://agent.example.com/x402/access");
|
|
118
62
|
expect(requirements.resource.method).toBe("POST");
|
|
119
63
|
|
|
120
64
|
// Payment requirements
|
|
@@ -143,209 +87,13 @@ describe("x402-http-middleware", () => {
|
|
|
143
87
|
});
|
|
144
88
|
});
|
|
145
89
|
|
|
146
|
-
describe("createX402HttpMiddleware", () => {
|
|
147
|
-
let engine: ChallengeEngine;
|
|
148
|
-
let config: SellerConfig;
|
|
149
|
-
let middleware: ReturnType<typeof createX402HttpMiddleware>;
|
|
150
|
-
|
|
151
|
-
beforeEach(() => {
|
|
152
|
-
config = makeConfig();
|
|
153
|
-
const store = new TestChallengeStore();
|
|
154
|
-
const seenTxStore = new TestSeenTxStore();
|
|
155
|
-
const adapter = new MockPaymentAdapter();
|
|
156
|
-
|
|
157
|
-
engine = new ChallengeEngine({
|
|
158
|
-
config,
|
|
159
|
-
store,
|
|
160
|
-
seenTxStore,
|
|
161
|
-
adapter,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
middleware = createX402HttpMiddleware(engine, config);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("should pass through if X-A2A-Extensions header is present", async () => {
|
|
168
|
-
const req = createMockRequest(
|
|
169
|
-
{
|
|
170
|
-
method: "message/send",
|
|
171
|
-
params: {
|
|
172
|
-
message: {
|
|
173
|
-
parts: [
|
|
174
|
-
{
|
|
175
|
-
kind: "data",
|
|
176
|
-
data: { type: "AccessRequest", requestId: "test", planId: "basic" },
|
|
177
|
-
},
|
|
178
|
-
],
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
"x-a2a-extensions":
|
|
184
|
-
"https://github.com/google-agentic-commerce/a2a-x402/blob/main/spec/v0.2",
|
|
185
|
-
},
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
let nextCalled = false;
|
|
189
|
-
const next = (() => {
|
|
190
|
-
nextCalled = true;
|
|
191
|
-
}) as NextFunction;
|
|
192
|
-
const mockRes = createMockResponse();
|
|
193
|
-
|
|
194
|
-
await middleware(req as Request, mockRes.res as Response, next);
|
|
195
|
-
|
|
196
|
-
expect(nextCalled).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("should pass through if not message/send", async () => {
|
|
200
|
-
const req = createMockRequest({
|
|
201
|
-
method: "task/get",
|
|
202
|
-
params: {},
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
let nextCalled = false;
|
|
206
|
-
const next = (() => {
|
|
207
|
-
nextCalled = true;
|
|
208
|
-
}) as NextFunction;
|
|
209
|
-
const mockRes = createMockResponse();
|
|
210
|
-
|
|
211
|
-
await middleware(req as Request, mockRes.res as Response, next);
|
|
212
|
-
|
|
213
|
-
expect(nextCalled).toBe(true);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("should pass through if no AccessRequest in message parts", async () => {
|
|
217
|
-
const req = createMockRequest({
|
|
218
|
-
method: "message/send",
|
|
219
|
-
params: {
|
|
220
|
-
message: {
|
|
221
|
-
parts: [{ kind: "data", data: { type: "SomethingElse" } }],
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
let nextCalled = false;
|
|
227
|
-
const next = (() => {
|
|
228
|
-
nextCalled = true;
|
|
229
|
-
}) as NextFunction;
|
|
230
|
-
const mockRes = createMockResponse();
|
|
231
|
-
|
|
232
|
-
await middleware(req as Request, mockRes.res as Response, next);
|
|
233
|
-
|
|
234
|
-
expect(nextCalled).toBe(true);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("should return HTTP 402 for AccessRequest without X-Payment", async () => {
|
|
238
|
-
const req = createMockRequest({
|
|
239
|
-
method: "message/send",
|
|
240
|
-
params: {
|
|
241
|
-
message: {
|
|
242
|
-
parts: [
|
|
243
|
-
{
|
|
244
|
-
kind: "data",
|
|
245
|
-
data: {
|
|
246
|
-
type: "AccessRequest",
|
|
247
|
-
requestId: "req-123",
|
|
248
|
-
planId: "basic",
|
|
249
|
-
resourceId: "default",
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const next = (() => {}) as NextFunction;
|
|
258
|
-
const mockRes = createMockResponse();
|
|
259
|
-
|
|
260
|
-
await middleware(req as Request, mockRes.res as Response, next);
|
|
261
|
-
|
|
262
|
-
expect(mockRes.statusCode).toBe(402);
|
|
263
|
-
expect(mockRes.jsonData.x402Version).toBe(2);
|
|
264
|
-
expect(mockRes.jsonData.resource).toBeDefined();
|
|
265
|
-
expect(mockRes.jsonData.accepts).toHaveLength(1);
|
|
266
|
-
expect(mockRes.jsonData.accepts[0].amount).toBe("990000");
|
|
267
|
-
|
|
268
|
-
// challengeId from PENDING record should be in the response
|
|
269
|
-
expect(mockRes.jsonData.challengeId).toBeDefined();
|
|
270
|
-
expect(mockRes.jsonData.challengeId).toMatch(/^http-/);
|
|
271
|
-
|
|
272
|
-
// Check PAYMENT-REQUIRED header is set
|
|
273
|
-
expect(mockRes.headers["payment-required"]).toBeDefined();
|
|
274
|
-
const decodedHeader = JSON.parse(
|
|
275
|
-
Buffer.from(mockRes.headers["payment-required"]!, "base64").toString(),
|
|
276
|
-
);
|
|
277
|
-
expect(decodedHeader.x402Version).toBe(2);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test("should return 400 for invalid tier", async () => {
|
|
281
|
-
const req = createMockRequest({
|
|
282
|
-
method: "message/send",
|
|
283
|
-
params: {
|
|
284
|
-
message: {
|
|
285
|
-
parts: [
|
|
286
|
-
{
|
|
287
|
-
kind: "data",
|
|
288
|
-
data: {
|
|
289
|
-
type: "AccessRequest",
|
|
290
|
-
requestId: "req-123",
|
|
291
|
-
planId: "invalid-tier",
|
|
292
|
-
resourceId: "default",
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
],
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const next = (() => {}) as NextFunction;
|
|
301
|
-
const mockRes = createMockResponse();
|
|
302
|
-
|
|
303
|
-
await middleware(req as Request, mockRes.res as Response, next);
|
|
304
|
-
|
|
305
|
-
expect(mockRes.statusCode).toBe(400);
|
|
306
|
-
expect(mockRes.jsonData.code).toBe("TIER_NOT_FOUND");
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test("should parse AccessRequest from text part", async () => {
|
|
310
|
-
const req = createMockRequest({
|
|
311
|
-
method: "message/send",
|
|
312
|
-
params: {
|
|
313
|
-
message: {
|
|
314
|
-
parts: [
|
|
315
|
-
{
|
|
316
|
-
kind: "text",
|
|
317
|
-
text: JSON.stringify({
|
|
318
|
-
type: "AccessRequest",
|
|
319
|
-
requestId: "req-123",
|
|
320
|
-
planId: "basic",
|
|
321
|
-
resourceId: "default",
|
|
322
|
-
}),
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
const next = (() => {}) as NextFunction;
|
|
330
|
-
const mockRes = createMockResponse();
|
|
331
|
-
|
|
332
|
-
await middleware(req as Request, mockRes.res as Response, next);
|
|
333
|
-
|
|
334
|
-
expect(mockRes.statusCode).toBe(402);
|
|
335
|
-
expect(mockRes.jsonData.x402Version).toBe(2);
|
|
336
|
-
|
|
337
|
-
// Check PAYMENT-REQUIRED header is set
|
|
338
|
-
expect(mockRes.headers["payment-required"]).toBeDefined();
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
|
|
342
90
|
describe("settleViaFacilitator", () => {
|
|
343
91
|
const mockPaymentPayload = {
|
|
344
92
|
x402Version: 2,
|
|
345
93
|
network: "eip155:84532",
|
|
346
94
|
scheme: "exact",
|
|
347
95
|
resource: {
|
|
348
|
-
url: "https://agent.example.com/
|
|
96
|
+
url: "https://agent.example.com/x402/access",
|
|
349
97
|
method: "POST",
|
|
350
98
|
description: "Access to default",
|
|
351
99
|
mimeType: "application/json",
|
|
@@ -62,26 +62,25 @@ describe("buildAgentCard", () => {
|
|
|
62
62
|
test("has two A2A spec-compliant skills", () => {
|
|
63
63
|
const card = buildAgentCard(makeConfig());
|
|
64
64
|
expect(card.skills).toHaveLength(2);
|
|
65
|
-
expect(card.skills[0]!.id).toBe("discover-
|
|
65
|
+
expect(card.skills[0]!.id).toBe("discover-plans");
|
|
66
66
|
expect(card.skills[1]!.id).toBe("request-access");
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
test("discover-
|
|
69
|
+
test("discover-plans skill has correct structure", () => {
|
|
70
70
|
const card = buildAgentCard(makeConfig());
|
|
71
71
|
const skill = card.skills[0]!;
|
|
72
72
|
|
|
73
|
-
expect(skill.id).toBe("discover-
|
|
74
|
-
expect(skill.name).toBe("Discover
|
|
75
|
-
expect(skill.description).toContain("Browse available
|
|
76
|
-
expect(skill.description).toContain("/
|
|
73
|
+
expect(skill.id).toBe("discover-plans");
|
|
74
|
+
expect(skill.name).toBe("Discover Plans");
|
|
75
|
+
expect(skill.description).toContain("Browse available");
|
|
76
|
+
expect(skill.description).toContain("/discovery");
|
|
77
77
|
expect(skill.tags).toContain("discovery");
|
|
78
78
|
expect(skill.tags).toContain("catalog");
|
|
79
79
|
expect(skill.examples).toBeDefined();
|
|
80
80
|
expect(skill.examples!.length).toBeGreaterThan(0);
|
|
81
81
|
|
|
82
|
-
// A2A spec: skills should NOT have pricing,
|
|
82
|
+
// A2A spec: skills should NOT have pricing, outputSchema, url
|
|
83
83
|
expect((skill as any).pricing).toBeUndefined();
|
|
84
|
-
expect((skill as any).inputSchema).toBeUndefined();
|
|
85
84
|
expect((skill as any).outputSchema).toBeUndefined();
|
|
86
85
|
expect((skill as any).url).toBeUndefined();
|
|
87
86
|
});
|
|
@@ -100,9 +99,13 @@ describe("buildAgentCard", () => {
|
|
|
100
99
|
expect(skill.examples).toBeDefined();
|
|
101
100
|
expect(skill.examples!.length).toBeGreaterThan(0);
|
|
102
101
|
|
|
103
|
-
//
|
|
102
|
+
// inputSchema is present for machine-readable validation
|
|
103
|
+
expect((skill as any).inputSchema).toBeDefined();
|
|
104
|
+
expect((skill as any).inputSchema.required).toContain("planId");
|
|
105
|
+
expect((skill as any).inputSchema.required).toContain("requestId");
|
|
106
|
+
|
|
107
|
+
// A2A spec: skills should NOT have pricing, outputSchema, url
|
|
104
108
|
expect((skill as any).pricing).toBeUndefined();
|
|
105
|
-
expect((skill as any).inputSchema).toBeUndefined();
|
|
106
109
|
expect((skill as any).outputSchema).toBeUndefined();
|
|
107
110
|
expect((skill as any).url).toBeUndefined();
|
|
108
111
|
});
|
|
@@ -113,7 +116,7 @@ describe("buildAgentCard", () => {
|
|
|
113
116
|
const discoverSkill = card.skills[0]!;
|
|
114
117
|
expect(discoverSkill.examples).toBeDefined();
|
|
115
118
|
expect(discoverSkill.examples!.length).toBeGreaterThan(0);
|
|
116
|
-
expect(discoverSkill.examples!.some((ex) => ex.includes("/
|
|
119
|
+
expect(discoverSkill.examples!.some((ex) => ex.includes("/discovery"))).toBe(true);
|
|
117
120
|
|
|
118
121
|
const requestSkill = card.skills[1]!;
|
|
119
122
|
expect(requestSkill.examples).toBeDefined();
|
|
@@ -150,7 +153,7 @@ describe("buildAgentCard", () => {
|
|
|
150
153
|
|
|
151
154
|
// Still just two skills regardless of tier count
|
|
152
155
|
expect(card.skills).toHaveLength(2);
|
|
153
|
-
expect(card.skills[0]!.id).toBe("discover-
|
|
156
|
+
expect(card.skills[0]!.id).toBe("discover-plans");
|
|
154
157
|
expect(card.skills[1]!.id).toBe("request-access");
|
|
155
158
|
});
|
|
156
159
|
|
|
@@ -300,7 +300,6 @@ function createMockSql() {
|
|
|
300
300
|
// Add helper methods
|
|
301
301
|
sql.unsafe = (str: string) => str;
|
|
302
302
|
sql.json = (obj: unknown) => obj;
|
|
303
|
-
// biome-ignore lint/suspicious/noExplicitAny: mock implementation
|
|
304
303
|
sql.begin = async (fn: (sql: any) => Promise<any>) => fn(sql);
|
|
305
304
|
|
|
306
305
|
// Expose tables for inspection
|
|
@@ -316,7 +315,6 @@ function createMockSql() {
|
|
|
316
315
|
(value: string): any;
|
|
317
316
|
unsafe(value: string): any;
|
|
318
317
|
json(value: unknown): any;
|
|
319
|
-
// biome-ignore lint/suspicious/noExplicitAny: mock type
|
|
320
318
|
begin(fn: (sql: any) => Promise<any>): Promise<any>;
|
|
321
319
|
_tables: Map<string, Row[]>; // for inspection
|
|
322
320
|
} & { _tables: Map<string, Row[]> };
|
package/src/core/agent-card.ts
CHANGED
|
@@ -8,30 +8,30 @@ export function buildAgentCard(config: SellerConfig): AgentCard {
|
|
|
8
8
|
|
|
9
9
|
const baseUrl = config.agentUrl.replace(/\/$/, "");
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const planIds = config.plans.map((p) => p.planId);
|
|
12
|
+
|
|
13
|
+
// Two A2A spec-compliant skills
|
|
12
14
|
// Skill 1: Discovery (free) — browse the product catalog
|
|
13
15
|
// Skill 2: Purchase (x402-gated) — buy an access token
|
|
14
16
|
const skills: AgentSkill[] = [
|
|
15
17
|
{
|
|
16
|
-
id: "discover-
|
|
17
|
-
name: "Discover
|
|
18
|
+
id: "discover-plans",
|
|
19
|
+
name: "Discover Plans",
|
|
18
20
|
description: [
|
|
19
|
-
`Browse available
|
|
21
|
+
`Browse available plans and pricing for ${config.agentName}.`,
|
|
20
22
|
`Returns the product catalog with plan IDs, prices (USDC on ${networkName}), wallet address, and chain ID.`,
|
|
21
|
-
`
|
|
23
|
+
`GET to ${baseUrl}/discovery to discover plans.`,
|
|
22
24
|
].join(" "),
|
|
23
25
|
tags: ["discovery", "catalog", "x402"],
|
|
24
|
-
examples: [
|
|
25
|
-
|
|
26
|
-
`Or call without planId to get 402 response with product catalog`,
|
|
27
|
-
],
|
|
26
|
+
examples: [`GET ${baseUrl}/discovery`],
|
|
27
|
+
endpoint: { url: `${baseUrl}/discovery`, method: "GET" },
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
id: "request-access",
|
|
31
31
|
name: "Request Access",
|
|
32
32
|
description: [
|
|
33
33
|
`Purchase access to a ${config.agentName} product plan via x402 payment on ${networkName}.`,
|
|
34
|
-
`First call discover-
|
|
34
|
+
`First call discover-plans to get available plans.`,
|
|
35
35
|
`Then POST to ${baseUrl}/x402/access with planId and requestId to initiate purchase.`,
|
|
36
36
|
`Server responds with x402 payment challenge.`,
|
|
37
37
|
`Complete payment on-chain and include PAYMENT-SIGNATURE header to receive access token.`,
|
|
@@ -43,6 +43,22 @@ export function buildAgentCard(config: SellerConfig): AgentCard {
|
|
|
43
43
|
`Pay USDC on-chain, retry same request with PAYMENT-SIGNATURE header`,
|
|
44
44
|
`Receive 200 with access token`,
|
|
45
45
|
],
|
|
46
|
+
endpoint: { url: `${baseUrl}/x402/access`, method: "POST" },
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
required: ["planId", "requestId"],
|
|
50
|
+
properties: {
|
|
51
|
+
planId: { type: "string", enum: planIds },
|
|
52
|
+
requestId: { type: "string", format: "uuid" },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
workflow: [
|
|
56
|
+
"POST body with planId + requestId to endpoint.url — expect 402",
|
|
57
|
+
"Extract payment requirements from 402 response body",
|
|
58
|
+
`Sign and broadcast USDC transfer on ${networkName}`,
|
|
59
|
+
"Retry same POST with PAYMENT-SIGNATURE header containing the transaction hash",
|
|
60
|
+
"Receive 200 with accessToken",
|
|
61
|
+
],
|
|
46
62
|
},
|
|
47
63
|
];
|
|
48
64
|
|
|
@@ -109,7 +109,7 @@ export class ChallengeEngine {
|
|
|
109
109
|
chainId: record.chainId,
|
|
110
110
|
destination: record.destination,
|
|
111
111
|
expiresAt: record.expiresAt.toISOString(),
|
|
112
|
-
description: `Send ${record.amount} USDC to ${record.destination} on chain ${record.chainId}. Then
|
|
112
|
+
description: `Send ${record.amount} USDC to ${record.destination} on chain ${record.chainId}. Then replay the same POST /x402/access request with the PAYMENT-SIGNATURE header containing the signed EIP-3009 authorization for challengeId "${record.challengeId}", requestId "${record.requestId}", chainId ${record.chainId}, amount "${record.amount}", asset "USDC".`,
|
|
113
113
|
resourceVerified: true,
|
|
114
114
|
};
|
|
115
115
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { ChallengeEngine } from "./challenge-engine.js";
|
|
|
12
12
|
export { validateSellerConfig } from "./config-validation.js";
|
|
13
13
|
export type { RefundConfig, RefundResult } from "./refund.js";
|
|
14
14
|
// Refund Utility
|
|
15
|
-
export { processRefunds } from "./refund.js";
|
|
15
|
+
export { processRefunds, retryFailedRefunds } from "./refund.js";
|
|
16
16
|
export type { PostgresStoreConfig } from "./storage/postgres.js";
|
|
17
17
|
// Storage — Postgres
|
|
18
18
|
export {
|