x402-surface-check 0.2.6 → 0.2.8

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
@@ -18,13 +18,26 @@ npx --yes x402-surface-check --endpoint --method POST https://x402.rpc.ankr.com/
18
18
  - No-payment HTTP 402 challenge shape
19
19
  - x402 v1 and v2 price fields
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 ?? '',
@@ -365,6 +376,24 @@ function valueList(value) {
365
376
  return []
366
377
  }
367
378
 
379
+ function displayMetadataValue(value) {
380
+ if (value === null || value === undefined || value === '') return '-'
381
+ if (Array.isArray(value)) {
382
+ return value.map(displayMetadataValue).filter(item => item && item !== '-').join(', ') || '-'
383
+ }
384
+ if (typeof value === 'object') {
385
+ const parts = [
386
+ value.name,
387
+ value.operator,
388
+ value.url,
389
+ value.jurisdiction,
390
+ value.network,
391
+ ].filter(item => item !== null && item !== undefined && item !== '').map(String)
392
+ return parts.join(' / ') || Object.keys(value).join(', ') || '-'
393
+ }
394
+ return String(value)
395
+ }
396
+
368
397
  function capabilityList(value) {
369
398
  if (!Array.isArray(value)) return []
370
399
  return value.map(item => item?.id ?? item?.name ?? item).filter(Boolean).map(String)
@@ -374,6 +403,36 @@ function challengeAccepts(result) {
374
403
  return Array.isArray(result.body.json?.accepts) ? result.body.json.accepts : []
375
404
  }
376
405
 
406
+ function acceptAmountValue(accept) {
407
+ return accept.maxAmountRequired ?? accept.maxAmount ?? accept.amount ?? ''
408
+ }
409
+
410
+ function acceptAssetValue(accept) {
411
+ return accept.asset ?? accept.token ?? accept.currency ?? ''
412
+ }
413
+
414
+ function acceptDecimals(accept) {
415
+ const value = accept.decimals ?? accept.extra?.decimals ?? accept.methodDetails?.decimals
416
+ const numeric = Number(value)
417
+ return Number.isFinite(numeric) ? numeric : 6
418
+ }
419
+
420
+ function usesDecimalAmount(accept, result) {
421
+ if (accept.maxAmountRequired !== undefined || accept.maxAmount !== undefined) return false
422
+ if (accept.amount === undefined || accept.amount === null || accept.amount === '') return false
423
+ const amount = String(accept.amount)
424
+ if (amount.includes('.')) return true
425
+ if (!accept.asset && (accept.token || result.headers?.['x-payment-token'])) return true
426
+ return result.headers?.['x-payment-amount'] === amount
427
+ }
428
+
429
+ function challengePrice(accept, result) {
430
+ const amount = acceptAmountValue(accept)
431
+ return usesDecimalAmount(accept, result)
432
+ ? moneyFromDecimal(amount)
433
+ : moneyFromAtomic(amount, acceptDecimals(accept))
434
+ }
435
+
377
436
  function hasPaymentChallenge(result) {
378
437
  const challenge = result.body.json
379
438
  return challengeAccepts(result).length > 0 || Boolean(challenge?.resource || challenge?.payment || result.headers?.['www-authenticate'])
@@ -383,7 +442,7 @@ function challengeSummary(result) {
383
442
  const challenge = result.body.json
384
443
  const firstAccept = challenge?.accepts?.[0] ?? {}
385
444
  const hasChallenge = hasPaymentChallenge(result)
386
- const amount = firstAccept.amount ?? firstAccept.maxAmountRequired ?? firstAccept.maxAmount ?? ''
445
+ const amount = acceptAmountValue(firstAccept)
387
446
  const resourceUrl = challenge?.resource?.url ?? firstAccept.resource ?? ''
388
447
  const extraResource = firstAccept.extra?.resource ?? firstAccept.resource ?? ''
389
448
 
@@ -393,9 +452,9 @@ function challengeSummary(result) {
393
452
  resourceUrl,
394
453
  network: firstAccept.network ?? '',
395
454
  amount,
396
- price: moneyFromAtomic(amount),
455
+ price: hasChallenge ? challengePrice(firstAccept, result) : '',
397
456
  payTo: firstAccept.payTo ?? '',
398
- asset: firstAccept.asset ?? '',
457
+ asset: acceptAssetValue(firstAccept),
399
458
  timeout: firstAccept.maxTimeoutSeconds ?? '',
400
459
  extraResource,
401
460
  }
@@ -520,7 +579,7 @@ function formatMarkdown(report) {
520
579
  `- Type: ${report.directEndpoint ? 'direct endpoint' : (document.openapi ? 'OpenAPI' : 'x402 manifest or JSON document')}`,
521
580
  `- Agent: ${document.agent?.name ?? '-'}`,
522
581
  `- Wallet: ${document.agent?.wallet ?? '-'}`,
523
- `- Facilitator: ${document.facilitator ?? '-'}`,
582
+ `- Facilitator: ${displayMetadataValue(document.facilitator)}`,
524
583
  `- Networks: ${valueList(document.networks).join(', ') || '-'}`,
525
584
  `- Capabilities: ${capabilityList(document.capabilities).join(', ') || '-'}`,
526
585
  `- Probed endpoints: ${report.entries.length}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-surface-check",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "No-payment x402 public-surface checker for manifests, OpenAPI specs, and HTTP 402 challenges.",
5
5
  "type": "module",
6
6
  "bin": {