Loading...
Loading...
Compare original and translation side by side
| Environment | URL |
|---|---|
| Mainnet Public | |
| Mainnet Private | |
| Testnet Public | |
| Testnet Private | |
| 环境 | URL |
|---|---|
| 主网公开环境 | |
| 主网私有环境 | |
| 测试网公开环境 | |
| 测试网私有环境 | |
1. Connect to WebSocket endpoint
2. Authenticate (for private streams)
3. Subscribe to topics
4. Receive real-time messages
5. Handle reconnection/heartbeat1. 连接到WebSocket端点
2. 完成身份验证(私有数据流)
3. 订阅主题
4. 接收实时消息
5. 处理重连/心跳机制const ws = new WebSocket('wss://ws.orderly.org/ws/stream');
ws.onopen = () => {
// Subscribe to orderbook
ws.send(
JSON.stringify({
id: 'sub_ob_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@orderbook',
})
);
// Subscribe to trades
ws.send(
JSON.stringify({
id: 'sub_trades_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@trade',
})
);
// Subscribe to 24h ticker
ws.send(
JSON.stringify({
id: 'sub_ticker_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@24hrTicker',
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};const ws = new WebSocket('wss://ws.orderly.org/ws/stream');
ws.onopen = () => {
// 订阅订单簿
ws.send(
JSON.stringify({
id: 'sub_ob_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@orderbook',
})
);
// 订阅交易数据
ws.send(
JSON.stringify({
id: 'sub_trades_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@trade',
})
);
// 订阅24小时行情
ws.send(
JSON.stringify({
id: 'sub_ticker_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@24hrTicker',
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};| Topic | Description |
|---|---|
| Full orderbook snapshot |
| Incremental updates |
| Trade executions |
| Kline/candlestick data |
| Mark price updates |
| Index price updates |
| 24h statistics |
| All symbols mark prices |
| All symbols index prices |
| 主题 | 描述 |
|---|---|
| 完整订单簿快照 |
| 增量更新 |
| 交易执行数据 |
| K线/蜡烛图数据 |
| 标记价格更新 |
| 指数价格更新 |
| 24小时统计数据 |
| 所有交易对的标记价格 |
| 所有交易对的指数价格 |
interface OrderbookMessage {
topic: string;
ts: number; // Timestamp
data: {
asks: [string, string][]; // [price, quantity]
bids: [string, string][];
prevTs: number; // Previous timestamp for sequencing
};
}
// Subscribe
{ "event": "subscribe", "topic": "PERP_ETH_USDC@orderbookupdate" }
// Maintain local orderbook
let orderbook = { asks: [], bids: [] };
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.topic?.includes('orderbookupdate')) {
// Apply incremental updates
for (const [price, qty] of msg.data.asks) {
updateLevel(orderbook.asks, price, qty);
}
for (const [price, qty] of msg.data.bids) {
updateLevel(orderbook.bids, price, qty);
}
}
};
function updateLevel(levels: [string, string][], price: string, qty: string) {
const idx = levels.findIndex(l => l[0] === price);
if (qty === '0') {
// Remove level
if (idx !== -1) levels.splice(idx, 1);
} else {
// Update or insert
if (idx !== -1) {
levels[idx] = [price, qty];
} else {
levels.push([price, qty]);
// Sort (asks ascending, bids descending)
levels.sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]));
}
}
}interface OrderbookMessage {
topic: string;
ts: number; // 时间戳
data: {
asks: [string, string][]; // [价格, 数量]
bids: [string, string][];
prevTs: number; // 用于排序的上一个时间戳
};
}
// 订阅
{ "event": "subscribe", "topic": "PERP_ETH_USDC@orderbookupdate" }
// 维护本地订单簿
let orderbook = { asks: [], bids: [] };
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.topic?.includes('orderbookupdate')) {
// 应用增量更新
for (const [price, qty] of msg.data.asks) {
updateLevel(orderbook.asks, price, qty);
}
for (const [price, qty] of msg.data.bids) {
updateLevel(orderbook.bids, price, qty);
}
}
};
function updateLevel(levels: [string, string][], price: string, qty: string) {
const idx = levels.findIndex(l => l[0] === price);
if (qty === '0') {
// 删除该档位
if (idx !== -1) levels.splice(idx, 1);
} else {
// 更新或插入档位
if (idx !== -1) {
levels[idx] = [price, qty];
} else {
levels.push([price, qty]);
// 排序(卖单升序,买单降序)
levels.sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]));
}
}
}// Available intervals: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
ws.send(JSON.stringify({
id: 'sub_kline',
event: 'subscribe',
topic: 'PERP_ETH_USDC@kline_1m',
}));
// Message format
{
"topic": "PERP_ETH_USDC@kline_1m",
"ts": 1699123456789,
"data": {
"t": 1699123440000, // Kline start time
"o": "3000.00", // Open
"h": "3010.00", // High
"l": "2995.00", // Low
"c": "3005.00", // Close
"v": "123.45", // Volume
"qv": "370350.00" // Quote volume
}
}// 可用时间间隔:1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
ws.send(JSON.stringify({
id: 'sub_kline',
event: 'subscribe',
topic: 'PERP_ETH_USDC@kline_1m',
}));
// 消息格式
{
"topic": "PERP_ETH_USDC@kline_1m",
"ts": 1699123456789,
"data": {
"t": 1699123440000, // K线开始时间
"o": "3000.00", // 开盘价
"h": "3010.00", // 最高价
"l": "2995.00", // 最低价
"c": "3005.00", // 收盘价
"v": "123.45", // 成交量
"qv": "370350.00" // 成交额
}
}import { signAsync } from '@noble/ed25519';
const accountId = 'your_account_id';
const privateKey = new Uint8Array(32); // Your Ed25519 private key
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);
// Encode as base64url (browser & Node.js compatible)
const base64 = btoa(String.fromCharCode(...signature));
const base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
// Send authentication
ws.send(
JSON.stringify({
id: 'auth_1',
event: 'auth',
params: {
orderly_key: `ed25519:${publicKeyBase58}`,
sign: base64url,
timestamp: timestamp,
},
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'auth' && data.success) {
console.log('Authenticated successfully');
// Now subscribe to private topics
subscribePrivateTopics();
}
};import { signAsync } from '@noble/ed25519';
const accountId = 'your_account_id';
const privateKey = new Uint8Array(32); // 你的Ed25519私钥
const ws = new WebSocket(`wss://ws-private-evm.orderly.org/v2/ws/private/stream/${accountId}`);
ws.onopen = async () => {
const timestamp = Date.now();
const message = timestamp.toString();
const signature = await signAsync(new TextEncoder().encode(message), privateKey);
// 编码为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: `ed25519:${publicKeyBase58}`,
sign: base64url,
timestamp: timestamp,
},
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'auth' && data.success) {
console.log('身份验证成功');
// 现在订阅私有主题
subscribePrivateTopics();
}
};| Topic | Description |
|---|---|
| Order status updates |
| Position changes |
| Balance updates |
| Liquidation warnings |
| Wallet/deposit updates |
| Account updates |
| Settlement updates |
| General notifications |
| 主题 | 描述 |
|---|---|
| 订单状态更新 |
| 仓位变更 |
| 余额更新 |
| 强平预警 |
| 钱包/存款更新 |
| 账户信息更新 |
| 结算更新 |
| 通用通知 |
function subscribePrivateTopics() {
// Subscribe to order updates
ws.send(
JSON.stringify({
id: 'sub_exec',
event: 'subscribe',
topic: 'executionreport',
// Optional: filter by symbol
params: { symbol: 'PERP_ETH_USDC' },
})
);
}
// Message format
interface ExecutionReport {
topic: 'executionreport';
ts: number;
data: {
orderId: string;
clientOrderId?: string;
symbol: string;
side: 'BUY' | 'SELL';
orderType: string;
status: 'NEW' | 'PARTIAL_FILLED' | 'FILLED' | 'CANCELLED' | 'REJECTED';
price: string;
quantity: string;
executedQty: string;
avgPrice?: string;
leavesQty: string;
lastExecutedPrice?: string;
lastExecutedQty?: string;
fee?: string;
feeToken?: string;
reason?: string;
};
}function subscribePrivateTopics() {
// 订阅订单更新
ws.send(
JSON.stringify({
id: 'sub_exec',
event: 'subscribe',
topic: 'executionreport',
// 可选:按交易对过滤
params: { symbol: 'PERP_ETH_USDC' },
})
);
}
// 消息格式
interface ExecutionReport {
topic: 'executionreport';
ts: number;
data: {
orderId: string;
clientOrderId?: string;
symbol: string;
side: 'BUY' | 'SELL';
orderType: string;
status: 'NEW' | 'PARTIAL_FILLED' | 'FILLED' | 'CANCELLED' | 'REJECTED';
price: string;
quantity: string;
executedQty: string;
avgPrice?: string;
leavesQty: string;
lastExecutedPrice?: string;
lastExecutedQty?: string;
fee?: string;
feeToken?: string;
reason?: string;
};
}ws.send(JSON.stringify({
id: 'sub_position',
event: 'subscribe',
topic: 'position',
}));
// Message format
{
"topic": "position",
"ts": 1699123456789,
"data": {
"symbol": "PERP_ETH_USDC",
"positionQty": 0.5,
"averageOpenPrice": 3000,
"markPrice": 3100,
"unrealizedPnl": 50,
"leverage": 10,
"liqPrice": 2700,
"mmr": 0.01,
"imr": 0.02
}
}ws.send(JSON.stringify({
id: 'sub_position',
event: 'subscribe',
topic: 'position',
}));
// 消息格式
{
"topic": "position",
"ts": 1699123456789,
"data": {
"symbol": "PERP_ETH_USDC",
"positionQty": 0.5,
"averageOpenPrice": 3000,
"markPrice": 3100,
"unrealizedPnl": 50,
"leverage": 10,
"liqPrice": 2700,
"mmr": 0.01,
"imr": 0.02
}
}ws.send(JSON.stringify({
id: 'sub_balance',
event: 'subscribe',
topic: 'balance',
}));
// Message format
{
"topic": "balance",
"ts": 1699123456789,
"data": {
"token": "USDC",
"total": "10000.00",
"free": "8000.00",
"locked": "2000.00",
"unsettledPnl": "500.00"
}
}ws.send(JSON.stringify({
id: 'sub_balance',
event: 'subscribe',
topic: 'balance',
}));
// 消息格式
{
"topic": "balance",
"ts": 1699123456789,
"data": {
"token": "USDC",
"total": "10000.00",
"free": "8000.00",
"locked": "2000.00",
"unsettledPnl": "500.00"
}
}import {
useOrderbookStream,
usePositionStream,
useOrderStream,
useBalance
} from '@orderly.network/hooks';
function TradingInterface({ symbol }: { symbol: string }) {
// Orderbook - automatically connects to WebSocket
const { asks, bids } = useOrderbookStream(symbol);
// Positions - real-time updates
const { rows: positions } = usePositionStream();
// Orders - real-time updates
const [orders] = useOrderStream({ status: OrderStatus.INCOMPLETE });
// Balance
const balance = useBalance();
return (
<div>
<OrderbookWidget asks={asks} bids={bids} />
<PositionsTable positions={positions} />
<OrdersTable orders={orders} />
<BalanceDisplay balance={balance} />
</div>
);
}import {
useOrderbookStream,
usePositionStream,
useOrderStream,
useBalance
} from '@orderly.network/hooks';
function TradingInterface({ symbol }: { symbol: string }) {
// 订单簿 - 自动连接到WebSocket
const { asks, bids } = useOrderbookStream(symbol);
// 仓位 - 实时更新
const { rows: positions } = usePositionStream();
// 订单 - 实时更新
const [orders] = useOrderStream({ status: OrderStatus.INCOMPLETE });
// 余额
const balance = useBalance();
return (
<div>
<OrderbookWidget asks={asks} bids={bids} />
<PositionsTable positions={positions} />
<OrdersTable orders={orders} />
<BalanceDisplay balance={balance} />
</div>
);
}// Orderly sends ping, you respond with pong
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'ping') {
ws.send(JSON.stringify({ event: 'pong' }));
}
};
// Or use native WebSocket ping/pong
ws.onopen = () => {
// Some clients need explicit ping
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ event: 'ping' }));
}
}, 30000);
};// Orderly发送ping,你需要回复pong
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'ping') {
ws.send(JSON.stringify({ event: 'pong' }));
}
};
// 或使用原生WebSocket的ping/pong
ws.onopen = () => {
// 部分客户端需要显式发送ping
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ event: 'ping' }));
}
}, 30000);
};class OrderlyWebSocket {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private subscriptions: string[] = [];
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.reconnectAttempts = 0;
this.authenticate();
this.resubscribe();
};
this.ws.onclose = () => {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(
() => {
this.reconnectAttempts++;
this.connect();
},
Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)
);
}
};
}
private resubscribe() {
for (const topic of this.subscriptions) {
this.ws?.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
subscribe(topic: string) {
this.subscriptions.push(topic);
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
}class OrderlyWebSocket {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private subscriptions: string[] = [];
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.reconnectAttempts = 0;
this.authenticate();
this.resubscribe();
};
this.ws.onclose = () => {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(
() => {
this.reconnectAttempts++;
this.connect();
},
Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)
);
}
};
}
private resubscribe() {
for (const topic of this.subscriptions) {
this.ws?.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
subscribe(topic: string) {
this.subscriptions.push(topic);
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
}ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.success === false) {
console.error('WebSocket error:', data.errorMsg);
switch (data.errorMsg) {
case 'invalid symbol':
// Handle invalid symbol
break;
case 'authentication failed':
// Re-authenticate
break;
case 'rate limit exceeded':
// Slow down subscription requests
break;
}
}
};
// Error response format
{
"id": "sub_ob_1",
"event": "subscribe",
"success": false,
"errorMsg": "invalid symbol"
}ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.success === false) {
console.error('WebSocket错误:', data.errorMsg);
switch (data.errorMsg) {
case 'invalid symbol':
// 处理无效交易对
break;
case 'authentication failed':
// 重新进行身份验证
break;
case 'rate limit exceeded':
// 降低订阅请求频率
break;
}
}
};
// 错误响应格式
{
"id": "sub_ob_1",
"event": "subscribe",
"success": false,
"errorMsg": "invalid symbol"
}| Action | Limit |
|---|---|
| Subscriptions per connection | 100 |
| Messages per second | 50 |
| Connection per IP | 10 |
| 操作 | 限制 |
|---|---|
| 单连接订阅数 | 100 |
| 每秒消息数 | 50 |
| 单IP连接数 | 10 |
ed25519:{publicKey}ed25519:{publicKey}prevTsprevTs