本页为简体中文译文。如有歧义,请以英文原文为准。 阅读英文版 →
跳转到主要内容

代理授权

为做市商提供的多密钥签名支持。

概述​

代理授权允许专用签名密钥(代理)代表交易钱包下单和撤单。适用场景包括:

  • 安全性:将交易钱包密钥保存在冷存储中,使用热密钥进行签名
  • 运营:多名团队成员可以使用不同的密钥签名
  • 自动化:自动化系统可以使用专用密钥签名

两种授权模型​

Hypercall 根据产品不同,采用两套独立的委托系统:

产品委托类型存储位置设置方式
期权(REST API)代理授权Hypercall 引擎状态POST /approve-agent
永续合约API 钱包链上(您的 Account 合约)hc_update_api_wallet 指令

这两套系统相互独立。为期权交易批准的代理不会获得永续合约的授权,反之亦然。

期权:代理授权(链下)​

代理代表交易钱包签署 PlaceOrder 和 CancelOrder EIP-712 消息。在订单进入引擎之前,API 会根据引擎的授权状态检查该代理。

永续合约:API 钱包(链上)​

API 钱包在 HypercallApiSign EIP-712 域中签署永续合约订单和撤单。Hypercall 在接受订单之前,会通过您的 Account 合约(isApiWalletActive())检查签名者。在 TEE 路由下(应用默认使用,API 客户端可选择启用),一个安全飞地持有您 Account 专用的独立 Hyperliquid 交易密钥,由其签署并发送原生订单;在智能合约路由下(应用中的高级设置),订单通过您的 Account 合约提交。签名参考请参阅 EIP-712 签名,流程说明请参阅永续合约。

如需添加或移除 API 钱包,请提交由账户管理钱包签名的 hc_update_api_wallet 指令。

该设计与 Hyperliquid 的 API 钱包模型一致。Hyperliquid 在 Nonces and API wallets 中将其称为 API 钱包(也称代理钱包),并在 Exchange endpoint 中说明了 approveAgent。

Nonce 重放保护​

Nonce 追踪遵循 Hyperliquid nonce 模型。所有签名操作(订单、代理批准/撤销、QP 握手)共享每个签名者的 nonce 空间。引擎为每个签名者地址存储最高的 100 个 nonce。新 nonce 在满足以下条件时被接受:

  1. !stored_set.contains(nonce):不是重复值
  2. 当集合已存满 100 条记录时,nonce > min(stored_set):大于已存储的最小 nonce
  3. nonce 位于服务器时间戳的 (T - 2 天, T + 1 天) 范围内

允许乱序 nonce(例如,只要都高于集合最小值,先用 nonce 105 再用 102 均可生效)。集合上限为 100 条,新记录加入时会淘汰最旧的记录。这可以防止单个过大的 nonce 导致账户无法使用。

Nonce 签名者是签署 EIP-712 消息的地址:订单为 API 钱包,代理授权为恢复出的签名者,握手为 QP 钱包。客户端应使用 Date.now()(毫秒级时间戳)作为 nonce 初始值,并单调递增。

期权代理授权​

直接签名​

如果 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 发帖可能会更长时间地缓存正向的代理授权结果,因此刚被撤销的代理在该缓存过期或尽力而为的失效通知到达相关边缘节点之前,可能仍然可以发帖。该缓存不会授权任何交易 API 变更操作。
  • 撤销后,代理将无法再为该钱包签名

撤销所有代理​

端点:DELETE /revoke-all-agents

请求:{ "nonce": 3, "signature": "0x..." },由钱包所有者对 RevokeAllAgents(uint64 nonce) 签名。移除恢复出的钱包下的所有代理。

查询已授权代理​

端点:GET /authorized-agents?wallet=...

查询参数:

  • wallet(必填)

响应:

{
"agents": [
"0x...",
"0x..."
]
}

说明:

  • 仅返回有效且未过期的代理

代理使用​

使用代理签署订单​

代理获得批准后:

  1. 使用代理钱包签署订单:使用代理钱包签署 PlaceOrder / CancelOrder 消息
  2. 设置 wallet 字段:将 wallet 字段设置为交易钱包地址(而非代理地址)
  3. 中间件验证:中间件验证该代理是否已获得该钱包的授权

示例:

// 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 中返回错误

授权检查​

中间件(单笔订单)​

端点:

  • POST /order
  • DELETE /order
  • DELETE /order_cloid

检查:signature_and_agent_middleware 验证:

  1. 签名恢复成功
  2. 签名者已获授权(signer == wallet 或代理已授权)

处理器(批量订单)​

端点:

  • POST /bulk_order
  • DELETE /bulk_order
  • DELETE /bulk_order_cloid

检查:在处理器中逐项验证:

  1. 逐项恢复签名
  2. 逐项检查代理授权

授权存储​

代理授权是引擎持有的状态。ApproveAgent 和 RevokeAgent 命令会记入日志,通过引擎重放恢复,并通过读取快照提供给 API 检查使用。

授权逻辑​

签名者授权采用明确的"或"逻辑:

  • 直接签名:signer == wallet。
  • 代理签名:引擎快照中包含一条 wallet_address == <wallet> 且 agent_address == <signer> 的授权记录,并且 expires_at 为空或晚于当前时间。

过期​

引擎支持为授权设置 expires_at,并将过期的代理视为未授权。POST /approve-agent 不会设置该字段,因此通过 API 批准的代理在被撤销前始终有效。

安全注意事项​

  1. 代理密钥安全:妥善保护代理私钥(使用硬件钱包或安全的密钥管理方案)
  2. 定期审计:定期通过 GET /authorized-agents 检查已授权的代理
  3. 撤销闲置代理:撤销不再需要的代理

最佳实践​

  1. 使用代理实现自动化:将交易钱包密钥保存在冷存储中,为自动化系统使用代理
  2. 限制代理范围:仅批准确实需要访问权限的代理
  3. 监控代理使用情况:追踪哪些代理在下单
  4. 及时撤销:代理不再需要时立即撤销

常见问题​

"Signer 0x... is not authorized to act for wallet 0x..."​

原因:代理未获批准,或授权已过期/已被撤销。

解决方法:通过 POST /approve-agent 批准代理,或直接使用钱包签名。

代理授权不生效​

原因:

  • 代理不在引擎持有的授权状态中
  • 授权已被撤销
  • 授权已过期

解决方法:通过 GET /authorized-agents?wallet=... 检查代理状态。