Best Practices

Performance optimization tips for efficient DLMM interactions. Minimize gas usage, batch operations effectively, and implement robust patterns for production applications.

Gas Optimization

Batch Operations

// ❌ Bad: Multiple transactions
await sdk.Pair.removeLiquidity(pair, { positionId, binIds: [1] });
await sdk.Pair.removeLiquidity(pair, { positionId, binIds: [2] });
await sdk.Pair.removeLiquidity(pair, { positionId, binIds: [3] });

// ✅ Good: Single transaction
await sdk.Pair.removeLiquidity(pair, { 
  positionId, 
  binIds: [1, 2, 3] 
});

Minimize Bin Count

// Gas scales with number of bins
const gasEfficient = {
  // ❌ Excessive: 100 bins
  wide: Array.from({length: 101}, (_, i) => i - 50),
  
  // ✅ Efficient: Focused liquidity
  focused: Array.from({length: 21}, (_, i) => i - 10)
};

// Consider gas cost vs capital efficiency
function optimizeBinCount(expectedVolatility: number): number {
  if (expectedVolatility < 0.01) return 10;  // Stable
  if (expectedVolatility < 0.05) return 20;  // Normal
  return 30; // Volatile
}

Efficient Data Fetching

Cache Pair Data

class PairCache {
  private cache = new Map<string, { data: LBPair, timestamp: number }>();
  private ttl = 60000; // 1 minute
  
  async getPair(pairAddress: string): Promise<LBPair> {
    const cached = this.cache.get(pairAddress);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }
    
    const pair = await sdk.Pair.getPair(pairAddress);
    if (pair) {
      this.cache.set(pairAddress, { data: pair, timestamp: Date.now() });
    }
    
    return pair;
  }
}

Batch RPC Calls

// ❌ Bad: Sequential calls
const pairs = [];
for (const address of pairAddresses) {
  const pair = await sdk.Pair.getPair(address);
  pairs.push(pair);
}

// ✅ Good: Parallel calls
const pairs = await Promise.all(
  pairAddresses.map(address => sdk.Pair.getPair(address))
);

Selective Data Loading

// Only fetch what you need
async function getPositionSummary(positionId: string) {
  // Don't fetch full bin details if only need count
  const position = await sdk.Position.getLbPosition(positionId);
  
  // Lazy load expensive data
  let bins = null;
  const getBins = async () => {
    if (!bins) {
      const pair = await sdk.Pair.getPair(position.pair_id);
      bins = await sdk.Position.getPositionBins(pair, positionId);
    }
    return bins;
  };
  
  return { position, getBins };
}

Smart Contract Interactions

Reuse Transactions

// Build transaction once, execute multiple times
const buildAddLiquidityTx = (params: AddLiquidityParams) => {
  const tx = new Transaction();
  // ... build transaction
  return tx;
};

// Reuse for similar operations
const template = buildAddLiquidityTx(baseParams);

Optimize Call Data

// Minimize distribution arrays
function compressDistribution(
  distribution: number[]
): { indices: number[], values: number[] } {
  const indices = [];
  const values = [];
  
  distribution.forEach((value, index) => {
    if (value > 0) {
      indices.push(index);
      values.push(value);
    }
  });
  
  return { indices, values };
}

Error Handling Patterns

Retry with Backoff

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      
      // Exponential backoff
      const delay = Math.min(1000 * Math.pow(2, i), 10000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

Circuit Breaker

class CircuitBreaker {
  private failures = 0;
  private lastFailTime = 0;
  private threshold = 5;
  private timeout = 60000; // 1 minute
  
  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.isOpen()) {
      throw new Error("Circuit breaker is open");
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  private isOpen(): boolean {
    return this.failures >= this.threshold && 
           Date.now() - this.lastFailTime < this.timeout;
  }
  
  private onSuccess() {
    this.failures = 0;
  }
  
  private onFailure() {
    this.failures++;
    this.lastFailTime = Date.now();
  }
}

Memory Management

Clean Up Large Objects

// Release memory after use
async function processLargeDataset(pair: LBPair) {
  let reserves = await sdk.Pair.getPairReserves(pair);
  
  // Process data
  const result = processReserves(reserves);
  
  // Clear reference
  reserves = null;
  
  return result;
}

Stream Large Results

// Process in chunks for large datasets
async function* streamPositionBins(
  pair: LBPair,
  positionId: string,
  chunkSize: number = 50
) {
  const bins = await sdk.Position.getPositionBins(pair, positionId);
  
  for (let i = 0; i < bins.length; i += chunkSize) {
    yield bins.slice(i, i + chunkSize);
  }
}

// Usage
for await (const chunk of streamPositionBins(pair, positionId)) {
  processChunk(chunk);
}

Production Checklist

Pre-deployment

const productionChecks = {
  // 1. Input validation
  validateInputs: (params: any) => {
    if (!params.amount || params.amount <= 0n) {
      throw new Error("Invalid amount");
    }
  },
  
  // 2. Network check
  ensureMainnet: () => {
    if (sdk.network !== "mainnet") {
      throw new Error("Not on mainnet");
    }
  },
  
  // 3. Gas buffer
  addGasBuffer: (estimatedGas: bigint) => {
    return (estimatedGas * 120n) / 100n; // 20% buffer
  },
  
  // 4. Slippage protection
  defaultSlippage: 0.5, // 0.5%
  maxSlippage: 5.0     // 5%
};

Performance Monitoring

// Track operation performance
class PerformanceMonitor {
  private metrics = new Map<string, number[]>();
  
  async measure<T>(
    name: string,
    fn: () => Promise<T>
  ): Promise<T> {
    const start = performance.now();
    
    try {
      return await fn();
    } finally {
      const duration = performance.now() - start;
      
      if (!this.metrics.has(name)) {
        this.metrics.set(name, []);
      }
      
      this.metrics.get(name)!.push(duration);
      
      // Log slow operations
      if (duration > 5000) {
        console.warn(`Slow operation: ${name} took ${duration}ms`);
      }
    }
  }
  
  getStats(name: string) {
    const times = this.metrics.get(name) || [];
    return {
      avg: times.reduce((a, b) => a + b, 0) / times.length,
      min: Math.min(...times),
      max: Math.max(...times)
    };
  }
}

Key Principles

  1. Batch when possible - Reduce transaction count

  2. Cache frequently used data - Minimize RPC calls

  3. Validate early - Fail fast with clear errors

  4. Handle errors gracefully - Implement retries

  5. Monitor performance - Track and optimize

  6. Use appropriate data structures - Maps for lookups

  7. Clean up resources - Prevent memory leaks

Last updated