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

lib/ccxt/signing/hmac_sha512_gate.ex

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 payload as newline-separated string:
`METHOD\\npath\\nquery_string\\nbody_hash\\ntimestamp`
4. Sign with HMAC-SHA512 using raw secret (NOT base64 decoded)
5. Signature is hex-encoded
## Configuration
signing: %{
pattern: :hmac_sha512_gate,
api_key_header: "KEY",
signature_header: "SIGN",
timestamp_header: "Timestamp"
}
## Payload Construction
```
method = "GET" or "POST"
path = "/spot/orders" (with /api/v4 prefix for signing)
query_string = "currency_pair=BTC_USDT" (sorted, urlencoded)
body_hash = sha512(body) or sha512("") if no body
timestamp = unix_seconds
payload = method + "\\n" + signing_path + "\\n" + query + "\\n" + body_hash + "\\n" + timestamp
signature = hex(hmac_sha512(payload, secret))
```
"""
@behaviour CCXT.Signing.Behaviour
alias CCXT.Credentials
alias CCXT.Signing
@doc """
Signs a request using the Gate.io HMAC-SHA512 pattern.
"""
@impl true
@spec sign(Signing.request(), Credentials.t(), Signing.config()) :: Signing.signed_request()
def sign(request, credentials, config) do
timestamp = Signing.timestamp_seconds()
timestamp_string = to_string(timestamp)
# Build query string from params
query_string = Signing.urlencode(request.params)
# Hash the body (empty string if no body)
# Gate.io requires hashing even when body is empty - sha512("") is a valid hash
body = request.body || ""
body_hash = hash_sha512(body)
# Build the signing path (Gate.io uses /api/v4 prefix for signing)
signing_path = Map.get(config, :signing_path_prefix, "/api/v4") <> request.path
# Build payload as newline-separated string
method = request.method |> to_string() |> String.upcase()
payload = Enum.join([method, signing_path, query_string, body_hash, timestamp_string], "\n")
# Sign with HMAC-SHA512 (secret is used directly, NOT base64 decoded)
signature =
payload
|> Signing.hmac_sha512(credentials.secret)
|> Signing.encode_hex()
# Build URL with query string
url =
if query_string == "" do
request.path
else
request.path <> "?" <> query_string
end
headers = build_headers(credentials, signature, timestamp_string, config)
%{
url: url,
method: request.method,
headers: headers,
# Normalize: empty string body becomes nil in the signed request
# (the hash was computed above; we don't need to send empty string over the wire)
body: if(body == "", do: nil, else: body)
}
end
defp hash_sha512(data) do
:sha512
|> :crypto.hash(data)
|> Signing.encode_hex()
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