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

身份验证与签名

写操作的 EIP-712 签名要求。

生产环境签名域

生产环境请使用链 ID 999。在 Hypercall 获取更多测试网 HYPE 之前,测试网暂时停用。

概述​

所有写操作都需要 EIP-712 类型化数据签名。签名者必须满足以下条件之一:

  1. 是钱包地址本身(直接签名),或
  2. 是该钱包的已授权代理(参见代理授权)

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、批量订单和 WebSocket PlaceOrder 之间共享;在一种传输方式上使用过的 Nonce 不能在另一种传输方式上再次使用。
  • 允许存在间隔。引擎为每个签名者保留最近的 100 个 Nonce,一旦保留满 100 个,新 Nonce 必须大于其中最小的那个。

最佳实践:

  • 以毫秒级纪元时间为种子,并按签名者持久化最后一个 Nonce,返回 max(now_ms, last + 1)
  • 所有使用同一密钥签名的进程共享同一个分配器
  • 如果请求失败,请使用新的 Nonce 重新签名

代理授权​

如果使用代理钱包签名:

  1. 授权代理(一次性操作):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1791273600000,
    "signature": "0x..." # Signed by wallet owner
    }
  2. 使用代理钱包为订单签名:

    • 使用代理钱包对 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 恢复总会得出某个地址。如果您签名的任何内容与发送的内容不一致,恢复出的地址就不是您的密钥,请求会在此处失败,而不是返回签名错误。

原因:

  1. price 或 size 的字符串格式在签名和发送之间发生了变化(例如 "100.0" 与 "100")
  2. 某个已签名字段与 JSON 请求体不一致(route、clientId/client_id、nonce、tif),或者对 route 签了名但请求体中省略了它,反之亦然
  3. 域错误(链 ID、名称、版本、验证合约)
  4. 请求使用了错误的结构体(例如在 PUT /order 上使用了 PlaceOrder 而不是 ReplaceOrder)
  5. 签名者确实既不是该钱包,也不是已授权的代理。请通过 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);

测试签名​

测试签名生成的示例步骤:

  1. 使用您的实现生成签名
  2. 通过 POST /order 发送测试订单
  3. 验证响应:HTTP 200 且 status 为 "ACKED",或为附带 reason 的 "REJECTED"
  4. 如果请求失败并返回 HTTP 401 unauthorized 且带有陌生的签名者地址,或返回 HTTP 400 signature_verification_failed,请检查:
    • price 和 size 的字符串格式
    • 域参数(尤其是 chainId)
    • 每个已签名字段是否都与请求体一致,包括 route 和 nonce

安全注意事项​

  1. 切勿泄露私钥:使用硬件钱包或安全的密钥管理方案
  2. Nonce 管理:为每个签名密钥使用持久化的毫秒时间戳 Nonce
  3. 代理授权:定期通过 GET /authorized-agents 审计已授权的代理
  4. 字符串编码:确保 price 和 size 在签名和请求中均为字符串

参考资料​