本页由机器翻译。英文原文为权威版本。 阅读英文版
跳转到主要内容

认证与签名

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

生产环境签名域

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

概述

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

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

EIP-712 域

域分隔符

{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

链 ID999(生产环境)

消息类型

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),用于防止重放攻击

关键提示pricesize 在签名消息和 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 请求体中的 pricesize 必须使用与签名时完全相同的字符串值

{
"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 重试)

代理授权

如果使用代理钱包进行签名:

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

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

    • 使用代理钱包签署 PlaceOrder / CancelOrder 消息
    • wallet 字段设置为交易钱包地址
    • 中间件会验证该代理是否已获该钱包授权

完整的代理授权详情请参见代理授权

永续合约订单(兼容 Hyperliquid)

永续合约订单使用不同的 EIP-712 域和消息格式(兼容 Hyperliquid Core):

{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

消息:包含 MessagePack 编码订单数据的 Agent 结构体。

具体字段请参见当前的签名编码指南。

常见错误

"Signature verification failed"

原因

  1. pricesize 以数字而非字符串形式发送
  2. 签名和发送之间字符串格式发生了变化(例如 "100.0""100"
  3. Nonce 错误
  4. 域错误(链 ID、名称、版本)
  5. 代理未获该钱包授权

"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);

测试签名

测试签名生成的示例流程:

  1. 使用您的实现生成签名
  2. 通过 POST /order 发送测试订单
  3. 验证响应:status="ACKED" 或带原因的 status="REJECTED"
  4. 如果返回 "signature_verification_failed",请检查:
    • pricesize 的字符串格式
    • 域参数(尤其是 chainId
    • Nonce 是否正确

安全注意事项

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

参考资料