@ashlar-blue/x402-trust 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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/dist/attested-directory.d.ts +78 -0
  4. package/dist/attested-directory.d.ts.map +1 -0
  5. package/dist/attested-directory.js +163 -0
  6. package/dist/attested-directory.js.map +1 -0
  7. package/dist/badges.d.ts +131 -0
  8. package/dist/badges.d.ts.map +1 -0
  9. package/dist/badges.js +213 -0
  10. package/dist/badges.js.map +1 -0
  11. package/dist/catalog-mcp.d.ts +145 -0
  12. package/dist/catalog-mcp.d.ts.map +1 -0
  13. package/dist/catalog-mcp.js +409 -0
  14. package/dist/catalog-mcp.js.map +1 -0
  15. package/dist/census-reconcile.d.ts +69 -0
  16. package/dist/census-reconcile.d.ts.map +1 -0
  17. package/dist/census-reconcile.js +162 -0
  18. package/dist/census-reconcile.js.map +1 -0
  19. package/dist/directory-classify.d.ts +47 -0
  20. package/dist/directory-classify.d.ts.map +1 -0
  21. package/dist/directory-classify.js +54 -0
  22. package/dist/directory-classify.js.map +1 -0
  23. package/dist/discovery.d.ts +304 -0
  24. package/dist/discovery.d.ts.map +1 -0
  25. package/dist/discovery.js +926 -0
  26. package/dist/discovery.js.map +1 -0
  27. package/dist/evidence-ref.d.ts +96 -0
  28. package/dist/evidence-ref.d.ts.map +1 -0
  29. package/dist/evidence-ref.js +179 -0
  30. package/dist/evidence-ref.js.map +1 -0
  31. package/dist/facilitator-config.d.ts +83 -0
  32. package/dist/facilitator-config.d.ts.map +1 -0
  33. package/dist/facilitator-config.js +94 -0
  34. package/dist/facilitator-config.js.map +1 -0
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +18 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/manifest-sig.d.ts +131 -0
  40. package/dist/manifest-sig.d.ts.map +1 -0
  41. package/dist/manifest-sig.js +262 -0
  42. package/dist/manifest-sig.js.map +1 -0
  43. package/dist/merkle-transcript.d.ts +34 -0
  44. package/dist/merkle-transcript.d.ts.map +1 -0
  45. package/dist/merkle-transcript.js +162 -0
  46. package/dist/merkle-transcript.js.map +1 -0
  47. package/dist/open-catalog.d.ts +111 -0
  48. package/dist/open-catalog.d.ts.map +1 -0
  49. package/dist/open-catalog.js +307 -0
  50. package/dist/open-catalog.js.map +1 -0
  51. package/dist/reconcile.d.ts +150 -0
  52. package/dist/reconcile.d.ts.map +1 -0
  53. package/dist/reconcile.js +136 -0
  54. package/dist/reconcile.js.map +1 -0
  55. package/dist/trust-sdk.d.ts +127 -0
  56. package/dist/trust-sdk.d.ts.map +1 -0
  57. package/dist/trust-sdk.js +147 -0
  58. package/dist/trust-sdk.js.map +1 -0
  59. package/dist/types.d.ts +213 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +68 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +52 -0
@@ -0,0 +1,111 @@
1
+ import { resolveX402 } from './discovery.js';
2
+ /**
3
+ * Registrable domain: last two labels, EXCEPT one more label is kept
4
+ * above a shared-platform suffix (the tenant is the operator there).
5
+ * Still not a full PSL lookup; for multi-level ccTLD suffixes (co.uk)
6
+ * this under-splits, which errs toward MORE diversity enforcement.
7
+ */
8
+ export declare function registrableDomainOf(domain: string): string;
9
+ /** Domains we re-resolve every refresh; peers extend from here. */
10
+ export declare const SEED_DOMAINS: string[];
11
+ export interface MirrorEntry {
12
+ resource: string;
13
+ network: string;
14
+ scheme?: string;
15
+ asset?: string;
16
+ payTo?: string;
17
+ lastUpdated?: string;
18
+ }
19
+ export interface DiscoveredEntry {
20
+ domain: string;
21
+ via: 'dns-txt' | 'well-known';
22
+ kind: string;
23
+ baseUrl?: string;
24
+ kinds: {
25
+ scheme: string;
26
+ network: string;
27
+ }[];
28
+ resources: string[];
29
+ /** True only when the attestation claim is independently checkable. */
30
+ attestationVerifiable: boolean;
31
+ peers: string[];
32
+ lastVerified: string;
33
+ }
34
+ export interface OpenCatalogState {
35
+ mirror: MirrorEntry[];
36
+ mirrorTotalUpstream: number;
37
+ mirrorFetchedAt: string | null;
38
+ discovered: DiscoveredEntry[];
39
+ crawledAt: string | null;
40
+ }
41
+ export interface RefreshDeps {
42
+ fetchImpl?: typeof fetch;
43
+ /** Injected resolver (tests). Defaults to the real resolveX402. */
44
+ resolver?: typeof resolveX402;
45
+ statePath?: string;
46
+ now?: () => string;
47
+ seeds?: string[];
48
+ }
49
+ export declare function loadCatalog(statePath?: string): OpenCatalogState;
50
+ /** sha256 of the exact snapshot bytes being served — the re-derivation anchor. */
51
+ export declare function catalogSnapshotDigest(statePath?: string): string | null;
52
+ /**
53
+ * Mirror the CDP Bazaar's public list. Failures leave the previous
54
+ * mirror intact — a stale mirror with a timestamp beats an empty one.
55
+ */
56
+ export declare function refreshMirror(deps?: RefreshDeps): Promise<{
57
+ fetched: number;
58
+ totalUpstream: number;
59
+ }>;
60
+ /**
61
+ * Crawl discovery records from the seed set, traversing peers
62
+ * breadth-first. Every domain is resolved from scratch by the reference
63
+ * resolver, so all of the extension's bounds and refusals apply; a
64
+ * domain that fails simply doesn't appear this round (freshness is the
65
+ * point — yesterday's entry must not survive on yesterday's proof).
66
+ */
67
+ export declare function refreshDiscovery(deps?: RefreshDeps): Promise<{
68
+ resolved: number;
69
+ attempted: number;
70
+ }>;
71
+ export interface OpenCatalogQuery {
72
+ source?: 'bazaar-mirror' | 'discovery';
73
+ network?: string;
74
+ attested?: boolean;
75
+ limit?: number;
76
+ offset?: number;
77
+ }
78
+ export interface OpenCatalogRow {
79
+ source: 'bazaar-mirror' | 'discovery';
80
+ resource: string;
81
+ network: string;
82
+ scheme?: string;
83
+ attestationVerifiable?: boolean;
84
+ lastSeen: string | null;
85
+ /** How to re-derive this entry without trusting us. */
86
+ provenance: string;
87
+ }
88
+ /** Unified, provenance-tagged view over both sources. */
89
+ export declare function queryCatalog(q?: OpenCatalogQuery, statePath?: string): {
90
+ rows: OpenCatalogRow[];
91
+ pagination: {
92
+ limit: number;
93
+ offset: number;
94
+ total: number;
95
+ };
96
+ };
97
+ /** The headline numbers — the story in one object. */
98
+ export declare function catalogStats(statePath?: string): {
99
+ name: string;
100
+ independence: string;
101
+ mirrored: number;
102
+ mirrorTotalUpstream: number;
103
+ mirrorFetchedAt: string | null;
104
+ discovered: number;
105
+ attested: number;
106
+ crawledAt: string | null;
107
+ networksExcludedByBazaar: string[];
108
+ /** sha256 of the exact snapshot file being served; null before first refresh. */
109
+ snapshotDigest: string | null;
110
+ };
111
+ //# sourceMappingURL=open-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-catalog.d.ts","sourceRoot":"","sources":["../src/open-catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,307 @@
1
+ // GENERATED from src/x402/open-catalog.ts by scripts/sdk/sync-sdk.ts — do not edit here.
2
+ // The canonical module is the one FlareClaw runs in production; this is a copy so
3
+ // that what we publish and what we run cannot diverge. Edit the canonical file.
4
+ /**
5
+ * The x402 Open Catalog — a decentralized, attestation-aware x402 index.
6
+ * (Community-run; independent of the x402 Foundation and of Coinbase.
7
+ * Named for the ecosystem it serves, not the operator that runs it —
8
+ * anyone can rebuild this catalog from public data, which is the point.)
9
+ *
10
+ * The ecosystem's only "directory" today is the CDP Bazaar, and listing
11
+ * there requires settling through Coinbase's facilitator (measured
12
+ * 2026-08-06: all 14,506 resources; a resource on any other facilitator
13
+ * cannot be listed at all). That is vendor inventory, not an index.
14
+ *
15
+ * This catalog is the open counterpart, built from three provenance-
16
+ * tagged sources, every entry re-derivable by anyone:
17
+ *
18
+ * - `bazaar-mirror` — the CDP Bazaar's own public API, mirrored with
19
+ * attribution. Their catalog is public data; the spec says curated
20
+ * lists are caches that "can be regenerated by crawling". We are
21
+ * the regeneration.
22
+ * - `discovery` — domains resolved per the x402 `discovery`
23
+ * extension (#2979): DNS TXT → /.well-known/x402 → live /supported,
24
+ * peers traversed breadth-first. This is the lane the Bazaar
25
+ * structurally excludes — and the only lane that carries
26
+ * ATTESTATION data, because discovery manifests declare it and we
27
+ * verify whether the claim is independently checkable.
28
+ *
29
+ * What this deliberately is NOT: an endorsement list. Source, freshness
30
+ * and attestation status ride on every entry so a consumer can apply
31
+ * its own policy; "discovery is not endorsement" holds here exactly as
32
+ * it does in the spec.
33
+ */
34
+ import { readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
35
+ import { dirname } from 'node:path';
36
+ import { createHash } from 'node:crypto';
37
+ import { resolveX402 } from './discovery.js';
38
+ const BAZAAR_URL = 'https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources';
39
+ const DEFAULT_STATE_FILE = '.flareclaw/open-catalog.json';
40
+ /** Bounded mirror: far above today's 14.5k, far below a memory problem. */
41
+ const MIRROR_CAP = 30_000;
42
+ /** Discovery crawl bounds per refresh. */
43
+ const CRAWL_MAX_DOMAINS = 50;
44
+ const CRAWL_MAX_DEPTH = 2;
45
+ /**
46
+ * Frontier diversity by registrable domain (spec, per the meloliva14
47
+ * census): the 32-entry peers cap bounds fan-out per manifest but
48
+ * nothing per operator — four censused single-operator domains each
49
+ * hold 32+ hosts, so a crawler seeded inside such a fleet would walk a
50
+ * full frontier without ever leaving it. Bounding visits per
51
+ * registrable domain keeps the crawl measuring the network, not one
52
+ * operator's fleet.
53
+ */
54
+ const CRAWL_MAX_PER_REGISTRABLE = 4;
55
+ /**
56
+ * Shared-platform suffixes treated as PUBLIC: the name directly above
57
+ * each entry is a DIFFERENT operator deploying on the same platform.
58
+ * Without this, last-two-labels hands 202 independent vercel.app
59
+ * deployments ONE operator's crawl budget — the most diverse quarter
60
+ * of the censused network (394/1,521 hosts, meloliva14 #2979) gets the
61
+ * tightest cap, inverting the diversity rule's intent.
62
+ *
63
+ * Each entry is NOT a raw suffix — it is the DEPTH at which a new
64
+ * operator begins, and that depth differs per platform (meloliva14's
65
+ * railway catch): Cloudflare accounts sit directly under `workers.dev`
66
+ * (the worker name above the account is not a new operator), while
67
+ * Railway tenants sit under `up.railway.app` — listing `railway.app`
68
+ * would collapse 66 tenants into one `up.railway.app` bucket, the
69
+ * same merge this list exists to prevent, one level down. Cloud Run
70
+ * has the identical shape at `a.run.app` (every censused host is
71
+ * `*.a.run.app` — including the network's largest payer, so not a
72
+ * corner nobody occupies). Test for a new entry: does the label
73
+ * directly above it change hands between unrelated parties? If it
74
+ * does, the boundary is BELOW it, not at it — and RUN THE TEST AS A
75
+ * SCRIPT over census data; that is what caught the second case in a
76
+ * minute (meloliva14). The wildcard-DNS mappers (`sslip.io`,
77
+ * `nip.io`) are listed for a DIFFERENT reason: every name under them
78
+ * resolves for whoever asks and there is no account level at all, so
79
+ * the operator-boundary test does not apply — they are here so each
80
+ * name stands alone. Hardcoded rather than a PSL dependency
81
+ * (zero-dependency rule); needs occasional updating as platforms
82
+ * appear in the wild.
83
+ */
84
+ const PLATFORM_SUFFIXES = [
85
+ 'vercel.app', 'workers.dev', 'up.railway.app', 'onrender.com', 'fly.dev',
86
+ 'replit.app', 'netlify.app', 'a.run.app', 'sslip.io', 'nip.io',
87
+ ];
88
+ /**
89
+ * Registrable domain: last two labels, EXCEPT one more label is kept
90
+ * above a shared-platform suffix (the tenant is the operator there).
91
+ * Still not a full PSL lookup; for multi-level ccTLD suffixes (co.uk)
92
+ * this under-splits, which errs toward MORE diversity enforcement.
93
+ */
94
+ export function registrableDomainOf(domain) {
95
+ const clean = domain.toLowerCase().replace(/\.$/, '');
96
+ for (const suffix of PLATFORM_SUFFIXES) {
97
+ if (clean.length > suffix.length + 1 && clean.endsWith('.' + suffix)) {
98
+ const above = clean.slice(0, -(suffix.length + 1)).split('.');
99
+ return `${above[above.length - 1]}.${suffix}`;
100
+ }
101
+ }
102
+ return clean.split('.').slice(-2).join('.');
103
+ }
104
+ /** Domains we re-resolve every refresh; peers extend from here. */
105
+ export const SEED_DOMAINS = ['flareclaw.app', 'api.flareclaw.app', 'testnet.flareclaw.app'];
106
+ function emptyState() {
107
+ return { mirror: [], mirrorTotalUpstream: 0, mirrorFetchedAt: null, discovered: [], crawledAt: null };
108
+ }
109
+ // The state file is ~7MB at current mirror size; parsing it per public
110
+ // request would be a self-inflicted DoS. Cache the parsed state (and
111
+ // its sha256 — the snapshot digest consumers verify against) keyed by
112
+ // file mtime, so refreshes invalidate naturally and reads are free.
113
+ const stateCache = new Map();
114
+ export function loadCatalog(statePath = DEFAULT_STATE_FILE) {
115
+ try {
116
+ const mtimeMs = statSync(statePath).mtimeMs;
117
+ const hit = stateCache.get(statePath);
118
+ if (hit && hit.mtimeMs === mtimeMs)
119
+ return hit.state;
120
+ const raw = readFileSync(statePath);
121
+ const state = JSON.parse(raw.toString('utf-8'));
122
+ stateCache.set(statePath, {
123
+ mtimeMs,
124
+ state,
125
+ digest: `sha256:${createHash('sha256').update(raw).digest('hex')}`,
126
+ });
127
+ return state;
128
+ }
129
+ catch {
130
+ return emptyState();
131
+ }
132
+ }
133
+ /** sha256 of the exact snapshot bytes being served — the re-derivation anchor. */
134
+ export function catalogSnapshotDigest(statePath = DEFAULT_STATE_FILE) {
135
+ loadCatalog(statePath); // populate/refresh the cache entry
136
+ return stateCache.get(statePath)?.digest ?? null;
137
+ }
138
+ function saveCatalog(state, statePath) {
139
+ mkdirSync(dirname(statePath), { recursive: true });
140
+ writeFileSync(statePath, JSON.stringify(state));
141
+ stateCache.delete(statePath); // next read re-parses + re-hashes
142
+ }
143
+ /**
144
+ * Mirror the CDP Bazaar's public list. Failures leave the previous
145
+ * mirror intact — a stale mirror with a timestamp beats an empty one.
146
+ */
147
+ export async function refreshMirror(deps = {}) {
148
+ const fetchImpl = deps.fetchImpl ?? fetch;
149
+ const statePath = deps.statePath ?? DEFAULT_STATE_FILE;
150
+ const now = deps.now ?? (() => new Date().toISOString());
151
+ const state = loadCatalog(statePath);
152
+ const mirror = [];
153
+ let offset = 0;
154
+ let totalUpstream = 0;
155
+ for (;;) {
156
+ const res = await fetchImpl(`${BAZAAR_URL}?limit=100&offset=${offset}`, {
157
+ signal: AbortSignal.timeout(20_000),
158
+ });
159
+ if (!res.ok)
160
+ throw new Error(`bazaar mirror fetch ${res.status} at offset ${offset}`);
161
+ const page = (await res.json());
162
+ const items = page.items ?? [];
163
+ totalUpstream = page.pagination?.total ?? totalUpstream;
164
+ for (const it of items) {
165
+ if (typeof it.resource !== 'string')
166
+ continue;
167
+ const a = it.accepts?.[0] ?? {};
168
+ mirror.push({
169
+ resource: it.resource,
170
+ network: a.network ?? 'unknown',
171
+ ...(a.scheme ? { scheme: a.scheme } : {}),
172
+ ...(a.asset ? { asset: a.asset } : {}),
173
+ ...(a.payTo ? { payTo: a.payTo } : {}),
174
+ ...(it.lastUpdated ? { lastUpdated: it.lastUpdated } : {}),
175
+ });
176
+ if (mirror.length >= MIRROR_CAP)
177
+ break;
178
+ }
179
+ offset += items.length;
180
+ if (items.length === 0 || offset >= totalUpstream || mirror.length >= MIRROR_CAP)
181
+ break;
182
+ }
183
+ state.mirror = mirror;
184
+ state.mirrorTotalUpstream = totalUpstream;
185
+ state.mirrorFetchedAt = now();
186
+ saveCatalog(state, statePath);
187
+ return { fetched: mirror.length, totalUpstream };
188
+ }
189
+ /**
190
+ * Crawl discovery records from the seed set, traversing peers
191
+ * breadth-first. Every domain is resolved from scratch by the reference
192
+ * resolver, so all of the extension's bounds and refusals apply; a
193
+ * domain that fails simply doesn't appear this round (freshness is the
194
+ * point — yesterday's entry must not survive on yesterday's proof).
195
+ */
196
+ export async function refreshDiscovery(deps = {}) {
197
+ const statePath = deps.statePath ?? DEFAULT_STATE_FILE;
198
+ const resolver = deps.resolver ?? resolveX402;
199
+ const now = deps.now ?? (() => new Date().toISOString());
200
+ const state = loadCatalog(statePath);
201
+ const frontier = (deps.seeds ?? SEED_DOMAINS).map(domain => ({ domain, depth: 0 }));
202
+ const seen = new Set();
203
+ const perRegistrable = new Map();
204
+ const discovered = [];
205
+ let attempted = 0;
206
+ while (frontier.length > 0 && seen.size < CRAWL_MAX_DOMAINS) {
207
+ const { domain, depth } = frontier.shift();
208
+ if (seen.has(domain))
209
+ continue;
210
+ // Frontier diversity: a fleet may not crowd out the rest of the
211
+ // network. Seeds are exempt (depth 0 — the operator chose them).
212
+ const reg = registrableDomainOf(domain);
213
+ if (depth > 0 && (perRegistrable.get(reg) ?? 0) >= CRAWL_MAX_PER_REGISTRABLE)
214
+ continue;
215
+ perRegistrable.set(reg, (perRegistrable.get(reg) ?? 0) + 1);
216
+ seen.add(domain);
217
+ attempted++;
218
+ try {
219
+ const r = await resolver(domain, {
220
+ ...(deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}),
221
+ noCache: true,
222
+ });
223
+ discovered.push({
224
+ domain,
225
+ via: r.via,
226
+ kind: r.manifest.kind,
227
+ ...(r.manifest.facilitator ? { baseUrl: r.manifest.facilitator.baseUrl } : {}),
228
+ kinds: (r.liveKinds ?? r.manifest.facilitator?.kinds ?? [])
229
+ .map(k => ({ scheme: k.scheme, network: k.network })),
230
+ resources: (r.manifest.resources ?? []).map(x => x.url),
231
+ attestationVerifiable: r.attestationUsable === true,
232
+ peers: r.peers ?? [],
233
+ lastVerified: now(),
234
+ });
235
+ if (depth < CRAWL_MAX_DEPTH) {
236
+ for (const p of r.peers ?? [])
237
+ frontier.push({ domain: p, depth: depth + 1 });
238
+ }
239
+ }
240
+ catch {
241
+ // Unresolvable this round → absent this round. Negative
242
+ // caching inside the resolver already rate-limits retries.
243
+ }
244
+ }
245
+ state.discovered = discovered;
246
+ state.crawledAt = now();
247
+ saveCatalog(state, statePath);
248
+ return { resolved: discovered.length, attempted };
249
+ }
250
+ /** Unified, provenance-tagged view over both sources. */
251
+ export function queryCatalog(q = {}, statePath = DEFAULT_STATE_FILE) {
252
+ const state = loadCatalog(statePath);
253
+ let rows = [];
254
+ if (q.source !== 'bazaar-mirror') {
255
+ for (const d of state.discovered) {
256
+ const nets = d.kinds.map(k => k.network);
257
+ rows.push({
258
+ source: 'discovery',
259
+ resource: d.baseUrl ?? `https://${d.domain}`,
260
+ network: nets.join(',') || 'unknown',
261
+ ...(d.kinds[0]?.scheme ? { scheme: d.kinds[0].scheme } : {}),
262
+ attestationVerifiable: d.attestationVerifiable,
263
+ lastSeen: d.lastVerified,
264
+ provenance: `resolveX402("${d.domain}") — DNS TXT _x402.${d.domain} / https://${d.domain}/.well-known/x402`,
265
+ });
266
+ }
267
+ }
268
+ if (q.source !== 'discovery') {
269
+ for (const m of state.mirror) {
270
+ rows.push({
271
+ source: 'bazaar-mirror',
272
+ resource: m.resource,
273
+ network: m.network,
274
+ ...(m.scheme ? { scheme: m.scheme } : {}),
275
+ lastSeen: state.mirrorFetchedAt,
276
+ provenance: `${BAZAAR_URL} (public CDP Bazaar API, mirrored with attribution)`,
277
+ });
278
+ }
279
+ }
280
+ if (q.network)
281
+ rows = rows.filter(r => r.network.split(',').includes(q.network));
282
+ if (q.attested === true)
283
+ rows = rows.filter(r => r.attestationVerifiable === true);
284
+ const offset = Math.max(0, q.offset ?? 0);
285
+ const limit = Math.min(Math.max(1, q.limit ?? 50), 500);
286
+ return { rows: rows.slice(offset, offset + limit), pagination: { limit, offset, total: rows.length } };
287
+ }
288
+ /** The headline numbers — the story in one object. */
289
+ export function catalogStats(statePath = DEFAULT_STATE_FILE) {
290
+ const s = loadCatalog(statePath);
291
+ const snapshotDigest = catalogSnapshotDigest(statePath);
292
+ const bazaarNets = new Set(s.mirror.map(m => m.network));
293
+ const excluded = [...new Set(s.discovered.flatMap(d => d.kinds.map(k => k.network)).filter(n => !bazaarNets.has(n)))].sort();
294
+ return {
295
+ name: 'The x402 Open Catalog',
296
+ independence: 'community-run; independent of the x402 Foundation and of Coinbase — every entry re-derivable from public data',
297
+ mirrored: s.mirror.length,
298
+ mirrorTotalUpstream: s.mirrorTotalUpstream,
299
+ mirrorFetchedAt: s.mirrorFetchedAt,
300
+ discovered: s.discovered.length,
301
+ attested: s.discovered.filter(d => d.attestationVerifiable).length,
302
+ crawledAt: s.crawledAt,
303
+ networksExcludedByBazaar: excluded,
304
+ snapshotDigest,
305
+ };
306
+ }
307
+ //# sourceMappingURL=open-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-catalog.js","sourceRoot":"","sources":["../src/open-catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * reconcile — the determinate answer to "what happened to this
3
+ * authorization?" (Iman's cross-domain step 4, served as a query).
4
+ *
5
+ * The observation behind it: on this rail the hard half of
6
+ * reconciliation is already structural — an EIP-3009 nonce is
7
+ * consume-on-entry (`authorizationState`), and settlement is
8
+ * written-inside-the-act (the `AuthorizationUsed` event + FCUSD/FCYD
9
+ * `Transfer` in the same tx). What third parties lack is the JOIN.
10
+ * This module performs it and returns a verdict a cold party
11
+ * re-derives from public RPC alone:
12
+ *
13
+ * settled the nonce is consumed on-chain; evidence
14
+ * names the settlement tx + the transfer it
15
+ * carried (payer → payTo, amount)
16
+ * refused-not-charged the nonce is unconsumed AND the authorization
17
+ * window has closed — it can never settle
18
+ * indeterminate with a MACHINE-READABLE reason, never a
19
+ * silent shrug: rail unreachable, window still
20
+ * open (could still settle), out-of-scope asset
21
+ *
22
+ * PURITY: the decision table is pure over injected observations; all
23
+ * chain I/O lives behind the `RailReader` port (tests inject stubs,
24
+ * the facilitator wires public-RPC reads). No keys anywhere — this is
25
+ * read-only enumeration of a public rail.
26
+ */
27
+ export type ReconcileOutcome = 'settled' | 'refused-not-charged' | 'indeterminate';
28
+ export interface ReconcileQuery {
29
+ /** EIP-3009 asset contract address (FCUSD/FCYD on Coston2). */
30
+ asset: string;
31
+ /** The authorizer (`from` of the authorization). */
32
+ payer: string;
33
+ /** The 32-byte authorization nonce, 0x-hex. */
34
+ nonce: string;
35
+ /** The authorization's validBefore (unix seconds), when the caller
36
+ * knows it — required to prove refused-not-charged (an unconsumed
37
+ * nonce inside its window could still settle). */
38
+ validBefore?: number;
39
+ }
40
+ /** What the settlement use looked like on-chain, when found. */
41
+ export interface SettlementUse {
42
+ tx: string;
43
+ blockNumber: number;
44
+ /** FCUSD/FCYD transfers the settlement tx carried. */
45
+ transfers: {
46
+ from: string;
47
+ to: string;
48
+ amountAtomic: string;
49
+ }[];
50
+ }
51
+ /**
52
+ * How a consumed nonce was consumed.
53
+ *
54
+ * THE 2026-08-22 CORRECTION (Circadian-agent, x402#3226): `authorizationState`
55
+ * returns true for `used` OR `cancelled`. EIP-3009's `cancelAuthorization`
56
+ * consumes the nonce and MOVES NO TOKENS — Circle's own revert string on Base
57
+ * USDC says it plainly: "authorization is used or canceled", one state, two
58
+ * causes. So the consumed flag alone cannot distinguish "the payer paid" from
59
+ * "the payer withdrew the offer", which are opposite verdicts.
60
+ *
61
+ * The events do distinguish them, and both are indexed on (authorizer, nonce),
62
+ * so a topic filter resolves it exactly and more cheaply than scanning for a
63
+ * matching Transfer:
64
+ * AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce)
65
+ * AuthorizationCanceled(address indexed authorizer, bytes32 indexed nonce)
66
+ */
67
+ export type ConsumptionKind = 'used' | 'cancelled';
68
+ /** What the consumption looked like on-chain, when the event was found. */
69
+ export interface ConsumptionRecord {
70
+ kind: ConsumptionKind;
71
+ tx: string;
72
+ blockNumber: number;
73
+ /** Transfers the tx carried. Always empty for `cancelled` — that is the point. */
74
+ transfers: {
75
+ from: string;
76
+ to: string;
77
+ amountAtomic: string;
78
+ }[];
79
+ }
80
+ /** Chain I/O port. Implementations MUST throw on I/O failure — the
81
+ * decision table maps a throw to `indeterminate: rail unreachable`,
82
+ * never to a definite verdict. */
83
+ export interface RailReader {
84
+ /** EIP-3009 `authorizationState(payer, nonce)` — consumed (used OR cancelled)? */
85
+ authorizationConsumed(asset: string, payer: string, nonce: string): Promise<boolean>;
86
+ /**
87
+ * Resolve HOW the nonce was consumed, by topic filter on
88
+ * AuthorizationUsed / AuthorizationCanceled. Returns null only when
89
+ * neither event is locatable — a genuine reader-range limit, and the
90
+ * ONLY case for which "widen the range" is honest advice.
91
+ */
92
+ findConsumption(asset: string, payer: string, nonce: string): Promise<ConsumptionRecord | null>;
93
+ /**
94
+ * The chain height this reader is answering at.
95
+ *
96
+ * OPTIONAL ON THE PORT, ALWAYS REPORTED IN THE VERDICT. Circadian-agent
97
+ * (x402#3226, 2026-08-22) put this ahead of the clock and gave the reason
98
+ * that settles it: "a verdict without a read height cannot be replayed, so
99
+ * two people disagreeing have no way to tell a reorg from a bug from a
100
+ * different node." Reorgs are the famous case, but disagreement is the
101
+ * common one — without a height, two parties comparing verdicts cannot
102
+ * even establish that they read the same chain state.
103
+ *
104
+ * A reader that cannot supply it still gets a verdict; the verdict says
105
+ * `replayable: false` so the degradation is visible rather than assumed
106
+ * away. Absent must never be indistinguishable from present.
107
+ */
108
+ blockHeight?(): Promise<number>;
109
+ }
110
+ /**
111
+ * WHERE AND WHEN the verdict was read. Carried on every outcome, including
112
+ * failures, because the failures are exactly the verdicts people argue about.
113
+ */
114
+ export interface ReconcileObservation {
115
+ /** Chain height the rail was read at, or null when the reader cannot say. */
116
+ blockHeight: number | null;
117
+ /**
118
+ * The clock reading used, in unix seconds. The clock enters this decision
119
+ * at exactly ONE boundary (`now > validBefore`), so publishing the reading
120
+ * lets a disputing party recompute that comparison instead of trusting it.
121
+ */
122
+ clockUnixSeconds: number;
123
+ /** True when the boundary above actually decided this verdict. */
124
+ clockWasLoadBearing: boolean;
125
+ /** False when blockHeight is null — this verdict cannot be re-read at a fixed state. */
126
+ replayable: boolean;
127
+ }
128
+ export interface ReconcileVerdict {
129
+ outcome: ReconcileOutcome;
130
+ /** One sentence a human reads; the outcome is what machines branch on. */
131
+ reason: string;
132
+ /** Machine-readable cause, stable vocabulary. */
133
+ cause: 'nonce-used' | 'authorization-cancelled' | 'consumption-kind-unknown' | 'window-closed-unconsumed' | 'window-open-unconsumed' | 'window-unknown-unconsumed' | 'out-of-scope-asset' | 'invalid-query' | 'rail-unreachable';
134
+ /** Present whenever the consumption event was located, on either verdict. */
135
+ consumption?: ConsumptionRecord;
136
+ /** Where and when this was read. Always present, on every outcome. */
137
+ observation: ReconcileObservation;
138
+ /** How a cold party re-derives this verdict with no trust in us. */
139
+ rederive: string;
140
+ }
141
+ /** Assets this reconciler speaks for. Injectable so tests stay pure and
142
+ * the list can grow without touching the decision table. */
143
+ export interface ReconcileScope {
144
+ /** lowercase address → symbol for known EIP-3009 assets. */
145
+ assets: Record<string, string>;
146
+ network: string;
147
+ rpc: string;
148
+ }
149
+ export declare function reconcileAuthorization(q: ReconcileQuery, reader: RailReader, scope: ReconcileScope, now: () => number): Promise<ReconcileVerdict>;
150
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"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"}