Authentification & Signature
Exigences de signature EIP-712 pour les opérations d'écriture.
Utilisez le chain ID 999 pour la production. Le testnet est temporairement désactivé jusqu'à ce que Hypercall acquière davantage de HYPE de testnet.
Vue d'ensemble
Toutes les opérations d'écriture nécessitent des signatures de données typées EIP-712. Le signataire doit soit :
- Être l'adresse du portefeuille elle-même (signature directe), OU
- Être un agent autorisé pour le portefeuille (voir Autorisation d'agent)
Domaine EIP-712
Séparateur de domaine :
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID : 999 (production)
Types de messages
PlaceOrder
Struct avec route explicite :
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Struct 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 (p. ex.,"BTC-20250131-100000-C")side:"Buy"ou"Sell"(correspondance exacte de la chaîne requise)size: Taille sous forme de chaîne de caractères (p. ex.,"0.1") - DOIT correspondre exactement à ce qui est envoyé dans le JSONprice: Prix sous forme de chaîne de caractères (p. ex.,"100.0") - DOIT correspondre exactement à ce qui est envoyé dans le JSONtif: Durée de validité (time-in-force) :"gtc","ioc"ou"fok"(correspondance exacte de la chaîne requise)route: Route d'ordre optionnelle :"best_execution","book_only"ou"rfq_only"(correspondance exacte de la chaîne requise lorsqu'elle est présente)clientId: ID d'ordre fourni par le client (chaîne, peut être vide"")nonce: Nonce unique (uint64) pour la protection contre les attaques par rejeu
Critique : price et size DOIVENT être des chaînes de caractères à la fois dans le message signé et dans le corps de la requête JSON. Le formatage des chaînes doit correspondre exactement.
Valeur par défaut de la route : route est optionnel au moins jusqu'au 4 juillet 2026. Lorsqu'il est omis du JSON, omettez-le également des données typées. Le serveur traite une route omise comme best_execution. POST /order renvoie des en-têtes de dépréciation lorsque la route est omise. Les clients devraient envoyer route ; il ne deviendra pas obligatoire avant le 4 juillet 2026, mais pourrait le devenir ultérieurement.
CancelOrder
Struct :
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Exigences des champs :
wallet: Adresse du portefeuilleorderId: ID de l'ordre sous forme de chaîne de caractères (p. ex.,"123")nonce: Nonce unique
CancelOrderByClientId
Struct :
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Exigences des champs :
wallet: Adresse du portefeuilleclientId: ID d'ordre client (chaîne de caractères)nonce: Nonce unique
ApproveAgent
Struct :
struct ApproveAgent {
address agent;
uint64 nonce;
}
Exigences des champs :
agent: Adresse du portefeuille de l'agent à autorisernonce: Nonce unique
Remarque : Le portefeuille autorisant l'agent est dérivé de la signature récupérée.
RevokeAgent
Struct :
struct RevokeAgent {
address agent;
uint64 nonce;
}
Exigences des champs :
agent: Adresse du portefeuille de l'agent à révoquernonce: Nonce unique
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: 123
};
É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 :
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 :
{
"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..."
}
Gestion des nonces
Exigences :
- Les nonces DOIVENT être uniques par portefeuille
- Les nonces DEVRAIENT être croissants (prévient les attaques par rejeu)
- Les nonces ne sont pas validés pour une stricte monotonie (les écarts sont autorisés)
Bonnes pratiques :
- Utilisez un compteur persistant par portefeuille
- Incrémentez après chaque signature réussie
- Gérez les écarts de nonces avec souplesse (p. ex., si une requête échoue, réessayez avec le même nonce)
Autorisation d'agent
Si vous utilisez un portefeuille agent pour signer :
-
Approuver l'agent (une seule fois) :
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
Signer les ordres avec le portefeuille agent :
- Utilisez le portefeuille agent pour signer les messages
PlaceOrder/CancelOrder - Définissez le champ
walletsur l'adresse du portefeuille de trading - Le middleware vérifie que l'agent est autorisé pour ce portefeuille
- Utilisez le portefeuille agent pour signer les messages
Voir Autorisation d'agent pour tous les détails sur l'autorisation des agents.
Ordres sur perpétuels (compatibles Hyperliquid)
Les ordres sur contrats perpétuels utilisent un domaine EIP-712 et un format de message différents (compatibilité avec Hyperliquid Core) :
Domaine :
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Message : struct Agent avec des données d'ordre encodées en MessagePack.
Consultez le guide d'encodage des signatures en vigueur pour connaître les champs exacts.
Erreurs courantes
« Signature verification failed »
Causes :
priceousizeenvoyé sous forme de nombre au lieu de chaîne de caractères- Formatage de la chaîne modifié entre la signature et l'envoi (p. ex.,
"100.0"vs"100") - Nonce incorrect
- Domaine incorrect (chain ID, nom, version)
- Agent non autorisé pour le portefeuille
« Unauthorized: signer not authorized for wallet »
Cause : Le signataire n'est ni le portefeuille lui-même ni un agent autorisé.
Solution : Approuvez l'agent via POST /approve-agent ou signez directement avec le portefeuille.
Exemples d'implémentation
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);
Tester les signatures
Exemple pour tester la génération de signatures :
- Générez une signature avec votre implémentation
- Envoyez un ordre de test via
POST /order - Vérifiez la réponse :
status="ACKED"oustatus="REJECTED"avec la raison - En cas de
"signature_verification_failed", vérifiez :- Le formatage des chaînes
priceetsize - Les paramètres du domaine (en particulier
chainId) - L'exactitude du nonce
- Le formatage des chaînes
Considérations de sécurité
- N'exposez jamais les clés privées : Utilisez des portefeuilles matériels ou une gestion de clés sécurisée
- Gestion des nonces : Utilisez des nonces persistants et croissants par portefeuille
- 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 de caractères à 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 : gérée par la pile de récupération de signature et de middleware.