lynx-graph-sdk
v0.0.7
Published
TypeScript SDK for querying Lynx Protocol subgraphs
Downloads
223
Maintainers
Readme
Lynx Graph SDK
A TypeScript SDK for querying Lynx Protocol subgraphs across multiple chains. This SDK provides type-safe queries, helper functions, and utilities for both frontend and backend applications.
🚀 Latest Release (v0.0.4)
New Features
- 🛡️ Advanced Error Handling: Custom error classes with retry logic and recovery suggestions
- ✅ Input Validation: Comprehensive validation for all inputs with helpful error messages
- 🚦 Rate Limiting: Built-in rate limiting with request queuing and priority support
- 📊 Enhanced Analytics: Improved metrics calculation using actual subgraph data
- 🔄 Multi-Network Support: Seamless switching between Sonic, Boba, and Flare networks
Installation
npm install lynx-graph-sdk
# or with yarn
yarn add lynx-graph-sdk@beta
# or with pnpm
pnpm add lynx-graph-sdk@betaRequirements
- Node.js >= 18.0.0
- TypeScript >= 5.3.0 (for TypeScript projects)
- ethers ^5.7.2 (included as dependency)
Quick Start
import {
LynxGraphClient,
Network,
PositionPhase,
ValidationError,
RateLimitError
} from 'lynx-graph-sdk';
// Initialize client with your subgraph endpoints
const client = new LynxGraphClient({
endpoints: [
{
url: 'https://api.studio.thegraph.com/query/YOUR_ID/lynx-sonic/version/latest',
network: Network.SONIC,
chainId: 146,
name: 'Sonic'
},
// Add other networks as needed
],
defaultNetwork: Network.SONIC,
cacheEnabled: true,
cacheTTL: 60, // 1 minute
maxRetries: 3,
retryDelay: 1000,
});
// Query with automatic validation and error handling
try {
const positions = await client.getPositions({
trader: '0x123...', // Automatically validated
currentPhase: PositionPhase.OPENED,
first: 10,
});
} catch (error) {
if (error instanceof ValidationError) {
console.error('Invalid input:', error.suggestion);
} else if (error instanceof RateLimitError) {
console.error('Rate limited, retry after:', error.retryAfter);
}
}
// Get pool statistics with enhanced metrics
const poolHelpers = new PoolHelpers(client);
const tvl = await poolHelpers.getPoolTVL('sonic-usdc');
console.log('TVL:', tvl.tvl);
console.log('Pending Deposits:', tvl.pendingDeposits);Configuration
Required: Subgraph Endpoints
You must provide your own subgraph endpoints when initializing the client. The SDK does not include default endpoints.
const endpoints = [
{
url: 'https://your-sonic-subgraph-url',
network: Network.SONIC,
chainId: 146,
name: 'Sonic'
},
{
url: 'https://your-boba-subgraph-url',
network: Network.BOBA,
chainId: 288,
name: 'Boba'
},
{
url: 'https://your-flare-subgraph-url',
network: Network.FLARE,
chainId: 14,
name: 'Flare'
}
];
const client = new LynxGraphClient({ endpoints });Features
🛡️ Enterprise-Grade Error Handling
import { LynxSDKError, NetworkError, DefaultErrorHandler } from 'lynx-graph-sdk';
// Custom error handler with retry logic
const errorHandler = new DefaultErrorHandler(
3, // max retries
1000, // base delay
30000 // max delay
);
// Automatic retry for transient errors
if (error instanceof NetworkError && errorHandler.shouldRetry(error)) {
const delay = errorHandler.getRetryDelay(error, attemptNumber);
await new Promise(resolve => setTimeout(resolve, delay));
// Retry the operation
}✅ Comprehensive Input Validation
import { validateAddress, validateBigNumber, ValidationChain } from 'lynx-graph-sdk';
// Individual validators
const address = validateAddress('0x742d...', 'trader');
const amount = validateBigNumber('1000000', 'collateral', {
min: '100000',
max: '10000000000',
allowZero: false
});
// Batch validation
const chain = new ValidationChain();
chain
.check(() => validateAddress(userAddress))
.check(() => validateLeverage(leverage))
.check(() => validateTimestamp(timestamp));
if (!chain.isValid()) {
console.error('Validation errors:', chain.getErrors());
}🚦 Advanced Rate Limiting
import { RateLimiter, MultiNetworkRateLimiter } from 'lynx-graph-sdk';
// Per-network rate limiting
const networkLimiter = new MultiNetworkRateLimiter({
SONIC: { capacity: 10, refillRate: 2 },
BOBA: { capacity: 5, refillRate: 1 },
FLARE: { capacity: 8, refillRate: 1.5 }
});
// Execute with automatic queuing
await networkLimiter.execute(
'SONIC',
async () => client.getPositions(filter),
{ priority: 10 } // High priority request
);🔍 Type-Safe Queries
All queries are fully typed with TypeScript, providing autocomplete and type checking.
🌐 Multi-Chain Support
Built-in support for:
- Sonic (Fantom)
- Boba Network
- Flare Network
🚀 Performance
- Built-in caching with TTL
- Automatic retries with exponential backoff
- Request batching and deduplication
- Optimized GraphQL queries
- Rate limiting to prevent 429 errors
🛠 Helper Functions
Pre-built helpers for common use cases:
- Position analysis with P&L tracking
- Pool metrics and TVL calculations
- Analytics and trend analysis
- Trader statistics and leaderboards
- Risk assessment tools
Core Concepts
Client Configuration
const client = new LynxGraphClient({
// Custom endpoints (optional)
endpoints: [
{
url: 'https://your-subgraph-url',
network: Network.SONIC,
chainId: 146,
name: 'Sonic',
},
],
// Default network
defaultNetwork: Network.SONIC,
// Retry configuration
maxRetries: 3,
retryDelay: 1000,
// Request timeout
timeout: 30000,
// Cache configuration
cacheEnabled: true,
cacheTTL: 60, // seconds
});Network Switching
// Switch network
client.setNetwork(Network.BOBA);
// Get current network
const currentNetwork = client.getNetwork();
// Get available networks
const networks = client.getAvailableNetworks();Query Examples
Position Queries
import { PositionPhase, Direction, Trigger } from 'lynx-graph-sdk';
// Get all positions with filters
const positions = await client.getPositions({
trader: '0x123...',
pool: 'sonic-usdc',
direction: Direction.LONG,
currentPhase: PositionPhase.OPENED,
leverage_gte: '200', // 2x leverage
openTimestamp_gte: Math.floor(Date.now() / 1000) - 86400, // Last 24h
orderBy: PositionOrderBy.openTimestamp,
orderDirection: OrderBy.DESC,
first: 50,
});
// Get specific position
const position = await client.getPositionById('position-id');
// Get active positions for a trader
const activePositions = await client.getActivePositions(
'0x123...', // trader
'sonic-usdc', // pool (optional)
{ first: 100 }
);Pool Queries
// Get all pools
const pools = await client.getPools({
sourceChainId: 146, // Sonic
orderBy: PoolOrderBy.name,
});
// Get pool details
const pool = await client.getPoolById('sonic-usdc');
// Get pool epochs
const epochs = await client.getPoolEpochs('sonic-usdc', {
epochNumber_gte: 100,
first: 10,
});
// Get liquidity providers
const providers = await client.getProviders({
pool: 'sonic-usdc',
amountSupplying_gte: '1000000000000000000000', // 1000 tokens
orderBy: ProviderOrderBy.amountSupplying,
orderDirection: OrderBy.DESC,
});Analytics Queries
// Get daily analytics
const dailyData = await client.getDailyAnalytics({
pool: 'sonic-usdc',
timestamp_gte: Math.floor(Date.now() / 1000) - (30 * 86400), // Last 30 days
orderBy: TimeBucketOrderBy.timestamp,
orderDirection: OrderBy.ASC,
});
// Get hourly analytics
const hourlyData = await client.getHourlyAnalytics({
pool: 'sonic-usdc',
timestamp_gte: Math.floor(Date.now() / 1000) - 86400, // Last 24h
});
// Get global statistics
const globalStats = await client.getGlobalStats('sonic-usdc');Helper Classes
PositionHelpers
import { PositionHelpers } from 'lynx-graph-sdk';
const positionHelpers = new PositionHelpers(client);
// Get trader's open positions
const openPositions = await positionHelpers.getTraderOpenPositions('0x123...');
// Get P&L history
const pnlHistory = await positionHelpers.getTraderPnLHistory(
'0x123...',
new Date('2024-01-01'),
new Date()
);
console.log(`Total P&L: ${pnlHistory.totalPnL}`);
console.log(`Win Rate: ${pnlHistory.winRate}%`);
// Get positions at risk of liquidation
const riskyPositions = await positionHelpers.getPositionsAtRisk(
'sonic-usdc',
0.2 // 20% from liquidation
);
// Calculate aggregate statistics
const stats = positionHelpers.calculateStats(positions);
console.log(`Total Volume: ${stats.totalVolume}`);
console.log(`Average Leverage: ${stats.avgLeverage}x`);PoolHelpers
import { PoolHelpers } from 'lynx-graph-sdk';
const poolHelpers = new PoolHelpers(client);
// Get TVL
const tvl = await poolHelpers.getPoolTVL('sonic-usdc');
console.log(`TVL: $${tvl.tvl}`);
console.log(`Net TVL: $${tvl.netTVL}`);
// Get utilization
const utilization = await poolHelpers.getPoolUtilization('sonic-usdc');
console.log(`Utilization Rate: ${utilization.utilizationRate}%`);
console.log(`Available Liquidity: $${utilization.availableLiquidity}`);
// Get pool performance
const performance = await poolHelpers.getPoolPerformance('sonic-usdc', 30);
console.log(`APY: ${performance.apy}%`);
console.log(`Daily Volume: $${performance.avgDailyVolume}`);
// Get top providers
const topProviders = await poolHelpers.getTopProviders('sonic-usdc', 10);
topProviders.forEach(({ provider, sharePercent }) => {
console.log(`${provider.address}: ${sharePercent}%`);
});
// Check pool health
const health = await poolHelpers.getPoolHealth('sonic-usdc');
if (!health.isHealthy) {
console.log('Warnings:', health.warnings.join(', '));
}AnalyticsHelpers
import { AnalyticsHelpers } from 'lynx-graph-sdk';
const analyticsHelpers = new AnalyticsHelpers(client);
// Get protocol-wide stats
const protocolStats = await analyticsHelpers.getProtocolStats();
console.log(`Total Volume: $${protocolStats.totalVolume}`);
console.log(`Total Positions: ${protocolStats.totalPositions}`);
// Get volume trends
const volumeTrends = await analyticsHelpers.getVolumeTrends(
'sonic-usdc',
30,
'day'
);
// Get fee breakdown
const feeTrends = await analyticsHelpers.getFeeTrends('sonic-usdc', 30);
// Get open interest trends
const oiTrends = await analyticsHelpers.getOpenInterestTrends(
'sonic-usdc',
30,
'hour'
);
// Compare pools
const comparison = await analyticsHelpers.comparePoolsPerformance(
['sonic-usdc', 'sonic-eth'],
30
);Utility Functions
Formatting
import {
formatBigNumber,
formatPrice,
formatLeverage,
formatPercentage,
formatTimestamp,
calculatePnlPercentage
} from 'lynx-graph-sdk';
// Format amounts with decimals
const formatted = formatBigNumber('1000000000000000000', 18); // "1.0"
// Format prices (8 decimals)
const price = formatPrice('10000000000'); // "100.00"
// Format leverage
const leverage = formatLeverage('200'); // "2.00"
// Calculate P&L percentage
const pnlPercent = calculatePnlPercentage(
'50000000000000000000', // 50 profit
'1000000000000000000000' // 1000 collateral
); // "5.00%"Address Validation
import { isValidAddress, normalizeAddress } from 'lynx-graph-sdk';
if (isValidAddress(userInput)) {
const normalized = normalizeAddress(userInput);
// Use normalized address
}Advanced Usage
Custom Queries
import { gql } from 'lynx-graph-sdk';
const CUSTOM_QUERY = gql`
query GetCustomData($pool: String!) {
lexPoolEntity(id: $pool) {
id
name
positions(first: 10, orderBy: openTimestamp, orderDirection: desc) {
id
trader {
id
}
extra_pnl
}
}
}
`;
// Use raw GraphQL client
const graphClient = client.getGraphQLClient(Network.SONIC);
const data = await graphClient.request(CUSTOM_QUERY, { pool: 'sonic-usdc' });Query Builder
The SDK includes a powerful QueryBuilder for creating dynamic GraphQL queries with conditional where clauses:
import { QueryBuilder } from 'lynx-graph-sdk';
// Build dynamic queries with conditional filters
const builder = new QueryBuilder('positions', 'GetPositions')
.select('id', 'trader { id }', 'extra_pnl')
.paginate(50, 0)
.orderBy('openTimestamp', 'desc');
// Only add where conditions if values exist
if (traderId) builder.where('trader', traderId);
if (minLeverage) builder.where('leverage', minLeverage, 'gte');
if (poolId) builder.where('pool', poolId);
// Build the query - automatically handles null/undefined values
const { query, variables } = builder.buildConditional();
// The QueryBuilder solves The Graph's null value filter issues
// It only includes where clauses for defined valuesCache Management
// Clear all cache
client.clearCache();
// Clear specific cache key
client.clearCacheForKey('getPoolById:sonic-usdc');
// Disable cache for specific query
const client = new LynxGraphClient({
cacheEnabled: false, // Disable globally
});Error Handling
The SDK provides comprehensive error handling with specific error types and recovery suggestions:
import {
LynxSDKError,
NetworkError,
ValidationError,
QueryError,
TimeoutError,
RateLimitError,
DataIntegrityError,
ConfigurationError,
SubgraphSyncError,
isLynxSDKError,
getErrorType
} from 'lynx-graph-sdk';
try {
const positions = await client.getPositions({
trader: '0xinvalid'
});
} catch (error) {
if (isLynxSDKError(error)) {
console.error(`Error [${error.code}]: ${error.message}`);
console.log('Suggestion:', error.suggestion);
console.log('Context:', error.context);
// Handle specific error types
switch (error.code) {
case 'VALIDATION_ERROR':
// Show validation message to user
break;
case 'RATE_LIMIT_ERROR':
// Wait and retry
const rateLimitError = error as RateLimitError;
await new Promise(r => setTimeout(r, rateLimitError.retryAfter * 1000));
break;
case 'NETWORK_ERROR':
case 'TIMEOUT_ERROR':
// Retry with backoff
break;
case 'SUBGRAPH_SYNC_ERROR':
// Use historical data or wait for sync
const syncError = error as SubgraphSyncError;
console.log(`Subgraph is ${syncError.chainBlock - syncError.syncedBlock} blocks behind`);
break;
}
}
}Error Recovery
The SDK includes automatic retry logic for transient errors:
import { DefaultErrorHandler } from 'lynx-graph-sdk';
const errorHandler = new DefaultErrorHandler();
async function queryWithRetry<T>(
operation: () => Promise<T>,
maxAttempts = 3
): Promise<T> {
let lastError: LynxSDKError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
if (!isLynxSDKError(error) || !errorHandler.shouldRetry(error)) {
throw error;
}
lastError = error;
if (attempt < maxAttempts) {
const delay = errorHandler.getRetryDelay(error, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError!;
}Integration Examples
React Hook
import { useState, useEffect } from 'react';
import { LynxGraphClient, Position, Network } from 'lynx-graph-sdk';
function useTraderPositions(trader: string, network: Network) {
const [positions, setPositions] = useState<Position[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const client = new LynxGraphClient({ defaultNetwork: network });
async function fetchPositions() {
try {
setLoading(true);
const data = await client.getTraderPositions(trader);
setPositions(data);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}
fetchPositions();
}, [trader, network]);
return { positions, loading, error };
}Node.js Backend
import express from 'express';
import { LynxGraphClient, PoolHelpers, Network } from 'lynx-graph-sdk';
const app = express();
const client = new LynxGraphClient({
defaultNetwork: Network.SONIC,
cacheEnabled: true,
cacheTTL: 300, // 5 minutes
});
const poolHelpers = new PoolHelpers(client);
app.get('/api/pools/:poolId/tvl', async (req, res) => {
try {
const tvl = await poolHelpers.getPoolTVL(req.params.poolId);
res.json(tvl);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/api/pools/:poolId/health', async (req, res) => {
try {
const health = await poolHelpers.getPoolHealth(req.params.poolId);
res.json(health);
} catch (error) {
res.status(500).json({ error: error.message });
}
});Type Definitions
The SDK exports all types from the GraphQL schema:
import {
// Entities
Position,
Trader,
LexPoolEntity,
Epoch,
Provider,
TimeBucketDayData,
TimeBucketHourData,
LexGlobalTracker,
// Enums
PositionPhase,
Direction,
Trigger,
OpenOrderType,
PairGroupName,
SupplyRequestState,
// Filters
PositionFilter,
PoolFilter,
EpochFilter,
// Config
Network,
SDKConfig,
} from 'lynx-graph-sdk';Troubleshooting
Common Issues
Rate Limiting
If you're hitting rate limits frequently:
// Increase rate limit configuration
const client = new LynxGraphClient({
maxRetries: 5,
retryDelay: 2000,
});
// Or use custom rate limiter
const rateLimiter = new RateLimiter({
defaultCapacity: 20,
defaultRefillRate: 2,
maxQueueSize: 100,
});Validation Errors
For address validation issues:
// Ensure addresses are properly formatted
const address = '0x' + userInput.toLowerCase().replace(/^0x/, '');
const validatedAddress = validateAddress(address);Network Switching
// Check available networks before switching
const networks = client.getAvailableNetworks();
if (networks.includes(Network.BOBA)) {
client.setNetwork(Network.BOBA);
}Migration Guide
From Direct GraphQL Queries
Before:
const query = gql`
query GetPositions($trader: String!) {
positions(where: { trader: $trader }) {
id
leverage
}
}
`;
const result = await graphClient.request(query, { trader });After:
const positions = await client.getPositions({
trader,
first: 100
});From v0.0.0 to v0.0.1-beta
- Update imports:
// Old
import { LynxGraphClient } from 'lynx-graph-sdk';
// New - import error types
import {
LynxGraphClient,
ValidationError,
RateLimitError
} from 'lynx-graph-sdk';- Add error handling:
// Wrap queries in try-catch
try {
const result = await client.getPositions(filter);
} catch (error) {
// Handle specific error types
}- Update pool helper usage:
// TVL response structure changed
const tvl = await poolHelpers.getPoolTVL(poolId);
// Now includes: tvl, pendingDeposits, pendingRedeems, netTVL📚 Documentation
Additional Documentation
For more detailed documentation and guides, check out the docs/ directory:
- SDK Integration Guide - Complete guide for integrating the Lynx Graph SDK into your application
- SDK Architecture - Detailed documentation of the SDK's internal architecture and design patterns
API Documentation
Full API documentation with all available methods, types, and examples is available in the integration guide. Key sections include:
- Client initialization and configuration
- Query methods and filters
- Helper functions for analytics and calculations
- Error handling and retry strategies
- Rate limiting and performance optimization
- Multi-network support
Contributing
- Clone the repository
- Install dependencies:
npm install - Build the SDK:
npm run build - Run tests:
npm test - Run linter:
npm run lint
Development
# Run tests in watch mode
npm run test:watch
# Check types
npm run typecheck
# Generate GraphQL types
npm run generate
# Run with debug logging
DEBUG=lynx-graph-sdk* npm testChangelog
v0.0.4 (2024-12-28)
- Critical Fix: getActivePositions now properly returns active positions
- Moved test scripts to sdk/scripts/ directory
- Added npm test commands for validation
v0.0.3 (2024-12-28)
- Initial fix attempt for getActivePositions (incomplete)
- Added comprehensive validation script
- Stable release from beta
v0.0.1-beta.2 (2024-08-17)
- Enhanced type safety and query optimizations
v0.0.1-beta.1 (2024-08-16)
- 🛡️ Added comprehensive error handling system
- ✅ Implemented input validation for all parameters
- 🚦 Added rate limiting with request queuing
- 📊 Updated helpers to use actual subgraph schema
- 🔧 Fixed TypeScript compilation issues
- 🔍 Added QueryBuilder for dynamic GraphQL queries
- 🐛 Fixed negative number formatting in BigNumber utilities
- 📚 Enhanced documentation and examples
Support
- Documentation: Full API Documentation
- Issues: GitHub Issues
- Discord: Join our Discord
License
MIT © Lynx Protocol
