Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Integración y desarrollo14 min de lectura

Módulo utils de Hyperliquid: firma, hashing y codificación de acciones

Un análisis profundo de la capa utils de hyperliquid-python-sdk: hashing determinista de acciones, firma de datos tipados EIP-712, formateo de floats para el wire y los modos de fallo que rompen las solicitudes firmadas.

TL;DR

La capa utils de hyperliquid-python-sdk existe porque el endpoint de exchange de Hyperliquid no acepta un cuerpo JSON con token bearer para acciones de trading. En su lugar, el cliente construye una acción tipada, la hashea de forma determinista, firma ese hash con la clave privada del API wallet usando datos tipados al estilo EIP-712 y adjunta la firma, el nonce y la dirección de vault a la solicitud. El módulo utils proporciona los helpers de hashing de acciones, la construcción de la firma y los helpers de formateo de floats para el wire que mantienen los bytes firmados idénticos a los bytes transmitidos. El formateo de floats es un riesgo de corrección: si el cliente firma una cadena decimal y transmite otra, el hash reconstruido difiere y el exchange rechaza la solicitud. Este artículo explica el mecanismo, proporciona ejemplos ejecutables en Python para el envío de órdenes y sobres multi-acción, y asigna los modos de fallo a pasos de diagnóstico.

Por qué Hyperliquid requiere una capa de firma en el cliente

La API de exchange de Hyperliquid no es una API REST convencional donde un token bearer autoriza un cuerpo JSON. Según la documentación de la API de Hyperliquid, las acciones de trading deben ser firmadas por un API wallet cuya dirección esté registrada en la cuenta. El exchange verifica que la solicitud fue autorizada por ese wallet, que es el mecanismo que permite a un API wallet operar sin tener permiso de retiro.

La capa de firma, por tanto, se sitúa entre la lógica de tu aplicación y el transporte HTTP. Toma una acción estructurada, produce un hash determinista de esa acción, firma el hash como datos tipados EIP-712 sobre un dominio y envuelve el resultado con un nonce y una dirección de vault opcional. El hyperliquid-python-sdk expone esto en su módulo utils para que los llamadores no reimplementen las reglas de hashing y codificación.

Esta separación también explica por qué los artículos sobre 'hyperliquid utils github' no deberían convertirse en instrucciones de instalación. El artefacto importante es el modelo mental a nivel de protocolo: acción, hash determinista, firma de datos tipados, sobre nonce/vault, POST. Ese modelo se transfiere entre los SDKs de Python, Rust y Go y entre las integraciones de CCXT.

  • El endpoint de exchange multiplexa acciones tipadas en lugar de exponer una ruta por acción.
  • La clave del API wallet autoriza acciones de trading pero es distinta de la autoridad de retiro de la cuenta.
  • El payload firmado debe ser byte a byte idéntico al payload que reconstruye el servidor.

Las tres responsabilidades del módulo utils

La capa utils tiene tres tareas distintas, y confundirlas es una fuente común de errores de integración. Primero, hashea el payload de la acción de forma determinista para que los bytes firmados coincidan con el payload exacto que reconstruye el servidor. Segundo, construye la firma sobre los datos tipados, siguiendo el esquema de hashing y firma estructurada EIP-712 descrito en EIP-712. Tercero, formatea los floats en cadenas de wire con un número fijo de decimales.

Los helpers de formateo de floats no son cosméticos. Los precios y tamaños deben convertirse a cadena con el número correcto de decimales para cada activo antes del hashing. Si el cliente firma '1.2345678901' y transmite '1.23456789', el hash difiere y el exchange rechaza o valora mal la orden. Los helpers de formateo de utils y las reglas de decimales de tamaño y precio por moneda deben usarse de forma consistente en ambos lados de la frontera de firma.

El manejo de direcciones y claves es la cuarta responsabilidad implícita. La clave privada del API wallet debe cargarse desde el entorno o un gestor de secretos, nunca confirmarse en el control de código fuente, y el objeto wallet del SDK debe ser el único lugar donde vive la clave en el proceso. El hub de aprendizaje de OnFinality tiene material relacionado sobre selección de endpoints y manejo de errores que complementa este artículo centrado en la firma.

  • Hashing de acciones: serialización determinista del payload de la acción.
  • Construcción de la firma: firma de datos tipados sobre el dominio de Hyperliquid.
  • Formateo de floats: conversión a cadena con decimales fijos para precios y tamaños.
  • Manejo de claves: carga desde entorno o gestor de secretos, un único objeto wallet en el proceso.

Hashing de acciones y el requisito de identidad de bytes

El hash se calcula sobre el payload de la acción después de haberlo normalizado a la representación de wire. Eso significa que los campos numéricos ya deben ser cadenas con los decimales correctos, el orden de los campos debe seguir el codificador del SDK, y los campos opcionales como la dirección de vault deben estar presentes o ausentes de forma consistente. El servidor reconstruye el mismo payload a partir del cuerpo de la solicitud y recalcula el hash; cualquier divergencia produce un desajuste de firma o de hash del payload.

En la práctica, el patrón más seguro es construir la acción una vez, formatearla una vez con los helpers de utils y luego usar ese mismo objeto tanto para el hashing como para la transmisión. No formatees para mostrar y luego vuelvas a formatear para transmitir, y no dejes que un serializador JSON reordene o recuantice campos entre los dos pasos.

El artículo Manejo de errores de la API de Hyperliquid y rechazos de órdenes cubre cómo el exchange expone estos desajustes, pero la causa raíz casi siempre es una divergencia de formateo u orden en el cliente.

  • Normaliza los campos numéricos a cadenas antes del hashing.
  • Usa un único objeto de acción formateado tanto para firmar como para transmitir.
  • Evita serializadores intermedios que reordenen o recuanticen campos.

Firma de datos tipados EIP-712 sobre el dominio de Hyperliquid

La firma se produce sobre datos tipados en lugar de sobre un hash crudo del cuerpo JSON. La estructura de datos tipados incluye un separador de dominio y el tipo de acción, lo que vincula la firma al exchange de Hyperliquid y evita la repetición contra un dominio diferente. La especificación EIP-712 define las reglas de hashing y firma que sigue el SDK.

El sobre adjunto a la solicitud contiene los componentes de la firma, un nonce y una dirección de vault opcional. Los nonces se rastrean por firmante y cada nueva acción debe ser mayor que el nonce más pequeño de los últimos 100 del firmante y no debe haberse usado nunca antes, por lo que un contador monotónicamente creciente por firmante es la regla práctica; un nonce repetido o caducado se rechaza incluso cuando la firma en sí es válida. El campo de dirección de vault solo tiene sentido cuando la acción se ejecuta en nombre de un vault.

Como la estructura de datos tipados es a nivel de protocolo, es idéntica entre clientes. Una firma producida por el SDK de Python y una firma producida por el SDK de Rust o Go sobre la misma acción y nonce deberían verificarse de forma idéntica. Por eso el modelo mental se transfiere y por eso el módulo utils se entiende mejor como una implementación del protocolo, no como una comodidad específica de Python.

  • El separador de dominio vincula la firma al exchange de Hyperliquid.
  • El nonce debe ser monotónico por firmante.
  • La dirección de vault es opcional y solo se establece cuando se actúa en nombre de un vault.

Ejemplo ejecutable: firmar y enviar una orden, luego leerla de vuelta

El ejemplo siguiente se mantiene dentro de la capa utils en lugar de ocultarla detrás de Exchange.order: construye la orden de wire, la convierte en la acción firmada, la hashea y la firma con sign_l1_action, e imprime el payload exacto que va al endpoint de exchange. Asume que la clave privada del API wallet y la dirección de la cuenta maestra están en variables de entorno. El endpoint es un marcador de posición aquí; selecciona uno de la página de endpoints RPC de Hyperliquid (RPC Assistant) o de tu propia infraestructura.

Dos detalles importan más que el código repetitivo. Primero, el payload que se imprime es el payload que debe enviarse: volver a serializar la orden con una representación de float diferente cambia los bytes de msgpack y, por tanto, el hash. Segundo, los campos firmados p y s son cadenas producidas por float_to_wire, no floats, porque msgpack codifica enteros y floats de forma diferente y el exchange reconstruye la acción a partir de las cadenas transmitidas. Para confirmar el viaje de ida y vuelta, lee la orden de vuelta con el endpoint info por oid y compara el precio y el tamaño reportados con las cadenas que firmaste.

import json, os
from eth_account import Account
from hyperliquid.utils.constants import MAINNET_API_URL
from hyperliquid.utils.signing import (
    float_to_wire, get_timestamp_ms, order_request_to_order_wire,
    order_wires_to_order_action, sign_l1_action,
)

acct = Account.from_key(os.environ["HL_API_WALLET_PRIVATE_KEY"])  # API wallet

# 1) Build the action exactly as the signed payload will carry it.
req = {"coin": "BTC", "is_buy": True, "sz": 0.001, "limit_px": 60000.0,
       "order_type": {"limit": {"tif": "Gtc"}}, "reduce_only": False}
order = order_request_to_order_wire(req, asset=0)      # p and s become strings
action = order_wires_to_order_action([order], grouping="na")

# 2) Sign: msgpack(action) + 8-byte nonce + vault flag -> keccak -> EIP-712 "Agent".
nonce = get_timestamp_ms()
signature = sign_l1_action(acct, action, None, nonce, None, True)

# 3) Transmit exactly the strings that were signed.
payload = {"action": action, "nonce": nonce, "signature": signature, "vaultAddress": None}
print(json.dumps(payload, indent=2))
# POST as JSON to MAINNET_API_URL + "/exchange" with your HTTP client.

print("signed p:", repr(action["orders"][0]["p"]),
      "signed s:", repr(action["orders"][0]["s"]),
      "float_to_wire(60000.0):", repr(float_to_wire(60000.0)))

Reutilizar un sobre firmado entre tipos de acción

El endpoint de exchange multiplexa acciones tipadas, por lo que la misma ruta de firma se aplica a órdenes, cancelaciones y actualizaciones de apalancamiento. El conjunto de campos difiere por acción, pero la estructura del sobre es la misma: acción, nonce, firma y dirección de vault opcional. Ver esto en código hace concreta la abstracción y muestra dónde divergen los campos de cada acción.

El ejemplo siguiente emite una cancelación y una solicitud de actualización de apalancamiento a través del mismo objeto exchange. Cada llamada construye internamente la acción, la formatea, la hashea, la firma y adjunta un nonce nuevo. Cada llamada vuelve a firmar una acción nueva con su propio nonce, por lo que el lector debería observar que no se necesita código de firma separado por tipo de acción; la capa utils maneja las diferencias de codificación.

import os
from hyperliquid.exchange import Exchange
from hyperliquid.utils import constants
from eth_account import Account

api_key = os.environ["HL_API_WALLET_PRIVATE_KEY"]
account = Account.from_key(api_key)
account_address = os.environ["HL_ACCOUNT_ADDRESS"]

exchange = Exchange(account, constants.MAINNET_API_URL, account_address=account_address)

# Place an order to obtain an oid.
order_result = exchange.order(
    "ETH", True, 0.01, 3000.0, {"limit": {"tif": "Gtc"}}, reduce_only=False
)
print("order_result:", order_result)

# Cancel the order using the same signing path.
if order_result.get("status") == "ok":
    oid = order_result["response"]["data"]["statuses"][0]["resting"]["oid"]
    cancel_result = exchange.cancel("ETH", oid)
    print("cancel_result:", cancel_result)

# Update leverage for the same asset; a different action type, same envelope.
leverage_result = exchange.update_leverage(5, "ETH", is_cross=True)
print("leverage_result:", leverage_result)

El formateo de floats como riesgo de corrección

El formateo de floats es la fuente más común de desajustes de hash del payload. Cada activo tiene sus propias reglas de decimales de tamaño y precio, y la representación de wire debe respetarlas. Si el cliente firma un valor con más decimales de los que permite el activo, o con un redondeo diferente al valor transmitido, el servidor reconstruye un payload diferente y la verificación de la firma falla.

Los helpers de utils existen para centralizar este formateo. Úsalos para cada campo numérico que participe en la acción firmada, y no apliques redondeo adicional después del formateo. Si tu aplicación necesita mostrar un precio de forma diferente, formatea para mostrar por separado del payload firmado.

Un diagnóstico útil es registrar la acción formateada inmediatamente antes de firmar y compararla con el cuerpo de la solicitud capturado en la capa de transporte. Si los dos difieren en cualquier cadena numérica, el desajuste está en el lado del cliente y ninguna cantidad de reintentos lo solucionará.

  • Usa los helpers de formateo del SDK para todos los campos numéricos firmados.
  • No vuelvas a redondear después del formateo.
  • Registra la acción formateada y compárala con el cuerpo transmitido.

Medir tu propio viaje de ida y vuelta contra tu endpoint

Como el comportamiento del endpoint varía según el proveedor, la única medición fiable es la que realizas contra tu propio endpoint. La tabla siguiente es una plantilla: rellénala con observaciones de tu entorno en lugar de confiar en cifras publicadas. El objetivo es distinguir los fallos de firma de los fallos de transporte o del motor de riesgo.

Ejecuta la misma acción firmada repetidamente y registra la categoría de resultado de cada intento. Si los fallos se agrupan en errores de firma o de hash del payload, el problema está en la capa de firma. Si los fallos se agrupan en errores de nonce, el problema es de ordenación. Si los fallos se agrupan en respuestas de margen o riesgo, la capa de firma funciona y el problema es el estado de la cuenta.

  • Columnas de la tabla de resultados: intento, tipo de acción, categoría de resultado, código de error, hash del payload formateado, hash del payload transmitido.
  • Categorías de resultado: aceptado, error de firma, desajuste de hash del payload, error de nonce, rechazo del motor de riesgo, error de transporte.
  • Compara los hashes formateado y transmitido para cada intento fallido.

Modos de fallo y el paso de diagnóstico que los distingue

Los errores de validación de firma y dirección suelen significar que el API wallet equivocado está firmando, o que el dominio de firma no coincide con el entorno. Confirma que la dirección del API wallet está registrada en la cuenta y que el SDK apunta a la red correcta. La documentación de la API de Hyperliquid describe el modelo de registro.

Los desajustes de hash del payload casi siempre se remontan al formateo de floats o al orden de los campos. Compara la acción formateada con el cuerpo transmitido. La reutilización o los problemas de ordenación de nonces producen errores incluso cuando la firma es válida; comprueba que el nonce es monotónico por firmante y que las solicitudes concurrentes no comparten un nonce.

Los rechazos por margen insuficiente son respuestas del motor de riesgo, no errores de firma. La firma se verificó y la acción se analizó; la cuenta simplemente carecía de margen. El artículo Manejo de errores de la API de Hyperliquid y rechazos de órdenes cubre las formas de respuesta, y la guía de límites de tasa de la API de Hyperliquid cubre el comportamiento de limitación que puede hacerse pasar por fallos de transporte.

  • Error de firma: verifica el registro del API wallet y el dominio de firma.
  • Desajuste de hash del payload: compara las cadenas numéricas formateadas y transmitidas.
  • Error de nonce: impón nonces monotónicos por firmante.
  • Rechazo del motor de riesgo: inspecciona el margen y el estado de la posición de la cuenta.

Relación con CCXT y los SDKs de Rust y Go

La estructura de hashing y datos tipados es a nivel de protocolo e idéntica entre clientes. Las integraciones de CCXT y los SDKs de Rust y Go implementan el mismo pipeline de acción a hash a firma, por lo que el modelo mental se transfiere incluso cuando la superficie de la API difiere. Por eso la capa utils se estudia mejor como una implementación del protocolo que como una utilidad específica de Python.

Al comparar implementaciones, céntrate en tres cosas: cómo normaliza cada cliente los campos numéricos, cómo construye el separador de dominio y cómo gestiona los nonces. Las diferencias en estas tres áreas explican la mayoría de las discrepancias entre clientes. Los artículos Canales de trades y fills por WebSocket de Hyperliquid y Detección de heartbeat ping/pong por WebSocket de Hyperliquid cubren el lado de streaming, que usa una ruta de autenticación diferente a la de las acciones firmadas del exchange.

  • Compara la normalización numérica entre clientes.
  • Compara la construcción del separador de dominio.
  • Compara las estrategias de gestión de nonces.

Limitaciones, compensaciones y próximos pasos

La capa utils no elimina la necesidad de entender el protocolo. Centraliza el formateo y la firma, pero no puede impedir que un llamador vuelva a formatear un valor después de firmar o que comparta un nonce entre solicitudes concurrentes. Trata la acción formateada como inmutable una vez hasheada.

El comportamiento específico del proveedor, incluida la disponibilidad del endpoint y los límites de tasa, varía según el proveedor y debe verificarse con tus propias mediciones. Las páginas de precios de RPC y servicio de API describen las ofertas de OnFinality, y la página de endpoints RPC de Hyperliquid (RPC Assistant) ayuda con la selección de endpoints. Para contexto a nivel de red, consulta la página de la red Hyperliquid.

Como siguiente paso, instrumenta tu cliente para registrar la acción formateada, el hash calculado y el cuerpo transmitido en cada solicitud, y luego ejecuta el ejercicio de la tabla de resultados contra tu endpoint. Esa práctica convierte los fallos de firma de errores opacos en eventos diagnosticables.

  • Trata la acción formateada como inmutable después del hashing.
  • Verifica el comportamiento del proveedor con tus propias mediciones.
  • Instrumenta el registro de la acción formateada, el hash y el cuerpo transmitido.
RPC Assistant

Recursos relacionados de RPC Assistant

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar