
Research
/Security News
Miasma Mini Shai-Hulud Hits ImmobiliareLabs npm Packages
Miasma Mini Shai-Hulud hits @immobiliarelabs Backstage plugins, targeting GitLab and LDAP auth packages on npm.
@pear-protocol/hyperliquid
Advanced tools
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',
});
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);
}
});
| Field | Type | Description |
|---|---|---|
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 |
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; // geometric mean ratio
snapshot.priceRatio; // long/short price (1L:1S only, else null)
snapshot.price; // single-asset price (1L or 1S only, else null)
snapshot.weightedRatio24hAgo; // weightedRatio 24h ago
snapshot.priceRatio24hAgo; // priceRatio 24h ago (1L:1S only, else null)
snapshot.price24hAgo; // price 24h ago (1L or 1S only, else null)
snapshot.netFundingRate; // weighted net funding in decimal form (e.g. 0.01 for 1%)
snapshot.maxLeverage; // largest max leverage across selected assets
snapshot.maxLeverageBySymbol; // { BTC: 50, ETH: 25, ... }
},
);
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; // ('active' | 'watchlist' | 'ai-picks')[]
b.name; // optional display name
b.longTokens; // TokenSelection[] (symbol + weight)
b.shortTokens;
b.weightedRatio; // computed from live prices
b.netFundingRate;
b.oi; // basket open interest (USD)
b.volume; // basket 24h volume (USD)
b.maxLeverage;
b.maxLeverageBySymbol;
}
});
Filter by category on the client:
const aiPicks = baskets.filter((b) => b.categories.includes('ai-picks'));
Historical and real-time OHLCV candle data for any basket or individual asset.
const { chart } = sdk.market;
// 1. Configure tokens
chart.setTokens(
[{ symbol: 'BTC', weight: 0.25 }, { symbol: 'ETH', weight: 0.25 }],
[{ symbol: 'SOL', weight: 0.5 }],
);
// 2. Set candle interval (default: '1h')
chart.setCandleInterval('15m');
// 3. Fetch historical bars (requires chart type)
const bars = await chart.getBars('weighted-ratio', startMs, endMs);
const assetBars = await chart.getAssetBars('BTC', startMs, endMs);
// 4. Real-time basket updates
const subId = chart.subscribeRealtimeBars('weighted-ratio', (bar) => {
console.log(bar.time, bar.close);
});
// 5. Real-time single-asset updates
const assetSubId = chart.subscribeRealtimeAssetBars('BTC', (bar) => {
console.log(bar.time, bar.close);
});
// 6. Unsubscribe
chart.unsubscribeRealtimeBars(subId);
chart.unsubscribeRealtimeBars(assetSubId);
// 7. Get earliest data boundary (useful for "no data" pagination)
const boundary = chart.getEffectiveDataBoundary(); // ms timestamp | null
// 8. Clear cached data (call after changing tokens or interval)
chart.clearCache();
// 9. Cleanup
chart.destroy();
| Type | Description |
|---|---|
'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 |
type CandleInterval =
| '1m' | '3m' | '5m' | '15m' | '30m'
| '1h' | '2h' | '4h' | '8h' | '12h'
| '1d' | '3d' | '1w' | '1M';
interface Bar {
time: number; // ms timestamp
open: number;
high: number;
low: number;
close: number;
}
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; // 'BTC'
snapshot.bids; // price-descending levels
snapshot.asks; // price-ascending levels
snapshot.aggregation; // active aggregation bucket
snapshot.ts; // ms timestamp
});
const bbo = orderbook.bbo;
bbo.bestBid;
bbo.bestAsk;
bbo.spread;
bbo.spreadPct;
orderbook.setAggregation(100);
const snapshot = orderbook.getSnapshot(); // OrderbookSnapshot | null
orderbook.unsubscribe(subId);
orderbook.destroy();
interface OrderbookLevel {
price: number;
size: number;
}
interface OrderbookSnapshot {
symbol: string;
bids: OrderbookLevel[];
asks: OrderbookLevel[];
aggregation: number;
ts: number;
}
The SDK chart plugs into TradingView's IBasicDataFeed. Two key methods to wire up:
getBars — Historical DataConvert 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.
// Map TradingView resolution strings to SDK intervals:
// '1' → '1m', '5' → '5m', '60' → '1h', '240' → '4h', '1D' → '1d', etc.
const sdkInterval = tvResolutionToSdkInterval(resolution);
chart.setCandleInterval(sdkInterval);
const startMs = periodParams.from * 1000;
const endMs = periodParams.to * 1000;
// For basket symbols → chart.getBars(chartType, startMs, endMs)
// For individual assets (e.g. 'LONG:BTC') → chart.getAssetBars('BTC', startMs, endMs)
const bars = await chart.getBars('weighted-ratio', startMs, endMs);
// When no data, use getEffectiveDataBoundary() to tell TradingView where data starts
if (bars.length === 0) {
const boundary = chart.getEffectiveDataBoundary();
onResult([], { noData: true, nextTime: boundary ? boundary / 1000 : undefined });
}
subscribeBars — Real-time UpdatesSubscribe to live bar updates. Map each TradingView listenerGuid to an SDK subscription ID for cleanup.
// For basket symbols:
const subId = chart.subscribeRealtimeBars('weighted-ratio', (bar) => {
onTick({ time: bar.time, open: bar.open, high: bar.high, low: bar.low, close: bar.close });
});
// For individual assets (used in 'performance' overlays):
const subId = chart.subscribeRealtimeAssetBars('BTC', (bar) => {
onTick({ time: bar.time, open: bar.open, high: bar.high, low: bar.low, close: bar.close });
});
// Unsubscribe:
chart.unsubscribeRealtimeBars(subId);
EIP-712 signature-based login. Tokens auto-refresh on 401.
// 1. Get message to sign
const { data: msg } = await sdk.api.public.auth.getEIP712Message(address, clientId, chainId);
// 2. Sign with wallet (e.g. wagmi signTypedData)
const signature = await signTypedData({ ... });
// 3. Login
await sdk.api.public.auth.login({
address,
clientId: CLIENT_ID,
details: { signature, timestamp: eip712.timestamp, chainId },
method: 'eip712',
});
// 4. Set address for WebSocket subscriptions
sdk.setAddress(address);
Token persistence:
// Restore from storage
sdk.api.setTokens(savedAccessToken, savedRefreshToken);
// Read current tokens
const accessToken = sdk.api.getAccessToken();
const refreshToken = sdk.api.getRefreshToken();
// Listen for refreshes
sdk.api.onTokenRefreshed = (access, refresh) => {
localStorage.setItem('tokens', JSON.stringify({ access, refresh }));
};
sdk.api.onLogout = () => {
localStorage.removeItem('tokens');
};
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):
// Sign and send via Hyperliquid exchange API
{
"type": "approveBuilderFee",
"builder": "0xA47D4d99191db54A4829cdf3de2417E527c3b042",
"maxFeeRate": "0.06%"
}
Agent wallet approval — if isAgentApproved() returns false, create and approve via:
await sdk.api.private.agentWallet.create();
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.
await sdk.api.private.positions.close(positionId, { executionType: 'MARKET' });
await sdk.api.private.positions.closeAll({ executionType: 'MARKET' });
await sdk.api.private.positions.adjust(positionId, {
adjustmentType: 'INCREASE',
usdValue: 500,
executionType: 'MARKET',
});
await sdk.api.private.positions.adjustLeverage(positionId, {
leverage: 10,
});
await sdk.api.private.positions.updateRiskParameters(positionId, {
takeProfit: { type: 'PERCENTAGE', value: 15 },
stopLoss: { type: 'PERCENTAGE', value: -8, trailingStop: true },
});
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 },
});
// plan.assets — what will be traded
// plan.skippedAssets — what can't be changed and why
await sdk.api.private.positions.rebalance(positionId, {
targetWeights: { BTC: 0.3, ETH: 0.2, SOL: 0.5 },
});
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, // 2%
});
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; // last expected weights computed by the bot
autoRebalance.notifiedAt; // set when notify-mode drift is emitted
autoRebalance.acknowledgedAt; // set after acknowledgement
await sdk.api.private.positions.acknowledgeAutoRebalance(positionId);
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 }],
}]);
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,
});
await sdk.api.private.orders.spot({
coin: 'HYPE',
isBuy: true,
sz: 100,
limitPx: null,
});
await sdk.api.private.orders.cancel(orderId);
await sdk.api.private.orders.cancelTwap(orderId);
All subscriptions return an unsubscribe function. Require sdk.setAddress() first.
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;
}
}
});
sdk.user.orders((orders) => {
for (const ord of orders) {
ord.orderId;
ord.orderType; // 'MARKET' | 'TRIGGER' | 'TWAP' | 'TP' | 'SL'
ord.status; // 'OPEN' | 'PROCESSING' | 'EXECUTED'
ord.longAssets;
ord.shortAssets;
}
});
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; // 'TP_ORDER_FILLED' | 'POSITION_LIQUIDATED' | ...
n.parameters; // typed per category
n.is_read;
}
});
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');
// Account
await sdk.api.private.accounts.summary();
await sdk.api.private.portfolio.get();
// Agent wallet
await sdk.api.private.agentWallet.get();
await sdk.api.private.agentWallet.create();
// API keys
await sdk.api.private.apiKeys.list();
await sdk.api.private.apiKeys.create({ name: 'Bot Key' });
// Positions — see Trading section above
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);
// Orders
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 });
// History
await sdk.api.private.tradeHistory.list({ limit: 100 });
await sdk.api.private.notifications.list({ limit: 50 });
await sdk.api.private.notifications.markRead({ ids: ['...'] });
// Watchlist
await sdk.api.private.watchlist.get();
await sdk.api.private.watchlist.toggle({ longAssets: [...], shortAssets: [...] });
// Sync
await sdk.api.private.sync.fills({ user, fills, assetPositions });
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; // 404
err.code; // 'POSITION_NOT_FOUND'
err.message; // 'Position abc123 not found or already closed'
err.originalError; // raw AxiosError (for headers, response body, etc.)
}
}
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; // ACTIVE_TRADE_TIMEOUT
case 409: showConflict(err.message); break;
case 429: backoff(); break;
default: showGenericError(); break;
}
}
if (err instanceof PearApiError && err.code === PearErrorCode.ACTIVE_TRADE_TIMEOUT) {
// Another trade is in progress, retry shortly
await sleep(2000);
return retry();
}
When the server is unreachable, PearApiError is thrown with statusCode: 0 and code: 'NETWORK_ERROR'.
if (err instanceof PearApiError && err.statusCode === 0) {
showOfflineMessage();
}
| Code | Status | When |
|---|---|---|
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 |
sdk.destroy(); // closes all WebSocket connections and subscriptions
FAQs
SDK for Pear Protocol Hyperliquid API
The npm package @pear-protocol/hyperliquid receives a total of 266 weekly downloads. As such, @pear-protocol/hyperliquid popularity was classified as not popular.
We found that @pear-protocol/hyperliquid 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.

Research
/Security News
Miasma Mini Shai-Hulud hits @immobiliarelabs Backstage plugins, targeting GitLab and LDAP auth packages on npm.

Security News
Rolldown paused Rust React Compiler integration after a 5MB binary size increase raised concerns about shipping React-specific code to all Vite users.

Security News
/Research
Mini Shai-Hulud expands into the Go ecosystem after hitting LeoPlatform npm packages and targeting GitHub Actions workflows.