Core ConceptsЖурнал (двойная запись)

Журнал (двойная запись)

Каждое движение средств — сбалансированные проводки, а баланс вычисляется из журнала. Available и reserved, исторические балансы, идемпотентность и неизменяемость.

В V3 Custody деньги — это не число, которое перезаписывается, а журнал. Любое движение средств записывается двойной записью, а баланс вычисляется из этих записей. Поэтому на вопрос «сколько и почему» вы отвечаете запросом, а не расследованием.

Проводки, которые всегда сходятся

Каждая запись журнала (entry) состоит из проводок (postings), и их сумма равна нулю. Одна сторона трогает счёт вашего адреса, другая — встречную сторону движения (ончейн-контур или корректировку). Ноль в сумме — это гарантия, что средства не появляются и не исчезают, а только перемещаются.

Журнал доступен только на чтение и неизменяем:

  • GET /api/v1/vaults/{code}/ledger/entries — immutable-записи с проводками (сумма постингов = 0). Сортировка по postedAt (сначала свежие), keyset-пагинация, фильтры from / to / token. Обычный участник видит только записи, затрагивающие его адреса; администратор — весь Vault.

Баланс вычисляется, а не хранится

Баланс — это свёртка журнала, а не отдельно хранимое число: он обновляется атомарно с проводками, поэтому не может разойтись с ними. Каждый баланс делится на два состояния:

  • available — свободные средства, которые можно тратить.
  • reserved — заблокировано под ожидающими переводами, но всё ещё принадлежит владельцу.

«Владеемый» баланс — это available + reserved.

  • GET /api/v1/vaults/{code}/balances — балансы Vault'а, пользователя или адреса; параметры summary, addressId, token. В ответе — агрегаты по токенам (totals).

Reserved: средства «в пути», но ваши

Когда создаётся перевод, сумма сначала резервируется — ещё до того, как транзакция уйдёт в сеть. Резерв защищает от двойной траты: те же деньги нельзя отправить дважды.

Дальше судьба резерва зависит от Policy Engine. Если перевод одобрен и подтверждён — резерв списывается. Если поставлен в очередь на подтверждение — сумма ждёт в reserved. Если отменён или отклонён — возвращается в available. Средства всё это время остаются вашими.

Баланс на любой момент прошлого

Поскольку журнал неизменяем, позицию можно восстановить на любую дату — из проводок, зафиксированных до этого момента:

  • GET /api/v1/vaults/{code}/balances/as-of?at=... — баланс на момент at, рассчитанный из проводок с postedAt <= at. Прошлое не меняется задним числом. «Владеемый» баланс = available + reserved; обычный участник видит только свои адреса.

Это то, что нужно для выписок на конец периода и сверок: ответ на «сколько было на 31 марта» всегда одинаковый.

Идемпотентность: один запрос — один платёж

У перевода есть idempotencyKey. Повтор запроса с тем же ключом вернёт уже созданную операцию, а не создаст новую. Значит, ретраи при сетевых сбоях безопасны — двойного платежа не будет.

Ничего не переписывается — только добавляется

Записи журнала не редактируются и не удаляются. Ошибку исправляют новой операцией, связанной с исходной, — так история сохраняется целиком:

  • POST /api/v1/vaults/{code}/operation-relations — явная связь: refund, reversal, correction, replacement. Встречный платёж не считается возвратом автоматически — связь указывается явно. Частичный возврат задаётся relatedAmountRawtokenId).

Плюс у каждой операции есть история статусов:

  • GET /api/v1/vaults/{code}/operations/{type}/{id}/status-history — хронология переходов статуса.

Лента операций и журнал — два взгляда

Журнал (проводки) — это бухгалтерская истина. Для человека же удобнее лента операций — бизнес-события:

  • GET /api/v1/vaults/{code}/history — ваша активность в Vault'е одной лентой (ваши переводы и депозиты на ваши адреса), по (createdAt, id) убыв., keyset. Фильтры: direction=in|out, from / to, статус (например, зависшие переводы).

Проводки отвечают на «как это отражено в учёте», лента — на «что произошло».

Суммы в журнале и балансах хранятся в минимальных единицах токена (raw), а показываются в десятичном виде по decimals сети. Поэтому в ответах встречаются и raw-, и десятичные значения сумм.

Что дальше