x402-surface-check 0.2.7 → 0.2.9

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
@@ -16,15 +16,28 @@ npx --yes x402-surface-check --endpoint --method POST https://x402.rpc.ankr.com/
16
16
 
17
17
  - Manifest endpoint discovery from `items[]`, `endpoints[]`, `x402Endpoints`, category arrays, resource strings, and OpenAPI paths
18
18
  - No-payment HTTP 402 challenge shape
19
- - x402 v1 and v2 price fields
19
+ - x402 v1 and v2 price fields, including `accepts[]` and `schemes[]` challenge arrays
20
20
  - MPP `WWW-Authenticate: Payment` and x402 V2 `WWW-Authenticate: X402 requirements=...` challenges
21
- - `amount` / `maxAmountRequired`, `asset`, `network`, and `payTo`
21
+ - Atomic-unit `amount` / `maxAmountRequired` fields, plus legacy decimal `amount` + `token` x402 v1 challenges
22
+ - `asset` or token metadata, `network`, and `payTo`
22
23
  - Placeholder recipients such as zero addresses and Solana system-program values
23
24
  - Testnet or staging rails such as Base Sepolia and Solana devnet
24
25
  - HTTPS resource URLs and stable resource metadata
25
26
  - Browser CORS allowance for `X-PAYMENT`
26
27
  - Over-broad public method surfaces
27
28
  - Auth, validation, and free/trial responses that appear before a payment challenge, without piling on missing-field findings when no challenge was actually returned
29
+ - Object-valued document metadata such as facilitator objects, without `[object Object]` report artifacts
30
+
31
+ ## Public Proof
32
+
33
+ Recent public no-payment checks have found and verified real launch fixes:
34
+
35
+ - TensorFeed: parameter-required premium routes moved behind canonical x402 V2 challenges, then verified clean. https://github.com/solana-foundation/pay-skills/pull/68#issuecomment-4455360068
36
+ - x402jp: weather routes that returned 500 now return structured Base x402 challenges. https://github.com/solana-foundation/pay-skills/pull/58#issuecomment-4455401355
37
+ - Spraay: resource echo and browser payment-header behavior verified clean. https://github.com/solana-foundation/pay-skills/pull/60#issuecomment-4455519760
38
+ - 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
39
+
40
+ Field notes and browser version: https://tateprograms.com/x402-surface-check.html
28
41
 
29
42
  ## Options
30
43
 
@@ -88,6 +88,16 @@ function moneyFromAtomic(amount, decimals = 6) {
88
88
  })}`
89
89
  }
90
90
 
91
+ function moneyFromDecimal(amount) {
92
+ if (amount === '' || amount === null || amount === undefined) return ''
93
+ const numeric = Number(amount)
94
+ if (!Number.isFinite(numeric)) return String(amount ?? '')
95
+ return `$${numeric.toLocaleString(undefined, {
96
+ maximumFractionDigits: 6,
97
+ minimumFractionDigits: numeric < 0.01 ? 3 : 2,
98
+ })}`
99
+ }
100
+
91
101
  function uniqueEntries(entries, limit) {
92
102
  const seen = new Set()
93
103
  return entries
@@ -264,6 +274,7 @@ function parsePaymentAuthenticate(value) {
264
274
  maxTimeoutSeconds: '',
265
275
  extra: {
266
276
  description: request.description ?? '',
277
+ decimals: request.methodDetails?.decimals ?? '',
267
278
  expires: params.expires ?? '',
268
279
  id: params.id ?? '',
269
280
  intent: params.intent ?? '',
@@ -321,8 +332,9 @@ async function probeEndpoint(entry) {
321
332
  const authenticateChallenge = parsePaymentAuthenticate(response.headers.get('www-authenticate'))
322
333
  ?? parseX402Authenticate(response.headers.get('www-authenticate'))
323
334
 
324
- if (!body.json?.accepts?.length) {
325
- if (headerChallenge) {
335
+ const bodyHasChallenge = Array.isArray(body.json?.accepts) || Array.isArray(body.json?.schemes)
336
+ if (!bodyHasChallenge) {
337
+ if (headerChallenge && typeof headerChallenge === 'object') {
326
338
  body.json = headerChallenge
327
339
  }
328
340
  else if (authenticateChallenge) {
@@ -389,7 +401,40 @@ function capabilityList(value) {
389
401
  }
390
402
 
391
403
  function challengeAccepts(result) {
392
- return Array.isArray(result.body.json?.accepts) ? result.body.json.accepts : []
404
+ if (Array.isArray(result.body.json?.accepts)) return result.body.json.accepts
405
+ if (Array.isArray(result.body.json?.schemes)) return result.body.json.schemes
406
+ return []
407
+ }
408
+
409
+ function acceptAmountValue(accept) {
410
+ return accept.maxAmountRequired ?? accept.maxAmount ?? accept.amount ?? ''
411
+ }
412
+
413
+ function acceptAssetValue(accept) {
414
+ return accept.asset ?? accept.token ?? accept.currency ?? ''
415
+ }
416
+
417
+ function acceptDecimals(accept) {
418
+ const value = accept.decimals ?? accept.extra?.decimals ?? accept.methodDetails?.decimals
419
+ const numeric = Number(value)
420
+ return Number.isFinite(numeric) ? numeric : 6
421
+ }
422
+
423
+ function usesDecimalAmount(accept, result) {
424
+ const rawAmount = acceptAmountValue(accept)
425
+ if (rawAmount === undefined || rawAmount === null || rawAmount === '') return false
426
+ const amount = String(rawAmount)
427
+ if (amount.includes('.')) return true
428
+ if (accept.maxAmountRequired !== undefined || accept.maxAmount !== undefined) return false
429
+ if (!accept.asset && (accept.token || result.headers?.['x-payment-token'])) return true
430
+ return result.headers?.['x-payment-amount'] === amount
431
+ }
432
+
433
+ function challengePrice(accept, result) {
434
+ const amount = acceptAmountValue(accept)
435
+ return usesDecimalAmount(accept, result)
436
+ ? moneyFromDecimal(amount)
437
+ : moneyFromAtomic(amount, acceptDecimals(accept))
393
438
  }
394
439
 
395
440
  function hasPaymentChallenge(result) {
@@ -399,9 +444,9 @@ function hasPaymentChallenge(result) {
399
444
 
400
445
  function challengeSummary(result) {
401
446
  const challenge = result.body.json
402
- const firstAccept = challenge?.accepts?.[0] ?? {}
447
+ const firstAccept = challengeAccepts(result)[0] ?? {}
403
448
  const hasChallenge = hasPaymentChallenge(result)
404
- const amount = firstAccept.amount ?? firstAccept.maxAmountRequired ?? firstAccept.maxAmount ?? ''
449
+ const amount = acceptAmountValue(firstAccept)
405
450
  const resourceUrl = challenge?.resource?.url ?? firstAccept.resource ?? ''
406
451
  const extraResource = firstAccept.extra?.resource ?? firstAccept.resource ?? ''
407
452
 
@@ -411,9 +456,9 @@ function challengeSummary(result) {
411
456
  resourceUrl,
412
457
  network: firstAccept.network ?? '',
413
458
  amount,
414
- price: moneyFromAtomic(amount),
459
+ price: hasChallenge ? challengePrice(firstAccept, result) : '',
415
460
  payTo: firstAccept.payTo ?? '',
416
- asset: firstAccept.asset ?? '',
461
+ asset: acceptAssetValue(firstAccept),
417
462
  timeout: firstAccept.maxTimeoutSeconds ?? '',
418
463
  extraResource,
419
464
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {