Xác thực & Ký
Yêu cầu chữ ký EIP-712 cho các thao tác ghi.
Sử dụng chain ID 999 cho production. Testnet tạm thời bị vô hiệu hóa cho đến khi Hypercall có thêm HYPE testnet.
Tổng quan
Tất cả các thao tác ghi đều yêu cầu chữ ký dữ liệu có kiểu EIP-712. Người ký phải:
- Là chính địa chỉ ví (ký trực tiếp), HOẶC
- Là agent được ủy quyền cho ví đó (xem Agent auth)
EIP-712 Domain
Domain separator:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID: 999 (production)
Các loại Message
PlaceOrder
Struct với route được chỉ định rõ:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Struct khi route bị bỏ qua:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Yêu cầu về trường dữ liệu:
wallet: Địa chỉ ví (chuỗi hex có tiền tố 0x)symbol: Symbol của quyền chọn (ví dụ:"BTC-20250131-100000-C")side:"Buy"hoặc"Sell"(yêu cầu khớp chuỗi chính xác)size: Kích thước dưới dạng chuỗi (ví dụ:"0.1") - PHẢI khớp chính xác với giá trị được gửi trong JSONprice: Giá dưới dạng chuỗi (ví dụ:"100.0") - PHẢI khớp chính xác với giá trị được gửi trong JSONtif: Time-in-force:"gtc","ioc", hoặc"fok"(yêu cầu khớp chuỗi chính xác)route: Route lệnh tùy chọn:"best_execution","book_only", hoặc"rfq_only"(yêu cầu khớp chuỗi chính xác khi có mặt)clientId: ID lệnh do client cung cấp (chuỗi, có thể để trống"")nonce: Nonce duy nhất (uint64) để chống replay attack
Quan trọng: price và size PHẢI là chuỗi trong cả message được ký lẫn body JSON của request. Định dạng chuỗi phải khớp chính xác.
Route mặc định: route là tùy chọn cho đến ít nhất ngày 4 tháng 7 năm 2026. Khi bỏ qua trong JSON, hãy bỏ qua nó trong typed data luôn. Server xử lý route bị bỏ qua như best_execution. POST /order trả về các header deprecation khi route bị bỏ qua. Client nên gửi route; nó sẽ không trở thành bắt buộc trước ngày 4 tháng 7 năm 2026, nhưng có thể trở thành bắt buộc sau đó.
CancelOrder
Struct:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Yêu cầu về trường dữ liệu:
wallet: Địa chỉ víorderId: ID lệnh dưới dạng chuỗi (ví dụ:"123")nonce: Nonce duy nhất
CancelOrderByClientId
Struct:
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
Yêu cầu về trường dữ liệu:
wallet: Địa chỉ víclientId: ID lệnh của client (chuỗi)nonce: Nonce duy nhất
ApproveAgent
Struct:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Yêu cầu về trường dữ liệu:
agent: Địa chỉ ví agent cần ủy quyềnnonce: Nonce duy nhất
Lưu ý: Ví thực hiện ủy quyền cho agent được suy ra từ chữ ký được khôi phục.
RevokeAgent
Struct:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Yêu cầu về trường dữ liệu:
agent: Địa chỉ ví agent cần thu hồinonce: Nonce duy nhất
Quy trình Ký
Bước 1: Tạo Message
Ví dụ cho PlaceOrder với route được chỉ định rõ:
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
};
Bước 2: Định nghĩa Domain
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Bước 3: Ký Typed Data
Sử dụng 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);
Bước 4: Gửi Request
Quan trọng: Body JSON của request phải sử dụng chính xác cùng các giá trị chuỗi cho price và 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..."
}
Quản lý Nonce
Yêu cầu:
- Nonce PHẢI là duy nhất cho mỗi ví
- Nonce NÊN tăng dần (ngăn chặn replay attack)
- Nonce không được kiểm tra tính đơn điệu nghiêm ngặt (cho phép có khoảng trống)
Thực hành tốt nhất:
- Sử dụng bộ đếm bền vững (persistent counter) cho mỗi ví
- Tăng giá trị sau mỗi lần ký thành công
- Xử lý khoảng trống nonce một cách linh hoạt (ví dụ: nếu request thất bại, thử lại với cùng nonce)
Ủy quyền Agent
Nếu sử dụng ví agent để ký:
-
Phê duyệt agent (một lần duy nhất):
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
Ký lệnh bằng ví agent:
- Sử dụng ví agent để ký các message
PlaceOrder/CancelOrder - Đặt trường
walletlà địa chỉ ví giao dịch - Middleware xác minh agent đã được ủy quyền cho ví đó
- Sử dụng ví agent để ký các message
Xem Agent auth để biết chi tiết đầy đủ về ủy quyền agent.
Lệnh Perp (Tương thích Hyperliquid)
Lệnh perp sử dụng EIP-712 domain và định dạng message khác (tương thích với Hyperliquid Core):
Domain:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Message: Struct Agent với dữ liệu lệnh được mã hóa bằng MessagePack.
Xem hướng dẫn mã hóa chữ ký hiện tại để biết các trường chính xác.
Các lỗi thường gặp
"Signature verification failed"
Nguyên nhân:
pricehoặcsizeđược gửi dưới dạng số thay vì chuỗi- Định dạng chuỗi thay đổi giữa lúc ký và lúc gửi (ví dụ:
"100.0"so với"100") - Nonce sai
- Domain sai (chain ID, name, version)
- Agent chưa được ủy quyền cho ví
"Unauthorized: signer not authorized for wallet"
Nguyên nhân: Người ký không phải là chính ví đó và cũng không phải là agent được ủy quyền.
Giải pháp: Phê duyệt agent qua POST /approve-agent hoặc ký trực tiếp bằng ví.
Ví dụ Triển khai
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);
Kiểm tra Chữ ký
Ví dụ về kiểm tra việc tạo chữ ký:
- Tạo chữ ký bằng cách triển khai của bạn
- Gửi lệnh thử qua
POST /order - Xác minh phản hồi:
status="ACKED"hoặcstatus="REJECTED"kèm lý do - Nếu gặp
"signature_verification_failed", hãy kiểm tra:- Định dạng chuỗi của
pricevàsize - Các tham số domain (đặc biệt là
chainId) - Tính đúng đắn của nonce
- Định dạng chuỗi của
Cân nhắc về Bảo mật
- Không bao giờ để lộ khóa riêng tư: Sử dụng ví phần cứng hoặc quản lý khóa an toàn
- Quản lý nonce: Sử dụng nonce bền vững, tăng dần cho mỗi ví
- Ủy quyền agent: Thường xuyên kiểm tra các agent được ủy quyền qua
GET /authorized-agents - Mã hóa chuỗi: Đảm bảo
pricevàsizelà chuỗi trong cả lúc ký và trong request
Tài liệu tham khảo
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Triển khai: được xử lý bởi stack khôi phục chữ ký và middleware.