Авторизация агентов
Поддержка подписи несколькими ключами для маркетмейкеров.
Обзор
Авторизация агентов позволяет выделенному ключу подписи (агенту) выставлять и отменять заявки от имени торгового кошелька. Это полезно для следующих задач:
- Безопасность: ключи торгового кошелька хранятся в холодном хранилище, а для подписи используются горячие ключи
- Операционная работа: несколько участников команды могут подписывать заявки разными ключами
- Автоматизация: автоматические системы подписывают заявки выделенными ключами
Две модели авторизации
В Hypercall есть две независимые системы делегирования, в зависимости от продукта:
| Продукт | Тип делегирования | Хранение | Как настроить |
|---|---|---|---|
| Опционы (REST API) | Авторизация агентов | Состояние движка Hypercall | POST /approve-agent |
| Перпы | API-кошелёк | В блокчейне (ваш контракт Account) | Директива hc_update_api_wallet |
Эти системы не связаны между собой. Одобрение агента для торговли опционами не даёт ему права торговать перпами, и наоборот.
Опционы: авторизация агентов (вне блокчейна)
Агенты подписывают EIP-712 сообщения PlaceOrder и CancelOrder от имени торгового кошелька. API проверяет агента по состоянию авторизации в движке до того, как заявка попадёт в движок.
Перпы: API-кошелёк (в блокчейне)
API-кошельки подписывают заявки и отмены по перпам в EIP-712 домене HypercallApiSign. Перед приёмом заявки Hypercall сверяет подписанта с вашим контрактом Account (isApiWalletActive()). На маршруте TEE (используется в приложении по умолчанию, а для API-клиентов включается по желанию) защищённый анклав, хранящий отдельный торговый ключ Hyperliquid для вашего Account, подписывает и отправляет нативную заявку. На маршруте через смарт-контракт, который включается в расширенных настройках приложения, заявка проходит через ваш контракт Account. Справочник по подписи смотрите в разделе Подпись EIP-712, а описание процесса в разделе Перпы.
Чтобы добавить или удалить API-кошелёк, отправьте директиву hc_update_api_wallet, подписанную кошельком-менеджером аккаунта.
Эта схема повторяет модель API-кошельков Hyperliquid. В документации Hyperliquid они называются API-кошельками (также agent wallets) и описаны в разделе Nonces and API wallets, а метод approveAgent описан в разделе Exchange endpoint.
Защита от повторного использования nonce
Отслеживание nonce следует модели nonce Hyperliquid. Все подписанные действия (заявки, одобрение и отзыв агентов, QP-рукопожатия) используют общее пространство nonce для каждого подписанта. Движок хранит 100 наибольших nonce для каждого адреса подписанта. Новый nonce принимается, если:
!stored_set.contains(nonce): он не является дубликатомnonce > min(stored_set), когда в наборе 100 записей: он больше наименьшего сохранённого noncenonceнаходится в интервале (T - 2 дня, T + 1 день) относительно времени сервера
Nonce можно отправлять не по порядку (например, сначала 105, затем 102: оба будут приняты, если превышают минимум набора). Набор ограничен 100 записями, поэтому при добавлении новых самые старые вытесняются. Это не позволяет заблокировать аккаунт одним слишком большим nonce.
Подписант nonce это адрес, подписавший EIP-712 сообщение: API-кошелёк для заявок, восстановленный подписант для авторизации агентов, QP-кошелёк для рукопожатий. В качестве начального значения nonce клиентам следует использовать Date.now() (время эпохи в миллисекундах) и монотонно его увеличивать.
Авторизация агентов для опционов
Прямая подпись
Если signer == wallet, заявка авторизована всегда (подпись самим владельцем).
Подпись агентом
Если signer != wallet, подписант должен быть авторизованным агентом:
- Агент должен присутствовать в состоянии авторизации, которым владеет движок
- Срок действия авторизации не должен истечь
- Источниками восстановления после перезапуска служат снапшоты движка и журнал движка
Одобрение агента
Эндпоинт: POST /approve-agent
Запрос: ApproveAgentRequest
{
"agent": "0x...",
"nonce": 1,
"signature": "0x..."
}
Подпись:
- Владелец кошелька подписывает сообщение
ApproveAgent - Кошелёк определяется по восстановленной подписи
- Агент указывается в поле
agentзапроса
Структура EIP-712:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Ответ: ApproveAgentResponse
{
"success": true,
"error": null
}
Примечания:
- Авторизация агента является журналируемым состоянием движка и сохраняется после перезапусков
- Одобрения, выданные через этот эндпоинт, не имеют срока действия
Отзыв агента
Эндпоинт: DELETE /revoke-agent
Запрос: RevokeAgentRequest
{
"agent": "0x...",
"nonce": 2,
"signature": "0x..."
}
Подпись:
- Владелец кошелька подписывает сообщение
RevokeAgent - Кошелёк определяется по восстановленной подписи
Структура EIP-712:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Ответ: RevokeAgentResponse
Примечания:
- Применяет журналируемую команду
RevokeAgentк состоянию авторизации, которым владеет движок - Проверки авторизации торгового API читают состояние бэкенда и применяют отзывы к торговым запросам. Публикация сообщений в Trollbox может дольше кешировать положительный результат авторизации агента, поэтому недавно отозванный агент может какое-то время публиковать сообщения, пока не истечёт кеш или пока инвалидация по принципу best-effort не дойдёт до соответствующей edge-локации. Этот кеш не даёт права на изменяющие операции торгового API.
- После отзыва агент больше не может подписывать от имени кошелька
Отзыв всех агентов
Эндпоинт: DELETE /revoke-all-agents
Запрос: { "nonce": 3, "signature": "0x..." }, подписывается владельцем кошелька по структуре RevokeAllAgents(uint64 nonce). Удаляет всех агентов восстановленного кошелька.
Получение авторизованных агентов
Эндпоинт: GET /authorized-agents?wallet=...
Параметры запроса:
wallet(обязательный)
Ответ:
{
"agents": [
"0x...",
"0x..."
]
}
Примечания:
- Возвращает только активных агентов с неистёкшим сроком действия
Использование агента
Подпись заявок агентом
После одобрения агента:
- Подпишите заявку кошельком агента: используйте кошелёк агента для подписи сообщений
PlaceOrder/CancelOrder - Заполните поле wallet: укажите в поле
walletадрес торгового кошелька (а не адрес агента) - Проверка в middleware: middleware проверяет, что агент авторизован для этого кошелька
Пример:
// Agent wallet signs the order
const agentSigner = new ethers.Wallet(agentPrivateKey);
const message = {
wallet: "0x...", // Trading wallet (not agent)
symbol: "BTC-20250131-100000-C",
side: "Buy",
size: "0.1",
price: "100.0",
tif: "gtc",
clientId: "mm-1",
nonce: 1
};
// Sign with agent wallet
const signature = await agentSigner._signTypedData(domain, types, message);
// Send request. The REST body uses snake_case `client_id`.
const { clientId, ...rest } = message;
const response = await fetch('/order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...rest,
client_id: clientId,
signature
})
});
Пакетные заявки через агента
Пакетные эндпоинты проверяют авторизацию агента для каждого элемента отдельно:
- Каждая заявка в
POST /bulk_orderможет быть подписана другим агентом - Авторизация агента проверяется для каждого элемента
- Если элемент не проходит проверку авторизации агента, для него в
BulkOrderResultвозвращается ошибка
Проверки авторизации
Middleware (одиночные заявки)
Эндпоинты:
POST /orderDELETE /orderDELETE /order_cloid
Проверка: signature_and_agent_middleware проверяет, что:
- Восстановление подписи прошло успешно
- Подписант авторизован (signer == wallet ИЛИ агент авторизован)
Обработчик (пакетные заявки)
Эндпоинты:
POST /bulk_orderDELETE /bulk_orderDELETE /bulk_order_cloid
Проверка: поэлементная проверка в обработчике:
- Восстановление подписи для каждого элемента
- Проверка авторизации агента для каждого элемента
Хранение авторизаций
Авторизация агентов является состоянием, которым владеет движок. Команды ApproveAgent и RevokeAgent журналируются, восстанавливаются при воспроизведении журнала движка и доступны через снапшот для чтения при проверках в API.
Логика авторизации
Авторизация подписанта задаётся явной логикой ИЛИ:
- Прямая подпись:
signer == wallet. - Подпись агентом: снапшот движка содержит запись авторизации с
wallet_address == <wallet>иagent_address == <signer>, аexpires_atотсутствует или находится в будущем.
Срок действия
Движок поддерживает поле expires_at у авторизации и считает агента с истёкшим сроком
неавторизованным. POST /approve-agent это поле не устанавливает, поэтому агенты,
одобренные через API, остаются активными до отзыва.
Вопросы безопасности
- Безопасность ключей агентов: защищайте приватные ключи агентов (используйте аппаратные кошельки или защищённое управление ключами)
- Регулярный аудит: регулярно проверяйте список авторизованных агентов через
GET /authorized-agents - Отзыв неиспользуемых агентов: отзывайте агентов, которые больше не нужны
Рекомендации
- Используйте агентов для автоматизации: храните ключи торгового кошелька в холодном хранилище, а для автоматических систем используйте агентов
- Ограничивайте круг агентов: одобряйте только тех агентов, которым действительно нужен доступ
- Отслеживайте активность агентов: следите за тем, какие агенты выставляют заявки
- Отзывайте без промедления: сразу отзывайте агентов, которые больше не нужны
Частые проблемы
"Signer 0x... is not authorized to act for wallet 0x..."
Причина: агент не одобрен, либо срок авторизации истёк или она отозвана.
Решение: одобрите агента через POST /approve-agent или подписывайте заявки напрямую кошельком.
Авторизация агента не работает
Причины:
- Агент отсутствует в состоянии авторизации, которым владеет движок
- Авторизация была отозвана
- Срок действия авторизации истёк
Решение: проверьте статус агента через GET /authorized-agents?wallet=....