chain-insights 0.68.2 → 0.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +1 -0
  2. package/dist/apps/claude-view.html +39 -38
  3. package/dist/cli.cjs +31 -5
  4. package/dist/cli.mjs +31 -5
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/dex-registry-05wAhSa0.mjs +383 -0
  7. package/dist/dex-registry-05wAhSa0.mjs.map +1 -0
  8. package/dist/dex-registry-KOTJhAQf.cjs +411 -0
  9. package/dist/dex-tool-DIKN4dXB.cjs +536 -0
  10. package/dist/dex-tool-DpQKS7Te.mjs +497 -0
  11. package/dist/dex-tool-DpQKS7Te.mjs.map +1 -0
  12. package/dist/{format-DB2IH2U5.mjs → format-CSesEBvS.mjs} +4 -4
  13. package/dist/format-CSesEBvS.mjs.map +1 -0
  14. package/dist/{format-BjZuYDr8.cjs → format-DfhRQq4L.cjs} +3 -3
  15. package/dist/{graph-schema-DHd2fGbr.mjs → graph-schema-C7LgLhlY.mjs} +11 -4
  16. package/dist/graph-schema-C7LgLhlY.mjs.map +1 -0
  17. package/dist/{graph-schema-C_7aZ8RL.cjs → graph-schema-DHZi7kUu.cjs} +10 -3
  18. package/dist/mcp-proxy.cjs +113 -79
  19. package/dist/mcp-proxy.d.cts +4 -0
  20. package/dist/mcp-proxy.d.cts.map +1 -1
  21. package/dist/mcp-proxy.d.mts +4 -0
  22. package/dist/mcp-proxy.d.mts.map +1 -1
  23. package/dist/mcp-proxy.mjs +110 -79
  24. package/dist/mcp-proxy.mjs.map +1 -1
  25. package/dist/{tool-visibility-B4RAFMaB.cjs → tool-visibility-BopFnGsT.cjs} +5 -0
  26. package/dist/{tool-visibility-kclgGKL5.mjs → tool-visibility-Dq-Ruj-8.mjs} +6 -1
  27. package/dist/tool-visibility-Dq-Ruj-8.mjs.map +1 -0
  28. package/docs/mcp-proxy.md +71 -0
  29. package/docs/stability.md +3 -2
  30. package/package.json +3 -2
  31. package/registry/README.md +133 -0
  32. package/registry/robinhood.json +769 -0
  33. package/registry/robinhood.md +102 -0
  34. package/skills/chain-insights-dex/SKILL.md +104 -96
  35. package/skills/chain-insights-dex/agents/openai.yaml +1 -1
  36. package/skills/chain-insights-dex/reference.md +8 -27
  37. package/skills/chain-insights-dex-gauge/SKILL.md +6 -4
  38. package/skills/chain-insights-dex-launchpad/SKILL.md +6 -3
  39. package/skills/chain-insights-dex-vault/SKILL.md +4 -2
  40. package/skills/chain-insights-virtuals/SKILL.md +8 -4
  41. package/dist/format-DB2IH2U5.mjs.map +0 -1
  42. package/dist/graph-schema-DHd2fGbr.mjs.map +0 -1
  43. package/dist/tool-visibility-kclgGKL5.mjs.map +0 -1
@@ -0,0 +1,383 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
+ import { n as chainInsightsHome } from "./cache-location-BO2XkfgI.mjs";
3
+ import { fileURLToPath } from "node:url";
4
+ import path from "node:path";
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
7
+ //#region src/mcp/dex-registry.ts
8
+ /**
9
+ * The exchange registry, as the proxy tool meta_dex_lookup reads it.
10
+ *
11
+ * The reviewed table lives in this repository, registry/<network>.md, and its
12
+ * served copy registry/<network>.json is checked in CI
13
+ * (scripts/pin-dex-registry.mjs). The proxy reads the copy three ways, in this
14
+ * order:
15
+ *
16
+ * cached ~/.chain-insights/cache/dex-registry-<network>.json, younger than one hour
17
+ * fetched the copy on main, https://raw.githubusercontent.com/chainswarm/chain-insights/main/registry/<network>.json,
18
+ * within 3 s, at most once an hour per network and never two in flight
19
+ * packaged registry/<network>.json of the installed package, when the fetch fails and no cache exists
20
+ *
21
+ * A fetched file is used only when it passes the same rules the pin script
22
+ * applies. A malformed file never replaces a good cache: the stale cache keeps
23
+ * answering and one line names the fault. So a proxy that never reaches GitHub
24
+ * behaves like the skill memo of its own version, and a row merged on main
25
+ * reaches every running proxy within the hour, with no release.
26
+ *
27
+ * matchDex turns a name into one exchange: normalise, exact on key, display and
28
+ * aliases, then within two letters of an alias of four or more characters. One
29
+ * winner is matched, a tie is listed, never guessed, and no hit names every
30
+ * covered exchange.
31
+ */
32
+ var dex_registry_exports = /* @__PURE__ */ __exportAll({
33
+ DEX_REGISTRY_FETCH_BUDGET_MS: () => DEX_REGISTRY_FETCH_BUDGET_MS,
34
+ DEX_REGISTRY_PUBLIC_URL: () => DEX_REGISTRY_PUBLIC_URL,
35
+ DEX_REGISTRY_SCHEMA: () => DEX_REGISTRY_SCHEMA,
36
+ DEX_REGISTRY_TTL_MS: () => DEX_REGISTRY_TTL_MS,
37
+ DEX_REGISTRY_URL_ENV: () => DEX_REGISTRY_URL_ENV,
38
+ DexRegistryError: () => DexRegistryError,
39
+ NEAR_DISTANCE: () => 2,
40
+ NEAR_MIN_ALIAS_LENGTH: () => 4,
41
+ ROLES: () => ROLES,
42
+ TRAIL_TYPES: () => TRAIL_TYPES,
43
+ damerauLevenshtein: () => damerauLevenshtein,
44
+ dexRegistryCachePath: () => dexRegistryCachePath,
45
+ dexRegistryUrl: () => dexRegistryUrl,
46
+ loadRegistry: () => loadRegistry,
47
+ matchDex: () => matchDex,
48
+ normaliseName: () => normaliseName,
49
+ normalizeDexNetwork: () => normalizeDexNetwork,
50
+ packagedRegistryDir: () => packagedRegistryDir,
51
+ readPackagedRegistry: () => readPackagedRegistry,
52
+ registryFault: () => registryFault
53
+ });
54
+ const DEX_REGISTRY_SCHEMA = "chain-insights.dex-registry.v1";
55
+ /** How long a cached registry stays fresh. CACHE_TTL_MS (24 h) stays the lifetime of the schema caches. */
56
+ const DEX_REGISTRY_TTL_MS = 36e5;
57
+ /** The most a fetch of the public file may take on the call path. */
58
+ const DEX_REGISTRY_FETCH_BUDGET_MS = 3e3;
59
+ const DEX_REGISTRY_URL_ENV = "CHAIN_INSIGHTS_DEX_REGISTRY_URL";
60
+ const DEX_REGISTRY_PUBLIC_URL = "https://raw.githubusercontent.com/chainswarm/chain-insights/main/registry/{network}.json";
61
+ const TRAIL_TYPES = [
62
+ "plain",
63
+ "vault",
64
+ "gauge",
65
+ "launchpad"
66
+ ];
67
+ const ROLES = [
68
+ "factory",
69
+ "manager",
70
+ "router"
71
+ ];
72
+ /** A refusal with its own words for the caller: a network the registry does not cover. */
73
+ var DexRegistryError = class extends Error {
74
+ constructor(message) {
75
+ super(message);
76
+ this.name = "DexRegistryError";
77
+ }
78
+ };
79
+ const ADDRESS_RE = /^0x[0-9a-f]{40}$/;
80
+ const POOL_RE = /^0x(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
81
+ const DEX_RE = /^[a-z0-9][a-z0-9._-]{0,31}$/;
82
+ const NETWORK_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
83
+ function isRecord(value) {
84
+ return !!value && typeof value === "object" && !Array.isArray(value);
85
+ }
86
+ const isStringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
87
+ /** The network as a lower-case identifier; it becomes part of a file name and a URL. */
88
+ function normalizeDexNetwork(raw) {
89
+ const network = typeof raw === "string" ? raw.trim().toLowerCase() : "";
90
+ if (!network) throw new DexRegistryError("invalid_network: network is required. Pass the identifier of a served network, for example robinhood.");
91
+ if (!NETWORK_RE.test(network)) throw new DexRegistryError(`invalid_network: "${String(raw)}" is not a network identifier`);
92
+ return network;
93
+ }
94
+ /**
95
+ * The fault of a registry document, or null when it is valid for the network:
96
+ * the rules of registry/README.md, the same ones the pin script applies.
97
+ */
98
+ function registryFault(value, network) {
99
+ if (!isRecord(value)) return "the file is not a JSON object";
100
+ if (value.schema !== "chain-insights.dex-registry.v1") return `schema is ${JSON.stringify(value.schema)}, not ${DEX_REGISTRY_SCHEMA}`;
101
+ if (value.network !== network) return `network is ${JSON.stringify(value.network)}, not ${network}`;
102
+ if (!Number.isInteger(value.registry_version) || value.registry_version < 0) return "registry_version is not a whole number";
103
+ if (typeof value.recorded_on !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value.recorded_on)) return "recorded_on is not a YYYY-MM-DD day";
104
+ if (!Array.isArray(value.rows) || value.rows.length === 0) return "rows is empty";
105
+ const keys = /* @__PURE__ */ new Set();
106
+ for (const [index, row] of value.rows.entries()) {
107
+ const at = `rows[${index}]`;
108
+ if (!isRecord(row)) return `${at} is not an object`;
109
+ if (typeof row.dex !== "string" || !DEX_RE.test(row.dex)) return `${at}.dex is not a key: ${String(row.dex)}`;
110
+ if (keys.has(row.dex)) return `${at}.dex ${row.dex} is listed twice`;
111
+ keys.add(row.dex);
112
+ if (typeof row.display !== "string" || row.display.trim() === "") return `${at} (${row.dex}) has no display`;
113
+ if (!isStringList(row.aliases)) return `${at} (${row.dex}) aliases is not a list of names`;
114
+ if (!TRAIL_TYPES.includes(row.trail_type)) return `${at} (${row.dex}) trail_type ${String(row.trail_type)} is not plain, vault, gauge or launchpad`;
115
+ if (!isStringList(row.adapters)) return `${at} (${row.dex}) adapters is not a list`;
116
+ if (!isRecord(row.roles)) return `${at} (${row.dex}) roles is not a map`;
117
+ for (const [address, role] of Object.entries(row.roles)) {
118
+ if (!ADDRESS_RE.test(address)) return `${at} (${row.dex}) address ${address} is not lowercase 0x plus 40 hex characters`;
119
+ if (!ROLES.includes(role)) return `${at} (${row.dex}) role ${String(role)} is not factory, manager or router`;
120
+ }
121
+ for (const field of ["factories", "routers"]) {
122
+ const list = row[field];
123
+ if (!isStringList(list)) return `${at} (${row.dex}) ${field} is not a list`;
124
+ for (const address of list) {
125
+ if (!ADDRESS_RE.test(address)) return `${at} (${row.dex}) address ${address} is not lowercase 0x plus 40 hex characters`;
126
+ if (!(address in row.roles)) return `${at} (${row.dex}) ${field} names ${address}, which roles lacks`;
127
+ }
128
+ }
129
+ if (row.factories.length === 0) return `${at} (${row.dex}) has no factory or manager`;
130
+ if (row.example_pool !== null && (typeof row.example_pool !== "string" || !POOL_RE.test(row.example_pool))) return `${at} (${row.dex}) example_pool ${String(row.example_pool)} is not a pool`;
131
+ if (row.pool_floor !== null && (!Number.isInteger(row.pool_floor) || row.pool_floor < 0)) return `${at} (${row.dex}) pool_floor is not a whole number`;
132
+ if (!Number.isInteger(row.since_block) || row.since_block < 0) return `${at} (${row.dex}) since_block is not a block`;
133
+ if (typeof row.source !== "string" || !row.source.startsWith("https://")) return `${at} (${row.dex}) source is not an https link`;
134
+ }
135
+ return null;
136
+ }
137
+ /** The URL of the served copy: the public file on main, or the override with {network} filled in. */
138
+ function dexRegistryUrl(network, env = process.env) {
139
+ const template = env["CHAIN_INSIGHTS_DEX_REGISTRY_URL"]?.trim() || "https://raw.githubusercontent.com/chainswarm/chain-insights/main/registry/{network}.json";
140
+ if (template.includes("{network}")) return template.replaceAll("{network}", network);
141
+ return template.endsWith("/") ? `${template}${network}.json` : template;
142
+ }
143
+ function dexRegistryCachePath(network) {
144
+ return path.join(chainInsightsHome(), "cache", `dex-registry-${network}.json`);
145
+ }
146
+ /** The registry/ folder of the installed package: next to dist/, or the repository root when run from source. */
147
+ function packagedRegistryDir() {
148
+ const candidates = [fileURLToPath(new URL("../registry", import.meta.url)), fileURLToPath(new URL("../../registry", import.meta.url))];
149
+ return candidates.find((candidate) => existsSync(path.join(candidate, "robinhood.json"))) ?? candidates[0];
150
+ }
151
+ /** The packaged copy of a network, or null when the package carries none. A packaged copy that fails the rules is a defect: it throws. */
152
+ function readPackagedRegistry(network, dir = packagedRegistryDir()) {
153
+ let parsed;
154
+ try {
155
+ parsed = JSON.parse(readFileSync(path.join(dir, `${network}.json`), "utf8"));
156
+ } catch {
157
+ return null;
158
+ }
159
+ const fault = registryFault(parsed, network);
160
+ if (fault) throw new Error(`the packaged exchange registry registry/${network}.json is malformed: ${fault}`);
161
+ return parsed;
162
+ }
163
+ const documentOf = (file) => ({
164
+ schema: file.schema,
165
+ network: file.network,
166
+ registry_version: file.registry_version,
167
+ recorded_on: file.recorded_on,
168
+ rows: file.rows
169
+ });
170
+ async function readCache(network) {
171
+ let parsed;
172
+ try {
173
+ parsed = JSON.parse(await readFile(dexRegistryCachePath(network), "utf8"));
174
+ } catch {
175
+ return null;
176
+ }
177
+ if (!isRecord(parsed) || registryFault(parsed, network) !== null) return null;
178
+ if (typeof parsed.checked_at_ms !== "number" || typeof parsed.cached_at_ms !== "number") return null;
179
+ if (parsed.source !== "fetched" && parsed.source !== "packaged") return null;
180
+ return parsed;
181
+ }
182
+ async function writeCache(file) {
183
+ const target = dexRegistryCachePath(file.network);
184
+ await mkdir(path.dirname(target), { recursive: true });
185
+ const scratch = `${target}.${process.pid}.tmp`;
186
+ await writeFile(scratch, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
187
+ await rename(scratch, target);
188
+ }
189
+ async function fetchRegistry(network, options) {
190
+ const url = dexRegistryUrl(network, options.env);
191
+ const fetchImpl = options.fetch ?? fetch;
192
+ let response;
193
+ try {
194
+ response = await fetchImpl(url, { signal: AbortSignal.timeout(options.budgetMs ?? 3e3) });
195
+ } catch (err) {
196
+ return { fault: `${url} unreachable: ${err instanceof Error ? err.message : String(err)}` };
197
+ }
198
+ if (response.status !== 200) return { fault: `${url} answered HTTP ${response.status}` };
199
+ let parsed;
200
+ try {
201
+ parsed = JSON.parse(await response.text());
202
+ } catch {
203
+ return { fault: `${url} is not JSON` };
204
+ }
205
+ const fault = registryFault(parsed, network);
206
+ if (fault) return { fault: `${url} is malformed: ${fault}` };
207
+ return { document: parsed };
208
+ }
209
+ const inFlight = /* @__PURE__ */ new Map();
210
+ const defaultLog = (line) => {
211
+ process.stderr.write(`[chain-insights] dex-registry: ${line}\n`);
212
+ };
213
+ /**
214
+ * The registry of one network: the fresh cache, else the public file, else the
215
+ * stale cache, else the packaged copy. A packaged copy with a higher registry
216
+ * version than the cache or the public file wins, so an upgrade is never
217
+ * answered from an older file. Never throws for a network the package covers.
218
+ */
219
+ async function loadRegistry(rawNetwork, options = {}) {
220
+ const network = normalizeDexNetwork(rawNetwork);
221
+ const now = options.now ?? Date.now;
222
+ const log = options.log ?? defaultLog;
223
+ const packaged = readPackagedRegistry(network, options.packagedDir);
224
+ const newer = (document) => packaged !== null && packaged.registry_version > document.registry_version;
225
+ const fromPackaged = () => ({
226
+ document: packaged,
227
+ source: "packaged",
228
+ loaded_at_ms: now()
229
+ });
230
+ const cache = await readCache(network);
231
+ const age = cache ? now() - cache.checked_at_ms : Infinity;
232
+ if (cache && age >= 0 && age < 36e5) {
233
+ if (newer(cache)) return fromPackaged();
234
+ return {
235
+ document: documentOf(cache),
236
+ source: cache.source === "fetched" ? "cached" : "packaged",
237
+ loaded_at_ms: now()
238
+ };
239
+ }
240
+ const running = inFlight.get(network);
241
+ if (running) return running;
242
+ const load = (async () => {
243
+ const outcome = await fetchRegistry(network, options);
244
+ const checked = now();
245
+ const save = async (file) => {
246
+ try {
247
+ await writeCache(file);
248
+ } catch (err) {
249
+ log(`the cache ${dexRegistryCachePath(network)} could not be written: ${err instanceof Error ? err.message : String(err)}`);
250
+ }
251
+ };
252
+ if ("document" in outcome) {
253
+ const { document } = outcome;
254
+ await save({
255
+ ...document,
256
+ cached_at_ms: checked,
257
+ checked_at_ms: checked,
258
+ source: "fetched"
259
+ });
260
+ if (newer(document)) return fromPackaged();
261
+ return {
262
+ document,
263
+ source: "fetched",
264
+ loaded_at_ms: checked
265
+ };
266
+ }
267
+ log(outcome.fault);
268
+ if (cache) {
269
+ await save({
270
+ ...cache,
271
+ checked_at_ms: checked
272
+ });
273
+ if (newer(cache)) return fromPackaged();
274
+ return {
275
+ document: documentOf(cache),
276
+ source: cache.source === "fetched" ? "cached" : "packaged",
277
+ loaded_at_ms: checked
278
+ };
279
+ }
280
+ if (!packaged) throw new DexRegistryError(`network_not_covered: the exchange registry covers no network "${network}", and ${outcome.fault}`);
281
+ await save({
282
+ ...packaged,
283
+ cached_at_ms: checked,
284
+ checked_at_ms: checked,
285
+ source: "packaged"
286
+ });
287
+ return fromPackaged();
288
+ })();
289
+ inFlight.set(network, load);
290
+ try {
291
+ return await load;
292
+ } finally {
293
+ inFlight.delete(network);
294
+ }
295
+ }
296
+ /** Lower case, with every space, ".", "-" and "_" removed: "Uni v4" and "uni-v4" are "univ4". */
297
+ function normaliseName(name) {
298
+ return name.toLowerCase().replace(/[\s.\-_]+/g, "");
299
+ }
300
+ /** The optimal string alignment distance: insertions, deletions, substitutions and swaps of two neighbours. */
301
+ function damerauLevenshtein(a, b) {
302
+ const rows = a.length + 1;
303
+ const cols = b.length + 1;
304
+ const d = Array.from({ length: rows }, (_, i) => Array.from({ length: cols }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
305
+ for (let i = 1; i < rows; i += 1) for (let j = 1; j < cols; j += 1) {
306
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
307
+ let best = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
308
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) best = Math.min(best, d[i - 2][j - 2] + 1);
309
+ d[i][j] = best;
310
+ }
311
+ return d[a.length][b.length];
312
+ }
313
+ /**
314
+ * One name against the registry rows:
315
+ * one exact hit matched
316
+ * several exact hits candidates at distance 0, matched null
317
+ * no exact hit, one nearest exchange matched, took_for the name
318
+ * no exact hit, a tie at the nearest candidates, matched null
319
+ * nothing within two letters matched null, no candidates
320
+ */
321
+ function matchDex(name, rows) {
322
+ const covered = rows.map((row) => row.display);
323
+ const wanted = normaliseName(name);
324
+ if (!wanted) return {
325
+ matched: null,
326
+ candidates: [],
327
+ covered
328
+ };
329
+ const exact = rows.filter((row) => [
330
+ row.dex,
331
+ row.display,
332
+ ...row.aliases
333
+ ].some((written) => normaliseName(written) === wanted));
334
+ if (exact.length === 1) return {
335
+ matched: exact[0],
336
+ candidates: [],
337
+ covered
338
+ };
339
+ if (exact.length > 1) return {
340
+ matched: null,
341
+ candidates: exact.map((row) => ({
342
+ dex: row.dex,
343
+ display: row.display,
344
+ distance: 0
345
+ })),
346
+ covered
347
+ };
348
+ const near = [];
349
+ for (const row of rows) {
350
+ const distances = row.aliases.map(normaliseName).filter((alias) => alias.length >= 4).map((alias) => damerauLevenshtein(wanted, alias));
351
+ const distance = Math.min(...distances);
352
+ if (distance <= 2) near.push({
353
+ row,
354
+ distance
355
+ });
356
+ }
357
+ if (near.length === 0) return {
358
+ matched: null,
359
+ candidates: [],
360
+ covered
361
+ };
362
+ const least = Math.min(...near.map((hit) => hit.distance));
363
+ const nearest = near.filter((hit) => hit.distance === least);
364
+ if (nearest.length === 1) return {
365
+ matched: nearest[0].row,
366
+ took_for: name.trim(),
367
+ candidates: [],
368
+ covered
369
+ };
370
+ return {
371
+ matched: null,
372
+ candidates: nearest.map(({ row, distance }) => ({
373
+ dex: row.dex,
374
+ display: row.display,
375
+ distance
376
+ })),
377
+ covered
378
+ };
379
+ }
380
+ //#endregion
381
+ export { normalizeDexNetwork as a, matchDex as i, dex_registry_exports as n, loadRegistry as r, DexRegistryError as t };
382
+
383
+ //# sourceMappingURL=dex-registry-05wAhSa0.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dex-registry-05wAhSa0.mjs","names":[],"sources":["../src/mcp/dex-registry.ts"],"sourcesContent":["/**\n * The exchange registry, as the proxy tool meta_dex_lookup reads it.\n *\n * The reviewed table lives in this repository, registry/<network>.md, and its\n * served copy registry/<network>.json is checked in CI\n * (scripts/pin-dex-registry.mjs). The proxy reads the copy three ways, in this\n * order:\n *\n * cached ~/.chain-insights/cache/dex-registry-<network>.json, younger than one hour\n * fetched the copy on main, https://raw.githubusercontent.com/chainswarm/chain-insights/main/registry/<network>.json,\n * within 3 s, at most once an hour per network and never two in flight\n * packaged registry/<network>.json of the installed package, when the fetch fails and no cache exists\n *\n * A fetched file is used only when it passes the same rules the pin script\n * applies. A malformed file never replaces a good cache: the stale cache keeps\n * answering and one line names the fault. So a proxy that never reaches GitHub\n * behaves like the skill memo of its own version, and a row merged on main\n * reaches every running proxy within the hour, with no release.\n *\n * matchDex turns a name into one exchange: normalise, exact on key, display and\n * aliases, then within two letters of an alias of four or more characters. One\n * winner is matched, a tie is listed, never guessed, and no hit names every\n * covered exchange.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { chainInsightsHome } from './cache-location.js'\n\nexport const DEX_REGISTRY_SCHEMA = 'chain-insights.dex-registry.v1' as const\n/** How long a cached registry stays fresh. CACHE_TTL_MS (24 h) stays the lifetime of the schema caches. */\nexport const DEX_REGISTRY_TTL_MS = 60 * 60 * 1000\n/** The most a fetch of the public file may take on the call path. */\nexport const DEX_REGISTRY_FETCH_BUDGET_MS = 3_000\nexport const DEX_REGISTRY_URL_ENV = 'CHAIN_INSIGHTS_DEX_REGISTRY_URL'\nexport const DEX_REGISTRY_PUBLIC_URL =\n 'https://raw.githubusercontent.com/chainswarm/chain-insights/main/registry/{network}.json'\n\nexport const TRAIL_TYPES = ['plain', 'vault', 'gauge', 'launchpad'] as const\nexport type TrailType = (typeof TRAIL_TYPES)[number]\nexport const ROLES = ['factory', 'manager', 'router'] as const\nexport type Role = (typeof ROLES)[number]\nexport type RegistrySource = 'fetched' | 'cached' | 'packaged'\n\n/** One exchange of the served copy. */\nexport interface DexEntry {\n dex: string\n display: string\n aliases: string[]\n trail_type: TrailType\n adapters: string[]\n roles: Record<string, Role>\n /** Its factory addresses, or the one manager of a vault. */\n factories: string[]\n /** Its routers, or the manager of a vault: a vault forwards its own swaps. */\n routers: string[]\n example_pool: string | null\n pool_floor: number | null\n since_block: number\n source: string\n}\n\nexport interface DexRegistryDocument {\n schema: typeof DEX_REGISTRY_SCHEMA\n network: string\n registry_version: number\n recorded_on: string\n rows: DexEntry[]\n}\n\nexport interface LoadedRegistry {\n document: DexRegistryDocument\n source: RegistrySource\n loaded_at_ms: number\n}\n\n/** A refusal with its own words for the caller: a network the registry does not cover. */\nexport class DexRegistryError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'DexRegistryError'\n }\n}\n\nconst ADDRESS_RE = /^0x[0-9a-f]{40}$/\n// An example pool is a pool address, or the pool id of a manager that keeps its pools by id.\nconst POOL_RE = /^0x(?:[0-9a-f]{40}|[0-9a-f]{64})$/\nconst DEX_RE = /^[a-z0-9][a-z0-9._-]{0,31}$/\nconst NETWORK_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nconst isStringList = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every((entry) => typeof entry === 'string')\n\n/** The network as a lower-case identifier; it becomes part of a file name and a URL. */\nexport function normalizeDexNetwork(raw: unknown): string {\n const network = typeof raw === 'string' ? raw.trim().toLowerCase() : ''\n if (!network) {\n throw new DexRegistryError(\n 'invalid_network: network is required. Pass the identifier of a served network, for example robinhood.'\n )\n }\n if (!NETWORK_RE.test(network)) {\n throw new DexRegistryError(`invalid_network: \"${String(raw)}\" is not a network identifier`)\n }\n return network\n}\n\n/**\n * The fault of a registry document, or null when it is valid for the network:\n * the rules of registry/README.md, the same ones the pin script applies.\n */\nexport function registryFault(value: unknown, network: string): string | null {\n if (!isRecord(value)) return 'the file is not a JSON object'\n if (value.schema !== DEX_REGISTRY_SCHEMA) {\n return `schema is ${JSON.stringify(value.schema)}, not ${DEX_REGISTRY_SCHEMA}`\n }\n if (value.network !== network)\n return `network is ${JSON.stringify(value.network)}, not ${network}`\n if (!Number.isInteger(value.registry_version) || (value.registry_version as number) < 0) {\n return 'registry_version is not a whole number'\n }\n if (typeof value.recorded_on !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value.recorded_on)) {\n return 'recorded_on is not a YYYY-MM-DD day'\n }\n if (!Array.isArray(value.rows) || value.rows.length === 0) return 'rows is empty'\n const keys = new Set<string>()\n for (const [index, row] of value.rows.entries()) {\n const at = `rows[${index}]`\n if (!isRecord(row)) return `${at} is not an object`\n if (typeof row.dex !== 'string' || !DEX_RE.test(row.dex))\n return `${at}.dex is not a key: ${String(row.dex)}`\n if (keys.has(row.dex)) return `${at}.dex ${row.dex} is listed twice`\n keys.add(row.dex)\n if (typeof row.display !== 'string' || row.display.trim() === '')\n return `${at} (${row.dex}) has no display`\n if (!isStringList(row.aliases)) return `${at} (${row.dex}) aliases is not a list of names`\n if (!TRAIL_TYPES.includes(row.trail_type as TrailType)) {\n return `${at} (${row.dex}) trail_type ${String(row.trail_type)} is not plain, vault, gauge or launchpad`\n }\n if (!isStringList(row.adapters)) return `${at} (${row.dex}) adapters is not a list`\n if (!isRecord(row.roles)) return `${at} (${row.dex}) roles is not a map`\n for (const [address, role] of Object.entries(row.roles)) {\n if (!ADDRESS_RE.test(address))\n return `${at} (${row.dex}) address ${address} is not lowercase 0x plus 40 hex characters`\n if (!ROLES.includes(role as Role))\n return `${at} (${row.dex}) role ${String(role)} is not factory, manager or router`\n }\n for (const field of ['factories', 'routers'] as const) {\n const list = row[field]\n if (!isStringList(list)) return `${at} (${row.dex}) ${field} is not a list`\n for (const address of list) {\n if (!ADDRESS_RE.test(address))\n return `${at} (${row.dex}) address ${address} is not lowercase 0x plus 40 hex characters`\n if (!(address in row.roles))\n return `${at} (${row.dex}) ${field} names ${address}, which roles lacks`\n }\n }\n if ((row.factories as string[]).length === 0)\n return `${at} (${row.dex}) has no factory or manager`\n if (\n row.example_pool !== null &&\n (typeof row.example_pool !== 'string' || !POOL_RE.test(row.example_pool))\n ) {\n return `${at} (${row.dex}) example_pool ${String(row.example_pool)} is not a pool`\n }\n if (\n row.pool_floor !== null &&\n (!Number.isInteger(row.pool_floor) || (row.pool_floor as number) < 0)\n ) {\n return `${at} (${row.dex}) pool_floor is not a whole number`\n }\n if (!Number.isInteger(row.since_block) || (row.since_block as number) < 0) {\n return `${at} (${row.dex}) since_block is not a block`\n }\n if (typeof row.source !== 'string' || !row.source.startsWith('https://')) {\n return `${at} (${row.dex}) source is not an https link`\n }\n }\n return null\n}\n\n/** The URL of the served copy: the public file on main, or the override with {network} filled in. */\nexport function dexRegistryUrl(network: string, env: NodeJS.ProcessEnv = process.env): string {\n const override = env[DEX_REGISTRY_URL_ENV]?.trim()\n const template = override || DEX_REGISTRY_PUBLIC_URL\n if (template.includes('{network}')) return template.replaceAll('{network}', network)\n return template.endsWith('/') ? `${template}${network}.json` : template\n}\n\nexport function dexRegistryCachePath(network: string): string {\n return path.join(chainInsightsHome(), 'cache', `dex-registry-${network}.json`)\n}\n\n/** The registry/ folder of the installed package: next to dist/, or the repository root when run from source. */\nexport function packagedRegistryDir(): string {\n const candidates = [\n fileURLToPath(new URL('../registry', import.meta.url)),\n fileURLToPath(new URL('../../registry', import.meta.url)),\n ]\n return (\n candidates.find((candidate) => existsSync(path.join(candidate, 'robinhood.json'))) ??\n candidates[0]!\n )\n}\n\n/** The packaged copy of a network, or null when the package carries none. A packaged copy that fails the rules is a defect: it throws. */\nexport function readPackagedRegistry(\n network: string,\n dir: string = packagedRegistryDir()\n): DexRegistryDocument | null {\n let parsed: unknown\n try {\n parsed = JSON.parse(readFileSync(path.join(dir, `${network}.json`), 'utf8'))\n } catch {\n return null\n }\n const fault = registryFault(parsed, network)\n if (fault)\n throw new Error(\n `the packaged exchange registry registry/${network}.json is malformed: ${fault}`\n )\n return parsed as DexRegistryDocument\n}\n\ninterface CacheFile {\n schema: typeof DEX_REGISTRY_SCHEMA\n network: string\n registry_version: number\n recorded_on: string\n rows: DexEntry[]\n /** When the copy was read: the fetch, or the first start with no fetch. */\n cached_at_ms: number\n /** When a fetch was last tried. The cache is fresh for an hour after it. */\n checked_at_ms: number\n source: 'fetched' | 'packaged'\n}\n\nconst documentOf = (file: CacheFile): DexRegistryDocument => ({\n schema: file.schema,\n network: file.network,\n registry_version: file.registry_version,\n recorded_on: file.recorded_on,\n rows: file.rows,\n})\n\nasync function readCache(network: string): Promise<CacheFile | null> {\n let parsed: unknown\n try {\n parsed = JSON.parse(await readFile(dexRegistryCachePath(network), 'utf8'))\n } catch {\n return null\n }\n if (!isRecord(parsed) || registryFault(parsed, network) !== null) return null\n if (typeof parsed.checked_at_ms !== 'number' || typeof parsed.cached_at_ms !== 'number')\n return null\n if (parsed.source !== 'fetched' && parsed.source !== 'packaged') return null\n return parsed as unknown as CacheFile\n}\n\nasync function writeCache(file: CacheFile): Promise<void> {\n const target = dexRegistryCachePath(file.network)\n await mkdir(path.dirname(target), { recursive: true })\n // Written beside the target and renamed, so a second proxy never reads half of it.\n const scratch = `${target}.${process.pid}.tmp`\n await writeFile(scratch, JSON.stringify(file, null, 2) + '\\n', { mode: 0o600 })\n await rename(scratch, target)\n}\n\nexport interface LoadRegistryOptions {\n fetch?: typeof fetch\n now?: () => number\n env?: NodeJS.ProcessEnv\n /** One line per fault: a failed fetch, a malformed file, a cache that cannot be written. */\n log?: (line: string) => void\n packagedDir?: string\n budgetMs?: number\n}\n\ntype FetchOutcome = { document: DexRegistryDocument } | { fault: string }\n\nasync function fetchRegistry(network: string, options: LoadRegistryOptions): Promise<FetchOutcome> {\n const url = dexRegistryUrl(network, options.env)\n const fetchImpl = options.fetch ?? fetch\n let response: Response\n try {\n response = await fetchImpl(url, {\n signal: AbortSignal.timeout(options.budgetMs ?? DEX_REGISTRY_FETCH_BUDGET_MS),\n })\n } catch (err) {\n return { fault: `${url} unreachable: ${err instanceof Error ? err.message : String(err)}` }\n }\n if (response.status !== 200) return { fault: `${url} answered HTTP ${response.status}` }\n let parsed: unknown\n try {\n parsed = JSON.parse(await response.text())\n } catch {\n return { fault: `${url} is not JSON` }\n }\n const fault = registryFault(parsed, network)\n if (fault) return { fault: `${url} is malformed: ${fault}` }\n return { document: parsed as DexRegistryDocument }\n}\n\n// Fetches in flight, by network: two calls at once share one fetch.\nconst inFlight = new Map<string, Promise<LoadedRegistry>>()\n\nconst defaultLog = (line: string): void => {\n process.stderr.write(`[chain-insights] dex-registry: ${line}\\n`)\n}\n\n/**\n * The registry of one network: the fresh cache, else the public file, else the\n * stale cache, else the packaged copy. A packaged copy with a higher registry\n * version than the cache or the public file wins, so an upgrade is never\n * answered from an older file. Never throws for a network the package covers.\n */\nexport async function loadRegistry(\n rawNetwork: unknown,\n options: LoadRegistryOptions = {}\n): Promise<LoadedRegistry> {\n const network = normalizeDexNetwork(rawNetwork)\n const now = options.now ?? Date.now\n const log = options.log ?? defaultLog\n const packaged = readPackagedRegistry(network, options.packagedDir)\n const newer = (document: DexRegistryDocument): boolean =>\n packaged !== null && packaged.registry_version > document.registry_version\n const fromPackaged = (): LoadedRegistry => ({\n document: packaged!,\n source: 'packaged',\n loaded_at_ms: now(),\n })\n\n const cache = await readCache(network)\n const age = cache ? now() - cache.checked_at_ms : Infinity\n if (cache && age >= 0 && age < DEX_REGISTRY_TTL_MS) {\n if (newer(cache)) return fromPackaged()\n return {\n document: documentOf(cache),\n source: cache.source === 'fetched' ? 'cached' : 'packaged',\n loaded_at_ms: now(),\n }\n }\n\n const running = inFlight.get(network)\n if (running) return running\n const load = (async (): Promise<LoadedRegistry> => {\n const outcome = await fetchRegistry(network, options)\n const checked = now()\n const save = async (file: CacheFile): Promise<void> => {\n try {\n await writeCache(file)\n } catch (err) {\n log(\n `the cache ${dexRegistryCachePath(network)} could not be written: ${err instanceof Error ? err.message : String(err)}`\n )\n }\n }\n if ('document' in outcome) {\n const { document } = outcome\n await save({ ...document, cached_at_ms: checked, checked_at_ms: checked, source: 'fetched' })\n if (newer(document)) return fromPackaged()\n return { document, source: 'fetched', loaded_at_ms: checked }\n }\n log(outcome.fault)\n if (cache) {\n // The stale cache keeps answering, and the next fetch waits an hour.\n await save({ ...cache, checked_at_ms: checked })\n if (newer(cache)) return fromPackaged()\n return {\n document: documentOf(cache),\n source: cache.source === 'fetched' ? 'cached' : 'packaged',\n loaded_at_ms: checked,\n }\n }\n if (!packaged) {\n throw new DexRegistryError(\n `network_not_covered: the exchange registry covers no network \"${network}\", and ${outcome.fault}`\n )\n }\n await save({ ...packaged, cached_at_ms: checked, checked_at_ms: checked, source: 'packaged' })\n return fromPackaged()\n })()\n inFlight.set(network, load)\n try {\n return await load\n } finally {\n inFlight.delete(network)\n }\n}\n\n// ─── The name match ───────────────────────────────────────────────────────────\n\n/** Lower case, with every space, \".\", \"-\" and \"_\" removed: \"Uni v4\" and \"uni-v4\" are \"univ4\". */\nexport function normaliseName(name: string): string {\n return name.toLowerCase().replace(/[\\s.\\-_]+/g, '')\n}\n\n/** The optimal string alignment distance: insertions, deletions, substitutions and swaps of two neighbours. */\nexport function damerauLevenshtein(a: string, b: string): number {\n const rows = a.length + 1\n const cols = b.length + 1\n const d: number[][] = Array.from({ length: rows }, (_, i) =>\n Array.from({ length: cols }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))\n )\n for (let i = 1; i < rows; i += 1) {\n for (let j = 1; j < cols; j += 1) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1\n let best = Math.min(d[i - 1]![j]! + 1, d[i]![j - 1]! + 1, d[i - 1]![j - 1]! + cost)\n if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {\n best = Math.min(best, d[i - 2]![j - 2]! + 1)\n }\n d[i]![j] = best\n }\n }\n return d[a.length]![b.length]!\n}\n\n/** The most letters a near hit may differ by, and the shortest alias a near hit is taken from. */\nexport const NEAR_DISTANCE = 2\nexport const NEAR_MIN_ALIAS_LENGTH = 4\n\nexport interface DexCandidate {\n dex: string\n display: string\n distance: number\n}\n\nexport interface DexMatchResult {\n matched: DexEntry | null\n /** The name as typed, when the match is a near hit. */\n took_for?: string\n candidates: DexCandidate[]\n /** Every covered exchange, by display name. */\n covered: string[]\n}\n\n/**\n * One name against the registry rows:\n * one exact hit matched\n * several exact hits candidates at distance 0, matched null\n * no exact hit, one nearest exchange matched, took_for the name\n * no exact hit, a tie at the nearest candidates, matched null\n * nothing within two letters matched null, no candidates\n */\nexport function matchDex(name: string, rows: DexEntry[]): DexMatchResult {\n const covered = rows.map((row) => row.display)\n const wanted = normaliseName(name)\n if (!wanted) return { matched: null, candidates: [], covered }\n const exact = rows.filter((row) =>\n [row.dex, row.display, ...row.aliases].some((written) => normaliseName(written) === wanted)\n )\n if (exact.length === 1) return { matched: exact[0]!, candidates: [], covered }\n if (exact.length > 1) {\n return {\n matched: null,\n candidates: exact.map((row) => ({ dex: row.dex, display: row.display, distance: 0 })),\n covered,\n }\n }\n const near: Array<{ row: DexEntry; distance: number }> = []\n for (const row of rows) {\n const distances = row.aliases\n .map(normaliseName)\n .filter((alias) => alias.length >= NEAR_MIN_ALIAS_LENGTH)\n .map((alias) => damerauLevenshtein(wanted, alias))\n const distance = Math.min(...distances)\n if (distance <= NEAR_DISTANCE) near.push({ row, distance })\n }\n if (near.length === 0) return { matched: null, candidates: [], covered }\n const least = Math.min(...near.map((hit) => hit.distance))\n const nearest = near.filter((hit) => hit.distance === least)\n if (nearest.length === 1)\n return { matched: nearest[0]!.row, took_for: name.trim(), candidates: [], covered }\n return {\n matched: null,\n candidates: nearest.map(({ row, distance }) => ({\n dex: row.dex,\n display: row.display,\n distance,\n })),\n covered,\n }\n}\n"],"mappings":"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"}