Эта страница переведена автоматически. Оригинал на английском языке является каноническим. Читать на английском
Перейти к основному содержимому

Аутентификация и подпись

Требования к подписям 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: Клиентский 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)

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

Если для подписи используется кошелёк агента:

  1. Авторизуйте агента (однократно):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1,
    "signature": "0x..." # Signed by wallet owner
    }
  2. Подписывайте заявки кошельком агента:

    • Используйте кошелёк агента для подписи сообщений PlaceOrder / CancelOrder
    • Укажите в поле wallet адрес торгового кошелька
    • Middleware проверяет, что агент авторизован для этого кошелька

Полные детали авторизации агентов см. в разделе Авторизация агентов.

Заявки на перпы (совместимость с Hyperliquid)

Заявки на бессрочные контракты используют другой домен EIP-712 и формат сообщений (совместимость с Hyperliquid Core):

Домен:

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

Сообщение: структура Agent с данными заявки, закодированными в MessagePack.

Точный состав полей см. в актуальном руководстве по кодированию подписей.

Типичные ошибки

"Signature verification failed"

Причины:

  1. price или size отправлены как число вместо строки
  2. Форматирование строки изменилось между подписанием и отправкой (например, "100.0" vs "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 являются строками и при подписании, и в запросе

Ссылки