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 auth.ex
Raw

lib/ccxt/ws/auth.ex

defmodule CCXT.WS.Auth do
@moduledoc """
WebSocket authentication pattern library.
This module provides a unified interface for WebSocket authentication across
exchanges. Instead of per-exchange auth code, we implement parameterized
patterns that cover all CCXT Pro exchanges with private WS channels.
## Auth Patterns
| Pattern | Exchanges | Description |
|---------|-----------|-------------|
| `:direct_hmac_expiry` | Bybit, Bitmex | GET/realtime + expires, op: "auth" |
| `:iso_passphrase` | OKX, Bitget | ISO timestamp + passphrase |
| `:jsonrpc_linebreak` | Deribit | JSON-RPC with linebreak payload |
| `:sha384_nonce` | Bitfinex | AUTH + nonce with SHA384 |
| `:sha512_newline` | Gate | Newline-separated with SHA512 |
| `:listen_key` | Binance | REST pre-auth for listen key |
| `:rest_token` | Kraken | REST pre-auth for token |
| `:inline_subscribe` | Coinbase | Auth in subscribe messages |
## Usage
Auth is configured in the spec's ws.auth section and used via the adapter:
# Authenticate before subscribing to private channels
:ok = Adapter.authenticate(adapter)
# Subscribe to private channel
:ok = Adapter.subscribe(adapter, watch_balance_sub)
For manual auth message building:
config = spec.ws.auth
{:ok, message} = CCXT.WS.Auth.build_auth_message(credentials, config)
"""
alias CCXT.Credentials
alias CCXT.WS.Auth.DirectHmacExpiry
alias CCXT.WS.Auth.InlineSubscribe
alias CCXT.WS.Auth.IsoPassphrase
alias CCXT.WS.Auth.JsonrpcLinebreak
alias CCXT.WS.Auth.ListenKey
alias CCXT.WS.Auth.RestToken
alias CCXT.WS.Auth.Sha384Nonce
alias CCXT.WS.Auth.Sha512Newline
@type pattern ::
:direct_hmac_expiry
| :iso_passphrase
| :jsonrpc_linebreak
| :sha384_nonce
| :sha512_newline
| :htx_variant
| :listen_key
| :rest_token
| :inline_subscribe
| :generic_hmac
@type config :: map()
@type auth_message :: map()
@type pre_auth_result :: {:ok, map()} | {:error, term()}
@type build_result :: {:ok, auth_message()} | :no_message | {:error, term()}
@doc """
Performs pre-authentication if the pattern requires it (REST call for token/key).
Returns `{:ok, pre_auth_data}` with any data needed for WS connection,
or `{:error, reason}` on failure.
For patterns that don't need pre-auth, returns `{:ok, %{}}`.
"""
@spec pre_auth(pattern(), Credentials.t(), config(), keyword()) :: pre_auth_result()
def pre_auth(:listen_key, credentials, config, opts) do
ListenKey.pre_auth(credentials, config, opts)
end
def pre_auth(:rest_token, credentials, config, opts) do
RestToken.pre_auth(credentials, config, opts)
end
def pre_auth(_pattern, _credentials, _config, _opts) do
{:ok, %{}}
end
@doc """
Builds the WebSocket authentication message.
Returns `{:ok, message}` with the JSON-encodable auth message,
`:no_message` if no WS message is needed, or `{:error, reason}`.
"""
@spec build_auth_message(pattern(), Credentials.t(), config(), keyword()) :: build_result()
def build_auth_message(:direct_hmac_expiry, credentials, config, opts) do
DirectHmacExpiry.build_auth_message(credentials, config, opts)
end
def build_auth_message(:iso_passphrase, credentials, config, opts) do
IsoPassphrase.build_auth_message(credentials, config, opts)
end
def build_auth_message(:jsonrpc_linebreak, credentials, config, opts) do
JsonrpcLinebreak.build_auth_message(credentials, config, opts)
end
def build_auth_message(:sha384_nonce, credentials, config, opts) do
Sha384Nonce.build_auth_message(credentials, config, opts)
end
def build_auth_message(:sha512_newline, credentials, config, opts) do
Sha512Newline.build_auth_message(credentials, config, opts)
end
def build_auth_message(:listen_key, _credentials, _config, _opts) do
# Listen key pattern: auth via REST, no WS message needed
:no_message
end
def build_auth_message(:rest_token, _credentials, _config, _opts) do
# Rest token pattern: token sent in subscribe messages, no standalone auth
:no_message
end
def build_auth_message(:inline_subscribe, _credentials, _config, _opts) do
# Inline pattern: auth included in subscribe, no standalone auth
:no_message
end
def build_auth_message(:generic_hmac, credentials, config, opts) do
# Generic fallback - try to build based on config
DirectHmacExpiry.build_auth_message(credentials, config, opts)
end
def build_auth_message(:htx_variant, _credentials, _config, _opts) do
# TODO: HTX has market-specific auth formats (different for spot vs futures).
# Implement CCXT.WS.Auth.HtxVariant when HTX WebSocket support is needed.
{:error, {:not_implemented, :htx_variant}}
end
def build_auth_message(pattern, _credentials, _config, _opts) do
{:error, {:unknown_pattern, pattern}}
end
@doc """
Builds auth data to include in subscribe messages (for inline_subscribe pattern).
"""
@spec build_subscribe_auth(pattern(), Credentials.t(), config(), String.t() | nil, list() | nil) ::
map() | nil
def build_subscribe_auth(:inline_subscribe, credentials, config, channel, symbols) do
InlineSubscribe.build_subscribe_auth(credentials, config, channel, symbols)
end
def build_subscribe_auth(:rest_token, _credentials, config, _channel, _symbols) do
# Include token from pre-auth in subscribe messages
token = config[:token]
if token, do: %{"token" => token}
end
def build_subscribe_auth(_pattern, _credentials, _config, _channel, _symbols) do
nil
end
@doc """
Handles the authentication response from the server.
Returns `:ok` on successful auth, `{:ok, auth_meta}` with metadata
(e.g., `%{ttl_ms: 900_000}` for session expiry scheduling), or
`{:error, reason}` on failure.
"""
@spec handle_auth_response(pattern(), map(), map()) :: :ok | {:ok, map()} | {:error, term()}
def handle_auth_response(:direct_hmac_expiry, response, state) do
DirectHmacExpiry.handle_auth_response(response, state)
end
def handle_auth_response(:iso_passphrase, response, state) do
IsoPassphrase.handle_auth_response(response, state)
end
def handle_auth_response(:jsonrpc_linebreak, response, state) do
JsonrpcLinebreak.handle_auth_response(response, state)
end
def handle_auth_response(:sha384_nonce, response, state) do
Sha384Nonce.handle_auth_response(response, state)
end
def handle_auth_response(:sha512_newline, response, state) do
Sha512Newline.handle_auth_response(response, state)
end
def handle_auth_response(_pattern, response, _state) do
# Default: check for common success indicators
cond do
response["success"] == true -> :ok
response["event"] == "auth" && response["status"] == "OK" -> :ok
response["result"] && response["result"]["access_token"] -> :ok
true -> {:error, {:auth_failed, response}}
end
end
@doc """
Returns the list of supported auth patterns.
"""
@spec patterns() :: [pattern()]
def patterns do
[
:direct_hmac_expiry,
:iso_passphrase,
:jsonrpc_linebreak,
:sha384_nonce,
:sha512_newline,
:htx_variant,
:listen_key,
:rest_token,
:inline_subscribe,
:generic_hmac
]
end
@doc """
Checks if a pattern requires pre-authentication.
"""
@spec requires_pre_auth?(pattern()) :: boolean()
def requires_pre_auth?(:listen_key), do: true
def requires_pre_auth?(:rest_token), do: true
def requires_pre_auth?(_), do: false
@doc """
Checks if a pattern uses inline authentication.
"""
@spec inline_auth?(pattern()) :: boolean()
def inline_auth?(:inline_subscribe), do: true
def inline_auth?(_), do: false
@doc """
Returns the auth module for a pattern (for testing).
"""
@spec module_for_pattern(pattern()) :: module() | nil
def module_for_pattern(:direct_hmac_expiry), do: DirectHmacExpiry
def module_for_pattern(:iso_passphrase), do: IsoPassphrase
def module_for_pattern(:jsonrpc_linebreak), do: JsonrpcLinebreak
def module_for_pattern(:sha384_nonce), do: Sha384Nonce
def module_for_pattern(:sha512_newline), do: Sha512Newline
def module_for_pattern(:listen_key), do: ListenKey
def module_for_pattern(:rest_token), do: RestToken
def module_for_pattern(:inline_subscribe), do: InlineSubscribe
def module_for_pattern(_), do: nil
end