Автентифікація та підписування
Вимоги до підписів EIP-712 для операцій запису.
Для продакшну використовуйте chain ID 999. Тестнет тимчасово вимкнено, доки Hypercall не отримає більше тестових HYPE.
Огляд
Усі операції запису потребують підписів типізованих даних EIP-712. Підписант має бути:
- Самою адресою гаманця (пряме підписування), АБО
- Авторизованим агентом цього гаманця (див. Авторизація агентів)
Домен EIP-712
Роздільник домену:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID: 999 (продакшн)
Типи повідомлень
PlaceOrder
Структура з явно вказаним маршрутом:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Структура без маршруту:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Вимоги до полів:
wallet: адреса гаманця (шістнадцятковий рядок із префіксом 0x)symbol: символ опціону (наприклад,"BTC-20250131-100000-C")side:"Buy"або"Sell"(потрібен точний збіг рядка)size: розмір як рядок (наприклад,"0.1") - МАЄ точно збігатися з тим, що надсилається в JSONprice: ціна як рядок (наприклад,"100.0") - МАЄ точно збігатися з тим, що надсилається в JSONtif: термін дії ордера:"gtc","ioc"або"fok"(потрібен точний збіг рядка)route: необов'язковий маршрут ордера:"best_execution"або"book_only"(якщо вказано, потрібен точний збіг рядка).POST /bulk_orderприймає лише"book_only"."rfq_only"входить до словника підписуваних значень, але наразі всі ендпоінти ордерів його відхиляють; для виконання через RFQ використовуйте ендпоінти RFQ.clientId: ідентифікатор ордера, наданий клієнтом (рядок, може бути порожнім"")nonce: nonce у вигляді мітки часу в мілісекундах (uint64), унікальний для кожного підписанта. Див. Керування nonce
Критично: price і size МАЮТЬ бути рядками як у підписаному повідомленні, так і в тілі JSON-запиту. Форматування рядків має збігатися точно.
Маршрут за замовчуванням: надсилайте route. Ордер без нього все ще приймається і маршрутизується як best_execution, але POST /order повертає заголовки Deprecation, Sunset і Warning, а оприлюднена дата припинення підтримки (4 липня 2026 року) вже минула. Форму без маршруту можуть прибрати без додаткового пільгового періоду. Якщо ви все ж не вказуєте route у JSON, не вказуйте його і в типізованих даних.
PlaceOrderReduceOnly
Ордер лише на зменшення позиції ("reduce_only": true у тілі JSON) підписує окрему структуру. Вона потребує явно вказаного route.
struct PlaceOrderReduceOnly {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
bool reduceOnly;
uint64 nonce;
}
reduceOnly має дорівнювати true і збігатися з тілом запиту. Ордер лише на зменшення без підписаного route відхиляється з помилкою missing_field.
ReplaceOrder
PUT /order і кожен елемент PUT /bulk_order підписують ReplaceOrder. Ця структура не має поля route; ключ route у тілі JSON ігнорується.
struct ReplaceOrder {
address wallet;
string orderId;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
orderId є десятковим рядком ордера, що замінюється. Заміна лише на зменшення ("reduce_only": true) підписує ReplaceOrderReduceOnly, яка додає bool reduceOnly між clientId і nonce. TypeScript SDK експортує обидві карти типів.
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
RevokeAllAgents
DELETE /revoke-all-agents видаляє всіх агентів гаманця, що підписує запит.
struct RevokeAllAgents {
uint64 nonce;
}
Симуляція ризику
POST /risk/simulate/portfolio і POST /risk/simulate/orders є публічними
запитами до рушія лише для читання. Вони не потребують підпису, автентифікації
чи nonce. Поле wallet визначає знімок акаунта, який буде оцінено.
Приклад запиту для портфеля:
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"}
]
}'
Приклад атомарного запиту ордерів:
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 є необов'язковим. Його елементи розглядаються як GTC-ордери
в книзі заявок і допускаються послідовно в порядку масиву перед orders. Лише для цього
запиту вони оцінюються разом з авторитетними активними відкритими ордерами
та активними резервуваннями акаунта. Стан рушія не змінюється. У разі
успішної симуляції simulated і delta враховують усю гіпотетичну
послідовність разом із запропонованим ордером у книзі або атомарним пакетом. Обидва масиви ордерів
разом можуть містити не більше 50 елементів.
Відповіді симуляції є знімками стану, а не гарантіями виконання. Маршрутизація, доступність RPI, зіставлення, допуск nonce і майбутній стан зупинки торгів не симулюються. Пізніший реальний ордер може дати інший результат після зміни стану рушія.
Процес підписування
Крок 1: сформуйте повідомлення
Приклад для PlaceOrder з явно вказаним маршрутом:
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
};
Крок 2: визначте домен
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Крок 3: підпишіть типізовані дані
З ethers.js v6 (у v5 метод називається _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);
Крок 4: надішліть запит
Критично: тіло JSON-запиту має містити точно ті самі рядкові значення для price і size:
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
Вимоги:
- Nonce має бути строго в межах від двох днів до і одного дня після серверного часу в мілісекундах, тож використовуйте поточний Unix-час у мілісекундах. Малі лічильники на зразок
1або123відхиляються. - Nonce відстежуються для кожного ключа підпису окремо. Для ордерів і скасувань це агент, якщо підписує агент; для схвалення та відкликання агентів це гаманець власника.
- Nonce не можна використати повторно. Nonce ордерів спільні для
POST /order, пакетних ордерів і WebSocketPlaceOrder; nonce, використаний в одному транспорті, не можна повторно використати в іншому. - Пропуски дозволені. Рушій зберігає 100 найновіших nonce для кожного підписанта, і коли їх набирається 100, новий nonce має бути більшим за найменший зі збережених.
Рекомендації:
- Ініціалізуйте лічильник із часу epoch у мілісекундах і зберігайте останній nonce для кожного підписанта, повертаючи
max(now_ms, last + 1) - Використовуйте один спільний аллокатор для всіх процесів, що підписують тим самим ключем
- Якщо запит не вдався, підпишіть його знову з новим nonce
Авторизація агентів
Якщо для підписування використовується гаманець агента:
-
Схваліть агента (одноразово):
POST /approve-agent{"agent": "0x...","nonce": 1791273600000,"signature": "0x..." # Signed by wallet owner} -
Підписуйте ордери гаманцем агента:
- Використовуйте гаманець агента для підписування повідомлень
PlaceOrder/CancelOrder - Вкажіть у полі
walletадресу торгового гаманця - Проміжне ПЗ перевіряє, що агент авторизований для цього гаманця
- Використовуйте гаманець агента для підписування повідомлень
Повні відомості про авторизацію агентів див. у розділі Авторизація агентів.
Ордери на перпи
Ордери на перпи не використовують наведений вище домен Hypercall. Вони підписуються як HLOrder у домені HypercallApiSign (chain ID 999, нульовий верифікаційний контракт) API-гаманцем, зареєстрованим у вашому акаунті Hypercall. Структуру та одиниці див. у розділі Підписування EIP-712.
Маршрут TEE в API вмикається за бажанням: ви обираєте його, викликаючи ендпоінти TEE. У застосунку Hypercall він використовується за замовчуванням. Щоб скористатися ним, надішліть підписаний ордер на POST /v1/tee-perp/orders/submit з полями account, nonce, action і signature. Hypercall виконує перевірку маржі, після чого захищений анклав підписує та надсилає нативний ордер Hyperliquid для вашого акаунта. Кожен ордер на перп містить builder code Hypercall з комісією 0%, тому менеджер акаунта має один раз схвалити цього builder перед першим ордером (крок Enable trading у застосунку). Без цього ордер відхиляється з помилкою builder_not_approved. Опис процесу див. у розділі Перпи, а схеми запиту та відповіді в довіднику API.
Власний домен Hyperliquid Exchange (chain ID 1337, корисне навантаження Agent у MessagePack) API Hypercall не приймає.
Поширені помилки
unauthorized (HTTP 401): "Signer 0x... is not authorized to act for wallet 0x..."
Відновлення EIP-712 завжди повертає якусь адресу. Якщо хоч щось із підписаного відрізняється від надісланого, відновлена адреса не належатиме вашому ключу, і запит завершиться помилкою саме тут, а не помилкою підпису.
Причини:
- Форматування рядка
priceабоsizeзмінилося між підписуванням і надсиланням (наприклад,"100.0"проти"100") - Підписане поле відрізняється від тіла JSON (
route,clientId/client_id,nonce,tif), абоrouteпідписано, але не вказано в тілі, чи навпаки - Неправильний домен (chain ID, name, version, verifying contract)
- Неправильна структура для запиту (наприклад,
PlaceOrderзамістьReplaceOrderдляPUT /order) - Підписант справді не є ні гаманцем, ні схваленим агентом. Схваліть агента через
POST /approve-agentабо підписуйте безпосередньо гаманцем
Якщо в повідомленні вказано незнайому вам адресу підписанта, причина в пунктах 1-4.
signature_verification_failed (HTTP 400)
Причина: підпис взагалі не вдалося декодувати або відновити, наприклад через неправильну довжину чи некоректний hex.
invalid_parameter (HTTP 400): "price must be provided as a string for signature verification"
Причина: price або size надіслано як JSON-число. Надсилайте обидва значення рядками.
Приклади реалізації
Python (eth_account 0.10 або новіший)
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);
Тестування підписів
Приклад перевірки генерації підпису:
- Згенеруйте підпис за допомогою своєї реалізації
- Надішліть тестовий ордер через
POST /order - Перевірте відповідь: HTTP 200 зі
status"ACKED"або"REJECTED"з полемreason - Якщо запит завершився помилкою HTTP 401
unauthorizedз незнайомою адресою підписанта або HTTP 400signature_verification_failed, перевірте:- Форматування рядків
priceіsize - Параметри домену (особливо
chainId) - Що кожне підписане поле збігається з тілом запиту, зокрема
routeіnonce
- Форматування рядків
Міркування безпеки
- Ніколи не розкривайте приватні ключі: використовуйте апаратні гаманці або захищене керування ключами
- Керування nonce: використовуйте збережувані nonce у вигляді мілісекундних міток часу для кожного ключа підпису
- Авторизація агентів: регулярно перевіряйте авторизованих агентів через
GET /authorized-agents - Кодування рядків: переконайтеся, що
priceіsizeє рядками як під час підписування, так і в запиті
Посилання
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Реалізація: забезпечується стеком відновлення підписів і проміжного ПЗ.