Twin Bridge поддерживает оба направления обмена — выберите сценарий ниже
Обменник выставляет счёт через ELQR → клиент оплачивает QR в виджете провайдера → провайдер зачисляет USDT клиенту
Провайдер обращается к TB. Rate Engine параллельно опрашивает всех доступных VASP-операторов — курс, ликвидность, аптайм. Scoring выбирает лучший маршрут. Курс кэшируется в Redis на 30 сек.
Клиент подтверждает. Провайдер отправляет: сумму KGS, пару KGS/USDT, крипто-адрес клиента (куда слать USDT) и KYC-токен. TB передаёт запрос выбранному VASP. VASP генерирует QR через ELQR и возвращает ссылку в TB.
TB передаёт QR-ссылку от обменника провайдеру. Провайдер отображает QR в своём виджете. Клиент сканирует код и оплачивает KGS на счёт обменника через ELQR.
ELQR фиксирует получение KGS на счёт VASP. VASP (получив ответ от ELQR) отправляет подписанный webhook в TB. TB верифицирует HMAC-подпись, атомарно обновляет статус и пишет событие в Outbox.
TB получает подтверждение оплаты KGS от VASP и атомарно переходит PROCESSING → COMPLETED. Провайдер в своём внутреннем ledger'е пополняет баланс USDT клиента. USDT settlement между VASP и провайдером происходит off-chain периодически (weekly/daily) — TB это не оркестрирует.
Outbox Worker доставляет событие payment.completed на webhook-URL провайдера. Каждый переход статуса — в event_log с SHA-256 хэш-цепочкой.
Провайдер дебетует USDT клиента внутри своей системы → обменник выплачивает KGS клиенту через ELQR
Провайдер обращается к TB. Rate Engine параллельно опрашивает всех VASP-операторов и выбирает лучший курс выкупа USDT. Scoring: курс 50%, надёжность 30%, ликвидность 15%, скорость 5%.
Провайдер отправляет: сумму USDT, направление OFF_RAMP, реквизиты клиента (телефон или банк для получения KGS) и KYC-токен. TB фиксирует rate_locked и маршрутизирует к лучшему VASP.
Провайдер автоматически списывает USDT с внутреннего баланса клиента — это моментальный update в его ledger'е, никаких on-chain операций не требуется. Клиент не подписывает транзакцию, ничего не сканирует.
Outbox Worker передаёт команду ExecuteKGSPayout() выбранному VASP. В payload — сумма KGS, реквизиты получателя (телефон/банк), idempotency key. VASP подтверждает приём запроса.
VASP переводит KGS клиенту через ELQR на его банковский счёт или телефон. VASP фиксирует подтверждение платежа и шлёт подписанный webhook в TB. TB атомарно переходит в COMPLETED.
Outbox Worker доставляет payment.completed на webhook-URL провайдера. USDT, которые провайдер списал у клиента, будут переведены VASP'у off-chain в следующем settlement-цикле (weekly/daily) — TB это не оркестрирует, только предоставляет audit trail.
Клиент не оплатил в течение TTL — транзакция отменяется
Провайдер создал транзакцию. TB вернул провайдеру: ON_RAMP — QR-ссылку для оплаты счёта обменника через ELQR. OFF_RAMP — провайдер автоматически дебетует USDT с кошелька клиента.
TB's QRExpiryScheduler отслеживает время жизни каждой транзакции. Если ELQR или блокчейн не прислали подтверждение до истечения TTL — TB сам инициирует отмену. Либо провайдер явно вызывает cancel API.
TB обновляет статус транзакции. В рамках одной pg-транзакции: UPDATE статуса + INSERT в Outbox + INSERT в event_log. Либо всё, либо ничего.
TB передаёт команду отмены на VASP-оператор. VASP разблокирует зарезервированный адрес или QR-слот. Ресурсы возвращаются в пул.
Outbox Worker доставляет событие payment.cancelled на webhook-URL провайдера. Провайдер показывает клиенту сообщение об отмене.
Переход в CANCELLED записан в event_log с SHA-256 хэшом. Причина отмены (TTL, ручная отмена, ошибка провайдера) — в аудит-поле записи.
Оплата поступила, но обработка у VASP завершилась ошибкой
ELQR или блокчейн подтвердили поступление средств. TB получил webhook от VASP с подтверждением оплаты и обновил статус.
Outbox Worker вызывает Execute() у VASP. VASP возвращает явную ошибку: технический сбой, превышение лимита ликвидности, недоступность платёжной системы. TB получает ответ с кодом ошибки и переводит транзакцию в FAILED.
Если VASP не отвечает в течение таймаута — TB переходит в UNKNOWN, а не FAILED. TB не знает что произошло: polling до получения чёткого ответа, повторный вызов ЗАПРЕЩЁН.
Получив явный ответ об ошибке от VASP — TB фиксирует FAILED. В рамках одной pg-транзакции: статус + Outbox + event_log. Деньги клиента требуют решения.
Outbox Worker доставляет событие payment.failed с кодом ошибки на webhook провайдера. Провайдер информирует клиента и инициирует возврат.
FAILED означает: Execute() вернул ошибку или ReconciliationEngine получил NOT_FOUND — деньги у VASP не оказались.