Autenticação e Assinatura
Requisitos de assinatura EIP-712 para operações de escrita.
Use o chain ID 999 para produção. A testnet está temporariamente desativada até que a Hypercall adquira mais HYPE de testnet.
Visão Geral
Todas as operações de escrita exigem assinaturas de dados tipados EIP-712. O signatário deve:
- Ser o próprio endereço da carteira (assinatura direta), OU
- Ser um agente autorizado para a carteira (veja Autenticação de agente)
Domínio EIP-712
Domain separator:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID: 999 (produção)
Tipos de Mensagem
PlaceOrder
Struct com route explícito:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Struct quando o route é omitido:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Requisitos dos campos:
wallet: Endereço da carteira (string hex com prefixo 0x)symbol: Símbolo da opção (por exemplo,"BTC-20250131-100000-C")side:"Buy"ou"Sell"(correspondência exata da string obrigatória)size: Tamanho como string (por exemplo,"0.1") - DEVE corresponder exatamente ao que é enviado no JSONprice: Preço como string (por exemplo,"100.0") - DEVE corresponder exatamente ao que é enviado no JSONtif: Time-in-force:"gtc","ioc"ou"fok"(correspondência exata da string obrigatória)route: Rota de ordem opcional:"best_execution","book_only"ou"rfq_only"(correspondência exata da string obrigatória quando presente)clientId: ID da ordem fornecido pelo cliente (string, pode ser vazio"")nonce: Nonce único (uint64) para proteção contra replay
Crítico: price e size DEVEM ser strings tanto na mensagem assinada quanto no corpo da requisição JSON. A formatação da string deve corresponder exatamente.
Padrão de route: route é opcional até pelo menos 4 de julho de 2026. Quando omitido do JSON, omita-o também dos dados tipados. O servidor trata route omitido como best_execution. POST /order retorna headers de deprecação quando o route é omitido. Os clientes devem enviar route; ele não se tornará obrigatório antes de 4 de julho de 2026, mas pode se tornar obrigatório depois.
CancelOrder
Struct:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Requisitos dos campos:
wallet: Endereço da carteiraorderId: ID da ordem como string (por exemplo,"123")nonce: Nonce único
CancelOrderByClientId
Struct:
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Requisitos dos campos:
wallet: Endereço da carteiraclientId: ID da ordem do cliente (string)nonce: Nonce único
ApproveAgent
Struct:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Requisitos dos campos:
agent: Endereço da carteira do agente a ser autorizadononce: Nonce único
Nota: A carteira que autoriza o agente é derivada da assinatura recuperada.
RevokeAgent
Struct:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Requisitos dos campos:
agent: Endereço da carteira do agente a ser revogadononce: Nonce único
Processo de Assinatura
Passo 1: Construir a Mensagem
Exemplo para PlaceOrder com route explícito:
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
};
Passo 2: Definir o Domínio
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Passo 3: Assinar os Dados 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);
Passo 4: Enviar a Requisição
Crítico: O corpo da requisição JSON deve usar exatamente os mesmos valores de string para price e 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..."
}
Gerenciamento de Nonce
Requisitos:
- Os nonces DEVEM ser únicos por carteira
- Os nonces DEVEM ser incrementais (previne ataques de replay)
- Os nonces não são validados quanto à monotonicidade estrita (lacunas são permitidas)
Boas práticas:
- Use um contador persistente por carteira
- Incremente após cada assinatura bem-sucedida
- Trate lacunas de nonce de forma adequada (por exemplo, se uma requisição falhar, tente novamente com o mesmo nonce)
Autorização de Agentes
Se estiver usando uma carteira de agente para assinar:
-
Aprovar o agente (uma única vez):
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
Assinar ordens com a carteira do agente:
- Use a carteira do agente para assinar mensagens
PlaceOrder/CancelOrder - Defina o campo
walletcom o endereço da carteira de trading - O middleware verifica se o agente está autorizado para essa carteira
- Use a carteira do agente para assinar mensagens
Veja Autenticação de agente para detalhes completos sobre autorização de agentes.
Ordens de Perpétuos (Compatível com Hyperliquid)
Ordens de contratos perpétuos usam um domínio EIP-712 e um formato de mensagem diferentes (compatibilidade com Hyperliquid Core):
Domínio:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Mensagem: struct Agent com dados da ordem codificados em MessagePack.
Consulte o guia atual de codificação de assinaturas para os campos exatos.
Erros Comuns
"Signature verification failed"
Causas:
priceousizeenviados como número em vez de string- Formatação da string alterada entre a assinatura e o envio (por exemplo,
"100.0"vs"100") - Nonce incorreto
- Domínio incorreto (chain ID, name, version)
- Agente não autorizado para a carteira
"Unauthorized: signer not authorized for wallet"
Causa: O signatário não é a própria carteira e não é um agente autorizado.
Solução: Aprove o agente via POST /approve-agent ou assine diretamente com a carteira.
Exemplos de Implementação
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);
Testando Assinaturas
Exemplo para testar a geração de assinaturas:
- Gere a assinatura com a sua implementação
- Envie uma ordem de teste via
POST /order - Verifique a resposta:
status="ACKED"oustatus="REJECTED"com o motivo - Se receber
"signature_verification_failed", verifique:- Formatação das strings de
priceesize - Parâmetros do domínio (especialmente
chainId) - Correção do nonce
- Formatação das strings de
Considerações de Segurança
- Nunca exponha chaves privadas: Use hardware wallets ou gerenciamento seguro de chaves
- Gerenciamento de nonce: Use nonces persistentes e incrementais por carteira
- Autorização de agentes: Audite regularmente os agentes autorizados via
GET /authorized-agents - Codificação de strings: Garanta que
priceesizesejam strings tanto na assinatura quanto na requisição
Referências
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Implementação: tratada pela stack de recuperação de assinaturas e middleware.