chainhint-mcp 1.3.2 → 1.3.3
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/dist/evidence.d.ts +29 -0
- package/dist/evidence.js +88 -0
- package/dist/index.js +12 -3
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Address evidence wording — MIRROR of the main repo's
|
|
3
|
+
* backend/supabase/functions/_shared/evidence-labels.ts (migration 129).
|
|
4
|
+
* Change both or neither; test/evidence.test.mjs pins the strings.
|
|
5
|
+
*
|
|
6
|
+
* Rule (owner decision 2026-09-14): "Sanctioned" is printed only for a
|
|
7
|
+
* sanctions designation. A law-enforcement attribution, an issuer freeze, a
|
|
8
|
+
* national seizure order and an industry blocklist each say what they are and
|
|
9
|
+
* that they are not a sanctions designation.
|
|
10
|
+
*/
|
|
11
|
+
export declare const EVIDENCE_CLASSES: readonly ["sanctions_designation", "law_enforcement_attribution", "issuer_freeze", "national_seizure_order", "industry_blocklist", "analyst_verified"];
|
|
12
|
+
export type EvidenceClass = typeof EVIDENCE_CLASSES[number];
|
|
13
|
+
export interface EvidenceItem {
|
|
14
|
+
class: EvidenceClass | string;
|
|
15
|
+
authority: string;
|
|
16
|
+
subject?: string | null;
|
|
17
|
+
document_ref?: string | null;
|
|
18
|
+
document_url?: string | null;
|
|
19
|
+
document_date?: string | null;
|
|
20
|
+
legal_basis?: string | null;
|
|
21
|
+
token?: string | null;
|
|
22
|
+
chain?: string | null;
|
|
23
|
+
first_seen_at?: string | null;
|
|
24
|
+
}
|
|
25
|
+
export declare const NOT_A_SANCTIONS_DESIGNATION = "Not a sanctions designation.";
|
|
26
|
+
/** "⛔ Sanctioned — US OFAC · … " / "🧊 Frozen by issuer — Tether (USDT, TRON) · … Not a sanctions designation." */
|
|
27
|
+
export declare function evidenceText(e: EvidenceItem): string | null;
|
|
28
|
+
/** Designations first, then by strength of proof; unknown classes dropped. */
|
|
29
|
+
export declare function evidenceLines(items: ReadonlyArray<EvidenceItem> | null | undefined): string[];
|
package/dist/evidence.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Address evidence wording — MIRROR of the main repo's
|
|
3
|
+
* backend/supabase/functions/_shared/evidence-labels.ts (migration 129).
|
|
4
|
+
* Change both or neither; test/evidence.test.mjs pins the strings.
|
|
5
|
+
*
|
|
6
|
+
* Rule (owner decision 2026-09-14): "Sanctioned" is printed only for a
|
|
7
|
+
* sanctions designation. A law-enforcement attribution, an issuer freeze, a
|
|
8
|
+
* national seizure order and an industry blocklist each say what they are and
|
|
9
|
+
* that they are not a sanctions designation.
|
|
10
|
+
*/
|
|
11
|
+
export const EVIDENCE_CLASSES = [
|
|
12
|
+
"sanctions_designation",
|
|
13
|
+
"law_enforcement_attribution",
|
|
14
|
+
"issuer_freeze",
|
|
15
|
+
"national_seizure_order",
|
|
16
|
+
"industry_blocklist",
|
|
17
|
+
"analyst_verified",
|
|
18
|
+
];
|
|
19
|
+
export const NOT_A_SANCTIONS_DESIGNATION = "Not a sanctions designation.";
|
|
20
|
+
const TITLES = {
|
|
21
|
+
sanctions_designation: { icon: "⛔", title: "Sanctioned" },
|
|
22
|
+
law_enforcement_attribution: { icon: "🚩", title: "Law-enforcement attribution" },
|
|
23
|
+
issuer_freeze: { icon: "🧊", title: "Frozen by issuer" },
|
|
24
|
+
national_seizure_order: { icon: "🏛️", title: "National seizure order" },
|
|
25
|
+
industry_blocklist: { icon: "⚠️", title: "Industry blocklist" },
|
|
26
|
+
analyst_verified: { icon: "🔎", title: "Analyst-verified" },
|
|
27
|
+
};
|
|
28
|
+
const CHAIN_NAMES = { bsc: "BSC", tron: "TRON", ton: "TON" };
|
|
29
|
+
function isEvidenceClass(v) {
|
|
30
|
+
return typeof v === "string" && EVIDENCE_CLASSES.includes(v);
|
|
31
|
+
}
|
|
32
|
+
function chainName(chain) {
|
|
33
|
+
const c = (chain ?? "").trim().toLowerCase();
|
|
34
|
+
if (!c)
|
|
35
|
+
return "";
|
|
36
|
+
return CHAIN_NAMES[c] ?? c.charAt(0).toUpperCase() + c.slice(1);
|
|
37
|
+
}
|
|
38
|
+
function day(value) {
|
|
39
|
+
if (!value)
|
|
40
|
+
return null;
|
|
41
|
+
const m = /^(\d{4}-\d{2}-\d{2})/.exec(value.trim());
|
|
42
|
+
return m ? m[1] : null;
|
|
43
|
+
}
|
|
44
|
+
const join = (parts, sep) => parts.filter((p) => !!p && p.trim() !== "").join(sep);
|
|
45
|
+
function attributionLine(e) {
|
|
46
|
+
const date = day(e.document_date);
|
|
47
|
+
let head = join([e.authority, e.document_ref], ", ");
|
|
48
|
+
if (date)
|
|
49
|
+
head = `${head} (${date})`;
|
|
50
|
+
return e.subject ? `${head}: ${e.subject}` : head;
|
|
51
|
+
}
|
|
52
|
+
/** "⛔ Sanctioned — US OFAC · … " / "🧊 Frozen by issuer — Tether (USDT, TRON) · … Not a sanctions designation." */
|
|
53
|
+
export function evidenceText(e) {
|
|
54
|
+
if (!isEvidenceClass(e.class))
|
|
55
|
+
return null;
|
|
56
|
+
const { icon, title } = TITLES[e.class];
|
|
57
|
+
const date = day(e.document_date);
|
|
58
|
+
let line;
|
|
59
|
+
switch (e.class) {
|
|
60
|
+
case "sanctions_designation":
|
|
61
|
+
line = join([e.authority, e.document_ref ?? e.legal_basis, date, e.subject], " · ");
|
|
62
|
+
break;
|
|
63
|
+
case "issuer_freeze": {
|
|
64
|
+
const what = join([e.token, chainName(e.chain)], ", ");
|
|
65
|
+
const when = date ?? join(["first seen by ChainHint", day(e.first_seen_at)], " ");
|
|
66
|
+
line = join([what ? `${e.authority} (${what})` : e.authority, when], " · ");
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case "law_enforcement_attribution":
|
|
70
|
+
case "national_seizure_order":
|
|
71
|
+
line = attributionLine(e);
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
line = join([e.authority, e.subject, date ?? (e.first_seen_at ? `first seen by ChainHint ${day(e.first_seen_at)}` : null)], " · ");
|
|
75
|
+
}
|
|
76
|
+
const caveat = e.class === "sanctions_designation" ? null : NOT_A_SANCTIONS_DESIGNATION;
|
|
77
|
+
return `${icon} ${caveat ? `${title} — ${line}. ${caveat}` : `${title} — ${line}`}`;
|
|
78
|
+
}
|
|
79
|
+
/** Designations first, then by strength of proof; unknown classes dropped. */
|
|
80
|
+
export function evidenceLines(items) {
|
|
81
|
+
if (!items?.length)
|
|
82
|
+
return [];
|
|
83
|
+
const rank = (c) => EVIDENCE_CLASSES.indexOf(c);
|
|
84
|
+
return [...items]
|
|
85
|
+
.filter((e) => isEvidenceClass(e.class))
|
|
86
|
+
.sort((a, b) => rank(a.class) - rank(b.class))
|
|
87
|
+
.map((e) => `- ${evidenceText(e)}`);
|
|
88
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -18,12 +18,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
18
18
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import { apiErrorMessage, formatRiskLevel, lookupRiskLines, traceStatusLine } from "./verdicts.js";
|
|
21
|
+
import { evidenceLines } from "./evidence.js";
|
|
21
22
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
22
23
|
const API_KEY = process.env.CHAINHINT_API_KEY;
|
|
23
24
|
const BASE_URL = process.env.CHAINHINT_API_URL ?? "https://kjiwfwymnuzxriokhcjk.supabase.co/functions/v1";
|
|
24
25
|
const SUPABASE_URL = process.env.CHAINHINT_SUPABASE_URL ?? "https://kjiwfwymnuzxriokhcjk.supabase.co";
|
|
25
26
|
const SUPABASE_ANON_KEY = process.env.CHAINHINT_SUPABASE_ANON_KEY ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqaXdmd3ltbnV6eHJpb2toY2prIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI3MzkxODgsImV4cCI6MjA4ODMxNTE4OH0.VqzzF_jI8zF072cbjWEDbYo3PnMDlIPy621iWkXEqyo";
|
|
26
|
-
const VERSION = "1.3.
|
|
27
|
+
const VERSION = "1.3.3";
|
|
27
28
|
const USER_AGENT = `chainhint-mcp/${VERSION}`;
|
|
28
29
|
const FREE_CHECKS_PER_DAY = 3;
|
|
29
30
|
const UPGRADE_HINT = "set CHAINHINT_API_KEY (Agency plan, https://chainhint.com/pricing) for 10,000/day";
|
|
@@ -256,9 +257,14 @@ server.tool("check_wallet_risk", "Fast risk check for a crypto wallet address ag
|
|
|
256
257
|
else {
|
|
257
258
|
lines.push(`**Risk Score:** ${d.risk_score}/100 — **${(d.risk_level ?? formatRiskLevel(d.risk_score)).toUpperCase()}**`);
|
|
258
259
|
}
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
// sanctions.hit comes only from a sanctions designation (API since migration 129);
|
|
261
|
+
// the evidence lines say which authority and that other classes are not sanctions.
|
|
262
|
+
const walletEvidence = evidenceLines(d.evidence);
|
|
263
|
+
if (d.sanctions?.hit && !walletEvidence.some((l) => l.startsWith("- ⛔"))) {
|
|
264
|
+
lines.push(`⛔ **SANCTIONED**`);
|
|
261
265
|
}
|
|
266
|
+
if (walletEvidence.length)
|
|
267
|
+
lines.push(`**Evidence:**`, ...walletEvidence);
|
|
262
268
|
if (d.entity?.name) {
|
|
263
269
|
const sub = d.entity.subcategory ? ` / ${d.entity.subcategory}` : "";
|
|
264
270
|
const ver = d.entity.verified ? ", verified" : "";
|
|
@@ -328,6 +334,9 @@ server.tool("lookup_address", "Detailed lookup of a blockchain address: entity a
|
|
|
328
334
|
lines.push(`**Labels:** ${[...new Set(d.labels)].join(", ")}`);
|
|
329
335
|
// Never a score or "CLEAN" when there was nothing to score (verdicts.ts).
|
|
330
336
|
lines.push(...lookupRiskLines(d));
|
|
337
|
+
const lookupEvidence = evidenceLines(d.evidence);
|
|
338
|
+
if (lookupEvidence.length)
|
|
339
|
+
lines.push(`**Evidence:**`, ...lookupEvidence);
|
|
331
340
|
const sanctionIds = d.sanctions?.identifications ?? [];
|
|
332
341
|
if (sanctionIds.length) {
|
|
333
342
|
const names = sanctionIds.map((s) => [s.name, s.program].filter(Boolean).join(" / ")).filter(Boolean);
|