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 operaciones de escritura.

Dominio de firma en producción

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:

  1. Ser la propia dirección de la wallet (firma directa), O
  2. 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 JSON
  • price: Precio como string (p. ej., "100.0") - DEBE coincidir exactamente con lo que se envía en el JSON
  • tif: 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 wallet
  • orderId: 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 wallet
  • clientId: 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 autorizar
  • 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 a revocar
  • nonce: 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:

  1. Aprobar el agente (una sola vez):

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

    • Use la wallet del agente para firmar 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

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:

  1. price o size enviados como número en lugar de string
  2. El formato de la cadena cambió entre la firma y el envío (p. ej., "100.0" vs "100")
  3. Nonce incorrecto
  4. Dominio incorrecto (chain ID, nombre, versión)
  5. 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:

  1. Genere la firma con su implementación
  2. Envíe una orden de prueba vía POST /order
  3. Verifique la respuesta: status="ACKED" o status="REJECTED" con el motivo
  4. Si recibe "signature_verification_failed", verifique:
    • El formato de cadena de price y size
    • Los parámetros del dominio (especialmente chainId)
    • La corrección del nonce

Consideraciones de Seguridad

  1. Nunca exponga claves privadas: Use hardware wallets o gestión segura de claves
  2. Gestión de nonces: Use nonces persistentes e incrementales por wallet
  3. Autorización de agentes: Audite regularmente los agentes autorizados vía GET /authorized-agents
  4. Codificación de strings: Asegúrese de que price y size sean strings tanto en la firma como en la solicitud

Referencias