chain-insights 0.68.2 → 0.69.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 (38) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.cjs +30 -4
  3. package/dist/cli.mjs +30 -4
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/dex-registry-05wAhSa0.mjs +383 -0
  6. package/dist/dex-registry-05wAhSa0.mjs.map +1 -0
  7. package/dist/dex-registry-KOTJhAQf.cjs +411 -0
  8. package/dist/dex-tool-DIKN4dXB.cjs +536 -0
  9. package/dist/dex-tool-DpQKS7Te.mjs +497 -0
  10. package/dist/dex-tool-DpQKS7Te.mjs.map +1 -0
  11. package/dist/{format-DB2IH2U5.mjs → format-CSesEBvS.mjs} +4 -4
  12. package/dist/format-CSesEBvS.mjs.map +1 -0
  13. package/dist/{format-BjZuYDr8.cjs → format-DfhRQq4L.cjs} +3 -3
  14. package/dist/mcp-proxy.cjs +53 -59
  15. package/dist/mcp-proxy.d.cts +2 -0
  16. package/dist/mcp-proxy.d.cts.map +1 -1
  17. package/dist/mcp-proxy.d.mts +2 -0
  18. package/dist/mcp-proxy.d.mts.map +1 -1
  19. package/dist/mcp-proxy.mjs +51 -59
  20. package/dist/mcp-proxy.mjs.map +1 -1
  21. package/dist/{tool-visibility-B4RAFMaB.cjs → tool-visibility-BopFnGsT.cjs} +5 -0
  22. package/dist/{tool-visibility-kclgGKL5.mjs → tool-visibility-Dq-Ruj-8.mjs} +6 -1
  23. package/dist/tool-visibility-Dq-Ruj-8.mjs.map +1 -0
  24. package/docs/mcp-proxy.md +71 -0
  25. package/docs/stability.md +3 -2
  26. package/package.json +3 -2
  27. package/registry/README.md +133 -0
  28. package/registry/robinhood.json +769 -0
  29. package/registry/robinhood.md +102 -0
  30. package/skills/chain-insights-dex/SKILL.md +104 -96
  31. package/skills/chain-insights-dex/agents/openai.yaml +1 -1
  32. package/skills/chain-insights-dex/reference.md +8 -27
  33. package/skills/chain-insights-dex-gauge/SKILL.md +6 -4
  34. package/skills/chain-insights-dex-launchpad/SKILL.md +6 -3
  35. package/skills/chain-insights-dex-vault/SKILL.md +4 -2
  36. package/skills/chain-insights-virtuals/SKILL.md +8 -4
  37. package/dist/format-DB2IH2U5.mjs.map +0 -1
  38. package/dist/tool-visibility-kclgGKL5.mjs.map +0 -1
@@ -47,6 +47,11 @@ const PUBLIC_MCP_TOOL_ALLOWED_ARGS = {
47
47
  "queries",
48
48
  "per_query_timeout_seconds",
49
49
  "time_scope"
50
+ ],
51
+ meta_dex_lookup: [
52
+ "network",
53
+ "name",
54
+ "pools"
50
55
  ]
51
56
  };
52
57
  function isHiddenRemoteToolName(name) {
@@ -47,6 +47,11 @@ const PUBLIC_MCP_TOOL_ALLOWED_ARGS = {
47
47
  "queries",
48
48
  "per_query_timeout_seconds",
49
49
  "time_scope"
50
+ ],
51
+ meta_dex_lookup: [
52
+ "network",
53
+ "name",
54
+ "pools"
50
55
  ]
51
56
  };
52
57
  function isHiddenRemoteToolName(name) {
@@ -86,4 +91,4 @@ function validatePublicMcpToolArguments(name, args) {
86
91
  //#endregion
87
92
  export { tool_visibility_exports as a, isAppOnlyTool as i, PUBLIC_MCP_TOOL_ALLOWED_ARGS as n, PUBLIC_MCP_TOOL_REQUIRED_ARGS as r, HIDDEN_REMOTE_TOOL_NAMES as t };
88
93
 
89
- //# sourceMappingURL=tool-visibility-kclgGKL5.mjs.map
94
+ //# sourceMappingURL=tool-visibility-Dq-Ruj-8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-visibility-Dq-Ruj-8.mjs","names":[],"sources":["../src/mcp/tool-visibility.ts"],"sourcesContent":["import { McpToolError, formatMcpToolError } from './print-result.js'\n\nexport const HIDDEN_REMOTE_TOOL_NAMES = new Set([\n 'topup',\n 'address_risk',\n // Hidden until its verdict is fixed: never listed, never registered, never\n // called through the proxy. The remote graph endpoint does not serve it.\n 'aml_address_risk',\n 'trace_victim_funds',\n 'trace_suspect_funds',\n 'trace_deposit_sources',\n 'trace_funds',\n 'track_funds',\n // Retired tools: never exposed publicly. Built by concatenation so the\n // retired names do not appear as literals in this file.\n ['aml_trace_victim', '_funds'].join(''),\n ['aml_trace_suspect', '_funds'].join(''),\n ['aml_trace_deposit', '_sources'].join(''),\n 'network_capabilities',\n 'usage_status',\n 'subscription_status',\n 'balance',\n 'help',\n ['money', '_flows_between_exchanges'].join(''),\n 'address_connection_risk',\n])\n\nexport const PUBLIC_MCP_TOOL_REQUIRED_ARGS: Record<string, string[]> = {\n graph_query: ['query', 'network'],\n graph_query_batch: ['network', 'queries'],\n}\n\n// normalizeRemoteToolArguments FILTERS pass-through arguments to this list, so\n// an argument that is missing here is silently STRIPPED and the caller gets a\n// default-bounded result with no error and no warning. `time_scope` shipped\n// broken for exactly this reason. Every tunable search bound below\n// (per_address_limit, row_limit) must therefore be listed the moment it\n// appears on a tool schema, and is covered end-to-end through the proxy in\n// tests/mcp-proxy.test.ts.\nexport const PUBLIC_MCP_TOOL_ALLOWED_ARGS: Record<string, string[]> = {\n // `time_scope` narrows a `USE topology` query to a temporal-shard subset\n graph_query: ['query', 'network', 'time_scope'],\n graph_query_batch: ['network', 'queries', 'per_query_timeout_seconds', 'time_scope'],\n // A local tool of the proxy (src/mcp/dex-tool.ts): the exchange by name.\n meta_dex_lookup: ['network', 'name', 'pools'],\n}\n\nexport function isHiddenRemoteToolName(name: string): boolean {\n return HIDDEN_REMOTE_TOOL_NAMES.has(name)\n}\n\n/**\n * True for an MCP Apps tool only a view may call: `_meta.ui.visibility` is set\n * and does not include \"model\" (for example graph_expand, visibility [\"app\"]).\n * Such a tool never appears in a tool list built for the model or the CLI.\n */\nexport function isAppOnlyTool(tool: { _meta?: Record<string, unknown> }): boolean {\n const ui = tool._meta?.ui\n if (!ui || typeof ui !== 'object' || Array.isArray(ui)) return false\n const visibility = (ui as Record<string, unknown>).visibility\n return Array.isArray(visibility) && !visibility.includes('model')\n}\n\nexport function visibleRemoteTools<T extends { name: string; _meta?: Record<string, unknown> }>(\n tools: T[]\n): T[] {\n return tools.filter((tool) => !isHiddenRemoteToolName(tool.name) && !isAppOnlyTool(tool))\n}\n\nexport function assertPublicMcpToolName(name: string): void {\n if (!isHiddenRemoteToolName(name)) return\n const replacement =\n name === 'network_capabilities'\n ? ' Use meta_network_capabilities instead.'\n : name === 'usage_status'\n ? ' Use meta_usage_status instead.'\n : name === 'subscription_status'\n ? ' Use meta_subscription_status instead.'\n : name === 'balance'\n ? ' Use wallet_balance instead.'\n : name === 'help'\n ? ' Use meta_help instead.'\n : ''\n throw new Error(`MCP tool '${name}' is not exposed by Chain Insights.${replacement}`)\n}\n\nexport function formatMcpCallError(tool: string, err: unknown): string {\n // A tool error with an envelope is the server's own answer: its text, then the\n // line of its error_detail. It is never matched against the unknown-tool\n // pattern below. A tool error with no envelope keeps the old reading, because\n // a server can answer an unknown tool as an error reply.\n if (err instanceof McpToolError && err.errorDetail) return formatMcpToolError(err)\n const message = err instanceof Error ? err.message : String(err)\n if (/\\bunknown tool\\b|\\btool\\b.*\\bnot found\\b/i.test(message)) {\n return `Unknown MCP tool \"${tool}\". Run \\`cia mcp tools --refresh\\` to list available tools.`\n }\n return message\n}\n\nexport function validatePublicMcpToolArguments(name: string, args: Record<string, unknown>): void {\n const allowedArgs = PUBLIC_MCP_TOOL_ALLOWED_ARGS[name]\n if (!allowedArgs) return\n\n const unsupportedArgs = Object.keys(args).filter((argName) => !allowedArgs.includes(argName))\n if (unsupportedArgs.length === 0) return\n\n throw new Error(\n [\n `Unsupported argument${unsupportedArgs.length === 1 ? '' : 's'} for ${name}: ${unsupportedArgs.join(', ')}.`,\n `Allowed arguments: ${allowedArgs.join(', ')}.`,\n ].join(' ')\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAEA,MAAa,2CAA2B,IAAI,IAAI;CAC9C;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CAGA,CAAC,oBAAoB,QAAQ,CAAC,CAAC,KAAK,EAAE;CACtC,CAAC,qBAAqB,QAAQ,CAAC,CAAC,KAAK,EAAE;CACvC,CAAC,qBAAqB,UAAU,CAAC,CAAC,KAAK,EAAE;CACzC;CACA;CACA;CACA;CACA;CACA,CAAC,SAAS,0BAA0B,CAAC,CAAC,KAAK,EAAE;CAC7C;AACF,CAAC;AAED,MAAa,gCAA0D;CACrE,aAAa,CAAC,SAAS,SAAS;CAChC,mBAAmB,CAAC,WAAW,SAAS;AAC1C;AASA,MAAa,+BAAyD;CAEpE,aAAa;EAAC;EAAS;EAAW;CAAY;CAC9C,mBAAmB;EAAC;EAAW;EAAW;EAA6B;CAAY;CAEnF,iBAAiB;EAAC;EAAW;EAAQ;CAAO;AAC9C;AAEA,SAAgB,uBAAuB,MAAuB;CAC5D,OAAO,yBAAyB,IAAI,IAAI;AAC1C;;;;;;AAOA,SAAgB,cAAc,MAAoD;CAChF,MAAM,KAAK,KAAK,OAAO;CACvB,IAAI,CAAC,MAAM,OAAO,OAAO,YAAY,MAAM,QAAQ,EAAE,GAAG,OAAO;CAC/D,MAAM,aAAc,GAA+B;CACnD,OAAO,MAAM,QAAQ,UAAU,KAAK,CAAC,WAAW,SAAS,OAAO;AAClE;AAEA,SAAgB,mBACd,OACK;CACL,OAAO,MAAM,QAAQ,SAAS,CAAC,uBAAuB,KAAK,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC;AAC1F;AAEA,SAAgB,wBAAwB,MAAoB;CAC1D,IAAI,CAAC,uBAAuB,IAAI,GAAG;CAanC,MAAM,IAAI,MAAM,aAAa,KAAK,qCAXhC,SAAS,yBACL,4CACA,SAAS,iBACP,oCACA,SAAS,wBACP,2CACA,SAAS,YACP,iCACA,SAAS,SACP,4BACA,IACsE;AACtF;AAEA,SAAgB,mBAAmB,MAAc,KAAsB;CAKrE,IAAI,eAAe,gBAAgB,IAAI,aAAa,OAAO,mBAAmB,GAAG;CACjF,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC/D,IAAI,4CAA4C,KAAK,OAAO,GAC1D,OAAO,qBAAqB,KAAK;CAEnC,OAAO;AACT;AAEA,SAAgB,+BAA+B,MAAc,MAAqC;CAChG,MAAM,cAAc,6BAA6B;CACjD,IAAI,CAAC,aAAa;CAElB,MAAM,kBAAkB,OAAO,KAAK,IAAI,CAAC,CAAC,QAAQ,YAAY,CAAC,YAAY,SAAS,OAAO,CAAC;CAC5F,IAAI,gBAAgB,WAAW,GAAG;CAElC,MAAM,IAAI,MACR,CACE,uBAAuB,gBAAgB,WAAW,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,gBAAgB,KAAK,IAAI,EAAE,IAC1G,sBAAsB,YAAY,KAAK,IAAI,EAAE,EAC/C,CAAC,CAAC,KAAK,GAAG,CACZ;AACF"}
package/docs/mcp-proxy.md CHANGED
@@ -81,6 +81,7 @@ The proxy:
81
81
  | --------------------------- | ------------------------------------------------------------------------------------ |
82
82
  | `meta_network_capabilities` | Show the current Chain Insights network/tool support matrix |
83
83
  | `meta_schema` | Show the live graph schema of a network: labels, link types, fields, indexes |
84
+ | `meta_dex_lookup` | Turn an exchange name into its factories, routers or manager, pools and first reads |
84
85
  | `meta_rules` | Read the answer rules when the `chain-insights-cypher` skill is not loaded |
85
86
  | `meta_help` | Show Chain Insights tool and workflow guidance |
86
87
  | `meta_usage_status` | Check the caller's daily free-tier graph query allowance |
@@ -101,6 +102,76 @@ note, and such a schema is kept for 10 minutes, not 24 hours. In the terminal,
101
102
  structured form and `--refresh` to rebuild it.
102
103
  `cia mcp call meta_schema network=robinhood` does the same.
103
104
 
105
+ `meta_dex_lookup` turns the name of a decentralized exchange, a launchpad or a
106
+ vault into its addresses. It takes `network` (required), `name` (optional:
107
+ with no name it lists every covered exchange) and `pools` (default `true`).
108
+
109
+ 1. The name is matched against the exchange registry,
110
+ [`registry/robinhood.json`](../registry/robinhood.json): exact on the key,
111
+ the display name and the aliases, then within two letters of an alias of
112
+ four or more characters. One winner is `matched`; a misspelling is named in
113
+ `took_for`. A tie is listed in `candidates`, never guessed. No hit returns
114
+ `covered`, every covered exchange, and sends no graph read.
115
+ 2. For a match, one `graph_query_batch` reads `Head` for the three family
116
+ lags, each factory, manager and router by its address (`confirmed` is true
117
+ when the graph shows a contract), and the ten biggest pools each factory
118
+ deployed. It is billed as any read; the match itself costs nothing.
119
+ `pools: false` drops the pool reads.
120
+ 3. The answer carries `recipe`, the first reads of the exchange's trail type
121
+ with its addresses filled in, and the answer `hint` every graph reply
122
+ carries.
123
+
124
+ The registry is read from the public file on `main`,
125
+ `https://raw.githubusercontent.com/chainswarm/chain-insights/main/registry/<network>.json`,
126
+ within 3 seconds, and cached for one hour in
127
+ `~/.chain-insights/cache/dex-registry-<network>.json` (mode 0600). When the
128
+ fetch fails, the stale cache answers; with no cache, the copy packaged with
129
+ the installed version does. A malformed public file never replaces a good
130
+ cache. `registry_source` in the answer says which copy answered: `fetched`,
131
+ `cached` or `packaged`. `CHAIN_INSIGHTS_DEX_REGISTRY_URL` points the fetch
132
+ somewhere else, for tests and the lab; `{network}` in it is filled in.
133
+
134
+ ## Host parity
135
+
136
+ **A rule a host never loads does not exist for that host.** Each host loads a
137
+ different part of what this package ships:
138
+
139
+ | What a host loads | Claude Code | Desktop Code mode | Codex | Hermes | scout agent | Desktop chat / claude.ai |
140
+ |---|---|---|---|---|---|---|
141
+ | skill files (`cia setup`) | yes | yes | yes | yes | copied, 2 of 6 | no |
142
+ | server instructions | yes | yes | yes | yes | yes | no (ignored) |
143
+ | `meta_rules` | yes | yes | yes | yes | yes | no (never called) |
144
+ | tool descriptions | yes | yes | yes | yes | yes | yes |
145
+ | result `hint` | yes | yes | yes | yes | yes | yes |
146
+ | protocol tool doors | yes | yes | yes | yes | yes | yes |
147
+
148
+ The scout agent pins the CLI by version in its image and copies only
149
+ `chain-insights-cypher` and `chain-insights-virtuals` into its project, yet asks
150
+ an exchange question by name every run. Until 0.69.0 that question ran with no
151
+ exchange skill: the same gap as Desktop chat. From 0.69.0 the door answers it.
152
+
153
+ ```mermaid
154
+ flowchart LR
155
+ Q["a question that names an exchange"] --> D["meta_dex_lookup<br/>the tool door, read by every host"]
156
+ Q --> S["skills/chain-insights-dex*<br/>read by hosts that load skills"]
157
+ S -->|"step 1"| D
158
+ D --> R["addresses, pools, first reads, hint"]
159
+ ```
160
+
161
+ The rule:
162
+
163
+ 1. A protocol skill ships with a tool door: frontmatter `door: <tool>`.
164
+ 2. The door's description carries the rule the skill opens with, and names
165
+ the skill's trigger words.
166
+ 3. The skill names the door as its first step.
167
+
168
+ A host that loads skills gets both. A host that loads none gets the door.
169
+ `tests/host-parity.test.ts` checks it: every
170
+ `skills/chain-insights-<protocol>/SKILL.md` has a door, the door is in
171
+ `LOCAL_TOOL_NAMES` of `src/mcp/proxy.ts`, and its description names the
172
+ skill's trigger words. The core skill `chain-insights-cypher` is exempt: its
173
+ door is `meta_rules` plus the `graph_query` description.
174
+
104
175
  ## Views in Claude
105
176
 
106
177
  Claude Desktop and Cowork sessions on your computer draw MCP Apps views next
package/docs/stability.md CHANGED
@@ -18,12 +18,13 @@ Exit codes are contract. Scripts and agent harnesses may rely on them.
18
18
 
19
19
  ### MCP tool names
20
20
 
21
- The public MCP surface is these ten tools:
21
+ The public MCP surface is these eleven tools:
22
22
 
23
23
  - `graph_query`
24
24
  - `graph_query_batch`
25
25
  - `meta_network_capabilities`
26
26
  - `meta_schema`
27
+ - `meta_dex_lookup`
27
28
  - `meta_rules`
28
29
  - `meta_help`
29
30
  - `meta_usage_status`
@@ -31,7 +32,7 @@ The public MCP surface is these ten tools:
31
32
  - `wallet_balance`
32
33
  - `wallet_topup`
33
34
 
34
- `graph_expand` is the eleventh tool the proxy registers. The view calls it
35
+ `graph_expand` is the twelfth tool the proxy registers. The view calls it
35
36
  when you click in a picture; hosts keep it out of the model's tool list. It
36
37
  is not part of the public surface.
37
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chain-insights",
3
- "version": "0.68.2",
3
+ "version": "0.69.0",
4
4
  "description": "AML investigation CLI and MCP proxy for blockchain risk screening and Chain Insights Graph access",
5
5
  "homepage": "https://chain-insights.ai",
6
6
  "repository": {
@@ -50,6 +50,7 @@
50
50
  "dist",
51
51
  "skills",
52
52
  "!skills/rbmk-*",
53
+ "registry",
53
54
  "docs/*.md"
54
55
  ],
55
56
  "scripts": {
@@ -92,7 +93,7 @@
92
93
  "vitest": "^5.0.3"
93
94
  },
94
95
  "chainInsights": {
95
- "dexRegistryVersion": 1
96
+ "dexRegistryVersion": 2
96
97
  },
97
98
  "overrides": {
98
99
  "ws": "8.21.0",
@@ -0,0 +1,133 @@
1
+ # The exchange registry
2
+
3
+ **One reviewed table per network names every decentralized exchange and its
4
+ factory, manager and router contracts.** A user names an exchange (Aero,
5
+ Uniswap), not a factory address. This folder is the one home of name to
6
+ addresses. The proxy tool `meta_dex_lookup` serves it to every host.
7
+
8
+ ```mermaid
9
+ flowchart LR
10
+ MD["registry/robinhood.md<br/>one row per exchange and role"]
11
+ PIN["scripts/pin-dex-registry.mjs<br/>md to json, --check in CI"]
12
+ JSON["registry/robinhood.json<br/>the served copy"]
13
+ RAW["raw.githubusercontent.com<br/>main branch"]
14
+ PROXY["meta_dex_lookup<br/>cached 1 hour, packaged copy offline"]
15
+ MEMO["scripts/render-dex-registry.mjs<br/>the memo tables of the exchange skills"]
16
+ MD --> PIN --> JSON
17
+ JSON --> RAW --> PROXY
18
+ JSON --> MEMO
19
+ ```
20
+
21
+ ## The files
22
+
23
+ | File | Rows served | Why |
24
+ |---|---|---|
25
+ | [robinhood.md](robinhood.md) | 44 | registry version 2, recorded 2026-10-24; the one `dyor` row is not served |
26
+ | [robinhood.json](robinhood.json) | 29 exchanges | the served copy of `robinhood.md`, never edited by hand |
27
+
28
+ A change to the rows merged on `main` reaches every running proxy within one
29
+ hour. No release is needed for a data change.
30
+
31
+ ## The review rule
32
+
33
+ 1. **No source link, no row.** Every row carries the one link that confirms
34
+ it. Pin the link to a commit where the source has one.
35
+ 2. **No chain check, no serve.** A row is served only when the chain's own
36
+ data shows the address does what its role says:
37
+ - `factory`: a contract that created pools;
38
+ - `manager`: a contract that holds pools;
39
+ - `router`: a contract that forwards swaps.
40
+ Write the check into the file's `## Verification` section.
41
+ 3. **An unchecked row stays in the table.** Its verification cell says
42
+ `not served:` and the reason. It is served later, when a check passes.
43
+ 4. **The chain wins over the source.** When the chain shows a different
44
+ factory or block than the source, the row follows the chain and the file
45
+ says so.
46
+ 5. **An exchange needs a pool row.** An exchange with no served row of role
47
+ `factory` or `manager` is refused whole, router rows included.
48
+ 6. **One key per exchange.** A second spelling is an alias, never a second
49
+ key. Two exchanges may share an alias only when a user's word fits both
50
+ (`Balancer`): the lookup then lists both and guesses neither.
51
+
52
+ ## The columns
53
+
54
+ The `## Rows` table has twelve columns, in this order.
55
+
56
+ | Column | Means |
57
+ |---|---|
58
+ | dex | the key as a user says it, in lowercase: `aero`, `uniswap-v4`. Letters, digits, `.`, `_` and `-`, at most 32 |
59
+ | display | the exchange name as a reader sees it, at most 96 characters. Every row of an exchange carries the same display |
60
+ | aliases | the names a user types, comma separated, on one row of the exchange or spread over several |
61
+ | trail type | how money is traced through the exchange: `plain`, `vault`, `gauge` or `launchpad`. Every row of an exchange carries the same trail type |
62
+ | adapter | the adapter id of the DEX indexer that reads the row's pools: `cl-gauge-v1`, `uniswap-v4` |
63
+ | role | `factory` creates pools, `manager` holds pools, `router` forwards swaps |
64
+ | address | lowercase `0x` hex in backticks |
65
+ | valid from block | the first block the row holds from, `0` when it is not known yet |
66
+ | example pool | optional: one pool of the exchange, read live by the release suite. Empty or `-` when not given |
67
+ | pool floor | optional: the least number of pools the exchange must show on a lane. Empty or `-` when not given |
68
+ | source link | the one link that confirms the row, as `[text](https://…)` |
69
+ | verification | `served`, or `not served:` and the reason |
70
+
71
+ Each network file states `Registry version: N`. Bump it in every change to
72
+ the rows. Each file also states `Recorded on: YYYY-MM-DD`, the day of the
73
+ last row change. The pin script copies both into the served copy.
74
+
75
+ A row is refused for one of these reasons:
76
+
77
+ | Reason | When |
78
+ |---|---|
79
+ | `malformed row` | the row has the wrong number of cells, or no display |
80
+ | `duplicate row` | the same key, role and address twice |
81
+ | `bad dex key` | the key is not lowercase letters, digits, `.`, `_` or `-` |
82
+ | `bad address` | the address is not lowercase `0x` and 40 hex digits |
83
+ | `unknown role` | the role is not `factory`, `manager` or `router` |
84
+ | `unknown trail type` | the trail type is not `plain`, `vault`, `gauge` or `launchpad` |
85
+ | `bad adapter` | the adapter is empty or not an id |
86
+ | `no source link` | the source link is not `[text](https://…)` |
87
+ | `bad block`, `bad example pool`, `bad pool floor` | the cell is not a number or a pool |
88
+ | `too long` | a value does not fit its column |
89
+ | `dex rows disagree` | two rows of one exchange give a different display, trail type, example pool or pool floor |
90
+ | `no factory or manager row for the dex` | the exchange has router rows only |
91
+
92
+ ## The check
93
+
94
+ Regenerate the served copy after a row change:
95
+
96
+ ```bash
97
+ node scripts/pin-dex-registry.mjs registry/robinhood.md
98
+ ```
99
+
100
+ Check that the committed copy is what the file gives. CI runs this on every
101
+ pull request:
102
+
103
+ ```bash
104
+ node scripts/pin-dex-registry.mjs --check registry/robinhood.md
105
+ ```
106
+
107
+ It prints `rows: N served, M refused, K not served` and
108
+ `registry/robinhood.json up to date, registry version V`. It exits 1 when a
109
+ row is refused or the committed copy differs, and names both versions.
110
+
111
+ The skill memo follows the served copy:
112
+
113
+ ```bash
114
+ node scripts/render-dex-registry.mjs # rewrite the memo tables
115
+ node scripts/render-dex-registry.mjs --check # exit 1 when a memo table differs
116
+ ```
117
+
118
+ ## The served copy
119
+
120
+ `registry/<network>.json` has the schema `chain-insights.dex-registry.v1`:
121
+
122
+ - one entry per exchange, in the order the file first names it;
123
+ - `factories` lists its `factory` and `manager` addresses, `routers` its
124
+ `router` addresses; an exchange with no router row lists its manager there,
125
+ because a vault forwards its own swaps;
126
+ - `adapters`, `since_block` and `source` come from the factory and manager
127
+ rows: every adapter in order, the lowest block, the first source;
128
+ - `example_pool` and `pool_floor` are `null` when the exchange gives none.
129
+
130
+ The release suite `tests/dex-registry-live.integration.test.ts` reads every
131
+ row against a live graph per release candidate: every factory, manager and
132
+ router is a contract, every factory with a pool floor shows at least that many
133
+ pools, and every example pool is a `:Pool` deployed by its factory.