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.
- package/README.md +2 -2
- package/dist/cli.cjs +13 -13
- package/dist/cli.mjs +13 -13
- package/dist/{dex-tool-DLWnff1n.cjs → dex-tool-85_76Q8i.cjs} +5 -5
- package/dist/{dex-tool-DsaAIa-a.mjs → dex-tool-ib_ICiyV.mjs} +6 -6
- package/dist/dex-tool-ib_ICiyV.mjs.map +1 -0
- package/dist/{graph-schema-ClqjzgrM.mjs → graph-schema-Bxqwukqu.mjs} +5 -5
- package/dist/graph-schema-Bxqwukqu.mjs.map +1 -0
- package/dist/{graph-schema-BGbJumCY.cjs → graph-schema-DNJXDaTn.cjs} +4 -4
- package/dist/mcp-proxy.cjs +22 -22
- package/dist/mcp-proxy.mjs +22 -22
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{print-result-C3YazZBw.mjs → print-result-BtEAn2Wl.mjs} +2 -2
- package/dist/print-result-BtEAn2Wl.mjs.map +1 -0
- package/dist/{print-result-Cyqrlx8P.cjs → print-result-if-AbNLY.cjs} +1 -1
- package/dist/{public-tools-Bl_l_rKK.cjs → public-tools-COG3pCpD.cjs} +8 -8
- package/dist/{public-tools-CQjGt7_q.mjs → public-tools-DIJs4SHz.mjs} +9 -9
- package/dist/public-tools-DIJs4SHz.mjs.map +1 -0
- package/dist/{tool-visibility-BNwvcuVx.cjs → tool-visibility-DTAYvE9E.cjs} +1 -1
- package/dist/{tool-visibility-B6983rxo.mjs → tool-visibility-zcmwMSbn.mjs} +2 -2
- package/dist/{tool-visibility-B6983rxo.mjs.map → tool-visibility-zcmwMSbn.mjs.map} +1 -1
- package/dist/{transport-error-D4hmpkqL.mjs → transport-error-C7OQjmC7.mjs} +2 -2
- package/dist/{transport-error-D4hmpkqL.mjs.map → transport-error-C7OQjmC7.mjs.map} +1 -1
- package/dist/{transport-error-CgISST8l.cjs → transport-error-qDWQn8hb.cjs} +1 -1
- package/dist/{update-CZDHSi_u.mjs → update-DqBhBXFs.mjs} +2 -2
- package/dist/{update-CZDHSi_u.mjs.map → update-DqBhBXFs.mjs.map} +1 -1
- package/dist/{update-DGyRlm3J.cjs → update-NowrCmjR.cjs} +1 -1
- package/docs/graph-query-compatibility.md +13 -13
- package/docs/graph-tools.md +22 -22
- package/docs/mcp-proxy.md +1 -1
- package/package.json +1 -1
- package/skills/chain-insights-cypher/SKILL.md +27 -27
- package/skills/chain-insights-dex/SKILL.md +2 -2
- package/skills/chain-insights-dex/reference.md +1 -1
- package/skills/chain-insights-dex-gauge/SKILL.md +4 -4
- package/skills/chain-insights-dex-launchpad/SKILL.md +2 -2
- package/skills/chain-insights-virtuals/reference.md +5 -5
- package/dist/dex-tool-DsaAIa-a.mjs.map +0 -1
- package/dist/graph-schema-ClqjzgrM.mjs.map +0 -1
- package/dist/print-result-C3YazZBw.mjs.map +0 -1
- 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 /
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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-
|
|
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
|
|
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:
|
|
190
|
-
...side("in", inOffset, `(b:Address)-[f:
|
|
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
|
-
"
|
|
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-
|
|
1225
|
+
//# sourceMappingURL=graph-schema-Bxqwukqu.mjs.map
|