Разработка KYC/AML для крипто-обменника
KYC (Know Your Customer) и AML (Anti-Money Laundering) — регуляторные требования, которые становятся обязательными для крипто-обменников в большинстве юрисдикций. FATF (Financial Action Task Force) и ЕС требуют верификацию для транзакций выше определённых порогов. Без KYC/AML обменник рискует блокировкой платёжных провайдеров и юридическими проблемами.
Архитектура KYC/AML системы
Уровни верификации (KYC Tiers)
type KYCTier int
const (
TierUnverified KYCTier = 0 // без верификации
TierBasic KYCTier = 1 // email + телефон
TierStandard KYCTier = 2 // ID документ + selfie
TierEnhanced KYCTier = 3 // + источник средств
)
type TierLimits struct {
DailyLimitUSD decimal.Decimal
TxLimitUSD decimal.Decimal
RequiredKYC KYCTier
}
var DefaultLimits = map[KYCTier]TierLimits{
TierUnverified: {DailyLimitUSD: decimal.New(1000, 0), TxLimitUSD: decimal.New(500, 0)},
TierBasic: {DailyLimitUSD: decimal.New(3000, 0), TxLimitUSD: decimal.New(1500, 0)},
TierStandard: {DailyLimitUSD: decimal.New(15000, 0), TxLimitUSD: decimal.New(5000, 0)},
TierEnhanced: {DailyLimitUSD: decimal.New(100000, 0), TxLimitUSD: decimal.New(50000, 0)},
}
Интеграция с KYC провайдерами
Разрабатывать OCR документов и liveness detection самостоятельно — нецелесообразно. Используем KYC-as-a-Service:
Sumsub — популярный выбор для крипто. Проверка документов 80+ стран, liveness detection, база данных Watchlist:
import axios from 'axios';
import crypto from 'crypto';
class SumsubKYC {
private appToken: string;
private secretKey: string;
private signRequest(method: string, url: string, body?: string): string {
const ts = Math.floor(Date.now() / 1000).toString();
const payload = ts + method.toUpperCase() + url + (body || '');
const hmac = crypto.createHmac('sha256', this.secretKey).update(payload).digest('hex');
return `${ts}:${hmac}`;
}
async createApplicant(externalUserId: string, levelName: string = 'basic-kyc-level') {
const url = '/resources/applicants';
const body = JSON.stringify({ externalUserId, levelName });
const response = await axios.post(`https://api.sumsub.com${url}`, body, {
headers: {
'X-App-Token': this.appToken,
'X-App-Access-Sig': this.signRequest('POST', url, body),
'Content-Type': 'application/json',
},
});
return response.data; // applicantId
}
async generateAccessToken(applicantId: string): Promise<string> {
const url = `/resources/accessTokens?userId=${applicantId}`;
const response = await axios.post(`https://api.sumsub.com${url}`, '', {
headers: {
'X-App-Token': this.appToken,
'X-App-Access-Sig': this.signRequest('POST', url),
},
});
return response.data.token;
}
// Webhook от Sumsub при изменении статуса верификации
async handleWebhook(payload: SumsubWebhook) {
const { applicantId, reviewResult } = payload;
switch (reviewResult.reviewAnswer) {
case 'GREEN': // верифицирован
await this.updateUserKYCStatus(applicantId, 'verified');
break;
case 'RED': // отклонён
const reasons = reviewResult.rejectLabels;
await this.updateUserKYCStatus(applicantId, 'rejected', reasons);
break;
}
}
}
Frontend интеграция — Sumsub WebSDK встраивается в страницу верификации:
// React компонент верификации
import SumsubWebSdk from '@sumsub/websdk-react';
function KYCVerification({ userId }: { userId: string }) {
const [accessToken, setAccessToken] = useState<string | null>(null);
useEffect(() => {
api.getKYCToken(userId).then(setAccessToken);
}, [userId]);
if (!accessToken) return <Loading />;
return (
<SumsubWebSdk
accessToken={accessToken}
expirationHandler={() => api.getKYCToken(userId)}
onMessage={(type, payload) => {
if (type === 'idCheck.applicantReviewComplete') {
router.push('/kyc/pending');
}
}}
onError={(error) => console.error('KYC error:', error)}
/>
);
}
AML: мониторинг транзакций
Blockchain аналитика
Для крипто-транзакций нужно проверять происхождение средств. Отправка с миксера, darknet marketplace или sanctioned адреса — красный флаг.
Провайдеры blockchain analytics:
class ChainAnalysisAML {
async checkAddress(address: string, currency: string): Promise<RiskScore> {
const response = await this.client.post('/v2/users', {
userId: `check-${Date.now()}`,
currency,
address,
});
return {
risk: response.data.risk, // "low" | "medium" | "high" | "severe"
riskScore: response.data.riskScore, // 0-10
exposures: response.data.exposures, // [{category, value}]
flags: response.data.flags, // конкретные причины риска
};
}
}
// Пример интеграции в процесс обмена
async function checkIncomingDeposit(txHash: string, fromAddress: string, currency: string) {
const riskScore = await chainAnalysis.checkAddress(fromAddress, currency);
if (riskScore.risk === 'severe') {
await freezeTransaction(txHash, 'HIGH_RISK_ADDRESS');
await notifyCompliance(txHash, riskScore);
return false;
}
if (riskScore.risk === 'high') {
await flagForReview(txHash, riskScore);
// Продолжаем, но помечаем для ручной проверки
}
return true;
}
Elliptic и TRM Labs — альтернативы Chainalysis, с разными coverage и ценовой политикой.
Правила AML
type AMLRule struct {
Name string
Check func(tx Transaction, user User) AMLResult
Action AMLAction // ALLOW, FLAG, BLOCK
}
var AMLRules = []AMLRule{
{
Name: "structuring_detection",
Check: func(tx Transaction, user User) AMLResult {
// Подозрительно: несколько транзакций чуть ниже порога KYC
recentTxs := db.GetUserTransactions(user.ID, 24*time.Hour)
totalValue := sum(recentTxs)
// Несколько транзакций ~$990 подряд — попытка обойти KYC лимит
if len(recentTxs) > 3 && totalValue > KYCThreshold*0.8 {
return AMLResult{Risk: "high", Reason: "potential_structuring"}
}
return AMLResult{Risk: "low"}
},
Action: FLAG,
},
{
Name: "high_risk_country",
Check: func(tx Transaction, user User) AMLResult {
highRiskCountries := []string{"KP", "IR", "SY", "CU"}
if contains(highRiskCountries, user.Country) {
return AMLResult{Risk: "severe", Reason: "sanctioned_jurisdiction"}
}
return AMLResult{Risk: "low"}
},
Action: BLOCK,
},
{
Name: "pep_screening",
Check: func(tx Transaction, user User) AMLResult {
// PEP = Politically Exposed Person
isPEP := pepDatabase.CheckName(user.FullName, user.DateOfBirth)
if isPEP {
return AMLResult{Risk: "medium", Reason: "pep_detected"}
}
return AMLResult{Risk: "low"}
},
Action: FLAG,
},
}
Watchlist screening
Проверка по санкционным спискам (OFAC SDN, EU Consolidated List, UN Sanctions):
type WatchlistScreener struct {
ofacList []SanctionEntry
euList []SanctionEntry
updateFreq time.Duration
}
func (ws *WatchlistScreener) ScreenName(name, dob string) []SanctionMatch {
// Fuzzy matching — имена могут быть транслитерированы по-разному
var matches []SanctionMatch
for _, entry := range append(ws.ofacList, ws.euList...) {
similarity := fuzzy.MatchScore(name, entry.Name)
if similarity > 0.85 { // 85% схожесть
matches = append(matches, SanctionMatch{
Entry: entry,
Similarity: similarity,
ListSource: entry.ListSource,
})
}
}
return matches
}
SAR (Suspicious Activity Report)
При обнаружении подозрительной активности — обязательная подача SAR в регулятора (FinCEN в США, Росфинмониторинг в России):
type SAR struct {
UserID int64
TransactionID string
Amount decimal.Decimal
Currency string
SuspiciousType string // structuring, layering, unusual_pattern, etc.
Description string
SupportingDocs []string
FiledAt time.Time
}
// Автоматическая генерация черновика SAR при алерте
func (c *ComplianceEngine) GenerateSARDraft(alert AMLAlert) *SAR {
user := c.db.GetUser(alert.UserID)
txHistory := c.db.GetTransactionHistory(alert.UserID, 90*24*time.Hour)
return &SAR{
UserID: alert.UserID,
TransactionID: alert.TxID,
SuspiciousType: alert.RuleTriggered,
Description: fmt.Sprintf(
"Пользователь %s (KYC: %s) совершил транзакцию на %s %s "+
"с адреса с высоким риском (score: %.1f). "+
"История транзакций за 90 дней: %d операций на сумму %s",
user.FullName, user.KYCTier, alert.Amount, alert.Currency,
alert.RiskScore, len(txHistory), totalVolume(txHistory),
),
}
}
Compliance Dashboard
Для команды compliance — отдельный интерфейс:
- Очередь на ручную проверку (high/medium risk транзакции)
- История всех AML алертов с детализацией
- Статистика: сколько транзакций заблокировано, сколько SAR подано
- Управление AML правилами и порогами
Сроки и интеграции
| Компонент | Срок |
|---|---|
| KYC тиры + Sumsub интеграция | 2–3 недели |
| AML правила + транзакционный мониторинг | 2–3 недели |
| Blockchain analytics (Chainalysis/Elliptic) | 1–2 недели |
| Watchlist screening | 1–2 недели |
| Compliance dashboard | 2–3 недели |
Полная KYC/AML система для обменника: 2–3 месяца. Требует юридической консультации по целевым юрисдикциям для настройки порогов и требований.







