认证与签名
写操作的 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"或"rfq_only"(存在时要求字符串完全匹配)clientId:客户端提供的订单 ID(字符串,可以为空"")nonce:唯一 nonce(uint64),用于防止重放攻击
关键提示:price 和 size 在签名消息和 JSON 请求体中都必须是字符串。字符串格式必须完全一致。
Route 默认值:在 2026 年 7 月 4 日之前,route 为可选字段。当 JSON 中省略该字段时,类型化数据中也应省略。服务器将省略的 route 视为 best_execution。省略 route 时,POST /order 会返回弃用提示头。客户端应发送 route;该字段在 2026 年 7 月 4 日之前不会成为必填项,但之后可能成为必填项。
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
签名流程
第 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: 123
};
第 2 步:定义域
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
第 3 步:签署类型化数据
使用 ethers.js:
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 必须使用与签名时完全相同的字符串值:
{
"wallet": "0x1111111111111111111111111111111111111111",
"symbol": "BTC-20250131-100000-C",
"price": "100.0", // Same string as signed
"size": "0.1", // Same string as signed
"side": "Buy",
"tif": "gtc",
"route": "best_execution",
"client_id": "mm-1",
"nonce": 123,
"signature": "0x..."
}
Nonce 管理
要求:
- 每个钱包的 nonce 必须唯一
- Nonce 应当递增(防止重放攻击)
- 系统不会对 nonce 进行严格单调性校验(允许存在间隔)
最佳实践:
- 为每个钱包使用持久化计数器
- 每次成功签名后递增
- 妥善处理 nonce 间隔(例如,请求失败时可使用相同的 nonce 重试)
代理授权
如果使用代理钱包进行签名:
-
授权代理(一次性操作):
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
使用代理钱包签署订单:
- 使用代理钱包签署
PlaceOrder/CancelOrder消息 - 将
wallet字段设置为交易钱包地址 - 中间件会验证该代理是否已获该钱包授权
- 使用代理钱包签署
完整的代理授权详情请参见代理授权。
永续合约订单(兼容 Hyperliquid)
永续合约订单使用不同的 EIP-712 域和消息格式(兼容 Hyperliquid Core):
域:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
消息:包含 MessagePack 编码订单数据的 Agent 结构体。
具体字段请参见当前的签名编码指南。
常见错误
"Signature verification failed"
原因:
price或size以数字而非字符串形式发送- 签名和发送之间字符串格式发生了变化(例如
"100.0"与"100") - Nonce 错误
- 域错误(链 ID、名称、版本)
- 代理未获该钱包授权
"Unauthorized: signer not authorized for wallet"
原因:签名者既不是钱包本身,也不是授权代理。
解决方案:通过 POST /approve-agent 授权代理,或直接使用钱包签名。
实现示例
Python (eth_account)
from eth_account import Account
from eth_account.messages import encode_structured_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": 123
}
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_structured_data(structured_msg)
signed = Account.sign_message(encoded, private_key)
signature = signed.signature.hex()
JavaScript (ethers.js)
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: 123
};
const signature = await signer._signTypedData(domain, types, message);
测试签名
测试签名生成的示例流程:
- 使用您的实现生成签名
- 通过
POST /order发送测试订单 - 验证响应:
status="ACKED"或带原因的status="REJECTED" - 如果返回
"signature_verification_failed",请检查:price和size的字符串格式- 域参数(尤其是
chainId) - Nonce 是否正确
安全注意事项
- 切勿泄露私钥:使用硬件钱包或安全的密钥管理方案
- Nonce 管理:为每个钱包使用持久化、递增的 nonce
- 代理授权:通过
GET /authorized-agents定期审计已授权的代理 - 字符串编码:确保
price和size在签名和请求中均为字符串
参考资料
- EIP-712:https://eips.ethereum.org/EIPS/eip-712
- 实现:由签名恢复和中间件技术栈处理。