x402check 0.0.1 → 0.1.1

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 ADDED
@@ -0,0 +1,135 @@
1
+ # x402check
2
+
3
+ Validate [x402](https://www.x402.org/) payment configurations. Works in Node, browsers, and edge runtimes — zero dependencies.
4
+
5
+ **[x402check.com](https://www.x402check.com)** — try it in the browser
6
+
7
+ ## Install
8
+
9
+ ```
10
+ npm i x402check
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```js
16
+ import { validate } from 'x402check'
17
+
18
+ const result = validate({
19
+ x402Version: 2,
20
+ accepts: [{
21
+ scheme: 'exact',
22
+ network: 'base',
23
+ payTo: '0x1234567890abcdef1234567890abcdef12345678',
24
+ asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
25
+ amount: '10000',
26
+ maxTimeoutSeconds: 300
27
+ }]
28
+ })
29
+
30
+ result.valid // true | false
31
+ result.errors // ValidationIssue[]
32
+ result.warnings // ValidationIssue[]
33
+ result.normalized // canonical v2 config
34
+ ```
35
+
36
+ ## API
37
+
38
+ ### `validate(input, options?)`
39
+
40
+ Validates a config object or JSON string. Returns errors, warnings, and a normalized v2 config.
41
+
42
+ ```js
43
+ validate(configOrJson)
44
+ validate(configOrJson, { strict: true }) // promotes warnings to errors
45
+ ```
46
+
47
+ **Returns:** `ValidationResult`
48
+
49
+ ```ts
50
+ {
51
+ valid: boolean
52
+ version: 'v2' | 'v1' | 'unknown'
53
+ errors: ValidationIssue[]
54
+ warnings: ValidationIssue[]
55
+ normalized: NormalizedConfig | null
56
+ }
57
+ ```
58
+
59
+ Each issue includes a machine-readable `code`, a `field` path, a human-readable `message`, and an optional `fix` suggestion.
60
+
61
+ ### `extractConfig(response)`
62
+
63
+ Extracts an x402 config from an HTTP 402 response. Checks the JSON body first, then falls back to the `PAYMENT-REQUIRED` header (base64 or raw JSON).
64
+
65
+ ```js
66
+ const res = await fetch(url)
67
+ const { config, source, error } = extractConfig({
68
+ body: await res.json(),
69
+ headers: res.headers
70
+ })
71
+ // source: 'body' | 'header' | null
72
+ ```
73
+
74
+ ### `detect(input)`
75
+
76
+ Returns the config format: `'v2'`, `'v1'`, or `'unknown'`.
77
+
78
+ ```js
79
+ detect({ x402Version: 2, accepts: [...] }) // 'v2'
80
+ ```
81
+
82
+ ### `normalize(input)`
83
+
84
+ Converts any supported config to canonical v2 shape. Returns `null` if the format is unrecognized.
85
+
86
+ ```js
87
+ const v2Config = normalize(v1Config)
88
+ ```
89
+
90
+ ### Address validation
91
+
92
+ ```js
93
+ import { validateAddress, validateEvmAddress, validateSolanaAddress } from 'x402check'
94
+
95
+ validateAddress(addr, 'eip155:8453', 'payTo') // dispatches by network
96
+ validateEvmAddress(addr, 'payTo') // EIP-55 checksum verification
97
+ validateSolanaAddress(addr, 'payTo') // base58, 32-byte decode check
98
+ ```
99
+
100
+ ### Network & asset registry
101
+
102
+ ```js
103
+ import {
104
+ isKnownNetwork, getNetworkInfo, getCanonicalNetwork,
105
+ isKnownAsset, getAssetInfo, isValidCaip2
106
+ } from 'x402check'
107
+
108
+ isValidCaip2('eip155:8453') // true
109
+ getCanonicalNetwork('base') // 'eip155:8453'
110
+ getNetworkInfo('eip155:8453') // { name: 'Base', type: 'evm', testnet: false }
111
+ isKnownAsset('eip155:8453', '0x833…') // true
112
+ getAssetInfo('eip155:8453', '0x833…') // { symbol: 'USDC', name: 'USD Coin', decimals: 6 }
113
+ ```
114
+
115
+ ## Supported formats
116
+
117
+ | Format | `x402Version` | Status |
118
+ |--------|---------------|--------|
119
+ | v2 | `2` | Recommended |
120
+ | v1 | `1` | Supported, auto-normalized to v2 |
121
+
122
+ ## Validation checks
123
+
124
+ - Required fields (`scheme`, `network`, `amount`, `asset`, `payTo`)
125
+ - Amount is a numeric string > 0
126
+ - Network is valid [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md)
127
+ - EVM addresses: `0x`-prefixed, 40 hex chars, EIP-55 checksum
128
+ - Solana addresses: base58, decodes to 32 bytes
129
+ - Known network and asset registry warnings
130
+ - `maxTimeoutSeconds` is a positive integer (if present)
131
+ - Resource URL format (if present)
132
+
133
+ ## License
134
+
135
+ MIT
package/dist/index.cjs CHANGED
@@ -1824,9 +1824,101 @@ function runPipeline(input, options) {
1824
1824
  };
1825
1825
  }
1826
1826
 
1827
+ //#endregion
1828
+ //#region src/extraction/extract.ts
1829
+ /**
1830
+ * Get a header value, case-insensitive.
1831
+ * Supports both Headers objects and plain Record<string, string>.
1832
+ */
1833
+ function getHeader(headers, name) {
1834
+ if (!headers) return null;
1835
+ if (typeof headers.get === "function") return headers.get(name);
1836
+ const lower = name.toLowerCase();
1837
+ for (const key of Object.keys(headers)) if (key.toLowerCase() === lower) return headers[key];
1838
+ return null;
1839
+ }
1840
+ /**
1841
+ * Decode base64 string to UTF-8 text.
1842
+ * Works in both browser (atob) and Node (Buffer).
1843
+ */
1844
+ function decodeBase64(encoded) {
1845
+ if (typeof atob === "function") return atob(encoded);
1846
+ return Buffer.from(encoded, "base64").toString("utf-8");
1847
+ }
1848
+ /**
1849
+ * Check if a parsed object looks like it contains x402 config fields.
1850
+ */
1851
+ function hasX402Fields(obj) {
1852
+ if (!obj || typeof obj !== "object") return false;
1853
+ const rec = obj;
1854
+ return !!(rec.accepts || rec.payTo || rec.x402Version);
1855
+ }
1856
+ /**
1857
+ * Try to parse the PAYMENT-REQUIRED header value as a base64-encoded JSON config.
1858
+ */
1859
+ function tryHeaderExtraction(headers) {
1860
+ const headerValue = getHeader(headers, "payment-required");
1861
+ if (!headerValue) return null;
1862
+ try {
1863
+ const decoded = decodeBase64(headerValue);
1864
+ const parsed = JSON.parse(decoded);
1865
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return {
1866
+ config: parsed,
1867
+ source: "header"
1868
+ };
1869
+ } catch {}
1870
+ try {
1871
+ const parsed = JSON.parse(headerValue);
1872
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return {
1873
+ config: parsed,
1874
+ source: "header"
1875
+ };
1876
+ } catch {}
1877
+ return null;
1878
+ }
1879
+ /**
1880
+ * Extract an x402 config from an HTTP 402 response.
1881
+ *
1882
+ * Extraction priority:
1883
+ * 1. JSON body — if it parses and has x402 fields (accepts, payTo, x402Version)
1884
+ * 2. PAYMENT-REQUIRED header — base64-decoded JSON fallback
1885
+ *
1886
+ * Never throws. Returns structured result with error message on failure.
1887
+ *
1888
+ * @param response - Response-like object with body and/or headers
1889
+ * @returns Extraction result with config, source, and error
1890
+ */
1891
+ function extractConfig(response) {
1892
+ const body = response.body;
1893
+ if (body && typeof body === "object" && !Array.isArray(body) && hasX402Fields(body)) return {
1894
+ config: body,
1895
+ source: "body",
1896
+ error: null
1897
+ };
1898
+ if (typeof body === "string" && body.trim()) try {
1899
+ const parsed = JSON.parse(body);
1900
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && hasX402Fields(parsed)) return {
1901
+ config: parsed,
1902
+ source: "body",
1903
+ error: null
1904
+ };
1905
+ } catch {}
1906
+ const headerResult = tryHeaderExtraction(response.headers);
1907
+ if (headerResult) return {
1908
+ config: headerResult.config,
1909
+ source: headerResult.source,
1910
+ error: null
1911
+ };
1912
+ return {
1913
+ config: null,
1914
+ source: null,
1915
+ error: "No x402 config found in response body or PAYMENT-REQUIRED header"
1916
+ };
1917
+ }
1918
+
1827
1919
  //#endregion
1828
1920
  //#region src/index.ts
1829
- const VERSION = "0.0.1";
1921
+ const VERSION = "0.1.0";
1830
1922
 
1831
1923
  //#endregion
1832
1924
  exports.CAIP2_REGEX = CAIP2_REGEX;
@@ -1838,6 +1930,7 @@ exports.SIMPLE_NAME_TO_CAIP2 = SIMPLE_NAME_TO_CAIP2;
1838
1930
  exports.VERSION = VERSION;
1839
1931
  exports.decodeBase58 = decodeBase58;
1840
1932
  exports.detect = detect;
1933
+ exports.extractConfig = extractConfig;
1841
1934
  exports.getAssetInfo = getAssetInfo;
1842
1935
  exports.getCanonicalNetwork = getCanonicalNetwork;
1843
1936
  exports.getNetworkInfo = getNetworkInfo;