Разработка системы sanctions screening

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы sanctions screening
Средняя
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы sanctions screening

Sanctions screening — проверка клиентов и контрагентов против официальных санкционных списков. Для крипто-бизнеса это двухуровневая задача: скрининг личности клиента (OFAC SDN, EU sanctions, UN) и скрининг crypto адресов (OFAC SDN Crypto Addresses, Chainalysis Sanctioned).

Источники санкционных данных

OFAC SDN List (США): самый важный. Обновляется несколько раз в неделю. XML формат включает crypto адреса с 2018 года (Tornado Cash, OFAC designates). Доступен бесплатно: https://www.treasury.gov/ofac/downloads/SDN_advanced.xml.

EU Consolidated Sanctions: https://webgate.ec.europa.eu/fsd/fsf. Охватывает все EU санкции.

UN Security Council: https://scsanctions.un.org/.

UK OFSI: Post-Brexit UK sanctions список.

ComplyAdvantage / Refinitiv: коммерческие провайдеры которые агрегируют все списки + enrichment.

Парсинг OFAC SDN для crypto адресов

import { parseStringPromise } from "xml2js";
import axios from "axios";

interface SanctionedCryptoAddress {
  address: string;
  currency: string; // XBT, ETH, USDT, etc.
  entityName: string;
  programTags: string[];
}

async function fetchOFACCryptoAddresses(): Promise<SanctionedCryptoAddress[]> {
  const response = await axios.get(
    "https://www.treasury.gov/ofac/downloads/SDN_advanced.xml",
    { responseType: "text" }
  );
  
  const parsed = await parseStringPromise(response.data);
  const sdnEntries = parsed.sdnList.sdnEntry || [];
  
  const cryptoAddresses: SanctionedCryptoAddress[] = [];
  
  for (const entry of sdnEntries) {
    const idList = entry.idList?.[0]?.id || [];
    
    for (const id of idList) {
      const idType = id.idType?.[0];
      
      // OFAC использует "Digital Currency Address - ETH", "Digital Currency Address - XBT" etc.
      if (idType?.includes("Digital Currency Address")) {
        const currency = idType.split(" - ")[1];
        cryptoAddresses.push({
          address: id.idNumber?.[0]?.toLowerCase(),
          currency,
          entityName: `${entry.lastName?.[0]} ${entry.firstName?.[0] || ""}`.trim(),
          programTags: (entry.programList?.[0]?.program || []),
        });
      }
    }
  }
  
  return cryptoAddresses;
}

Система скрининга

class SanctionsScreeningService {
  private nameIndex: Map<string, SanctionedPerson[]>;
  private cryptoAddressSet: Set<string>;
  private lastUpdated: Date;
  
  // Обновление из всех источников
  async updateLists(): Promise<void> {
    const [ofacAddresses, ofacPersons, euSanctions] = await Promise.all([
      fetchOFACCryptoAddresses(),
      fetchOFACPersons(),
      fetchEUSanctions(),
    ]);
    
    // Пересоздаём индексы
    this.cryptoAddressSet = new Set(ofacAddresses.map(a => a.address.toLowerCase()));
    
    // Fuzzy name index для person screening
    this.nameIndex = buildNameIndex([...ofacPersons, ...euSanctions]);
    
    this.lastUpdated = new Date();
    await this.cache.set("sanctions_last_updated", this.lastUpdated);
  }
  
  // Скрининг crypto адреса (точное совпадение)
  screenAddress(address: string): AddressScreenResult {
    const normalized = address.toLowerCase();
    if (this.cryptoAddressSet.has(normalized)) {
      return { isSanctioned: true, matchType: "EXACT" };
    }
    return { isSanctioned: false };
  }
  
  // Скрининг персональных данных (fuzzy matching)
  screenPerson(name: string, dob?: string, country?: string): PersonScreenResult {
    const candidates = this.nameIndex.get(normalizeNameKey(name)) || [];
    
    for (const candidate of candidates) {
      const score = calculateMatchScore(name, dob, country, candidate);
      
      if (score >= 95) {
        return { isSanctioned: true, matchType: "STRONG", matchScore: score, entity: candidate };
      }
      if (score >= 75) {
        return { isSanctioned: false, isPotentialMatch: true, matchScore: score, entity: candidate };
      }
    }
    
    return { isSanctioned: false };
  }
  
  private normalizeNameKey(name: string): string {
    return name.toLowerCase()
      .replace(/[^a-z\s]/g, "")
      .split(" ")
      .sort()
      .join(" ");
  }
}

Fuzzy Matching для имён

Имена переводятся между алфавитами (Александр = Alexander = Alexandre), меняются (maiden name), пишутся с ошибками. Точное строковое совпадение даёт много ложноотрицательных:

import Fuse from "fuse.js";
import { transliterate } from "transliteration";

function calculateMatchScore(
  inputName: string,
  inputDob: string | undefined,
  inputCountry: string | undefined,
  candidate: SanctionedPerson
): number {
  // Транслитерация (Іванов → Ivanov)
  const normalizedInput = transliterate(inputName.toLowerCase());
  const normalizedCandidate = transliterate(candidate.name.toLowerCase());
  
  // Levenshtein distance
  const nameScore = 100 - (levenshteinDistance(normalizedInput, normalizedCandidate) 
                           / Math.max(normalizedInput.length, normalizedCandidate.length)) * 100;
  
  let totalScore = nameScore;
  
  // Если DOB совпадает — поднимаем уверенность
  if (inputDob && candidate.dob) {
    if (inputDob === candidate.dob) totalScore = Math.min(100, totalScore + 20);
    else totalScore = Math.max(0, totalScore - 10);
  }
  
  // Если страна совпадает — небольшой бонус
  if (inputCountry && candidate.countries?.includes(inputCountry)) {
    totalScore = Math.min(100, totalScore + 5);
  }
  
  return totalScore;
}

Continuous monitoring

Санкционные списки обновляются неожиданно (экстренные designations). Нужен cron job для синхронизации:

// Обновление каждые 2 часа
@Cron("0 */2 * * *")
async syncSanctionsList() {
  await this.sanctionsService.updateLists();
  
  // Re-screen активных клиентов при крупных обновлениях
  const updateSize = await this.detectSignificantUpdate();
  if (updateSize > 10) {
    await this.rescreenActiveCustomers();
  }
}

Разработка sanctions screening системы с OFAC/EU parsing, fuzzy name matching и continuous monitoring — 2-3 недели.