No platform wants a malicious wallet walking in the front door. ChainHound reads a wallet's real on-chain history and hands back a risk score — you decide the threshold. "Reject anything above 40" is a one-line check against a response field, not a research project.
Two verification depths, same riskScore / riskLabel shape both times — pick the one that matches how much you need to trust the answer. Both skip ENS name registration (a sequential on-chain write, and the slowest part of the pipeline) — a gate needs a fast verdict, not a pretty name. The web app at chain-hound-nu.vercel.app still registers every wallet as a real ENSv2 subname; these two endpoints just don't wait on it.
Single-wallet verification. Risk-scores one address from its own transaction history alone — Uniswap V3 swaps, Aave V3 lending, on-chain fund flow. No counterparties followed. Use this when you just need "is this one address clean?" before an approval, deposit, or sign-up.
{
"walletAddress": "0x8f3a91c2b7e4a1f0d9c6b5e3a2f1d0c9b8a7e6f5"
}{
"walletAddress": "0x8f3a...",
"chain": "mainnet",
"swaps": { "...": "raw Uniswap V3 result" },
"lending": { "...": "raw Aave V3 result" },
"fundFlow": { "sent": [...], "received": [...] },
"riskAnalysis": {
"riskScore": 62,
"riskLabel": "Medium",
"flags": [ { "severity": "Medium",
"title": "..." } ],
"positiveSignals": [ "..." ]
}
}If scoring fails, riskAnalysis is { error: string } instead — no riskScore. Check for .error first.
Trail verification. Verifies the wallet plus its most recent outgoing counterparties (one hop out), risk-scores each independently, and rolls the results into one deterministic pass/fail trail verdict — the trail is only as clean as its riskiest link. Use this for higher-stakes gates (large withdrawals, custody onboarding) where a clean wallet fed by a dirty one still shouldn't pass.
{
"walletAddress": "0x8f3a91c2b7e4a1f0d9c6b5e3a2f1d0c9b8a7e6f5"
}{
"rootWallet": "0x8f3a...",
"nodes": [
{ "wallet": "0x8f3a...", "depth": 0,
"riskScore": 62, "riskLabel": "Medium",
"isSink": false },
{ "wallet": "0x1a2b...", "depth": 1,
"riskScore": 88, "riskLabel": "High",
"isSink": false }
],
"edges": [ { "from": "0x8f3a...", "to": "0x1a2b...",
"amount": "1.4", "token": "ETH", "timestamp": 1234 } ],
"stats": { "totalNodesAnalyzed": 2, "totalEdgesFound": 1 },
"overallRisk": { "score": 88, "label": "High",
"passed": false, "scoredNodes": 2, "totalNodes": 2,
"reason": "Highest risk in this trail comes from
a wallet 1 hop out (0x1a2b...): High (88/100)" }
}If scoredNodes is 0, overallRisk.score is absent and passed is false (fails closed) — nothing in the trail could be scored, not "the trail is clean."
Live Uniswap V3 subgraph lookup (via The Graph's Subgraph MCP) for one wallet's recent swap activity — the same lookup the two verification endpoints above run internally, exposed standalone so it can be chained as its own step (e.g. pull this as supporting evidence after a wallet is flagged).
{
"walletAddress": "0x8f3a91c2b7e4a1f0d9c6b5e3a2f1d0c9b8a7e6f5"
}{
"walletAddress": "0x8f3a...",
"chain": "mainnet",
"swaps": { "...": "raw Uniswap V3 subgraph result" }
}How a platform actually uses this — apply your own threshold to the score. Check for a failed/missing score first: riskScore > threshold silently evaluates tofalse when the score is missing, which fails open — exactly the wrong default for a gate meant to keep malicious wallets out.
const res = await fetch("https://chain-hound-production.up.railway.app/api/agent/wallet", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ walletAddress }),
});
const { riskAnalysis } = await res.json();
if ("error" in riskAnalysis || riskAnalysis.riskScore === undefined) {
// couldn't be scored — fail closed (flag for manual review), don't wave it through
} else if (riskAnalysis.riskScore > 40) {
// block, flag for review, or route to /api/agent/trace for a deeper check
}Full OpenAPI 3.0 spec: /openapi/chainhound.json — paste this URL into Bazantic's Spec URL field, or any OpenAPI-compatible client, to wire these up as callable tools.