Цю сторінку перекладено автоматично. Оригінал англійською мовою є канонічним. Читати англійською →
Перейти к основному содержимому

Автентифікація та підписування

Вимоги до підписів EIP-712 для операцій запису.

Домен підписування в основній мережі

Для продакшну використовуйте chain ID 999. Тестнет тимчасово вимкнено, доки Hypercall не отримає більше тестових HYPE.

Огляд​

Усі операції запису потребують підписів типізованих даних EIP-712. Підписант має бути:

  1. Самою адресою гаманця (пряме підписування), АБО
  2. Авторизованим агентом цього гаманця (див. Авторизація агентів)

Домен 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") - МАЄ точно збігатися з тим, що надсилається в JSON
  • price: ціна як рядок (наприклад, "100.0") - МАЄ точно збігатися з тим, що надсилається в JSON
  • tif: термін дії ордера: "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, пакетних ордерів і WebSocket PlaceOrder; nonce, використаний в одному транспорті, не можна повторно використати в іншому.
  • Пропуски дозволені. Рушій зберігає 100 найновіших nonce для кожного підписанта, і коли їх набирається 100, новий nonce має бути більшим за найменший зі збережених.

Рекомендації:

  • Ініціалізуйте лічильник із часу epoch у мілісекундах і зберігайте останній nonce для кожного підписанта, повертаючи max(now_ms, last + 1)
  • Використовуйте один спільний аллокатор для всіх процесів, що підписують тим самим ключем
  • Якщо запит не вдався, підпишіть його знову з новим nonce

Авторизація агентів​

Якщо для підписування використовується гаманець агента:

  1. Схваліть агента (одноразово):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1791273600000,
    "signature": "0x..." # Signed by wallet owner
    }
  2. Підписуйте ордери гаманцем агента:

    • Використовуйте гаманець агента для підписування повідомлень 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 завжди повертає якусь адресу. Якщо хоч щось із підписаного відрізняється від надісланого, відновлена адреса не належатиме вашому ключу, і запит завершиться помилкою саме тут, а не помилкою підпису.

Причини:

  1. Форматування рядка price або size змінилося між підписуванням і надсиланням (наприклад, "100.0" проти "100")
  2. Підписане поле відрізняється від тіла JSON (route, clientId/client_id, nonce, tif), або route підписано, але не вказано в тілі, чи навпаки
  3. Неправильний домен (chain ID, name, version, verifying contract)
  4. Неправильна структура для запиту (наприклад, PlaceOrder замість ReplaceOrder для PUT /order)
  5. Підписант справді не є ні гаманцем, ні схваленим агентом. Схваліть агента через 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);

Тестування підписів​

Приклад перевірки генерації підпису:

  1. Згенеруйте підпис за допомогою своєї реалізації
  2. Надішліть тестовий ордер через POST /order
  3. Перевірте відповідь: HTTP 200 зі status "ACKED" або "REJECTED" з полем reason
  4. Якщо запит завершився помилкою HTTP 401 unauthorized з незнайомою адресою підписанта або HTTP 400 signature_verification_failed, перевірте:
    • Форматування рядків price і size
    • Параметри домену (особливо chainId)
    • Що кожне підписане поле збігається з тілом запиту, зокрема route і nonce

Міркування безпеки​

  1. Ніколи не розкривайте приватні ключі: використовуйте апаратні гаманці або захищене керування ключами
  2. Керування nonce: використовуйте збережувані nonce у вигляді мілісекундних міток часу для кожного ключа підпису
  3. Авторизація агентів: регулярно перевіряйте авторизованих агентів через GET /authorized-agents
  4. Кодування рядків: переконайтеся, що price і size є рядками як під час підписування, так і в запиті

Посилання​