身份验证与签名
写操作的 EIP-712 签名要求。
生产环境请使用链 ID 999。在 Hypercall 获取更多测试网 HYPE 之前,测试网暂时停用。
概述
所有写操作都需要 EIP-712 类型化数据签名。签名者必须满足以下条件之一:
- 是钱包地址本身(直接签名),或
- 是该钱包的已授权代理(参见代理授权)
EIP-712 域
域分隔符:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
链 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:钱包地址(以 0x 开头的十六进制字符串)symbol:期权代码(例如"BTC-20250131-100000-C")side:"Buy"或"Sell"(必须精确匹配字符串)size:以字符串表示的数量(例如"0.1"),必须与 JSON 中发送的值完全一致price:以字符串表示的价格(例如"100.0"),必须与 JSON 中发送的值完全一致tif:订单有效期类型:"gtc"、"ioc"或"fok"(必须精确匹配字符串)route:可选的订单路由:"best_execution"或"book_only"(提供时必须精确匹配字符串)。POST /bulk_order仅接受"book_only"。"rfq_only"属于签名词汇表的一部分,但目前所有下单端点都会拒绝该值;如需 RFQ 执行,请使用 RFQ 端点。clientId:客户端提供的订单 ID(字符串,可为空"")nonce:毫秒时间戳 Nonce(uint64),每个签名者唯一。参见 Nonce 管理
关键:price 和 size 在签名消息和 JSON 请求体中都必须是字符串。字符串格式必须完全一致。
route 默认值:请发送 route。未包含该字段的订单仍会被接受并按 best_execution 路由,但 POST /order 会在响应中返回 Deprecation、Sunset 和 Warning 头,且已公布的停用日期(2026 年 7 月 4 日)已过。省略 route 的形式可能会在没有额外宽限期的情况下被移除。如果您确实在 JSON 中省略了 route,也请在类型化数据中省略它。
PlaceOrderReduceOnly
只减仓订单(JSON 请求体中 "reduce_only": true)使用单独的结构体签名,且必须显式指定 route。
struct PlaceOrderReduceOnly {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
bool reduceOnly;
uint64 nonce;
}
reduceOnly 必须为 true 并与请求体一致。未对 route 签名的只减仓订单将以 missing_field 被拒绝。
ReplaceOrder
PUT /order 以及 PUT /bulk_order 中的每个条目都对 ReplaceOrder 签名。该结构体没有 route 字段;JSON 请求体中的 route 键会被忽略。
struct ReplaceOrder {
address wallet;
string orderId;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
orderId 是被替换订单的十进制字符串。只减仓的替换订单("reduce_only": true)对 ReplaceOrderReduceOnly 签名,它在 clientId 和 nonce 之间增加了 bool reduceOnly。TypeScript SDK 导出了这两种类型映射。
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
RevokeAllAgents
DELETE /revoke-all-agents 会移除签名钱包的所有代理。
struct RevokeAllAgents {
uint64 nonce;
}
风险模拟
POST /risk/simulate/portfolio 和 POST /risk/simulate/orders 是公开的只读引擎权威请求。它们不需要签名、身份验证或 Nonce。wallet 字段用于选择要评估的账户快照。
投资组合请求示例:
curl -X POST https://api.hypercall.xyz/risk/simulate/portfolio \
-H 'Content-Type: application/json' \
-d '{
"wallet": "0x1111111111111111111111111111111111111111",
"add_positions": true,
"positions": [
{"symbol": "BTC-20261231-100000-C", "quantity": "1.25"}
]
}'
原子订单请求示例:
curl -X POST https://api.hypercall.xyz/risk/simulate/orders \
-H 'Content-Type: application/json' \
-d '{
"wallet": "0x1111111111111111111111111111111111111111",
"execution_mode": "atomic",
"hypothetical_open_orders": [
{"symbol": "BTC-PERP", "side": "Buy", "size": "0.1", "price": "100000", "reduce_only": false}
],
"orders": [
{"symbol": "BTC-20261231-100000-C", "side": "Buy", "size": "1", "price": "4200", "reduce_only": false},
{"symbol": "BTC-20261231-120000-C", "side": "Sell", "size": "1", "price": "1800", "reduce_only": false}
]
}'
hypothetical_open_orders 为可选字段。其中的条目被视为挂单中的 GTC 订单,并在 orders 之前按数组顺序依次准入。仅针对本次请求,这些条目会与账户权威的实时挂单和有效预留一并评估。引擎状态不会被修改。模拟成功时,simulated 和 delta 包含完整的假设订单序列,以及拟议的挂单或原子组合。两个订单数组合计最多可包含 50 个条目。
模拟响应只是快照,并非执行保证。路由、RPI 可用性、撮合、Nonce 准入以及未来的交易暂停状态均不在模拟范围内。引擎状态推进后,之后提交的真实订单结果可能有所不同。
签名流程
第 1 步:构造消息
显式指定 route 的 PlaceOrder 示例:
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: Date.now() // current ms epoch; send this exact value in the body
};
第 2 步:定义域
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
第 3 步:对类型化数据签名
使用 ethers.js v6(在 v5 中该方法为 _signTypedData):
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 必须使用完全相同的字符串值:
const body = {
wallet: message.wallet,
symbol: message.symbol,
price: message.price, // same string as signed
size: message.size, // same string as signed
side: message.side,
tif: message.tif,
route: message.route,
client_id: message.clientId,
nonce: message.nonce, // same value as signed
signature
};
Nonce 管理
要求:
- Nonce 必须严格落在服务器毫秒时间之前两天到之后一天的范围内,因此请使用当前的 Unix 毫秒时间。
1或123之类的小计数器会被拒绝。 - Nonce 按签名密钥跟踪。对于订单和撤单,由代理签名时按代理跟踪;对于代理授权和撤销,按所有者钱包跟踪。
- Nonce 不可重复使用。订单 Nonce 在
POST /order、批量订单和 WebSocketPlaceOrder之间共享;在一种传输方式上使用过的 Nonce 不能在另一种传输方式上再次使用。 - 允许存在间隔。引擎为每个签名者保留最近的 100 个 Nonce,一旦保留满 100 个,新 Nonce 必须大于其中最小的那个。
最佳实践:
- 以毫秒级纪元时间为种子,并按签名者持久化最后一个 Nonce,返回
max(now_ms, last + 1) - 所有使用同一密钥签名的进程共享同一个分配器
- 如果请求失败,请使用新的 Nonce 重新签名
代理授权
如果使用代理钱包签名:
-
授权代理(一次性操作):
POST /approve-agent{"agent": "0x...","nonce": 1791273600000,"signature": "0x..." # Signed by wallet owner} -
使用代理钱包为订单签名:
- 使用代理钱包对
PlaceOrder/CancelOrder消息签名 - 将
wallet字段设置为交易钱包地址 - 中间件会验证该代理是否已获得该钱包的授权
- 使用代理钱包对
完整的代理授权说明请参见代理授权。
永续合约订单
永续合约订单不使用上述 Hypercall 域。它们由在您的 Hypercall 账户上注册的 API 钱包,以 HypercallApiSign 域(链 ID 999,零地址验证合约)中的 HLOrder 进行签名。结构体和单位请参见 EIP-712 签名。
通过 API 使用时,TEE 路由需主动选择:调用 TEE 端点即表示选择该路由。在 Hypercall 应用中它是默认路由。使用时,请将已签名订单连同 account、nonce、action 和 signature 提交到 POST /v1/tee-perp/orders/submit。Hypercall 执行保证金检查后,由安全飞地为您的账户签署并发送原生 Hyperliquid 订单。每笔永续合约订单都附带 Hypercall 建设者代码(builder code),费率为 0%,因此账户管理者必须在首笔订单之前批准该建设者一次(即应用中的启用交易步骤)。未批准时,订单将以 builder_not_approved 被拒绝。流程请参见永续合约,请求和响应结构请参见 API 参考。
Hypercall API 不接受 Hyperliquid 自身的 Exchange 域(链 ID 1337,MessagePack Agent 载荷)。
常见错误
unauthorized(HTTP 401):"Signer 0x... is not authorized to act for wallet 0x..."
EIP-712 恢复总会得出某个地址。如果您签名的任何内容与发送的内容不一致,恢复出的地址就不是您的密钥,请求会在此处失败,而不是返回签名错误。
原因:
price或size的字符串格式在签名和发送之间发生了变化(例如"100.0"与"100")- 某个已签名字段与 JSON 请求体不一致(
route、clientId/client_id、nonce、tif),或者对route签了名但请求体中省略了它,反之亦然 - 域错误(链 ID、名称、版本、验证合约)
- 请求使用了错误的结构体(例如在
PUT /order上使用了PlaceOrder而不是ReplaceOrder) - 签名者确实既不是该钱包,也不是已授权的代理。请通过
POST /approve-agent授权代理,或直接使用钱包签名
当错误消息中出现您不认识的签名者地址时,属于原因 1 至 4。
signature_verification_failed(HTTP 400)
原因:签名完全无法解码或恢复,例如长度错误或十六进制无效。
invalid_parameter(HTTP 400):"price must be provided as a string for signature verification"
原因:price 或 size 以 JSON 数字形式发送。请将两者都以字符串形式发送。
实现示例
Python(eth_account 0.10 或更高版本)
import time
from eth_account import Account
from eth_account.messages import encode_typed_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": int(time.time() * 1000) # current ms epoch; send this exact value in the body
}
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_typed_data(full_message=structured_msg)
signed = Account.sign_message(encoded, private_key)
signature = signed.signature.hex()
JavaScript(ethers.js v6)
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: Date.now() // current ms epoch; send this exact value in the body
};
const signature = await signer.signTypedData(domain, types, message);
测试签名
测试签名生成的示例步骤:
- 使用您的实现生成签名
- 通过
POST /order发送测试订单 - 验证响应:HTTP 200 且
status为"ACKED",或为附带reason的"REJECTED" - 如果请求失败并返回 HTTP 401
unauthorized且带有陌生的签名者地址,或返回 HTTP 400signature_verification_failed,请检查:price和size的字符串格式- 域参数(尤其是
chainId) - 每个已签名字段是否都与请求体一致,包括
route和nonce
安全注意事项
- 切勿泄露私钥:使用硬件钱包或安全的密钥管理方案
- Nonce 管理:为每个签名密钥使用持久化的毫秒时间戳 Nonce
- 代理授权:定期通过
GET /authorized-agents审计已授权的代理 - 字符串编码:确保
price和size在签名和请求中均为字符串
参考资料
- EIP-712:https://eips.ethereum.org/EIPS/eip-712
- 实现:由签名恢复和中间件栈处理。