Trang này được dịch tự động. Bản gốc tiếng Anh là phiên bản chính thức. Đọc bằng tiếng Anh
Chuyển đến nội dung chính

Xác thực & Ký

Yêu cầu chữ ký EIP-712 cho các thao tác ghi.

Signing domain cho môi trường production

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:

  1. Là chính địa chỉ ví (ký trực tiếp), HOẶC
  2. 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 JSON
  • price: 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 JSON
  • tif: 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: pricesize 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ền
  • nonce: 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ồi
  • nonce: 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 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..."
}

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ý:

  1. Phê duyệt agent (một lần duy nhất):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1,
    "signature": "0x..." # Signed by wallet owner
    }
  2. Ký lệnh bằng ví agent:

    • Sử dụng ví agent để ký các message PlaceOrder / CancelOrder
    • Đặt trường wallet là địa chỉ ví giao dịch
    • Middleware xác minh agent đã được ủy quyền cho ví đó

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:

  1. price hoặc size được gửi dưới dạng số thay vì chuỗi
  2. Đị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")
  3. Nonce sai
  4. Domain sai (chain ID, name, version)
  5. 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ý:

  1. Tạo chữ ký bằng cách triển khai của bạn
  2. Gửi lệnh thử qua POST /order
  3. Xác minh phản hồi: status="ACKED" hoặc status="REJECTED" kèm lý do
  4. Nếu gặp "signature_verification_failed", hãy kiểm tra:
    • Định dạng chuỗi của pricesize
    • Các tham số domain (đặc biệt là chainId)
    • Tính đúng đắn của nonce

Cân nhắc về Bảo mật

  1. 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
  2. Quản lý nonce: Sử dụng nonce bền vững, tăng dần cho mỗi ví
  3. Ủy quyền agent: Thường xuyên kiểm tra các agent được ủy quyền qua GET /authorized-agents
  4. Mã hóa chuỗi: Đảm bảo pricesize là chuỗi trong cả lúc ký và trong request

Tài liệu tham khảo