orderly-api-authentication

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Orderly Network: API Authentication

Orderly Network:API认证

This skill covers both authentication layers in Orderly Network: wallet signatures (EIP-712 for EVM, Ed25519 message signing for Solana) for account registration and key management, and Ed25519 signatures for API request authentication.
本指南涵盖Orderly Network的两层认证机制:用于账户注册和密钥管理的钱包签名(EVM使用EIP-712,Solana使用Ed25519消息签名),以及用于API请求认证的Ed25519签名

When to Use

适用场景

  • Setting up new Orderly accounts and API keys (EVM or Solana)
  • Building server-side trading bots
  • Implementing direct API calls
  • Understanding the two-layer authentication flow
  • Debugging signature issues
  • 注册新的Orderly账户和API密钥(EVM或Solana)
  • 构建服务器端交易机器人
  • 实现直接API调用
  • 理解双层认证流程
  • 调试签名相关问题

Prerequisites

前置条件

  • A Web3 wallet (MetaMask, WalletConnect for EVM; Phantom, Solflare for Solana)
  • A Broker ID (e.g.,
    woofi_dex
    , or your own)
  • Node.js 18+ installed (for programmatic usage)
  • Understanding of EIP-712 typed data signing (EVM) or Ed25519 message signing (Solana) and Ed25519 cryptography
  • Web3钱包(EVM使用MetaMask、WalletConnect;Solana使用Phantom、Solflare)
  • Broker ID(例如
    woofi_dex
    ,或您自定义的ID)
  • 安装Node.js 18+(用于程序化调用)
  • 了解EIP-712类型数据签名(EVM)或Ed25519消息签名(Solana)以及Ed25519加密算法

Authentication Overview

认证概述

Orderly Network uses a two-layer authentication system supporting both EVM and Solana wallets:
┌─────────────────────────────────────────────────────────────┐
│  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              │
└─────────────────────────────────────────────────────────────┘
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              │
└─────────────────────────────────────────────────────────────┘

Authentication Flow

认证流程

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
1. 用户连接钱包
2. 钱包签署EIP-712消息以注册账户
3. 创建账户ID
4. 用户生成Ed25519密钥对
5. 钱包签署EIP-712消息以授权Ed25519密钥
6. Ed25519密钥用于后续所有API请求

Environment Configuration

环境配置

EnvironmentAPI Base URLWebSocket 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
Note: These API base URLs work for both EVM and Solana wallets. Orderly's API is omnichain - the same endpoints handle both chains.
环境API基础URLWebSocket URL
主网
https://api.orderly.org
wss://ws.orderly.org/ws/stream
测试网
https://testnet-api.orderly.org
wss://testnet-ws.orderly.org/ws/stream
注意:这些API基础URL同时适用于EVM和Solana钱包。Orderly的API是多链兼容的——相同的端点可处理两条链的请求。

Getting Supported Chains

获取支持的链

Don't hardcode chain IDs. Fetch them dynamically for your broker:
typescript
// 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
请勿硬编码链ID,动态获取您的Broker支持的链:
typescript
// 获取您的Broker支持的链
const response = await fetch(`https://api.orderly.org/v1/public/chain_info?broker_id=${BROKER_ID}`);

const { data } = await response.json();
// data.chains包含支持的chain_id
// 将这些chain ID用于EIP-712域配置

EIP-712 Domain Configuration

EIP-712域配置

Orderly uses two different EIP-712 domains depending on the operation:
Domain TypeUse CaseMainnetTestnet
Off-chainAccount registration, API key management
0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
On-chainWithdrawals, internal transfers, settle PnL
0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203
0x1826B75e2ef249173FC735149AE4B8e9ea10abff
Important: The on-chain
verifyingContract
is the Ledger contract on Orderly L2. This is a single contract for all chains (not per-chain). Vault contracts exist on each supported EVM chain for deposits, but the Ledger is the source of truth for on-chain operations.
Orderly根据操作类型使用两个不同的EIP-712域
域类型适用场景主网测试网
链下账户注册、API密钥管理
0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
链上提现、内部转账、平仓盈亏结算
0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203
0x1826B75e2ef249173FC735149AE4B8e9ea10abff
重要提示:链上的
verifyingContract
是Orderly L2上的Ledger合约。这是一个适用于所有链的单一合约(而非每条链单独一个)。每个支持的EVM链上都有用于存款的Vault合约,但Ledger是链上操作的可信源。

Off-Chain Domain (Registration, API Keys)

链下域(注册、API密钥)

Used for operations that don't directly interact with smart contracts:
typescript
const OFFCHAIN_DOMAIN = {
  name: 'Orderly',
  version: '1',
  chainId: 421614, // Connected chain ID (e.g., Arbitrum Sepolia)
  verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
};
用于不直接与智能合约交互的操作:
typescript
const OFFCHAIN_DOMAIN = {
  name: 'Orderly',
  version: '1',
  chainId: 421614, // 连接的chain ID(例如Arbitrum Sepolia)
  verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
};

On-Chain Domain (Withdrawals, Transfers)

链上域(提现、转账)

Used for operations that interact with the Ledger contract on Orderly L2:
typescript
const ONCHAIN_DOMAIN = {
  name: 'Orderly',
  version: '1',
  chainId: 42161, // Connected chain ID
  verifyingContract: isTestnet
    ? '0x1826B75e2ef249173FC735149AE4B8e9ea10abff'
    : '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203',
};

用于与Orderly L2上的Ledger合约交互的操作:
typescript
const ONCHAIN_DOMAIN = {
  name: 'Orderly',
  version: '1',
  chainId: 42161, // 连接的chain ID
  verifyingContract: isTestnet
    ? '0x1826B75e2ef249173FC735149AE4B8e9ea10abff'
    : '0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203',
};

Part 1: EIP-712 Wallet Authentication

第一部分:EIP-712钱包认证

Wallet authentication is required for account-level operations that need proof of ownership.
对于需要所有权证明的账户级操作,必须进行钱包认证。

When to Use EIP-712

EIP-712适用场景

  • Account Registration: Creating a new Orderly account
  • API Key Management: Adding or removing Ed25519 API keys
  • Withdrawals: Requesting token withdrawals from the vault
  • Admin Operations: Setting IP restrictions, managing account settings
  • 账户注册:创建新的Orderly账户
  • API密钥管理:添加或删除Ed25519 API密钥
  • 提现:从金库申请代币提现
  • 管理员操作:设置IP限制、管理账户设置

Account Registration

账户注册

Step 1: Check Existing Account

步骤1:检查现有账户

Before registration, verify if the wallet already has an account:
typescript
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
注册前,验证钱包是否已关联账户:
typescript
const BROKER_ID = 'woofi_dex'; // 您的Broker ID
const walletAddress = '0x...'; // 用户的钱包地址

const response = await fetch(
  `https://testnet-api.orderly.org/v1/get_account?broker_id=${BROKER_ID}&user_address=${walletAddress}`
);

const data = await response.json();
// 如果data.success为true,说明账户已存在
// 否则,继续注册

Step 2: Fetch Registration Nonce

步骤2:获取注册Nonce

Retrieve a unique nonce required for registration (valid for 2 minutes):
typescript
const nonceResponse = await fetch('https://testnet-api.orderly.org/v1/registration_nonce');
const { data: nonce } = await nonceResponse.json();
console.log('Registration nonce:', nonce);
获取注册所需的唯一Nonce(有效期2分钟):
typescript
const nonceResponse = await fetch('https://testnet-api.orderly.org/v1/registration_nonce');
const { data: nonce } = await nonceResponse.json();
console.log('注册Nonce:', nonce);

Step 3: Sign Registration Message

步骤3:签署注册消息

Create and sign an EIP-712 typed message:
typescript
// 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',
    },
  ],
});
创建并签署EIP-712类型消息:
typescript
// 注册消息类型
const REGISTRATION_TYPES = {
  Registration: [
    { name: 'brokerId', type: 'string' },
    { name: 'chainId', type: 'uint256' },
    { name: 'timestamp', type: 'uint64' },
    { name: 'registrationNonce', type: 'uint256' },
  ],
};

// 创建消息
const registerMessage = {
  brokerId: BROKER_ID,
  chainId: 421614,
  timestamp: Date.now(),
  registrationNonce: nonce,
};

// 使用钱包签署(例如MetaMask)- 注册时使用OFFCHAIN_DOMAIN
const signature = await window.ethereum.request({
  method: 'eth_signTypedData_v4',
  params: [
    walletAddress,
    {
      types: REGISTRATION_TYPES,
      domain: OFFCHAIN_DOMAIN,
      message: registerMessage,
      primaryType: 'Registration',
    },
  ],
});

Step 4: Submit Registration

步骤4:提交注册

Send the signed payload to create the Orderly Account ID:
typescript
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
发送签署后的负载以创建Orderly账户ID:
typescript
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('账户ID:', result.data.account_id);
// 保存此账户ID - API认证时需要使用

API Key Management (Orderly Key)

API密钥管理(Orderly密钥)

Once you have an account, you need to register Ed25519 keys for API access.
拥有账户后,您需要注册Ed25519密钥以进行API访问。

Generate Ed25519 Key Pair

生成Ed25519密钥对

typescript
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
typescript
import { getPublicKeyAsync, utils } from '@noble/ed25519';

// 生成32字节私钥(加密安全)
const privateKey = utils.randomPrivateKey();

// 推导公钥
const publicKey = await getPublicKeyAsync(privateKey);

// 将公钥编码为base58格式(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)}`;

// 将字节转换为十六进制字符串以便存储
function bytesToHex(bytes: Uint8Array): string {
  return Array.from(bytes)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
}

console.log('Orderly密钥:', orderlyKey);
console.log('私钥(十六进制):', bytesToHex(privateKey));
// 安全保存私钥 - 切勿分享

Sign Add Orderly Key Message

签署添加Orderly密钥消息

Associate the Ed25519 key with your account via EIP-712:
typescript
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',
    },
  ],
});
通过EIP-712将Ed25519密钥与您的账户关联:
typescript
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', // 权限:read、trading、asset(逗号分隔)
  timestamp: Date.now(),
  expiration: Date.now() + 31536000000, // 1年后过期
};

// API密钥管理使用OFFCHAIN_DOMAIN
const addKeySignature = await window.ethereum.request({
  method: 'eth_signTypedData_v4',
  params: [
    walletAddress,
    {
      types: ADD_KEY_TYPES,
      domain: OFFCHAIN_DOMAIN,
      message: addKeyMessage,
      primaryType: 'AddOrderlyKey',
    },
  ],
});

Submit Orderly Key

提交Orderly密钥

Register the API key:
typescript
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);
注册API密钥:
typescript
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('密钥已注册:', keyResult.success);

Orderly Key Scopes

Orderly密钥权限范围

When registering an API key, specify permissions:
ScopePermissions
read
Read positions, orders, balance
trading
Place, cancel, modify orders
asset
Deposit, withdraw, internal transfer
Multiple scopes can be combined comma-separated:
'read,trading,asset'
注册API密钥时,指定权限:
权限范围权限说明
read
查看仓位、订单、余额
trading
下单、撤单、修改订单
asset
存款、提现、内部转账
多个权限范围可通过逗号组合:
'read,trading,asset'

Remove Orderly Key

删除Orderly密钥

To remove a key (requires Ed25519 authentication with another valid key):
typescript
// 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
    }),
  }
);

删除密钥(需要使用另一个有效密钥进行Ed25519认证):
typescript
// POST /v1/client/remove_orderly_key
const removeResponse = await signAndSendRequest(
  accountId,
  privateKey, // 必须是另一个有效的密钥
  'https://api.orderly.org/v1/client/remove_orderly_key',
  {
    method: 'POST',
    body: JSON.stringify({
      orderly_key: 'ed25519:...', // 要删除的密钥
    }),
  }
);

Solana Wallet Authentication

Solana钱包认证

Solana wallets use native Ed25519 message signing (not EIP-712) for account operations. Solana wallets already use Ed25519 keys natively, making the signing process simpler but requiring different message formatting.
Solana钱包使用原生Ed25519消息签名(而非EIP-712)进行账户操作。Solana钱包本身就原生使用Ed25519密钥,因此签署流程更简单,但需要不同的消息格式。

Solana vs EVM Authentication

Solana与EVM认证对比

AspectEVM WalletsSolana Wallets
Signing MethodEIP-712 typed dataPlain message signing
Key Typesecp256k1Ed25519 (native)
Account Lookup
/v1/get_account
/v1/get_account?chain_type=SOL
Message FormatStructured JSON typesRaw bytes via adapter
SignatureEthereum signatureEd25519 signature
对比项EVM钱包Solana钱包
签署方式EIP-712类型数据纯消息签署
密钥类型secp256k1Ed25519(原生)
账户查询
/v1/get_account
/v1/get_account?chain_type=SOL
消息格式结构化JSON类型原始字节通过适配器处理
签名Ethereum签名Ed25519签名

Account Lookup

账户查询

Check if a Solana wallet already has an Orderly account:
typescript
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账户:
typescript
import { PublicKey } from '@solana/web3.js';

const BROKER_ID = 'woofi_dex';
const solanaAddress = '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU'; // Base58格式地址

// Solana账户需要chain_type=SOL参数
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包含Orderly账户ID
// 账户ID格式与EVM不同(不是keccak256哈希)

Message Signing with Solana Adapter

使用Solana适配器进行消息签署

Orderly provides a Solana adapter to generate properly formatted messages:
typescript
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
  },
});
Orderly提供Solana适配器以生成格式正确的消息:
typescript
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';

// 设置钱包适配器
const walletAdapter = new DefaultSolanaWalletAdapter();

// 使用钱包详情初始化
walletAdapter.active({
  address: solanaAddress,
  provider: {
    connection: new Connection(clusterApiUrl('devnet')), // 或'mainnet-beta'
    signMessage: async (msg: Uint8Array) => {
      // 使用Solana钱包签署(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链ID
  },
});

Registration Flow

注册流程

Step 1: Fetch Registration Nonce

步骤1:获取注册Nonce

typescript
const nonceResponse = await fetch('https://testnet-api.orderly.org/v1/registration_nonce');
const { data: nonce } = await nonceResponse.json();
typescript
const nonceResponse = await fetch('https://testnet-api.orderly.org/v1/registration_nonce');
const { data: nonce } = await nonceResponse.json();

Step 2: Generate and Sign Registration Message

步骤2:生成并签署注册消息

typescript
// 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);
typescript
// 使用适配器生成注册消息
const registerMessage = await walletAdapter.generateRegisterMessage({
  brokerId: BROKER_ID,
  timestamp: Date.now(),
  registrationNonce: nonce,
});

// 使用Solana钱包签署(原始消息字节,而非EIP-712)
const signature = await wallet.signMessage(registerMessage.message);

Step 3: Submit Registration

步骤3:提交注册

typescript
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);
typescript
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', // Solana必填
  }),
});

const result = await registerResponse.json();
console.log('账户ID:', result.data.account_id);

API Key Management (Orderly Key)

API密钥管理(Orderly密钥)

Generate Ed25519 Key Pair

生成Ed25519密钥对

Same as EVM - locally generate an Ed25519 key pair:
typescript
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)}`;
与EVM流程相同——本地生成Ed25519密钥对:
typescript
import { getPublicKeyAsync, utils } from '@noble/ed25519';
import bs58 from 'bs58';

// 生成密钥对
const privateKey = utils.randomPrivateKey();
const publicKey = await getPublicKeyAsync(privateKey);
const orderlyKey = `ed25519:${bs58.encode(publicKey)}`;

Sign Add Orderly Key Message

签署添加Orderly密钥消息

typescript
// 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);
typescript
// 使用适配器生成添加密钥消息
const addKeyMessage = await walletAdapter.generateAddKeyMessage({
  brokerId: BROKER_ID,
  orderlyKey: orderlyKey,
  scope: 'read,trading',
  timestamp: Date.now(),
  expiration: Date.now() + 31536000000, // 1年
});

// 使用Solana钱包签署
const signature = await wallet.signMessage(addKeyMessage.message);

Submit Orderly Key

提交Orderly密钥

typescript
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',
  }),
});
typescript
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',
  }),
});

Withdrawal Signing

提现签署

Withdrawals require wallet signature on both EVM and Solana:
typescript
// 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
  }),
});
EVM和Solana的提现都需要钱包签署:
typescript
// 获取提现Nonce
const nonceRes = await fetch(`${BASE_URL}/v1/withdraw_nonce`);
const {
  data: { withdraw_nonce },
} = await nonceRes.json();

// 生成提现消息
const withdrawMessage = await walletAdapter.generateWithdrawMessage({
  brokerId: BROKER_ID,
  receiver: solanaAddress,
  token: 'USDC',
  amount: '1000',
  timestamp: Date.now(),
  nonce: Number(withdraw_nonce),
});

// 使用Solana钱包签署
const signature = await wallet.signMessage(withdrawMessage.message);

// 提交提现请求
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', // 主网
    // verifyingContract: '0x1826B75e2ef249173FC735149AE4B8e9ea10abff', // 测试网
  }),
});

Settle PnL Signing

平仓盈亏结算签署

typescript
// 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',
  }),
});
typescript
// 获取结算Nonce
const nonceRes = await fetch(`${BASE_URL}/v1/settle_nonce`);
const {
  data: { settle_nonce },
} = await nonceRes.json();

// 生成结算消息
const settleMessage = await walletAdapter.generateSettleMessage({
  brokerId: BROKER_ID,
  timestamp: Date.now(),
  settlePnlNonce: settle_nonce,
});

// 使用Solana钱包签署
const signature = await wallet.signMessage(settleMessage.message);

// 提交结算请求
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-Specific Configuration

Solana专属配置

EnvironmentSolana Chain IDSolana ClusterOrderly Vault AddressVerifying Contract
Mainnet900900900
mainnet-beta
ErBmAD61mGFKvrFNaTJuxoPwqrS8GgtwtqJTJVjFWx9Q
0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203
Testnet901901901
devnet
9shwxWDUNhtwkHocsUAmrNAQfBH2DHh4njdAEdHZZkF2
0x1826B75e2ef249173FC735149AE4B8e9ea10abff
Note: API base URLs are the same for EVM and Solana. See the Environment Configuration section at the top of this skill.
环境Solana链IDSolana集群Orderly金库地址验证合约
主网900900900
mainnet-beta
ErBmAD61mGFKvrFNaTJuxoPwqrS8GgtwtqJTJVjFWx9Q
0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203
测试网901901901
devnet
9shwxWDUNhtwkHocsUAmrNAQfBH2DHh4njdAEdHZZkF2
0x1826B75e2ef249173FC735149AE4B8e9ea10abff
注意:API基础URL对于EVM和Solana是相同的。请参阅本指南顶部的【环境配置】章节。

Important Differences

重要差异

Account ID Generation

账户ID生成

  • EVM:
    keccak256(address, keccak256(brokerId))
  • Solana: Returned from
    /v1/get_account
    API (not a hash)
  • EVM
    keccak256(address, keccak256(brokerId))
  • Solana:从
    /v1/get_account
    API返回(不是哈希值)

Message Signing

消息签署

  • EVM: Uses
    eth_signTypedData_v4
    with structured EIP-712 types
  • Solana: Uses raw message bytes signed with Ed25519
  • EVM:使用
    eth_signTypedData_v4
    和结构化EIP-712类型
  • Solana:使用Ed25519签署原始消息字节

No Domain Separator

无域分隔符

Solana doesn't use EIP-712 domain configuration:
typescript
// EVM - requires domain
domain: {
  name: 'Orderly',
  version: '1',
  chainId: 42161,
  verifyingContract: '0x...',
}

// Solana - no domain, just raw message
const message = await walletAdapter.generateRegisterMessage({...});

Solana不使用EIP-712域配置:
typescript
// EVM - 需要域
domain: {
  name: 'Orderly',
  version: '1',
  chainId: 42161,
  verifyingContract: '0x...',
}

// Solana - 无域,仅原始消息
const message = await walletAdapter.generateRegisterMessage({...});

Part 2: Ed25519 API Authentication

第二部分:Ed25519 API认证

Once you have registered an Ed25519 key via wallet signing (EIP-712 for EVM or Ed25519 message signing for Solana), you use that key for all API operations.
通过钱包签署(EVM使用EIP-712或Solana使用Ed25519消息签署)注册Ed25519密钥后,您即可使用该密钥进行所有API操作。

Required Headers

必填请求头

HeaderDescription
orderly-timestamp
Unix timestamp in milliseconds
orderly-account-id
Your Orderly account ID
orderly-key
Your public key prefixed with
ed25519:
orderly-signature
Base64url-encoded Ed25519 signature
请求头描述
orderly-timestamp
毫秒级Unix时间戳
orderly-account-id
您的Orderly账户ID
orderly-key
前缀为
ed25519:
的公钥
orderly-signature
Base64url编码的Ed25519签名

Generating Ed25519 Key Pair

生成Ed25519密钥对

typescript
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
typescript
import { getPublicKeyAsync, utils } from '@noble/ed25519';

// 生成私钥(32字节加密安全随机字节)
const privateKey = utils.randomPrivateKey();

// 推导公钥
const publicKey = await getPublicKeyAsync(privateKey);

// 将公钥编码为base58格式(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;
  }

  // 处理前导零
  for (const byte of bytes) {
    if (byte === 0) {
      result = '1' + result;
    } else {
      break;
    }
  }

  return result;
}

const orderlyKey = `ed25519:${encodeBase58(publicKey)}`;

// 将字节转换为十六进制字符串(兼容浏览器和Node.js)
function bytesToHex(bytes: Uint8Array): string {
  return Array.from(bytes)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
}

console.log('私钥(十六进制):', bytesToHex(privateKey));
console.log('公钥(base58):', orderlyKey);

// 安全保存私钥 - 切勿分享

Signing Requests

签署请求

Message Construction

消息构造

typescript
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);
typescript
function buildSignMessage(timestamp: number, method: string, path: string, body?: string): string {
  // 消息格式:timestamp + method + path + body
  // 注意:各部分之间无空格或分隔符
  return `${timestamp}${method}${path}${body || ''}`;
}

// 示例
const timestamp = Date.now();

// GET请求(无请求体)
const getMessage = buildSignMessage(timestamp, 'GET', '/v1/positions');

// POST请求(有请求体)
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);

Creating the Signature

创建签名

typescript
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(/=+$/, '');
}
typescript
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);

  // 使用Ed25519签署
  const signatureBytes = await signAsync(new TextEncoder().encode(message), privateKey);

  // 编码为base64url(不是base64)
  // 先转换为base64,再转换为URL安全格式
  const base64 = btoa(String.fromCharCode(...signatureBytes));
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

Sign and Send Request Helper

签署并发送请求工具函数

For a simple, standalone authentication helper that always works correctly with query parameters and proper Content-Type headers:
typescript
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 ?? {}),
  });
}
This helper function:
  • Properly parses the URL to extract both pathname and search (query) parameters
  • Correctly sets Content-Type based on HTTP method (GET/DELETE use
    application/x-www-form-urlencoded
    , others use
    application/json
    )
  • Constructs the signature message with timestamp + method + pathname + search + body
  • Returns the fetch response for further processing
以下是一个简单的独立认证工具函数,可正确处理查询参数和合适的Content-Type请求头:
typescript
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
  • 构造签名消息:timestamp + method + pathname + search + body
  • 返回fetch响应以供后续处理

Usage Examples

使用示例

typescript
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' }
);
typescript
const baseUrl = 'https://api.orderly.org';
const accountId = '0x123...';
const privateKey = new Uint8Array(32); // 您的私钥

// 带查询参数的GET请求
const positions = await signAndSendRequest(accountId, privateKey, `${baseUrl}/v1/positions`);
const positionsData = await positions.json();

// 带查询参数的GET请求
const orders = await signAndSendRequest(
  accountId,
  privateKey,
  `${baseUrl}/v1/orders?symbol=PERP_ETH_USDC&status=INCOMPLETE`
);
const ordersData = await orders.json();

// 带请求体的POST请求
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请求
const cancel = await signAndSendRequest(
  accountId,
  privateKey,
  `${baseUrl}/v1/order?order_id=123&symbol=PERP_ETH_USDC`,
  { method: 'DELETE' }
);

Error Handling Helper

错误处理工具函数

typescript
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;
}
typescript
class OrderlyApiError extends Error {
  code: number;
  details: any;

  constructor(response: any) {
    super(response.message || 'API错误');
    this.code = response.code;
    this.details = response;
  }
}

// 带错误处理的使用方式
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;
}

Query Parameters

查询参数

Query parameters must be included in the signature message. The URL is parsed to extract both pathname and search parameters:
typescript
// 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!
查询参数必须包含在签名消息中。URL会被解析以提取路径名和查询参数:
typescript
// 正确方式 - 查询参数从URL中解析
const url = new URL('/v1/orders?symbol=PERP_ETH_USDC&status=INCOMPLETE', baseUrl);
// 消息:timestamp + method + pathname + search
// 结果:"1234567890123GET/v1/orders?symbol=PERP_ETH_USDC&status=INCOMPLETE"

// 错误方式 - 签署后单独添加查询参数
const path = '/v1/orders';
const signature = await sign(timestamp, 'GET', path);
const url = `${path}?symbol=PERP_ETH_USDC`; // 签名不匹配!

Common Errors

常见错误

Signature Mismatch (Code 10016)

签名不匹配(错误码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)
原因:签名与预期值不匹配

检查项:
1. 消息格式:timestamp + method + path + body(无空格)
2. 请求方法为大写:GET、POST、DELETE、PUT
3. 路径包含查询参数
4. 请求体为精确的JSON字符串(空格一致)
5. 签名为base64url编码(而非base64)

Timestamp Expired (Code 10017)

时间戳过期(错误码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
原因:时间戳过于陈旧或超前

解决方案:
- 确保服务器时钟同步
- 时间戳必须在±30秒范围内
- 在签署前立即生成时间戳

Invalid Orderly Key (Code 10019)

无效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
原因:公钥格式错误

解决方案:
- 必须以'ed25519:'为前缀
- 公钥必须为base58编码
- 密钥必须已注册到账户

Orderly Key Scopes

Orderly密钥权限范围

When registering an API key, specify permissions:
ScopePermissions
read
Read positions, orders, balance
trading
Place, cancel, modify orders
asset
Deposit, withdraw, internal transfer
typescript
// 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
};
注册API密钥时,指定权限:
权限范围权限说明
read
查看仓位、订单、余额
trading
下单、撤单、修改订单
asset
存款、提现、内部转账
typescript
// 通过EIP-712签署添加密钥时
const addKeyMessage = {
  brokerId: 'woofi_dex',
  chainId: 42161,
  orderlyKey: 'ed25519:...',
  scope: 'read,trading', // 多个权限范围逗号分隔
  timestamp: Date.now(),
  expiration: Date.now() + 31536000000, // 1年
};

Security Best Practices

安全最佳实践

Store Private Keys Securely

安全存储私钥

typescript
// 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.
typescript
// 切勿硬编码私钥
// 错误示例:
const privateKey = new Uint8Array([1, 2, 3, ...]);

// 正确示例:从环境变量加载
const privateKeyHex = process.env.ORDERLY_PRIVATE_KEY;
// 将十六进制字符串转换为Uint8Array(兼容浏览器和Node.js)
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);

// 更佳方案:使用安全密钥管理服务
// 例如AWS KMS、HashiCorp Vault等

Key Rotation

密钥轮换

Rotate your API keys periodically for security:
typescript
// 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}`);
}
定期轮换API密钥以提升安全性:
typescript
// 生成新的密钥对
const newPrivateKey = utils.randomPrivateKey();
const newPublicKey = await getPublicKeyAsync(newPrivateKey);

// 注册新密钥(需要通过EIP-712进行钱包签署)
// POST /v1/orderly_key - 不需要Ed25519认证
const orderlyKey = `ed25519:${encodeBase58(newPublicKey)}`;
const timestamp = Date.now();
const expiration = timestamp + 31536000000; // 1年

const addKeyMessage = {
  brokerId: 'your_broker_id',
  chainId: 42161, // Arbitrum主网
  orderlyKey: orderlyKey,
  scope: 'read,trading', // 逗号分隔的权限范围
  timestamp: timestamp,
  expiration: expiration,
};

// 使用钱包签署(EIP-712)
const addKeySignature = await wallet.signTypedData({
  domain: {
    name: 'Orderly',
    version: '1',
    chainId: 42161,
    verifyingContract: '0x...', // 合约地址
  },
  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(`注册新密钥失败: ${registerResult.message}`);
}

// 更新应用配置
config.privateKey = newPrivateKey;
config.orderlyKey = orderlyKey;

// 使用已认证请求删除旧密钥
// POST /v1/client/remove_orderly_key - 需要Ed25519认证
const oldOrderlyKey = `ed25519:${encodeBase58(oldPublicKey)}`;
const removeResponse = await signAndSendRequest(
  accountId,
  newPrivateKey, // 使用新密钥进行认证
  '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(`删除旧密钥失败: ${removeResult.message}`);
}

IP Restrictions

IP限制

typescript
// 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:...' }
typescript
// 为密钥设置IP限制
POST /v1/client/set_orderly_key_ip_restriction
请求体: {
  orderly_key: 'ed25519:...',
  ip_list: ['1.2.3.4', '5.6.7.8'],
}

// 获取当前限制
GET /v1/client/orderly_key_ip_restriction?orderly_key={key}

// 重置(移除)限制
POST /v1/client/reset_orderly_key_ip_restriction
请求体: { orderly_key: 'ed25519:...' }

WebSocket Authentication

WebSocket认证

WebSocket also requires Ed25519 authentication:
typescript
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,
      },
    })
  );
};
WebSocket同样需要Ed25519认证:
typescript
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);

  // 转换为base64url格式(兼容浏览器和Node.js)
  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,
      },
    })
  );
};

Testing Authentication

测试认证

typescript
// 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
  }
}
typescript
// 验证密钥是否有效
GET /v1/get_orderly_key?orderly_key={key}

// 响应
{
  "success": true,
  "data": {
    "account_id": "0x...",
    "valid": true,
    "scope": "read,trading",
    "expires_at": 1735689600000
  }
}

Supported Chains

支持的链

ChainChain IDMainnetTestnet
Arbitrum42161 / 421614
Optimism10 / 11155420
Base8453 / 84532
Ethereum1 / 11155111
Solana900900900 / 901901901
Mantle5000 / 5003
链ID主网测试网
Arbitrum42161 / 421614
Optimism10 / 11155420
Base8453 / 84532
Ethereum1 / 11155111
Solana900900900 / 901901901
Mantle5000 / 5003

Common Issues

常见问题

EIP-712 Errors

EIP-712错误

"Nonce expired" error
  • Nonces are valid for 2 minutes only
  • Fetch a new nonce and retry
"Account already exists" error
  • The wallet is already registered with this broker
  • Use
    /v1/get_account
    to retrieve existing account info
"Invalid signature" error
  • Ensure the EIP-712 domain matches exactly (name, version, chainId, verifyingContract)
  • Check chain ID matches your network
  • Verify the message structure matches the types
  • Use
    eth_signTypedData_v4
    not
    eth_signTypedData
"Nonce过期"错误
  • Nonce仅在2分钟内有效
  • 获取新的Nonce并重试
"账户已存在"错误
  • 该钱包已在此Broker下注册
  • 使用
    /v1/get_account
    获取现有账户信息
"无效签名"错误
  • 确保EIP-712域完全匹配(name、version、chainId、verifyingContract)
  • 检查chain ID与您的网络匹配
  • 验证消息结构与类型匹配
  • 使用
    eth_signTypedData_v4
    而非
    eth_signTypedData

Ed25519 Errors

Ed25519错误

Signature Mismatch (Code 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)
Timestamp Expired (Code 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
Invalid Orderly Key (Code 10019)
Cause: Public key format incorrect

Solution:
- Must be prefixed with 'ed25519:'
- Public key must be base58 encoded
- Key must be registered to account
签名不匹配(错误码10016)
原因:签名与预期值不匹配

检查项:
1. 消息格式:timestamp + method + path + body(无空格)
2. 请求方法为大写:GET、POST、DELETE、PUT
3. 路径包含查询参数
4. 请求体为精确的JSON字符串(空格一致)
5. 签名为base64url编码(而非base64)
时间戳过期(错误码10017)
原因:时间戳过于陈旧或超前

解决方案:
- 确保服务器时钟同步
- 时间戳必须在±30秒范围内
- 在签署前立即生成时间戳
无效Orderly密钥(错误码10019)
原因:公钥格式错误

解决方案:
- 必须以'ed25519:'为前缀
- 公钥必须为base58编码
- 密钥必须已注册到账户

Authentication Comparison

认证对比

AspectEIP-712 Wallet AuthEd25519 API Auth
PurposeAccount operations, key managementTrading, reading data
SignerUser's Web3 walletLocally-generated Ed25519 key
Key typeEthereum private keyEd25519 key pair
Endpoints
/v1/register_account
,
/v1/orderly_key
All other endpoints
Signature typeEIP-712 typed dataRaw Ed25519 + base64url
ScopeCreate/manage API keysUse API keys for trading
对比项EIP-712钱包认证Ed25519 API认证
用途账户操作、密钥管理交易、数据读取
签署者用户的Web3钱包本地生成的Ed25519密钥
密钥类型Ethereum私钥Ed25519密钥对
端点
/v1/register_account
,
/v1/orderly_key
所有其他端点
签名类型EIP-712类型数据原始Ed25519 + base64url
范围创建/管理API密钥使用API密钥进行交易

Related Skills

相关指南

  • orderly-trading-orders - Using authenticated endpoints
  • orderly-websocket-streaming - WebSocket authentication
  • orderly-sdk-react-hooks - React SDK for simplified auth
  • orderly-deposit-withdraw - Fund your account
  • orderly-trading-orders - 使用已认证端点
  • orderly-websocket-streaming - WebSocket认证
  • orderly-sdk-react-hooks - 用于简化认证的React SDK
  • orderly-deposit-withdraw - 为账户充值