chain-insights 0.71.0 → 0.71.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -499,7 +499,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
499
499
  try {
500
500
  const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-DwddPLcY.cjs"));
501
501
  const { formatToolsTable } = await Promise.resolve().then(() => require("./format-DfhRQq4L.cjs"));
502
- const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-BopFnGsT.cjs")).then((n) => n.tool_visibility_exports);
502
+ const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-DvKqmHo3.cjs")).then((n) => n.tool_visibility_exports);
503
503
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BxtMoo9c.cjs")).then((n) => n.config_exports);
504
504
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Ypw9BNbn.cjs")).then((n) => n.client_exports);
505
505
  const config = await loadConfig();
@@ -522,7 +522,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
522
522
  }
523
523
  }
524
524
  console.log(formatToolsTable(visibleRemoteTools(tools)));
525
- const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await Promise.resolve().then(() => require("./dex-tool-DqAHl4_z.cjs")).then((n) => n.dex_tool_exports);
525
+ const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await Promise.resolve().then(() => require("./dex-tool-B4T8Czhm.cjs")).then((n) => n.dex_tool_exports);
526
526
  console.log("");
527
527
  console.log(formatToolsTable([{
528
528
  name: META_DEX_LOOKUP_TOOL,
@@ -535,7 +535,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
535
535
  })).addCommand(createCliCommand("call").description("Call a low-level MCP tool directly (advanced)").option("--json", "Print machine-readable JSON output").argument("<tool>", "Tool name to call").argument("[args...]", "Key=value arguments (e.g. address=0x1234... network=robinhood)").action(async (tool, rawArgs, opts) => {
536
536
  try {
537
537
  const { parseMcpCallArgs } = await Promise.resolve().then(() => require("./call-args-DhJiv6T8.cjs"));
538
- const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-BopFnGsT.cjs")).then((n) => n.tool_visibility_exports);
538
+ const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-DvKqmHo3.cjs")).then((n) => n.tool_visibility_exports);
539
539
  let args = parseMcpCallArgs(rawArgs);
540
540
  assertPublicMcpToolName(tool);
541
541
  validatePublicMcpToolArguments(tool, args);
@@ -556,7 +556,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
556
556
  return;
557
557
  }
558
558
  if (tool === "meta_dex_lookup") {
559
- const { handleMetaDexLookup } = await Promise.resolve().then(() => require("./dex-tool-DqAHl4_z.cjs")).then((n) => n.dex_tool_exports);
559
+ const { handleMetaDexLookup } = await Promise.resolve().then(() => require("./dex-tool-B4T8Czhm.cjs")).then((n) => n.dex_tool_exports);
560
560
  const { loadRegistry } = await Promise.resolve().then(() => require("./dex-registry-KOTJhAQf.cjs")).then((n) => n.dex_registry_exports);
561
561
  const pools = args["pools"] === void 0 ? void 0 : String(args["pools"]).toLowerCase() !== "false";
562
562
  const result = await withGraphMcpClient("chain-insights-cli-dex-lookup", (client) => handleMetaDexLookup({
@@ -636,7 +636,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
636
636
  });
637
637
  });
638
638
  } catch (err) {
639
- const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-BopFnGsT.cjs")).then((n) => n.tool_visibility_exports);
639
+ const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-DvKqmHo3.cjs")).then((n) => n.tool_visibility_exports);
640
640
  console.error(formatMcpCallError(tool, err));
641
641
  process.exit(1);
642
642
  }
package/dist/cli.mjs CHANGED
@@ -497,7 +497,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
497
497
  try {
498
498
  const { loadSchema, saveSchema } = await import("./schema-cache-BT-kHy_r.mjs");
499
499
  const { formatToolsTable } = await import("./format-CSesEBvS.mjs");
500
- const { visibleRemoteTools } = await import("./tool-visibility-Dq-Ruj-8.mjs").then((n) => n.a);
500
+ const { visibleRemoteTools } = await import("./tool-visibility-D-pFpuoz.mjs").then((n) => n.a);
501
501
  const { loadConfig } = await import("./config-9MabJh5R.mjs").then((n) => n.t);
502
502
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-gsLi528K.mjs").then((n) => n.r);
503
503
  const config = await loadConfig();
@@ -520,7 +520,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
520
520
  }
521
521
  }
522
522
  console.log(formatToolsTable(visibleRemoteTools(tools)));
523
- const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await import("./dex-tool-CeggX4M4.mjs").then((n) => n.a);
523
+ const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await import("./dex-tool-B5Qwz1Zd.mjs").then((n) => n.a);
524
524
  console.log("");
525
525
  console.log(formatToolsTable([{
526
526
  name: META_DEX_LOOKUP_TOOL,
@@ -533,7 +533,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
533
533
  })).addCommand(createCliCommand("call").description("Call a low-level MCP tool directly (advanced)").option("--json", "Print machine-readable JSON output").argument("<tool>", "Tool name to call").argument("[args...]", "Key=value arguments (e.g. address=0x1234... network=robinhood)").action(async (tool, rawArgs, opts) => {
534
534
  try {
535
535
  const { parseMcpCallArgs } = await import("./call-args-Bdw2sV4n.mjs");
536
- const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await import("./tool-visibility-Dq-Ruj-8.mjs").then((n) => n.a);
536
+ const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await import("./tool-visibility-D-pFpuoz.mjs").then((n) => n.a);
537
537
  let args = parseMcpCallArgs(rawArgs);
538
538
  assertPublicMcpToolName(tool);
539
539
  validatePublicMcpToolArguments(tool, args);
@@ -554,7 +554,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
554
554
  return;
555
555
  }
556
556
  if (tool === "meta_dex_lookup") {
557
- const { handleMetaDexLookup } = await import("./dex-tool-CeggX4M4.mjs").then((n) => n.a);
557
+ const { handleMetaDexLookup } = await import("./dex-tool-B5Qwz1Zd.mjs").then((n) => n.a);
558
558
  const { loadRegistry } = await import("./dex-registry-05wAhSa0.mjs").then((n) => n.n);
559
559
  const pools = args["pools"] === void 0 ? void 0 : String(args["pools"]).toLowerCase() !== "false";
560
560
  const result = await withGraphMcpClient("chain-insights-cli-dex-lookup", (client) => handleMetaDexLookup({
@@ -634,7 +634,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
634
634
  });
635
635
  });
636
636
  } catch (err) {
637
- const { formatMcpCallError } = await import("./tool-visibility-Dq-Ruj-8.mjs").then((n) => n.a);
637
+ const { formatMcpCallError } = await import("./tool-visibility-D-pFpuoz.mjs").then((n) => n.a);
638
638
  console.error(formatMcpCallError(tool, err));
639
639
  process.exit(1);
640
640
  }
@@ -360,7 +360,7 @@ function readMatch(entry, members, read) {
360
360
  }
361
361
  const ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/;
362
362
  function hintFor(result, name, address = null) {
363
- if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}. Send no read for it.`;
363
+ if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}: it is not a registered exchange contract. Send no exchange read for it; read the graph for what the address did.`;
364
364
  if (address !== null && result.matched) {
365
365
  const { matched } = result;
366
366
  return `${ANSWER_HINT} Say in one line: "${address} is a ${result.took_for} row of ${matched.display}." Ask nothing. Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, anchored on the addresses above, and load ${matched.skill} when this host has skills. Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.`;
@@ -360,7 +360,7 @@ function readMatch(entry, members, read) {
360
360
  }
361
361
  const ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/;
362
362
  function hintFor(result, name, address = null) {
363
- if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}. Send no read for it.`;
363
+ if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}: it is not a registered exchange contract. Send no exchange read for it; read the graph for what the address did.`;
364
364
  if (address !== null && result.matched) {
365
365
  const { matched } = result;
366
366
  return `${ANSWER_HINT} Say in one line: "${address} is a ${result.took_for} row of ${matched.display}." Ask nothing. Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, anchored on the addresses above, and load ${matched.skill} when this host has skills. Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.`;
@@ -515,4 +515,4 @@ async function handleMetaDexLookup(args, deps) {
515
515
  //#endregion
516
516
  export { dex_tool_exports as a, META_DEX_LOOKUP_TOOL as i, META_DEX_LOOKUP_DESCRIPTION as n, handleMetaDexLookup as o, META_DEX_LOOKUP_TITLE as r, withAnswerHintResult as s, META_DEX_LOOKUP_ANNOTATIONS as t };
517
517
 
518
- //# sourceMappingURL=dex-tool-CeggX4M4.mjs.map
518
+ //# sourceMappingURL=dex-tool-B5Qwz1Zd.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dex-tool-CeggX4M4.mjs","names":["isRecord"],"sources":["../src/mcp/answer-hint.ts","../src/mcp/dex-recipes.ts","../src/mcp/dex-tool.ts"],"sourcesContent":["/**\n * The two answer rules that still slip in long answers, carried on every\n * graph reply in the result's `hint` field. Claude Code has a Stop hook that\n * reads the answer; Codex and Claude Desktop have no such hook, and in the\n * loop's third prompt set they broke one of these two rules in three of\n * thirteen conversations. The reply is the one thing every host hands the\n * model right before it writes, so the rules ride on it. A `hint` the\n * server already set is kept.\n */\n\nexport const ANSWER_HINT =\n 'Answer: write every address in full (0x plus 40 characters), also when it repeats one named earlier and also when the user asks for shorter ones (name them instead); ' +\n 'a transfer moves tokens, so write \"tokens worth about N USD\", never \"sent N USD\" or a $ figure; ' +\n 'never draw the flows in text, ASCII or Mermaid, the host draws them.'\n\ntype TextBlock = { type: string; text?: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The structured content with the hint set when the server left it empty. */\nexport function withAnswerHint(structuredContent: unknown): unknown {\n if (!isRecord(structuredContent) || !isRecord(structuredContent.facts)) return structuredContent\n if (typeof structuredContent.hint === 'string' && structuredContent.hint.trim()) return structuredContent\n return { ...structuredContent, hint: ANSWER_HINT }\n}\n\n/** A graph tool result whose structured content and single JSON text block both carry the hint. */\nexport function withAnswerHintResult<T extends { content?: TextBlock[]; structuredContent?: unknown }>(result: T): T {\n const structured = withAnswerHint(result.structuredContent)\n if (structured === result.structuredContent) return result\n const content = result.content ?? []\n const only = content.length === 1 && content[0]?.type === 'text' ? content[0] : null\n let parsed: unknown = undefined\n try {\n parsed = only?.text ? JSON.parse(only.text) : undefined\n } catch {\n parsed = undefined\n }\n const textIsStructured = parsed !== undefined && JSON.stringify(parsed) === JSON.stringify(result.structuredContent)\n return { ...result, structuredContent: structured, content: textIsStructured ? [{ ...only, text: JSON.stringify(structured) }] : content }\n}\n","/**\n * The first reads of each trail type, filled with the addresses of one\n * exchange. meta_dex_lookup returns them, so a host that loads no skill\n * (Claude Desktop chat) still gets the tested reads of\n * skills/chain-insights-dex*. The shapes are the skills' own: R3 and R4 of\n * chain-insights-dex, G1, G4 and G5 of the gauge trail, V1, V3 and V4 of the\n * vault trail, L1 to L3 of the launchpad trail.\n *\n * A value the lookup cannot know (a seller, a day, a transaction) is written\n * as a placeholder in angle brackets, and the step's `why` says where it comes\n * from. Every address the lookup knows is written in full.\n */\n\nimport type { DexEntry, TrailType } from './dex-registry.js'\n\nexport interface RecipeStep {\n step: string\n why: string\n query: string\n}\n\n/** The trail-type skill of each trail, for hosts that load skills. */\nexport const TRAIL_SKILLS: Record<TrailType, string> = {\n plain: 'chain-insights-dex',\n gauge: 'chain-insights-dex-gauge',\n vault: 'chain-insights-dex-vault',\n launchpad: 'chain-insights-dex-launchpad',\n}\n\nconst SELLER = '<seller address>'\nconst DAY = '<YYYY-MM-DD>'\nconst TX = '<tx_id>'\nconst TOKEN = '<launched token address>'\n\n/** The pools one factory deployed, by lifetime volume (R3). */\nexport function poolsByFactoryQuery(factory: string, limit = 10): string {\n return (\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[:DEPLOYED_CONTRACT]->(p:Pool) WHERE p.total_volume_usd IS NOT NULL ` +\n 'RETURN f.address AS from_address, p.address AS to_address, p.labels AS to_labels, p.total_volume_usd AS total_volume_usd, p.tx_total_count AS tx_total_count ' +\n `ORDER BY p.total_volume_usd DESC LIMIT ${limit}`\n )\n}\n\n/** The senders of one pool on FLOWS_TO, by lifetime volume (R4, G1). */\nfunction sellersIntoPoolQuery(pool: string): string {\n return (\n `USE topology MATCH (s:Address)-[f:FLOWS_TO]->(p:Address {address: \"${pool}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, p.address AS to_address, labels(s) AS from_node_labels, f.amount_usd_sum AS amount_usd_sum, f.tx_count AS tx_count, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp, s.labels AS from_labels, p.labels AS to_labels, s.is_exchange AS from_is_exchange, p.is_exchange AS to_is_exchange ' +\n 'ORDER BY f.amount_usd_sum DESC LIMIT 25'\n )\n}\n\n/** The payers whose swaps name one contract as the recipient, by sold volume (V4 shape). */\nfunction sellersIntoContractQuery(contract: string): string {\n return (\n `USE topology MATCH (s:Address)-[sw:SWAPPED]->(r:Address {address: \"${contract}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(s) AS from_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.sold_usd DESC LIMIT 25'\n )\n}\n\n/** One seller's swaps (R5, G2, V1). */\nfunction sellerSwapsQuery(): string {\n return (\n `USE topology MATCH (s:Address {address: \"${SELLER}\"})-[sw:SWAPPED]->(r:Address) ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(r) AS to_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.swap_count DESC LIMIT 25'\n )\n}\n\n/** The facts SWAP rows of a seller and one recipient on one day (R6, G4, V3). */\nfunction swapRowsQuery(recipient: string): string {\n return (\n `USE facts MATCH (s:Address {address: \"${SELLER}\"})-[x:SWAP]->(r:Address {address: \"${recipient}\"}) WHERE x.block_date = \"${DAY}\" ` +\n 'RETURN x.tx_id AS tx_id, x.block_timestamp AS block_timestamp, x.payer AS payer, x.recipient AS recipient, x.sold_asset_symbol AS sold_asset_symbol, x.sold_usd AS sold_usd, x.bought_asset_symbol AS bought_asset_symbol, x.bought_usd AS bought_usd LIMIT 50'\n )\n}\n\n/** The transfers one router made in one transaction (G5). */\nfunction routerPayoutQuery(router: string): string {\n return (\n `USE facts MATCH (r:Address {address: \"${router}\"})-[t:TRANSFER]->(b:Address) WHERE t.block_date = \"${DAY}\" AND t.tx_id = \"${TX}\" ` +\n 'RETURN r.address AS from_address, b.address AS to_address, t.amount_usd AS amount_usd, t.asset_symbol AS asset_symbol, t.kind AS kind, t.tx_id AS tx_id LIMIT 10'\n )\n}\n\nconst dayStep: RecipeStep = {\n step: 'The day of a swap link',\n why: 'A SWAPPED link carries heights, not times: read the block of its last_seen_height for the day the facts reads need.',\n query:\n 'USE chain MATCH (b:Block {block_height: <last_seen_height>}) RETURN b.block_date, b.block_timestamp',\n}\n\n/**\n * The pool a trail starts from: the busiest pool read, else the registry's\n * example pool, else none.\n */\nfunction startPool(entry: DexEntry, topPool: string | null): string | null {\n return topPool ?? entry.example_pool ?? null\n}\n\nfunction poolsStep(entry: DexEntry): RecipeStep | null {\n const factory = entry.factories.find((address) => entry.roles[address] === 'factory')\n if (!factory) return null\n return {\n step: 'Pools of the exchange, by factory',\n why: `The pools ${entry.display} deployed, by lifetime volume. One batch member per factory: ${entry.factories.filter((a) => entry.roles[a] === 'factory').join(', ')}.`,\n query: poolsByFactoryQuery(factory),\n }\n}\n\n/** The first reads of the exchange's trail type, with its addresses filled in. */\nexport function recipeFor(entry: DexEntry, topPool: string | null): RecipeStep[] {\n const pool = startPool(entry, topPool)\n const steps: RecipeStep[] = []\n switch (entry.trail_type) {\n case 'gauge': {\n const router = entry.routers[0]\n if (router) {\n steps.push({\n step: 'Sellers into the router',\n why: `On ${entry.display} a swap pays the router, and the router pays on. These are the payers whose swaps name the router ${router}; the router is never the buyer.`,\n query: sellersIntoContractQuery(router),\n })\n }\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n if (router) {\n steps.push(dayStep)\n steps.push({\n step: \"A seller's swaps through the router on one day\",\n why: 'Fill the seller from the first step and the day from the block read: each row gives the tx_id of one swap.',\n query: swapRowsQuery(router),\n })\n steps.push({\n step: 'Who the router paid in that transaction',\n why: 'Fill the day and the tx_id: the receiver of the bought asset is the buyer. A pool or a pool manager there is the next leg, not the buyer.',\n query: routerPayoutQuery(router),\n })\n }\n break\n }\n case 'vault': {\n const manager = entry.factories[0]!\n steps.push({\n step: 'The swaps the pool manager settles',\n why: `${entry.display} keeps every pool in one manager, ${manager}. A manager deploys nothing: its pools are the pools of the swaps it settles.`,\n query: sellersIntoContractQuery(manager),\n })\n steps.push({\n step: \"One seller's swaps\",\n why: 'Fill a seller from the first step. A recipient that is the manager is never the buyer: read the facts row past it.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The facts SWAP row past the manager',\n why: 'Fill the seller and the day: the recipient column names who the manager paid.',\n query: swapRowsQuery(manager),\n })\n break\n }\n case 'launchpad': {\n const factory = entry.factories[0]!\n steps.push({\n step: 'Launches of the launchpad, newest first',\n why: `The contracts ${factory} created, with the creator of each.`,\n query:\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[d:DEPLOYED_CONTRACT]->(t:Address) ` +\n 'RETURN f.address AS from_address, t.address AS to_address, labels(t) AS to_node_labels, t.labels AS to_labels, d.tx_origin AS creator, d.tx_id AS tx_id, d.block_height AS block_height, d.block_timestamp AS created_timestamp ORDER BY d.block_height DESC LIMIT 25',\n })\n steps.push({\n step: 'Traders on the curve of one launched token',\n why: 'Fill a launched token from the first step.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[f:FLOWS_TO]-(b:Address) WHERE NOT t:Pool ` +\n 'RETURN startNode(f).address AS from_address, endNode(f).address AS to_address, labels(b) AS node_labels, f.tx_count AS tx_count, f.amount_usd_sum AS amount_usd_sum, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp ORDER BY f.tx_count DESC LIMIT 25',\n })\n steps.push({\n step: 'The first pool of one launched token',\n why: 'The oldest ADDED_LIQUIDITY into a :Pool is the graduation. None means it has not graduated in the graph yet.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[l:ADDED_LIQUIDITY]->(p:Pool) ` +\n 'RETURN t.address AS from_address, p.address AS to_address, p.labels AS to_labels, l.asset AS asset, l.usd AS usd, l.first_seen_height AS first_seen_height ORDER BY l.first_seen_height ASC LIMIT 5',\n })\n break\n }\n case 'plain': {\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO, by lifetime volume. A seller that is a contract is often an aggregator or a router: say \"a contract\".`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n steps.push({\n step: \"Who a seller's swaps paid\",\n why: 'Fill a seller from the first step. Keep the rows whose pools hold the pool: the recipient is the buyer.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The swaps of a seller and a recipient on one day',\n why: 'Fill the seller, the recipient and the day.',\n query: swapRowsQuery('<recipient address>'),\n })\n break\n }\n }\n return steps\n}\n","/**\n * meta_dex_lookup: an exchange's name becomes its addresses.\n *\n * A local tool of the proxy. It matches the name against the exchange registry\n * (src/mcp/dex-registry.ts) at no cost, then sends one graph_query_batch of\n * anchored reads: Head for the family lag, each factory, manager and router by\n * its address, and the pools each factory deployed. Nothing is read by name and\n * nothing is gated on a family lag: a factory deployed months ago is not stale\n * because the swap family is behind. A name that is not covered sends no read.\n *\n * The description below is the contract with every host, Claude Desktop chat\n * included, which reads no skill: it carries the rule the exchange skills open\n * with. tests/host-parity.test.ts holds it to the skills' trigger words.\n */\n\nimport { ANSWER_HINT } from './answer-hint.js'\nimport { recipeFor, TRAIL_SKILLS, type RecipeStep, poolsByFactoryQuery } from './dex-recipes.js'\nimport {\n DexRegistryError,\n matchDex,\n normalizeDexNetwork,\n type DexCandidate,\n type DexEntry,\n type DexMatchResult,\n type LoadedRegistry,\n type RegistrySource,\n type Role,\n type TrailType,\n} from './dex-registry.js'\n\nexport const META_DEX_LOOKUP_TOOL = 'meta_dex_lookup'\nexport const META_DEX_LOOKUP_SCHEMA = 'chain-insights.meta-dex-lookup.v1' as const\nexport const META_DEX_LOOKUP_TITLE = 'Exchange by name or address'\nexport const META_DEX_LOOKUP_DESCRIPTION =\n 'Call this first when a question names a decentralized exchange, a launchpad or a vault ' +\n '(Aero, Aerodrome, Velodrome, up., Raphael, Topaz, Uniswap, PancakeSwap, Balancer, Curve, ' +\n \"Penguin, Virtuals, ...) and gives no address. It turns the name into the exchange's \" +\n 'factories, routers or pool manager, its biggest pools, and the first reads to send, ' +\n 'with every address in full. A misspelling within two letters is taken and named in ' +\n 'took_for. When matched is null the name is not covered: say so, list covered, and send ' +\n 'no graph query for the name. Never ask the user for a pool, factory or router address. ' +\n 'When the user gives a contract address instead of a name, send it as address (0x plus 40 hex, ' +\n 'any case, with no name): the answer is the exchange whose factory, router or manager it is, ' +\n 'and took_for names the role it holds. When matched is null no exchange row holds the address: ' +\n 'send no read for it. Give a name or an address, not both.'\nexport const META_DEX_LOOKUP_ANNOTATIONS = {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n} as const\nexport const META_DEX_LOOKUP_ARGS = ['network', 'name', 'address', 'pools'] as const\n\n/** The pools returned per exchange, and per factory read. */\nexport const POOLS_LIMIT = 10\n/** A batch holds at most 20 members; Head and the pool reads take their share. */\nconst CONTRACT_MEMBERS_MAX = 12\n\nexport interface ContractRow {\n address: string\n role: Role\n confirmed: boolean\n confirmed_by?: string\n}\n\nexport interface PoolRow {\n address: string\n labels: string[]\n total_volume_usd: number\n tx_total_count: number\n}\n\nexport interface FamilyLag {\n behind_money: number | null\n behind_dex: number | null\n behind_transfers: number | null\n}\n\nexport interface DexMatch {\n dex: string\n display: string\n trail_type: TrailType\n /** The skill a host with skills loads next. */\n skill: string\n factories: ContractRow[]\n routers: ContractRow[]\n manager: ContractRow | null\n pools: PoolRow[]\n recipe: RecipeStep[]\n}\n\nexport interface CoveredExchange {\n dex: string\n display: string\n trail_type: TrailType\n factories: string[]\n routers: string[]\n}\n\nexport interface MetaDexLookupResult {\n schema: typeof META_DEX_LOOKUP_SCHEMA\n network: string\n registry_version: number\n registry_source: RegistrySource\n matched: DexMatch | null\n /** A near name hit: the name as typed. An address hit: the role it holds and the verification cell. */\n took_for?: string\n candidates: DexCandidate[]\n covered: string[]\n /** With no name: every covered exchange, with its key, trail type and contracts. */\n exchanges?: CoveredExchange[]\n families: FamilyLag | null\n /** What a read could not do: a lane that is not reachable, a member that was refused. */\n notes?: string[]\n hint: string\n}\n\n/** The batch reply as the remote client returns it. */\nexport type BatchAnswer = {\n content?: unknown[]\n structuredContent?: unknown\n isError?: boolean\n}\n\nexport interface DexToolDependencies {\n loadRegistry: (network: string) => Promise<LoadedRegistry>\n /** One graph_query_batch through the proxy's remote client, billed as any read. */\n graphQueryBatch: (args: {\n network: string\n queries: Array<{ id: string; query: string }>\n }) => Promise<BatchAnswer>\n /** The reason no read can be sent now, or undefined when the graph is reachable. */\n unavailable?: () => Promise<string | undefined> | string | undefined\n /** The text of a call that threw: payment required, transport. */\n describeFailure: (err: unknown) => string\n}\n\nexport type MetaDexLookupToolResult = {\n content: Array<{ type: 'text'; text: string }>\n structuredContent?: Record<string, unknown>\n isError: boolean\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction errorResult(text: string): MetaDexLookupToolResult {\n return { content: [{ type: 'text', text }], isError: true }\n}\n\ntype Member = { id?: unknown; ok?: unknown; results?: unknown; error?: unknown }\n\nfunction membersOf(answer: BatchAnswer): Map<string, Member> {\n const facts = isRecord(answer.structuredContent) ? answer.structuredContent.facts : undefined\n const queries = isRecord(facts) ? facts.queries : undefined\n const members = new Map<string, Member>()\n if (Array.isArray(queries)) {\n for (const member of queries)\n if (isRecord(member) && typeof member.id === 'string') members.set(member.id, member)\n }\n return members\n}\n\nconst rowsOf = (member: Member | undefined): Array<Record<string, unknown>> =>\n member?.ok === true && Array.isArray(member.results) ? member.results.filter(isRecord) : []\n\nconst numberOrNull = (value: unknown): number | null => {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))\n return Number(value)\n return null\n}\n\nconst stringList = (value: unknown): string[] =>\n Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : []\n\n/** A column of a row, read under its alias or as the query wrote it (h.behind_dex). */\nfunction column(row: Record<string, unknown>, name: string, variable: string): unknown {\n return name in row ? row[name] : row[`${variable}.${name}`]\n}\n\nexport const HEAD_QUERY =\n 'USE chain MATCH (h:Head) RETURN h.behind_money, h.behind_dex, h.behind_transfers'\n\nexport function contractQuery(address: string): string {\n return (\n `USE topology MATCH (a:Address {address: \"${address}\"}) ` +\n 'RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT 1'\n )\n}\n\nexport function contractListQuery(addresses: string[]): string {\n return (\n `USE topology MATCH (a:Address) WHERE a.address IN [${addresses.map((a) => `\"${a}\"`).join(', ')}] ` +\n `RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT ${addresses.length}`\n )\n}\n\n/** The anchored reads of one exchange: Head, each contract, and the pools of each factory. */\nexport function lookupQueries(\n entry: DexEntry,\n pools: boolean\n): Array<{ id: string; query: string }> {\n const contracts = Object.keys(entry.roles)\n const queries: Array<{ id: string; query: string }> = [{ id: 'head', query: HEAD_QUERY }]\n if (contracts.length <= CONTRACT_MEMBERS_MAX) {\n for (const address of contracts)\n queries.push({ id: `contract:${address}`, query: contractQuery(address) })\n } else {\n queries.push({ id: 'contracts', query: contractListQuery(contracts) })\n }\n if (pools) {\n for (const factory of entry.factories.filter((address) => entry.roles[address] === 'factory')) {\n queries.push({ id: `pools:${factory}`, query: poolsByFactoryQuery(factory, POOLS_LIMIT) })\n }\n }\n return queries\n}\n\nfunction confirmation(\n row: Record<string, unknown> | undefined\n): Pick<ContractRow, 'confirmed' | 'confirmed_by'> {\n if (!row) return { confirmed: false }\n if (row.is_contract === true) return { confirmed: true, confirmed_by: 'is_contract' }\n if (stringList(row.node_labels).includes('Contract'))\n return { confirmed: true, confirmed_by: ':Contract' }\n return { confirmed: false }\n}\n\nfunction readMatch(\n entry: DexEntry,\n members: Map<string, Member>,\n read: boolean\n): {\n contracts: Map<string, ContractRow>\n pools: PoolRow[]\n families: FamilyLag | null\n notes: string[]\n} {\n const notes: string[] = []\n const contracts = new Map<string, ContractRow>()\n const listed = new Map(rowsOf(members.get('contracts')).map((row) => [String(row.address), row]))\n for (const [address, role] of Object.entries(entry.roles)) {\n const row = members.has(`contract:${address}`)\n ? rowsOf(members.get(`contract:${address}`))[0]\n : listed.get(address)\n contracts.set(address, { address, role, ...confirmation(row) })\n }\n const byAddress = new Map<string, PoolRow>()\n for (const [id, member] of members) {\n if (!id.startsWith('pools:')) continue\n for (const row of rowsOf(member)) {\n const address = typeof row.to_address === 'string' ? row.to_address : null\n const volume = numberOrNull(row.total_volume_usd)\n if (!address || volume === null || byAddress.has(address)) continue\n byAddress.set(address, {\n address,\n labels: stringList(row.to_labels),\n total_volume_usd: volume,\n tx_total_count: numberOrNull(row.tx_total_count) ?? 0,\n })\n }\n }\n const pools = [...byAddress.values()]\n .sort((a, b) => b.total_volume_usd - a.total_volume_usd)\n .slice(0, POOLS_LIMIT)\n const head = rowsOf(members.get('head'))[0]\n const families: FamilyLag | null = head\n ? {\n behind_money: numberOrNull(column(head, 'behind_money', 'h')),\n behind_dex: numberOrNull(column(head, 'behind_dex', 'h')),\n behind_transfers: numberOrNull(column(head, 'behind_transfers', 'h')),\n }\n : null\n if (read) {\n for (const [id, member] of members) {\n if (member.ok !== true)\n notes.push(\n `${id}: ${typeof member.error === 'string' ? member.error : 'the read was not served'}`\n )\n }\n }\n return { contracts, pools, families, notes }\n}\n\nconst ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/\n\nfunction hintFor(\n result: Omit<MetaDexLookupResult, 'hint'>,\n name: string | null,\n address: string | null = null\n): string {\n if (address !== null && !result.matched) {\n return `${ANSWER_HINT} No exchange row holds ${address}. Send no read for it.`\n }\n if (address !== null && result.matched) {\n const { matched } = result\n return (\n `${ANSWER_HINT} Say in one line: \"${address} is a ${result.took_for} row of ${matched.display}.\" Ask nothing. ` +\n `Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (name === null) {\n return `${ANSWER_HINT} These are the exchanges the registry covers. Call meta_dex_lookup with a name for its addresses and first reads.`\n }\n if (result.matched) {\n const { matched } = result\n const took = result.took_for\n ? `Say in one line: \"I took ${matched.display} for ${result.took_for}.\" Ask nothing. `\n : ''\n return (\n `${ANSWER_HINT} ${took}Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (result.candidates.length > 0) {\n const names = result.candidates\n .map((candidate) => `${candidate.display} (${candidate.dex})`)\n .join(', ')\n return `${ANSWER_HINT} ${result.candidates.length} rows fit \"${name}\": ${names}. Name both or pick one: call meta_dex_lookup with each key. Never guess one, never ask the user for an address.`\n }\n return `${ANSWER_HINT} ${name} is not covered. Send no query for the name: say so in one line and list the covered exchanges by name.`\n}\n\nfunction textResult(structured: MetaDexLookupResult): MetaDexLookupToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],\n structuredContent: structured as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\n/** meta_dex_lookup, {network, name?, address?, pools?}. Every argument is checked before the registry is read. */\nexport async function handleMetaDexLookup(\n args: unknown,\n deps: DexToolDependencies\n): Promise<MetaDexLookupToolResult> {\n const record = isRecord(args) ? args : {}\n let network: string\n try {\n network = normalizeDexNetwork(record.network)\n } catch (err) {\n return errorResult((err as Error).message)\n }\n const rawName = record.name\n if (rawName !== undefined && rawName !== null && typeof rawName !== 'string') {\n return errorResult('invalid_name: name must be the exchange name as text')\n }\n const name = typeof rawName === 'string' && rawName.trim() !== '' ? rawName.trim() : null\n const rawAddress = record.address\n if (rawAddress !== undefined && rawAddress !== null && typeof rawAddress !== 'string') {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const addressText = typeof rawAddress === 'string' && rawAddress.trim() !== '' ? rawAddress.trim() : null\n if (name !== null && addressText !== null) return errorResult('Give a name or an address, not both.')\n if (addressText !== null && !ADDRESS_FORM.test(addressText)) {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const address = addressText === null ? null : addressText.toLowerCase()\n const rawPools = record.pools\n if (rawPools !== undefined && rawPools !== null && typeof rawPools !== 'boolean') {\n return errorResult('invalid_pools: pools must be true or false')\n }\n const pools = rawPools !== false\n\n let loaded: LoadedRegistry\n try {\n loaded = await deps.loadRegistry(network)\n } catch (err) {\n return errorResult(\n err instanceof DexRegistryError\n ? err.message\n : `exchange registry unavailable: ${(err as Error).message}`\n )\n }\n const { document } = loaded\n const base = {\n schema: META_DEX_LOOKUP_SCHEMA,\n network,\n registry_version: document.registry_version,\n registry_source: loaded.source,\n }\n\n if (name === null && address === null) {\n const result = {\n ...base,\n matched: null,\n candidates: [],\n covered: document.rows.map((row) => row.display),\n exchanges: document.rows.map((row) => ({\n dex: row.dex,\n display: row.display,\n trail_type: row.trail_type,\n factories: row.factories,\n routers: row.routers,\n })),\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, null) })\n }\n\n let match: DexMatchResult\n if (address !== null) {\n const holder = document.rows.find((row) => address in row.roles)\n match = {\n matched: holder ?? null,\n ...(holder ? { took_for: `${holder.roles[address]}, served` } : {}),\n candidates: [],\n covered: document.rows.map((row) => row.display),\n }\n } else {\n match = matchDex(name as string, document.rows)\n }\n if (!match.matched) {\n const result = {\n ...base,\n matched: null,\n candidates: match.candidates,\n covered: match.covered,\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n }\n\n const entry = match.matched\n const queries = lookupQueries(entry, pools)\n let members = new Map<string, Member>()\n const notes: string[] = []\n const unavailable = await deps.unavailable?.()\n if (unavailable) {\n notes.push(\n `No graph read was sent: ${unavailable} The contracts are the reviewed registry rows, not yet confirmed on the graph.`\n )\n } else {\n try {\n const answer = await deps.graphQueryBatch({ network, queries })\n members = membersOf(answer)\n if (answer.isError === true && members.size === 0) {\n const first = Array.isArray(answer.content) ? answer.content[0] : undefined\n notes.push(\n `The graph refused the reads: ${isRecord(first) && typeof first.text === 'string' ? first.text : 'no reason given'}`\n )\n }\n } catch (err) {\n notes.push(`The graph reads failed: ${deps.describeFailure(err)}`)\n }\n }\n const read = readMatch(entry, members, members.size > 0)\n notes.push(...read.notes)\n const contractsOf = (role: Role): ContractRow[] =>\n [...read.contracts.values()].filter((contract) => contract.role === role)\n const topPool = read.pools[0]?.address ?? null\n const matched: DexMatch = {\n dex: entry.dex,\n display: entry.display,\n trail_type: entry.trail_type,\n skill: TRAIL_SKILLS[entry.trail_type],\n factories: contractsOf('factory'),\n routers: contractsOf('router'),\n manager: contractsOf('manager')[0] ?? null,\n pools: read.pools,\n recipe: recipeFor(entry, topPool),\n }\n const result = {\n ...base,\n matched,\n ...(match.took_for ? { took_for: match.took_for } : {}),\n candidates: [],\n covered: match.covered,\n families: read.families,\n ...(notes.length > 0 ? { notes } : {}),\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dex-tool-B5Qwz1Zd.mjs","names":["isRecord"],"sources":["../src/mcp/answer-hint.ts","../src/mcp/dex-recipes.ts","../src/mcp/dex-tool.ts"],"sourcesContent":["/**\n * The two answer rules that still slip in long answers, carried on every\n * graph reply in the result's `hint` field. Claude Code has a Stop hook that\n * reads the answer; Codex and Claude Desktop have no such hook, and in the\n * loop's third prompt set they broke one of these two rules in three of\n * thirteen conversations. The reply is the one thing every host hands the\n * model right before it writes, so the rules ride on it. A `hint` the\n * server already set is kept.\n */\n\nexport const ANSWER_HINT =\n 'Answer: write every address in full (0x plus 40 characters), also when it repeats one named earlier and also when the user asks for shorter ones (name them instead); ' +\n 'a transfer moves tokens, so write \"tokens worth about N USD\", never \"sent N USD\" or a $ figure; ' +\n 'never draw the flows in text, ASCII or Mermaid, the host draws them.'\n\ntype TextBlock = { type: string; text?: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The structured content with the hint set when the server left it empty. */\nexport function withAnswerHint(structuredContent: unknown): unknown {\n if (!isRecord(structuredContent) || !isRecord(structuredContent.facts)) return structuredContent\n if (typeof structuredContent.hint === 'string' && structuredContent.hint.trim()) return structuredContent\n return { ...structuredContent, hint: ANSWER_HINT }\n}\n\n/** A graph tool result whose structured content and single JSON text block both carry the hint. */\nexport function withAnswerHintResult<T extends { content?: TextBlock[]; structuredContent?: unknown }>(result: T): T {\n const structured = withAnswerHint(result.structuredContent)\n if (structured === result.structuredContent) return result\n const content = result.content ?? []\n const only = content.length === 1 && content[0]?.type === 'text' ? content[0] : null\n let parsed: unknown = undefined\n try {\n parsed = only?.text ? JSON.parse(only.text) : undefined\n } catch {\n parsed = undefined\n }\n const textIsStructured = parsed !== undefined && JSON.stringify(parsed) === JSON.stringify(result.structuredContent)\n return { ...result, structuredContent: structured, content: textIsStructured ? [{ ...only, text: JSON.stringify(structured) }] : content }\n}\n","/**\n * The first reads of each trail type, filled with the addresses of one\n * exchange. meta_dex_lookup returns them, so a host that loads no skill\n * (Claude Desktop chat) still gets the tested reads of\n * skills/chain-insights-dex*. The shapes are the skills' own: R3 and R4 of\n * chain-insights-dex, G1, G4 and G5 of the gauge trail, V1, V3 and V4 of the\n * vault trail, L1 to L3 of the launchpad trail.\n *\n * A value the lookup cannot know (a seller, a day, a transaction) is written\n * as a placeholder in angle brackets, and the step's `why` says where it comes\n * from. Every address the lookup knows is written in full.\n */\n\nimport type { DexEntry, TrailType } from './dex-registry.js'\n\nexport interface RecipeStep {\n step: string\n why: string\n query: string\n}\n\n/** The trail-type skill of each trail, for hosts that load skills. */\nexport const TRAIL_SKILLS: Record<TrailType, string> = {\n plain: 'chain-insights-dex',\n gauge: 'chain-insights-dex-gauge',\n vault: 'chain-insights-dex-vault',\n launchpad: 'chain-insights-dex-launchpad',\n}\n\nconst SELLER = '<seller address>'\nconst DAY = '<YYYY-MM-DD>'\nconst TX = '<tx_id>'\nconst TOKEN = '<launched token address>'\n\n/** The pools one factory deployed, by lifetime volume (R3). */\nexport function poolsByFactoryQuery(factory: string, limit = 10): string {\n return (\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[:DEPLOYED_CONTRACT]->(p:Pool) WHERE p.total_volume_usd IS NOT NULL ` +\n 'RETURN f.address AS from_address, p.address AS to_address, p.labels AS to_labels, p.total_volume_usd AS total_volume_usd, p.tx_total_count AS tx_total_count ' +\n `ORDER BY p.total_volume_usd DESC LIMIT ${limit}`\n )\n}\n\n/** The senders of one pool on FLOWS_TO, by lifetime volume (R4, G1). */\nfunction sellersIntoPoolQuery(pool: string): string {\n return (\n `USE topology MATCH (s:Address)-[f:FLOWS_TO]->(p:Address {address: \"${pool}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, p.address AS to_address, labels(s) AS from_node_labels, f.amount_usd_sum AS amount_usd_sum, f.tx_count AS tx_count, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp, s.labels AS from_labels, p.labels AS to_labels, s.is_exchange AS from_is_exchange, p.is_exchange AS to_is_exchange ' +\n 'ORDER BY f.amount_usd_sum DESC LIMIT 25'\n )\n}\n\n/** The payers whose swaps name one contract as the recipient, by sold volume (V4 shape). */\nfunction sellersIntoContractQuery(contract: string): string {\n return (\n `USE topology MATCH (s:Address)-[sw:SWAPPED]->(r:Address {address: \"${contract}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(s) AS from_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.sold_usd DESC LIMIT 25'\n )\n}\n\n/** One seller's swaps (R5, G2, V1). */\nfunction sellerSwapsQuery(): string {\n return (\n `USE topology MATCH (s:Address {address: \"${SELLER}\"})-[sw:SWAPPED]->(r:Address) ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(r) AS to_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.swap_count DESC LIMIT 25'\n )\n}\n\n/** The facts SWAP rows of a seller and one recipient on one day (R6, G4, V3). */\nfunction swapRowsQuery(recipient: string): string {\n return (\n `USE facts MATCH (s:Address {address: \"${SELLER}\"})-[x:SWAP]->(r:Address {address: \"${recipient}\"}) WHERE x.block_date = \"${DAY}\" ` +\n 'RETURN x.tx_id AS tx_id, x.block_timestamp AS block_timestamp, x.payer AS payer, x.recipient AS recipient, x.sold_asset_symbol AS sold_asset_symbol, x.sold_usd AS sold_usd, x.bought_asset_symbol AS bought_asset_symbol, x.bought_usd AS bought_usd LIMIT 50'\n )\n}\n\n/** The transfers one router made in one transaction (G5). */\nfunction routerPayoutQuery(router: string): string {\n return (\n `USE facts MATCH (r:Address {address: \"${router}\"})-[t:TRANSFER]->(b:Address) WHERE t.block_date = \"${DAY}\" AND t.tx_id = \"${TX}\" ` +\n 'RETURN r.address AS from_address, b.address AS to_address, t.amount_usd AS amount_usd, t.asset_symbol AS asset_symbol, t.kind AS kind, t.tx_id AS tx_id LIMIT 10'\n )\n}\n\nconst dayStep: RecipeStep = {\n step: 'The day of a swap link',\n why: 'A SWAPPED link carries heights, not times: read the block of its last_seen_height for the day the facts reads need.',\n query:\n 'USE chain MATCH (b:Block {block_height: <last_seen_height>}) RETURN b.block_date, b.block_timestamp',\n}\n\n/**\n * The pool a trail starts from: the busiest pool read, else the registry's\n * example pool, else none.\n */\nfunction startPool(entry: DexEntry, topPool: string | null): string | null {\n return topPool ?? entry.example_pool ?? null\n}\n\nfunction poolsStep(entry: DexEntry): RecipeStep | null {\n const factory = entry.factories.find((address) => entry.roles[address] === 'factory')\n if (!factory) return null\n return {\n step: 'Pools of the exchange, by factory',\n why: `The pools ${entry.display} deployed, by lifetime volume. One batch member per factory: ${entry.factories.filter((a) => entry.roles[a] === 'factory').join(', ')}.`,\n query: poolsByFactoryQuery(factory),\n }\n}\n\n/** The first reads of the exchange's trail type, with its addresses filled in. */\nexport function recipeFor(entry: DexEntry, topPool: string | null): RecipeStep[] {\n const pool = startPool(entry, topPool)\n const steps: RecipeStep[] = []\n switch (entry.trail_type) {\n case 'gauge': {\n const router = entry.routers[0]\n if (router) {\n steps.push({\n step: 'Sellers into the router',\n why: `On ${entry.display} a swap pays the router, and the router pays on. These are the payers whose swaps name the router ${router}; the router is never the buyer.`,\n query: sellersIntoContractQuery(router),\n })\n }\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n if (router) {\n steps.push(dayStep)\n steps.push({\n step: \"A seller's swaps through the router on one day\",\n why: 'Fill the seller from the first step and the day from the block read: each row gives the tx_id of one swap.',\n query: swapRowsQuery(router),\n })\n steps.push({\n step: 'Who the router paid in that transaction',\n why: 'Fill the day and the tx_id: the receiver of the bought asset is the buyer. A pool or a pool manager there is the next leg, not the buyer.',\n query: routerPayoutQuery(router),\n })\n }\n break\n }\n case 'vault': {\n const manager = entry.factories[0]!\n steps.push({\n step: 'The swaps the pool manager settles',\n why: `${entry.display} keeps every pool in one manager, ${manager}. A manager deploys nothing: its pools are the pools of the swaps it settles.`,\n query: sellersIntoContractQuery(manager),\n })\n steps.push({\n step: \"One seller's swaps\",\n why: 'Fill a seller from the first step. A recipient that is the manager is never the buyer: read the facts row past it.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The facts SWAP row past the manager',\n why: 'Fill the seller and the day: the recipient column names who the manager paid.',\n query: swapRowsQuery(manager),\n })\n break\n }\n case 'launchpad': {\n const factory = entry.factories[0]!\n steps.push({\n step: 'Launches of the launchpad, newest first',\n why: `The contracts ${factory} created, with the creator of each.`,\n query:\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[d:DEPLOYED_CONTRACT]->(t:Address) ` +\n 'RETURN f.address AS from_address, t.address AS to_address, labels(t) AS to_node_labels, t.labels AS to_labels, d.tx_origin AS creator, d.tx_id AS tx_id, d.block_height AS block_height, d.block_timestamp AS created_timestamp ORDER BY d.block_height DESC LIMIT 25',\n })\n steps.push({\n step: 'Traders on the curve of one launched token',\n why: 'Fill a launched token from the first step.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[f:FLOWS_TO]-(b:Address) WHERE NOT t:Pool ` +\n 'RETURN startNode(f).address AS from_address, endNode(f).address AS to_address, labels(b) AS node_labels, f.tx_count AS tx_count, f.amount_usd_sum AS amount_usd_sum, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp ORDER BY f.tx_count DESC LIMIT 25',\n })\n steps.push({\n step: 'The first pool of one launched token',\n why: 'The oldest ADDED_LIQUIDITY into a :Pool is the graduation. None means it has not graduated in the graph yet.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[l:ADDED_LIQUIDITY]->(p:Pool) ` +\n 'RETURN t.address AS from_address, p.address AS to_address, p.labels AS to_labels, l.asset AS asset, l.usd AS usd, l.first_seen_height AS first_seen_height ORDER BY l.first_seen_height ASC LIMIT 5',\n })\n break\n }\n case 'plain': {\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO, by lifetime volume. A seller that is a contract is often an aggregator or a router: say \"a contract\".`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n steps.push({\n step: \"Who a seller's swaps paid\",\n why: 'Fill a seller from the first step. Keep the rows whose pools hold the pool: the recipient is the buyer.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The swaps of a seller and a recipient on one day',\n why: 'Fill the seller, the recipient and the day.',\n query: swapRowsQuery('<recipient address>'),\n })\n break\n }\n }\n return steps\n}\n","/**\n * meta_dex_lookup: an exchange's name becomes its addresses.\n *\n * A local tool of the proxy. It matches the name against the exchange registry\n * (src/mcp/dex-registry.ts) at no cost, then sends one graph_query_batch of\n * anchored reads: Head for the family lag, each factory, manager and router by\n * its address, and the pools each factory deployed. Nothing is read by name and\n * nothing is gated on a family lag: a factory deployed months ago is not stale\n * because the swap family is behind. A name that is not covered sends no read.\n *\n * The description below is the contract with every host, Claude Desktop chat\n * included, which reads no skill: it carries the rule the exchange skills open\n * with. tests/host-parity.test.ts holds it to the skills' trigger words.\n */\n\nimport { ANSWER_HINT } from './answer-hint.js'\nimport { recipeFor, TRAIL_SKILLS, type RecipeStep, poolsByFactoryQuery } from './dex-recipes.js'\nimport {\n DexRegistryError,\n matchDex,\n normalizeDexNetwork,\n type DexCandidate,\n type DexEntry,\n type DexMatchResult,\n type LoadedRegistry,\n type RegistrySource,\n type Role,\n type TrailType,\n} from './dex-registry.js'\n\nexport const META_DEX_LOOKUP_TOOL = 'meta_dex_lookup'\nexport const META_DEX_LOOKUP_SCHEMA = 'chain-insights.meta-dex-lookup.v1' as const\nexport const META_DEX_LOOKUP_TITLE = 'Exchange by name or address'\nexport const META_DEX_LOOKUP_DESCRIPTION =\n 'Call this first when a question names a decentralized exchange, a launchpad or a vault ' +\n '(Aero, Aerodrome, Velodrome, up., Raphael, Topaz, Uniswap, PancakeSwap, Balancer, Curve, ' +\n \"Penguin, Virtuals, ...) and gives no address. It turns the name into the exchange's \" +\n 'factories, routers or pool manager, its biggest pools, and the first reads to send, ' +\n 'with every address in full. A misspelling within two letters is taken and named in ' +\n 'took_for. When matched is null the name is not covered: say so, list covered, and send ' +\n 'no graph query for the name. Never ask the user for a pool, factory or router address. ' +\n 'When the user gives a contract address instead of a name, send it as address (0x plus 40 hex, ' +\n 'any case, with no name): the answer is the exchange whose factory, router or manager it is, ' +\n 'and took_for names the role it holds. When matched is null no exchange row holds the address: ' +\n 'send no read for it. Give a name or an address, not both.'\nexport const META_DEX_LOOKUP_ANNOTATIONS = {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n} as const\nexport const META_DEX_LOOKUP_ARGS = ['network', 'name', 'address', 'pools'] as const\n\n/** The pools returned per exchange, and per factory read. */\nexport const POOLS_LIMIT = 10\n/** A batch holds at most 20 members; Head and the pool reads take their share. */\nconst CONTRACT_MEMBERS_MAX = 12\n\nexport interface ContractRow {\n address: string\n role: Role\n confirmed: boolean\n confirmed_by?: string\n}\n\nexport interface PoolRow {\n address: string\n labels: string[]\n total_volume_usd: number\n tx_total_count: number\n}\n\nexport interface FamilyLag {\n behind_money: number | null\n behind_dex: number | null\n behind_transfers: number | null\n}\n\nexport interface DexMatch {\n dex: string\n display: string\n trail_type: TrailType\n /** The skill a host with skills loads next. */\n skill: string\n factories: ContractRow[]\n routers: ContractRow[]\n manager: ContractRow | null\n pools: PoolRow[]\n recipe: RecipeStep[]\n}\n\nexport interface CoveredExchange {\n dex: string\n display: string\n trail_type: TrailType\n factories: string[]\n routers: string[]\n}\n\nexport interface MetaDexLookupResult {\n schema: typeof META_DEX_LOOKUP_SCHEMA\n network: string\n registry_version: number\n registry_source: RegistrySource\n matched: DexMatch | null\n /** A near name hit: the name as typed. An address hit: the role it holds and the verification cell. */\n took_for?: string\n candidates: DexCandidate[]\n covered: string[]\n /** With no name: every covered exchange, with its key, trail type and contracts. */\n exchanges?: CoveredExchange[]\n families: FamilyLag | null\n /** What a read could not do: a lane that is not reachable, a member that was refused. */\n notes?: string[]\n hint: string\n}\n\n/** The batch reply as the remote client returns it. */\nexport type BatchAnswer = {\n content?: unknown[]\n structuredContent?: unknown\n isError?: boolean\n}\n\nexport interface DexToolDependencies {\n loadRegistry: (network: string) => Promise<LoadedRegistry>\n /** One graph_query_batch through the proxy's remote client, billed as any read. */\n graphQueryBatch: (args: {\n network: string\n queries: Array<{ id: string; query: string }>\n }) => Promise<BatchAnswer>\n /** The reason no read can be sent now, or undefined when the graph is reachable. */\n unavailable?: () => Promise<string | undefined> | string | undefined\n /** The text of a call that threw: payment required, transport. */\n describeFailure: (err: unknown) => string\n}\n\nexport type MetaDexLookupToolResult = {\n content: Array<{ type: 'text'; text: string }>\n structuredContent?: Record<string, unknown>\n isError: boolean\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction errorResult(text: string): MetaDexLookupToolResult {\n return { content: [{ type: 'text', text }], isError: true }\n}\n\ntype Member = { id?: unknown; ok?: unknown; results?: unknown; error?: unknown }\n\nfunction membersOf(answer: BatchAnswer): Map<string, Member> {\n const facts = isRecord(answer.structuredContent) ? answer.structuredContent.facts : undefined\n const queries = isRecord(facts) ? facts.queries : undefined\n const members = new Map<string, Member>()\n if (Array.isArray(queries)) {\n for (const member of queries)\n if (isRecord(member) && typeof member.id === 'string') members.set(member.id, member)\n }\n return members\n}\n\nconst rowsOf = (member: Member | undefined): Array<Record<string, unknown>> =>\n member?.ok === true && Array.isArray(member.results) ? member.results.filter(isRecord) : []\n\nconst numberOrNull = (value: unknown): number | null => {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))\n return Number(value)\n return null\n}\n\nconst stringList = (value: unknown): string[] =>\n Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : []\n\n/** A column of a row, read under its alias or as the query wrote it (h.behind_dex). */\nfunction column(row: Record<string, unknown>, name: string, variable: string): unknown {\n return name in row ? row[name] : row[`${variable}.${name}`]\n}\n\nexport const HEAD_QUERY =\n 'USE chain MATCH (h:Head) RETURN h.behind_money, h.behind_dex, h.behind_transfers'\n\nexport function contractQuery(address: string): string {\n return (\n `USE topology MATCH (a:Address {address: \"${address}\"}) ` +\n 'RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT 1'\n )\n}\n\nexport function contractListQuery(addresses: string[]): string {\n return (\n `USE topology MATCH (a:Address) WHERE a.address IN [${addresses.map((a) => `\"${a}\"`).join(', ')}] ` +\n `RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT ${addresses.length}`\n )\n}\n\n/** The anchored reads of one exchange: Head, each contract, and the pools of each factory. */\nexport function lookupQueries(\n entry: DexEntry,\n pools: boolean\n): Array<{ id: string; query: string }> {\n const contracts = Object.keys(entry.roles)\n const queries: Array<{ id: string; query: string }> = [{ id: 'head', query: HEAD_QUERY }]\n if (contracts.length <= CONTRACT_MEMBERS_MAX) {\n for (const address of contracts)\n queries.push({ id: `contract:${address}`, query: contractQuery(address) })\n } else {\n queries.push({ id: 'contracts', query: contractListQuery(contracts) })\n }\n if (pools) {\n for (const factory of entry.factories.filter((address) => entry.roles[address] === 'factory')) {\n queries.push({ id: `pools:${factory}`, query: poolsByFactoryQuery(factory, POOLS_LIMIT) })\n }\n }\n return queries\n}\n\nfunction confirmation(\n row: Record<string, unknown> | undefined\n): Pick<ContractRow, 'confirmed' | 'confirmed_by'> {\n if (!row) return { confirmed: false }\n if (row.is_contract === true) return { confirmed: true, confirmed_by: 'is_contract' }\n if (stringList(row.node_labels).includes('Contract'))\n return { confirmed: true, confirmed_by: ':Contract' }\n return { confirmed: false }\n}\n\nfunction readMatch(\n entry: DexEntry,\n members: Map<string, Member>,\n read: boolean\n): {\n contracts: Map<string, ContractRow>\n pools: PoolRow[]\n families: FamilyLag | null\n notes: string[]\n} {\n const notes: string[] = []\n const contracts = new Map<string, ContractRow>()\n const listed = new Map(rowsOf(members.get('contracts')).map((row) => [String(row.address), row]))\n for (const [address, role] of Object.entries(entry.roles)) {\n const row = members.has(`contract:${address}`)\n ? rowsOf(members.get(`contract:${address}`))[0]\n : listed.get(address)\n contracts.set(address, { address, role, ...confirmation(row) })\n }\n const byAddress = new Map<string, PoolRow>()\n for (const [id, member] of members) {\n if (!id.startsWith('pools:')) continue\n for (const row of rowsOf(member)) {\n const address = typeof row.to_address === 'string' ? row.to_address : null\n const volume = numberOrNull(row.total_volume_usd)\n if (!address || volume === null || byAddress.has(address)) continue\n byAddress.set(address, {\n address,\n labels: stringList(row.to_labels),\n total_volume_usd: volume,\n tx_total_count: numberOrNull(row.tx_total_count) ?? 0,\n })\n }\n }\n const pools = [...byAddress.values()]\n .sort((a, b) => b.total_volume_usd - a.total_volume_usd)\n .slice(0, POOLS_LIMIT)\n const head = rowsOf(members.get('head'))[0]\n const families: FamilyLag | null = head\n ? {\n behind_money: numberOrNull(column(head, 'behind_money', 'h')),\n behind_dex: numberOrNull(column(head, 'behind_dex', 'h')),\n behind_transfers: numberOrNull(column(head, 'behind_transfers', 'h')),\n }\n : null\n if (read) {\n for (const [id, member] of members) {\n if (member.ok !== true)\n notes.push(\n `${id}: ${typeof member.error === 'string' ? member.error : 'the read was not served'}`\n )\n }\n }\n return { contracts, pools, families, notes }\n}\n\nconst ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/\n\nfunction hintFor(\n result: Omit<MetaDexLookupResult, 'hint'>,\n name: string | null,\n address: string | null = null\n): string {\n if (address !== null && !result.matched) {\n return `${ANSWER_HINT} No exchange row holds ${address}: it is not a registered exchange contract. Send no exchange read for it; read the graph for what the address did.`\n }\n if (address !== null && result.matched) {\n const { matched } = result\n return (\n `${ANSWER_HINT} Say in one line: \"${address} is a ${result.took_for} row of ${matched.display}.\" Ask nothing. ` +\n `Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (name === null) {\n return `${ANSWER_HINT} These are the exchanges the registry covers. Call meta_dex_lookup with a name for its addresses and first reads.`\n }\n if (result.matched) {\n const { matched } = result\n const took = result.took_for\n ? `Say in one line: \"I took ${matched.display} for ${result.took_for}.\" Ask nothing. `\n : ''\n return (\n `${ANSWER_HINT} ${took}Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (result.candidates.length > 0) {\n const names = result.candidates\n .map((candidate) => `${candidate.display} (${candidate.dex})`)\n .join(', ')\n return `${ANSWER_HINT} ${result.candidates.length} rows fit \"${name}\": ${names}. Name both or pick one: call meta_dex_lookup with each key. Never guess one, never ask the user for an address.`\n }\n return `${ANSWER_HINT} ${name} is not covered. Send no query for the name: say so in one line and list the covered exchanges by name.`\n}\n\nfunction textResult(structured: MetaDexLookupResult): MetaDexLookupToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],\n structuredContent: structured as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\n/** meta_dex_lookup, {network, name?, address?, pools?}. Every argument is checked before the registry is read. */\nexport async function handleMetaDexLookup(\n args: unknown,\n deps: DexToolDependencies\n): Promise<MetaDexLookupToolResult> {\n const record = isRecord(args) ? args : {}\n let network: string\n try {\n network = normalizeDexNetwork(record.network)\n } catch (err) {\n return errorResult((err as Error).message)\n }\n const rawName = record.name\n if (rawName !== undefined && rawName !== null && typeof rawName !== 'string') {\n return errorResult('invalid_name: name must be the exchange name as text')\n }\n const name = typeof rawName === 'string' && rawName.trim() !== '' ? rawName.trim() : null\n const rawAddress = record.address\n if (rawAddress !== undefined && rawAddress !== null && typeof rawAddress !== 'string') {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const addressText = typeof rawAddress === 'string' && rawAddress.trim() !== '' ? rawAddress.trim() : null\n if (name !== null && addressText !== null) return errorResult('Give a name or an address, not both.')\n if (addressText !== null && !ADDRESS_FORM.test(addressText)) {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const address = addressText === null ? null : addressText.toLowerCase()\n const rawPools = record.pools\n if (rawPools !== undefined && rawPools !== null && typeof rawPools !== 'boolean') {\n return errorResult('invalid_pools: pools must be true or false')\n }\n const pools = rawPools !== false\n\n let loaded: LoadedRegistry\n try {\n loaded = await deps.loadRegistry(network)\n } catch (err) {\n return errorResult(\n err instanceof DexRegistryError\n ? err.message\n : `exchange registry unavailable: ${(err as Error).message}`\n )\n }\n const { document } = loaded\n const base = {\n schema: META_DEX_LOOKUP_SCHEMA,\n network,\n registry_version: document.registry_version,\n registry_source: loaded.source,\n }\n\n if (name === null && address === null) {\n const result = {\n ...base,\n matched: null,\n candidates: [],\n covered: document.rows.map((row) => row.display),\n exchanges: document.rows.map((row) => ({\n dex: row.dex,\n display: row.display,\n trail_type: row.trail_type,\n factories: row.factories,\n routers: row.routers,\n })),\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, null) })\n }\n\n let match: DexMatchResult\n if (address !== null) {\n const holder = document.rows.find((row) => address in row.roles)\n match = {\n matched: holder ?? null,\n ...(holder ? { took_for: `${holder.roles[address]}, served` } : {}),\n candidates: [],\n covered: document.rows.map((row) => row.display),\n }\n } else {\n match = matchDex(name as string, document.rows)\n }\n if (!match.matched) {\n const result = {\n ...base,\n matched: null,\n candidates: match.candidates,\n covered: match.covered,\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n }\n\n const entry = match.matched\n const queries = lookupQueries(entry, pools)\n let members = new Map<string, Member>()\n const notes: string[] = []\n const unavailable = await deps.unavailable?.()\n if (unavailable) {\n notes.push(\n `No graph read was sent: ${unavailable} The contracts are the reviewed registry rows, not yet confirmed on the graph.`\n )\n } else {\n try {\n const answer = await deps.graphQueryBatch({ network, queries })\n members = membersOf(answer)\n if (answer.isError === true && members.size === 0) {\n const first = Array.isArray(answer.content) ? answer.content[0] : undefined\n notes.push(\n `The graph refused the reads: ${isRecord(first) && typeof first.text === 'string' ? first.text : 'no reason given'}`\n )\n }\n } catch (err) {\n notes.push(`The graph reads failed: ${deps.describeFailure(err)}`)\n }\n }\n const read = readMatch(entry, members, members.size > 0)\n notes.push(...read.notes)\n const contractsOf = (role: Role): ContractRow[] =>\n [...read.contracts.values()].filter((contract) => contract.role === role)\n const topPool = read.pools[0]?.address ?? null\n const matched: DexMatch = {\n dex: entry.dex,\n display: entry.display,\n trail_type: entry.trail_type,\n skill: TRAIL_SKILLS[entry.trail_type],\n factories: contractsOf('factory'),\n routers: contractsOf('router'),\n manager: contractsOf('manager')[0] ?? null,\n pools: read.pools,\n recipe: recipeFor(entry, topPool),\n }\n const result = {\n ...base,\n matched,\n ...(match.took_for ? { took_for: match.took_for } : {}),\n candidates: [],\n covered: match.covered,\n families: read.families,\n ...(notes.length > 0 ? { notes } : {}),\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n}\n"],"mappings":"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"}
@@ -2,13 +2,13 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
3
3
  const require_print_result = require("./print-result-qVhhuSdo.cjs");
4
4
  const require_client = require("./client-Ypw9BNbn.cjs");
5
- const require_tool_visibility = require("./tool-visibility-BopFnGsT.cjs");
5
+ const require_tool_visibility = require("./tool-visibility-DvKqmHo3.cjs");
6
6
  const require_usage_status = require("./usage-status-DonwoLR3.cjs");
7
7
  const require_subscription_status = require("./subscription-status-ru99UCPZ.cjs");
8
8
  const require_capabilities = require("./capabilities-DGavTSoW.cjs");
9
9
  const require_graph_schema = require("./graph-schema-BoUMa03H.cjs");
10
10
  const require_action_log = require("./action-log-HniMMF7f.cjs");
11
- const require_dex_tool = require("./dex-tool-DqAHl4_z.cjs");
11
+ const require_dex_tool = require("./dex-tool-B4T8Czhm.cjs");
12
12
  const require_dex_registry = require("./dex-registry-KOTJhAQf.cjs");
13
13
  let node_url = require("node:url");
14
14
  let node_path = require("node:path");
@@ -1,12 +1,12 @@
1
1
  import { a as withTimestampTextResult, c as PACKAGE_VERSION, o as withoutUtcProperties } from "./print-result-CJ8d7lmz.mjs";
2
2
  import { t as PaymentRequiredError } from "./client-gsLi528K.mjs";
3
- import { i as isAppOnlyTool, n as PUBLIC_MCP_TOOL_ALLOWED_ARGS, r as PUBLIC_MCP_TOOL_REQUIRED_ARGS, t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-Dq-Ruj-8.mjs";
3
+ import { i as isAppOnlyTool, n as PUBLIC_MCP_TOOL_ALLOWED_ARGS, r as PUBLIC_MCP_TOOL_REQUIRED_ARGS, t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-D-pFpuoz.mjs";
4
4
  import { n as primitiveBackendUsageStatus, t as freeTierSpentText } from "./usage-status-Frot6fPG.mjs";
5
5
  import { n as unavailableSubscriptionStatus } from "./subscription-status-DA9DCFrC.mjs";
6
6
  import { r as mirrorGraphNetworkCapabilities } from "./capabilities-BBVDIECk.mjs";
7
7
  import { a as handleMetaSchema, c as GRAPH_EXPAND_TITLE, l as GRAPH_EXPAND_TOOL, n as META_SCHEMA_DESCRIPTION, o as FLOWS_MAX_OFFSET, r as META_SCHEMA_TITLE, s as GRAPH_EXPAND_DESCRIPTION, t as GraphSchemaError, u as handleGraphExpand } from "./graph-schema-DqZUTnP0.mjs";
8
8
  import { r as appendActionLog, t as actionLogSignalsFromResult } from "./action-log-D49Par9P.mjs";
9
- import { i as META_DEX_LOOKUP_TOOL, n as META_DEX_LOOKUP_DESCRIPTION, o as handleMetaDexLookup, r as META_DEX_LOOKUP_TITLE, s as withAnswerHintResult, t as META_DEX_LOOKUP_ANNOTATIONS } from "./dex-tool-CeggX4M4.mjs";
9
+ import { i as META_DEX_LOOKUP_TOOL, n as META_DEX_LOOKUP_DESCRIPTION, o as handleMetaDexLookup, r as META_DEX_LOOKUP_TITLE, s as withAnswerHintResult, t as META_DEX_LOOKUP_ANNOTATIONS } from "./dex-tool-B5Qwz1Zd.mjs";
10
10
  import { r as loadRegistry } from "./dex-registry-05wAhSa0.mjs";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import path from "node:path";
@@ -51,6 +51,7 @@ const PUBLIC_MCP_TOOL_ALLOWED_ARGS = {
51
51
  meta_dex_lookup: [
52
52
  "network",
53
53
  "name",
54
+ "address",
54
55
  "pools"
55
56
  ]
56
57
  };
@@ -91,4 +92,4 @@ function validatePublicMcpToolArguments(name, args) {
91
92
  //#endregion
92
93
  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 };
93
94
 
94
- //# sourceMappingURL=tool-visibility-Dq-Ruj-8.mjs.map
95
+ //# sourceMappingURL=tool-visibility-D-pFpuoz.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-visibility-D-pFpuoz.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', 'address', '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;EAAW;CAAO;AACzD;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"}
@@ -51,6 +51,7 @@ const PUBLIC_MCP_TOOL_ALLOWED_ARGS = {
51
51
  meta_dex_lookup: [
52
52
  "network",
53
53
  "name",
54
+ "address",
54
55
  "pools"
55
56
  ]
56
57
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chain-insights",
3
- "version": "0.71.0",
3
+ "version": "0.71.3",
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": {
@@ -93,7 +93,7 @@
93
93
  "vitest": "^5.0.3"
94
94
  },
95
95
  "chainInsights": {
96
- "dexRegistryVersion": 2
96
+ "dexRegistryVersion": 3
97
97
  },
98
98
  "overrides": {
99
99
  "ws": "8.21.0",
@@ -22,8 +22,8 @@ flowchart LR
22
22
 
23
23
  | File | Rows served | Why |
24
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 |
25
+ | [robinhood.md](robinhood.md) | 47 | registry version 3, recorded 2026-10-25; the `dyor` row and the Ekubo positions row are not served |
26
+ | [robinhood.json](robinhood.json) | 31 exchanges | the served copy of `robinhood.md`, never edited by hand |
27
27
 
28
28
  A change to the rows merged on `main` reaches every running proxy within one
29
29
  hour. No release is needed for a data change.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "chain-insights.dex-registry.v1",
3
3
  "network": "robinhood",
4
- "registry_version": 2,
5
- "recorded_on": "2026-10-24",
4
+ "registry_version": 3,
5
+ "recorded_on": "2026-10-25",
6
6
  "rows": [
7
7
  {
8
8
  "dex": "aero",
@@ -519,6 +519,32 @@
519
519
  "since_block": 0,
520
520
  "source": "https://sourcify.dev/server/v2/contract/4663/0x28082618ba2073e602230188e4f4c46e9b2169eb"
521
521
  },
522
+ {
523
+ "dex": "ekubo",
524
+ "display": "Ekubo",
525
+ "aliases": [
526
+ "Ekubo",
527
+ "Ekubo Protocol",
528
+ "ekubo.org"
529
+ ],
530
+ "trail_type": "vault",
531
+ "adapters": [
532
+ "ekubo-core"
533
+ ],
534
+ "roles": {
535
+ "0x00000000000014aa86c5d3c41765bb24e11bd701": "manager"
536
+ },
537
+ "factories": [
538
+ "0x00000000000014aa86c5d3c41765bb24e11bd701"
539
+ ],
540
+ "routers": [
541
+ "0x00000000000014aa86c5d3c41765bb24e11bd701"
542
+ ],
543
+ "example_pool": null,
544
+ "pool_floor": null,
545
+ "since_block": 33534,
546
+ "source": "https://github.com/EkuboProtocol/evm-contracts/blob/933c813ca154475a3c7eab8234869f6db151c682/broadcast/DeployAll.s.sol/4663/run-latest.json"
547
+ },
522
548
  {
523
549
  "dex": "ramses",
524
550
  "display": "Ramses",
@@ -718,6 +744,33 @@
718
744
  "since_block": 0,
719
745
  "source": "https://github.com/chainswarm/devops/blob/de68f8676adac3e2f004fb0cd24c313f9516846e/charts/chainswarm-indexer-evm-dex/registry/robinhood-v24.json"
720
746
  },
747
+ {
748
+ "dex": "spcx",
749
+ "display": "SPCX launchpad",
750
+ "aliases": [
751
+ "SPCX",
752
+ "SPCX launchpad",
753
+ "spcx launchpad"
754
+ ],
755
+ "trail_type": "launchpad",
756
+ "adapters": [
757
+ "spcx-launchpad"
758
+ ],
759
+ "roles": {
760
+ "0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091": "factory",
761
+ "0x190b452d16e12e3beedd76a030ea991fe3856c33": "router"
762
+ },
763
+ "factories": [
764
+ "0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091"
765
+ ],
766
+ "routers": [
767
+ "0x190b452d16e12e3beedd76a030ea991fe3856c33"
768
+ ],
769
+ "example_pool": "0x1de3f839184cf48696cbb9cc283855eb32dc2c51",
770
+ "pool_floor": 1,
771
+ "since_block": 0,
772
+ "source": "https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md"
773
+ },
721
774
  {
722
775
  "dex": "pons",
723
776
  "display": "pons",
@@ -6,9 +6,9 @@ columns and the check are in the [README](README.md). The served copy is
6
6
  [robinhood.json](robinhood.json), written by
7
7
  `node scripts/pin-dex-registry.mjs registry/robinhood.md`.
8
8
 
9
- Registry version: 2.
9
+ Registry version: 3.
10
10
 
11
- Recorded on: 2026-10-24.
11
+ Recorded on: 2026-10-25.
12
12
 
13
13
  ## Rows
14
14
 
@@ -46,6 +46,8 @@ Recorded on: 2026-10-24.
46
46
  | orvex | Orvex | Orvex, Orvex MetaDEX, orvex.fi, ORVX | vault | pancake-infinity-cl | manager | `0xd01c774d4a66408326bc65728ac5ae5aaf004032` | 0 | - | - | [DefiLlama orvex](https://github.com/DefiLlama/dimension-adapters/blob/master/dexs/orvex.ts) | served |
47
47
  | swaap | Swaap v2 | Swaap, Swaap Finance, swaap.xyz, Balancer, Balancer v2, Balancer v2 vault | vault | balancer-v2 | manager | `0xd315a9c38ec871068fec378e4ce78af528c76293` | 0 | - | - | [Swaap docs](https://docs.swaap.xyz/) | served |
48
48
  | balancer-v3 | unnamed Balancer v3 vault | Balancer, Balancer v3, Balancer v3 vault | vault | balancer-v3 | manager | `0x28082618ba2073e602230188e4f4c46e9b2169eb` | 0 | - | - | [Sourcify verified source](https://sourcify.dev/server/v2/contract/4663/0x28082618ba2073e602230188e4f4c46e9b2169eb) | served |
49
+ | ekubo | Ekubo | Ekubo, Ekubo Protocol, ekubo.org | vault | ekubo-core | manager | `0x00000000000014aa86c5d3c41765bb24e11bd701` | 33534 | - | - | [Ekubo robinhood deployment](https://github.com/EkuboProtocol/evm-contracts/blob/933c813ca154475a3c7eab8234869f6db151c682/broadcast/DeployAll.s.sol/4663/run-latest.json) | served |
50
+ | ekubo | Ekubo | | vault | ekubo-core | positions | `0xda38ac72ce7220c4dd7719d114ef94edadb8f068` | 18272006 | - | - | [Ekubo robinhood Ve33 deployment](https://github.com/EkuboProtocol/evm-contracts/blob/933c813ca154475a3c7eab8234869f6db151c682/broadcast/DeployVe33.s.sol/4663/run-latest.json) | not served: the positions NFT creates no pool, holds no pool and forwards no swap; the registry serves the roles factory, manager and router only |
49
51
  | ramses | Ramses | Ramses, Ramses Exchange, RAM, ramses.xyz, Ramses DLMM | plain | trader-joe-lb | factory | `0xdcd5f77697914e27f56fd263ef82923c8524abac` | 0 | 0x06ffaa507a9c3e7025294a642f1e1c75da3dfb4a | 150 | [Ramses contract addresses](https://www.ramses.xyz/docs/contract-addresses) | served |
50
52
  | ramses | Ramses | | plain | uniswap-v2 | factory | `0x43b2bf9f33036a02fc7a00935571c2a6b0108e66` | 0 | - | - | [Ramses contract addresses](https://www.ramses.xyz/docs/contract-addresses) | served |
51
53
  | ramses | Ramses | | plain | trader-joe-lb | router | `0xd0019e86edb35e1fedaab03aed5c3c60f115d28b` | 0 | - | - | [Ramses contract addresses](https://www.ramses.xyz/docs/contract-addresses) | served |
@@ -57,8 +59,8 @@ Recorded on: 2026-10-24.
57
59
  | curve | Curve | Curve, Curve Finance, CRV, curve.finance | plain | curve-stable | factory | `0x8271e06e5887fe5ba05234f5315c19f3ec90e8ad` | 0 | 0x7e0539140f16b3a8ed23ad69d6fea6a38fb8b299 | 25 | [curve-core deployments](https://github.com/curvefi/curve-core/blob/main/deployments/prod/robinhood.yaml) | served |
58
60
  | metric | Metric | Metric, Metric OMM, metric.xyz | plain | metric-omm | factory | `0x2e0a99a2e352043d1ca489cf45bf7b511bccc738` | 0 | 0x584ac6010b095bf99398fae8203a3260a03d2b30 | 30 | [Metric SDK addresses](https://github.com/Metricxyz/sdk/blob/main/src/addresses.ts) | served |
59
61
  | penguin | Penguin | Penguin, Penguin launchpad | launchpad | penguin-launchpad | factory | `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | 0 | - | - | [devops registry v24](https://github.com/chainswarm/devops/blob/de68f8676adac3e2f004fb0cd24c313f9516846e/charts/chainswarm-indexer-evm-dex/registry/robinhood-v24.json) | served |
60
- | spcx | SPCX launchpad | SPCX, SPCX launchpad, spcx launchpad | launchpad | spcx-launchpad | factory | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | 0 | 0x1de3f839184cf48696cbb9cc283855eb32dc2c51 | 1 | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) | not served: adapter not rolled |
61
- | spcx | SPCX launchpad | | launchpad | spcx-launchpad | router | `0x190b452d16e12e3beedd76a030ea991fe3856c33` | 0 | - | - | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) | not served: adapter not rolled |
62
+ | spcx | SPCX launchpad | SPCX, SPCX launchpad, spcx launchpad | launchpad | spcx-launchpad | factory | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | 0 | 0x1de3f839184cf48696cbb9cc283855eb32dc2c51 | 1 | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) | served |
63
+ | spcx | SPCX launchpad | | launchpad | spcx-launchpad | router | `0x190b452d16e12e3beedd76a030ea991fe3856c33` | 0 | - | - | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) | served |
62
64
  | pons | pons | pons, pons launchpad | launchpad | bonding-curve | factory | `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | 0 | - | - | [pons deployment](https://github.com/DefiLlama/DefiLlama-Adapters/blob/main/projects/pons-v2/index.js) | served |
63
65
  | virtuals | Virtuals Protocol | Virtuals, VIRTUAL, Virtuals Protocol | launchpad | uniswap-v2 | factory | `0xfc2e4da3edb2e18100473339c763705d263d20a9` | 0 | - | - | [change silent-factory-discovery-and-labels](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/archive/2026-10-16-silent-factory-discovery-and-labels/design.md) | served |
64
66
 
@@ -88,6 +90,10 @@ swaps it forwarded.
88
90
  | `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | factory | created 48 contracts; none carries `:Pool` on lane A yet |
89
91
  | `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | factory | 1,469,383 transactions on the contract itself; created 1 contract, no `:Pool` yet |
90
92
  | `0x8bceaa40b9acdfaedf85adf4ff01f5ad6517937f` | factory | 27,926 `uniswap-v2` pools, 10,573,207,859 USD. Uniswap's own v2 factory per its deployments page; Virtuals agents create most of its pairs, which is why the indexer registry stacks it as `virtuals`. The exchange row is `uniswap-v2`; `0xfc2e4da3edb2e18100473339c763705d263d20a9` is the Virtuals bonding factory per the Virtuals change |
93
+ | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | factory | read from the production node on 2026-10-25: its creation event names the launched token, its curve and the creator (sample transaction `0x77b1bcc700d5f1d7513d080d3fe265831529c237065fffed83e665f89cc3f019`, block 95,797,703); 2,182 curves, quoted in SPCX `0x4a0e65a3eccec6dbe60ae065f2e7bb85fae35eea` and two other stock tokens. The example curve `0x1de3f839184cf48696cbb9cc283855eb32dc2c51` logged 1,028 trades in 7M blocks. No source names the operator, so the key stays `spcx` |
94
+ | `0x190b452d16e12e3beedd76a030ea991fe3856c33` | router | the SPCX legs of the sample trade `0xe200a831908a9b937e08eb4c16fdefd016f606980b9ef71f788994c76020708e` run trader, this contract, curve. The trader stays the payer: the router is never a swap end |
95
+ | `0x00000000000014aa86c5d3c41765bb24e11bd701` | manager | the Ekubo core, deployed at block 33,534 per Ekubo's robinhood deployment file. Read from the production node on 2026-10-25 over 20,000 blocks: 111 swaps and 65 position updates logged by the core itself, 28 pools initialized, each pool kept by a 32-byte pool id. Sample transaction `0x7926c9fab003029a42e7f398e3183611c49583a082b4aac781003cb43f5a96d9`, block 97,745,015 |
96
+ | `0xda38ac72ce7220c4dd7719d114ef94edadb8f068` | positions | "Ekubo Ve33 Positions", deployed at block 18,272,006 with the core as its first argument per Ekubo's Ve33 deployment file. It owns the core's position updates and mints one NFT per position. It is not served: it creates no pool, holds no pool and forwards no swap, and a liquidity owner is the NFT's holder, never this contract |
91
97
 
92
98
  `0x0d1ebb179cdbca88d74c923c4255cb2b17474afd` created 254,650 `uniswap-v2`
93
99
  pairs with 38 USD of volume in all: the Flap launchpad's pair factory per
@@ -102,3 +108,8 @@ Registry version 2 (2026-10-24) moved the file from the private registry of
102
108
  version 1 to this repository, renamed the key column `dex`, and gave `Balancer`
103
109
  to the two Balancer vaults (`balancer-v3` and `swaap`), so a question that says
104
110
  "Balancer" lists both instead of guessing one.
111
+
112
+ Registry version 3 (2026-10-25) serves the SPCX launchpad and Ekubo. Both
113
+ adapters (`spcx-launchpad`, `ekubo-core`) run on lane A and lane B from
114
+ indexer registry version 27, rolled at 21:42 CET. Their backfills were still
115
+ running at the flip, so neither exchange carries a pool floor yet.
@@ -85,7 +85,7 @@ hand. When the tool and the memo differ, follow the tool: it reads the
85
85
  newest file.
86
86
 
87
87
  <!-- dex-registry:start -->
88
- Memo of the exchange registry, version 2, robinhood, recorded 2026-10-24.
88
+ Memo of the exchange registry, version 3, robinhood, recorded 2026-10-25.
89
89
 
90
90
  | Exchange | Key | Also written | Trail | Factories, or the manager | Router |
91
91
  |---|---|---|---|---|---|
@@ -108,6 +108,7 @@ Memo of the exchange registry, version 2, robinhood, recorded 2026-10-24.
108
108
  | Orvex | `orvex` | Orvex, Orvex MetaDEX, orvex.fi, ORVX | vault | manager `0xd01c774d4a66408326bc65728ac5ae5aaf004032` | the manager |
109
109
  | Swaap v2 | `swaap` | Swaap, Swaap Finance, swaap.xyz, Balancer, Balancer v2, Balancer v2 vault | vault | manager `0xd315a9c38ec871068fec378e4ce78af528c76293` | the manager |
110
110
  | unnamed Balancer v3 vault | `balancer-v3` | Balancer, Balancer v3, Balancer v3 vault | vault | manager `0x28082618ba2073e602230188e4f4c46e9b2169eb` | the manager |
111
+ | Ekubo | `ekubo` | Ekubo, Ekubo Protocol, ekubo.org | vault | manager `0x00000000000014aa86c5d3c41765bb24e11bd701` | the manager |
111
112
  | Ramses | `ramses` | Ramses, Ramses Exchange, RAM, ramses.xyz, Ramses DLMM | plain | `0xdcd5f77697914e27f56fd263ef82923c8524abac`, `0x43b2bf9f33036a02fc7a00935571c2a6b0108e66` | `0xd0019e86edb35e1fedaab03aed5c3c60f115d28b` |
112
113
  | SectorOne | `sectorone` | SectorOne, SectorOne DLMM | plain | `0x3d8dc63ddc9b992cfde89f7aea6f18717e84687d` | none |
113
114
  | Hoodit | `hoodit` | Hoodit, hoodit.xyz | plain | `0x22602d966defd638ee94e97a92e2eb0934c3fe1b` | none |
@@ -116,6 +117,7 @@ Memo of the exchange registry, version 2, robinhood, recorded 2026-10-24.
116
117
  | Curve | `curve` | Curve, Curve Finance, CRV, curve.finance | plain | `0x8271e06e5887fe5ba05234f5315c19f3ec90e8ad` | none |
117
118
  | Metric | `metric` | Metric, Metric OMM, metric.xyz | plain | `0x2e0a99a2e352043d1ca489cf45bf7b511bccc738` | none |
118
119
  | Penguin | `penguin` | Penguin, Penguin launchpad | launchpad | `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | none |
120
+ | SPCX launchpad | `spcx` | SPCX, SPCX launchpad, spcx launchpad | launchpad | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | `0x190b452d16e12e3beedd76a030ea991fe3856c33` |
119
121
  | pons | `pons` | pons, pons launchpad | launchpad | `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | none |
120
122
  | Virtuals Protocol | `virtuals` | Virtuals, VIRTUAL, Virtuals Protocol | launchpad | `0xfc2e4da3edb2e18100473339c763705d263d20a9` | none |
121
123
  <!-- dex-registry:end -->
@@ -1,15 +1,17 @@
1
1
  ---
2
2
  name: chain-insights-dex-launchpad
3
3
  door: meta_dex_lookup
4
- description: Use for the dex-launchpad trail - token launchpads with a bonding curve on robinhood other than Virtuals, such as Penguin and the unnamed bonding-curve launchpad - when a question asks who launched a token there, who bought or sold on its curve, whether and when it graduated, or which pool it moved to. Loaded by chain-insights-dex for a launchpad row of its memo. Virtuals questions belong to chain-insights-virtuals.
4
+ description: Use for the dex-launchpad trail - token launchpads with a bonding curve on robinhood other than Virtuals, such as Penguin, the SPCX launchpad and the unnamed bonding-curve launchpad, and the unnamed v4 launch factories that create a token and open its Uniswap v4 pool in one transaction - when a question asks who launched a token there or through a factory address, who bought or sold on its curve, whether and when it graduated, or which pool it moved to. Loaded by chain-insights-dex for a launchpad row of its memo. Virtuals questions belong to chain-insights-virtuals.
5
5
  ---
6
6
 
7
- # Chain Insights: the launchpad trail (Penguin, bonding curves)
7
+ # Chain Insights: the launchpad trail (Penguin, SPCX, bonding curves)
8
8
 
9
9
  Load `chain-insights-cypher` first. The first step for a launchpad named
10
- with no address is `meta_dex_lookup` (Penguin, pons, Flap): it returns the
10
+ with no address is `meta_dex_lookup` (Penguin, SPCX, pons, Flap): it returns the
11
11
  launchpad's factory and its first reads. With no such tool, the memo of
12
- `chain-insights-dex` names the factory. Their answer rules hold here:
12
+ `chain-insights-dex` names the factory. A contract address that no row
13
+ holds may be a v4 launch factory: see "A v4 launch factory" below. Their
14
+ answer rules hold here:
13
15
  every address in full, tokens worth an amount in USD, no query text in the
14
16
  answer, times from the `_utc` twins.
15
17
 
@@ -31,6 +33,7 @@ A launchpad token lives in two stages:
31
33
  | Launchpad | Factory | What the factory deploys |
32
34
  |---|---|---|
33
35
  | Penguin | `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | the launched token, which is also its own curve |
36
+ | SPCX launchpad | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | the launched token and its curve, a contract of its own, quoted in SPCX or another stock token |
34
37
  | unnamed launchpad | `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | a launch contract; its name is not proven yet |
35
38
 
36
39
  Call the second one "an unnamed bonding-curve launchpad". Never give it a
@@ -50,7 +53,9 @@ name you have not read.
50
53
  3. **Who bought and sold on the curve** (L2): the launched contract's own
51
54
  `FLOWS_TO` links, both directions, ranked by transactions. Money in is a
52
55
  buy, money out is a payout. On Penguin the token contract is the curve,
53
- so these are its traders.
56
+ so these are its traders. On the SPCX launchpad the curve is a contract
57
+ of its own and a `:Pool`: its traders are the `SWAPPED` links into the
58
+ curve, and the token's own links are not the curve stage.
54
59
  4. A fresh token has no price: its `amount_usd` can be empty. Value a buy by
55
60
  what was paid for it, and say when no price exists.
56
61
 
@@ -64,6 +69,51 @@ name you have not read.
64
69
  7. After graduation the token trades on that pool. Load `chain-insights-dex`
65
70
  for the pool's exchange and its trail.
66
71
 
72
+ ## A v4 launch factory
73
+
74
+ Some launchpads have no name and no registry row, on purpose. The DEX
75
+ indexer finds them by their shape, with one rule for every operator:
76
+ `v4-launch-factory-v1`.
77
+
78
+ ```
79
+ creator ── one transaction ──▶ factory F ──DEPLOYED_CONTRACT──▶ token
80
+ │
81
+ └──▶ Uniswap v4 pool manager opens a pool
82
+ of that token: its first pool, a pool id
83
+ ```
84
+
85
+ - **The shape.** In one transaction a contract F creates a token, and the
86
+ Uniswap v4 pool manager `0x8366a39cc670b4001a1121b8f6a443a643e40951`
87
+ opens a pool holding that token. F is a launch factory.
88
+ - **A launchpad needs two launches.** F is a launchpad when it repeats the
89
+ shape: two tokens or more. A contract with one launch is a one-off: say
90
+ "a contract that launched one token", never a launchpad.
91
+ - **No name, no row.** `meta_dex_lookup` with F's address answers
92
+ `matched: null`. For F that means "no exchange row", not "no data": read
93
+ the graph below. Never give F a name you have not read.
94
+ - **What the graph holds today.** The launches are rows of the rule
95
+ `v4-launch-factory-v1` in the warehouse. The graph does not mark F yet:
96
+ no `:Launchpad`, no `:Pool`, no rule name in its `labels`. The
97
+ `:Launchpad` mark for a factory of two launches or more is coming in the
98
+ graph's pools layer. Until a read of F shows it, do not ask by mark for
99
+ F: read the shape from its links.
100
+
101
+ 1. **The launches**: L1, anchored on F. Each `DEPLOYED_CONTRACT` link is a
102
+ contract F created. Count the tokens to say whether F is a launchpad.
103
+ 2. **Who launched a token**: `tx_origin` on the link, the sender of the
104
+ launch transaction. F is the factory, never the creator. A launch sent
105
+ through a bundler of contract wallets names the bundler, not the user:
106
+ say so when the same `tx_origin` sends many unrelated launches.
107
+ 3. **The first pool** (L4): the token's Uniswap v4 pool, kept inside the
108
+ pool manager by a 32-byte pool id. It is not a contract of its own: say
109
+ "a pool of the Uniswap v4 pool manager". Read the id from `pools` on a
110
+ `SWAPPED` link that sold or bought the token, anchored on the creator.
111
+ When no such link shows, say the graph does not serve that pool id yet.
112
+ The creator and the launch transaction still answer who launched it.
113
+ 4. **The trades** are ordinary Uniswap v4 swaps on the pool manager. Load
114
+ `chain-insights-dex-vault` and follow its trail. The pool manager is
115
+ never the buyer, and no topology hop goes out of it.
116
+
67
117
  ## The next lead
68
118
 
69
119
  End with who to trace next: the creator's first funders (the newest-links
@@ -95,3 +145,11 @@ USE topology
95
145
  MATCH (t:Address {address: "0xf3b01a81dc8a4216d92e74b40113e9e1b10a4dac"})-[l:ADDED_LIQUIDITY]->(p:Pool)
96
146
  RETURN t.address AS from_address, p.address AS to_address, p.labels AS to_labels, l.asset AS asset, l.usd AS usd, l.first_seen_height AS first_seen_height ORDER BY l.first_seen_height ASC LIMIT 5
97
147
  ```
148
+
149
+ ### L4. The first pool of a v4 launch, from the creator's swaps
150
+
151
+ ```cypher
152
+ USE topology
153
+ MATCH (c:Address {address: "0x705da6eb557a987960294597954d75e9be2a1983"})-[sw:SWAPPED]->(m:Address {address: "0x8366a39cc670b4001a1121b8f6a443a643e40951"}) WHERE sw.sold_asset = "0xf3b01a81dc8a4216d92e74b40113e9e1b10a4dac" OR sw.bought_asset = "0xf3b01a81dc8a4216d92e74b40113e9e1b10a4dac"
154
+ RETURN c.address AS from_address, m.address AS to_address, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ORDER BY sw.last_seen_height ASC LIMIT 25
155
+ ```
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: chain-insights-dex-vault
3
3
  door: meta_dex_lookup
4
- description: Use for the dex-vault trail - exchanges where one pool manager contract holds every pool, such as Uniswap v4, Balancer v2, Balancer v3 and PancakeSwap Infinity on robinhood - when a seller's swap names the pool manager or vault, when a question asks who bought past the manager, or which pools such an exchange has. Loaded by chain-insights-dex for a vault row of its memo.
4
+ description: Use for the dex-vault trail - exchanges where one pool manager contract holds every pool, such as Uniswap v4, Balancer v2, Balancer v3, PancakeSwap Infinity and Ekubo on robinhood - when a seller's swap names the pool manager or vault, when a question asks who bought past the manager, or which pools such an exchange has. Loaded by chain-insights-dex for a vault row of its memo.
5
5
  ---
6
6
 
7
- # Chain Insights: the vault trail (Uniswap v4, Balancer, PancakeSwap Infinity)
7
+ # Chain Insights: the vault trail (Uniswap v4, Balancer, PancakeSwap Infinity, Ekubo)
8
8
 
9
9
  Load `chain-insights-cypher` first, then `chain-insights-dex`: the
10
10
  exchange's manager comes from `meta_dex_lookup`, or from the memo there when
@@ -13,8 +13,8 @@ address in full, tokens worth an amount in USD, no query text in the answer,
13
13
  times from the `_utc` twins.
14
14
 
15
15
  On a vault exchange one contract, the **pool manager** (Balancer calls it
16
- the vault), holds the tokens of every pool. A swap pays the manager and the
17
- manager pays out:
16
+ the vault, Ekubo the core), holds the tokens of every pool. A swap pays the
17
+ manager and the manager pays out:
18
18
 
19
19
  ```
20
20
  seller ──SWAPPED──▶ pool manager ──▶ buyer
@@ -32,6 +32,7 @@ buyer.** Name it for what it is, in full: "the Uniswap v4 pool manager
32
32
  | Balancer v2 | `0xd315a9c38ec871068fec378e4ce78af528c76293` |
33
33
  | Balancer v3 | `0x28082618ba2073e602230188e4f4c46e9b2169eb` |
34
34
  | PancakeSwap Infinity | `0xd01c774d4a66408326bc65728ac5ae5aaf004032` |
35
+ | Ekubo | `0x00000000000014aa86c5d3c41765bb24e11bd701` |
35
36
 
36
37
  ## The buyer past the manager
37
38
 
@@ -64,7 +65,12 @@ the `pools` of the swaps it settles:
64
65
  2. **The events of one pool**: take one seller from V4 and read its facts
65
66
  `SWAP` rows with the manager on one day (V3).
66
67
  3. A pool id here may name the pool, not a contract of its own. Say "a pool
67
- of the Uniswap v4 pool manager", never "a pool contract".
68
+ of the Uniswap v4 pool manager", never "a pool contract". Ekubo keeps
69
+ every pool by a 32-byte pool id the same way.
70
+ 4. On Ekubo, every position is opened through the positions NFT
71
+ `0xda38ac72ce7220c4dd7719d114ef94edadb8f068` (Ekubo Ve33 Positions). It is
72
+ shared infrastructure, never a liquidity provider: the provider is the
73
+ holder of the position's NFT.
68
74
 
69
75
  ## The next lead
70
76
 
@@ -69,7 +69,7 @@ agent is created on Uniswap's own v2 factory
69
69
  most of that factory's pairs are Virtuals agents.
70
70
 
71
71
  <!-- dex-registry:start -->
72
- The factories of the exchange registry, version 2, row `virtuals`:
72
+ The factories of the exchange registry, version 3, row `virtuals`:
73
73
 
74
74
  | Factory | Address |
75
75
  |---|---|
@@ -1 +0,0 @@
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"}