Skip to main content
Your users complain about poor swap rates and high slippage, but you don’t want to rebuild your entire trading interface. Adding Saros concentrated liquidity gives users better rates while keeping your existing UI intact.

What You’ll Achieve

  • 20-50% better swap rates for users
  • No disruption to existing functionality
  • Easy rollback if needed
  • Improved user satisfaction and retention

When to Use This Approach

  • You have working swap functionality but want better rates
  • Users are losing money to slippage on large trades
  • You want to test Saros without breaking existing features
  • You need competitive rates to retain users

Integration Scenarios

This guide covers common integration patterns:
  • Portfolio Tracker
  • DeFi Dashboard
  • DEX Aggregator
Adding: Swap functionality to existing token portfolio view User flow: View token → Click swap → Execute via Saros DLMM

Integration Strategy

1. Non-Disruptive Installation

Add Saros SDK without affecting existing dependencies:
# Install only what you need
npm install @saros-finance/dlmm-sdk

# No need to change existing Solana packages
# Works with your current @solana/web3.js version

2. Create Isolated Swap Hook

// hooks/useSarosSwap.ts
import { useState, useCallback } from 'react';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { PublicKey } from '@solana/web3.js';
import { LiquidityBookServices } from '@saros-finance/dlmm-sdk';

interface SwapResult {
  signature: string;
  amountIn: string;
  amountOut: string;
  priceImpact: number;
}

interface SwapError {
  code: string;
  message: string;
}

export const useSarosSwap = () => {
  const { connection } = useConnection();
  const { publicKey, signTransaction } = useWallet();
  const [isSwapping, setIsSwapping] = useState(false);
  const [lastSwap, setLastSwap] = useState<SwapResult | null>(null);
  const [error, setError] = useState<SwapError | null>(null);

  // Initialize DLMM service
  const dlmmService = new LiquidityBookServices(connection);

  const getSwapQuote = useCallback(async (
    fromMint: string,
    toMint: string,
    amount: string,
    decimalsIn: number,
    decimalsOut: number,
    slippage: number = 0.5
  ) => {
    if (!publicKey) throw new Error('Wallet not connected');

    try {
      const pairAddress = await dlmmService.findBestPair(
        new PublicKey(fromMint),
        new PublicKey(toMint)
      );

      const quote = await dlmmService.getQuote({
        pair: pairAddress,
        tokenBase: new PublicKey(fromMint),
        tokenQuote: new PublicKey(toMint),
        amount: BigInt(parseFloat(amount) * Math.pow(10, decimalsIn)),
        swapForY: true,
        isExactInput: true,
        tokenBaseDecimal: decimalsIn,
        tokenQuoteDecimal: decimalsOut,
        slippage
      });

      return {
        amountOut: (Number(quote.amountOut) / Math.pow(10, decimalsOut)).toString(),
        priceImpact: quote.priceImpact,
        route: quote.route,
        fee: quote.fee
      };
    } catch (err) {
      console.error('Quote failed:', err);
      throw err;
    }
  }, [publicKey, dlmmService]);

  const executeSwap = useCallback(async (
    fromMint: string,
    toMint: string,
    amount: string,
    decimalsIn: number,
    decimalsOut: number,
    slippage: number = 0.5
  ) => {
    if (!publicKey || !signTransaction) throw new Error('Wallet not connected');

    setIsSwapping(true);
    setError(null);

    try {
      // Get fresh quote
      const quote = await getSwapQuote(fromMint, toMint, amount, decimalsIn, decimalsOut, slippage);
      
      const pairAddress = await dlmmService.findBestPair(
        new PublicKey(fromMint),
        new PublicKey(toMint)
      );

      // Execute swap
      const transaction = await dlmmService.swap({
        pair: pairAddress,
        tokenBase: new PublicKey(fromMint),
        tokenQuote: new PublicKey(toMint),
        amount: BigInt(parseFloat(amount) * Math.pow(10, decimalsIn)),
        swapForY: true,
        isExactInput: true,
        wallet: publicKey,
        slippage
      });

      const signedTx = await signTransaction(transaction);
      const signature = await connection.sendRawTransaction(signedTx.serialize());
      
      await connection.confirmTransaction(signature, 'confirmed');

      const result: SwapResult = {
        signature,
        amountIn: amount,
        amountOut: quote.amountOut,
        priceImpact: quote.priceImpact
      };

      setLastSwap(result);
      return result;

    } catch (err: any) {
      const swapError: SwapError = {
        code: err.code || 'SWAP_FAILED',
        message: err.message || 'Swap execution failed'
      };
      setError(swapError);
      throw swapError;
    } finally {
      setIsSwapping(false);
    }
  }, [publicKey, signTransaction, connection, dlmmService, getSwapQuote]);

  return {
    getSwapQuote,
    executeSwap,
    isSwapping,
    lastSwap,
    error,
    clearError: () => setError(null)
  };
};

3. Create Compact Swap Component

// components/SarosSwapWidget.tsx
import React, { useState, useEffect } from 'react';
import { useSarosSwap } from '../hooks/useSarosSwap';

interface Token {
  mint: string;
  symbol: string;
  decimals: number;
  logoUri?: string;
}

interface SarosSwapWidgetProps {
  tokens: Token[];
  onSwapComplete?: (result: any) => void;
  className?: string;
}

export const SarosSwapWidget: React.FC<SarosSwapWidgetProps> = ({
  tokens,
  onSwapComplete,
  className = ''
}) => {
  const { getSwapQuote, executeSwap, isSwapping, error } = useSarosSwap();
  
  const [fromToken, setFromToken] = useState(tokens[0]);
  const [toToken, setToToken] = useState(tokens[1]);
  const [amount, setAmount] = useState('');
  const [estimatedOutput, setEstimatedOutput] = useState('');
  const [priceImpact, setPriceImpact] = useState(0);

  // Auto-quote with debouncing
  useEffect(() => {
    if (!amount || !fromToken || !toToken || parseFloat(amount) <= 0) {
      setEstimatedOutput('');
      setPriceImpact(0);
      return;
    }

    const timeoutId = setTimeout(async () => {
      try {
        const quote = await getSwapQuote(
          fromToken.mint,
          toToken.mint,
          amount,
          fromToken.decimals,
          toToken.decimals
        );
        setEstimatedOutput(parseFloat(quote.amountOut).toFixed(6));
        setPriceImpact(quote.priceImpact);
      } catch (err) {
        console.error('Quote failed:', err);
        setEstimatedOutput('Error');
        setPriceImpact(0);
      }
    }, 500);

    return () => clearTimeout(timeoutId);
  }, [amount, fromToken, toToken, getSwapQuote]);

  const handleSwap = async () => {
    try {
      const result = await executeSwap(
        fromToken.mint,
        toToken.mint,
        amount,
        fromToken.decimals,
        toToken.decimals
      );
      
      onSwapComplete?.(result);
      setAmount('');
      setEstimatedOutput('');
    } catch (err) {
      // Error handled by hook
    }
  };

  const swapTokens = () => {
    const temp = fromToken;
    setFromToken(toToken);
    setToToken(temp);
    setAmount('');
    setEstimatedOutput('');
  };

  return (
    <div className={`saros-swap-widget ${className}`}>
      <div className="swap-header">
        <h3>⚡ Saros Swap</h3>
        <span className="concentrated-liquidity-badge">Concentrated Liquidity</span>
      </div>

      {/* From Token */}
      <div className="token-input">
        <div className="token-input-header">
          <span>From</span>
          <select
            value={fromToken.mint}
            onChange={(e) => setFromToken(tokens.find(t => t.mint === e.target.value)!)}
          >
            {tokens.map(token => (
              <option key={token.mint} value={token.mint}>
                {token.symbol}
              </option>
            ))}
          </select>
        </div>
        <input
          type="number"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          placeholder="0.00"
          className="amount-input"
        />
      </div>

      {/* Swap Direction Button */}
      <div className="swap-direction">
        <button onClick={swapTokens} className="swap-direction-btn">
          ↕️
        </button>
      </div>

      {/* To Token */}
      <div className="token-input">
        <div className="token-input-header">
          <span>To</span>
          <select
            value={toToken.mint}
            onChange={(e) => setToToken(tokens.find(t => t.mint === e.target.value)!)}
          >
            {tokens.map(token => (
              <option key={token.mint} value={token.mint}>
                {token.symbol}
              </option>
            ))}
          </select>
        </div>
        <input
          type="text"
          value={estimatedOutput}
          placeholder="0.00"
          disabled
          className="amount-input"
        />
      </div>

      {/* Price Impact */}
      {priceImpact > 0 && (
        <div className="price-impact">
          <span>Price Impact: </span>
          <span className={priceImpact > 3 ? 'high-impact' : 'low-impact'}>
            {priceImpact.toFixed(2)}%
          </span>
        </div>
      )}

      {/* Error Display */}
      {error && (
        <div className="error-message">
          {error.message}
        </div>
      )}

      {/* Swap Button */}
      <button
        onClick={handleSwap}
        disabled={!amount || !estimatedOutput || isSwapping || estimatedOutput === 'Error'}
        className="swap-button"
      >
        {isSwapping ? '🔄 Swapping...' : '⚡ Execute Saros Swap'}
      </button>

      {/* Benefits Display */}
      <div className="benefits">
        <div className="benefit-item">
          <span>🎯 Concentrated Liquidity</span>
        </div>
        <div className="benefit-item">
          <span>💰 Better Prices</span>
        </div>
        <div className="benefit-item">
          <span>⚡ Sub-1% Price Impact</span>
        </div>
      </div>

      <style jsx>{`
        .saros-swap-widget {
          background: #f8f9fa;
          border-radius: 12px;
          padding: 1rem;
          border: 1px solid #e1e8ed;
          max-width: 400px;
        }

        .swap-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 1rem;
        }

        .concentrated-liquidity-badge {
          background: #4CAF50;
          color: white;
          padding: 0.25rem 0.5rem;
          border-radius: 12px;
          font-size: 0.8rem;
          font-weight: bold;
        }

        .token-input {
          background: white;
          border-radius: 8px;
          padding: 0.75rem;
          margin-bottom: 0.5rem;
        }

        .token-input-header {
          display: flex;
          justify-content: space-between;
          margin-bottom: 0.5rem;
          font-size: 0.9rem;
          color: #666;
        }

        .amount-input {
          width: 100%;
          border: none;
          outline: none;
          font-size: 1.1rem;
          background: transparent;
        }

        .swap-direction {
          text-align: center;
          margin: 0.5rem 0;
        }

        .swap-direction-btn {
          background: #6366F1;
          color: white;
          border: none;
          border-radius: 50%;
          width: 32px;
          height: 32px;
          cursor: pointer;
          font-size: 1rem;
        }

        .price-impact {
          font-size: 0.9rem;
          margin: 0.5rem 0;
        }

        .low-impact {
          color: #4CAF50;
        }

        .high-impact {
          color: #f44336;
        }

        .error-message {
          background: #ffebee;
          color: #c62828;
          padding: 0.5rem;
          border-radius: 6px;
          font-size: 0.9rem;
          margin: 0.5rem 0;
        }

        .swap-button {
          width: 100%;
          background: #6366F1;
          color: white;
          border: none;
          border-radius: 8px;
          padding: 0.75rem;
          font-size: 1rem;
          font-weight: bold;
          cursor: pointer;
          margin-bottom: 1rem;
        }

        .swap-button:disabled {
          background: #ccc;
          cursor: not-allowed;
        }

        .benefits {
          display: grid;
          grid-template-columns: repeat(3, 1fr);
          gap: 0.5rem;
          font-size: 0.8rem;
        }

        .benefit-item {
          text-align: center;
          color: #666;
        }
      `}</style>
    </div>
  );
};

4. Integration into Existing App

// pages/portfolio.tsx (or your existing component)
import React from 'react';
import { SarosSwapWidget } from '../components/SarosSwapWidget';

// Your existing component
const PortfolioPage = () => {
  // Your existing logic...
  
  const supportedTokens = [
    {
      mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
      symbol: 'USDC',
      decimals: 6
    },
    {
      mint: 'So11111111111111111111111111111111111111112',
      symbol: 'SOL',
      decimals: 9
    },
    {
      mint: 'C98A4nkJXhpVZNAZdHUA95RpTF3T4whtQubL3YobiUX9',
      symbol: 'C98',
      decimals: 6
    }
  ];

  const handleSwapComplete = (result: any) => {
    console.log('Swap completed:', result);
    
    // Optional: Refresh balances, show notification, etc.
    // This integrates with your existing state management
    refreshPortfolioBalances();
    showSuccessNotification(`Swapped ${result.amountIn} for ${result.amountOut}`);
  };

  return (
    <div className="portfolio-container">
      {/* Your existing portfolio components */}
      <div className="existing-portfolio-content">
        {/* ... your existing JSX ... */}
      </div>

      {/* Add Saros swap widget */}
      <div className="swap-widget-container">
        <SarosSwapWidget
          tokens={supportedTokens}
          onSwapComplete={handleSwapComplete}
          className="portfolio-swap-widget"
        />
      </div>
    </div>
  );
};

5. Advanced Integration Patterns

A. Context-Based Integration
// contexts/SarosContext.tsx
import React, { createContext, useContext, ReactNode } from 'react';
import { useSarosSwap } from '../hooks/useSarosSwap';

const SarosContext = createContext<ReturnType<typeof useSarosSwap> | null>(null);

export const SarosProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
  const sarosSwap = useSarosSwap();
  
  return (
    <SarosContext.Provider value={sarosSwap}>
      {children}
    </SarosContext.Provider>
  );
};

export const useSaros = () => {
  const context = useContext(SarosContext);
  if (!context) {
    throw new Error('useSaros must be used within SarosProvider');
  }
  return context;
};
B. Analytics Integration
// Track swap events in your existing analytics
const handleSwapComplete = (result: any) => {
  // Your existing analytics (Google Analytics, Mixpanel, etc.)
  analytics.track('Saros Swap Completed', {
    fromToken: result.fromSymbol,
    toToken: result.toSymbol,
    amountIn: result.amountIn,
    amountOut: result.amountOut,
    priceImpact: result.priceImpact,
    timestamp: Date.now()
  });
};
C. Toast Notifications
// Integrate with your existing notification system
import { useToast } from '../hooks/useToast'; // Your existing hook

const { executeSwap, error } = useSarosSwap();
const { showToast } = useToast();

useEffect(() => {
  if (error) {
    showToast({
      type: 'error',
      message: `Swap failed: ${error.message}`,
      duration: 5000
    });
  }
}, [error, showToast]);

Testing Integration

1. Development Testing

# Test in development environment
npm run dev

# Verify integration:
# 1. Existing functionality still works
# 2. Swap widget loads without errors
# 3. Wallet connection works for both existing and new features
# 4. No CSS conflicts or layout issues

2. Production Checklist

// production-config.ts
export const PRODUCTION_CONFIG = {
  // Use mainnet endpoints for production
  rpcEndpoint: process.env.NODE_ENV === 'production' 
    ? 'https://api.mainnet-beta.solana.com'
    : 'https://api.devnet.solana.com',
    
  // Supported token list for production
  supportedTokens: [
    {
      mint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
      symbol: 'USDC',
      decimals: 6,
      verified: true
    },
    {
      mint: 'So11111111111111111111111111111111111111112', // SOL
      symbol: 'SOL',
      decimals: 9,
      verified: true
    }
    // Add only verified tokens for production
  ],
  
  // Conservative slippage for production
  defaultSlippage: 0.5,
  maxSlippage: 5.0,
  
  // Error tracking
  enableErrorReporting: true
};

3. Error Handling Strategy

// Graceful degradation
const SarosSwapWidget = ({ tokens, onSwapComplete }) => {
  const [isSupported, setIsSupported] = useState(true);
  
  useEffect(() => {
    // Check if user's environment supports Saros
    checkSarosSupport()
      .then(setIsSupported)
      .catch(() => setIsSupported(false));
  }, []);
  
  if (!isSupported) {
    return (
      <div className="swap-widget-unavailable">
        <p>⚠️ Advanced swap features temporarily unavailable</p>
        <p>Your existing functionality continues to work normally</p>
      </div>
    );
  }
  
  return <SarosSwapComponent {...props} />;
};

Production Error Handling & Recovery

Real applications need robust error handling for network failures, wallet issues, and transaction problems:
// Enhanced error handling in useSarosSwap hook
const executeSwapWithRetry = useCallback(async (
  swapParams: SwapParams,
  maxRetries: number = 3
) => {
  let lastError: Error;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await executeSwap(swapParams);
    } catch (error: any) {
      lastError = error;
      
      // Handle specific error types
      if (error.code === 'NETWORK_ERROR' || error.code === 'RPC_TIMEOUT') {
        if (attempt < maxRetries) {
          // Exponential backoff
          await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
          continue;
        }
      }
      
      // Don't retry for user errors
      if (error.code === 'USER_REJECTED' || error.code === 'INSUFFICIENT_FUNDS') {
        throw error;
      }
      
      // Don't retry for final attempt
      if (attempt === maxRetries) break;
    }
  }
  
  throw lastError!;
}, [executeSwap]);
// Transaction confirmation with timeout and fallback
const confirmTransactionWithFallback = async (signature: string) => {
  const timeout = 30000; // 30 seconds
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    try {
      const status = await connection.getSignatureStatus(signature);
      
      if (status.value?.confirmationStatus === 'confirmed') {
        return { success: true, signature };
      }
      
      if (status.value?.err) {
        return { 
          success: false, 
          error: `Transaction failed: ${status.value.err}`,
          signature 
        };
      }
      
      await new Promise(resolve => setTimeout(resolve, 2000));
    } catch (error) {
      console.warn('Status check failed, retrying...');
      await new Promise(resolve => setTimeout(resolve, 3000));
    }
  }
  
  // Timeout - transaction might still succeed
  return { 
    success: false, 
    error: 'Transaction confirmation timeout - may still complete',
    signature 
  };
};
const getUserFriendlyError = (error: any): string => {
  const errorMap = {
    'USER_REJECTED': 'Transaction was cancelled. Please try again.',
    'INSUFFICIENT_FUNDS': 'Insufficient balance for this swap.',
    'SLIPPAGE_EXCEEDED': 'Price moved too much. Increase slippage tolerance.',
    'NETWORK_ERROR': 'Network connection issue. Please check your internet.',
    'RPC_TIMEOUT': 'Request timed out. Please try again.',
    'PAIR_NOT_FOUND': 'No liquidity available for this token pair.',
    'AMOUNT_TOO_SMALL': 'Swap amount is too small. Minimum 0.001 tokens.',
    'AMOUNT_TOO_LARGE': 'Swap amount exceeds available liquidity.'
  };
  
  return errorMap[error.code] || 'Swap failed. Please try again or contact support.';
};

// Usage in component
const handleSwap = async () => {
  try {
    setError(null);
    const result = await executeSwapWithRetry(swapParams);
    onSwapSuccess(result);
  } catch (error: any) {
    setError(getUserFriendlyError(error));
    
    // Log for debugging but don't expose technical details
    console.error('Swap failed:', {
      code: error.code,
      message: error.message,
      stack: error.stack
    });
  }
};
// Component with comprehensive error states
return (
  <div className="saros-swap-widget">
    {error && (
      <div className="error-banner">
        <span className="error-icon">⚠️</span>
        <div>
          <p className="error-message">{error}</p>
          {error.includes('network') && (
            <button onClick={retryConnection} className="retry-btn">
              Retry Connection
            </button>
          )}
          {error.includes('slippage') && (
            <button onClick={increaseSlippage} className="fix-btn">
              Increase Slippage
            </button>
          )}
        </div>
      </div>
    )}
    
    {isSwapping ? (
      <div className="swap-loading">
        <div className="spinner" />
        <p>Processing swap...</p>
        <button onClick={cancelSwap} className="cancel-btn">
          Cancel
        </button>
      </div>
    ) : (
      <div className="swap-form">
        {/* Normal swap interface */}
      </div>
    )}
  </div>
);

Benefits of This Integration Approach

✅ Non-Disruptive: Existing functionality remains untouched ✅ Modular: Easy to enable/disable or customize ✅ Performance: Only loads Saros SDK when needed ✅ User Experience: Seamless integration with existing UI ✅ Maintainable: Isolated components for easy updates ✅ Production Ready: Error handling and fallbacks included

Common Integration Issues & Solutions

Issue: Wallet conflicts between existing and Saros integrations Solution: Use shared wallet context, don’t initialize multiple wallet providers Issue: RPC rate limiting with additional requests Solution: Implement request caching and use connection pooling Issue: Bundle size increase Solution: Use dynamic imports and code splitting for swap components Issue: State management conflicts Solution: Use isolated state for Saros components, communicate via callbacks This integration approach allows you to enhance your existing application with Saros’s concentrated liquidity benefits while maintaining stability and user experience.