@pear-protocol/hyperliquid
TypeScript SDK for building on Pear Protocol — pair trading on Hyperliquid.
npm install @pear-protocol/hyperliquid
import { PearSdk } from '@pear-protocol/hyperliquid';
const sdk = new PearSdk();
const sdkWithCustomPearHost = new PearSdk({
basePath: 'https://your-pear-api.example.com',
pearWsUrl: 'wss://your-pear-api.example.com/ws',
});
1. Market Data
Instruments
Real-time prices, funding rates, and leverage for every listed asset.
const unsub = sdk.market.instrument((instruments) => {
for (const token of instruments) {
console.log(token.assetName, token.markPrice, token.funding);
}
});
assetName | string | Symbol ("BTC", "ETH") |
markPrice | number | Mark price |
oraclePrice | number | Oracle price |
prevDayPrice | number | Previous day price |
priceChange24hPercent | number | 24h change % |
funding | number | Funding rate |
maxLeverage | number | Max leverage |
Basket Snapshot
Real-time computed metrics for any long/short basket. Weights are decimals (0-1), and the sum of all long + short weights must equal 1.
const unsub = sdk.market.basket(
[{ symbol: 'BTC', weight: 0.25 }, { symbol: 'ETH', weight: 0.25 }],
[{ symbol: 'SOL', weight: 0.5 }],
(snapshot) => {
snapshot.weightedRatio;
snapshot.priceRatio;
snapshot.price;
snapshot.weightedRatio24hAgo;
snapshot.priceRatio24hAgo;
snapshot.price24hAgo;
snapshot.netFundingRate;
snapshot.maxLeverage;
snapshot.maxLeverageBySymbol;
},
);
Ideas (Picks)
Pre-curated trading baskets streamed via WebSocket. Includes actives, watchlist (when authenticated), and AI-generated pairs.
const unsub = sdk.market.picks((baskets) => {
for (const b of baskets) {
b.categories;
b.name;
b.longTokens;
b.shortTokens;
b.weightedRatio;
b.netFundingRate;
b.oi;
b.volume;
b.maxLeverage;
b.maxLeverageBySymbol;
}
});
Filter by category on the client:
const aiPicks = baskets.filter((b) => b.categories.includes('ai-picks'));
Chart
Historical and real-time OHLCV candle data for any basket or individual asset.
const { chart } = sdk.market;
chart.setTokens(
[{ symbol: 'BTC', weight: 0.25 }, { symbol: 'ETH', weight: 0.25 }],
[{ symbol: 'SOL', weight: 0.5 }],
);
chart.setCandleInterval('15m');
const bars = await chart.getBars('weighted-ratio', startMs, endMs);
const assetBars = await chart.getAssetBars('BTC', startMs, endMs);
const subId = chart.subscribeRealtimeBars('weighted-ratio', (bar) => {
console.log(bar.time, bar.close);
});
const assetSubId = chart.subscribeRealtimeAssetBars('BTC', (bar) => {
console.log(bar.time, bar.close);
});
chart.unsubscribeRealtimeBars(subId);
chart.unsubscribeRealtimeBars(assetSubId);
const boundary = chart.getEffectiveDataBoundary();
chart.clearCache();
chart.destroy();
Chart Types
'weighted-ratio' | Geometric mean of weighted long basket / short basket prices |
'price-ratio' | Long / short price ratio (1L:1S pairs only, else empty) |
'performance' | Individual asset performance from a baseline — use with overlays |
'price' | Single-asset price |
Candle Intervals
type CandleInterval =
| '1m' | '3m' | '5m' | '15m' | '30m'
| '1h' | '2h' | '4h' | '8h' | '12h'
| '1d' | '3d' | '1w' | '1M';
Bar Shape
interface Bar {
time: number;
open: number;
high: number;
low: number;
close: number;
}
Orderbook
Real-time Hyperliquid L2 orderbook snapshots for a single asset. Orderbooks use the same shared Hyperliquid WebSocket connection as sdk.market.chart, and sdk.market.createOrderbook uses the latest instrument mark price for aggregation params.
const orderbook = await sdk.market.createOrderbook({
symbol: 'BTC',
depth: 10,
aggregation: 0,
});
const subId = orderbook.subscribe((snapshot) => {
snapshot.symbol;
snapshot.bids;
snapshot.asks;
snapshot.aggregation;
snapshot.ts;
});
const bbo = orderbook.bbo;
bbo.bestBid;
bbo.bestAsk;
bbo.spread;
bbo.spreadPct;
orderbook.setAggregation(100);
const snapshot = orderbook.getSnapshot();
orderbook.unsubscribe(subId);
orderbook.destroy();
Orderbook Types
interface OrderbookLevel {
price: number;
size: number;
}
interface OrderbookSnapshot {
symbol: string;
bids: OrderbookLevel[];
asks: OrderbookLevel[];
aggregation: number;
ts: number;
}
TradingView Datafeed Integration
The SDK chart plugs into TradingView's IBasicDataFeed. Two key methods to wire up:
getBars — Historical Data
Convert TradingView's resolution to a CandleInterval, set it on the chart, then fetch bars. TradingView passes timestamps in seconds — multiply by 1000 for the SDK.
const sdkInterval = tvResolutionToSdkInterval(resolution);
chart.setCandleInterval(sdkInterval);
const startMs = periodParams.from * 1000;
const endMs = periodParams.to * 1000;
const bars = await chart.getBars('weighted-ratio', startMs, endMs);
if (bars.length === 0) {
const boundary = chart.getEffectiveDataBoundary();
onResult([], { noData: true, nextTime: boundary ? boundary / 1000 : undefined });
}
subscribeBars — Real-time Updates
Subscribe to live bar updates. Map each TradingView listenerGuid to an SDK subscription ID for cleanup.
const subId = chart.subscribeRealtimeBars('weighted-ratio', (bar) => {
onTick({ time: bar.time, open: bar.open, high: bar.high, low: bar.low, close: bar.close });
});
const subId = chart.subscribeRealtimeAssetBars('BTC', (bar) => {
onTick({ time: bar.time, open: bar.open, high: bar.high, low: bar.low, close: bar.close });
});
chart.unsubscribeRealtimeBars(subId);
2. Authentication
EIP-712 signature-based login. Tokens auto-refresh on 401.
const { data: msg } = await sdk.api.public.auth.getEIP712Message(address, clientId, chainId);
const signature = await signTypedData({ ... });
await sdk.api.public.auth.login({
address,
clientId: CLIENT_ID,
details: { signature, timestamp: eip712.timestamp, chainId },
method: 'eip712',
});
sdk.setAddress(address);
Token persistence:
sdk.api.setTokens(savedAccessToken, savedRefreshToken);
const accessToken = sdk.api.getAccessToken();
const refreshToken = sdk.api.getRefreshToken();
sdk.api.onTokenRefreshed = (access, refresh) => {
localStorage.setItem('tokens', JSON.stringify({ access, refresh }));
};
sdk.api.onLogout = () => {
localStorage.removeItem('tokens');
};
Hyperliquid Readiness Checks
Before trading, the wallet must be onboarded on Hyperliquid and have the required approvals:
const onboarded = await sdk.user.isHyperliquidOnboarded();
const builderOk = await sdk.user.isBuilderApproved();
const agentOk = await sdk.user.isAgentApproved();
Hyperliquid onboarding — isHyperliquidOnboarded() checks the Hyperliquid userRole endpoint and returns true only when the wallet role is user.
Builder fee approval — if isBuilderApproved() returns false, the user must send an approveBuilderFee action to the Hyperliquid exchange endpoint (docs):
{
"type": "approveBuilderFee",
"builder": "0xA47D4d99191db54A4829cdf3de2417E527c3b042",
"maxFeeRate": "0.06%"
}
Agent wallet approval — if isAgentApproved() returns false, create and approve via:
await sdk.api.private.agentWallet.create();
3. Trading
Open a Position
await sdk.api.private.positions.create({
usdValue: 1000,
leverage: 5,
longAssets: [{ asset: 'BTC', weight: 0.25 }, { asset: 'ETH', weight: 0.25 }],
shortAssets: [{ asset: 'SOL', weight: 0.5 }],
executionType: 'MARKET',
slippage: 0.02,
takeProfit: { type: 'PERCENTAGE', value: 10 },
stopLoss: { type: 'PERCENTAGE', value: -5 },
autoRebalance: {
weightSource: 'POSITION_TARGET',
action: 'NOTIFY',
minimumDriftPct: 0.02,
},
});
autoRebalance is optional and attaches a position-linked auto-rebalance rule after an immediate market position is opened. See Auto Rebalance below for supported weight sources and trade/notify behavior.
Close
await sdk.api.private.positions.close(positionId, { executionType: 'MARKET' });
await sdk.api.private.positions.closeAll({ executionType: 'MARKET' });
Adjust Size & Leverage
await sdk.api.private.positions.adjust(positionId, {
adjustmentType: 'INCREASE',
usdValue: 500,
executionType: 'MARKET',
});
await sdk.api.private.positions.adjustLeverage(positionId, {
leverage: 10,
});
Update TP/SL
await sdk.api.private.positions.updateRiskParameters(positionId, {
takeProfit: { type: 'PERCENTAGE', value: 15 },
stopLoss: { type: 'PERCENTAGE', value: -8, trailingStop: true },
});
Rebalance Weights
Preview then execute weight changes on an existing position:
const { data: plan } = await sdk.api.private.positions.planRebalance(positionId, {
targetWeights: { BTC: 0.3, ETH: 0.2, SOL: 0.5 },
});
await sdk.api.private.positions.rebalance(positionId, {
targetWeights: { BTC: 0.3, ETH: 0.2, SOL: 0.5 },
});
Auto Rebalance
Attach a position-linked auto-rebalance rule. Positions must have at least one active long asset and one active short asset (1:1, 1:n, n:1, and n:n are supported).
Use the position's stored target weights:
await sdk.api.private.positions.upsertAutoRebalance(positionId, {
weightSource: 'POSITION_TARGET',
action: 'NOTIFY',
minimumDriftPct: 0.02,
});
Use Agent Pear beta weighting. The backend calls the stat-arb pair API for 1:1 positions and the basket API for 1:n, n:1, or n:n positions:
await sdk.api.private.positions.upsertAutoRebalance(positionId, {
weightSource: 'AGENT_PEAR_BETA_WEIGHTING',
timeframe: '1h',
action: 'TRADE',
minimumDriftPct: 0.02,
});
Read and acknowledge the current config:
const { data: autoRebalance } =
await sdk.api.private.positions.getAutoRebalance(positionId);
autoRebalance.expectedWeights;
autoRebalance.notifiedAt;
autoRebalance.acknowledgedAt;
await sdk.api.private.positions.acknowledgeAutoRebalance(positionId);
Adjust (Advanced)
Set target absolute sizes per asset on an existing position:
await sdk.api.private.positions.adjustAdvance(positionId, [{
longAssets: [{ asset: 'BTC', size: 0.5 }, { asset: 'ETH', size: 2.0 }],
shortAssets: [{ asset: 'SOL', size: 10.0 }],
}]);
TWAP Orders
await sdk.api.private.positions.create({
usdValue: 5000,
leverage: 3,
longAssets: [{ asset: 'BTC', weight: 0.5 }],
shortAssets: [{ asset: 'ETH', weight: 0.5 }],
executionType: 'TWAP',
twapDuration: 3600,
twapInterval: 300,
});
Spot Orders
await sdk.api.private.orders.spot({
coin: 'HYPE',
isBuy: true,
sz: 100,
limitPx: null,
});
Cancel Orders
await sdk.api.private.orders.cancel(orderId);
await sdk.api.private.orders.cancelTwap(orderId);
4. Tracking
All subscriptions return an unsubscribe function. Require sdk.setAddress() first.
Positions
Enriched with real-time mark prices from Hyperliquid.
const unsub = sdk.user.positions((positions) => {
for (const pos of positions) {
pos.positionId;
pos.entryRatio;
pos.markRatio;
pos.unrealizedPnl;
pos.unrealizedPnlPercentage;
pos.marginUsed;
pos.positionValue;
pos.stopLoss;
pos.takeProfit;
pos.autoRebalance;
for (const asset of [...pos.longAssets, ...pos.shortAssets]) {
asset.coin;
asset.entryPrice;
asset.actualSize;
asset.leverage;
asset.unrealizedPnl;
asset.currentWeight;
asset.fundingPaid;
asset.liquidationPrice;
}
}
});
Orders
sdk.user.orders((orders) => {
for (const ord of orders) {
ord.orderId;
ord.orderType;
ord.status;
ord.longAssets;
ord.shortAssets;
}
});
Account & History
sdk.user.account((account) => {
account.accountMode;
account.withdrawableByCollateral;
});
const unsubMargin = sdk.user.basketMargin(
[{ symbol: 'BTC', weight: 0.5 }],
[{ symbol: 'ETH', weight: 0.5 }],
(basket) => {
const margin = basket.calculate({
leverageBySymbol: { BTC: 5, ETH: 5 },
notionalUsd: 1000,
});
margin.maxSizeUsd;
margin.canOpen;
margin.perCollateral;
margin.availableMarginByCollateral;
},
);
const maxSize = sdk.user.getMaxBasketSize(
[{ symbol: 'BTC', weight: 0.5 }],
[{ symbol: 'ETH', weight: 0.5 }],
{ leverageBySymbol: { BTC: 5, ETH: 5 } },
);
sdk.user.accountSummary((summary) => { ... });
sdk.user.tradeHistories((histories) => { ... });
sdk.user.twapDetails((twaps) => { ... });
sdk.user.notifications((notifications) => {
for (const n of notifications) {
n.category;
n.parameters;
n.is_read;
}
});
5. REST API Reference
Public (no auth)
await sdk.api.public.markets.list({ searchText: 'BTC', sort: 'volume' });
await sdk.api.public.markets.active();
await sdk.api.public.fills.list({ address: '0x...', assetName: 'BTC' });
await sdk.api.public.stats.addressStats('0xabc,0xdef');
Private (requires auth)
await sdk.api.private.accounts.summary();
await sdk.api.private.portfolio.get();
await sdk.api.private.agentWallet.get();
await sdk.api.private.agentWallet.create();
await sdk.api.private.apiKeys.list();
await sdk.api.private.apiKeys.create({ name: 'Bot Key' });
await sdk.api.private.positions.list();
await sdk.api.private.positions.create({ ... });
await sdk.api.private.positions.close(id, { ... });
await sdk.api.private.positions.closeAll({ ... });
await sdk.api.private.positions.adjust(id, { ... });
await sdk.api.private.positions.adjustAdvance(id, [{ longAssets: [{ asset, size }], shortAssets: [{ asset, size }] }]);
await sdk.api.private.positions.adjustLeverage(id, { ... });
await sdk.api.private.positions.updateRiskParameters(id, { ... });
await sdk.api.private.positions.planRebalance(id, { targetWeights });
await sdk.api.private.positions.rebalance(id, { targetWeights });
await sdk.api.private.positions.getAutoRebalance(id);
await sdk.api.private.positions.upsertAutoRebalance(id, {
weightSource: 'AGENT_PEAR_BETA_WEIGHTING',
timeframe: '1h',
action: 'NOTIFY',
minimumDriftPct: 0.02,
});
await sdk.api.private.positions.acknowledgeAutoRebalance(id);
await sdk.api.private.orders.list({ page: 1, limit: 50, status: 'EXECUTED' });
await sdk.api.private.orders.open();
await sdk.api.private.orders.twap();
await sdk.api.private.orders.cancel(orderId);
await sdk.api.private.orders.cancelTwap(orderId);
await sdk.api.private.orders.spot({ ... });
await sdk.api.private.orders.triggers({ category: 'TP' });
await sdk.api.private.orders.kalshiTriggers({ category: 'active', search: '', cursor: '', pageSize: 20 });
await sdk.api.private.tradeHistory.list({ limit: 100 });
await sdk.api.private.notifications.list({ limit: 50 });
await sdk.api.private.notifications.markRead({ ids: ['...'] });
await sdk.api.private.watchlist.get();
await sdk.api.private.watchlist.toggle({ longAssets: [...], shortAssets: [...] });
await sdk.api.private.sync.fills({ user, fills, assetPositions });
6. Error Handling
All API methods throw PearApiError on failure. Each error carries a machine-readable code, HTTP statusCode, and human-readable message.
import { PearApiError, PearErrorCode } from '@pear-protocol/hyperliquid';
try {
await sdk.api.private.positions.close(positionId, { executionType: 'MARKET' });
} catch (err) {
if (err instanceof PearApiError) {
err.statusCode;
err.code;
err.message;
err.originalError;
}
}
Handling by Status Code
try {
await sdk.api.private.positions.create({ ... });
} catch (err) {
if (!(err instanceof PearApiError)) throw err;
switch (err.statusCode) {
case 400: showValidationError(err.message); break;
case 404: showNotFound(err.message); break;
case 408: retryAfterDelay(); break;
case 409: showConflict(err.message); break;
case 429: backoff(); break;
default: showGenericError(); break;
}
}
Handling by Error Code
if (err instanceof PearApiError && err.code === PearErrorCode.ACTIVE_TRADE_TIMEOUT) {
await sleep(2000);
return retry();
}
Network Errors
When the server is unreachable, PearApiError is thrown with statusCode: 0 and code: 'NETWORK_ERROR'.
if (err instanceof PearApiError && err.statusCode === 0) {
showOfflineMessage();
}
Error Codes Reference
POSITION_NOT_FOUND | 404 | Position doesn't exist or already closed |
POSITION_NOT_OPEN | 400 | Position exists but isn't open |
POSITION_UNAUTHORIZED | 403 | Position belongs to another user |
ORDER_NOT_FOUND | 404 | Order doesn't exist |
INVALID_ORDER_STATUS | 400 | Order can't be cancelled in current status |
INVALID_ORDER_TYPE | 400 | Wrong order type for the endpoint |
ACTIVE_TRADE_TIMEOUT | 408 | Another trade is still processing |
UNSUPPORTED_EXECUTION_TYPE | 400 | Execution type not supported for this action |
DUPLICATE_TRIGGER | 409 | Position already has an active trigger |
INVALID_ADDRESS | 400 | Malformed wallet address |
MISSING_REQUIRED_FIELD | 400 | Required parameter missing |
INVALID_FIELD_VALUE | 400 | Parameter value is invalid |
UNSUPPORTED_TRIGGER_TYPE | 400 | Trigger type not supported |
INVALID_POSITION_STRUCTURE | 400 | Position assets don't match trigger requirements |
INVALID_LADDER_CONFIG | 400 | Missing or invalid ladder configuration |
TWAP_DURATION_REQUIRED | 400 | TWAP order missing duration |
TWAP_INSUFFICIENT_VALUE | 400 | Asset value too low for TWAP chunking |
INVALID_RISK_PARAMETERS | 400 | Can't update TP/SL for this position |
HL_CANCEL_FAILED | 500 | Failed to cancel order on Hyperliquid |
LEVERAGE_CONFIG_FAILED | 400 | Leverage configuration failed |
VAULT_WALLET_NOT_FOUND | 404 | Vault wallet doesn't exist |
VAULT_UNAUTHORIZED | 403 | Vault doesn't belong to this user |
VAULT_UNSUPPORTED_TOKEN | 400 | Token not supported for this vault operation |
INTERNAL_ERROR | 500 | Unexpected server error |
NETWORK_ERROR | 0 | Server unreachable |
7. Cleanup
sdk.destroy();