mcpField
O
orderly-api-authentication

orderly-api-authentication

github.com/orderlynetwork安全276 次安装1 周前更新
代码仓库

关于 orderly-api-authentication

面向 Orderly Network 开发者的认证技能,说明 EVM 与 Solana 钱包注册、API 密钥管理及请求签名流程,涵盖两层认证机制。

主要功能

  1. 01EVM 钱包的 EIP-712 签名
  2. 02Solana 钱包消息签名
  3. 03Ed25519 API 请求认证
  4. 04账户注册与 API 密钥管理
  5. 05读取、交易和资产权限范围

适用场景

  1. 01为 EVM 或 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 调用
  • 了解双层身份验证流程
  • 调试签名问题

前提条件

  • Web3 钱包(EVM 使用 MetaMask、WalletConnect;Solana 使用 Phantom、Solflare)
  • Broker ID(例如 woofi_dex,或你自己的 ID)
  • 已安装 Node.js 18+(用于编程方式操作)
  • 了解 EIP-712 类型化数据签名(EVM)、Ed25519 消息签名(Solana)以及 Ed25519 密码学

身份验证概览

Orderly Network 使用支持 EVM 和 Solana 钱包的双层身份验证系统:

┌─────────────────────────────────────────────────────────────┐
│  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
主网https://api.orderly.orgwss://ws.orderly.org/ws/stream
测试网https://testnet-api.orderly.orgwss://testnet-ws.orderly.org/ws/stream

注意:这些 API 基础 URL 同时适用于 EVM 和 Solana 钱包。Orderly 的 API 是全链通用的——相同的端点可处理这两条链。

获取支持的链

不要将链 ID 硬编码。请动态获取适用于你的 broker 的链:

// 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 根据操作类型使用两种不同的 EIP-712 域:

域类型使用场景主网测试网
链下账户注册、API 密钥管理0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
链上提现、内部转账、结算 PnL0x6F7a338F2aA472838dEFD3283eB360d4Dff5D2030x1826B75e2ef249173FC735149AE4B8e9ea10abff

重要:链上 verifyingContract 是 Orderly L2 上的 Ledger 合约。所有链共用此合约(而非每条链各自一个)。每条受支持的 EVM 链上都有用于存款的 Vault 合约,但 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 密钥
  • 提现:请求从金库提取代币
  • 管理操作:设置 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)

创建账户后,需要注册 Ed25519 密钥以访问 API。

生成 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 密钥消息

通过 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 密钥

注册 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 密钥权限范围

注册 API 密钥时,请指定权限:

权限范围权限
read读取持仓、订单和余额
trading下单、取消和修改订单
asset存款、提现和内部转账

多个权限范围可以用逗号分隔并组合:'read,trading,asset'

移除 Orderly 密钥

要移除密钥(需要使用另一个有效密钥进行 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 密钥)

生成 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 密钥消息
// 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 密钥
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
  }),
});

结算 PnL 签名

// 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 金库地址验证合约
主网900900900mainnet-betaErBmAD61mGFKvrFNaTJuxoPwqrS8GgtwtqJTJVjFWx9Q0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203
测试网901901901devnet9shwxWDUNhtwkHocsUAmrNAQfBH2DHh4njdAEdHZZkF20x1826B75e2ef249173FC735149AE4B8e9ea10abff

注意:EVM 和 Solana 使用相同的 API 基础 URL。请参阅本技能顶部的环境配置部分。

重要差异

账户 ID 生成
  • EVM:keccak256(address, keccak256(brokerId))
  • Solana:由 /v1/get_account API 返回(不是哈希值)
消息签名
  • EVM:使用 eth_signTypedData_v4 和结构化 EIP-712 类型
  • 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 身份验证

通过钱包签名注册 Ed25519 密钥后(EVM 使用 EIP-712,Solana 使用 Ed25519 消息签名),您可以使用该密钥执行所有 API 操作。

必需的标头

标头说明
orderly-timestampUnix 时间戳,单位为毫秒
orderly-account-id你的 Orderly 账户 ID
orderly-key添加 ed25519: 前缀的公钥
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,以提取路径名和查询参数
  • 根据 HTTP 方法正确设置 Content-Type(GET/DELETE 使用 application/x-www-form-urlencoded,其他方法使用 application/json)
  • 使用时间戳 + 方法 + 路径名 + 查询参数 + 请求正文构造签名消息
  • 返回 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 会被解析,以提取路径名和查询参数:

// 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 已过期”错误

  • Nonce 仅在 2 分钟内有效
  • 获取新的 nonce 并重试

“账户已存在”错误

  • 该钱包已在此经纪商处注册
  • 使用 /v1/get_account 获取现有账户信息

“签名无效”错误

  • 确保 EIP-712 域完全匹配(name、version、chainId、verifyingContract)
  • 检查链 ID 是否与网络匹配
  • 确认消息结构与类型一致
  • 使用 eth_signTypedData_v4,而不是 eth_signTypedData
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 - 为账户注资

评论

支持 Markdown