Аутентификация и подпись
Требования к подписям EIP-712 для операций записи.
Используйте chain ID 999 для продакшена. Тестнет временно отключён, пока Hypercall не получит больше тестнет-HYPE.
Обзор
Все операции записи требуют подписей типизированных данных EIP-712. Подписант должен быть либо:
- Самим адресом кошелька (прямая подпись), ЛИБО
- Авторизованным агентом для кошелька (см. Авторизация агентов)
Домен EIP-712
Разделитель домена:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID: 999 (продакшен)
Типы сообщений
PlaceOrder
Структура с явным указанием route:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Структура, если route опущен:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Требования к полям:
wallet: Адрес кошелька (hex-строка с префиксом 0x)symbol: Символ опциона (например,"BTC-20250131-100000-C")side:"Buy"или"Sell"(требуется точное совпадение строки)size: Размер в виде строки (например,"0.1") — ДОЛЖЕН точно совпадать с тем, что отправляется в JSONprice: Цена в виде строки (например,"100.0") — ДОЛЖНА точно совпадать с тем, что отправляется в JSONtif: Время действия заявки (time-in-force):"gtc","ioc"или"fok"(требуется точное совпадение строки)route: Необязательный маршрут заявки:"best_execution","book_only"или"rfq_only"(при наличии требуется точное совпадение строки)clientId: Клиентский ID заявки (строка, может быть пустой"")nonce: Уникальный nonce (uint64) для защиты от повторного воспроизведения
Критично: price и size ДОЛЖНЫ быть строками как в подписываемом сообщении, так и в теле JSON-запроса. Форматирование строк должно совпадать точно.
Значение route по умолчанию: route остаётся необязательным как минимум до 4 июля 2026 года. Если он опущен в JSON, опустите его и в типизированных данных. Сервер интерпретирует отсутствующий route как best_execution. При отсутствии route POST /order возвращает заголовки об устаревании (deprecation). Клиентам рекомендуется передавать route; он не станет обязательным до 4 июля 2026 года, но может стать обязательным позже.
CancelOrder
Структура:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Требования к полям:
wallet: Адрес кошелькаorderId: ID заявки в виде строки (например,"123")nonce: Уникальный nonce
CancelOrderByClientId
Структура:
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Требования к полям:
wallet: Адрес кошелькаclientId: Клиентский ID заявки (строка)nonce: Уникальный nonce
ApproveAgent
Структура:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Требования к полям:
agent: Адрес кошелька агента, которого нужно авторизоватьnonce: Уникальный nonce
Примечание: Кошелёк, авторизующий агента, определяется из восстановленной подписи.
RevokeAgent
Структура:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Требования к полям:
agent: Адрес кошелька агента, авторизацию которого нужно отозватьnonce: Уникальный nonce
Процесс подписания
Шаг 1: Сформируйте сообщение
Пример для PlaceOrder с явным указанием 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
};
Шаг 2: Определите домен
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Шаг 3: Подпишите типизированные данные
С использованием 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);
Шаг 4: Отправьте запрос
Критично: Тело JSON-запроса должно содержать точно те же строковые значения для price и 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
Требования:
- Nonce ДОЛЖНЫ быть уникальными для каждого кошелька
- Nonce СЛЕДУЕТ делать возрастающими (защита от повторного воспроизведения)
- Строгая монотонность nonce не проверяется (пропуски допустимы)
Рекомендации:
- Используйте персистентный счётчик для каждого кошелька
- Инкрементируйте после каждой успешной подписи
- Корректно обрабатывайте пропуски в nonce (например, при неудачном запросе повторите его с тем же nonce)
Авторизация агентов
Если для подписи используется кошелёк агента:
-
Авторизуйте агента (однократно):
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
Подписывайте заявки кошельком агента:
- Используйте кошелёк агента для подписи сообщений
PlaceOrder/CancelOrder - Укажите в поле
walletадрес торгового кошелька - Middleware проверяет, что агент авторизован для этого кошелька
- Используйте кошелёк агента для подписи сообщений
Полные детали авторизации агентов см. в разделе Авторизация агентов.
Заявки на перпы (совместимость с Hyperliquid)
Заявки на бессрочные контракты используют другой домен EIP-712 и формат сообщений (совместимость с Hyperliquid Core):
Домен:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Сообщение: структура Agent с данными заявки, закодированными в MessagePack.
Точный состав полей см. в актуальном руководстве по кодированию подписей.
Типичные ошибки
"Signature verification failed"
Причины:
priceилиsizeотправлены как число вместо строки- Форматирование строки изменилось между подписанием и отправкой (например,
"100.0"vs"100") - Неверный nonce
- Неверный домен (chain ID, name, version)
- Агент не авторизован для кошелька
"Unauthorized: signer not authorized for wallet"
Причина: Подписант не является самим кошельком и не является авторизованным агентом.
Решение: Авторизуйте агента через POST /approve-agent или подписывайте напрямую кошельком.
Примеры реализации
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);
Тестирование подписей
Пример проверки генерации подписи:
- Сгенерируйте подпись своей реализацией
- Отправьте тестовую заявку через
POST /order - Проверьте ответ:
status="ACKED"илиstatus="REJECTED"с указанием причины - При ошибке
"signature_verification_failed"проверьте:- Форматирование строк
priceиsize - Параметры домена (особенно
chainId) - Корректность nonce
- Форматирование строк
Вопросы безопасности
- Никогда не раскрывайте приватные ключи: Используйте аппаратные кошельки или защищённое управление ключами
- Управление nonce: Используйте персистентные возрастающие nonce для каждого кошелька
- Авторизация агентов: Регулярно проверяйте список авторизованных агентов через
GET /authorized-agents - Кодирование строк: Убедитесь, что
priceиsizeявляются строками и при подписании, и в запросе
Ссылки
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Реализация: обеспечивается стеком восстановления подписей и middleware.