defmodule CCXT.Signing.HmacSha512Gate do @moduledoc """ HMAC-SHA512 Gate.io-style signing pattern. Used by: Gate.io ## How it works 1. Hash the body with SHA512 (empty string if no body) 2. Generate timestamp (seconds) 3. Build newline-separated payload: `METHOD\\npath\\nquery\\nbody_hash\\ntimestamp` 4. Sign with HMAC-SHA512, encode as hex 5. Secret used raw (NOT base64 decoded) ## Configuration signing: %{ pattern: :hmac_sha512_gate, api_key_header: "KEY", signature_header: "SIGN", timestamp_header: "Timestamp", signing_path_prefix: "/api/v4" } """ @behaviour CCXT.Signing.Behaviour alias CCXT.Credentials alias CCXT.Signing @impl true @spec sign(Signing.request(), Credentials.t(), Signing.config()) :: Signing.signed_request() def sign(request, credentials, config) do timestamp_string = to_string(Signing.timestamp_seconds_from_config(config)) query_string = Signing.urlencode(request.params) # Gate.io requires hashing even empty body body = request.body || "" body_hash = body |> Signing.sha512() |> Signing.encode_hex() signing_path = Map.get(config, :signing_path_prefix, "/api/v4") <> request.path method = request.method |> to_string() |> String.upcase() payload = Enum.join([method, signing_path, query_string, body_hash, timestamp_string], "\n") signature = payload |> Signing.hmac_sha512(credentials.secret) |> Signing.encode_hex() url = if query_string == "", do: request.path, else: request.path <> "?" <> query_string headers = build_headers(credentials, signature, timestamp_string, config) %{ url: url, method: request.method, headers: headers, body: if(body == "", do: nil, else: body) } end defp build_headers(credentials, signature, timestamp, config) do api_key_header = Map.get(config, :api_key_header, "KEY") signature_header = Map.get(config, :signature_header, "SIGN") timestamp_header = Map.get(config, :timestamp_header, "Timestamp") [ {api_key_header, credentials.api_key}, {signature_header, signature}, {timestamp_header, timestamp}, {"Content-Type", "application/json"} ] end end