Authentication & Signing
EIP-712 signature requirements for write operations.
Use chain ID 999 for production. Testnet is temporarily disabled until Hypercall acquires more testnet HYPE.
Overview
All write operations require EIP-712 typed data signatures. The signer must either:
- Be the wallet address itself (direct signing), OR
- Be an authorized agent for the wallet (see Agent auth)
EIP-712 Domain
Domain separator:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID: 999 (production)
Message Types
PlaceOrder
Struct with explicit route:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Struct when route is omitted:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Field requirements:
wallet: Wallet address (0x-prefixed hex string)symbol: Option symbol (e.g.,"BTC-20250131-100000-C")side:"Buy"or"Sell"(exact string match required)size: Size as string (e.g.,"0.1") - MUST match exactly what is sent in JSONprice: Price as string (e.g.,"100.0") - MUST match exactly what is sent in JSONtif: Time-in-force:"gtc","ioc", or"fok"(exact string match required)route: Optional order route:"best_execution","book_only", or"rfq_only"(exact string match required when present)clientId: Client-provided order ID (string, can be empty"")nonce: Unique nonce (uint64) for replay protection
Critical: price and size MUST be strings in both the signed message and the JSON request body. String formatting must match exactly.
Route default: route is optional until at least July 4, 2026. When omitted from JSON, omit it from the typed data too. The server treats omitted route as best_execution. POST /order returns deprecation headers for omitted route. Clients should send route; it will not become required before July 4, 2026, but may become required later.
CancelOrder
Struct:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Field requirements:
wallet: Wallet addressorderId: Order ID as string (e.g.,"123")nonce: Unique nonce
CancelOrderByClientId
Struct:
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Field requirements:
wallet: Wallet addressclientId: Client order ID (string)nonce: Unique nonce
ApproveAgent
Struct:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Field requirements:
agent: Agent wallet address to authorizenonce: Unique nonce
Note: The wallet authorizing the agent is derived from the recovered signature.
RevokeAgent
Struct:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Field requirements:
agent: Agent wallet address to revokenonce: Unique nonce
Risk simulation
POST /risk/simulate/portfolio and POST /risk/simulate/orders are public,
read-only engine-authority requests. They require no signature, authentication,
or nonce. The wallet field selects the account snapshot to evaluate.
Portfolio request example:
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"}
]
}'
Atomic order request example:
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 is optional. Its entries are treated as resting GTC
orders and admitted sequentially in array order before orders. For this
request only, they are evaluated together with the account's authoritative live
open orders and active reservations. The engine state is not modified. On a
successful simulation, simulated and delta include the entire hypothetical
sequence plus the proposed resting order or atomic package. The two order arrays
may contain at most 50 entries in total.
Simulation responses are snapshots, not execution guarantees. Routing, RPI availability, matching, nonce admission, and future trading-halt state are not simulated. A later real order can differ after engine state advances.
Signing Process
Step 1: Construct Message
Example for PlaceOrder with explicit route:
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
};
Step 2: Define Domain
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Step 3: Sign Typed Data
Using 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);
Step 4: Send Request
Critical: The JSON request body must use the exact same string values for price and 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..."
}
Nonce Management
Requirements:
- Nonces MUST be unique per wallet
- Nonces SHOULD be incrementing (prevents replay attacks)
- Nonces are not validated for strict monotonicity (gaps allowed)
Best practices:
- Use a persistent counter per wallet
- Increment after each successful signature
- Handle nonce gaps gracefully (e.g., if a request fails, retry with same nonce)
Agent Authorization
If using an agent wallet to sign:
-
Approve agent (one-time):
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
Sign orders with agent wallet:
- Use agent wallet to sign
PlaceOrder/CancelOrdermessages - Set
walletfield to the trading wallet address - Middleware verifies agent is authorized for that wallet
- Use agent wallet to sign
See Agent auth for full agent authorization details.
Perp Orders (Hyperliquid-Compatible)
Perp orders use a different EIP-712 domain and message format (Hyperliquid Core compatibility):
Domain:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Message: Agent struct with MessagePack-encoded order data.
See the current signature encoding guide for exact fields.
Common Errors
"Signature verification failed"
Causes:
priceorsizesent as number instead of string- String formatting changed between signing and sending (e.g.,
"100.0"vs"100") - Wrong nonce
- Wrong domain (chain ID, name, version)
- Agent not authorized for wallet
"Unauthorized: signer not authorized for wallet"
Cause: Signer is not the wallet itself and is not an authorized agent.
Solution: Approve agent via POST /approve-agent or sign with wallet directly.
Implementation Examples
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);
Testing Signatures
Example for testing signature generation:
- Generate signature with your implementation
- Send test order via
POST /order - Verify response:
status="ACKED"orstatus="REJECTED"with reason - If
"signature_verification_failed", check:- String formatting of
priceandsize - Domain parameters (especially
chainId) - Nonce correctness
- String formatting of
Security Considerations
- Never expose private keys: Use hardware wallets or secure key management
- Nonce management: Use persistent, incrementing nonces per wallet
- Agent authorization: Regularly audit authorized agents via
GET /authorized-agents - String encoding: Ensure
priceandsizeare strings in both signing and request
References
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Implementation: handled by the signature recovery and middleware stack.