orderly-websocket-streaming について
Orderly Networkでリアルタイム画面を構築する開発者向けのスキルです。WebSocketの認証、公開・非公開ストリームの購読、接続維持を扱います。
主な機能
- 01公開市場データの購読
- 02認証付きアカウントストリーム
- 03ハートビートと再接続の処理
- 04React SDKのストリーミングフック
- 05ストリームエラーと順序の確認
活用シーン
- 01板情報と約定のリアルタイム表示
- 02注文執行とポジション変化の追跡
- 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} | Kline/ローソク足データ |
{symbol}@markprice | マーク価格の更新 |
{symbol}@indexprice | インデックス価格の更新 |
{symbol}@24hrTicker | 24 時間の統計 |
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]));
}
}
}
Kline ストリーム
// 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フックはありません。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 |
| 1 秒あたりのメッセージ数 | 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 対応