Packages

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

Current section

Files

Jump to
ccxt_client lib ccxt unified.ex
Raw

lib/ccxt/unified.ex

defmodule CCXT.Unified do
@moduledoc false
# Internal: method definitions and dispatch for unified API functions.
#
# The @method_defs list is the single source of truth for unified methods.
# Each entry: {elixir_name, js_capability_name, required_params, description}.
# CCXT module generates functions + bang variants + api() declarations from
# this list at compile time.
#
# Optional params (since, limit, price, tag, symbols, etc.) go in opts.
alias CCXT.Dispatch
alias CCXT.Error
alias CCXT.Exchange
alias CCXT.Registry
alias CCXT.Symbol
# Dispatch-level opts separated from exchange params
@dispatch_opts [:endpoint_index, :timeout, :plug, :headers, :base_url]
# ===========================================================================
# Method Definitions
#
# {elixir_name, js_capability_name, required_params, description}
#
# Signature convention:
# - Required positional args only (exchange is implicit, opts is appended)
# - Optional CCXT params (since, limit, price, symbol when optional) → opts
# - Generated function: func(exchange, ...required, opts \\ [])
# ===========================================================================
# Curated descriptions for the most-used unified methods.
# Remaining methods get auto-generated descriptions via description_for/1.
@curated_descriptions %{
fetch_ticker: "Fetch latest ticker (price, volume, bid/ask) for a trading pair.",
fetch_tickers: "Fetch tickers for all or specified trading pairs.",
fetch_order_book: "Fetch the order book (bids and asks) for a symbol.",
fetch_trades: "Fetch recent public trades for a symbol.",
fetch_ohlcv: "Fetch OHLCV candlestick data for a symbol and timeframe.",
fetch_markets: "Fetch all available markets and trading pairs.",
fetch_currencies: "Fetch all available currencies and their details.",
fetch_time: "Fetch the exchange server time.",
fetch_status: "Fetch the exchange operational status.",
create_order: "Create a new order on the exchange.",
create_orders: "Create multiple orders in a single request.",
cancel_order: "Cancel an existing order by ID.",
cancel_all_orders: "Cancel all open orders, optionally filtered by symbol.",
edit_order: "Edit an existing order (modify price, amount, etc.).",
fetch_order: "Fetch details of a specific order by ID.",
fetch_orders: "Fetch a list of orders, optionally filtered by symbol.",
fetch_open_orders: "Fetch all currently open orders.",
fetch_closed_orders: "Fetch completed (filled) orders.",
fetch_balance: "Fetch account balance across all currencies.",
fetch_my_trades: "Fetch the authenticated user's trade history.",
fetch_positions: "Fetch all open derivative positions.",
fetch_position: "Fetch a specific derivative position for a symbol.",
set_leverage: "Set leverage for a symbol on the exchange.",
set_margin_mode: "Set margin mode (cross/isolated) for a symbol.",
fetch_funding_rate: "Fetch the current funding rate for a perpetual swap.",
fetch_funding_rates: "Fetch funding rates for all perpetual swaps.",
fetch_deposit_address: "Fetch a deposit address for a currency.",
withdraw: "Withdraw funds to an external address.",
fetch_deposits: "Fetch deposit history.",
fetch_withdrawals: "Fetch withdrawal history.",
transfer: "Transfer funds between exchange accounts (e.g., spot to futures).",
fetch_trading_fee: "Fetch the trading fee for a specific symbol.",
fetch_trading_fees: "Fetch trading fees for all symbols.",
fetch_ledger: "Fetch the account ledger (transaction history).",
close_position: "Close a derivative position for a symbol.",
fetch_open_interest: "Fetch open interest for a perpetual or futures symbol.",
fetch_leverage: "Fetch current leverage setting for a symbol.",
fetch_mark_price: "Fetch the mark price for a derivative symbol.",
fetch_liquidations: "Fetch recent liquidation events for a symbol.",
fetch_option_chain: "Fetch the full options chain for an underlying asset.",
fetch_greeks: "Fetch option greeks (delta, gamma, theta, vega) for a symbol."
}
@method_defs [
# -----------------------------------------------------------------------
# Market Data (public)
# -----------------------------------------------------------------------
{:fetch_ticker, "fetchTicker", [:symbol]},
{:fetch_tickers, "fetchTickers", []},
{:fetch_order_book, "fetchOrderBook", [:symbol]},
{:fetch_order_books, "fetchOrderBooks", []},
{:fetch_l2_order_book, "fetchL2OrderBook", [:symbol]},
{:fetch_l3_order_book, "fetchL3OrderBook", [:symbol]},
{:fetch_trades, "fetchTrades", [:symbol]},
{:fetch_ohlcv, "fetchOHLCV", [:symbol, :timeframe]},
{:fetch_markets, "fetchMarkets", []},
{:fetch_markets_by_type, "fetchMarketsByType", [:type]},
{:fetch_markets_by_type_and_sub_type, "fetchMarketsByTypeAndSubType", [:type, :sub_type]},
{:fetch_spot_markets, "fetchSpotMarkets", []},
{:fetch_swap_markets, "fetchSwapMarkets", []},
{:fetch_future_markets, "fetchFutureMarkets", []},
{:fetch_option_markets, "fetchOptionMarkets", []},
{:fetch_contract_markets, "fetchContractMarkets", []},
{:fetch_swap_and_future_markets, "fetchSwapAndFutureMarkets", []},
{:fetch_inverse_swap_markets, "fetchInverseSwapMarkets", []},
{:fetch_uta_markets, "fetchUTAMarkets", []},
{:fetch_swap_balance, "fetchSwapBalance", []},
{:fetch_usdt_markets, "fetchUSDTMarkets", []},
{:fetch_currencies, "fetchCurrencies", []},
{:fetch_currency, "fetchCurrency", [:code]},
{:fetch_currency_by_id, "fetchCurrencyById", [:id]},
{:fetch_time, "fetchTime", []},
{:fetch_status, "fetchStatus", []},
{:fetch_bids_asks, "fetchBidsAsks", []},
{:fetch_last_prices, "fetchLastPrices", []},
{:fetch_market, "fetchMarket", [:symbol]},
{:fetch_market_by_id, "fetchMarketById", [:id]},
{:fetch_mark_price, "fetchMarkPrice", [:symbol]},
{:fetch_mark_prices, "fetchMarkPrices", []},
{:fetch_market_leverage_tiers, "fetchMarketLeverageTiers", [:symbol]},
{:fetch_derivatives_market_leverage_tiers, "fetchDerivativesMarketLeverageTiers", [:symbol]},
# Funding rates
{:fetch_funding_rate, "fetchFundingRate", [:symbol]},
{:fetch_funding_rates, "fetchFundingRates", []},
{:fetch_funding_rate_history, "fetchFundingRateHistory", [:symbol]},
{:fetch_funding_history, "fetchFundingHistory", [:symbol]},
{:fetch_funding_interval, "fetchFundingInterval", [:symbol]},
{:fetch_funding_intervals, "fetchFundingIntervals", []},
{:fetch_funding_limits, "fetchFundingLimits", []},
# Open interest
{:fetch_open_interest, "fetchOpenInterest", [:symbol]},
{:fetch_open_interest_history, "fetchOpenInterestHistory", [:symbol]},
{:fetch_open_interests, "fetchOpenInterests", []},
{:fetch_derivatives_open_interest_history, "fetchDerivativesOpenInterestHistory", [:symbol]},
# Analytics
{:fetch_long_short_ratio_history, "fetchLongShortRatioHistory", [:symbol]},
{:fetch_liquidations, "fetchLiquidations", [:symbol]},
{:fetch_volatility_history, "fetchVolatilityHistory", [:symbol]},
# Options
{:fetch_option, "fetchOption", [:symbol]},
{:fetch_option_chain, "fetchOptionChain", [:symbol]},
{:fetch_greeks, "fetchGreeks", [:symbol]},
{:fetch_all_greeks, "fetchAllGreeks", []},
{:fetch_underlying_assets, "fetchUnderlyingAssets", []},
{:fetch_option_underlyings, "fetchOptionUnderlyings", []},
{:fetch_option_positions, "fetchOptionPositions", []},
{:fetch_option_ohlcv, "fetchOptionOHLCV", [:symbol, :timeframe]},
# Exchange-specific OHLCV variants
{:fetch_contract_ohlcv, "fetchContractOHLCV", [:symbol, :timeframe]},
{:fetch_spot_ohlcv, "fetchSpotOHLCV", [:symbol, :timeframe]},
{:fetch_uta_ohlcv, "fetchUTAOHLCV", [:symbol, :timeframe]},
# Exchange-specific tickers
{:fetch_contract_tickers, "fetchContractTickers", []},
# -----------------------------------------------------------------------
# Trading — Order Creation
# -----------------------------------------------------------------------
{:create_order, "createOrder", [:symbol, :type, :side, :amount]},
{:create_orders, "createOrders", [:orders]},
{:create_spot_order, "createSpotOrder", [:symbol, :type, :side, :amount]},
{:create_spot_orders, "createSpotOrders", [:orders]},
{:create_contract_order, "createContractOrder", [:symbol, :type, :side, :amount]},
{:create_contract_orders, "createContractOrders", [:orders]},
{:create_swap_order, "createSwapOrder", [:symbol, :type, :side, :amount]},
{:create_uta_order, "createUtaOrder", [:symbol, :type, :side, :amount]},
{:create_uta_orders, "createUtaOrders", [:orders]},
{:create_order_with_take_profit_and_stop_loss, "createOrderWithTakeProfitAndStopLoss",
[:symbol, :type, :side, :amount]},
{:create_trailing_amount_order, "createTrailingAmountOrder", [:symbol, :type, :side, :amount]},
{:create_trailing_percent_order, "createTrailingPercentOrder", [:symbol, :type, :side, :amount]},
{:create_twap_order, "createTwapOrder", [:symbol, :type, :side, :amount]},
{:create_market_buy_order_with_cost, "createMarketBuyOrderWithCost", [:symbol, :cost]},
{:create_market_sell_order_with_cost, "createMarketSellOrderWithCost", [:symbol, :cost]},
{:create_market_order_with_cost, "createMarketOrderWithCost", [:symbol, :side, :cost]},
# -----------------------------------------------------------------------
# Trading — Order Cancellation
# -----------------------------------------------------------------------
{:cancel_order, "cancelOrder", [:id]},
{:cancel_orders, "cancelOrders", [:ids]},
{:cancel_all_orders, "cancelAllOrders", []},
{:cancel_all_orders_after, "cancelAllOrdersAfter", [:timeout]},
{:cancel_orders_for_symbols, "cancelOrdersForSymbols", [:orders]},
{:cancel_spot_order, "cancelSpotOrder", [:id]},
{:cancel_contract_order, "cancelContractOrder", [:id]},
{:cancel_unified_order, "cancelUnifiedOrder", [:id]},
{:cancel_uta_order, "cancelUtaOrder", [:id]},
{:cancel_uta_orders, "cancelUtaOrders", [:ids]},
{:cancel_twap_order, "cancelTwapOrder", [:id]},
{:cancel_all_spot_orders, "cancelAllSpotOrders", []},
{:cancel_all_contract_orders, "cancelAllContractOrders", []},
{:cancel_all_uta_orders, "cancelAllUtaOrders", []},
# -----------------------------------------------------------------------
# Trading — Order Editing
# -----------------------------------------------------------------------
{:edit_order, "editOrder", [:id, :symbol, :type, :side]},
{:edit_orders, "editOrders", [:orders]},
{:edit_contract_order, "editContractOrder", [:id, :symbol, :type, :side]},
{:edit_spot_order, "editSpotOrder", [:id, :symbol, :type, :side]},
# -----------------------------------------------------------------------
# Trading — Order Fetching
# -----------------------------------------------------------------------
{:fetch_order, "fetchOrder", [:id]},
{:fetch_order_classic, "fetchOrderClassic", [:id]},
{:fetch_orders, "fetchOrders", []},
{:fetch_orders_classic, "fetchOrdersClassic", []},
{:fetch_open_order, "fetchOpenOrder", [:id]},
{:fetch_open_orders, "fetchOpenOrders", []},
{:fetch_closed_order, "fetchClosedOrder", [:id]},
{:fetch_closed_orders, "fetchClosedOrders", []},
{:fetch_canceled_orders, "fetchCanceledOrders", []},
{:fetch_canceled_and_closed_orders, "fetchCanceledAndClosedOrders", []},
{:fetch_order_trades, "fetchOrderTrades", [:id]},
{:fetch_order_status, "fetchOrderStatus", [:id]},
{:fetch_orders_by_ids, "fetchOrdersByIds", [:ids]},
{:fetch_orders_by_state, "fetchOrdersByState", [:state]},
{:fetch_orders_by_status, "fetchOrdersByStatus", [:status]},
{:fetch_orders_by_type, "fetchOrdersByType", [:type]},
# Exchange-specific order variants
{:fetch_spot_order, "fetchSpotOrder", [:id]},
{:fetch_spot_orders, "fetchSpotOrders", []},
{:fetch_spot_order_trades, "fetchSpotOrderTrades", [:id]},
{:fetch_spot_orders_by_states, "fetchSpotOrdersByStates", []},
{:fetch_spot_orders_by_status, "fetchSpotOrdersByStatus", []},
{:fetch_open_spot_orders, "fetchOpenSpotOrders", []},
{:fetch_open_swap_orders, "fetchOpenSwapOrders", []},
{:fetch_closed_spot_orders, "fetchClosedSpotOrders", []},
{:fetch_closed_contract_orders, "fetchClosedContractOrders", []},
{:fetch_canceled_and_closed_spot_orders, "fetchCanceledAndClosedSpotOrders", []},
{:fetch_canceled_and_closed_swap_orders, "fetchCanceledAndClosedSwapOrders", []},
{:fetch_contract_order, "fetchContractOrder", [:id]},
{:fetch_contract_orders, "fetchContractOrders", []},
{:fetch_contract_orders_by_status, "fetchContractOrdersByStatus", []},
{:fetch_uta_order, "fetchUtaOrder", [:id]},
{:fetch_uta_orders_by_status, "fetchUtaOrdersByStatus", []},
{:fetch_uta_canceled_and_closed_orders, "fetchUtaCanceledAndClosedOrders", []},
{:fetch_adl_rank, "fetchADLRank", []},
# -----------------------------------------------------------------------
# Account — Balance & Info
# -----------------------------------------------------------------------
{:fetch_balance, "fetchBalance", []},
{:fetch_spot_balance, "fetchSpotBalance", []},
{:fetch_contract_balance, "fetchContractBalance", []},
{:fetch_margin_balance, "fetchMarginBalance", []},
{:fetch_financial_balance, "fetchFinancialBalance", []},
{:fetch_uta_balance, "fetchUtaBalance", []},
{:fetch_account, "fetchAccount", []},
{:fetch_accounts, "fetchAccounts", []},
{:fetch_account_positions, "fetchAccountPositions", []},
{:create_account, "createAccount", []},
{:create_sub_account, "createSubAccount", []},
# -----------------------------------------------------------------------
# Account — Trades
# -----------------------------------------------------------------------
{:fetch_my_trades, "fetchMyTrades", []},
{:fetch_my_spot_trades, "fetchMySpotTrades", []},
{:fetch_my_contract_trades, "fetchMyContractTrades", []},
{:fetch_my_uta_trades, "fetchMyUtaTrades", []},
{:fetch_my_buys, "fetchMyBuys", []},
{:fetch_my_sells, "fetchMySells", []},
{:fetch_my_dust_trades, "fetchMyDustTrades", []},
{:fetch_my_liquidations, "fetchMyLiquidations", []},
{:fetch_my_settlement_history, "fetchMySettlementHistory", []},
# -----------------------------------------------------------------------
# Account — Positions
# -----------------------------------------------------------------------
{:fetch_position, "fetchPosition", [:symbol]},
{:fetch_positions, "fetchPositions", []},
{:fetch_positions_for_symbol, "fetchPositionsForSymbol", [:symbol]},
{:fetch_positions_history, "fetchPositionsHistory", []},
{:fetch_position_history, "fetchPositionHistory", []},
{:fetch_positions_risk, "fetchPositionsRisk", []},
{:fetch_positions_adl_rank, "fetchPositionsADLRank", []},
{:close_position, "closePosition", [:symbol]},
{:close_all_positions, "closeAllPositions", []},
{:fetch_position_mode, "fetchPositionMode", []},
{:set_position_mode, "setPositionMode", [:hedge_mode]},
# -----------------------------------------------------------------------
# Account — Leverage & Margin
# -----------------------------------------------------------------------
{:fetch_leverage, "fetchLeverage", [:symbol]},
{:fetch_leverages, "fetchLeverages", []},
{:fetch_leverage_tiers, "fetchLeverageTiers", []},
{:set_leverage, "setLeverage", [:leverage, :symbol]},
{:fetch_margin_mode, "fetchMarginMode", [:symbol]},
{:fetch_margin_modes, "fetchMarginModes", []},
{:set_margin_mode, "setMarginMode", [:margin_mode, :symbol]},
{:set_margin, "setMargin", [:amount, :symbol]},
{:add_margin, "addMargin", [:amount, :symbol]},
{:reduce_margin, "reduceMargin", [:amount, :symbol]},
{:fetch_margin_adjustment_history, "fetchMarginAdjustmentHistory", []},
# -----------------------------------------------------------------------
# Account — Fees & Trading Limits
# -----------------------------------------------------------------------
{:fetch_trading_fee, "fetchTradingFee", [:symbol]},
{:fetch_trading_fees, "fetchTradingFees", []},
{:fetch_trading_limits, "fetchTradingLimits", []},
{:fetch_trading_limits_by_id, "fetchTradingLimitsById", [:id]},
{:fetch_private_trading_fee, "fetchPrivateTradingFee", [:symbol]},
{:fetch_private_trading_fees, "fetchPrivateTradingFees", []},
{:fetch_public_trading_fee, "fetchPublicTradingFee", [:symbol]},
{:fetch_public_trading_fees, "fetchPublicTradingFees", []},
{:fetch_transaction_fee, "fetchTransactionFee", [:code]},
{:fetch_transaction_fees, "fetchTransactionFees", []},
{:fetch_private_transaction_fees, "fetchPrivateTransactionFees", []},
{:fetch_public_transaction_fees, "fetchPublicTransactionFees", []},
# -----------------------------------------------------------------------
# Account — Ledger
# -----------------------------------------------------------------------
{:fetch_ledger, "fetchLedger", []},
{:fetch_ledger_entry, "fetchLedgerEntry", [:id]},
{:fetch_ledger_by_entries, "fetchLedgerByEntries", []},
{:fetch_ledger_entries_by_ids, "fetchLedgerEntriesByIds", [:ids]},
# -----------------------------------------------------------------------
# Funding — Deposits
# -----------------------------------------------------------------------
{:fetch_deposit, "fetchDeposit", [:id]},
{:fetch_deposits, "fetchDeposits", []},
{:fetch_deposit_address, "fetchDepositAddress", [:code]},
{:fetch_deposit_addresses, "fetchDepositAddresses", []},
{:fetch_deposit_addresses_by_network, "fetchDepositAddressesByNetwork", [:code]},
{:fetch_network_deposit_address, "fetchNetworkDepositAddress", [:code]},
{:fetch_contract_deposit_address, "fetchContractDepositAddress", [:code]},
{:create_deposit_address, "createDepositAddress", [:code]},
{:fetch_deposit_method_id, "fetchDepositMethodId", [:code]},
{:fetch_deposit_method_ids, "fetchDepositMethodIds", []},
{:fetch_deposit_methods, "fetchDepositMethods", [:code]},
{:fetch_payment_methods, "fetchPaymentMethods", []},
# -----------------------------------------------------------------------
# Funding — Withdrawals
# -----------------------------------------------------------------------
{:withdraw, "withdraw", [:code, :amount, :address]},
{:fetch_withdrawal, "fetchWithdrawal", [:id]},
{:fetch_withdrawals, "fetchWithdrawals", []},
{:fetch_withdraw_addresses, "fetchWithdrawAddresses", []},
{:fetch_contract_withdrawals, "fetchContractWithdrawals", []},
{:fetch_contract_deposits, "fetchContractDeposits", []},
# -----------------------------------------------------------------------
# Funding — Deposit/Withdraw Fees
# -----------------------------------------------------------------------
{:fetch_deposit_withdraw_fee, "fetchDepositWithdrawFee", [:code]},
{:fetch_deposit_withdraw_fees, "fetchDepositWithdrawFees", []},
{:fetch_private_deposit_withdraw_fees, "fetchPrivateDepositWithdrawFees", []},
{:fetch_public_deposit_withdraw_fees, "fetchPublicDepositWithdrawFees", []},
# -----------------------------------------------------------------------
# Funding — Deposits + Withdrawals combined
# -----------------------------------------------------------------------
{:fetch_deposits_withdrawals, "fetchDepositsWithdrawals", []},
{:fetch_transactions, "fetchTransactions", []},
{:fetch_transactions_by_type, "fetchTransactionsByType", [:type]},
# -----------------------------------------------------------------------
# Funding — Transfers
# -----------------------------------------------------------------------
{:transfer, "transfer", [:code, :amount, :from_account, :to_account]},
{:fetch_transfer, "fetchTransfer", [:id]},
{:fetch_transfers, "fetchTransfers", []},
{:transfer_between_main_and_sub_account, "transferBetweenMainAndSubAccount",
[:code, :amount, :from_account, :to_account]},
{:transfer_between_sub_accounts, "transferBetweenSubAccounts", [:code, :amount, :from_account, :to_account]},
{:transfer_classic, "transferClassic", [:code, :amount, :from_account, :to_account]},
{:transfer_out, "transferOut", [:code, :amount, :address]},
{:transfer_uta, "transferUta", [:code, :amount, :from_account, :to_account]},
# -----------------------------------------------------------------------
# Margin — Borrowing
# -----------------------------------------------------------------------
{:borrow_cross_margin, "borrowCrossMargin", [:code, :amount]},
{:borrow_isolated_margin, "borrowIsolatedMargin", [:symbol, :code, :amount]},
{:repay_cross_margin, "repayCrossMargin", [:code, :amount]},
{:repay_isolated_margin, "repayIsolatedMargin", [:symbol, :code, :amount]},
{:repay_margin, "repayMargin", [:code, :amount]},
{:fetch_borrow_interest, "fetchBorrowInterest", []},
{:fetch_borrow_rate_history, "fetchBorrowRateHistory", [:code]},
{:fetch_borrow_rate_histories, "fetchBorrowRateHistories", []},
{:fetch_cross_borrow_rate, "fetchCrossBorrowRate", [:code]},
{:fetch_cross_borrow_rates, "fetchCrossBorrowRates", []},
{:fetch_isolated_borrow_rate, "fetchIsolatedBorrowRate", [:symbol]},
{:fetch_isolated_borrow_rates, "fetchIsolatedBorrowRates", []},
# -----------------------------------------------------------------------
# Convert
# -----------------------------------------------------------------------
{:create_convert_trade, "createConvertTrade", [:from_code, :to_code, :amount]},
{:fetch_convert_currencies, "fetchConvertCurrencies", []},
{:fetch_convert_quote, "fetchConvertQuote", [:from_code, :to_code, :amount]},
{:fetch_convert_trade, "fetchConvertTrade", [:id]},
{:fetch_convert_trade_history, "fetchConvertTradeHistory", []},
# -----------------------------------------------------------------------
# Settlement
# -----------------------------------------------------------------------
{:fetch_settlement_history, "fetchSettlementHistory", []},
# -----------------------------------------------------------------------
# Portfolios & Vaults
# -----------------------------------------------------------------------
{:fetch_portfolios, "fetchPortfolios", []},
{:fetch_portfolio_details, "fetchPortfolioDetails", [:portfolio_id]},
{:create_vault, "createVault", [:code, :amount]},
# -----------------------------------------------------------------------
# Gift Codes
# -----------------------------------------------------------------------
{:create_gift_code, "createGiftCode", [:code, :amount]}
]
@doc "Returns all unified method definitions as `{elixir_name, js_name, required_params, description}` tuples."
@spec method_defs() :: [{atom(), String.t(), [atom()], String.t()}]
def method_defs do
Enum.map(@method_defs, fn {name, js_name, params} ->
{name, js_name, params, description_for(name)}
end)
end
@doc "Returns the description for a unified method, curated or auto-generated."
@spec description_for(atom()) :: String.t()
def description_for(name) do
Map.get_lazy(@curated_descriptions, name, fn -> auto_description(name) end)
end
# Auto-generates a description from the method name.
# "fetch_funding_rate" → "Fetch funding rate."
defp auto_description(name) do
name
|> Atom.to_string()
|> String.replace("_", " ")
|> then(fn s -> String.upcase(String.first(s)) <> String.slice(s, 1..-1//1) end)
|> Kernel.<>(".")
end
# ===========================================================================
# Dispatch
# ===========================================================================
@doc "Dispatches a unified method call through module resolution and endpoint selection."
@spec call(Exchange.t(), atom(), String.t(), map(), keyword()) ::
{:ok, map()} | {:error, Error.t() | term()}
def call(%Exchange{} = exchange, method_atom, capability_name, params, opts) do
with {:ok, module} <- require_module(exchange),
{:ok, config} <- resolve_endpoint(module, method_atom, exchange.id, capability_name, opts) do
# Strip :endpoint_index — it's our opt, not Dispatch/Req's
dispatch_opts = Keyword.delete(opts, :endpoint_index)
final_params =
params
|> maybe_denormalize_symbol(exchange)
|> maybe_merge_request_defaults(exchange, capability_name)
Dispatch.call(exchange, config, final_params, dispatch_opts)
end
end
@doc """
Denormalize a unified `"symbol"` param (e.g. `"BTC/USDT"`) into the exchange's
native form (e.g. `"BTCUSDT"` on Binance, `"BTC-PERPETUAL"` on Deribit swap)
before the params reach Dispatch.
No-op when:
* `params` has no `"symbol"` key
* `params["symbol"]` is not a binary
* `exchange.symbol_patterns` has no entry for the detected market type
(`CCXT.Symbol.to_exchange_id/2` returns the input unchanged)
Raw generated endpoint callers (`CCXT.Bybit.public_get_v5_market_tickers/3`)
bypass this — they're expected to pass exchange-native symbols already.
"""
@spec maybe_denormalize_symbol(map(), Exchange.t()) :: map()
def maybe_denormalize_symbol(%{"symbol" => unified} = params, %Exchange{} = exchange) when is_binary(unified) do
Map.put(params, "symbol", Symbol.to_exchange_id(unified, exchange))
end
def maybe_denormalize_symbol(params, _exchange), do: params
@doc """
Merge per-method default request-body params into the caller's params.
Reads `structure.request_defaults` literals materialized onto the Exchange
struct at construction time (schema 2.0.1+, upstream ccxt_extract Task 73c).
Caller-supplied params win over defaults (`Map.put_new` semantics) — matches
CCXT JS's `this.extend(request, params)` precedence.
Unblocks exchanges like hyperliquid whose unified methods route through a
single endpoint discriminated by a literal body field (e.g. `fetchTime`
`POST /info` with `{ "type": "exchangeStatus" }`).
No-op when:
* `exchange.request_defaults` has no entry for the method
* the entries map is empty (all entries were `kind: "unresolved"` and filtered
out at Exchange construction time)
"""
@spec maybe_merge_request_defaults(map(), Exchange.t(), String.t()) :: map()
def maybe_merge_request_defaults(params, %Exchange{request_defaults: defaults}, js_name)
when is_map(params) and is_binary(js_name) do
case Map.get(defaults, js_name) do
literals when is_map(literals) and map_size(literals) > 0 ->
Enum.reduce(literals, params, fn {k, v}, acc -> Map.put_new(acc, k, v) end)
_ ->
params
end
end
@doc "Separates dispatch-level opts from exchange params."
@spec split_opts(keyword()) :: {keyword(), keyword()}
def split_opts(opts), do: Keyword.split(opts, @dispatch_opts)
@doc "Builds a string-keyed params map from required param names/values and extra opts."
@spec build_params([atom()], [term()], keyword()) :: map()
def build_params(required_names, required_values, extra) do
params =
required_names
|> Enum.zip(required_values)
|> Map.new(fn {k, v} -> {to_string(k), v} end)
Enum.reduce(extra, params, fn {k, v}, acc ->
Map.put_new(acc, to_string(k), v)
end)
end
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
# Resolves the exchange module from struct field or registry fallback
defp require_module(%Exchange{module: module}) when not is_nil(module), do: {:ok, module}
defp require_module(%Exchange{id: id}) do
case Registry.module_for(id) do
nil ->
{:error,
Error.not_supported(
exchange: id,
message: "No compiled module found for exchange #{id}"
)}
module ->
{:ok, module}
end
end
# Looks up unified endpoint configs and selects one.
# exchange_id and capability_name are used for descriptive error messages.
defp resolve_endpoint(module, method_atom, exchange_id, capability_name, opts) do
case module.__unified_endpoint__(method_atom) do
[] ->
{:error,
Error.not_supported(
exchange: exchange_id,
message: "#{exchange_id} does not support #{capability_name}"
)}
configs ->
{:ok, select_endpoint(configs, opts)}
end
end
# Selects endpoint config: override via :endpoint_index, else first in list
defp select_endpoint(configs, opts) do
case Keyword.get(opts, :endpoint_index) do
nil -> hd(configs)
idx -> Enum.at(configs, idx) || hd(configs)
end
end
end