Authentification et signature
Exigences de signature EIP-712 pour les opérations d'écriture.
Utilisez l'identifiant de chaîne 999 en production. Le testnet est temporairement désactivé jusqu'à ce que Hypercall obtienne davantage de HYPE de testnet.
Vue d'ensemble
Toutes les opérations d'écriture exigent des signatures EIP-712 en données typées. Le signataire doit :
- Être l'adresse du portefeuille elle-même (signature directe), OU
- Être un agent autorisé pour ce portefeuille (voir Authentification des agents)
Domaine EIP-712
Séparateur de domaine :
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Identifiant de chaîne : 999 (production)
Types de messages
PlaceOrder
Structure avec route explicite :
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Structure lorsque la route est omise :
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Exigences des champs :
wallet: adresse du portefeuille (chaîne hexadécimale préfixée par 0x)symbol: symbole de l'option (par exemple"BTC-20250131-100000-C")side:"Buy"ou"Sell"(correspondance exacte de la chaîne requise)size: taille sous forme de chaîne (par exemple"0.1") - DOIT correspondre exactement à ce qui est envoyé dans le JSONprice: prix sous forme de chaîne (par exemple"100.0") - DOIT correspondre exactement à ce qui est envoyé dans le JSONtif: durée de validité :"gtc","ioc"ou"fok"(correspondance exacte de la chaîne requise)route: route d'ordre optionnelle :"best_execution"ou"book_only"(correspondance exacte de la chaîne requise lorsqu'elle est présente).POST /bulk_ordern'accepte que"book_only"."rfq_only"fait partie du vocabulaire signé, mais tous les endpoints d'ordres le rejettent actuellement ; utilisez les endpoints RFQ pour une exécution en RFQ.clientId: identifiant d'ordre fourni par le client (chaîne, peut être vide"")nonce: nonce sous forme d'horodatage en millisecondes (uint64), unique par signataire. Voir Gestion des nonces
Critique : price et size DOIVENT être des chaînes, à la fois dans le message signé et dans le corps de la requête JSON. Le formatage des chaînes doit correspondre exactement.
Route par défaut : envoyez route. Un ordre sans ce champ est encore accepté et routé en best_execution, mais POST /order répond avec les en-têtes Deprecation, Sunset et Warning, et la date de fin de prise en charge publiée (4 juillet 2026) est dépassée. La forme sans route peut être supprimée sans nouveau délai de grâce. Si vous omettez route du JSON, omettez-le également des données typées.
PlaceOrderReduceOnly
Un ordre reduce-only ("reduce_only": true dans le corps JSON) signe une structure distincte. Il exige une route explicite.
struct PlaceOrderReduceOnly {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
bool reduceOnly;
uint64 nonce;
}
reduceOnly doit valoir true et correspondre au corps de la requête. Un ordre reduce-only sans route signée est rejeté avec missing_field.
ReplaceOrder
PUT /order et chaque entrée de PUT /bulk_order signent un ReplaceOrder. Cette structure n'a pas de champ route ; une clé route dans le corps JSON est ignorée.
struct ReplaceOrder {
address wallet;
string orderId;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
orderId est la chaîne décimale de l'ordre remplacé. Un remplacement reduce-only ("reduce_only": true) signe ReplaceOrderReduceOnly, qui ajoute bool reduceOnly entre clientId et nonce. Le SDK TypeScript exporte les deux définitions de types.
CancelOrder
Structure :
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Exigences des champs :
wallet: adresse du portefeuilleorderId: identifiant de l'ordre sous forme de chaîne (par exemple"123")nonce: nonce unique
CancelOrderByClientId
Structure :
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Exigences des champs :
wallet: adresse du portefeuilleclientId: identifiant d'ordre client (chaîne)nonce: nonce unique
ApproveAgent
Structure :
struct ApproveAgent {
address agent;
uint64 nonce;
}
Exigences des champs :
agent: adresse du portefeuille de l'agent à autorisernonce: nonce unique
Remarque : le portefeuille qui autorise l'agent est déduit de la signature récupérée.
RevokeAgent
Structure :
struct RevokeAgent {
address agent;
uint64 nonce;
}
Exigences des champs :
agent: adresse du portefeuille de l'agent à révoquernonce: nonce unique
RevokeAllAgents
DELETE /revoke-all-agents supprime tous les agents du portefeuille signataire.
struct RevokeAllAgents {
uint64 nonce;
}
Simulation de risque
POST /risk/simulate/portfolio et POST /risk/simulate/orders sont des requêtes publiques,
en lecture seule, adressées à l'autorité du moteur. Elles ne requièrent ni signature, ni authentification,
ni nonce. Le champ wallet sélectionne l'instantané de compte à évaluer.
Exemple de requête de portefeuille :
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"}
]
}'
Exemple de requête d'ordres atomique :
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 est optionnel. Ses entrées sont traitées comme des ordres GTC
en attente dans le carnet et admises séquentiellement, dans l'ordre du tableau, avant orders. Pour cette
requête uniquement, elles sont évaluées conjointement avec les ordres ouverts réels et faisant autorité
du compte ainsi que ses réservations actives. L'état du moteur n'est pas modifié. En cas de
simulation réussie, simulated et delta incluent l'ensemble de la séquence
hypothétique ainsi que l'ordre en attente proposé ou le paquet atomique. Les deux tableaux d'ordres
peuvent contenir au maximum 50 entrées au total.
Les réponses de simulation sont des instantanés, pas des garanties d'exécution. Le routage, la disponibilité du RPI, l'appariement, l'admission des nonces et un éventuel état futur de suspension des échanges ne sont pas simulés. Un ordre réel ultérieur peut donner un résultat différent une fois que l'état du moteur a évolué.
Processus de signature
Étape 1 : construire le message
Exemple pour PlaceOrder avec route explicite :
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
};
Étape 2 : définir le domaine
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Étape 3 : signer les données typées
Avec ethers.js v6 (en v5, la méthode s'appelle _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);
Étape 4 : envoyer la requête
Critique : le corps de la requête JSON doit utiliser exactement les mêmes valeurs de chaîne pour price et 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
};
Gestion des nonces
Exigences :
- Les nonces doivent se situer strictement entre deux jours avant et un jour après l'heure du serveur en millisecondes ; utilisez donc l'heure Unix actuelle en millisecondes. Les petits compteurs tels que
1ou123sont rejetés. - Les nonces sont suivis par clé de signature. Pour les ordres et les annulations, il s'agit de l'agent lorsqu'un agent signe ; pour l'approbation et la révocation d'agents, il s'agit du portefeuille propriétaire.
- Un nonce ne peut pas être réutilisé. Les nonces d'ordres sont partagés entre
POST /order, les ordres groupés etPlaceOrdervia WebSocket ; un nonce utilisé sur un canal ne peut pas être réutilisé sur un autre. - Les écarts sont autorisés. Le moteur conserve les 100 nonces les plus récents par signataire et, dès qu'il en détient 100, un nouveau nonce doit être supérieur au plus petit nonce conservé.
Bonnes pratiques :
- Initialisez à partir de l'horodatage epoch en millisecondes et conservez de façon persistante le dernier nonce par signataire, en renvoyant
max(now_ms, last + 1) - Partagez un même allocateur entre tous les processus qui signent avec la même clé
- Si une requête échoue, signez-la de nouveau avec un nouveau nonce
Autorisation des agents
Si vous utilisez un portefeuille d'agent pour signer :
-
Approuver l'agent (une seule fois) :
POST /approve-agent{"agent": "0x...","nonce": 1791273600000,"signature": "0x..." # Signed by wallet owner} -
Signer les ordres avec le portefeuille de l'agent :
- Utilisez le portefeuille de l'agent pour signer les messages
PlaceOrder/CancelOrder - Renseignez le champ
walletavec l'adresse du portefeuille de trading - Le middleware vérifie que l'agent est autorisé pour ce portefeuille
- Utilisez le portefeuille de l'agent pour signer les messages
Voir Authentification des agents pour tous les détails sur l'autorisation des agents.
Ordres sur perpétuels
Les ordres sur contrats perpétuels n'utilisent pas le domaine Hypercall ci-dessus. Ils sont signés sous la forme d'un HLOrder dans le domaine HypercallApiSign (identifiant de chaîne 999, contrat de vérification nul) par un portefeuille API enregistré sur votre compte Hypercall. Voir Signature EIP-712 pour la structure et les unités.
La route TEE est optionnelle via l'API : vous la choisissez en appelant les endpoints TEE. Dans l'application Hypercall, c'est le comportement par défaut. Pour l'utiliser, soumettez l'ordre signé à POST /v1/tee-perp/orders/submit avec account, nonce, action et signature. Hypercall effectue le contrôle de marge, puis une enclave sécurisée signe et envoie l'ordre Hyperliquid natif pour votre compte. Chaque ordre sur perpétuel porte le builder code Hypercall avec des frais de 0 %, de sorte que le gestionnaire du compte doit approuver ce builder une fois avant le premier ordre (l'étape Enable trading de l'application). Sans cette approbation, l'ordre est rejeté avec builder_not_approved. Voir Perpétuels pour le déroulement et la référence de l'API pour le schéma de requête et de réponse.
Le domaine Exchange propre à Hyperliquid (identifiant de chaîne 1337, charges utiles Agent en MessagePack) n'est pas accepté par l'API Hypercall.
Erreurs courantes
unauthorized (HTTP 401) : "Signer 0x... is not authorized to act for wallet 0x..."
La récupération EIP-712 renvoie toujours une adresse. Si un élément signé diffère de ce que vous avez envoyé, l'adresse récupérée n'est pas celle de votre clé, et la requête échoue à cette étape plutôt qu'avec une erreur de signature.
Causes :
- Le formatage de la chaîne
priceousizea changé entre la signature et l'envoi (par exemple"100.0"contre"100") - Un champ signé diffère du corps JSON (
route,clientId/client_id,nonce,tif), ouroutea été signé mais omis du corps, ou l'inverse - Mauvais domaine (identifiant de chaîne, nom, version, contrat de vérification)
- Mauvaise structure pour la requête (par exemple
PlaceOrderau lieu deReplaceOrdersurPUT /order) - Le signataire n'est réellement ni le portefeuille ni un agent approuvé. Approuvez l'agent via
POST /approve-agentou signez directement avec le portefeuille
Si le message mentionne une adresse de signataire que vous ne reconnaissez pas, il s'agit des causes 1 à 4.
signature_verification_failed (HTTP 400)
Cause : la signature n'a pas pu être décodée ni récupérée, par exemple en raison d'une longueur incorrecte ou d'un hexadécimal invalide.
invalid_parameter (HTTP 400) : "price must be provided as a string for signature verification"
Cause : price ou size a été envoyé sous forme de nombre JSON. Envoyez les deux sous forme de chaînes.
Exemples d'implémentation
Python (eth_account 0.10 ou ultérieur)
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);
Tester les signatures
Exemple de test de génération de signature :
- Générez une signature avec votre implémentation
- Envoyez un ordre de test via
POST /order - Vérifiez la réponse : HTTP 200 avec
status"ACKED", ou"REJECTED"accompagné d'unreason - Si la requête échoue avec HTTP 401
unauthorizedet une adresse de signataire inconnue, ou HTTP 400signature_verification_failed, vérifiez :- Le formatage des chaînes
priceetsize - Les paramètres du domaine (en particulier
chainId) - Que chaque champ signé correspond au corps, y compris
routeetnonce
- Le formatage des chaînes
Considérations de sécurité
- N'exposez jamais vos clés privées : utilisez des portefeuilles matériels ou une gestion sécurisée des clés
- Gestion des nonces : utilisez des nonces persistants, sous forme d'horodatages en millisecondes, par clé de signature
- Autorisation des agents : auditez régulièrement les agents autorisés via
GET /authorized-agents - Encodage des chaînes : assurez-vous que
priceetsizesont des chaînes, à la fois lors de la signature et dans la requête
Références
- EIP-712 : https://eips.ethereum.org/EIPS/eip-712
- Implémentation : assurée par la pile de récupération de signature et de middleware.