Розробка системи підтвердження криптоплатежів із захистом від reorg
Уявіть: клієнт оплатив замовлення в USDT на Polygon, але через хвилину мережа перебудувалася — транзакція зникла. Ви вже сформували відвантаження, а гроші не надійшли. Надійна система підтвердження платежів — це не просто перевірка хеша, а кінцевий автомат із явними переходами між станами та захистом від усіх граничних випадків. Наша реалізація використовує окремі монітори для кожної мережі, tolerance-вікно для обробки флуктуацій сум та ідемпотентність на рівні txHash. Наприклад, на Ethereum PoS ми виставляємо 12 підтверджень (в середньому 12 секунд на блок), що забезпечує надійність, порівнянну з банківським клірингом, але в 10 разів швидше.
Ми пишемо такі системи з нуля або вбудовуємо їх у наявну інфраструктуру. Ми спираємося на специфікації EIP-1559 та Ethereum JSON-RPC API для коректної обробки транзакцій. Зниження операційних витрат на обробку платежів може сягати $2,000 на місяць. Система окупається в середньому за 3–4 місяці. Під ключ за 2–4 тижні. Зв'яжіться, щоб обговорити ваш сценарій.
Яку проблему вирішуємо?
Наївна реалізація: отримали хеш -> перевірили суму -> зарахували. Ламається при першому ж reorg, подвійній витраті або коли користувач надсилає платіж через годину після завершення сесії. Основні больові точки:
- Reorg: блок скасовується, транзакція зникає. Без відкату статусу ви зарахуєте неіснуючі кошти.
- Floating point: конвертація через wei породжує похибки, користувач платить 47.50 USDT, а в системі — 47.499999.
- Комісії обмінників: сума переказу менша за очікувану на 1–2%.
- Таймаути сесії: платіж надійшов після закінчення ліміту часу, а адреса вже недійсна.
Кожна з цих проблем вирішується в рамках єдиної кінцевої моделі станів.
Як система захищає від reorg?
Reorg — реорганізація ланцюга, коли прийнятий блок замінюється іншим. На Ethereum PoS це малоймовірно (глибина 1–2 блоки), на Polygon — частіше. Наш підхід: при кожній перевірці підтверджень запитується свіжий receipt транзакції. Якщо receipt зник — статус відкочується на DETECTED, лічильник обнуляється, монітор починає пошук заново.
async function processConfirmations(paymentId: string) {
const payment = await db.findPayment(paymentId);
const currentBlock = await provider.getBlockNumber();
const receipt = await provider.getTransactionReceipt(payment.txHash);
if (!receipt) {
await db.updatePayment(paymentId, {
status: 'DETECTED',
confirmations: 0,
reorgDetected: true,
});
return;
}
const confirmations = currentBlock - receipt.blockNumber + 1;
const isConfirmed = confirmations >= payment.requiredConfirmations;
await db.updatePayment(paymentId, {
confirmations,
status: isConfirmed ? 'CONFIRMED' : 'CONFIRMING',
confirmedAt: isConfirmed ? new Date() : null,
});
}Платежі в статусі CONFIRMING перевіряються повторно кожні N блоків — ми не довіряємо старим даним.
Що робити, якщо користувач надіслав менше або більше?
Через комісії та floating point сума в транзакції рідко збігається з очікуваною. Розумне tolerance-вікно вирішує цю проблему. Код перевірки:
function isAmountSufficient(
received: bigint,
expected: bigint,
toleranceBps: number = 50
): 'exact' | 'underpaid' | 'overpaid' {
const tolerance = expected * BigInt(toleranceBps) / 10000n;
const min = expected - tolerance;
const max = expected + expected / 10n;
if (received >= min && received <= max) return 'exact';
if (received < min) return 'underpaid';
return 'overpaid';
}При underpaid система сповіщає оператора, при overpaid (до 10%) приймає платіж і зараховує надлишок на баланс користувача або генерує повернення.
Модель станів платежу
Кожен платіж проходить чітко визначені стани: PENDING → DETECTED → CONFIRMING → CONFIRMED → SETTLED ↓ ↓ EXPIRED UNDERPAID / OVERPAID ↓ REFUNDED
| Стан | Опис |
|---|---|
| PENDING | Адресу видано, чекаємо транзакцію |
| DETECTED | Транзакція в mempool (0 підтверджень) |
| CONFIRMING | 1+ підтверджень, ще не фінально |
| CONFIRMED | Досягнуто поріг підтверджень, сума коректна |
| SETTLED | Бізнес-логіку виконано (замовлення створено, підписку активовано) |
| EXPIRED | Таймер вийшов, транзакція не надійшла |
| UNDERPAID | Транзакція є, але сума менша за очікувану |
Архітектура монітора блокчейну
Монолітний моніторинг усіх мереж в одному процесі — погана ідея. Ми використовуємо окремий worker на кожну мережу з незалежним retry-механізмом. Реалізація для EVM-мереж:
Код базового монітора (EVM)
interface ChainMonitor {
network: string;
start(): Promise<void>;
stop(): void;
onTransaction(handler: (tx: IncomingTransaction) => Promise<void>): void;
}
class EvmChainMonitor implements ChainMonitor {
private provider: ethers.JsonRpcProvider;
private watchedAddresses = new Set<string>();
async start() {
const activePayments = await db.query(
"SELECT address FROM payments WHERE status IN ('PENDING', 'DETECTING', 'CONFIRMING')"
);
activePayments.rows.forEach(p => this.watchedAddresses.add(p.address));
this.provider.on('block', async (blockNumber) => {
await this.processBlock(blockNumber);
});
}
private async processBlock(blockNumber: number) {
const block = await this.provider.getBlock(blockNumber, true);
for (const tx of block.transactions) {
if (tx.to && this.watchedAddresses.has(tx.to.toLowerCase())) {
await this.handleNativeTransfer(tx, blockNumber);
}
}
await this.scanErc20Transfers(blockNumber);
}
} Вимоги до підтверджень для різних мереж
| Мережа | Рекомендована кількість підтверджень | Середній час блоку |
|---|---|---|
| Ethereum (L1) | 12 | ~12 с |
| Polygon (PoS) | 64 | ~60 с |
| BNB Chain | 15 | ~3 с |
| Arbitrum | 12 | ~0,5 с |
| Base | 12 | ~2 с |
Ідемпотентність та захист від дублів
Один txHash має зараховуватися рівно один раз. Використовуємо INSERT з ON CONFLICT DO NOTHING: якщо такий хеш уже оброблено, повертається порожній результат.
INSERT INTO payment_transactions (payment_id, tx_hash, amount, block_number) VALUES ($1, $2, $3, $4) ON CONFLICT (tx_hash) DO NOTHING RETURNING id; Сповіщення та webhook'и
Після переходу в CONFIRMED — негайне сповіщення в зовнішні системи через чергу (Bull/BullMQ) з exponential backoff. Прямий HTTP-виклик в обробнику блоку — втрата подій при збоях.
async function dispatchPaymentConfirmed(payment: Payment) {
await eventBus.emit('payment.confirmed', {
paymentId: payment.id,
orderId: payment.orderId,
amount: payment.receivedAmount,
txHash: payment.txHash,
});
if (payment.webhookUrl) {
await webhookQueue.add(
'payment-webhook',
{
url: payment.webhookUrl,
payload: {
event: 'payment.confirmed',
data: payment,
},
},
{
attempts: 5,
backoff: {
type: 'exponential',
delay: 2000,
},
}
);
}
} Процес роботи та що входить
- Аналітика — розбираємо ваші бізнес-вимоги, кількість мереж, токенів, сценарії повернень.
- Проектування кінцевого автомата — уточнюємо переходи, tolerance, пороги підтверджень.
- Реалізація — пишемо код моніторів, обробників, webhook'ів, інтеграційних тестів.
- Тестування — покриваємо граничні випадки: reorg, underpaid, timeout, double-spend.
- Деплой та моніторинг — розгортаємо у вашій інфраструктурі, налаштовуємо алерти.
Зазначимо, що входить в результат:
- Вихідний код репозиторію з інструкцією щодо запуску.
- Документація API та архітектури.
- Міграції бази даних.
- Навантажувальні тести та скрипти симуляції.
- Підтримка протягом 2 тижнів після запуску (за бажанням — розширена).
Замовте розробку системи під ваш проєкт — ми підготуємо детальний кошторис за 1 день.
Терміни та гарантії
Типові терміни — від 2 до 4 тижнів залежно від кількості мереж та складності бізнес-логіки. Вартість розраховується індивідуально, але ми гарантуємо прозоре ціноутворення. Ми працюємо з блокчейн-проєктами понад 5 років та реалізували десятки подібних систем. Гарантуємо стабільну роботу під навантаженням до 10 000 транзакцій на годину.
Отримайте консультацію: напишіть нам, і ми оцінимо ваш проєкт.







