DEV Community

PublicAML
PublicAML

Posted on Originally published at publicaml.org

Free KYT for Crypto Wallets — Check Address Risk Before You Send (JavaScript)

Free KYT for Crypto Wallets — Check Address Risk Before You Send (JavaScript)

Before a user hits Send, you usually know the destination address. That is the cheapest place to run KYT (Know Your Transaction) / AML screening: if the counterparty looks like a mixer hop, sanctioned cluster, or known scam sink, warn or block.

Most commercial AML/KYT APIs need contracts, keys, and paid credits. You can ship a working pre-send check with a free public endpoint and ~20 lines of JavaScript.

What you will build

A tiny helper that:

  1. Takes a wallet address + chain (ETH, BTC, …)
  2. Calls POST https://intelapi.publicaml.org/v1/enrich
  3. Returns an aml_score (0–100) plus labels useful for UX

No API key. Rate limit on the free tier is roughly 1k requests/hour — enough for wallets, bots, and demos.

Copy-paste example

/**
 * Free KYT / AML address enrichment (no API key).
 * Docs / product: https://publicaml.org/
 */
export async function checkAddressRisk(walletAddress, chain = "ETH") {
  const res = await fetch("https://intelapi.publicaml.org/v1/enrich", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      addresses: [{ wallet_address: walletAddress, chain }],
    }),
  });

  if (!res.ok) {
    throw new Error(`PublicAML enrich failed: ${res.status}`);
  }

  const { entities } = await res.json();
  const entity = entities?.[0];
  if (!entity) throw new Error("Empty enrich response");

  return {
    address: entity.wallet_address,
    chain: entity.chain,
    amlScore: entity.aml_score, // 0–100
    label: entity.label, // e.g. "Bitfinex"
    category: entity.category, // e.g. "cex"
    breakdown: entity.aml_score_breakdown,
    signals: entity.behavioral_signals,
  };
}

export function riskLevel(score) {
  if (score >= 70) return "high";
  if (score >= 40) return "medium";
  return "low";
}

// Example
const risk = await checkAddressRisk(
  "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  "ETH"
);
console.log(risk.amlScore, risk.label, riskLevel(risk.amlScore));
Enter fullscreen mode Exit fullscreen mode

How to use the score in a wallet UX

Score Suggested UX
0–39 Allow (optional soft info if category is interesting)
40–69 Warning modal: show label / top risk categories
70–100 Hard block or require typed confirmation

Important nuance: a known CEX deposit address can show infrastructure floors in the breakdown while the surfaced aml_score stays low. Always display label + category next to the number so users understand why.

async function beforeSend(to, chain) {
  const r = await checkAddressRisk(to, chain);
  const level = riskLevel(r.amlScore);

  if (level === "high") {
    throw new Error(
      `Blocked: high KYT risk (${r.amlScore}). Label: ${r.label ?? "unknown"}`
    );
  }

  if (level === "medium") {
    // showConfirm(...) in your UI
    console.warn("Medium risk destination", r);
  }

  return r;
}
Enter fullscreen mode Exit fullscreen mode

KYT vs KYC vs AML (30-second version)

  • KYC — who the user is (identity docs).
  • AML — policies + monitoring to prevent money laundering.
  • KYT — risk of this payment path (address / hops / exposure) right before or after a transfer.

Wallet and DeFi UIs mostly need KYT on the destination (and sometimes the user’s own funding sources). This API is address enrichment — perfect for pre-send gates.

Try it from the shell

curl -sS -X POST 'https://intelapi.publicaml.org/v1/enrich' \
  -H 'Content-Type: application/json' \
  -d '{"addresses":[{"wallet_address":"0x742d35Cc6634C0532925a3b844Bc454e4438f44e","chain":"ETH"}]}' \
  | jq '.entities[0] | {aml_score, label, category, chain}'
Enter fullscreen mode Exit fullscreen mode

Where to go next

Free forever for this use case — if you ship a wallet or OTC desk, there is no excuse not to screen the counterparty before broadcast.

Top comments (0)