openapi: 3.1.0
info:
  title: 1st Node — Crypto Data & Intelligence API
  version: "1.1.0"
  description: >
    Единый API поверх собственных архивных нод и индексаторов: RAW JSON-RPC,
    канонические данные (блоки/транзакции), балансы и net-worth, цены (Chainlink),
    intelligence и AML. Один ключ, один баланс в кредитах (1 USD = 1000 кредитов).


    Аутентификация data-plane — заголовок `Authorization: Bearer sk_live_...`.
    Ответы содержат поле `_mock` (true, если данные пришли из детерминированного
    мока, а не из реального data-plane) и, где применимо, блок `derived`
    (источник + уверенность). Готовность сети зависит от двух независимых
    вещей: индексатора (ClickHouse-данные — canonical/address/intelligence/AML)
    и живой ноды (`/rpc`, `/tron/wallet/*`) — сеть может быть готова по одному
    и не готова по другому. Сеть без живой ноды отдаёт честную ошибку от `/rpc`
    вместо тихого простоя. `/rpc?chain=tron` работает как для любой EVM-сети
    (тот же контракт, без правок на стороне клиента): java-tron поддерживает
    TVM-совместимый `eth_*` JSON-RPC напрямую (модуль включён), тело
    пересылается ноде как есть, ответ — verbatim, включая `eth_getBalance`,
    `eth_call`, `eth_getLogs` и остальные стандартные методы. `value` в TRX
    — нативные единицы TRON (SUN), не отмасштабированы под wei. Если живая
    нода временно недоступна, `/rpc?chain=tron` откатывается на встроенный
    транслятор поверх нативного REST API той же ноды (покрывает базовые
    `eth_chainId`/`eth_blockNumber`/`eth_getBlockByNumber`/
    `eth_getTransactionByHash`/`eth_getTransactionReceipt`/`eth_call`, для
    остального — явная JSON-RPC-ошибка, не тихий сбой). `/tron/wallet/{method}`
    — прямой REST API ноды 1:1, для полного нативного доступа в любом случае.
  license:
    name: Proprietary
servers:
  - url: https://1st-node.com/v1
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - name: Core
    description: RAW JSON-RPC passthrough и канонические данные (блоки, транзакции)
  - name: Address
    description: Балансы и совокупная стоимость портфеля
  - name: Prices
    description: Спот-цены (Chainlink, mainnet)
  - name: Intelligence
    description: Кластеризация адресов
  - name: AML
    description: Скрининг и risk-score
  - name: Billing
    description: Баланс, пополнение (в т.ч. крипто-эквайринг), метеринг — session-auth (ЛК)
  - name: Payments
    description: Non-custodial крипто-эквайринг — xpub-деривация приёмных адресов, charge'и, подписанные webhooks
  - name: Custody
    description: Кошельки (custodial и watch_only) и выплаты по всем сетям (EVM / TRON / BTC), policy-гарды

paths:
  /rpc:
    post:
      tags: [Core]
      summary: JSON-RPC passthrough на архивную ноду выбранной сети
      description: >
        Тело запроса пересылается ноде как есть; ответ возвращается verbatim
        (включая `chain=tron` — java-tron отвечает TVM-совместимым `eth_*`
        JSON-RPC напрямую). Если живая нода временно недоступна для
        `chain=tron`, запрос откатывается на встроенный транслятор поверх
        нативного REST API той же ноды (см. описание API выше); метод, не
        покрытый транслятором, возвращает `200` с JSON-RPC `error` в теле
        (это не сбой — валидный ответ по спецификации JSON-RPC), а не `502`.
      operationId: rpcPassthrough
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/JsonRpcRequest' }
            examples:
              blockNumber:
                value: { jsonrpc: "2.0", id: 1, method: eth_blockNumber, params: [] }
      responses:
        '200':
          description: JSON-RPC ответ ноды (verbatim)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/JsonRpcResponse' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /tron/wallet/{method}:
    post:
      tags: [Core]
      summary: TronGrid-совместимый passthrough на нативный REST API TRON-ноды
      description: >
        TRON не говорит на Ethereum JSON-RPC — TRON-ноды (java-tron) и клиенты,
        написанные под них (в т.ч. большинство TRON SDK), общаются через REST
        `POST /wallet/<method>` (getnowblock, gettransactionbyid,
        broadcasttransaction, …). Это прямой аналог `/rpc` для сети `tron`:
        тело пересылается ноде как есть, ответ возвращается verbatim. `method`
        — только имя метода без `/wallet/` и без слэшей (`[a-zA-Z0-9_]`).
      operationId: tronWalletPassthrough
      parameters:
        - name: method
          in: path
          required: true
          schema: { type: string, pattern: '^[a-zA-Z0-9_]+$' }
          example: getnowblock
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object }
            examples:
              getnowblock: { value: {} }
      responses:
        '200':
          description: Ответ java-tron REST API (verbatim)
          content:
            application/json:
              schema: { type: object }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '502':
          description: >
            Провайдерская ошибка (как и везде в API) — сюда же попадает
            невалидный `method` (не `[a-zA-Z0-9_]+`), не только сбой ноды.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /core/block:
    get:
      tags: [Core]
      summary: Блок сети — последний, либо по number/hash через ?ref=
      operationId: getBlock
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: ref
          in: query
          description: Опционально — номер блока (десятичный) или 0x-хеш. Без ref возвращается последний проиндексированный блок.
          schema: { type: string }
      responses:
        '200':
          description: Блок (полная форма при ?ref=, краткая для последнего)
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Block'
                  - $ref: '#/components/schemas/BlockFull'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '402': { $ref: '#/components/responses/InsufficientCredits' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /core/blocks:
    get:
      tags: [Core]
      summary: Диапазон блоков [from,to] (newest-first, макс. 1000)
      operationId: getBlocks
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: from
          in: query
          required: true
          schema: { type: integer, format: int64, minimum: 0 }
        - name: to
          in: query
          required: true
          schema: { type: integer, format: int64, minimum: 0 }
      responses:
        '200':
          description: Заголовки блоков (span > 1000 усекается к from+999)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BlocksPage' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /core/block/receipts:
    get:
      tags: [Core]
      summary: Receipts всех транзакций блока (status/gas + логи)
      operationId: getBlockReceipts
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: number
          in: query
          required: true
          schema: { type: integer, format: int64, minimum: 0 }
      responses:
        '200':
          description: Receipts по индексу транзакции
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BlockReceipts' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /core/tx:
    get:
      tags: [Core]
      summary: Транзакция по хешу
      operationId: getTransaction
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: hash
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Транзакция
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Transaction' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /core/tx/logs:
    get:
      tags: [Core]
      summary: Событийные логи транзакции
      operationId: getTransactionLogs
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: hash
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Упорядоченные логи (по log_index)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TxLogs' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /core/tx/transfers:
    get:
      tags: [Core]
      summary: Движения активов транзакции (токены + нативная монета)
      operationId: getTransactionTransfers
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: hash
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Декодированные трансферы транзакции
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TxTransfers' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/balances:
    get:
      tags: [Address]
      summary: Балансы адреса (native + ERC-20 из индексированных трансферов)
      operationId: getAddressBalances
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
      responses:
        '200':
          description: Балансы
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Balances' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/net-worth:
    get:
      tags: [Address]
      summary: Совокупная стоимость портфеля в USD
      operationId: getAddressNetWorth
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
      responses:
        '200':
          description: Net worth
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NetWorth' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/transactions:
    get:
      tags: [Address]
      summary: Транзакции адреса (отправленные и полученные), most-recent-first
      description: >
        Страница транзакций, в которых адрес участвовал как отправитель
        (direction=out) или получатель (direction=in). Пагинация курсором по
        block_number: next_cursor из ответа передаётся в ?cursor= следующего
        запроса. limit 1..200 (по умолчанию 50).
      operationId: getAddressTransactions
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Страница транзакций
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AddressActivity' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/transfers:
    get:
      tags: [Address]
      summary: Токен-трансферы адреса (ERC-20/721/1155, TRC-20), most-recent-first
      operationId: getAddressTransfers
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - name: direction
          in: query
          description: Фильтр направления. in — входящие, out — исходящие, both/пусто — оба.
          schema: { type: string, enum: [in, out, both], default: both }
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Страница токен-трансферов
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AddressTransfers' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/counterparties:
    get:
      tags: [Address]
      summary: Топ-контрагенты адреса (по числу токен-трансферов), с лейблами
      operationId: getAddressCounterparties
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - name: direction
          in: query
          description: out — кому отправлял, in — от кого получал, both/пусто — оба.
          schema: { type: string, enum: [in, out, both], default: both }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Контрагенты (обогащены entity/category/risky при наличии лейбла)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Counterparties' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/stats:
    get:
      tags: [Address]
      summary: Сводка активности адреса (счётчики, first/last seen, uniq)
      operationId: getAddressStats
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
      responses:
        '200':
          description: Статистика адреса
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AddressStats' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/portfolio:
    get:
      tags: [Address]
      summary: Портфель адреса с оценкой USD (native + токены)
      operationId: getAddressPortfolio
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
      responses:
        '200':
          description: Холдинги + суммарная стоимость USD (mainnet Chainlink; иначе 0 с честной provenance)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Portfolio' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /token/meta:
    get:
      tags: [Tokens]
      summary: Метаданные токена (symbol/decimals/standard/supply)
      operationId: getTokenMeta
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: address
          in: query
          required: true
          description: Адрес токен-контракта.
          schema: { type: string }
      responses:
        '200':
          description: Метаданные (found=false, если токен неизвестен индексу)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokenMeta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /token/holders:
    get:
      tags: [Tokens]
      summary: Топ-холдеры токена по чистому балансу (из индексир. трансферов)
      operationId: getTokenHolders
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: address
          in: query
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Холдеры с положительным net-балансом, по убыванию
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokenHolders' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /token/transfers:
    get:
      tags: [Tokens]
      summary: История трансферов токена (newest-first)
      operationId: getTokenTransfers
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: address
          in: query
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Страница трансферов токена
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokenTransfers' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /wallets/net-worth:
    get:
      tags: [Address]
      summary: Мультичейн net-worth для набора адресов (в один вызов)
      operationId: getWalletsNetWorth
      parameters:
        - name: addresses
          in: query
          required: true
          description: Адреса через запятую (до 25).
          schema: { type: string }
      responses:
        '200':
          description: Net-worth по адресам с разбивкой по сетям
          content:
            application/json:
              schema: { $ref: '#/components/schemas/WalletNetWorth' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /dex/trades:
    get:
      tags: [DeFi]
      summary: Свопы пула (Uniswap V2/V3 и форки) — декод из индексированных логов
      description: >
        Swap-события пула декодируются на лету из logs (V2/V3 сигнатуры).
        amount0/amount1 — знаковые дельты токенов с точки зрения пула
        (плюс — в пул, минус — из пула). Отдельный DEX-индексер не нужен.
      operationId: getDexTrades
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: pool
          in: query
          required: true
          description: Адрес пула (пары).
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Страница свопов пула
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DexTradesResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /dex/pool:
    get:
      tags: [DeFi]
      summary: Резервы V2-пула (из последнего Sync-лога)
      operationId: getDexPool
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: pool
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: reserve0/reserve1 (found=false для V3/без Sync)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/DexPoolResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /nft/collection:
    get:
      tags: [NFT]
      summary: Метаданные коллекции (on-chain name/symbol)
      description: >
        name()/symbol() коллекции, прочитанные с наших нод (сайдкар nftmetasync).
        found=false, пока сайдкар не синхронизировал коллекцию.
      operationId: getNftCollection
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: collection
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Метаданные коллекции
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NftCollectionMeta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /nft/collection/stats:
    get:
      tags: [NFT]
      summary: Статистика NFT-коллекции (transfers/unique/mints/burns)
      operationId: getNftCollectionStats
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: collection
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Агрегаты коллекции
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NftCollectionStats' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /token/allowances:
    get:
      tags: [Tokens]
      summary: Текущие allowances токена (owner->spender, ненулевые)
      operationId: getTokenAllowances
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: address
          in: query
          required: true
          description: Адрес токен-контракта.
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Живые разрешения на токен
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokenAllowancesResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /lending/events:
    get:
      tags: [DeFi]
      summary: События Aave V3 (supply/withdraw/borrow/repay) из логов
      operationId: getLendingEvents
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Лента событий кредитования, newest-first
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LendingEventsResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /lending/liquidations:
    get:
      tags: [DeFi]
      summary: Ликвидации Aave V3 (декод LiquidationCall из логов)
      operationId: getLiquidations
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Страница ликвидаций (newest-first)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LiquidationsResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /dex/ohlcv:
    get:
      tags: [DeFi]
      summary: OHLCV-свечи пула (из свопов, raw token1/token0 ratio)
      description: >
        Свечи цены пула из декодированных Swap-логов. Цена — raw-отношение
        |amount1|/|amount0|; decimals — постоянный масштаб, форма свечи корректна,
        клиент домасштабирует по decimals токенов. Volume — сумма |amount0|.
      operationId: getDexOHLCV
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: pool
          in: query
          required: true
          schema: { type: string }
        - name: interval
          in: query
          schema: { type: string, enum: [minute, hour, day, week], default: day }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Свечи, newest-first
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OHLCVResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /dex/sandwiches:
    get:
      tags: [DeFi]
      summary: MEV-сэндвичи пула (детекция из Swap-логов)
      description: >
        В одном блоке на одном пуле атакующий свопает до и после жертвы (в
        противоположных направлениях), «зажимая» её. Детектится из тех же Swap-логов.
      operationId: getDexSandwiches
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: pool
          in: query
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Обнаруженные сэндвичи
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SandwichesResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /nft/transfers:
    get:
      tags: [NFT]
      summary: Переводы NFT-коллекции (ERC-721/1155) — из token_transfers
      operationId: getNftTransfers
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: collection
          in: query
          required: true
          description: Адрес NFT-контракта.
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Страница переводов коллекции
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NftTransfersResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /nft/holders:
    get:
      tags: [NFT]
      summary: Текущие держатели ERC-721 коллекции (владелец = последний получатель)
      operationId: getNftHolders
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: collection
          in: query
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Держатели по числу токенов (по убыванию)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NftHoldersResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /metrics/catalog:
    get:
      tags: [Metrics]
      summary: Список доступных on-chain метрик
      operationId: getMetricsCatalog
      responses:
        '200':
          description: Каталог метрик
          content:
            application/json:
              schema:
                type: object
                properties:
                  metrics:
                    type: array
                    items: { $ref: '#/components/schemas/MetricInfo' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /metrics:
    get:
      tags: [Metrics]
      summary: Дневной ряд on-chain метрики (агрегация по индексу)
      description: >
        Метрики считаются на лету по уже проиндексированным таблицам
        (transactions/token_transfers/native_transfers). Список ключей — в
        /metrics/catalog. Пример: metric=active_addresses.
      operationId: getMetric
      parameters:
        - name: metric
          in: query
          required: true
          description: Ключ метрики (см. /metrics/catalog).
          schema: { type: string }
        - $ref: '#/components/parameters/ChainQuery'
        - name: from
          in: query
          schema: { type: string, format: date }
        - name: to
          in: query
          schema: { type: string, format: date }
      responses:
        '200':
          description: Ряд значений по дням
          content:
            application/json:
              schema: { $ref: '#/components/schemas/MetricSeries' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /btc/address/balance:
    get:
      tags: [BTC]
      summary: Net-баланс UTXO-адреса (received − spent, сатоши)
      operationId: getBtcAddressBalance
      parameters:
        - $ref: '#/components/parameters/BtcChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
      responses:
        '200':
          description: Баланс (сатоши-строки, точные для крупных DOGE-адресов)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BtcBalance' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /btc/address/transactions:
    get:
      tags: [BTC]
      summary: Транзакции UTXO-адреса (newest-first)
      operationId: getBtcAddressTransactions
      parameters:
        - $ref: '#/components/parameters/BtcChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Страница транзакций (direction in/out/in,out)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BtcAddrActivity' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /btc/tx:
    get:
      tags: [BTC]
      summary: UTXO-транзакция с входами и выходами
      operationId: getBtcTx
      parameters:
        - $ref: '#/components/parameters/BtcChainQuery'
        - name: txid
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Транзакция (found=false, если не найдена)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BtcTxDetail' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /core/gas:
    get:
      tags: [Core]
      summary: Оценка gas по base_fee последнего блока
      operationId: getGas
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
      responses:
        '200':
          description: base_fee + оценочные тиры (эстимейт, не mempool-оракул)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GasResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/nfts:
    get:
      tags: [Address]
      summary: NFT-холдинги адреса (ERC-721 коллекции)
      operationId: getAddressNfts
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Коллекции с числом токенов
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AddressNFTsResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/history:
    get:
      tags: [Address]
      summary: Единая лента активности (токен + native), in/out
      operationId: getAddressHistory
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Лента движений, newest-first
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AddressHistoryResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /token/spam-check:
    get:
      tags: [Tokens]
      summary: Эвристика spam/airdrop для токена
      operationId: getTokenSpamCheck
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: address
          in: query
          required: true
          description: Адрес токен-контракта.
          schema: { type: string }
      responses:
        '200':
          description: Флаг is_spam + причины + метрики распределения
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokenSpamResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /search:
    get:
      tags: [Search]
      summary: Универсальный поиск (адрес/tx/блок/токен)
      operationId: getSearch
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: q
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Типизированные совпадения
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SearchResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /address/approvals:
    get:
      tags: [Address]
      summary: Активные ERC-20 allowances адреса (value-at-risk)
      operationId: getAddressApprovals
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Текущие ненулевые разрешения (token+spender)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ApprovalsResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /dex/new-pools:
    get:
      tags: [DeFi]
      summary: Новые пулы (PairCreated/PoolCreated) — sniper-фид
      operationId: getDexNewPools
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Недавно созданные пулы, newest-first
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NewPoolsResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /dex/pools:
    get:
      tags: [DeFi]
      summary: Пулы, включающие токен (token0/token1)
      description: >-
        Токен-центричный поиск пулов по реестру xchain.dex_pools. В отличие от
        /dex/new-pools (хронологическая лента создания из factory-событий), сюда
        попадают и пулы, разрешённые on-chain (eth_call token0/token1), чьё
        событие создания предшествует проиндексированному диапазону — например,
        старые высоколиквидные пары. Поле source различает происхождение.
      operationId: getDexPools
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: token
          in: query
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Пулы с данным токеном
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NewPoolsResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /token/holders/stats:
    get:
      tags: [Tokens]
      summary: Распределение держателей токена (концентрация top-1/top-10)
      operationId: getTokenHoldersStats
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: address
          in: query
          required: true
          description: Адрес токен-контракта.
          schema: { type: string }
      responses:
        '200':
          description: Число держателей + концентрация
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TokenHoldersStats' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /intel/token/who-bought-sold:
    get:
      tags: [Intelligence]
      summary: Топ-покупатели и продавцы токена (по объёму)
      operationId: getWhoBoughtSold
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: token
          in: query
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Buyers/sellers по кумулятивному объёму
          content:
            application/json:
              schema: { $ref: '#/components/schemas/WhoBoughtSold' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /prices:
    get:
      tags: [Prices]
      summary: Спот-цены в USD (Chainlink, mainnet)
      operationId: getPrices
      parameters:
        - name: symbols
          in: query
          description: Список символов через запятую, напр. `ETH,USDC,WBTC`
          schema: { type: string }
      responses:
        '200':
          description: Цены
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Prices' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /market/price-history:
    get:
      tags: [Prices]
      summary: Дневной USD-ряд цены актива (close)
      description: >
        Историческая дневная цена (USD close) актива по символу (ETH, USDC, …) из
        индексированного ряда xchain.prices. O/H/L/V недоступны (в хранилище только
        дневной close) — возвращается честный ряд точек, а не синтетические свечи.
      operationId: getMarketPriceHistory
      parameters:
        - name: asset
          in: query
          required: true
          description: Символ актива, напр. ETH.
          schema: { type: string }
        - $ref: '#/components/parameters/ChainQuery'
        - name: from
          in: query
          description: Начало диапазона (YYYY-MM-DD).
          schema: { type: string, format: date }
        - name: to
          in: query
          description: Конец диапазона (YYYY-MM-DD).
          schema: { type: string, format: date }
      responses:
        '200':
          description: Ряд дневных цен
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PriceHistory' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /intelligence/cluster:
    get:
      tags: [Intelligence]
      summary: Кластер адреса (эвристики co-spend)
      operationId: getCluster
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/ChainQuery'
      responses:
        '200':
          description: Кластер
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Cluster' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /intelligence/graph:
    get:
      tags: [Intelligence]
      summary: Граф движения средств (multi-hop, назад/вперёд)
      description: >
        Трейсит поток средств вокруг адреса по индексированным переводам:
        назад (in — кто финансировал) и/или вперёд (out — куда ушло), на
        заданное число поколений. Fan-out ограничен top-K контрагентов на узел,
        общий обход — глобальным лимитом узлов (флаг truncated). Покрытие: ERC-20/
        TRC-20 на индексируемых сетях (native/UTXO пока не индексируются).
      operationId: getGraphTrace
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/ChainQuery'
        - name: direction
          in: query
          schema: { type: string, enum: [in, out, both], default: both }
        - name: generations
          in: query
          description: Глубина обхода в поколениях (1..6).
          schema: { type: integer, minimum: 1, maximum: 6, default: 2 }
        - name: fanout
          in: query
          description: Максимум контрагентов на узел (top-K по числу переводов).
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: min_transfers
          in: query
          description: Порог отсечения пыли — минимум переводов на ребро.
          schema: { type: integer, minimum: 1, default: 1 }
        - name: assets
          in: query
          description: Класс активов для трассировки. all — токены + нативная монета; token — только ERC-20/TRC-20; native — только нативная монета.
          schema: { type: string, enum: [all, token, native], default: all }
      responses:
        '200':
          description: Граф (узлы + рёбра)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GraphTrace' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /intelligence/labels:
    get:
      tags: [Intelligence]
      summary: Лейбл адреса из реестра (entity/category/source/confidence)
      operationId: getAddressLabel
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
      responses:
        '200':
          description: Атрибуция (found=false, если адрес неизвестен реестру)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/LabelInfo' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /intel/smart-money:
    get:
      tags: [Intelligence]
      summary: Лидерборд smart-money (realized net USD flow)
      description: >
        Топ-адреса по чистому реализованному USD-потоку (продано − куплено по
        дневным ценам) над прайсуемыми токенами. Сигнал-ранжирование, не
        верифицированный PnL и не AML-метка. Наполняется сайдкаром smartmoneysync.
      operationId: getSmartMoney
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Лидерборд
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SmartMoneyResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /intel/token/first-buyers:
    get:
      tags: [Intelligence]
      summary: Ранние покупатели токена (по времени первого получения)
      operationId: getTokenFirstBuyers
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: token
          in: query
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Адреса-первополучатели, oldest-first
          content:
            application/json:
              schema: { $ref: '#/components/schemas/FirstBuyersResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /intel/holders-overlap:
    get:
      tags: [Intelligence]
      summary: Пересечение держателей набора токенов
      description: >
        Адреса, держащие (net-баланс > 0) не менее `min` из перечисленных токенов.
        Полезно для поиска общей аудитории набора токенов.
      operationId: getHoldersOverlap
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - name: tokens
          in: query
          required: true
          description: Адреса токенов через запятую (>=2).
          schema: { type: string }
        - name: min
          in: query
          description: Минимум токенов из набора (по умолчанию 2).
          schema: { type: integer, minimum: 2 }
        - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Держатели, most-overlapping-first
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HoldersOverlapResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /aml/screening:
    post:
      tags: [AML]
      summary: Зарегистрировать адрес на скрининг (register-then-poll)
      operationId: registerScreening
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/ChainQuery'
      responses:
        '202':
          description: Зарегистрировано; поллить по screening_id
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Screening' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /aml/risk:
    get:
      tags: [AML]
      summary: Risk-score адреса
      operationId: getAddressRisk
      parameters:
        - $ref: '#/components/parameters/AddressQuery'
        - $ref: '#/components/parameters/ChainQuery'
      responses:
        '200':
          description: Риск-оценка
          content:
            application/json:
              schema: { $ref: '#/components/schemas/RiskScore' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /aml/sanctions:
    get:
      tags: [AML]
      summary: Проверка адреса по санкционным спискам (OFAC/EU/UN, terrorism, CE)
      operationId: getSanctionsCheck
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
      responses:
        '200':
          description: sanctioned + категория/сущность/источник при попадании
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SanctionsResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /aml/exposure:
    get:
      tags: [AML]
      summary: Мульти-хоп экспозиция к рисковым адресам (direct + indirect) + USD-taint
      description: >
        Экспозиция адреса к рисковым источникам: прямые (1-hop) контрагенты И
        косвенные — рисковые адреса, достижимые через N хопов трансферов (параметр
        hops, 1..4, по умолчанию 2), с указанием кратчайшей дистанции. Ядро AML:
        не только «взаимодействовал ли с санкционным адресом», но и «в скольки хопах
        средства от незаконного источника». Плюс разбивка по категориям и USD-обмен
        с прямыми рисковыми (native+token по дневной цене). Обход ограничен по ширине
        (bounded BFS: топ-K контрагентов по обороту на каждом хопе).
      operationId: getExposure
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - name: hops
          in: query
          required: false
          description: Глубина трейсинга (1..4; по умолчанию 2). 1 = только прямые.
          schema: { type: integer, minimum: 1, maximum: 4, default: 2 }
      responses:
        '200':
          description: Экспозиция (direct + indirect + categories + exposure_usd)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Exposure' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /aml/source-of-funds:
    get:
      tags: [AML]
      summary: Источник средств — haircut-трейсинг назад по потоку (по категориям %)
      description: >-
        Обратный haircut-трейсинг происхождения средств: вес адреса пропорционально
        USD-стоимости раскладывается по источникам входящих переводов, обход
        останавливается на известных сущностях (биржа/миксер/санкции — «сквозь»
        сервис не трассируем), затухает по хопам, отбрасывает пыль. Возвращает
        разбивку по категориям (%) и риск-бакетам (trusted/suspicious/danger),
        именованные сущности-источники с кратчайшей дистанцией (hops) и USD, а также
        received_usd (база) и traced_pct (доля средств, привязанная к сущности).
        Ядро AML-отчёта. Обход ограничен по ширине (top-K фронтир на хоп).
      operationId: getSourceOfFunds
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - name: hops
          in: query
          required: false
          description: Глубина трейсинга назад (1..6; по умолчанию 5).
          schema: { type: integer, minimum: 1, maximum: 6, default: 5 }
      responses:
        '200':
          description: Источник средств (категории % + сущности + hops)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SourceOfFunds' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /aml/patterns:
    get:
      tags: [AML]
      summary: Детекция паттернов отмывания (peel-chain / layering / mixer)
      description: >-
        Идёт вперёд по доминирующему потоку от адреса и распознаёт типологии
        отмывания: peel_chain (цепочка адресов, каждый пересылает основную массу
        дальше, «отщипывая» меньшую часть), layering (длинная pass-through цепочка
        без экономической ветвистости), mixer_hop (доминирующий поток достигает
        известного миксера или адреса с mixer-топологией). Возвращает найденные
        паттерны с confidence, числом хопов и путём-доказательством. Обход линейный
        (одна доминирующая грань на хоп) и ограничен по глубине.
      operationId: getPatterns
      parameters:
        - $ref: '#/components/parameters/ChainQuery'
        - $ref: '#/components/parameters/AddressQuery'
        - name: hops
          in: query
          required: false
          description: Глубина обхода вперёд (1..12; по умолчанию 8).
          schema: { type: integer, minimum: 1, maximum: 12, default: 8 }
      responses:
        '200':
          description: Найденные паттерны отмывания
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PatternResult' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '501': { $ref: '#/components/responses/NotImplemented' }
        '502': { $ref: '#/components/responses/ProviderError' }

  /pricing:
    get:
      tags: [Billing]
      summary: Публичный прайс-лист (кредитная стоимость эндпоинтов)
      operationId: getPublicPricing
      security: []
      responses:
        '200':
          description: Правила тарификации
          content:
            application/json:
              schema:
                type: object
                properties:
                  pricing:
                    type: array
                    items: { $ref: '#/components/schemas/PricingRule' }

  /billing/crypto/charge:
    post:
      tags: [Billing]
      summary: Создать крипто-пополнение (on-chain стейблкоин-депозит)
      description: >
        session-auth (роль owner/billing/admin). Возвращает депозит-интент:
        адрес и ТОЧНУЮ сумму (с nonce-тегом) к переводу. Ничего не кредитует —
        баланс пополняется автоматически после того, как наш watcher увидит
        входящий transfer в индексе. 503, если крипто-эквайринг выключен.
        Для сети tron (USDT-TRC20) deposit_address возвращается в формате
        Base58Check (T...); для EVM-сетей — в нижнем регистре 0x-hex.
      operationId: createCryptoCharge
      security:
        - SessionAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount_usd]
              properties:
                amount_usd: { type: number, minimum: 1, maximum: 1000000 }
                chain: { type: string, enum: [ethereum, optimism, avalanche, tron], default: ethereum }
                asset: { type: string, enum: [USDC, USDT], default: USDC }
      responses:
        '200':
          description: Депозит-интент
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CryptoCharge' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '503':
          description: Крипто-эквайринг не включён
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /billing/crypto/charges:
    get:
      tags: [Billing]
      summary: Список крипто-пополнений организации
      operationId: listCryptoCharges
      security:
        - SessionAuth: []
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 50, maximum: 200 } }
        - { name: offset, in: query, schema: { type: integer, default: 0 } }
      responses:
        '200':
          description: Список
          content:
            application/json:
              schema:
                type: object
                properties:
                  charges:
                    type: array
                    items: { $ref: '#/components/schemas/CryptoCharge' }
                  limit: { type: integer }
                  offset: { type: integer }

  /billing/crypto/charges/{id}:
    get:
      tags: [Billing]
      summary: Статус одного крипто-пополнения (для поллинга)
      operationId: getCryptoCharge
      security:
        - SessionAuth: []
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: Charge
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CryptoCharge' }
        '404': { $ref: '#/components/responses/NotFound' }

  /payments/xpub:
    put:
      tags: [Payments]
      summary: Зарегистрировать/обновить extended PUBLIC key (xpub) мерчанта
      description: >
        Non-custodial эквайринг: xpub регистрируется один раз, из него для каждого
        charge деривируется уникальный приёмный адрес. Приватных ключей платформа
        не хранит; xprv отвергается. `key_type`: `evm` (EVM-сети + TRON) или `btc`.
      operationId: setMerchantXpub
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [xpub]
              properties:
                key_type: { type: string, enum: [evm, btc], default: evm }
                xpub: { type: string, description: Extended public key (xpub…) }
            examples:
              evm: { value: { key_type: evm, xpub: "xpub661MyMwAq..." } }
      responses:
        '200':
          description: Сохранено; возвращает webhook-секрет для проверки колбэков
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
                  key_type: { type: string, example: evm }
                  webhook_secret: { type: string, example: "whsec_..." }
        '400': { $ref: '#/components/responses/BadRequest' }
    get:
      tags: [Payments]
      summary: Получить зарегистрированный xpub и счётчик деривации
      operationId: getMerchantXpub
      parameters:
        - { name: key_type, in: query, schema: { type: string, enum: [evm, btc], default: evm } }
      responses:
        '200':
          description: xpub (публичные данные)
          content:
            application/json:
              schema:
                type: object
                properties:
                  key_type: { type: string }
                  xpub: { type: string }
                  next_index: { type: integer }
                  webhook_secret: { type: string }
        '404': { $ref: '#/components/responses/NotFound' }

  /payments/charges:
    post:
      tags: [Payments]
      summary: Создать charge — свежий приёмный адрес под конкретную оплату
      description: >
        Деривирует уникальный адрес из xpub мерчанта и открывает charge. Оплата
        отслеживается воркером по точному совпадению (сеть, актив/токен, адрес,
        сумма); при поступлении вызывается подписанный (HMAC) webhook мерчанта.
      operationId: createPaymentCharge
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [chain, asset, amount]
              properties:
                chain: { type: string, example: tron, description: сеть (tron/ethereum/bitcoin/…) }
                asset: { type: string, example: USDT, description: символ актива (для BTC-сетей — нативная монета) }
                amount: { type: string, example: "12.50", description: человеко-читаемая сумma (десятичная) }
                order_id: { type: string, description: непрозрачный идентификатор заказа мерчанта }
                callback_url: { type: string, format: uri }
                expires_in_sec: { type: integer, default: 1800 }
            examples:
              trc20: { value: { chain: tron, asset: USDT, amount: "12.50", order_id: "ord_1001" } }
      responses:
        '201':
          description: Charge создан
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PaymentCharge' }
        '400': { $ref: '#/components/responses/BadRequest' }
    get:
      tags: [Payments]
      summary: Список charge'ей мерчанта
      operationId: listPaymentCharges
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 50 } }
        - { name: offset, in: query, schema: { type: integer, default: 0 } }
      responses:
        '200':
          description: Список
          content:
            application/json:
              schema:
                type: object
                properties:
                  charges: { type: array, items: { $ref: '#/components/schemas/PaymentCharge' } }

  /payments/charges/{id}:
    get:
      tags: [Payments]
      summary: Статус одного charge (для поллинга)
      operationId: getPaymentCharge
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: Charge
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PaymentCharge' }
        '404': { $ref: '#/components/responses/NotFound' }

  /custody/wallets:
    post:
      tags: [Custody]
      summary: Создать кошелёк — custodial или watch_only
      description: >
        `custodial`: платформа генерирует и хранит ключ (AES-256-GCM в БД) и может
        подписывать/бродкастить выплаты (требует включённого CUSTODY_MASTER_KEY).
        `watch_only`: ключ у клиента, платформа отслеживает адрес и строит
        неподписанные транзакции. Адрес возвращается в форме сети (0x / TRON base58 /
        bech32). enc_privkey никогда не отдаётся.
      operationId: createCustodyWallet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [mode, chain]
              properties:
                mode: { type: string, enum: [custodial, watch_only] }
                chain: { type: string, example: bitcoin }
                label: { type: string }
                address: { type: string, description: обязателен для watch_only — адрес клиента }
            examples:
              custodialBtc: { value: { mode: custodial, chain: bitcoin, label: "payouts-1" } }
              watchOnlyTron: { value: { mode: watch_only, chain: tron, address: "T..." } }
      responses:
        '201':
          description: Кошелёк создан
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustodyWallet' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '501': { description: custodial-режим выключен (CUSTODY_MASTER_KEY не задан) }
    get:
      tags: [Custody]
      summary: Список кошельков
      operationId: listCustodyWallets
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 100 } }
        - { name: offset, in: query, schema: { type: integer, default: 0 } }
      responses:
        '200':
          description: Список
          content:
            application/json:
              schema:
                type: object
                properties:
                  wallets: { type: array, items: { $ref: '#/components/schemas/CustodyWallet' } }

  /custody/wallets/{id}:
    get:
      tags: [Custody]
      summary: Получить кошелёк
      operationId: getCustodyWallet
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: Кошелёк
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustodyWallet' }
        '404': { $ref: '#/components/responses/NotFound' }

  /custody/wallets/{id}/balance:
    get:
      tags: [Custody]
      summary: Баланс кошелька (нативный + токены)
      operationId: getCustodyWalletBalance
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: Балансы
          content:
            application/json:
              schema: { type: object }
        '404': { $ref: '#/components/responses/NotFound' }

  /custody/wallets/{id}/transfers:
    post:
      tags: [Custody]
      summary: Выплата с кошелька (chain-generic — EVM / TRON / BTC)
      description: >
        Диспетчеризуется по семейству сети. Для watch_only возвращается неподписанная
        транзакция (или план входов для BTC) на клиентскую подпись. Для custodial —
        подписывается в памяти (ключ зануляется сразу) и бродкастится; при недоступной
        BTC-ноде возвращается подписанный `raw_tx` для внешнего релея (идемпотентно по
        txid). Применяются policy-гарды (whitelist + суточный лимит). Для BTC `amount`
        в нативной монете, поддерживается опциональный `fee_sat`.
      operationId: createCustodyTransfer
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [to, amount]
              properties:
                to: { type: string, description: адрес получателя (в форме сети) }
                asset: { type: string, description: символ токена; пусто/native — нативная монета }
                amount: { type: string, example: "0.01" }
                fee_sat: { type: integer, description: "BTC: абсолютная комиссия в сатоши; 0/пусто — оценка" }
            examples:
              btc: { value: { to: "bc1q...", amount: "0.01" } }
              erc20: { value: { to: "0x...", asset: USDT, amount: "25" } }
      responses:
        '201':
          description: Выплата построена/подписана/забродкащена (см. поле mode/status)
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string, format: uuid }
                  mode: { type: string, enum: [unsigned, signed, broadcast] }
                  status: { type: string }
                  tx_hash: { type: string }
                  txid: { type: string }
                  raw_tx: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '403': { description: policy_rejected — адрес не в whitelist }
        '422': { description: insufficient_funds / build_error }

  /custody/policy:
    get:
      tags: [Custody]
      summary: Текущая policy выплат (whitelist, лимиты)
      operationId: getCustodyPolicy
      responses:
        '200':
          description: Policy
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustodyPolicy' }
    put:
      tags: [Custody]
      summary: Обновить policy выплат
      operationId: setCustodyPolicy
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CustodyPolicy' }
      responses:
        '200':
          description: Обновлено
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CustodyPolicy' }
        '400': { $ref: '#/components/responses/BadRequest' }

  /custody/whitelist:
    get:
      tags: [Custody]
      summary: Список адресов-получателей в whitelist
      operationId: listCustodyWhitelist
      responses:
        '200':
          description: Список
          content:
            application/json:
              schema:
                type: object
                properties:
                  whitelist: { type: array, items: { type: object } }
    post:
      tags: [Custody]
      summary: Добавить адрес в whitelist
      operationId: addCustodyWhitelist
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [chain, address]
              properties:
                chain: { type: string }
                address: { type: string }
                label: { type: string }
      responses:
        '201': { description: Добавлено }
        '400': { $ref: '#/components/responses/BadRequest' }
    delete:
      tags: [Custody]
      summary: Удалить адрес из whitelist
      operationId: removeCustodyWhitelist
      parameters:
        - { name: chain, in: query, required: true, schema: { type: string } }
        - { name: address, in: query, required: true, schema: { type: string } }
      responses:
        '200': { description: Удалено }
        '404': { $ref: '#/components/responses/NotFound' }

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: "Authorization: Bearer sk_live_... (data-plane API-ключ)"
    SessionAuth:
      type: apiKey
      in: cookie
      name: platform_session
      description: >
        Cookie-сессия ЛК. Мутации требуют заголовок `X-CSRF-Token`, равный
        значению cookie `platform_csrf`.

  parameters:
    ChainQuery:
      name: chain
      in: query
      description: Каноническое имя сети (алиасы eth/op/avax/trx разрешаются).
      schema:
        type: string
        default: ethereum
        enum: [ethereum, optimism, avalanche, base, arbitrum, bsc, polygon, tron]
    AddressQuery:
      name: address
      in: query
      required: true
      schema: { type: string }
    Limit:
      name: limit
      in: query
      description: Размер страницы (1..200, по умолчанию 50).
      schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
    Cursor:
      name: cursor
      in: query
      description: Курсор пагинации (next_cursor из предыдущего ответа).
      schema: { type: string }
    BtcChainQuery:
      name: chain
      in: query
      required: true
      description: UTXO-сеть.
      schema:
        type: string
        enum: [bitcoin, litecoin, dogecoin, bitcoin-cash]

  responses:
    Unauthorized:
      description: Отсутствует/невалиден API-ключ
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    InsufficientCredits:
      description: Недостаточно кредитов
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: Превышен rate-limit
      headers:
        Retry-After: { schema: { type: integer } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Не найдено
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    BadRequest:
      description: Некорректный ввод
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: Недостаточно прав (роль)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    ProviderError:
      description: Ошибка апстрим-провайдера (нода/индекс)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotImplemented:
      description: Ручка требует реального data-plane (CLICKHOUSE_URL); dev-мок её не обслуживает
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code: { type: string }
            message: { type: string }

    Provenance:
      type: object
      description: Источник производных данных и уверенность пайплайна.
      properties:
        source: { type: string }
        confidence: { type: number, minimum: 0, maximum: 1 }

    JsonRpcRequest:
      type: object
      required: [jsonrpc, method]
      properties:
        jsonrpc: { type: string, const: "2.0" }
        id: { type: [string, integer] }
        method: { type: string }
        params: { type: array, items: {} }

    JsonRpcResponse:
      type: object
      properties:
        jsonrpc: { type: string, const: "2.0" }
        id: { type: [string, integer] }
        result: {}
        error:
          type: [object, "null"]
          properties:
            code: { type: integer }
            message: { type: string }

    Block:
      type: object
      properties:
        _mock: { type: boolean }
        chain: { type: string }
        number: { type: integer }
        hash: { type: string }
        timestamp: { type: integer, description: Unix seconds }
        tx_count: { type: integer }
        gas_used: { type: string }

    Transaction:
      type: object
      properties:
        _mock: { type: boolean }
        chain: { type: string }
        hash: { type: string }
        status: { type: string, enum: [success, failed] }
        from: { type: string }
        to: { type: string }
        value: { type: string, description: base units }

    AssetBalance:
      type: object
      properties:
        asset: { type: string, description: "'native' или адрес ERC-20 контракта" }
        amount: { type: string, description: точные base units }
        usd: { type: number }

    Balances:
      type: object
      properties:
        _mock: { type: boolean }
        address: { type: string }
        chain: { type: string }
        balances:
          type: array
          items: { $ref: '#/components/schemas/AssetBalance' }
        derived: { $ref: '#/components/schemas/Provenance' }

    NetWorth:
      type: object
      properties:
        _mock: { type: boolean }
        address: { type: string }
        net_worth_usd: { type: number }
        chains:
          type: array
          items: { type: string }
        derived: { $ref: '#/components/schemas/Provenance' }

    Prices:
      type: object
      properties:
        _mock: { type: boolean }
        prices:
          type: object
          additionalProperties: { type: number }
        derived: { $ref: '#/components/schemas/Provenance' }

    Cluster:
      type: object
      properties:
        _mock: { type: boolean }
        address: { type: string }
        cluster_id: { type: string }
        entity: { type: string }
        category: { type: string }
        address_count: { type: integer }
        derived: { $ref: '#/components/schemas/Provenance' }

    Screening:
      type: object
      properties:
        _mock: { type: boolean }
        screening_id: { type: string }
        status: { type: string }
        poll_after: { type: integer, description: "рекомендованная пауза перед поллингом, сек" }

    RiskScore:
      type: object
      properties:
        _mock: { type: boolean }
        address: { type: string }
        risk_score: { type: integer, minimum: 0, maximum: 100 }
        risk_level: { type: string, enum: [low, medium, high, severe] }
        flags:
          type: array
          items: { type: string }
        exposure_usd:
          type: number
          description: "USD-стоимость нативных переводов с sanctioned/mixer контрагентами (обе стороны), по дневной референс-цене; 0 при отсутствии такой экспозиции"
        components:
          type: array
          description: "объяснимая разбивка risk_score: по одному сигналу на каждую собственную метку/экспозицию контрагента, с эффективными баллами после hop-decay; noisy-OR combine (1 - Π(1-rᵢ))"
          items: { $ref: '#/components/schemas/ScoreComponent' }
        derived: { $ref: '#/components/schemas/Provenance' }

    ScoreComponent:
      type: object
      properties:
        name: { type: string, description: "self_designation | counterparty_exposure" }
        category: { type: string, description: "категория метки, породившей сигнал" }
        hops: { type: integer, description: "0 = сам адрес; >0 = дистанция до контрагента" }
        weight: { type: integer, minimum: 0, maximum: 100, description: "эффективные баллы сигнала после hop-decay" }
        detail: { type: string, description: "провенанс: имя сущности или категория" }

    GraphNode:
      type: object
      properties:
        address: { type: string }
        depth: { type: integer, description: "число хопов от корня (0 = корень)" }
        entity: { type: string }
        category: { type: string, description: "метка из реестра или unknown" }
        risky: { type: boolean }

    GraphEdge:
      type: object
      properties:
        from: { type: string }
        to: { type: string }
        token: { type: string }
        value: { type: string, description: "сумма raw-единиц токена (Int256)" }
        transfers: { type: integer }
        depth: { type: integer }

    GraphTrace:
      type: object
      properties:
        _mock: { type: boolean }
        root: { type: string }
        chain: { type: string }
        direction: { type: string, enum: [in, out, both] }
        generations: { type: integer }
        nodes:
          type: array
          items: { $ref: '#/components/schemas/GraphNode' }
        edges:
          type: array
          items: { $ref: '#/components/schemas/GraphEdge' }
        truncated: { type: boolean, description: "true если сработал лимит fan-out/узлов" }
        derived: { $ref: '#/components/schemas/Provenance' }

    LogEntry:
      type: object
      properties:
        block_number: { type: integer, format: int64 }
        log_index: { type: integer }
        address: { type: string }
        topics: { type: array, items: { type: string } }
        data: { type: string }
    TxLogs:
      type: object
      properties:
        chain: { type: string }
        hash: { type: string }
        logs: { type: array, items: { $ref: '#/components/schemas/LogEntry' } }
    TransferEntry:
      type: object
      description: Движение актива — токен (kind=token) или нативная монета (kind=native). value — точное целое в базовых единицах (строка).
      properties:
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        tx_hash: { type: string }
        log_index: { type: integer }
        kind: { type: string, enum: [token, native] }
        standard: { type: integer, description: '1=ERC20,2=ERC721,3=ERC1155,4=TRC20' }
        token: { type: string }
        symbol: { type: string, description: 'Символ токена из token_meta (только kind=token; отсутствует если неизвестен)' }
        decimals: { type: integer, description: 'Десятичные токена из token_meta для масштабирования value (отсутствует если неизвестны; 0 — валидное значение)' }
        from: { type: string }
        to: { type: string }
        value: { type: string }
        direction: { type: string, description: 'in|out (в ленте /address/history)' }
    TxTransfers:
      type: object
      properties:
        chain: { type: string }
        hash: { type: string }
        transfers: { type: array, items: { $ref: '#/components/schemas/TransferEntry' } }
    AddressTx:
      type: object
      properties:
        hash: { type: string }
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        from: { type: string }
        to: { type: string }
        value: { type: string }
        status: { type: integer, description: '1=success, 0=failed' }
        direction: { type: string, enum: [in, out] }
    AddressActivity:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        transactions: { type: array, items: { $ref: '#/components/schemas/AddressTx' } }
        next_cursor: { type: string, description: 'Передать в ?cursor= для следующей страницы; отсутствует на последней.' }
    AddressTransfers:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        transfers: { type: array, items: { $ref: '#/components/schemas/TransferEntry' } }
        next_cursor: { type: string }

    BlockFull:
      type: object
      properties:
        chain: { type: string }
        number: { type: integer, format: int64 }
        hash: { type: string }
        parent_hash: { type: string }
        timestamp: { type: integer, format: int64 }
        miner: { type: string }
        gas_used: { type: string }
        gas_limit: { type: string }
        base_fee: { type: string }
        tx_count: { type: integer }
        size: { type: integer }
    BlocksPage:
      type: object
      properties:
        chain: { type: string }
        from: { type: integer, format: int64 }
        to: { type: integer, format: int64 }
        blocks: { type: array, items: { $ref: '#/components/schemas/BlockFull' } }
    Receipt:
      type: object
      properties:
        tx_hash: { type: string }
        tx_index: { type: integer }
        from: { type: string }
        to: { type: string }
        status: { type: integer, description: '1=success, 0=failed' }
        gas_used: { type: integer, format: int64 }
        contract_created: { type: string }
        logs: { type: array, items: { $ref: '#/components/schemas/LogEntry' } }
    BlockReceipts:
      type: object
      properties:
        chain: { type: string }
        block_number: { type: integer, format: int64 }
        receipts: { type: array, items: { $ref: '#/components/schemas/Receipt' } }

    GasResult:
      type: object
      properties:
        chain: { type: string }
        block_number: { type: integer, format: int64 }
        base_fee: { type: string }
        slow: { type: string }
        normal: { type: string }
        fast: { type: string }
        note: { type: string }
    NftHolding:
      type: object
      properties:
        collection: { type: string }
        count: { type: integer, format: int64 }
    AddressNFTsResult:
      type: object
      properties:
        chain: { type: string }
        address: { type: string }
        holdings: { type: array, items: { $ref: '#/components/schemas/NftHolding' } }
    AddressHistoryResult:
      type: object
      properties:
        chain: { type: string }
        address: { type: string }
        items: { type: array, items: { $ref: '#/components/schemas/TransferEntry' } }
        next_cursor: { type: string }
    TokenSpamResult:
      type: object
      properties:
        chain: { type: string }
        token: { type: string }
        is_spam: { type: boolean }
        reasons: { type: array, items: { type: string } }
        senders: { type: integer, format: int64 }
        receivers: { type: integer, format: int64 }
        transfers: { type: integer, format: int64 }
        zero_value_transfers: { type: integer, format: int64 }

    SearchHit:
      type: object
      properties:
        type: { type: string, enum: [transaction, block, address, token] }
        chain: { type: string }
        value: { type: string }
        symbol: { type: string }
        label: { type: string }
    SearchResult:
      type: object
      properties:
        query: { type: string }
        hits: { type: array, items: { $ref: '#/components/schemas/SearchHit' } }
    Approval:
      type: object
      properties:
        token: { type: string }
        spender: { type: string }
        allowance: { type: string }
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
    ApprovalsResult:
      type: object
      properties:
        chain: { type: string }
        owner: { type: string }
        approvals: { type: array, items: { $ref: '#/components/schemas/Approval' } }
    NewPool:
      type: object
      properties:
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        tx_hash: { type: string }
        factory: { type: string }
        protocol: { type: string, enum: [uniswap_v2, uniswap_v3] }
        pool: { type: string }
        token0: { type: string }
        token1: { type: string }
        fee: { type: string }
        source: { type: string, description: "Registry provenance (dex/pools only): factory_events or onchain (resolved via eth_call token0/token1 for pools whose creation predates the indexed range)." }
    NewPoolsResult:
      type: object
      properties:
        chain: { type: string }
        pools: { type: array, items: { $ref: '#/components/schemas/NewPool' } }
        next_cursor: { type: string }
    TokenHoldersStats:
      type: object
      properties:
        chain: { type: string }
        token: { type: string }
        holders: { type: integer, format: int64 }
        total_held: { type: string }
        top1_held: { type: string }
        top10_held: { type: string }
        top1_pct: { type: number }
        top10_pct: { type: number }
    TokenParty:
      type: object
      properties:
        address: { type: string }
        amount: { type: string }
    WhoBoughtSold:
      type: object
      properties:
        chain: { type: string }
        token: { type: string }
        buyers: { type: array, items: { $ref: '#/components/schemas/TokenParty' } }
        sellers: { type: array, items: { $ref: '#/components/schemas/TokenParty' } }

    DexPoolResult:
      type: object
      properties:
        chain: { type: string }
        pool: { type: string }
        protocol: { type: string }
        token0: { type: string }
        token1: { type: string }
        reserve0: { type: string }
        reserve1: { type: string }
        tvl_usd: { type: number }
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        found: { type: boolean }
    NftCollectionMeta:
      type: object
      properties:
        chain: { type: string }
        collection: { type: string }
        name: { type: string }
        symbol: { type: string }
        standard: { type: integer }
        transfers: { type: integer, format: int64 }
        found: { type: boolean }
    NftCollectionStats:
      type: object
      properties:
        chain: { type: string }
        collection: { type: string }
        transfers: { type: integer, format: int64 }
        unique_tokens: { type: integer, format: int64 }
        mints: { type: integer, format: int64 }
        burns: { type: integer, format: int64 }
    TokenAllowance:
      type: object
      properties:
        owner: { type: string }
        spender: { type: string }
        allowance: { type: string }
        block_number: { type: integer, format: int64 }
    TokenAllowancesResult:
      type: object
      properties:
        chain: { type: string }
        token: { type: string }
        allowances: { type: array, items: { $ref: '#/components/schemas/TokenAllowance' } }

    LendingEvent:
      type: object
      properties:
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        tx_hash: { type: string }
        log_index: { type: integer }
        type: { type: string, enum: [supply, withdraw, borrow, repay] }
        asset: { type: string }
        user: { type: string }
        amount: { type: string }
    LendingEventsResult:
      type: object
      properties:
        chain: { type: string }
        protocol: { type: string }
        events: { type: array, items: { $ref: '#/components/schemas/LendingEvent' } }
        next_cursor: { type: string }
    Candle:
      type: object
      properties:
        time: { type: integer, format: int64 }
        open: { type: number }
        high: { type: number }
        low: { type: number }
        close: { type: number }
        volume: { type: number }
        trades: { type: integer }
    OHLCVResult:
      type: object
      properties:
        chain: { type: string }
        pool: { type: string }
        interval: { type: string }
        price: { type: string }
        candles: { type: array, items: { $ref: '#/components/schemas/Candle' } }
    Liquidation:
      type: object
      properties:
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        tx_hash: { type: string }
        log_index: { type: integer }
        collateral_asset: { type: string }
        debt_asset: { type: string }
        user: { type: string }
        liquidator: { type: string }
        debt_covered: { type: string }
        collateral_seized: { type: string }
    LiquidationsResult:
      type: object
      properties:
        chain: { type: string }
        protocol: { type: string }
        liquidations: { type: array, items: { $ref: '#/components/schemas/Liquidation' } }
        next_cursor: { type: string }

    Sandwich:
      type: object
      properties:
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        pool: { type: string }
        attacker: { type: string }
        victim: { type: string }
        frontrun_tx: { type: string }
        victim_tx: { type: string }
        backrun_tx: { type: string }
    SandwichesResult:
      type: object
      properties:
        chain: { type: string }
        pool: { type: string }
        sandwiches: { type: array, items: { $ref: '#/components/schemas/Sandwich' } }
        next_cursor: { type: string }

    DexTrade:
      type: object
      properties:
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        tx_hash: { type: string }
        log_index: { type: integer }
        pool: { type: string }
        protocol: { type: string, enum: [uniswap_v2, uniswap_v3] }
        sender: { type: string }
        recipient: { type: string }
        amount0: { type: string, description: 'Знаковая дельта token0 (перспектива пула)' }
        amount1: { type: string }
    DexTradesResult:
      type: object
      properties:
        chain: { type: string }
        pool: { type: string }
        trades: { type: array, items: { $ref: '#/components/schemas/DexTrade' } }
        next_cursor: { type: string }

    WalletChainWorth:
      type: object
      properties:
        chain: { type: string }
        usd: { type: number }
    WalletWorth:
      type: object
      properties:
        address: { type: string }
        total_usd: { type: number }
        chains: { type: array, items: { $ref: '#/components/schemas/WalletChainWorth' } }
    WalletNetWorth:
      type: object
      properties:
        wallets: { type: array, items: { $ref: '#/components/schemas/WalletWorth' } }
        grand_total_usd: { type: number }
    NftTransfer:
      type: object
      properties:
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        tx_hash: { type: string }
        log_index: { type: integer }
        standard: { type: integer, description: '2=ERC721, 3=ERC1155' }
        collection: { type: string }
        from: { type: string }
        to: { type: string }
        token_id: { type: string }
        value: { type: string }
    NftTransfersResult:
      type: object
      properties:
        chain: { type: string }
        collection: { type: string }
        transfers: { type: array, items: { $ref: '#/components/schemas/NftTransfer' } }
        next_cursor: { type: string }
    NftHolder:
      type: object
      properties:
        address: { type: string }
        count: { type: integer, format: int64 }
    NftHoldersResult:
      type: object
      properties:
        chain: { type: string }
        collection: { type: string }
        holders: { type: array, items: { $ref: '#/components/schemas/NftHolder' } }
    MetricInfo:
      type: object
      properties:
        key: { type: string }
        description: { type: string }
    MetricPoint:
      type: object
      properties:
        day: { type: string, format: date }
        value: { type: number }
    MetricSeries:
      type: object
      properties:
        metric: { type: string }
        chain: { type: string }
        from: { type: string, format: date }
        to: { type: string, format: date }
        points: { type: array, items: { $ref: '#/components/schemas/MetricPoint' } }

    BtcBalance:
      type: object
      properties:
        chain: { type: string }
        address: { type: string }
        received: { type: string }
        spent: { type: string }
        balance: { type: string }
    BtcAddrTx:
      type: object
      properties:
        txid: { type: string }
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        direction: { type: string, description: 'in | out | in,out' }
    BtcAddrActivity:
      type: object
      properties:
        chain: { type: string }
        address: { type: string }
        transactions: { type: array, items: { $ref: '#/components/schemas/BtcAddrTx' } }
        next_cursor: { type: string }
    BtcTxInput:
      type: object
      properties:
        input_index: { type: integer }
        spent_txid: { type: string }
        spent_vout: { type: integer }
        address: { type: string }
        value: { type: integer, format: int64 }
    BtcTxOutput:
      type: object
      properties:
        vout: { type: integer }
        address: { type: string }
        value: { type: integer, format: int64 }
        script_type: { type: string }
    BtcTxDetail:
      type: object
      properties:
        chain: { type: string }
        txid: { type: string }
        block_number: { type: integer, format: int64 }
        timestamp: { type: integer, format: int64 }
        is_coinbase: { type: integer }
        fee: { type: integer, format: int64 }
        size: { type: integer }
        weight: { type: integer }
        inputs: { type: array, items: { $ref: '#/components/schemas/BtcTxInput' } }
        outputs: { type: array, items: { $ref: '#/components/schemas/BtcTxOutput' } }
        found: { type: boolean }

    SmartMoneyEntry:
      type: object
      properties:
        address: { type: string }
        realized_usd: { type: number }
        tokens_traded: { type: integer, format: int64 }
        entity: { type: string }
        category: { type: string }
    SmartMoneyResult:
      type: object
      properties:
        chain: { type: string }
        metric: { type: string }
        addresses: { type: array, items: { $ref: '#/components/schemas/SmartMoneyEntry' } }
    FirstBuyer:
      type: object
      properties:
        address: { type: string }
        first_block: { type: integer, format: int64 }
        first_timestamp: { type: integer, format: int64 }
        first_amount: { type: string }
    FirstBuyersResult:
      type: object
      properties:
        chain: { type: string }
        token: { type: string }
        buyers: { type: array, items: { $ref: '#/components/schemas/FirstBuyer' } }
    OverlapHolder:
      type: object
      properties:
        address: { type: string }
        tokens_held: { type: integer, format: int64 }
        tokens: { type: array, items: { type: string } }
    HoldersOverlapResult:
      type: object
      properties:
        chain: { type: string }
        tokens: { type: array, items: { type: string } }
        min: { type: integer }
        holders: { type: array, items: { $ref: '#/components/schemas/OverlapHolder' } }

    LabelInfo:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        found: { type: boolean }
        entity: { type: string }
        category: { type: string }
        source: { type: string }
        confidence: { type: number }
        risky: { type: boolean }
    SanctionsResult:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        sanctioned: { type: boolean }
        category: { type: string }
        entity: { type: string }
        source: { type: string }
    ExposureParty:
      type: object
      properties:
        address: { type: string }
        entity: { type: string }
        category: { type: string }
    IndirectHit:
      type: object
      description: Рисковый адрес, достижимый через N хопов (hops>=2); hops — кратчайшая дистанция.
      properties:
        address: { type: string }
        entity: { type: string }
        category: { type: string }
        hops: { type: integer }
    Exposure:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        direct: { type: array, items: { $ref: '#/components/schemas/ExposureParty' } }
        indirect: { type: array, items: { $ref: '#/components/schemas/IndirectHit' }, description: 'рисковые адреса в 2..max_hops хопах' }
        max_hops: { type: integer }
        categories: { type: object, additionalProperties: { type: integer }, description: 'category -> число рисковых адресов (direct+indirect)' }
        exposure_usd: { type: number }
        derived: { $ref: '#/components/schemas/Provenance' }
    EntitySource:
      type: object
      description: Терминальная сущность-источник в haircut-трейсе.
      properties:
        address: { type: string }
        entity: { type: string }
        category: { type: string }
        hops: { type: integer, description: 'кратчайшая дистанция (в переводах) до источника' }
        weight: { type: number, description: 'доля прослеженной стоимости средств' }
        exposure_usd: { type: number }
    SourceOfFunds:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        max_hops: { type: integer }
        received_usd: { type: number, description: 'всего получено адресом (база USD-атрибуции)' }
        traced_pct: { type: number, description: '% стоимости, привязанной к известной сущности' }
        categories: { type: object, additionalProperties: { type: number }, description: 'category -> % прослеженной стоимости' }
        risk_buckets: { type: object, additionalProperties: { type: number }, description: 'trusted|suspicious|danger -> %' }
        sources: { type: array, items: { $ref: '#/components/schemas/EntitySource' } }
        derived: { $ref: '#/components/schemas/Provenance' }

    PatternHit:
      type: object
      description: Одна обнаруженная типология отмывания с путём-доказательством.
      properties:
        type: { type: string, description: 'peel_chain | layering | mixer_hop' }
        confidence: { type: number }
        hops: { type: integer }
        path: { type: array, items: { type: string }, description: 'цепочка адресов-доказательство' }
        detail: { type: string }
    PatternResult:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        max_hops: { type: integer }
        patterns: { type: array, items: { $ref: '#/components/schemas/PatternHit' } }
        derived: { $ref: '#/components/schemas/Provenance' }

    PricePoint:
      type: object
      properties:
        day: { type: string, format: date }
        usd: { type: number }
        source: { type: string }
    PriceHistory:
      type: object
      properties:
        asset: { type: string }
        chain: { type: string }
        from: { type: string, format: date }
        to: { type: string, format: date }
        points: { type: array, items: { $ref: '#/components/schemas/PricePoint' } }

    TokenMeta:
      type: object
      properties:
        chain: { type: string }
        address: { type: string }
        symbol: { type: string }
        name: { type: string }
        decimals: { type: integer }
        standard: { type: integer, description: '1=ERC20,2=ERC721,3=ERC1155,4=TRC20' }
        total_supply: { type: string }
        creator: { type: string }
        verified: { type: boolean }
        source: { type: string }
        found: { type: boolean }
    TokenHolder:
      type: object
      properties:
        address: { type: string }
        balance: { type: string }
    TokenHolders:
      type: object
      properties:
        chain: { type: string }
        token: { type: string }
        holders: { type: array, items: { $ref: '#/components/schemas/TokenHolder' } }
    TokenTransfers:
      type: object
      properties:
        chain: { type: string }
        token: { type: string }
        transfers: { type: array, items: { $ref: '#/components/schemas/TransferEntry' } }
        next_cursor: { type: string }

    Counterparty:
      type: object
      properties:
        address: { type: string }
        transfers: { type: integer, format: int64 }
        tokens: { type: integer, format: int64, description: 'Число различных токен-контрактов' }
        entity: { type: string }
        category: { type: string }
        risky: { type: boolean }
    Counterparties:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        direction: { type: string, enum: [in, out, both] }
        counterparties: { type: array, items: { $ref: '#/components/schemas/Counterparty' } }
    AddressStats:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        sent_tx: { type: integer, format: int64 }
        received_tx: { type: integer, format: int64 }
        tx_count: { type: integer, format: int64 }
        first_block: { type: integer, format: int64 }
        last_block: { type: integer, format: int64 }
        first_timestamp: { type: integer, format: int64 }
        last_timestamp: { type: integer, format: int64 }
        token_transfers: { type: integer, format: int64 }
        unique_tokens: { type: integer, format: int64 }
        unique_counterparties: { type: integer, format: int64 }
    Portfolio:
      type: object
      properties:
        address: { type: string }
        chain: { type: string }
        holdings: { type: array, items: { $ref: '#/components/schemas/AssetBalance' } }
        total_usd: { type: number }
        derived: { $ref: '#/components/schemas/Provenance' }

    PricingRule:
      type: object
      properties:
        endpoint: { type: string }
        credit_weight: { type: integer }

    CryptoCharge:
      type: object
      properties:
        id: { type: string, format: uuid }
        ref: { type: string }
        status: { type: string, enum: [pending, confirmed, expired] }
        chain: { type: string }
        asset: { type: string }
        token_address: { type: string }
        deposit_address: { type: string }
        amount_usd: { type: number }
        credits: { type: integer }
        decimals: { type: integer }
        expected_amount: { type: string, description: точные base units, которые нужно перевести }
        expected_amount_display: { type: string, description: человекочитаемая сумма }
        tx_hash: { type: string }
        expires_at: { type: string, format: date-time }
        confirmed_at: { type: [string, "null"], format: date-time }
        created_at: { type: string, format: date-time }

    CustodyWallet:
      type: object
      properties:
        id: { type: string, format: uuid }
        mode: { type: string, enum: [custodial, watch_only] }
        chain: { type: string }
        address: { type: string, description: адрес в форме сети (0x / TRON base58 / bech32) }
        label: { type: string }
        custodial: { type: boolean }
        created_at: { type: string, format: date-time }

    CustodyPolicy:
      type: object
      properties:
        whitelist_enabled: { type: boolean, description: выплаты только на адреса из whitelist }
        max_daily_transfers: { type: integer, description: суточный лимит числа выплат на кошелёк (0 — без лимита) }

    PaymentCharge:
      type: object
      properties:
        id: { type: string, format: uuid }
        order_id: { type: string }
        chain: { type: string }
        asset: { type: string }
        deposit_address: { type: string, description: уникальный приёмный адрес (в форме сети) }
        amount: { type: string, description: человекочитаемая сумма }
        expected_amount: { type: string, description: точные base units для совпадения }
        decimals: { type: integer }
        status: { type: string, enum: [pending, paid, expired] }
        tx_hash: { type: string }
        callback_url: { type: string, format: uri }
        created_at: { type: string, format: date-time }
        expires_at: { type: string, format: date-time }
        paid_at: { type: [string, "null"], format: date-time }
