Xác thực & Ký
Các yêu cầu về chữ ký EIP-712 cho các thao tác ghi.
Hãy 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 trên testnet.
Tổng quan
Mọi thao tác ghi đều yêu cầu chữ ký dữ liệu có kiểu EIP-712. Người ký phải:
- Chính là địa chỉ ví (ký trực tiếp), HOẶC
- Là agent được ủy quyền cho ví đó (xem Xác thực agent)
Domain EIP-712
Domain separator:
{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
Chain ID: 999 (production)
Các loại thông điệp
PlaceOrder
Struct có route tường minh:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}
Struct khi bỏ qua route:
struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
Yêu cầu đối với các trường:
wallet: Địa chỉ ví (chuỗi hex có tiền tố 0x)symbol: Ký hiệu quyền chọn (ví dụ:"BTC-20250131-100000-C")side:"Buy"hoặc"Sell"(phải khớp chính xác chuỗi)size: Khối lượng dưới dạng chuỗi (ví dụ:"0.1"), PHẢI khớp chính xác với giá trị 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ị gửi trong JSONtif: Time-in-force:"gtc","ioc"hoặc"fok"(phải khớp chính xác chuỗi)route: Route của lệnh, không bắt buộc:"best_execution"hoặc"book_only"(phải khớp chính xác chuỗi khi có mặt).POST /bulk_orderchỉ chấp nhận"book_only"."rfq_only"thuộc bộ giá trị có thể ký nhưng hiện mọi endpoint đặt lệnh đều từ chối giá trị này; hãy dùng các endpoint RFQ để khớp lệnh qua RFQ.clientId: ID lệnh do client cung cấp (chuỗi, có thể để trống"")nonce: Nonce dạng timestamp tính bằng mili giây (uint64), duy nhất theo từng người ký. Xem Quản lý nonce
Quan trọng: price và size PHẢI là chuỗi ở cả thông điệp được ký lẫn body của request JSON. Định dạng chuỗi phải khớp chính xác.
Route mặc định: hãy gửi route. Lệnh không có trường này vẫn được chấp nhận và được định tuyến theo best_execution, nhưng POST /order sẽ trả về các header Deprecation, Sunset và Warning, và ngày ngừng hỗ trợ đã công bố (4 tháng 7 năm 2026) đã qua. Dạng lệnh bỏ qua route có thể bị loại bỏ mà không có thêm thời gian ân hạn. Khi bạn bỏ route khỏi JSON, hãy bỏ nó khỏi dữ liệu có kiểu (typed data) luôn.
PlaceOrderReduceOnly
Lệnh reduce-only ("reduce_only": true trong body JSON) ký một struct riêng. Lệnh này bắt buộc phải có route tường minh.
struct PlaceOrderReduceOnly {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
bool reduceOnly;
uint64 nonce;
}
reduceOnly phải là true và khớp với body của request. Lệnh reduce-only không có route được ký sẽ bị từ chối với lỗi missing_field.
ReplaceOrder
PUT /order và từng phần tử của PUT /bulk_order ký một ReplaceOrder. Struct này không có trường route; khóa route trong body JSON sẽ bị bỏ qua.
struct ReplaceOrder {
address wallet;
string orderId;
string symbol;
string side;
string size;
string price;
string tif;
string clientId;
uint64 nonce;
}
orderId là chuỗi thập phân của lệnh đang được thay thế. Lệnh thay thế reduce-only ("reduce_only": true) ký ReplaceOrderReduceOnly, struct này thêm bool reduceOnly vào giữa clientId và nonce. TypeScript SDK xuất cả hai type map.
CancelOrder
Struct:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
Yêu cầu đối với các trường:
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 đối với các trường:
wallet: Địa chỉ víclientId: ID lệnh phía client (chuỗi)nonce: Nonce duy nhất
ApproveAgent
Struct:
struct ApproveAgent {
address agent;
uint64 nonce;
}
Yêu cầu đối với các trường:
agent: Địa chỉ ví agent cần ủy quyềnnonce: Nonce duy nhất
Lưu ý: Ví ủy quyền cho agent được suy ra từ chữ ký đã khôi phục.
RevokeAgent
Struct:
struct RevokeAgent {
address agent;
uint64 nonce;
}
Yêu cầu đối với các trường:
agent: Địa chỉ ví agent cần thu hồinonce: Nonce duy nhất
RevokeAllAgents
DELETE /revoke-all-agents xóa toàn bộ agent của ví ký.
struct RevokeAllAgents {
uint64 nonce;
}
Mô phỏng rủi ro
POST /risk/simulate/portfolio và POST /risk/simulate/orders là các request
công khai, chỉ đọc, do engine làm nguồn thẩm quyền. Chúng không yêu cầu chữ ký,
xác thực hay nonce. Trường wallet chọn snapshot tài khoản cần đánh giá.
Ví dụ request danh mục:
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"}
]
}'
Ví dụ request lệnh atomic:
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 là tùy chọn. Các phần tử của nó được xem như các lệnh GTC
đang chờ và được chấp nhận tuần tự theo thứ tự trong mảng, trước orders. Chỉ riêng
trong request này, chúng được đánh giá cùng với các lệnh đang mở thực tế và các khoản
giữ chỗ đang hoạt động của tài khoản theo dữ liệu có thẩm quyền. Trạng thái engine không
bị thay đổi. Khi mô phỏng thành công, simulated và delta bao gồm toàn bộ chuỗi lệnh
giả định cộng với lệnh chờ hoặc gói atomic được đề xuất. Hai mảng lệnh cộng lại
có tối đa 50 phần tử.
Phản hồi mô phỏng là snapshot, không phải cam kết khớp lệnh. Việc định tuyến, tính khả dụng của RPI, khớp lệnh, chấp nhận nonce và trạng thái tạm dừng giao dịch trong tương lai không được mô phỏng. Một lệnh thật đặt sau đó có thể cho kết quả khác khi trạng thái engine đã thay đổi.
Quy trình ký
Bước 1: Tạo thông điệp
Ví dụ cho PlaceOrder có route tường minh:
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
};
Bước 2: Định nghĩa domain
const domain = {
name: "Hypercall",
version: "1",
chainId: 999,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
Bước 3: Ký dữ liệu có kiểu
Sử dụng ethers.js v6 (ở v5, phương thức là _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);
Bước 4: Gửi request
Quan trọng: Body của request JSON phải dùng đúng các giá trị chuỗi giống hệt cho price và 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
};
Quản lý nonce
Yêu cầu:
- Nonce phải nằm nghiêm ngặt trong khoảng từ hai ngày trước đến một ngày sau thời gian máy chủ tính bằng mili giây, vì vậy hãy dùng thời gian Unix hiện tại tính bằng mili giây. Các bộ đếm nhỏ như
1hoặc123sẽ bị từ chối. - Nonce được theo dõi theo từng khóa ký. Với lệnh đặt và lệnh hủy, đó là agent khi agent ký; với việc phê duyệt và thu hồi agent, đó là ví chủ sở hữu.
- Không thể dùng lại nonce. Nonce của lệnh được dùng chung giữa
POST /order, lệnh hàng loạt vàPlaceOrderqua WebSocket; nonce đã dùng trên một kênh truyền không thể dùng lại trên kênh khác. - Cho phép có khoảng trống. Engine lưu 100 nonce gần nhất cho mỗi người ký, và khi đã lưu đủ 100, nonce mới phải lớn hơn nonce nhỏ nhất đang được lưu.
Thực hành tốt nhất:
- Khởi tạo từ thời gian epoch tính bằng mili giây và lưu bền nonce cuối cùng cho mỗi người ký, trả về
max(now_ms, last + 1) - Dùng chung một bộ cấp phát cho mọi tiến trình ký bằng cùng một khóa
- Nếu request thất bại, hãy ký lại với nonce mới
Ủy quyền agent
Nếu dùng ví agent để ký:
-
Phê duyệt agent (một lần):
POST /approve-agent{"agent": "0x...","nonce": 1791273600000,"signature": "0x..." # Signed by wallet owner} -
Ký lệnh bằng ví agent:
- Dùng ví agent để ký các thông điệp
PlaceOrder/CancelOrder - Đặt trường
walletlà địa chỉ ví giao dịch - Middleware xác minh agent đã được ủy quyền cho ví đó
- Dùng ví agent để ký các thông điệp
Xem Xác thực agent để biết đầy đủ chi tiết về ủy quyền agent.
Lệnh perp
Lệnh perp không dùng domain Hypercall ở trên. Chúng được ký dưới dạng HLOrder trong domain HypercallApiSign (chain ID 999, verifying contract bằng 0) bởi một API wallet đã đăng ký trên Hypercall Account của bạn. Xem Ký EIP-712 để biết struct và đơn vị.
Route TEE là tùy chọn khi dùng API: bạn chọn nó bằng cách gọi các endpoint TEE. Trong ứng dụng Hypercall, đây là route mặc định. Để sử dụng, hãy gửi lệnh đã ký tới POST /v1/tee-perp/orders/submit với account, nonce, action và signature. Hypercall thực hiện kiểm tra ký quỹ, sau đó một secure enclave ký và gửi lệnh Hyperliquid gốc cho Account của bạn. Mọi lệnh perp đều mang builder code của Hypercall với mức phí 0%, vì vậy người quản lý Account phải phê duyệt builder đó một lần trước lệnh đầu tiên (bước Enable trading trong ứng dụng). Nếu chưa phê duyệt, lệnh sẽ bị từ chối với lỗi builder_not_approved. Xem Perps để biết luồng xử lý và API Reference để biết schema của request và response.
Domain Exchange riêng của Hyperliquid (chain ID 1337, payload Agent dạng MessagePack) không được Hypercall API chấp nhận.
Các lỗi thường gặp
unauthorized (HTTP 401): "Signer 0x... is not authorized to act for wallet 0x..."
Việc khôi phục EIP-712 luôn trả về một địa chỉ nào đó. Nếu bất kỳ dữ liệu nào bạn đã ký khác với dữ liệu bạn gửi, địa chỉ khôi phục được sẽ không phải khóa của bạn, và request thất bại tại đây thay vì báo lỗi chữ ký.
Nguyên nhân:
- Định dạng chuỗi
pricehoặcsizethay đổi giữa lúc ký và lúc gửi (ví dụ:"100.0"so với"100") - Một trường được ký khác với body JSON (
route,clientId/client_id,nonce,tif), hoặcrouteđược ký nhưng bị bỏ khỏi body, hoặc ngược lại - Sai domain (chain ID, name, version, verifying contract)
- Sai struct cho request (ví dụ dùng
PlaceOrderthay vìReplaceOrderchoPUT /order) - Người ký thực sự không phải ví cũng không phải agent đã được phê duyệt. Hãy phê duyệt agent qua
POST /approve-agenthoặc ký trực tiếp bằng ví
Khi thông báo lỗi nêu một địa chỉ người ký mà bạn không nhận ra, nguyên nhân thuộc các mục 1 đến 4.
signature_verification_failed (HTTP 400)
Nguyên nhân: Hoàn toàn không thể giải mã hoặc khôi phục chữ ký, ví dụ sai độ dài hoặc hex không hợp lệ.
invalid_parameter (HTTP 400): "price must be provided as a string for signature verification"
Nguyên nhân: price hoặc size được gửi dưới dạng số JSON. Hãy gửi cả hai dưới dạng chuỗi.
Ví dụ triển khai
Python (eth_account 0.10 trở lên)
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);
Kiểm thử chữ ký
Ví dụ kiểm thử việc tạo chữ ký:
- Tạo chữ ký bằng phần triển khai của bạn
- Gửi lệnh thử qua
POST /order - Kiểm tra phản hồi: HTTP 200 với
statuslà"ACKED", hoặc"REJECTED"kèmreason - Nếu request thất bại với HTTP 401
unauthorizedvà một địa chỉ người ký lạ, hoặc HTTP 400signature_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) - Mọi trường được ký đều khớp với body, bao gồm
routevànonce
- Định dạng chuỗi của
Lưu ý bảo mật
- Không bao giờ để lộ khóa riêng tư: Dùng ví cứng hoặc giải pháp quản lý khóa an toàn
- Quản lý nonce: Dùng nonce dạng timestamp mili giây, được lưu bền, theo từng khóa ký
- Ủy quyền agent: Thường xuyên rà soát các agent đã được ủy quyền qua
GET /authorized-agents - Mã hóa chuỗi: Đảm bảo
pricevàsizelà chuỗi ở cả bước ký lẫn request
Tài liệu tham khảo
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- Triển khai: do lớp khôi phục chữ ký và middleware đảm nhiệm.