mcpField
O
orderly-websocket-streaming

orderly-websocket-streaming

github.com/orderlynetwork其他253 次安装今天更新
代码仓库

关于 orderly-websocket-streaming

面向使用 Orderly Network 构建实时界面的开发者,介绍 WebSocket 认证、公共与私有数据订阅及连接维护,涵盖行情和账户流。

主要功能

  1. 01订阅公共市场数据
  2. 02认证私有账户数据流
  3. 03处理心跳与重连
  4. 04使用 React SDK 流式 Hooks
  5. 05检查流错误与消息序列

适用场景

  1. 01展示实时订单簿和成交数据
  2. 02跟踪订单执行与持仓变化
  3. 03监控余额和清算警告

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/orderlynetwork/skills --skill orderly-websocket-streaming

安装后在 Claude Code 中输入/orderly-websocket-streaming确认已加载。

详细介绍

Orderly Network:WebSocket 流式传输

本技能介绍如何在 Orderly Network 上建立 WebSocket 连接、进行身份验证并订阅实时数据流。

适用场景

  • 构建实时交易界面
  • 流式传输订单簿数据
  • 接收成交通知
  • 监控持仓更新

前置条件

  • Orderly 账户 ID
  • 用于签名的 Ed25519 私钥
  • WebSocket 客户端库(原生 WebSocket、socket.io 等)

WebSocket 端点

环境URL
主网公共端点wss://ws.orderly.org/ws/stream
主网私有端点wss://ws-private-evm.orderly.org/v2/ws/private/stream/{account_id}
测试网公共端点wss://testnet-ws.orderly.org/ws/stream
测试网私有端点wss://testnet-ws-private-evm.orderly.org/v2/ws/private/stream/{account_id}

连接流程

1. Connect to WebSocket endpoint
2. Authenticate (for private streams)
3. Subscribe to topics
4. Receive real-time messages
5. Handle reconnection/heartbeat

公共数据流(无需身份验证)

连接并订阅
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);
};
可用的公共主题
主题说明
{symbol}@orderbook完整订单簿快照
{symbol}@orderbookupdate增量更新
{symbol}@trade成交记录
{symbol}@kline_{interval}K 线/蜡烛图数据
{symbol}@markprice标记价格更新
{symbol}@indexprice指数价格更新
{symbol}@24hrTicker24 小时统计数据
markprice所有交易对的标记价格
indexprice所有交易对的指数价格
订单簿数据流
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]));
    }
  }
}
K 线数据流
// 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
  }
}

私有数据流(需要身份验证)

身份验证
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();
  }
};
私有主题
主题说明
executionreport订单状态更新
position持仓变更
balance余额更新
liquidationsaccount强平警告
wallet钱包/存款更新
account账户更新
settle结算更新
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;
  };
}
持仓数据流
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_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"
  }
}

React SDK:内置流式传输

Orderly SDK 会自动处理 WebSocket 连接:

import {
  useOrderbookStream,
  usePositionStream,
  useOrderStream,
  useCollateral,
  useHoldingStream
} from '@orderly.network/hooks';
import { OrderStatus } from '@orderly.network/types';

function TradingInterface({ symbol }: { symbol: string }) {
  // Orderbook - automatically connects to WebSocket
  const { asks, bids } = useOrderbookStream(symbol);

  // Positions - real-time updates (returns a 3-tuple)
  const [positions] = usePositionStream();

  // Orders - real-time updates
  const [orders] = useOrderStream({ status: OrderStatus.INCOMPLETE });

  // Balance / collateral and real-time holdings
  const { freeCollateral } = useCollateral({ dp: 2 });
  const holdings = useHoldingStream();

  return (
    <div>
      <OrderbookWidget asks={asks} bids={bids} />
      <PositionsTable positions={positions?.rows ?? []} />
      <OrdersTable orders={orders ?? []} />
      <BalanceDisplay freeCollateral={freeCollateral} holdings={holdings} />
    </div>
  );
}

没有 useBalance hook。请使用 useCollateral(账户价值/可用抵押品)或 useHoldingStream(按代币提供的持仓数据流)。usePositionStream 会返回一个 3 元组 [positions, calc, status]——请将其解构为数组,而非对象。

连接管理

心跳
// 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);
};
重新连接
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"
}

速率限制

操作限制
每个连接的订阅数100
每秒消息数50
每个 IP 的连接数10

常见问题

“身份验证失败”错误
  • 检查时间戳是否为当前时间(而非未来或过去的时间)
  • 验证签名编码(base64url)
  • 确保 orderly_key 格式为 ed25519:{publicKey}
连接频繁断开
  • 实现心跳/pong
  • 检查网络稳定性
  • 使用重新连接逻辑
消息缺失
  • 检查订阅是否成功
  • 验证主题名称格式
  • 监控错误消息
订单簿不同步
  • 使用 prevTs 检测遗漏的消息
  • 重新订阅以获取最新快照
  • 实现序列验证

Related Skills

  • orderly-api-authentication - Ed25519 signing details
  • orderly-trading-orders - Order management
  • orderly-positions-tpsl - Position monitoring
  • orderly-sdk-react-hooks - SDK streaming hooks

评论

支持 Markdown