x402-surface-check 0.2.12 → 0.2.13

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
@@ -15,7 +15,8 @@ npx --yes x402-surface-check --endpoint --method POST https://x402.rpc.ankr.com/
15
15
  ## What It Checks
16
16
 
17
17
  - Manifest endpoint discovery from `items[]`, `endpoints[]`, `x402Endpoints`, category arrays, resource strings, and OpenAPI paths
18
- - Linked discovery documents via `discovery_url`, `discoveryUrl`, `resources_url`, or `resourcesUrl`
18
+ - Linked discovery documents via `discovery_url`, `discoveryUrl`, `resources_url`, `resourcesUrl`, or manifest-level OpenAPI links
19
+ - OpenAPI query/path examples and JSON request-body examples for safer no-payment probes
19
20
  - No-payment HTTP 402 challenge shape
20
21
  - x402 v1 and v2 price fields, including `accepts[]` and `schemes[]` challenge arrays
21
22
  - MPP `WWW-Authenticate: Payment` and x402 V2 `WWW-Authenticate: X402 requirements=...` challenges
@@ -29,7 +30,7 @@ npx --yes x402-surface-check --endpoint --method POST https://x402.rpc.ankr.com/
29
30
  - Over-broad public method surfaces
30
31
  - Auth, validation, and free/trial responses that appear before a payment challenge, without piling on missing-field findings when no challenge was actually returned
31
32
  - Operational health/status endpoints, without treating expected free health checks as paid-route failures
32
- - Object-valued document metadata such as facilitator objects, without `[object Object]` report artifacts
33
+ - Object-valued document metadata such as facilitator or network objects, without `[object Object]` report artifacts
33
34
 
34
35
  ## Public Proof
35
36
 
@@ -41,7 +42,8 @@ Recent public no-payment checks have found and verified real launch fixes:
41
42
  - UZPROOF: schemes-style Solana x402 challenge and browser payment-header behavior verified clean. https://github.com/solana-foundation/pay-skills/pull/28#issuecomment-4455613892
42
43
  - HYRE Agent: OpenAPI-declared prices found 10x below live 402 challenge prices. https://github.com/solana-foundation/pay-skills/pull/19#issuecomment-4455641258
43
44
  - anchor-x402: multi-rail x402 challenges verified, with browser preflight blockers isolated before merge. https://github.com/solana-foundation/pay-skills/pull/47#issuecomment-4455678163
44
- - Agent Trust Bench: live discovery URL and browser-compatibility notes for adversarial agent-payment resources. https://github.com/solana-foundation/pay-skills/pull/23#issuecomment-4455484414
45
+ - Agent Trust Bench: linked discovery URL and browser-compatibility notes verified clean for adversarial agent-payment resources. https://github.com/solana-foundation/pay-skills/pull/23#issuecomment-4455722170
46
+ - Solrouter: private LLM inference route verified with HTTPS resource-binding and price-alignment notes. https://github.com/solana-foundation/pay-skills/pull/39#issuecomment-4455800060
45
47
 
46
48
  Field notes and browser version: https://tateprograms.com/x402-surface-check.html
47
49
 
@@ -137,6 +137,7 @@ function linkedDiscoveryUrl(document, sourceUrl) {
137
137
  ?? document?.discoveryUrl
138
138
  ?? document?.resources_url
139
139
  ?? document?.resourcesUrl
140
+ ?? (/^(https?:\/\/|\/)/i.test(String(document?.openapi ?? '')) ? document.openapi : '')
140
141
  if (typeof rawUrl !== 'string' || !rawUrl.trim()) return ''
141
142
  return endpointUrl(rawUrl, documentBaseUrl(document, sourceUrl), sourceUrl)
142
143
  }
@@ -150,6 +151,79 @@ function operationExpectedPrice(operation) {
150
151
  return numeric === null ? null : numeric
151
152
  }
152
153
 
154
+ function exampleValue(schemaOrParameter) {
155
+ if (!schemaOrParameter || typeof schemaOrParameter !== 'object') return undefined
156
+ const schema = schemaOrParameter.schema ?? schemaOrParameter
157
+ const value = schemaOrParameter.example
158
+ ?? schema.example
159
+ ?? schema.default
160
+ ?? (Array.isArray(schema.enum) ? schema.enum[0] : undefined)
161
+ if (value !== undefined) return value
162
+ if (schema.type === 'string') return ''
163
+ if (schema.type === 'number' || schema.type === 'integer') return 0
164
+ if (schema.type === 'boolean') return false
165
+ return undefined
166
+ }
167
+
168
+ function mediaExample(media) {
169
+ if (!media || typeof media !== 'object') return undefined
170
+ if (media.example !== undefined) return media.example
171
+ const examples = media.examples && typeof media.examples === 'object'
172
+ ? Object.values(media.examples)
173
+ : []
174
+ const firstExample = examples.find(Boolean)
175
+ if (firstExample?.value !== undefined) return firstExample.value
176
+ if (firstExample?.externalValue) return undefined
177
+
178
+ const schema = media.schema
179
+ if (!schema || typeof schema !== 'object' || schema.type !== 'object') return undefined
180
+ const body = {}
181
+ const properties = schema.properties && typeof schema.properties === 'object'
182
+ ? schema.properties
183
+ : {}
184
+ const required = new Set(Array.isArray(schema.required) ? schema.required : Object.keys(properties))
185
+
186
+ for (const [name, property] of Object.entries(properties)) {
187
+ if (!required.has(name)) continue
188
+ const value = exampleValue(property)
189
+ if (value !== undefined) body[name] = value
190
+ }
191
+
192
+ return Object.keys(body).length ? body : undefined
193
+ }
194
+
195
+ function operationRequestBody(operation) {
196
+ const content = operation?.requestBody?.content
197
+ if (!content || typeof content !== 'object') return undefined
198
+ const media = content['application/json']
199
+ ?? content['application/*+json']
200
+ ?? Object.entries(content).find(([type]) => /json/i.test(type))?.[1]
201
+ return mediaExample(media)
202
+ }
203
+
204
+ function openApiProbeUrl(path, operation, baseUrl) {
205
+ const parameters = Array.isArray(operation?.parameters) ? operation.parameters : []
206
+ let resolvedPath = path
207
+ const searchParams = new URLSearchParams()
208
+
209
+ for (const parameter of parameters) {
210
+ const value = exampleValue(parameter)
211
+ if (value === undefined || value === '') continue
212
+ if (parameter.in === 'path') {
213
+ resolvedPath = resolvedPath.replaceAll(`{${parameter.name}}`, encodeURIComponent(String(value)))
214
+ }
215
+ else if (parameter.in === 'query') {
216
+ searchParams.set(parameter.name, String(value))
217
+ }
218
+ }
219
+
220
+ const url = path.startsWith('http') ? new URL(resolvedPath) : new URL(resolvedPath, baseUrl)
221
+ for (const [name, value] of searchParams.entries()) {
222
+ url.searchParams.set(name, value)
223
+ }
224
+ return url.toString()
225
+ }
226
+
153
227
  function endpointEntries(document, sourceUrl, limit) {
154
228
  const entries = []
155
229
  const baseUrl = documentBaseUrl(document, sourceUrl)
@@ -207,12 +281,13 @@ function endpointEntries(document, sourceUrl, limit) {
207
281
  for (const method of methods) {
208
282
  const operation = operations[method]
209
283
  if (!operation || typeof operation !== 'object') continue
210
- const url = path.startsWith('http') ? path : new URL(path, baseUrl).toString()
284
+ const url = openApiProbeUrl(path, operation, baseUrl)
211
285
  entries.push({
212
286
  name: operation.operationId ?? `${method.toUpperCase()} ${path}`,
213
287
  url,
214
288
  method: method.toUpperCase(),
215
289
  expectedPriceUsd: operationExpectedPrice(operation),
290
+ requestBody: operationRequestBody(operation),
216
291
  })
217
292
  }
218
293
  }
@@ -347,7 +422,9 @@ async function probeEndpoint(entry) {
347
422
  accept: 'application/json',
348
423
  'content-type': 'application/json',
349
424
  },
350
- body: method === 'GET' || method === 'HEAD' ? undefined : '{}',
425
+ body: method === 'GET' || method === 'HEAD'
426
+ ? undefined
427
+ : JSON.stringify(entry.requestBody ?? {}),
351
428
  })
352
429
  const body = await readText(response)
353
430
  const headerChallenge = parseEncodedChallenge(
@@ -395,7 +472,7 @@ async function probePreflight(entry, origin) {
395
472
  }
396
473
 
397
474
  function valueList(value) {
398
- if (Array.isArray(value)) return value.map(String)
475
+ if (Array.isArray(value)) return value.map(displayMetadataValue)
399
476
  if (value && typeof value === 'object') return Object.keys(value)
400
477
  if (typeof value === 'string') return [value]
401
478
  return []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {