Packages

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

Current section

Files

Jump to
ccxt_client lib ccxt signing custom.ex
Raw

lib/ccxt/signing/custom.ex

defmodule CCXT.Signing.Custom do
@moduledoc """
Custom signing escape hatch for exchanges with non-standard authentication.
Used by: <5% of exchanges that don't fit the standard patterns.
The primary contract for custom signing is `CCXT.Signing.Behaviour`. This
module delegates to a user-provided module that implements `sign/3`.
## Configuration
signing: %{
pattern: :custom,
custom_module: MyApp.CustomSigners.MyExchange
}
## Custom Module Implementation
Custom modules should implement `CCXT.Signing.Behaviour`:
defmodule MyApp.CustomSigners.MyExchange do
@behaviour CCXT.Signing.Behaviour
@impl true
def sign(request, credentials, config) do
%{
url: request.path,
method: request.method,
headers: [{"X-API-KEY", credentials.api_key}],
body: request.body
}
end
end
## Validation
Use `validate_module/1` to verify a module at runtime:
{:ok, _} = CCXT.Signing.Custom.validate_module(MyApp.CustomSigners.MyExchange)
See `CCXT.Signing.Behaviour` for the full contract and available helpers.
"""
@behaviour CCXT.Signing.Behaviour
alias CCXT.Credentials
alias CCXT.Signing
@doc """
Callback for custom signing implementations.
## Parameters
- `request` - Map with `:method`, `:path`, `:body`, and `:params`
- `credentials` - `CCXT.Credentials` struct
- `config` - Exchange-specific signing configuration
## Returns
A signed request map with `:url`, `:method`, `:headers`, and `:body`.
"""
@callback sign(Signing.request(), Credentials.t(), Signing.config()) :: Signing.signed_request()
@doc """
Validates that a module implements the signing contract (`sign/3`).
Returns `{:ok, module}` if the module exports `sign/3`, or
`{:error, reason}` with an actionable message.
"""
@spec validate_module(module()) :: {:ok, module()} | {:error, String.t()}
def validate_module(module) when is_atom(module) do
case Code.ensure_loaded(module) do
{:module, _} ->
if function_exported?(module, :sign, 3) do
{:ok, module}
else
{:error, "#{inspect(module)} must implement sign/3 (see CCXT.Signing.Behaviour)"}
end
{:error, reason} ->
{:error, "Could not load #{inspect(module)}: #{reason} (see CCXT.Signing.Behaviour)"}
end
end
@doc """
Delegates signing to the custom module specified in config.
Raises `ArgumentError` if `:custom_module` is not specified in config
or if the module does not implement `sign/3`.
"""
@impl true
@spec sign(Signing.request(), Credentials.t(), Signing.config()) :: Signing.signed_request()
def sign(request, credentials, config) do
case Map.fetch(config, :custom_module) do
{:ok, module} when is_atom(module) ->
case validate_module(module) do
{:ok, _} ->
module.sign(request, credentials, config)
{:error, reason} ->
raise ArgumentError, reason
end
_ ->
raise ArgumentError, """
Custom signing pattern requires :custom_module in config.
Example:
signing: %{
pattern: :custom,
custom_module: MyApp.CustomSigners.MyExchange
}
"""
end
end
end