Esta página fue traducida automáticamente. El original en inglés es la versión canónica. Leer en inglés →
Saltar al contenido principal

Autenticación y firma

Requisitos de firma EIP-712 para las operaciones de escritura.

Dominio de firma en producción

Use el chain ID 999 para producción. La testnet está deshabilitada temporalmente hasta que Hypercall adquiera más HYPE de testnet.

Descripción general​

Todas las operaciones de escritura requieren firmas EIP-712 de datos tipados. El firmante debe cumplir una de estas condiciones:

  1. Ser la propia dirección de la wallet (firma directa), O
  2. Ser un agente autorizado para la wallet (consulte Autenticación de agentes)

Dominio EIP-712​

Separador de dominio:

{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

Chain ID: 999 (producción)

Tipos de mensaje​

PlaceOrder​

Struct con ruta explícita:

struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}

Struct cuando se omite la ruta:

struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}

Requisitos de los campos:

  • wallet: Dirección de la wallet (cadena hexadecimal con prefijo 0x)
  • symbol: Símbolo de la opción (p. ej., "BTC-20250131-100000-C")
  • side: "Buy" o "Sell" (se requiere coincidencia exacta de la cadena)
  • size: Tamaño como cadena (p. ej., "0.1") - DEBE coincidir exactamente con lo enviado en el JSON
  • price: Precio como cadena (p. ej., "100.0") - DEBE coincidir exactamente con lo enviado en el JSON
  • tif: Time-in-force: "gtc", "ioc" o "fok" (se requiere coincidencia exacta de la cadena)
  • route: Ruta opcional de la orden: "best_execution" o "book_only" (se requiere coincidencia exacta de la cadena cuando está presente). POST /bulk_order solo acepta "book_only". "rfq_only" forma parte del vocabulario firmado, pero actualmente todos los endpoints de órdenes lo rechazan; use los endpoints de RFQ para ejecutar vía RFQ.
  • clientId: ID de orden proporcionado por el cliente (cadena, puede estar vacía "")
  • nonce: Nonce basado en marca de tiempo en milisegundos (uint64), único por firmante. Consulte Gestión de nonces

Crítico: price y size DEBEN ser cadenas tanto en el mensaje firmado como en el cuerpo JSON de la solicitud. El formato de las cadenas debe coincidir exactamente.

Ruta por defecto: envíe route. Una orden sin este campo sigue aceptándose y se enruta como best_execution, pero POST /order responde con los encabezados Deprecation, Sunset y Warning, y la fecha de retirada publicada (4 de julio de 2026) ya pasó. La forma sin ruta puede eliminarse sin un período de gracia adicional. Si omite route en el JSON, omítalo también en los datos tipados.

PlaceOrderReduceOnly​

Una orden reduce-only ("reduce_only": true en el cuerpo JSON) firma un struct distinto. Requiere un route explícito.

struct PlaceOrderReduceOnly {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
bool reduceOnly;
uint64 nonce;
}

reduceOnly debe ser true y coincidir con el cuerpo de la solicitud. Una orden reduce-only sin un route firmado se rechaza con missing_field.

ReplaceOrder​

PUT /order y cada entrada de PUT /bulk_order firman un ReplaceOrder. No tiene campo route; una clave route en el cuerpo JSON se ignora.

struct ReplaceOrder {
address wallet;
string orderId;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}

orderId es la cadena decimal de la orden que se reemplaza. Un reemplazo reduce-only ("reduce_only": true) firma ReplaceOrderReduceOnly, que añade bool reduceOnly entre clientId y nonce. El SDK de TypeScript exporta ambos mapas de tipos.

CancelOrder​

Struct:

struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}

Requisitos de los campos:

  • wallet: Dirección de la wallet
  • orderId: ID de la orden como cadena (p. ej., "123")
  • nonce: Nonce único

CancelOrderByClientId​

Struct:

struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}

Requisitos de los campos:

  • wallet: Dirección de la wallet
  • clientId: ID de orden del cliente (cadena)
  • nonce: Nonce único

ApproveAgent​

Struct:

struct ApproveAgent {
address agent;
uint64 nonce;
}

Requisitos de los campos:

  • agent: Dirección de la wallet del agente que se autoriza
  • nonce: Nonce único

Nota: La wallet que autoriza al agente se deriva de la firma recuperada.

RevokeAgent​

Struct:

struct RevokeAgent {
address agent;
uint64 nonce;
}

Requisitos de los campos:

  • agent: Dirección de la wallet del agente que se revoca
  • nonce: Nonce único

RevokeAllAgents​

DELETE /revoke-all-agents elimina todos los agentes de la wallet firmante.

struct RevokeAllAgents {
uint64 nonce;
}

Simulación de riesgo​

POST /risk/simulate/portfolio y POST /risk/simulate/orders son solicitudes públicas de solo lectura dirigidas a la autoridad del engine. No requieren firma, autenticación ni nonce. El campo wallet selecciona el snapshot de la cuenta que se evalúa.

Ejemplo de solicitud de cartera:

curl -X POST https://api.hypercall.xyz/risk/simulate/portfolio \
-H 'Content-Type: application/json' \
-d '{
"wallet": "0x1111111111111111111111111111111111111111",
"add_positions": true,
"positions": [
{"symbol": "BTC-20261231-100000-C", "quantity": "1.25"}
]
}'

Ejemplo de solicitud de órdenes atómicas:

curl -X POST https://api.hypercall.xyz/risk/simulate/orders \
-H 'Content-Type: application/json' \
-d '{
"wallet": "0x1111111111111111111111111111111111111111",
"execution_mode": "atomic",
"hypothetical_open_orders": [
{"symbol": "BTC-PERP", "side": "Buy", "size": "0.1", "price": "100000", "reduce_only": false}
],
"orders": [
{"symbol": "BTC-20261231-100000-C", "side": "Buy", "size": "1", "price": "4200", "reduce_only": false},
{"symbol": "BTC-20261231-120000-C", "side": "Sell", "size": "1", "price": "1800", "reduce_only": false}
]
}'

hypothetical_open_orders es opcional. Sus entradas se tratan como órdenes GTC en reposo y se admiten secuencialmente, en el orden del array, antes de orders. Solo para esta solicitud, se evalúan junto con las órdenes abiertas en vivo y las reservas activas autoritativas de la cuenta. El estado del engine no se modifica. Si la simulación tiene éxito, simulated y delta incluyen toda la secuencia hipotética más la orden en reposo propuesta o el paquete atómico. Entre los dos arrays de órdenes puede haber como máximo 50 entradas en total.

Las respuestas de simulación son snapshots, no garantías de ejecución. No se simulan el enrutamiento, la disponibilidad de RPI, el matching, la admisión de nonces ni el estado futuro de suspensión de la negociación. Una orden real posterior puede diferir una vez que avance el estado del engine.

Proceso de firma​

Paso 1: Construir el mensaje​

Ejemplo de PlaceOrder con ruta explícita:

const message = {
wallet: "0x1111111111111111111111111111111111111111",
symbol: "BTC-20250131-100000-C",
side: "Buy",
size: "0.1", // MUST be string
price: "100.0", // MUST be string
tif: "gtc",
route: "best_execution",
clientId: "mm-1",
nonce: Date.now() // current ms epoch; send this exact value in the body
};

Paso 2: Definir el dominio​

const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};

Paso 3: Firmar los datos tipados​

Con ethers.js v6 (en v5 el método es _signTypedData):

const signature = await signer.signTypedData(domain, {
PlaceOrder: [
{ name: "wallet", type: "address" },
{ name: "symbol", type: "string" },
{ name: "side", type: "string" },
{ name: "size", type: "string" },
{ name: "price", type: "string" },
{ name: "tif", type: "string" },
{ name: "route", type: "string" },
{ name: "clientId", type: "string" },
{ name: "nonce", type: "uint64" }
]
}, message);

Paso 4: Enviar la solicitud​

Crítico: El cuerpo JSON de la solicitud debe usar exactamente los mismos valores de cadena para price y size:

const body = {
wallet: message.wallet,
symbol: message.symbol,
price: message.price, // same string as signed
size: message.size, // same string as signed
side: message.side,
tif: message.tif,
route: message.route,
client_id: message.clientId,
nonce: message.nonce, // same value as signed
signature
};

Gestión de nonces​

Requisitos:

  • Los nonces deben estar estrictamente dentro del intervalo de dos días antes y un día después de la hora del servidor en milisegundos, así que use la hora Unix actual en milisegundos. Los contadores pequeños como 1 o 123 se rechazan.
  • Los nonces se registran por clave de firma. Para órdenes y cancelaciones, es el agente cuando firma un agente; para la aprobación y revocación de agentes, es la wallet titular.
  • Un nonce no puede reutilizarse. Los nonces de órdenes se comparten entre POST /order, las órdenes en bloque y PlaceOrder por WebSocket; un nonce usado en un transporte no puede reutilizarse en otro.
  • Se permiten huecos. El engine conserva los 100 nonces más recientes por firmante y, una vez que tiene 100, un nuevo nonce debe ser mayor que el más pequeño conservado.

Buenas prácticas:

  • Inicialice a partir del tiempo epoch en milisegundos y persista el último nonce por firmante, devolviendo max(now_ms, last + 1)
  • Comparta un único asignador entre todos los procesos que firman con la misma clave
  • Si una solicitud falla, fírmela de nuevo con un nonce nuevo

Autorización de agentes​

Si usa una wallet de agente para firmar:

  1. Aprobar el agente (una sola vez):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1791273600000,
    "signature": "0x..." # Signed by wallet owner
    }
  2. Firmar órdenes con la wallet del agente:

    • Use la wallet del agente para firmar los mensajes PlaceOrder / CancelOrder
    • Establezca el campo wallet con la dirección de la wallet de trading
    • El middleware verifica que el agente esté autorizado para esa wallet

Consulte Autenticación de agentes para ver todos los detalles de la autorización de agentes.

Órdenes de perpetuos​

Las órdenes de perpetuos no usan el dominio Hypercall anterior. Se firman como un HLOrder en el dominio HypercallApiSign (chain ID 999, contrato verificador cero) mediante una API wallet registrada en su Hypercall Account. Consulte Firma EIP-712 para ver el struct y las unidades.

La ruta TEE es opcional en la API: se elige llamando a los endpoints TEE. En la app de Hypercall es la opción por defecto. Para usarla, envíe la orden firmada a POST /v1/tee-perp/orders/submit con account, nonce, action y signature. Hypercall ejecuta la verificación de margen y, a continuación, un enclave seguro firma y envía la orden nativa de Hyperliquid para su Account. Cada orden de perpetuos lleva el builder code de Hypercall con una comisión del 0%, por lo que el manager de la Account debe aprobar ese builder una vez antes de la primera orden (el paso Enable trading de la app). Sin esa aprobación, la orden se rechaza con builder_not_approved. Consulte Perpetuos para ver el flujo y la Referencia de la API para el esquema de solicitud y respuesta.

El dominio Exchange propio de Hyperliquid (chain ID 1337, payloads Agent en MessagePack) no es aceptado por la API de Hypercall.

Errores comunes​

unauthorized (HTTP 401): "Signer 0x... is not authorized to act for wallet 0x..."​

La recuperación EIP-712 siempre devuelve alguna dirección. Si algo de lo que firmó difiere de lo que envió, la dirección recuperada no es su clave y la solicitud falla aquí en lugar de con un error de firma.

Causas:

  1. El formato de la cadena de price o size cambió entre la firma y el envío (p. ej., "100.0" frente a "100")
  2. Un campo firmado difiere del cuerpo JSON (route, clientId/client_id, nonce, tif), o route se firmó pero se omitió en el cuerpo, o al revés
  3. Dominio incorrecto (chain ID, name, version, contrato verificador)
  4. Struct incorrecto para la solicitud (por ejemplo, PlaceOrder en lugar de ReplaceOrder en PUT /order)
  5. El firmante realmente no es ni la wallet ni un agente aprobado. Apruebe el agente mediante POST /approve-agent o firme directamente con la wallet

Cuando el mensaje indica una dirección de firmante que usted no reconoce, se trata de las causas 1 a 4.

signature_verification_failed (HTTP 400)​

Causa: La firma no pudo decodificarse ni recuperarse en absoluto, por ejemplo por longitud incorrecta o hexadecimal no válido.

invalid_parameter (HTTP 400): "price must be provided as a string for signature verification"​

Causa: price o size se envió como número JSON. Envíe ambos como cadenas.

Ejemplos de implementación​

Python (eth_account 0.10 o posterior)​

import time

from eth_account import Account
from eth_account.messages import encode_typed_data

domain = {
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

message = {
"wallet": "0x1111111111111111111111111111111111111111",
"symbol": "BTC-20250131-100000-C",
"side": "Buy",
"size": "0.1",
"price": "100.0",
"tif": "gtc",
"route": "best_execution",
"clientId": "mm-1",
"nonce": int(time.time() * 1000) # current ms epoch; send this exact value in the body
}

types = {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
],
"PlaceOrder": [
{"name": "wallet", "type": "address"},
{"name": "symbol", "type": "string"},
{"name": "side", "type": "string"},
{"name": "size", "type": "string"},
{"name": "price", "type": "string"},
{"name": "tif", "type": "string"},
{"name": "route", "type": "string"},
{"name": "clientId", "type": "string"},
{"name": "nonce", "type": "uint64"}
]
}

structured_msg = {
"types": types,
"domain": domain,
"primaryType": "PlaceOrder",
"message": message
}

encoded = encode_typed_data(full_message=structured_msg)
signed = Account.sign_message(encoded, private_key)
signature = signed.signature.hex()

JavaScript (ethers.js v6)​

const { ethers } = require("ethers");

const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};

const types = {
PlaceOrder: [
{ name: "wallet", type: "address" },
{ name: "symbol", type: "string" },
{ name: "side", type: "string" },
{ name: "size", type: "string" },
{ name: "price", type: "string" },
{ name: "tif", type: "string" },
{ name: "route", type: "string" },
{ name: "clientId", type: "string" },
{ name: "nonce", type: "uint64" }
]
};

const message = {
wallet: "0x1111111111111111111111111111111111111111",
symbol: "BTC-20250131-100000-C",
side: "Buy",
size: "0.1",
price: "100.0",
tif: "gtc",
route: "best_execution",
clientId: "mm-1",
nonce: Date.now() // current ms epoch; send this exact value in the body
};

const signature = await signer.signTypedData(domain, types, message);

Prueba de firmas​

Ejemplo para probar la generación de firmas:

  1. Genere la firma con su implementación
  2. Envíe una orden de prueba mediante POST /order
  3. Verifique la respuesta: HTTP 200 con status "ACKED", o "REJECTED" con un reason
  4. Si la solicitud falla con HTTP 401 unauthorized y una dirección de firmante desconocida, o con HTTP 400 signature_verification_failed, revise:
    • El formato de las cadenas de price y size
    • Los parámetros del dominio (especialmente chainId)
    • Que cada campo firmado coincida con el cuerpo, incluidos route y nonce

Consideraciones de seguridad​

  1. Nunca exponga claves privadas: Use hardware wallets o una gestión segura de claves
  2. Gestión de nonces: Use nonces persistentes basados en marca de tiempo en milisegundos por clave de firma
  3. Autorización de agentes: Audite periódicamente los agentes autorizados mediante GET /authorized-agents
  4. Codificación de cadenas: Asegúrese de que price y size sean cadenas tanto en la firma como en la solicitud

Referencias​