エージェント認可
マーケットメイカー向けのマルチキー署名機能です。
概要
エージェント認可を使うと、専用の署名鍵(エージェント)が取引ウォレットに代わって注文の発注とキャンセルを行えます。主な用途は次のとおりです。
- セキュリティ: 取引ウォレットの鍵はコールドストレージに保管し、署名にはホットキーを使います
- 運用: チームの複数メンバーがそれぞれ別の鍵で署名できます
- 自動化: 自動売買システムが専用の鍵で署名できます
2つの認可モデル
Hypercallでは、プロダクトごとに別々の委任の仕組みを用意しています。
| プロダクト | 委任の種類 | 保存先 | 設定方法 |
|---|---|---|---|
| オプション (REST API) | エージェント認可 | Hypercallエンジンの状態 | POST /approve-agent |
| パーペチュアル | APIウォレット | オンチェーン(お客様のAccountコントラクト) | hc_update_api_wallet ディレクティブ |
この2つは互いに独立した仕組みです。オプション取引用にエージェントを承認しても、パーペチュアルの取引は認可されません。逆も同様です。
オプション: エージェント認可(オフチェーン)
エージェントは、取引ウォレットに代わって PlaceOrder と CancelOrder のEIP-712メッセージに署名します。APIは、注文がエンジンに届く前に、エンジンの認可状態と照らしてエージェントを確認します。
パーペチュアル: APIウォレット(オンチェーン)
APIウォレットは、HypercallApiSign EIP-712ドメインでパーペチュアルの注文とキャンセルに署名します。Hypercallは注文を受け付ける前に、お客様のAccountコントラクト(isApiWalletActive())と照らして署名者を確認します。TEEルート(アプリのデフォルトで、APIクライアントはオプトイン)では、お客様のAccount専用の別のHyperliquid取引鍵を保持するセキュアエンクレーブが、ネイティブ注文に署名して送信します。アプリの詳細設定にあるスマートコントラクトルートでは、注文はお客様のAccountコントラクトを経由します。署名のリファレンスは EIP-712署名 を、フローは パーペチュアル を参照してください。
APIウォレットを追加または削除するには、アカウントのマネージャーウォレットで署名した hc_update_api_wallet ディレクティブを送信します。
これはHyperliquidのAPIウォレットモデルに倣ったものです。Hyperliquidは Nonces and API wallets でこれをAPIウォレット(エージェントウォレットとも呼ばれます)として説明しており、approveAgent については Exchange endpoint に記載しています。
ノンスによるリプレイ保護
ノンスの追跡は Hyperliquidのノンスモデル に従います。署名済みのすべてのアクション(注文、エージェントの承認と取り消し、QPハンドシェイク)は、署名者ごとに共通のノンス空間を使います。エンジンは署名者アドレスごとに、値の大きい上位100個のノンスを保存します。新しいノンスは次の条件をすべて満たす場合に受け付けられます。
!stored_set.contains(nonce): 重複していないこと- セットに100件保存されている場合は
nonce > min(stored_set): 保存済みの最小ノンスより大きいこと nonceがサーバーのタイムスタンプに対して (T - 2日, T + 1日) の範囲内にあること
順序が前後したノンスも使えます(例: ノンス105の後に102を送っても、どちらもセットの最小値より大きければ両方とも有効です)。セットの上限は100件で、新しいノンスが追加されると最も古いものから削除されます。これにより、極端に大きなノンスを1つ送っただけでアカウントが使えなくなる事態を防ぎます。
ノンスの署名者は、EIP-712メッセージに署名したアドレスです。注文ではAPIウォレット、エージェント認可では復元された署名者、ハンドシェイクではQPウォレットが該当します。クライアントは Date.now()(ミリ秒単位のエポック)をノンスの初期値とし、単調に増加させてください。
オプションのエージェント認可
直接署名
signer == wallet の場合、注文は常に認可されます(自己署名)。
エージェント署名
signer != wallet の場合、署名者は認可済みのエージェントでなければなりません。
- エージェントがエンジン所有の認可状態に存在すること
- 認可の有効期限が切れていないこと
- 再起動時に永続的なソースとなるのは、エンジンのスナップショットとエンジンのジャーナルです
エージェントの承認
エンドポイント: POST /approve-agent
リクエスト: ApproveAgentRequest
{
"agent": "0x...",
"nonce": 1,
"signature": "0x..."
}
署名:
- ウォレットの所有者が
ApproveAgentメッセージに署名します - ウォレットは復元された署名から導出されます
- エージェントはリクエストの
agentフィールドで指定します
EIP-712構造体:
struct ApproveAgent {
address agent;
uint64 nonce;
}
レスポンス: ApproveAgentResponse
{
"success": true,
"error": null
}
補足:
- エージェント認可はジャーナルに記録されるエンジン状態であり、再起動後も保持されます
- このエンドポイントで行った承認に有効期限はありません
エージェントの取り消し
エンドポイント: DELETE /revoke-agent
リクエスト: RevokeAgentRequest
{
"agent": "0x...",
"nonce": 2,
"signature": "0x..."
}
署名:
- ウォレットの所有者が
RevokeAgentメッセージに署名します - ウォレットは復元された署名から導出されます
EIP-712構造体:
struct RevokeAgent {
address agent;
uint64 nonce;
}
レスポンス: RevokeAgentResponse
補足:
- ジャーナルに記録される
RevokeAgentコマンドを、エンジン所有の認可状態に適用します - 取引APIの認可チェックはバックエンドの状態を読み取り、取引リクエストに対して取り消しを適用します。Trollboxへの投稿では、エージェントの認可済み状態が長めにキャッシュされる場合があります。そのため、取り消した直後のエージェントでも、キャッシュが期限切れになるか、ベストエフォートの無効化が該当するエッジロケーションに届くまでは投稿できることがあります。このキャッシュによって取引APIの変更操作が認可されることはありません。
- 取り消し後、エージェントはそのウォレットのために署名できなくなります
すべてのエージェントの取り消し
エンドポイント: DELETE /revoke-all-agents
リクエスト: { "nonce": 3, "signature": "0x..." }。ウォレットの所有者が RevokeAllAgents(uint64 nonce) に対して署名します。復元されたウォレットのエージェントをすべて削除します。
認可済みエージェントの取得
エンドポイント: GET /authorized-agents?wallet=...
クエリパラメータ:
wallet(必須)
レスポンス:
{
"agents": [
"0x...",
"0x..."
]
}
補足:
- 有効期限内の有効なエージェントのみを返します
エージェントの使い方
エージェントによる注文の署名
エージェントが承認されたら、次の手順で使います。
- エージェントウォレットで注文に署名する:
PlaceOrder/CancelOrderメッセージにはエージェントウォレットで署名します - walletフィールドを設定する:
walletフィールドには取引ウォレットのアドレスを設定します(エージェントのアドレスではありません) - ミドルウェアによる検証: ミドルウェアが、そのウォレットに対してエージェントが認可されているかを検証します
例:
// Agent wallet signs the order
const agentSigner = new ethers.Wallet(agentPrivateKey);
const message = {
wallet: "0x...", // Trading wallet (not agent)
symbol: "BTC-20250131-100000-C",
side: "Buy",
size: "0.1",
price: "100.0",
tif: "gtc",
clientId: "mm-1",
nonce: 1
};
// Sign with agent wallet
const signature = await agentSigner._signTypedData(domain, types, message);
// Send request. The REST body uses snake_case `client_id`.
const { clientId, ...rest } = message;
const response = await fetch('/order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...rest,
client_id: clientId,
signature
})
});
エージェントによる一括注文
一括エンドポイントでは、エージェント認可を項目ごとに検証します。
POST /bulk_orderの各注文は、それぞれ別のエージェントが署名できます- エージェント認可は項目ごとにチェックされます
- エージェント認可に失敗した項目は、
BulkOrderResult内でその項目のみエラーを返します
認可チェック
ミドルウェア(単一注文)
エンドポイント:
POST /orderDELETE /orderDELETE /order_cloid
チェック: signature_and_agent_middleware が次の点を検証します。
- 署名の復元に成功すること
- 署名者が認可されていること(signer == wallet、またはエージェントが認可済み)
ハンドラー(一括注文)
エンドポイント:
POST /bulk_orderDELETE /bulk_orderDELETE /bulk_order_cloid
チェック: ハンドラー内で項目ごとに検証します。
- 項目ごとの署名の復元
- 項目ごとのエージェント認可チェック
認可の保存
エージェント認可はエンジン所有の状態です。ApproveAgent と RevokeAgent のコマンドはジャーナルに記録され、エンジンのリプレイによって復元され、APIのチェック用に読み取りスナップショットを通じて公開されます。
認可ロジック
署名者の認可は、明示的なOR条件で判定します。
- 直接署名:
signer == wallet。 - エージェント署名: エンジンのスナップショットに
wallet_address == <wallet>かつagent_address == <signer>の認可レコードが存在し、expires_atが未設定か将来の日時であること。
有効期限
エンジンは認可に expires_at を設定でき、期限切れのエージェントは未認可として扱います。POST /approve-agent では有効期限を設定しないため、APIで承認したエージェントは取り消すまで有効です。
セキュリティ上の考慮事項
- エージェント鍵のセキュリティ: エージェントの秘密鍵を保護してください(ハードウェアウォレットや安全な鍵管理を使用します)
- 定期的な監査:
GET /authorized-agentsで認可済みエージェントを定期的に確認してください - 未使用エージェントの取り消し: 不要になったエージェントは取り消してください
ベストプラクティス
- 自動化にはエージェントを使う: 取引ウォレットの鍵はコールドストレージに保管し、自動売買システムにはエージェントを使います
- エージェントの範囲を限定する: アクセスが必要なエージェントのみを承認します
- エージェントの利用状況を監視する: どのエージェントが注文を出しているかを追跡します
- 速やかに取り消す: 不要になったエージェントはすぐに取り消します
よくある問題
"Signer 0x... is not authorized to act for wallet 0x..."
原因: エージェントが承認されていないか、認可が期限切れまたは取り消し済みです。
解決策: POST /approve-agent でエージェントを承認するか、ウォレットで直接署名してください。
エージェント認可が機能しない
原因:
- エージェントがエンジン所有の認可状態に存在しない
- 認可が取り消されている
- 認可の有効期限が切れている
解決策: GET /authorized-agents?wallet=... でエージェントの状態を確認してください。