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

Підписання EIP-712

Цей документ описує три домени підписання EIP-712, що використовуються для он-чейн дій: Agent Requests (запити агента), Manager Actions (дії менеджера) та RSM Commands (команди RSM).

Огляд

Більшість дій протоколу вимагають типізованих підписів EIP-712. Підписантом має бути:

  1. Agent (API-гаманець): авторизований через Exchange.addApiWallet — підписує торгові запити
  2. Manager: власник акаунта — підписує виведення коштів і перекази активів
  3. RSM Signer: контролюється протоколом — підписує команди ліквідації/ребалансування

Контракт Exchange перевіряє підписи та передає дії до Processor, який кодує їх як повідомлення ActionCaster.

Непідписані точки входу для поповнення

Не кожен виклик, пов'язаний з поповненням, є дією EIP-712. Ці методи є прямими транзакціями, що надсилаються гаманцем-платником або роутером:

function depositUsdcFor(address account, uint256 amount) external;
function depositOption(address account, address token, uint256 amount) external;

depositUsdcFor навмисно не підписується, оскільки msg.sender — це лише платник USDC. Акаунт Hypercall, який отримує кошти, — це явний аргумент account та поле події UsdcDeposit.account. Роутери та зепи можуть викликати цей метод, тому індексери та бекенд-сервіси не повинні використовувати msg.sender для визначення отримувача коштів.

depositOption спалює опціонні токени з msg.sender та емітує Deposit(account, msg.sender, token, amount) для індексера RSM. Шлях зарахування опціонів керується подіями та не використовує підпис менеджера, агента або RSM від депонента.

Роздільники доменів EIP-712

Усі три домени використовують однакову структуру, але різні назви:

{
"name": "<DomainName>",
"version": "1",
"chainId": <chainId>,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

Chain ID:

  • Тестнет: 998
  • Основна мережа: 999

Домен 1: Agent Requests (HypercallAgentSign)

Назва домену: "HypercallAgentSign"

Попередньо обчислені роздільники доменів:

  • Тестнет: 0x8f0a44075cd4e0c79e5bd379a6fad5fa1329a4ea76d74e4edfa1138933d35e8a
  • Основна мережа: використовуйте chain ID 999 та конфігурацію розгорнутого верифікатора для активного середовища.

Підписант: API-гаманець (має бути авторизований через Exchange.addApiWallet)

Nonce: захист від повторного використання для кожного підписанта. Рушій зберігає 100 найвищих nonce на підписанта. Новий nonce має бути більшим за найменший у наборі та ще не використаним. Nonce повинні бути в межах (T - 2 дні, T + 1 день) від часової мітки сервера. Он-чейн Exchange.isNonceUsed(signer, nonce) відстежує використання через бітову карту

HLRequestOrder

Розміщує перп/спот ордери на HyperLiquid.

Структура:

struct HLOrder {
uint32 asset; // HyperLiquid asset ID
bool isBuy; // true = buy, false = sell
uint64 limitPx; // Limit price (fixed-point)
uint64 sz; // Size (fixed-point)
bool reduceOnly; // true = reduce-only order
uint8 encodedTif; // Time-in-force encoding
uint128 cloid; // Client order ID (0 = auto-generate)
}

struct HLRequestOrder {
HLOrder[] orders;
uint64 nonce;
}

Type Hash:

  • HL_ORDER_TYPE_HASH: keccak256("HLOrder(uint32 asset,bool isBuy,uint64 limitPx,uint64 sz,bool reduceOnly,uint8 encodedTif,uint128 cloid)")
  • HL_ORDER_REQUEST_TYPE_HASH: keccak256("HLRequestOrder(HLOrder[] orders,uint64 nonce)HLOrder(...)")

Кодування:

  1. Хешуйте кожен HLOrder за допомогою structHash(HLOrder)
  2. Запакуйте хеші ордерів: keccak256(abi.encodePacked(orderHashes))
  3. Хешуйте запит: keccak256(abi.encode(HL_ORDER_REQUEST_TYPE_HASH, packedOrderHashes, nonce))
  4. Дайджест EIP-712: MessageHashUtils.toTypedDataHash(domainSeparator, structHash)

Приклад (ethers.js):

const domain = {
name: "HypercallAgentSign",
version: "1",
chainId: 998, // testnet
verifyingContract: ethers.ZeroAddress
};

const types = {
HLOrder: [
{ name: "asset", type: "uint32" },
{ name: "isBuy", type: "bool" },
{ name: "limitPx", type: "uint64" },
{ name: "sz", type: "uint64" },
{ name: "reduceOnly", type: "bool" },
{ name: "encodedTif", type: "uint8" },
{ name: "cloid", type: "uint128" }
],
HLRequestOrder: [
{ name: "orders", type: "HLOrder[]" },
{ name: "nonce", type: "uint64" }
]
};

const message = {
orders: [{
asset: 0, // BTC perp
isBuy: true,
limitPx: 50000000000, // $50,000 (fixed-point)
sz: 1000000, // 0.001 BTC (fixed-point)
reduceOnly: false,
encodedTif: 0, // GTC
cloid: 0 // auto-generate
}],
nonce: 1
};

const signature = await apiWalletSigner.signTypedData(domain, types, message);

Он-чейн точка входу: Exchange.hlRequestOrder(HLRequestOrder memory request, bytes memory signature)

Результат Processor: кодує кожен ордер як ActionCasterEncoder.limitOrder(...) та повертає дії bytes[].

HLRequestCancel

Скасовує ордери за ідентифікатором ордера.

Структура:

struct HLCancel {
uint32 asset;
uint64 oid; // Order ID from HyperLiquid
}

struct HLRequestCancel {
HLCancel[] cancels;
uint64 nonce;
}

Type Hash:

  • HL_CANCEL_TYPE_HASH: keccak256("HLCancel(uint32 asset,uint64 oid)")
  • HL_CANCEL_REQUEST_TYPE_HASH: keccak256("HLRequestCancel(HLCancel[] cancels,uint64 nonce)HLCancel(...)")

Приклад:

const message = {
cancels: [{
asset: 0,
oid: 12345
}],
nonce: 2
};

const signature = await apiWalletSigner.signTypedData(domain, types, message);

Он-чейн точка входу: Exchange.hlRequestCancel(HLRequestCancel memory request, bytes memory signature)

HLRequestCancelByCloid

Скасовує ордери за клієнтським ідентифікатором ордера.

Структура:

struct HLCancelByCloid {
uint32 asset;
uint128 cloid; // Client order ID
}

struct HLRequestCancelByCloid {
HLCancelByCloid[] cancels;
uint64 nonce;
}

Type Hash:

  • HL_CANCEL_BY_CLOID_TYPE_HASH: keccak256("HLCancelByCloid(uint32 asset,uint128 cloid)")
  • HL_CANCEL_BY_CLOID_REQUEST_TYPE_HASH: keccak256("HLRequestCancelByCloid(HLCancelByCloid[] cancels,uint64 nonce)HLCancelByCloid(...)")

Приклад:

const message = {
cancels: [{
asset: 0,
cloid: 9876543210
}],
nonce: 3
};

const signature = await apiWalletSigner.signTypedData(domain, types, message);

Он-чейн точка входу: Exchange.hlRequestCancelByCloid(HLRequestCancelByCloid memory request, bytes memory signature)

Домен 2: Manager Actions (HypercallManagerSign)

Назва домену: "HypercallManagerSign"

Попередньо обчислені роздільники доменів:

  • Тестнет: 0xd1f76b6138be892c14b71b0569bdb049cb44f239d34c78ef1ffaacd2466f9f18
  • Основна мережа: буде визначено пізніше

Підписант: менеджер акаунта (EOA, що створив акаунт)

Nonce: захист від повторного використання для кожного менеджера. Та сама модель обмеженого набору, що й для nonce агента: зберігаються 100 найвищих nonce, новий nonce має перевищувати мінімум набору та не бути дублікатом. Он-чейн відстежується через Exchange.isNonceUsed(manager, nonce)

HLActionSendAsset

Надсилає активи з акаунта до пункту призначення через ActionCaster.

Структура:

struct HLActionSendAsset {
address account;
uint64 nonce;
address destination;
uint32 srcDex; // Source DEX (type(uint32).max = HyperCore)
uint32 dstDex; // Destination DEX (type(uint32).max = HyperCore)
uint64 token; // Token ID
uint64 amountWei; // Amount in wei
}

Type Hash: keccak256("HLActionSendAsset(address account,uint64 nonce,address destination,uint32 srcDex,uint32 dstDex,uint64 token,uint64 amountWei)")

Вимоги:

  • signer == managers[account] (перевіряється он-чейн)
  • Якщо destination == Exchange, токен має підтримуватися (_checkExchangeToken)

Приклад:

const domain = {
name: "HypercallManagerSign",
version: "1",
chainId: 998,
verifyingContract: ethers.ZeroAddress
};

const types = {
HLActionSendAsset: [
{ name: "account", type: "address" },
{ name: "nonce", type: "uint64" },
{ name: "destination", type: "address" },
{ name: "srcDex", type: "uint32" },
{ name: "dstDex", type: "uint32" },
{ name: "token", type: "uint64" },
{ name: "amountWei", type: "uint64" }
]
};

const message = {
account: accountAddress,
nonce: 1,
destination: recipientAddress,
srcDex: 0xFFFFFFFF, // HyperCore
dstDex: 0xFFFFFFFF, // HyperCore
token: 0, // USDC
amountWei: 1000000 // 1 USDC (6 decimals)
};

const signature = await managerSigner.signTypedData(domain, types, message);

Он-чейн точка входу: Exchange.hlActionSendAsset(HLActionSendAsset memory action, bytes memory signature)

Результат Processor: кодується як ActionCasterEncoder.sendAsset(...).

HCActionWithdrawToken

Виводить токени з Exchange в акаунт.

Структура:

struct HCActionWithdrawToken {
address account;
uint64 nonce;
uint32 srcDex;
uint32 dstDex;
uint64 token;
uint64 amountWei;
}

Type Hash: keccak256("HCActionWithdrawToken(address account,uint64 nonce,uint32 srcDex,uint32 dstDex,uint64 token,uint64 amountWei)")

Вимоги:

  • signer == managers[account]
  • Токен має підтримуватися (_checkExchangeToken — наразі лише спотовий USDC)
  • Акаунт має бути активований на HyperCore (ActionCasterUtils.checkAccountActivated)

Поведінка:

  • Exchange ініціює дії ActionCaster (а не акаунт)
  • Переказує токен з Exchange в акаунт на HyperCore

Приклад:

const message = {
account: accountAddress,
nonce: 2,
srcDex: 0xFFFFFFFF, // Exchange
dstDex: 0xFFFFFFFF, // HyperCore
token: 0, // USDC
amountWei: 5000000 // 5 USDC
};

const signature = await managerSigner.signTypedData(domain, types, message);

Он-чейн точка входу: Exchange.hcActionWithdrawToken(HCActionWithdrawToken memory action, bytes memory signature)

HCActionWithdrawOption

Виводить опціонні токени з Exchange до отримувача на HyperEVM.

Структура:

struct HCActionWithdrawOption {
address account;
uint64 nonce;
address recipient;
address option; // Option token address
uint256 amountWei; // Amount in wei
}

Type Hash: keccak256("HCActionWithdrawOption(address account,uint64 nonce,address recipient,address option,uint256 amountWei)")

Вимоги:

  • signer == managers[account]
  • option має підтримуватися (optionRegistry.isSupportedOption(option))

Поведінка:

  • Без дій ActionCaster (на відміну від інших виведень)
  • Мінтить опціонний токен для recipient через IOptionToken(option).mint(recipient, amountWei)
  • Емітує Withdraw(account, recipient, option, amountWei)

Приклад:

const message = {
account: accountAddress,
nonce: 3,
recipient: recipientAddress,
option: optionTokenAddress,
amountWei: ethers.parseEther("1.0") // 1 option token
};

const signature = await managerSigner.signTypedData(domain, types, message);

Он-чейн точка входу: Exchange.hcActionWithdrawOption(HCActionWithdrawOption memory action, bytes memory signature)

Домен 3: RSM Commands (HypercallRsmSign)

Назва домену: "HypercallRsmSign"

Попередньо обчислені роздільники доменів:

  • Тестнет: 0x650b282053fb61d3fd477bdc28f6434311fe905e27cc4ca643e87e802c45938c
  • Основна мережа: буде визначено пізніше

Підписант: RSM Signer (встановлюється через Exchange.setRsmSigner, перевіряється он-чейн)

Nonce: nonce для кожного RSM-підписанта (відстежується через Exchange.nextNonce[rsmSigner])

Команди RSM можуть викликатися роллю SEQUENCER_ROLE; маркетмейкери не викликають їх безпосередньо.

RsmCommandRebalance

Виконує reduce-only IOC ордер на HyperCore для ребалансування позиції.

Структура:

struct RsmCommandRebalance {
address target; // Account to rebalance
uint64 nonce;
uint32 asset;
bool isBuy;
uint64 limitPx;
uint64 sz;
}

Type Hash: keccak256("RsmCommandRebalance(address target,uint64 nonce,uint32 asset,bool isBuy,uint64 limitPx,uint64 sz)")

Вимоги:

  • signer == rsmSigner (перевіряється он-чейн)
  • Викликач повинен мати SEQUENCER_ROLE

Поведінка:

  • Кодується як ActionCasterEncoder.limitOrder з reduceOnly: true та encodedTif: 3 (IOC)
  • Виконується на цільовому акаунті

Он-чейн точка входу: Exchange.rsmCommandRebalance(RsmCommandRebalance memory cmd, bytes memory signature)

RsmCommandRepay

Депонує токени в Exchange від імені акаунта (використовується для погашень при ліквідації).

Структура:

struct RsmCommandRepay {
address target;
uint64 nonce;
uint32 srcDex;
uint32 dstDex;
uint64 token;
uint64 amountWei;
}

Type Hash: keccak256("RsmCommandRepay(address target,uint64 nonce,uint32 srcDex,uint32 dstDex,uint64 token,uint64 amountWei)")

Вимоги:

  • signer == rsmSigner
  • Викликач повинен мати SEQUENCER_ROLE
  • Токен має підтримуватися (_checkExchangeToken)

Поведінка:

  • Кодується як ActionCasterEncoder.sendAsset з destination: EXCHANGE
  • Виконується на цільовому акаунті

Он-чейн точка входу: Exchange.rsmCommandRepay(RsmCommandRepay memory cmd, bytes memory signature)

Управління nonce

Кожен підписант (API-гаманець, менеджер, RSM-підписант) має незалежний простір nonce:

mapping(address signer => uint256 nonce) public nextNonce;
mapping(address signer => BitMaps.BitMap) private _nonces; // Tracks used nonces

Правила:

  1. Nonce мають бути строго зростаючими (прогалини не забороняються, але підтримується nextNonce)
  2. Після використання nonce не може бути використаний повторно (перевіряється через isNonceUsed(signer, nonce))
  3. nextNonce[signer] — це мінімальний гарантовано невикористаний nonce (нижчі nonce можуть бути невикористаними, якщо вони були пропущені)

Запит статусу nonce:

function isNonceUsed(address signer, uint256 nonce) external view returns (bool);

Найкраща практика: відстежуйте nonce поза мережею та збільшуйте їх атомарно. Використовуйте nextNonce як перевірку коректності.

Потік перевірки підпису

  1. Поза мережею: підписант створює дайджест EIP-712 та підписує приватним ключем
  2. Он-чейн: Exchange отримує підписане повідомлення та викликає Processor.process*
  3. Processor: перевіряє підпис, відновлює підписанта, кодує дії ActionCaster
  4. Exchange: перевіряє nonce, перевіряє авторизацію (менеджер/API-гаманець/RSM), виконує дії

Приклад потоку (HLRequestOrder):

1. API Wallet signs HLRequestOrder with nonce=1
2. RSM Sequencer calls Exchange.hlRequestOrder(request, signature)
3. Processor.hlRequestOrder verifies signature, recovers API wallet
4. Exchange._useNonce(apiWallet, 1) checks and marks nonce as used
5. Exchange._getAccountByApiWallet(apiWallet) returns Account
6. Account.performCoreActions(orderActions) executes ActionCaster calls

Застарілі функції

Наступні функції застаріли, але все ще існують для зворотної сумісності:

  • placeCoreOrders (використовуйте hlRequestOrder)
  • cancelCoreOrders (використовуйте hlRequestCancel)
  • cancelCoreOrdersByCloid (використовуйте hlRequestCancelByCloid)

Вони використовують застарілу схему кодування MsgPack та домен CoreSignatures ("Exchange", chainId 1337). Не використовуйте для нових інтеграцій.

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

  1. Зберігання приватних ключів: зберігайте ключі API-гаманця та менеджера безпечно (апаратний гаманець для менеджера, зашифроване сховище для API-гаманців).

  2. Повторне використання nonce: ніколи не використовуйте nonce повторно. Відстежуйте nonce поза мережею та збільшуйте їх атомарно.

  3. Роздільник домену: завжди використовуйте правильний chain ID (998 для тестнету, для основної мережі буде визначено пізніше). Переконайтеся, що роздільник домену відповідає константам контракту.

  4. Перевірка підпису: контракт перевіряє підписи он-чейн. Не довіряйте перевірці підписів поза мережею для критичних операцій.

  5. Менеджер vs API-гаманець: менеджери контролюють володіння акаунтом та виведення коштів. API-гаманці підписують лише торгові запити. Використовуйте окремі ключі.

Посилання

  • Onboarding для створення акаунта та налаштування API-гаманця
  • API Authentication для автентифікації API поза мережею