認証と署名
書き込み操作におけるEIP-712署名の要件です。
本番環境ではチェーンID 999 を使用してください。テストネットは、Hypercallがより多くのテストネットHYPEを取得するまで一時的に無効化されています。
概要
すべての書き込み操作にはEIP-712型付きデータ署名が必要です。署名者は以下のいずれかである必要があります。
- ウォレットアドレス自身(直接署名)、または
- ウォレットの認可済みエージェント(エージェント認可を参照)
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プレフィックス付きの16進文字列)symbol: オプションシンボル(例:"BTC-20250131-100000-C")side:"Buy"または"Sell"(完全一致が必要)size: 文字列としてのサイズ(例:"0.1")- JSONで送信する値と完全に一致する必要がありますprice: 文字列としての価格(例:"100.0")- JSONで送信する値と完全に一致する必要がありますtif: 執行条件(Time-in-force):"gtc"、"ioc"、"fok"のいずれか(完全一致が必要)route: オプショナルな注文ルート:"best_execution"、"book_only"、"rfq_only"のいずれか(指定する場合は完全一致が必要)clientId: クライアント側で指定する注文ID(文字列、空文字""も可)nonce: リプレイ攻撃防止のための一意のノンス(uint64)
重要: price と size は、署名対象メッセージとJSONリクエストボディの両方で文字列でなければなりません。文字列のフォーマットは完全に一致する必要があります。
routeのデフォルト: route は少なくとも2026年7月4日までオプショナルです。JSONから省略する場合は、型付きデータからも省略してください。サーバーは省略された route を best_execution として扱います。routeが省略された場合、POST /order は非推奨(deprecation)ヘッダーを返します。クライアントは route を送信することが推奨されます。2026年7月4日より前に必須になることはありませんが、その後必須になる可能性があります。
CancelOrder
構造体:
struct CancelOrder {
address wallet;
string orderId;
uint64 nonce;
}
フィールド要件:
wallet: ウォレットアドレスorderId: 文字列としての注文ID(例:"123")nonce: 一意のノンス
CancelOrderByClientId
構造体:
struct CancelOrderByClientId {
address wallet;
string clientId;
uint64 nonce;
}
フィールド要件:
wallet: ウォレットアドレスclientId: クライアント注文ID(文字列)nonce: 一意のノンス
ApproveAgent
構造体:
struct ApproveAgent {
address agent;
uint64 nonce;
}
フィールド要件:
agent: 認可するエージェントのウォレットアドレスnonce: 一意のノンス
注: エージェントを認可するウォレットは、復元された署名から導出されます。
RevokeAgent
構造体:
struct RevokeAgent {
address agent;
uint64 nonce;
}
フィールド要件:
agent: 認可を取り消すエージェントのウォレットアドレス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リクエストボディでは、price と 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..."
}
ノンス管理
要件:
- ノンスはウォレットごとに一意でなければなりません(MUST)
- ノンスはインクリメントされるべきです(SHOULD)(リプレイ攻撃の防止)
- ノンスの厳密な単調増加は検証されません(欠番も許容されます)
ベストプラクティス:
- ウォレットごとに永続的なカウンターを使用する
- 署名が成功するたびにインクリメントする
- ノンスの欠番を適切に処理する(例: リクエストが失敗した場合、同じノンスでリトライする)
エージェント認可
エージェントウォレットで署名する場合:
-
エージェントの承認(1回のみ):
POST /approve-agent{"agent": "0x...","nonce": 1,"signature": "0x..." # Signed by wallet owner} -
エージェントウォレットで注文に署名:
- エージェントウォレットで
PlaceOrder/CancelOrderメッセージに署名する walletフィールドには取引用ウォレットのアドレスを設定する- ミドルウェアが、エージェントがそのウォレットに対して認可されているか検証します
- エージェントウォレットで
エージェント認可の詳細についてはエージェント認可を参照してください。
パーペチュアル注文(Hyperliquid互換)
パーペチュアル注文では、異なるEIP-712ドメインとメッセージフォーマット(Hyperliquid Core互換)を使用します。
ドメイン:
{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}
メッセージ: MessagePackでエンコードされた注文データを持つ Agent 構造体。
正確なフィールドについては、最新の署名エンコーディングガイドを参照してください。
よくあるエラー
"Signature verification failed"
原因:
priceまたはsizeを文字列ではなく数値として送信した- 署名時と送信時で文字列のフォーマットが変わった(例:
"100.0"と"100") - ノンスが正しくない
- ドメインが正しくない(チェーンID、name、version)
- エージェントがウォレットに対して認可されていない
"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);
署名のテスト
署名生成をテストする手順の例:
- 自分の実装で署名を生成する
POST /orderでテスト注文を送信する- レスポンスを確認する:
status="ACKED"または理由付きのstatus="REJECTED" "signature_verification_failed"の場合、以下を確認する:priceとsizeの文字列フォーマット- ドメインパラメータ(特に
chainId) - ノンスの正当性
セキュリティ上の考慮事項
- 秘密鍵を決して公開しない: ハードウェアウォレットまたは安全な鍵管理を使用する
- ノンス管理: ウォレットごとに永続的でインクリメントされるノンスを使用する
- エージェント認可:
GET /authorized-agentsで認可済みエージェントを定期的に監査する - 文字列エンコーディング: 署名時とリクエスト時の両方で
priceとsizeが文字列であることを確認する
参考資料
- EIP-712: https://eips.ethereum.org/EIPS/eip-712
- 実装: 署名復元およびミドルウェアスタックによって処理されます。