Esta página foi traduzida automaticamente. O original em inglês é a versão canônica. Ler em inglês →
Pular para o conteúdo principal

Autenticação e assinatura

Requisitos de assinatura EIP-712 para operações de escrita.

Domínio de assinatura em produção

Use o chain ID 999 em produção. A testnet está temporariamente desativada até que a Hypercall obtenha mais HYPE de testnet.

Visão geral​

Todas as operações de escrita exigem assinaturas de dados tipados EIP-712. O signatário deve:

  1. Ser o próprio endereço da carteira (assinatura direta), OU
  2. Ser um agente autorizado para a carteira (veja Autenticação de agentes)

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 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 hexadecimal com prefixo 0x)
  • symbol: símbolo da opção (ex.: "BTC-20250131-100000-C")
  • side: "Buy" ou "Sell" (correspondência exata da string obrigatória)
  • size: tamanho como string (ex.: "0.1"). DEVE corresponder exatamente ao que é enviado no JSON
  • price: preço como string (ex.: "100.0"). DEVE corresponder exatamente ao que é enviado no JSON
  • tif: time-in-force: "gtc", "ioc" ou "fok" (correspondência exata da string obrigatória)
  • route: rota da ordem opcional: "best_execution" ou "book_only" (correspondência exata da string obrigatória quando presente). POST /bulk_order aceita apenas "book_only". "rfq_only" faz parte do vocabulário assinado, mas todos os endpoints de ordem o rejeitam atualmente; use os endpoints de RFQ para execução via RFQ.
  • clientId: ID da ordem fornecido pelo cliente (string, pode ser vazio "")
  • nonce: nonce baseado em timestamp em milissegundos (uint64), único por signatário. Veja Gerenciamento de nonce

Crítico: price e size DEVEM ser strings tanto na mensagem assinada quanto no corpo JSON da requisição. A formatação das strings deve corresponder exatamente.

Route padrão: envie route. Uma ordem sem ele ainda é aceita e roteada como best_execution, mas POST /order responde com os headers Deprecation, Sunset e Warning, e a data de sunset publicada (4 de julho de 2026) já passou. A forma sem route pode ser removida sem novo período de carência. Se você omitir route do JSON, omita-o também dos dados tipados.

PlaceOrderReduceOnly​

Uma ordem reduce-only ("reduce_only": true no corpo JSON) assina uma struct separada. Ela exige um 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 deve ser true e corresponder ao corpo da requisição. Uma ordem reduce-only sem route assinado é rejeitada com missing_field.

ReplaceOrder​

PUT /order e cada entrada de PUT /bulk_order assinam um ReplaceOrder. Ele não tem campo route; uma chave route no corpo JSON é ignorada.

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

orderId é a string decimal da ordem que está sendo substituída. Uma substituição reduce-only ("reduce_only": true) assina ReplaceOrderReduceOnly, que adiciona bool reduceOnly entre clientId e nonce. O SDK TypeScript exporta os dois mapas de tipos.

CancelOrder​

Struct:

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

Requisitos dos campos:

  • wallet: endereço da carteira
  • orderId: ID da ordem como string (ex.: "123")
  • nonce: nonce único

CancelOrderByClientId​

Struct:

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

Requisitos dos campos:

  • wallet: endereço da carteira
  • clientId: 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 autorizado
  • nonce: nonce único

Observação: 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 revogado
  • nonce: nonce único

RevokeAllAgents​

DELETE /revoke-all-agents remove todos os agentes da carteira signatária.

struct RevokeAllAgents {
uint64 nonce;
}

Simulação de risco​

POST /risk/simulate/portfolio e POST /risk/simulate/orders são requisições públicas, somente leitura, com autoridade do engine. Elas não exigem assinatura, autenticação nem nonce. O campo wallet seleciona o snapshot da conta a ser avaliado.

Exemplo de requisição de portfólio:

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"}
]
}'

Exemplo de requisição de ordens 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 é opcional. Suas entradas são tratadas como ordens GTC em repouso no livro e admitidas sequencialmente na ordem do array, antes de orders. Apenas nesta requisição, elas são avaliadas junto com as ordens abertas ativas e as reservas ativas da conta, conforme o estado autoritativo. O estado do engine não é modificado. Em uma simulação bem-sucedida, simulated e delta incluem toda a sequência hipotética mais a ordem em repouso proposta ou o pacote atômico. Os dois arrays de ordens podem conter no máximo 50 entradas no total.

As respostas de simulação são snapshots, não garantias de execução. Roteamento, disponibilidade de RPI, matching, admissão de nonce e estado futuro de suspensão de negociação não são simulados. Uma ordem real posterior pode ter resultado diferente depois que o estado do engine avançar.

Processo de assinatura​

Passo 1: construir a mensagem​

Exemplo de 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: Date.now() // current ms epoch; send this exact value in the body
};

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 v6 (na v5 o método é _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);

Passo 4: enviar a requisição​

Crítico: o corpo JSON da requisição deve usar exatamente os mesmos valores de string para price e 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
};

Gerenciamento de nonce​

Requisitos:

  • Os nonces devem estar estritamente dentro do intervalo de dois dias antes e um dia depois do horário do servidor em milissegundos, então use o tempo Unix atual em milissegundos. Contadores pequenos como 1 ou 123 são rejeitados.
  • Os nonces são rastreados por chave de assinatura. Para ordens e cancelamentos, é o agente quando um agente assina; para aprovação e revogação de agentes, é a carteira titular.
  • Um nonce não pode ser reutilizado. Os nonces de ordens são compartilhados entre POST /order, ordens em lote e PlaceOrder via WebSocket; um nonce usado em um transporte não pode ser reutilizado em outro.
  • Lacunas são permitidas. O engine mantém os 100 nonces mais recentes por signatário e, quando já guarda 100, um novo nonce deve ser maior que o menor deles.

Boas práticas:

  • Inicialize a partir do tempo epoch em milissegundos e persista o último nonce por signatário, retornando max(now_ms, last + 1)
  • Compartilhe um único alocador entre todos os processos que assinam com a mesma chave
  • Se uma requisição falhar, assine-a novamente com um novo nonce

Autorização de agentes​

Se você usar uma carteira de agente para assinar:

  1. Aprove o agente (uma única vez):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1791273600000,
    "signature": "0x..." # Signed by wallet owner
    }
  2. Assine ordens com a carteira do agente:

    • Use a carteira do agente para assinar mensagens PlaceOrder / CancelOrder
    • Defina o campo wallet com o endereço da carteira de negociação
    • O middleware verifica se o agente está autorizado para essa carteira

Veja Autenticação de agentes para todos os detalhes sobre autorização de agentes.

Ordens de perpétuos​

Ordens de contratos perpétuos não usam o domínio Hypercall acima. Elas são assinadas como HLOrder no domínio HypercallApiSign (chain ID 999, verifying contract zerado) por uma API wallet registrada na sua Hypercall Account. Veja Assinatura EIP-712 para a struct e as unidades.

A rota TEE é opcional na API: você a escolhe chamando os endpoints TEE. No app da Hypercall, ela é o padrão. Para usá-la, envie a ordem assinada para POST /v1/tee-perp/orders/submit com account, nonce, action e signature. A Hypercall executa a verificação de margem e, em seguida, um enclave seguro assina e envia a ordem nativa da Hyperliquid para a sua Account. Toda ordem de perpétuo carrega o builder code da Hypercall com taxa de 0%, então o manager da Account precisa aprovar esse builder uma vez antes da primeira ordem (a etapa Enable trading do app). Sem isso, a ordem é rejeitada com builder_not_approved. Veja Perpétuos para o fluxo e a Referência da API para o schema de requisição e resposta.

O domínio Exchange da própria Hyperliquid (chain ID 1337, payloads Agent em MessagePack) não é aceito pela API da Hypercall.

Erros comuns​

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

A recuperação EIP-712 sempre produz algum endereço. Se qualquer coisa que você assinou diferir do que você enviou, o endereço recuperado não será o da sua chave, e a requisição falha aqui, e não com um erro de assinatura.

Causas:

  1. A formatação da string de price ou size mudou entre a assinatura e o envio (ex.: "100.0" vs "100")
  2. Um campo assinado difere do corpo JSON (route, clientId/client_id, nonce, tif), ou route foi assinado mas omitido do corpo, ou o inverso
  3. Domínio errado (chain ID, name, version, verifying contract)
  4. Struct errada para a requisição (por exemplo, PlaceOrder em vez de ReplaceOrder em PUT /order)
  5. O signatário de fato não é nem a carteira nem um agente aprovado. Aprove o agente via POST /approve-agent ou assine diretamente com a carteira

Quando a mensagem cita um endereço de signatário que você não reconhece, a causa é uma das causas 1 a 4.

signature_verification_failed (HTTP 400)​

Causa: a assinatura não pôde ser decodificada nem recuperada, por exemplo, por tamanho errado ou hex inválido.

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

Causa: price ou size foi enviado como número JSON. Envie ambos como strings.

Exemplos de implementação​

Python (eth_account 0.10 ou 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);

Testando assinaturas​

Exemplo para testar a geração de assinaturas:

  1. Gere a assinatura com a sua implementação
  2. Envie uma ordem de teste via POST /order
  3. Verifique a resposta: HTTP 200 com status "ACKED", ou "REJECTED" com um reason
  4. Se a requisição falhar com HTTP 401 unauthorized e um endereço de signatário desconhecido, ou com HTTP 400 signature_verification_failed, verifique:
    • A formatação das strings de price e size
    • Os parâmetros do domínio (especialmente chainId)
    • Se todos os campos assinados correspondem ao corpo, incluindo route e nonce

Considerações de segurança​

  1. Nunca exponha chaves privadas: use hardware wallets ou um gerenciamento de chaves seguro
  2. Gerenciamento de nonce: use nonces persistentes, baseados em timestamp em milissegundos, por chave de assinatura
  3. Autorização de agentes: audite regularmente os agentes autorizados via GET /authorized-agents
  4. Codificação de strings: garanta que price e size sejam strings tanto na assinatura quanto na requisição

Referências​