orderly-api-authentication
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOrderly 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., , or your own)
woofi_dex - 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(例如,或您自定义的ID)
woofi_dex - 安装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 calls1. 用户连接钱包
2. 钱包签署EIP-712消息以注册账户
3. 创建账户ID
4. 用户生成Ed25519密钥对
5. 钱包签署EIP-712消息以授权Ed25519密钥
6. Ed25519密钥用于后续所有API请求Environment Configuration
环境配置
| Environment | API Base URL | WebSocket URL |
|---|---|---|
| Mainnet | | |
| Testnet | | |
Note: These API base URLs work for both EVM and Solana wallets. Orderly's API is omnichain - the same endpoints handle both chains.
| 环境 | API基础URL | WebSocket URL |
|---|---|---|
| 主网 | | |
| 测试网 | | |
注意:这些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 Type | Use Case | Mainnet | Testnet |
|---|---|---|---|
| Off-chain | Account registration, API key management | | |
| On-chain | Withdrawals, internal transfers, settle PnL | | |
Important: The on-chainis 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.verifyingContract
Orderly根据操作类型使用两个不同的EIP-712域:
| 域类型 | 适用场景 | 主网 | 测试网 |
|---|---|---|---|
| 链下 | 账户注册、API密钥管理 | | |
| 链上 | 提现、内部转账、平仓盈亏结算 | | |
重要提示:链上的是Orderly L2上的Ledger合约。这是一个适用于所有链的单一合约(而非每条链单独一个)。每个支持的EVM链上都有用于存款的Vault合约,但Ledger是链上操作的可信源。verifyingContract
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 ITtypescript
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:
| Scope | Permissions |
|---|---|
| Read positions, orders, balance |
| Place, cancel, modify orders |
| Deposit, withdraw, internal transfer |
Multiple scopes can be combined comma-separated:
'read,trading,asset'注册API密钥时,指定权限:
| 权限范围 | 权限说明 |
|---|---|
| 查看仓位、订单、余额 |
| 下单、撤单、修改订单 |
| 存款、提现、内部转账 |
多个权限范围可通过逗号组合:
'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认证对比
| Aspect | EVM Wallets | Solana Wallets |
|---|---|---|
| Signing Method | EIP-712 typed data | Plain message signing |
| Key Type | secp256k1 | Ed25519 (native) |
| Account Lookup | | |
| Message Format | Structured JSON types | Raw bytes via adapter |
| Signature | Ethereum signature | Ed25519 signature |
| 对比项 | EVM钱包 | Solana钱包 |
|---|---|---|
| 签署方式 | EIP-712类型数据 | 纯消息签署 |
| 密钥类型 | secp256k1 | Ed25519(原生) |
| 账户查询 | | |
| 消息格式 | 结构化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专属配置
| Environment | Solana Chain ID | Solana Cluster | Orderly Vault Address | Verifying Contract |
|---|---|---|---|---|
| Mainnet | 900900900 | | | |
| Testnet | 901901901 | | | |
Note: API base URLs are the same for EVM and Solana. See the Environment Configuration section at the top of this skill.
| 环境 | Solana链ID | Solana集群 | Orderly金库地址 | 验证合约 |
|---|---|---|---|---|
| 主网 | 900900900 | | | |
| 测试网 | 901901901 | | | |
注意:API基础URL对于EVM和Solana是相同的。请参阅本指南顶部的【环境配置】章节。
Important Differences
重要差异
Account ID Generation
账户ID生成
- EVM:
keccak256(address, keccak256(brokerId)) - Solana: Returned from API (not a hash)
/v1/get_account
- EVM:
keccak256(address, keccak256(brokerId)) - Solana:从API返回(不是哈希值)
/v1/get_account
Message Signing
消息签署
- EVM: Uses with structured EIP-712 types
eth_signTypedData_v4 - Solana: Uses raw message bytes signed with Ed25519
- EVM:使用和结构化EIP-712类型
eth_signTypedData_v4 - 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
必填请求头
| Header | Description |
|---|---|
| Unix timestamp in milliseconds |
| Your Orderly account ID |
| Your public key prefixed with |
| Base64url-encoded Ed25519 signature |
| 请求头 | 描述 |
|---|---|
| 毫秒级Unix时间戳 |
| 您的Orderly账户ID |
| 前缀为 |
| 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 ITtypescript
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 , others use
application/x-www-form-urlencoded)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:
| Scope | Permissions |
|---|---|
| Read positions, orders, balance |
| Place, cancel, modify orders |
| 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密钥时,指定权限:
| 权限范围 | 权限说明 |
|---|---|
| 查看仓位、订单、余额 |
| 下单、撤单、修改订单 |
| 存款、提现、内部转账 |
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
支持的链
| Chain | Chain ID | Mainnet | Testnet |
|---|---|---|---|
| Arbitrum | 42161 / 421614 | ✅ | ✅ |
| Optimism | 10 / 11155420 | ✅ | ✅ |
| Base | 8453 / 84532 | ✅ | ✅ |
| Ethereum | 1 / 11155111 | ✅ | ✅ |
| Solana | 900900900 / 901901901 | ✅ | ✅ |
| Mantle | 5000 / 5003 | ✅ | ✅ |
| 链 | 链ID | 主网 | 测试网 |
|---|---|---|---|
| Arbitrum | 42161 / 421614 | ✅ | ✅ |
| Optimism | 10 / 11155420 | ✅ | ✅ |
| Base | 8453 / 84532 | ✅ | ✅ |
| Ethereum | 1 / 11155111 | ✅ | ✅ |
| Solana | 900900900 / 901901901 | ✅ | ✅ |
| Mantle | 5000 / 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 to retrieve existing account info
/v1/get_account
"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 not
eth_signTypedData_v4eth_signTypedData
"Nonce过期"错误
- Nonce仅在2分钟内有效
- 获取新的Nonce并重试
"账户已存在"错误
- 该钱包已在此Broker下注册
- 使用获取现有账户信息
/v1/get_account
"无效签名"错误
- 确保EIP-712域完全匹配(name、version、chainId、verifyingContract)
- 检查chain ID与您的网络匹配
- 验证消息结构与类型匹配
- 使用而非
eth_signTypedData_v4eth_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 signingInvalid 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
认证对比
| Aspect | EIP-712 Wallet Auth | Ed25519 API Auth |
|---|---|---|
| Purpose | Account operations, key management | Trading, reading data |
| Signer | User's Web3 wallet | Locally-generated Ed25519 key |
| Key type | Ethereum private key | Ed25519 key pair |
| Endpoints | | All other endpoints |
| Signature type | EIP-712 typed data | Raw Ed25519 + base64url |
| Scope | Create/manage API keys | Use API keys for trading |
| 对比项 | EIP-712钱包认证 | Ed25519 API认证 |
|---|---|---|
| 用途 | 账户操作、密钥管理 | 交易、数据读取 |
| 签署者 | 用户的Web3钱包 | 本地生成的Ed25519密钥 |
| 密钥类型 | Ethereum私钥 | Ed25519密钥对 |
| 端点 | | 所有其他端点 |
| 签名类型 | 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 - 为账户充值