Эта страница переведена автоматически. Оригинал на английском языке является каноническим. Читать на английском →
Перейти к основному содержимому

WebSocket API

Потоковая передача данных в реальном времени для торговли опционами на Hypercall.

Интерактивный справочник

Откройте интерактивный справочник WebSocket API: в нём удобнее просматривать живые примеры и описание схем.

Машиночитаемая спецификация

Скачайте спецификацию AsyncAPI для программного использования.

Аддитивное развитие схемы

В объектах сообщений WebSocket могут появляться новые поля без предварительного уведомления. Клиенты обязаны игнорировать поля, которые им неизвестны. Hypercall не удаляет поля из существующей схемы сообщения.

Подключение​

Подключитесь к wss://HOST/ws:

Эндпоинты:

  • Продакшн: wss://api.hypercall.xyz/ws
  • Локально: ws://localhost:3000/ws
Статус тестнета

Тестнет временно отключён, пока Hypercall не получит больше HYPE в тестнете.

Сжатие​

/ws поддерживает permessage-deflate по RFC 7692. Включите его в клиенте, чтобы сократить трафик рыночных данных: на записанном продакшн-трафике независимо сжатые фреймы примерно на 45% меньше, а пакеты индексных цен примерно на 70% меньше.

  • Клиент предлагает расширение в запросе на апгрейд, например Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits.
  • Если сервер принимает расширение, ответ 101 содержит Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover. В этом случае каждое сообщение сервера сжимается отдельно, без общего скользящего окна, а сообщения размером менее нескольких сотен байт по-прежнему отправляются без сжатия. Библиотека распаковывает их прозрачно.
  • Если в ответе нет заголовка Sec-WebSocket-Extensions, все фреймы передаются без сжатия. Сжатие включается отдельно для каждого окружения, поэтому клиенты должны поддерживать оба варианта; JSON-сообщения в обоих случаях идентичны. Продакшн пока не принимает расширение, поэтому сейчас фреймы в продакшне не сжимаются.
  • Клиент может сжимать отправляемые данные. Сервер распаковывает каждое сообщение клиента объёмом до 256 KiB и закрывает соединение, если сообщение превышает этот предел.

Браузеры согласуют расширение автоматически. Большинству библиотек изменения не нужны:

Python (websockets включает сжатие по умолчанию):

import websockets

async with websockets.connect("wss://api.hypercall.xyz/ws", compression="deflate") as ws:
...

Node.js (ws):

import WebSocket from "ws";

const ws = new WebSocket("wss://api.hypercall.xyz/ws", { perMessageDeflate: true });

Rust: WsClient из hypercall-client по умолчанию согласует permessage-deflate (см. Пример: клиент на Rust). Обычные tungstenite и tokio-tungstenite пока его не реализуют, поэтому подключаются без сжатия. Чтобы использовать сжатие со своим сокетом, возьмите клиент с его поддержкой, например yawc:

let url = "wss://api.hypercall.xyz/ws".parse()?;
let mut ws = yawc::WebSocket::connect(url)
.with_options(yawc::Options::default().with_low_latency_compression())
.await?;

Идентификация кошелька​

Чтобы получать данные по аутентифицированным каналам (заявки, исполнения, портфель), после подключения идентифицируйте свой кошелёк сообщением Authenticate:

{"type": "Authenticate", "wallet": "0x1234..."}

Сервер отвечает подтверждением:

{"type": "Authenticated", "wallet": "0x1234..."}

После получения Authenticated можно подписываться на аутентифицированные каналы. Если адрес кошелька некорректен, сервер отвечает сообщением Error, а соединение остаётся открытым.

Устарело: аутентификация через параметр запроса

Параметр запроса ?wallet= пока поддерживается для обратной совместимости, но считается устаревшим и будет удалён в одном из следующих релизов. Используйте описанный выше способ через сообщение.

Поддержание соединения​

Сервер применяет WebSocket-heartbeat:

  • Отправляет управляющий фрейм Ping каждые 20 секунд
  • Ожидает ответный Pong в течение 60 секунд
  • Закрывает соединение с кодом 1008 и причиной pong timeout, если клиент перестаёт отвечать

Браузерные реализации WebSocket обрабатывают ping/pong автоматически. Многие Rust-библиотеки для WebSocket, включая tungstenite и tokio-tungstenite, тоже сами обрабатывают управляющие фреймы ping/pong. Прежде чем добавлять ручную обработку Pong, сверьтесь с документацией своей библиотеки. Собственные реализации и реализации на «сырых» сокетах обязаны отвечать на фреймы Ping фреймами Pong.

Восстановление медленного потребителя​

Сервер закрывает соединение /ws, если оно не успевает выгрузить исходящие данные в пределах настроенных защитных лимитов по числу сообщений, объёму закодированных байт, возрасту очереди или времени записи в сокет. Если соединение ещё способно принять фрейм закрытия, сервер использует код 1008 и компактную JSON-причину:

{"error":"slow_consumer","class":"ordered_public","cause":"message_age","recovery":"snapshot_resubscribe"}

Поля причины:

ПолеЗначение
classКласс доставки, фрейм которого превысил защитный предел.
causemessage_limit, byte_limit, message_age или write_timeout.
recoveryОбязательное следующее действие, например resubscribe, snapshot_resubscribe, portfolio_refetch или rest_reconcile.

После любого разрыва переподключитесь, при необходимости снова идентифицируйте кошелёк, повторно подпишитесь и сверьте текущее состояние, прежде чем обрабатывать новые события. Упорядоченным публичным каналам нужен свежий снимок. Приватные каналы событий требуют сверки через авторитетный REST-интерфейс, поскольку воспроизведение по курсору пока недоступно. Полностью зависшее соединение может оборваться раньше, чем клиент прочитает причину закрытия, поэтому этот порядок восстановления нужно применять и при некорректном закрытии.

Используйте отдельные соединения для высокочастотных публичных рыночных данных и для аутентифицированных команд или приватных потоков. Классы доставки определяют метрики и поведение при восстановлении, но фреймы одного соединения всё равно проходят через единый упорядоченный путь записи в сокет. Поэтому зависшая запись публичных данных может задержать последующие приватные фреймы на том же соединении, пока дедлайн записи не закроет его.

Подписка на каналы​

Чтобы подписаться, отправьте JSON-сообщение:

{"type": "Subscribe", "channel": "orderbook"}

Чтобы отписаться:

{"type": "Unsubscribe", "channel": "orderbook"}

Вы получите подтверждение:

{"type": "Subscribed", "channel": "orderbook"}

Фильтрация по символам​

Каналы order_updates и fills поддерживают необязательный фильтр symbols. Если он задан, сервер отправляет только сообщения, базовый актив которых совпадает с одним из указанных символов.

{"type": "Subscribe", "channel": "order_updates", "symbols": ["BTC"]}

Принимаются как тикеры базовых активов ("BTC"), так и полные имена инструментов ("BTC-20260131-100000-C"). Чтобы добавить символы, отправьте ещё один Subscribe. Чтобы убрать конкретные символы:

{"type": "Unsubscribe", "channel": "order_updates", "symbols": ["BTC"]}

Если symbols не указаны, пересылаются все обновления по вашему кошельку.

Публичные каналы orderbook, trades, indicative_market_data и indicative_market_data_v2 тоже принимают symbols, но сопоставляют их с точными символами инструментов (без учёта регистра), а не с базовыми активами:

{"type": "Subscribe", "channel": "orderbook", "symbols": ["BTC-20260131-100000-C"]}

Без symbols эти каналы передают данные по всем инструментам.

Фильтрация опционной доски​

Канал options_chain поддерживает фильтрацию по символам инструментов, дате экспирации и типу опциона:

{
"type": "Subscribe",
"channel": "options_chain",
"symbols": ["BTC-20260131-100000-C"],
"expiry": "2026-01-31",
"option_type": "call"
}
ФильтрЗначенияПо умолчанию
symbolsМассив полных символов инструментов (например, ["BTC-20260131-100000-C"])Все инструменты
expiryСтрока даты "YYYY-MM-DD", сопоставляется с календарной датой экспирации инструмента по UTCВсе экспирации
option_type"call", "put", "both" или не указан (оба)Оба

Пакетная доставка​

indicative_market_data_v2 передаёт те же строки, что и indicative_market_data, но по несколько строк в одном фрейме:

{"type": "Subscribe", "channel": "indicative_market_data_v2", "symbols": ["BTC-20260131-100000-C"]}

Сервер подтверждает подписку с пакетным маркером:

{"type": "Subscribed", "channel": "indicative_market_data_v2", "batch": true}

Затем он удерживает каждую строку не дольше одного пакетного окна (по умолчанию 25 мс) и отправляет строки, опубликованные за это окно, одним фреймом IndicativeMarketDataBatch. Пакет также отправляется досрочно, как только в нём набирается 256 строк.

  • Определение поддержки: только подтверждение с "batch": true означает, что сервер работает в пакетном режиме. Серверы, выпущенные до появления этого канала, подтверждают любое имя канала без маркера и затем ничего не присылают, а текущие серверы отвечают на неизвестный канал ошибкой unknown_channel. В обоих случаях отпишитесь от indicative_market_data_v2 и подпишитесь на indicative_market_data на том же соединении.
  • Задержка: строка приходит максимум на одно окно позже, чем в indicative_market_data. На продакшн-трафике с окном по умолчанию строки ждали в среднем 12 мс и никогда не дольше 25 мс. Если вы реагируете на отдельные обновления котировок в пределах миллисекунд, используйте канал v1.
  • Трафик: само по себе пакетирование экономит около 7% байт. Основная экономия достигается вместе с permessage-deflate: сжатые пакеты примерно в 9,8 раза меньше несжатых однострочных фреймов при подписке без фильтра и примерно в 8,3 раза меньше для одной опционной доски, тогда как построчное сжатие даёт лишь около 1,8 раза.
  • Порядок: строки в updates идут в порядке публикации, и пакеты приходят по порядку. Строки не схлопываются, поэтому один инструмент может встретиться в пакете несколько раз. Применяя строки по порядку, вы получите то же состояние, что и на канале v1.
  • Снимок: снимок при подписке также приходит пакетными фреймами.
  • Фильтры: symbols работает так же, как в indicative_market_data. При сужении фильтра или отписке сервер сначала отправляет строки, уже ожидающие отправки по старому фильтру, а затем подтверждение Unsubscribed, поэтому строки не теряются и не дублируются.
  • Независимые каналы: одно соединение может подписаться на оба канала с разными фильтрами. indicative_market_data не изменился.

Доступные каналы​

КаналТребуется аутентификацияОписание
orderbookНетОбновления книги заявок L2 по всем символам
tradesНетПубличная лента сделок
market_updatesНетИзменения листинга рынков (создан/удалён/истёк)
options_chainНетИнкрементальные обновления опционной доски (фильтр по symbols, expiry, option_type)
index_pricesНетСпотовые/индексные цены всех базовых активов в реальном времени
indicative_market_dataНетПоток котировщиков из списка допуска. Пока не общедоступен
indicative_market_data_v2НетТот же поток в пакетных фреймах. См. Пакетная доставка
order_updatesДаИзменения статуса ваших заявок (фильтр по символу)
fillsДаИсполнения ваших сделок (фильтр по символу)
portfolioДаОбновления ваших позиций и баланса
liquidationДаИзменения вашего состояния ликвидации
rfqДаКотировки RFQ, обновления статуса и уведомления об исполнении
experimental_order_updates, experimental_combo_fillsДаЭкспериментальные комбо-каналы. См. Experimental Combo API
experimental_combo_tradesНетЭкспериментальные публичные комбо-сделки

Данные соревнований и лидербордов не отдаются через api.hypercall.xyz ни по WebSocket, ни по REST. Подписка на выведенные из эксплуатации каналы competition или competition_engagement возвращает Error. Торговое приложение получает данные соревнований по HTTP от аналитического сервиса https://api-ui.hypercall.xyz, например GET /v2/competitions, GET /v2/competitions/{id}, GET /v2/competitions/leaderboard и GET /v2/competitions/all-time/leaderboard.

Типы сообщений​

Размещение заявки (аутентифицированное)​

Размещение заявки через командный путь WebSocket.

{
"type": "PlaceOrder",
"wallet": "0x1234...",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"size": "1",
"price": "100",
"tif": "gtc",
"route": "book_only",
"client_id": "my-order-1",
"nonce": 1780000000000,
"signature": "0x..."
}

Значение nonce выше приведено для иллюстрации. Генерируйте новый nonce для каждой заявки из текущего Unix-времени в миллисекундах, например Date.now() в JavaScript. Скопированное фиксированное значение выйдет за пределы допустимого окна и будет отклонено.

ПолеТипОписание
walletstringАдрес кошелька, которому принадлежит заявка
symbolstringСимвол опциона
sidestring"Buy" или "Sell"
sizestringРазмер контракта, в точности совпадающий с подписанным значением
pricestringЛимитная цена, в точности совпадающая с подписанным значением
tifstringНеобязательный срок действия заявки (time-in-force), по умолчанию "gtc"
routestringИспользуйте "book_only". Заявка без route пока принимается, но этот вариант устарел, и срок его отключения уже прошёл.
client_idstringНеобязательный клиентский ID заявки
reduce_onlybooleanНеобязательно, по умолчанию false. Заявка reduce-only подписывается как PlaceOrderReduceOnly и требует явного route
mmp_enabledbooleanНеобязательно, по умолчанию false
builder_code_addressstringНеобязательный кошелёк builder code
nonceintegerОбязательный nonce подписи: текущее Unix-время в миллисекундах. Должен находиться в пределах двух дней до и одного дня после текущего времени. Каждый nonce принимается один раз для каждого подписанта, при этом заявки через REST и WebSocket используют общее пространство nonce. Повторно отправленный фрейм отклоняется.
signaturestringПодпись EIP-712 PlaceOrder

Соединение должно предварительно отправить Authenticate; иначе сервер ответит {"type": "Error", "message": "Not authenticated"}. Подписантом должен быть wallet либо одобренный для него агент.

Сейчас WebSocket-команда PlaceOrder направляет заявку непосредственно в книгу заявок. route="best_execution" и route="rfq_only" на WebSocket отклоняются, поскольку этот путь пока не выполняет маршрутизацию RPI/RFQ. Для best_execution используйте POST /order.

На принятую заявку сервер отвечает на том же соединении сообщением OrderResult:

{
"type": "OrderResult",
"order_id": 12345,
"status": "ACKED",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"price": "100",
"size": "1"
}

Если заявка не принята, добавляется поле reason. Ошибки валидации, подписи и лимитов запросов приходят как Error, у которого message начинается с Order placement failed:. Последующие изменения статуса приходят в order_updates.

Обновление книги заявок​

Снимок/обновление книги заявок L2 по символу.

{
"type": "OrderbookUpdate",
"symbol": "BTC-20260131-100000-C",
"option_token_address": "0x...",
"bids": [["95000.5", "10.5"], ["94999.0", "25.0"]],
"asks": [["95001.0", "8.0"], ["95002.5", "15.0"]],
"timestamp": 1737331200000
}
ПолеТипОписание
symbolstringСимвол опциона
option_token_addressstring?Ончейн-адрес токена опциона, если известен
bidsarrayУровни бида в виде кортежей [price, size], размер в контрактах в удобочитаемом формате
asksarrayУровни аска в виде кортежей [price, size], размер в контрактах в удобочитаемом формате
timestampintegerВременная метка Unix (миллисекунды)

Сделка​

Публичное событие сделки.

{
"type": "Trade",
"symbol": "BTC-20260131-100000-C",
"price": "0.0523",
"size": "5.0",
"side": "Buy",
"timestamp": 1737331200000
}
ПолеТипОписание
symbolstringСимвол опциона
pricestringЦена сделки в USD
sizestringОбъём сделки в контрактах
sidestringСторона агрессора (Buy или Sell)
timestampintegerВременная метка Unix (миллисекунды)

Исполнение (аутентифицированное)​

Уведомление об исполнении вашей сделки.

{
"type": "Fill",
"order_id": 12345,
"fill_id": 67890,
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"price": "0.0523",
"size": "5.0",
"timestamp": 1737331200000,
"wallet_address": "0x1234...abcd",
"fee": "0",
"trade_id": 99999,
"is_taker": true,
"builder_code_address": null,
"builder_code_fee": null,
"instrument_type": "option"
}
ПолеТипОписание
order_idintegerID вашей заявки
fill_idintegerID исполнения
symbolstringСимвол опциона
sidestringВаша сторона в сделке (Buy или Sell)
pricestringЦена исполнения в USD
sizestringОбъём исполнения в контрактах
timestampintegerВременная метка Unix (миллисекунды)
wallet_addressstringАдрес вашего кошелька
feestringСписанная торговая комиссия. Возвращает 0, пока комиссии площадки на запуске отключены
trade_idintegerУникальный ID сделки
is_takerbooleanБыли ли вы тейкером
builder_code_addressstring?Кошелёк builder code (если есть)
builder_code_feestring?Комиссия builder code. Возвращает null, пока комиссии площадки на запуске отключены
instrument_typestring"option"

Обновление портфеля (аутентифицированное)​

Отправляется в канале portfolio для позиций, балансов, маржи и греков. При подписке сервер отправляет снимок Initial, а затем инкрементальные обновления.

type каждого фрейма содержит вид обновления:

Вид обновленияПоля помимо timestamp
Initialpositions
PositionUpdateposition
BalanceUpdatetotal_margin_used
MarginUpdatespan_margin, margin_summary, margin_mode, total_margin_used, available_balance
GreeksUpdateper_leg, aggregate

timestamp во фреймах portfolio указывается в секундах Unix, в отличие от миллисекундных меток в других каналах.

Пример обновления греков:

{
"type": "GreeksUpdate",
"timestamp": 1737331200,
"per_leg": [
{
"symbol": "BTC-20260131-100000-C",
"quantity": "2.0",
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
],
"aggregate": {
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
}

Для пустых портфелей обновления греков содержат:

  • per_leg: []
  • aggregate: null

Обновление заявки (аутентифицированное)​

Уведомление об изменении статуса заявки. Обновления PARTIALLY_FILLED включены. filled_size — это накопленный исполненный объём по всем исполнениям заявки, а не объём последнего исполнения.

{
"type": "OrderUpdate",
"order_id": 12345,
"client_id": "my-order-1",
"request": {
"price": "0.0523",
"size": "10.0",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"tif": "gtc"
},
"status": "PARTIALLY_FILLED",
"filled_size": "0.75",
"timestamp": 1767225600000,
"reason": null,
"wallet_address": "0x0000000000000000000000000000000000000001",
"instrument_type": "option"
}

status принимает одно из значений ACKED, OPEN, PARTIALLY_FILLED, FILLED, CANCELED или REJECTED. client_id опускается, если у заявки его нет. instrument_type равен "option" или "perp".

Обновление рынка​

Изменения листинга рынков.

Рынок создан:

{
"type": "MarketUpdate",
"action": "Created",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1737331200000
}

Рынок истёк:

{
"type": "MarketUpdate",
"action": "Expired",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1738281600000
}

action принимает значения Created, Expired, Deleted (содержит только symbol и timestamp) или ComboCreated (см. Experimental Combo API).

Обновление опционной доски​

Отправляется в options_chain при изменении листинга или лучших цен в его книге заявок. action принимает значение Upsert или Remove.

{
"type": "OptionsChainUpdate",
"action": "Upsert",
"currency": "BTC",
"expiry": 1769846400,
"row": {
"strike": 100000.0,
"call": {
"symbol": "BTC-20260131-100000-C",
"bid_price_usd": 1520.0,
"ask_price_usd": 1580.0
}
},
"timestamp": 1737331200000
}

Строка Upsert содержит одну ногу (call или put) с полями ноги из снимка опционной доски: symbol, option_token_address, цены бид и аск, IV, размер в контрактах и номинал в USD, greeks_abs и greeks_cash (пример выше сокращён). Недоступные значения равны null; нога, не затронутая обновлением, опускается. Remove содержит currency, expiry, strike, option_type, symbol и timestamp и отправляется, когда инструмент удаляется, истекает или переходит в состояние ожидания расчёта. expiry указывается в секундах Unix.

Экспирация позиции (аутентифицированное)​

Отправляется в канале portfolio, когда ваша позиция рассчитывается при экспирации.

{
"type": "PositionExpired",
"wallet_address": "0x1234...abcd",
"symbol": "BTC-20260131-100000-C",
"position_size": "10.0",
"settlement_price": "105000",
"settlement_value": "500.0",
"settlement_entry_price": "100.0",
"cost_basis": "1000.0",
"net_pnl": "-500.0",
"timestamp": 1738281600000
}

settlement_entry_price, cost_basis и net_pnl равны null, если недоступны.

Изменение состояния ликвидации (аутентифицированное)​

Отправляется в канале liquidation, когда меняется состояние ликвидации вашего счёта.

{
"type": "LiquidationStateChange",
"wallet_address": "0x1234...abcd",
"previous_state": "healthy",
"new_state": "pre_liquidation",
"liquidation_mode": "partial",
"margin_mode": "standard",
"equity": "10000.0",
"mm_required": "10500.0",
"maintenance_margin": "-500.0",
"shortfall": "500.0",
"partial_liquidation": { "...": "..." },
"full_liquidation": null,
"timestamp": 1737331200000
}
ПолеОписание
previous_state, new_statehealthy, pre_liquidation, in_liquidation или liquidated
liquidation_modepartial, full или null, если процесс ликвидации не активен
margin_modestandard или portfolio
equityСобственные средства счёта
mm_requiredТребование по поддерживающей марже
maintenance_marginСобственные средства за вычетом требования по поддерживающей марже
shortfallСумма, на которую счёт не дотягивает до поддерживающей маржи
partial_liquidationХод частичной ликвидации (время входа, целевой уровень собственных средств, дедлайн эскалации, активные ликвидационные заявки, бонус) или null
full_liquidationДетали аукциона полной ликвидации (ID аукциона, время начала, необходимая маржа, а по завершении победитель, бонус и транзакция урегулирования) или null

Значение каждого состояния описано в разделе Ликвидация.

Обновление индексных цен​

Пакет спотовых/индексных цен по всем базовым активам.

{
"type": "IndexPriceUpdate",
"prices": [
{"underlying": "BTC", "price": "97250.50", "timestamp": 1737331200000},
{"underlying": "ETH", "price": "3200.00", "timestamp": 1737331199980},
{"underlying": "NVDA", "price": "182.40", "timestamp": 1737331199950}
],
"timestamp": 1737331199950
}
ПолеТипОписание
pricesarrayМассив записей {underlying, price, timestamp} по каждому отслеживаемому базовому активу
prices[].underlyingstringСимвол базового актива (например, "BTC", "ETH")
prices[].pricestringТекущая спотовая/индексная цена в USD
prices[].timestampintegerВремя наблюдения в источнике для этого базового актива, миллисекунды Unix
timestampintegerСамое раннее время наблюдения в источнике среди включённых в prices, миллисекунды Unix

Индикативные рыночные данные​

Поток котировщиков из списка допуска с агрегированными лучшими бидом/аском от зарегистрированных поставщиков котировок. Этот канал пока не общедоступен. Используйте рыночные данные через REST и аутентифицированные каналы заявок/исполнений/портфеля, если Hypercall не включил для вашей интеграции стриминг котировщиков.

{
"type": "IndicativeMarketData",
"instrument": "BTC-20260131-100000-C",
"best_bid": "0.0520",
"best_ask": "0.0530",
"indicative_bid_size": "50.0",
"indicative_ask_size": "25.0",
"num_providers": 3,
"published_at": 1737331200004,
"timestamp": 1737331200000
}
ПолеТипОписание
instrumentstringСимвол опциона
best_bidstringНеобязательно: лучшая агрегированная цена бид
best_askstringНеобязательно: лучшая агрегированная цена аск
bid_ivnumberНеобязательно: подразумеваемая волатильность лучшего бида
ask_ivnumberНеобязательно: подразумеваемая волатильность лучшего аска
indicative_bid_sizestringНеобязательно: суммарный объём бидов по всем поставщикам
indicative_ask_sizestringНеобязательно: суммарный объём асков по всем поставщикам
num_providersintegerЧисло активных поставщиков котировок
rfq_provider_quotesarrayНеобязательно: котировки по каждому поставщику: wallet, bid_price, ask_price, max_bid_size, max_ask_size, updated_at
published_atintegerВремя публикации строки по часам API, миллисекунды Unix. Возраст rfq_provider_quotes измеряйте относительно этого значения, а не своих локальных часов
timestampintegerВремя последней котировки поставщика, миллисекунды Unix

Пакет индикативных рыночных данных​

Отправляется в канале indicative_market_data_v2 (см. Пакетная доставка). Каждый элемент updates содержит те же поля, что и сообщение IndicativeMarketData, но без type.

{
"type": "IndicativeMarketDataBatch",
"flushed_at": 1737331200025,
"updates": [
{
"instrument": "BTC-20260131-100000-C",
"best_bid": "0.0520",
"best_ask": "0.0530",
"num_providers": 3,
"published_at": 1737331200004,
"timestamp": 1737331200000
},
{
"instrument": "BTC-20260131-105000-C",
"best_bid": "0.0310",
"best_ask": "0.0322",
"num_providers": 2,
"published_at": 1737331200004,
"timestamp": 1737331200001
}
]
}
ПолеТипОписание
flushed_atintegerВремя отправки пакета по часам API, миллисекунды Unix. flushed_at - published_at даёт задержку пакетирования строки
updatesarrayСтроки, опубликованные за окно, от самых ранних к поздним

Котировки RFQ (аутентифицированное)​

Котировки, полученные в ответ на ваш запрос RFQ.

{
"type": "RfqQuotes",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"quotes": [
{
"quote_id": "660e8400-e29b-41d4-a716-446655440001",
"net_premium": "52.30",
"expires_at": 1737331225000
}
],
"status": "quoted",
"taker_wallet": "0x1234...abcd"
}

Обновление статуса RFQ (аутентифицированное)​

Изменение статуса отправленного вами RFQ.

{
"type": "RfqStatusUpdate",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "executed",
"taker_wallet": "0x1234...abcd"
}

Синхронизация часов​

Отправьте ClockSync с произвольной строкой nonce, чтобы получить отметку часов API на том же соединении, например для оценки транспортной задержки:

{"type": "ClockSync", "nonce": "abc"}
{"type": "ClockSynced", "nonce": "abc", "server_at": 1737331200000}

server_at указывается в миллисекундах Unix. Аутентификация не требуется.

Ошибка​

Сообщение об ошибке сервера. Если присутствует code, ориентируйтесь на него, а не на message.

{
"type": "Error",
"message": "Unknown channel 'foobar'",
"code": "unknown_channel",
"channel": "foobar"
}
ПолеТипОписание
messagestringОписание в удобочитаемом виде
codestringНеобязательно: стабильный код причины. unknown_channel: в Subscribe указан несуществующий канал
channelstringНеобязательно: канал, указанный в отклонённом Subscribe

На Subscribe к каналу, которого нет в разделе Доступные каналы, сервер отвечает ошибкой unknown_channel, и подписка не создаётся. Прежние серверы подтверждали любое имя и затем ничего не присылали.

Остальные ошибки содержат только message. Примеры: Authentication required for this channel при подписке на аутентифицированный канал до Authenticate, Not authenticated при PlaceOrder до Authenticate, а также Invalid wallet address: ....

Аутентификация​

Для аутентифицированных каналов после подключения требуется сообщение идентификации кошелька:

{"type": "Authenticate", "wallet": "0x1234567890abcdef..."}

Сообщения в аутентифицированных каналах фильтруются, чтобы показывать только данные вашего кошелька. Для WebSocket-соединений подпись не требуется.

Пример: клиент на Python​

import asyncio
import websockets
import json

async def main():
uri = "wss://api.hypercall.xyz/ws"

async with websockets.connect(uri) as ws:
# Identify the wallet before subscribing to authenticated channels.
await ws.send(json.dumps({
"type": "Authenticate",
"wallet": "0xYourWallet"
}))

# Subscribe to orderbook
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "orderbook"
}))

# Subscribe to fills for BTC only
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "fills",
"symbols": ["BTC"]
}))

# Listen for messages
async for message in ws:
data = json.loads(message)
print(f"Received: {data['type']}")

asyncio.run(main())

Пример: клиент на TypeScript​

const ws = new WebSocket("wss://api.hypercall.xyz/ws");

ws.onopen = () => {
ws.send(JSON.stringify({ type: "Authenticate", wallet: "0xYourWallet" }));

// Subscribe to channels
ws.send(JSON.stringify({ type: "Subscribe", channel: "orderbook" }));

// Subscribe to order updates filtered to BTC
ws.send(JSON.stringify({
type: "Subscribe",
channel: "order_updates",
symbols: ["BTC"],
}));
};

ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(`Received: ${msg.type}`);

if (msg.type === "OrderbookUpdate") {
console.log(`${msg.symbol}: ${msg.bids.length} bids, ${msg.asks.length} asks`);
}
};

Пример: клиент на Rust​

WsClient из крейта hypercall-client сам идентифицирует кошелёк, восстанавливает подписки после переподключения и отвечает на пинги сервера. По умолчанию он предлагает сжатие permessage-deflate и переходит на несжатые фреймы, если сервер его не принимает; JSON-сообщения в обоих случаях одинаковы. Чтобы отключить предложение сжатия, вызовите with_permessage_deflate(false).

use hypercall_client::WsClient;

#[tokio::main]
async fn main() -> hypercall_client::Result<()> {
let ws = WsClient::new();
ws.connect("https://api.hypercall.xyz", None).await?;
ws.subscribe(vec!["orderbook", "index_prices"]).await?;

if let Some(msg) = ws.wait_for_message(|_| true, 5_000).await {
println!("Received: {msg:?}");
}
Ok(())
}