@pear-protocol/symm-core
v0.1.0
Published
TypeScript SDK for Symm Engine API - trading positions, TP/SL orders, and real-time WebSocket streams
Readme
@pear-protocol/symm-core
TypeScript SDK for Symm Engine API - trading positions, TP/SL config, and real-time WebSocket streams.
Features
- ✅ TypeScript - Full type safety with comprehensive type definitions
- ✅ REST API Client - Complete API coverage for positions, orders, and TP/SL config
- ✅ WebSocket Client - Real-time data streaming with automatic reconnection
- ✅ Multi-chain - Support for Arbitrum and Base networks
- ✅ Tree-shakeable - Modular exports for optimal bundle size
- ✅ Zero dependencies - Only peer dependency on
viemfor type compatibility
Installation
# npm
npm install @pear-protocol/symm-core viem
# yarn
yarn add @pear-protocol/symm-core viem
# pnpm
pnpm add @pear-protocol/symm-core viem
# bun
bun add @pear-protocol/symm-core viemQuick Start
Basic Usage
import { createSymmSDK } from '@pear-protocol/symm-core';
// Create SDK instance
const { client, ws } = createSymmSDK({
apiUrl: 'https://api.symm.io',
wsUrl: 'wss://ws.symm.io',
defaultChainId: 42161 // Arbitrum
});
// Open a basket (single, pair, or multi-leg)
const result = await client.openBasketPosition({
accountAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
authToken: 'your-siwe-token',
longPositions: [
{ symbol: 'BTC', quantity: '1.0', leverage: 10 }
],
slippage: 'auto'
});
console.log('Position opened:', result.data);Response Shape (camelCase by default)
The SDK transforms responses to camelCase by default. To opt out and keep raw snake_case:
const sdk = createSymmSDK({
apiUrl: 'https://api.symm.io',
responseAdapter: false
});Raw types are also exported:
RawPosition, RawClosePositionResponse, RawPendingInstantOpenItem, RawAssetLeg.
Auth Provider + Retry
const sdk = createSymmSDK({
apiUrl: 'https://api.symm.io',
authProvider: async () => 'your-siwe-token',
retry: { retries: 3, backoffMs: 250 }
});WebSocket Streaming
import { createSymmWebSocket } from '@pear-protocol/symm-core';
const ws = createSymmWebSocket({
url: 'wss://ws.symm.io',
autoReconnect: true
});
// Connect
await ws.connect();
// Subscribe to position updates
ws.subscribeToPositions(
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
42161, // Arbitrum
(data) => {
console.log('Position update:', data);
}
);
// Subscribe to open orders
ws.subscribeToOpenOrders(
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
42161,
(data) => {
console.log('Open orders:', data);
}
);API Reference
REST Client
Position Management
// Open basket position
await client.openBasketPosition(request);
// Close position
await client.closePosition({
positionId: 'pos_123',
quantityToClose: '1.0',
authToken: 'token'
});
// Cancel close
await client.cancelClose({
quoteId: '123',
authToken: 'token'
});
// Advanced: open basket with polling/status updates
await sdk.positions.openBasket(request, {
pollPending: true,
onStatus: (status) => console.log(status),
onLegUpdate: (leg) => console.log(leg),
});
// Advanced: close with polling/status updates
await sdk.positions.close(
{ positionId: 'pos_123', authToken: 'token' },
{
pollPending: true,
accountAddress: '0x...',
chainId: 42161,
onStatus: (status) => console.log(status),
},
);
// Get open positions
await client.getOpenPositions({
// Use address (subaccount) or mainAddress (all subaccounts)
address: '0x...',
chainId: 42161
});
// Get positions (on-chain)
await client.getPositions({
address: '0x...'
});
// Get trade history
await client.getTradeHistory({
// Use accountAddress (subaccount) or mainAddress (all subaccounts)
accountAddress: '0x...',
limit: 100
});
// Get user stats (positions analytics)
await client.getPositionStats('0x...');
// Get quote details
await client.getQuote(quoteId);
// Get pending IDs
await client.getPendingIds({
address: '0x...'
});
// Cancel pending open
await client.cancelOpen({
quoteId: '123',
authToken: 'token'
});
// Get pending instant opens
await client.getPendingInstantOpens({
accountAddress: '0x...',
authToken: 'token'
});TP/SL Management
// Set TP/SL config
await client.setTpsl({
positionId: 'pos_123',
takeProfit: { triggerType: 'price', triggerValue: '55000' },
stopLoss: { triggerType: 'price', triggerValue: '47500' },
authToken: 'token'
});
// Cancel TP/SL config
await client.cancelTpsl({
positionId: 'pos_123',
chainId: 42161,
authToken: 'token'
});
// Get TP/SL orders
await client.getTpslOrders({
// Use address (subaccount) or mainAddress (all subaccounts)
address: '0x...',
status: 'active'
});
// Cancel TP/SL config
await client.cancelTpsl({
positionId: 'pos_123',
type: 'take_profit',
authToken: 'token'
});Orders
// Get orders (address or mainAddress)
await client.getOrders({ address: '0x...', status: 'open' });
// TWAP orders
await client.getTwapOrders({ address: '0x...' });
await client.getTwapOrder('order_123');
await client.cancelTwapOrder('order_123');
// Trigger config
await client.getTriggerConfig('order_123');
await client.upsertTriggerConfig('order_123', { config: { /* trigger config */ } });
await client.clearTriggerConfig('order_123');Accounts
await client.getAccounts({ chainId: 42161, userAddress: '0x...' });
await client.getAccountsLength({ chainId: 42161, userAddress: '0x...' });
await client.getBalanceInfo({ chainId: 42161, userAddress: '0x...' });
await client.getAccountSummary({ chainId: 42161, userAddress: '0x...' });
await client.getAccountData({ chainId: 42161, address: '0x...' });
await client.getAccountsWithPositions({ chainId: 42161, userAddress: '0x...' });Markets
// Market groups (basket pairs with ratios, volume, funding)
await client.getMarkets({ active: 'true', sort: 'volume:desc', pageSize: '50' });
await client.getMarkets({ searchText: 'BTC', netFunding: 'positive' });
// Hedger markets (individual symbol data)
await client.getHedgerMarkets({ chainId: 42161, searchText: 'BTC' });
await client.getLockedParams({ chainId: 42161, marketName: 'BTCUSDT', leverage: 10 });
await client.refreshMarketsCache(42161);Funding
await client.getFundingRates({ chainId: 42161, limit: 50 });
await client.getFundingPayments({ address: '0x...', chainId: 42161 });Metrics (Legacy Support)
await client.getMetrics();
await client.getWeeklyVolume();
await client.getUserStats({ address: '0x...' });
await client.getDailyStats({ period: '7D' });WebSocket Client
Connection Management
const ws = createSymmWebSocket({
url: 'wss://ws.symm.io',
autoReconnect: true,
reconnectInterval: 5000,
pingInterval: 30000
});
// Connect
await ws.connect();
// Disconnect
ws.disconnect();
// Check connection status
const connected = ws.isConnected();Subscriptions
// Positions (5s interval)
ws.subscribeToPositions(address, chainId, handler);
// Open orders (5s interval)
ws.subscribeToOpenOrders(address, chainId, handler);
// Trades (10s interval)
ws.subscribeToTrades(address, chainId, handler);
// Executions (2s interval)
ws.subscribeToExecutions(address, chainId, handler);
// TP/SL updates (5s interval)
ws.subscribeToTpsl(address, chainId, handler);
// Notifications (10s interval)
ws.subscribeToNotifications(address, chainId, handler);
// TWAP orders (5s interval)
ws.subscribeToTwapOrders(address, chainId, handler);
// Trigger orders (5s interval)
ws.subscribeToTriggerOrders(address, chainId, handler);
// Prediction triggers (5s interval)
ws.subscribeToPredictionTriggers(address, chainId, handler);
// Unsubscribe
ws.unsubscribe('positions', address, chainId, handler);
// Unsubscribe all
ws.unsubscribeAll();Event Handlers
// Connection events
ws.onConnect(() => console.log('Connected'));
ws.onDisconnect(() => console.log('Disconnected'));
ws.onError((error) => console.error('Error:', error));Utilities
import {
isValidAddress,
normalizeAddress,
formatWei,
applySlippage,
calculatePnlPercent,
getUnixTimestamp,
createDeadline,
isSupportedChainId,
getChainName
} from '@pear-protocol/symm-core';
// Address validation
if (isValidAddress('0x...')) {
const normalized = normalizeAddress('0x...');
}
// Number formatting
const readable = formatWei('1000000000000000000', 18, 4); // "1.0000"
// Price utilities
const priceWithSlippage = applySlippage('50000', 0.5, true); // 0.5% slippage
const pnl = calculatePnlPercent('50000', '55000', true); // 10%
// Time utilities
const now = getUnixTimestamp();
const deadline = createDeadline(300); // 5 minutes from now
// Chain utilities
if (isSupportedChainId(42161)) {
const chainName = getChainName(42161); // "Arbitrum One"
}Constants
import {
ARBITRUM_CHAIN_ID,
BASE_CHAIN_ID,
DIAMOND_ADDRESS,
CHAIN_CONFIGS,
WS_CHANNELS
} from '@pear-protocol/symm-core';
console.log(ARBITRUM_CHAIN_ID); // 42161
console.log(BASE_CHAIN_ID); // 8453
console.log(DIAMOND_ADDRESS[42161]); // "0x8F06..."
console.log(CHAIN_CONFIGS[42161]); // { chainId, name, rpcUrl, explorerUrl }Advanced Usage
Custom Configuration
import { SymmClient, SymmWebSocket } from '@pear-protocol/symm-core';
// Custom REST client
const client = new SymmClient({
baseUrl: 'https://api.symm.io',
defaultChainId: 42161,
timeout: 30000,
headers: {
'X-API-Key': 'your-key'
}
});
// Custom WebSocket client
const ws = new SymmWebSocket({
url: 'wss://ws.symm.io',
autoReconnect: true,
reconnectInterval: 5000,
maxReconnectAttempts: 10,
pingInterval: 30000
});Error Handling
import { isApiError, getErrorMessage } from '@pear-protocol/symm-core';
try {
await client.openBasketPosition(request);
} catch (error) {
if (isApiError(error)) {
console.error('API Error:', error.error);
console.error('Details:', error.details);
} else {
console.error('Error:', getErrorMessage(error));
}
}TypeScript Types
import type {
OpenBasketPositionRequest,
OpenPositionDto,
AssetLeg,
TpslOrder,
TpslOrderType,
SupportedChainId,
NotificationCategory
} from '@pear-protocol/symm-core';
const request: OpenBasketPositionRequest = {
chainId: 42161,
accountAddress: '0x...',
authToken: 'token',
longPositions: [{ symbol: 'BTC', quantity: '1.0', leverage: 10 }]
};
const handlePosition = (position: OpenPositionDto) => {
console.log(position.positionId, position.entryRatio);
console.log(position.longAssets, position.shortAssets);
};
const firstLeg: AssetLeg = {
symbolId: 1,
symbol: 'BTC',
quantity: '0.1',
openedPrice: '50000'
};
const category: NotificationCategory = NotificationCategory.POSITION_LIQUIDATED;Modular Imports
Import only what you need for optimal bundle size:
// Client only
import { createSymmClient } from '@pear-protocol/symm-core/client';
// WebSocket only
import { createSymmWebSocket } from '@pear-protocol/symm-core/websocket';
// Types only
import type { Position, TpslOrder } from '@pear-protocol/symm-core/types';
// Utils only
import { isValidAddress, formatWei } from '@pear-protocol/symm-core/utils';
// Constants only
import { ARBITRUM_CHAIN_ID, DIAMOND_ADDRESS } from '@pear-protocol/symm-core/constants';Examples
Complete Trading Flow
import { createSymmSDK } from '@pear-protocol/symm-core';
const { client, ws } = createSymmSDK({
apiUrl: 'https://api.symm.io',
wsUrl: 'wss://ws.symm.io'
});
// 1. Connect WebSocket
await ws.connect();
// 2. Subscribe to executions
ws.subscribeToExecutions('0x...', 42161, (data) => {
console.log('Trade executed:', data);
});
// 3. Open position with TP/SL
const result = await client.openBasketPosition({
accountAddress: '0x...',
authToken: 'token',
tpslConfig: {
takeProfit: {
triggerType: 'price',
triggerValue: '55000', // 10% profit
quantityToClose: '1.0'
},
stopLoss: {
triggerType: 'price',
triggerValue: '47500', // 5% loss
quantityToClose: '1.0'
}
},
longPositions: [
{
symbol: 'BTC',
quantity: '1.0',
leverage: 10
}
]
});
console.log('Position opened with TP/SL:', result);Node.js Usage
For Node.js environments, you may need to provide a WebSocket implementation:
import { createSymmWebSocket } from '@pear-protocol/symm-core';
import WebSocket from 'ws';
const ws = createSymmWebSocket({
url: 'wss://ws.symm.io',
WebSocket: WebSocket as any
});License
MIT
Support
- Documentation: https://docs.symm.io
- GitHub: https://github.com/symm/symm-engine
- Discord: https://discord.gg/symm
