Boros WebSocket
This guide explains how to directly connect to Boros's WebSocket service using Socket.IO client.
Basic Usageβ
Here's a complete example of how to connect to and use the WebSocket:
import { io } from 'socket.io-client';
// Initialize the socket connection
const socket = io('wss://api.boros.finance/pendle-dapp-v3', {
path: '/socket/socket.io',
reconnectionAttempts: 5,
transports: ['websocket']
});
Connection Eventsβ
Handling Connectionβ
socket.on('connect', () => {
console.log('Connected to WebSocket server');
});
socket.on('disconnect', () => {
console.log('Disconnected from WebSocket server');
});
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
});
Subscribing to Channelsβ
To receive updates, you need to:
- Subscribe to a channel
- Listen for updates on that channel
// Subscribe to a channel
socket.emit('subscribe', 'statistics:MARKET_ID');
// Listen for updates
socket.on('statistics:MARKET_ID:update', (data) => {
console.log('Received market statistics update:', data);
});
Cleanupβ
Always clean up your WebSocket connections when they're no longer needed:
function cleanup() {
// Unsubscribe from channels
socket.emit('unsubscribe', 'statistics:MARKET_ID');
// Remove listeners
socket.off('statistics:MARKET_ID:update');
// Disconnect
socket.disconnect();
}
Channel Typesβ
Market Channelsβ
| Channel | Event | Description |
|---|---|---|
orderbook:MARKET_ID:TICK_SIZE | orderbook:MARKET_ID:TICK_SIZE:update | Orderbook updates. Accepted TICK_SIZE: 0.1, 0.01, 0.001, 0.0001, 0.00001 |
market-trade:MARKET_ID | market-trade:MARKET_ID:update | Individual trade executions with rate, size, blockTimestamp, txHash |
statistics:MARKET_ID | statistics:MARKET_ID:update | Market statistics: markApr, midApr, lastTradedApr, floatingApr, volume24h, notionalOI, nextSettlementTime, longYieldApr |
market-data:MARKET_ID | market-data-update | Real-time market data from on-chain events. See Market Data Events below. |
Account Channelsβ
| Channel | Event | Description |
|---|---|---|
account:ACCOUNT | account:ACCOUNT:update | Legacy notification β tells you something changed (type: Position, LimitOrder, or Collateral). Requires REST API refetch. |
account-updates:ROOT_ADDRESS | position-update | Detailed position changes with pre/post state. See Account Update Events. |
account-updates:ROOT_ADDRESS | order-update | Detailed order state changes (placed, filled, cancelled, purged). |
account-updates:ROOT_ADDRESS | settlement-update | Detailed settlement data (yield paid/received, APR rates, fees). |
Replace MARKET_ID with your market identifier. For ACCOUNT, use AccountLib.pack(address, accountId). For ROOT_ADDRESS, use your lowercased wallet address (e.g., 0xdac17...).
account-updates over accountThe legacy account:ACCOUNT channel only tells you that something changed β you still need to call the REST API to find out what. The new account-updates:ROOT_ADDRESS channel includes full event details in the payload, eliminating the need for follow-up API calls.
Best Practicesβ
-
Connection Management
- Always handle connection errors
- Implement reconnection logic if needed
- Clean up connections when no longer needed
-
Event Handling
- Subscribe to channels after connection is established
- Remove listeners before disconnecting
- Handle potential errors in data processing
-
Resource Management
- Unsubscribe from channels you no longer need
- Don't create multiple connections unnecessarily
- Clean up resources when your application closes
Example Implementationβ
Here's a complete example putting it all together:
import { io } from 'socket.io-client';
class PendleWebSocket {
private socket: any;
constructor() {
this.socket = io('wss://api.boros.finance/pendle-dapp-v3', {
path: '/socket/socket.io',
reconnectionAttempts: 5,
transports: ['websocket']
});
this.setupEventHandlers();
}
private setupEventHandlers() {
this.socket.on('connect', () => {
console.log('Connected to WebSocket server');
});
this.socket.on('disconnect', () => {
console.log('Disconnected from WebSocket server');
});
this.socket.on('connect_error', (error) => {
console.error('Connection error:', error);
});
}
public subscribeToMarket(marketId: string) {
const channel = `statistics:${marketId}`;
this.socket.emit('subscribe', channel);
this.socket.on(`${channel}:update`, (data) => {
console.log(`Received update for ${marketId}:`, data);
});
}
public unsubscribeFromMarket(marketId: string) {
const channel = `statistics:${marketId}`;
this.socket.emit('unsubscribe', channel);
this.socket.off(`${channel}:update`);
}
public disconnect() {
this.socket.disconnect();
}
}
// Usage example:
const ws = new PendleWebSocket();
ws.subscribeToMarket('YOUR_MARKET_ID');
// Clean up when done
// ws.unsubscribeFromMarket('YOUR_MARKET_ID');
// ws.disconnect();
Account Change Trackingβ
You can subscribe to your account channel to receive notifications when your account state changes (e.g., order fills, position updates, balance changes).
Subscribing to Account Updatesβ
The account channel format is account:ACCOUNT, where ACCOUNT is your wallet address (lowercased) concatenated with your account ID.
For example, if your wallet address is 0xdAC17F958D2ee523a2206206994597C13D831ec7 and your account ID is 0 (the default account ID in Boros), the channel would be:
account:0xdac17f958d2ee523a2206206994597c13d831ec700
You can use AccountLib.pack() from @pendle/sdk-boros to construct this:
import { AccountLib } from '@pendle/sdk-boros';
const account = AccountLib.pack('0xdAC17F958D2ee523a2206206994597C13D831ec7', 0);
// account = '0xdac17f958d2ee523a2206206994597c13d831ec700'
// Subscribe to account updates
// Format: account:ACCOUNT
const channel = `account:${AccountLib.pack(walletAddress, accountId)}`;
socket.emit('subscribe', channel);
// Listen for account update events
socket.on(`${channel}:update`, (data) => {
console.log('Account updated:', data);
// Refetch the latest account data via API
refreshAccountData();
});
Recommended Patternβ
When you receive an account update event, refetch the relevant API endpoints to get the complete updated state:
import axios from "axios";
import { MarketAccLib, CROSS_MARKET_ID } from "@pendle/sdk-boros";
async function refreshAccountData() {
const marketAcc = MarketAccLib.pack(walletAddress, accountId, tokenId, CROSS_MARKET_ID);
// Refetch account info
const { data: accountInfo } = await axios.post(
`${API_BASE_URL}/open-api/v1/accounts/market-acc-infos`,
{ marketAccs: [marketAcc] }
);
// Refetch active orders
const { data: orders } = await axios.get(
`${API_BASE_URL}/open-api/v1/accounts/limit-orders`,
{
params: {
userAddress: walletAddress,
accountId: accountId,
isActive: true,
},
}
);
// Update your local state
updateLocalState(accountInfo, orders);
}
Market Data Eventsβ
Subscribe to market-data:MARKET_ID to receive real-time market data derived from on-chain events. The event name is market-data-update.
Payload fields:
| Field | Type | Description |
|---|---|---|
mId | number | Market ID |
bn | number | Block number of the on-chain event |
bt | number | Block timestamp (unix) |
oi | number | Notional open interest |
mid | number | Mid APR (from implied rate, AMM rate, or bid/ask average) |
mk | number | Mark APR (from contract) |
lt | number | Last traded APR |
nst | number | Next settlement time (unix) |
bb | number? | Best bid APR (undefined if no bids) |
ba | number? | Best ask APR (undefined if no asks) |
ai | number? | AMM implied APR (undefined if no AMM) |
socket.emit('subscribe', `market-data:${marketId}`);
socket.on('market-data-update', (data) => {
console.log(`Market ${data.mId}: mark=${data.mk}, mid=${data.mid}, OI=${data.oi}`);
});
Account Update Eventsβ
Subscribe to account-updates:ROOT_ADDRESS (lowercased wallet address) to receive detailed, structured events when your positions, orders, or settlements change.
Unlike the legacy account:ACCOUNT channel which only notifies you that something changed, these events include the full details β no REST API refetch needed.
Position Update (position-update)β
Emitted when a position size or rate changes (trade fill, liquidation).
| Field | Type | Description |
|---|---|---|
ei | number | Event index (on-chain sequence number) |
bn | number | Block number |
bt | number | Block timestamp (unix) |
tx | Hex | Transaction hash |
mId | number | Market ID |
ma | MarketAcc | Packed market account |
prf | string | Previous fixed rate (FixedX18 raw) |
prs | string | Previous position size (FixedX18 raw) |
ptf | string | Post-trade fixed rate (FixedX18 raw) |
pts | string | Post-trade position size (FixedX18 raw) |
Order Update (order-update)β
Emitted when an order is placed, partially filled, fully filled, cancelled, or purged.
| Field | Type | Description |
|---|---|---|
ei | number | Event index |
bn | number | Block number |
bt | number | Block timestamp (unix) |
tx | Hex | Transaction hash |
mId | number | Market ID |
oId | string | Order ID |
sd | Side | 0 = Long, 1 = Short |
ps | string | Original placed size (FixedX18 raw) |
us | string | Remaining unfilled size (FixedX18 raw) |
tk | number | Tick (price level) |
ot | OrderType | Order type: 0 = Limit, 1 = Market, 2 = TakeProfitMarket, 3 = StopLossMarket |
os | LimitOrderStatus | Order status: 0 = Filling (unfilled/partially filled), 1 = Cancelled, 2 = FullyFilled, 3 = Expired (market matured), 4 = Purged |
efs | string? | Size filled by this specific event (undefined if no fill) |
Settlement Update (settlement-update)β
Emitted when a funding rate settlement is processed for a position. Only emitted for positions with non-zero size.
settlement-update events are currently only emitted for a few active market makers, not for all users. General user support will be added in the future. For now, use GET /accounts/settlements or the legacy account:ACCOUNT channel to track settlements.
| Field | Type | Description |
|---|---|---|
ei | number | Event index |
bn | number | Block number |
bt | number | Block timestamp (unix) |
tx | Hex | Transaction hash |
ma | MarketAcc | Packed market account |
mId | number | Market ID |
ps | string | Position size (negative = short) |
yp | string | Yield paid |
yr | string | Yield received |
pa | number | Annualized fixed rate paid |
ra | number | Annualized floating rate received |
fee | string | Settlement fee |
Example: Subscribing to Account Updatesβ
const root = walletAddress.toLowerCase();
socket.emit('subscribe', `account-updates:${root}`);
socket.on('position-update', (data) => {
console.log(`Position changed on market ${data.mId}:`);
console.log(` Size: ${data.prs} β ${data.pts}`);
console.log(` Rate: ${data.prf} β ${data.ptf}`);
console.log(` Tx: ${data.tx}`);
});
socket.on('order-update', (data) => {
console.log(`Order ${data.oId} on market ${data.mId}: status=${data.os}`);
if (data.efs) console.log(` Filled: ${data.efs}`);
});
socket.on('settlement-update', (data) => {
console.log(`Settlement on market ${data.mId}: paid=${data.yp}, received=${data.yr}`);
});
Position sizes, rates, yields, and fees are returned as FixedX18 raw string values (18-decimal fixed-point). Use FixedX18.fromBigIntString(value).toNumber() from @pendle/boros-offchain-math to convert to human-readable numbers.
Complete Account Tracking Exampleβ
import { io } from 'socket.io-client';
import axios from 'axios';
import { AccountLib } from '@pendle/sdk-boros';
class AccountTracker {
private socket: any;
private walletAddress: string;
private accountId: number;
constructor(walletAddress: string, accountId: number = 0) {
this.walletAddress = walletAddress.toLowerCase();
this.accountId = accountId;
this.socket = io('wss://api.boros.finance/pendle-dapp-v3', {
path: '/socket/socket.io',
reconnectionAttempts: 5,
transports: ['websocket'],
});
this.setupConnection();
}
private setupConnection() {
this.socket.on('connect', () => {
console.log('Connected - subscribing to account updates');
this.subscribeToAccount();
});
this.socket.on('disconnect', () => {
console.log('Disconnected from WebSocket');
});
}
private subscribeToAccount() {
const channel = `account:${AccountLib.pack(this.walletAddress as `0x${string}`, this.accountId)}`;
this.socket.emit('subscribe', channel);
console.log('Subscribed to account:', channel);
this.socket.on(`${channel}:update`, async (data: any) => {
console.log('Account change detected:', data);
// Fetch updated data from API
await this.refreshAccountData();
});
}
private async refreshAccountData() {
try {
// Fetch latest account state
const { data } = await axios.post('https://api.boros.finance/open-api/v1/accounts/market-acc-infos', {
marketAccs: [
/* your marketAcc */
],
});
console.log('Updated account data:', data);
// Process updated data...
} catch (error) {
console.error('Failed to refresh account data:', error);
}
}
public disconnect() {
const channel = `account:${AccountLib.pack(this.walletAddress as `0x${string}`, this.accountId)}`;
this.socket.emit('unsubscribe', channel);
this.socket.off(`${channel}:update`);
this.socket.disconnect();
}
}
// Usage
const tracker = new AccountTracker('0xdAC17F958D2ee523a2206206994597C13D831ec7', 0);
// Clean up when done
// tracker.disconnect();
Error Handlingβ
Always implement proper error handling:
socket.on('connect_error', (error) => {
console.error('Connection failed:', error);
// Implement your error handling logic
});
socket.on('error', (error) => {
console.error('Socket error:', error);
// Implement your error handling logic
});