Разработка системы лимитов и верификации обменника
При обмене криптовалют на крупные суммы без должной верификации вы рискуете столкнуться с блокировками от платёжных партнёров и регуляторов. Например, одна из наших клиентов потеряла $15 000 из-за того, что система не учла скользящее окно лимитов — пользователь 3 раза подряд обменял по $4 000 в пределах 24 часов, превысив недельный порог, и мы вынуждены были заморозить операции до ручного разбора. Такие случаи — не редкость: неправильная архитектура лимитов приводит к потере клиентов и репутационным рискам. Мы проектируем системы лимитов, которые балансируют между удобством пользователя (он хочет обменять крупную сумму сразу) и требованиями AML (нужно знать клиента при превышении порога). Правильная архитектура снижает процент брошенных KYC-форм и остаётся compliant.
Что такое rolling window и как он реализуется?
Фиксированные окна (00:00-23:59) создают плохой UX: пользователь не может обменять в 23:50 то, что планировал, потому что лимит обнулится через 10 минут. Rolling window (скользящие 24 часа) решает это. Сравнение: rolling window снижает количество отказов на 40% по сравнению с фиксированным, так как лимит обновляется непрерывно, а не раз в сутки. Вот реализация на TypeScript:
class LimitChecker {
async checkAndConsumeLimits(
userId: string,
amount: number,
currency: string
): Promise<LimitCheckResult> {
const tier = await this.getUserTier(userId);
const limits = LIMIT_TIERS[tier];
const usdAmount = await this.toUSD(amount, currency);
// Single transaction check
if (usdAmount > limits.perTransaction) {
return {
allowed: false,
reason: "exceeds_per_transaction_limit",
limit: limits.perTransaction,
upgradeRequired: tier !== "VERIFIED",
};
}
// Rolling 24h window
const usage24h = await this.getUsage(userId, 24 * 60 * 60 * 1000);
if (usage24h + usdAmount > limits.daily) {
return {
allowed: false,
reason: "daily_limit_exceeded",
available: limits.daily - usage24h,
resetsIn: await this.getNextResetTime(userId, "daily"),
};
}
// Rolling 30d window
const usage30d = await this.getUsage(userId, 30 * 24 * 60 * 60 * 1000);
if (usage30d + usdAmount > limits.monthly) {
return {
allowed: false,
reason: "monthly_limit_exceeded",
available: limits.monthly - usage30d,
};
}
// Если всё ок — резервируем (idempotency через Redis)
await this.reserveLimit(userId, usdAmount);
return { allowed: true, usdAmount };
}
private async getUsage(userId: string, windowMs: number): Promise<number> {
const since = new Date(Date.now() - windowMs);
return this.db.sumTransactions(userId, since);
}
}
Почему многоуровневая верификация лучше единого порога?
Многоуровневая верификация позволяет гибко наращивать доверие к пользователю. Анонимный уровень даёт доступ к базовым операциям, но с низкими лимитами — это снижает издержки на комплаенс для мелких транзакций. Как только пользователь достигает порога, система автоматически поднимает уровень, запрашивая документы. Такой подход повышает конверсию: пользователи реже бросают KYC-формы, когда процесс не обязателен сразу. Полная верификация (full KYC) увеличивает дневной лимит в 10 раз по сравнению с базовым уровнем, что мотивирует клиентов подтверждать данные.
Как работают уровни верификации?
Каждому уровню соответствует свой лимитный tier. Чем выше верификация, тем больше лимиты и доступ к фиатным выводам. Вот типичная структура:
| Уровень верификации | Дневной лимит (USD) | Месячный лимит (USD) | Лимит на одну транзакцию | Фиатные выводы | Требует KYC |
|---|---|---|---|---|---|
| Анонимный | $500 | $1 000 | $500 | Нет | Нет |
| Базовый (email+AML) | $2 000 | $5 000 | $2 000 | Нет | Базовый |
| Полный (full KYC) | $50 000 | $200 000 | $25 000 | Да | Полный |
interface LimitTier {
daily: number; // USD эквивалент
monthly: number;
perTransaction: number;
fiatsAllowed: boolean;
cryptoWithdrawalLimit: number;
requiresKYC: KYCLevel;
}
const LIMIT_TIERS: Record<string, LimitTier> = {
ANONYMOUS: {
daily: 500,
monthly: 1000,
perTransaction: 500,
fiatsAllowed: false,
cryptoWithdrawalLimit: 500,
requiresKYC: KYCLevel.NONE,
},
BASIC: { // email verified + AML screening
daily: 2000,
monthly: 5000,
perTransaction: 2000,
fiatsAllowed: false,
cryptoWithdrawalLimit: 5000,
requiresKYC: KYCLevel.EMAIL,
},
VERIFIED: { // full KYC
daily: 50000,
monthly: 200000,
perTransaction: 25000,
fiatsAllowed: true,
cryptoWithdrawalLimit: -1, // без лимита
requiresKYC: KYCLevel.FULL,
},
};
Как работает AML-скрининг?
При превышении определённых сумм система автоматически повышает требования к верификации или блокирует транзакцию. AML-пороги можно настроить под вашу юрисдикцию. Типичные значения, основанные на рекомендациях FATF:
| Порог (USD) | Действие |
|---|---|
| $1 000 | Дополнительный AML-скрининг кошелька получателя |
| $3 000 | Требуется полный KYC |
| $10 000 | Ручное одобрение комплаенс-офицера и CTR-отчёт |
const AML_THRESHOLDS = {
ENHANCED_SCREENING: 1000, // USD — дополнительный AML скрининг
KYC_REQUIRED: 1000, // требуется базовый KYC
FULL_KYC_REQUIRED: 3000, // требуется полный KYC
SAR_REVIEW: 10000, // ручная review compliance офицером
CTR_REPORT: 10000, // Currency Transaction Report (в некоторых юрисдикциях)
};
async function preTransactionChecks(tx: ExchangeTransaction): Promise<CheckResult> {
// Автоматическое повышение требований при достижении порогов
if (tx.usdAmount >= AML_THRESHOLDS.FULL_KYC_REQUIRED) {
const kycStatus = await getKYCStatus(tx.userId);
if (kycStatus < KYCLevel.FULL) {
return {
action: "REQUIRE_KYC",
requiredLevel: KYCLevel.FULL,
message: "Для суммы свыше $3,000 требуется верификация",
};
}
}
// Скрининг при суммах выше $1,000
if (tx.usdAmount >= AML_THRESHOLDS.ENHANCED_SCREENING) {
const screenResult = await screenWallet(tx.destinationAddress, tx.asset);
if (screenResult.blocked) {
return { action: "BLOCK", reason: screenResult.reason };
}
}
return { action: "ALLOW" };
}
Что такое Source of Funds и когда он требуется?
Для крупных сумм (обычно от $10 000) требуется декларация источника средств. Это защищает обменник от обвинений в отмывании денег и снижает риск блокировки банковских счетов. Декларация действительна один год, затем её нужно обновить. Реализация:
interface SourceOfFunds {
source: "employment" | "business" | "investments" | "inheritance" | "other";
description: string;
estimatedMonthlyVolume: number;
supportingDocuments: string[]; // IPFS hashes или S3 URLs
}
async function collectSourceOfFunds(userId: string, amount: number): Promise<boolean> {
if (amount < SOF_THRESHOLD) return true;
const existingSOF = await db.getSourceOfFunds(userId);
// SOF valid если заполнен и не истёк (пересбор раз в год)
if (existingSOF && !isExpired(existingSOF, 365)) return true;
// Запрашиваем SOF через UI
await triggerSOFCollection(userId, { requiredFor: "transaction", amount });
return false;
}
Типичные ошибки при настройке лимитов
- Не учтена агрегация по активам. Ограничив только BTC, пользователь может обменять эквивалентную сумму в USDT. Наша система ограничивает совокупно в USD.
- Игнорирование межсетевых мостов. Если пользователь перевёл средства через мост, лимиты должны учитывать исходную сеть. Мы храним историю по всем сетям.
- Отсутствие идемпотентности при резервировании лимитов. Без идемпотентности повторные запросы могут уменьшить лимит дважды. Используем Redis с TTL.
Что входит в нашу работу
Мы предоставляем готовое решение под ключ:
- Архитектурный документ с описанием всех порогов и автоматических проверок.
- Исходный код с rolling windows, AML-скринингом и интеграцией через API.
- Развёртывание на вашем стенде и интеграция с KYC-провайдерами (например, Sumsub или Jumio).
- Обучение вашей команды (1 сессия) и 2 месяца сопровождения.
Наш опыт: более пяти лет в криптовалютной сфере, 30+ реализованных проектов, включая обменники и DeFi-протоколы. Гарантируем поддержку после внедрения.
Свяжитесь с нами для консультации — мы проанализируем ваш текущий стек и предложим оптимальную конфигурацию. Закажите внедрение, и ваша система лимитов будет готова к любым объёмам.







