chain-insights 0.71.4 → 0.73.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +38 -3
  2. package/dist/buy-97M6dtEu.cjs +195 -0
  3. package/dist/buy-_mpdRieG.mjs +195 -0
  4. package/dist/buy-_mpdRieG.mjs.map +1 -0
  5. package/dist/{capabilities-DGavTSoW.cjs → capabilities-Ds3320iS.cjs} +1 -1
  6. package/dist/{capabilities-BBVDIECk.mjs → capabilities-DxojbX4D.mjs} +2 -2
  7. package/dist/{capabilities-BBVDIECk.mjs.map → capabilities-DxojbX4D.mjs.map} +1 -1
  8. package/dist/{claude-desktop-CTaBCUIm.mjs → claude-desktop-6jwX0Lux.mjs} +18 -3
  9. package/dist/{claude-desktop-CTaBCUIm.mjs.map → claude-desktop-6jwX0Lux.mjs.map} +1 -1
  10. package/dist/{claude-desktop-BI_mGkZ6.cjs → claude-desktop-Jmm4PlBZ.cjs} +17 -2
  11. package/dist/cli.cjs +163 -31
  12. package/dist/cli.mjs +163 -31
  13. package/dist/cli.mjs.map +1 -1
  14. package/dist/{client-gsLi528K.mjs → client-7qOfAdpH.mjs} +31 -12
  15. package/dist/client-7qOfAdpH.mjs.map +1 -0
  16. package/dist/{client-Ypw9BNbn.cjs → client-C9uwHnBS.cjs} +33 -14
  17. package/dist/{dex-registry-KOTJhAQf.cjs → dex-registry-Chs79Ukc.cjs} +11 -8
  18. package/dist/{dex-registry-05wAhSa0.mjs → dex-registry-rziydXYq.mjs} +12 -9
  19. package/dist/{dex-registry-05wAhSa0.mjs.map → dex-registry-rziydXYq.mjs.map} +1 -1
  20. package/dist/{dex-tool-B4T8Czhm.cjs → dex-tool-DLWnff1n.cjs} +6 -4
  21. package/dist/{dex-tool-B5Qwz1Zd.mjs → dex-tool-DsaAIa-a.mjs} +7 -5
  22. package/dist/{dex-tool-B5Qwz1Zd.mjs.map → dex-tool-DsaAIa-a.mjs.map} +1 -1
  23. package/dist/{graph-schema-BoUMa03H.cjs → graph-schema-BGbJumCY.cjs} +63 -25
  24. package/dist/{graph-schema-DqZUTnP0.mjs → graph-schema-ClqjzgrM.mjs} +59 -27
  25. package/dist/{graph-schema-DqZUTnP0.mjs.map → graph-schema-ClqjzgrM.mjs.map} +1 -1
  26. package/dist/index.cjs +5 -5
  27. package/dist/index.d.cts +7 -0
  28. package/dist/index.d.cts.map +1 -1
  29. package/dist/index.d.mts +7 -0
  30. package/dist/index.d.mts.map +1 -1
  31. package/dist/index.mjs +5 -5
  32. package/dist/known-keys-B87ZqOgE.mjs +78 -0
  33. package/dist/known-keys-B87ZqOgE.mjs.map +1 -0
  34. package/dist/known-keys-PKZF5GYd.cjs +99 -0
  35. package/dist/mcp-proxy.cjs +86 -39
  36. package/dist/mcp-proxy.d.cts.map +1 -1
  37. package/dist/mcp-proxy.d.mts.map +1 -1
  38. package/dist/mcp-proxy.mjs +86 -39
  39. package/dist/mcp-proxy.mjs.map +1 -1
  40. package/dist/{public-tools-Dj130EuJ.cjs → public-tools-Bl_l_rKK.cjs} +8 -5
  41. package/dist/{public-tools-B77ZT0jt.mjs → public-tools-CQjGt7_q.mjs} +9 -6
  42. package/dist/{public-tools-B77ZT0jt.mjs.map → public-tools-CQjGt7_q.mjs.map} +1 -1
  43. package/dist/quote-B9sYNOiv.cjs +70 -0
  44. package/dist/quote-DfTut0cx.mjs +68 -0
  45. package/dist/quote-DfTut0cx.mjs.map +1 -0
  46. package/dist/robinhood-CCTMagwM.mjs +226 -0
  47. package/dist/robinhood-CCTMagwM.mjs.map +1 -0
  48. package/dist/robinhood-ChiT69sc.cjs +313 -0
  49. package/dist/rules-BD-cNHMX.cjs +31 -0
  50. package/dist/rules-BWdbOtHi.mjs +10 -0
  51. package/dist/rules-BWdbOtHi.mjs.map +1 -0
  52. package/dist/{schema-cache-DwddPLcY.cjs → schema-cache-C7v9ml6S.cjs} +1 -1
  53. package/dist/{schema-cache-BT-kHy_r.mjs → schema-cache-DRObKUvS.mjs} +3 -3
  54. package/dist/{schema-cache-BT-kHy_r.mjs.map → schema-cache-DRObKUvS.mjs.map} +1 -1
  55. package/dist/{skill-refresh-FXhQpW94.cjs → skill-refresh-Be2D5FrX.cjs} +2 -2
  56. package/dist/{skill-refresh-I5MSULwP.mjs → skill-refresh-xYWZ_hua.mjs} +3 -3
  57. package/dist/{skill-refresh-I5MSULwP.mjs.map → skill-refresh-xYWZ_hua.mjs.map} +1 -1
  58. package/dist/subscription-status-9tMfD90J.cjs +168 -0
  59. package/dist/subscription-status-DIuH84fM.mjs +148 -0
  60. package/dist/subscription-status-DIuH84fM.mjs.map +1 -0
  61. package/dist/{tools-BHfphgy9.cjs → tools-Cmnq7yip.cjs} +21 -4
  62. package/dist/{tools-BnThuvE2.mjs → tools-D9ep9E7Q.mjs} +22 -5
  63. package/dist/tools-D9ep9E7Q.mjs.map +1 -0
  64. package/dist/topup--nWtDnWP.cjs +43 -0
  65. package/dist/topup-CfFe1bGS.mjs +45 -0
  66. package/dist/topup-CfFe1bGS.mjs.map +1 -0
  67. package/dist/{topup-server-DzeM_YAt.mjs → topup-server-B0VKHslr.mjs} +2 -2
  68. package/dist/{topup-server-DzeM_YAt.mjs.map → topup-server-B0VKHslr.mjs.map} +1 -1
  69. package/dist/{topup-server-BIbSfvX5.cjs → topup-server-BkGM8P99.cjs} +1 -1
  70. package/dist/{topup-server-Bh7MlzRt.cjs → topup-server-C58eWflv.cjs} +284 -69
  71. package/dist/{topup-server-BrkPhqJG.mjs → topup-server-RQ6-c92F.mjs} +274 -71
  72. package/dist/topup-server-RQ6-c92F.mjs.map +1 -0
  73. package/dist/{wallet-B3hAr70M.cjs → wallet-CgIAfdxL.cjs} +34 -0
  74. package/dist/{wallet-GNDl3FkR.mjs → wallet-QTo5YCFR.mjs} +35 -1
  75. package/dist/wallet-QTo5YCFR.mjs.map +1 -0
  76. package/docs/development.md +1 -1
  77. package/docs/graph-tools.md +2 -1
  78. package/docs/mcp-proxy.md +30 -4
  79. package/docs/stability.md +3 -2
  80. package/package.json +1 -1
  81. package/skills/chain-insights-cypher/SKILL.md +9 -0
  82. package/dist/client-gsLi528K.mjs.map +0 -1
  83. package/dist/subscription-status-DA9DCFrC.mjs +0 -32
  84. package/dist/subscription-status-DA9DCFrC.mjs.map +0 -1
  85. package/dist/subscription-status-ru99UCPZ.cjs +0 -40
  86. package/dist/tools-BnThuvE2.mjs.map +0 -1
  87. package/dist/topup-server-BrkPhqJG.mjs.map +0 -1
  88. package/dist/wallet-GNDl3FkR.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
- const require_client = require("./client-Ypw9BNbn.cjs");
3
- const require_capabilities = require("./capabilities-DGavTSoW.cjs");
2
+ const require_client = require("./client-C9uwHnBS.cjs");
3
+ const require_capabilities = require("./capabilities-Ds3320iS.cjs");
4
4
  const require_cache_location = require("./cache-location-CBVzUIxq.cjs");
5
5
  let node_path = require("node:path");
6
6
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
@@ -45,13 +45,42 @@ let node_crypto = require("node:crypto");
45
45
  const FLOWS_SCHEMA = "chain-insights.flows.v1";
46
46
  const GRAPH_EXPAND_TOOL = "graph_expand";
47
47
  const FLOWS_NETWORK = "robinhood";
48
+ /** How many senders and how many receivers one page shows, at most. */
49
+ const FLOWS_PER_SIDE = 250;
50
+ /** The view data of one answer: at most this many addresses, the centre included... */
51
+ const FLOWS_MAX_NODES = 500;
52
+ /**
53
+ * ...and under this many characters of JSON. Hosts drop an app tool result
54
+ * over about 150,000 characters.
55
+ */
56
+ const FLOWS_MAX_CHARS = 14e4;
57
+ /**
58
+ * The graph endpoint cuts a reply at 200 rows, whatever the LIMIT, so one side
59
+ * of a page is read in reads of at most this many rows.
60
+ */
61
+ const FLOWS_READ_ROWS = 200;
48
62
  /** Where a graph_expand page may start on each side, at most. */
49
63
  const FLOWS_MAX_OFFSET = 1e4;
50
64
  const TRANSFERS_SCHEMA = "chain-insights.transfers.v1";
65
+ /** A link answer lists at most this many transfers... */
66
+ const TRANSFERS_MAX = 50;
67
+ /**
68
+ * ...from the rows one read brings back. A facts read takes no ORDER BY and
69
+ * the server cuts a reply at 200 rows, so the read asks for the whole cap and
70
+ * the newest rows are picked here.
71
+ */
72
+ const TRANSFERS_READ_ROWS = 200;
73
+ /** The text of a link answer names at most this many transfers. */
74
+ const TRANSFERS_SUMMARY_ROWS = 5;
75
+ /** 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. */
76
+ const TRANSFERS_MAX_DAYS = 7;
77
+ /** Where a paged link answer stops reading back when the caller names no first_day. */
78
+ const TRANSFERS_FLOOR_DAY = "2015-01-01";
51
79
  const ADDRESS_SCHEMA = "chain-insights.address.v1";
52
80
  const INVALID_ADDRESS_TEXT = "invalid_address: a full 0x address of 40 hexadecimal characters is needed, for example 0x04911a118f11c75667e4d0dfb8e640af5a353550";
53
81
  const OFFSET_TOO_LARGE_TEXT = `invalid_offset: in_offset and out_offset must each be at most ${FLOWS_MAX_OFFSET}`;
54
82
  const OFFSET_NEGATIVE_TEXT = "invalid_offset: in_offset and out_offset must each be a whole number, 0 or more";
83
+ const INVALID_DAY_TEXT = "invalid_day: day must be a calendar date written YYYY-MM-DD (UTC), for example 2026-07-10";
55
84
  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";
56
85
  const INVALID_LIMIT_TEXT = "invalid_limit: limit must be a whole number from 1 to 50";
57
86
  const INVALID_SKIP_TEXT = "invalid_skip: skip must be a whole number of 0 or more";
@@ -90,7 +119,7 @@ function normalizeFlowsDay(raw) {
90
119
  * checks, so they are written into the read as literals.
91
120
  */
92
121
  function transfersReadFor(from, to, day) {
93
- 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`;
122
+ 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 ${TRANSFERS_READ_ROWS}`;
94
123
  }
95
124
  /** The UTC day before a YYYY-MM-DD day. */
96
125
  function dayBefore(day) {
@@ -139,9 +168,9 @@ const PAGE_ORDER = " ORDER BY f.last_seen_timestamp DESC, b.address";
139
168
  function flowsReadsFor(address, inOffset, outOffset) {
140
169
  const side = (id, offset, match) => {
141
170
  const reads = [];
142
- for (let done = 0; done < 251; done += 200) {
171
+ for (let done = 0; done < 251; done += FLOWS_READ_ROWS) {
143
172
  const skip = offset + done;
144
- const limit = Math.min(200, 251 - done);
173
+ const limit = Math.min(FLOWS_READ_ROWS, 251 - done);
145
174
  reads.push({
146
175
  id,
147
176
  query: `USE topology MATCH ${match} RETURN ${PARTY_FIELDS}${PAGE_ORDER}${skip > 0 ? ` SKIP ${skip}` : ""} LIMIT ${limit}`,
@@ -234,7 +263,7 @@ function assembleFlowView(network, page, senders, receivers, truncated) {
234
263
  return view;
235
264
  }
236
265
  function fitsFlowBudget(view) {
237
- return view.nodes.length <= 500 && JSON.stringify(view).length < 14e4;
266
+ return view.nodes.length <= FLOWS_MAX_NODES && JSON.stringify(view).length < FLOWS_MAX_CHARS;
238
267
  }
239
268
  /**
240
269
  * How many senders and receivers stay after `drops` of the oldest links go,
@@ -259,9 +288,9 @@ function keptAfterDrops(senders, receivers, drops) {
259
288
  * per dropped link.
260
289
  */
261
290
  function buildFlowView(network, page) {
262
- const overflow = page.senders.length > 250 || page.receivers.length > 250;
263
- const senders = page.senders.slice(0, 250);
264
- const receivers = page.receivers.slice(0, 250);
291
+ const overflow = page.senders.length > FLOWS_PER_SIDE || page.receivers.length > FLOWS_PER_SIDE;
292
+ const senders = page.senders.slice(0, FLOWS_PER_SIDE);
293
+ const receivers = page.receivers.slice(0, FLOWS_PER_SIDE);
265
294
  const viewAfter = (drops) => {
266
295
  const [s, r] = keptAfterDrops(senders.length, receivers.length, drops);
267
296
  return assembleFlowView(network, page, senders.slice(0, s), receivers.slice(0, r), overflow || drops > 0);
@@ -368,8 +397,8 @@ function buildTransfersView(network, from, to, day, rows) {
368
397
  from,
369
398
  to,
370
399
  day,
371
- transfers: newestFirst.slice(0, 50),
372
- truncated: newestFirst.length > 50
400
+ transfers: newestFirst.slice(0, TRANSFERS_MAX),
401
+ truncated: newestFirst.length > TRANSFERS_MAX
373
402
  };
374
403
  }
375
404
  function transferLine(transfer) {
@@ -381,8 +410,8 @@ function transfersSummary(view) {
381
410
  const span = paged && view.through_day !== view.day ? `${view.through_day} to ${view.day}` : view.day;
382
411
  const head = `Transfers from ${view.from} to ${view.to} on ${span} (UTC) on ${view.network}`;
383
412
  if (view.transfers.length === 0) return paged && view.next ? `${head}: none; earlier days remain before ${view.next.day}.` : `${head}: none.`;
384
- 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)];
385
- const rest = view.transfers.length - 5;
413
+ 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, TRANSFERS_SUMMARY_ROWS).map(transferLine)];
414
+ const rest = view.transfers.length - TRANSFERS_SUMMARY_ROWS;
386
415
  if (rest > 0) lines.push(`- and ${rest} more in the picture.`);
387
416
  if (paged && view.next) lines.push(`- earlier transfers remain, from ${view.next.day} back.`);
388
417
  return lines.join("\n");
@@ -439,7 +468,7 @@ function rowsOf(answer) {
439
468
  function checkNetwork(args) {
440
469
  const rawNetwork = typeof args.network === "string" ? args.network.trim().toLowerCase() : "";
441
470
  if (!rawNetwork) return { error: "invalid_network: network is required; pass robinhood" };
442
- if (rawNetwork !== "robinhood") return { error: `invalid_network: graph_expand reads the robinhood graph only; got "${String(args.network).trim()}"` };
471
+ if (rawNetwork !== FLOWS_NETWORK) return { error: `invalid_network: graph_expand reads the robinhood graph only; got "${String(args.network).trim()}"` };
443
472
  return FLOWS_NETWORK;
444
473
  }
445
474
  /** The address of a node or of one end of a link; `name` says which end in a link refusal. */
@@ -449,10 +478,10 @@ function checkAddress(raw, name) {
449
478
  return { error: name ? INVALID_ADDRESS_TEXT.replace("invalid_address:", `invalid_address (${name}):`) : INVALID_ADDRESS_TEXT };
450
479
  }
451
480
  function checkDay(raw) {
452
- return normalizeFlowsDay(raw) ?? { error: "invalid_day: day must be a calendar date written YYYY-MM-DD (UTC), for example 2026-07-10" };
481
+ return normalizeFlowsDay(raw) ?? { error: INVALID_DAY_TEXT };
453
482
  }
454
483
  function checkLimit(value) {
455
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 50) return { error: INVALID_LIMIT_TEXT };
484
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > TRANSFERS_MAX) return { error: INVALID_LIMIT_TEXT };
456
485
  return value;
457
486
  }
458
487
  function checkSkip(value) {
@@ -469,7 +498,7 @@ function checkFirstDay(value, day) {
469
498
  function checkOffset(value) {
470
499
  if (value === void 0 || value === null) return 0;
471
500
  if (typeof value !== "number" || !Number.isInteger(value) || value < 0) return { error: OFFSET_NEGATIVE_TEXT };
472
- if (value > 1e4) return { error: OFFSET_TOO_LARGE_TEXT };
501
+ if (value > FLOWS_MAX_OFFSET) return { error: OFFSET_TOO_LARGE_TEXT };
473
502
  return value;
474
503
  }
475
504
  /**
@@ -565,7 +594,7 @@ async function readDay(deps, network, from, to, day) {
565
594
  if (!rows) return { error: "graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows" };
566
595
  return {
567
596
  rows: rows.map(transferOf).sort((a, b) => (b.block_timestamp ?? -Infinity) - (a.block_timestamp ?? -Infinity)),
568
- cut: rows.length >= 200
597
+ cut: rows.length >= TRANSFERS_READ_ROWS
569
598
  };
570
599
  }
571
600
  /**
@@ -578,7 +607,7 @@ async function readDay(deps, network, from, to, day) {
578
607
  async function runLinkPage(deps, network, from, to, day, page) {
579
608
  const unavailable = deps.unavailable?.();
580
609
  if (unavailable) return errorResult$1(unavailable);
581
- const floor = page.firstDay ?? "2015-01-01";
610
+ const floor = page.firstDay ?? TRANSFERS_FLOOR_DAY;
582
611
  const transfers = [];
583
612
  let current = day;
584
613
  let skip = page.skip;
@@ -588,7 +617,7 @@ async function runLinkPage(deps, network, from, to, day, page) {
588
617
  let next = null;
589
618
  for (;;) {
590
619
  if (current < floor) break;
591
- if (daysRead >= 7) {
620
+ if (daysRead >= TRANSFERS_MAX_DAYS) {
592
621
  next = {
593
622
  day: current,
594
623
  skip
@@ -806,12 +835,12 @@ async function saveGraphSchemaCache(network, document, endpoint, complete, now)
806
835
  * adds a note. It never fails the tool.
807
836
  */
808
837
  var graph_schema_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
809
- ADDRESS_SAMPLE_LIMIT: () => 20,
838
+ ADDRESS_SAMPLE_LIMIT: () => ADDRESS_SAMPLE_LIMIT,
810
839
  FACTS_TRANSFER_COLUMNS: () => FACTS_TRANSFER_COLUMNS,
811
840
  GRAPH_SCHEMA_ID: () => GRAPH_SCHEMA_ID,
812
841
  GRAPH_SCHEMA_SOURCE: () => GRAPH_SCHEMA_SOURCE,
813
842
  GraphSchemaError: () => GraphSchemaError,
814
- LINK_SAMPLE_LIMIT: () => 5,
843
+ LINK_SAMPLE_LIMIT: () => LINK_SAMPLE_LIMIT,
815
844
  META_SCHEMA_DESCRIPTION: () => META_SCHEMA_DESCRIPTION,
816
845
  META_SCHEMA_TITLE: () => META_SCHEMA_TITLE,
817
846
  SAMPLED_LINK_TYPES: () => SAMPLED_LINK_TYPES,
@@ -827,6 +856,9 @@ const GRAPH_SCHEMA_ID = "chain-insights.graph-schema.v1";
827
856
  const GRAPH_SCHEMA_SOURCE = "live catalog";
828
857
  const META_SCHEMA_TITLE = "Graph schema";
829
858
  const META_SCHEMA_DESCRIPTION = "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. Call it first when you need field names. It is cached for 24 hours; pass refresh=true to rebuild it.";
859
+ /** Fields of an address come from this many sampled nodes, and of a link from this many sampled links. */
860
+ const ADDRESS_SAMPLE_LIMIT = 20;
861
+ const LINK_SAMPLE_LIMIT = 5;
830
862
  /** The link types an agent queries. Each is sampled only when the graph lists it. */
831
863
  const SAMPLED_LINK_TYPES = [
832
864
  "FLOWS_TO",
@@ -876,10 +908,10 @@ const CATALOG_READS = {
876
908
  }
877
909
  };
878
910
  function addressSampleQuery() {
879
- return `USE topology MATCH (a:Address) RETURN keys(a) AS keys LIMIT 20`;
911
+ return `USE topology MATCH (a:Address) RETURN keys(a) AS keys LIMIT ${ADDRESS_SAMPLE_LIMIT}`;
880
912
  }
881
913
  function linkSampleQuery(linkType) {
882
- return `USE topology MATCH ()-[r:${linkType}]->() RETURN keys(r) AS keys LIMIT 5`;
914
+ return `USE topology MATCH ()-[r:${linkType}]->() RETURN keys(r) AS keys LIMIT ${LINK_SAMPLE_LIMIT}`;
883
915
  }
884
916
  /** A refusal with its own words for the caller: a bad argument, an unserved network, an endpoint that is down. */
885
917
  var GraphSchemaError = class extends Error {
@@ -1053,7 +1085,7 @@ async function buildGraphSchema(options) {
1053
1085
  if (rows) topology.samples[type] = unionOfKeys(rows);
1054
1086
  }
1055
1087
  const unsampled = topology.link_types.filter((type) => !SAMPLED_LINK_TYPES.includes(type));
1056
- if (unsampled.length > 0) remarks.push(`Link types with no sample: ${unsampled.join(", ")}. Read the keys of one with USE topology MATCH ()-[r:<TYPE>]->() RETURN keys(r) AS keys LIMIT 5.`);
1088
+ if (unsampled.length > 0) remarks.push(`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}.`);
1057
1089
  }
1058
1090
  const facts = {
1059
1091
  relationships: [],
@@ -1231,6 +1263,12 @@ Object.defineProperty(exports, "META_SCHEMA_TITLE", {
1231
1263
  return META_SCHEMA_TITLE;
1232
1264
  }
1233
1265
  });
1266
+ Object.defineProperty(exports, "TRANSFERS_MAX", {
1267
+ enumerable: true,
1268
+ get: function() {
1269
+ return TRANSFERS_MAX;
1270
+ }
1271
+ });
1234
1272
  Object.defineProperty(exports, "graph_schema_exports", {
1235
1273
  enumerable: true,
1236
1274
  get: function() {
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
- import { t as PaymentRequiredError } from "./client-gsLi528K.mjs";
3
- import { n as findNetworkCapability, r as mirrorGraphNetworkCapabilities } from "./capabilities-BBVDIECk.mjs";
2
+ import { t as PaymentRequiredError } from "./client-7qOfAdpH.mjs";
3
+ import { n as findNetworkCapability, r as mirrorGraphNetworkCapabilities } from "./capabilities-DxojbX4D.mjs";
4
4
  import { n as chainInsightsHome, t as CACHE_TTL_MS } from "./cache-location-BO2XkfgI.mjs";
5
5
  import path from "node:path";
6
6
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
@@ -44,13 +44,42 @@ import { createHash } from "node:crypto";
44
44
  const FLOWS_SCHEMA = "chain-insights.flows.v1";
45
45
  const GRAPH_EXPAND_TOOL = "graph_expand";
46
46
  const FLOWS_NETWORK = "robinhood";
47
+ /** How many senders and how many receivers one page shows, at most. */
48
+ const FLOWS_PER_SIDE = 250;
49
+ /** The view data of one answer: at most this many addresses, the centre included... */
50
+ const FLOWS_MAX_NODES = 500;
51
+ /**
52
+ * ...and under this many characters of JSON. Hosts drop an app tool result
53
+ * over about 150,000 characters.
54
+ */
55
+ const FLOWS_MAX_CHARS = 14e4;
56
+ /**
57
+ * The graph endpoint cuts a reply at 200 rows, whatever the LIMIT, so one side
58
+ * of a page is read in reads of at most this many rows.
59
+ */
60
+ const FLOWS_READ_ROWS = 200;
47
61
  /** Where a graph_expand page may start on each side, at most. */
48
62
  const FLOWS_MAX_OFFSET = 1e4;
49
63
  const TRANSFERS_SCHEMA = "chain-insights.transfers.v1";
64
+ /** A link answer lists at most this many transfers... */
65
+ const TRANSFERS_MAX = 50;
66
+ /**
67
+ * ...from the rows one read brings back. A facts read takes no ORDER BY and
68
+ * the server cuts a reply at 200 rows, so the read asks for the whole cap and
69
+ * the newest rows are picked here.
70
+ */
71
+ const TRANSFERS_READ_ROWS = 200;
72
+ /** The text of a link answer names at most this many transfers. */
73
+ const TRANSFERS_SUMMARY_ROWS = 5;
74
+ /** 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. */
75
+ const TRANSFERS_MAX_DAYS = 7;
76
+ /** Where a paged link answer stops reading back when the caller names no first_day. */
77
+ const TRANSFERS_FLOOR_DAY = "2015-01-01";
50
78
  const ADDRESS_SCHEMA = "chain-insights.address.v1";
51
79
  const INVALID_ADDRESS_TEXT = "invalid_address: a full 0x address of 40 hexadecimal characters is needed, for example 0x04911a118f11c75667e4d0dfb8e640af5a353550";
52
80
  const OFFSET_TOO_LARGE_TEXT = `invalid_offset: in_offset and out_offset must each be at most ${FLOWS_MAX_OFFSET}`;
53
81
  const OFFSET_NEGATIVE_TEXT = "invalid_offset: in_offset and out_offset must each be a whole number, 0 or more";
82
+ const INVALID_DAY_TEXT = "invalid_day: day must be a calendar date written YYYY-MM-DD (UTC), for example 2026-07-10";
54
83
  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
84
  const INVALID_LIMIT_TEXT = "invalid_limit: limit must be a whole number from 1 to 50";
56
85
  const INVALID_SKIP_TEXT = "invalid_skip: skip must be a whole number of 0 or more";
@@ -89,7 +118,7 @@ function normalizeFlowsDay(raw) {
89
118
  * checks, so they are written into the read as literals.
90
119
  */
91
120
  function transfersReadFor(from, to, day) {
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`;
121
+ 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 ${TRANSFERS_READ_ROWS}`;
93
122
  }
94
123
  /** The UTC day before a YYYY-MM-DD day. */
95
124
  function dayBefore(day) {
@@ -138,9 +167,9 @@ const PAGE_ORDER = " ORDER BY f.last_seen_timestamp DESC, b.address";
138
167
  function flowsReadsFor(address, inOffset, outOffset) {
139
168
  const side = (id, offset, match) => {
140
169
  const reads = [];
141
- for (let done = 0; done < 251; done += 200) {
170
+ for (let done = 0; done < 251; done += FLOWS_READ_ROWS) {
142
171
  const skip = offset + done;
143
- const limit = Math.min(200, 251 - done);
172
+ const limit = Math.min(FLOWS_READ_ROWS, 251 - done);
144
173
  reads.push({
145
174
  id,
146
175
  query: `USE topology MATCH ${match} RETURN ${PARTY_FIELDS}${PAGE_ORDER}${skip > 0 ? ` SKIP ${skip}` : ""} LIMIT ${limit}`,
@@ -233,7 +262,7 @@ function assembleFlowView(network, page, senders, receivers, truncated) {
233
262
  return view;
234
263
  }
235
264
  function fitsFlowBudget(view) {
236
- return view.nodes.length <= 500 && JSON.stringify(view).length < 14e4;
265
+ return view.nodes.length <= FLOWS_MAX_NODES && JSON.stringify(view).length < FLOWS_MAX_CHARS;
237
266
  }
238
267
  /**
239
268
  * How many senders and receivers stay after `drops` of the oldest links go,
@@ -258,9 +287,9 @@ function keptAfterDrops(senders, receivers, drops) {
258
287
  * per dropped link.
259
288
  */
260
289
  function buildFlowView(network, page) {
261
- const overflow = page.senders.length > 250 || page.receivers.length > 250;
262
- const senders = page.senders.slice(0, 250);
263
- const receivers = page.receivers.slice(0, 250);
290
+ const overflow = page.senders.length > FLOWS_PER_SIDE || page.receivers.length > FLOWS_PER_SIDE;
291
+ const senders = page.senders.slice(0, FLOWS_PER_SIDE);
292
+ const receivers = page.receivers.slice(0, FLOWS_PER_SIDE);
264
293
  const viewAfter = (drops) => {
265
294
  const [s, r] = keptAfterDrops(senders.length, receivers.length, drops);
266
295
  return assembleFlowView(network, page, senders.slice(0, s), receivers.slice(0, r), overflow || drops > 0);
@@ -367,8 +396,8 @@ function buildTransfersView(network, from, to, day, rows) {
367
396
  from,
368
397
  to,
369
398
  day,
370
- transfers: newestFirst.slice(0, 50),
371
- truncated: newestFirst.length > 50
399
+ transfers: newestFirst.slice(0, TRANSFERS_MAX),
400
+ truncated: newestFirst.length > TRANSFERS_MAX
372
401
  };
373
402
  }
374
403
  function transferLine(transfer) {
@@ -380,8 +409,8 @@ function transfersSummary(view) {
380
409
  const span = paged && view.through_day !== view.day ? `${view.through_day} to ${view.day}` : view.day;
381
410
  const head = `Transfers from ${view.from} to ${view.to} on ${span} (UTC) on ${view.network}`;
382
411
  if (view.transfers.length === 0) return paged && view.next ? `${head}: none; earlier days remain before ${view.next.day}.` : `${head}: none.`;
383
- 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)];
384
- const rest = view.transfers.length - 5;
412
+ 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, TRANSFERS_SUMMARY_ROWS).map(transferLine)];
413
+ const rest = view.transfers.length - TRANSFERS_SUMMARY_ROWS;
385
414
  if (rest > 0) lines.push(`- and ${rest} more in the picture.`);
386
415
  if (paged && view.next) lines.push(`- earlier transfers remain, from ${view.next.day} back.`);
387
416
  return lines.join("\n");
@@ -438,7 +467,7 @@ function rowsOf(answer) {
438
467
  function checkNetwork(args) {
439
468
  const rawNetwork = typeof args.network === "string" ? args.network.trim().toLowerCase() : "";
440
469
  if (!rawNetwork) return { error: "invalid_network: network is required; pass robinhood" };
441
- if (rawNetwork !== "robinhood") return { error: `invalid_network: graph_expand reads the robinhood graph only; got "${String(args.network).trim()}"` };
470
+ if (rawNetwork !== FLOWS_NETWORK) return { error: `invalid_network: graph_expand reads the robinhood graph only; got "${String(args.network).trim()}"` };
442
471
  return FLOWS_NETWORK;
443
472
  }
444
473
  /** The address of a node or of one end of a link; `name` says which end in a link refusal. */
@@ -448,10 +477,10 @@ function checkAddress(raw, name) {
448
477
  return { error: name ? INVALID_ADDRESS_TEXT.replace("invalid_address:", `invalid_address (${name}):`) : INVALID_ADDRESS_TEXT };
449
478
  }
450
479
  function checkDay(raw) {
451
- return normalizeFlowsDay(raw) ?? { error: "invalid_day: day must be a calendar date written YYYY-MM-DD (UTC), for example 2026-07-10" };
480
+ return normalizeFlowsDay(raw) ?? { error: INVALID_DAY_TEXT };
452
481
  }
453
482
  function checkLimit(value) {
454
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 50) return { error: INVALID_LIMIT_TEXT };
483
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > TRANSFERS_MAX) return { error: INVALID_LIMIT_TEXT };
455
484
  return value;
456
485
  }
457
486
  function checkSkip(value) {
@@ -468,7 +497,7 @@ function checkFirstDay(value, day) {
468
497
  function checkOffset(value) {
469
498
  if (value === void 0 || value === null) return 0;
470
499
  if (typeof value !== "number" || !Number.isInteger(value) || value < 0) return { error: OFFSET_NEGATIVE_TEXT };
471
- if (value > 1e4) return { error: OFFSET_TOO_LARGE_TEXT };
500
+ if (value > FLOWS_MAX_OFFSET) return { error: OFFSET_TOO_LARGE_TEXT };
472
501
  return value;
473
502
  }
474
503
  /**
@@ -564,7 +593,7 @@ async function readDay(deps, network, from, to, day) {
564
593
  if (!rows) return { error: "graph_query_failed: the graph endpoint answered without chain-insights.result.v1 rows" };
565
594
  return {
566
595
  rows: rows.map(transferOf).sort((a, b) => (b.block_timestamp ?? -Infinity) - (a.block_timestamp ?? -Infinity)),
567
- cut: rows.length >= 200
596
+ cut: rows.length >= TRANSFERS_READ_ROWS
568
597
  };
569
598
  }
570
599
  /**
@@ -577,7 +606,7 @@ async function readDay(deps, network, from, to, day) {
577
606
  async function runLinkPage(deps, network, from, to, day, page) {
578
607
  const unavailable = deps.unavailable?.();
579
608
  if (unavailable) return errorResult$1(unavailable);
580
- const floor = page.firstDay ?? "2015-01-01";
609
+ const floor = page.firstDay ?? TRANSFERS_FLOOR_DAY;
581
610
  const transfers = [];
582
611
  let current = day;
583
612
  let skip = page.skip;
@@ -587,7 +616,7 @@ async function runLinkPage(deps, network, from, to, day, page) {
587
616
  let next = null;
588
617
  for (;;) {
589
618
  if (current < floor) break;
590
- if (daysRead >= 7) {
619
+ if (daysRead >= TRANSFERS_MAX_DAYS) {
591
620
  next = {
592
621
  day: current,
593
622
  skip
@@ -805,12 +834,12 @@ async function saveGraphSchemaCache(network, document, endpoint, complete, now)
805
834
  * adds a note. It never fails the tool.
806
835
  */
807
836
  var graph_schema_exports = /* @__PURE__ */ __exportAll({
808
- ADDRESS_SAMPLE_LIMIT: () => 20,
837
+ ADDRESS_SAMPLE_LIMIT: () => ADDRESS_SAMPLE_LIMIT,
809
838
  FACTS_TRANSFER_COLUMNS: () => FACTS_TRANSFER_COLUMNS,
810
839
  GRAPH_SCHEMA_ID: () => GRAPH_SCHEMA_ID,
811
840
  GRAPH_SCHEMA_SOURCE: () => GRAPH_SCHEMA_SOURCE,
812
841
  GraphSchemaError: () => GraphSchemaError,
813
- LINK_SAMPLE_LIMIT: () => 5,
842
+ LINK_SAMPLE_LIMIT: () => LINK_SAMPLE_LIMIT,
814
843
  META_SCHEMA_DESCRIPTION: () => META_SCHEMA_DESCRIPTION,
815
844
  META_SCHEMA_TITLE: () => META_SCHEMA_TITLE,
816
845
  SAMPLED_LINK_TYPES: () => SAMPLED_LINK_TYPES,
@@ -826,6 +855,9 @@ const GRAPH_SCHEMA_ID = "chain-insights.graph-schema.v1";
826
855
  const GRAPH_SCHEMA_SOURCE = "live catalog";
827
856
  const META_SCHEMA_TITLE = "Graph schema";
828
857
  const META_SCHEMA_DESCRIPTION = "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. Call it first when you need field names. It is cached for 24 hours; pass refresh=true to rebuild it.";
858
+ /** Fields of an address come from this many sampled nodes, and of a link from this many sampled links. */
859
+ const ADDRESS_SAMPLE_LIMIT = 20;
860
+ const LINK_SAMPLE_LIMIT = 5;
829
861
  /** The link types an agent queries. Each is sampled only when the graph lists it. */
830
862
  const SAMPLED_LINK_TYPES = [
831
863
  "FLOWS_TO",
@@ -875,10 +907,10 @@ const CATALOG_READS = {
875
907
  }
876
908
  };
877
909
  function addressSampleQuery() {
878
- return `USE topology MATCH (a:Address) RETURN keys(a) AS keys LIMIT 20`;
910
+ return `USE topology MATCH (a:Address) RETURN keys(a) AS keys LIMIT ${ADDRESS_SAMPLE_LIMIT}`;
879
911
  }
880
912
  function linkSampleQuery(linkType) {
881
- return `USE topology MATCH ()-[r:${linkType}]->() RETURN keys(r) AS keys LIMIT 5`;
913
+ return `USE topology MATCH ()-[r:${linkType}]->() RETURN keys(r) AS keys LIMIT ${LINK_SAMPLE_LIMIT}`;
882
914
  }
883
915
  /** A refusal with its own words for the caller: a bad argument, an unserved network, an endpoint that is down. */
884
916
  var GraphSchemaError = class extends Error {
@@ -1052,7 +1084,7 @@ async function buildGraphSchema(options) {
1052
1084
  if (rows) topology.samples[type] = unionOfKeys(rows);
1053
1085
  }
1054
1086
  const unsampled = topology.link_types.filter((type) => !SAMPLED_LINK_TYPES.includes(type));
1055
- if (unsampled.length > 0) remarks.push(`Link types with no sample: ${unsampled.join(", ")}. Read the keys of one with USE topology MATCH ()-[r:<TYPE>]->() RETURN keys(r) AS keys LIMIT 5.`);
1087
+ if (unsampled.length > 0) remarks.push(`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}.`);
1056
1088
  }
1057
1089
  const facts = {
1058
1090
  relationships: [],
@@ -1188,6 +1220,6 @@ async function handleMetaSchema(args, deps) {
1188
1220
  }
1189
1221
  }
1190
1222
  //#endregion
1191
- 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 };
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 };
1192
1224
 
1193
- //# sourceMappingURL=graph-schema-DqZUTnP0.mjs.map
1225
+ //# sourceMappingURL=graph-schema-ClqjzgrM.mjs.map