x402check 0.2.0 → 0.3.0

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/dist/cli.mjs ADDED
@@ -0,0 +1,2353 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { resolve as resolve$1 } from "node:path";
4
+
5
+ //#region src/types/errors.ts
6
+ /**
7
+ * Error and warning code vocabulary for x402check
8
+ */
9
+ const ErrorCode = {
10
+ INVALID_JSON: "INVALID_JSON",
11
+ NOT_OBJECT: "NOT_OBJECT",
12
+ UNKNOWN_FORMAT: "UNKNOWN_FORMAT",
13
+ MISSING_VERSION: "MISSING_VERSION",
14
+ INVALID_VERSION: "INVALID_VERSION",
15
+ MISSING_ACCEPTS: "MISSING_ACCEPTS",
16
+ EMPTY_ACCEPTS: "EMPTY_ACCEPTS",
17
+ INVALID_ACCEPTS: "INVALID_ACCEPTS",
18
+ MISSING_SCHEME: "MISSING_SCHEME",
19
+ MISSING_NETWORK: "MISSING_NETWORK",
20
+ INVALID_NETWORK_FORMAT: "INVALID_NETWORK_FORMAT",
21
+ MISSING_AMOUNT: "MISSING_AMOUNT",
22
+ INVALID_AMOUNT: "INVALID_AMOUNT",
23
+ ZERO_AMOUNT: "ZERO_AMOUNT",
24
+ MISSING_ASSET: "MISSING_ASSET",
25
+ MISSING_PAY_TO: "MISSING_PAY_TO",
26
+ MISSING_RESOURCE: "MISSING_RESOURCE",
27
+ INVALID_URL: "INVALID_URL",
28
+ INVALID_TIMEOUT: "INVALID_TIMEOUT",
29
+ INVALID_EVM_ADDRESS: "INVALID_EVM_ADDRESS",
30
+ BAD_EVM_CHECKSUM: "BAD_EVM_CHECKSUM",
31
+ NO_EVM_CHECKSUM: "NO_EVM_CHECKSUM",
32
+ INVALID_SOLANA_ADDRESS: "INVALID_SOLANA_ADDRESS",
33
+ ADDRESS_NETWORK_MISMATCH: "ADDRESS_NETWORK_MISMATCH",
34
+ INVALID_BAZAAR_INFO: "INVALID_BAZAAR_INFO",
35
+ INVALID_BAZAAR_SCHEMA: "INVALID_BAZAAR_SCHEMA",
36
+ INVALID_BAZAAR_INFO_INPUT: "INVALID_BAZAAR_INFO_INPUT",
37
+ INVALID_OUTPUT_SCHEMA: "INVALID_OUTPUT_SCHEMA",
38
+ INVALID_OUTPUT_SCHEMA_INPUT: "INVALID_OUTPUT_SCHEMA_INPUT",
39
+ MISSING_INPUT_SCHEMA: "MISSING_INPUT_SCHEMA",
40
+ UNKNOWN_NETWORK: "UNKNOWN_NETWORK",
41
+ UNKNOWN_ASSET: "UNKNOWN_ASSET",
42
+ LEGACY_FORMAT: "LEGACY_FORMAT",
43
+ MISSING_MAX_TIMEOUT: "MISSING_MAX_TIMEOUT"
44
+ };
45
+ /**
46
+ * Human-readable error messages for all error codes
47
+ */
48
+ const ErrorMessages = {
49
+ INVALID_JSON: "Input is not valid JSON",
50
+ NOT_OBJECT: "Input must be an object",
51
+ UNKNOWN_FORMAT: "Missing required x402Version field (must be 1 or 2)",
52
+ MISSING_VERSION: "Missing required field: x402Version",
53
+ INVALID_VERSION: "Invalid x402Version value (must be 1 or 2)",
54
+ MISSING_ACCEPTS: "Missing required field: accepts",
55
+ EMPTY_ACCEPTS: "accepts array cannot be empty",
56
+ INVALID_ACCEPTS: "accepts must be an array",
57
+ MISSING_SCHEME: "Missing required field: scheme",
58
+ MISSING_NETWORK: "Missing required field: network",
59
+ INVALID_NETWORK_FORMAT: "Network must use CAIP-2 format (namespace:reference), e.g. eip155:8453",
60
+ MISSING_AMOUNT: "Missing required field: amount",
61
+ INVALID_AMOUNT: "Amount must be a numeric string in atomic units",
62
+ ZERO_AMOUNT: "Amount must be greater than zero",
63
+ MISSING_ASSET: "Missing required field: asset",
64
+ MISSING_PAY_TO: "Missing required field: payTo",
65
+ MISSING_RESOURCE: "Missing required field: resource",
66
+ INVALID_URL: "resource.url is not a valid URL format",
67
+ INVALID_TIMEOUT: "maxTimeoutSeconds must be a positive integer",
68
+ INVALID_EVM_ADDRESS: "Invalid EVM address format",
69
+ BAD_EVM_CHECKSUM: "EVM address has invalid checksum",
70
+ NO_EVM_CHECKSUM: "EVM address is all-lowercase with no checksum protection",
71
+ INVALID_SOLANA_ADDRESS: "Invalid Solana address format",
72
+ ADDRESS_NETWORK_MISMATCH: "Address format does not match network type",
73
+ INVALID_BAZAAR_INFO: "extensions.bazaar.info must be an object with input and output",
74
+ INVALID_BAZAAR_SCHEMA: "extensions.bazaar.schema must be a valid JSON Schema object",
75
+ INVALID_BAZAAR_INFO_INPUT: "extensions.bazaar.info.input must include type and method",
76
+ INVALID_OUTPUT_SCHEMA: "accepts[i].outputSchema must be an object with input and output",
77
+ INVALID_OUTPUT_SCHEMA_INPUT: "accepts[i].outputSchema.input must include type and method",
78
+ MISSING_INPUT_SCHEMA: "No input schema found (no bazaar extension or outputSchema) -- consider adding one so agents know how to call your API",
79
+ UNKNOWN_NETWORK: "Network is not in the known registry -- config may still work but cannot be fully validated",
80
+ UNKNOWN_ASSET: "Asset is not in the known registry -- config may still work but cannot be fully validated",
81
+ LEGACY_FORMAT: "Config uses legacy flat format -- consider upgrading to x402 v2",
82
+ MISSING_MAX_TIMEOUT: "Consider adding maxTimeoutSeconds for better security"
83
+ };
84
+
85
+ //#endregion
86
+ //#region src/types/parse-input.ts
87
+ /**
88
+ * Parse input that may be either a JSON string or an object
89
+ * API-04: Accept string | object
90
+ */
91
+ function parseInput(input) {
92
+ if (typeof input === "string") try {
93
+ return { parsed: JSON.parse(input) };
94
+ } catch {
95
+ return {
96
+ parsed: null,
97
+ error: {
98
+ code: ErrorCode.INVALID_JSON,
99
+ field: "$",
100
+ message: ErrorMessages.INVALID_JSON,
101
+ severity: "error"
102
+ }
103
+ };
104
+ }
105
+ return { parsed: input };
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/detection/guards.ts
110
+ /**
111
+ * Check if value is a non-null, non-array object
112
+ */
113
+ function isRecord(value) {
114
+ return typeof value === "object" && value !== null && !Array.isArray(value);
115
+ }
116
+ /**
117
+ * Check if config has an accepts array
118
+ */
119
+ function hasAcceptsArray(config) {
120
+ return "accepts" in config && Array.isArray(config.accepts);
121
+ }
122
+ /**
123
+ * Type guard for v2 config
124
+ * Checks for accepts array + x402Version: 2
125
+ * Note: resource is required by spec but its absence is a validation error, not a detection failure
126
+ */
127
+ function isV2Config(value) {
128
+ if (!isRecord(value)) return false;
129
+ if (!hasAcceptsArray(value)) return false;
130
+ return "x402Version" in value && value.x402Version === 2;
131
+ }
132
+ /**
133
+ * Type guard for v1 config
134
+ * Checks for accepts array + x402Version: 1
135
+ */
136
+ function isV1Config(value) {
137
+ if (!isRecord(value)) return false;
138
+ if (!hasAcceptsArray(value)) return false;
139
+ return "x402Version" in value && value.x402Version === 1;
140
+ }
141
+
142
+ //#endregion
143
+ //#region src/detection/detect.ts
144
+ /**
145
+ * Detect the format of an x402 config
146
+ *
147
+ * @param input - JSON string or parsed object
148
+ * @returns ConfigFormat literal: 'v2' | 'v1' | 'unknown'
149
+ *
150
+ * Detection requires x402Version field:
151
+ * 1. v2: accepts array + x402Version: 2
152
+ * 2. v1: accepts array + x402Version: 1
153
+ * 3. unknown: anything else (including versionless configs)
154
+ */
155
+ function detect(input) {
156
+ const { parsed, error } = parseInput(input);
157
+ if (error) return "unknown";
158
+ if (isV2Config(parsed)) return "v2";
159
+ if (isV1Config(parsed)) return "v1";
160
+ return "unknown";
161
+ }
162
+
163
+ //#endregion
164
+ //#region src/detection/normalize.ts
165
+ /**
166
+ * Normalize any x402 config format to canonical v2 shape
167
+ *
168
+ * @param input - JSON string or parsed object
169
+ * @returns NormalizedConfig or null if format is unknown/invalid
170
+ *
171
+ * Normalization rules:
172
+ * - v2: Pass through with new object (FMT-07)
173
+ * - v1: Map maxAmountRequired → amount, lift per-entry resource (FMT-06)
174
+ * - unknown: Return null
175
+ *
176
+ * All transformations preserve extensions and extra fields (FMT-08)
177
+ */
178
+ function normalize$1(input) {
179
+ const { parsed, error } = parseInput(input);
180
+ if (error) return null;
181
+ const format = detect(parsed);
182
+ switch (format) {
183
+ case "v2": return normalizeV2(parsed);
184
+ case "v1": return normalizeV1ToV2(parsed);
185
+ case "unknown": return null;
186
+ default: return format;
187
+ }
188
+ }
189
+ /**
190
+ * Normalize v2 config (pass-through with new object)
191
+ * FMT-07: v2 configs are already canonical, just create new object
192
+ */
193
+ function normalizeV2(config) {
194
+ const result = {
195
+ x402Version: 2,
196
+ accepts: [...config.accepts],
197
+ resource: config.resource
198
+ };
199
+ if (config.error !== void 0) result.error = config.error;
200
+ if (config.extensions !== void 0) result.extensions = config.extensions;
201
+ return result;
202
+ }
203
+ /**
204
+ * Normalize v1 config to v2
205
+ * FMT-06: Map maxAmountRequired → amount, lift per-entry resource to top level
206
+ */
207
+ function normalizeV1ToV2(config) {
208
+ let topLevelResource = void 0;
209
+ const result = {
210
+ x402Version: 2,
211
+ accepts: config.accepts.map((entry) => {
212
+ if (entry.resource && !topLevelResource) topLevelResource = entry.resource;
213
+ const mapped = {
214
+ scheme: entry.scheme,
215
+ network: entry.network,
216
+ amount: entry.maxAmountRequired,
217
+ asset: entry.asset,
218
+ payTo: entry.payTo
219
+ };
220
+ if (entry.maxTimeoutSeconds !== void 0) mapped.maxTimeoutSeconds = entry.maxTimeoutSeconds;
221
+ if (entry.extra !== void 0) mapped.extra = entry.extra;
222
+ return mapped;
223
+ })
224
+ };
225
+ if (topLevelResource !== void 0) result.resource = topLevelResource;
226
+ if (config.error !== void 0) result.error = config.error;
227
+ if (config.extensions !== void 0) result.extensions = config.extensions;
228
+ return result;
229
+ }
230
+
231
+ //#endregion
232
+ //#region src/registries/networks.ts
233
+ const CAIP2_REGEX = /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/;
234
+ const KNOWN_NETWORKS = {
235
+ "eip155:8453": {
236
+ name: "Base",
237
+ type: "evm",
238
+ testnet: false
239
+ },
240
+ "eip155:84532": {
241
+ name: "Base Sepolia",
242
+ type: "evm",
243
+ testnet: true
244
+ },
245
+ "eip155:43114": {
246
+ name: "Avalanche C-Chain",
247
+ type: "evm",
248
+ testnet: false
249
+ },
250
+ "eip155:43113": {
251
+ name: "Avalanche Fuji",
252
+ type: "evm",
253
+ testnet: true
254
+ },
255
+ "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": {
256
+ name: "Solana Mainnet",
257
+ type: "solana",
258
+ testnet: false
259
+ },
260
+ "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": {
261
+ name: "Solana Devnet",
262
+ type: "solana",
263
+ testnet: true
264
+ },
265
+ "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": {
266
+ name: "Solana Testnet",
267
+ type: "solana",
268
+ testnet: true
269
+ },
270
+ "stellar:pubnet": {
271
+ name: "Stellar Mainnet",
272
+ type: "stellar",
273
+ testnet: false
274
+ },
275
+ "stellar:testnet": {
276
+ name: "Stellar Testnet",
277
+ type: "stellar",
278
+ testnet: true
279
+ },
280
+ "aptos:1": {
281
+ name: "Aptos Mainnet",
282
+ type: "aptos",
283
+ testnet: false
284
+ },
285
+ "aptos:2": {
286
+ name: "Aptos Testnet",
287
+ type: "aptos",
288
+ testnet: true
289
+ }
290
+ };
291
+ function isValidCaip2(value) {
292
+ return CAIP2_REGEX.test(value);
293
+ }
294
+ function isKnownNetwork(caip2) {
295
+ return caip2 in KNOWN_NETWORKS;
296
+ }
297
+ function getNetworkInfo(caip2) {
298
+ return KNOWN_NETWORKS[caip2];
299
+ }
300
+ function getNetworkNamespace(caip2) {
301
+ if (!isValidCaip2(caip2)) return;
302
+ const colonIndex = caip2.indexOf(":");
303
+ return colonIndex > 0 ? caip2.substring(0, colonIndex) : void 0;
304
+ }
305
+
306
+ //#endregion
307
+ //#region ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/_u64.js
308
+ /**
309
+ * Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.
310
+ * @todo re-check https://issues.chromium.org/issues/42212588
311
+ * @module
312
+ */
313
+ const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
314
+ const _32n = /* @__PURE__ */ BigInt(32);
315
+ function fromBig(n, le = false) {
316
+ if (le) return {
317
+ h: Number(n & U32_MASK64),
318
+ l: Number(n >> _32n & U32_MASK64)
319
+ };
320
+ return {
321
+ h: Number(n >> _32n & U32_MASK64) | 0,
322
+ l: Number(n & U32_MASK64) | 0
323
+ };
324
+ }
325
+ function split(lst, le = false) {
326
+ const len = lst.length;
327
+ let Ah = new Uint32Array(len);
328
+ let Al = new Uint32Array(len);
329
+ for (let i = 0; i < len; i++) {
330
+ const { h, l } = fromBig(lst[i], le);
331
+ [Ah[i], Al[i]] = [h, l];
332
+ }
333
+ return [Ah, Al];
334
+ }
335
+ const rotlSH = (h, l, s) => h << s | l >>> 32 - s;
336
+ const rotlSL = (h, l, s) => l << s | h >>> 32 - s;
337
+ const rotlBH = (h, l, s) => l << s - 32 | h >>> 64 - s;
338
+ const rotlBL = (h, l, s) => h << s - 32 | l >>> 64 - s;
339
+
340
+ //#endregion
341
+ //#region ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/utils.js
342
+ /**
343
+ * Utilities for hex, bytes, CSPRNG.
344
+ * @module
345
+ */
346
+ /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
347
+ /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
348
+ function isBytes$1(a) {
349
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
350
+ }
351
+ /** Asserts something is positive integer. */
352
+ function anumber$1(n, title = "") {
353
+ if (!Number.isSafeInteger(n) || n < 0) {
354
+ const prefix = title && `"${title}" `;
355
+ throw new Error(`${prefix}expected integer >= 0, got ${n}`);
356
+ }
357
+ }
358
+ /** Asserts something is Uint8Array. */
359
+ function abytes$1(value, length, title = "") {
360
+ const bytes = isBytes$1(value);
361
+ const len = value?.length;
362
+ const needsLen = length !== void 0;
363
+ if (!bytes || needsLen && len !== length) {
364
+ const prefix = title && `"${title}" `;
365
+ const ofLen = needsLen ? ` of length ${length}` : "";
366
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
367
+ throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
368
+ }
369
+ return value;
370
+ }
371
+ /** Asserts a hash instance has not been destroyed / finished */
372
+ function aexists(instance, checkFinished = true) {
373
+ if (instance.destroyed) throw new Error("Hash instance has been destroyed");
374
+ if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called");
375
+ }
376
+ /** Asserts output is properly-sized byte array */
377
+ function aoutput(out, instance) {
378
+ abytes$1(out, void 0, "digestInto() output");
379
+ const min = instance.outputLen;
380
+ if (out.length < min) throw new Error("\"digestInto() output\" expected to be of length >=" + min);
381
+ }
382
+ /** Cast u8 / u16 / u32 to u32. */
383
+ function u32(arr) {
384
+ return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
385
+ }
386
+ /** Zeroize a byte array. Warning: JS provides no guarantees. */
387
+ function clean(...arrays) {
388
+ for (let i = 0; i < arrays.length; i++) arrays[i].fill(0);
389
+ }
390
+ /** Is current platform little-endian? Most are. Big-Endian platform: IBM */
391
+ const isLE = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
392
+ /** The byte swap operation for uint32 */
393
+ function byteSwap(word) {
394
+ return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255;
395
+ }
396
+ /** In place byte swap for Uint32Array */
397
+ function byteSwap32(arr) {
398
+ for (let i = 0; i < arr.length; i++) arr[i] = byteSwap(arr[i]);
399
+ return arr;
400
+ }
401
+ const swap32IfBE = isLE ? (u) => u : byteSwap32;
402
+ const hasHexBuiltin$1 = typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function";
403
+ /** Creates function with outputLen, blockLen, create properties from a class constructor. */
404
+ function createHasher(hashCons, info = {}) {
405
+ const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
406
+ const tmp = hashCons(void 0);
407
+ hashC.outputLen = tmp.outputLen;
408
+ hashC.blockLen = tmp.blockLen;
409
+ hashC.create = (opts) => hashCons(opts);
410
+ Object.assign(hashC, info);
411
+ return Object.freeze(hashC);
412
+ }
413
+
414
+ //#endregion
415
+ //#region ../../node_modules/.pnpm/@noble+hashes@2.0.1/node_modules/@noble/hashes/sha3.js
416
+ /**
417
+ * SHA3 (keccak) hash function, based on a new "Sponge function" design.
418
+ * Different from older hashes, the internal state is bigger than output size.
419
+ *
420
+ * Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
421
+ * [Website](https://keccak.team/keccak.html),
422
+ * [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub).
423
+ *
424
+ * Check out `sha3-addons` module for cSHAKE, k12, and others.
425
+ * @module
426
+ */
427
+ const _0n = BigInt(0);
428
+ const _1n = BigInt(1);
429
+ const _2n = BigInt(2);
430
+ const _7n = BigInt(7);
431
+ const _256n = BigInt(256);
432
+ const _0x71n = BigInt(113);
433
+ const SHA3_PI = [];
434
+ const SHA3_ROTL = [];
435
+ const _SHA3_IOTA = [];
436
+ for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
437
+ [x, y] = [y, (2 * x + 3 * y) % 5];
438
+ SHA3_PI.push(2 * (5 * y + x));
439
+ SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
440
+ let t = _0n;
441
+ for (let j = 0; j < 7; j++) {
442
+ R = (R << _1n ^ (R >> _7n) * _0x71n) % _256n;
443
+ if (R & _2n) t ^= _1n << (_1n << BigInt(j)) - _1n;
444
+ }
445
+ _SHA3_IOTA.push(t);
446
+ }
447
+ const IOTAS = split(_SHA3_IOTA, true);
448
+ const SHA3_IOTA_H = IOTAS[0];
449
+ const SHA3_IOTA_L = IOTAS[1];
450
+ const rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s);
451
+ const rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s);
452
+ /** `keccakf1600` internal function, additionally allows to adjust round count. */
453
+ function keccakP(s, rounds = 24) {
454
+ const B = new Uint32Array(10);
455
+ for (let round = 24 - rounds; round < 24; round++) {
456
+ for (let x = 0; x < 10; x++) B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
457
+ for (let x = 0; x < 10; x += 2) {
458
+ const idx1 = (x + 8) % 10;
459
+ const idx0 = (x + 2) % 10;
460
+ const B0 = B[idx0];
461
+ const B1 = B[idx0 + 1];
462
+ const Th = rotlH(B0, B1, 1) ^ B[idx1];
463
+ const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
464
+ for (let y = 0; y < 50; y += 10) {
465
+ s[x + y] ^= Th;
466
+ s[x + y + 1] ^= Tl;
467
+ }
468
+ }
469
+ let curH = s[2];
470
+ let curL = s[3];
471
+ for (let t = 0; t < 24; t++) {
472
+ const shift = SHA3_ROTL[t];
473
+ const Th = rotlH(curH, curL, shift);
474
+ const Tl = rotlL(curH, curL, shift);
475
+ const PI = SHA3_PI[t];
476
+ curH = s[PI];
477
+ curL = s[PI + 1];
478
+ s[PI] = Th;
479
+ s[PI + 1] = Tl;
480
+ }
481
+ for (let y = 0; y < 50; y += 10) {
482
+ for (let x = 0; x < 10; x++) B[x] = s[y + x];
483
+ for (let x = 0; x < 10; x++) s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
484
+ }
485
+ s[0] ^= SHA3_IOTA_H[round];
486
+ s[1] ^= SHA3_IOTA_L[round];
487
+ }
488
+ clean(B);
489
+ }
490
+ /** Keccak sponge function. */
491
+ var Keccak = class Keccak {
492
+ state;
493
+ pos = 0;
494
+ posOut = 0;
495
+ finished = false;
496
+ state32;
497
+ destroyed = false;
498
+ blockLen;
499
+ suffix;
500
+ outputLen;
501
+ enableXOF = false;
502
+ rounds;
503
+ constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
504
+ this.blockLen = blockLen;
505
+ this.suffix = suffix;
506
+ this.outputLen = outputLen;
507
+ this.enableXOF = enableXOF;
508
+ this.rounds = rounds;
509
+ anumber$1(outputLen, "outputLen");
510
+ if (!(0 < blockLen && blockLen < 200)) throw new Error("only keccak-f1600 function is supported");
511
+ this.state = new Uint8Array(200);
512
+ this.state32 = u32(this.state);
513
+ }
514
+ clone() {
515
+ return this._cloneInto();
516
+ }
517
+ keccak() {
518
+ swap32IfBE(this.state32);
519
+ keccakP(this.state32, this.rounds);
520
+ swap32IfBE(this.state32);
521
+ this.posOut = 0;
522
+ this.pos = 0;
523
+ }
524
+ update(data) {
525
+ aexists(this);
526
+ abytes$1(data);
527
+ const { blockLen, state } = this;
528
+ const len = data.length;
529
+ for (let pos = 0; pos < len;) {
530
+ const take = Math.min(blockLen - this.pos, len - pos);
531
+ for (let i = 0; i < take; i++) state[this.pos++] ^= data[pos++];
532
+ if (this.pos === blockLen) this.keccak();
533
+ }
534
+ return this;
535
+ }
536
+ finish() {
537
+ if (this.finished) return;
538
+ this.finished = true;
539
+ const { state, suffix, pos, blockLen } = this;
540
+ state[pos] ^= suffix;
541
+ if ((suffix & 128) !== 0 && pos === blockLen - 1) this.keccak();
542
+ state[blockLen - 1] ^= 128;
543
+ this.keccak();
544
+ }
545
+ writeInto(out) {
546
+ aexists(this, false);
547
+ abytes$1(out);
548
+ this.finish();
549
+ const bufferOut = this.state;
550
+ const { blockLen } = this;
551
+ for (let pos = 0, len = out.length; pos < len;) {
552
+ if (this.posOut >= blockLen) this.keccak();
553
+ const take = Math.min(blockLen - this.posOut, len - pos);
554
+ out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
555
+ this.posOut += take;
556
+ pos += take;
557
+ }
558
+ return out;
559
+ }
560
+ xofInto(out) {
561
+ if (!this.enableXOF) throw new Error("XOF is not possible for this instance");
562
+ return this.writeInto(out);
563
+ }
564
+ xof(bytes) {
565
+ anumber$1(bytes);
566
+ return this.xofInto(new Uint8Array(bytes));
567
+ }
568
+ digestInto(out) {
569
+ aoutput(out, this);
570
+ if (this.finished) throw new Error("digest() was already called");
571
+ this.writeInto(out);
572
+ this.destroy();
573
+ return out;
574
+ }
575
+ digest() {
576
+ return this.digestInto(new Uint8Array(this.outputLen));
577
+ }
578
+ destroy() {
579
+ this.destroyed = true;
580
+ clean(this.state);
581
+ }
582
+ _cloneInto(to) {
583
+ const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
584
+ to ||= new Keccak(blockLen, suffix, outputLen, enableXOF, rounds);
585
+ to.state32.set(this.state32);
586
+ to.pos = this.pos;
587
+ to.posOut = this.posOut;
588
+ to.finished = this.finished;
589
+ to.rounds = rounds;
590
+ to.suffix = suffix;
591
+ to.outputLen = outputLen;
592
+ to.enableXOF = enableXOF;
593
+ to.destroyed = this.destroyed;
594
+ return to;
595
+ }
596
+ };
597
+ const genKeccak = (suffix, blockLen, outputLen, info = {}) => createHasher(() => new Keccak(blockLen, suffix, outputLen), info);
598
+ /** keccak-256 hash function. Different from SHA3-256. */
599
+ const keccak_256 = /* @__PURE__ */ genKeccak(1, 136, 32);
600
+
601
+ //#endregion
602
+ //#region src/crypto/keccak256.ts
603
+ /**
604
+ * Keccak-256 hash function wrapper
605
+ * Uses @noble/hashes for audited, tree-shakeable implementation
606
+ */
607
+ /**
608
+ * Compute Keccak-256 hash (NOT SHA-3)
609
+ *
610
+ * @param input - String or Uint8Array to hash
611
+ * @returns Lowercase hex string (64 chars, no 0x prefix)
612
+ */
613
+ function keccak256(input) {
614
+ const hash = keccak_256(typeof input === "string" ? new TextEncoder().encode(input) : input);
615
+ return Array.from(hash).map((b) => b.toString(16).padStart(2, "0")).join("");
616
+ }
617
+
618
+ //#endregion
619
+ //#region src/crypto/eip55.ts
620
+ /**
621
+ * EIP-55 mixed-case checksum address encoding
622
+ * Spec: https://eips.ethereum.org/EIPS/eip-55
623
+ */
624
+ /**
625
+ * Convert an Ethereum address to EIP-55 checksummed format
626
+ *
627
+ * @param address - 42-character hex address (0x-prefixed)
628
+ * @returns Checksummed address with mixed case
629
+ */
630
+ function toChecksumAddress(address) {
631
+ const lowerHex = address.slice(2).toLowerCase();
632
+ const hash = keccak256(lowerHex);
633
+ let result = "0x";
634
+ for (let i = 0; i < lowerHex.length; i++) {
635
+ const char = lowerHex[i];
636
+ const hashChar = hash[i];
637
+ if (!char || !hashChar) continue;
638
+ if (char >= "a" && char <= "f") result += parseInt(hashChar, 16) >= 8 ? char.toUpperCase() : char;
639
+ else result += char;
640
+ }
641
+ return result;
642
+ }
643
+ /**
644
+ * Check if an address has valid EIP-55 checksum
645
+ *
646
+ * Returns false for all-lowercase or all-uppercase addresses
647
+ * (these are valid formats but do not match their checksummed version)
648
+ *
649
+ * @param address - Address to validate
650
+ * @returns True if checksum is valid
651
+ */
652
+ function isValidChecksum(address) {
653
+ return address === toChecksumAddress(address);
654
+ }
655
+
656
+ //#endregion
657
+ //#region src/validation/evm-address.ts
658
+ /**
659
+ * EVM address validation with EIP-55 checksum verification
660
+ */
661
+ const EVM_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/;
662
+ /**
663
+ * Validate an EVM address format and checksum
664
+ *
665
+ * Returns errors for invalid format, warnings for checksum issues
666
+ *
667
+ * @param address - Address to validate
668
+ * @param field - Field path for error reporting
669
+ * @returns Array of validation issues (empty if valid)
670
+ */
671
+ function validateEvmAddress(address, field) {
672
+ if (!EVM_ADDRESS_REGEX.test(address)) return [{
673
+ code: ErrorCode.INVALID_EVM_ADDRESS,
674
+ field,
675
+ message: "EVM address must be 42 hex characters with 0x prefix",
676
+ severity: "error",
677
+ fix: "Format: 0x followed by 40 hex digits (0-9, a-f, A-F)"
678
+ }];
679
+ const hexPart = address.slice(2);
680
+ if (address === address.toLowerCase() && /[a-f]/.test(hexPart)) return [{
681
+ code: ErrorCode.NO_EVM_CHECKSUM,
682
+ field,
683
+ message: "EVM address is all-lowercase with no checksum protection",
684
+ severity: "warning",
685
+ fix: `Use checksummed address to detect typos: ${toChecksumAddress(address)}`
686
+ }];
687
+ if (/^[0-9A-F]{40}$/.test(hexPart) && /[A-F]/.test(hexPart)) return [];
688
+ if (/^[0-9]{40}$/.test(hexPart)) return [];
689
+ if (!isValidChecksum(address)) return [{
690
+ code: ErrorCode.BAD_EVM_CHECKSUM,
691
+ field,
692
+ message: "EVM address has invalid checksum (EIP-55)",
693
+ severity: "warning",
694
+ fix: `Expected: ${toChecksumAddress(address)}`
695
+ }];
696
+ return [];
697
+ }
698
+
699
+ //#endregion
700
+ //#region ../../node_modules/.pnpm/@scure+base@2.0.0/node_modules/@scure/base/index.js
701
+ /*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
702
+ function isBytes(a) {
703
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
704
+ }
705
+ /** Asserts something is Uint8Array. */
706
+ function abytes(b) {
707
+ if (!isBytes(b)) throw new Error("Uint8Array expected");
708
+ }
709
+ function isArrayOf(isString, arr) {
710
+ if (!Array.isArray(arr)) return false;
711
+ if (arr.length === 0) return true;
712
+ if (isString) return arr.every((item) => typeof item === "string");
713
+ else return arr.every((item) => Number.isSafeInteger(item));
714
+ }
715
+ function afn(input) {
716
+ if (typeof input !== "function") throw new Error("function expected");
717
+ return true;
718
+ }
719
+ function astr(label, input) {
720
+ if (typeof input !== "string") throw new Error(`${label}: string expected`);
721
+ return true;
722
+ }
723
+ function anumber(n) {
724
+ if (!Number.isSafeInteger(n)) throw new Error(`invalid integer: ${n}`);
725
+ }
726
+ function aArr(input) {
727
+ if (!Array.isArray(input)) throw new Error("array expected");
728
+ }
729
+ function astrArr(label, input) {
730
+ if (!isArrayOf(true, input)) throw new Error(`${label}: array of strings expected`);
731
+ }
732
+ function anumArr(label, input) {
733
+ if (!isArrayOf(false, input)) throw new Error(`${label}: array of numbers expected`);
734
+ }
735
+ /**
736
+ * @__NO_SIDE_EFFECTS__
737
+ */
738
+ function chain(...args) {
739
+ const id = (a) => a;
740
+ const wrap = (a, b) => (c) => a(b(c));
741
+ return {
742
+ encode: args.map((x) => x.encode).reduceRight(wrap, id),
743
+ decode: args.map((x) => x.decode).reduce(wrap, id)
744
+ };
745
+ }
746
+ /**
747
+ * Encodes integer radix representation to array of strings using alphabet and back.
748
+ * Could also be array of strings.
749
+ * @__NO_SIDE_EFFECTS__
750
+ */
751
+ function alphabet(letters) {
752
+ const lettersA = typeof letters === "string" ? letters.split("") : letters;
753
+ const len = lettersA.length;
754
+ astrArr("alphabet", lettersA);
755
+ const indexes = new Map(lettersA.map((l, i) => [l, i]));
756
+ return {
757
+ encode: (digits) => {
758
+ aArr(digits);
759
+ return digits.map((i) => {
760
+ if (!Number.isSafeInteger(i) || i < 0 || i >= len) throw new Error(`alphabet.encode: digit index outside alphabet "${i}". Allowed: ${letters}`);
761
+ return lettersA[i];
762
+ });
763
+ },
764
+ decode: (input) => {
765
+ aArr(input);
766
+ return input.map((letter) => {
767
+ astr("alphabet.decode", letter);
768
+ const i = indexes.get(letter);
769
+ if (i === void 0) throw new Error(`Unknown letter: "${letter}". Allowed: ${letters}`);
770
+ return i;
771
+ });
772
+ }
773
+ };
774
+ }
775
+ /**
776
+ * @__NO_SIDE_EFFECTS__
777
+ */
778
+ function join(separator = "") {
779
+ astr("join", separator);
780
+ return {
781
+ encode: (from) => {
782
+ astrArr("join.decode", from);
783
+ return from.join(separator);
784
+ },
785
+ decode: (to) => {
786
+ astr("join.decode", to);
787
+ return to.split(separator);
788
+ }
789
+ };
790
+ }
791
+ /**
792
+ * Pad strings array so it has integer number of bits
793
+ * @__NO_SIDE_EFFECTS__
794
+ */
795
+ function padding(bits, chr = "=") {
796
+ anumber(bits);
797
+ astr("padding", chr);
798
+ return {
799
+ encode(data) {
800
+ astrArr("padding.encode", data);
801
+ while (data.length * bits % 8) data.push(chr);
802
+ return data;
803
+ },
804
+ decode(input) {
805
+ astrArr("padding.decode", input);
806
+ let end = input.length;
807
+ if (end * bits % 8) throw new Error("padding: invalid, string should have whole number of bytes");
808
+ for (; end > 0 && input[end - 1] === chr; end--) if ((end - 1) * bits % 8 === 0) throw new Error("padding: invalid, string has too much padding");
809
+ return input.slice(0, end);
810
+ }
811
+ };
812
+ }
813
+ /**
814
+ * @__NO_SIDE_EFFECTS__
815
+ */
816
+ function normalize(fn) {
817
+ afn(fn);
818
+ return {
819
+ encode: (from) => from,
820
+ decode: (to) => fn(to)
821
+ };
822
+ }
823
+ /**
824
+ * Slow: O(n^2) time complexity
825
+ */
826
+ function convertRadix(data, from, to) {
827
+ if (from < 2) throw new Error(`convertRadix: invalid from=${from}, base cannot be less than 2`);
828
+ if (to < 2) throw new Error(`convertRadix: invalid to=${to}, base cannot be less than 2`);
829
+ aArr(data);
830
+ if (!data.length) return [];
831
+ let pos = 0;
832
+ const res = [];
833
+ const digits = Array.from(data, (d) => {
834
+ anumber(d);
835
+ if (d < 0 || d >= from) throw new Error(`invalid integer: ${d}`);
836
+ return d;
837
+ });
838
+ const dlen = digits.length;
839
+ while (true) {
840
+ let carry = 0;
841
+ let done = true;
842
+ for (let i = pos; i < dlen; i++) {
843
+ const digit = digits[i];
844
+ const fromCarry = from * carry;
845
+ const digitBase = fromCarry + digit;
846
+ if (!Number.isSafeInteger(digitBase) || fromCarry / from !== carry || digitBase - digit !== fromCarry) throw new Error("convertRadix: carry overflow");
847
+ const div = digitBase / to;
848
+ carry = digitBase % to;
849
+ const rounded = Math.floor(div);
850
+ digits[i] = rounded;
851
+ if (!Number.isSafeInteger(rounded) || rounded * to + carry !== digitBase) throw new Error("convertRadix: carry overflow");
852
+ if (!done) continue;
853
+ else if (!rounded) pos = i;
854
+ else done = false;
855
+ }
856
+ res.push(carry);
857
+ if (done) break;
858
+ }
859
+ for (let i = 0; i < data.length - 1 && data[i] === 0; i++) res.push(0);
860
+ return res.reverse();
861
+ }
862
+ const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
863
+ const radix2carry = /* @__NO_SIDE_EFFECTS__ */ (from, to) => from + (to - gcd(from, to));
864
+ const powers = /* @__PURE__ */ (() => {
865
+ let res = [];
866
+ for (let i = 0; i < 40; i++) res.push(2 ** i);
867
+ return res;
868
+ })();
869
+ /**
870
+ * Implemented with numbers, because BigInt is 5x slower
871
+ */
872
+ function convertRadix2(data, from, to, padding) {
873
+ aArr(data);
874
+ if (from <= 0 || from > 32) throw new Error(`convertRadix2: wrong from=${from}`);
875
+ if (to <= 0 || to > 32) throw new Error(`convertRadix2: wrong to=${to}`);
876
+ if (/* @__PURE__ */ radix2carry(from, to) > 32) throw new Error(`convertRadix2: carry overflow from=${from} to=${to} carryBits=${/* @__PURE__ */ radix2carry(from, to)}`);
877
+ let carry = 0;
878
+ let pos = 0;
879
+ const max = powers[from];
880
+ const mask = powers[to] - 1;
881
+ const res = [];
882
+ for (const n of data) {
883
+ anumber(n);
884
+ if (n >= max) throw new Error(`convertRadix2: invalid data word=${n} from=${from}`);
885
+ carry = carry << from | n;
886
+ if (pos + from > 32) throw new Error(`convertRadix2: carry overflow pos=${pos} from=${from}`);
887
+ pos += from;
888
+ for (; pos >= to; pos -= to) res.push((carry >> pos - to & mask) >>> 0);
889
+ const pow = powers[pos];
890
+ if (pow === void 0) throw new Error("invalid carry");
891
+ carry &= pow - 1;
892
+ }
893
+ carry = carry << to - pos & mask;
894
+ if (!padding && pos >= from) throw new Error("Excess padding");
895
+ if (!padding && carry > 0) throw new Error(`Non-zero padding: ${carry}`);
896
+ if (padding && pos > 0) res.push(carry >>> 0);
897
+ return res;
898
+ }
899
+ /**
900
+ * @__NO_SIDE_EFFECTS__
901
+ */
902
+ function radix(num) {
903
+ anumber(num);
904
+ const _256 = 2 ** 8;
905
+ return {
906
+ encode: (bytes) => {
907
+ if (!isBytes(bytes)) throw new Error("radix.encode input should be Uint8Array");
908
+ return convertRadix(Array.from(bytes), _256, num);
909
+ },
910
+ decode: (digits) => {
911
+ anumArr("radix.decode", digits);
912
+ return Uint8Array.from(convertRadix(digits, num, _256));
913
+ }
914
+ };
915
+ }
916
+ /**
917
+ * If both bases are power of same number (like `2**8 <-> 2**64`),
918
+ * there is a linear algorithm. For now we have implementation for power-of-two bases only.
919
+ * @__NO_SIDE_EFFECTS__
920
+ */
921
+ function radix2(bits, revPadding = false) {
922
+ anumber(bits);
923
+ if (bits <= 0 || bits > 32) throw new Error("radix2: bits should be in (0..32]");
924
+ if (/* @__PURE__ */ radix2carry(8, bits) > 32 || /* @__PURE__ */ radix2carry(bits, 8) > 32) throw new Error("radix2: carry overflow");
925
+ return {
926
+ encode: (bytes) => {
927
+ if (!isBytes(bytes)) throw new Error("radix2.encode input should be Uint8Array");
928
+ return convertRadix2(Array.from(bytes), 8, bits, !revPadding);
929
+ },
930
+ decode: (digits) => {
931
+ anumArr("radix2.decode", digits);
932
+ return Uint8Array.from(convertRadix2(digits, bits, 8, revPadding));
933
+ }
934
+ };
935
+ }
936
+ function unsafeWrapper(fn) {
937
+ afn(fn);
938
+ return function(...args) {
939
+ try {
940
+ return fn.apply(null, args);
941
+ } catch (e) {}
942
+ };
943
+ }
944
+ /**
945
+ * base16 encoding from RFC 4648.
946
+ * @example
947
+ * ```js
948
+ * base16.encode(Uint8Array.from([0x12, 0xab]));
949
+ * // => '12AB'
950
+ * ```
951
+ */
952
+ const base16 = chain(radix2(4), alphabet("0123456789ABCDEF"), join(""));
953
+ /**
954
+ * base32 encoding from RFC 4648. Has padding.
955
+ * Use `base32nopad` for unpadded version.
956
+ * Also check out `base32hex`, `base32hexnopad`, `base32crockford`.
957
+ * @example
958
+ * ```js
959
+ * base32.encode(Uint8Array.from([0x12, 0xab]));
960
+ * // => 'CKVQ===='
961
+ * base32.decode('CKVQ====');
962
+ * // => Uint8Array.from([0x12, 0xab])
963
+ * ```
964
+ */
965
+ const base32 = chain(radix2(5), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), padding(5), join(""));
966
+ /**
967
+ * base32 encoding from RFC 4648. No padding.
968
+ * Use `base32` for padded version.
969
+ * Also check out `base32hex`, `base32hexnopad`, `base32crockford`.
970
+ * @example
971
+ * ```js
972
+ * base32nopad.encode(Uint8Array.from([0x12, 0xab]));
973
+ * // => 'CKVQ'
974
+ * base32nopad.decode('CKVQ');
975
+ * // => Uint8Array.from([0x12, 0xab])
976
+ * ```
977
+ */
978
+ const base32nopad = chain(radix2(5), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), join(""));
979
+ /**
980
+ * base32 encoding from RFC 4648. Padded. Compared to ordinary `base32`, slightly different alphabet.
981
+ * Use `base32hexnopad` for unpadded version.
982
+ * @example
983
+ * ```js
984
+ * base32hex.encode(Uint8Array.from([0x12, 0xab]));
985
+ * // => '2ALG===='
986
+ * base32hex.decode('2ALG====');
987
+ * // => Uint8Array.from([0x12, 0xab])
988
+ * ```
989
+ */
990
+ const base32hex = chain(radix2(5), alphabet("0123456789ABCDEFGHIJKLMNOPQRSTUV"), padding(5), join(""));
991
+ /**
992
+ * base32 encoding from RFC 4648. No padding. Compared to ordinary `base32`, slightly different alphabet.
993
+ * Use `base32hex` for padded version.
994
+ * @example
995
+ * ```js
996
+ * base32hexnopad.encode(Uint8Array.from([0x12, 0xab]));
997
+ * // => '2ALG'
998
+ * base32hexnopad.decode('2ALG');
999
+ * // => Uint8Array.from([0x12, 0xab])
1000
+ * ```
1001
+ */
1002
+ const base32hexnopad = chain(radix2(5), alphabet("0123456789ABCDEFGHIJKLMNOPQRSTUV"), join(""));
1003
+ /**
1004
+ * base32 encoding from RFC 4648. Doug Crockford's version.
1005
+ * https://www.crockford.com/base32.html
1006
+ * @example
1007
+ * ```js
1008
+ * base32crockford.encode(Uint8Array.from([0x12, 0xab]));
1009
+ * // => '2ANG'
1010
+ * base32crockford.decode('2ANG');
1011
+ * // => Uint8Array.from([0x12, 0xab])
1012
+ * ```
1013
+ */
1014
+ const base32crockford = chain(radix2(5), alphabet("0123456789ABCDEFGHJKMNPQRSTVWXYZ"), join(""), normalize((s) => s.toUpperCase().replace(/O/g, "0").replace(/[IL]/g, "1")));
1015
+ const hasBase64Builtin = typeof Uint8Array.from([]).toBase64 === "function" && typeof Uint8Array.fromBase64 === "function";
1016
+ const decodeBase64Builtin = (s, isUrl) => {
1017
+ astr("base64", s);
1018
+ const re = isUrl ? /^[A-Za-z0-9=_-]+$/ : /^[A-Za-z0-9=+/]+$/;
1019
+ const alphabet = isUrl ? "base64url" : "base64";
1020
+ if (s.length > 0 && !re.test(s)) throw new Error("invalid base64");
1021
+ return Uint8Array.fromBase64(s, {
1022
+ alphabet,
1023
+ lastChunkHandling: "strict"
1024
+ });
1025
+ };
1026
+ /**
1027
+ * base64 from RFC 4648. Padded.
1028
+ * Use `base64nopad` for unpadded version.
1029
+ * Also check out `base64url`, `base64urlnopad`.
1030
+ * Falls back to built-in function, when available.
1031
+ * @example
1032
+ * ```js
1033
+ * base64.encode(Uint8Array.from([0x12, 0xab]));
1034
+ * // => 'Eqs='
1035
+ * base64.decode('Eqs=');
1036
+ * // => Uint8Array.from([0x12, 0xab])
1037
+ * ```
1038
+ */
1039
+ const base64 = hasBase64Builtin ? {
1040
+ encode(b) {
1041
+ abytes(b);
1042
+ return b.toBase64();
1043
+ },
1044
+ decode(s) {
1045
+ return decodeBase64Builtin(s, false);
1046
+ }
1047
+ } : chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), padding(6), join(""));
1048
+ /**
1049
+ * base64 from RFC 4648. No padding.
1050
+ * Use `base64` for padded version.
1051
+ * @example
1052
+ * ```js
1053
+ * base64nopad.encode(Uint8Array.from([0x12, 0xab]));
1054
+ * // => 'Eqs'
1055
+ * base64nopad.decode('Eqs');
1056
+ * // => Uint8Array.from([0x12, 0xab])
1057
+ * ```
1058
+ */
1059
+ const base64nopad = chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), join(""));
1060
+ /**
1061
+ * base64 from RFC 4648, using URL-safe alphabet. Padded.
1062
+ * Use `base64urlnopad` for unpadded version.
1063
+ * Falls back to built-in function, when available.
1064
+ * @example
1065
+ * ```js
1066
+ * base64url.encode(Uint8Array.from([0x12, 0xab]));
1067
+ * // => 'Eqs='
1068
+ * base64url.decode('Eqs=');
1069
+ * // => Uint8Array.from([0x12, 0xab])
1070
+ * ```
1071
+ */
1072
+ const base64url = hasBase64Builtin ? {
1073
+ encode(b) {
1074
+ abytes(b);
1075
+ return b.toBase64({ alphabet: "base64url" });
1076
+ },
1077
+ decode(s) {
1078
+ return decodeBase64Builtin(s, true);
1079
+ }
1080
+ } : chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), padding(6), join(""));
1081
+ /**
1082
+ * base64 from RFC 4648, using URL-safe alphabet. No padding.
1083
+ * Use `base64url` for padded version.
1084
+ * @example
1085
+ * ```js
1086
+ * base64urlnopad.encode(Uint8Array.from([0x12, 0xab]));
1087
+ * // => 'Eqs'
1088
+ * base64urlnopad.decode('Eqs');
1089
+ * // => Uint8Array.from([0x12, 0xab])
1090
+ * ```
1091
+ */
1092
+ const base64urlnopad = chain(radix2(6), alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), join(""));
1093
+ const genBase58 = /* @__NO_SIDE_EFFECTS__ */ (abc) => chain(radix(58), alphabet(abc), join(""));
1094
+ /**
1095
+ * base58: base64 without ambigous characters +, /, 0, O, I, l.
1096
+ * Quadratic (O(n^2)) - so, can't be used on large inputs.
1097
+ * @example
1098
+ * ```js
1099
+ * base58.decode('01abcdef');
1100
+ * // => '3UhJW'
1101
+ * ```
1102
+ */
1103
+ const base58 = /* @__PURE__ */ genBase58("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
1104
+ const BECH_ALPHABET = chain(alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), join(""));
1105
+ const POLYMOD_GENERATORS = [
1106
+ 996825010,
1107
+ 642813549,
1108
+ 513874426,
1109
+ 1027748829,
1110
+ 705979059
1111
+ ];
1112
+ function bech32Polymod(pre) {
1113
+ const b = pre >> 25;
1114
+ let chk = (pre & 33554431) << 5;
1115
+ for (let i = 0; i < POLYMOD_GENERATORS.length; i++) if ((b >> i & 1) === 1) chk ^= POLYMOD_GENERATORS[i];
1116
+ return chk;
1117
+ }
1118
+ function bechChecksum(prefix, words, encodingConst = 1) {
1119
+ const len = prefix.length;
1120
+ let chk = 1;
1121
+ for (let i = 0; i < len; i++) {
1122
+ const c = prefix.charCodeAt(i);
1123
+ if (c < 33 || c > 126) throw new Error(`Invalid prefix (${prefix})`);
1124
+ chk = bech32Polymod(chk) ^ c >> 5;
1125
+ }
1126
+ chk = bech32Polymod(chk);
1127
+ for (let i = 0; i < len; i++) chk = bech32Polymod(chk) ^ prefix.charCodeAt(i) & 31;
1128
+ for (let v of words) chk = bech32Polymod(chk) ^ v;
1129
+ for (let i = 0; i < 6; i++) chk = bech32Polymod(chk);
1130
+ chk ^= encodingConst;
1131
+ return BECH_ALPHABET.encode(convertRadix2([chk % powers[30]], 30, 5, false));
1132
+ }
1133
+ /**
1134
+ * @__NO_SIDE_EFFECTS__
1135
+ */
1136
+ function genBech32(encoding) {
1137
+ const ENCODING_CONST = encoding === "bech32" ? 1 : 734539939;
1138
+ const _words = radix2(5);
1139
+ const fromWords = _words.decode;
1140
+ const toWords = _words.encode;
1141
+ const fromWordsUnsafe = unsafeWrapper(fromWords);
1142
+ function encode(prefix, words, limit = 90) {
1143
+ astr("bech32.encode prefix", prefix);
1144
+ if (isBytes(words)) words = Array.from(words);
1145
+ anumArr("bech32.encode", words);
1146
+ const plen = prefix.length;
1147
+ if (plen === 0) throw new TypeError(`Invalid prefix length ${plen}`);
1148
+ const actualLength = plen + 7 + words.length;
1149
+ if (limit !== false && actualLength > limit) throw new TypeError(`Length ${actualLength} exceeds limit ${limit}`);
1150
+ const lowered = prefix.toLowerCase();
1151
+ const sum = bechChecksum(lowered, words, ENCODING_CONST);
1152
+ return `${lowered}1${BECH_ALPHABET.encode(words)}${sum}`;
1153
+ }
1154
+ function decode(str, limit = 90) {
1155
+ astr("bech32.decode input", str);
1156
+ const slen = str.length;
1157
+ if (slen < 8 || limit !== false && slen > limit) throw new TypeError(`invalid string length: ${slen} (${str}). Expected (8..${limit})`);
1158
+ const lowered = str.toLowerCase();
1159
+ if (str !== lowered && str !== str.toUpperCase()) throw new Error(`String must be lowercase or uppercase`);
1160
+ const sepIndex = lowered.lastIndexOf("1");
1161
+ if (sepIndex === 0 || sepIndex === -1) throw new Error(`Letter "1" must be present between prefix and data only`);
1162
+ const prefix = lowered.slice(0, sepIndex);
1163
+ const data = lowered.slice(sepIndex + 1);
1164
+ if (data.length < 6) throw new Error("Data must be at least 6 characters long");
1165
+ const words = BECH_ALPHABET.decode(data).slice(0, -6);
1166
+ const sum = bechChecksum(prefix, words, ENCODING_CONST);
1167
+ if (!data.endsWith(sum)) throw new Error(`Invalid checksum in ${str}: expected "${sum}"`);
1168
+ return {
1169
+ prefix,
1170
+ words
1171
+ };
1172
+ }
1173
+ const decodeUnsafe = unsafeWrapper(decode);
1174
+ function decodeToBytes(str) {
1175
+ const { prefix, words } = decode(str, false);
1176
+ return {
1177
+ prefix,
1178
+ words,
1179
+ bytes: fromWords(words)
1180
+ };
1181
+ }
1182
+ function encodeFromBytes(prefix, bytes) {
1183
+ return encode(prefix, toWords(bytes));
1184
+ }
1185
+ return {
1186
+ encode,
1187
+ decode,
1188
+ encodeFromBytes,
1189
+ decodeToBytes,
1190
+ decodeUnsafe,
1191
+ fromWords,
1192
+ fromWordsUnsafe,
1193
+ toWords
1194
+ };
1195
+ }
1196
+ /**
1197
+ * bech32 from BIP 173. Operates on words.
1198
+ * For high-level, check out scure-btc-signer:
1199
+ * https://github.com/paulmillr/scure-btc-signer.
1200
+ */
1201
+ const bech32 = genBech32("bech32");
1202
+ /**
1203
+ * bech32m from BIP 350. Operates on words.
1204
+ * It was to mitigate `bech32` weaknesses.
1205
+ * For high-level, check out scure-btc-signer:
1206
+ * https://github.com/paulmillr/scure-btc-signer.
1207
+ */
1208
+ const bech32m = genBech32("bech32m");
1209
+ const hasHexBuiltin = typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function";
1210
+ const hexBuiltin = {
1211
+ encode(data) {
1212
+ abytes(data);
1213
+ return data.toHex();
1214
+ },
1215
+ decode(s) {
1216
+ astr("hex", s);
1217
+ return Uint8Array.fromHex(s);
1218
+ }
1219
+ };
1220
+ /**
1221
+ * hex string decoder. Uses built-in function, when available.
1222
+ * @example
1223
+ * ```js
1224
+ * const b = hex.decode("0102ff"); // => new Uint8Array([ 1, 2, 255 ])
1225
+ * const str = hex.encode(b); // "0102ff"
1226
+ * ```
1227
+ */
1228
+ const hex = hasHexBuiltin ? hexBuiltin : chain(radix2(4), alphabet("0123456789abcdef"), join(""), normalize((s) => {
1229
+ if (typeof s !== "string" || s.length % 2 !== 0) throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
1230
+ return s.toLowerCase();
1231
+ }));
1232
+
1233
+ //#endregion
1234
+ //#region src/crypto/base58.ts
1235
+ /**
1236
+ * Base58 decoder wrapper
1237
+ * Uses @scure/base for audited, tree-shakeable implementation
1238
+ */
1239
+ /**
1240
+ * Decode a Base58-encoded string to bytes
1241
+ *
1242
+ * Preserves leading zero bytes (represented as leading '1' characters)
1243
+ *
1244
+ * @param input - Base58 string
1245
+ * @returns Decoded bytes
1246
+ * @throws Error if input contains invalid Base58 characters
1247
+ */
1248
+ function decodeBase58(input) {
1249
+ try {
1250
+ return base58.decode(input);
1251
+ } catch (error) {
1252
+ const message = error instanceof Error ? error.message : String(error);
1253
+ throw new Error(`Invalid Base58: ${message}`);
1254
+ }
1255
+ }
1256
+
1257
+ //#endregion
1258
+ //#region src/validation/solana-address.ts
1259
+ /**
1260
+ * Solana address validation (Base58 + 32-byte length)
1261
+ */
1262
+ const SOLANA_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
1263
+ /**
1264
+ * Validate a Solana address (Base58 encoded public key)
1265
+ *
1266
+ * Checks Base58 format and verifies decoded length is exactly 32 bytes
1267
+ *
1268
+ * @param address - Address to validate
1269
+ * @param field - Field path for error reporting
1270
+ * @returns Array of validation issues (empty if valid)
1271
+ */
1272
+ function validateSolanaAddress(address, field) {
1273
+ if (!SOLANA_ADDRESS_REGEX.test(address)) return [{
1274
+ code: ErrorCode.INVALID_SOLANA_ADDRESS,
1275
+ field,
1276
+ message: "Solana address must be 32-44 Base58 characters",
1277
+ severity: "error",
1278
+ fix: "Valid characters: 1-9, A-H, J-N, P-Z, a-k, m-z (no 0, O, I, l)"
1279
+ }];
1280
+ try {
1281
+ const decoded = decodeBase58(address);
1282
+ if (decoded.length !== 32) return [{
1283
+ code: ErrorCode.INVALID_SOLANA_ADDRESS,
1284
+ field,
1285
+ message: `Solana address must decode to 32 bytes, got ${decoded.length}`,
1286
+ severity: "error",
1287
+ fix: "Verify address is a valid Solana public key"
1288
+ }];
1289
+ } catch (error) {
1290
+ return [{
1291
+ code: ErrorCode.INVALID_SOLANA_ADDRESS,
1292
+ field,
1293
+ message: "Invalid Base58 encoding",
1294
+ severity: "error",
1295
+ fix: error instanceof Error ? error.message : "Check Base58 encoding"
1296
+ }];
1297
+ }
1298
+ return [];
1299
+ }
1300
+
1301
+ //#endregion
1302
+ //#region src/validation/address.ts
1303
+ /**
1304
+ * Address validation with CAIP-2 namespace dispatch
1305
+ *
1306
+ * Dispatches to chain-specific validators based on network namespace
1307
+ */
1308
+ /**
1309
+ * Validate an address for a specific network
1310
+ *
1311
+ * Dispatches to appropriate chain-specific validator based on CAIP-2 namespace:
1312
+ * - eip155:* → EVM address validation
1313
+ * - solana:* → Solana address validation
1314
+ * - stellar:*, aptos:* → Accept any string (deep validation deferred)
1315
+ * - Unknown namespaces → Accept any string (registry warnings handled elsewhere)
1316
+ *
1317
+ * Cross-chain mismatches are caught naturally by dispatch:
1318
+ * - EVM address (0x...) on Solana network → fails Solana Base58 validation
1319
+ * - Solana address on EVM network → fails EVM 0x-prefix validation
1320
+ *
1321
+ * @param address - Address to validate
1322
+ * @param network - CAIP-2 network identifier
1323
+ * @param field - Field path for error reporting
1324
+ * @returns Array of validation issues (empty if valid)
1325
+ */
1326
+ function validateAddress(address, network, field) {
1327
+ const namespace = getNetworkNamespace(network);
1328
+ if (namespace === void 0) return [];
1329
+ switch (namespace) {
1330
+ case "eip155": return validateEvmAddress(address, field);
1331
+ case "solana": return validateSolanaAddress(address, field);
1332
+ case "stellar":
1333
+ case "aptos": return [];
1334
+ default: return [];
1335
+ }
1336
+ }
1337
+
1338
+ //#endregion
1339
+ //#region src/validation/rules/structure.ts
1340
+ /**
1341
+ * Validate input structure: parse JSON, check object, detect format.
1342
+ *
1343
+ * @param input - Raw JSON string or object
1344
+ * @returns StructureResult with parsed object, detected format, and issues
1345
+ */
1346
+ function validateStructure(input) {
1347
+ const issues = [];
1348
+ const { parsed, error } = parseInput(input);
1349
+ if (error) return {
1350
+ parsed: null,
1351
+ format: "unknown",
1352
+ issues: [error]
1353
+ };
1354
+ if (!isRecord(parsed)) {
1355
+ issues.push({
1356
+ code: ErrorCode.NOT_OBJECT,
1357
+ field: "$",
1358
+ message: ErrorMessages.NOT_OBJECT,
1359
+ severity: "error"
1360
+ });
1361
+ return {
1362
+ parsed: null,
1363
+ format: "unknown",
1364
+ issues
1365
+ };
1366
+ }
1367
+ const format = detect(parsed);
1368
+ if (format === "unknown") issues.push({
1369
+ code: ErrorCode.UNKNOWN_FORMAT,
1370
+ field: "$",
1371
+ message: ErrorMessages.UNKNOWN_FORMAT,
1372
+ severity: "error"
1373
+ });
1374
+ return {
1375
+ parsed,
1376
+ format,
1377
+ issues
1378
+ };
1379
+ }
1380
+
1381
+ //#endregion
1382
+ //#region src/validation/rules/version.ts
1383
+ /**
1384
+ * Validate x402Version field.
1385
+ *
1386
+ * Since normalize() always sets x402Version: 2, this mainly validates
1387
+ * the original format's version field. If somehow the value isn't 1 or 2,
1388
+ * push INVALID_VERSION error.
1389
+ *
1390
+ * Note: No MISSING_VERSION check needed here because normalize() always
1391
+ * sets it. The orchestrator handles version-related warnings for legacy
1392
+ * formats via the legacy rule module.
1393
+ *
1394
+ * @param config - Normalized config
1395
+ * @param _detectedFormat - Detected format (reserved for future use)
1396
+ * @returns Array of validation issues
1397
+ */
1398
+ function validateVersion(config, _detectedFormat) {
1399
+ const issues = [];
1400
+ const version = config.x402Version;
1401
+ if (version !== 1 && version !== 2) issues.push({
1402
+ code: ErrorCode.INVALID_VERSION,
1403
+ field: "x402Version",
1404
+ message: ErrorMessages.INVALID_VERSION,
1405
+ severity: "error"
1406
+ });
1407
+ return issues;
1408
+ }
1409
+
1410
+ //#endregion
1411
+ //#region src/validation/rules/fields.ts
1412
+ /**
1413
+ * Validate required fields on a single accepts entry.
1414
+ *
1415
+ * @param entry - Accepts entry to validate
1416
+ * @param fieldPath - Dot-notation path for issue reporting (e.g. "accepts[0]")
1417
+ * @returns Array of validation issues
1418
+ */
1419
+ function validateFields(entry, fieldPath) {
1420
+ const issues = [];
1421
+ if (!entry.scheme) issues.push({
1422
+ code: ErrorCode.MISSING_SCHEME,
1423
+ field: `${fieldPath}.scheme`,
1424
+ message: ErrorMessages.MISSING_SCHEME,
1425
+ severity: "error"
1426
+ });
1427
+ if (!entry.network) issues.push({
1428
+ code: ErrorCode.MISSING_NETWORK,
1429
+ field: `${fieldPath}.network`,
1430
+ message: ErrorMessages.MISSING_NETWORK,
1431
+ severity: "error"
1432
+ });
1433
+ if (!entry.amount) issues.push({
1434
+ code: ErrorCode.MISSING_AMOUNT,
1435
+ field: `${fieldPath}.amount`,
1436
+ message: ErrorMessages.MISSING_AMOUNT,
1437
+ severity: "error"
1438
+ });
1439
+ if (!entry.asset) issues.push({
1440
+ code: ErrorCode.MISSING_ASSET,
1441
+ field: `${fieldPath}.asset`,
1442
+ message: ErrorMessages.MISSING_ASSET,
1443
+ severity: "error"
1444
+ });
1445
+ if (!entry.payTo) issues.push({
1446
+ code: ErrorCode.MISSING_PAY_TO,
1447
+ field: `${fieldPath}.payTo`,
1448
+ message: ErrorMessages.MISSING_PAY_TO,
1449
+ severity: "error"
1450
+ });
1451
+ return issues;
1452
+ }
1453
+ /**
1454
+ * Validate the accepts array itself (presence, type, emptiness).
1455
+ *
1456
+ * @param config - Normalized config
1457
+ * @returns Array of validation issues
1458
+ */
1459
+ function validateAccepts(config) {
1460
+ const issues = [];
1461
+ if (!Array.isArray(config.accepts)) {
1462
+ issues.push({
1463
+ code: ErrorCode.INVALID_ACCEPTS,
1464
+ field: "accepts",
1465
+ message: ErrorMessages.INVALID_ACCEPTS,
1466
+ severity: "error"
1467
+ });
1468
+ return issues;
1469
+ }
1470
+ if (config.accepts.length === 0) issues.push({
1471
+ code: ErrorCode.EMPTY_ACCEPTS,
1472
+ field: "accepts",
1473
+ message: ErrorMessages.EMPTY_ACCEPTS,
1474
+ severity: "error"
1475
+ });
1476
+ return issues;
1477
+ }
1478
+ /**
1479
+ * Validate resource object on normalized config.
1480
+ *
1481
+ * For v2 configs, resource is expected. Its absence is a warning, not an error,
1482
+ * since some v2 configs work without it.
1483
+ *
1484
+ * Also validates URL format via new URL() constructor (RULE-04).
1485
+ *
1486
+ * @param config - Normalized config
1487
+ * @param detectedFormat - Detected format
1488
+ * @returns Array of validation issues
1489
+ */
1490
+ function validateResource(config, detectedFormat) {
1491
+ const issues = [];
1492
+ if (!config.resource) {
1493
+ if (detectedFormat === "v2") issues.push({
1494
+ code: ErrorCode.MISSING_RESOURCE,
1495
+ field: "resource",
1496
+ message: ErrorMessages.MISSING_RESOURCE,
1497
+ severity: "warning"
1498
+ });
1499
+ return issues;
1500
+ }
1501
+ if (!config.resource.url) {
1502
+ issues.push({
1503
+ code: ErrorCode.MISSING_RESOURCE,
1504
+ field: "resource.url",
1505
+ message: ErrorMessages.MISSING_RESOURCE,
1506
+ severity: "warning"
1507
+ });
1508
+ return issues;
1509
+ }
1510
+ try {
1511
+ new URL(config.resource.url);
1512
+ } catch {
1513
+ issues.push({
1514
+ code: ErrorCode.INVALID_URL,
1515
+ field: "resource.url",
1516
+ message: "resource.url is not a valid URL format",
1517
+ severity: "warning"
1518
+ });
1519
+ }
1520
+ return issues;
1521
+ }
1522
+
1523
+ //#endregion
1524
+ //#region src/registries/simple-names.ts
1525
+ const SIMPLE_NAME_TO_CAIP2 = {
1526
+ base: "eip155:8453",
1527
+ "base-sepolia": "eip155:84532",
1528
+ base_sepolia: "eip155:84532",
1529
+ avalanche: "eip155:43114",
1530
+ "avalanche-fuji": "eip155:43113",
1531
+ solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
1532
+ "solana-devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
1533
+ "solana-testnet": "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z",
1534
+ stellar: "stellar:pubnet",
1535
+ "stellar-testnet": "stellar:testnet",
1536
+ aptos: "aptos:1"
1537
+ };
1538
+ function getCanonicalNetwork(name) {
1539
+ return SIMPLE_NAME_TO_CAIP2[name.toLowerCase()];
1540
+ }
1541
+
1542
+ //#endregion
1543
+ //#region src/registries/assets.ts
1544
+ const KNOWN_ASSETS = {
1545
+ "eip155:8453": { "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": {
1546
+ symbol: "USDC",
1547
+ name: "USD Coin",
1548
+ decimals: 6
1549
+ } },
1550
+ "eip155:84532": { "0x036cbd53842c5426634e7929541ec2318f3dcf7e": {
1551
+ symbol: "USDC",
1552
+ name: "USD Coin",
1553
+ decimals: 6
1554
+ } },
1555
+ "eip155:43114": { "0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e": {
1556
+ symbol: "USDC",
1557
+ name: "USD Coin",
1558
+ decimals: 6
1559
+ } },
1560
+ "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": { EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: {
1561
+ symbol: "USDC",
1562
+ name: "USD Coin",
1563
+ decimals: 6
1564
+ } }
1565
+ };
1566
+ function isKnownAsset(network, address) {
1567
+ const networkAssets = KNOWN_ASSETS[network];
1568
+ if (!networkAssets) return false;
1569
+ return (getNetworkNamespace(network) === "eip155" ? address.toLowerCase() : address) in networkAssets;
1570
+ }
1571
+ function getAssetInfo(network, address) {
1572
+ const networkAssets = KNOWN_ASSETS[network];
1573
+ if (!networkAssets) return;
1574
+ return networkAssets[getNetworkNamespace(network) === "eip155" ? address.toLowerCase() : address];
1575
+ }
1576
+
1577
+ //#endregion
1578
+ //#region src/validation/rules/network.ts
1579
+ /**
1580
+ * Validate network field on a single accepts entry.
1581
+ *
1582
+ * Checks CAIP-2 format and known network registry. Provides fix
1583
+ * suggestions for simple chain names that have canonical CAIP-2 mappings.
1584
+ *
1585
+ * @param entry - Accepts entry to validate
1586
+ * @param fieldPath - Dot-notation path for issue reporting (e.g. "accepts[0]")
1587
+ * @returns Array of validation issues
1588
+ */
1589
+ function validateNetwork(entry, fieldPath) {
1590
+ const issues = [];
1591
+ if (!entry.network) return issues;
1592
+ if (!isValidCaip2(entry.network)) {
1593
+ const canonical = getCanonicalNetwork(entry.network);
1594
+ if (canonical) issues.push({
1595
+ code: ErrorCode.INVALID_NETWORK_FORMAT,
1596
+ field: `${fieldPath}.network`,
1597
+ message: ErrorMessages.INVALID_NETWORK_FORMAT,
1598
+ severity: "error",
1599
+ fix: `Use '${canonical}' instead of '${entry.network}'`
1600
+ });
1601
+ else issues.push({
1602
+ code: ErrorCode.INVALID_NETWORK_FORMAT,
1603
+ field: `${fieldPath}.network`,
1604
+ message: ErrorMessages.INVALID_NETWORK_FORMAT,
1605
+ severity: "error"
1606
+ });
1607
+ return issues;
1608
+ }
1609
+ if (!isKnownNetwork(entry.network)) issues.push({
1610
+ code: ErrorCode.UNKNOWN_NETWORK,
1611
+ field: `${fieldPath}.network`,
1612
+ message: ErrorMessages.UNKNOWN_NETWORK,
1613
+ severity: "warning"
1614
+ });
1615
+ return issues;
1616
+ }
1617
+ /**
1618
+ * Validate asset field on a single accepts entry.
1619
+ *
1620
+ * Checks if the asset is known for the given network. Only checks
1621
+ * when both network and asset are present and network is valid.
1622
+ *
1623
+ * @param entry - Accepts entry to validate
1624
+ * @param fieldPath - Dot-notation path for issue reporting (e.g. "accepts[0]")
1625
+ * @returns Array of validation issues
1626
+ */
1627
+ function validateAsset(entry, fieldPath) {
1628
+ const issues = [];
1629
+ if (!entry.asset) return issues;
1630
+ if (entry.network && isValidCaip2(entry.network) && !isKnownAsset(entry.network, entry.asset)) issues.push({
1631
+ code: ErrorCode.UNKNOWN_ASSET,
1632
+ field: `${fieldPath}.asset`,
1633
+ message: ErrorMessages.UNKNOWN_ASSET,
1634
+ severity: "warning"
1635
+ });
1636
+ return issues;
1637
+ }
1638
+
1639
+ //#endregion
1640
+ //#region src/validation/rules/amount.ts
1641
+ /**
1642
+ * Validate amount field on a single accepts entry.
1643
+ *
1644
+ * Amount must be a digit-only string (no decimals, signs, or scientific notation)
1645
+ * and must be greater than zero.
1646
+ *
1647
+ * @param entry - Accepts entry to validate
1648
+ * @param fieldPath - Dot-notation path for issue reporting (e.g. "accepts[0]")
1649
+ * @returns Array of validation issues
1650
+ */
1651
+ function validateAmount(entry, fieldPath) {
1652
+ const issues = [];
1653
+ if (!entry.amount) return issues;
1654
+ if (!/^\d+$/.test(entry.amount)) {
1655
+ issues.push({
1656
+ code: ErrorCode.INVALID_AMOUNT,
1657
+ field: `${fieldPath}.amount`,
1658
+ message: ErrorMessages.INVALID_AMOUNT,
1659
+ severity: "error"
1660
+ });
1661
+ return issues;
1662
+ }
1663
+ if (entry.amount === "0") issues.push({
1664
+ code: ErrorCode.ZERO_AMOUNT,
1665
+ field: `${fieldPath}.amount`,
1666
+ message: ErrorMessages.ZERO_AMOUNT,
1667
+ severity: "error"
1668
+ });
1669
+ return issues;
1670
+ }
1671
+ /**
1672
+ * Validate maxTimeoutSeconds on a single accepts entry.
1673
+ *
1674
+ * For v2 format, missing timeout produces a warning.
1675
+ * When present, timeout must be a positive integer (RULE-10).
1676
+ *
1677
+ * @param entry - Accepts entry to validate
1678
+ * @param fieldPath - Dot-notation path for issue reporting (e.g. "accepts[0]")
1679
+ * @param detectedFormat - Detected config format
1680
+ * @returns Array of validation issues
1681
+ */
1682
+ function validateTimeout(entry, fieldPath, detectedFormat) {
1683
+ const issues = [];
1684
+ if (entry.maxTimeoutSeconds === void 0) {
1685
+ if (detectedFormat === "v2") issues.push({
1686
+ code: ErrorCode.MISSING_MAX_TIMEOUT,
1687
+ field: `${fieldPath}.maxTimeoutSeconds`,
1688
+ message: ErrorMessages.MISSING_MAX_TIMEOUT,
1689
+ severity: "warning"
1690
+ });
1691
+ return issues;
1692
+ }
1693
+ if (typeof entry.maxTimeoutSeconds !== "number") {
1694
+ issues.push({
1695
+ code: ErrorCode.INVALID_TIMEOUT,
1696
+ field: `${fieldPath}.maxTimeoutSeconds`,
1697
+ message: ErrorMessages.INVALID_TIMEOUT,
1698
+ severity: "error"
1699
+ });
1700
+ return issues;
1701
+ }
1702
+ if (!Number.isInteger(entry.maxTimeoutSeconds)) {
1703
+ issues.push({
1704
+ code: ErrorCode.INVALID_TIMEOUT,
1705
+ field: `${fieldPath}.maxTimeoutSeconds`,
1706
+ message: ErrorMessages.INVALID_TIMEOUT,
1707
+ severity: "error"
1708
+ });
1709
+ return issues;
1710
+ }
1711
+ if (entry.maxTimeoutSeconds <= 0) issues.push({
1712
+ code: ErrorCode.INVALID_TIMEOUT,
1713
+ field: `${fieldPath}.maxTimeoutSeconds`,
1714
+ message: ErrorMessages.INVALID_TIMEOUT,
1715
+ severity: "error"
1716
+ });
1717
+ return issues;
1718
+ }
1719
+
1720
+ //#endregion
1721
+ //#region src/validation/rules/legacy.ts
1722
+ /**
1723
+ * Validate for legacy format usage and produce upgrade suggestions.
1724
+ *
1725
+ * @param _config - Normalized config (reserved for future use)
1726
+ * @param detectedFormat - Detected config format
1727
+ * @param _originalInput - Original input object (reserved for future use)
1728
+ * @returns Array of validation issues (warnings)
1729
+ */
1730
+ function validateLegacy(_config, detectedFormat, _originalInput) {
1731
+ const issues = [];
1732
+ if (detectedFormat === "v1") issues.push({
1733
+ code: ErrorCode.LEGACY_FORMAT,
1734
+ field: "$",
1735
+ message: ErrorMessages.LEGACY_FORMAT,
1736
+ severity: "warning",
1737
+ fix: "Upgrade to x402 v2 -- use amount instead of maxAmountRequired, add resource object"
1738
+ });
1739
+ return issues;
1740
+ }
1741
+
1742
+ //#endregion
1743
+ //#region src/validation/rules/extensions.ts
1744
+ /**
1745
+ * Check whether a value is a non-null plain object (not an array).
1746
+ */
1747
+ function isObject(v) {
1748
+ return v !== null && typeof v === "object" && !Array.isArray(v);
1749
+ }
1750
+ /**
1751
+ * Validate `extensions.bazaar` when present.
1752
+ *
1753
+ * Checks:
1754
+ * - bazaar is an object
1755
+ * - bazaar.info exists and is an object with input (type + method) and output
1756
+ * - bazaar.schema exists and looks like a JSON Schema object
1757
+ *
1758
+ * @returns Array of warning issues (empty when bazaar is absent or valid)
1759
+ */
1760
+ function validateBazaar(config) {
1761
+ const issues = [];
1762
+ if (!config.extensions) return issues;
1763
+ const bazaar = config.extensions["bazaar"];
1764
+ if (bazaar === void 0) return issues;
1765
+ if (!isObject(bazaar)) {
1766
+ issues.push({
1767
+ code: ErrorCode.INVALID_BAZAAR_INFO,
1768
+ field: "extensions.bazaar",
1769
+ message: "extensions.bazaar must be an object",
1770
+ severity: "warning",
1771
+ fix: "Set extensions.bazaar to an object with info and schema properties"
1772
+ });
1773
+ return issues;
1774
+ }
1775
+ const info = bazaar["info"];
1776
+ if (!isObject(info)) issues.push({
1777
+ code: ErrorCode.INVALID_BAZAAR_INFO,
1778
+ field: "extensions.bazaar.info",
1779
+ message: ErrorMessages.INVALID_BAZAAR_INFO,
1780
+ severity: "warning",
1781
+ fix: "Add an info object with input and output properties describing your API"
1782
+ });
1783
+ else {
1784
+ const input = info["input"];
1785
+ if (!isObject(input) || !input["type"] || !input["method"]) issues.push({
1786
+ code: ErrorCode.INVALID_BAZAAR_INFO_INPUT,
1787
+ field: "extensions.bazaar.info.input",
1788
+ message: ErrorMessages.INVALID_BAZAAR_INFO_INPUT,
1789
+ severity: "warning",
1790
+ fix: "Add input.type (e.g. \"application/json\") and input.method (e.g. \"POST\")"
1791
+ });
1792
+ const output = info["output"];
1793
+ if (!isObject(output)) issues.push({
1794
+ code: ErrorCode.INVALID_BAZAAR_INFO,
1795
+ field: "extensions.bazaar.info.output",
1796
+ message: "extensions.bazaar.info.output must be an object",
1797
+ severity: "warning",
1798
+ fix: "Add an output object describing the API response format"
1799
+ });
1800
+ }
1801
+ const schema = bazaar["schema"];
1802
+ if (!isObject(schema) || !schema["type"] && !schema["$schema"] && !schema["properties"]) issues.push({
1803
+ code: ErrorCode.INVALID_BAZAAR_SCHEMA,
1804
+ field: "extensions.bazaar.schema",
1805
+ message: ErrorMessages.INVALID_BAZAAR_SCHEMA,
1806
+ severity: "warning",
1807
+ fix: "Add a JSON Schema object with type, $schema, or properties"
1808
+ });
1809
+ return issues;
1810
+ }
1811
+ /**
1812
+ * Validate `accepts[].outputSchema` on the raw parsed input.
1813
+ *
1814
+ * Uses the raw parsed object because AcceptsEntry strips outputSchema during normalization.
1815
+ *
1816
+ * Checks per entry with outputSchema:
1817
+ * - outputSchema is an object
1818
+ * - outputSchema.input exists with type and method
1819
+ * - outputSchema.output exists and is an object
1820
+ *
1821
+ * @returns Array of warning issues
1822
+ */
1823
+ function validateOutputSchema(parsed) {
1824
+ const issues = [];
1825
+ const accepts = parsed["accepts"];
1826
+ if (!Array.isArray(accepts)) return issues;
1827
+ for (let i = 0; i < accepts.length; i++) {
1828
+ const entry = accepts[i];
1829
+ if (!isObject(entry)) continue;
1830
+ const outputSchema = entry["outputSchema"];
1831
+ if (outputSchema === void 0) continue;
1832
+ const fieldPath = `accepts[${i}].outputSchema`;
1833
+ if (!isObject(outputSchema)) {
1834
+ issues.push({
1835
+ code: ErrorCode.INVALID_OUTPUT_SCHEMA,
1836
+ field: fieldPath,
1837
+ message: ErrorMessages.INVALID_OUTPUT_SCHEMA,
1838
+ severity: "warning",
1839
+ fix: "Set outputSchema to an object with input and output properties"
1840
+ });
1841
+ continue;
1842
+ }
1843
+ const input = outputSchema["input"];
1844
+ if (!isObject(input) || !input["type"] || !input["method"]) issues.push({
1845
+ code: ErrorCode.INVALID_OUTPUT_SCHEMA_INPUT,
1846
+ field: `${fieldPath}.input`,
1847
+ message: ErrorMessages.INVALID_OUTPUT_SCHEMA_INPUT,
1848
+ severity: "warning",
1849
+ fix: "Add input.type (e.g. \"application/json\") and input.method (e.g. \"POST\")"
1850
+ });
1851
+ const output = outputSchema["output"];
1852
+ if (!isObject(output)) issues.push({
1853
+ code: ErrorCode.INVALID_OUTPUT_SCHEMA,
1854
+ field: `${fieldPath}.output`,
1855
+ message: "accepts[i].outputSchema.output must be an object",
1856
+ severity: "warning",
1857
+ fix: "Add an output object describing the API response format"
1858
+ });
1859
+ }
1860
+ return issues;
1861
+ }
1862
+ /**
1863
+ * Emit a warning when neither `extensions.bazaar` nor any `accepts[].outputSchema` is present.
1864
+ *
1865
+ * @returns Single-element array with MISSING_INPUT_SCHEMA warning, or empty array
1866
+ */
1867
+ function validateMissingSchema(config, parsed) {
1868
+ if (config.extensions && config.extensions["bazaar"] !== void 0) return [];
1869
+ const accepts = parsed["accepts"];
1870
+ if (Array.isArray(accepts)) {
1871
+ for (const entry of accepts) if (isObject(entry) && entry["outputSchema"] !== void 0) return [];
1872
+ }
1873
+ return [{
1874
+ code: ErrorCode.MISSING_INPUT_SCHEMA,
1875
+ field: "extensions",
1876
+ message: ErrorMessages.MISSING_INPUT_SCHEMA,
1877
+ severity: "warning",
1878
+ fix: "Add extensions.bazaar with info and schema to help agents discover your API -- see https://bazaar.x402.org"
1879
+ }];
1880
+ }
1881
+
1882
+ //#endregion
1883
+ //#region src/validation/orchestrator.ts
1884
+ /**
1885
+ * Validate an x402 config through the full pipeline.
1886
+ *
1887
+ * Takes any input (JSON string or object), runs it through:
1888
+ * 1. Structure validation (parse, object check, format detection)
1889
+ * 2. Normalization to canonical v2 shape
1890
+ * 3. Version, accepts, resource validation
1891
+ * 4. Per-entry field, network, asset, amount, timeout, address validation
1892
+ * 5. Legacy format warnings
1893
+ * 6. Strict mode promotion (warnings -> errors)
1894
+ *
1895
+ * NEVER throws -- all invalid inputs produce structured error results.
1896
+ *
1897
+ * @param input - JSON string or parsed object to validate
1898
+ * @param options - Validation options (e.g. strict mode)
1899
+ * @returns Structured validation result
1900
+ */
1901
+ function validate(input, options) {
1902
+ try {
1903
+ return runPipeline(input, options);
1904
+ } catch {
1905
+ return {
1906
+ valid: false,
1907
+ version: "unknown",
1908
+ errors: [{
1909
+ code: ErrorCode.UNKNOWN_FORMAT,
1910
+ field: "$",
1911
+ message: "Unexpected validation error",
1912
+ severity: "error"
1913
+ }],
1914
+ warnings: [],
1915
+ normalized: null
1916
+ };
1917
+ }
1918
+ }
1919
+ /**
1920
+ * Internal pipeline implementation.
1921
+ * Separated from validate() so the try/catch safety net is clean.
1922
+ */
1923
+ function runPipeline(input, options) {
1924
+ const structure = validateStructure(input);
1925
+ if (structure.issues.length > 0) return {
1926
+ valid: false,
1927
+ version: structure.format || "unknown",
1928
+ errors: structure.issues,
1929
+ warnings: [],
1930
+ normalized: null
1931
+ };
1932
+ const parsed = structure.parsed;
1933
+ const format = structure.format;
1934
+ const normalized = normalize$1(parsed);
1935
+ if (normalized === null) return {
1936
+ valid: false,
1937
+ version: format,
1938
+ errors: [{
1939
+ code: ErrorCode.UNKNOWN_FORMAT,
1940
+ field: "$",
1941
+ message: ErrorMessages.UNKNOWN_FORMAT,
1942
+ severity: "error"
1943
+ }],
1944
+ warnings: [],
1945
+ normalized: null
1946
+ };
1947
+ const errors = [];
1948
+ const warnings = [];
1949
+ errors.push(...validateVersion(normalized, format));
1950
+ errors.push(...validateAccepts(normalized));
1951
+ warnings.push(...validateResource(normalized, format));
1952
+ if (Array.isArray(normalized.accepts) && normalized.accepts.length > 0) for (let i = 0; i < normalized.accepts.length; i++) {
1953
+ const entry = normalized.accepts[i];
1954
+ const fieldPath = `accepts[${i}]`;
1955
+ errors.push(...validateFields(entry, fieldPath));
1956
+ for (const issue of validateNetwork(entry, fieldPath)) if (issue.severity === "error") errors.push(issue);
1957
+ else warnings.push(issue);
1958
+ warnings.push(...validateAsset(entry, fieldPath));
1959
+ errors.push(...validateAmount(entry, fieldPath));
1960
+ for (const issue of validateTimeout(entry, fieldPath, format)) if (issue.severity === "error") errors.push(issue);
1961
+ else warnings.push(issue);
1962
+ if (entry.payTo && entry.network) for (const issue of validateAddress(entry.payTo, entry.network, `${fieldPath}.payTo`)) if (issue.severity === "error") errors.push(issue);
1963
+ else warnings.push(issue);
1964
+ }
1965
+ warnings.push(...validateLegacy(normalized, format, parsed));
1966
+ warnings.push(...validateBazaar(normalized));
1967
+ warnings.push(...validateOutputSchema(parsed));
1968
+ warnings.push(...validateMissingSchema(normalized, parsed));
1969
+ if (options?.strict === true) {
1970
+ for (const warning of warnings) errors.push({
1971
+ ...warning,
1972
+ severity: "error"
1973
+ });
1974
+ warnings.length = 0;
1975
+ }
1976
+ return {
1977
+ valid: errors.length === 0,
1978
+ version: format,
1979
+ errors,
1980
+ warnings,
1981
+ normalized
1982
+ };
1983
+ }
1984
+
1985
+ //#endregion
1986
+ //#region src/extraction/extract.ts
1987
+ /**
1988
+ * Get a header value, case-insensitive.
1989
+ * Supports both Headers objects and plain Record<string, string>.
1990
+ */
1991
+ function getHeader(headers, name) {
1992
+ if (!headers) return null;
1993
+ if (typeof headers.get === "function") return headers.get(name);
1994
+ const lower = name.toLowerCase();
1995
+ for (const key of Object.keys(headers)) if (key.toLowerCase() === lower) return headers[key];
1996
+ return null;
1997
+ }
1998
+ /**
1999
+ * Decode base64 string to UTF-8 text.
2000
+ * Works in both browser (atob) and Node (Buffer).
2001
+ */
2002
+ function decodeBase64(encoded) {
2003
+ if (typeof atob === "function") return atob(encoded);
2004
+ return Buffer.from(encoded, "base64").toString("utf-8");
2005
+ }
2006
+ /**
2007
+ * Check if a parsed object looks like it contains x402 config fields.
2008
+ */
2009
+ function hasX402Fields(obj) {
2010
+ if (!obj || typeof obj !== "object") return false;
2011
+ const rec = obj;
2012
+ return !!(rec.accepts || rec.payTo || rec.x402Version);
2013
+ }
2014
+ /**
2015
+ * Try to parse the PAYMENT-REQUIRED header value as a base64-encoded JSON config.
2016
+ */
2017
+ function tryHeaderExtraction(headers) {
2018
+ const headerValue = getHeader(headers, "payment-required");
2019
+ if (!headerValue) return null;
2020
+ try {
2021
+ const decoded = decodeBase64(headerValue);
2022
+ const parsed = JSON.parse(decoded);
2023
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return {
2024
+ config: parsed,
2025
+ source: "header"
2026
+ };
2027
+ } catch {}
2028
+ try {
2029
+ const parsed = JSON.parse(headerValue);
2030
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return {
2031
+ config: parsed,
2032
+ source: "header"
2033
+ };
2034
+ } catch {}
2035
+ return null;
2036
+ }
2037
+ /**
2038
+ * Extract an x402 config from an HTTP 402 response.
2039
+ *
2040
+ * Extraction priority:
2041
+ * 1. JSON body — if it parses and has x402 fields (accepts, payTo, x402Version)
2042
+ * 2. PAYMENT-REQUIRED header — base64-decoded JSON fallback
2043
+ *
2044
+ * Never throws. Returns structured result with error message on failure.
2045
+ *
2046
+ * @param response - Response-like object with body and/or headers
2047
+ * @returns Extraction result with config, source, and error
2048
+ */
2049
+ function extractConfig(response) {
2050
+ const body = response.body;
2051
+ if (body && typeof body === "object" && !Array.isArray(body) && hasX402Fields(body)) return {
2052
+ config: body,
2053
+ source: "body",
2054
+ error: null
2055
+ };
2056
+ if (typeof body === "string" && body.trim()) try {
2057
+ const parsed = JSON.parse(body);
2058
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && hasX402Fields(parsed)) return {
2059
+ config: parsed,
2060
+ source: "body",
2061
+ error: null
2062
+ };
2063
+ } catch {}
2064
+ const headerResult = tryHeaderExtraction(response.headers);
2065
+ if (headerResult) return {
2066
+ config: headerResult.config,
2067
+ source: headerResult.source,
2068
+ error: null
2069
+ };
2070
+ return {
2071
+ config: null,
2072
+ source: null,
2073
+ error: "No x402 config found in response body or PAYMENT-REQUIRED header"
2074
+ };
2075
+ }
2076
+
2077
+ //#endregion
2078
+ //#region src/check.ts
2079
+ /**
2080
+ * Check an HTTP 402 response: extract config, validate, and enrich with registry data.
2081
+ *
2082
+ * Never throws. All failures are represented in the returned CheckResult.
2083
+ *
2084
+ * @param response - Response-like object with body and/or headers
2085
+ * @param options - Validation options (e.g. strict mode)
2086
+ * @returns Unified check result
2087
+ */
2088
+ function check(response, options) {
2089
+ const extraction = extractConfig(response);
2090
+ if (!extraction.config) return {
2091
+ extracted: false,
2092
+ source: null,
2093
+ extractionError: extraction.error,
2094
+ valid: false,
2095
+ version: "unknown",
2096
+ errors: [],
2097
+ warnings: [],
2098
+ normalized: null,
2099
+ summary: [],
2100
+ raw: null
2101
+ };
2102
+ const validation = validate(extraction.config, options);
2103
+ const summary = [];
2104
+ const accepts = validation.normalized?.accepts ?? [];
2105
+ for (let i = 0; i < accepts.length; i++) {
2106
+ const entry = accepts[i];
2107
+ const networkInfo = getNetworkInfo(entry.network);
2108
+ const assetInfo = getAssetInfo(entry.network, entry.asset);
2109
+ summary.push({
2110
+ index: i,
2111
+ network: entry.network,
2112
+ networkName: networkInfo?.name ?? entry.network,
2113
+ networkType: networkInfo?.type ?? null,
2114
+ payTo: entry.payTo,
2115
+ amount: entry.amount,
2116
+ asset: entry.asset,
2117
+ assetSymbol: assetInfo?.symbol ?? null,
2118
+ assetDecimals: assetInfo?.decimals ?? null,
2119
+ scheme: entry.scheme
2120
+ });
2121
+ }
2122
+ return {
2123
+ extracted: true,
2124
+ source: extraction.source,
2125
+ extractionError: null,
2126
+ valid: validation.valid,
2127
+ version: validation.version,
2128
+ errors: validation.errors,
2129
+ warnings: validation.warnings,
2130
+ normalized: validation.normalized,
2131
+ summary,
2132
+ raw: extraction.config
2133
+ };
2134
+ }
2135
+
2136
+ //#endregion
2137
+ //#region src/index.ts
2138
+ const VERSION = "0.3.0";
2139
+
2140
+ //#endregion
2141
+ //#region src/cli.ts
2142
+ /**
2143
+ * x402check CLI
2144
+ *
2145
+ * Validate x402 payment configurations from the command line.
2146
+ *
2147
+ * Usage:
2148
+ * x402check <json> Validate inline JSON
2149
+ * x402check <file.json> Validate a JSON file
2150
+ * x402check <url> Fetch URL, extract + validate 402 config
2151
+ * echo '{}' | x402check Validate from stdin
2152
+ * x402check --version Print version
2153
+ * x402check --help Print help
2154
+ *
2155
+ * Flags:
2156
+ * --strict Promote all warnings to errors
2157
+ * --json Output raw JSON (for piping)
2158
+ * --quiet Only print errors (exit code only)
2159
+ */
2160
+ function parseArgs(argv) {
2161
+ const args = {
2162
+ input: null,
2163
+ strict: false,
2164
+ json: false,
2165
+ quiet: false,
2166
+ help: false,
2167
+ version: false
2168
+ };
2169
+ for (const arg of argv) if (arg === "--strict") args.strict = true;
2170
+ else if (arg === "--json") args.json = true;
2171
+ else if (arg === "--quiet" || arg === "-q") args.quiet = true;
2172
+ else if (arg === "--help" || arg === "-h") args.help = true;
2173
+ else if (arg === "--version" || arg === "-v") args.version = true;
2174
+ else if (!arg.startsWith("-")) args.input = arg;
2175
+ return args;
2176
+ }
2177
+ const HELP = `x402check v${VERSION} — validate x402 payment configurations
2178
+
2179
+ Usage:
2180
+ x402check <json> Validate inline JSON string
2181
+ x402check <file.json> Validate a JSON file
2182
+ x402check <url> Fetch URL and check 402 response
2183
+ echo '...' | x402check Validate from stdin
2184
+
2185
+ Flags:
2186
+ --strict Promote all warnings to errors
2187
+ --json Output raw JSON result
2188
+ --quiet Suppress output, exit code only
2189
+ -h, --help Show this help
2190
+ -v, --version Show version
2191
+
2192
+ Exit codes:
2193
+ 0 Valid config (or --help/--version)
2194
+ 1 Invalid config or errors found
2195
+ 2 Input error (no input, bad file, fetch failure)
2196
+
2197
+ Examples:
2198
+ x402check '{"x402Version":2,"accepts":[...]}'
2199
+ x402check config.json
2200
+ x402check https://api.example.com/resource --strict
2201
+ curl -s https://example.com | x402check --json
2202
+ `;
2203
+ function isUrl(s) {
2204
+ return /^https?:\/\//i.test(s);
2205
+ }
2206
+ function isJsonLike(s) {
2207
+ const trimmed = s.trim();
2208
+ return trimmed.startsWith("{") || trimmed.startsWith("[");
2209
+ }
2210
+ function readStdin() {
2211
+ return new Promise((resolve, reject) => {
2212
+ const chunks = [];
2213
+ const { stdin } = process;
2214
+ if (stdin.isTTY) {
2215
+ resolve("");
2216
+ return;
2217
+ }
2218
+ stdin.on("data", (chunk) => chunks.push(chunk));
2219
+ stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
2220
+ stdin.on("error", reject);
2221
+ });
2222
+ }
2223
+ async function fetchUrl(url) {
2224
+ const res = await fetch(url);
2225
+ const headers = {};
2226
+ res.headers.forEach((value, key) => {
2227
+ headers[key] = value;
2228
+ });
2229
+ let body;
2230
+ if ((res.headers.get("content-type") || "").includes("json")) body = await res.json();
2231
+ else body = await res.text();
2232
+ return {
2233
+ status: res.status,
2234
+ body,
2235
+ headers
2236
+ };
2237
+ }
2238
+ function formatIssue(issue) {
2239
+ const line = ` ${issue.severity === "error" ? "\x1B[31m✗\x1B[0m" : "\x1B[33m⚠\x1B[0m"} ${issue.code} [${issue.field}]: ${issue.message}`;
2240
+ if (issue.fix) return line + `\n ↳ ${issue.fix}`;
2241
+ return line;
2242
+ }
2243
+ function formatValidationResult(result, args) {
2244
+ if (args.json) return JSON.stringify(result, null, 2);
2245
+ if (args.quiet) return "";
2246
+ const lines = [];
2247
+ if (result.valid) lines.push(`\x1b[32m✓ Valid\x1b[0m x402 config (${result.version})`);
2248
+ else lines.push(`\x1b[31m✗ Invalid\x1b[0m x402 config (${result.version})`);
2249
+ if (result.errors.length > 0) {
2250
+ lines.push("");
2251
+ lines.push(`Errors (${result.errors.length}):`);
2252
+ for (const e of result.errors) lines.push(formatIssue(e));
2253
+ }
2254
+ if (result.warnings.length > 0) {
2255
+ lines.push("");
2256
+ lines.push(`Warnings (${result.warnings.length}):`);
2257
+ for (const w of result.warnings) lines.push(formatIssue(w));
2258
+ }
2259
+ return lines.join("\n");
2260
+ }
2261
+ function formatCheckResult(result, args) {
2262
+ if (args.json) return JSON.stringify(result, null, 2);
2263
+ if (args.quiet) return "";
2264
+ const lines = [];
2265
+ if (!result.extracted) {
2266
+ lines.push(`\x1b[31m✗ No x402 config found\x1b[0m`);
2267
+ if (result.extractionError) lines.push(` ${result.extractionError}`);
2268
+ return lines.join("\n");
2269
+ }
2270
+ lines.push(`Extracted from: ${result.source}`);
2271
+ if (result.valid) lines.push(`\x1b[32m✓ Valid\x1b[0m x402 config (${result.version})`);
2272
+ else lines.push(`\x1b[31m✗ Invalid\x1b[0m x402 config (${result.version})`);
2273
+ if (result.summary.length > 0) {
2274
+ lines.push("");
2275
+ lines.push("Payment options:");
2276
+ for (const s of result.summary) {
2277
+ const symbol = s.assetSymbol ?? s.asset;
2278
+ const net = s.networkName;
2279
+ lines.push(` [${s.index}] ${s.amount} ${symbol} on ${net} → ${s.payTo.slice(0, 10)}...`);
2280
+ }
2281
+ }
2282
+ if (result.errors.length > 0) {
2283
+ lines.push("");
2284
+ lines.push(`Errors (${result.errors.length}):`);
2285
+ for (const e of result.errors) lines.push(formatIssue(e));
2286
+ }
2287
+ if (result.warnings.length > 0) {
2288
+ lines.push("");
2289
+ lines.push(`Warnings (${result.warnings.length}):`);
2290
+ for (const w of result.warnings) lines.push(formatIssue(w));
2291
+ }
2292
+ return lines.join("\n");
2293
+ }
2294
+ async function main() {
2295
+ const args = parseArgs(process.argv.slice(2));
2296
+ if (args.version) {
2297
+ console.log(VERSION);
2298
+ return 0;
2299
+ }
2300
+ if (args.help) {
2301
+ console.log(HELP);
2302
+ return 0;
2303
+ }
2304
+ let input = args.input;
2305
+ if (!input) {
2306
+ const stdinData = await readStdin();
2307
+ if (stdinData.trim()) input = stdinData.trim();
2308
+ }
2309
+ if (!input) {
2310
+ console.error("No input provided. Run x402check --help for usage.");
2311
+ return 2;
2312
+ }
2313
+ if (isUrl(input)) try {
2314
+ const { status, body, headers } = await fetchUrl(input);
2315
+ if (status !== 402) {
2316
+ if (!args.quiet) console.log(`HTTP ${status} (expected 402)`);
2317
+ }
2318
+ const result = check({
2319
+ body,
2320
+ headers
2321
+ }, { strict: args.strict });
2322
+ const output = formatCheckResult(result, args);
2323
+ if (output) console.log(output);
2324
+ return result.valid ? 0 : 1;
2325
+ } catch (err) {
2326
+ console.error(`Fetch failed: ${err.message}`);
2327
+ return 2;
2328
+ }
2329
+ if (!isJsonLike(input)) {
2330
+ const filePath = resolve$1(input);
2331
+ if (!existsSync(filePath)) {
2332
+ console.error(`File not found: ${filePath}`);
2333
+ return 2;
2334
+ }
2335
+ try {
2336
+ input = readFileSync(filePath, "utf-8");
2337
+ } catch (err) {
2338
+ console.error(`Cannot read file: ${err.message}`);
2339
+ return 2;
2340
+ }
2341
+ }
2342
+ const result = validate(input, { strict: args.strict });
2343
+ const output = formatValidationResult(result, args);
2344
+ if (output) console.log(output);
2345
+ return result.valid ? 0 : 1;
2346
+ }
2347
+ main().then((code) => process.exit(code), (err) => {
2348
+ console.error(`Unexpected error: ${err.message}`);
2349
+ process.exit(2);
2350
+ });
2351
+
2352
+ //#endregion
2353
+ export { };