이 페이지는 자동 번역되었습니다. 영어 원문이 정본입니다. 영어로 읽기
메인 콘텐츠로 건너뛰기

인증 및 서명

쓰기 작업을 위한 EIP-712 서명 요건입니다.

프로덕션 서명 도메인

프로덕션에서는 체인 ID 999를 사용하세요. 테스트넷은 Hypercall이 추가 테스트넷 HYPE를 확보할 때까지 일시적으로 비활성화되어 있습니다.

개요

모든 쓰기 작업에는 EIP-712 타입 데이터 서명이 필요합니다. 서명자는 다음 중 하나여야 합니다:

  1. 지갑 주소 자체(직접 서명), 또는
  2. 해당 지갑에 대해 권한이 부여된 에이전트(에이전트 인증 참조)

EIP-712 도메인

도메인 구분자:

{
"name": "Hypercall",
"version": "1",
"chainId": 999,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

체인 ID: 999 (프로덕션)

메시지 유형

PlaceOrder

명시적 라우트가 포함된 구조체:

struct PlaceOrder {
address wallet;
string symbol;
string side;
string size;
string price;
string tif;
string route;
string clientId;
uint64 nonce;
}

라우트가 생략된 경우의 구조체:

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)

중요: pricesize는 서명된 메시지와 JSON 요청 본문 모두에서 반드시 문자열이어야 합니다. 문자열 형식이 정확히 일치해야 합니다.

라우트 기본값: route는 최소 2026년 7월 4일까지 선택 사항입니다. JSON에서 생략하는 경우 타입 데이터에서도 생략하세요. 서버는 생략된 routebest_execution으로 처리합니다. 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단계: 메시지 구성

명시적 라우트를 포함한 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 요청 본문은 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..."
}

논스 관리

요건:

  • 논스는 지갑별로 반드시 고유해야 합니다
  • 논스는 증가하는 값을 사용하는 것이 좋습니다 (재전송 공격 방지)
  • 논스는 엄격한 단조 증가 여부를 검증하지 않습니다 (간격 허용)

모범 사례:

  • 지갑별로 영구적인 카운터를 사용하세요
  • 각 서명이 성공할 때마다 카운터를 증가시키세요
  • 논스 간격을 유연하게 처리하세요 (예: 요청 실패 시 동일한 논스로 재시도)

에이전트 권한 부여

에이전트 지갑으로 서명하는 경우:

  1. 에이전트 승인 (1회):

    POST /approve-agent
    {
    "agent": "0x...",
    "nonce": 1,
    "signature": "0x..." # Signed by wallet owner
    }
  2. 에이전트 지갑으로 주문 서명:

    • 에이전트 지갑을 사용하여 PlaceOrder / CancelOrder 메시지에 서명
    • wallet 필드를 거래 지갑 주소로 설정
    • 미들웨어가 해당 지갑에 대해 에이전트 권한이 있는지 검증

전체 에이전트 권한 부여에 대한 자세한 내용은 에이전트 인증을 참조하세요.

무기한 계약 주문 (Hyperliquid 호환)

무기한 계약 주문은 다른 EIP-712 도메인과 메시지 형식을 사용합니다 (Hyperliquid Core 호환성):

도메인:

{
"name": "Exchange",
"version": "1",
"chainId": 1337,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

메시지: MessagePack으로 인코딩된 주문 데이터가 포함된 Agent 구조체.

정확한 필드는 최신 서명 인코딩 가이드를 참조하세요.

일반적인 오류

"Signature verification failed"

원인:

  1. price 또는 size를 문자열이 아닌 숫자로 전송
  2. 서명과 전송 사이에 문자열 형식이 변경됨 (예: "100.0" vs "100")
  3. 잘못된 논스
  4. 잘못된 도메인 (체인 ID, name, version)
  5. 지갑에 대한 에이전트 권한 없음

"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);

서명 테스트

서명 생성 테스트 예시:

  1. 구현체로 서명을 생성합니다
  2. POST /order를 통해 테스트 주문을 전송합니다
  3. 응답을 확인합니다: status="ACKED" 또는 사유가 포함된 status="REJECTED"
  4. "signature_verification_failed"인 경우 다음을 확인하세요:
    • pricesize의 문자열 형식
    • 도메인 파라미터 (특히 chainId)
    • 논스의 정확성

보안 고려 사항

  1. 개인 키를 절대 노출하지 마세요: 하드웨어 지갑 또는 안전한 키 관리 솔루션을 사용하세요
  2. 논스 관리: 지갑별로 영구적이고 증가하는 논스를 사용하세요
  3. 에이전트 권한 부여: GET /authorized-agents를 통해 권한이 부여된 에이전트를 정기적으로 점검하세요
  4. 문자열 인코딩: 서명과 요청 모두에서 pricesize가 문자열인지 확인하세요

참고 자료