chain-insights 0.74.1 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.cjs +13 -13
  3. package/dist/cli.mjs +13 -13
  4. package/dist/{dex-tool-DLWnff1n.cjs → dex-tool-85_76Q8i.cjs} +5 -5
  5. package/dist/{dex-tool-DsaAIa-a.mjs → dex-tool-ib_ICiyV.mjs} +6 -6
  6. package/dist/dex-tool-ib_ICiyV.mjs.map +1 -0
  7. package/dist/{graph-schema-ClqjzgrM.mjs → graph-schema-Bxqwukqu.mjs} +5 -5
  8. package/dist/graph-schema-Bxqwukqu.mjs.map +1 -0
  9. package/dist/{graph-schema-BGbJumCY.cjs → graph-schema-DNJXDaTn.cjs} +4 -4
  10. package/dist/mcp-proxy.cjs +22 -22
  11. package/dist/mcp-proxy.mjs +22 -22
  12. package/dist/mcp-proxy.mjs.map +1 -1
  13. package/dist/{print-result-C3YazZBw.mjs → print-result-BtEAn2Wl.mjs} +2 -2
  14. package/dist/print-result-BtEAn2Wl.mjs.map +1 -0
  15. package/dist/{print-result-Cyqrlx8P.cjs → print-result-if-AbNLY.cjs} +1 -1
  16. package/dist/{public-tools-Bl_l_rKK.cjs → public-tools-COG3pCpD.cjs} +8 -8
  17. package/dist/{public-tools-CQjGt7_q.mjs → public-tools-DIJs4SHz.mjs} +9 -9
  18. package/dist/public-tools-DIJs4SHz.mjs.map +1 -0
  19. package/dist/{tool-visibility-BNwvcuVx.cjs → tool-visibility-DTAYvE9E.cjs} +1 -1
  20. package/dist/{tool-visibility-B6983rxo.mjs → tool-visibility-zcmwMSbn.mjs} +2 -2
  21. package/dist/{tool-visibility-B6983rxo.mjs.map → tool-visibility-zcmwMSbn.mjs.map} +1 -1
  22. package/dist/{transport-error-D4hmpkqL.mjs → transport-error-C7OQjmC7.mjs} +2 -2
  23. package/dist/{transport-error-D4hmpkqL.mjs.map → transport-error-C7OQjmC7.mjs.map} +1 -1
  24. package/dist/{transport-error-CgISST8l.cjs → transport-error-qDWQn8hb.cjs} +1 -1
  25. package/dist/{update-CZDHSi_u.mjs → update-DqBhBXFs.mjs} +2 -2
  26. package/dist/{update-CZDHSi_u.mjs.map → update-DqBhBXFs.mjs.map} +1 -1
  27. package/dist/{update-DGyRlm3J.cjs → update-NowrCmjR.cjs} +1 -1
  28. package/docs/graph-query-compatibility.md +13 -13
  29. package/docs/graph-tools.md +22 -22
  30. package/docs/mcp-proxy.md +1 -1
  31. package/package.json +1 -1
  32. package/skills/chain-insights-cypher/SKILL.md +27 -27
  33. package/skills/chain-insights-dex/SKILL.md +2 -2
  34. package/skills/chain-insights-dex/reference.md +1 -1
  35. package/skills/chain-insights-dex-gauge/SKILL.md +4 -4
  36. package/skills/chain-insights-dex-launchpad/SKILL.md +2 -2
  37. package/skills/chain-insights-virtuals/reference.md +5 -5
  38. package/dist/dex-tool-DsaAIa-a.mjs.map +0 -1
  39. package/dist/graph-schema-ClqjzgrM.mjs.map +0 -1
  40. package/dist/print-result-C3YazZBw.mjs.map +0 -1
  41. package/dist/public-tools-CQjGt7_q.mjs.map +0 -1
package/README.md CHANGED
@@ -173,7 +173,7 @@ Graph queries choose the read graph explicitly:
173
173
 
174
174
  | Graph | Use it for |
175
175
  | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
176
- | `topology` | The unified address / FLOWS_TO / OPERATED_BY / LINKED graph — recent and full historical fund-flow traversal, and swap, liquidity-pool and bridge totals (see [Graph tools](docs/graph-tools.md#swaps-liquidity-pools-and-bridges)) |
176
+ | `topology` | The unified address / FLOWED_TO / OPERATED_BY / LINKED graph — recent and full historical fund-flow traversal, and swap, liquidity-pool and bridge totals (see [Graph tools](docs/graph-tools.md#swaps-liquidity-pools-and-bridges)) |
177
177
  | `facts` | Bounded individual `TRANSFER` rows with amount, `amount_usd`, asset, transaction, and block facts, plus single `SWAP`, `LIQUIDITY_ADD`, `LIQUIDITY_REMOVE` and `BRIDGE_CROSSING` rows |
178
178
  | `chain` | One keyed lookup on the chain node: a transaction by `tx_id`, a block by `block_height` or `block_hash`, an address at one block (balance, nonce, kind), or the head |
179
179
 
@@ -315,7 +315,7 @@ Batch across graph views:
315
315
  ```bash
316
316
  cia mcp call graph_query_batch \
317
317
  network=robinhood \
318
- 'queries=[{"id":"count","query":"USE topology MATCH (a:Address) RETURN count(a) AS count LIMIT 1"},{"id":"flows","query":"USE topology MATCH (src:Address)-[f:FLOWS_TO]->(dst:Address) RETURN src.address AS source, dst.address AS target, f.amount_usd_sum AS amount_usd_sum, f.tx_count AS tx_count LIMIT 3"},{"id":"linked","query":"USE topology MATCH (a:Address)-[l:LINKED]-(b:Address) RETURN a.address AS address, b.address AS linked_address, l.basis AS basis, l.confidence AS confidence LIMIT 3"},{"id":"node_metrics","query":"USE topology MATCH (a:Address {address:\"FULL_ADDRESS\"}) RETURN a.address AS address, a.tx_out_count AS tx_out_count, a.tx_in_count AS tx_in_count LIMIT 1"}]'
318
+ 'queries=[{"id":"count","query":"USE topology MATCH (a:Address) RETURN count(a) AS count LIMIT 1"},{"id":"flows","query":"USE topology MATCH (src:Address)-[f:FLOWED_TO]->(dst:Address) RETURN src.address AS source, dst.address AS target, f.amount_usd_sum AS amount_usd_sum, f.tx_count AS tx_count LIMIT 3"},{"id":"linked","query":"USE topology MATCH (a:Address)-[l:LINKED]-(b:Address) RETURN a.address AS address, b.address AS linked_address, l.basis AS basis, l.confidence AS confidence LIMIT 3"},{"id":"node_metrics","query":"USE topology MATCH (a:Address {address:\"FULL_ADDRESS\"}) RETURN a.address AS address, a.tx_out_count AS tx_out_count, a.tx_in_count AS tx_in_count LIMIT 1"}]'
319
319
  ```
320
320
 
321
321
  More query examples (manual fund-flow reads, pagination):
package/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
- const require_print_result = require("./print-result-Cyqrlx8P.cjs");
2
+ const require_print_result = require("./print-result-if-AbNLY.cjs");
3
3
  let commander = require("commander");
4
4
  let node_child_process = require("node:child_process");
5
5
  let node_url = require("node:url");
@@ -53,7 +53,7 @@ async function withStatusReader(fn) {
53
53
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BxtMoo9c.cjs")).then((n) => n.config_exports);
54
54
  const config = await loadConfig();
55
55
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-C9uwHnBS.cjs")).then((n) => n.client_exports);
56
- const { toGraphMcpEndpointError } = await Promise.resolve().then(() => require("./transport-error-CgISST8l.cjs"));
56
+ const { toGraphMcpEndpointError } = await Promise.resolve().then(() => require("./transport-error-qDWQn8hb.cjs"));
57
57
  const endpoint = resolveGraphMcpEndpoint(config);
58
58
  const fetchImpl = await createConfiguredGraphMcpFetch(config);
59
59
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
@@ -217,7 +217,7 @@ async function withGraphMcpClient(name, fn) {
217
217
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BxtMoo9c.cjs")).then((n) => n.config_exports);
218
218
  const config = await loadConfig();
219
219
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-C9uwHnBS.cjs")).then((n) => n.client_exports);
220
- const { toGraphMcpEndpointError } = await Promise.resolve().then(() => require("./transport-error-CgISST8l.cjs"));
220
+ const { toGraphMcpEndpointError } = await Promise.resolve().then(() => require("./transport-error-qDWQn8hb.cjs"));
221
221
  const endpoint = resolveGraphMcpEndpoint(config);
222
222
  const paymentFetch = await createConfiguredGraphMcpFetch(config);
223
223
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
@@ -275,7 +275,7 @@ async function printNetworkCapability(name, opts) {
275
275
  async function printNetworkSchema(name, opts) {
276
276
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BxtMoo9c.cjs")).then((n) => n.config_exports);
277
277
  const { resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-C9uwHnBS.cjs")).then((n) => n.client_exports);
278
- const { formatGraphSchemaText, getGraphSchema } = await Promise.resolve().then(() => require("./graph-schema-BGbJumCY.cjs")).then((n) => n.graph_schema_exports);
278
+ const { formatGraphSchemaText, getGraphSchema } = await Promise.resolve().then(() => require("./graph-schema-DNJXDaTn.cjs")).then((n) => n.graph_schema_exports);
279
279
  const config = await loadConfig();
280
280
  const schema = await getGraphSchema({
281
281
  network: name,
@@ -288,11 +288,11 @@ async function printNetworkSchema(name, opts) {
288
288
  function addAmlAddressRiskCommand(parent, networksCommand) {
289
289
  parent.addCommand(createCliCommand("aml-address-risk").description("CIA workflow: screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new commander.Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
290
290
  try {
291
- const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-Bl_l_rKK.cjs"));
291
+ const { resolveAmlAddressRiskVersion } = await Promise.resolve().then(() => require("./public-tools-COG3pCpD.cjs"));
292
292
  const requestedVersion = opts.toolVersion ?? opts.version;
293
293
  resolveAmlAddressRiskVersion(requestedVersion);
294
294
  await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
295
- const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-Bl_l_rKK.cjs"));
295
+ const { runAmlAddressRisk } = await Promise.resolve().then(() => require("./public-tools-COG3pCpD.cjs"));
296
296
  const result = await runAmlAddressRisk(client, {
297
297
  address: opts.address,
298
298
  network: opts.network,
@@ -342,7 +342,7 @@ program.command("status").description("Show toolkit status and configuration").a
342
342
  });
343
343
  program.command("update").description("Check npmjs for a newer Chain Insights release and update this CLI").option("--check", "Only check for a newer release").option("--dry-run", "Print the update command without running it").action(async (opts) => {
344
344
  try {
345
- const { checkForUpdate, runPackageUpdate } = await Promise.resolve().then(() => require("./update-DGyRlm3J.cjs"));
345
+ const { checkForUpdate, runPackageUpdate } = await Promise.resolve().then(() => require("./update-NowrCmjR.cjs"));
346
346
  const result = await checkForUpdate();
347
347
  if (result.error) throw new Error(`Could not check npmjs for updates: ${result.error}`);
348
348
  if (!result.updateAvailable || !result.latestVersion) {
@@ -626,7 +626,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
626
626
  try {
627
627
  const { loadSchema, saveSchema } = await Promise.resolve().then(() => require("./schema-cache-C7v9ml6S.cjs"));
628
628
  const { formatToolsTable } = await Promise.resolve().then(() => require("./format-DfhRQq4L.cjs"));
629
- const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-BNwvcuVx.cjs")).then((n) => n.tool_visibility_exports);
629
+ const { visibleRemoteTools } = await Promise.resolve().then(() => require("./tool-visibility-DTAYvE9E.cjs")).then((n) => n.tool_visibility_exports);
630
630
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BxtMoo9c.cjs")).then((n) => n.config_exports);
631
631
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-C9uwHnBS.cjs")).then((n) => n.client_exports);
632
632
  const config = await loadConfig();
@@ -649,7 +649,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
649
649
  }
650
650
  }
651
651
  console.log(formatToolsTable(visibleRemoteTools(tools)));
652
- const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await Promise.resolve().then(() => require("./dex-tool-DLWnff1n.cjs")).then((n) => n.dex_tool_exports);
652
+ const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await Promise.resolve().then(() => require("./dex-tool-85_76Q8i.cjs")).then((n) => n.dex_tool_exports);
653
653
  console.log("");
654
654
  console.log(formatToolsTable([{
655
655
  name: META_DEX_LOOKUP_TOOL,
@@ -662,7 +662,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
662
662
  })).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) => {
663
663
  try {
664
664
  const { parseMcpCallArgs } = await Promise.resolve().then(() => require("./call-args-C8Gp5gco.cjs"));
665
- const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-BNwvcuVx.cjs")).then((n) => n.tool_visibility_exports);
665
+ const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-DTAYvE9E.cjs")).then((n) => n.tool_visibility_exports);
666
666
  let args = parseMcpCallArgs(rawArgs);
667
667
  assertPublicMcpToolName(tool);
668
668
  validatePublicMcpToolArguments(tool, args);
@@ -683,7 +683,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
683
683
  return;
684
684
  }
685
685
  if (tool === "meta_dex_lookup") {
686
- const { handleMetaDexLookup } = await Promise.resolve().then(() => require("./dex-tool-DLWnff1n.cjs")).then((n) => n.dex_tool_exports);
686
+ const { handleMetaDexLookup } = await Promise.resolve().then(() => require("./dex-tool-85_76Q8i.cjs")).then((n) => n.dex_tool_exports);
687
687
  const { loadRegistry } = await Promise.resolve().then(() => require("./dex-registry-Chs79Ukc.cjs")).then((n) => n.dex_registry_exports);
688
688
  const { metaDexLookupArgs } = await Promise.resolve().then(() => require("./call-args-C8Gp5gco.cjs"));
689
689
  const result = await withGraphMcpClient("chain-insights-cli-dex-lookup", (client) => handleMetaDexLookup(metaDexLookupArgs(args), {
@@ -749,7 +749,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
749
749
  }
750
750
  const { graphToolRequestOptions } = await Promise.resolve().then(() => require("./request-timeout-CYmQF8N7.cjs"));
751
751
  const requestOptions = graphToolRequestOptions(tool);
752
- const { withoutUtcProperties } = await Promise.resolve().then(() => require("./print-result-Cyqrlx8P.cjs")).then((n) => n.timestamp_text_exports);
752
+ const { withoutUtcProperties } = await Promise.resolve().then(() => require("./print-result-if-AbNLY.cjs")).then((n) => n.timestamp_text_exports);
753
753
  args = withoutUtcProperties(tool, args);
754
754
  const result = requestOptions ? await client.callTool({
755
755
  name: tool,
@@ -764,7 +764,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
764
764
  });
765
765
  });
766
766
  } catch (err) {
767
- const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-BNwvcuVx.cjs")).then((n) => n.tool_visibility_exports);
767
+ const { formatMcpCallError } = await Promise.resolve().then(() => require("./tool-visibility-DTAYvE9E.cjs")).then((n) => n.tool_visibility_exports);
768
768
  console.error(formatMcpCallError(tool, err));
769
769
  process.exit(1);
770
770
  }
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as PACKAGE_VERSION, r as printMcpTextContent, s as PACKAGE_INFO } from "./print-result-C3YazZBw.mjs";
1
+ import { c as PACKAGE_VERSION, r as printMcpTextContent, s as PACKAGE_INFO } from "./print-result-BtEAn2Wl.mjs";
2
2
  import { Command, Option } from "commander";
3
3
  import { execFileSync } from "node:child_process";
4
4
  import { fileURLToPath } from "node:url";
@@ -51,7 +51,7 @@ async function withStatusReader(fn) {
51
51
  const { loadConfig } = await import("./config-9MabJh5R.mjs").then((n) => n.t);
52
52
  const config = await loadConfig();
53
53
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-7qOfAdpH.mjs").then((n) => n.r);
54
- const { toGraphMcpEndpointError } = await import("./transport-error-D4hmpkqL.mjs");
54
+ const { toGraphMcpEndpointError } = await import("./transport-error-C7OQjmC7.mjs");
55
55
  const endpoint = resolveGraphMcpEndpoint(config);
56
56
  const fetchImpl = await createConfiguredGraphMcpFetch(config);
57
57
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
@@ -215,7 +215,7 @@ async function withGraphMcpClient(name, fn) {
215
215
  const { loadConfig } = await import("./config-9MabJh5R.mjs").then((n) => n.t);
216
216
  const config = await loadConfig();
217
217
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-7qOfAdpH.mjs").then((n) => n.r);
218
- const { toGraphMcpEndpointError } = await import("./transport-error-D4hmpkqL.mjs");
218
+ const { toGraphMcpEndpointError } = await import("./transport-error-C7OQjmC7.mjs");
219
219
  const endpoint = resolveGraphMcpEndpoint(config);
220
220
  const paymentFetch = await createConfiguredGraphMcpFetch(config);
221
221
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
@@ -273,7 +273,7 @@ async function printNetworkCapability(name, opts) {
273
273
  async function printNetworkSchema(name, opts) {
274
274
  const { loadConfig } = await import("./config-9MabJh5R.mjs").then((n) => n.t);
275
275
  const { resolveGraphMcpEndpoint } = await import("./client-7qOfAdpH.mjs").then((n) => n.r);
276
- const { formatGraphSchemaText, getGraphSchema } = await import("./graph-schema-ClqjzgrM.mjs").then((n) => n.i);
276
+ const { formatGraphSchemaText, getGraphSchema } = await import("./graph-schema-Bxqwukqu.mjs").then((n) => n.i);
277
277
  const config = await loadConfig();
278
278
  const schema = await getGraphSchema({
279
279
  network: name,
@@ -286,11 +286,11 @@ async function printNetworkSchema(name, opts) {
286
286
  function addAmlAddressRiskCommand(parent, networksCommand) {
287
287
  parent.addCommand(createCliCommand("aml-address-risk").description("CIA workflow: screen an address for AML risk, exchange behavior, and optional comparison with another address").requiredOption("--address <address>", "Full blockchain address to screen").requiredOption("--network <network>", `Network to query. Run \`${networksCommand}\` for supported networks.`).option("--compare-address <address>", "Optional second address to compare against the screened address").option("--json", "Print machine-readable JSON output").option("--version <version>", "AML tool contract version. Omit to use the latest version (currently v1)").addOption(new Option("--tool-version <version>", "Internal alias for the AML tool version").hideHelp()).action(async (opts) => {
288
288
  try {
289
- const { resolveAmlAddressRiskVersion } = await import("./public-tools-CQjGt7_q.mjs");
289
+ const { resolveAmlAddressRiskVersion } = await import("./public-tools-DIJs4SHz.mjs");
290
290
  const requestedVersion = opts.toolVersion ?? opts.version;
291
291
  resolveAmlAddressRiskVersion(requestedVersion);
292
292
  await withGraphMcpClient("chain-insights-cli-aml-address-risk", async (client) => {
293
- const { runAmlAddressRisk } = await import("./public-tools-CQjGt7_q.mjs");
293
+ const { runAmlAddressRisk } = await import("./public-tools-DIJs4SHz.mjs");
294
294
  const result = await runAmlAddressRisk(client, {
295
295
  address: opts.address,
296
296
  network: opts.network,
@@ -340,7 +340,7 @@ program.command("status").description("Show toolkit status and configuration").a
340
340
  });
341
341
  program.command("update").description("Check npmjs for a newer Chain Insights release and update this CLI").option("--check", "Only check for a newer release").option("--dry-run", "Print the update command without running it").action(async (opts) => {
342
342
  try {
343
- const { checkForUpdate, runPackageUpdate } = await import("./update-CZDHSi_u.mjs");
343
+ const { checkForUpdate, runPackageUpdate } = await import("./update-DqBhBXFs.mjs");
344
344
  const result = await checkForUpdate();
345
345
  if (result.error) throw new Error(`Could not check npmjs for updates: ${result.error}`);
346
346
  if (!result.updateAvailable || !result.latestVersion) {
@@ -624,7 +624,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
624
624
  try {
625
625
  const { loadSchema, saveSchema } = await import("./schema-cache-DRObKUvS.mjs");
626
626
  const { formatToolsTable } = await import("./format-CSesEBvS.mjs");
627
- const { visibleRemoteTools } = await import("./tool-visibility-B6983rxo.mjs").then((n) => n.a);
627
+ const { visibleRemoteTools } = await import("./tool-visibility-zcmwMSbn.mjs").then((n) => n.a);
628
628
  const { loadConfig } = await import("./config-9MabJh5R.mjs").then((n) => n.t);
629
629
  const { createConfiguredGraphMcpFetch, resolveGraphMcpEndpoint } = await import("./client-7qOfAdpH.mjs").then((n) => n.r);
630
630
  const config = await loadConfig();
@@ -647,7 +647,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
647
647
  }
648
648
  }
649
649
  console.log(formatToolsTable(visibleRemoteTools(tools)));
650
- const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await import("./dex-tool-DsaAIa-a.mjs").then((n) => n.a);
650
+ const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await import("./dex-tool-ib_ICiyV.mjs").then((n) => n.a);
651
651
  console.log("");
652
652
  console.log(formatToolsTable([{
653
653
  name: META_DEX_LOOKUP_TOOL,
@@ -660,7 +660,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
660
660
  })).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) => {
661
661
  try {
662
662
  const { parseMcpCallArgs } = await import("./call-args-B0oMK1-J.mjs");
663
- const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await import("./tool-visibility-B6983rxo.mjs").then((n) => n.a);
663
+ const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await import("./tool-visibility-zcmwMSbn.mjs").then((n) => n.a);
664
664
  let args = parseMcpCallArgs(rawArgs);
665
665
  assertPublicMcpToolName(tool);
666
666
  validatePublicMcpToolArguments(tool, args);
@@ -681,7 +681,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
681
681
  return;
682
682
  }
683
683
  if (tool === "meta_dex_lookup") {
684
- const { handleMetaDexLookup } = await import("./dex-tool-DsaAIa-a.mjs").then((n) => n.a);
684
+ const { handleMetaDexLookup } = await import("./dex-tool-ib_ICiyV.mjs").then((n) => n.a);
685
685
  const { loadRegistry } = await import("./dex-registry-rziydXYq.mjs").then((n) => n.n);
686
686
  const { metaDexLookupArgs } = await import("./call-args-B0oMK1-J.mjs");
687
687
  const result = await withGraphMcpClient("chain-insights-cli-dex-lookup", (client) => handleMetaDexLookup(metaDexLookupArgs(args), {
@@ -747,7 +747,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
747
747
  }
748
748
  const { graphToolRequestOptions } = await import("./request-timeout-lBBe0mGf.mjs");
749
749
  const requestOptions = graphToolRequestOptions(tool);
750
- const { withoutUtcProperties } = await import("./print-result-C3YazZBw.mjs").then((n) => n.i);
750
+ const { withoutUtcProperties } = await import("./print-result-BtEAn2Wl.mjs").then((n) => n.i);
751
751
  args = withoutUtcProperties(tool, args);
752
752
  const result = requestOptions ? await client.callTool({
753
753
  name: tool,
@@ -762,7 +762,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
762
762
  });
763
763
  });
764
764
  } catch (err) {
765
- const { formatMcpCallError } = await import("./tool-visibility-B6983rxo.mjs").then((n) => n.a);
765
+ const { formatMcpCallError } = await import("./tool-visibility-zcmwMSbn.mjs").then((n) => n.a);
766
766
  console.error(formatMcpCallError(tool, err));
767
767
  process.exit(1);
768
768
  }
@@ -62,9 +62,9 @@ const TOKEN = "<launched token address>";
62
62
  function poolsByFactoryQuery(factory, limit = 10) {
63
63
  return `USE topology MATCH (f:Address {address: "${factory}"})-[:DEPLOYED_CONTRACT]->(p:Pool) WHERE p.total_volume_usd IS NOT NULL 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 ORDER BY p.total_volume_usd DESC LIMIT ${limit}`;
64
64
  }
65
- /** The senders of one pool on FLOWS_TO, by lifetime volume (R4, G1). */
65
+ /** The senders of one pool on FLOWED_TO, by lifetime volume (R4, G1). */
66
66
  function sellersIntoPoolQuery(pool) {
67
- return `USE topology MATCH (s:Address)-[f:FLOWS_TO]->(p:Address {address: "${pool}"}) WHERE NOT s:Pool 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 ORDER BY f.amount_usd_sum DESC LIMIT 25`;
67
+ return `USE topology MATCH (s:Address)-[f:FLOWED_TO]->(p:Address {address: "${pool}"}) WHERE NOT s:Pool 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 ORDER BY f.amount_usd_sum DESC LIMIT 25`;
68
68
  }
69
69
  /** The payers whose swaps name one contract as the recipient, by sold volume (V4 shape). */
70
70
  function sellersIntoContractQuery(contract) {
@@ -117,7 +117,7 @@ function recipeFor(entry, topPool) {
117
117
  });
118
118
  if (pool) steps.push({
119
119
  step: "Sellers into the busiest pool",
120
- why: `The senders of ${pool} on FLOWS_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,
120
+ why: `The senders of ${pool} on FLOWED_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,
121
121
  query: sellersIntoPoolQuery(pool)
122
122
  });
123
123
  else {
@@ -169,7 +169,7 @@ function recipeFor(entry, topPool) {
169
169
  steps.push({
170
170
  step: "Traders on the curve of one launched token",
171
171
  why: "Fill a launched token from the first step.",
172
- query: `USE topology MATCH (t:Address {address: "${TOKEN}"})-[f:FLOWS_TO]-(b:Address) WHERE NOT t:Pool 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`
172
+ query: `USE topology MATCH (t:Address {address: "${TOKEN}"})-[f:FLOWED_TO]-(b:Address) WHERE NOT t:Pool 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`
173
173
  });
174
174
  steps.push({
175
175
  step: "The first pool of one launched token",
@@ -181,7 +181,7 @@ function recipeFor(entry, topPool) {
181
181
  case "plain":
182
182
  if (pool) steps.push({
183
183
  step: "Sellers into the busiest pool",
184
- 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".`,
184
+ why: `The senders of ${pool} on FLOWED_TO, by lifetime volume. A seller that is a contract is often an aggregator or a router: say "a contract".`,
185
185
  query: sellersIntoPoolQuery(pool)
186
186
  });
187
187
  else {
@@ -62,9 +62,9 @@ const TOKEN = "<launched token address>";
62
62
  function poolsByFactoryQuery(factory, limit = 10) {
63
63
  return `USE topology MATCH (f:Address {address: "${factory}"})-[:DEPLOYED_CONTRACT]->(p:Pool) WHERE p.total_volume_usd IS NOT NULL 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 ORDER BY p.total_volume_usd DESC LIMIT ${limit}`;
64
64
  }
65
- /** The senders of one pool on FLOWS_TO, by lifetime volume (R4, G1). */
65
+ /** The senders of one pool on FLOWED_TO, by lifetime volume (R4, G1). */
66
66
  function sellersIntoPoolQuery(pool) {
67
- return `USE topology MATCH (s:Address)-[f:FLOWS_TO]->(p:Address {address: "${pool}"}) WHERE NOT s:Pool 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 ORDER BY f.amount_usd_sum DESC LIMIT 25`;
67
+ return `USE topology MATCH (s:Address)-[f:FLOWED_TO]->(p:Address {address: "${pool}"}) WHERE NOT s:Pool 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 ORDER BY f.amount_usd_sum DESC LIMIT 25`;
68
68
  }
69
69
  /** The payers whose swaps name one contract as the recipient, by sold volume (V4 shape). */
70
70
  function sellersIntoContractQuery(contract) {
@@ -117,7 +117,7 @@ function recipeFor(entry, topPool) {
117
117
  });
118
118
  if (pool) steps.push({
119
119
  step: "Sellers into the busiest pool",
120
- why: `The senders of ${pool} on FLOWS_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,
120
+ why: `The senders of ${pool} on FLOWED_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,
121
121
  query: sellersIntoPoolQuery(pool)
122
122
  });
123
123
  else {
@@ -169,7 +169,7 @@ function recipeFor(entry, topPool) {
169
169
  steps.push({
170
170
  step: "Traders on the curve of one launched token",
171
171
  why: "Fill a launched token from the first step.",
172
- query: `USE topology MATCH (t:Address {address: "${TOKEN}"})-[f:FLOWS_TO]-(b:Address) WHERE NOT t:Pool 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`
172
+ query: `USE topology MATCH (t:Address {address: "${TOKEN}"})-[f:FLOWED_TO]-(b:Address) WHERE NOT t:Pool 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`
173
173
  });
174
174
  steps.push({
175
175
  step: "The first pool of one launched token",
@@ -181,7 +181,7 @@ function recipeFor(entry, topPool) {
181
181
  case "plain":
182
182
  if (pool) steps.push({
183
183
  step: "Sellers into the busiest pool",
184
- 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".`,
184
+ why: `The senders of ${pool} on FLOWED_TO, by lifetime volume. A seller that is a contract is often an aggregator or a router: say "a contract".`,
185
185
  query: sellersIntoPoolQuery(pool)
186
186
  });
187
187
  else {
@@ -517,4 +517,4 @@ async function handleMetaDexLookup(args, deps) {
517
517
  //#endregion
518
518
  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 };
519
519
 
520
- //# sourceMappingURL=dex-tool-DsaAIa-a.mjs.map
520
+ //# sourceMappingURL=dex-tool-ib_ICiyV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dex-tool-ib_ICiyV.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 FLOWED_TO, by lifetime volume (R4, G1). */\nfunction sellersIntoPoolQuery(pool: string): string {\n return (\n `USE topology MATCH (s:Address)-[f:FLOWED_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 FLOWED_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:FLOWED_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 FLOWED_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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"}
@@ -19,7 +19,7 @@ import { createHash } from "node:crypto";
19
19
  * 250 newest senders and 250 newest receivers, a text summary and
20
20
  * chain-insights.flows.v1 in structuredContent. Composed from at most five
21
21
  * anchored graph_query reads on the topology graph: the node, then its
22
- * outgoing and its incoming FLOWS_TO, newest first, two reads a side because
22
+ * outgoing and its incoming FLOWED_TO, newest first, two reads a side because
23
23
  * a reply holds at most 200 rows.
24
24
  * - link, {network, from, to, day}: the transfers between two known addresses
25
25
  * on one UTC day, newest first, at most 50, a short text summary and
@@ -186,8 +186,8 @@ function flowsReadsFor(address, inOffset, outOffset) {
186
186
  skip: 0,
187
187
  limit: 1
188
188
  },
189
- ...side("out", outOffset, `(a:Address {address: '${address}'})-[f:FLOWS_TO]->(b:Address)`),
190
- ...side("in", inOffset, `(b:Address)-[f:FLOWS_TO]->(a:Address {address: '${address}'})`)
189
+ ...side("out", outOffset, `(a:Address {address: '${address}'})-[f:FLOWED_TO]->(b:Address)`),
190
+ ...side("in", inOffset, `(b:Address)-[f:FLOWED_TO]->(a:Address {address: '${address}'})`)
191
191
  ];
192
192
  }
193
193
  /** The role word of an address, first match wins. */
@@ -860,7 +860,7 @@ const ADDRESS_SAMPLE_LIMIT = 20;
860
860
  const LINK_SAMPLE_LIMIT = 5;
861
861
  /** The link types an agent queries. Each is sampled only when the graph lists it. */
862
862
  const SAMPLED_LINK_TYPES = [
863
- "FLOWS_TO",
863
+ "FLOWED_TO",
864
864
  "OPERATED_BY",
865
865
  "LINKED",
866
866
  "SWAPPED",
@@ -1222,4 +1222,4 @@ async function handleMetaSchema(args, deps) {
1222
1222
  //#endregion
1223
1223
  export { handleMetaSchema as a, GRAPH_EXPAND_TITLE as c, handleGraphExpand as d, graph_schema_exports as i, GRAPH_EXPAND_TOOL as l, META_SCHEMA_DESCRIPTION as n, FLOWS_MAX_OFFSET as o, META_SCHEMA_TITLE as r, GRAPH_EXPAND_DESCRIPTION as s, GraphSchemaError as t, TRANSFERS_MAX as u };
1224
1224
 
1225
- //# sourceMappingURL=graph-schema-ClqjzgrM.mjs.map
1225
+ //# sourceMappingURL=graph-schema-Bxqwukqu.mjs.map