Ecosystem Docs (dev-first)
Структура
- Папка сервиса (
escrow/,wallet/,bridge/): свойREADME.md, а внутри —flows/,decisions/(ADRы),problems/,diagrams/(Mermaid/PlantUML). Всё локально к сервису. flows/— общие/сквозные флоу (когда затрагиваются несколько сервисов).main/— общие черновики, резюме ключевых решений.problems/— расследования инцидентов, руткозы, «что пошло не так».
Основные правила
- Определи цель заметки в первой строке.
- Если фиксируется решение → мини‑ADR:
Context → Decision → Consequences. - Диаграммы — только текстовые (Mermaid/PlantUML) рядом с MD.
- Ссылки вместо копипасты (код, регуляторные документы, RFC/ADR).
- В конце — 3–5 буллетов «что дальше» или чек‑лист.
Быстрый шаблон записи (копируй в новый файл)
Title: <кратко>
Status: draft
Owner: <ник>
Context: <1–3 предложения>
Decision / Notes: <факты, решения>
Links: <код/ADR/источники>
Next: <до 5 пунктов>
Rules (anti‑redundant)
- Цель первой строкой. Нет цели — нет файла.
- Один файл — одна тема. >600 строк или >3 крупные темы → дроби.
- Папка сервиса (escrow/wallet/bridge) имеет свой
README.md; внутри —flows/,decisions/(ADR),problems/,diagrams/. Общие/сквозные флоу — в корневомflows/. - Не дублируй определения: всё в
glossary.md(когда появится) или ссылайся на первоисточник. - Решения фиксируй как мини‑ADR (
Context/Decision/Consequences), не переписывай длинными абзацами. - Ссылки вместо копипаста: код, RFC/ADR, регуляторные PDF.
- Диаграммы только в текстовых форматах (Mermaid/PlantUML) рядом с MD; картинки без исходников запрещены.
- Статус в шапке:
draft/accepted/deprecated/superseded; помечай владельца. - В конце каждого файла — блок
Next(до 5 пунктов) или чек‑лист; пустым не оставлять. - Убирай воду: факты, решения, риски, next steps. Истории и рассуждения — в
main/или в отдельный черновик. - Обновил код/арх/процесс → оставь ссылку в релевантном файле (не копируй текст).
ADR / flows (минимальный набор)
- Именуй decision с датой:
decision-YYYY-MM-DD-topic-name.md(облегчает хронологию). - Flow и код идут одним PR; документация лежит в
src/.../flowsилиsrc/.../decisions(mdbook). - Каждое решение = одна страница по шаблону (
adr/adr-template.md). - Статусы:
draft → proposed → accepted → deprecated → superseded; фиксируй переход. - В релизной ноте пиши, какие решения стали accepted (живыми) в релизе.
ADR / Flow процесс (MVP)
- Где лежит: код + flow в одном PR; текст — в
src/<domain>/(decisions|flows)/. Ставь ссылку на PR/код вLinks. - Нумерация: глобально
ADR-001,ADR-002, … без дат в имени файла. Храни вsrc/<domain>/decisions/ADR-xxx-<slug>.md. - Формат: один экран по шаблону
adr-template.md(Context/Decision/Consequences/Next). - Статус:
draft → accepted → deprecated → superseded; обновляй при изменении решения. - Релизы: в релиз-ноте фиксируй, какие ADR перешли в
accepted.
Как завести новый ADR
- Возьми
src/adr/adr-template.md. - Подставь следующий номер
ADR-xxx(смотри соседние ADR) и короткий slug. - Положи файл в
src/<domain>/decisions/. - Добавь ссылку в
src/SUMMARY.mdи в README домена; обнови релизную заметку при переходе вaccepted.
ADR-XXX: <краткое имя>
Status: draft
Owner: <владелец>
Domain: <wallet/escrow/bridge/...>
Links: PR <#>, code refs, related flows/diagrams
Created:
Context
- Почему меняем/фиксируем решение. Коротко, фактами.
Decision
- Суть решения (1–3 пункта).
Consequences
- Что выигрываем/риски/альтернативы.
Next
- Следующие шаги (до 5 пунктов).
Title: Mervey Ltd — company snapshot Summary:
- Юрисдикция: AIFC/AFSA, Казахстан (Астана). Mervey Ltd, БИН 251040901583.
- Продукт: некостодиальный мульти-блокчейн кошелёк + P2P-эскроу + E2E-мессенджер.
- Ключевой принцип: компания не хранит приватные ключи/сид; эскроу — некастодиальный.
- Гео-фокус: Центральная Азия/СНГ; санкционные и ограниченные юрисдикции — блокируем.
Problem (blockchain team focus):
- Нет безопасного и доступного фиат↔крипто P2P в СНГ/ЦА после закрытия/ограничений крупных рампов (LocalBitcoins, Binance P2P).
- Высокий риск мошенничества в оффчейн P2P: нужен on-chain escrow с прозрачными правилами и событиями для споров.
- Мерчанты распылены: нужен агрегатор с проверенными адресами/ролями и единым SLA/таймерами в контракте.
- Требуются кросс-чейн онбординг и релиз без централизованного custody.
Goal (blockchain team):
- Построить безопасную, отказоустойчивую, проверяемую архитектуру (некастодиальный кошелёк + on-chain escrow + кросс-чейн свопы), с чёткими правилами ролей/доступов, таймерами, аудитом и минимизацией доверия к серверу.
Invariants (must-hold):
- Некастодиальность: приватные ключи/сид/подписи — только на клиенте; серверы не имеют доступа и не подписывают.
- On-chain escrow: средства блокируются в смарт-контракте; релиз только по правилам контракта/ролей; никакого серверного custody.
- Кросс-чейн взаимодействия.
- Шифрование: E2E для мессенджера (чаты/звонки); на сервере — только метаданные, без контента.
- KYC/KYB и санкции: risk-based, санкционные юрисдикции блокируются, проверка мерчантов обязательна.
- Гео/санкции в продуктах: офферинг фиат ↔ крипто доступен только в разрешённых регионах; применение гео-блокировок.
- Минимизация данных: храним только то, что нужно для P2P/безопасности/комплаенса; нет журналов приватных данных ключей/сид - ни в коем случае.
- Прозрачность/аудит: контракты открыты, адреса ролей публичны; события в цепочке — источник правды для споров.
- Безопасные таймеры: SLA/timeout для сделок в эскроу формализованы в контракте; автоканцел без ручного custody.
Links:
- Регуляторный бизнес-план (PDF): см. репо/хранилище, не копировать текст.
- Архитектура ключей (Notion): <ссылка>
- P2P Escrow Exchange Aggregator (Notion): <ссылка>
- Liquidity Treasury Service (Notion): <ссылка>
- Cross-chain bridge services (Notion): <ссылка>
- Merchant flow (Notion): <ссылка>
Next:
- Слинковать с ADR по эскроу/кошельку, когда появятся.
- Уточнить владельца и дату ревью.
Wallet service docs
Цель: держать флоу и решения по кошельку (non-custodial, SSS/SLIP-39, E2E) в одном месте.
Состав
flows/— флоу кошелька (создание, восстановление, шардинг).decisions/— ADR/мини-решения.problems/— локальные инциденты/руткозы.diagrams/— Mermaid/PlantUML источники.
Как обновлять
- Любое решение → мини-ADR: Context / Decision / Consequences / Links / Next.
- Флоу храним здесь; сквозные темы — в корневом
flows/. - Ссылки на код/репы/tx, без копипасты.
Links: добавить адреса SDK/клиентов, когда будут.
Архитектура управления криптографическими ключами
Последнее обновление: 2025-01-11
Корневая архитектура: OpenZeppelin AccessControl
Release Wallet (1-2)
Кошелек, решающий проблему Release. Имеет децентрализованное хранение и роль release в Solidity-контракте.
Проблема: есть 3 агента поддержки. Каким образом они будут подписывать release он-чейн?
Если 1 агент может release
- один недовольный сотрудник → украл деньги
- взлом одного ноутбука → денег как и не было
- шантаж одного человека → украли деньги
- невозможно доказать злоупотребление
Трейдофф: или делать Gnosis Safe Wallet тоже для поддержки и их подписи, по газу 140–160K (эквивалент ~0.4 доллара).
Wallet / Flows
Title: Wallet creation + recovery (2-of-3, user+server+guard) Status: draft (superseded by cloud-backup decision, оставляем как архив) Owner: <ник> Goal: Описать опциональный флоу кошелька с recovery через 2/3 SSS: шард пользователя, E2E-зашифрованный шард на сервере, шард гварда. Сервер не способен восстановить без участия гварда/пользователя.
Decisions:
- Active:
wallet/decisions/decision-2026-01-17-cloud-backup-supersedes-sss.md(cloud backup заменяет этот флоу). - Archive:
wallet/decisions/decision-2026-01-13-recovery-2of3.md,wallet/decisions/decision-2026-01-14-sss-format-and-oob.md.
Open questions (если вернёмся к 2-of-3):
- Формат SSS-конверта: точный layout/AAD, тест-векторы, негативные тесты на подмену metadata.
- Протокол E2E: Noise/X3DH детали, double-wrapping S-shard (кто чей pubkey использует, ротация).
- UX гварда: OOB-канал, подтверждение/отклонение, таймауты, rate-limit попыток.
- Replay/abort: как инвалидировать старые recovery sessions, защита от re-play уведомлений гварду.
- Хранение и удаление: сроки retention S-shard ciphertext, когда подчистка, аудит действий.
- Device posture: требования к устройству гварда (TEE/KeyStore/SE, KDF параметры), политика при смене устройства.
- KYC/антириск: кто источник, какие сигналы, частота повторного KYC.
Roles:
- User (U) — владелец кошелька, генерирует сид, держит шард U.
- Server (S) — курьер шифртекста для шарда S; не имеет ключей к расшифровке.
- Guard (G) — доверенное лицо/устройство, держит шард G, подтверждает recovery, тревожит владельца.
- KYC provider — даёт сигнал допуска (не даёт ключи).
Invariants (must hold):
- Non-custodial: сервер не видит сид/приватные ключи; хранит только шифртекст шарда S и метаданные.
- SSS k=2, n=3 (classic Shamir): любые 2 из 3 восстанавливают секрет.
- Share binding: шарды всегда упакованы в аутентифицированную обёртку (AEAD) с
wallet_id/sss_profile_id/version/role/index(нельзя незаметно перемешать шарды между кошельками/сессиями). - E2E: шарды S и G всегда в шифре под ключи получателей; локальный KDF (Argon2id) + AES-GCM/ChaCha20-Poly1305.
- Recovery требует участия G (OOB подтверждение) и сессионного ключа U; KYC — только сигнал, не ключ.
- Таймауты сессий, аудит событий; без участия G и U сервер не способен инициировать/завершить recovery.
Terminology:
- OOB (out-of-band) подтверждение: гвард подтверждает recovery по независимому каналу (звонок/код/мессенджер/второе устройство), чтобы проверить запрос и тревожить владельца при атаке.
Flow A — обычный кошелёк (без шардов):
- U генерирует seed (12/24), локально шифрует бэкап (пароль→Argon2id→AES-GCM).
- Нет шардов, нет серверных копий. Потеря seed = невосстановимо.
Flow B — кошелёк с recovery 2/3: Creation:
- U генерирует seed локально.
- Split seed → classic Shamir SSS 2/3: U-shard, S-shard, G-shard.
- U-shard: хранится локально, шифруется паролем (Argon2id).
- S-shard: шифруется E2E под pubkey U_recovery и pubkey G (двойное обёртывание или гибрид), отправляется и хранится на сервере как шифртекст. Метаданные: wallet_id, sss_profile_id, sss_format_version, k/n, share_index, guard_id, статус.
- G-shard: хранится у гварда локально, шифруется паролем/био-гейтом + KDF.
Recovery (когда утеряно устройство U):
- U с новым устройством создаёт recovery session, публикует сессионный pubkey, проходит KYC (сигнал допуска/антириск).
- Сервер уведомляет G о запросе (push). G выполняет OOB проверку (звонок/мессенджер/код).
- G разблокирует G-shard (паскод+био), шифрует под сессионный pubkey U, отправляет по E2E.
- Сервер отдаёт S-shard шифртекст (у него нет ключей), U расшифровывает S-shard локально своим recovery key (который сервер не знает).
- U объединяет любые 2 из 3 (обычно G-shard + S-shard) → получает seed → импортирует кошелёк.
- Сессия закрывается, сессионные ключи стираются, событие аудита фиксируется.
Anti-abuse / внутренний атакующий:
- Сервер не имеет ключей для S-shard; без G участие невозможно завершить recovery.
- Любая попытка «тихого» запуска recovery требует G-активации, которая тревожит владельца.
- Rate-limit/таймаут recovery-сессий; логирование событий
recovery_requested,guard_approved/denied,recovery_completed/failed.
Риски и митигация (остаточные):
- Компрометация устройства G или слабый KDF → оффлайн-брут G-shard. Требуется жёсткий KDF/био-гейт и защитное хранилище (TEE/KeyStore/Secure Enclave).
- Социнжиниринг G: смягчать OOB-подтверждением и уведомлением владельца.
- Потеря и U-shard, и G-shard одновременно при недоступности S-shard → невосстановимо.
Wallet / Decisions
Active
- ✅ Cloud Backup Recovery (supersedes SSS) — принято 17.01.2026
Superseded / Archive
- ❌ Recovery 2-of-3 SSS + Guard — superseded by cloud backup
- ❌ SSS format + OOB — superseded by cloud backup
Title: Cloud Backup Recovery (supersedes 2-of-3 SSS/Guard approach) Status: proposed Owner: Artur Date: 17.01.2026
Context
Рассматривались два подхода к recovery:
- 2-of-3 SSS с guard + server shard + user shard (decision-2026-01-13)
- Cloud backup с шифрованием на клиенте
Проблема: SSS+guard — избыточная сложность для B2C non-custodial wallet без чёткого целевого пользователя.
Decision
Принимаем подход: Encrypted Cloud Backup в облако пользователя (iCloud/Google Drive).
Отказываемся от SSS/guard для основного флоу. Концентрируем ресурсы на escrow.
Обоснование
1. Анализ индустрии
Сравнение топовых non-custodial кошельков:
| Wallet | MAU/скачивания | Recovery подход |
|---|---|---|
| MetaMask | 30+ млн | Seed phrase only, нет встроенного recovery |
| Trust Wallet | 60+ млн скачиваний | Pure non-custodial, seed only |
| Ledger | 6 млн устройств | Seed + опциональный Ledger Recover (платно, semi-custody) |
| Trezor | 1+ млн устройств | Полностью некастодиальный, seed only |
| Exodus | 6 млн | Self-custody, seed only |
| Phantom | 7+ млн | Seed-based recovery |
| Rainbow | 3+ млн | Self-custody, seed |
| Rabby | растущая база | Seed фраза |
| Coinbase Wallet | — | User-controlled keys, seed |
| Zerion | 1+ млн | Seed-based |
Вывод: Ни один успешный кошелёк не использует SSS для массового пользователя. Быть может они глупее нас? Нет — они выбрали простоту.
2. Целевой пользователь (Максат)
Кто: Максат, 29–35, не крипто-гик, пользуется Kaspi/банками. Хочет хранить небольшую/среднюю сумму, переводы, P2P/escrow.
Проблема: "Боюсь потерять доступ при утере телефона. Не хочу разбираться в seed-фразах."
Что нужно: "Потерял телефон → купил новый → восстановил кошелёк". Точка.
Что НЕ нужно: Разбираться в шардах, гвардах, OOB-подтверждениях, процедурах rewrap.
3. Риск-анализ SSS для B2C
Формула риска:
risk = кол-во объектов хранения × кол-во операций × человеческий фактор
Для 2-of-3 SSS:
- 3 объекта (U-shard, S-shard, G-shard)
- N операций жизненного цикла:
- Генерация, раздача долей, подтверждение принятия
- Смена телефона, переустановка ОС, миграция
- Rewrap при смене guard pubkey
- OOB-подтверждения, таймауты
Оценка (оптимистичная):
- Вероятность потери/порчи одного объекта за 2–3 года: 10–15%
- Вероятность ошибки на одной критической операции: 2–5%
Результат: Даже при небольших рисках долей и операций, для B2C 2-of-3 SSS даёт ≈7% вероятность полной потери за 2–3 года. Для массового продукта это слишком высокий риск.
4. Инженерные проблемы SSS
Фрагментация стандартов:
- Отсутствуют единые константы (SLIP-39, Trezor SSS, другие реализации несовместимы).
- Исторические уязвимости (интерполяция, восстановление с меньшим порогом): https://btcarmory.com/fragmented-backup-vuln/
- Отсутствие единого "API" для SSS долей → lock-in на 5–10 лет.
Сложность реализации:
- Требует команды опытных криптографов.
- Огромная поверхность для багов: rewrap, ротация, guard lifecycle, OOB.
- Бэкенд: оркестрация шардов, метаданные, guard registry, аудит, таймауты.
Вердикт: "Мы лечим кашель бегом под ледяным дождём."
5. Аргумент "для кого?"
Будь мы B2B-провайдером для миллионных сетей — SSS был бы необходимостью. Там другая ответственность, суммы, SLA.
Но мы делаем B2C consumer wallet. SSS+guard — это "технологичность ради технологичности", без целевого пользователя, проблему которого это решает.
Решение: Cloud Backup (Best Practice)
Как работает
Онбординг:
- "Создать кошелёк"
- "Сделать резервную копию"
- Выбрать способ:
- Cloud backup: "Сохранить зашифрованную копию"
- Вариант UX: "Придумай пароль восстановления" (только для шифрования; мы его не знаем)
- И/или: passkey (локальная биометрия + платформенный keychain)
- Готово: в облаке пользователя лежит зашифрованный бэкап.
Восстановление (новое устройство):
- "Восстановить кошелёк"
- Войти в своё облако (iCloud/Google Drive)
- Ввести пароль восстановления
- Seed расшифровывается локально → кошелёк восстановлен
Технический флоу
- Seed создаётся на устройстве, никуда не уходит.
- При бэкапе: seed шифруется локально паролем восстановления (или passkey).
- Ciphertext сохраняется в iCloud / Google Drive пользователя (не на наш сервер).
- При восстановлении: пользователь входит в своё облако, скачивает ciphertext, вводит пароль → seed расшифровывается локально.
Преимущества
| Критерий | SSS + Guard | Cloud Backup (iCloud/GDrive) |
|---|---|---|
| Сложность бэкенда | Высокая (шарды, rewrap, OOB, таймауты) | Минимальная (мы ничего не храним) |
| UX для Максата | Непонятен ("гвард? шард?") | Понятен ("сохрани в облако, запомни пароль") |
| Регуляторный риск | Есть (храним ciphertext) | Минимален (всё у юзера) |
| Точка отказа | Guard недоступен / сервер лёг / rewrap-баг | Только Apple/Google (стабильны) |
| Индустрия | Почти никто (enterprise MPC) | MetaMask, Trust Wallet, Phantom, Rainbow |
Что даёт:
- Мы не храним ничего — ни ключей, ни шифртекстов. Регулятору нечего требовать.
- Пользователь сам отвечает за своё облако и свой пароль. Это честный non-custodial.
- Если забыл пароль — есть seed phrase (всегда). Это fallback, а не дыра.
- Бэкенд минимальный: профиль, escrow-статусы, уведомления. Без криптографической оркестрации.
Ответственность = Некастодиальность
Подход "бэкап в облако юзера" — Best Practice:
- Ответственен за свои решения пользователь.
- Мы не можем восстановить его кошелёк, даже если захотим.
- Это полностью совпадает с концепцией некастодиальности: "твои ключи — твоя ответственность".
Consequences
Positive:
- Простота реализации и поддержки
- Минимальный бэкенд (нет шардов, guard registry, rewrap)
- Регуляторная чистота (не храним ничего)
- Знакомый UX (как у MetaMask/Trust/Phantom)
- Ресурсы команды → на escrow (основной продукт)
Negative:
- Зависимость от облачных провайдеров пользователя (но они надёжнее нашего гипотетического guard-сервиса)
- Нет "wow-фактора" для crypto-гиков (но это не наша аудитория)
Neutral:
- Для enterprise/китов можно добавить SSS позже как опциональный "Advanced tier"
Implementation
MVP:
- Seed phrase generation + onboarding с проверкой бэкапа
- Encrypted cloud backup (iCloud/Google Drive)
- Recovery flow: download ciphertext → decrypt locally
- Fallback: seed phrase restore (всегда доступен)
Backend scope:
- Никаких шардов, guard registry, rewrap
- Только: профиль, escrow-статусы, уведомления, аудит
Mobile scope:
- Платформенные API для iCloud/Google Drive
- KDF (Argon2/scrypt) для пароля восстановления
- Passkey support (опционально)
Links
- Supersedes:
wallet/decisions/decision-2026-01-13-recovery-2of3.md - Related:
escrow/decisions/decision-2026-01-14-kyc-provider-integration.md - Target users: Notion (см. контекст decision)
Next Steps
- Прототип encrypted cloud backup (iOS/Android)
- UX копирайт для онбординга ("пароль восстановления" vs "seed phrase")
- Интеграция с iCloud/Google Drive API
- Документация для пользователей: "Что делать, если забыл пароль?" → seed phrase
- Отменить/архивировать SSS-related tasks
Вывод: Простота спасёт продукт. SSS — в архив.
Title: Adopt 2-of-3 recovery with guard + E2E server shard (supersedes KYC-SSS draft) Status: superseded Superseded by: decision-2026-01-17-cloud-backup-supersedes-sss.md Owner: Artur Date: 13.01.2026
Context:
- 2026-01-13 рассматривался вариант «KYC-провайдер выдаёт пароль из лица» (KYC-SSS). Риски: центр доверия в KYC, deepfake/replay, "костыльное" (нестадартное) решение открывающее фронт для атак, сервер/провайдер может выпустить ключ без пользователя.
- Нужен компромисс между UX и некастодиальностью: пользователь не теряет все средства при утере устройства, сервер не получает доступа к сид.
Decision:
- Принят флоу 2/3 SSS: шард U у пользователя, шард S хранится на сервере только как шифртекст, шард G у гварда. Любые 2 из 3 восстанавливают сид.
- Сервер не знает ключей расшифровки S-shard; S-shard шифруется E2E под pubkey восстановления пользователя и pubkey гварда (двойная обёртка/гибрид).
- Recovery требует участия гварда (разблокировка G-shard под KDF+био/паскод) и сессионного ключа пользователя; KYC даёт только сигнал допуска, не ключ.
- Таймауты, rate-limit, аудит событий recovery; OOB подтверждение гварда (тревога владельцу).
Safety rationale:
- Нет приватных ключей/сид на сервере; шифртекст бесполезен без ключей U/G.
- Сервер не может «тихо» запустить восстановление: нужен G-shard + сессионный ключ U, плюс OOB-подтверждение.
- KYC не даёт доступ к секретам, только риск-сигнал; биометрия — локальный гейт, а не сетевой пароль.
- Ротация/ревокация возможна: смена guard pubkey, rewrap S-shard, перевыпуск SSS.
Децентрализация:
- UX: восстановление требует участия гварда; потеря и U-shard, и G-shard при недоступности S-shard = невосстановимо.
- Server scope: хранит только шифртекст S-shard + метаданные (wallet_id, k/n, guard_id, статусы); обслуживает уведомления и аудит.
- Guard responsibilities: хранить G-shard локально в защищённом хранилище; подтверждать запросы OOB; тревожить владельца.
Links:
- Flow:
wallet/flows/flow-wallet-recovery-2of3.md - Rules/invariants:
.cursorrules
Прописать процедуру и решение rewrap при смене guard pubkey и при ротации recovery ключей.
Title: Wallet: SSS format — classic Shamir + share binding (versioned, authenticated); SLIP-39 optional Status: superseded Superseded by: decision-2026-01-17-cloud-backup-supersedes-sss.md Owner: Artur Date: 2026-01-14
Context:
- В recovery 2-of-3 (U+S+G) нужно делить сид на шарды (k=2, n=3) так, чтобы:
- нельзя было подменить/перемешать шарды между кошельками/профилями/сессиями незаметно;
- формат был простым и предсказуемым для реализации и тест-векторов;
- сервер оставался курьером шифртекста (non-custodial).
- SLIP-39 удобен для ручного восстановления (мнемоники), но добавляет UX/форматную нагрузку; сейчас recovery — машинный (через приложения U/G).
Decision:
- Принять classic Shamir Secret Sharing (Shamir over GF(256)/bytes) для split/merge seed (k=2, n=3).
- Сделать обязательное binding + versioning для каждого шарда через аутентифицированную обёртку:
- шард никогда не хранится/передаётся как “голые” байты Shamir, только как ciphertext в AEAD (AES-GCM / ChaCha20-Poly1305);
- в AEAD AAD (или внутри зашифрованного заголовка) включаем минимум:
sss_format_versionwallet_idsss_profile_id(идентификатор набора шардов/ротации)k,n,share_index,shard_role(U/S/G)- опционально:
guard_id,created_at
- при попытке “скрестить” шарды между кошельками/профилями/ролями — AEAD аутентификация должна падать (детект до merge).
- SLIP-39 оставить опцией, если появится требование ручного восстановления (человек вводит слова). Это отдельный UX-путь, не блокирующий текущий.
OOB (out-of-band) подтверждение — что это:
- OOB подтверждение — проверка recovery-запроса гвардом по независимому каналу, не зависящему от сервера/приложения: звонок владельцу, заранее оговорённый код, личный контакт, второе устройство/мессенджер и т.п.
- Цель: (1) удостовериться, что запрос инициировал владелец, (2) тревожить владельца при атаке, (3) снизить риск социального инжиниринга/компрометации канала push.
Consequences:
- Формат шардов становится версируемым и проверяемым: легче мигрировать/ротировать и ловить ошибки интеграции.
- Реализация обязана везде использовать AEAD с AAD (локально для U/G, на сервере для хранения S-shard), иначе binding теряется.
- SLIP-39 не внедряем сейчас, чтобы не усложнять UX и крипто-спеку без требования.
Links:
- Flow:
wallet/flows/flow-wallet-recovery-2of3.md - Recovery decision (2-of-3):
wallet/decisions/decision-2026-01-13-recovery-2of3.md
Next:
-
Зафиксировать точный binary layout заголовка (fields + sizes) и
sss_format_version=1 - Выбрать/проверить библиотеку Shamir (GF(256)) и добавить test vectors (split/merge + negative tests на подмену metadata)
-
Описать ротацию
sss_profile_id(перевыпуск шардов, rewrap S-shard при смене guard pubkey)
Escrow service docs
Цель: держать решения и флоу для эскроу (P2P обмен, роли, антифрод) в одном месте.
Состав
flows/— флоу эскроу (пример:flows/flow-exchange.md).decisions/— ADR/мини-решения.problems/— локальные инциденты/руткозы.diagrams/— Mermaid/PlantUML источники.
Как обновлять
- Любое решение → мини-ADR: Context / Decision / Consequences / Links / Next.
- Флоу храним здесь (для сервиса), сквозные — в корневом
flows/. - Ссылки на код/контракты/tx, не копипаста.
Links: добавь адреса контрактов и репозиторий, когда будут.
Escrow / Flows
Title: Escrow exchange flow (buyer <-> seller via platform) Status: draft Owner: Artur Goal: Описать off/on-chain шаги обменки с ролями, включая диспут и вызовы релиза через HSM/tx-gateway. Инварианты: non-custodial, роли, дедлайн, события.
Roles:
-
Buyer — покупает крипту за фиат.
-
Seller (merchant) — продаёт крипту, ждёт фиат.
-
Platform (escrow) — блокирует/разрешает релиз по правилам, без доступа к средствам; роли через
EXCHANGE_ROLE. -
Non-custodial: контракт держит средства, сервер не имеет ключей пользователей.
-
Роли/доступ:
EXCHANGE_ROLEна whitelisted адреса кидается; админ — multisig/owner. -
Дедлайн: каждая сделка имеет
deadline; по таймауту — auto-cancel/timeout path. -
Events: все ключевые действия эмитят события для аудита/алертов.
-
Pausable/ReentrancyGuard паттерны; без прокси, если не обосновано.
- Offchain (юридика + тех):
- Обменка/мерчант проходит KYC/KYB, подписывает договор.
- Сообщает onchain-адреса работы:
0xA1...(main hot),0xB2...(backup). - SLA/timeout фиксируются в договоре и в контрактном
deadline.
- Onchain (настройка ролей):
- Multisig/owner вызывает
grantRole(EXCHANGE_ROLE, hot)иgrantRole(EXCHANGE_ROLE, backup). - Модификатор:
modifier onlyExchange() {
require(hasRole(EXCHANGE_ROLE, msg.sender), "not exchange");
_;
}
- Создание сделки (обменкой):
createDeal(
dealId,
recipient, // адрес релизера/получателя
amount,
token,
deadline // SLA время
)
- recipient — адрес того, кто получит релиз после подтверждения оплаты.
- deadline — жёсткий таймер; по истечении — cancel/timeout.
- События:
DealCreated(dealId, recipient, token, amount, deadline).
- Диспут кейс (упрощённый поток):
- Buyer отправил фиат.
- Seller утверждает «деньги не дошли».
- Backend вызывает TX Gateway:
openDispute(dealId, sides, paymentStatus, timestamp, log). - Расследование offchain, сбор стейтментов/доказательств.
- Backend принимает решение release/refund/dispute-keep:
- формирует callData для контракта (release или refund или отметка диспута);
- передаёт в signer/HSM (см. "Release wallet" архитектуру).
- Контракт выполняет действие; эмитится событие
DealReleasedилиDealRefunded/DealDisputed.
- Выходное состояние:
- Сделка завершена (release или refund), комиссия списана.
- События зафиксированы; по таймауту — auto-cancel/auto-refund (уточнить в коде).
Notes / Links:
- Архитектура управления криптографическими ключами:
. - Добавить адреса контрактов/tx, когда будут.
Next:
- Формализовать timeout/cancel/partial release кейсы и события алертов.
- Добавить Mermaid диаграмму (normal + dispute).
- Пример tx hash в тестовой сети.
Escrow / Decisions
- Escrow simplify
- Escrow: KYC/KYB integration & EXCHANGE_ROLE lifecycle
- Escrow: KYC/KYB/KYT flow + rough cost model
- Escrow observability: SLA monitor + dispute indexer
- Multisig control for Escrow release (Gnosis Safe deployment)
Title: Escrow design: minimal, auditable, multisig-controlled (Kleros only as future option) Status: draft Owner: Artur Date: 12.01.2026
Context:
- Рассматривали механизм Kleros как опцию на будущее (внешний арбитраж).
- Для MVP нужен максимально «тупой», формально проверяемый, изолированный контракт.
- Цель: быстрый и предсказуемый escrow с минимальной поверхностью атаки, ясной ролью multisig.
Decision:
- Для MVP: multisig/authorized release, без внешнего арбитража. Kleros — только как возможный плагин в будущем.
- Контракт максимально простой: AccessControl (EXCHANGE_ROLE), ReentrancyGuard, SafeERC20, опционально Pausable; события на каждое действие.
- Диспут offchain; onchain — вызов
releaseByMultisigилиrefundByMultisigавторизованным адресом (multisig/tx-gateway). - Жёсткие инварианты и фиксированные адреса через constructor; без upgradeable proxy, без owner-settable адресов.
Security principles (обязательные):
- Неизменяемый контракт (без прокси, адреса заданы в constructor).
- Нет приватных ключей на бэкенде: бэкенд только создаёт сделки, читает события/view, не подписывает пользовательские tx.
- Только 4 пути движения средств:
deposit(),releaseByMultisig(),refundByMultisig(),ruleByArbitrator()(если будет),timeoutWithdraw(). - ReentrancyGuard ✅; Checks-Effects-Interactions ✅; SafeERC20 ✅; Pausable ⚠ (опционально); Event логирование ✅; Hard limits ✅; invariant tests ✅; fuzz tests ✅.
Storage model (эскроу-хранилище):
- Варианты: отдельный escrow на сделку или общий пул с internal accounting.
- Для крупных объёмов — предпочтителен один контракт-хранилище +
mapping(uint256 => Deal):
mapping(uint256 => Deal) public deals;
struct Deal {
address buyer;
address seller;
uint256 amount;
State state;
}
Consequences:
- Проще аудит/формальная проверка; меньше зависимостей и газа.
- Предсказуемый вывод: решение принимает whitelisted multisig/tx-gateway по SLA.
- Нет публичного арбитража Kleros — компенсируется договором, логами, событиями onchain.
Links:
- Flow:
escrow/flows/flow-exchange.md - Rules/invariants:
.cursorrules
Next:
- Зафиксировать формальные SLA/timeout в контракте (auto-cancel/auto-refund).
- Добавить тесты на роли, паузу, reentrancy, deadline.
- Рассмотреть опциональный плагин внешнего арбитража как v2 (без Kleros-тяжести).
Title: Escrow: KYC/KYB integration & EXCHANGE_ROLE lifecycle (recovery uses risk signal only) Status: draft Owner: Artur Inspector Date: 2026-01-14
Context:
- Mervey — некастодиальный кошелёк + P2P эскроу. Сервер не хранит ключи/сид.
- Для escrow мерчанты должны быть проверены (KYC/KYB) и допущены к on-chain ролям (
EXCHANGE_ROLE). - Для recovery KYC провайдер должен давать только risk-based сигнал допуска (не ключ/пароль), чтобы не становиться центром доверия.
- Нужен санкционный скрининг + гео-ограничения (risk-based, ongoing monitoring).
Decision / Scope:
Backend (основная работа):
- Интеграция API KYC-провайдера (Sumsub/Onfido/Veriff/др.)
-
Хранение статусов:
pending | approved | rejected | expired - Webhooks от провайдера → обновление статусов и аудит
- Санкционный скрининг (OFAC/EU/UN) + geo блок
- Ongoing monitoring (перепроверка при изменениях в списках / risk events)
Blockchain / smart contracts (включая tx-gateway):
-
Escrow: после
KYC_APPROVEDдля merchant →grantRole(EXCHANGE_ROLE, merchant_address)через multisig/tx-gateway/HSM -
Escrow audit: emit
MerchantWhitelisted(address indexed merchant, bytes32 kycHash)(без PII; хеш записи/аттестации) -
Escrow revocation: при
expired/rejected→revokeRole(EXCHANGE_ROLE, merchant_address)(offchain триггер → onchain) - Recovery: KYC = только сигнал допуска перед стартом recovery session (backend ↔ KYC API), не ключ
-
Events: аудит-события для комплаенса (например
KYCCheckPassed,RecoveryInitiated)
Backend ↔ blockchain wiring (целевое):
[Merchant] → [KYC Provider] → [Backend webhook]
↓
[DB status=approved]
↓
[TX Gateway / HSM signer] → [Contract: grantRole]
↓
[Event: MerchantWhitelisted]
Profile / gating (KYC-шнутый или нет?):
| Роль | KYC обязателен | Что даёт |
|---|---|---|
| Обычный юзер | Нет (non-custodial) | Кошелёк работает без KYC |
| Recovery | Да (сигнал) | Допуск к recovery session, не ключ |
| Мерчант (escrow) | Да (KYC/KYB) | EXCHANGE_ROLE, доступ к P2P |
| Высокие лимиты | Возможно | Tier-система, если будет |
Consequences:
- KYC не ломает базовый non-custodial продукт, но обязателен для мерчантов и risk-gated recovery.
- On-chain enforcement делается через роли (
EXCHANGE_ROLE), а не через хранение PII в контракте. - У комплаенса появляется auditable trail: webhook logs + on-chain events.
Questions (to backend/product/legal):
- Какой провайдер выбран и какие обязательные поля/страны/лимиты?
- Какие данные храним локально vs запрашиваем по API (минимизация данных)?
- Tier-система лимитов или бинарный допуск?
- Частота/триггеры ongoing monitoring?
-
Нужен ли onchain аудит через
kycHashили достаточно событий + offchain хранилища?
Links:
- Escrow flow:
escrow/flows/flow-exchange.md - Wallet recovery flow:
wallet/flows/flow-wallet-recovery-2of3.md - Recovery decision:
wallet/decisions/decision-2026-01-13-recovery-2of3.md - Company snapshot:
main/company.md
Next:
- Уточнить провайдера и получить доступ к sandbox API
-
Согласовать формат
MerchantWhitelistedи процесс ревокации роли -
Прототипировать tx-gateway →
grantRole/revokeRoleфлоу (HSM signer) - Интеграционные тесты: mock webhook → on-chain роль выставлена/снята
Title: Escrow observability: SLA monitor + dispute indexer as separate services Status: draft Owner: backend/ops Date: 2026-01-18
Context:
- Эскроу требует детерминированных SLA таймеров и поискового слоя по спорам.
- Нужны быстрые алерты и разбор спорных сделок без ручного просмотра логов.
- Сервисы должны оставаться read-only к цепи, без приватных ключей.
Decision:
- Заводим два микросервиса:
on-chain SLA monitorиdispute event indexer, каждый как отдельный Deployment (1 pod) с managed Postgres. - Доступ к цепи только через
rpc-gateway-rotator(кворум RPC, без прямых публичных endpoint). - Оба сервиса обязательны к прод-защите: /health, /ready, /metrics, алерты по ingest lag, retries с backoff.
- Конфиги (chains/contracts/webhooks/slack) — только через файлы/секреты, без перекомпиляции; hot-reload допускается.
- Стейт восстанавливаемый: реплей из цепи + БД; никаких локальных томов.
Consequences:
- SLA-события и споры становятся наблюдаемыми и алертируемыми без изменений смарт-контрактов.
- Один источник RPC-правды (gateway) снижает инконсисентность (флап рпс) обоих сервисов.
- Managed БД упрощает бэкап и обновления, но требует сетевых правил к кластеру.
- Операции получают /metrics для Grafana/alertmanager; саппорт — быстрый поиск и свежие таймеры.
Next:
- Закрепить владельцев сервисов (oncall) и SLO: ingest lag, alert latency, search latency.
- Задать дефолтные конфиги chains/contracts и мок-каналы алертов для dev.
- Подготовить CI job: сборка образов + k8s deploy манифесты (см. deploy-sla-monitor-dispute-indexer.md).
Escrow / Problems
Расследования инцидентов, руткозы, "что пошло не так" в контексте escrow.
Проблема: кому верить — банку или чеку юзера?
Коротко, разговорно: спор в эскроу, фиат подтверждается криво. Юзер шлёт чек, банк/PSP даёт вебхук или выписку, мерчант говорит «не дошло», multisig должен решать. Ниже — больные вопросы без ответов (пока).
- Источник истины: чей сигнал важнее — webhook PSP, выписка мерчанта, или «скрин чека» от покупателя?
- Что делаем при chargeback/clawback спустя N дней, если мы уже сделали release? Кто несёт риск?
- Если банк задержал платёж, а SLA таймер сработал и мы сделали refund — как лечить двойные выплаты, есть ли «reopen»?
- Мерчант в блоке/hold у банка: держим ли крипту на таймере до разблокировки или сразу рефандим? Кто решает и на каких доказательствах?
- Кто имеет полномочия финального on-chain решения: multisig вслепую по флагу fiat_confirmed, или нужен второй фактор (аудитор)?
- Можно ли подделать чек/скрин? Есть ли обязательная верификация через API банка/PSP или запрещаем offchain доказательства без оракула?
- Reorg/L2 лаг: как синхронизировать on-chain release/refund с поздним fiat_confirmed, чтобы не словить рассинхрон?
- Как логируем и подписываем решения диспута, чтобы потом доказать, что multisig действовал по процессу (а не «на глаз»)?
- Если два разных PSP дают разные статусы по одному платежу, что выбираем: majority, приоритет «более сильного» PSP, или стоп-краник?
- Какой SLA на fiat_pending перед авто-refund? Что, если PSP не даёт финального статуса сутками?
- Кто платит комиссию/штрафы за холд/chargeback — мерчант, покупатель или платформа?
- Есть ли возможность «частичного релиза» при частичном зачислении фиата, или только binary release/refund?
- Как уведомляем стороны об отклонениях: алерты в Slack/webhook, нужен ли ручной ack?
- Нужен ли отдельный «arbiter role» (offchain) с правом блокировать решение multisig до выяснения?
- Как управляем ключами multisig/tx-gateway, чтобы компрометация не привела к массовым неправильным релизам?
- Если PSP сломан (нет webhook), используем ли «pull» проверку вручную/по API или ставим сделку в паузу?
- Какая политика при AML-флагах: auto-freeze on-chain? кто потом размораживает?
- Можно ли дать мерчанту self-service «апелляцию» без участия платформы, или это риск злоупотреблений?
- Что делаем с уже завершёнными сделками, если позже приходит банк-отзыв: ведём ли offchain долг мерчанта/покупателя?
- Требуем ли escrow балансы/депозиты от мерчантов как страховку под банковские риски?
Статус: Частично покрыто интеграцией с finic (решение от 25 января 2026).
Deploy: SLA monitor & dispute indexer (VPS + k3s, 1 pod each)
Коротко: k3s на VPS, один namespace, по одному Deployment, managed Postgres на облаке (отдельные БД). Ничего не хранится локально, только стейт в БД и метрики в Prometheus.
Пререквизиты
- VPS c Ubuntu 22.04+, 2 vCPU/4GB+.
- k3s без traefik (используем встроенный Service/Ingress по вкусу).
- Managed Postgres x2 (sla_db, dispute_db), security group разрешает доступ с VPS.
- Доступ к RPC через
rpc-gateway-rotatorURL.
Установка k3s (пример)
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik" sh -
kubectl create ns escrow-obs
Секреты (подставь URI)
kubectl -n escrow-obs create secret generic sla-secrets \
--from-literal=DB_DSN=postgres://user:pass@host:5432/sla_db?sslmode=require \
--from-literal=RPC_URL=https://rpc-gw.internal \
--from-literal=WS_URL=wss://rpc-gw.internal/ws
kubectl -n escrow-obs create secret generic dispute-secrets \
--from-literal=DB_DSN=postgres://user:pass@host:5432/dispute_db?sslmode=require \
--from-literal=RPC_URL=https://rpc-gw.internal \
--from-literal=WS_URL=wss://rpc-gw.internal/ws
Минимальный манифест (оба сервиса)
apiVersion: apps/v1
kind: Deployment
metadata: {name: sla-monitor, namespace: escrow-obs}
spec:
replicas: 1
selector: {matchLabels: {app: sla-monitor}}
template:
metadata: {labels: {app: sla-monitor}}
spec:
containers:
- name: app
image: registry/sla-monitor:TAG
envFrom: [{secretRef: {name: sla-secrets}}]
ports: [{containerPort: 8080, name: http}]
readinessProbe: {httpGet: {path: /ready, port: http}, periodSeconds: 10}
livenessProbe: {httpGet: {path: /health, port: http}, periodSeconds: 20}
resources: {requests: {cpu: "200m", memory: "256Mi"}, limits: {cpu: "500m", memory: "512Mi"}}
---
apiVersion: v1
kind: Service
metadata: {name: sla-monitor, namespace: escrow-obs}
spec:
selector: {app: sla-monitor}
ports: [{port: 80, targetPort: http}]
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: dispute-indexer, namespace: escrow-obs}
spec:
replicas: 1
selector: {matchLabels: {app: dispute-indexer}}
template:
metadata: {labels: {app: dispute-indexer}}
spec:
containers:
- name: app
image: registry/dispute-indexer:TAG
envFrom: [{secretRef: {name: dispute-secrets}}]
ports: [{containerPort: 8080, name: http}]
readinessProbe: {httpGet: {path: /ready, port: http}, periodSeconds: 10}
livenessProbe: {httpGet: {path: /health, port: http}, periodSeconds: 20}
resources: {requests: {cpu: "200m", memory: "256Mi"}, limits: {cpu: "500m", memory: "512Mi"}}
---
apiVersion: v1
kind: Service
metadata: {name: dispute-indexer, namespace: escrow-obs}
spec:
selector: {app: dispute-indexer}
ports: [{port: 80, targetPort: http}]
Экспонирование
- Для приватки хватит
kubectl port-forwardили internal LB. - Для внешки: k3s ingress + TLS (Caddy/NGINX) → сервисы выше.
Наблюдаемость
- /metrics в Prometheus; алерты: ingest_lag_blocks, alert_latency_seconds, rpc_errors_total.
- /recent-violations и /events должны отвечать после старта (smoke).
Апдейт
kubectl -n escrow-obs set image deploy/sla-monitor app=registry/sla-monitor:NEW
kubectl -n escrow-obs set image deploy/dispute-indexer app=registry/dispute-indexer:NEW
Bridge service docs
Цель: держать флоу и решения по bridge (кросс-чейн свопы, интеграции) в одном месте.
Состав
flows/— флоу bridge.decisions/— ADR/мини-решения.problems/— локальные инциденты/руткозы.diagrams/— Mermaid/PlantUML источники.
Как обновлять
- Любое решение → мини-ADR: Context / Decision / Consequences / Links / Next.
- Флоу храним здесь; сквозные темы — в корневом
flows/. - Ссылки на код/контракты, без копипасты.
Next
- Добавить первый флоу или ADR, когда появится реализация bridge.
Cross-service flows
Цель: собирать сквозные флоу, затрагивающие несколько сервисов (wallet/escrow/bridge).
Структура
- Один файл — один сквозной флоу.
- Формат: Goal → Actors → Steps (off/on-chain) → Events/Timers → Risks/Next.
Next
- Добавить первый сквозной флоу (пример: onramp/offramp через escrow + wallet).