npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@solncebro/exchange-engine

v0.13.0

Published

Universal TypeScript client library for Binance and Bybit with unified API, type safety, and WebSocket support

Downloads

1,130

Readme

@solncebro/exchange-engine

Universal TypeScript client library for cryptocurrency trading on Binance and Bybit with unified API, WebSocket support, and native type safety.

Latest Release

Current version: 0.13.0

  • New OrderTypeEnum values: StopLimit, TakeProfitLimit for atomic STOP_LOSS_LIMIT / TAKE_PROFIT_LIMIT on Binance Spot and Bybit Spot.
  • New enums: TriggerByEnum (MarkPrice | LastPrice | IndexPrice), OrderFilterEnum (Order | tpslOrder | StopOrder), MarketUnitEnum (baseCoin | quoteCoin).
  • CreateOrderWebSocketArgs extended with triggerBy, closeOnTrigger, orderFilter, marketUnit, trailingDelta, quoteOrderQty.
  • Spot/futures order param mappings split: separate BINANCE_FUTURES_ORDER_TYPE_REVERSE / BINANCE_SPOT_ORDER_TYPE_REVERSE. On spot, closePosition/workingType/positionSide/reduceOnly and Bybit triggerDirection/triggerBy/closeOnTrigger are no longer set.
  • BybitLinear.fetchPositionMode() implemented via GET /v5/position/list?category=linear&settleCoin=USDT (Hedge if any positionIdx ∈ {1,2}; OneWay if all 0; undefined if no positions).
  • BREAKING: ExchangeClient.fetchPositionMode() now returns Promise<PositionModeEnum | undefined>.
  • awaitWebSocketConnectionsReady() on ExchangeClient; Binance Futures public stream uses /market/ws with batched SUBSCRIBE, stale reconnect, and optional messageCount / lastMessageTimestamp on WebSocketConnectionInfo.
  • New utility formatWebSocketConnectionsReport.
  • Bybit instruments with status === 'PreLaunch' are normalized as active (isActive: true).

Full release notes: CHANGELOG.md

Features

  • 🔀 Single API for multiple exchanges — same code works with Binance or Bybit
  • 🎯 Type-safe unified types — all responses normalized to consistent types (Kline, Ticker, Position, etc.)
  • 📊 REST & WebSocket support — fetch historical data and subscribe to real-time streams
  • 🔄 Automatic reconnection — resilient WebSocket connections with exponential backoff
  • 📝 Comprehensive logging — built-in structured logging via custom logger interface
  • 🚀 Zero dependencies — only axios and websocket-engine

Installation

yarn add @solncebro/exchange-engine

Quick Start

import { Exchange } from '@solncebro/exchange-engine';
import { pinoLogger } from './logger'; // your logger instance

// Create exchange instance (works identically for 'binance' or 'bybit')
const exchange = new Exchange('binance', {
  config: { apiKey: process.env.API_KEY, secret: process.env.API_SECRET },
  logger: pinoLogger,
  onNotify: (msg) => telegramBot.send(msg), // optional notifications
});

// Load trade symbols (markets)
await exchange.futures.loadTradeSymbols();

// Fetch historical klines
const klines = await exchange.futures.fetchKlines('BTCUSDT', '1h', { limit: 100 });
console.log(klines[0]); // { openTimestamp, openPrice, highPrice, lowPrice, closePrice, volume, ... }

// Get current tickers
const tickers = await exchange.futures.fetchTickers();

// Subscribe to real-time klines
exchange.futures.subscribeKlines({
  symbol: 'BTCUSDT',
  interval: '1m',
  handler: (kline) => {
    console.log(`[${kline.openTimestamp}] ${kline.closePrice}`);
  },
});

// Create an order via WebSocket
const order = await exchange.futures.createOrderWebSocket({
  symbol: 'BTCUSDT',
  type: 'market',
  side: 'buy',
  amount: 0.01,
  price: 0, // ignored for market orders
});

// Fetch position info
const position = await exchange.futures.fetchPosition('BTCUSDT');
console.log(`Leverage: ${position.leverage}, Contracts: ${position.contracts}`);

// Set leverage
await exchange.futures.setLeverage(10, 'BTCUSDT');

// Close connection
await exchange.close();

API Reference

Exchange (Main Entry Point)

const exchange = new Exchange('binance' | 'bybit', {
  config: { apiKey: string; secret: string; recvWindow?: number };
  logger: ExchangeLogger;
  onNotify?: (message: string) => void | Promise<void>;
});

// Access exchange clients
exchange.futures   // BinanceFutures | BybitLinear
exchange.spot      // BinanceSpot | BybitSpot

// Cleanup
await exchange.close();

ExchangeClient Interface

All four classes (BinanceFutures, BinanceSpot, BybitLinear, BybitSpot) implement this interface:

Market Data (REST)

// Load and cache trade symbols (markets)
await client.loadTradeSymbols(): Promise<TradeSymbolBySymbol>;

// Fetch current ticker prices
await client.fetchTickers(): Promise<TickerBySymbol>;

// Fetch historical candlestick data
await client.fetchKlines(
  symbol: string,
  interval: KlineInterval,
  options?: FetchPageWithLimitArgs
): Promise<Kline[]>;

// Get account balance
await client.fetchBalances(): Promise<AccountBalances>;

Trading (REST + WebSocket)

// Create order via WebSocket
await client.createOrderWebSocket({
  symbol: string;
  type: 'market' | 'limit';
  side: 'buy' | 'sell';
  amount: number;
  price: number;
}): Promise<Order>;

// Cancel an order
await client.cancelOrder(symbol: string, orderId: string): Promise<Order>;

// Fetch order history
await client.fetchOrderHistory(symbol: string, options?: FetchPageWithLimitArgs): Promise<Order[]>;

// Fetch open orders
await client.fetchOpenOrders(symbol?: string): Promise<Order[]>;

Futures-Specific

// Fetch position details
await client.fetchPosition(symbol: string): Promise<Position>;

// Set leverage (Binance: 2-125x, Bybit: 1-99.5x)
await client.setLeverage(leverage: number, symbol: string): Promise<void>;

// Set margin mode
await client.setMarginMode(marginMode: 'isolated' | 'cross', symbol: string): Promise<void>;

Real-Time Data (WebSocket)

// Subscribe to kline updates
client.subscribeKlines({
  symbol: string;
  interval: KlineInterval;
  handler: (kline: Kline) => void;
}): void;

// Unsubscribe
client.unsubscribeKlines({ symbol, interval, handler }): void;

Precision

// Format amount to exchange precision
const formatted = client.amountToPrecision('BTCUSDT', 0.12345);

// Format price to exchange precision
const formatted = client.priceToPrecision('BTCUSDT', 65432.1);

Unified Types

All types are normalized across exchanges. No raw exchange formats leak out.

// Candlestick
interface Kline {
  openTimestamp: number;
  openPrice: number;
  highPrice: number;
  lowPrice: number;
  closePrice: number;
  volume: number;
  closeTimestamp: number;
  quoteAssetVolume: number;
  numberOfTrades: number;
  isClosed?: boolean;
}

// Current price
interface Ticker {
  symbol: string;
  lastPrice: number;
  priceChangePercent: number; // 24h change %
  timestamp: number;
}

// Trade symbol (market metadata)
interface TradeSymbol {
  symbol: string;
  baseAsset: string;
  quoteAsset: string;
  settle: string;
  isActive: boolean;
  type: 'spot' | 'swap' | 'future';
  isLinear: boolean;
  contractSize: number;
  contractType: string;
  filter: TradeSymbolFilter;
}

// Open position (futures)
interface Position {
  symbol: string;
  side: 'long' | 'short' | 'both';
  contracts: number;
  entryPrice: number;
  markPrice: number;
  unrealizedPnl: number;
  leverage: number;
  marginMode: 'isolated' | 'cross';
  liquidationPrice: number;
  info: Record<string, unknown>; // raw exchange data
}

// Placed order
interface Order {
  id: string;
  clientOrderId: string;
  symbol: string;
  side: 'Buy' | 'Sell';
  type: 'Market' | 'Limit' | 'StopMarket' | 'TakeProfit' | 'TrailingStop';
  amount: number;
  price: number;
  filledAmount: number;
  status: 'open' | 'closed' | 'canceled' | 'rejected';
  timestamp: number;
}

// Account balance
interface Balance {
  asset: string;
  free: number;
  locked: number;
  total: number;
}

// Account balances
interface AccountBalances {
  balanceByAsset: BalanceByAsset; // Map<string, Balance>
  totalWalletBalance: number;
  totalAvailableBalance: number;
}

Logger Interface

Provide any logger that implements this interface:

interface ExchangeLogger {
  debug(message: string): void;
  info(message: string): void;
  warn(message: string): void;
  error(message: string): void;
  fatal(message: string): void;
}

Example with Pino

import pino from 'pino';

const logger = pino({
  level: 'info',
  transport: {
    target: 'pino-pretty',
    options: { colorize: true },
  },
});

const exchange = new Exchange('binance', {
  config: { apiKey, secret },
  logger, // pino instance is compatible
});

Exchange Differences

API Keys & Permissions

  • Binance: Read, Trade, Withdraw permissions (for different features)
  • Bybit: Single API key handles all

Order Placement

  • Binance: createOrderWebSocket() prefers WebSocket with REST fallback
  • Bybit: createOrderWebSocket() uses dedicated trade WebSocket stream

Position Modes

  • Binance: Supports Hedge Mode (separate long/short) and One-Way Mode
  • Bybit: Always supports both buy and sell sides simultaneously

Funding Rates

  • Binance: 8 times per day at fixed UTC times
  • Bybit: Hourly funding

These differences are transparent — the same code works for both.

Performance Tips

  1. Batch requests — use Promise.all() for multiple operations

    const [tickers, position, balance] = await Promise.all([
      client.fetchTickers(),
      client.fetchPosition('BTCUSDT'),
      client.fetchBalances(),
    ]);
  2. Reuse trade symbols — call loadTradeSymbols() once at startup

    const tradeSymbols = await client.loadTradeSymbols();
    const symbols = [...tradeSymbols.keys()];
  3. Limit historical data — fetch only needed range

    const klines = await client.fetchKlines('BTCUSDT', '1h', {
      limit: 100,
      startTime: Date.now() - 100 * 60 * 60 * 1000, // last 100 hours
    });
  4. Subscribe instead of polling — WebSocket is more efficient

    // Instead of:
    setInterval(() => fetchTickers(), 5000);
    
    // Use:
    client.subscribeKlines({ symbol, interval, handler });

Error Handling

Exchange-specific errors are thrown as ExchangeError with structured code and exchange fields:

Binance futures returns no-op validation responses for unchanged settings. Codes -4059 (No need to change position side.) and -4046 (No need to change margin type.) are handled as successful no-op operations in setPositionMode() and setMarginMode().

import { ExchangeError } from '@solncebro/exchange-engine';

try {
  await exchange.futures.setLeverage(100, 'BTCUSDT');
} catch (error) {
  if (error instanceof ExchangeError) {
    console.error(`[${error.exchange}] Error ${error.code}: ${error.message}`);
  }
}

Extending the Library

Adding new endpoints follows a standard pattern:

  1. HTTP Client → add method to BinanceFuturesHttpClient or BybitHttpClient
  2. Normalizer → add raw type + normalization function
  3. Interface → add method to ExchangeClient
  4. Implementation → implement in all 4 exchange classes

Example: adding fetchOpenInterest(symbol)

// 1. In BinanceFuturesHttpClient
private async fetchOpenInterestRaw(symbol: string): Promise<BinanceRawOpenInterest> {
  return this.get('/fapi/v1/openInterest', { symbol });
}

// 2. In binanceNormalizer.ts
export function normalizeOpenInterest(raw: BinanceRawOpenInterest): OpenInterest {
  return { symbol: raw.symbol, openInterest: parseFloat(raw.openInterest) };
}

// 3. In ExchangeClient interface
fetchOpenInterest(symbol: string): Promise<OpenInterest>;

// 4. In BinanceFutures
async fetchOpenInterest(symbol: string): Promise<OpenInterest> {
  const raw = await this.httpClient.fetchOpenInterestRaw(symbol);
  return normalizeOpenInterest(raw);
}

License

MIT

Support