Autenticación y Firma
Requisitos de firma EIP-712 para operaciones de escritura.
Use el chain ID 999 para producción. La testnet está temporalmente deshabilitada hasta que Hypercall adquiera más HYPE de testnet.
Descripción General
Todas las operaciones de escritura requieren firmas de datos tipados EIP-712. El firmante debe:
- Ser la propia dirección de la wallet (firma directa), O
- Ser un agente autorizado para la wallet (vea Autorización de agentes)
Dominio EIP-712
Separador de dominio:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID: 999 (producción)
Tipos de Mensajes
PlaceOrder
Struct con route 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 route:
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 string (p. ej.,"0.1") - DEBE coincidir exactamente con lo que se envía en el JSONprice: Precio como string (p. ej.,"100.0") - DEBE coincidir exactamente con lo que se envía en el JSONtif: Time-in-force:"gtc","ioc"o"fok"(se requiere coincidencia exacta de la cadena)route: Ruta de la orden opcional:"best_execution","book_only"o"rfq_only"(se requiere coincidencia exacta de la cadena cuando está presente)clientId: ID de orden proporcionado por el cliente (string, puede estar vacío"")nonce: Nonce único (uint64) para protección contra ataques de repetición
Crítico: price y size DEBEN ser strings tanto en el mensaje firmado como en el cuerpo de la solicitud JSON. El formato de las cadenas debe coincidir exactamente.
Valor por defecto de route: route es opcional al menos hasta el 4 de julio de 2026. Cuando se omite del JSON, omítalo también de los datos tipados. El servidor trata la route omitida como best_execution. POST /order devuelve encabezados de deprecación cuando se omite route. Los clientes deberían enviar route; no será obligatorio antes del 4 de julio de 2026, pero podría volverse obligatorio más adelante.
CancelOrder
Struct:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Requisitos de los campos:
wallet: Dirección de la walletorderId: ID de la orden como string (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 walletclientId: ID de orden del cliente (string)nonce: Nonce único
ApproveAgent
Struct:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Requisitos de los campos:
agent: Dirección de la wallet del agente a autorizarnonce: 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 a revocarnonce: Nonce único
Proceso de Firma
Paso 1: Construir el Mensaje
Ejemplo para PlaceOrder con route 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: 123
};
Paso 2: Definir el Dominio
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Paso 3: Firmar los Datos Tipados
Usando ethers.js:
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 de la solicitud JSON debe usar exactamente los mismos valores string para price y size:
{
"wallet": "0x1111111111111111111111111111111111111111",
"symbol": "BTC-20250131-100000-C",
"price": "100.0", // Same string as signed
"size": "0.1", // Same string as signed
"side": "Buy",
"tif": "gtc",
"route": "best_execution",
"client_id": "mm-1",
"nonce": 123,
"signature": "0x..."
}
Gestión de Nonces
Requisitos:
- Los nonces DEBEN ser únicos por wallet
- Los nonces DEBERÍAN ser incrementales (previene ataques de repetición)
- Los nonces no se validan por monotonicidad estricta (se permiten saltos)
Mejores prácticas:
- Use un contador persistente por wallet
- Incremente después de cada firma exitosa
- Maneje los saltos de nonce con cuidado (p. ej., si una solicitud falla, reintente con el mismo nonce)
Autorización de Agentes
Si usa una wallet de agente para firmar:
-
Aprobar el agente (una sola vez):
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
Firmar órdenes con la wallet del agente:
- Use la wallet del agente para firmar mensajes
PlaceOrder/CancelOrder - Establezca el campo
walletcon la dirección de la wallet de trading - El middleware verifica que el agente esté autorizado para esa wallet
- Use la wallet del agente para firmar mensajes
Vea Autorización de agentes para los detalles completos de autorización de agentes.
Órdenes de Perpetuos (Compatibles con Hyperliquid)
Las órdenes de perpetuos usan un dominio EIP-712 y un formato de mensaje diferentes (compatibilidad con Hyperliquid Core):
Dominio:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Mensaje: struct Agent con datos de orden codificados en MessagePack.
Consulte la guía actual de codificación de firmas para los campos exactos.
Errores Comunes
"Signature verification failed"
Causas:
priceosizeenviados como número en lugar de string- El formato de la cadena cambió entre la firma y el envío (p. ej.,
"100.0"vs"100") - Nonce incorrecto
- Dominio incorrecto (chain ID, nombre, versión)
- Agente no autorizado para la wallet
"Unauthorized: signer not authorized for wallet"
Causa: El firmante no es la propia wallet y no es un agente autorizado.
Solución: Apruebe el agente vía POST /approve-agent o firme directamente con la wallet.
Ejemplos de Implementación
Python (eth_account)
from eth_account import Account
from eth_account.messages import encode_structured_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": 123
}
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_structured_data(structured_msg)
signed = Account.sign_message(encoded, private_key)
signature = signed.signature.hex()
JavaScript (ethers.js)
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: 123
};
const signature = await signer._signTypedData(domain, types, message);
Pruebas de Firmas
Ejemplo para probar la generación de firmas:
- Genere la firma con su implementación
- Envíe una orden de prueba vía
POST /order - Verifique la respuesta:
status="ACKED"ostatus="REJECTED"con el motivo - Si recibe
"signature_verification_failed", verifique:- El formato de cadena de
priceysize - Los parámetros del dominio (especialmente
chainId) - La corrección del nonce
- El formato de cadena de
Consideraciones de Seguridad
- Nunca exponga claves privadas: Use hardware wallets o gestión segura de claves
- Gestión de nonces: Use nonces persistentes e incrementales por wallet
- Autorización de agentes: Audite regularmente los agentes autorizados vía
GET /authorized-agents - Codificación de strings: Asegúrese de que
priceysizesean strings tanto en la firma como en la solicitud
Referencias
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Implementación: gestionada por la pila de recuperación de firmas y middleware.