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

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

Вимоги до підписів 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

Структура з явним 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") - МАЄ точно збігатися з тим, що надсилається в JSON
  • price: ціна як рядок (наприклад, "100.0") - МАЄ точно збігатися з тим, що надсилається в JSON
  • tif: час дії заявки (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)

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

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

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

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

    • Використовуйте гаманець агента для підписання повідомлень PlaceOrder / CancelOrder
    • Установіть у полі wallet адресу торгового гаманця
    • Проміжне програмне забезпечення перевіряє, що агент авторизований для цього гаманця

Див. Авторизація агентів для повних деталей авторизації агентів.

Ордери на перпи (сумісні з Hyperliquid)

Ордери на безстрокові контракти використовують інший домен EIP-712 та формат повідомлення (сумісність з Hyperliquid Core):

Домен:

{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

Повідомлення: структура Agent з даними ордера, закодованими у форматі MessagePack.

Точні поля див. в актуальному посібнику з кодування підписів.

Поширені помилки

"Signature verification failed"

Причини:

  1. price або size надіслано як число замість рядка
  2. Форматування рядка змінилося між підписанням і надсиланням (наприклад, "100.0" проти "100")
  3. Неправильний nonce
  4. Неправильний домен (chain ID, name, version)
  5. Агент не авторизований для гаманця

"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);

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

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

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

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

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

Джерела

  • EIP-712: https://eips.ethereum.org/EIPS/eip-712
  • Реалізація: обробляється стеком відновлення підписів і проміжного програмного забезпечення.