from __future__ import annotations import hashlib import hmac import time from collections.abc import Mapping from urllib.parse import urlencode def utc_timestamp_ms() -> int: return int(time.time() * 1000) def canonical_query(params: Mapping[str, object]) -> str: filtered = { key: value for key, value in params.items() if value is not None and value != "" } ordered = sorted(filtered.items(), key=lambda item: item[0]) return urlencode(ordered, doseq=True) def sign_query(secret_key: str, query: str) -> str: return hmac.new( secret_key.encode("utf-8"), query.encode("utf-8"), hashlib.sha256, ).hexdigest() def build_signed_params( params: Mapping[str, object], secret_key: str, *, timestamp_ms: int | None = None, recv_window_ms: int | None = 5_000, ) -> dict[str, object]: signed = dict(params) signed["timestamp"] = utc_timestamp_ms() if timestamp_ms is None else int(timestamp_ms) try: parsed_recv_window = int(recv_window_ms) if recv_window_ms is not None else 5_000 except Exception: parsed_recv_window = 5_000 signed["recvWindow"] = parsed_recv_window if parsed_recv_window > 0 else 5_000 query = canonical_query(signed) signed["signature"] = sign_query(secret_key, query) return signed