KYC/AML System Development for Crypto Exchange

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
KYC/AML System Development for Crypto Exchange
Complex
~1-2 weeks
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Exchange KYC/AML System Development

KYC/AML for crypto exchange is not a form "enter your passport". It is a comprehensive system that solves regulatory licensing requirements, manages risks, and does not kill conversion rate. Most exchanges lose 40-60% of users at KYC funnel — this is an architecture and UX problem, not just regulation.

Tiered KYC Structure

Industry standard is multi-level verification:

Tier 0 (no KYC): platform browsing only. No transactions.

Tier 1 (Email + AML check): up to $1,000 USD equivalent per month. Crypto deposit and withdrawal only. Automatic wallet screening via Chainalysis or Elliptic on each deposit. Verification time: < 1 minute.

Tier 2 (Full KYC): up to $50,000/month. Passport + liveness check. Provider: Sumsub, Onfido, Jumio. Time: automatically 2-5 minutes, manual review up to 24 hours for complex cases. Unlocked: fiat deposit/withdrawal, unlimited crypto withdrawal.

Tier 3 (Enhanced Due Diligence): unlimited. Source of Funds + Source of Wealth + extended background check. VIP clients only, manual processing by compliance officer.

KYC Service Architecture

// KYC State Machine
enum KYCStatus {
  NOT_STARTED = 'not_started',
  PENDING = 'pending',
  UNDER_REVIEW = 'under_review',
  APPROVED = 'approved',
  REJECTED = 'rejected',
  RESUBMISSION_REQUIRED = 'resubmission_required',
}

interface UserKYCProfile {
  userId: string;
  tier: 0 | 1 | 2 | 3;
  status: KYCStatus;
  applicantId?: string;     // Sumsub applicant ID
  approvedAt?: Date;
  expiresAt?: Date;         // re-verification every N years
  riskScore: number;        // 0-100, calculated on approval
  pep: boolean;             // Politically Exposed Person
  sanctioned: boolean;
  walletRiskLevels: Map<string, WalletRisk>; // cached wallet assessments
}

class KYCService {
  async initiateKYC(userId: string, targetTier: number): Promise<{ accessToken: string }> {
    const accessToken = await this.sumsubClient.createApplicantToken(
      userId,
      this.getLevelName(targetTier)
    );
    
    await this.db.updateUserKYC(userId, { 
      status: KYCStatus.PENDING,
      initiatedAt: new Date() 
    });
    
    return { accessToken };
  }
  
  async processWebhook(payload: SumsubWebhook): Promise<void> {
    const { applicantId, reviewResult, type } = payload;
    const userId = await this.db.getUserByApplicantId(applicantId);
    
    if (type === 'applicantReviewed' && reviewResult.reviewAnswer === 'GREEN') {
      const riskData = await this.calculateRiskScore(applicantId);
      
      await this.db.updateUserKYC(userId, {
        status: KYCStatus.APPROVED,
        tier: 2,
        approvedAt: new Date(),
        expiresAt: new Date(Date.now() + 2 * 365 * 24 * 60 * 60 * 1000), // 2 years
        riskScore: riskData.score,
        pep: riskData.pep,
      });
      
      await this.notifyUser(userId, 'kyc_approved');
      await this.unlockFeatures(userId, 2);
      
    } else if (reviewResult.reviewAnswer === 'RED') {
      const reason = this.mapRejectionReason(reviewResult.reviewRejectType);
      await this.handleRejection(userId, reason);
    }
  }
  
  private async calculateRiskScore(applicantId: string): Promise<RiskData> {
    const applicantData = await this.sumsubClient.getApplicant(applicantId);
    
    // PEP check
    const pepResult = await this.amlProvider.checkPEP(
      applicantData.info.firstName,
      applicantData.info.lastName,
      applicantData.info.dob,
      applicantData.info.country
    );
    
    // Sanctions check (OFAC, EU, UN)
    const sanctionsResult = await this.amlProvider.checkSanctions(applicantData.info);
    
    // Risk scoring
    let score = 0;
    if (pepResult.isPEP) score += 40;
    if (sanctionsResult.isSanctioned) score = 100; // automatic reject
    if (HIGH_RISK_COUNTRIES.includes(applicantData.info.country)) score += 20;
    
    return { score, pep: pepResult.isPEP, sanctioned: sanctionsResult.isSanctioned };
  }
}

On-chain AML Screening

Every incoming deposit and every withdrawal is screened via wallet screening:

class WalletScreeningService {
  async screenDepositAddress(
    walletAddress: string,
    asset: string,
    amount: number,
    userId: string
  ): Promise<ScreeningResult> {
    
    // Cache: same address not re-screened every time
    const cached = await this.cache.get(`wallet:${walletAddress}`);
    if (cached && cached.age < 3600) return cached.result; // 1 hour TTL
    
    const [chainalysisResult, ellipticResult] = await Promise.all([
      this.chainalysis.getAddressRisk(walletAddress, asset),
      this.elliptic.getWalletRisk(walletAddress),
    ]);
    
    const riskScore = Math.max(chainalysisResult.riskScore, ellipticResult.riskScore);
    const categories = [...new Set([
      ...chainalysisResult.categories,
      ...ellipticResult.categories,
    ])];
    
    const result: ScreeningResult = {
      allowed: riskScore < 70 && !this.hasBlockedCategory(categories),
      riskScore,
      categories,
      requiresReview: riskScore >= 40 && riskScore < 70,
    };
    
    await this.cache.set(`wallet:${walletAddress}`, { result, age: Date.now() });
    await this.logScreening(userId, walletAddress, result);
    
    if (!result.allowed) {
      await this.alertComplianceTeam(userId, walletAddress, result);
    }
    
    return result;
  }
  
  private hasBlockedCategory(categories: string[]): boolean {
    const BLOCKED = ['darknet_market', 'ransomware', 'stolen_funds', 'sanctions'];
    return categories.some(c => BLOCKED.includes(c));
  }
}

Transaction Monitoring (TM)

Ongoing monitoring of transactions to identify suspicious patterns after verification:

Structuring detection: multiple transactions slightly below reporting threshold (classic smurfing).

Velocity monitoring: sharp increase in activity — 10x from normal volume per day.

Round-trip detection: funds are withdrawn and returned through several hops.

Mixing/tumbling indicators: transactions through known mixing services (Tornado Cash and similar).

class TransactionMonitor {
  async analyzeTransaction(tx: Transaction): Promise<AlertLevel> {
    const userHistory = await this.db.getUserTxHistory(tx.userId, 30); // 30 days
    
    const checks = await Promise.all([
      this.checkStructuring(tx, userHistory),
      this.checkVelocity(tx, userHistory),
      this.checkGeographicAnomalies(tx),
      this.checkTimePatterns(tx, userHistory),
    ]);
    
    const maxLevel = Math.max(...checks.map(c => c.level));
    
    if (maxLevel >= AlertLevel.HIGH) {
      await this.createSAR(tx, checks.filter(c => c.level >= AlertLevel.MEDIUM));
    }
    
    return maxLevel;
  }
  
  private async checkStructuring(tx: Transaction, history: Transaction[]): Promise<Check> {
    const threshold = await this.getReportingThreshold(tx.currency);
    
    // Multiple transactions within 24 hours totaling above threshold,
    // each individually below
    const last24h = history.filter(h => 
      Date.now() - h.timestamp < 86400000 && h.amount < threshold
    );
    const total24h = last24h.reduce((sum, h) => sum + h.amount, 0) + tx.amount;
    
    if (total24h >= threshold * 0.9 && last24h.length >= 3) {
      return { level: AlertLevel.HIGH, reason: 'structuring_detected' };
    }
    
    return { level: AlertLevel.NONE };
  }
}

SAR (Suspicious Activity Report) Automation

When alerts trigger — automatic draft SAR generation for compliance officer:

async function generateSARDraft(
  userId: string,
  transactions: Transaction[],
  alerts: Alert[]
): Promise<SARDocument> {
  const user = await getUserKYCData(userId);
  
  return {
    reportType: 'SUSPICIOUS_ACTIVITY',
    filingEntity: COMPANY_DETAILS,
    subject: {
      name: `${user.firstName} ${user.lastName}`,
      address: user.residenceAddress,
      dob: user.dateOfBirth,
      idNumber: user.documentNumber,
    },
    suspiciousActivity: {
      dateRange: { from: transactions[0].date, to: transactions[transactions.length - 1].date },
      totalAmount: transactions.reduce((sum, t) => sum + t.usdValue, 0),
      description: generateNarrative(alerts, transactions),
      alertTypes: alerts.map(a => a.type),
    },
    supportingTransactions: transactions.map(formatForSAR),
  };
}

Technical Stack

Component Technology
KYC provider Sumsub (primary) / Onfido (backup)
AML on-chain Chainalysis KYT + Elliptic
PEP/Sanctions Refinitiv World-Check or ComplyAdvantage
Transaction monitoring custom + Chainalysis Reactor
SAR management custom compliance module
Backend Node.js + TypeScript + PostgreSQL
Queue BullMQ (Redis) for async processing

Development Timeline

Component Timeline
KYC tier system + Sumsub integration 3-4 weeks
AML wallet screening 2 weeks
Transaction monitoring engine 3-4 weeks
SAR management system 2 weeks
Compliance dashboard 2-3 weeks
Testing + compliance review 2-3 weeks

Complete KYC/AML system for exchange: 3-4 months development.