Authentifizierung & Signierung
EIP-712-Signaturanforderungen für Schreibvorgänge.
Verwenden Sie für die Produktion die Chain-ID 999. Das Testnet ist vorübergehend deaktiviert, bis Hypercall mehr Testnet-HYPE erhält.
Überblick
Alle Schreibvorgänge erfordern EIP-712-Typed-Data-Signaturen. Der Signierende muss entweder:
- die Wallet-Adresse selbst sein (direkte Signierung), ODER
- ein autorisierter Agent für die Wallet sein (siehe Agent-Autorisierung)
EIP-712-Domain
Domain Separator:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain-ID: 999 (Produktion)
Nachrichtentypen
PlaceOrder
Struct mit explizitem Route-Feld:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Struct ohne Route-Feld:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Feldanforderungen:
wallet: Wallet-Adresse (hexadezimaler String mit Präfix 0x)symbol: Optionssymbol (z. B."BTC-20250131-100000-C")side:"Buy"oder"Sell"(exakte String-Übereinstimmung erforderlich)size: Größe als String (z. B."0.1") - MUSS exakt mit dem im JSON gesendeten Wert übereinstimmenprice: Preis als String (z. B."100.0") - MUSS exakt mit dem im JSON gesendeten Wert übereinstimmentif: Time-in-Force:"gtc","ioc"oder"fok"(exakte String-Übereinstimmung erforderlich)route: Optionale Order-Route:"best_execution"oder"book_only"(exakte String-Übereinstimmung erforderlich, falls vorhanden).POST /bulk_orderakzeptiert nur"book_only"."rfq_only"gehört zum signierten Vokabular, wird aber derzeit von jedem Order-Endpunkt abgelehnt; verwenden Sie für RFQ-Ausführung die RFQ-Endpunkte.clientId: Vom Client vergebene Order-ID (String, darf leer sein:"")nonce: Nonce als Millisekunden-Zeitstempel (uint64), eindeutig pro Signierendem. Siehe Nonce-Verwaltung
Wichtig: price und size MÜSSEN Strings sein, sowohl in der signierten Nachricht als auch im JSON-Request-Body. Die String-Formatierung muss exakt übereinstimmen.
Standard-Route: Senden Sie route mit. Eine Order ohne dieses Feld wird weiterhin akzeptiert und als best_execution geroutet, aber POST /order antwortet mit den Headern Deprecation, Sunset und Warning, und das veröffentlichte Sunset-Datum (4. Juli 2026) ist bereits verstrichen. Die Variante ohne Route kann ohne weitere Übergangsfrist entfernt werden. Wenn Sie route im JSON weglassen, lassen Sie es auch in den Typed Data weg.
PlaceOrderReduceOnly
Eine Reduce-only-Order ("reduce_only": true im JSON-Body) signiert ein separates Struct. Sie erfordert ein explizites route.
struct PlaceOrderReduceOnly {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
bool reduceOnly;
uint64 nonce;
}
reduceOnly muss true sein und mit dem Request-Body übereinstimmen. Eine Reduce-only-Order ohne signiertes route wird mit missing_field abgelehnt.
ReplaceOrder
PUT /order und jeder Eintrag von PUT /bulk_order signieren ein ReplaceOrder. Es hat kein route-Feld; ein route-Schlüssel im JSON-Body wird ignoriert.
struct ReplaceOrder {
address wallet;
string orderId;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
orderId ist der dezimale String der zu ersetzenden Order. Eine Reduce-only-Ersetzung ("reduce_only": true) signiert ReplaceOrderReduceOnly, das bool reduceOnly zwischen clientId und nonce einfügt. Das TypeScript-SDK exportiert beide Type-Maps.
CancelOrder
Struct:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Feldanforderungen:
wallet: Wallet-AdresseorderId: Order-ID als String (z. B."123")nonce: Eindeutige Nonce
CancelOrderByClientId
Struct:
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Feldanforderungen:
wallet: Wallet-AdresseclientId: Client-Order-ID (String)nonce: Eindeutige Nonce
ApproveAgent
Struct:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Feldanforderungen:
agent: Zu autorisierende Agent-Wallet-Adressenonce: Eindeutige Nonce
Hinweis: Die Wallet, die den Agent autorisiert, wird aus der wiederhergestellten Signatur abgeleitet.
RevokeAgent
Struct:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Feldanforderungen:
agent: Agent-Wallet-Adresse, deren Berechtigung widerrufen werden sollnonce: Eindeutige Nonce
RevokeAllAgents
DELETE /revoke-all-agents entfernt alle Agents der signierenden Wallet.
struct RevokeAllAgents {
uint64 nonce;
}
Risikosimulation
POST /risk/simulate/portfolio und POST /risk/simulate/orders sind öffentliche,
schreibgeschützte Anfragen an die Engine-Autorität. Sie erfordern weder Signatur,
Authentifizierung noch Nonce. Das Feld wallet wählt den auszuwertenden Konto-Snapshot aus.
Beispiel für eine Portfolio-Anfrage:
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"}
]
}'
Beispiel für eine atomare Order-Anfrage:
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 ist optional. Die Einträge werden als ruhende GTC-Orders
behandelt und in Array-Reihenfolge nacheinander vor orders zugelassen. Nur für diese
Anfrage werden sie zusammen mit den maßgeblichen offenen Live-Orders und aktiven
Reservierungen des Kontos ausgewertet. Der Engine-Zustand wird nicht verändert. Bei
erfolgreicher Simulation enthalten simulated und delta die gesamte hypothetische
Sequenz sowie die vorgeschlagene ruhende Order oder das atomare Paket. Die beiden
Order-Arrays dürfen zusammen höchstens 50 Einträge enthalten.
Simulationsantworten sind Momentaufnahmen, keine Ausführungsgarantien. Routing, RPI-Verfügbarkeit, Matching, Nonce-Zulassung und künftige Handelsstopps werden nicht simuliert. Eine spätere echte Order kann abweichen, nachdem sich der Engine-Zustand weiterentwickelt hat.
Signiervorgang
Schritt 1: Nachricht erstellen
Beispiel für PlaceOrder mit explizitem Route-Feld:
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
};
Schritt 2: Domain definieren
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Schritt 3: Typed Data signieren
Mit ethers.js v6 (in v5 heißt die Methode _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);
Schritt 4: Anfrage senden
Wichtig: Der JSON-Request-Body muss für price und size exakt dieselben String-Werte verwenden:
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
};
Nonce-Verwaltung
Anforderungen:
- Nonces müssen strikt zwischen zwei Tagen vor und einem Tag nach der Serverzeit in Millisekunden liegen. Verwenden Sie daher die aktuelle Unix-Zeit in Millisekunden. Kleine Zähler wie
1oder123werden abgelehnt. - Nonces werden pro Signierschlüssel verfolgt. Bei Orders und Stornierungen ist das der Agent, wenn ein Agent signiert; bei Agent-Genehmigung und -Widerruf ist es die Eigentümer-Wallet.
- Eine Nonce kann nicht wiederverwendet werden. Order-Nonces werden von
POST /order, Bulk-Orders und WebSocket-PlaceOrdergemeinsam genutzt; eine auf einem Transportweg verwendete Nonce kann auf keinem anderen erneut verwendet werden. - Lücken sind zulässig. Die Engine speichert die 100 jüngsten Nonces pro Signierendem. Sobald 100 gespeichert sind, muss eine neue Nonce größer sein als die kleinste gespeicherte.
Best Practices:
- Initialisieren Sie mit der Epoch-Zeit in Millisekunden, speichern Sie die letzte Nonce pro Signierendem dauerhaft und geben Sie
max(now_ms, last + 1)zurück - Verwenden Sie einen gemeinsamen Allokator für alle Prozesse, die mit demselben Schlüssel signieren
- Schlägt eine Anfrage fehl, signieren Sie sie erneut mit einer neuen Nonce
Agent-Autorisierung
Wenn Sie mit einer Agent-Wallet signieren:
-
Agent genehmigen (einmalig):
POST /approve-agent{"agent": "0x...","nonce": 1791273600000,"signature": "0x..." # Signed by wallet owner} -
Orders mit der Agent-Wallet signieren:
- Signieren Sie
PlaceOrder- /CancelOrder-Nachrichten mit der Agent-Wallet - Setzen Sie das Feld
walletauf die Adresse der Trading-Wallet - Die Middleware prüft, ob der Agent für diese Wallet autorisiert ist
- Signieren Sie
Ausführliche Informationen zur Agent-Autorisierung finden Sie unter Agent-Autorisierung.
Perp-Orders
Perp-Orders verwenden nicht die oben beschriebene Hypercall-Domain. Sie werden als HLOrder in der Domain HypercallApiSign (Chain-ID 999, Null-Verifying-Contract) von einer API-Wallet signiert, die auf Ihrem Hypercall-Account registriert ist. Struct und Einheiten finden Sie unter EIP-712-Signierung.
Die TEE-Route ist über die API optional: Sie wählen sie, indem Sie die TEE-Endpunkte aufrufen. In der Hypercall-App ist sie der Standard. Senden Sie dazu die signierte Order an POST /v1/tee-perp/orders/submit mit account, nonce, action und signature. Hypercall führt die Margin-Prüfung durch, anschließend signiert eine sichere Enclave die native Hyperliquid-Order für Ihren Account und sendet sie ab. Jede Perp-Order enthält den Hypercall-Builder-Code mit 0 % Gebühr. Der Account-Manager muss diesen Builder daher vor der ersten Order einmalig genehmigen (der Schritt Enable trading in der App). Andernfalls wird die Order mit builder_not_approved abgelehnt. Den Ablauf finden Sie unter Perps, das Request- und Response-Schema in der API-Referenz.
Die eigene Exchange-Domain von Hyperliquid (Chain-ID 1337, MessagePack-Agent-Payloads) wird von der Hypercall-API nicht akzeptiert.
Häufige Fehler
unauthorized (HTTP 401): "Signer 0x... is not authorized to act for wallet 0x..."
Die EIP-712-Wiederherstellung liefert immer irgendeine Adresse. Weicht etwas, das Sie signiert haben, von dem ab, was Sie gesendet haben, ist die wiederhergestellte Adresse nicht Ihr Schlüssel, und die Anfrage scheitert hier statt mit einem Signaturfehler.
Ursachen:
- Die String-Formatierung von
priceodersizehat sich zwischen Signieren und Senden geändert (z. B."100.0"vs."100") - Ein signiertes Feld weicht vom JSON-Body ab (
route,clientId/client_id,nonce,tif), oderroutewurde signiert, aber im Body weggelassen, oder umgekehrt - Falsche Domain (Chain-ID, Name, Version, Verifying Contract)
- Falsches Struct für die Anfrage (zum Beispiel
PlaceOrderstattReplaceOrderbeiPUT /order) - Der Signierende ist tatsächlich weder die Wallet noch ein genehmigter Agent. Genehmigen Sie den Agent über
POST /approve-agentoder signieren Sie direkt mit der Wallet
Nennt die Meldung eine Signer-Adresse, die Sie nicht kennen, liegt eine der Ursachen 1 bis 4 vor.
signature_verification_failed (HTTP 400)
Ursache: Die Signatur konnte überhaupt nicht dekodiert oder wiederhergestellt werden, zum Beispiel wegen falscher Länge oder ungültigem Hex.
invalid_parameter (HTTP 400): "price must be provided as a string for signature verification"
Ursache: price oder size wurde als JSON-Zahl gesendet. Senden Sie beide als Strings.
Implementierungsbeispiele
Python (eth_account 0.10 oder neuer)
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);
Signaturen testen
Beispiel zum Testen der Signaturerzeugung:
- Erzeugen Sie eine Signatur mit Ihrer Implementierung
- Senden Sie eine Test-Order über
POST /order - Prüfen Sie die Antwort: HTTP 200 mit
status"ACKED"oder"REJECTED"mit einemreason - Schlägt die Anfrage mit HTTP 401
unauthorizedund einer unbekannten Signer-Adresse oder mit HTTP 400signature_verification_failedfehl, prüfen Sie:- die String-Formatierung von
priceundsize - die Domain-Parameter (insbesondere
chainId) - ob jedes signierte Feld mit dem Body übereinstimmt, einschließlich
routeundnonce
- die String-Formatierung von
Sicherheitshinweise
- Private Schlüssel niemals offenlegen: Verwenden Sie Hardware-Wallets oder eine sichere Schlüsselverwaltung
- Nonce-Verwaltung: Verwenden Sie persistente Nonces auf Basis von Millisekunden-Zeitstempeln pro Signierschlüssel
- Agent-Autorisierung: Überprüfen Sie autorisierte Agents regelmäßig über
GET /authorized-agents - String-Kodierung: Stellen Sie sicher, dass
priceundsizesowohl beim Signieren als auch in der Anfrage Strings sind
Referenzen
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Implementierung: erfolgt durch den Stack für Signaturwiederherstellung und Middleware.