Packages

Elixir client library for cryptocurrency exchanges — generated from CCXT specs via compile-time macros.

Current section

Files

Jump to
ccxt_client lib ccxt ws subscription behaviour.ex
Raw

lib/ccxt/ws/subscription/behaviour.ex

defmodule CCXT.WS.Subscription.Behaviour do
@moduledoc """
Behaviour for WebSocket subscription pattern implementations.
Each pattern module converts a list of pre-formatted channel strings (or
per-pattern objects) into the exchange-native subscribe frame. Frame
encoding (Jason) happens at the WS boundary in `CCXT.WS.subscribe/3`, not
inside pattern modules — modules return plain maps.
## Return shape
`subscribe/2` and `unsubscribe/2` return `map() | [map()] | {:error, term()}`.
- Most exchanges accept an array of channels in a single frame → return a
`map()`.
- HTX (`:sub_subscribe`) and BingX (`:reqtype_sub`) require one frame per
channel → return a `[map()]` with one frame per channel.
- Upbit-style custom (`config[:custom_type] == "array_format"`) also
returns a `[map()]`.
- Implementations that enforce an input-shape contract may return
`{:error, term()}` (e.g. `:multiple_maps_not_supported` or
`:mixed_channel_types` from `EventSubscribe` / `MethodParams`).
`CCXT.WS.Subscription.build_subscribe/3` passes these through to the
caller verbatim rather than wrapping them in `{:ok, _}`.
`CCXT.WS.subscribe/3` handles both frame shapes by iterating the list
when present and sending one `ZenWebsocket.Client.send_message/2` per
frame.
## Channel formatting is the caller's responsibility
Pattern modules do **not** format channel strings from unified symbols.
Callers pass pre-formatted channels like `"tickers.BTCUSDT"` or
`"market.btcusdt.ticker"`. A future task may introduce spec-driven
channel templates; this behaviour is deliberately narrow.
## Implementing a Pattern
defmodule CCXT.WS.Subscription.OpSubscribe do
@behaviour CCXT.WS.Subscription.Behaviour
@impl true
def subscribe(channels, config) do
%{
(config[:op_field] || "op") => "subscribe",
(config[:args_field] || "args") => channels
}
end
@impl true
def unsubscribe(channels, config) do
%{
(config[:op_field] || "op") => "unsubscribe",
(config[:args_field] || "args") => channels
}
end
end
"""
@type channel :: String.t() | map()
@type config :: map()
@type frame :: map()
@callback subscribe(channels :: [channel()], config :: config()) ::
frame() | [frame()] | {:error, term()}
@callback unsubscribe(channels :: [channel()], config :: config()) ::
frame() | [frame()] | {:error, term()}
@optional_callbacks [unsubscribe: 2]
end