@t2000/discovery 10.20.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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/index.cjs +388 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +377 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 t2000
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @t2000/discovery
|
|
2
|
+
|
|
3
|
+
Probe and validate x402 seller endpoints on Sui — the discovery half of the
|
|
4
|
+
`@t2000/serve` stack.
|
|
5
|
+
|
|
6
|
+
- **`probe(url)`** — hit an endpoint expecting a 402; reads BOTH dialects
|
|
7
|
+
(the x402 `accepts[]` envelope and the `WWW-Authenticate: Payment` header)
|
|
8
|
+
and normalizes known-USDC raw units back to decimal amounts.
|
|
9
|
+
- **`fetchOpenApi` / `extractEndpoints`** — pull `{origin}/openapi.json` and
|
|
10
|
+
extract every paid operation (summary, price, schema).
|
|
11
|
+
- **`validateOpenApi`** — lint a seller's document for the catalog contract.
|
|
12
|
+
- **`discover` / `check`** — origin-level sweeps combining the above.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { probe, extractEndpoints, validateOpenApi } from '@t2000/discovery';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Zero runtime dependencies. The serve↔probe integration test in
|
|
19
|
+
`@t2000/serve` is the CI gate that keeps the two halves of the dialect from
|
|
20
|
+
drifting — the cross-repo skew class (discovery lagging serve's `accepts[]`)
|
|
21
|
+
that motivated moving this in-monorepo.
|
|
22
|
+
|
|
23
|
+
## Relationship to @suimpp/discovery
|
|
24
|
+
|
|
25
|
+
Ported from `@suimpp/discovery@0.2.2` (2026-08-03), tests included; the CLI
|
|
26
|
+
stayed behind. Protocol mirrors remain published as `@suimpp/{mpp,discovery}`
|
|
27
|
+
— **this package is the SSOT for the t2000 stack**.
|
|
28
|
+
|
|
29
|
+
MIT © t2000
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
|
|
5
|
+
var SUI_USDC_TESTNET_TYPE = "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC";
|
|
6
|
+
var KNOWN_SUI_CURRENCIES = /* @__PURE__ */ new Set([SUI_USDC_TYPE, SUI_USDC_TESTNET_TYPE]);
|
|
7
|
+
var SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{1,64}$/;
|
|
8
|
+
var VALIDATION_CODES = {
|
|
9
|
+
OPENAPI_FETCH_FAILED: "OPENAPI_FETCH_FAILED",
|
|
10
|
+
OPENAPI_PARSE_FAILED: "OPENAPI_PARSE_FAILED",
|
|
11
|
+
OPENAPI_VERSION_INVALID: "OPENAPI_VERSION_INVALID",
|
|
12
|
+
NO_PAID_ENDPOINTS: "NO_PAID_ENDPOINTS",
|
|
13
|
+
MISSING_402_RESPONSE: "MISSING_402_RESPONSE",
|
|
14
|
+
MISSING_REQUEST_BODY: "MISSING_REQUEST_BODY",
|
|
15
|
+
MISSING_PROTOCOLS: "MISSING_PROTOCOLS",
|
|
16
|
+
PROTOCOL_NOT_MPP: "PROTOCOL_NOT_MPP",
|
|
17
|
+
MISSING_PRICING: "MISSING_PRICING",
|
|
18
|
+
INVALID_PRICE_FORMAT: "INVALID_PRICE_FORMAT",
|
|
19
|
+
MISSING_GUIDANCE: "MISSING_GUIDANCE",
|
|
20
|
+
HIGH_ROUTE_COUNT: "HIGH_ROUTE_COUNT",
|
|
21
|
+
PROBE_FAILED: "PROBE_FAILED",
|
|
22
|
+
PROBE_NOT_402: "PROBE_NOT_402",
|
|
23
|
+
PROBE_INVALID_RECIPIENT: "PROBE_INVALID_RECIPIENT",
|
|
24
|
+
PROBE_UNKNOWN_CURRENCY: "PROBE_UNKNOWN_CURRENCY",
|
|
25
|
+
PROBE_REALM_MISMATCH: "PROBE_REALM_MISMATCH"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/discover.ts
|
|
29
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
30
|
+
async function fetchOpenApi(origin) {
|
|
31
|
+
const url = new URL("/openapi.json", origin).toString();
|
|
32
|
+
const res = await fetch(url, {
|
|
33
|
+
headers: { accept: "application/json" },
|
|
34
|
+
signal: AbortSignal.timeout(15e3)
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
throw new Error(`GET ${url} returned ${res.status}`);
|
|
38
|
+
}
|
|
39
|
+
return await res.json();
|
|
40
|
+
}
|
|
41
|
+
function extractEndpoints(doc) {
|
|
42
|
+
const endpoints = [];
|
|
43
|
+
for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
|
|
44
|
+
for (const method of HTTP_METHODS) {
|
|
45
|
+
const op = pathItem[method];
|
|
46
|
+
if (!op || !op["x-payment-info"]) continue;
|
|
47
|
+
endpoints.push({
|
|
48
|
+
path,
|
|
49
|
+
method: method.toUpperCase(),
|
|
50
|
+
operationId: op.operationId,
|
|
51
|
+
summary: op.summary,
|
|
52
|
+
paymentInfo: op["x-payment-info"],
|
|
53
|
+
hasRequestBody: !!op.requestBody,
|
|
54
|
+
has402Response: !!op.responses?.["402"]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return endpoints;
|
|
59
|
+
}
|
|
60
|
+
function validateOpenApi(doc, endpoints, _origin) {
|
|
61
|
+
const issues = [];
|
|
62
|
+
if (!doc.openapi?.startsWith("3.1")) {
|
|
63
|
+
issues.push({
|
|
64
|
+
code: VALIDATION_CODES.OPENAPI_VERSION_INVALID,
|
|
65
|
+
severity: "error",
|
|
66
|
+
message: `Expected OpenAPI 3.1.x, got "${doc.openapi}"`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (endpoints.length === 0) {
|
|
70
|
+
issues.push({
|
|
71
|
+
code: VALIDATION_CODES.NO_PAID_ENDPOINTS,
|
|
72
|
+
severity: "error",
|
|
73
|
+
message: "No operations with x-payment-info found"
|
|
74
|
+
});
|
|
75
|
+
return issues;
|
|
76
|
+
}
|
|
77
|
+
if (endpoints.length > 80) {
|
|
78
|
+
issues.push({
|
|
79
|
+
code: VALIDATION_CODES.HIGH_ROUTE_COUNT,
|
|
80
|
+
severity: "warning",
|
|
81
|
+
message: `High route count (${endpoints.length}) may exceed agent token budgets for zero-hop injection`
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (!doc.info?.["x-guidance"] && !doc["x-service-info"]) {
|
|
85
|
+
issues.push({
|
|
86
|
+
code: VALIDATION_CODES.MISSING_GUIDANCE,
|
|
87
|
+
severity: "warning",
|
|
88
|
+
message: "No x-guidance in info or x-service-info at root \u2014 agents may lack context"
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
for (const ep of endpoints) {
|
|
92
|
+
const loc = `${ep.method} ${ep.path}`;
|
|
93
|
+
if (!ep.has402Response) {
|
|
94
|
+
issues.push({
|
|
95
|
+
code: VALIDATION_CODES.MISSING_402_RESPONSE,
|
|
96
|
+
severity: "error",
|
|
97
|
+
message: `Missing 402 response definition`,
|
|
98
|
+
path: ep.path,
|
|
99
|
+
method: ep.method
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (["POST", "PUT", "PATCH"].includes(ep.method) && !ep.hasRequestBody) {
|
|
103
|
+
issues.push({
|
|
104
|
+
code: VALIDATION_CODES.MISSING_REQUEST_BODY,
|
|
105
|
+
severity: "warning",
|
|
106
|
+
message: `${loc} \u2014 POST/PUT/PATCH without requestBody schema`,
|
|
107
|
+
path: ep.path,
|
|
108
|
+
method: ep.method
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const pi = ep.paymentInfo;
|
|
112
|
+
if (!pi.protocols || pi.protocols.length === 0) {
|
|
113
|
+
issues.push({
|
|
114
|
+
code: VALIDATION_CODES.MISSING_PROTOCOLS,
|
|
115
|
+
severity: "warning",
|
|
116
|
+
message: `${loc} \u2014 x-payment-info missing "protocols" field`,
|
|
117
|
+
path: ep.path,
|
|
118
|
+
method: ep.method
|
|
119
|
+
});
|
|
120
|
+
} else if (!pi.protocols.includes("mpp")) {
|
|
121
|
+
issues.push({
|
|
122
|
+
code: VALIDATION_CODES.PROTOCOL_NOT_MPP,
|
|
123
|
+
severity: "error",
|
|
124
|
+
message: `${loc} \u2014 protocols ${JSON.stringify(pi.protocols)} does not include "mpp"`,
|
|
125
|
+
path: ep.path,
|
|
126
|
+
method: ep.method
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const mode = pi.pricingMode;
|
|
130
|
+
if (mode === "fixed" && !pi.price) {
|
|
131
|
+
issues.push({
|
|
132
|
+
code: VALIDATION_CODES.MISSING_PRICING,
|
|
133
|
+
severity: "error",
|
|
134
|
+
message: `${loc} \u2014 pricingMode "fixed" but no price specified`,
|
|
135
|
+
path: ep.path,
|
|
136
|
+
method: ep.method
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (pi.price && !/^\$?\d+(\.\d+)?$/.test(pi.price)) {
|
|
140
|
+
issues.push({
|
|
141
|
+
code: VALIDATION_CODES.INVALID_PRICE_FORMAT,
|
|
142
|
+
severity: "warning",
|
|
143
|
+
message: `${loc} \u2014 price "${pi.price}" may not be parseable`,
|
|
144
|
+
path: ep.path,
|
|
145
|
+
method: ep.method
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return issues;
|
|
150
|
+
}
|
|
151
|
+
async function discover(origin) {
|
|
152
|
+
const normalizedOrigin = origin.startsWith("http") ? origin : `https://${origin}`;
|
|
153
|
+
const specUrl = new URL("/openapi.json", normalizedOrigin).toString();
|
|
154
|
+
try {
|
|
155
|
+
const doc = await fetchOpenApi(normalizedOrigin);
|
|
156
|
+
const endpoints = extractEndpoints(doc);
|
|
157
|
+
const issues = validateOpenApi(doc, endpoints, normalizedOrigin);
|
|
158
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
159
|
+
return {
|
|
160
|
+
ok: !hasErrors,
|
|
161
|
+
origin: normalizedOrigin,
|
|
162
|
+
specUrl,
|
|
163
|
+
title: doc.info?.title ?? "Unknown",
|
|
164
|
+
version: doc.info?.version ?? "0.0.0",
|
|
165
|
+
guidance: doc.info?.["x-guidance"],
|
|
166
|
+
endpoints,
|
|
167
|
+
totalEndpoints: Object.keys(doc.paths ?? {}).length,
|
|
168
|
+
paidEndpoints: endpoints.length,
|
|
169
|
+
issues
|
|
170
|
+
};
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
origin: normalizedOrigin,
|
|
175
|
+
specUrl,
|
|
176
|
+
title: "Unknown",
|
|
177
|
+
version: "0.0.0",
|
|
178
|
+
endpoints: [],
|
|
179
|
+
totalEndpoints: 0,
|
|
180
|
+
paidEndpoints: 0,
|
|
181
|
+
issues: [
|
|
182
|
+
{
|
|
183
|
+
code: VALIDATION_CODES.OPENAPI_FETCH_FAILED,
|
|
184
|
+
severity: "error",
|
|
185
|
+
message: `Failed to fetch OpenAPI: ${err instanceof Error ? err.message : String(err)}`
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/probe.ts
|
|
193
|
+
function decodeRequest(encoded) {
|
|
194
|
+
try {
|
|
195
|
+
const json = Buffer.from(encoded, "base64").toString("utf-8");
|
|
196
|
+
const parsed = JSON.parse(json);
|
|
197
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
function challengeFromAccepts(accepts) {
|
|
203
|
+
if (!Array.isArray(accepts)) return null;
|
|
204
|
+
const sui = accepts.filter(
|
|
205
|
+
(a) => a && typeof a === "object" && a.scheme === "exact" && typeof a.network === "string" && a.network.startsWith("sui")
|
|
206
|
+
);
|
|
207
|
+
const entry = sui.find((a) => a.network === "sui:mainnet") ?? sui[0];
|
|
208
|
+
if (!entry || typeof entry.payTo !== "string") return null;
|
|
209
|
+
const challenge = { recipient: entry.payTo };
|
|
210
|
+
if (typeof entry.asset === "string") challenge.currency = entry.asset;
|
|
211
|
+
if (typeof entry.maxAmountRequired === "string" && /^\d+$/.test(entry.maxAmountRequired)) {
|
|
212
|
+
challenge.amount = typeof entry.asset === "string" && KNOWN_SUI_CURRENCIES.has(entry.asset) ? (Number(entry.maxAmountRequired) / 1e6).toString() : entry.maxAmountRequired;
|
|
213
|
+
}
|
|
214
|
+
if (typeof entry.resource === "string") {
|
|
215
|
+
try {
|
|
216
|
+
challenge.realm = new URL(entry.resource).hostname;
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return challenge;
|
|
221
|
+
}
|
|
222
|
+
function extractChallenge(headers, body) {
|
|
223
|
+
const wwwAuth = headers.get("www-authenticate");
|
|
224
|
+
if (wwwAuth) {
|
|
225
|
+
const params = {};
|
|
226
|
+
for (const match of wwwAuth.matchAll(/(\w+)="([^"]*)"/g)) {
|
|
227
|
+
params[match[1]] = match[2];
|
|
228
|
+
}
|
|
229
|
+
if (params.request) {
|
|
230
|
+
const decoded = decodeRequest(params.request);
|
|
231
|
+
Object.assign(params, decoded);
|
|
232
|
+
}
|
|
233
|
+
if (params.recipient || params.currency) return params;
|
|
234
|
+
}
|
|
235
|
+
if (body && typeof body === "object") {
|
|
236
|
+
const b = body;
|
|
237
|
+
if (b.recipient || b.currency || b.amount) return b;
|
|
238
|
+
if (b.paymentRequirements && typeof b.paymentRequirements === "object") {
|
|
239
|
+
return b.paymentRequirements;
|
|
240
|
+
}
|
|
241
|
+
const fromAccepts = challengeFromAccepts(b.accepts);
|
|
242
|
+
if (fromAccepts) return fromAccepts;
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
async function probe(url, expectedOrigin) {
|
|
247
|
+
const issues = [];
|
|
248
|
+
try {
|
|
249
|
+
const res = await fetch(url, {
|
|
250
|
+
method: "POST",
|
|
251
|
+
headers: { "content-type": "application/json" },
|
|
252
|
+
body: JSON.stringify({}),
|
|
253
|
+
signal: AbortSignal.timeout(15e3),
|
|
254
|
+
redirect: "follow"
|
|
255
|
+
});
|
|
256
|
+
if (res.status !== 402) {
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
url,
|
|
260
|
+
statusCode: res.status,
|
|
261
|
+
hasSuiPayment: false,
|
|
262
|
+
issues: [
|
|
263
|
+
{
|
|
264
|
+
code: VALIDATION_CODES.PROBE_NOT_402,
|
|
265
|
+
severity: "error",
|
|
266
|
+
message: `Expected 402 Payment Required, got ${res.status}`
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
let body;
|
|
272
|
+
try {
|
|
273
|
+
body = await res.json();
|
|
274
|
+
} catch {
|
|
275
|
+
body = null;
|
|
276
|
+
}
|
|
277
|
+
const challenge = extractChallenge(res.headers, body);
|
|
278
|
+
if (!challenge) {
|
|
279
|
+
return {
|
|
280
|
+
ok: false,
|
|
281
|
+
url,
|
|
282
|
+
statusCode: 402,
|
|
283
|
+
hasSuiPayment: false,
|
|
284
|
+
issues: [
|
|
285
|
+
{
|
|
286
|
+
code: VALIDATION_CODES.PROBE_FAILED,
|
|
287
|
+
severity: "error",
|
|
288
|
+
message: "Got 402 but could not extract payment challenge from headers or body"
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (challenge.recipient && !SUI_ADDRESS_REGEX.test(challenge.recipient)) {
|
|
294
|
+
issues.push({
|
|
295
|
+
code: VALIDATION_CODES.PROBE_INVALID_RECIPIENT,
|
|
296
|
+
severity: "error",
|
|
297
|
+
message: `Recipient "${challenge.recipient}" is not a valid Sui address`
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (challenge.currency && !KNOWN_SUI_CURRENCIES.has(challenge.currency)) {
|
|
301
|
+
issues.push({
|
|
302
|
+
code: VALIDATION_CODES.PROBE_UNKNOWN_CURRENCY,
|
|
303
|
+
severity: "warning",
|
|
304
|
+
message: `Currency "${challenge.currency}" is not a recognized Sui USDC type`
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (expectedOrigin && challenge.realm) {
|
|
308
|
+
const expectedHost = new URL(expectedOrigin).hostname;
|
|
309
|
+
if (challenge.realm !== expectedHost && !challenge.realm.endsWith(`.${expectedHost}`)) {
|
|
310
|
+
issues.push({
|
|
311
|
+
code: VALIDATION_CODES.PROBE_REALM_MISMATCH,
|
|
312
|
+
severity: "error",
|
|
313
|
+
message: `Payment realm "${challenge.realm}" does not match origin host "${expectedHost}"`
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
318
|
+
return {
|
|
319
|
+
ok: !hasErrors,
|
|
320
|
+
url,
|
|
321
|
+
statusCode: 402,
|
|
322
|
+
hasSuiPayment: true,
|
|
323
|
+
recipient: challenge.recipient,
|
|
324
|
+
currency: challenge.currency,
|
|
325
|
+
amount: challenge.amount,
|
|
326
|
+
realm: challenge.realm,
|
|
327
|
+
issues
|
|
328
|
+
};
|
|
329
|
+
} catch (err) {
|
|
330
|
+
return {
|
|
331
|
+
ok: false,
|
|
332
|
+
url,
|
|
333
|
+
statusCode: 0,
|
|
334
|
+
hasSuiPayment: false,
|
|
335
|
+
issues: [
|
|
336
|
+
{
|
|
337
|
+
code: VALIDATION_CODES.PROBE_FAILED,
|
|
338
|
+
severity: "error",
|
|
339
|
+
message: `Probe failed: ${err instanceof Error ? err.message : String(err)}`
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/check.ts
|
|
347
|
+
async function check(origin, options = {}) {
|
|
348
|
+
const { probeEndpoint = true } = options;
|
|
349
|
+
const normalizedOrigin = origin.startsWith("http") ? origin : `https://${origin}`;
|
|
350
|
+
const discovery = await discover(normalizedOrigin);
|
|
351
|
+
let probeResult = void 0;
|
|
352
|
+
if (probeEndpoint && discovery.endpoints.length > 0) {
|
|
353
|
+
const firstPost = discovery.endpoints.find((e) => e.method === "POST");
|
|
354
|
+
const target = firstPost ?? discovery.endpoints[0];
|
|
355
|
+
const probeUrl = new URL(target.path, normalizedOrigin).toString();
|
|
356
|
+
probeResult = await probe(probeUrl, normalizedOrigin);
|
|
357
|
+
}
|
|
358
|
+
const allIssues = [
|
|
359
|
+
...discovery.issues,
|
|
360
|
+
...probeResult?.issues ?? []
|
|
361
|
+
];
|
|
362
|
+
const errors = allIssues.filter((i) => i.severity === "error").length;
|
|
363
|
+
const warnings = allIssues.filter((i) => i.severity === "warning").length;
|
|
364
|
+
return {
|
|
365
|
+
ok: errors === 0,
|
|
366
|
+
origin: normalizedOrigin,
|
|
367
|
+
discovery,
|
|
368
|
+
probe: probeResult,
|
|
369
|
+
summary: {
|
|
370
|
+
totalIssues: allIssues.length,
|
|
371
|
+
errors,
|
|
372
|
+
warnings
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
exports.KNOWN_SUI_CURRENCIES = KNOWN_SUI_CURRENCIES;
|
|
378
|
+
exports.SUI_USDC_TESTNET_TYPE = SUI_USDC_TESTNET_TYPE;
|
|
379
|
+
exports.SUI_USDC_TYPE = SUI_USDC_TYPE;
|
|
380
|
+
exports.VALIDATION_CODES = VALIDATION_CODES;
|
|
381
|
+
exports.check = check;
|
|
382
|
+
exports.discover = discover;
|
|
383
|
+
exports.extractEndpoints = extractEndpoints;
|
|
384
|
+
exports.fetchOpenApi = fetchOpenApi;
|
|
385
|
+
exports.probe = probe;
|
|
386
|
+
exports.validateOpenApi = validateOpenApi;
|
|
387
|
+
//# sourceMappingURL=index.cjs.map
|
|
388
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/discover.ts","../src/probe.ts","../src/check.ts"],"names":[],"mappings":";;;AAAO,IAAM,aAAA,GACX;AAEK,IAAM,qBAAA,GACX;AAEK,IAAM,uCAAuB,IAAI,GAAA,CAAI,CAAC,aAAA,EAAe,qBAAqB,CAAC;AAE3E,IAAM,iBAAA,GAAoB,uBAAA;AAE1B,IAAM,gBAAA,GAAmB;AAAA,EAC9B,oBAAA,EAAsB,sBAAA;AAAA,EACtB,oBAAA,EAAsB,sBAAA;AAAA,EACtB,uBAAA,EAAyB,yBAAA;AAAA,EACzB,iBAAA,EAAmB,mBAAA;AAAA,EACnB,oBAAA,EAAsB,sBAAA;AAAA,EACtB,oBAAA,EAAsB,sBAAA;AAAA,EACtB,iBAAA,EAAmB,mBAAA;AAAA,EACnB,gBAAA,EAAkB,kBAAA;AAAA,EAClB,eAAA,EAAiB,iBAAA;AAAA,EACjB,oBAAA,EAAsB,sBAAA;AAAA,EACtB,gBAAA,EAAkB,kBAAA;AAAA,EAClB,gBAAA,EAAkB,kBAAA;AAAA,EAClB,YAAA,EAAc,cAAA;AAAA,EACd,aAAA,EAAe,eAAA;AAAA,EACf,uBAAA,EAAyB,yBAAA;AAAA,EACzB,sBAAA,EAAwB,wBAAA;AAAA,EACxB,oBAAA,EAAsB;AACxB;;;ACpBA,IAAM,eAAe,CAAC,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,SAAS,QAAQ,CAAA;AAE7D,eAAsB,aAAa,MAAA,EAA0C;AAC3E,EAAA,MAAM,MAAM,IAAI,GAAA,CAAI,eAAA,EAAiB,MAAM,EAAE,QAAA,EAAS;AACtD,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,IAC3B,OAAA,EAAS,EAAE,MAAA,EAAQ,kBAAA,EAAmB;AAAA,IACtC,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,IAAM;AAAA,GACnC,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,IAAA,EAAO,GAAG,CAAA,UAAA,EAAa,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAAA,EACrD;AACA,EAAA,OAAQ,MAAM,IAAI,IAAA,EAAK;AACzB;AAEO,SAAS,iBAAiB,GAAA,EAA4C;AAC3E,EAAA,MAAM,YAAkC,EAAC;AAEzC,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,QAAQ,CAAA,IAAK,MAAA,CAAO,QAAQ,GAAA,CAAI,KAAA,IAAS,EAAE,CAAA,EAAG;AAC9D,IAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,MAAA,MAAM,EAAA,GAAK,SAAS,MAAM,CAAA;AAC1B,MAAA,IAAI,CAAC,EAAA,IAAM,CAAC,EAAA,CAAG,gBAAgB,CAAA,EAAG;AAElC,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,IAAA;AAAA,QACA,MAAA,EAAQ,OAAO,WAAA,EAAY;AAAA,QAC3B,aAAa,EAAA,CAAG,WAAA;AAAA,QAChB,SAAS,EAAA,CAAG,OAAA;AAAA,QACZ,WAAA,EAAa,GAAG,gBAAgB,CAAA;AAAA,QAChC,cAAA,EAAgB,CAAC,CAAC,EAAA,CAAG,WAAA;AAAA,QACrB,cAAA,EAAgB,CAAC,CAAC,EAAA,CAAG,YAAY,KAAK;AAAA,OACvC,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,SAAA;AACT;AAEO,SAAS,eAAA,CACd,GAAA,EACA,SAAA,EACA,OAAA,EACmB;AACnB,EAAA,MAAM,SAA4B,EAAC;AAEnC,EAAA,IAAI,CAAC,GAAA,CAAI,OAAA,EAAS,UAAA,CAAW,KAAK,CAAA,EAAG;AACnC,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,MAAM,gBAAA,CAAiB,uBAAA;AAAA,MACvB,QAAA,EAAU,OAAA;AAAA,MACV,OAAA,EAAS,CAAA,6BAAA,EAAgC,GAAA,CAAI,OAAO,CAAA,CAAA;AAAA,KACrD,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,MAAM,gBAAA,CAAiB,iBAAA;AAAA,MACvB,QAAA,EAAU,OAAA;AAAA,MACV,OAAA,EAAS;AAAA,KACV,CAAA;AACD,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,SAAA,CAAU,SAAS,EAAA,EAAI;AACzB,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,MAAM,gBAAA,CAAiB,gBAAA;AAAA,MACvB,QAAA,EAAU,SAAA;AAAA,MACV,OAAA,EAAS,CAAA,kBAAA,EAAqB,SAAA,CAAU,MAAM,CAAA,uDAAA;AAAA,KAC/C,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,CAAC,IAAI,IAAA,GAAO,YAAY,KAAK,CAAC,GAAA,CAAI,gBAAgB,CAAA,EAAG;AACvD,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,MAAM,gBAAA,CAAiB,gBAAA;AAAA,MACvB,QAAA,EAAU,SAAA;AAAA,MACV,OAAA,EAAS;AAAA,KACV,CAAA;AAAA,EACH;AAEA,EAAA,KAAA,MAAW,MAAM,SAAA,EAAW;AAC1B,IAAA,MAAM,MAAM,CAAA,EAAG,EAAA,CAAG,MAAM,CAAA,CAAA,EAAI,GAAG,IAAI,CAAA,CAAA;AAEnC,IAAA,IAAI,CAAC,GAAG,cAAA,EAAgB;AACtB,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,oBAAA;AAAA,QACvB,QAAA,EAAU,OAAA;AAAA,QACV,OAAA,EAAS,CAAA,+BAAA,CAAA;AAAA,QACT,MAAM,EAAA,CAAG,IAAA;AAAA,QACT,QAAQ,EAAA,CAAG;AAAA,OACZ,CAAA;AAAA,IACH;AAEA,IAAA,IAAI,CAAC,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA,CAAE,QAAA,CAAS,EAAA,CAAG,MAAM,CAAA,IAAK,CAAC,EAAA,CAAG,cAAA,EAAgB;AACtE,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,oBAAA;AAAA,QACvB,QAAA,EAAU,SAAA;AAAA,QACV,OAAA,EAAS,GAAG,GAAG,CAAA,iDAAA,CAAA;AAAA,QACf,MAAM,EAAA,CAAG,IAAA;AAAA,QACT,QAAQ,EAAA,CAAG;AAAA,OACZ,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,KAAK,EAAA,CAAG,WAAA;AAEd,IAAA,IAAI,CAAC,EAAA,CAAG,SAAA,IAAa,EAAA,CAAG,SAAA,CAAU,WAAW,CAAA,EAAG;AAC9C,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,iBAAA;AAAA,QACvB,QAAA,EAAU,SAAA;AAAA,QACV,OAAA,EAAS,GAAG,GAAG,CAAA,gDAAA,CAAA;AAAA,QACf,MAAM,EAAA,CAAG,IAAA;AAAA,QACT,QAAQ,EAAA,CAAG;AAAA,OACZ,CAAA;AAAA,IACH,WAAW,CAAC,EAAA,CAAG,SAAA,CAAU,QAAA,CAAS,KAAK,CAAA,EAAG;AACxC,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,gBAAA;AAAA,QACvB,QAAA,EAAU,OAAA;AAAA,QACV,OAAA,EAAS,GAAG,GAAG,CAAA,kBAAA,EAAgB,KAAK,SAAA,CAAU,EAAA,CAAG,SAAS,CAAC,CAAA,uBAAA,CAAA;AAAA,QAC3D,MAAM,EAAA,CAAG,IAAA;AAAA,QACT,QAAQ,EAAA,CAAG;AAAA,OACZ,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,OAAO,EAAA,CAAG,WAAA;AAChB,IAAA,IAAI,IAAA,KAAS,OAAA,IAAW,CAAC,EAAA,CAAG,KAAA,EAAO;AACjC,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,eAAA;AAAA,QACvB,QAAA,EAAU,OAAA;AAAA,QACV,OAAA,EAAS,GAAG,GAAG,CAAA,kDAAA,CAAA;AAAA,QACf,MAAM,EAAA,CAAG,IAAA;AAAA,QACT,QAAQ,EAAA,CAAG;AAAA,OACZ,CAAA;AAAA,IACH;AAEA,IAAA,IAAI,GAAG,KAAA,IAAS,CAAC,mBAAmB,IAAA,CAAK,EAAA,CAAG,KAAK,CAAA,EAAG;AAClD,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,oBAAA;AAAA,QACvB,QAAA,EAAU,SAAA;AAAA,QACV,OAAA,EAAS,CAAA,EAAG,GAAG,CAAA,eAAA,EAAa,GAAG,KAAK,CAAA,sBAAA,CAAA;AAAA,QACpC,MAAM,EAAA,CAAG,IAAA;AAAA,QACT,QAAQ,EAAA,CAAG;AAAA,OACZ,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,MAAA;AACT;AAEA,eAAsB,SAAS,MAAA,EAAyC;AACtE,EAAA,MAAM,mBAAmB,MAAA,CAAO,UAAA,CAAW,MAAM,CAAA,GAAI,MAAA,GAAS,WAAW,MAAM,CAAA,CAAA;AAC/E,EAAA,MAAM,UAAU,IAAI,GAAA,CAAI,eAAA,EAAiB,gBAAgB,EAAE,QAAA,EAAS;AAEpE,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,YAAA,CAAa,gBAAgB,CAAA;AAC/C,IAAA,MAAM,SAAA,GAAY,iBAAiB,GAAG,CAAA;AACtC,IAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,GAAA,EAAK,SAAA,EAAW,gBAAgB,CAAA;AAE/D,IAAA,MAAM,YAAY,MAAA,CAAO,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,aAAa,OAAO,CAAA;AAEzD,IAAA,OAAO;AAAA,MACL,IAAI,CAAC,SAAA;AAAA,MACL,MAAA,EAAQ,gBAAA;AAAA,MACR,OAAA;AAAA,MACA,KAAA,EAAO,GAAA,CAAI,IAAA,EAAM,KAAA,IAAS,SAAA;AAAA,MAC1B,OAAA,EAAS,GAAA,CAAI,IAAA,EAAM,OAAA,IAAW,OAAA;AAAA,MAC9B,QAAA,EAAU,GAAA,CAAI,IAAA,GAAO,YAAY,CAAA;AAAA,MACjC,SAAA;AAAA,MACA,gBAAgB,MAAA,CAAO,IAAA,CAAK,IAAI,KAAA,IAAS,EAAE,CAAA,CAAE,MAAA;AAAA,MAC7C,eAAe,SAAA,CAAU,MAAA;AAAA,MACzB;AAAA,KACF;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,MAAA,EAAQ,gBAAA;AAAA,MACR,OAAA;AAAA,MACA,KAAA,EAAO,SAAA;AAAA,MACP,OAAA,EAAS,OAAA;AAAA,MACT,WAAW,EAAC;AAAA,MACZ,cAAA,EAAgB,CAAA;AAAA,MAChB,aAAA,EAAe,CAAA;AAAA,MACf,MAAA,EAAQ;AAAA,QACN;AAAA,UACE,MAAM,gBAAA,CAAiB,oBAAA;AAAA,UACvB,QAAA,EAAU,OAAA;AAAA,UACV,OAAA,EAAS,4BAA4B,GAAA,YAAe,KAAA,GAAQ,IAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA;AACvF;AACF,KACF;AAAA,EACF;AACF;;;ACvLA,SAAS,cAAc,OAAA,EAAyC;AAC9D,EAAA,IAAI;AACF,IAAA,MAAM,OAAO,MAAA,CAAO,IAAA,CAAK,SAAS,QAAQ,CAAA,CAAE,SAAS,OAAO,CAAA;AAC5D,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B,IAAA,IAAI,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,EAAU,OAAO,MAAA;AAAA,EACnD,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,OAAO,EAAC;AACV;AAeA,SAAS,qBAAqB,OAAA,EAAuC;AACnE,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,OAAO,GAAG,OAAO,IAAA;AACpC,EAAA,MAAM,MAAM,OAAA,CAAQ,MAAA;AAAA,IAClB,CAAC,CAAA,KACC,CAAA,IACA,OAAO,CAAA,KAAM,YACb,CAAA,CAAE,MAAA,KAAW,OAAA,IACb,OAAO,EAAE,OAAA,KAAY,QAAA,IACrB,CAAA,CAAE,OAAA,CAAQ,WAAW,KAAK;AAAA,GAC9B;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,IAAA,CAAK,CAAA,CAAA,KAAK,EAAE,OAAA,KAAY,aAAa,CAAA,IAAK,GAAA,CAAI,CAAC,CAAA;AACjE,EAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,CAAM,KAAA,KAAU,UAAU,OAAO,IAAA;AAEtD,EAAA,MAAM,SAAA,GAA0B,EAAE,SAAA,EAAW,KAAA,CAAM,KAAA,EAAM;AACzD,EAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,EAAU,SAAA,CAAU,WAAW,KAAA,CAAM,KAAA;AAChE,EAAA,IAAI,OAAO,MAAM,iBAAA,KAAsB,QAAA,IAAY,QAAQ,IAAA,CAAK,KAAA,CAAM,iBAAiB,CAAA,EAAG;AACxF,IAAA,SAAA,CAAU,SACR,OAAO,KAAA,CAAM,KAAA,KAAU,QAAA,IAAY,qBAAqB,GAAA,CAAI,KAAA,CAAM,KAAK,CAAA,GAAA,CAClE,OAAO,KAAA,CAAM,iBAAiB,IAAI,GAAA,EAAW,QAAA,KAC9C,KAAA,CAAM,iBAAA;AAAA,EACd;AACA,EAAA,IAAI,OAAO,KAAA,CAAM,QAAA,KAAa,QAAA,EAAU;AACtC,IAAA,IAAI;AACF,MAAA,SAAA,CAAU,KAAA,GAAQ,IAAI,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA,CAAE,QAAA;AAAA,IAC5C,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACX;AACA,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,gBAAA,CAAiB,SAAkB,IAAA,EAAoC;AAC9E,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA;AAC9C,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,MAAM,SAAiC,EAAC;AACxC,IAAA,KAAA,MAAW,KAAA,IAAS,OAAA,CAAQ,QAAA,CAAS,kBAAkB,CAAA,EAAG;AACxD,MAAA,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,MAAM,CAAC,CAAA;AAAA,IAC5B;AAGA,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,MAAM,OAAA,GAAU,aAAA,CAAc,MAAA,CAAO,OAAO,CAAA;AAC5C,MAAA,MAAA,CAAO,MAAA,CAAO,QAAQ,OAAO,CAAA;AAAA,IAC/B;AAEA,IAAA,IAAI,MAAA,CAAO,SAAA,IAAa,MAAA,CAAO,QAAA,EAAU,OAAO,MAAA;AAAA,EAClD;AAEA,EAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACpC,IAAA,MAAM,CAAA,GAAI,IAAA;AACV,IAAA,IAAI,EAAE,SAAA,IAAa,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,QAAQ,OAAO,CAAA;AAElD,IAAA,IAAI,CAAA,CAAE,mBAAA,IAAuB,OAAO,CAAA,CAAE,wBAAwB,QAAA,EAAU;AACtE,MAAA,OAAO,CAAA,CAAE,mBAAA;AAAA,IACX;AAIA,IAAA,MAAM,WAAA,GAAc,oBAAA,CAAqB,CAAA,CAAE,OAAO,CAAA;AAClD,IAAA,IAAI,aAAa,OAAO,WAAA;AAAA,EAC1B;AAEA,EAAA,OAAO,IAAA;AACT;AAEA,eAAsB,KAAA,CAAM,KAAa,cAAA,EAA+C;AACtF,EAAA,MAAM,SAA4B,EAAC;AAEnC,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAC3B,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,CAAA;AAAA,MACvB,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,IAAM,CAAA;AAAA,MAClC,QAAA,EAAU;AAAA,KACX,CAAA;AAED,IAAA,IAAI,GAAA,CAAI,WAAW,GAAA,EAAK;AACtB,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA;AAAA,QACJ,GAAA;AAAA,QACA,YAAY,GAAA,CAAI,MAAA;AAAA,QAChB,aAAA,EAAe,KAAA;AAAA,QACf,MAAA,EAAQ;AAAA,UACN;AAAA,YACE,MAAM,gBAAA,CAAiB,aAAA;AAAA,YACvB,QAAA,EAAU,OAAA;AAAA,YACV,OAAA,EAAS,CAAA,mCAAA,EAAsC,GAAA,CAAI,MAAM,CAAA;AAAA;AAC3D;AACF,OACF;AAAA,IACF;AAEA,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI;AACF,MAAA,IAAA,GAAO,MAAM,IAAI,IAAA,EAAK;AAAA,IACxB,CAAA,CAAA,MAAQ;AACN,MAAA,IAAA,GAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,SAAA,GAAY,gBAAA,CAAiB,GAAA,CAAI,OAAA,EAAS,IAAI,CAAA;AACpD,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA;AAAA,QACJ,GAAA;AAAA,QACA,UAAA,EAAY,GAAA;AAAA,QACZ,aAAA,EAAe,KAAA;AAAA,QACf,MAAA,EAAQ;AAAA,UACN;AAAA,YACE,MAAM,gBAAA,CAAiB,YAAA;AAAA,YACvB,QAAA,EAAU,OAAA;AAAA,YACV,OAAA,EAAS;AAAA;AACX;AACF,OACF;AAAA,IACF;AAEA,IAAA,IAAI,UAAU,SAAA,IAAa,CAAC,kBAAkB,IAAA,CAAK,SAAA,CAAU,SAAS,CAAA,EAAG;AACvE,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,uBAAA;AAAA,QACvB,QAAA,EAAU,OAAA;AAAA,QACV,OAAA,EAAS,CAAA,WAAA,EAAc,SAAA,CAAU,SAAS,CAAA,4BAAA;AAAA,OAC3C,CAAA;AAAA,IACH;AAEA,IAAA,IAAI,UAAU,QAAA,IAAY,CAAC,qBAAqB,GAAA,CAAI,SAAA,CAAU,QAAQ,CAAA,EAAG;AACvE,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,MAAM,gBAAA,CAAiB,sBAAA;AAAA,QACvB,QAAA,EAAU,SAAA;AAAA,QACV,OAAA,EAAS,CAAA,UAAA,EAAa,SAAA,CAAU,QAAQ,CAAA,mCAAA;AAAA,OACzC,CAAA;AAAA,IACH;AAEA,IAAA,IAAI,cAAA,IAAkB,UAAU,KAAA,EAAO;AACrC,MAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,cAAc,CAAA,CAAE,QAAA;AAC7C,MAAA,IAAI,SAAA,CAAU,KAAA,KAAU,YAAA,IAAgB,CAAC,SAAA,CAAU,MAAM,QAAA,CAAS,CAAA,CAAA,EAAI,YAAY,CAAA,CAAE,CAAA,EAAG;AACrF,QAAA,MAAA,CAAO,IAAA,CAAK;AAAA,UACV,MAAM,gBAAA,CAAiB,oBAAA;AAAA,UACvB,QAAA,EAAU,OAAA;AAAA,UACV,OAAA,EAAS,CAAA,eAAA,EAAkB,SAAA,CAAU,KAAK,iCAAiC,YAAY,CAAA,CAAA;AAAA,SACxF,CAAA;AAAA,MACH;AAAA,IACF;AAEA,IAAA,MAAM,YAAY,MAAA,CAAO,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,aAAa,OAAO,CAAA;AAEzD,IAAA,OAAO;AAAA,MACL,IAAI,CAAC,SAAA;AAAA,MACL,GAAA;AAAA,MACA,UAAA,EAAY,GAAA;AAAA,MACZ,aAAA,EAAe,IAAA;AAAA,MACf,WAAW,SAAA,CAAU,SAAA;AAAA,MACrB,UAAU,SAAA,CAAU,QAAA;AAAA,MACpB,QAAQ,SAAA,CAAU,MAAA;AAAA,MAClB,OAAO,SAAA,CAAU,KAAA;AAAA,MACjB;AAAA,KACF;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,GAAA;AAAA,MACA,UAAA,EAAY,CAAA;AAAA,MACZ,aAAA,EAAe,KAAA;AAAA,MACf,MAAA,EAAQ;AAAA,QACN;AAAA,UACE,MAAM,gBAAA,CAAiB,YAAA;AAAA,UACvB,QAAA,EAAU,OAAA;AAAA,UACV,OAAA,EAAS,iBAAiB,GAAA,YAAe,KAAA,GAAQ,IAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA;AAC5E;AACF,KACF;AAAA,EACF;AACF;;;ACpMA,eAAsB,KAAA,CACpB,MAAA,EACA,OAAA,GAAwB,EAAC,EACH;AACtB,EAAA,MAAM,EAAE,aAAA,GAAgB,IAAA,EAAK,GAAI,OAAA;AAEjC,EAAA,MAAM,mBAAmB,MAAA,CAAO,UAAA,CAAW,MAAM,CAAA,GAAI,MAAA,GAAS,WAAW,MAAM,CAAA,CAAA;AAC/E,EAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,gBAAgB,CAAA;AAEjD,EAAA,IAAI,WAAA,GAAc,MAAA;AAClB,EAAA,IAAI,aAAA,IAAiB,SAAA,CAAU,SAAA,CAAU,MAAA,GAAS,CAAA,EAAG;AACnD,IAAA,MAAM,YAAY,SAAA,CAAU,SAAA,CAAU,KAAK,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,MAAM,CAAA;AACnE,IAAA,MAAM,MAAA,GAAS,SAAA,IAAa,SAAA,CAAU,SAAA,CAAU,CAAC,CAAA;AACjD,IAAA,MAAM,WAAW,IAAI,GAAA,CAAI,OAAO,IAAA,EAAM,gBAAgB,EAAE,QAAA,EAAS;AACjE,IAAA,WAAA,GAAc,MAAM,KAAA,CAAM,QAAA,EAAU,gBAAgB,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,SAAA,GAAY;AAAA,IAChB,GAAG,SAAA,CAAU,MAAA;AAAA,IACb,GAAI,WAAA,EAAa,MAAA,IAAU;AAAC,GAC9B;AAEA,EAAA,MAAM,SAAS,SAAA,CAAU,MAAA,CAAO,OAAK,CAAA,CAAE,QAAA,KAAa,OAAO,CAAA,CAAE,MAAA;AAC7D,EAAA,MAAM,WAAW,SAAA,CAAU,MAAA,CAAO,OAAK,CAAA,CAAE,QAAA,KAAa,SAAS,CAAA,CAAE,MAAA;AAEjE,EAAA,OAAO;AAAA,IACL,IAAI,MAAA,KAAW,CAAA;AAAA,IACf,MAAA,EAAQ,gBAAA;AAAA,IACR,SAAA;AAAA,IACA,KAAA,EAAO,WAAA;AAAA,IACP,OAAA,EAAS;AAAA,MACP,aAAa,SAAA,CAAU,MAAA;AAAA,MACvB,MAAA;AAAA,MACA;AAAA;AACF,GACF;AACF","file":"index.cjs","sourcesContent":["export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n\nexport const SUI_USDC_TESTNET_TYPE =\n '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC';\n\nexport const KNOWN_SUI_CURRENCIES = new Set([SUI_USDC_TYPE, SUI_USDC_TESTNET_TYPE]);\n\nexport const SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{1,64}$/;\n\nexport const VALIDATION_CODES = {\n OPENAPI_FETCH_FAILED: 'OPENAPI_FETCH_FAILED',\n OPENAPI_PARSE_FAILED: 'OPENAPI_PARSE_FAILED',\n OPENAPI_VERSION_INVALID: 'OPENAPI_VERSION_INVALID',\n NO_PAID_ENDPOINTS: 'NO_PAID_ENDPOINTS',\n MISSING_402_RESPONSE: 'MISSING_402_RESPONSE',\n MISSING_REQUEST_BODY: 'MISSING_REQUEST_BODY',\n MISSING_PROTOCOLS: 'MISSING_PROTOCOLS',\n PROTOCOL_NOT_MPP: 'PROTOCOL_NOT_MPP',\n MISSING_PRICING: 'MISSING_PRICING',\n INVALID_PRICE_FORMAT: 'INVALID_PRICE_FORMAT',\n MISSING_GUIDANCE: 'MISSING_GUIDANCE',\n HIGH_ROUTE_COUNT: 'HIGH_ROUTE_COUNT',\n PROBE_FAILED: 'PROBE_FAILED',\n PROBE_NOT_402: 'PROBE_NOT_402',\n PROBE_INVALID_RECIPIENT: 'PROBE_INVALID_RECIPIENT',\n PROBE_UNKNOWN_CURRENCY: 'PROBE_UNKNOWN_CURRENCY',\n PROBE_REALM_MISMATCH: 'PROBE_REALM_MISMATCH',\n} as const;\n","import { VALIDATION_CODES } from './constants.js';\nimport type {\n OpenApiDocument,\n DiscoverResult,\n DiscoveredEndpoint,\n ValidationIssue,\n} from './types.js';\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;\n\nexport async function fetchOpenApi(origin: string): Promise<OpenApiDocument> {\n const url = new URL('/openapi.json', origin).toString();\n const res = await fetch(url, {\n headers: { accept: 'application/json' },\n signal: AbortSignal.timeout(15_000),\n });\n if (!res.ok) {\n throw new Error(`GET ${url} returned ${res.status}`);\n }\n return (await res.json()) as OpenApiDocument;\n}\n\nexport function extractEndpoints(doc: OpenApiDocument): DiscoveredEndpoint[] {\n const endpoints: DiscoveredEndpoint[] = [];\n\n for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {\n for (const method of HTTP_METHODS) {\n const op = pathItem[method];\n if (!op || !op['x-payment-info']) continue;\n\n endpoints.push({\n path,\n method: method.toUpperCase(),\n operationId: op.operationId,\n summary: op.summary,\n paymentInfo: op['x-payment-info'],\n hasRequestBody: !!op.requestBody,\n has402Response: !!op.responses?.['402'],\n });\n }\n }\n\n return endpoints;\n}\n\nexport function validateOpenApi(\n doc: OpenApiDocument,\n endpoints: DiscoveredEndpoint[],\n _origin?: string,\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n if (!doc.openapi?.startsWith('3.1')) {\n issues.push({\n code: VALIDATION_CODES.OPENAPI_VERSION_INVALID,\n severity: 'error',\n message: `Expected OpenAPI 3.1.x, got \"${doc.openapi}\"`,\n });\n }\n\n if (endpoints.length === 0) {\n issues.push({\n code: VALIDATION_CODES.NO_PAID_ENDPOINTS,\n severity: 'error',\n message: 'No operations with x-payment-info found',\n });\n return issues;\n }\n\n if (endpoints.length > 80) {\n issues.push({\n code: VALIDATION_CODES.HIGH_ROUTE_COUNT,\n severity: 'warning',\n message: `High route count (${endpoints.length}) may exceed agent token budgets for zero-hop injection`,\n });\n }\n\n if (!doc.info?.['x-guidance'] && !doc['x-service-info']) {\n issues.push({\n code: VALIDATION_CODES.MISSING_GUIDANCE,\n severity: 'warning',\n message: 'No x-guidance in info or x-service-info at root — agents may lack context',\n });\n }\n\n for (const ep of endpoints) {\n const loc = `${ep.method} ${ep.path}`;\n\n if (!ep.has402Response) {\n issues.push({\n code: VALIDATION_CODES.MISSING_402_RESPONSE,\n severity: 'error',\n message: `Missing 402 response definition`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n if (['POST', 'PUT', 'PATCH'].includes(ep.method) && !ep.hasRequestBody) {\n issues.push({\n code: VALIDATION_CODES.MISSING_REQUEST_BODY,\n severity: 'warning',\n message: `${loc} — POST/PUT/PATCH without requestBody schema`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n const pi = ep.paymentInfo;\n\n if (!pi.protocols || pi.protocols.length === 0) {\n issues.push({\n code: VALIDATION_CODES.MISSING_PROTOCOLS,\n severity: 'warning',\n message: `${loc} — x-payment-info missing \"protocols\" field`,\n path: ep.path,\n method: ep.method,\n });\n } else if (!pi.protocols.includes('mpp')) {\n issues.push({\n code: VALIDATION_CODES.PROTOCOL_NOT_MPP,\n severity: 'error',\n message: `${loc} — protocols ${JSON.stringify(pi.protocols)} does not include \"mpp\"`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n const mode = pi.pricingMode;\n if (mode === 'fixed' && !pi.price) {\n issues.push({\n code: VALIDATION_CODES.MISSING_PRICING,\n severity: 'error',\n message: `${loc} — pricingMode \"fixed\" but no price specified`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n if (pi.price && !/^\\$?\\d+(\\.\\d+)?$/.test(pi.price)) {\n issues.push({\n code: VALIDATION_CODES.INVALID_PRICE_FORMAT,\n severity: 'warning',\n message: `${loc} — price \"${pi.price}\" may not be parseable`,\n path: ep.path,\n method: ep.method,\n });\n }\n }\n\n return issues;\n}\n\nexport async function discover(origin: string): Promise<DiscoverResult> {\n const normalizedOrigin = origin.startsWith('http') ? origin : `https://${origin}`;\n const specUrl = new URL('/openapi.json', normalizedOrigin).toString();\n\n try {\n const doc = await fetchOpenApi(normalizedOrigin);\n const endpoints = extractEndpoints(doc);\n const issues = validateOpenApi(doc, endpoints, normalizedOrigin);\n\n const hasErrors = issues.some(i => i.severity === 'error');\n\n return {\n ok: !hasErrors,\n origin: normalizedOrigin,\n specUrl,\n title: doc.info?.title ?? 'Unknown',\n version: doc.info?.version ?? '0.0.0',\n guidance: doc.info?.['x-guidance'] as string | undefined,\n endpoints,\n totalEndpoints: Object.keys(doc.paths ?? {}).length,\n paidEndpoints: endpoints.length,\n issues,\n };\n } catch (err) {\n return {\n ok: false,\n origin: normalizedOrigin,\n specUrl,\n title: 'Unknown',\n version: '0.0.0',\n endpoints: [],\n totalEndpoints: 0,\n paidEndpoints: 0,\n issues: [\n {\n code: VALIDATION_CODES.OPENAPI_FETCH_FAILED,\n severity: 'error',\n message: `Failed to fetch OpenAPI: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n }\n}\n","import { SUI_ADDRESS_REGEX, KNOWN_SUI_CURRENCIES, VALIDATION_CODES } from './constants.js';\nimport type { ProbeResult, ValidationIssue } from './types.js';\n\ninterface MppChallenge {\n amount?: string;\n currency?: string;\n recipient?: string;\n realm?: string;\n network?: string;\n [key: string]: unknown;\n}\n\nfunction decodeRequest(encoded: string): Record<string, string> {\n try {\n const json = Buffer.from(encoded, 'base64').toString('utf-8');\n const parsed = JSON.parse(json);\n if (parsed && typeof parsed === 'object') return parsed;\n } catch {}\n return {};\n}\n\ninterface X402Accept {\n scheme?: unknown;\n network?: unknown;\n payTo?: unknown;\n asset?: unknown;\n maxAmountRequired?: unknown;\n resource?: unknown;\n}\n\n/** x402 `accepts[]` entry → the MPP challenge shape the rest of probe() reads.\n * `maxAmountRequired` is raw 6-decimal USDC units; the header dialect's\n * `amount` is a decimal string — convert (known-USDC only) so `amount` means\n * the same thing whichever dialect answered. */\nfunction challengeFromAccepts(accepts: unknown): MppChallenge | null {\n if (!Array.isArray(accepts)) return null;\n const sui = accepts.filter(\n (a: X402Accept) =>\n a &&\n typeof a === 'object' &&\n a.scheme === 'exact' &&\n typeof a.network === 'string' &&\n a.network.startsWith('sui'),\n ) as X402Accept[];\n const entry = sui.find(a => a.network === 'sui:mainnet') ?? sui[0];\n if (!entry || typeof entry.payTo !== 'string') return null;\n\n const challenge: MppChallenge = { recipient: entry.payTo };\n if (typeof entry.asset === 'string') challenge.currency = entry.asset;\n if (typeof entry.maxAmountRequired === 'string' && /^\\d+$/.test(entry.maxAmountRequired)) {\n challenge.amount =\n typeof entry.asset === 'string' && KNOWN_SUI_CURRENCIES.has(entry.asset)\n ? (Number(entry.maxAmountRequired) / 1_000_000).toString()\n : entry.maxAmountRequired;\n }\n if (typeof entry.resource === 'string') {\n try {\n challenge.realm = new URL(entry.resource).hostname;\n } catch {}\n }\n return challenge;\n}\n\nfunction extractChallenge(headers: Headers, body: unknown): MppChallenge | null {\n const wwwAuth = headers.get('www-authenticate');\n if (wwwAuth) {\n const params: Record<string, string> = {};\n for (const match of wwwAuth.matchAll(/(\\w+)=\"([^\"]*)\"/g)) {\n params[match[1]] = match[2];\n }\n\n // mppx encodes amount/currency/recipient inside a base64 `request` field\n if (params.request) {\n const decoded = decodeRequest(params.request);\n Object.assign(params, decoded);\n }\n\n if (params.recipient || params.currency) return params;\n }\n\n if (body && typeof body === 'object') {\n const b = body as Record<string, unknown>;\n if (b.recipient || b.currency || b.amount) return b as MppChallenge;\n\n if (b.paymentRequirements && typeof b.paymentRequirements === 'object') {\n return b.paymentRequirements as MppChallenge;\n }\n\n // x402 body-only sellers (@t2000/serve emits accepts[] and no\n // WWW-Authenticate) — the dialect this probe rejected until 0.2.2.\n const fromAccepts = challengeFromAccepts(b.accepts);\n if (fromAccepts) return fromAccepts;\n }\n\n return null;\n}\n\nexport async function probe(url: string, expectedOrigin?: string): Promise<ProbeResult> {\n const issues: ValidationIssue[] = [];\n\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({}),\n signal: AbortSignal.timeout(15_000),\n redirect: 'follow',\n });\n\n if (res.status !== 402) {\n return {\n ok: false,\n url,\n statusCode: res.status,\n hasSuiPayment: false,\n issues: [\n {\n code: VALIDATION_CODES.PROBE_NOT_402,\n severity: 'error',\n message: `Expected 402 Payment Required, got ${res.status}`,\n },\n ],\n };\n }\n\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n body = null;\n }\n\n const challenge = extractChallenge(res.headers, body);\n if (!challenge) {\n return {\n ok: false,\n url,\n statusCode: 402,\n hasSuiPayment: false,\n issues: [\n {\n code: VALIDATION_CODES.PROBE_FAILED,\n severity: 'error',\n message: 'Got 402 but could not extract payment challenge from headers or body',\n },\n ],\n };\n }\n\n if (challenge.recipient && !SUI_ADDRESS_REGEX.test(challenge.recipient)) {\n issues.push({\n code: VALIDATION_CODES.PROBE_INVALID_RECIPIENT,\n severity: 'error',\n message: `Recipient \"${challenge.recipient}\" is not a valid Sui address`,\n });\n }\n\n if (challenge.currency && !KNOWN_SUI_CURRENCIES.has(challenge.currency)) {\n issues.push({\n code: VALIDATION_CODES.PROBE_UNKNOWN_CURRENCY,\n severity: 'warning',\n message: `Currency \"${challenge.currency}\" is not a recognized Sui USDC type`,\n });\n }\n\n if (expectedOrigin && challenge.realm) {\n const expectedHost = new URL(expectedOrigin).hostname;\n if (challenge.realm !== expectedHost && !challenge.realm.endsWith(`.${expectedHost}`)) {\n issues.push({\n code: VALIDATION_CODES.PROBE_REALM_MISMATCH,\n severity: 'error',\n message: `Payment realm \"${challenge.realm}\" does not match origin host \"${expectedHost}\"`,\n });\n }\n }\n\n const hasErrors = issues.some(i => i.severity === 'error');\n\n return {\n ok: !hasErrors,\n url,\n statusCode: 402,\n hasSuiPayment: true,\n recipient: challenge.recipient,\n currency: challenge.currency,\n amount: challenge.amount,\n realm: challenge.realm,\n issues,\n };\n } catch (err) {\n return {\n ok: false,\n url,\n statusCode: 0,\n hasSuiPayment: false,\n issues: [\n {\n code: VALIDATION_CODES.PROBE_FAILED,\n severity: 'error',\n message: `Probe failed: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n }\n}\n","import { discover } from './discover.js';\nimport { probe } from './probe.js';\nimport type { CheckResult } from './types.js';\n\nexport interface CheckOptions {\n probeEndpoint?: boolean;\n}\n\nexport async function check(\n origin: string,\n options: CheckOptions = {},\n): Promise<CheckResult> {\n const { probeEndpoint = true } = options;\n\n const normalizedOrigin = origin.startsWith('http') ? origin : `https://${origin}`;\n const discovery = await discover(normalizedOrigin);\n\n let probeResult = undefined;\n if (probeEndpoint && discovery.endpoints.length > 0) {\n const firstPost = discovery.endpoints.find(e => e.method === 'POST');\n const target = firstPost ?? discovery.endpoints[0];\n const probeUrl = new URL(target.path, normalizedOrigin).toString();\n probeResult = await probe(probeUrl, normalizedOrigin);\n }\n\n const allIssues = [\n ...discovery.issues,\n ...(probeResult?.issues ?? []),\n ];\n\n const errors = allIssues.filter(i => i.severity === 'error').length;\n const warnings = allIssues.filter(i => i.severity === 'warning').length;\n\n return {\n ok: errors === 0,\n origin: normalizedOrigin,\n discovery,\n probe: probeResult,\n summary: {\n totalIssues: allIssues.length,\n errors,\n warnings,\n },\n };\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
interface PaymentInfo {
|
|
2
|
+
pricingMode?: string;
|
|
3
|
+
price?: string;
|
|
4
|
+
amount?: string;
|
|
5
|
+
currency?: string;
|
|
6
|
+
protocols?: string[];
|
|
7
|
+
intent?: string;
|
|
8
|
+
method?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
interface OpenApiOperation {
|
|
12
|
+
operationId?: string;
|
|
13
|
+
summary?: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
'x-payment-info'?: PaymentInfo;
|
|
16
|
+
requestBody?: Record<string, unknown>;
|
|
17
|
+
responses?: Record<string, unknown>;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface OpenApiPath {
|
|
21
|
+
[method: string]: OpenApiOperation;
|
|
22
|
+
}
|
|
23
|
+
interface OpenApiDocument {
|
|
24
|
+
openapi: string;
|
|
25
|
+
info: {
|
|
26
|
+
title: string;
|
|
27
|
+
version: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
'x-guidance'?: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
};
|
|
32
|
+
'x-discovery'?: {
|
|
33
|
+
ownershipProofs?: string[];
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
};
|
|
36
|
+
'x-service-info'?: Record<string, unknown>;
|
|
37
|
+
paths: Record<string, OpenApiPath>;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
interface DiscoveredEndpoint {
|
|
41
|
+
path: string;
|
|
42
|
+
method: string;
|
|
43
|
+
operationId?: string;
|
|
44
|
+
summary?: string;
|
|
45
|
+
paymentInfo: PaymentInfo;
|
|
46
|
+
hasRequestBody: boolean;
|
|
47
|
+
has402Response: boolean;
|
|
48
|
+
}
|
|
49
|
+
type Severity = 'error' | 'warning' | 'info';
|
|
50
|
+
interface ValidationIssue {
|
|
51
|
+
code: string;
|
|
52
|
+
severity: Severity;
|
|
53
|
+
message: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
method?: string;
|
|
56
|
+
}
|
|
57
|
+
interface DiscoverResult {
|
|
58
|
+
ok: boolean;
|
|
59
|
+
origin: string;
|
|
60
|
+
specUrl: string;
|
|
61
|
+
title: string;
|
|
62
|
+
version: string;
|
|
63
|
+
guidance?: string;
|
|
64
|
+
endpoints: DiscoveredEndpoint[];
|
|
65
|
+
totalEndpoints: number;
|
|
66
|
+
paidEndpoints: number;
|
|
67
|
+
issues: ValidationIssue[];
|
|
68
|
+
}
|
|
69
|
+
interface ProbeResult {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
url: string;
|
|
72
|
+
statusCode: number;
|
|
73
|
+
hasSuiPayment: boolean;
|
|
74
|
+
recipient?: string;
|
|
75
|
+
currency?: string;
|
|
76
|
+
amount?: string;
|
|
77
|
+
realm?: string;
|
|
78
|
+
issues: ValidationIssue[];
|
|
79
|
+
}
|
|
80
|
+
interface CheckResult {
|
|
81
|
+
ok: boolean;
|
|
82
|
+
origin: string;
|
|
83
|
+
discovery: DiscoverResult;
|
|
84
|
+
probe?: ProbeResult;
|
|
85
|
+
summary: {
|
|
86
|
+
totalIssues: number;
|
|
87
|
+
errors: number;
|
|
88
|
+
warnings: number;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
declare function fetchOpenApi(origin: string): Promise<OpenApiDocument>;
|
|
93
|
+
declare function extractEndpoints(doc: OpenApiDocument): DiscoveredEndpoint[];
|
|
94
|
+
declare function validateOpenApi(doc: OpenApiDocument, endpoints: DiscoveredEndpoint[], _origin?: string): ValidationIssue[];
|
|
95
|
+
declare function discover(origin: string): Promise<DiscoverResult>;
|
|
96
|
+
|
|
97
|
+
declare function probe(url: string, expectedOrigin?: string): Promise<ProbeResult>;
|
|
98
|
+
|
|
99
|
+
interface CheckOptions {
|
|
100
|
+
probeEndpoint?: boolean;
|
|
101
|
+
}
|
|
102
|
+
declare function check(origin: string, options?: CheckOptions): Promise<CheckResult>;
|
|
103
|
+
|
|
104
|
+
declare const SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
|
|
105
|
+
declare const SUI_USDC_TESTNET_TYPE = "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC";
|
|
106
|
+
declare const KNOWN_SUI_CURRENCIES: Set<string>;
|
|
107
|
+
declare const VALIDATION_CODES: {
|
|
108
|
+
readonly OPENAPI_FETCH_FAILED: "OPENAPI_FETCH_FAILED";
|
|
109
|
+
readonly OPENAPI_PARSE_FAILED: "OPENAPI_PARSE_FAILED";
|
|
110
|
+
readonly OPENAPI_VERSION_INVALID: "OPENAPI_VERSION_INVALID";
|
|
111
|
+
readonly NO_PAID_ENDPOINTS: "NO_PAID_ENDPOINTS";
|
|
112
|
+
readonly MISSING_402_RESPONSE: "MISSING_402_RESPONSE";
|
|
113
|
+
readonly MISSING_REQUEST_BODY: "MISSING_REQUEST_BODY";
|
|
114
|
+
readonly MISSING_PROTOCOLS: "MISSING_PROTOCOLS";
|
|
115
|
+
readonly PROTOCOL_NOT_MPP: "PROTOCOL_NOT_MPP";
|
|
116
|
+
readonly MISSING_PRICING: "MISSING_PRICING";
|
|
117
|
+
readonly INVALID_PRICE_FORMAT: "INVALID_PRICE_FORMAT";
|
|
118
|
+
readonly MISSING_GUIDANCE: "MISSING_GUIDANCE";
|
|
119
|
+
readonly HIGH_ROUTE_COUNT: "HIGH_ROUTE_COUNT";
|
|
120
|
+
readonly PROBE_FAILED: "PROBE_FAILED";
|
|
121
|
+
readonly PROBE_NOT_402: "PROBE_NOT_402";
|
|
122
|
+
readonly PROBE_INVALID_RECIPIENT: "PROBE_INVALID_RECIPIENT";
|
|
123
|
+
readonly PROBE_UNKNOWN_CURRENCY: "PROBE_UNKNOWN_CURRENCY";
|
|
124
|
+
readonly PROBE_REALM_MISMATCH: "PROBE_REALM_MISMATCH";
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export { type CheckOptions, type CheckResult, type DiscoverResult, type DiscoveredEndpoint, KNOWN_SUI_CURRENCIES, type OpenApiDocument, type OpenApiOperation, type OpenApiPath, type PaymentInfo, type ProbeResult, SUI_USDC_TESTNET_TYPE, SUI_USDC_TYPE, type Severity, VALIDATION_CODES, type ValidationIssue, check, discover, extractEndpoints, fetchOpenApi, probe, validateOpenApi };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
interface PaymentInfo {
|
|
2
|
+
pricingMode?: string;
|
|
3
|
+
price?: string;
|
|
4
|
+
amount?: string;
|
|
5
|
+
currency?: string;
|
|
6
|
+
protocols?: string[];
|
|
7
|
+
intent?: string;
|
|
8
|
+
method?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
interface OpenApiOperation {
|
|
12
|
+
operationId?: string;
|
|
13
|
+
summary?: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
'x-payment-info'?: PaymentInfo;
|
|
16
|
+
requestBody?: Record<string, unknown>;
|
|
17
|
+
responses?: Record<string, unknown>;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface OpenApiPath {
|
|
21
|
+
[method: string]: OpenApiOperation;
|
|
22
|
+
}
|
|
23
|
+
interface OpenApiDocument {
|
|
24
|
+
openapi: string;
|
|
25
|
+
info: {
|
|
26
|
+
title: string;
|
|
27
|
+
version: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
'x-guidance'?: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
};
|
|
32
|
+
'x-discovery'?: {
|
|
33
|
+
ownershipProofs?: string[];
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
};
|
|
36
|
+
'x-service-info'?: Record<string, unknown>;
|
|
37
|
+
paths: Record<string, OpenApiPath>;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
interface DiscoveredEndpoint {
|
|
41
|
+
path: string;
|
|
42
|
+
method: string;
|
|
43
|
+
operationId?: string;
|
|
44
|
+
summary?: string;
|
|
45
|
+
paymentInfo: PaymentInfo;
|
|
46
|
+
hasRequestBody: boolean;
|
|
47
|
+
has402Response: boolean;
|
|
48
|
+
}
|
|
49
|
+
type Severity = 'error' | 'warning' | 'info';
|
|
50
|
+
interface ValidationIssue {
|
|
51
|
+
code: string;
|
|
52
|
+
severity: Severity;
|
|
53
|
+
message: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
method?: string;
|
|
56
|
+
}
|
|
57
|
+
interface DiscoverResult {
|
|
58
|
+
ok: boolean;
|
|
59
|
+
origin: string;
|
|
60
|
+
specUrl: string;
|
|
61
|
+
title: string;
|
|
62
|
+
version: string;
|
|
63
|
+
guidance?: string;
|
|
64
|
+
endpoints: DiscoveredEndpoint[];
|
|
65
|
+
totalEndpoints: number;
|
|
66
|
+
paidEndpoints: number;
|
|
67
|
+
issues: ValidationIssue[];
|
|
68
|
+
}
|
|
69
|
+
interface ProbeResult {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
url: string;
|
|
72
|
+
statusCode: number;
|
|
73
|
+
hasSuiPayment: boolean;
|
|
74
|
+
recipient?: string;
|
|
75
|
+
currency?: string;
|
|
76
|
+
amount?: string;
|
|
77
|
+
realm?: string;
|
|
78
|
+
issues: ValidationIssue[];
|
|
79
|
+
}
|
|
80
|
+
interface CheckResult {
|
|
81
|
+
ok: boolean;
|
|
82
|
+
origin: string;
|
|
83
|
+
discovery: DiscoverResult;
|
|
84
|
+
probe?: ProbeResult;
|
|
85
|
+
summary: {
|
|
86
|
+
totalIssues: number;
|
|
87
|
+
errors: number;
|
|
88
|
+
warnings: number;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
declare function fetchOpenApi(origin: string): Promise<OpenApiDocument>;
|
|
93
|
+
declare function extractEndpoints(doc: OpenApiDocument): DiscoveredEndpoint[];
|
|
94
|
+
declare function validateOpenApi(doc: OpenApiDocument, endpoints: DiscoveredEndpoint[], _origin?: string): ValidationIssue[];
|
|
95
|
+
declare function discover(origin: string): Promise<DiscoverResult>;
|
|
96
|
+
|
|
97
|
+
declare function probe(url: string, expectedOrigin?: string): Promise<ProbeResult>;
|
|
98
|
+
|
|
99
|
+
interface CheckOptions {
|
|
100
|
+
probeEndpoint?: boolean;
|
|
101
|
+
}
|
|
102
|
+
declare function check(origin: string, options?: CheckOptions): Promise<CheckResult>;
|
|
103
|
+
|
|
104
|
+
declare const SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
|
|
105
|
+
declare const SUI_USDC_TESTNET_TYPE = "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC";
|
|
106
|
+
declare const KNOWN_SUI_CURRENCIES: Set<string>;
|
|
107
|
+
declare const VALIDATION_CODES: {
|
|
108
|
+
readonly OPENAPI_FETCH_FAILED: "OPENAPI_FETCH_FAILED";
|
|
109
|
+
readonly OPENAPI_PARSE_FAILED: "OPENAPI_PARSE_FAILED";
|
|
110
|
+
readonly OPENAPI_VERSION_INVALID: "OPENAPI_VERSION_INVALID";
|
|
111
|
+
readonly NO_PAID_ENDPOINTS: "NO_PAID_ENDPOINTS";
|
|
112
|
+
readonly MISSING_402_RESPONSE: "MISSING_402_RESPONSE";
|
|
113
|
+
readonly MISSING_REQUEST_BODY: "MISSING_REQUEST_BODY";
|
|
114
|
+
readonly MISSING_PROTOCOLS: "MISSING_PROTOCOLS";
|
|
115
|
+
readonly PROTOCOL_NOT_MPP: "PROTOCOL_NOT_MPP";
|
|
116
|
+
readonly MISSING_PRICING: "MISSING_PRICING";
|
|
117
|
+
readonly INVALID_PRICE_FORMAT: "INVALID_PRICE_FORMAT";
|
|
118
|
+
readonly MISSING_GUIDANCE: "MISSING_GUIDANCE";
|
|
119
|
+
readonly HIGH_ROUTE_COUNT: "HIGH_ROUTE_COUNT";
|
|
120
|
+
readonly PROBE_FAILED: "PROBE_FAILED";
|
|
121
|
+
readonly PROBE_NOT_402: "PROBE_NOT_402";
|
|
122
|
+
readonly PROBE_INVALID_RECIPIENT: "PROBE_INVALID_RECIPIENT";
|
|
123
|
+
readonly PROBE_UNKNOWN_CURRENCY: "PROBE_UNKNOWN_CURRENCY";
|
|
124
|
+
readonly PROBE_REALM_MISMATCH: "PROBE_REALM_MISMATCH";
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export { type CheckOptions, type CheckResult, type DiscoverResult, type DiscoveredEndpoint, KNOWN_SUI_CURRENCIES, type OpenApiDocument, type OpenApiOperation, type OpenApiPath, type PaymentInfo, type ProbeResult, SUI_USDC_TESTNET_TYPE, SUI_USDC_TYPE, type Severity, VALIDATION_CODES, type ValidationIssue, check, discover, extractEndpoints, fetchOpenApi, probe, validateOpenApi };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var SUI_USDC_TYPE = "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC";
|
|
3
|
+
var SUI_USDC_TESTNET_TYPE = "0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC";
|
|
4
|
+
var KNOWN_SUI_CURRENCIES = /* @__PURE__ */ new Set([SUI_USDC_TYPE, SUI_USDC_TESTNET_TYPE]);
|
|
5
|
+
var SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{1,64}$/;
|
|
6
|
+
var VALIDATION_CODES = {
|
|
7
|
+
OPENAPI_FETCH_FAILED: "OPENAPI_FETCH_FAILED",
|
|
8
|
+
OPENAPI_PARSE_FAILED: "OPENAPI_PARSE_FAILED",
|
|
9
|
+
OPENAPI_VERSION_INVALID: "OPENAPI_VERSION_INVALID",
|
|
10
|
+
NO_PAID_ENDPOINTS: "NO_PAID_ENDPOINTS",
|
|
11
|
+
MISSING_402_RESPONSE: "MISSING_402_RESPONSE",
|
|
12
|
+
MISSING_REQUEST_BODY: "MISSING_REQUEST_BODY",
|
|
13
|
+
MISSING_PROTOCOLS: "MISSING_PROTOCOLS",
|
|
14
|
+
PROTOCOL_NOT_MPP: "PROTOCOL_NOT_MPP",
|
|
15
|
+
MISSING_PRICING: "MISSING_PRICING",
|
|
16
|
+
INVALID_PRICE_FORMAT: "INVALID_PRICE_FORMAT",
|
|
17
|
+
MISSING_GUIDANCE: "MISSING_GUIDANCE",
|
|
18
|
+
HIGH_ROUTE_COUNT: "HIGH_ROUTE_COUNT",
|
|
19
|
+
PROBE_FAILED: "PROBE_FAILED",
|
|
20
|
+
PROBE_NOT_402: "PROBE_NOT_402",
|
|
21
|
+
PROBE_INVALID_RECIPIENT: "PROBE_INVALID_RECIPIENT",
|
|
22
|
+
PROBE_UNKNOWN_CURRENCY: "PROBE_UNKNOWN_CURRENCY",
|
|
23
|
+
PROBE_REALM_MISMATCH: "PROBE_REALM_MISMATCH"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/discover.ts
|
|
27
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
28
|
+
async function fetchOpenApi(origin) {
|
|
29
|
+
const url = new URL("/openapi.json", origin).toString();
|
|
30
|
+
const res = await fetch(url, {
|
|
31
|
+
headers: { accept: "application/json" },
|
|
32
|
+
signal: AbortSignal.timeout(15e3)
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
throw new Error(`GET ${url} returned ${res.status}`);
|
|
36
|
+
}
|
|
37
|
+
return await res.json();
|
|
38
|
+
}
|
|
39
|
+
function extractEndpoints(doc) {
|
|
40
|
+
const endpoints = [];
|
|
41
|
+
for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
|
|
42
|
+
for (const method of HTTP_METHODS) {
|
|
43
|
+
const op = pathItem[method];
|
|
44
|
+
if (!op || !op["x-payment-info"]) continue;
|
|
45
|
+
endpoints.push({
|
|
46
|
+
path,
|
|
47
|
+
method: method.toUpperCase(),
|
|
48
|
+
operationId: op.operationId,
|
|
49
|
+
summary: op.summary,
|
|
50
|
+
paymentInfo: op["x-payment-info"],
|
|
51
|
+
hasRequestBody: !!op.requestBody,
|
|
52
|
+
has402Response: !!op.responses?.["402"]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return endpoints;
|
|
57
|
+
}
|
|
58
|
+
function validateOpenApi(doc, endpoints, _origin) {
|
|
59
|
+
const issues = [];
|
|
60
|
+
if (!doc.openapi?.startsWith("3.1")) {
|
|
61
|
+
issues.push({
|
|
62
|
+
code: VALIDATION_CODES.OPENAPI_VERSION_INVALID,
|
|
63
|
+
severity: "error",
|
|
64
|
+
message: `Expected OpenAPI 3.1.x, got "${doc.openapi}"`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (endpoints.length === 0) {
|
|
68
|
+
issues.push({
|
|
69
|
+
code: VALIDATION_CODES.NO_PAID_ENDPOINTS,
|
|
70
|
+
severity: "error",
|
|
71
|
+
message: "No operations with x-payment-info found"
|
|
72
|
+
});
|
|
73
|
+
return issues;
|
|
74
|
+
}
|
|
75
|
+
if (endpoints.length > 80) {
|
|
76
|
+
issues.push({
|
|
77
|
+
code: VALIDATION_CODES.HIGH_ROUTE_COUNT,
|
|
78
|
+
severity: "warning",
|
|
79
|
+
message: `High route count (${endpoints.length}) may exceed agent token budgets for zero-hop injection`
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (!doc.info?.["x-guidance"] && !doc["x-service-info"]) {
|
|
83
|
+
issues.push({
|
|
84
|
+
code: VALIDATION_CODES.MISSING_GUIDANCE,
|
|
85
|
+
severity: "warning",
|
|
86
|
+
message: "No x-guidance in info or x-service-info at root \u2014 agents may lack context"
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
for (const ep of endpoints) {
|
|
90
|
+
const loc = `${ep.method} ${ep.path}`;
|
|
91
|
+
if (!ep.has402Response) {
|
|
92
|
+
issues.push({
|
|
93
|
+
code: VALIDATION_CODES.MISSING_402_RESPONSE,
|
|
94
|
+
severity: "error",
|
|
95
|
+
message: `Missing 402 response definition`,
|
|
96
|
+
path: ep.path,
|
|
97
|
+
method: ep.method
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (["POST", "PUT", "PATCH"].includes(ep.method) && !ep.hasRequestBody) {
|
|
101
|
+
issues.push({
|
|
102
|
+
code: VALIDATION_CODES.MISSING_REQUEST_BODY,
|
|
103
|
+
severity: "warning",
|
|
104
|
+
message: `${loc} \u2014 POST/PUT/PATCH without requestBody schema`,
|
|
105
|
+
path: ep.path,
|
|
106
|
+
method: ep.method
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const pi = ep.paymentInfo;
|
|
110
|
+
if (!pi.protocols || pi.protocols.length === 0) {
|
|
111
|
+
issues.push({
|
|
112
|
+
code: VALIDATION_CODES.MISSING_PROTOCOLS,
|
|
113
|
+
severity: "warning",
|
|
114
|
+
message: `${loc} \u2014 x-payment-info missing "protocols" field`,
|
|
115
|
+
path: ep.path,
|
|
116
|
+
method: ep.method
|
|
117
|
+
});
|
|
118
|
+
} else if (!pi.protocols.includes("mpp")) {
|
|
119
|
+
issues.push({
|
|
120
|
+
code: VALIDATION_CODES.PROTOCOL_NOT_MPP,
|
|
121
|
+
severity: "error",
|
|
122
|
+
message: `${loc} \u2014 protocols ${JSON.stringify(pi.protocols)} does not include "mpp"`,
|
|
123
|
+
path: ep.path,
|
|
124
|
+
method: ep.method
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const mode = pi.pricingMode;
|
|
128
|
+
if (mode === "fixed" && !pi.price) {
|
|
129
|
+
issues.push({
|
|
130
|
+
code: VALIDATION_CODES.MISSING_PRICING,
|
|
131
|
+
severity: "error",
|
|
132
|
+
message: `${loc} \u2014 pricingMode "fixed" but no price specified`,
|
|
133
|
+
path: ep.path,
|
|
134
|
+
method: ep.method
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (pi.price && !/^\$?\d+(\.\d+)?$/.test(pi.price)) {
|
|
138
|
+
issues.push({
|
|
139
|
+
code: VALIDATION_CODES.INVALID_PRICE_FORMAT,
|
|
140
|
+
severity: "warning",
|
|
141
|
+
message: `${loc} \u2014 price "${pi.price}" may not be parseable`,
|
|
142
|
+
path: ep.path,
|
|
143
|
+
method: ep.method
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return issues;
|
|
148
|
+
}
|
|
149
|
+
async function discover(origin) {
|
|
150
|
+
const normalizedOrigin = origin.startsWith("http") ? origin : `https://${origin}`;
|
|
151
|
+
const specUrl = new URL("/openapi.json", normalizedOrigin).toString();
|
|
152
|
+
try {
|
|
153
|
+
const doc = await fetchOpenApi(normalizedOrigin);
|
|
154
|
+
const endpoints = extractEndpoints(doc);
|
|
155
|
+
const issues = validateOpenApi(doc, endpoints, normalizedOrigin);
|
|
156
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
157
|
+
return {
|
|
158
|
+
ok: !hasErrors,
|
|
159
|
+
origin: normalizedOrigin,
|
|
160
|
+
specUrl,
|
|
161
|
+
title: doc.info?.title ?? "Unknown",
|
|
162
|
+
version: doc.info?.version ?? "0.0.0",
|
|
163
|
+
guidance: doc.info?.["x-guidance"],
|
|
164
|
+
endpoints,
|
|
165
|
+
totalEndpoints: Object.keys(doc.paths ?? {}).length,
|
|
166
|
+
paidEndpoints: endpoints.length,
|
|
167
|
+
issues
|
|
168
|
+
};
|
|
169
|
+
} catch (err) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
origin: normalizedOrigin,
|
|
173
|
+
specUrl,
|
|
174
|
+
title: "Unknown",
|
|
175
|
+
version: "0.0.0",
|
|
176
|
+
endpoints: [],
|
|
177
|
+
totalEndpoints: 0,
|
|
178
|
+
paidEndpoints: 0,
|
|
179
|
+
issues: [
|
|
180
|
+
{
|
|
181
|
+
code: VALIDATION_CODES.OPENAPI_FETCH_FAILED,
|
|
182
|
+
severity: "error",
|
|
183
|
+
message: `Failed to fetch OpenAPI: ${err instanceof Error ? err.message : String(err)}`
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/probe.ts
|
|
191
|
+
function decodeRequest(encoded) {
|
|
192
|
+
try {
|
|
193
|
+
const json = Buffer.from(encoded, "base64").toString("utf-8");
|
|
194
|
+
const parsed = JSON.parse(json);
|
|
195
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
function challengeFromAccepts(accepts) {
|
|
201
|
+
if (!Array.isArray(accepts)) return null;
|
|
202
|
+
const sui = accepts.filter(
|
|
203
|
+
(a) => a && typeof a === "object" && a.scheme === "exact" && typeof a.network === "string" && a.network.startsWith("sui")
|
|
204
|
+
);
|
|
205
|
+
const entry = sui.find((a) => a.network === "sui:mainnet") ?? sui[0];
|
|
206
|
+
if (!entry || typeof entry.payTo !== "string") return null;
|
|
207
|
+
const challenge = { recipient: entry.payTo };
|
|
208
|
+
if (typeof entry.asset === "string") challenge.currency = entry.asset;
|
|
209
|
+
if (typeof entry.maxAmountRequired === "string" && /^\d+$/.test(entry.maxAmountRequired)) {
|
|
210
|
+
challenge.amount = typeof entry.asset === "string" && KNOWN_SUI_CURRENCIES.has(entry.asset) ? (Number(entry.maxAmountRequired) / 1e6).toString() : entry.maxAmountRequired;
|
|
211
|
+
}
|
|
212
|
+
if (typeof entry.resource === "string") {
|
|
213
|
+
try {
|
|
214
|
+
challenge.realm = new URL(entry.resource).hostname;
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return challenge;
|
|
219
|
+
}
|
|
220
|
+
function extractChallenge(headers, body) {
|
|
221
|
+
const wwwAuth = headers.get("www-authenticate");
|
|
222
|
+
if (wwwAuth) {
|
|
223
|
+
const params = {};
|
|
224
|
+
for (const match of wwwAuth.matchAll(/(\w+)="([^"]*)"/g)) {
|
|
225
|
+
params[match[1]] = match[2];
|
|
226
|
+
}
|
|
227
|
+
if (params.request) {
|
|
228
|
+
const decoded = decodeRequest(params.request);
|
|
229
|
+
Object.assign(params, decoded);
|
|
230
|
+
}
|
|
231
|
+
if (params.recipient || params.currency) return params;
|
|
232
|
+
}
|
|
233
|
+
if (body && typeof body === "object") {
|
|
234
|
+
const b = body;
|
|
235
|
+
if (b.recipient || b.currency || b.amount) return b;
|
|
236
|
+
if (b.paymentRequirements && typeof b.paymentRequirements === "object") {
|
|
237
|
+
return b.paymentRequirements;
|
|
238
|
+
}
|
|
239
|
+
const fromAccepts = challengeFromAccepts(b.accepts);
|
|
240
|
+
if (fromAccepts) return fromAccepts;
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
async function probe(url, expectedOrigin) {
|
|
245
|
+
const issues = [];
|
|
246
|
+
try {
|
|
247
|
+
const res = await fetch(url, {
|
|
248
|
+
method: "POST",
|
|
249
|
+
headers: { "content-type": "application/json" },
|
|
250
|
+
body: JSON.stringify({}),
|
|
251
|
+
signal: AbortSignal.timeout(15e3),
|
|
252
|
+
redirect: "follow"
|
|
253
|
+
});
|
|
254
|
+
if (res.status !== 402) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
url,
|
|
258
|
+
statusCode: res.status,
|
|
259
|
+
hasSuiPayment: false,
|
|
260
|
+
issues: [
|
|
261
|
+
{
|
|
262
|
+
code: VALIDATION_CODES.PROBE_NOT_402,
|
|
263
|
+
severity: "error",
|
|
264
|
+
message: `Expected 402 Payment Required, got ${res.status}`
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
let body;
|
|
270
|
+
try {
|
|
271
|
+
body = await res.json();
|
|
272
|
+
} catch {
|
|
273
|
+
body = null;
|
|
274
|
+
}
|
|
275
|
+
const challenge = extractChallenge(res.headers, body);
|
|
276
|
+
if (!challenge) {
|
|
277
|
+
return {
|
|
278
|
+
ok: false,
|
|
279
|
+
url,
|
|
280
|
+
statusCode: 402,
|
|
281
|
+
hasSuiPayment: false,
|
|
282
|
+
issues: [
|
|
283
|
+
{
|
|
284
|
+
code: VALIDATION_CODES.PROBE_FAILED,
|
|
285
|
+
severity: "error",
|
|
286
|
+
message: "Got 402 but could not extract payment challenge from headers or body"
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (challenge.recipient && !SUI_ADDRESS_REGEX.test(challenge.recipient)) {
|
|
292
|
+
issues.push({
|
|
293
|
+
code: VALIDATION_CODES.PROBE_INVALID_RECIPIENT,
|
|
294
|
+
severity: "error",
|
|
295
|
+
message: `Recipient "${challenge.recipient}" is not a valid Sui address`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
if (challenge.currency && !KNOWN_SUI_CURRENCIES.has(challenge.currency)) {
|
|
299
|
+
issues.push({
|
|
300
|
+
code: VALIDATION_CODES.PROBE_UNKNOWN_CURRENCY,
|
|
301
|
+
severity: "warning",
|
|
302
|
+
message: `Currency "${challenge.currency}" is not a recognized Sui USDC type`
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (expectedOrigin && challenge.realm) {
|
|
306
|
+
const expectedHost = new URL(expectedOrigin).hostname;
|
|
307
|
+
if (challenge.realm !== expectedHost && !challenge.realm.endsWith(`.${expectedHost}`)) {
|
|
308
|
+
issues.push({
|
|
309
|
+
code: VALIDATION_CODES.PROBE_REALM_MISMATCH,
|
|
310
|
+
severity: "error",
|
|
311
|
+
message: `Payment realm "${challenge.realm}" does not match origin host "${expectedHost}"`
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
316
|
+
return {
|
|
317
|
+
ok: !hasErrors,
|
|
318
|
+
url,
|
|
319
|
+
statusCode: 402,
|
|
320
|
+
hasSuiPayment: true,
|
|
321
|
+
recipient: challenge.recipient,
|
|
322
|
+
currency: challenge.currency,
|
|
323
|
+
amount: challenge.amount,
|
|
324
|
+
realm: challenge.realm,
|
|
325
|
+
issues
|
|
326
|
+
};
|
|
327
|
+
} catch (err) {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
url,
|
|
331
|
+
statusCode: 0,
|
|
332
|
+
hasSuiPayment: false,
|
|
333
|
+
issues: [
|
|
334
|
+
{
|
|
335
|
+
code: VALIDATION_CODES.PROBE_FAILED,
|
|
336
|
+
severity: "error",
|
|
337
|
+
message: `Probe failed: ${err instanceof Error ? err.message : String(err)}`
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/check.ts
|
|
345
|
+
async function check(origin, options = {}) {
|
|
346
|
+
const { probeEndpoint = true } = options;
|
|
347
|
+
const normalizedOrigin = origin.startsWith("http") ? origin : `https://${origin}`;
|
|
348
|
+
const discovery = await discover(normalizedOrigin);
|
|
349
|
+
let probeResult = void 0;
|
|
350
|
+
if (probeEndpoint && discovery.endpoints.length > 0) {
|
|
351
|
+
const firstPost = discovery.endpoints.find((e) => e.method === "POST");
|
|
352
|
+
const target = firstPost ?? discovery.endpoints[0];
|
|
353
|
+
const probeUrl = new URL(target.path, normalizedOrigin).toString();
|
|
354
|
+
probeResult = await probe(probeUrl, normalizedOrigin);
|
|
355
|
+
}
|
|
356
|
+
const allIssues = [
|
|
357
|
+
...discovery.issues,
|
|
358
|
+
...probeResult?.issues ?? []
|
|
359
|
+
];
|
|
360
|
+
const errors = allIssues.filter((i) => i.severity === "error").length;
|
|
361
|
+
const warnings = allIssues.filter((i) => i.severity === "warning").length;
|
|
362
|
+
return {
|
|
363
|
+
ok: errors === 0,
|
|
364
|
+
origin: normalizedOrigin,
|
|
365
|
+
discovery,
|
|
366
|
+
probe: probeResult,
|
|
367
|
+
summary: {
|
|
368
|
+
totalIssues: allIssues.length,
|
|
369
|
+
errors,
|
|
370
|
+
warnings
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export { KNOWN_SUI_CURRENCIES, SUI_USDC_TESTNET_TYPE, SUI_USDC_TYPE, VALIDATION_CODES, check, discover, extractEndpoints, fetchOpenApi, probe, validateOpenApi };
|
|
376
|
+
//# sourceMappingURL=index.js.map
|
|
377
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/discover.ts","../src/probe.ts","../src/check.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const SUI_USDC_TYPE =\n '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';\n\nexport const SUI_USDC_TESTNET_TYPE =\n '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC';\n\nexport const KNOWN_SUI_CURRENCIES = new Set([SUI_USDC_TYPE, SUI_USDC_TESTNET_TYPE]);\n\nexport const SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{1,64}$/;\n\nexport const VALIDATION_CODES = {\n OPENAPI_FETCH_FAILED: 'OPENAPI_FETCH_FAILED',\n OPENAPI_PARSE_FAILED: 'OPENAPI_PARSE_FAILED',\n OPENAPI_VERSION_INVALID: 'OPENAPI_VERSION_INVALID',\n NO_PAID_ENDPOINTS: 'NO_PAID_ENDPOINTS',\n MISSING_402_RESPONSE: 'MISSING_402_RESPONSE',\n MISSING_REQUEST_BODY: 'MISSING_REQUEST_BODY',\n MISSING_PROTOCOLS: 'MISSING_PROTOCOLS',\n PROTOCOL_NOT_MPP: 'PROTOCOL_NOT_MPP',\n MISSING_PRICING: 'MISSING_PRICING',\n INVALID_PRICE_FORMAT: 'INVALID_PRICE_FORMAT',\n MISSING_GUIDANCE: 'MISSING_GUIDANCE',\n HIGH_ROUTE_COUNT: 'HIGH_ROUTE_COUNT',\n PROBE_FAILED: 'PROBE_FAILED',\n PROBE_NOT_402: 'PROBE_NOT_402',\n PROBE_INVALID_RECIPIENT: 'PROBE_INVALID_RECIPIENT',\n PROBE_UNKNOWN_CURRENCY: 'PROBE_UNKNOWN_CURRENCY',\n PROBE_REALM_MISMATCH: 'PROBE_REALM_MISMATCH',\n} as const;\n","import { VALIDATION_CODES } from './constants.js';\nimport type {\n OpenApiDocument,\n DiscoverResult,\n DiscoveredEndpoint,\n ValidationIssue,\n} from './types.js';\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;\n\nexport async function fetchOpenApi(origin: string): Promise<OpenApiDocument> {\n const url = new URL('/openapi.json', origin).toString();\n const res = await fetch(url, {\n headers: { accept: 'application/json' },\n signal: AbortSignal.timeout(15_000),\n });\n if (!res.ok) {\n throw new Error(`GET ${url} returned ${res.status}`);\n }\n return (await res.json()) as OpenApiDocument;\n}\n\nexport function extractEndpoints(doc: OpenApiDocument): DiscoveredEndpoint[] {\n const endpoints: DiscoveredEndpoint[] = [];\n\n for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {\n for (const method of HTTP_METHODS) {\n const op = pathItem[method];\n if (!op || !op['x-payment-info']) continue;\n\n endpoints.push({\n path,\n method: method.toUpperCase(),\n operationId: op.operationId,\n summary: op.summary,\n paymentInfo: op['x-payment-info'],\n hasRequestBody: !!op.requestBody,\n has402Response: !!op.responses?.['402'],\n });\n }\n }\n\n return endpoints;\n}\n\nexport function validateOpenApi(\n doc: OpenApiDocument,\n endpoints: DiscoveredEndpoint[],\n _origin?: string,\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n if (!doc.openapi?.startsWith('3.1')) {\n issues.push({\n code: VALIDATION_CODES.OPENAPI_VERSION_INVALID,\n severity: 'error',\n message: `Expected OpenAPI 3.1.x, got \"${doc.openapi}\"`,\n });\n }\n\n if (endpoints.length === 0) {\n issues.push({\n code: VALIDATION_CODES.NO_PAID_ENDPOINTS,\n severity: 'error',\n message: 'No operations with x-payment-info found',\n });\n return issues;\n }\n\n if (endpoints.length > 80) {\n issues.push({\n code: VALIDATION_CODES.HIGH_ROUTE_COUNT,\n severity: 'warning',\n message: `High route count (${endpoints.length}) may exceed agent token budgets for zero-hop injection`,\n });\n }\n\n if (!doc.info?.['x-guidance'] && !doc['x-service-info']) {\n issues.push({\n code: VALIDATION_CODES.MISSING_GUIDANCE,\n severity: 'warning',\n message: 'No x-guidance in info or x-service-info at root — agents may lack context',\n });\n }\n\n for (const ep of endpoints) {\n const loc = `${ep.method} ${ep.path}`;\n\n if (!ep.has402Response) {\n issues.push({\n code: VALIDATION_CODES.MISSING_402_RESPONSE,\n severity: 'error',\n message: `Missing 402 response definition`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n if (['POST', 'PUT', 'PATCH'].includes(ep.method) && !ep.hasRequestBody) {\n issues.push({\n code: VALIDATION_CODES.MISSING_REQUEST_BODY,\n severity: 'warning',\n message: `${loc} — POST/PUT/PATCH without requestBody schema`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n const pi = ep.paymentInfo;\n\n if (!pi.protocols || pi.protocols.length === 0) {\n issues.push({\n code: VALIDATION_CODES.MISSING_PROTOCOLS,\n severity: 'warning',\n message: `${loc} — x-payment-info missing \"protocols\" field`,\n path: ep.path,\n method: ep.method,\n });\n } else if (!pi.protocols.includes('mpp')) {\n issues.push({\n code: VALIDATION_CODES.PROTOCOL_NOT_MPP,\n severity: 'error',\n message: `${loc} — protocols ${JSON.stringify(pi.protocols)} does not include \"mpp\"`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n const mode = pi.pricingMode;\n if (mode === 'fixed' && !pi.price) {\n issues.push({\n code: VALIDATION_CODES.MISSING_PRICING,\n severity: 'error',\n message: `${loc} — pricingMode \"fixed\" but no price specified`,\n path: ep.path,\n method: ep.method,\n });\n }\n\n if (pi.price && !/^\\$?\\d+(\\.\\d+)?$/.test(pi.price)) {\n issues.push({\n code: VALIDATION_CODES.INVALID_PRICE_FORMAT,\n severity: 'warning',\n message: `${loc} — price \"${pi.price}\" may not be parseable`,\n path: ep.path,\n method: ep.method,\n });\n }\n }\n\n return issues;\n}\n\nexport async function discover(origin: string): Promise<DiscoverResult> {\n const normalizedOrigin = origin.startsWith('http') ? origin : `https://${origin}`;\n const specUrl = new URL('/openapi.json', normalizedOrigin).toString();\n\n try {\n const doc = await fetchOpenApi(normalizedOrigin);\n const endpoints = extractEndpoints(doc);\n const issues = validateOpenApi(doc, endpoints, normalizedOrigin);\n\n const hasErrors = issues.some(i => i.severity === 'error');\n\n return {\n ok: !hasErrors,\n origin: normalizedOrigin,\n specUrl,\n title: doc.info?.title ?? 'Unknown',\n version: doc.info?.version ?? '0.0.0',\n guidance: doc.info?.['x-guidance'] as string | undefined,\n endpoints,\n totalEndpoints: Object.keys(doc.paths ?? {}).length,\n paidEndpoints: endpoints.length,\n issues,\n };\n } catch (err) {\n return {\n ok: false,\n origin: normalizedOrigin,\n specUrl,\n title: 'Unknown',\n version: '0.0.0',\n endpoints: [],\n totalEndpoints: 0,\n paidEndpoints: 0,\n issues: [\n {\n code: VALIDATION_CODES.OPENAPI_FETCH_FAILED,\n severity: 'error',\n message: `Failed to fetch OpenAPI: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n }\n}\n","import { SUI_ADDRESS_REGEX, KNOWN_SUI_CURRENCIES, VALIDATION_CODES } from './constants.js';\nimport type { ProbeResult, ValidationIssue } from './types.js';\n\ninterface MppChallenge {\n amount?: string;\n currency?: string;\n recipient?: string;\n realm?: string;\n network?: string;\n [key: string]: unknown;\n}\n\nfunction decodeRequest(encoded: string): Record<string, string> {\n try {\n const json = Buffer.from(encoded, 'base64').toString('utf-8');\n const parsed = JSON.parse(json);\n if (parsed && typeof parsed === 'object') return parsed;\n } catch {}\n return {};\n}\n\ninterface X402Accept {\n scheme?: unknown;\n network?: unknown;\n payTo?: unknown;\n asset?: unknown;\n maxAmountRequired?: unknown;\n resource?: unknown;\n}\n\n/** x402 `accepts[]` entry → the MPP challenge shape the rest of probe() reads.\n * `maxAmountRequired` is raw 6-decimal USDC units; the header dialect's\n * `amount` is a decimal string — convert (known-USDC only) so `amount` means\n * the same thing whichever dialect answered. */\nfunction challengeFromAccepts(accepts: unknown): MppChallenge | null {\n if (!Array.isArray(accepts)) return null;\n const sui = accepts.filter(\n (a: X402Accept) =>\n a &&\n typeof a === 'object' &&\n a.scheme === 'exact' &&\n typeof a.network === 'string' &&\n a.network.startsWith('sui'),\n ) as X402Accept[];\n const entry = sui.find(a => a.network === 'sui:mainnet') ?? sui[0];\n if (!entry || typeof entry.payTo !== 'string') return null;\n\n const challenge: MppChallenge = { recipient: entry.payTo };\n if (typeof entry.asset === 'string') challenge.currency = entry.asset;\n if (typeof entry.maxAmountRequired === 'string' && /^\\d+$/.test(entry.maxAmountRequired)) {\n challenge.amount =\n typeof entry.asset === 'string' && KNOWN_SUI_CURRENCIES.has(entry.asset)\n ? (Number(entry.maxAmountRequired) / 1_000_000).toString()\n : entry.maxAmountRequired;\n }\n if (typeof entry.resource === 'string') {\n try {\n challenge.realm = new URL(entry.resource).hostname;\n } catch {}\n }\n return challenge;\n}\n\nfunction extractChallenge(headers: Headers, body: unknown): MppChallenge | null {\n const wwwAuth = headers.get('www-authenticate');\n if (wwwAuth) {\n const params: Record<string, string> = {};\n for (const match of wwwAuth.matchAll(/(\\w+)=\"([^\"]*)\"/g)) {\n params[match[1]] = match[2];\n }\n\n // mppx encodes amount/currency/recipient inside a base64 `request` field\n if (params.request) {\n const decoded = decodeRequest(params.request);\n Object.assign(params, decoded);\n }\n\n if (params.recipient || params.currency) return params;\n }\n\n if (body && typeof body === 'object') {\n const b = body as Record<string, unknown>;\n if (b.recipient || b.currency || b.amount) return b as MppChallenge;\n\n if (b.paymentRequirements && typeof b.paymentRequirements === 'object') {\n return b.paymentRequirements as MppChallenge;\n }\n\n // x402 body-only sellers (@t2000/serve emits accepts[] and no\n // WWW-Authenticate) — the dialect this probe rejected until 0.2.2.\n const fromAccepts = challengeFromAccepts(b.accepts);\n if (fromAccepts) return fromAccepts;\n }\n\n return null;\n}\n\nexport async function probe(url: string, expectedOrigin?: string): Promise<ProbeResult> {\n const issues: ValidationIssue[] = [];\n\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({}),\n signal: AbortSignal.timeout(15_000),\n redirect: 'follow',\n });\n\n if (res.status !== 402) {\n return {\n ok: false,\n url,\n statusCode: res.status,\n hasSuiPayment: false,\n issues: [\n {\n code: VALIDATION_CODES.PROBE_NOT_402,\n severity: 'error',\n message: `Expected 402 Payment Required, got ${res.status}`,\n },\n ],\n };\n }\n\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n body = null;\n }\n\n const challenge = extractChallenge(res.headers, body);\n if (!challenge) {\n return {\n ok: false,\n url,\n statusCode: 402,\n hasSuiPayment: false,\n issues: [\n {\n code: VALIDATION_CODES.PROBE_FAILED,\n severity: 'error',\n message: 'Got 402 but could not extract payment challenge from headers or body',\n },\n ],\n };\n }\n\n if (challenge.recipient && !SUI_ADDRESS_REGEX.test(challenge.recipient)) {\n issues.push({\n code: VALIDATION_CODES.PROBE_INVALID_RECIPIENT,\n severity: 'error',\n message: `Recipient \"${challenge.recipient}\" is not a valid Sui address`,\n });\n }\n\n if (challenge.currency && !KNOWN_SUI_CURRENCIES.has(challenge.currency)) {\n issues.push({\n code: VALIDATION_CODES.PROBE_UNKNOWN_CURRENCY,\n severity: 'warning',\n message: `Currency \"${challenge.currency}\" is not a recognized Sui USDC type`,\n });\n }\n\n if (expectedOrigin && challenge.realm) {\n const expectedHost = new URL(expectedOrigin).hostname;\n if (challenge.realm !== expectedHost && !challenge.realm.endsWith(`.${expectedHost}`)) {\n issues.push({\n code: VALIDATION_CODES.PROBE_REALM_MISMATCH,\n severity: 'error',\n message: `Payment realm \"${challenge.realm}\" does not match origin host \"${expectedHost}\"`,\n });\n }\n }\n\n const hasErrors = issues.some(i => i.severity === 'error');\n\n return {\n ok: !hasErrors,\n url,\n statusCode: 402,\n hasSuiPayment: true,\n recipient: challenge.recipient,\n currency: challenge.currency,\n amount: challenge.amount,\n realm: challenge.realm,\n issues,\n };\n } catch (err) {\n return {\n ok: false,\n url,\n statusCode: 0,\n hasSuiPayment: false,\n issues: [\n {\n code: VALIDATION_CODES.PROBE_FAILED,\n severity: 'error',\n message: `Probe failed: ${err instanceof Error ? err.message : String(err)}`,\n },\n ],\n };\n }\n}\n","import { discover } from './discover.js';\nimport { probe } from './probe.js';\nimport type { CheckResult } from './types.js';\n\nexport interface CheckOptions {\n probeEndpoint?: boolean;\n}\n\nexport async function check(\n origin: string,\n options: CheckOptions = {},\n): Promise<CheckResult> {\n const { probeEndpoint = true } = options;\n\n const normalizedOrigin = origin.startsWith('http') ? origin : `https://${origin}`;\n const discovery = await discover(normalizedOrigin);\n\n let probeResult = undefined;\n if (probeEndpoint && discovery.endpoints.length > 0) {\n const firstPost = discovery.endpoints.find(e => e.method === 'POST');\n const target = firstPost ?? discovery.endpoints[0];\n const probeUrl = new URL(target.path, normalizedOrigin).toString();\n probeResult = await probe(probeUrl, normalizedOrigin);\n }\n\n const allIssues = [\n ...discovery.issues,\n ...(probeResult?.issues ?? []),\n ];\n\n const errors = allIssues.filter(i => i.severity === 'error').length;\n const warnings = allIssues.filter(i => i.severity === 'warning').length;\n\n return {\n ok: errors === 0,\n origin: normalizedOrigin,\n discovery,\n probe: probeResult,\n summary: {\n totalIssues: allIssues.length,\n errors,\n warnings,\n },\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@t2000/discovery",
|
|
3
|
+
"version": "10.20.0",
|
|
4
|
+
"description": "Probe and validate x402 seller endpoints on Sui — 402 accepts[] / WWW-Authenticate probing plus OpenAPI paid-endpoint extraction. The discovery half of the @t2000 serve stack.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"x402",
|
|
22
|
+
"sui",
|
|
23
|
+
"discovery",
|
|
24
|
+
"openapi",
|
|
25
|
+
"agent",
|
|
26
|
+
"payments"
|
|
27
|
+
],
|
|
28
|
+
"author": "t2000",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/mission69b/t2000.git",
|
|
32
|
+
"directory": "packages/discovery"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://docs.t2000.ai/sell-to-agents/overview",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20",
|
|
37
|
+
"eslint": "^9",
|
|
38
|
+
"tsup": "^8",
|
|
39
|
+
"typescript": "^5",
|
|
40
|
+
"typescript-eslint": "^8.58.0",
|
|
41
|
+
"vitest": "^3"
|
|
42
|
+
},
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"lint": "eslint src/",
|
|
50
|
+
"clean": "rm -rf dist"
|
|
51
|
+
}
|
|
52
|
+
}
|