mcpField
O
orderly-api-authentication

orderly-api-authentication

github.com/orderlynetworkセキュリティ276 回インストール1 週間前に更新
リポジトリ

orderly-api-authentication について

Orderly Network の開発者向け認証スキルです。EVM と Solana のウォレット登録、API キー管理、リクエスト署名を扱い、アカウント操作と API 呼び出しの二層認証を説明します。

主な機能

  1. 01EVM ウォレットの EIP-712 署名
  2. 02Solana ウォレットのメッセージ署名
  3. 03Ed25519 による API リクエスト認証
  4. 04アカウント登録と API キー管理
  5. 05読み取り・取引・資産の権限範囲

活用シーン

  1. 01EVM または Solana ユーザーの Orderly アカウント登録
  2. 02取引ボット用 API キーの設定
  3. 03直接 API 呼び出しの認証実装
  4. 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 ベース URLWebSocket URL
Mainnethttps://api.orderly.orgwss://ws.orderly.org/ws/stream
Testnethttps://testnet-api.orderly.orgwss://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 ドメインを使用します。

ドメインの種類用途MainnetTestnet
オフチェーンアカウント登録、API キー管理0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
オンチェーン出金、内部送金、PnL の決済0x6F7a338F2aA472838dEFD3283eB360d4Dff5D2030x1826B75e2ef249173FC735149AE4B8e9ea10abff

重要:オンチェーンの 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 型付きデータプレーンなメッセージ署名
キーの種類secp256k1Ed25519(ネイティブ)
アカウントの検索/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 チェーン IDSolana クラスターOrderly Vault アドレス検証コントラクト
メインネット900900900mainnet-betaErBmAD61mGFKvrFNaTJuxoPwqrS8GgtwtqJTJVjFWx9Q0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203
テストネット901901901devnet9shwxWDUNhtwkHocsUAmrNAQfBH2DHh4njdAEdHZZkF20x1826B75e2ef249173FC735149AE4B8e9ea10abff

注:API のベース URL は EVM と Solana で共通です。このスキルの先頭にある環境設定のセクションを参照してください。

主な違い

アカウント ID の生成
  • EVM:keccak256(address, keccak256(brokerId))
  • Solana:/v1/get_account API から返されます(ハッシュではありません)
メッセージ署名
  • 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-idOrderly アカウント ID
orderly-keyed25519: を先頭に付けた公開鍵
orderly-signatureBase64url でエンコードされた 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メインネットテストネット
Arbitrum42161 / 421614✅✅
Optimism10 / 11155420✅✅
Base8453 / 84532✅✅
Ethereum1 / 11155111✅✅
Solana900900900 / 901901901✅✅
Mantle5000 / 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 - アカウントへの資金入金

コメント