Cette page a été traduite automatiquement. La version anglaise fait référence. Lire en anglais →
Aller au contenu principal

Authentification et signature

Exigences de signature EIP-712 pour les opérations d'écriture.

Domaine de signature en production

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 :

  1. Être l'adresse du portefeuille elle-même (signature directe), OU
  2. Ê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 JSON
  • price : prix sous forme de chaîne (par exemple "100.0") - DOIT correspondre exactement à ce qui est envoyé dans le JSON
  • tif : 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_order n'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 portefeuille
  • orderId : 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 portefeuille
  • clientId : 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 à autoriser
  • nonce : 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évoquer
  • nonce : 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 1 ou 123 sont 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 et PlaceOrder via 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 :

  1. Approuver l'agent (une seule fois) :

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1791273600000,
    "signature": "0x..." # Signed by wallet owner
    }
  2. Signer les ordres avec le portefeuille de l'agent :

    • Utilisez le portefeuille de l'agent pour signer les messages PlaceOrder / CancelOrder
    • Renseignez le champ wallet avec l'adresse du portefeuille de trading
    • Le middleware vérifie que l'agent est autorisé pour ce portefeuille

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 :

  1. Le formatage de la chaîne price ou size a changé entre la signature et l'envoi (par exemple "100.0" contre "100")
  2. Un champ signé diffère du corps JSON (route, clientId/client_id, nonce, tif), ou route a été signé mais omis du corps, ou l'inverse
  3. Mauvais domaine (identifiant de chaîne, nom, version, contrat de vérification)
  4. Mauvaise structure pour la requête (par exemple PlaceOrder au lieu de ReplaceOrder sur PUT /order)
  5. Le signataire n'est réellement ni le portefeuille ni un agent approuvé. Approuvez l'agent via POST /approve-agent ou 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 :

  1. Générez une signature avec votre implémentation
  2. Envoyez un ordre de test via POST /order
  3. Vérifiez la réponse : HTTP 200 avec status "ACKED", ou "REJECTED" accompagné d'un reason
  4. Si la requête échoue avec HTTP 401 unauthorized et une adresse de signataire inconnue, ou HTTP 400 signature_verification_failed, vérifiez :
    • Le formatage des chaînes price et size
    • Les paramètres du domaine (en particulier chainId)
    • Que chaque champ signé correspond au corps, y compris route et nonce

Considérations de sécurité​

  1. N'exposez jamais vos clés privées : utilisez des portefeuilles matériels ou une gestion sécurisée des clés
  2. Gestion des nonces : utilisez des nonces persistants, sous forme d'horodatages en millisecondes, par clé de signature
  3. Autorisation des agents : auditez régulièrement les agents autorisés via GET /authorized-agents
  4. Encodage des chaînes : assurez-vous que price et size sont des chaînes, à la fois lors de la signature et dans la requête

Références​