x402-surface-check 0.2.15 → 0.2.16
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 +2 -1
- package/bin/x402-surface-check.mjs +53 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,8 @@ npx --yes x402-surface-check --endpoint --method POST https://x402.rpc.ankr.com/
|
|
|
17
17
|
- Manifest endpoint discovery from `items[]`, `endpoints[]`, `resources[]`, `x402Endpoints`, category arrays, resource strings, and OpenAPI paths
|
|
18
18
|
- Linked discovery documents via `discovery_url`, `discoveryUrl`, `resources_url`, `resourcesUrl`, or manifest-level OpenAPI links
|
|
19
19
|
- OpenAPI `servers[]` base-path preservation, so `/paths` are probed through the documented gateway rather than the domain root
|
|
20
|
-
- OpenAPI query/path examples
|
|
20
|
+
- OpenAPI query/path examples, JSON request-body examples, and local `$ref` request schemas for safer no-payment probes
|
|
21
|
+
- OpenAPI paid-operation prioritization, so docs and discovery routes do not consume the probe limit before payment-bearing operations
|
|
21
22
|
- No-payment HTTP 402 challenge shape
|
|
22
23
|
- x402 v1 and v2 price fields, including `accepts[]` and `schemes[]` challenge arrays
|
|
23
24
|
- MPP `WWW-Authenticate: Payment` and x402 V2 `WWW-Authenticate: X402 requirements=...` challenges
|
|
@@ -157,21 +157,48 @@ function operationExpectedPrice(operation) {
|
|
|
157
157
|
return numeric === null ? null : numeric
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
function
|
|
160
|
+
function resolveLocalRef(ref, document) {
|
|
161
|
+
if (typeof ref !== 'string' || !ref.startsWith('#/')) return undefined
|
|
162
|
+
return ref
|
|
163
|
+
.slice(2)
|
|
164
|
+
.split('/')
|
|
165
|
+
.map(part => part.replaceAll('~1', '/').replaceAll('~0', '~'))
|
|
166
|
+
.reduce((value, part) => value?.[part], document)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function resolveSchema(schema, document, seen = new Set()) {
|
|
170
|
+
if (!schema || typeof schema !== 'object') return schema
|
|
171
|
+
if (!schema.$ref) return schema
|
|
172
|
+
if (seen.has(schema.$ref)) return schema
|
|
173
|
+
const resolved = resolveLocalRef(schema.$ref, document)
|
|
174
|
+
if (!resolved) return schema
|
|
175
|
+
seen.add(schema.$ref)
|
|
176
|
+
return resolveSchema(resolved, document, seen)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function exampleValue(schemaOrParameter, document) {
|
|
161
180
|
if (!schemaOrParameter || typeof schemaOrParameter !== 'object') return undefined
|
|
162
|
-
const schema = schemaOrParameter.schema ?? schemaOrParameter
|
|
181
|
+
const schema = resolveSchema(schemaOrParameter.schema ?? schemaOrParameter, document)
|
|
163
182
|
const value = schemaOrParameter.example
|
|
183
|
+
?? schema.const
|
|
164
184
|
?? schema.example
|
|
165
185
|
?? schema.default
|
|
166
186
|
?? (Array.isArray(schema.enum) ? schema.enum[0] : undefined)
|
|
167
187
|
if (value !== undefined) return value
|
|
168
|
-
if (schema.type === 'string')
|
|
169
|
-
|
|
188
|
+
if (schema.type === 'string') {
|
|
189
|
+
if (schema.format === 'uri') return 'https://example.com'
|
|
190
|
+
if (schema.format === 'date-time') return '2026-01-01T00:00:00.000Z'
|
|
191
|
+
if (schema.format === 'date') return '2026-01-01'
|
|
192
|
+
if (Number(schema.minLength) > 0) return 'example'
|
|
193
|
+
return ''
|
|
194
|
+
}
|
|
195
|
+
if (schema.type === 'integer') return Number.isFinite(Number(schema.minimum)) ? Number(schema.minimum) : 1
|
|
196
|
+
if (schema.type === 'number') return Number.isFinite(Number(schema.minimum)) ? Number(schema.minimum) : 1
|
|
170
197
|
if (schema.type === 'boolean') return false
|
|
171
198
|
return undefined
|
|
172
199
|
}
|
|
173
200
|
|
|
174
|
-
function mediaExample(media) {
|
|
201
|
+
function mediaExample(media, document) {
|
|
175
202
|
if (!media || typeof media !== 'object') return undefined
|
|
176
203
|
if (media.example !== undefined) return media.example
|
|
177
204
|
const examples = media.examples && typeof media.examples === 'object'
|
|
@@ -181,7 +208,7 @@ function mediaExample(media) {
|
|
|
181
208
|
if (firstExample?.value !== undefined) return firstExample.value
|
|
182
209
|
if (firstExample?.externalValue) return undefined
|
|
183
210
|
|
|
184
|
-
const schema = media.schema
|
|
211
|
+
const schema = resolveSchema(media.schema, document)
|
|
185
212
|
if (!schema || typeof schema !== 'object' || schema.type !== 'object') return undefined
|
|
186
213
|
const body = {}
|
|
187
214
|
const properties = schema.properties && typeof schema.properties === 'object'
|
|
@@ -191,29 +218,35 @@ function mediaExample(media) {
|
|
|
191
218
|
|
|
192
219
|
for (const [name, property] of Object.entries(properties)) {
|
|
193
220
|
if (!required.has(name)) continue
|
|
194
|
-
const value = exampleValue(property)
|
|
221
|
+
const value = exampleValue(property, document)
|
|
195
222
|
if (value !== undefined) body[name] = value
|
|
196
223
|
}
|
|
197
224
|
|
|
198
225
|
return Object.keys(body).length ? body : undefined
|
|
199
226
|
}
|
|
200
227
|
|
|
201
|
-
function operationRequestBody(operation) {
|
|
228
|
+
function operationRequestBody(operation, document) {
|
|
202
229
|
const content = operation?.requestBody?.content
|
|
203
230
|
if (!content || typeof content !== 'object') return undefined
|
|
204
231
|
const media = content['application/json']
|
|
205
232
|
?? content['application/*+json']
|
|
206
233
|
?? Object.entries(content).find(([type]) => /json/i.test(type))?.[1]
|
|
207
|
-
return mediaExample(media)
|
|
234
|
+
return mediaExample(media, document)
|
|
208
235
|
}
|
|
209
236
|
|
|
210
|
-
function
|
|
237
|
+
function operationPaymentSignal(operation) {
|
|
238
|
+
if (operation?.['x-payment-info'] || operation?.['x-payment'] || operation?.['x-x402'] || operation?.payment) return 2
|
|
239
|
+
if (operation?.responses && Object.hasOwn(operation.responses, '402')) return 1
|
|
240
|
+
return 0
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function openApiProbeUrl(path, operation, baseUrl, document) {
|
|
211
244
|
const parameters = Array.isArray(operation?.parameters) ? operation.parameters : []
|
|
212
245
|
let resolvedPath = path
|
|
213
246
|
const searchParams = new URLSearchParams()
|
|
214
247
|
|
|
215
248
|
for (const parameter of parameters) {
|
|
216
|
-
const value = exampleValue(parameter)
|
|
249
|
+
const value = exampleValue(parameter, document)
|
|
217
250
|
if (value === undefined || value === '') continue
|
|
218
251
|
if (parameter.in === 'path') {
|
|
219
252
|
resolvedPath = resolvedPath.replaceAll(`{${parameter.name}}`, encodeURIComponent(String(value)))
|
|
@@ -282,22 +315,28 @@ function endpointEntries(document, sourceUrl, limit) {
|
|
|
282
315
|
|
|
283
316
|
if (document.openapi && document.paths && typeof document.paths === 'object') {
|
|
284
317
|
const baseUrl = openApiServerBaseUrl(document, sourceUrl)
|
|
318
|
+
const openApiEntries = []
|
|
285
319
|
|
|
286
320
|
for (const [path, operations] of Object.entries(document.paths)) {
|
|
287
321
|
if (!operations || typeof operations !== 'object') continue
|
|
288
322
|
for (const method of methods) {
|
|
289
323
|
const operation = operations[method]
|
|
290
324
|
if (!operation || typeof operation !== 'object') continue
|
|
291
|
-
const url = openApiProbeUrl(path, operation, baseUrl)
|
|
292
|
-
|
|
325
|
+
const url = openApiProbeUrl(path, operation, baseUrl, document)
|
|
326
|
+
openApiEntries.push({
|
|
293
327
|
name: operation.operationId ?? `${method.toUpperCase()} ${path}`,
|
|
294
328
|
url,
|
|
295
329
|
method: method.toUpperCase(),
|
|
296
330
|
expectedPriceUsd: operationExpectedPrice(operation),
|
|
297
|
-
requestBody: operationRequestBody(operation),
|
|
331
|
+
requestBody: operationRequestBody(operation, document),
|
|
332
|
+
paymentSignal: operationPaymentSignal(operation),
|
|
298
333
|
})
|
|
299
334
|
}
|
|
300
335
|
}
|
|
336
|
+
|
|
337
|
+
entries.push(...openApiEntries
|
|
338
|
+
.sort((a, b) => b.paymentSignal - a.paymentSignal)
|
|
339
|
+
.map(({ paymentSignal, ...entry }) => entry))
|
|
301
340
|
}
|
|
302
341
|
|
|
303
342
|
for (const resource of document.resources ?? []) {
|