Автентифікація та підписання
Вимоги до підписів 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: ідентифікатор ордера, наданий клієнтом (рядок, може бути порожнім"")nonce: унікальний nonce (uint64) для захисту від повторного відтворення
Критично важливо: price та size МАЮТЬ бути рядками як у підписаному повідомленні, так і в тілі JSON-запиту. Форматування рядків має збігатися точно.
Значення route за замовчуванням: route є необов'язковим щонайменше до 4 липня 2026 року. Якщо його опущено в JSON, опустіть його й у типізованих даних. Сервер трактує опущений route як best_execution. POST /order повертає заголовки про застарілість (deprecation) для опущеного route. Клієнтам варто надсилати route; він не стане обов'язковим до 4 липня 2026 року, але може стати обов'язковим пізніше.
CancelOrder
Структура:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Вимоги до полів:
wallet: адреса гаманцяorderId: ідентифікатор ордера як рядок (наприклад,"123")nonce: унікальний nonce
CancelOrderByClientId
Структура:
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Вимоги до полів:
wallet: адреса гаманцяclientId: клієнтський ідентифікатор ордера (рядок)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адресу торгового гаманця - Проміжне програмне забезпечення перевіряє, що агент авторизований для цього гаманця
- Використовуйте гаманець агента для підписання повідомлень
Див. Авторизація агентів для повних деталей авторизації агентів.
Ордери на перпи (сумісні з Hyperliquid)
Ордери на безстрокові контракти використовують інший домен EIP-712 та формат повідомлення (сумісність з Hyperliquid Core):
Домен:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Повідомлення: структура Agent з даними ордера, закодованими у форматі MessagePack.
Точні поля див. в актуальному посібнику з кодування підписів.
Поширені помилки
"Signature verification failed"
Причини:
priceабоsizeнадіслано як число замість рядка- Форматування рядка змінилося між підписанням і надсиланням (наприклад,
"100.0"проти"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
- Реалізація: обробляється стеком відновлення підписів і проміжного програмного забезпечення.