Diese Seite wurde maschinell übersetzt. Das englische Original ist maßgeblich. Auf Englisch lesen →
Zum Hauptinhalt springen

Authentifizierung & Signierung

EIP-712-Signaturanforderungen für Schreibvorgänge.

Signing-Domain für Produktion

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:

  1. die Wallet-Adresse selbst sein (direkte Signierung), ODER
  2. 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 übereinstimmen
  • price: Preis als String (z. B. "100.0") - MUSS exakt mit dem im JSON gesendeten Wert übereinstimmen
  • tif: 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_order akzeptiert 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-Adresse
  • orderId: Order-ID als String (z. B. "123")
  • nonce: Eindeutige Nonce

CancelOrderByClientId​

Struct:

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

Feldanforderungen:

  • wallet: Wallet-Adresse
  • clientId: Client-Order-ID (String)
  • nonce: Eindeutige Nonce

ApproveAgent​

Struct:

struct ApproveAgent {
address agent;
uint64 nonce;
}

Feldanforderungen:

  • agent: Zu autorisierende Agent-Wallet-Adresse
  • nonce: 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 soll
  • nonce: 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 1 oder 123 werden 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-PlaceOrder gemeinsam 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:

  1. Agent genehmigen (einmalig):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1791273600000,
    "signature": "0x..." # Signed by wallet owner
    }
  2. Orders mit der Agent-Wallet signieren:

    • Signieren Sie PlaceOrder- / CancelOrder-Nachrichten mit der Agent-Wallet
    • Setzen Sie das Feld wallet auf die Adresse der Trading-Wallet
    • Die Middleware prüft, ob der Agent für diese Wallet autorisiert ist

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:

  1. Die String-Formatierung von price oder size hat sich zwischen Signieren und Senden geändert (z. B. "100.0" vs. "100")
  2. Ein signiertes Feld weicht vom JSON-Body ab (route, clientId/client_id, nonce, tif), oder route wurde signiert, aber im Body weggelassen, oder umgekehrt
  3. Falsche Domain (Chain-ID, Name, Version, Verifying Contract)
  4. Falsches Struct für die Anfrage (zum Beispiel PlaceOrder statt ReplaceOrder bei PUT /order)
  5. Der Signierende ist tatsächlich weder die Wallet noch ein genehmigter Agent. Genehmigen Sie den Agent über POST /approve-agent oder 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:

  1. Erzeugen Sie eine Signatur mit Ihrer Implementierung
  2. Senden Sie eine Test-Order über POST /order
  3. Prüfen Sie die Antwort: HTTP 200 mit status "ACKED" oder "REJECTED" mit einem reason
  4. Schlägt die Anfrage mit HTTP 401 unauthorized und einer unbekannten Signer-Adresse oder mit HTTP 400 signature_verification_failed fehl, prüfen Sie:
    • die String-Formatierung von price und size
    • die Domain-Parameter (insbesondere chainId)
    • ob jedes signierte Feld mit dem Body übereinstimmt, einschließlich route und nonce

Sicherheitshinweise​

  1. Private Schlüssel niemals offenlegen: Verwenden Sie Hardware-Wallets oder eine sichere Schlüsselverwaltung
  2. Nonce-Verwaltung: Verwenden Sie persistente Nonces auf Basis von Millisekunden-Zeitstempeln pro Signierschlüssel
  3. Agent-Autorisierung: Überprüfen Sie autorisierte Agents regelmäßig über GET /authorized-agents
  4. String-Kodierung: Stellen Sie sicher, dass price und size sowohl beim Signieren als auch in der Anfrage Strings sind

Referenzen​