chain-insights 0.56.21 → 0.57.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.
@@ -2,6 +2,8 @@
2
2
  const NUMERIC_ARG_KEYS = /* @__PURE__ */ new Set([
3
3
  "in_offset",
4
4
  "out_offset",
5
+ "limit",
6
+ "skip",
5
7
  "per_query_timeout_seconds",
6
8
  "incident_timestamp",
7
9
  "max_hops",
@@ -28,4 +30,4 @@ function parseMcpCallArgs(rawArgs) {
28
30
  //#endregion
29
31
  export { parseMcpCallArgs };
30
32
 
31
- //# sourceMappingURL=call-args-DPWeqIla.mjs.map
33
+ //# sourceMappingURL=call-args-Bdw2sV4n.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-args-Bdw2sV4n.mjs","names":[],"sources":["../src/mcp/call-args.ts"],"sourcesContent":["// `cia mcp call tool key=value` yields strings. A key listed here is coerced\n// to a number; anything else stays a string and is rejected by the tool's\n// numeric zod schema. Every tunable search bound must therefore appear here,\n// or passing it from the CLI fails with a confusing type error.\nconst NUMERIC_ARG_KEYS = new Set([\n 'in_offset',\n 'out_offset',\n 'limit',\n 'skip',\n 'per_query_timeout_seconds',\n 'incident_timestamp',\n 'max_hops',\n 'per_address_limit',\n 'row_limit',\n])\n\nfunction parseMcpArgValue(key: string, value: string): unknown {\n const trimmed = value.trim()\n if (!trimmed) return value\n\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n return JSON.parse(trimmed) as unknown\n }\n\n if (NUMERIC_ARG_KEYS.has(key) && /^-?\\d+(?:\\.\\d+)?$/.test(trimmed)) {\n return Number(trimmed)\n }\n\n return value\n}\n\nexport function parseMcpCallArgs(rawArgs: string[]): Record<string, unknown> {\n const args: Record<string, unknown> = {}\n for (const pair of rawArgs) {\n const eqIdx = pair.indexOf('=')\n if (eqIdx === -1) {\n throw new Error(`Invalid arg format: ${pair} (expected key=value)`)\n }\n const key = pair.slice(0, eqIdx)\n args[key] = parseMcpArgValue(key, pair.slice(eqIdx + 1))\n }\n return args\n}\n"],"mappings":";AAIA,MAAM,mCAAmB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,iBAAiB,KAAa,OAAwB;CAC7D,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,SAAS,OAAO;CAErB,IAAI,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG,GACnD,OAAO,KAAK,MAAM,OAAO;CAG3B,IAAI,iBAAiB,IAAI,GAAG,KAAK,oBAAoB,KAAK,OAAO,GAC/D,OAAO,OAAO,OAAO;CAGvB,OAAO;AACT;AAEA,SAAgB,iBAAiB,SAA4C;CAC3E,MAAM,OAAgC,CAAC;CACvC,KAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,QAAQ,KAAK,QAAQ,GAAG;EAC9B,IAAI,UAAU,IACZ,MAAM,IAAI,MAAM,uBAAuB,KAAK,sBAAsB;EAEpE,MAAM,MAAM,KAAK,MAAM,GAAG,KAAK;EAC/B,KAAK,OAAO,iBAAiB,KAAK,KAAK,MAAM,QAAQ,CAAC,CAAC;CACzD;CACA,OAAO;AACT"}
@@ -2,6 +2,8 @@
2
2
  const NUMERIC_ARG_KEYS = /* @__PURE__ */ new Set([
3
3
  "in_offset",
4
4
  "out_offset",
5
+ "limit",
6
+ "skip",
5
7
  "per_query_timeout_seconds",
6
8
  "incident_timestamp",
7
9
  "max_hops",
package/dist/cli.cjs CHANGED
@@ -152,7 +152,7 @@ async function printNetworkCapability(name, opts) {
152
152
  async function printNetworkSchema(name, opts) {
153
153
  const { loadConfig } = await Promise.resolve().then(() => require("./config-BxtMoo9c.cjs")).then((n) => n.config_exports);
154
154
  const { resolveGraphMcpEndpoint } = await Promise.resolve().then(() => require("./client-Ypw9BNbn.cjs")).then((n) => n.client_exports);
155
- const { formatGraphSchemaText, getGraphSchema } = await Promise.resolve().then(() => require("./graph-schema-CXD7NtEX.cjs")).then((n) => n.graph_schema_exports);
155
+ const { formatGraphSchemaText, getGraphSchema } = await Promise.resolve().then(() => require("./graph-schema-_u1aB_f1.cjs")).then((n) => n.graph_schema_exports);
156
156
  const config = await loadConfig();
157
157
  const schema = await getGraphSchema({
158
158
  network: name,
@@ -603,7 +603,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
603
603
  }
604
604
  })).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) => {
605
605
  try {
606
- const { parseMcpCallArgs } = await Promise.resolve().then(() => require("./call-args-Db8KXsQx.cjs"));
606
+ const { parseMcpCallArgs } = await Promise.resolve().then(() => require("./call-args-DhJiv6T8.cjs"));
607
607
  const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await Promise.resolve().then(() => require("./tool-visibility-CDe-ft0L.cjs")).then((n) => n.tool_visibility_exports);
608
608
  const args = parseMcpCallArgs(rawArgs);
609
609
  assertPublicMcpToolName(tool);
package/dist/cli.mjs CHANGED
@@ -150,7 +150,7 @@ async function printNetworkCapability(name, opts) {
150
150
  async function printNetworkSchema(name, opts) {
151
151
  const { loadConfig } = await import("./config-9MabJh5R.mjs").then((n) => n.t);
152
152
  const { resolveGraphMcpEndpoint } = await import("./client-gsLi528K.mjs").then((n) => n.r);
153
- const { formatGraphSchemaText, getGraphSchema } = await import("./graph-schema-D7wSNOT5.mjs").then((n) => n.i);
153
+ const { formatGraphSchemaText, getGraphSchema } = await import("./graph-schema-CwsQBYrP.mjs").then((n) => n.i);
154
154
  const config = await loadConfig();
155
155
  const schema = await getGraphSchema({
156
156
  network: name,
@@ -601,7 +601,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
601
601
  }
602
602
  })).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) => {
603
603
  try {
604
- const { parseMcpCallArgs } = await import("./call-args-DPWeqIla.mjs");
604
+ const { parseMcpCallArgs } = await import("./call-args-Bdw2sV4n.mjs");
605
605
  const { assertPublicMcpToolName, validatePublicMcpToolArguments } = await import("./tool-visibility-P9wETx-d.mjs").then((n) => n.a);
606
606
  const args = parseMcpCallArgs(rawArgs);
607
607
  assertPublicMcpToolName(tool);
@@ -25,6 +25,16 @@ import { createHash } from "node:crypto";
25
25
  * on one UTC day, newest first, at most 50, a short text summary and
26
26
  * chain-insights.transfers.v1 in structuredContent. One anchored USE facts
27
27
  * graph_query read.
28
+ * - link page, {network, from, to, day, limit, skip?, first_day?}: a page of
29
+ * `limit` transfers (1 to 50) newest first, read from `day` back one UTC day
30
+ * at a time, at most TRANSFERS_MAX_DAYS (a week of) reads a call, never before
31
+ * first_day. The answer carries `next` ({day, skip}, or null when every
32
+ * transfer back to first_day is listed) which the next call sends back as
33
+ * day and skip. The view's "More transfers".
34
+ * - details, {network, address, details: true}: the lifetime totals, counts,
35
+ * role and labels of one address, chain-insights.address.v1, one anchored
36
+ * topology read. The view fills its panel with it when the rows of a query
37
+ * carried no totals.
28
38
  *
29
39
  * Every read goes through the proxy's remote client, the same path and payment
30
40
  * wrapping a graph_query call takes, and is billed as a graph_query. Every
@@ -37,10 +47,15 @@ const FLOWS_NETWORK = "robinhood";
37
47
  /** Where a graph_expand page may start on each side, at most. */
38
48
  const FLOWS_MAX_OFFSET = 1e4;
39
49
  const TRANSFERS_SCHEMA = "chain-insights.transfers.v1";
50
+ const ADDRESS_SCHEMA = "chain-insights.address.v1";
40
51
  const INVALID_ADDRESS_TEXT = "invalid_address: a full 0x address of 40 hexadecimal characters is needed, for example 0x04911a118f11c75667e4d0dfb8e640af5a353550";
41
52
  const OFFSET_TOO_LARGE_TEXT = `invalid_offset: in_offset and out_offset must each be at most ${FLOWS_MAX_OFFSET}`;
42
53
  const OFFSET_NEGATIVE_TEXT = "invalid_offset: in_offset and out_offset must each be a whole number, 0 or more";
43
54
  const BOTH_FORMS_TEXT = "invalid_arguments: pass address (with in_offset and out_offset) to expand a node, or from, to and day to list the transfers of a link, not both";
55
+ const INVALID_LIMIT_TEXT = "invalid_limit: limit must be a whole number from 1 to 50";
56
+ const INVALID_SKIP_TEXT = "invalid_skip: skip must be a whole number of 0 or more";
57
+ const INVALID_FIRST_DAY_TEXT = "invalid_first_day: first_day must be a calendar date written YYYY-MM-DD (UTC), not after day";
58
+ const DETAILS_FORM_TEXT = "invalid_arguments: details takes address alone; pass no from, to, day or offsets with it";
44
59
  const ADDRESS_PATTERN = /^0x[0-9a-f]{40}$/;
45
60
  const DAY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
46
61
  const CODE_PREFIX = /^([a-z][a-z0-9_]*):\s*/;
@@ -76,6 +91,41 @@ function normalizeFlowsDay(raw) {
76
91
  function transfersReadFor(from, to, day) {
77
92
  return `USE facts MATCH (a:Address {address: "${from}"})-[t:TRANSFER]->(b:Address {address: "${to}"}) WHERE t.block_date = "${day}" RETURN t.tx_id AS tx_id, t.block_timestamp AS block_timestamp, t.amount AS amount, t.asset_symbol AS asset_symbol, t.amount_usd AS amount_usd LIMIT 200`;
78
93
  }
94
+ /** The UTC day before a YYYY-MM-DD day. */
95
+ function dayBefore(day) {
96
+ return (/* @__PURE__ */ new Date(Date.parse(`${day}T00:00:00Z`) - 864e5)).toISOString().slice(0, 10);
97
+ }
98
+ /**
99
+ * The one topology read of a details call: the address alone, with its
100
+ * lifetime rollups and activity span. The address has passed its check, so it
101
+ * is written into the read as a literal.
102
+ */
103
+ function detailsReadFor(address) {
104
+ return `USE topology MATCH (b:Address {address: "${address}"}) RETURN ${NODE_FIELDS}, b.first_activity_timestamp AS first_activity_timestamp, b.last_activity_timestamp AS last_activity_timestamp LIMIT 1`;
105
+ }
106
+ /** chain-insights.address.v1 from the one row of a details read. */
107
+ function buildAddressView(network, address, row) {
108
+ const node = flowsNodeOf({
109
+ ...row,
110
+ address
111
+ });
112
+ return {
113
+ schema: ADDRESS_SCHEMA,
114
+ network,
115
+ ...node,
116
+ is_exchange: row.is_exchange === true,
117
+ is_sanctioned: row.is_sanctioned === true,
118
+ is_scam: row.is_scam === true,
119
+ is_victim: row.is_victim === true,
120
+ pool: row.pool === true,
121
+ first_activity_timestamp: flowsNumberOrNull(row.first_activity_timestamp),
122
+ last_activity_timestamp: flowsNumberOrNull(row.last_activity_timestamp)
123
+ };
124
+ }
125
+ /** The one line of a details answer. */
126
+ function addressSummary(view) {
127
+ return `${view.address} on ${view.network}: ${view.role}; lifetime ${formatFlowsUsd(view.total_in_usd)} in, ${formatFlowsUsd(view.total_out_usd)} out; ${view.degree_in} senders and ${view.degree_out} receivers in the topology graph` + (view.labels.length ? `; labels ${view.labels.join(", ")}` : "") + ".";
128
+ }
79
129
  const NODE_FIELDS = "b.address AS address, b.labels AS labels, b.is_exchange AS is_exchange, b.is_sanctioned AS is_sanctioned, b.is_scam AS is_scam, b.is_victim AS is_victim, b:Pool AS pool, b.total_in_usd AS total_in_usd, b.total_out_usd AS total_out_usd, b.degree_in AS degree_in, b.degree_out AS degree_out";
80
130
  const PARTY_FIELDS = NODE_FIELDS + ", f.amount_usd_sum AS usd, f.tx_count AS tx_count, " + "f.first_seen_timestamp AS first_seen, f.last_seen_timestamp AS last_seen";
81
131
  const PAGE_ORDER = " ORDER BY f.last_seen_timestamp DESC, b.address";
@@ -320,11 +370,14 @@ function transferLine(transfer) {
320
370
  }
321
371
  /** The short text of a link answer: the pair, the day, the count and the newest few transfers. */
322
372
  function transfersSummary(view) {
323
- const head = `Transfers from ${view.from} to ${view.to} on ${view.day} (UTC) on ${view.network}`;
324
- if (view.transfers.length === 0) return `${head}: none.`;
325
- const lines = [`${head}: ${view.truncated ? `the newest ${view.transfers.length}, and the day holds more` : `${view.transfers.length}`}, newest first.`, ...view.transfers.slice(0, 5).map(transferLine)];
373
+ const paged = view.through_day !== void 0;
374
+ const span = paged && view.through_day !== view.day ? `${view.through_day} to ${view.day}` : view.day;
375
+ const head = `Transfers from ${view.from} to ${view.to} on ${span} (UTC) on ${view.network}`;
376
+ if (view.transfers.length === 0) return paged && view.next ? `${head}: none; earlier days remain before ${view.next.day}.` : `${head}: none.`;
377
+ const lines = [`${head}: ${view.truncated ? paged ? `${view.transfers.length}, and a day holds more than one read lists` : `the newest ${view.transfers.length}, and the day holds more` : `${view.transfers.length}`}, newest first.`, ...view.transfers.slice(0, 5).map(transferLine)];
326
378
  const rest = view.transfers.length - 5;
327
379
  if (rest > 0) lines.push(`- and ${rest} more in the picture.`);
380
+ if (paged && view.next) lines.push(`- earlier transfers remain, from ${view.next.day} back.`);
328
381
  return lines.join("\n");
329
382
  }
330
383
  function errorResult$1(text) {
@@ -384,6 +437,21 @@ function checkAddress(raw, name) {
384
437
  function checkDay(raw) {
385
438
  return normalizeFlowsDay(raw) ?? { error: "invalid_day: day must be a calendar date written YYYY-MM-DD (UTC), for example 2026-07-10" };
386
439
  }
440
+ function checkLimit(value) {
441
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 50) return { error: INVALID_LIMIT_TEXT };
442
+ return value;
443
+ }
444
+ function checkSkip(value) {
445
+ if (value === void 0 || value === null) return 0;
446
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) return { error: INVALID_SKIP_TEXT };
447
+ return value;
448
+ }
449
+ function checkFirstDay(value, day) {
450
+ if (value === void 0 || value === null) return null;
451
+ const first = normalizeFlowsDay(value);
452
+ if (!first || first > day) return { error: INVALID_FIRST_DAY_TEXT };
453
+ return first;
454
+ }
387
455
  function checkOffset(value) {
388
456
  if (value === void 0 || value === null) return 0;
389
457
  if (typeof value !== "number" || !Number.isInteger(value) || value < 0) return { error: OFFSET_NEGATIVE_TEXT };
@@ -467,6 +535,127 @@ async function runLink(deps, network, from, to, day) {
467
535
  isError: false
468
536
  };
469
537
  }
538
+ /** One facts read of a day, as the paged walk needs it: the rows newest first, or the error that ends the call. */
539
+ async function readDay(deps, network, from, to, day) {
540
+ let answer;
541
+ try {
542
+ answer = await deps.graphQuery({
543
+ network,
544
+ query: transfersReadFor(from, to, day)
545
+ });
546
+ } catch (err) {
547
+ return { error: deps.describeFailure(err) };
548
+ }
549
+ if (answer.isError === true) return { error: refusalText(answer) };
550
+ const rows = rowsOf(answer);
551
+ if (!rows) return { error: "graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows" };
552
+ return {
553
+ rows: rows.map(transferOf).sort((a, b) => (b.block_timestamp ?? -Infinity) - (a.block_timestamp ?? -Infinity)),
554
+ cut: rows.length >= 200
555
+ };
556
+ }
557
+ /**
558
+ * Read and answer one page of a link's transfers: from `day` back, one day a
559
+ * read, until the page holds `limit` transfers, TRANSFERS_MAX_DAYS days were
560
+ * read, or the day before first_day is reached. `skip` passes over the
561
+ * transfers of `day` an earlier page listed. The first read that does not
562
+ * answer ends the call.
563
+ */
564
+ async function runLinkPage(deps, network, from, to, day, page) {
565
+ const unavailable = deps.unavailable?.();
566
+ if (unavailable) return errorResult$1(unavailable);
567
+ const floor = page.firstDay ?? "2015-01-01";
568
+ const transfers = [];
569
+ let current = day;
570
+ let skip = page.skip;
571
+ let daysRead = 0;
572
+ let through = day;
573
+ let truncated = false;
574
+ let next = null;
575
+ for (;;) {
576
+ if (current < floor) break;
577
+ if (daysRead >= 7) {
578
+ next = {
579
+ day: current,
580
+ skip
581
+ };
582
+ break;
583
+ }
584
+ const read = await readDay(deps, network, from, to, current);
585
+ if ("error" in read) return errorResult$1(read.error);
586
+ daysRead += 1;
587
+ through = current;
588
+ if (read.cut) truncated = true;
589
+ const rest = read.rows.slice(skip);
590
+ const room = page.limit - transfers.length;
591
+ if (rest.length > room) {
592
+ transfers.push(...rest.slice(0, room));
593
+ next = {
594
+ day: current,
595
+ skip: skip + room
596
+ };
597
+ break;
598
+ }
599
+ transfers.push(...rest);
600
+ const earlier = dayBefore(current);
601
+ if (transfers.length >= page.limit) {
602
+ next = earlier < floor ? null : {
603
+ day: earlier,
604
+ skip: 0
605
+ };
606
+ break;
607
+ }
608
+ current = earlier;
609
+ skip = 0;
610
+ }
611
+ const view = {
612
+ schema: TRANSFERS_SCHEMA,
613
+ network,
614
+ from,
615
+ to,
616
+ day,
617
+ transfers,
618
+ truncated,
619
+ through_day: through,
620
+ days_read: daysRead,
621
+ next
622
+ };
623
+ return {
624
+ content: [{
625
+ type: "text",
626
+ text: transfersSummary(view)
627
+ }],
628
+ structuredContent: view,
629
+ isError: false
630
+ };
631
+ }
632
+ /** Read and answer the details of one address: a single anchored topology read. */
633
+ async function runDetails(deps, network, address) {
634
+ const unavailable = deps.unavailable?.();
635
+ if (unavailable) return errorResult$1(unavailable);
636
+ let answer;
637
+ try {
638
+ answer = await deps.graphQuery({
639
+ network,
640
+ query: detailsReadFor(address)
641
+ });
642
+ } catch (err) {
643
+ return errorResult$1(deps.describeFailure(err));
644
+ }
645
+ if (answer.isError === true) return errorResult$1(refusalText(answer));
646
+ const rows = rowsOf(answer);
647
+ if (!rows) return errorResult$1("graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows");
648
+ if (rows.length === 0) return errorResult$1(`${address} is not in the ${network} topology graph`);
649
+ const view = buildAddressView(network, address, rows[0]);
650
+ return {
651
+ content: [{
652
+ type: "text",
653
+ text: addressSummary(view)
654
+ }],
655
+ structuredContent: view,
656
+ isError: false
657
+ };
658
+ }
470
659
  function given(record, keys) {
471
660
  return keys.some((key) => record[key] !== void 0 && record[key] !== null);
472
661
  }
@@ -487,7 +676,8 @@ async function handleGraphExpand(args, deps) {
487
676
  if (isLink && given(record, [
488
677
  "address",
489
678
  "in_offset",
490
- "out_offset"
679
+ "out_offset",
680
+ "details"
491
681
  ])) return errorResult$1(BOTH_FORMS_TEXT);
492
682
  if (isLink) {
493
683
  const from = checkAddress(record.from, "from");
@@ -496,8 +686,32 @@ async function handleGraphExpand(args, deps) {
496
686
  if (typeof to !== "string") return errorResult$1(to.error);
497
687
  const day = checkDay(record.day);
498
688
  if (typeof day !== "string") return errorResult$1(day.error);
689
+ if (given(record, [
690
+ "limit",
691
+ "skip",
692
+ "first_day"
693
+ ])) {
694
+ const limit = checkLimit(record.limit);
695
+ if (typeof limit !== "number") return errorResult$1(limit.error);
696
+ const skip = checkSkip(record.skip);
697
+ if (typeof skip !== "number") return errorResult$1(skip.error);
698
+ const firstDay = checkFirstDay(record.first_day, day);
699
+ if (firstDay !== null && typeof firstDay !== "string") return errorResult$1(firstDay.error);
700
+ return runLinkPage(deps, network, from, to, day, {
701
+ skip,
702
+ firstDay,
703
+ limit
704
+ });
705
+ }
499
706
  return runLink(deps, network, from, to, day);
500
707
  }
708
+ if (record.details !== void 0 && record.details !== null) {
709
+ if (record.details !== true) return errorResult$1(DETAILS_FORM_TEXT);
710
+ if (given(record, ["in_offset", "out_offset"])) return errorResult$1(DETAILS_FORM_TEXT);
711
+ const address = checkAddress(record.address);
712
+ if (typeof address !== "string") return errorResult$1(address.error);
713
+ return runDetails(deps, network, address);
714
+ }
501
715
  const address = checkAddress(record.address);
502
716
  if (typeof address !== "string") return errorResult$1(address.error);
503
717
  const inOffset = checkOffset(record.in_offset);
@@ -507,7 +721,7 @@ async function handleGraphExpand(args, deps) {
507
721
  return runNode(deps, network, address, inOffset, outOffset);
508
722
  }
509
723
  const GRAPH_EXPAND_TITLE = "Expand a node or a link in the picture";
510
- const GRAPH_EXPAND_DESCRIPTION = "Expand an open Chain Insights picture. Called by the view when the investigator clicks, never by the model. A node, {network, address, in_offset?, out_offset?}, loads the newest senders and receivers of one address (up to 250 a side, from the offsets); billed as at most five graph_query topology reads. A link, {network, from, to, day}, lists the transfers between two addresses on one UTC day (YYYY-MM-DD), newest first, at most 50; billed as one graph_query facts read. Read-only; an argument refused before a read costs nothing.";
724
+ const GRAPH_EXPAND_DESCRIPTION = "Expand an open Chain Insights picture. Called by the view when the investigator clicks, never by the model. A node, {network, address, in_offset?, out_offset?}, loads the newest senders and receivers of one address (up to 250 a side, from the offsets); billed as at most five graph_query topology reads. A link, {network, from, to, day}, lists the transfers between two addresses on one UTC day (YYYY-MM-DD), newest first, at most 50; billed as one graph_query facts read. A link page, {network, from, to, day, limit, skip?, first_day?}, lists `limit` (1-50) transfers from `day` back across days, newest first, and answers a `next` cursor ({day, skip}) while older ones remain; billed as at most seven facts reads (a week). Details, {network, address, details: true}, answer the lifetime totals, counts, role and labels of one address; billed as one topology read. Read-only; an argument refused before a read costs nothing.";
511
725
  //#endregion
512
726
  //#region src/mcp/graph-schema-cache.ts
513
727
  /** How long a schema with a failed read stays fresh. */
@@ -963,4 +1177,4 @@ async function handleMetaSchema(args, deps) {
963
1177
  //#endregion
964
1178
  export { handleMetaSchema as a, GRAPH_EXPAND_TITLE as c, 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, handleGraphExpand as u };
965
1179
 
966
- //# sourceMappingURL=graph-schema-D7wSNOT5.mjs.map
1180
+ //# sourceMappingURL=graph-schema-CwsQBYrP.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-schema-CwsQBYrP.mjs","names":["errorResult","isRecord"],"sources":["../src/mcp/flows.ts","../src/mcp/graph-schema-cache.ts","../src/mcp/graph-schema.ts"],"sourcesContent":["/**\n * graph_expand, the one dedicated tool of the local proxy, called by the Chain\n * Insights view (ui://chain-insights/view) when the investigator clicks. Its\n * _meta says visibility [\"app\"], so a host keeps it away from the model: the\n * model writes its own graph_query, and the view draws the rows by their column\n * names.\n *\n * Two forms, told apart by the arguments:\n *\n * - node, {network, address, in_offset?, out_offset?}: one address with up to\n * 250 newest senders and 250 newest receivers, a text summary and\n * chain-insights.flows.v1 in structuredContent. Composed from at most five\n * anchored graph_query reads on the topology graph: the node, then its\n * outgoing and its incoming FLOWS_TO, newest first, two reads a side because\n * a reply holds at most 200 rows.\n * - link, {network, from, to, day}: the transfers between two known addresses\n * on one UTC day, newest first, at most 50, a short text summary and\n * chain-insights.transfers.v1 in structuredContent. One anchored USE facts\n * graph_query read.\n * - link page, {network, from, to, day, limit, skip?, first_day?}: a page of\n * `limit` transfers (1 to 50) newest first, read from `day` back one UTC day\n * at a time, at most TRANSFERS_MAX_DAYS (a week of) reads a call, never before\n * first_day. The answer carries `next` ({day, skip}, or null when every\n * transfer back to first_day is listed) which the next call sends back as\n * day and skip. The view's \"More transfers\".\n * - details, {network, address, details: true}: the lifetime totals, counts,\n * role and labels of one address, chain-insights.address.v1, one anchored\n * topology read. The view fills its panel with it when the rows of a query\n * carried no totals.\n *\n * Every read goes through the proxy's remote client, the same path and payment\n * wrapping a graph_query call takes, and is billed as a graph_query. Every\n * argument is checked before the first read, so a refused call reaches no graph\n * endpoint and costs nothing.\n */\n\nexport const FLOWS_SCHEMA = 'chain-insights.flows.v1'\nexport const GRAPH_EXPAND_TOOL = 'graph_expand'\nexport const FLOWS_NETWORK = 'robinhood'\n\n/** How many senders and how many receivers one page shows, at most. */\nexport const FLOWS_PER_SIDE = 250\n/** The view data of one answer: at most this many addresses, the centre included... */\nexport const FLOWS_MAX_NODES = 500\n/**\n * ...and under this many characters of JSON. Hosts drop an app tool result\n * over about 150,000 characters.\n */\nexport const FLOWS_MAX_CHARS = 140_000\n/**\n * The graph endpoint cuts a reply at 200 rows, whatever the LIMIT, so one side\n * of a page is read in reads of at most this many rows.\n */\nexport const FLOWS_READ_ROWS = 200\n/** The text of a node answer holds at most this many lines. */\nexport const FLOWS_SUMMARY_MAX_LINES = 20\n/** Where a graph_expand page may start on each side, at most. */\nexport const FLOWS_MAX_OFFSET = 10_000\n\nexport const TRANSFERS_SCHEMA = 'chain-insights.transfers.v1'\n/** A link answer lists at most this many transfers... */\nexport const TRANSFERS_MAX = 50\n/**\n * ...from the rows one read brings back. A facts read takes no ORDER BY and\n * the server cuts a reply at 200 rows, so the read asks for the whole cap and\n * the newest rows are picked here.\n */\nexport const TRANSFERS_READ_ROWS = 200\n/** The text of a link answer names at most this many transfers. */\nexport const TRANSFERS_SUMMARY_ROWS = 5\n/** A paged link answer reads at most this many days (a week) in one call: a sparse link costs at most this many reads a page, and a week of it comes back at once. */\nexport const TRANSFERS_MAX_DAYS = 7\n/** Where a paged link answer stops reading back when the caller names no first_day. */\nexport const TRANSFERS_FLOOR_DAY = '2015-01-01'\n\nexport const ADDRESS_SCHEMA = 'chain-insights.address.v1'\n\nexport type FlowNode = {\n address: string\n role: string\n labels: string[]\n total_in_usd: number\n total_out_usd: number\n degree_in: number\n degree_out: number\n}\n\nexport type FlowEdge = {\n from: string\n to: string\n usd: number\n tx_count: number\n first_seen_ms: number\n last_seen_ms: number\n}\n\nexport type FlowCursor = { in_offset: number; out_offset: number }\n\n/** The links of the centre not loaded yet, past the cursor, on each side. */\nexport type FlowRemaining = { senders: number; receivers: number }\n\nexport type FlowView = {\n schema: typeof FLOWS_SCHEMA\n network: string\n center: string\n nodes: FlowNode[]\n edges: FlowEdge[]\n cursor: FlowCursor\n truncated: boolean\n /** Set on every node page. A view that does not know it ignores it. */\n remaining?: FlowRemaining\n}\n\ntype Row = Record<string, unknown>\n\nexport type FlowsPage = {\n center: Row\n senders: Row[]\n receivers: Row[]\n inOffset: number\n outOffset: number\n}\n\n/** One graph_query answer as the remote client returns it. */\nexport type GraphQueryAnswer = {\n content?: Array<{ type: string; text?: string }>\n structuredContent?: Record<string, unknown>\n isError?: boolean\n}\n\nexport type FlowsToolResult = {\n content: Array<{ type: 'text'; text: string }>\n structuredContent?: Record<string, unknown>\n isError: boolean\n}\n\nexport type FlowsDependencies = {\n /** One graph_query call through the proxy's remote client. */\n graphQuery: (args: { network: string; query: string }) => Promise<GraphQueryAnswer>\n /** The text of a call that threw (payment required, transport failure). */\n describeFailure: (err: unknown) => string\n /** Why no read can run (the graph endpoint is not connected), checked after the arguments. */\n unavailable?: () => string | undefined\n now?: () => Date\n}\n\nexport const INVALID_ADDRESS_TEXT =\n 'invalid_address: a full 0x address of 40 hexadecimal characters is needed, for example 0x04911a118f11c75667e4d0dfb8e640af5a353550'\nexport const OFFSET_TOO_LARGE_TEXT = `invalid_offset: in_offset and out_offset must each be at most ${FLOWS_MAX_OFFSET}`\nexport const OFFSET_NEGATIVE_TEXT =\n 'invalid_offset: in_offset and out_offset must each be a whole number, 0 or more'\n\nexport const INVALID_DAY_TEXT =\n 'invalid_day: day must be a calendar date written YYYY-MM-DD (UTC), for example 2026-07-10'\nexport const BOTH_FORMS_TEXT =\n 'invalid_arguments: pass address (with in_offset and out_offset) to expand a node, or from, to and day to list the transfers of a link, not both'\nexport const INVALID_LIMIT_TEXT = 'invalid_limit: limit must be a whole number from 1 to 50'\nexport const INVALID_SKIP_TEXT = 'invalid_skip: skip must be a whole number of 0 or more'\nexport const INVALID_FIRST_DAY_TEXT =\n 'invalid_first_day: first_day must be a calendar date written YYYY-MM-DD (UTC), not after day'\nexport const DETAILS_FORM_TEXT =\n 'invalid_arguments: details takes address alone; pass no from, to, day or offsets with it'\n\nconst ADDRESS_PATTERN = /^0x[0-9a-f]{40}$/\nconst DAY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\nconst CODE_PREFIX = /^([a-z][a-z0-9_]*):\\s*/\n\n/**\n * The address lower-cased, or null when it is not 0x and 40 hexadecimal\n * characters. Only an address that passes is ever written into a read, so the\n * reads can carry it as a literal.\n */\nexport function normalizeFlowsAddress(raw: unknown): string | null {\n if (typeof raw !== 'string') return null\n const address = raw.trim().toLowerCase()\n return ADDRESS_PATTERN.test(address) ? address : null\n}\n\n/**\n * The day as YYYY-MM-DD when it is a real calendar date, or null. Only a day\n * that passes is ever written into a read.\n */\nexport function normalizeFlowsDay(raw: unknown): string | null {\n if (typeof raw !== 'string') return null\n const day = raw.trim()\n if (!DAY_PATTERN.test(day)) return null\n const time = Date.parse(`${day}T00:00:00Z`)\n if (Number.isNaN(time) || new Date(time).toISOString().slice(0, 10) !== day) return null\n return day\n}\n\n/**\n * The one facts read of a link: the transfers from one address to another on\n * one day. A facts read names the pair with one day, takes no ORDER BY and\n * holds at most 200 rows, so the read asks for the whole cap and the order is\n * made in buildTransfersView. Both addresses and the day have passed their\n * checks, so they are written into the read as literals.\n */\nexport function transfersReadFor(from: string, to: string, day: string): string {\n return (\n `USE facts MATCH (a:Address {address: \"${from}\"})-[t:TRANSFER]->(b:Address {address: \"${to}\"}) ` +\n `WHERE t.block_date = \"${day}\" ` +\n 'RETURN t.tx_id AS tx_id, t.block_timestamp AS block_timestamp, t.amount AS amount, ' +\n `t.asset_symbol AS asset_symbol, t.amount_usd AS amount_usd LIMIT ${TRANSFERS_READ_ROWS}`\n )\n}\n\n/** The UTC day before a YYYY-MM-DD day. */\nexport function dayBefore(day: string): string {\n return new Date(Date.parse(`${day}T00:00:00Z`) - 86_400_000).toISOString().slice(0, 10)\n}\n\n/**\n * The one topology read of a details call: the address alone, with its\n * lifetime rollups and activity span. The address has passed its check, so it\n * is written into the read as a literal.\n */\nexport function detailsReadFor(address: string): string {\n return (\n `USE topology MATCH (b:Address {address: \"${address}\"}) RETURN ${NODE_FIELDS}, ` +\n 'b.first_activity_timestamp AS first_activity_timestamp, b.last_activity_timestamp AS last_activity_timestamp LIMIT 1'\n )\n}\n\nexport type AddressView = FlowNode & {\n schema: typeof ADDRESS_SCHEMA\n network: string\n is_exchange: boolean\n is_sanctioned: boolean\n is_scam: boolean\n is_victim: boolean\n pool: boolean\n first_activity_timestamp: number | null\n last_activity_timestamp: number | null\n}\n\n/** chain-insights.address.v1 from the one row of a details read. */\nexport function buildAddressView(network: string, address: string, row: Row): AddressView {\n const node = flowsNodeOf({ ...row, address })\n return {\n schema: ADDRESS_SCHEMA,\n network,\n ...node,\n is_exchange: row.is_exchange === true,\n is_sanctioned: row.is_sanctioned === true,\n is_scam: row.is_scam === true,\n is_victim: row.is_victim === true,\n pool: row.pool === true,\n first_activity_timestamp: flowsNumberOrNull(row.first_activity_timestamp),\n last_activity_timestamp: flowsNumberOrNull(row.last_activity_timestamp),\n }\n}\n\n/** The one line of a details answer. */\nexport function addressSummary(view: AddressView): string {\n return (\n `${view.address} on ${view.network}: ${view.role}; lifetime ${formatFlowsUsd(view.total_in_usd)} in, ` +\n `${formatFlowsUsd(view.total_out_usd)} out; ${view.degree_in} senders and ${view.degree_out} receivers in the topology graph` +\n (view.labels.length ? `; labels ${view.labels.join(', ')}` : '') +\n '.'\n )\n}\n\nconst NODE_FIELDS =\n 'b.address AS address, b.labels AS labels, b.is_exchange AS is_exchange, ' +\n 'b.is_sanctioned AS is_sanctioned, b.is_scam AS is_scam, b.is_victim AS is_victim, b:Pool AS pool, ' +\n 'b.total_in_usd AS total_in_usd, b.total_out_usd AS total_out_usd, b.degree_in AS degree_in, b.degree_out AS degree_out'\nconst PARTY_FIELDS =\n NODE_FIELDS +\n ', f.amount_usd_sum AS usd, f.tx_count AS tx_count, ' +\n 'f.first_seen_timestamp AS first_seen, f.last_seen_timestamp AS last_seen'\n// Newest link first, the address breaking a tie, so a page and the next one\n// never share or skip a link.\nconst PAGE_ORDER = ' ORDER BY f.last_seen_timestamp DESC, b.address'\n\n/** One anchored read of a page, with the SKIP and LIMIT it carries. */\nexport type FlowsRead = { id: 'node' | 'out' | 'in'; query: string; skip: number; limit: number }\n\n/**\n * The anchored reads of one page: the node, then the receivers, then the\n * senders. Each side asks for one row more than it shows: the extra row says\n * another page exists. A reply holds at most FLOWS_READ_ROWS rows, so a side\n * is read in chunks on the same order, each starting where the last one ends.\n */\nexport function flowsReadsFor(address: string, inOffset: number, outOffset: number): FlowsRead[] {\n const side = (id: 'out' | 'in', offset: number, match: string): FlowsRead[] => {\n const reads: FlowsRead[] = []\n for (let done = 0; done < FLOWS_PER_SIDE + 1; done += FLOWS_READ_ROWS) {\n const skip = offset + done\n const limit = Math.min(FLOWS_READ_ROWS, FLOWS_PER_SIDE + 1 - done)\n reads.push({\n id,\n query: `USE topology MATCH ${match} RETURN ${PARTY_FIELDS}${PAGE_ORDER}${skip > 0 ? ` SKIP ${skip}` : ''} LIMIT ${limit}`,\n skip,\n limit,\n })\n }\n return reads\n }\n return [\n {\n id: 'node',\n query: `USE topology MATCH (b:Address {address: '${address}'}) RETURN ${NODE_FIELDS} LIMIT 1`,\n skip: 0,\n limit: 1,\n },\n ...side('out', outOffset, `(a:Address {address: '${address}'})-[f:FLOWS_TO]->(b:Address)`),\n ...side('in', inOffset, `(b:Address)-[f:FLOWS_TO]->(a:Address {address: '${address}'})`),\n ]\n}\n\n/** The role word of an address, first match wins. */\nexport function flowsRole(row: Row): string {\n if (row.is_exchange === true) return 'exchange'\n if (row.is_sanctioned === true) return 'sanctioned'\n if (row.is_scam === true) return 'scam'\n if (row.is_victim === true) return 'victim'\n if (row.pool === true) return 'pool'\n return 'other'\n}\n\nfunction flowsFloat(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0\n}\n\nfunction flowsInt(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? Math.trunc(value) : 0\n}\n\nfunction flowsNodeOf(row: Row): FlowNode {\n const labels = Array.isArray(row.labels)\n ? row.labels.filter((label): label is string => typeof label === 'string')\n : []\n return {\n address: typeof row.address === 'string' ? row.address : '',\n role: flowsRole(row),\n labels,\n total_in_usd: flowsFloat(row.total_in_usd),\n total_out_usd: flowsFloat(row.total_out_usd),\n degree_in: flowsInt(row.degree_in),\n degree_out: flowsInt(row.degree_out),\n }\n}\n\nfunction assembleFlowView(\n network: string,\n page: FlowsPage,\n senders: Row[],\n receivers: Row[],\n truncated: boolean\n): FlowView {\n const center = flowsNodeOf(page.center)\n const cursor = {\n in_offset: page.inOffset + senders.length,\n out_offset: page.outOffset + receivers.length,\n }\n const view: FlowView = {\n schema: FLOWS_SCHEMA,\n network,\n center: center.address,\n nodes: [center],\n edges: [],\n cursor,\n truncated,\n remaining: {\n senders: Math.max(0, center.degree_in - cursor.in_offset),\n receivers: Math.max(0, center.degree_out - cursor.out_offset),\n },\n }\n const seen = new Set([center.address])\n const add = (row: Row, incoming: boolean) => {\n const node = flowsNodeOf(row)\n if (!seen.has(node.address)) {\n seen.add(node.address)\n view.nodes.push(node)\n }\n view.edges.push({\n from: incoming ? node.address : center.address,\n to: incoming ? center.address : node.address,\n usd: flowsFloat(row.usd),\n tx_count: flowsInt(row.tx_count),\n first_seen_ms: flowsInt(row.first_seen),\n last_seen_ms: flowsInt(row.last_seen),\n })\n }\n for (const row of senders) add(row, true)\n for (const row of receivers) add(row, false)\n return view\n}\n\nfunction fitsFlowBudget(view: FlowView): boolean {\n return view.nodes.length <= FLOWS_MAX_NODES && JSON.stringify(view).length < FLOWS_MAX_CHARS\n}\n\n/**\n * How many senders and receivers stay after `drops` of the oldest links go,\n * one at a time from the longer side, the senders first on a tie.\n */\nfunction keptAfterDrops(senders: number, receivers: number, drops: number): [number, number] {\n let s = senders\n let r = receivers\n for (let left = Math.min(drops, s + r); left > 0; left -= 1) {\n if (s >= r) s -= 1\n else r -= 1\n }\n return [s, r]\n}\n\n/**\n * flows.v1 from one page: the centre first, then the senders and the\n * receivers, each newest first, at most FLOWS_PER_SIDE a side. When the view\n * data would hold more than FLOWS_MAX_NODES addresses or reach FLOWS_MAX_CHARS\n * characters, the oldest links go first, the view is marked truncated, and the\n * cursor still says where the next page starts.\n *\n * Each dropped link only makes the view smaller, so the fewest drops that fit\n * are found by halving: about ten views are measured for a full page, not one\n * per dropped link.\n */\nexport function buildFlowView(network: string, page: FlowsPage): FlowView {\n const overflow = page.senders.length > FLOWS_PER_SIDE || page.receivers.length > FLOWS_PER_SIDE\n const senders = page.senders.slice(0, FLOWS_PER_SIDE)\n const receivers = page.receivers.slice(0, FLOWS_PER_SIDE)\n const viewAfter = (drops: number) => {\n const [s, r] = keptAfterDrops(senders.length, receivers.length, drops)\n return assembleFlowView(\n network,\n page,\n senders.slice(0, s),\n receivers.slice(0, r),\n overflow || drops > 0\n )\n }\n // Dropping every link is the floor: that view is answered even when it does\n // not fit.\n let low = 0\n let high = senders.length + receivers.length\n while (low < high) {\n const mid = (low + high) >> 1\n if (fitsFlowBudget(viewAfter(mid))) high = mid\n else low = mid + 1\n }\n return viewAfter(low)\n}\n\nexport function formatFlowsUsd(value: number): string {\n if (!(value > 0)) return '$0'\n if (value < 0.01) return 'under $0.01'\n const whole = Math.floor(value)\n const cents = Math.round((value - whole) * 100)\n if (cents === 100) return formatFlowsUsd(whole + 1)\n const grouped = whole.toFixed(0).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')\n return `$${grouped}.${String(cents).padStart(2, '0')}`\n}\n\nfunction formatFlowsAge(lastSeenMs: number, now: Date): string {\n if (lastSeenMs <= 0) return 'at an unknown time'\n const date = new Date(lastSeenMs).toISOString().slice(0, 10)\n const days = Math.trunc((now.getTime() - lastSeenMs) / 86_400_000)\n if (days < 1) return `${date} (less than a day ago)`\n if (days === 1) return `${date} (1 day ago)`\n return `${date} (${days} days ago)`\n}\n\n/** The role word, with up to three address labels beside it. */\nfunction roleText(node: FlowNode | undefined): string {\n if (!node) return 'other'\n if (node.labels.length === 0) return node.role\n return `${node.role} (${node.labels.slice(0, 3).join(', ')})`\n}\n\nfunction sideLines(title: string, parties: string[]): string[] {\n if (parties.length === 0) return [`${title}, newest first: none.`]\n const lines = [`${title}, newest first (${parties.length}):`]\n // A side with more than six counterparties lists two on a line, so a full\n // page stays within the line budget.\n const perLine = parties.length > 6 ? 2 : 1\n for (let start = 0; start < parties.length; start += perLine) {\n lines.push(`- ${parties.slice(start, start + perLine).join('; ')}`)\n }\n return lines\n}\n\n/**\n * The text of a node answer: the centre, its role and lifetime totals, then\n * every counterparty of the view with its USD, transaction count and last-seen\n * date and age. At most FLOWS_SUMMARY_MAX_LINES lines.\n */\nexport function flowsSummary(view: FlowView, now: Date): string {\n const nodes = new Map(view.nodes.map((node) => [node.address, node]))\n const center = nodes.get(view.center)\n const party = (address: string, edge: FlowEdge) =>\n `${address} ${roleText(nodes.get(address))}, ${formatFlowsUsd(edge.usd)} in ${edge.tx_count} tx, last seen ${formatFlowsAge(edge.last_seen_ms, now)}`\n const senders: string[] = []\n const receivers: string[] = []\n for (const edge of view.edges) {\n if (edge.to === view.center) senders.push(party(edge.from, edge))\n else receivers.push(party(edge.to, edge))\n }\n const lines = [\n `Money flows of ${view.center} on ${view.network}: ${roleText(center)}; lifetime ${formatFlowsUsd(center?.total_in_usd ?? 0)} in, ${formatFlowsUsd(center?.total_out_usd ?? 0)} out; ${center?.degree_in ?? 0} senders and ${center?.degree_out ?? 0} receivers in the topology graph.`,\n ...sideLines('Recent senders', senders),\n ...sideLines('Recent receivers', receivers),\n ]\n const body = lines.slice(0, FLOWS_SUMMARY_MAX_LINES - 2)\n const footer = pagingLines(view, senders.length, receivers.length, center)\n return [...body, ...footer].join('\\n')\n}\n\n/** Where this page sits, and that the picture loads the rest when an address is clicked. */\nfunction pagingLines(\n view: FlowView,\n shownSenders: number,\n shownReceivers: number,\n center: FlowNode | undefined\n): string[] {\n const totalIn = center?.degree_in ?? 0\n const totalOut = center?.degree_out ?? 0\n const { in_offset: nextIn, out_offset: nextOut } = view.cursor\n const range = (shown: number, next: number, total: number) =>\n shown === 0 ? `none of ${total}` : `${next - shown + 1} to ${next} of ${total}`\n const where = `Showing senders ${range(shownSenders, nextIn, totalIn)} and receivers ${range(shownReceivers, nextOut, totalOut)}, newest first.`\n if (!view.truncated && nextIn >= totalIn && nextOut >= totalOut) return [where]\n return [\n where,\n 'More senders and receivers load when an address in the picture is double-clicked.',\n ]\n}\n\nexport type TransferRow = {\n tx_id: string\n block_timestamp: number | null\n amount: number | string | null\n asset_symbol: string | null\n amount_usd: number | null\n}\n\nexport type TransfersCursor = { day: string; skip: number }\n\nexport type TransfersView = {\n schema: typeof TRANSFERS_SCHEMA\n network: string\n from: string\n to: string\n day: string\n transfers: TransferRow[]\n truncated: boolean\n /** Paged answers only: the earliest day read, how many days the call read, and where the next page starts (null: every transfer back to first_day is listed). */\n through_day?: string\n days_read?: number\n next?: TransfersCursor | null\n}\n\nexport type TransfersPageRequest = { skip: number; firstDay: string | null; limit: number }\n\n/** A finite number, or a decimal string read as one, or null. */\nfunction flowsNumberOrNull(value: unknown): number | null {\n if (typeof value === 'number') return Number.isFinite(value) ? value : null\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\nfunction transferOf(row: Row): TransferRow {\n const amount =\n typeof row.amount === 'number' && Number.isFinite(row.amount)\n ? row.amount\n : typeof row.amount === 'string' && row.amount.trim() !== ''\n ? row.amount\n : null\n return {\n tx_id: typeof row.tx_id === 'string' ? row.tx_id : '',\n block_timestamp: flowsNumberOrNull(row.block_timestamp),\n amount,\n asset_symbol:\n typeof row.asset_symbol === 'string' && row.asset_symbol !== '' ? row.asset_symbol : null,\n amount_usd: flowsNumberOrNull(row.amount_usd),\n }\n}\n\n/**\n * chain-insights.transfers.v1 from the rows of one facts read: newest first,\n * at most TRANSFERS_MAX, truncated when more rows than that came back. The\n * facts layer takes no ORDER BY, so the order is made here.\n */\nexport function buildTransfersView(\n network: string,\n from: string,\n to: string,\n day: string,\n rows: Row[]\n): TransfersView {\n const newestFirst = rows\n .map(transferOf)\n .sort((a, b) => (b.block_timestamp ?? -Infinity) - (a.block_timestamp ?? -Infinity))\n return {\n schema: TRANSFERS_SCHEMA,\n network,\n from,\n to,\n day,\n transfers: newestFirst.slice(0, TRANSFERS_MAX),\n truncated: newestFirst.length > TRANSFERS_MAX,\n }\n}\n\nfunction transferLine(transfer: TransferRow): string {\n const time =\n transfer.block_timestamp === null\n ? 'at an unknown time'\n : `${new Date(transfer.block_timestamp).toISOString().slice(0, 19).replace('T', ' ')} UTC`\n const amount = transfer.amount === null ? 'an unknown amount' : String(transfer.amount)\n const asset = transfer.asset_symbol ? ` ${transfer.asset_symbol}` : ''\n const usd = transfer.amount_usd === null ? 'no USD price' : formatFlowsUsd(transfer.amount_usd)\n return `- ${time}, ${amount}${asset}, ${usd}, tx ${transfer.tx_id}`\n}\n\n/** The short text of a link answer: the pair, the day, the count and the newest few transfers. */\nexport function transfersSummary(view: TransfersView): string {\n const paged = view.through_day !== undefined\n const span = paged && view.through_day !== view.day ? `${view.through_day} to ${view.day}` : view.day\n const head = `Transfers from ${view.from} to ${view.to} on ${span} (UTC) on ${view.network}`\n if (view.transfers.length === 0) {\n return paged && view.next ? `${head}: none; earlier days remain before ${view.next.day}.` : `${head}: none.`\n }\n const count = view.truncated\n ? paged\n ? `${view.transfers.length}, and a day holds more than one read lists`\n : `the newest ${view.transfers.length}, and the day holds more`\n : `${view.transfers.length}`\n const lines = [\n `${head}: ${count}, newest first.`,\n ...view.transfers.slice(0, TRANSFERS_SUMMARY_ROWS).map(transferLine),\n ]\n const rest = view.transfers.length - TRANSFERS_SUMMARY_ROWS\n if (rest > 0) lines.push(`- and ${rest} more in the picture.`)\n if (paged && view.next) lines.push(`- earlier transfers remain, from ${view.next.day} back.`)\n return lines.join('\\n')\n}\n\nfunction errorResult(text: string): FlowsToolResult {\n return { content: [{ type: 'text', text }], isError: true }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction answerText(answer: GraphQueryAnswer): string {\n return (answer.content ?? [])\n .map((block) => (block.type === 'text' && typeof block.text === 'string' ? block.text : ''))\n .filter(Boolean)\n .join('\\n')\n .trim()\n}\n\n/**\n * The refusal of a read that did not answer, naming its code first:\n * error_detail.code when the graph endpoint sent its typed refusal, the code\n * its text starts with otherwise, and graph_query_failed when it names none.\n * The view marks the address it was expanding.\n */\nexport function refusalText(answer: GraphQueryAnswer): string {\n const text = answerText(answer)\n const detail = isRecord(answer.structuredContent)\n ? answer.structuredContent.error_detail\n : undefined\n const detailCode = isRecord(detail) && typeof detail.code === 'string' ? detail.code : ''\n const textCode = CODE_PREFIX.exec(text)?.[1] ?? ''\n const code = detailCode || textCode || 'graph_query_failed'\n const rest = textCode === code ? text.replace(CODE_PREFIX, '') : text\n return rest ? `${code}: ${rest}` : `${code}: the graph endpoint refused the read`\n}\n\n/** The endpoint says it cut the reply short (facts.query.truncated). */\nfunction cutByEndpoint(answer: GraphQueryAnswer): boolean {\n const facts = isRecord(answer.structuredContent) ? answer.structuredContent.facts : undefined\n const query = isRecord(facts) ? facts.query : undefined\n return isRecord(query) && query.truncated === true\n}\n\nfunction rowsOf(answer: GraphQueryAnswer): Row[] | null {\n const facts = isRecord(answer.structuredContent) ? answer.structuredContent.facts : undefined\n const query = isRecord(facts) ? facts.query : undefined\n const results = isRecord(query) ? query.results : undefined\n return Array.isArray(results) ? results.filter(isRecord) : null\n}\n\ntype Checked<T> = T | { error: string }\n\nfunction checkNetwork(args: Record<string, unknown>): Checked<string> {\n const rawNetwork = typeof args.network === 'string' ? args.network.trim().toLowerCase() : ''\n if (!rawNetwork) return { error: 'invalid_network: network is required; pass robinhood' }\n if (rawNetwork !== FLOWS_NETWORK) {\n return {\n error: `invalid_network: graph_expand reads the robinhood graph only; got \"${String(args.network).trim()}\"`,\n }\n }\n return FLOWS_NETWORK\n}\n\n/** The address of a node or of one end of a link; `name` says which end in a link refusal. */\nfunction checkAddress(raw: unknown, name?: string): Checked<string> {\n const address = normalizeFlowsAddress(raw)\n if (address) return address\n return {\n error: name\n ? INVALID_ADDRESS_TEXT.replace('invalid_address:', `invalid_address (${name}):`)\n : INVALID_ADDRESS_TEXT,\n }\n}\n\nfunction checkDay(raw: unknown): Checked<string> {\n const day = normalizeFlowsDay(raw)\n return day ?? { error: INVALID_DAY_TEXT }\n}\n\nfunction checkLimit(value: unknown): Checked<number> {\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 1 || value > TRANSFERS_MAX) {\n return { error: INVALID_LIMIT_TEXT }\n }\n return value\n}\n\nfunction checkSkip(value: unknown): Checked<number> {\n if (value === undefined || value === null) return 0\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) return { error: INVALID_SKIP_TEXT }\n return value\n}\n\nfunction checkFirstDay(value: unknown, day: string): Checked<string | null> {\n if (value === undefined || value === null) return null\n const first = normalizeFlowsDay(value)\n if (!first || first > day) return { error: INVALID_FIRST_DAY_TEXT }\n return first\n}\n\nfunction checkOffset(value: unknown): Checked<number> {\n if (value === undefined || value === null) return 0\n if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {\n return { error: OFFSET_NEGATIVE_TEXT }\n }\n if (value > FLOWS_MAX_OFFSET) return { error: OFFSET_TOO_LARGE_TEXT }\n return value\n}\n\n/**\n * Read and answer one node page. The reads run one after another: the node\n * first, so an address the graph does not hold costs one read; then the\n * receivers and the senders, the chunks of a side joined in order. A chunk that\n * comes back short, and not cut by the endpoint, ends its side, so the next\n * chunk of that side is not read or billed. The first read that does not\n * answer ends the call.\n */\nasync function runNode(\n deps: FlowsDependencies,\n network: string,\n address: string,\n inOffset: number,\n outOffset: number\n): Promise<FlowsToolResult> {\n const unavailable = deps.unavailable?.()\n if (unavailable) return errorResult(unavailable)\n const rows: Record<FlowsRead['id'], Row[]> = { node: [], out: [], in: [] }\n const ended = new Set<FlowsRead['id']>()\n for (const read of flowsReadsFor(address, inOffset, outOffset)) {\n if (ended.has(read.id)) continue\n let answer: GraphQueryAnswer\n try {\n answer = await deps.graphQuery({ network, query: read.query })\n } catch (err) {\n return errorResult(deps.describeFailure(err))\n }\n if (answer.isError === true) return errorResult(refusalText(answer))\n const found = rowsOf(answer)\n if (!found) {\n return errorResult(\n 'graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows'\n )\n }\n rows[read.id].push(...found)\n if (read.id === 'node' && found.length === 0) {\n return errorResult(`${address} is not in the ${network} topology graph`)\n }\n if (found.length < read.limit && !cutByEndpoint(answer)) ended.add(read.id)\n }\n const view = buildFlowView(network, {\n center: rows.node[0] ?? {},\n senders: rows.in,\n receivers: rows.out,\n inOffset,\n outOffset,\n })\n const now = deps.now ? deps.now() : new Date()\n return {\n content: [{ type: 'text', text: flowsSummary(view, now) }],\n structuredContent: view as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\n/** Read and answer the transfers of one link on one day: a single facts read. */\nasync function runLink(\n deps: FlowsDependencies,\n network: string,\n from: string,\n to: string,\n day: string\n): Promise<FlowsToolResult> {\n const unavailable = deps.unavailable?.()\n if (unavailable) return errorResult(unavailable)\n let answer: GraphQueryAnswer\n try {\n answer = await deps.graphQuery({ network, query: transfersReadFor(from, to, day) })\n } catch (err) {\n return errorResult(deps.describeFailure(err))\n }\n if (answer.isError === true) return errorResult(refusalText(answer))\n const rows = rowsOf(answer)\n if (!rows) {\n return errorResult(\n 'graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows'\n )\n }\n const view = buildTransfersView(network, from, to, day, rows)\n return {\n content: [{ type: 'text', text: transfersSummary(view) }],\n structuredContent: view as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\n/** One facts read of a day, as the paged walk needs it: the rows newest first, or the error that ends the call. */\nasync function readDay(\n deps: FlowsDependencies,\n network: string,\n from: string,\n to: string,\n day: string\n): Promise<{ rows: TransferRow[]; cut: boolean } | { error: string }> {\n let answer: GraphQueryAnswer\n try {\n answer = await deps.graphQuery({ network, query: transfersReadFor(from, to, day) })\n } catch (err) {\n return { error: deps.describeFailure(err) }\n }\n if (answer.isError === true) return { error: refusalText(answer) }\n const rows = rowsOf(answer)\n if (!rows) {\n return { error: 'graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows' }\n }\n const newestFirst = rows\n .map(transferOf)\n .sort((a, b) => (b.block_timestamp ?? -Infinity) - (a.block_timestamp ?? -Infinity))\n return { rows: newestFirst, cut: rows.length >= TRANSFERS_READ_ROWS }\n}\n\n/**\n * Read and answer one page of a link's transfers: from `day` back, one day a\n * read, until the page holds `limit` transfers, TRANSFERS_MAX_DAYS days were\n * read, or the day before first_day is reached. `skip` passes over the\n * transfers of `day` an earlier page listed. The first read that does not\n * answer ends the call.\n */\nasync function runLinkPage(\n deps: FlowsDependencies,\n network: string,\n from: string,\n to: string,\n day: string,\n page: TransfersPageRequest\n): Promise<FlowsToolResult> {\n const unavailable = deps.unavailable?.()\n if (unavailable) return errorResult(unavailable)\n const floor = page.firstDay ?? TRANSFERS_FLOOR_DAY\n const transfers: TransferRow[] = []\n let current = day\n let skip = page.skip\n let daysRead = 0\n let through = day\n let truncated = false\n let next: TransfersCursor | null = null\n for (;;) {\n if (current < floor) break\n if (daysRead >= TRANSFERS_MAX_DAYS) {\n next = { day: current, skip }\n break\n }\n const read = await readDay(deps, network, from, to, current)\n if ('error' in read) return errorResult(read.error)\n daysRead += 1\n through = current\n if (read.cut) truncated = true\n const rest = read.rows.slice(skip)\n const room = page.limit - transfers.length\n if (rest.length > room) {\n transfers.push(...rest.slice(0, room))\n next = { day: current, skip: skip + room }\n break\n }\n transfers.push(...rest)\n const earlier = dayBefore(current)\n if (transfers.length >= page.limit) {\n next = earlier < floor ? null : { day: earlier, skip: 0 }\n break\n }\n current = earlier\n skip = 0\n }\n const view: TransfersView = {\n schema: TRANSFERS_SCHEMA,\n network,\n from,\n to,\n day,\n transfers,\n truncated,\n through_day: through,\n days_read: daysRead,\n next,\n }\n return {\n content: [{ type: 'text', text: transfersSummary(view) }],\n structuredContent: view as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\n/** Read and answer the details of one address: a single anchored topology read. */\nasync function runDetails(\n deps: FlowsDependencies,\n network: string,\n address: string\n): Promise<FlowsToolResult> {\n const unavailable = deps.unavailable?.()\n if (unavailable) return errorResult(unavailable)\n let answer: GraphQueryAnswer\n try {\n answer = await deps.graphQuery({ network, query: detailsReadFor(address) })\n } catch (err) {\n return errorResult(deps.describeFailure(err))\n }\n if (answer.isError === true) return errorResult(refusalText(answer))\n const rows = rowsOf(answer)\n if (!rows) {\n return errorResult(\n 'graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows'\n )\n }\n if (rows.length === 0) return errorResult(`${address} is not in the ${network} topology graph`)\n const view = buildAddressView(network, address, rows[0]!)\n return {\n content: [{ type: 'text', text: addressSummary(view) }],\n structuredContent: view as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\nfunction given(record: Record<string, unknown>, keys: string[]): boolean {\n return keys.some((key) => record[key] !== undefined && record[key] !== null)\n}\n\n/**\n * graph_expand, the node form {network, address, in_offset?, out_offset?} or the\n * link form {network, from, to, day}. Every argument is checked before the\n * first read.\n */\nexport async function handleGraphExpand(\n args: unknown,\n deps: FlowsDependencies\n): Promise<FlowsToolResult> {\n const record = isRecord(args) ? args : {}\n const network = checkNetwork(record)\n if (typeof network !== 'string') return errorResult(network.error)\n\n const isLink = given(record, ['from', 'to', 'day'])\n if (isLink && given(record, ['address', 'in_offset', 'out_offset', 'details']))\n return errorResult(BOTH_FORMS_TEXT)\n\n if (isLink) {\n const from = checkAddress(record.from, 'from')\n if (typeof from !== 'string') return errorResult(from.error)\n const to = checkAddress(record.to, 'to')\n if (typeof to !== 'string') return errorResult(to.error)\n const day = checkDay(record.day)\n if (typeof day !== 'string') return errorResult(day.error)\n // A limit, a skip or a first_day asks for a page across days; without them the call is the one-day read.\n if (given(record, ['limit', 'skip', 'first_day'])) {\n const limit = checkLimit(record.limit)\n if (typeof limit !== 'number') return errorResult(limit.error)\n const skip = checkSkip(record.skip)\n if (typeof skip !== 'number') return errorResult(skip.error)\n const firstDay = checkFirstDay(record.first_day, day)\n if (firstDay !== null && typeof firstDay !== 'string') return errorResult(firstDay.error)\n return runLinkPage(deps, network, from, to, day, { skip, firstDay, limit })\n }\n return runLink(deps, network, from, to, day)\n }\n\n if (record.details !== undefined && record.details !== null) {\n if (record.details !== true) return errorResult(DETAILS_FORM_TEXT)\n if (given(record, ['in_offset', 'out_offset'])) return errorResult(DETAILS_FORM_TEXT)\n const address = checkAddress(record.address)\n if (typeof address !== 'string') return errorResult(address.error)\n return runDetails(deps, network, address)\n }\n\n const address = checkAddress(record.address)\n if (typeof address !== 'string') return errorResult(address.error)\n const inOffset = checkOffset(record.in_offset)\n if (typeof inOffset !== 'number') return errorResult(inOffset.error)\n const outOffset = checkOffset(record.out_offset)\n if (typeof outOffset !== 'number') return errorResult(outOffset.error)\n return runNode(deps, network, address, inOffset, outOffset)\n}\n\nexport const GRAPH_EXPAND_TITLE = 'Expand a node or a link in the picture'\n\nexport const GRAPH_EXPAND_DESCRIPTION =\n 'Expand an open Chain Insights picture. Called by the view when the investigator clicks, never by the model. ' +\n 'A node, {network, address, in_offset?, out_offset?}, loads the newest senders and receivers of one address ' +\n '(up to 250 a side, from the offsets); billed as at most five graph_query topology reads. A link, {network, from, to, day}, ' +\n 'lists the transfers between two addresses on one UTC day (YYYY-MM-DD), newest first, at most 50; billed as ' +\n 'one graph_query facts read. A link page, {network, from, to, day, limit, skip?, first_day?}, lists `limit` (1-50) ' +\n 'transfers from `day` back across days, newest first, and answers a `next` cursor ({day, skip}) while older ones ' +\n 'remain; billed as at most seven facts reads (a week). Details, {network, address, details: true}, answer the lifetime totals, ' +\n 'counts, role and labels of one address; billed as one topology read. Read-only; an argument refused before a read costs nothing.'\n","import { createHash } from 'node:crypto'\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport path from 'node:path'\nimport { CACHE_TTL_MS, chainInsightsHome } from './cache-location.js'\nimport type { GraphSchemaDocument } from './graph-schema.js'\n\n// The graph schema of one network on one endpoint, kept on disk beside the tool\n// list cache (src/mcp/schema-cache.ts) with the same home and the same 24 hour\n// lifetime: ~/.chain-insights/cache/schema-<network>-<endpoint hash>.json.\n//\n// A schema built while a read failed is kept for a short time only. Kept for\n// 24 hours, one refused or rate-limited read would hide a section of the schema\n// for a whole day.\n\n/** How long a schema with a failed read stays fresh. */\nexport const PARTIAL_SCHEMA_TTL_MS = 10 * 60 * 1000\n\ninterface GraphSchemaCacheFile {\n cached_at: number\n complete: boolean\n endpoint: string\n document: GraphSchemaDocument\n}\n\nfunction endpointHash(endpoint: string): string {\n return createHash('sha256').update(endpoint).digest('hex').slice(0, 12)\n}\n\n/** The cache file of a network on an endpoint. The network is an identifier already checked by the caller. */\nexport function graphSchemaCachePath(network: string, endpoint: string): string {\n return path.join(chainInsightsHome(), 'cache', `schema-${network}-${endpointHash(endpoint)}.json`)\n}\n\n/**\n * The cached schema, marked cached, or null when there is none, it is stale, or\n * the file cannot be read. A cache is derived data: a damaged file is a miss and\n * the next build replaces it.\n */\nexport async function loadGraphSchemaCache(\n network: string,\n endpoint: string,\n now: Date\n): Promise<GraphSchemaDocument | null> {\n let parsed: unknown\n try {\n parsed = JSON.parse(await readFile(graphSchemaCachePath(network, endpoint), 'utf8'))\n } catch {\n return null\n }\n const file = parsed as Partial<GraphSchemaCacheFile> | null\n if (\n !file ||\n typeof file.cached_at !== 'number' ||\n file.endpoint !== endpoint ||\n file.document?.schema !== 'chain-insights.graph-schema.v1'\n ) {\n return null\n }\n const lifetime = file.complete === true ? CACHE_TTL_MS : PARTIAL_SCHEMA_TTL_MS\n const age = now.getTime() - file.cached_at\n if (age < 0 || age > lifetime) return null\n return { ...file.document, cached: true }\n}\n\n/** `network` is the name the caller asked for, the same one a later load asks with. */\nexport async function saveGraphSchemaCache(\n network: string,\n document: GraphSchemaDocument,\n endpoint: string,\n complete: boolean,\n now: Date\n): Promise<void> {\n const target = graphSchemaCachePath(network, endpoint)\n const file: GraphSchemaCacheFile = {\n cached_at: now.getTime(),\n complete,\n endpoint,\n document: { ...document, cached: false },\n }\n await mkdir(path.dirname(target), { recursive: true })\n // Written beside the target and renamed, so a second proxy reading the file\n // never sees half of it.\n const scratch = `${target}.${process.pid}.tmp`\n await writeFile(scratch, JSON.stringify(file, null, 2) + '\\n', { mode: 0o600 })\n await rename(scratch, target)\n}\n","/**\n * meta_schema: the live graph schema of one network, read from the graph\n * endpoint and kept on disk for 24 hours.\n *\n * The graph serves four read-only catalog statements on `USE topology` through\n * the ordinary graph_query tool: `CALL db.labels()`, `CALL db.relationshipTypes()`,\n * `CALL db.propertyKeys()` and `SHOW INDEXES`. They name the labels, the link\n * types, every property key and the indexes. They do not say which keys belong to\n * which kind, so the main kinds are sampled with the two probe forms the server\n * admits (`keys(a)` on addresses, `keys(r)` on a link). The facts relationships\n * and the chain lookups come from the network's published capabilities.\n *\n * One build is one MCP session and runs its reads one after another: the public\n * endpoint allows about 30 requests a minute for each IP address, and opening a\n * session costs three of them. A read that fails leaves its section empty and\n * adds a note. It never fails the tool.\n */\n\nimport { findNetworkCapability, mirrorGraphNetworkCapabilities } from './capabilities.js'\nimport type { NetworkCapability } from './capabilities.js'\nimport { PaymentRequiredError } from './client.js'\nimport { refusalText, type GraphQueryAnswer } from './flows.js'\nimport { loadGraphSchemaCache, saveGraphSchemaCache } from './graph-schema-cache.js'\n\nexport const GRAPH_SCHEMA_ID = 'chain-insights.graph-schema.v1' as const\nexport const GRAPH_SCHEMA_SOURCE = 'live catalog' as const\nexport const META_SCHEMA_TITLE = 'Graph schema'\nexport const META_SCHEMA_DESCRIPTION =\n 'Return the live graph schema of one network: the labels, the link types, the field names of an address and of each main link, the indexes, the USE facts relationships with the TRANSFER columns, and the USE chain lookups. ' +\n 'Call it first when you need field names. It is cached for 24 hours; pass refresh=true to rebuild it.'\n\n/** Fields of an address come from this many sampled nodes, and of a link from this many sampled links. */\nexport const ADDRESS_SAMPLE_LIMIT = 20\nexport const LINK_SAMPLE_LIMIT = 5\n\n/** The link types an agent queries. Each is sampled only when the graph lists it. */\nexport const SAMPLED_LINK_TYPES = [\n 'FLOWS_TO',\n 'OPERATED_BY',\n 'LINKED',\n 'SWAPPED',\n 'BRIDGED',\n 'ADDED_LIQUIDITY',\n 'REMOVED_LIQUIDITY',\n] as const\n\n// The TRANSFER columns the cypher skill lists, in its order. tests/graph-schema.test.ts\n// holds this list to the skill and to the columns the graph server maps on TRANSFER\n// (tests/fixtures/facts-columns.json).\nexport const FACTS_TRANSFER_COLUMNS = [\n 'tx_id',\n 'block_date',\n 'block_height',\n 'block_timestamp',\n 'event_index',\n 'edge_index',\n 'kind',\n 'asset_contract',\n 'asset_symbol',\n 'amount',\n 'amount_usd',\n 'price_usd',\n 'price_missing',\n 'token_id',\n 'token_standard',\n 'operator_address',\n 'raw_amount',\n 'decimals',\n] as const\n\n// `CALL db.schema.visualization()` is not read. It bills about 427 units where\n// the four statements below bill 13 to 133 each, and it adds only which labels a\n// link type joins, which the cypher skill and the served hints already say.\nconst CATALOG_READS = {\n labels: { label: 'labels', query: 'USE topology CALL db.labels()' },\n linkTypes: { label: 'link types', query: 'USE topology CALL db.relationshipTypes()' },\n propertyKeys: { label: 'property keys', query: 'USE topology CALL db.propertyKeys()' },\n indexes: { label: 'indexes', query: 'USE topology SHOW INDEXES' },\n} as const\n\nexport function addressSampleQuery(): string {\n return `USE topology MATCH (a:Address) RETURN keys(a) AS keys LIMIT ${ADDRESS_SAMPLE_LIMIT}`\n}\n\nexport function linkSampleQuery(linkType: string): string {\n return `USE topology MATCH ()-[r:${linkType}]->() RETURN keys(r) AS keys LIMIT ${LINK_SAMPLE_LIMIT}`\n}\n\nexport interface GraphSchemaIndex {\n name: string\n entity: 'node' | 'link'\n /** The labels of a node index, the link types of a link index. */\n types: string[]\n properties: string[]\n state: string\n}\n\nexport interface GraphSchemaDocument {\n schema: typeof GRAPH_SCHEMA_ID\n network: string\n built_at: string\n source: typeof GRAPH_SCHEMA_SOURCE\n cached: boolean\n /** Units the reads of the build billed, summed from the replies. A cached document keeps the units of its build. */\n billed_units: number\n layers: {\n topology: {\n labels: string[]\n link_types: string[]\n property_keys: string[]\n indexes: GraphSchemaIndex[]\n samples: Record<string, string[]>\n }\n facts: { relationships: string[]; transfer_columns: string[] }\n chain: { lookups: string[] }\n }\n notes: string[]\n}\n\n/** A refusal with its own words for the caller: a bad argument, an unserved network, an endpoint that is down. */\nexport class GraphSchemaError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'GraphSchemaError'\n }\n}\n\nexport interface GraphSchemaClient {\n callTool(request: { name: string; arguments: Record<string, unknown> }): Promise<unknown>\n}\n\ntype Row = Record<string, unknown>\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction sortedUnique(values: Iterable<string>): string[] {\n return [...new Set(values)].sort()\n}\n\nfunction stringList(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === 'string' && entry !== '')\n : []\n}\n\nconst NETWORK_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/\n\n/**\n * The network as a lower-case identifier. It becomes part of a file name and of\n * every query, so only an identifier passes.\n */\nexport function normalizeSchemaNetwork(raw: unknown): string {\n const network = typeof raw === 'string' ? raw.trim().toLowerCase() : ''\n if (!network) {\n throw new GraphSchemaError(\n 'invalid_network: network is required. Pass the identifier of a served network, for example robinhood.'\n )\n }\n if (!NETWORK_PATTERN.test(network)) {\n throw new GraphSchemaError(\n `invalid_network: \"${String(raw).trim()}\" is not a network identifier. Pass the identifier of a served network, for example robinhood.`\n )\n }\n return network\n}\n\n// ─── Reading ──────────────────────────────────────────────────────────────────\n\nfunction queryBlock(structured: unknown): Row | null {\n const facts = isRecord(structured) ? structured.facts : undefined\n const query = isRecord(facts) ? facts.query : undefined\n return isRecord(query) ? query : null\n}\n\n/** The `facts.query` block of one graph_query reply, from structuredContent or from the JSON text. */\nfunction answerQuery(answer: GraphQueryAnswer): Row | null {\n const fromStructured = queryBlock(answer.structuredContent)\n if (fromStructured) return fromStructured\n for (const block of answer.content ?? []) {\n if (block.type !== 'text' || typeof block.text !== 'string') continue\n try {\n const parsed = queryBlock(JSON.parse(block.text))\n if (parsed) return parsed\n } catch {\n /* not JSON: try the next block */\n }\n }\n return null\n}\n\n// A failure after which every later read would fail the same way: the endpoint\n// limits requests, or it asks for payment. The build stops sending reads.\ntype Halt = 'rate_limit' | 'payment'\n\nfunction haltOfError(err: unknown): Halt | undefined {\n if (err instanceof PaymentRequiredError) return 'payment'\n const code = isRecord(err) ? err.code : undefined\n const message = err instanceof Error ? err.message : String(err)\n if (code === 429 || /\\b429\\b|too many requests|rate.?limit/i.test(message)) return 'rate_limit'\n if (/\\b402\\b|payment required/i.test(message)) return 'payment'\n return undefined\n}\n\nfunction haltOfAnswer(answer: GraphQueryAnswer): Halt | undefined {\n const detail = isRecord(answer.structuredContent)\n ? answer.structuredContent.error_detail\n : undefined\n return isRecord(detail) && detail.class === 'capacity' ? 'rate_limit' : undefined\n}\n\nexport interface BuildOptions {\n client: GraphSchemaClient\n /** A normalized network identifier. */\n network: string\n now?: () => Date\n /** A wait between two reads, in milliseconds. One build needs none: it stays under the rate limit. */\n pauseMs?: number\n sleep?: (ms: number) => Promise<void>\n}\n\nexport interface BuildResult {\n document: GraphSchemaDocument\n /** False when a read failed: the document has a section missing. */\n complete: boolean\n}\n\nfunction describeIndex(row: Row): GraphSchemaIndex | null {\n const name = typeof row.name === 'string' ? row.name : ''\n const entity =\n row.entityType === 'NODE' ? 'node' : row.entityType === 'RELATIONSHIP' ? 'link' : null\n const properties = stringList(row.properties)\n // A lookup index names no label and no property: it tells an agent nothing.\n if (!name || !entity || properties.length === 0) return null\n return {\n name,\n entity,\n types: stringList(row.labelsOrTypes),\n properties,\n state: typeof row.state === 'string' && row.state ? row.state : 'unknown',\n }\n}\n\nexport async function buildGraphSchema(options: BuildOptions): Promise<BuildResult> {\n const { client } = options\n const now = options.now ?? (() => new Date())\n const pauseMs = options.pauseMs ?? 0\n const sleep = options.sleep ?? ((ms: number) => new Promise<void>((done) => setTimeout(done, ms)))\n\n const problems: string[] = []\n const remarks: string[] = []\n let billed = 0\n let calls = 0\n let halted = false\n\n async function pace(): Promise<void> {\n if (calls > 0 && pauseMs > 0) await sleep(pauseMs)\n calls += 1\n }\n\n function fail(label: string, reason: string, halt?: Halt): null {\n problems.push(`${label} could not be read: ${reason}`)\n if (halt && !halted) {\n halted = true\n if (halt === 'payment') {\n problems.push(\n 'The endpoint asks for payment, so the reads after it were not sent. Fix the payment setup, then call again with refresh=true.'\n )\n } else {\n // The endpoint says how long to wait: \"retry after 23 seconds\".\n const wait = /retry after (\\d+) seconds?/i.exec(reason)?.[1]\n problems.push(\n `The endpoint is limiting requests, so the reads after it were not sent. Call again after ${wait ? `${wait} seconds` : 'a minute'} with refresh=true.`\n )\n }\n }\n return null\n }\n\n async function graphRead(network: string, label: string, query: string): Promise<Row[] | null> {\n if (halted) return null\n await pace()\n let answer: GraphQueryAnswer\n try {\n answer = (await client.callTool({\n name: 'graph_query',\n arguments: { network, query },\n })) as GraphQueryAnswer\n } catch (err) {\n return fail(label, err instanceof Error ? err.message : String(err), haltOfError(err))\n }\n const block = answerQuery(answer ?? {})\n if (block && typeof block.billable_units === 'number') billed += block.billable_units\n if (answer?.isError === true) {\n return fail(label, refusalText(answer), haltOfAnswer(answer))\n }\n if (!block || !Array.isArray(block.results)) {\n return fail(label, 'the reply holds no rows')\n }\n return block.results.filter(isRecord)\n }\n\n // ── The network: capabilities first, so an unserved name costs no graph read.\n let network = options.network\n let capability: NetworkCapability | undefined\n await pace()\n try {\n const answer = (await client.callTool({\n name: 'network_capabilities',\n arguments: {},\n })) as GraphQueryAnswer\n if (answer?.isError === true) {\n fail('network capabilities', refusalText(answer), haltOfAnswer(answer))\n } else {\n const facts = isRecord(answer?.structuredContent) ? answer.structuredContent.facts : undefined\n const capabilities = isRecord(facts) ? facts.capabilities : undefined\n const networks =\n isRecord(capabilities) && Array.isArray(capabilities.networks)\n ? capabilities.networks\n : null\n if (!networks) {\n fail('network capabilities', 'the reply lists no networks')\n } else {\n const document = mirrorGraphNetworkCapabilities({ networks })\n capability = findNetworkCapability(document, options.network)\n if (!capability) {\n const served = document.networks.map((entry) => entry.network).join(', ')\n throw new GraphSchemaError(\n `network_not_served: \"${options.network}\" is not a network this endpoint serves. Served networks: ${served || 'none'}.`\n )\n }\n network = capability.network\n }\n }\n } catch (err) {\n if (err instanceof GraphSchemaError) throw err\n fail('network capabilities', err instanceof Error ? err.message : String(err), haltOfError(err))\n }\n\n // ── Topology: the catalog, then a sample of the main kinds.\n const topology: GraphSchemaDocument['layers']['topology'] = {\n labels: [],\n link_types: [],\n property_keys: [],\n indexes: [],\n samples: {},\n }\n const topologyOff = capability?.layers?.topology?.enabled === false\n if (topologyOff) {\n remarks.push('The topology layer is off on this network.')\n } else {\n const labels = await graphRead(network, CATALOG_READS.labels.label, CATALOG_READS.labels.query)\n if (labels) topology.labels = sortedUnique(labels.flatMap((row) => stringList([row.label])))\n\n const linkTypes = await graphRead(\n network,\n CATALOG_READS.linkTypes.label,\n CATALOG_READS.linkTypes.query\n )\n if (linkTypes) {\n topology.link_types = sortedUnique(\n linkTypes.flatMap((row) => stringList([row.relationshipType]))\n )\n }\n\n const keys = await graphRead(\n network,\n CATALOG_READS.propertyKeys.label,\n CATALOG_READS.propertyKeys.query\n )\n if (keys) {\n topology.property_keys = sortedUnique(keys.flatMap((row) => stringList([row.propertyKey])))\n }\n\n const indexes = await graphRead(\n network,\n CATALOG_READS.indexes.label,\n CATALOG_READS.indexes.query\n )\n if (indexes) {\n topology.indexes = indexes\n .map(describeIndex)\n .filter((index): index is GraphSchemaIndex => index !== null)\n .sort((a, b) => a.name.localeCompare(b.name))\n }\n\n // An address is sampled unless the label list was read and has no Address.\n const labelsRead = labels !== null\n if (!labelsRead || topology.labels.includes('Address')) {\n const rows = await graphRead(network, 'the Address sample', addressSampleQuery())\n if (rows) topology.samples['Address'] = unionOfKeys(rows)\n }\n\n const sampled = SAMPLED_LINK_TYPES.filter((type) => topology.link_types.includes(type))\n if (linkTypes === null && !halted) {\n problems.push('The link samples were skipped: the link types are unknown.')\n }\n for (const type of sampled) {\n const rows = await graphRead(network, `the ${type} sample`, linkSampleQuery(type))\n if (rows) topology.samples[type] = unionOfKeys(rows)\n }\n\n const unsampled = topology.link_types.filter(\n (type) => !(SAMPLED_LINK_TYPES as readonly string[]).includes(type)\n )\n if (unsampled.length > 0) {\n remarks.push(\n `Link types with no sample: ${unsampled.join(', ')}. Read the keys of one with USE topology MATCH ()-[r:<TYPE>]->() RETURN keys(r) AS keys LIMIT ${LINK_SAMPLE_LIMIT}.`\n )\n }\n }\n\n // ── Facts and chain: what the network publishes.\n const facts: GraphSchemaDocument['layers']['facts'] = { relationships: [], transfer_columns: [] }\n const chain: GraphSchemaDocument['layers']['chain'] = { lookups: [] }\n if (capability) {\n if (capability.layers?.facts?.enabled === false) {\n remarks.push('The facts layer is off on this network.')\n } else {\n facts.relationships = stringList(capability.layers?.facts?.relationships)\n if (facts.relationships.includes('TRANSFER')) {\n facts.transfer_columns = [...FACTS_TRANSFER_COLUMNS]\n }\n }\n const admission = capability.chain_admission\n if (admission?.enabled === false) {\n remarks.push(`The chain layer is off (status ${admission.status ?? 'unknown'}).`)\n } else {\n chain.lookups = stringList(admission?.lookups)\n }\n }\n\n const words = [...topology.property_keys, ...Object.values(topology.samples).flat()]\n if (words.includes('synced_through_height') || words.includes('pair_key')) {\n remarks.push(\n 'synced_through_height and pair_key are sync bookkeeping: never filter or sort on them.'\n )\n }\n remarks.push('Fields come from a sample of the live graph. A rare field may be missing.')\n\n return {\n complete: problems.length === 0,\n document: {\n schema: GRAPH_SCHEMA_ID,\n network,\n built_at: now().toISOString(),\n source: GRAPH_SCHEMA_SOURCE,\n cached: false,\n billed_units: billed,\n layers: { topology, facts, chain },\n notes: [...problems, ...remarks],\n },\n }\n}\n\nfunction unionOfKeys(rows: Row[]): string[] {\n return sortedUnique(rows.flatMap((row) => stringList(row.keys)))\n}\n\n// ─── Reading from the cache, or building ──────────────────────────────────────\n\nexport interface GetGraphSchemaOptions {\n /** As the caller wrote it: normalized here. */\n network: unknown\n refresh?: boolean\n endpoint: string\n /**\n * Runs `fn` with one connected client. It is called once for a build and not at\n * all when the cache answers, so a cache hit opens no session.\n */\n withClient: <T>(fn: (client: GraphSchemaClient) => Promise<T>) => Promise<T>\n now?: () => Date\n pauseMs?: number\n sleep?: (ms: number) => Promise<void>\n}\n\n// Builds in flight, by endpoint and network: two callers that ask for one\n// schema at once share one build and pay for it once.\nconst building = new Map<string, Promise<GraphSchemaDocument>>()\n\nexport async function getGraphSchema(options: GetGraphSchemaOptions): Promise<GraphSchemaDocument> {\n const network = normalizeSchemaNetwork(options.network)\n const now = options.now ?? (() => new Date())\n if (options.refresh !== true) {\n const cached = await loadGraphSchemaCache(network, options.endpoint, now())\n if (cached) return cached\n }\n const key = `${options.endpoint}\\n${network}`\n const running = building.get(key)\n if (running) return running\n const build = (async () => {\n const { document, complete } = await options.withClient((client) =>\n buildGraphSchema({\n client,\n network,\n now,\n ...(options.pauseMs === undefined ? {} : { pauseMs: options.pauseMs }),\n ...(options.sleep ? { sleep: options.sleep } : {}),\n })\n )\n try {\n await saveGraphSchemaCache(network, document, options.endpoint, complete, now())\n } catch (err) {\n document.notes.push(\n `The schema could not be saved to the cache: ${err instanceof Error ? err.message : String(err)}`\n )\n }\n return document\n })()\n building.set(key, build)\n try {\n return await build\n } finally {\n building.delete(key)\n }\n}\n\n// ─── Text ─────────────────────────────────────────────────────────────────────\n\nfunction listLine(title: string, values: string[], empty = 'none read'): string {\n return `${title}: ${values.length > 0 ? values.join(', ') : empty}`\n}\n\n/** The schema as the model and the terminal read it. */\nexport function formatGraphSchemaText(document: GraphSchemaDocument): string {\n const { topology, facts, chain } = document.layers\n const origin = document.cached\n ? 'from the cache, kept 24 hours; refresh=true rebuilds it'\n : `read now for ${document.billed_units} billed units and cached for 24 hours`\n const lines = [\n `Graph schema, network ${document.network}`,\n `Source: ${document.source}, built ${document.built_at} (${origin})`,\n '',\n 'USE topology',\n listLine('Labels', topology.labels),\n listLine('Link types', topology.link_types),\n listLine(`Property keys (${topology.property_keys.length})`, topology.property_keys),\n ]\n lines.push(topology.indexes.length > 0 ? 'Indexes:' : 'Indexes: none read')\n for (const index of topology.indexes) {\n lines.push(\n `- ${index.entity} ${index.types.join('|')}(${index.properties.join(', ')}) ${index.state}`\n )\n }\n const sampled = Object.entries(topology.samples)\n lines.push(sampled.length > 0 ? 'Fields, from a sample:' : 'Fields, from a sample: none read')\n for (const [kind, fields] of sampled) lines.push(`- ${kind}: ${fields.join(', ')}`)\n lines.push(\n '',\n 'USE facts',\n listLine('Relationships', facts.relationships),\n listLine('TRANSFER columns', facts.transfer_columns),\n '',\n 'USE chain',\n listLine('Lookups', chain.lookups)\n )\n if (document.notes.length > 0) {\n lines.push('', 'Notes:', ...document.notes.map((note) => `- ${note}`))\n }\n return lines.join('\\n')\n}\n\n// ─── The MCP tool ─────────────────────────────────────────────────────────────\n\nexport interface GraphSchemaDependencies {\n endpoint: string\n withClient: GetGraphSchemaOptions['withClient']\n /** The text of a failure that is not a GraphSchemaError: payment required, transport. */\n describeFailure: (err: unknown) => string\n now?: () => Date\n pauseMs?: number\n sleep?: (ms: number) => Promise<void>\n}\n\nexport type MetaSchemaResult = {\n content: Array<{ type: 'text'; text: string }>\n structuredContent?: Record<string, unknown>\n isError: boolean\n}\n\nfunction errorResult(text: string): MetaSchemaResult {\n return { content: [{ type: 'text', text }], isError: true }\n}\n\n/** meta_schema, {network, refresh?}. Every argument is checked before the first read. */\nexport async function handleMetaSchema(\n args: unknown,\n deps: GraphSchemaDependencies\n): Promise<MetaSchemaResult> {\n const record = isRecord(args) ? args : {}\n const refresh = record.refresh\n if (refresh !== undefined && refresh !== null && typeof refresh !== 'boolean') {\n return errorResult('invalid_refresh: refresh must be true or false')\n }\n try {\n const document = await getGraphSchema({\n network: record.network,\n refresh: refresh === true,\n endpoint: deps.endpoint,\n withClient: deps.withClient,\n ...(deps.now ? { now: deps.now } : {}),\n ...(deps.pauseMs === undefined ? {} : { pauseMs: deps.pauseMs }),\n ...(deps.sleep ? { sleep: deps.sleep } : {}),\n })\n return {\n content: [{ type: 'text', text: formatGraphSchemaText(document) }],\n structuredContent: document as unknown as Record<string, unknown>,\n isError: false,\n }\n } catch (err) {\n return errorResult(err instanceof GraphSchemaError ? err.message : deps.describeFailure(err))\n }\n}\n"],"mappings":"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"}