EIP-712 签名
本文档描述用于链上操作的三个 EIP-712 签名域:Agent 请求、Manager 操作和 RSM 命令。
概述
大多数协议操作都需要 EIP-712 类型化数据签名。签名者必须是:
- Agent(API 钱包):通过
Exchange.addApiWallet授权 - 签署交易请求 - Manager:账户所有者 - 签署提款和资产转移
- RSM 签名者:协议控制 - 签署强制平仓/再平衡命令
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 事件字段。路由器和 zap 可能会调用此方法,因此索引器和后端服务不得使用 msg.sender 进行入账归属。
depositOption 从 msg.sender 销毁期权代币,并为 RSM 索引器发出 Deposit(account, msg.sender, token, amount) 事件。期权入账路径由事件驱动,不使用存款人的 manager、agent 或 RSM 签名。
EIP-712 域分隔符
所有三个域使用相同的结构但不同的名称:
{
"name": "<DomainName>",
"version": "1",
"chainId": <chainId>,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
链 ID:
- 测试网:
998 - 主网:
999
域 1:Agent 请求(HypercallAgentSign)
域名称:"HypercallAgentSign"
预计算域分隔符:
- 测试网:
0x8f0a44075cd4e0c79e5bd379a6fad5fa1329a4ea76d74e4edfa1138933d35e8a - 主网:使用链 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;
}
类型哈希:
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(...)")
编码:
- 使用
structHash(HLOrder)对每个HLOrder进行哈希 - 打包订单哈希:
keccak256(abi.encodePacked(orderHashes)) - 对请求进行哈希:
keccak256(abi.encode(HL_ORDER_REQUEST_TYPE_HASH, packedOrderHashes, nonce)) - 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
通过订单 ID 取消订单。
结构体:
struct HLCancel {
uint32 asset;
uint64 oid; // Order ID from HyperLiquid
}
struct HLRequestCancel {
HLCancel[] cancels;
uint64 nonce;
}
类型哈希:
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
通过客户端订单 ID 取消订单。
结构体:
struct HLCancelByCloid {
uint32 asset;
uint128 cloid; // Client order ID
}
struct HLRequestCancelByCloid {
HLCancelByCloid[] cancels;
uint64 nonce;
}
类型哈希:
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 操作(HypercallManagerSign)
域名称:"HypercallManagerSign"
预计算域分隔符:
- 测试网:
0xd1f76b6138be892c14b71b0569bdb049cb44f239d34c78ef1ffaacd2466f9f18 - 主网:待定
签名者:账户 Manager(创建该账户的 EOA)
Nonce:按 manager 的重放保护。与 Agent nonce 相同的有界集合模型:存储 100 个最高的 nonce,新 nonce 必须超过集合最小值且不能重复。在链上通过 Exchange.isNonceUsed(manager, nonce) 跟踪
HLActionSendAsset
通过 ActionCaster 将资产从 Account 发送到目标地址。
结构体:
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
}
类型哈希: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 提取代币到 Account。
结构体:
struct HCActionWithdrawToken {
address account;
uint64 nonce;
uint32 srcDex;
uint32 dstDex;
uint64 token;
uint64 amountWei;
}
类型哈希:keccak256("HCActionWithdrawToken(address account,uint64 nonce,uint32 srcDex,uint32 dstDex,uint64 token,uint64 amountWei)")
要求:
signer == managers[account]- 代币必须受支持(
_checkExchangeToken- 目前仅支持现货 USDC) - 账户必须已在 HyperCore 上激活(
ActionCasterUtils.checkAccountActivated)
行为:
- 由 Exchange 发起 ActionCaster 操作(而非 Account)
- 在 HyperCore 上将代币从 Exchange 转移到 Account
示例:
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
}
类型哈希:keccak256("HCActionWithdrawOption(address account,uint64 nonce,address recipient,address option,uint256 amountWei)")
要求:
signer == managers[account]option必须受支持(optionRegistry.isSupportedOption(option))
行为:
- 无 ActionCaster 操作(与其他提款不同)
- 通过
IOptionToken(option).mint(recipient, amountWei)向recipient铸造期权代币 - 发出
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 命令(HypercallRsmSign)
域名称:"HypercallRsmSign"
预计算域分隔符:
- 测试网:
0x650b282053fb61d3fd477bdc28f6434311fe905e27cc4ca643e87e802c45938c - 主网:待定
签名者:RSM 签名者(通过 Exchange.setRsmSigner 设置,链上验证)
Nonce:按 RSM 签名者的 nonce(由 Exchange.nextNonce[rsmSigner] 跟踪)
RSM 命令可由 SEQUENCER_ROLE 调用;做市商不会直接调用这些命令。
RsmCommandRebalance
在 HyperCore 上执行只减仓的 IOC 订单以再平衡持仓。
结构体:
struct RsmCommandRebalance {
address target; // Account to rebalance
uint64 nonce;
uint32 asset;
bool isBuy;
uint64 limitPx;
uint64 sz;
}
类型哈希: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;
}
类型哈希: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 钱包、manager、RSM 签名者)都有独立的 nonce 空间:
mapping(address signer => uint256 nonce) public nextNonce;
mapping(address signer => BitMaps.BitMap) private _nonces; // Tracks used nonces
规则:
- Nonce 必须严格递增(不要求连续,但会维护
nextNonce) - Nonce 一旦使用不能重用(通过
isNonceUsed(signer, nonce)检查) nextNonce[signer]是保证未使用的最小 nonce(如果跳过,更低的 nonce 也可能未被使用)
查询 Nonce 状态:
function isNonceUsed(address signer, uint256 nonce) external view returns (bool);
最佳实践:在链下跟踪 nonce 并以原子方式递增。将 nextNonce 用作合理性检查。
签名验证流程
- 链下:签名者创建 EIP-712 摘要并用私钥签名
- 链上:
Exchange接收已签名消息并调用Processor.process* - Processor:验证签名、恢复签名者、编码 ActionCaster 操作
- Exchange:检查 nonce、验证授权(manager/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)。请勿在新集成中使用。
安全注意事项
-
私钥存储:安全存储 API 钱包和 manager 密钥(manager 使用硬件钱包,API 钱包使用加密存储)。
-
Nonce 重放:切勿重用 nonce。在链下跟踪 nonce 并以原子方式递增。
-
域分隔符:始终使用正确的链 ID(测试网为 998,主网待定)。验证域分隔符与合约常量一致。
-
签名验证:合约在链上验证签名。对于关键操作,请勿信任链下签名验证。
-
Manager 与 API 钱包:Manager 控制账户所有权和提款。API 钱包仅签署交易请求。请使用独立的密钥。
参考资料
- Onboarding 了解账户创建和 API 钱包设置
- API Authentication 了解链下 API 认证