Розробка системи лімітів та верифікації обмінника
При обміні криптовалют на великі суми без належної верифікації ви ризикуєте зіткнутися з блокуваннями від платіжних партнерів та регуляторів. Наприклад, одна з наших клієнток втратила $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-протоколи. Гарантуємо підтримку після впровадження.
Зв'яжіться з нами для консультації — ми проаналізуємо ваш поточний стек і запропонуємо оптимальну конфігурацію. Замовте впровадження, і ваша система лімітів буде готова до будь-яких обсягів.







