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 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 and JSON request-body examples for safer no-payment probes
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 exampleValue(schemaOrParameter) {
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') return ''
169
- if (schema.type === 'number' || schema.type === 'integer') return 0
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 openApiProbeUrl(path, operation, baseUrl) {
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
- entries.push({
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 ?? []) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {