chainhint-mcp 1.2.1 → 1.3.1

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 CHANGED
@@ -86,6 +86,8 @@ Add an Agency key to the server entry (any client):
86
86
  Free tier: 2 of 3 checks left today — set CHAINHINT_API_KEY (Agency plan, https://chainhint.com/pricing) for 10,000/day.
87
87
  ```
88
88
 
89
+ When the address is in ChainHint's agent-infrastructure registry (agent-token launchpads, deployer factories, routers, payment facilitators, known agent wallets), the report adds an `🤖` line, e.g. *Known agent infrastructure: Virtuals launchpad* — a registry fact you can verify at its source, not a behavioural classification and not part of the risk score.
90
+
89
91
  ## Development (no build step)
90
92
 
91
93
  ```bash
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ const API_KEY = process.env.CHAINHINT_API_KEY;
22
22
  const BASE_URL = process.env.CHAINHINT_API_URL ?? "https://kjiwfwymnuzxriokhcjk.supabase.co/functions/v1";
23
23
  const SUPABASE_URL = process.env.CHAINHINT_SUPABASE_URL ?? "https://kjiwfwymnuzxriokhcjk.supabase.co";
24
24
  const SUPABASE_ANON_KEY = process.env.CHAINHINT_SUPABASE_ANON_KEY ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqaXdmd3ltbnV6eHJpb2toY2prIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI3MzkxODgsImV4cCI6MjA4ODMxNTE4OH0.VqzzF_jI8zF072cbjWEDbYo3PnMDlIPy621iWkXEqyo";
25
- const VERSION = "1.1.0";
25
+ const VERSION = "1.3.1";
26
26
  const USER_AGENT = `chainhint-mcp/${VERSION}`;
27
27
  const FREE_CHECKS_PER_DAY = 3;
28
28
  const UPGRADE_HINT = "set CHAINHINT_API_KEY (Agency plan, https://chainhint.com/pricing) for 10,000/day";
@@ -196,11 +196,29 @@ function formatExposure(label, buckets) {
196
196
  }),
197
197
  ];
198
198
  }
199
+ /**
200
+ * Same rule as chainhint.com (src/lib/attackerAttribution.ts): an address is
201
+ * "the attacker" only when the attribution was checked against the exploit
202
+ * transaction (attacker_address_source = analyst_verified). Everything else is
203
+ * a report — shown as one, and never as a HIGH-risk verdict.
204
+ */
205
+ function attackerAttribution(inc) {
206
+ if (!inc.attacker_address)
207
+ return { verified: false, label: "Attacker not identified" };
208
+ if (inc.attacker_address_source === "analyst_verified")
209
+ return { verified: true, label: "Attacker" };
210
+ return {
211
+ verified: false,
212
+ label: inc.source === "defillama"
213
+ ? "Reported attacker (DeFiLlama), unverified"
214
+ : "Reported attacker (user-provided), unverified",
215
+ };
216
+ }
199
217
  /** Public incident whose attacker_address matches, or null (errors swallowed — best effort). */
200
218
  async function findPublicIncidentByAttacker(address) {
201
219
  try {
202
220
  const rows = await supabaseGet("public_incidents_view", {
203
- select: "id,title,chain,amount_usd,estimated_loss_usd,risk_score",
221
+ select: "id,title,chain,amount_usd,estimated_loss_usd,risk_score,source,attacker_address_source",
204
222
  attacker_address: `eq.${normalizeAddr(address)}`,
205
223
  limit: "1",
206
224
  });
@@ -241,7 +259,11 @@ server.tool("check_wallet_risk", "Fast risk check for a crypto wallet address ag
241
259
  lines.push(`**Risk:** âšĒ NO DATA — address is not in ChainHint's labeled database. This is not evidence it is clean.`);
242
260
  const inc = await findPublicIncidentByAttacker(d.address);
243
261
  if (inc) {
244
- lines.push(`âš ī¸ **Known attacker in a public hack incident:** ${inc.title ?? "Unnamed incident"} (${inc.chain}, loss ${usd(inc.amount_usd ?? inc.estimated_loss_usd)}${inc.risk_score != null ? `, incident risk ${inc.risk_score}/100` : ""}). Treat as HIGH risk.`, `🔗 https://chainhint.com/incident/${inc.id}`);
262
+ const attribution = attackerAttribution({ attacker_address: d.address, ...inc });
263
+ const where = `${inc.title ?? "Unnamed incident"} (${inc.chain}, loss ${usd(inc.amount_usd ?? inc.estimated_loss_usd)}${inc.risk_score != null ? `, incident risk ${inc.risk_score}/100` : ""})`;
264
+ lines.push(attribution.verified
265
+ ? `âš ī¸ **Verified attacker in a public hack incident:** ${where}. The attribution was checked against the exploit transaction. Treat as HIGH risk.`
266
+ : `â„šī¸ **${attribution.label}, in a public hack incident:** ${where}. This attribution has not been verified against the exploit transaction — treat it as a lead to check, not as a risk verdict.`, `🔗 https://chainhint.com/incident/${inc.id}`);
245
267
  }
246
268
  lines.push(`Use lookup_address for an on-chain assessment (risk factors, GoPlus flags, counterparty exposure).`);
247
269
  }
@@ -264,6 +286,12 @@ server.tool("check_wallet_risk", "Fast risk check for a crypto wallet address ag
264
286
  }
265
287
  if (d.labels?.length)
266
288
  lines.push(`**Labels:** ${[...new Set(d.labels)].join(", ")}`);
289
+ if (d.agent) {
290
+ // Registry fact (agent launchpad / factory / facilitator / wallet), not
291
+ // a behavioural classification — mirrors the API's honest wording.
292
+ const cap = d.agent.summary.charAt(0).toUpperCase() + d.agent.summary.slice(1);
293
+ lines.push(`🤖 **${cap}** — a registry fact, not a behavioural classification.`);
294
+ }
267
295
  if (d.is_contract != null)
268
296
  lines.push(`**Type:** ${d.is_contract ? "Smart Contract" : "EOA (wallet)"}`);
269
297
  lines.push(`**In ChainHint DB:** ${d.found_in_db ? "yes" : "no"}${d.sources?.length ? ` (sources: ${d.sources.join(", ")})` : ""}`);
@@ -306,6 +334,10 @@ server.tool("lookup_address", "Detailed lookup of a blockchain address: entity a
306
334
  else {
307
335
  lines.push(`**Entity:** Unknown / unlabeled`);
308
336
  }
337
+ if (d.agent) {
338
+ const cap = d.agent.summary.charAt(0).toUpperCase() + d.agent.summary.slice(1);
339
+ lines.push(`🤖 **${cap}** — a registry fact, not a behavioural classification.`);
340
+ }
309
341
  if (d.labels?.length)
310
342
  lines.push(`**Labels:** ${[...new Set(d.labels)].join(", ")}`);
311
343
  if (d.risk) {
@@ -365,7 +397,7 @@ server.tool("get_trace_status", "Fund-trace summary for a publicly tracked crypt
365
397
  // public_incidents_view is the canonical anonymous read path (SECURITY DEFINER,
366
398
  // exposes only is_public rows and only public-safe columns).
367
399
  const queryParams = {
368
- select: "id,title,status,chain,attacker_address,amount_usd,estimated_loss_usd,risk_score,incident_type,hack_date,display_date,created_at,updated_at,source,endpoints,flow_graph,counterparty_exposure",
400
+ select: "id,title,status,chain,attacker_address,attacker_address_source,amount_usd,estimated_loss_usd,risk_score,incident_type,hack_date,display_date,created_at,updated_at,source,endpoints,flow_graph,counterparty_exposure",
369
401
  limit: "1",
370
402
  order: "created_at.desc",
371
403
  };
@@ -396,7 +428,7 @@ server.tool("get_trace_status", "Fund-trace summary for a publicly tracked crypt
396
428
  `**Incident ID:** ${inc.id}`,
397
429
  `**Chain:** ${inc.chain}`,
398
430
  `**Status:** ${status.toUpperCase()}`,
399
- `**Attacker:** ${inc.attacker_address}`,
431
+ `**${attackerAttribution(inc).label}:** ${inc.attacker_address ?? "—"}`,
400
432
  `**Loss:** ${usd(lossUsd)}`,
401
433
  `**Date:** ${date.slice(0, 10)}`,
402
434
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainhint-mcp",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "ChainHint MCP server — crypto risk intelligence tools for Claude Desktop and Cursor",
5
5
  "license": "MIT",
6
6
  "repository": {