orderly-api-authentication について
Orderly Network の開発者向け認証スキルです。EVM と Solana のウォレット登録、API キー管理、リクエスト署名を扱い、アカウント操作と API 呼び出しの二層認証を説明します。
主な機能
- 01EVM ウォレットの EIP-712 署名
- 02Solana ウォレットのメッセージ署名
- 03Ed25519 による API リクエスト認証
- 04アカウント登録と API キー管理
- 05読み取り・取引・資産の権限範囲
活用シーン
- 01EVM または Solana ユーザーの Orderly アカウント登録
- 02取引ボット用 API キーの設定
- 03直接 API 呼び出しの認証実装
- 04ウォレットやリクエスト署名の問題調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ orderlynetwork/ skills --skill orderly-api-authenticationインストール後、Claude Code で/orderly-api-authenticationを入力して読み込まれたか確認します。
詳細
Orderly Network:API 認証
このスキルでは、Orderly Network の両方の認証レイヤーを扱います。アカウント登録とキー管理に使用するウォレット署名(EVM の EIP-712、Solana の Ed25519 メッセージ署名)と、API リクエスト認証に使用するEd25519 署名です。
使用する場面
- Orderly アカウントと API キー(EVM または Solana)の新規設定
- サーバーサイドの取引ボットの構築
- API の直接呼び出しの実装
- 2 層の認証フローの把握
- 署名の問題のデバッグ
前提条件
- Web3 ウォレット(EVM の場合は MetaMask、WalletConnect、Solana の場合は Phantom、Solflare)
- Broker ID(例:
woofi_dex、または独自のもの) - Node.js 18 以降のインストール(プログラムで使用する場合)
- EIP-712 の型付きデータ署名(EVM)または Ed25519 メッセージ署名(Solana)、および Ed25519 暗号の知識
認証の概要
Orderly Network は、EVM と Solana の両方のウォレットに対応した2 層の認証システムを使用します。
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: Wallet Authentication │
│ ───────────────────────────── │
│ • Account registration │
│ • API key management (add/remove keys) │
│ • Privileged operations (withdrawals, admin) │
│ │
│ EVM: EIP-712 typed data signing │
│ Solana: Ed25519 message signing │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: API Authentication (Ed25519) │
│ ───────────────────────────────────── │
│ • Trading operations (place/cancel orders) │
│ • Reading account data (positions, balances) │
│ • WebSocket connections │
│ │
│ Signed by: Ed25519 key pair │
│ Key type: Locally-generated Ed25519 key pair │
└─────────────────────────────────────────────────────────────┘
認証フロー
1. User connects wallet
2. Wallet signs EIP-712 message to register account
3. Account ID is created
4. User generates Ed25519 key pair
5. Wallet signs EIP-712 message to authorize the Ed25519 key
6. Ed25519 key is used for all subsequent API calls
環境設定
| 環境 | API ベース URL | WebSocket URL |
|---|---|---|
| Mainnet | https://api.orderly.org | wss://ws.orderly.org/ws/stream |
| Testnet | https://testnet-api.orderly.org | wss://testnet-ws.orderly.org/ws/stream |
注:これらの API ベース URL は、EVM と Solana の両方のウォレットで使用できます。Orderly の API はオムニチェーン対応で、同じエンドポイントが両方のチェーンを処理します。
対応チェーンの取得
チェーン ID をハードコードしないでください。ブローカーに応じて動的に取得します。
// Get supported chains for your broker
const response = await fetch(`https://api.orderly.org/v1/public/chain_info?broker_id=${BROKER_ID}`);
const { data } = await response.json();
// data.chains contains supported chain_ids
// Use these chain IDs for EIP-712 domain configuration
EIP-712 ドメインの設定
Orderly では、操作に応じて2 種類の EIP-712 ドメインを使用します。
| ドメインの種類 | 用途 | Mainnet | Testnet |
|---|---|---|---|
| オフチェーン | アカウント登録、API キー管理 | 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC | 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC |
| オンチェーン | 出金、内部送金、PnL の決済 | 0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203 | 0x1826B75e2ef249173FC735149AE4B8e9ea10abff |
重要:オンチェーンの
verifyingContractは、Orderly L2 上のLedger コントラクトです。これはすべてのチェーンで共通する単一のコントラクトです(チェーンごとではありません)。入金用の Vault コントラクトは各対応 EVM チェーンに存在しますが、オンチェーン操作における信頼できる情報源は Ledger です。
オフチェーンのドメイン(登録、API キー)
スマートコントラクトと直接やり取りしない操作で使用します。
const OFFCHAIN_DOMAIN = {
name: 'Orderly',
version: '1',
chainId: 421614, // Connected chain ID (e.g., Arbitrum Sepolia)
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
};
オンチェーンのドメイン(出金、送金)
Orderly L2 上の Ledger コントラクトとやり取りする操作で使用します。
const ONCHAIN_DOMAIN = {
name: 'Orderly',
version: '1',
chainId: 42161, // Connected chain ID
verifyingContract: isTestnet
? '0x1826B75e2ef249173FC735149AE4B8e9ea10abff'
: '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203',
};
パート 1:EIP-712 ウォレット認証
所有権の証明が必要なアカウントレベルの操作には、ウォレット認証が必要です。
EIP-712 を使用する場面
- アカウント登録:新しい Orderly アカウントの作成
- API キー管理:Ed25519 API キーの追加または削除
- 出金:Vault からのトークン出金のリクエスト
- 管理者操作:IP 制限の設定、アカウント設定の管理
アカウント登録
ステップ 1:既存アカウントの確認
登録前に、ウォレットにすでにアカウントがあるか確認します。
const BROKER_ID = 'woofi_dex'; // Your broker ID
const walletAddress = '0x...'; // User's wallet address
const response = await fetch(
`https://testnet-api.orderly.org/v1/get_account?broker_id=${BROKER_ID}&user_address=${walletAddress}`
);
const data = await response.json();
// If data.success is true, account already exists
// If not, proceed with registration
ステップ 2:登録用 nonce の取得
登録に必要な一意の nonce を取得します(有効期間は 2 分です)。
const nonceResponse = await fetch('https://testnet-api.orderly.org/v1/registration_nonce');
const { data: nonce } = await nonceResponse.json();
console.log('Registration nonce:', nonce);
ステップ 3:登録メッセージへの署名
EIP-712 の型付きメッセージを作成して署名します。
// Registration Message Type
const REGISTRATION_TYPES = {
Registration: [
{ name: 'brokerId', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'timestamp', type: 'uint64' },
{ name: 'registrationNonce', type: 'uint256' },
],
};
// Create the message
const registerMessage = {
brokerId: BROKER_ID,
chainId: 421614,
timestamp: Date.now(),
registrationNonce: nonce,
};
// Sign with wallet (e.g., MetaMask) - Use OFFCHAIN_DOMAIN for registration
const signature = await window.ethereum.request({
method: 'eth_signTypedData_v4',
params: [
walletAddress,
{
types: REGISTRATION_TYPES,
domain: OFFCHAIN_DOMAIN,
message: registerMessage,
primaryType: 'Registration',
},
],
});
ステップ 4:登録の送信
署名済みのペイロードを送信して、Orderly Account ID を作成します。
const registerResponse = await fetch('https://testnet-api.orderly.org/v1/register_account', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: registerMessage,
signature: signature,
userAddress: walletAddress,
}),
});
const result = await registerResponse.json();
console.log('Account ID:', result.data.account_id);
// Store this account ID - you'll need it for API authentication
API キー管理(Orderly Key)
アカウントを取得したら、API アクセス用に Ed25519 キーを登録する必要があります。
Ed25519 キーペアの生成
import { getPublicKeyAsync, utils } from '@noble/ed25519';
// Generate 32-byte private key (cryptographically secure)
const privateKey = utils.randomPrivateKey();
// Derive public key
const publicKey = await getPublicKeyAsync(privateKey);
// Encode public key as base58 (required by Orderly)
function encodeBase58(bytes: Uint8Array): string {
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
let result = '';
let num = 0n;
for (const byte of bytes) {
num = num * 256n + BigInt(byte);
}
while (num > 0n) {
result = ALPHABET[Number(num % 58n)] + result;
num = num / 58n;
}
return result;
}
const orderlyKey = `ed25519:${encodeBase58(publicKey)}`;
// Convert bytes to hex string for storage
function bytesToHex(bytes: Uint8Array): string {
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
console.log('Orderly Key:', orderlyKey);
console.log('Private Key (hex):', bytesToHex(privateKey));
// STORE PRIVATE KEY SECURELY - NEVER SHARE IT
Orderly Key の追加メッセージへの署名
EIP-712 を使用して、Ed25519 キーをアカウントに関連付けます。
const ADD_KEY_TYPES = {
AddOrderlyKey: [
{ name: 'brokerId', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'orderlyKey', type: 'string' },
{ name: 'scope', type: 'string' },
{ name: 'timestamp', type: 'uint64' },
{ name: 'expiration', type: 'uint64' },
],
};
const addKeyMessage = {
brokerId: BROKER_ID,
chainId: 421614,
orderlyKey: orderlyKey,
scope: 'read,trading', // Permissions: read, trading, asset (comma-separated)
timestamp: Date.now(),
expiration: Date.now() + 31536000000, // 1 year from now
};
// Use OFFCHAIN_DOMAIN for API key management
const addKeySignature = await window.ethereum.request({
method: 'eth_signTypedData_v4',
params: [
walletAddress,
{
types: ADD_KEY_TYPES,
domain: OFFCHAIN_DOMAIN,
message: addKeyMessage,
primaryType: 'AddOrderlyKey',
},
],
});
Orderly Key の登録
API キーを登録します。
const keyResponse = await fetch('https://testnet-api.orderly.org/v1/orderly_key', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: addKeyMessage,
signature: addKeySignature,
userAddress: walletAddress,
}),
});
const keyResult = await keyResponse.json();
console.log('Key registered:', keyResult.success);
Orderly Key のスコープ
API キーを登録するときは、権限を指定します。
| スコープ | 権限 |
|---|---|
read | ポジション、注文、残高の読み取り |
trading | 注文の発注、キャンセル、変更 |
asset | 入金、出金、内部振替 |
複数のスコープは、カンマ区切りで組み合わせることができます:'read,trading,asset'
Orderly Key の削除
キーを削除するには、有効な別のキーによる Ed25519 認証が必要です。
// POST /v1/client/remove_orderly_key
const removeResponse = await signAndSendRequest(
accountId,
privateKey, // Must be a different valid key
'https://api.orderly.org/v1/client/remove_orderly_key',
{
method: 'POST',
body: JSON.stringify({
orderly_key: 'ed25519:...', // Key to remove
}),
}
);
Solana ウォレット認証
Solana ウォレットは、アカウント操作に ネイティブの Ed25519 メッセージ署名(EIP-712 ではありません)を使用します。Solana ウォレットはネイティブで Ed25519 キーを使用するため、署名プロセスはより簡単ですが、メッセージの形式は異なります。
Solana と EVM の認証
| 項目 | EVM ウォレット | Solana ウォレット |
|---|---|---|
| 署名方式 | EIP-712 型付きデータ | プレーンなメッセージ署名 |
| キーの種類 | secp256k1 | Ed25519(ネイティブ) |
| アカウントの検索 | /v1/get_account | /v1/get_account?chain_type=SOL |
| メッセージ形式 | 構造化 JSON 型 | アダプター経由の生バイト列 |
| 署名 | Ethereum 署名 | Ed25519 署名 |
アカウントの検索
Solana ウォレットに Orderly アカウントがすでにあるか確認します。
import { PublicKey } from '@solana/web3.js';
const BROKER_ID = 'woofi_dex';
const solanaAddress = '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU'; // Base58 address
// Solana accounts require chain_type=SOL parameter
const response = await fetch(
`https://testnet-api.orderly.org/v1/get_account?` +
`address=${solanaAddress}&` +
`broker_id=${BROKER_ID}&` +
`chain_type=SOL`
);
const data = await response.json();
// data.data.account_id contains the Orderly account ID
// Account ID format is different from EVM (not a keccak256 hash)
Solana アダプターによるメッセージ署名
Orderly は、適切な形式のメッセージを生成するための Solana アダプターを提供しています。
import { DefaultSolanaWalletAdapter } from '@orderly.network/default-solana-adapter';
import { Connection, clusterApiUrl, Keypair } from '@solana/web3.js';
import { signAsync } from '@noble/ed25519';
import bs58 from 'bs58';
// Setup wallet adapter
const walletAdapter = new DefaultSolanaWalletAdapter();
// Initialize with wallet details
walletAdapter.active({
address: solanaAddress,
provider: {
connection: new Connection(clusterApiUrl('devnet')), // or 'mainnet-beta'
signMessage: async (msg: Uint8Array) => {
// Sign with Solana wallet (Ed25519)
return await signAsync(msg, privateKeyBytes.slice(0, 32));
},
sendTransaction: async (tx, conn) => {
tx.sign([senderKeypair]);
return conn.sendTransaction(tx);
},
},
chain: {
id: network === 'mainnet' ? 900900900 : 901901901, // Solana chain IDs
},
});
登録フロー
ステップ 1:登録 nonce を取得
const nonceResponse = await fetch('https://testnet-api.orderly.org/v1/registration_nonce');
const { data: nonce } = await nonceResponse.json();
ステップ 2:登録メッセージを生成して署名
// Generate registration message using adapter
const registerMessage = await walletAdapter.generateRegisterMessage({
brokerId: BROKER_ID,
timestamp: Date.now(),
registrationNonce: nonce,
});
// Sign with Solana wallet (raw message bytes, not EIP-712)
const signature = await wallet.signMessage(registerMessage.message);
ステップ 3:登録を送信
const registerResponse = await fetch('https://testnet-api.orderly.org/v1/register_account', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: registerMessage.message,
signature: signature,
userAddress: solanaAddress,
chainType: 'SOL', // Required for Solana
}),
});
const result = await registerResponse.json();
console.log('Account ID:', result.data.account_id);
API キー管理(Orderly Key)
Ed25519 キーペアの生成
EVM の場合と同様に、Ed25519 キーペアをローカルで生成します。
import { getPublicKeyAsync, utils } from '@noble/ed25519';
import bs58 from 'bs58';
// Generate key pair
const privateKey = utils.randomPrivateKey();
const publicKey = await getPublicKeyAsync(privateKey);
const orderlyKey = `ed25519:${bs58.encode(publicKey)}`;
Orderly Key の追加メッセージへの署名
// Generate add key message using adapter
const addKeyMessage = await walletAdapter.generateAddKeyMessage({
brokerId: BROKER_ID,
orderlyKey: orderlyKey,
scope: 'read,trading',
timestamp: Date.now(),
expiration: Date.now() + 31536000000, // 1 year
});
// Sign with Solana wallet
const signature = await wallet.signMessage(addKeyMessage.message);
Orderly Key の登録
const keyResponse = await fetch('https://testnet-api.orderly.org/v1/orderly_key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: addKeyMessage.message,
signature: signature,
userAddress: solanaAddress,
chainType: 'SOL',
}),
});
出金への署名
出金には、EVM と Solana のどちらでもウォレット署名が必要です。
// Fetch withdraw nonce
const nonceRes = await fetch(`${BASE_URL}/v1/withdraw_nonce`);
const {
data: { withdraw_nonce },
} = await nonceRes.json();
// Generate withdraw message
const withdrawMessage = await walletAdapter.generateWithdrawMessage({
brokerId: BROKER_ID,
receiver: solanaAddress,
token: 'USDC',
amount: '1000',
timestamp: Date.now(),
nonce: Number(withdraw_nonce),
});
// Sign with Solana wallet
const signature = await wallet.signMessage(withdrawMessage.message);
// Submit withdrawal request
const res = await fetch(`${BASE_URL}/v1/withdraw_request`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: withdrawMessage.message,
signature: signature,
userAddress: solanaAddress,
verifyingContract: '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203', // Mainnet
// verifyingContract: '0x1826B75e2ef249173FC735149AE4B8e9ea10abff', // Testnet
}),
});
損益精算への署名
// Fetch settle nonce
const nonceRes = await fetch(`${BASE_URL}/v1/settle_nonce`);
const {
data: { settle_nonce },
} = await nonceRes.json();
// Generate settle message
const settleMessage = await walletAdapter.generateSettleMessage({
brokerId: BROKER_ID,
timestamp: Date.now(),
settlePnlNonce: settle_nonce,
});
// Sign with Solana wallet
const signature = await wallet.signMessage(settleMessage.message);
// Submit settle request
const res = await fetch(`${BASE_URL}/v1/settle_pnl`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: settleMessage.message,
signature: signature,
userAddress: solanaAddress,
verifyingContract: '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203',
}),
});
Solana 固有の設定
| 環境 | Solana チェーン ID | Solana クラスター | Orderly Vault アドレス | 検証コントラクト |
|---|---|---|---|---|
| メインネット | 900900900 | mainnet-beta | ErBmAD61mGFKvrFNaTJuxoPwqrS8GgtwtqJTJVjFWx9Q | 0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203 |
| テストネット | 901901901 | devnet | 9shwxWDUNhtwkHocsUAmrNAQfBH2DHh4njdAEdHZZkF2 | 0x1826B75e2ef249173FC735149AE4B8e9ea10abff |
注:API のベース URL は EVM と Solana で共通です。このスキルの先頭にある環境設定のセクションを参照してください。
主な違い
アカウント ID の生成
- EVM:
keccak256(address, keccak256(brokerId)) - Solana:
/v1/get_accountAPI から返されます(ハッシュではありません)
メッセージ署名
- EVM:構造化された EIP-712 型を使用する
eth_signTypedData_v4を使用します - Solana:Ed25519 で署名する生のメッセージバイト列を使用します
ドメインセパレーターなし
Solana では EIP-712 のドメイン設定を使用しません。
// EVM - requires domain
domain: {
name: 'Orderly',
version: '1',
chainId: 42161,
verifyingContract: '0x...',
}
// Solana - no domain, just raw message
const message = await walletAdapter.generateRegisterMessage({...});
パート 2:Ed25519 API 認証
ウォレット署名(EVM の場合は EIP-712、Solana の場合は Ed25519 メッセージ署名)によって Ed25519 キーを登録したら、そのキーをすべての API 操作で使用します。
必須ヘッダー
| ヘッダー | 説明 |
|---|---|
orderly-timestamp | ミリ秒単位の Unix タイムスタンプ |
orderly-account-id | Orderly アカウント ID |
orderly-key | ed25519: を先頭に付けた公開鍵 |
orderly-signature | Base64url でエンコードされた Ed25519 署名 |
Ed25519 キーペアの生成
import { getPublicKeyAsync, utils } from '@noble/ed25519';
// Generate private key (32 cryptographically secure random bytes)
const privateKey = utils.randomPrivateKey();
// Derive public key
const publicKey = await getPublicKeyAsync(privateKey);
// Encode public key as base58 (required by Orderly)
function encodeBase58(bytes: Uint8Array): string {
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const BASE = 58n;
let num = 0n;
for (const byte of bytes) {
num = num * 256n + BigInt(byte);
}
let result = '';
while (num > 0n) {
result = ALPHABET[Number(num % BASE)] + result;
num = num / BASE;
}
// Handle leading zeros
for (const byte of bytes) {
if (byte === 0) {
result = '1' + result;
} else {
break;
}
}
return result;
}
const orderlyKey = `ed25519:${encodeBase58(publicKey)}`;
// Convert bytes to hex string (browser & Node.js compatible)
function bytesToHex(bytes: Uint8Array): string {
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
console.log('Private Key (hex):', bytesToHex(privateKey));
console.log('Public Key (base58):', orderlyKey);
// STORE PRIVATE KEY SECURELY - NEVER SHARE IT
リクエストへの署名
メッセージの構築
function buildSignMessage(timestamp: number, method: string, path: string, body?: string): string {
// Message format: timestamp + method + path + body
// Note: No spaces or separators between parts
return `${timestamp}${method}${path}${body || ''}`;
}
// Examples
const timestamp = Date.now();
// GET request (no body)
const getMessage = buildSignMessage(timestamp, 'GET', '/v1/positions');
// POST request (with body)
const body = JSON.stringify({
symbol: 'PERP_ETH_USDC',
side: 'BUY',
order_type: 'LIMIT',
order_price: '3000',
order_quantity: '0.1',
});
const postMessage = buildSignMessage(timestamp, 'POST', '/v1/order', body);
署名の作成
import { signAsync } from '@noble/ed25519';
async function signRequest(
timestamp: number,
method: string,
path: string,
body: string | undefined,
privateKey: Uint8Array
): Promise<string> {
const message = buildSignMessage(timestamp, method, path, body);
// Sign with Ed25519
const signatureBytes = await signAsync(new TextEncoder().encode(message), privateKey);
// Encode as base64url (NOT base64)
// Convert to base64, then make it URL-safe
const base64 = btoa(String.fromCharCode(...signatureBytes));
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
署名してリクエストを送信するヘルパー
クエリパラメーターと適切な Content-Type ヘッダーを常に正しく処理する、シンプルな独立型の認証ヘルパーです。
import { getPublicKeyAsync, signAsync } from '@noble/ed25519';
import { encodeBase58 } from 'ethers';
export async function signAndSendRequest(
orderlyAccountId: string,
privateKey: Uint8Array | string,
input: URL | string,
init?: RequestInit | undefined
): Promise<Response> {
const timestamp = Date.now();
const encoder = new TextEncoder();
const url = new URL(input);
let message = `${String(timestamp)}${init?.method ?? 'GET'}${url.pathname}${url.search}`;
if (init?.body) {
message += init.body;
}
const orderlySignature = await signAsync(encoder.encode(message), privateKey);
return fetch(input, {
headers: {
'Content-Type':
init?.method !== 'GET' && init?.method !== 'DELETE'
? 'application/json'
: 'application/x-www-form-urlencoded',
'orderly-timestamp': String(timestamp),
'orderly-account-id': orderlyAccountId,
'orderly-key': `ed25519:${encodeBase58(await getPublicKeyAsync(privateKey))}`,
'orderly-signature': Buffer.from(orderlySignature).toString('base64url'),
...(init?.headers ?? {}),
},
...(init ?? {}),
});
}
このヘルパー関数は次の処理を行います。
- URL を適切に解析し、pathname と search(クエリ)パラメーターの両方を抽出します
- HTTP メソッドに基づいて Content-Type を正しく設定します(GET/DELETE では
application/x-www-form-urlencoded、それ以外ではapplication/jsonを使用します) - タイムスタンプ + メソッド + pathname + search + 本文で署名メッセージを構築します
- 後続の処理に使用できるよう、fetch レスポンスを返します
使用例
const baseUrl = 'https://api.orderly.org';
const accountId = '0x123...';
const privateKey = new Uint8Array(32); // Your private key
// GET request with query parameters
const positions = await signAndSendRequest(accountId, privateKey, `${baseUrl}/v1/positions`);
const positionsData = await positions.json();
// GET request with query params
const orders = await signAndSendRequest(
accountId,
privateKey,
`${baseUrl}/v1/orders?symbol=PERP_ETH_USDC&status=INCOMPLETE`
);
const ordersData = await orders.json();
// POST request with body
const order = await signAndSendRequest(accountId, privateKey, `${baseUrl}/v1/order`, {
method: 'POST',
body: JSON.stringify({
symbol: 'PERP_ETH_USDC',
side: 'BUY',
order_type: 'LIMIT',
order_price: '3000',
order_quantity: '0.1',
}),
});
const orderData = await order.json();
// DELETE request
const cancel = await signAndSendRequest(
accountId,
privateKey,
`${baseUrl}/v1/order?order_id=123&symbol=PERP_ETH_USDC`,
{ method: 'DELETE' }
);
エラー処理ヘルパー
class OrderlyApiError extends Error {
code: number;
details: any;
constructor(response: any) {
super(response.message || 'API Error');
this.code = response.code;
this.details = response;
}
}
// Usage with error handling
async function apiRequest(
accountId: string,
privateKey: Uint8Array,
url: string,
init?: RequestInit
) {
const response = await signAndSendRequest(accountId, privateKey, url, init);
const result = await response.json();
if (!result.success) {
throw new OrderlyApiError(result);
}
return result.data;
}
クエリパラメーター
クエリパラメーターは署名メッセージに含める必要があります。URL を解析して pathname と search パラメーターの両方を抽出します。
// Correct - query params are parsed from the URL
const url = new URL('/v1/orders?symbol=PERP_ETH_USDC&status=INCOMPLETE', baseUrl);
// Message: timestamp + method + pathname + search
// Result: "1234567890123GET/v1/orders?symbol=PERP_ETH_USDC&status=INCOMPLETE"
// Wrong - query params added separately after signing
const path = '/v1/orders';
const signature = await sign(timestamp, 'GET', path);
const url = `${path}?symbol=PERP_ETH_USDC`; // Signature mismatch!
よくあるエラー
署名の不一致(コード 10016)
Cause: Signature doesn't match expected value
Check:
1. Message format: timestamp + method + path + body (no spaces)
2. Method is uppercase: GET, POST, DELETE, PUT
3. Path includes query parameters
4. Body is exact JSON string (same whitespace)
5. Signature is base64url encoded (not base64)
タイムスタンプの有効期限切れ(コード 10017)
Cause: Timestamp is too old or too far in the future
Solution:
- Ensure server clock is synchronized
- Timestamp must be within ±30 seconds
- Generate timestamp immediately before signing
無効な Orderly キー(コード 10019)
Cause: Public key format incorrect
Solution:
- Must be prefixed with 'ed25519:'
- Public key must be base58 encoded
- Key must be registered to account
Orderly キーのスコープ
API キーを登録する際は、権限を指定します。
| スコープ | 権限 |
|---|---|
read | ポジション、注文、残高の読み取り |
trading | 注文の発注、キャンセル、変更 |
asset | 入金、出金、内部振替 |
// When adding key via EIP-712 signing
const addKeyMessage = {
brokerId: 'woofi_dex',
chainId: 42161,
orderlyKey: 'ed25519:...',
scope: 'read,trading', // Multiple scopes comma-separated
timestamp: Date.now(),
expiration: Date.now() + 31536000000, // 1 year
};
セキュリティのベストプラクティス
秘密鍵を安全に保管する
// NEVER hardcode private keys
// BAD:
const privateKey = new Uint8Array([1, 2, 3, ...]);
// GOOD: Load from environment
const privateKeyHex = process.env.ORDERLY_PRIVATE_KEY;
// Convert hex string to Uint8Array (browser & Node.js compatible)
function hexToBytes(hex: string): Uint8Array {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
}
return bytes;
}
const privateKey = hexToBytes(privateKeyHex);
// BETTER: Use secure key management
// AWS KMS, HashiCorp Vault, etc.
キーのローテーション
セキュリティのため、API キーを定期的にローテーションしてください。
// Generate new key pair
const newPrivateKey = utils.randomPrivateKey();
const newPublicKey = await getPublicKeyAsync(newPrivateKey);
// Register new key (requires wallet signature via EIP-712)
// POST /v1/orderly_key - No Ed25519 auth required
const orderlyKey = `ed25519:${encodeBase58(newPublicKey)}`;
const timestamp = Date.now();
const expiration = timestamp + 31536000000; // 1 year
const addKeyMessage = {
brokerId: 'your_broker_id',
chainId: 42161, // Arbitrum mainnet
orderlyKey: orderlyKey,
scope: 'read,trading', // Comma-separated scopes
timestamp: timestamp,
expiration: expiration,
};
// Sign with wallet (EIP-712)
const addKeySignature = await wallet.signTypedData({
domain: {
name: 'Orderly',
version: '1',
chainId: 42161,
verifyingContract: '0x...', // Contract address
},
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
AddOrderlyKey: [
{ name: 'brokerId', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'orderlyKey', type: 'string' },
{ name: 'scope', type: 'string' },
{ name: 'timestamp', type: 'uint256' },
{ name: 'expiration', type: 'uint256' },
],
},
primaryType: 'AddOrderlyKey',
message: addKeyMessage,
});
const registerResponse = await fetch('https://api.orderly.org/v1/orderly_key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: addKeyMessage,
signature: addKeySignature,
userAddress: walletAddress,
}),
});
const registerResult = await registerResponse.json();
if (!registerResult.success) {
throw new Error(`Failed to register key: ${registerResult.message}`);
}
// Update your application config
config.privateKey = newPrivateKey;
config.orderlyKey = orderlyKey;
// Remove old key using authenticated request
// POST /v1/client/remove_orderly_key - Requires Ed25519 auth
const oldOrderlyKey = `ed25519:${encodeBase58(oldPublicKey)}`;
const removeResponse = await signAndSendRequest(
accountId,
newPrivateKey, // Use the NEW key to authenticate
'https://api.orderly.org/v1/client/remove_orderly_key',
{
method: 'POST',
body: JSON.stringify({
orderly_key: oldOrderlyKey,
}),
}
);
const removeResult = await removeResponse.json();
if (!removeResult.success) {
throw new Error(`Failed to remove old key: ${removeResult.message}`);
}
IP 制限
// Set IP restriction for key
POST /v1/client/set_orderly_key_ip_restriction
Body: {
orderly_key: 'ed25519:...',
ip_list: ['1.2.3.4', '5.6.7.8'],
}
// Get current restrictions
GET /v1/client/orderly_key_ip_restriction?orderly_key={key}
// Reset (remove) restrictions
POST /v1/client/reset_orderly_key_ip_restriction
Body: { orderly_key: 'ed25519:...' }
WebSocket 認証
WebSocket でも Ed25519 認証が必要です。
const ws = new WebSocket(`wss://ws-private-evm.orderly.org/v2/ws/private/stream/${accountId}`);
ws.onopen = async () => {
const timestamp = Date.now();
const message = timestamp.toString();
const signature = await signAsync(new TextEncoder().encode(message), privateKey);
// Convert to base64url (browser & Node.js compatible)
const base64 = btoa(String.fromCharCode(...signature));
const base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
ws.send(
JSON.stringify({
id: 'auth_1',
event: 'auth',
params: {
orderly_key: orderlyKey,
sign: base64url,
timestamp: timestamp,
},
})
);
};
認証のテスト
// Verify key is valid
GET /v1/get_orderly_key?orderly_key={key}
// Response
{
"success": true,
"data": {
"account_id": "0x...",
"valid": true,
"scope": "read,trading",
"expires_at": 1735689600000
}
}
対応チェーン
| チェーン | チェーン ID | メインネット | テストネット |
|---|---|---|---|
| Arbitrum | 42161 / 421614 | ✅ | ✅ |
| Optimism | 10 / 11155420 | ✅ | ✅ |
| Base | 8453 / 84532 | ✅ | ✅ |
| Ethereum | 1 / 11155111 | ✅ | ✅ |
| Solana | 900900900 / 901901901 | ✅ | ✅ |
| Mantle | 5000 / 5003 | ✅ | ✅ |
よくある問題
EIP-712 エラー
「Nonce expired」エラー
- ノンスの有効期限は 2 分間です
- 新しいノンスを取得して再試行してください
「Account already exists」エラー
- このウォレットはすでにこのブローカーに登録されています
/v1/get_accountを使用して既存のアカウント情報を取得してください
「Invalid signature」エラー
- EIP-712 ドメイン(name、version、chainId、verifyingContract)が完全に一致していることを確認してください
- チェーン ID がネットワークと一致していることを確認してください
- メッセージ構造が型定義と一致していることを確認してください
eth_signTypedDataではなくeth_signTypedData_v4を使用してください
Ed25519 エラー
署名の不一致(コード 10016)
Cause: Signature doesn't match expected value
Check:
1. Message format: timestamp + method + path + body (no spaces)
2. Method is uppercase: GET, POST, DELETE, PUT
3. Path includes query parameters
4. Body is exact JSON string (same whitespace)
5. Signature is base64url encoded (not base64)
タイムスタンプの有効期限切れ(コード 10017)
Cause: Timestamp is too old or too far in the future
Solution:
- Ensure server clock is synchronized
- Timestamp must be within ±30 seconds
- Generate timestamp immediately before signing
無効な Orderly キー(コード 10019)
Cause: Public key format incorrect
Solution:
- Must be prefixed with 'ed25519:'
- Public key must be base58 encoded
- Key must be registered to account
認証方式の比較
| 項目 | EIP-712 ウォレット認証 | Ed25519 API 認証 |
|---|---|---|
| 目的 | アカウント操作、キー管理 | 取引、データの読み取り |
| 署名者 | ユーザーの Web3 ウォレット | ローカルで生成された Ed25519 キー |
| キーの種類 | Ethereum 秘密鍵 | Ed25519 キーペア |
| エンドポイント | /v1/register_account, /v1/orderly_key | その他すべてのエンドポイント |
| 署名の種類 | EIP-712 型付きデータ | 生の Ed25519 + base64url |
| スコープ | API キーの作成/管理 | API キーを使った取引 |
関連スキル
- orderly-trading-orders - 認証済みエンドポイントの使用
- orderly-websocket-streaming - WebSocket 認証
- orderly-sdk-react-hooks - 認証を簡略化する React SDK
- orderly-deposit-withdraw - アカウントへの資金入金