
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
@pear-protocol/exchanges-sdk
Advanced tools
Unified SDK for account state (balance, positions, per-asset leverage) across supported derivative exchanges — as a live WebSocket subscription or a stateless one-shot snapshot.
| Exchange | Connector | Market |
|---|---|---|
| Binance | binance | USDM Futures |
| Bybit | bybit | Linear Perpetuals |
| Hyperliquid | hyperliquid | Perpetuals |
| Lighter | lighter | Perpetuals |
| OKX | okx | Linear SWAP |
npm install @pear-protocol/exchanges-sdk
import PearSDK from '@pear-protocol/core-sdk';
import { ExchangesSDK } from '@pear-protocol/exchanges-sdk';
const sdk = new PearSDK({ /* ... */ });
const exchanges = new ExchangesSDK({
sdk,
options: {
builderAddress: '0x...',
builderFeeTenthsBps: 60,
integratorAccountIndex: 1234,
integratorMinPerpsMakerFee: 600,
integratorMinPerpsTakerFee: 600,
},
});
const connection = await exchanges.connect(tradeAccountId);
const status = await exchanges.status(connection);
if (status.status === 'warning') {
console.warn(status.reasons);
}
const tracker = exchanges.createTracker(connection);
const syncer = exchanges.createSyncer(connection);
await tracker.start();
const offBalance = tracker.trackBalance((balance) => {
if (!balance) return; // unsupported/unrepresentable account mode
console.log(balance.totalEquity, balance.unrealizedPnl);
});
const offPositions = tracker.trackPosition((positions) => {
for (const p of positions) console.log(p.symbol, p.side, p.size);
});
// Cleanup
offBalance();
offPositions();
await tracker.disconnect();
Pass demo: true to target each exchange's test or simulated-trading environment:
const exchanges = new ExchangesSDK({ sdk, demo: true });
Hyperliquid and Lighter public REST/WebSocket reads are routed to testnet. Lighter operations that require a backend-generated auth token or signature currently fail closed in demo mode because the existing supporting API signs for mainnet only; public snapshots and tracking remain available, without authenticated account-tier enrichment.
connect(tradeAccountId) fetches decrypted credentials and the trade account from the core API. It does not open a socket — the exchange WebSocket is created lazily when you call tracker.start() and torn down by tracker.disconnect(). Syncers perform one-shot REST reads. For an account snapshot with no socket, use snapshot().
The connector comes from the backend trade account.
const connection = await exchanges.connect(tradeAccountId);
connection.connector; // "hyperliquid"
connection.account.exchangeIdentifier;
connection.credentials;
For exchanges that need account health checks, pass status options when creating the SDK:
const exchanges = new ExchangesSDK({
sdk,
options: {
builderAddress: '0x...',
builderFeeTenthsBps: 60,
integratorAccountIndex: 1234,
integratorMinPerpsMakerFee: 600,
integratorMinPerpsTakerFee: 600,
},
});
Hyperliquid status requires the builder address and the minimum approved fee in tenths of a basis point. Lighter status requires the integrator account and minimum maker/taker approval caps in Lighter fee ticks (parts per million). These thresholds must match the fees configured by the trading backend.
Historical Hyperliquid fill synchronization identifies fills by bare tid and requests them aggregated by time, so partial fills of one crossing order arrive as a single fill. Consumers that persisted composite time:coin:tid identifiers must account for the transition overlap. Hyperliquid serves only the 10,000 most recent fills; an account with a longer history imports from that boundary forward, and the snapshot-alignment synthetics cover the residual gap. Synchronization fails closed when the exchange reports a maker rebate: the current core external-fill contract accepts only non-negative fees, so the SDK will not silently turn a rebate into a cost or advance the synchronization watermark past it.
Use status(connection) to check whether the account is ready for trading through PEAR.
const status = await exchanges.status(connection);
if (status.status === 'warning') {
for (const reason of status.reasons) {
console.warn(reason);
}
}
Possible warning reasons:
| Reason | Meaning |
|---|---|
api_key_invalid | API credentials failed the exchange account check |
hyperliquid_api_wallet_invalid | Hyperliquid agent wallet is missing or invalid |
hyperliquid_builder_fee_not_approved | Hyperliquid builder fee approval is missing |
hyperliquid_account_mode_check_failed | Hyperliquid account mode could not be read |
hyperliquid_account_mode_unsupported | Portfolio Margin or deprecated DEX Abstraction cannot be valued safely |
lighter_api_key_invalid | Lighter API key cannot create an auth token |
lighter_integrator_not_approved | Lighter integrator approval is missing |
When you only need the current balance and positions once — a request/response read rather than a live subscription — use snapshot(). It fetches over REST, opens no WebSocket, and leaves nothing running.
const { balance, positions } = await exchanges.snapshot(tradeAccountId);
balance?.totalEquity;
balance?.availableToTrade;
for (const p of positions) {
console.log(p.symbol, p.side, p.leverage, p.liquidationPrice);
}
snapshot() returns the same AccountBalance / AccountPosition shapes the live tracker produces — { balance: AccountBalance | null; positions: AccountPosition[] } — so either path can feed the same consumer. balance is null when the account type cannot be represented safely. Hyperliquid currently supports Standard and Unified calculations; Portfolio Margin and deprecated DEX Abstraction fail closed and are surfaced by account status.
There is nothing to tear down: no disconnect(), no socket. This is the read a stateless caller (such as an MCP tool) wants.
Hyperliquid Unified balances come from the spot clearinghouse state and use the documented maximum per-collateral maintenance ratio. Standard balances use per-DEX margin summaries, including isolated positions.
snapshotAssets()snapshot() carries leverage only for open positions.
To read the currently configured leverage and margin mode for any asset — positioned or not — use snapshotAssets().
const assets = await exchanges.snapshotAssets(tradeAccountId, ['BTC', 'ETH']);
assets.BTC?.leverage; // e.g. '20'
assets.BTC?.marginType; // 'cross' | 'isolated'
assets.ETH; // null when the exchange has no readable setting for that asset
It returns Record<string, TrackedAssetInfo | null> keyed by the requested asset id — the same TrackedAssetInfo shape the live trackAsset() path emits, and the same per-exchange identifier it uses (Hyperliquid coin, Binance/Bybit symbol, OKX instId, Lighter market id).
The read is batch-first: each connector services the whole list with as few requests as its exchange allows.
| Exchange | Requests for N assets | How |
|---|---|---|
| Binance | 1 | all-symbols positionRisk |
| Lighter | 2 | account read + market defaults |
| OKX | ceil(N/20) | leverage-info accepts 20 instIds per call |
| Bybit | 2 + k | settleCoin position lists; per-symbol fill-in for the k non-positioned symbols (direct per-symbol reads when N ≤ 2) |
| Hyperliquid | N (parallel) | activeAssetData info request is per-coin (unauthenticated, cheap) |
For Lighter, a custom margin setting on a market with no open position is not exposed by the REST account read, so it reports the market default — the same figure the live path shows before a position exists.
Each track* method returns an unsubscribe function. Callbacks fire with the current snapshot on subscribe (if available) and on every update.
tracker.trackBalance((balance) => {
if (!balance) return; // a live mode change can explicitly invalidate the cached balance
balance.totalEquity;
balance.walletBalance;
balance.unrealizedPnl;
balance.availableToTrade;
balance.initialMarginUsed;
balance.maintenanceMargin;
balance.marginRatio;
balance.accountType;
balance.accountTier; // Lighter account tier when reported
for (const a of balance.assets) {
a.asset; // "USDT", "USDC", ...
a.free;
a.used;
a.total;
a.usdValue;
}
});
tracker.trackPosition((positions) => {
for (const p of positions) {
p.symbol;
p.side; // "long" | "short" | "both"
p.size;
p.entryPrice;
p.unrealizedPnl;
p.leverage;
p.marginType; // "cross" | "isolated"
p.liquidationPrice; // string | null
}
});
Closed positions (size === '0') are dropped automatically.
tracker.trackAsset('ETH', (info) => {
info.coin;
info.leverage;
info.marginType;
});
Asset symbol format per exchange:
| Exchange | Format | Example |
|---|---|---|
| Binance | <BASE><QUOTE> | BTCUSDT |
| Bybit | <BASE><QUOTE> | BTCUSDT |
| Hyperliquid | base coin | BTC |
| Lighter | base coin | BTC |
| OKX | instId | BTC-USDT-SWAP |
Synchronous getters for the latest cached state on a running tracker:
tracker.getBalance(); // AccountBalance | null
tracker.getPositions(); // AccountPosition[]
tracker.getTrackedAsset('ETH'); // TrackedAssetInfo | null
tracker.isConnected;
tracker.isInitialized; // true after first balance/position state (asset metadata alone does not count)
balance.accountType is an exchange-specific enum. Render with:
import { ACCOUNT_TYPE_LABELS } from '@pear-protocol/exchanges-sdk';
ACCOUNT_TYPE_LABELS[balance.accountType]; // "Cross Margin", "Portfolio Margin", ...
await tracker.disconnect(); // closes the WebSocket opened by start()
Credentials and the trade account are fetched automatically on connect; Hyperliquid and Lighter account identifiers come from connection.account.exchangeIdentifier.
FAQs
Pear Protocol Exchanges SDK
The npm package @pear-protocol/exchanges-sdk receives a total of 1,213 weekly downloads. As such, @pear-protocol/exchanges-sdk popularity was classified as popular.
We found that @pear-protocol/exchanges-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.