syntax = "proto3";

// Пакет gRPC API микросервиса Ledger (внутренний баланс, депозиты, выводы, лента операций).
// Реализация: ledger_microservice; клиенты: gateway, mpc_service и др.
package ledger.v1;

option csharp_namespace = "Ledger.V1";
option java_multiple_files = true;
option java_package = "com.arbiwallet.ledger.v1";

service LedgerService {
  // Регистрация депозитного адреса: привязка on-chain адреса к пользователю, активу и сети (до зачисления средств).
  rpc RegisterDepositAddress(RegisterDepositAddressRequest) returns (RegisterDepositAddressResponse);
  // Зачисление средств на доступный баланс пользователя (подтверждённый депозит); идемпотентность по ключу и внешнему id операции.
  rpc CreditUserBalance(CreditUserBalanceRequest) returns (CreditUserBalanceResponse);
  // Зачисление депозита в резерв (AML review): total и reserved растут, available без изменений.
  rpc CreditDepositHold(CreditDepositHoldRequest) returns (CreditDepositHoldResponse);
  // Перевод суммы из резерва в available (админ: HELD → зачислено).
  rpc ReleaseDepositHold(ReleaseDepositHoldRequest) returns (ReleaseDepositHoldResponse);
  // Списание из резерва и уменьшение total без возврата в available (блокировка / compliance).
  rpc ForfeitDepositHold(ForfeitDepositHoldRequest) returns (ForfeitDepositHoldResponse);
  // Создание записи о выводе в учёте (черновик заявки); дальнейшие шаги — резерв, смена статусов, финализация.
  rpc RegisterWithdrawal(RegisterWithdrawalRequest) returns (RegisterWithdrawalResponse);
  // Блокировка (резерв) средств на балансе под конкретный вывод после прохождения проверок.
  rpc ReserveFunds(ReserveFundsRequest) returns (ReserveFundsResponse);
  // Снятие резерва (отмена вывода, ошибка custody и т.п.) с возвратом средств в available.
  rpc ReleaseFunds(ReleaseFundsRequest) returns (ReleaseFundsResponse);
  // Фиксация успешного вывода в сеть: списание из резерва, сохранение tx hash.
  rpc FinalizeWithdrawal(FinalizeWithdrawalRequest) returns (FinalizeWithdrawalResponse);
  // Обновление статуса вывода по внутреннему id или внешнему id custody (BitGo transfer id); переходы валидируются по правилам ledger.
  rpc UpdateWithdrawalStatus(UpdateWithdrawalStatusRequest) returns (UpdateWithdrawalStatusResponse);
  // Снимок баланса пользователя по одной паре (asset, network): available / reserved / total.
  rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse);
  // Все балансовые позиции пользователя по всем активам и сетям.
  rpc GetBalances(GetBalancesRequest) returns (GetBalancesResponse);
  // Балансовые позиции сразу для массива пользователей.
  rpc GetBalancesBatch(GetBalancesBatchRequest) returns (GetBalancesBatchResponse);
  // Курсы поддерживаемых активов к USD (внешний провайдер, кэш в Ledger); без user_id — публичная справка для UI.
  rpc GetFxRatesUsd(GetFxRatesUsdRequest) returns (GetFxRatesUsdResponse);
  // Постраничная лента операций пользователя (история для UI); курсор и offset — на усмотрение сервера.
  rpc GetTransactionFeed(GetTransactionFeedRequest) returns (GetTransactionFeedResponse);
  // Идемпотентное добавление внешнего события в UI-ленту (например card provider events).
  rpc AppendTransactionFeedItem(AppendTransactionFeedItemRequest) returns (TransactionFeedItem);
  // Админский список всех транзакций с пагинацией/фильтрами/поиском.
  rpc ListTransactions(ListTransactionsRequest) returns (ListTransactionsResponse);
  // Внутренний перевод между пользователями приложения (только ledger, без сетевой комиссии).
  rpc InternalTransfer(InternalTransferRequest) returns (InternalTransferResponse);
}

message RegisterDepositAddressRequest {
  string user_id = 1;              // Идентификатор пользователя (строка, часто decimal bigint).
  string address = 2;              // On-chain адрес приёма.
  string asset = 3;                // Код актива, напр. USDT.
  string network = 4;              // Сеть, напр. TRON, BSC.
  optional string custody_wallet_id = 5; // Опционально: id кошелька/аккаунта в custody.
}

message RegisterDepositAddressResponse {
  string id = 1;                   // Id записи адреса в ledger.
  string user_id = 2;
  string address = 3;
  string asset = 4;
  string network = 5;
  optional string custody_wallet_id = 6;
  string created_at = 7;           // ISO 8601.
}

message CreditUserBalanceRequest {
  string user_id = 1;
  string asset = 2;
  string network = 3;
  string amount = 4;               // Десятичная строка (фиксированная точность по правилам сервиса).
  string tx_hash = 5;            // Идентификатор транзакции в сети или суррогат для идемпотентности.
  string external_operation_id = 6; // Внешний id операции (custody, провайдер).
  string idempotency_key = 7;      // Ключ идемпотентности вызова.
  optional int32 confirmations = 8;   // Опционально: число подтверждений на момент зачисления.
  optional int32 log_index = 9;     // Для EVM: индекс лога; для TRON — 0 или не задано.
}

message CreditUserBalanceResponse {
  string deposit_id = 1;           // Id созданной/найденной записи депозита.
  string ledger_operation_id = 2; // Id ledger-операции.
  string available = 3;            // Доступно после операции.
  string reserved = 4;
  string total = 5;
  optional string transaction_feed_item_id = 6; // id строки ленты (DEPOSIT), для сокетов / UI.
}

message CreditDepositHoldRequest {
  string user_id = 1;
  string asset = 2;
  string network = 3;
  string amount = 4;
  string tx_hash = 5;
  string external_operation_id = 6;
  string idempotency_key = 7;
  optional int32 confirmations = 8;
  optional int32 log_index = 9;
}

message CreditDepositHoldResponse {
  string deposit_id = 1;
  string ledger_operation_id = 2;
  string available = 3;
  string reserved = 4;
  string total = 5;
  optional string transaction_feed_item_id = 6;
}

message ReleaseDepositHoldRequest {
  string user_id = 1;
  string external_operation_id = 2;
  string idempotency_key = 3;
}

message ReleaseDepositHoldResponse {
  string deposit_id = 1;
  string ledger_operation_id = 2;
  string available = 3;
  string reserved = 4;
  string total = 5;
  optional string transaction_feed_item_id = 6;
}

message ForfeitDepositHoldRequest {
  string user_id = 1;
  string external_operation_id = 2;
  string idempotency_key = 3;
  string reason = 4;
}

message ForfeitDepositHoldResponse {
  string deposit_id = 1;
  string ledger_operation_id = 2;
  string available = 3;
  string reserved = 4;
  string total = 5;
  optional string transaction_feed_item_id = 6;
}

message RegisterWithdrawalRequest {
  string user_id = 1;
  string asset = 2;
  string network = 3;
  string amount = 4;
  string destination_address = 5;  // Адрес получателя в сети.
  string request_id = 6;           // Внешний id заявки (idempotency с точки зрения продукта).
  optional string external_withdrawal_id = 7; // Id трансфера в custody (BitGo), если уже известен.
  string service_fee = 8;          // Комиссия сервиса, строка decimal.
  string network_fee = 9;          // Сетевая комиссия, строка decimal.
  string idempotency_key = 10;     // Ключ идемпотентности вызова RegisterWithdrawal.
}

message RegisterWithdrawalResponse {
  string withdrawal_id = 1;      // Внутренний id вывода в ledger.
  string status = 2;               // Текущий статус (enum строкой, см. реализацию).
  optional string transaction_feed_item_id = 3; // Устар.; первая строка ленты — после ReserveFunds (WITHDRAWAL_RESERVED).
}

message ReserveFundsRequest {
  string user_id = 1;
  string asset = 2;
  string network = 3;
  string amount = 4;
  string withdrawal_id = 5;        // Внутренний id вывода, под который резервируются средства.
  string idempotency_key = 6;
}

message ReserveFundsResponse {
  string ledger_operation_id = 1;
  string available = 2;
  string reserved = 3;
  string total = 4;
  // id строки ленты (WITHDRAWAL_RESERVED), после RegisterWithdrawal без WITHDRAWAL_CREATED.
  optional string transaction_feed_item_id = 5;
}

message ReleaseFundsRequest {
  string withdrawal_id = 1;
  string reason = 2;               // Причина снятия резерва (аудит).
  string idempotency_key = 3;
}

message ReleaseFundsResponse {
  string ledger_operation_id = 1;
  string available = 2;
  string reserved = 3;
  string total = 4;
}

message FinalizeWithdrawalRequest {
  string withdrawal_id = 1;
  string tx_hash = 2;
  string finalized_at = 3;         // ISO 8601 время финализации.
  string idempotency_key = 4;
}

message FinalizeWithdrawalResponse {
  string ledger_operation_id = 1;
  string available = 2;
  string reserved = 3;
  string total = 4;
}

message UpdateWithdrawalStatusRequest {
  oneof selector {
    string withdrawal_id = 1;      // Внутренний id вывода в ledger.
    string external_withdrawal_id = 2; // Id в custody (например BitGo transfer id).
  }
  string new_status = 3;           // Целевой статус (строка enum Prisma/ledger).
  optional string tx_hash = 4;
  optional string raw_payload_json = 5; // Сырой JSON от custody/webhook для аудита.
  // Тот же request_id, что при RegisterWithdrawal (UUID заявки). Нужен, если в Ledger
  // external_withdrawal_id = request_id от withdrawal_service, а custody шлёт BitGo transfer id.
  optional string withdraw_request_id = 6;
}

message UpdateWithdrawalStatusResponse {
  string withdrawal_id = 1;
  string status = 2;
  bool finalized = 3;              // Была ли выполнена финализация в рамках этого вызова.
  bool released = 4;               // Был ли снят резерв (отмена/ошибка).
}

message GetBalanceRequest {
  string user_id = 1;
  string asset = 2;
  string network = 3;
}

message GetBalanceResponse {
  string user_id = 1;
  string asset = 2;
  string network = 3;
  string available = 4;
  string reserved = 5;
  string total = 6;
}

message GetBalancesRequest {
  string user_id = 1;
}

message BalanceRow {
  string user_id = 1;
  string asset = 2;
  string network = 3;
  string available = 4;
  string reserved = 5;
  string total = 6;
}

message GetBalancesResponse {
  repeated BalanceRow balances = 1;
}

message GetBalancesBatchRequest {
  repeated string user_ids = 1;
}

message UserBalancesRow {
  string user_id = 1;
  repeated BalanceRow balances = 2;
}

message GetBalancesBatchResponse {
  repeated UserBalancesRow users = 1;
}

message GetFxRatesUsdRequest {}

message FxRateUsdRow {
  string asset = 1;
  string price_usd = 2;
}

message GetFxRatesUsdResponse {
  string quote_currency = 1;
  string updated_at = 2;
  bool stale = 3;
  repeated FxRateUsdRow rates = 4;
}

message TransactionFeedItem {
  string id = 1;
  string type = 2;                 // Тип события ленты (строка, см. TransactionFeedType в реализации).
  string asset = 3;
  string network = 4;
  optional string amount = 5;
  optional string reference_type = 6;
  optional string reference_id = 7;
  optional string title = 8;
  string created_at = 9;
  optional string metadata_json = 10; // JSON-строка с доп. полями.
}

message GetTransactionFeedRequest {
  string user_id = 1;
  int32 limit = 2;
  optional string cursor_id = 3;   // Курсор для следующей страницы.
  optional int32 offset = 4;
  optional string asset = 5;       // Фильтр по активу, напр. USDT.
  optional string network = 6;     // Фильтр по сети, напр. TRON, BSC.
  // Фильтр для UI: "wallet" | "card" | "withdrawal" | "deposit" | "internal_transfer".
  // wallet — UNION transaction_feed_items + card_operation; card — только card_operation (+ reference_id = cardId).
  // deposit включает записи reference_type deposit и deposit_hold.
  optional string reference_kind = 7;
  // true: все события по выводу (RESERVED, WITHDRAWAL_STATUS, FINALIZED, …). По умолчанию — одна строка на вывод (последнее событие).
  optional bool expand_withdrawal_timeline = 8;
  optional string reference_id = 9;
}

message GetTransactionFeedResponse {
  repeated TransactionFeedItem items = 1;
  optional string next_cursor_id = 2;
  int32 total_returned = 3;        // Число элементов в этом ответе.
}

message AppendTransactionFeedItemRequest {
  string user_id = 1;
  string type = 2;
  string asset = 3;
  string network = 4;
  optional string amount = 5;
  optional string reference_type = 6;
  optional string reference_id = 7;
  optional string title = 8;
  optional string metadata_json = 9;
  string idempotency_key = 10;
  optional string created_at = 11;
}

enum TransactionSortBy {
  TRANSACTION_SORT_BY_CREATED_AT = 0;
  TRANSACTION_SORT_BY_TYPE = 1;
  TRANSACTION_SORT_BY_ASSET = 2;
  TRANSACTION_SORT_BY_NETWORK = 3;
  TRANSACTION_SORT_BY_AMOUNT = 4;
}

enum TransactionSortOrder {
  TRANSACTION_SORT_ORDER_DESC = 0;
  TRANSACTION_SORT_ORDER_ASC = 1;
}

message ListTransactionsRequest {
  optional int32 page = 1;
  optional int32 limit = 2;
  optional TransactionSortBy sort_by = 3;
  optional TransactionSortOrder order = 4;
  optional string search = 5;
  optional string type_value = 6;
  optional string asset_value = 7;
  optional string network_value = 8;
  optional string user_id = 9;
  optional string reference_type_value = 10;
}

message ListTransactionsResponse {
  repeated TransactionFeedItem items = 1;
  int32 all_count = 2;
  int32 page = 3;
  int32 limit = 4;
}

message InternalTransferRequest {
  string from_user_id = 1;
  string to_user_id = 2;
  string asset = 3;
  // Необязательно: если пусто — сумма делится поровну между всеми сетями, где у отправителя есть available по активу.
  string network = 4;
  string amount = 5;               // Десятичная строка; списание с available отправителя.
  string idempotency_key = 6;
  // Как задан получатель на стороне Gateway: user_id | uid | email | phone | telegram
  string recipient_kind = 7;
  // Введённое пользователем значение
  string recipient_value = 8;
}

message InternalTransferResponse {
  string transfer_id = 1;
  string sender_ledger_operation_id = 2;
  string receiver_ledger_operation_id = 3;
  // Агрегат по всем сетям для актива (после перевода), не только по одной сети.
  string sender_available = 4;
  string sender_reserved = 5;
  string sender_total = 6;
  string receiver_available = 7;
  string receiver_reserved = 8;
  string receiver_total = 9;
  // id строки TransactionFeedItem у отправителя (INTERNAL_TRANSFER_SENT) — для истории / деталей
  string transaction_id = 10;
}
