AML Screening Integration (Chainalysis, Elliptic, Crystal)
AML screening of cryptocurrency transactions is a mandatory compliance element for any platform working with crypto. Task: on each deposit or withdrawal, check if the address is connected to sanctioned entities, darknet markets, stolen funds, or other high-risk activities.
Chainalysis KYT (Know Your Transaction)
Chainalysis is the market leader, used by most major exchanges and regulators for forensics. API is divided into several products: KYT for transaction monitoring, Reactor for investigation, Kryptos for entity data.
KYT API Integration
class ChainalysisClient {
private readonly baseURL = "https://api.chainalysis.com";
async registerAddress(address: string, asset: "USDT" | "ETH" | "BTC" | string): Promise<void> {
await this.post("/api/kyt/v2/users", {
userId: address,
asset,
});
}
async screenTransfer(params: {
asset: string;
network: string;
transferReference: string; // txHash or address
direction: "received" | "sent";
userId: string;
outputAddress?: string;
value?: string;
assetAmount?: number;
timestamp?: string;
}): Promise<TransferRisk> {
const response = await this.post("/api/kyt/v2/transfers", params);
return {
externalId: response.externalId,
riskScore: response.riskScore,
cluster: response.cluster,
status: response.status, // "APPROVED" | "BLOCKED" | "IN_REVIEW"
};
}
// Get detailed alerts for transfer
async getTransferAlerts(externalId: string): Promise<Alert[]> {
const response = await this.get(`/api/kyt/v2/transfers/${externalId}/alerts`);
return response.alerts;
}
private async post(path: string, body: any): Promise<any> {
const response = await fetch(`${this.baseURL}${path}`, {
method: "POST",
headers: {
"Token": this.apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
return response.json();
}
}
Risk Score Categories
| Score | Category | Automatic Action |
|---|---|---|
| 0-39 | LOW | Skip |
| 40-69 | MEDIUM | Flag for review |
| 70-100 | HIGH | Block |
| N/A | SEVERE | Block + SAR |
Categories that trigger automatic block regardless of score:
-
darknet_market -
ransomware -
stolen_funds -
sanctions -
terrorist_financing
Deposit Processing
async function processDeposit(deposit: Deposit): Promise<DepositResult> {
// Register address if new
await chainalysis.registerAddress(deposit.fromAddress, deposit.asset);
// Screen transaction
const risk = await chainalysis.screenTransfer({
asset: deposit.asset,
network: deposit.network,
transferReference: deposit.txHash,
direction: "received",
userId: deposit.userId,
value: deposit.usdValue.toString(),
assetAmount: deposit.amount,
});
if (risk.status === "BLOCKED") {
await freezeDeposit(deposit.id);
await notifyCompliance(deposit, risk);
return { status: "blocked", reason: risk.cluster?.category };
}
if (risk.status === "IN_REVIEW") {
await holdForReview(deposit.id);
await createComplianceTask(deposit, risk);
return { status: "pending_review" };
}
await creditUserAccount(deposit);
return { status: "approved" };
}
Elliptic Lens / Navigator
Elliptic is a Chainalysis competitor with similar functionality. Stronger in DeFi screening and cross-asset tracing.
class EllipticClient {
async getWalletRisk(address: string, asset: string): Promise<EllipticRisk> {
const response = await this.post("/v2/wallet/synchronous", {
subject: {
asset,
type: "address",
hash: address,
},
type: "wallet_exposure",
customer_reference: address,
});
return {
riskScore: response.risk_score, // 0-10 (Elliptic uses different scale)
exposures: response.exposures, // breakdown by categories
clusters: response.entities,
};
}
async getTransactionRisk(txHash: string, asset: string): Promise<EllipticRisk> {
return this.post("/v2/txs/synchronous", {
subject: { asset, type: "transaction", hash: txHash },
type: "indirect_exposure",
});
}
}
Elliptic score is 0-10 — need normalization for unified risk logic if using both providers.
Crystal Blockchain (for Eastern Europe)
Crystal is a European player, often preferred for projects from CIS/EU due to prices and support.
const crystalResponse = await axios.post(
"https://aml.crystalblockchain.com/api/v1/risks/check",
{
address,
currency: asset,
},
{
headers: { "X-Auth-Apikey": CRYSTAL_API_KEY },
}
);
// crystal returns: risk_score (0-100), signals (list of signals)
Dual-Provider Strategy
For production: two providers reduce false negative risk. Logic: BLOCK if at least one blocks, REVIEW if at least one flags.
async function dualProviderScreen(address: string, txHash: string): Promise<RiskDecision> {
const [chainalysisResult, ellipticResult] = await Promise.allSettled([
chainalysis.screenTransfer({ transferReference: txHash, ... }),
elliptic.getWalletRisk(address, asset),
]);
const c = chainalysisResult.status === "fulfilled" ? chainalysisResult.value : null;
const e = ellipticResult.status === "fulfilled" ? ellipticResult.value : null;
// If one provider unavailable — use the other
if (!c && !e) throw new Error("Both AML providers unavailable");
const maxScore = Math.max(
c?.riskScore ?? 0,
e ? e.riskScore * 10 : 0, // normalize Elliptic 0-10 → 0-100
);
if (maxScore >= 70 || c?.status === "BLOCKED") return { decision: "BLOCK", score: maxScore };
if (maxScore >= 40) return { decision: "REVIEW", score: maxScore };
return { decision: "ALLOW", score: maxScore };
}
Integration of one AML provider (API + blocking logic + compliance dashboard) — 1-2 weeks. Dual-provider system with automatic failover — 2-3 weeks.







