chain-insights 0.2.32 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -14
- package/dist/cases-Cp9DUbEV.mjs +6 -0
- package/dist/{cases-c0iV-XLI.cjs → cases-sTY5aXav.cjs} +3 -3
- package/dist/cli.cjs +122 -66
- package/dist/cli.mjs +122 -66
- package/dist/cli.mjs.map +1 -1
- package/dist/{viz-Da9YWN_I.cjs → data-extractor-Cavd7wHk.cjs} +11 -34
- package/dist/{viz-DkJyqlUu.mjs → data-extractor-DZUJu1Bz.mjs} +3 -32
- package/dist/data-extractor-DZUJu1Bz.mjs.map +1 -0
- package/dist/{dossier-Br62hCG7.cjs → dossier-BXy57V4-.cjs} +13 -1
- package/dist/{dossier-Bl0NkJKC.mjs → dossier-Bjpcbcxa.mjs} +4 -2
- package/dist/{dossier-Bl0NkJKC.mjs.map → dossier-Bjpcbcxa.mjs.map} +1 -1
- package/dist/export-BqTCO9lP.mjs +591 -0
- package/dist/export-BqTCO9lP.mjs.map +1 -0
- package/dist/export-DsXgtCwO.cjs +592 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{init-DBC9Ml33.mjs → init-DLBL_nVG.mjs} +27 -1
- package/dist/{init-DBC9Ml33.mjs.map → init-DLBL_nVG.mjs.map} +1 -1
- package/dist/{init-CFaUWgjK.cjs → init-zqbd7i-_.cjs} +26 -0
- package/dist/mcp-proxy.cjs +215 -77
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +215 -77
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{public-tools-BwguvIsf.cjs → public-tools-BvMb3H2P.cjs} +701 -1479
- package/dist/{public-tools-DoRNhMn9.mjs → public-tools-wJoAFDFa.mjs} +700 -1479
- package/dist/public-tools-wJoAFDFa.mjs.map +1 -0
- package/dist/{resolver-D7VBb0uB.mjs → resolver-2jXNtWQO.mjs} +12 -29
- package/dist/resolver-2jXNtWQO.mjs.map +1 -0
- package/dist/{resolver-BUU7ZgW-.cjs → resolver-CZdQwKvh.cjs} +11 -28
- package/dist/{runner-BCDeBYsR.cjs → runner-BhZ4lnF1.cjs} +2 -2
- package/dist/{runner-CTFK0Qcg.mjs → runner-DIJSbkjc.mjs} +3 -3
- package/dist/{runner-CTFK0Qcg.mjs.map → runner-DIJSbkjc.mjs.map} +1 -1
- package/dist/{selector-CTUiQrzI.mjs → selector-CF2o5gxN.mjs} +2 -2
- package/dist/{selector-CTUiQrzI.mjs.map → selector-CF2o5gxN.mjs.map} +1 -1
- package/dist/{selector-DBS2jYH4.cjs → selector-DfAMZEC9.cjs} +1 -1
- package/dist/{session-DwyikazY.cjs → session-BT7VpbAd.cjs} +13 -1
- package/dist/{session-Bha3zFrx.mjs → session-DROyhebe.mjs} +4 -2
- package/dist/{session-Bha3zFrx.mjs.map → session-DROyhebe.mjs.map} +1 -1
- package/dist/{store-BT2SCcQr.mjs → store-CTtqQtaE.mjs} +10 -4
- package/dist/{store-BT2SCcQr.mjs.map → store-CTtqQtaE.mjs.map} +1 -1
- package/dist/{store-DogLawSj.cjs → store-CqPfs47P.cjs} +37 -7
- package/dist/{tool-visibility-BHRFLXuU.mjs → tool-visibility-BpyZHRBi.mjs} +4 -2
- package/dist/tool-visibility-BpyZHRBi.mjs.map +1 -0
- package/dist/{tool-visibility-iAVQV3t0.cjs → tool-visibility-Buq7YdUZ.cjs} +3 -1
- package/dist/viz-5y24S5X1.mjs +35 -0
- package/dist/viz-5y24S5X1.mjs.map +1 -0
- package/dist/viz-Dqp3C5kb.cjs +44 -0
- package/docs/contributing.md +3 -2
- package/docs/graph-tools.md +126 -117
- package/docs/investigation-workspaces.md +17 -0
- package/docs/knowledge-exports.md +200 -0
- package/docs/mcp-proxy.md +16 -2
- package/package.json +1 -1
- package/skills/chain-insights-cypher/SKILL.md +6 -0
- package/skills/chain-insights-developer-experience/SKILL.md +26 -6
- package/skills/chain-insights-investigation/SKILL.md +64 -48
- package/skills/chain-insights-trace-funds/SKILL.md +80 -197
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +1 -1
- package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +4 -4
- package/dist/cases-qjPtbnUd.mjs +0 -6
- package/dist/public-tools-DoRNhMn9.mjs.map +0 -1
- package/dist/resolver-D7VBb0uB.mjs.map +0 -1
- package/dist/tool-visibility-BHRFLXuU.mjs.map +0 -1
- package/dist/viz-DkJyqlUu.mjs.map +0 -1
package/dist/mcp-proxy.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as PACKAGE_VERSION } from "./version-BA3J8hu4.mjs";
|
|
2
2
|
import { t as PaymentRequiredError } from "./client-D4JE7fFF.mjs";
|
|
3
|
-
import { t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-
|
|
3
|
+
import { t as HIDDEN_REMOTE_TOOL_NAMES } from "./tool-visibility-BpyZHRBi.mjs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
@@ -20,17 +20,19 @@ const LOCAL_TOOL_NAMES = new Set([
|
|
|
20
20
|
"case_resume",
|
|
21
21
|
"case_add_evidence",
|
|
22
22
|
"case_verify_evidence",
|
|
23
|
+
"case_export",
|
|
23
24
|
"case_update_dossier",
|
|
24
25
|
"case_start_session",
|
|
25
26
|
"case_end_session"
|
|
26
27
|
]);
|
|
27
|
-
const PUBLIC_GRAPHRAG_PROMPT_NAMES = new Set(["address-risk", "
|
|
28
|
+
const PUBLIC_GRAPHRAG_PROMPT_NAMES = new Set(["address-risk", "trace-tools"]);
|
|
28
29
|
const GRAPH_RESOURCE_URI = "ui://chain-insights/graph";
|
|
29
30
|
const GRAPH_APP_TOOL_NAMES = new Set([
|
|
30
31
|
"address_risk",
|
|
31
|
-
"scam_topology",
|
|
32
32
|
"stake_insights",
|
|
33
|
-
"
|
|
33
|
+
"trace_victim_funds",
|
|
34
|
+
"trace_suspect_funds",
|
|
35
|
+
"trace_deposit_sources"
|
|
34
36
|
]);
|
|
35
37
|
const GRAPH_ARRAY_KEYS = [
|
|
36
38
|
"nodes",
|
|
@@ -39,25 +41,28 @@ const GRAPH_ARRAY_KEYS = [
|
|
|
39
41
|
"edge_anchors"
|
|
40
42
|
];
|
|
41
43
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
42
|
-
const COMMA_SEPARATED_ADDRESS_FIELDS = new Set([
|
|
44
|
+
const COMMA_SEPARATED_ADDRESS_FIELDS = new Set([
|
|
45
|
+
"victim_addresses",
|
|
46
|
+
"known_suspect_addresses",
|
|
47
|
+
"suspect_addresses",
|
|
48
|
+
"deposit_addresses"
|
|
49
|
+
]);
|
|
43
50
|
const KNOWN_PUBLIC_TOOL_REQUIRED_ARGS = {
|
|
44
51
|
address_risk: ["address", "network"],
|
|
45
|
-
scam_topology: [
|
|
46
|
-
"victim_address",
|
|
47
|
-
"incident_timestamp_ms",
|
|
48
|
-
"network"
|
|
49
|
-
],
|
|
50
52
|
stake_insights: ["network"],
|
|
51
|
-
|
|
53
|
+
trace_victim_funds: ["victim_addresses", "network"],
|
|
54
|
+
trace_suspect_funds: ["suspect_addresses", "network"],
|
|
55
|
+
trace_deposit_sources: ["deposit_addresses", "network"],
|
|
52
56
|
graph_query: ["query", "network"],
|
|
53
57
|
graph_query_batch: ["network", "queries"]
|
|
54
58
|
};
|
|
55
59
|
const KNOWN_PUBLIC_TOOL_DESCRIPTIONS = {
|
|
56
60
|
network_capabilities: "Return supported Chain Insights networks, capability layers, tool availability, data retention windows, and freshness. Use this before choosing network-specific tools.",
|
|
57
61
|
address_risk: "Screen one full blockchain address for AML risk, behavior patterns, neighborhood context, exchange exposure, and optional comparison with compare_address. This includes the exchange-behavior analysis formerly covered by money_flows_between_exchanges. Use this as the first tool for a single-address investigation. The tool returns an investigator-ready summary; preserve full addresses exactly.",
|
|
58
|
-
scam_topology: "Build victim-incident laundering topology from one victim/source address and the earliest known incident timestamp. Traversal uses one explicit activity policy: node_relative_only by default, or global_incident_only when requested. Repeated targets are kept as non-expanding convergence edges. Returns ML-ready scam_labels plus review context and a track_funds-compatible graph report: primary flows, deposits, reverse_leads. Victims, exchange endpoints, and generic labeled context nodes are not automatic scam labels; preserve full addresses exactly.",
|
|
59
62
|
stake_insights: "Explain Bittensor staking behavior around one full address, coldkey, or hotkey. Requires network plus exactly one of address, coldkey, or hotkey. Returns net staked/unstaked amounts, active coldkey-hotkey-netuid relationships, aggregate stake movement amounts, top counterparties, first/last activity, source backend, query evidence, and optional graph report metadata.",
|
|
60
|
-
|
|
63
|
+
trace_victim_funds: "Trace victim/source funds forward through intermediaries to exchange deposit candidates. Use only when the input addresses are victims or trusted stolen-source addresses; do not use for suspected deposit addresses because traceback belongs to trace_deposit_sources. Exchange hot wallets are terminal only, never candidate deposits. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
|
|
64
|
+
trace_suspect_funds: "Trace suspected scammer, mule, operator, or laundering-ring funds forward to cashout topology. Use when the input addresses are suspect-controlled seeds; incident_timestamp_ms is optional. Do not use for victim/source addresses or suspected deposit endpoints. Exchange hot wallets are terminal only, never candidate suspects or intermediates. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
|
|
65
|
+
trace_deposit_sources: "Trace backward from suspected deposit/cashout addresses to upstream sources, shared funders, and convergence. Use only when the input addresses are suspected non-exchange deposit endpoints; do not treat these seeds as scammers and do not continue forward from discovered suspects here. Exchange hot wallets are excluded as seeds and upstream sources. Returns chain-insights.trace.v1 and preserves full addresses exactly.",
|
|
61
66
|
graph_query: "Run a read-only GQL/Cypher query through the Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment. Cross-layer correlated joins may be limited by the active graph endpoint; preserve full addresses exactly.",
|
|
62
67
|
graph_query_batch: "Run multiple read-only GQL/Cypher queries through the Chain Insights graph endpoint in one paid batch. Prefer this for related topology/facts reads."
|
|
63
68
|
};
|
|
@@ -72,9 +77,10 @@ const CHAIN_INSIGHTS_WORKFLOW = [
|
|
|
72
77
|
"Workflow:",
|
|
73
78
|
"1. If the user is starting or continuing an investigation, use case_open or case_list/case_resume first.",
|
|
74
79
|
"2. Do not call investigation tools until required arguments are known. Network is required; use network_capabilities to check supported networks, data layers, retention, and freshness, or ask the user if missing.",
|
|
75
|
-
"3. Use address_risk
|
|
80
|
+
"3. Use address_risk for single-address enrichment. Use trace_victim_funds for victim/source forward tracing, trace_deposit_sources for reverse traceback from suspected deposit endpoints, and trace_suspect_funds for suspect-controlled outbound laundering/cashout topology. Use stake_insights for Bittensor staking behavior. Use graph_query(_batch) only when the high-level trace tools do not answer the exact question.",
|
|
76
81
|
"4. After a material result, preserve it with case_add_evidence when a case is active or ask whether to create/select a case.",
|
|
77
|
-
"5. Use case_update_dossier for durable address/entity findings and case_start_session/case_end_session for session notes."
|
|
82
|
+
"5. Use case_update_dossier for durable address/entity findings and case_start_session/case_end_session for session notes.",
|
|
83
|
+
"6. When a case reaches a useful checkpoint, use case_verify_evidence and case_export to produce Obsidian, LLMWiki, Codex, Claude Code, and ChatGPT-ready files."
|
|
78
84
|
].join("\n");
|
|
79
85
|
const GRAPH_SCHEMA_HINTS = [
|
|
80
86
|
"Graph query hints for network=bittensor:",
|
|
@@ -85,6 +91,7 @@ const GRAPH_SCHEMA_HINTS = [
|
|
|
85
91
|
"- Risk and ML properties may appear as live hints, but source-of-truth risk rows are RiskScore facts.",
|
|
86
92
|
"- Common relationships include FLOWS_TO, OPERATED_FROM, SERVED_FROM, REGISTERED_NEURON, BELONGS_TO, SYBIL_CLUSTER, LAYERING_HOP, BURST_ACTIVITY, CYCLE_PARTICIPANT, SMURFING_CLUSTER.",
|
|
87
93
|
"- FLOWS_TO properties are scoped to the selected topology graph and commonly carry amount_sum, amount_usd_sum, tx_count, first_seen_timestamp, last_seen_timestamp, first_tx_id, last_tx_id. Confirm available fields through runtime schema before relying on them.",
|
|
94
|
+
"- Traversal rule: for BFS, fixed-hop fallback, shortest-path, or manual FLOWS_TO traversal, exchange hot wallets are terminal endpoints only. Do not expand from, through, or classify exchange nodes as deposit, suspect, or intermediate candidates; filter every non-terminal node with is_exchange IS NULL.",
|
|
88
95
|
"- Start schema discovery with endpoint-safe property reads: MATCH (n:Address) WHERE n.address IS NOT NULL RETURN n.labels AS labels, n.address AS address LIMIT 20",
|
|
89
96
|
"- Relationship discovery: MATCH (:Address)-[r:FLOWS_TO]->(:Address) RETURN r.amount_sum AS amount_sum, r.amount_usd_sum AS amount_usd_sum LIMIT 20",
|
|
90
97
|
"- graph_query uses the active Chain Insights graph endpoint. Use USE live_topology for recent topology, USE archive_topology for historical topology, and USE facts for labels, features, risk scores, assets, and enrichment.",
|
|
@@ -148,20 +155,27 @@ function knownPublicToolInputSchema(toolName) {
|
|
|
148
155
|
compare_address: z.string().optional().describe("Optional second full address for comparison"),
|
|
149
156
|
include_attachments: z.boolean().optional().describe("Include graph app report metadata")
|
|
150
157
|
};
|
|
151
|
-
case "
|
|
152
|
-
|
|
158
|
+
case "trace_victim_funds": return {
|
|
159
|
+
victim_addresses: z.string().min(1).describe("Comma-separated full victim/source addresses. Min 1, max 5."),
|
|
153
160
|
network: z.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
154
|
-
|
|
161
|
+
known_suspect_addresses: z.string().optional().describe("Optional known suspect addresses for context only. They are not reverse-traced by this tool. Max 5."),
|
|
162
|
+
incident_timestamp_ms: z.number().min(0).optional().describe("Optional incident timestamp in milliseconds."),
|
|
155
163
|
include_attachments: z.boolean().optional().describe("Include graph app report metadata")
|
|
156
164
|
};
|
|
157
|
-
case "
|
|
165
|
+
case "trace_suspect_funds": return {
|
|
158
166
|
network: z.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
159
|
-
|
|
160
|
-
incident_timestamp_ms: z.number().min(0).describe("
|
|
161
|
-
max_hops: z.number().int().min(1).max(
|
|
162
|
-
activity_policy: z.enum(["node_relative_only", "global_incident_only"]).optional().describe("Traversal activity policy. Default node_relative_only."),
|
|
167
|
+
suspect_addresses: z.string().min(1).describe("Comma-separated full suspected scammer, mule, operator, or laundering-ring addresses. Min 1, max 5."),
|
|
168
|
+
incident_timestamp_ms: z.number().min(0).optional().describe("Optional incident timestamp in milliseconds. This tool also works without a timestamp."),
|
|
169
|
+
max_hops: z.number().int().min(1).max(5).optional().describe("Maximum forward trace hops. Default 3."),
|
|
163
170
|
case_id: z.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest.")
|
|
164
171
|
};
|
|
172
|
+
case "trace_deposit_sources": return {
|
|
173
|
+
network: z.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
174
|
+
deposit_addresses: z.string().min(1).describe("Comma-separated full suspected deposit/cashout addresses. Min 1, max 5."),
|
|
175
|
+
max_hops: z.number().int().min(1).max(5).optional().describe("Maximum reverse traceback hops. Default 2."),
|
|
176
|
+
case_id: z.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
|
|
177
|
+
include_attachments: z.boolean().optional().describe("Include graph app report metadata")
|
|
178
|
+
};
|
|
165
179
|
case "stake_insights": return {
|
|
166
180
|
network: z.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
167
181
|
address: z.string().optional().describe("Full Bittensor address to inspect as either coldkey or hotkey. Provide exactly one of address, coldkey, or hotkey."),
|
|
@@ -417,24 +431,27 @@ function registerLocalPrompts(server, remotePromptNames) {
|
|
|
417
431
|
"",
|
|
418
432
|
"Present the summary as-is. Do not add analysis, verdicts, or risk assessments; the tool output already contains the risk assessment."
|
|
419
433
|
].join("\n"), "Address risk screening"));
|
|
420
|
-
if (!remotePromptNames.has("
|
|
421
|
-
title: "
|
|
422
|
-
description: "
|
|
434
|
+
if (!remotePromptNames.has("trace-tools")) server.registerPrompt("trace-tools", {
|
|
435
|
+
title: "Trace Tools",
|
|
436
|
+
description: "Choose trace_victim_funds, trace_deposit_sources, or trace_suspect_funds based on the evidence role.",
|
|
423
437
|
argsSchema: {
|
|
424
|
-
|
|
425
|
-
|
|
438
|
+
addresses: z.string().describe("Input addresses, comma-separated full addresses"),
|
|
439
|
+
role: z.enum([
|
|
440
|
+
"victim",
|
|
441
|
+
"suspect",
|
|
442
|
+
"deposit"
|
|
443
|
+
]).describe("Role of the supplied addresses"),
|
|
426
444
|
network: z.string().describe(NETWORK_DESCRIPTION)
|
|
427
445
|
}
|
|
428
|
-
}, async ({
|
|
429
|
-
const untrusted = untrusted_addresses?.trim() ? `\nKnown untrusted addresses:\n${untrusted_addresses}\n` : "";
|
|
446
|
+
}, async ({ addresses, role, network }) => {
|
|
430
447
|
return promptResult([
|
|
431
|
-
`Use Chain Insights
|
|
448
|
+
`Use Chain Insights ${role === "deposit" ? "trace_deposit_sources" : `trace_${role}_funds`} on ${network}.`,
|
|
449
|
+
"",
|
|
450
|
+
"Full addresses:",
|
|
451
|
+
addresses,
|
|
432
452
|
"",
|
|
433
|
-
"
|
|
434
|
-
|
|
435
|
-
untrusted,
|
|
436
|
-
"Present the summary as-is and include recommended next actions exactly as returned."
|
|
437
|
-
].join("\n"), "Trace stolen funds");
|
|
453
|
+
role === "deposit" ? "For deposit role, use trace_deposit_sources rather than trace_deposit_funds." : "Present the summary as-is and use continuation.recommended_next_tools for follow-up."
|
|
454
|
+
].join("\n"), "Trace role-specific funds");
|
|
438
455
|
});
|
|
439
456
|
server.registerPrompt("graph-query", {
|
|
440
457
|
title: "Federated Graph Query",
|
|
@@ -754,13 +771,13 @@ async function createProxy() {
|
|
|
754
771
|
}
|
|
755
772
|
}, async ({ name, tags, description }) => {
|
|
756
773
|
try {
|
|
757
|
-
const { CaseStore } = await import("./cases-
|
|
774
|
+
const { CaseStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
758
775
|
const created = await CaseStore.create({
|
|
759
776
|
name,
|
|
760
777
|
tags: parseTags(tags),
|
|
761
778
|
description: description ?? ""
|
|
762
779
|
});
|
|
763
|
-
const { casesRoot } = await import("./store-
|
|
780
|
+
const { casesRoot } = await import("./store-CTtqQtaE.mjs").then((n) => n.r);
|
|
764
781
|
return {
|
|
765
782
|
content: [{
|
|
766
783
|
type: "text",
|
|
@@ -794,7 +811,7 @@ async function createProxy() {
|
|
|
794
811
|
}
|
|
795
812
|
}, async ({ status }) => {
|
|
796
813
|
try {
|
|
797
|
-
const { CaseStore } = await import("./cases-
|
|
814
|
+
const { CaseStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
798
815
|
const cases = await CaseStore.list();
|
|
799
816
|
const filtered = status ? cases.filter((entry) => entry.status === status) : cases;
|
|
800
817
|
return {
|
|
@@ -819,7 +836,7 @@ async function createProxy() {
|
|
|
819
836
|
}
|
|
820
837
|
}, async ({ case_id }) => {
|
|
821
838
|
try {
|
|
822
|
-
const { CaseStore } = await import("./cases-
|
|
839
|
+
const { CaseStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
823
840
|
const context = await CaseStore.loadContext(case_id);
|
|
824
841
|
return {
|
|
825
842
|
content: [{
|
|
@@ -833,7 +850,7 @@ async function createProxy() {
|
|
|
833
850
|
}
|
|
834
851
|
});
|
|
835
852
|
server.registerTool("case_add_evidence", {
|
|
836
|
-
description: "Append a tool result or analyst note to a local case evidence manifest. Use after address_risk,
|
|
853
|
+
description: "Append a tool result or analyst note to a local case evidence manifest. Use after address_risk, trace_victim_funds, trace_suspect_funds, trace_deposit_sources, graph_query, or manual findings that should be preserved.",
|
|
837
854
|
inputSchema: {
|
|
838
855
|
case_id: z.string().min(1).describe("Chain Insights case ID"),
|
|
839
856
|
source: z.string().min(1).describe("Source tool or evidence origin"),
|
|
@@ -848,7 +865,7 @@ async function createProxy() {
|
|
|
848
865
|
}
|
|
849
866
|
}, async ({ case_id, source, content, query_params }) => {
|
|
850
867
|
try {
|
|
851
|
-
const { EvidenceStore } = await import("./cases-
|
|
868
|
+
const { EvidenceStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
852
869
|
const saved = await EvidenceStore.append(case_id, {
|
|
853
870
|
source,
|
|
854
871
|
content,
|
|
@@ -876,7 +893,7 @@ async function createProxy() {
|
|
|
876
893
|
}
|
|
877
894
|
}, async ({ case_id }) => {
|
|
878
895
|
try {
|
|
879
|
-
const { EvidenceStore } = await import("./cases-
|
|
896
|
+
const { EvidenceStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
880
897
|
const result = await EvidenceStore.verifyManifest(case_id);
|
|
881
898
|
return {
|
|
882
899
|
content: [{
|
|
@@ -889,6 +906,51 @@ async function createProxy() {
|
|
|
889
906
|
return caseToolError("Evidence verify", err);
|
|
890
907
|
}
|
|
891
908
|
});
|
|
909
|
+
server.registerTool("case_export", {
|
|
910
|
+
description: "Export a Chain Insights case to an Obsidian, LLMWiki, Codex, Claude Code, and ChatGPT-friendly local bundle.",
|
|
911
|
+
inputSchema: {
|
|
912
|
+
case_id: z.string().min(1).describe("Chain Insights case ID to export"),
|
|
913
|
+
target: z.enum(["obsidian-llmwiki"]).optional().describe("Export target. Default obsidian-llmwiki."),
|
|
914
|
+
mode: z.enum([
|
|
915
|
+
"private",
|
|
916
|
+
"partner",
|
|
917
|
+
"public"
|
|
918
|
+
]).optional().describe("Redaction mode. Default private."),
|
|
919
|
+
output_dir: z.string().optional().describe("Optional output directory. Defaults to published/<case-slug>.")
|
|
920
|
+
},
|
|
921
|
+
annotations: {
|
|
922
|
+
readOnlyHint: false,
|
|
923
|
+
destructiveHint: false,
|
|
924
|
+
idempotentHint: false,
|
|
925
|
+
openWorldHint: false
|
|
926
|
+
}
|
|
927
|
+
}, async ({ case_id, target, mode, output_dir }) => {
|
|
928
|
+
try {
|
|
929
|
+
const { exportCase } = await import("./export-BqTCO9lP.mjs");
|
|
930
|
+
const result = await exportCase({
|
|
931
|
+
caseId: case_id,
|
|
932
|
+
target: target ?? "obsidian-llmwiki",
|
|
933
|
+
mode: mode ?? "private",
|
|
934
|
+
outputDir: output_dir
|
|
935
|
+
});
|
|
936
|
+
return {
|
|
937
|
+
content: [{
|
|
938
|
+
type: "text",
|
|
939
|
+
text: [
|
|
940
|
+
`Case exported: ${result.outputDir}`,
|
|
941
|
+
`Manifest: ${result.manifestPath}`,
|
|
942
|
+
`Files: ${result.fileCount}`,
|
|
943
|
+
`Open first: ${result.nextFile}`,
|
|
944
|
+
...result.warnings.map((warning) => `Warning: ${warning}`)
|
|
945
|
+
].join("\n")
|
|
946
|
+
}],
|
|
947
|
+
structuredContent: result,
|
|
948
|
+
isError: false
|
|
949
|
+
};
|
|
950
|
+
} catch (err) {
|
|
951
|
+
return caseToolError("Case export", err);
|
|
952
|
+
}
|
|
953
|
+
});
|
|
892
954
|
server.registerTool("case_update_dossier", {
|
|
893
955
|
description: "Append a finding to an address/entity dossier inside a local Chain Insights case.",
|
|
894
956
|
inputSchema: {
|
|
@@ -911,7 +973,7 @@ async function createProxy() {
|
|
|
911
973
|
}
|
|
912
974
|
}, async ({ case_id, address, finding, entity_type }) => {
|
|
913
975
|
try {
|
|
914
|
-
const { DossierStore } = await import("./cases-
|
|
976
|
+
const { DossierStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
915
977
|
await DossierStore.appendFinding(case_id, address, finding, entity_type ?? "unknown");
|
|
916
978
|
return {
|
|
917
979
|
content: [{
|
|
@@ -939,7 +1001,7 @@ async function createProxy() {
|
|
|
939
1001
|
}
|
|
940
1002
|
}, async ({ case_id }) => {
|
|
941
1003
|
try {
|
|
942
|
-
const { SessionStore } = await import("./cases-
|
|
1004
|
+
const { SessionStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
943
1005
|
const session = await SessionStore.start(case_id);
|
|
944
1006
|
return {
|
|
945
1007
|
content: [{
|
|
@@ -967,7 +1029,7 @@ async function createProxy() {
|
|
|
967
1029
|
}
|
|
968
1030
|
}, async ({ case_id, findings, next_steps }) => {
|
|
969
1031
|
try {
|
|
970
|
-
const { SessionStore } = await import("./cases-
|
|
1032
|
+
const { SessionStore } = await import("./cases-Cp9DUbEV.mjs");
|
|
971
1033
|
await SessionStore.end(case_id, {
|
|
972
1034
|
findings: findings ?? "",
|
|
973
1035
|
nextSteps: next_steps ?? ""
|
|
@@ -1012,7 +1074,7 @@ async function createProxy() {
|
|
|
1012
1074
|
}],
|
|
1013
1075
|
isError: true
|
|
1014
1076
|
};
|
|
1015
|
-
const { addressRisk } = await import("./public-tools-
|
|
1077
|
+
const { addressRisk } = await import("./public-tools-wJoAFDFa.mjs");
|
|
1016
1078
|
const { writeGraphReport } = await import("./graph-reports-BDELxmpi.mjs");
|
|
1017
1079
|
const { ensureArtifactServer } = await import("./artifact-server-CP6LXQ9d.mjs");
|
|
1018
1080
|
const result = await addressRisk(remoteClient, {
|
|
@@ -1054,15 +1116,16 @@ async function createProxy() {
|
|
|
1054
1116
|
};
|
|
1055
1117
|
}
|
|
1056
1118
|
});
|
|
1057
|
-
if (!remoteToolNames.has("
|
|
1058
|
-
title: "
|
|
1059
|
-
description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.
|
|
1119
|
+
if (!remoteToolNames.has("trace_victim_funds")) registerAppTool(server, "trace_victim_funds", {
|
|
1120
|
+
title: "Trace Victim Funds",
|
|
1121
|
+
description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.trace_victim_funds,
|
|
1060
1122
|
inputSchema: {
|
|
1061
|
-
|
|
1123
|
+
victim_addresses: z.union([z.string().min(1), z.array(z.string().min(1))]).describe("Comma-separated full victim/source addresses, or an array. Min 1, max 5."),
|
|
1062
1124
|
network: z.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
1063
|
-
|
|
1125
|
+
known_suspect_addresses: z.union([z.string(), z.array(z.string())]).optional().describe("Known suspect addresses for context only. This tool does not reverse-trace them. Max 5."),
|
|
1064
1126
|
include_attachments: z.boolean().optional().describe("Include graph app report metadata"),
|
|
1065
1127
|
case_id: z.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
|
|
1128
|
+
incident_timestamp_ms: z.number().min(0).optional().describe("Optional incident timestamp in milliseconds."),
|
|
1066
1129
|
max_hops: z.number().int().min(1).max(5).optional(),
|
|
1067
1130
|
per_address_limit: z.number().int().min(1).max(10).optional(),
|
|
1068
1131
|
min_amount_sum: z.number().min(0).optional()
|
|
@@ -1074,7 +1137,7 @@ async function createProxy() {
|
|
|
1074
1137
|
idempotentHint: false,
|
|
1075
1138
|
openWorldHint: true
|
|
1076
1139
|
}
|
|
1077
|
-
}, async ({
|
|
1140
|
+
}, async ({ victim_addresses, known_suspect_addresses, network, case_id, incident_timestamp_ms, max_hops, per_address_limit, min_amount_sum }) => {
|
|
1078
1141
|
try {
|
|
1079
1142
|
if (!remoteConnected) return {
|
|
1080
1143
|
content: [{
|
|
@@ -1083,21 +1146,22 @@ async function createProxy() {
|
|
|
1083
1146
|
}],
|
|
1084
1147
|
isError: true
|
|
1085
1148
|
};
|
|
1086
|
-
const {
|
|
1149
|
+
const { traceVictimFunds } = await import("./public-tools-wJoAFDFa.mjs");
|
|
1087
1150
|
const { writeGraphReport } = await import("./graph-reports-BDELxmpi.mjs");
|
|
1088
1151
|
const { ensureArtifactServer } = await import("./artifact-server-CP6LXQ9d.mjs");
|
|
1089
|
-
const result = await
|
|
1090
|
-
|
|
1091
|
-
|
|
1152
|
+
const result = await traceVictimFunds(remoteClient, config, {
|
|
1153
|
+
victimAddresses: victim_addresses,
|
|
1154
|
+
knownSuspectAddresses: known_suspect_addresses,
|
|
1092
1155
|
network,
|
|
1093
1156
|
caseId: case_id,
|
|
1157
|
+
incidentTimestampMs: incident_timestamp_ms,
|
|
1094
1158
|
maxHops: max_hops,
|
|
1095
1159
|
perAddressLimit: per_address_limit,
|
|
1096
1160
|
minAmountSum: min_amount_sum
|
|
1097
1161
|
});
|
|
1098
1162
|
const report = await writeGraphReport(result.graphData, {
|
|
1099
1163
|
serverPort: config.serverPort,
|
|
1100
|
-
slug: `
|
|
1164
|
+
slug: `trace-victim-funds-${network}`
|
|
1101
1165
|
});
|
|
1102
1166
|
await ensureArtifactServer(config.serverPort);
|
|
1103
1167
|
return {
|
|
@@ -1123,22 +1187,24 @@ async function createProxy() {
|
|
|
1123
1187
|
return {
|
|
1124
1188
|
content: [{
|
|
1125
1189
|
type: "text",
|
|
1126
|
-
text: `
|
|
1190
|
+
text: `Trace victim funds failed: ${err.message}`
|
|
1127
1191
|
}],
|
|
1128
1192
|
isError: true
|
|
1129
1193
|
};
|
|
1130
1194
|
}
|
|
1131
1195
|
});
|
|
1132
|
-
if (!remoteToolNames.has("
|
|
1133
|
-
title: "
|
|
1134
|
-
description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.
|
|
1196
|
+
if (!remoteToolNames.has("trace_suspect_funds")) registerAppTool(server, "trace_suspect_funds", {
|
|
1197
|
+
title: "Trace Suspect Funds",
|
|
1198
|
+
description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.trace_suspect_funds,
|
|
1135
1199
|
inputSchema: {
|
|
1136
1200
|
network: z.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
1137
|
-
|
|
1138
|
-
incident_timestamp_ms: z.number().min(0).describe("
|
|
1139
|
-
max_hops: z.number().int().min(1).max(
|
|
1140
|
-
|
|
1141
|
-
|
|
1201
|
+
suspect_addresses: z.union([z.string().min(1), z.array(z.string().min(1))]).describe("Comma-separated full suspect-controlled addresses, or an array. Min 1, max 5."),
|
|
1202
|
+
incident_timestamp_ms: z.number().min(0).optional().describe("Optional incident timestamp in milliseconds. This tool works without it."),
|
|
1203
|
+
max_hops: z.number().int().min(1).max(5).optional().describe("Maximum forward trace hops. Default 3."),
|
|
1204
|
+
per_address_limit: z.number().int().min(1).max(10).optional(),
|
|
1205
|
+
min_amount_sum: z.number().min(0).optional(),
|
|
1206
|
+
case_id: z.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
|
|
1207
|
+
include_attachments: z.boolean().optional().describe("Include graph app report metadata")
|
|
1142
1208
|
},
|
|
1143
1209
|
_meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
|
|
1144
1210
|
annotations: {
|
|
@@ -1147,7 +1213,7 @@ async function createProxy() {
|
|
|
1147
1213
|
idempotentHint: false,
|
|
1148
1214
|
openWorldHint: true
|
|
1149
1215
|
}
|
|
1150
|
-
}, async ({
|
|
1216
|
+
}, async ({ suspect_addresses, incident_timestamp_ms, network, max_hops, per_address_limit, min_amount_sum, case_id }) => {
|
|
1151
1217
|
try {
|
|
1152
1218
|
if (!remoteConnected) return {
|
|
1153
1219
|
content: [{
|
|
@@ -1156,20 +1222,90 @@ async function createProxy() {
|
|
|
1156
1222
|
}],
|
|
1157
1223
|
isError: true
|
|
1158
1224
|
};
|
|
1159
|
-
const {
|
|
1225
|
+
const { traceSuspectFunds } = await import("./public-tools-wJoAFDFa.mjs");
|
|
1160
1226
|
const { writeGraphReport } = await import("./graph-reports-BDELxmpi.mjs");
|
|
1161
1227
|
const { ensureArtifactServer } = await import("./artifact-server-CP6LXQ9d.mjs");
|
|
1162
|
-
const result = await
|
|
1163
|
-
|
|
1228
|
+
const result = await traceSuspectFunds(remoteClient, config, {
|
|
1229
|
+
suspectAddresses: suspect_addresses,
|
|
1164
1230
|
network,
|
|
1165
1231
|
maxHops: max_hops,
|
|
1232
|
+
perAddressLimit: per_address_limit,
|
|
1233
|
+
minAmountSum: min_amount_sum,
|
|
1166
1234
|
incidentTimestampMs: incident_timestamp_ms,
|
|
1167
|
-
activityPolicyMode: activity_policy,
|
|
1168
1235
|
caseId: case_id
|
|
1169
1236
|
});
|
|
1170
1237
|
const report = await writeGraphReport(result.graphData, {
|
|
1171
1238
|
serverPort: config.serverPort,
|
|
1172
|
-
slug: `
|
|
1239
|
+
slug: `trace-suspect-funds-${network}`
|
|
1240
|
+
});
|
|
1241
|
+
await ensureArtifactServer(config.serverPort);
|
|
1242
|
+
return {
|
|
1243
|
+
content: [{
|
|
1244
|
+
type: "text",
|
|
1245
|
+
text: result.summaryText
|
|
1246
|
+
}],
|
|
1247
|
+
structuredContent: result.structuredContent,
|
|
1248
|
+
_meta: { chainInsights: { graph: {
|
|
1249
|
+
schema: report.schema,
|
|
1250
|
+
url: report.url
|
|
1251
|
+
} } },
|
|
1252
|
+
isError: false
|
|
1253
|
+
};
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
if (err instanceof PaymentRequiredError) return {
|
|
1256
|
+
content: [{
|
|
1257
|
+
type: "text",
|
|
1258
|
+
text: err.message
|
|
1259
|
+
}],
|
|
1260
|
+
isError: true
|
|
1261
|
+
};
|
|
1262
|
+
return {
|
|
1263
|
+
content: [{
|
|
1264
|
+
type: "text",
|
|
1265
|
+
text: `Trace suspect funds failed: ${err.message}`
|
|
1266
|
+
}],
|
|
1267
|
+
isError: true
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
if (!remoteToolNames.has("trace_deposit_sources")) registerAppTool(server, "trace_deposit_sources", {
|
|
1272
|
+
title: "Trace Deposit Sources",
|
|
1273
|
+
description: KNOWN_PUBLIC_TOOL_DESCRIPTIONS.trace_deposit_sources,
|
|
1274
|
+
inputSchema: {
|
|
1275
|
+
network: z.string().min(1).describe(NETWORK_DESCRIPTION),
|
|
1276
|
+
deposit_addresses: z.union([z.string().min(1), z.array(z.string().min(1))]).describe("Comma-separated full suspected deposit/cashout addresses, or an array. Min 1, max 5."),
|
|
1277
|
+
max_hops: z.number().int().min(1).max(5).optional().describe("Maximum reverse traceback hops. Default 2."),
|
|
1278
|
+
case_id: z.string().optional().describe("Optional Chain Insights case ID. When provided, compact evidence is appended to the case manifest."),
|
|
1279
|
+
include_attachments: z.boolean().optional().describe("Include graph app report metadata")
|
|
1280
|
+
},
|
|
1281
|
+
_meta: { ui: { resourceUri: GRAPH_RESOURCE_URI } },
|
|
1282
|
+
annotations: {
|
|
1283
|
+
readOnlyHint: false,
|
|
1284
|
+
destructiveHint: false,
|
|
1285
|
+
idempotentHint: false,
|
|
1286
|
+
openWorldHint: true
|
|
1287
|
+
}
|
|
1288
|
+
}, async ({ deposit_addresses, network, max_hops, case_id }) => {
|
|
1289
|
+
try {
|
|
1290
|
+
if (!remoteConnected) return {
|
|
1291
|
+
content: [{
|
|
1292
|
+
type: "text",
|
|
1293
|
+
text: `${remoteUnavailableMessage ?? `Graph MCP is not connected at ${graphMcpEndpoint}`}. Restart the Chain Insights MCP proxy after the endpoint is reachable.`
|
|
1294
|
+
}],
|
|
1295
|
+
isError: true
|
|
1296
|
+
};
|
|
1297
|
+
const { traceDepositSources } = await import("./public-tools-wJoAFDFa.mjs");
|
|
1298
|
+
const { writeGraphReport } = await import("./graph-reports-BDELxmpi.mjs");
|
|
1299
|
+
const { ensureArtifactServer } = await import("./artifact-server-CP6LXQ9d.mjs");
|
|
1300
|
+
const result = await traceDepositSources(remoteClient, config, {
|
|
1301
|
+
depositAddresses: deposit_addresses,
|
|
1302
|
+
network,
|
|
1303
|
+
maxHops: max_hops,
|
|
1304
|
+
caseId: case_id
|
|
1305
|
+
});
|
|
1306
|
+
const report = await writeGraphReport(result.graphData, {
|
|
1307
|
+
serverPort: config.serverPort,
|
|
1308
|
+
slug: `trace-deposit-sources-${network}`
|
|
1173
1309
|
});
|
|
1174
1310
|
await ensureArtifactServer(config.serverPort);
|
|
1175
1311
|
return {
|
|
@@ -1195,7 +1331,7 @@ async function createProxy() {
|
|
|
1195
1331
|
return {
|
|
1196
1332
|
content: [{
|
|
1197
1333
|
type: "text",
|
|
1198
|
-
text: `
|
|
1334
|
+
text: `Trace deposit sources failed: ${err.message}`
|
|
1199
1335
|
}],
|
|
1200
1336
|
isError: true
|
|
1201
1337
|
};
|
|
@@ -1233,7 +1369,7 @@ async function createProxy() {
|
|
|
1233
1369
|
}],
|
|
1234
1370
|
isError: true
|
|
1235
1371
|
};
|
|
1236
|
-
const { stakeInsights } = await import("./public-tools-
|
|
1372
|
+
const { stakeInsights } = await import("./public-tools-wJoAFDFa.mjs");
|
|
1237
1373
|
const { writeGraphReport } = await import("./graph-reports-BDELxmpi.mjs");
|
|
1238
1374
|
const { ensureArtifactServer } = await import("./artifact-server-CP6LXQ9d.mjs");
|
|
1239
1375
|
const result = await stakeInsights(remoteClient, {
|
|
@@ -1298,8 +1434,9 @@ async function createProxy() {
|
|
|
1298
1434
|
"- network_capabilities: inspect supported networks, data layers, tool availability, retention windows, and freshness.",
|
|
1299
1435
|
"- address_risk: screen a full address for AML risk, behavior, neighborhood, exchange exposure, and optional compare_address connection checks.",
|
|
1300
1436
|
"- stake_insights: explain Bittensor staking around one address, coldkey, or hotkey with net stake, movement amounts, counterparties, backend, and query evidence.",
|
|
1301
|
-
"-
|
|
1302
|
-
"-
|
|
1437
|
+
"- trace_victim_funds: trace up to five victim/source addresses forward to exchange deposit candidates.",
|
|
1438
|
+
"- trace_deposit_sources: trace backward from suspected deposit/cashout addresses to upstream funders and shared-source convergence.",
|
|
1439
|
+
"- trace_suspect_funds: trace up to five suspected scammer, mule, operator, or laundering-ring addresses forward to cashout topology.",
|
|
1303
1440
|
"- graph_query: run read-only GQL/Cypher through the universal graph endpoint. Use USE live_topology, USE archive_topology, or USE facts.",
|
|
1304
1441
|
"- graph_query_batch: run related read-only graph-language queries through one paid graph call.",
|
|
1305
1442
|
"",
|
|
@@ -1309,6 +1446,7 @@ async function createProxy() {
|
|
|
1309
1446
|
"- case_resume: load case context, evidence count, dossiers, and latest session.",
|
|
1310
1447
|
"- case_add_evidence: append a report or note to the case evidence manifest.",
|
|
1311
1448
|
"- case_verify_evidence: verify saved evidence integrity.",
|
|
1449
|
+
"- case_export: export a case for Obsidian, LLMWiki, Codex, Claude Code, and ChatGPT.",
|
|
1312
1450
|
"- case_update_dossier: add a finding to an address/entity dossier.",
|
|
1313
1451
|
"- case_start_session and case_end_session: record session notes.",
|
|
1314
1452
|
"",
|