Skip to main content
You’re locked into Raydium but want access to concentrated liquidity and better rates for your users. This guide shows you how to migrate safely without breaking your existing trading functionality.

What You’ll Achieve

  • 30-50% better swap execution for users
  • Access to concentrated liquidity features
  • Cleaner, more maintainable codebase
  • Risk-free migration with easy rollback
Before you start: This guide assumes you:
  • Have working Raydium integration in production or staging
  • Are comfortable with TypeScript and React
  • Have devnet and mainnet testing environments set up
  • Understand the differences between AMM and DLMM liquidity models

Migration strategy overview

We’ll use a parallel integration approach to minimize risk:
  1. Audit current Raydium dependencies and usage patterns
  2. Install Saros SDK alongside Raydium (both running in parallel)
  3. Implement feature parity on devnet with A/B testing capability
  4. Test performance comparison for 48 hours minimum
  5. Execute staged production migration with rollback plan
  6. Monitor performance improvements and optimize
Timeline: 2-4 hours for basic swap migration, additional time for advanced features.

Step 1: Audit your current Raydium integration

First, identify exactly which Raydium features you’re using:
# Search for all Raydium usage in your codebase
grep -r "raydium\|@raydium-io" src/ --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx"

# Check package.json for Raydium dependencies
grep -i raydium package.json

# Look for common Raydium patterns
grep -r "getMarket\|getPoolKeys\|swapInstruction" src/ --include="*.ts" --include="*.tsx"
Common patterns you’ll find:
// Typical Raydium integration patterns to replace
import { Market } from '@project-serum/serum';
import { TokenSwap } from '@solana/spl-token-swap';

// Pool data fetching
const poolInfo = await raydium.api.fetchPoolKeys();
const targetPool = poolInfo.find(pool => /* matching logic */);

// Swap execution  
const swapTransaction = await raydium.makeSwapInstruction({
  poolKeys: targetPool,
  userKeys: userKeys,
  amountIn: amount,
  amountOut: minAmountOut,
  fixedSide: "in"
});
Document your current implementation: Create migration-audit.md:
## Current Raydium Usage Audit

### Dependencies Found:
- [ ] @raydium-io/raydium-sdk: v1.x.x
- [ ] @project-serum/serum: v0.x.x  
- [ ] Custom pool fetching logic
- [ ] Swap instruction building

### Features Using Raydium:
- [ ] Token swaps (which pairs?)
- [ ] Pool data fetching  
- [ ] Price calculations
- [ ] Liquidity provision (if any)
- [ ] Yield farming (if any)

### Integration Points:
- [ ] Components: [list your swap components]
- [ ] Hooks: [list custom hooks using Raydium]
- [ ] Utils: [list utility functions]
- [ ] Services: [list service layer integrations]

Step 2: Install Saros SDK alongside Raydium

Install Saros SDKs without removing Raydium (we’ll run both in parallel):
# Install Saros SDKs
npm install @saros-finance/sdk @saros-finance/dlmm-sdk

# Install any additional dependencies you might need
npm install @solana/web3.js@latest # Ensure latest version compatibility

# Verify installation
npm list | grep saros
Update your environment configuration to support both protocols:
// config/protocols.ts
export const PROTOCOL_CONFIG = {
  raydium: {
    enabled: true,
    priority: 1, // Lower priority during migration
  },
  saros: {
    enabled: true,
    priority: 2, // Higher priority for new features
    dlmm: true, // Enable concentrated liquidity
    traditional: true, // Enable traditional AMM
  }
};

export const MIGRATION_CONFIG = {
  useParallelTesting: true,
  raydiumFallback: true,
  performanceTracking: true,
};

Step 3: Implement feature parity with Saros

Replace Pool Data Fetching

Before (Raydium):
// Old Raydium pool fetching
import { Liquidity } from '@raydium-io/raydium-sdk';

const getRadyiumPools = async () => {
  const poolKeys = await Liquidity.fetchAllPoolKeys(connection);
  return poolKeys.filter(pool => 
    pool.baseMint.equals(USDC_MINT) || 
    pool.quoteMint.equals(SOL_MINT)
  );
};
After (Saros):
// New Saros pool fetching
import * as saros from '@saros-finance/sdk';
import * as dlmm from '@saros-finance/dlmm-sdk';

const getSarosPools = async () => {
  try {
    // Get both traditional and DLMM pools
    const [traditionalPools, dlmmPools] = await Promise.all([
      saros.getAllPools(connection),
      dlmm.getAllPools(connection)
    ]);

    // Filter for your target token pairs
    const filteredTraditional = traditionalPools.filter(pool =>
      (pool.tokenA.equals(USDC_MINT) && pool.tokenB.equals(SOL_MINT)) ||
      (pool.tokenA.equals(SOL_MINT) && pool.tokenB.equals(USDC_MINT))
    );

    const filteredDLMM = dlmmPools.filter(pool =>
      (pool.tokenX.equals(USDC_MINT) && pool.tokenY.equals(SOL_MINT)) ||
      (pool.tokenX.equals(SOL_MINT) && pool.tokenY.equals(USDC_MINT))
    );

    return {
      traditional: filteredTraditional,
      dlmm: filteredDLMM,
      total: filteredTraditional.length + filteredDLMM.length
    };
  } catch (error) {
    console.error('Failed to fetch Saros pools:', error);
    throw error;
  }
};

Replace Swap Implementation

Before (Raydium):
// Old Raydium swap
const executeRadyiumSwap = async (
  tokenIn: PublicKey,
  tokenOut: PublicKey,  
  amount: number,
  slippage: number
) => {
  const poolKeys = await Liquidity.fetchPoolKeys(connection, tokenIn, tokenOut);
  const { transaction } = await Liquidity.makeSwapInstruction({
    connection,
    poolKeys,
    userKeys: {
      tokenAccountIn: userTokenAccountIn,
      tokenAccountOut: userTokenAccountOut,
      owner: wallet.publicKey
    },
    amountIn: amount,
    amountOut: 0, // Will be calculated
    fixedSide: 'in'
  });
  
  return await wallet.sendTransaction(transaction, connection);
};
After (Saros):
// New Saros swap with automatic best-route selection
const executeSarosSwap = async (
  tokenIn: PublicKey,
  tokenOut: PublicKey,
  amount: number,
  slippage: number
) => {
  try {
    // Step 1: Find best route (DLMM vs Traditional)
    const routes = await Promise.all([
      dlmm.getSwapQuote(connection, tokenIn, tokenOut, amount),
      saros.getSwapQuote(connection, tokenIn, tokenOut, amount)
    ]);

    // Step 2: Select best route based on output amount
    const bestRoute = routes.reduce((best, current) => 
      current.outputAmount > best.outputAmount ? current : best
    );

    console.log(`Selected ${bestRoute.type} route: ${bestRoute.outputAmount} output`);

    // Step 3: Execute swap using best route
    let transaction;
    if (bestRoute.type === 'dlmm') {
      transaction = await dlmm.swap({
        connection,
        wallet: wallet.publicKey,
        tokenIn,
        tokenOut,
        amountIn: amount,
        minimumAmountOut: bestRoute.outputAmount * (1 - slippage),
        poolAddress: bestRoute.poolAddress
      });
    } else {
      transaction = await saros.swap({
        connection,
        wallet: wallet.publicKey,
        tokenIn,
        tokenOut,
        amountIn: amount,
        minimumAmountOut: bestRoute.outputAmount * (1 - slippage),
        poolAddress: bestRoute.poolAddress
      });
    }

    return await wallet.sendTransaction(transaction, connection);
    
  } catch (error) {
    console.error('Saros swap failed:', error);
    throw error;
  }
};

Create Migration Hook for Easy Integration

Create hooks/useMigrationSwap.ts:
import { useState, useCallback } from 'react';
import { PublicKey } from '@solana/web3.js';
import { useWallet, useConnection } from '@solana/wallet-adapter-react';

export const useMigrationSwap = () => {
  const { connection } = useConnection();
  const wallet = useWallet();
  const [isLoading, setIsLoading] = useState(false);
  const [lastSwap, setLastSwap] = useState<any>(null);

  const swap = useCallback(async (
    tokenIn: PublicKey,
    tokenOut: PublicKey,
    amount: number,
    slippage: number = 0.005,
    preferredProtocol?: 'raydium' | 'saros' | 'auto'
  ) => {
    setIsLoading(true);
    const startTime = Date.now();
    
    try {
      let result;
      
      switch (preferredProtocol) {
        case 'raydium':
          result = await executeRadyiumSwap(tokenIn, tokenOut, amount, slippage);
          break;
          
        case 'saros':
          result = await executeSarosSwap(tokenIn, tokenOut, amount, slippage);
          break;
          
        default:
          // Auto-selection: try Saros first, fallback to Raydium
          try {
            result = await executeSarosSwap(tokenIn, tokenOut, amount, slippage);
            result.protocol = 'saros';
          } catch (sarosError) {
            console.log('Saros failed, falling back to Raydium:', sarosError);
            result = await executeRadyiumSwap(tokenIn, tokenOut, amount, slippage);
            result.protocol = 'raydium';
          }
      }
      
      const executionTime = Date.now() - startTime;
      
      setLastSwap({
        protocol: result.protocol,
        executionTime,
        signature: result.signature,
        timestamp: new Date()
      });
      
      // Log performance metrics for comparison
      console.log('Swap completed:', {
        protocol: result.protocol,
        executionTime: `${executionTime}ms`,
        signature: result.signature
      });
      
      return result;
      
    } catch (error) {
      console.error('Swap failed:', error);
      throw error;
    } finally {
      setIsLoading(false);
    }
  }, [connection, wallet]);

  return {
    swap,
    isLoading,
    lastSwap
  };
};

Step 4: Implement A/B testing

Create a testing component to compare protocols:
// components/MigrationTester.tsx
'use client'

import React, { useState } from 'react';
import { PublicKey } from '@solana/web3.js';
import { useMigrationSwap } from '@/hooks/useMigrationSwap';

export const MigrationTester = () => {
  const { swap, isLoading, lastSwap } = useMigrationSwap();
  const [amount, setAmount] = useState('');
  const [results, setResults] = useState<any[]>([]);

  const testBothProtocols = async () => {
    const testAmount = parseFloat(amount);
    if (!testAmount) return;

    const tokenIn = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); // USDC
    const tokenOut = new PublicKey('So11111111111111111111111111111111111111112'); // SOL

    try {
      // Test Raydium
      const raydiumStart = Date.now();
      const raydiumResult = await swap(tokenIn, tokenOut, testAmount, 0.005, 'raydium');
      const raydiumTime = Date.now() - raydiumStart;

      // Wait 5 seconds then test Saros
      setTimeout(async () => {
        const sarosStart = Date.now();
        const sarosResult = await swap(tokenIn, tokenOut, testAmount, 0.005, 'saros');
        const sarosTime = Date.now() - sarosStart;

        // Record comparison results
        const comparisonResult = {
          testAmount,
          raydium: {
            time: raydiumTime,
            signature: raydiumResult.signature,
          },
          saros: {
            time: sarosTime,
            signature: sarosResult.signature,
          },
          winner: sarosTime < raydiumTime ? 'saros' : 'raydium',
          improvement: ((raydiumTime - sarosTime) / raydiumTime * 100).toFixed(1)
        };

        setResults(prev => [...prev, comparisonResult]);
      }, 5000);

    } catch (error) {
      console.error('A/B test failed:', error);
    }
  };

  return (
    <div className="max-w-4xl mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Migration A/B Testing</h2>
      
      <div className="bg-white rounded-lg shadow p-6 mb-6">
        <div className="flex items-center space-x-4 mb-4">
          <input
            type="number"
            placeholder="Test amount (USDC)"
            value={amount}
            onChange={(e) => setAmount(e.target.value)}
            className="flex-1 p-2 border rounded"
          />
          <button
            onClick={testBothProtocols}
            disabled={isLoading || !amount}
            className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-300"
          >
            {isLoading ? 'Testing...' : 'Test Both Protocols'}
          </button>
        </div>
        
        {lastSwap && (
          <div className="p-3 bg-green-50 rounded border-l-4 border-green-400">
            <p className="text-sm">
              Last swap: {lastSwap.protocol} in {lastSwap.executionTime}ms
            </p>
          </div>
        )}
      </div>

      {results.length > 0 && (
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-lg font-medium mb-4">Performance Comparison Results</h3>
          
          <div className="space-y-4">
            {results.map((result, index) => (
              <div key={index} className="border rounded p-4">
                <div className="grid grid-cols-3 gap-4">
                  <div>
                    <p className="font-medium">Raydium</p>
                    <p className="text-sm text-gray-600">{result.raydium.time}ms</p>
                  </div>
                  <div>
                    <p className="font-medium">Saros</p>
                    <p className="text-sm text-gray-600">{result.saros.time}ms</p>
                  </div>
                  <div>
                    <p className="font-medium">Winner: {result.winner}</p>
                    <p className="text-sm text-green-600">
                      {result.improvement}% improvement
                    </p>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

Step 5: Execute staged production migration

Phase 1: Feature Flag Implementation

// utils/featureFlags.ts
export const FEATURE_FLAGS = {
  SAROS_MIGRATION: {
    enabled: process.env.NEXT_PUBLIC_SAROS_ENABLED === 'true',
    percentage: parseInt(process.env.NEXT_PUBLIC_SAROS_PERCENTAGE || '10'), // Start with 10%
    enforceForUsers: process.env.NEXT_PUBLIC_SAROS_FORCE_USERS?.split(',') || []
  }
};

export const shouldUseSaros = (userAddress?: string): boolean => {
  if (!FEATURE_FLAGS.SAROS_MIGRATION.enabled) return false;
  
  // Force for specific users (your test accounts)
  if (userAddress && FEATURE_FLAGS.SAROS_MIGRATION.enforceForUsers.includes(userAddress)) {
    return true;
  }
  
  // Percentage-based rollout
  const hash = userAddress ? userAddress.slice(-2) : '00';
  const userNumber = parseInt(hash, 16);
  return userNumber < (FEATURE_FLAGS.SAROS_MIGRATION.percentage * 2.56); // 0-255 range
};

Phase 2: Gradual Rollout Component

// components/MigrationSwapInterface.tsx
'use client'

import React from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { shouldUseSaros } from '@/utils/featureFlags';
import { useMigrationSwap } from '@/hooks/useMigrationSwap';

export const MigrationSwapInterface = () => {
  const { publicKey } = useWallet();
  const { swap, isLoading } = useMigrationSwap();
  
  const useSarosForThisUser = shouldUseSaros(publicKey?.toString());
  
  const executeSwap = async (tokenIn: any, tokenOut: any, amount: number) => {
    const protocol = useSarosForThisUser ? 'saros' : 'raydium';
    
    console.log(`User ${publicKey?.toString().slice(0, 8)} using ${protocol}`);
    
    try {
      const result = await swap(tokenIn, tokenOut, amount, 0.005, protocol);
      
      // Track migration performance
      if (typeof window !== 'undefined') {
        (window as any).gtag?.('event', 'swap_execution', {
          protocol: protocol,
          execution_time: result.executionTime || 0,
          user_cohort: useSarosForThisUser ? 'saros_migration' : 'raydium_control'
        });
      }
      
      return result;
    } catch (error) {
      console.error(`${protocol} swap failed:`, error);
      
      // Fallback strategy for Saros users
      if (useSarosForThisUser && protocol === 'saros') {
        console.log('Falling back to Raydium for this user');
        return await swap(tokenIn, tokenOut, amount, 0.005, 'raydium');
      }
      
      throw error;
    }
  };
  
  return (
    <div className="max-w-md mx-auto">
      {/* Your existing swap interface, but using executeSwap function above */}
      <div className="p-4 bg-white rounded-lg shadow">
        {useSarosForThisUser && (
          <div className="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-800">
            🚀 You're using our new Saros integration with improved performance!
          </div>
        )}
        
        {/* Rest of your swap interface */}
        {/* ... */}
      </div>
    </div>
  );
};

Step 6: Monitor and optimize performance

Performance Tracking Dashboard

Create components/MigrationDashboard.tsx:
'use client'

import React, { useState, useEffect } from 'react';

interface MigrationMetrics {
  totalSwaps: number;
  sarosSwaps: number;
  raydiumSwaps: number;
  averageLatency: {
    saros: number;
    raydium: number;
  };
  successRate: {
    saros: number;
    raydium: number;
  };
  userFeedback: {
    positive: number;
    negative: number;
  };
}

export const MigrationDashboard = () => {
  const [metrics, setMetrics] = useState<MigrationMetrics | null>(null);
  
  useEffect(() => {
    // In production, this would fetch real metrics from your analytics
    const mockMetrics: MigrationMetrics = {
      totalSwaps: 1247,
      sarosSwaps: 156, // 12.5% of users
      raydiumSwaps: 1091,
      averageLatency: {
        saros: 380, // ms
        raydium: 520  // ms
      },
      successRate: {
        saros: 98.7,
        raydium: 99.2
      },
      userFeedback: {
        positive: 23,
        negative: 3
      }
    };
    
    setMetrics(mockMetrics);
  }, []);
  
  if (!metrics) return <div>Loading migration metrics...</div>;
  
  const sarosPercentage = (metrics.sarosSwaps / metrics.totalSwaps * 100).toFixed(1);
  const latencyImprovement = ((metrics.averageLatency.raydium - metrics.averageLatency.saros) / metrics.averageLatency.raydium * 100).toFixed(1);
  
  return (
    <div className="max-w-6xl mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Migration Performance Dashboard</h2>
      
      <div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-sm font-medium text-gray-600 mb-2">Migration Progress</h3>
          <p className="text-3xl font-bold text-blue-600">{sarosPercentage}%</p>
          <p className="text-sm text-gray-500">{metrics.sarosSwaps}/{metrics.totalSwaps} swaps</p>
        </div>
        
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-sm font-medium text-gray-600 mb-2">Latency Improvement</h3>
          <p className="text-3xl font-bold text-green-600">{latencyImprovement}%</p>
          <p className="text-sm text-gray-500">{metrics.averageLatency.saros}ms vs {metrics.averageLatency.raydium}ms</p>
        </div>
        
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-sm font-medium text-gray-600 mb-2">Success Rate</h3>
          <p className="text-3xl font-bold text-purple-600">{metrics.successRate.saros}%</p>
          <p className="text-sm text-gray-500">Saros reliability</p>
        </div>
        
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-sm font-medium text-gray-600 mb-2">User Feedback</h3>
          <p className="text-3xl font-bold text-orange-600">
            {Math.round(metrics.userFeedback.positive / (metrics.userFeedback.positive + metrics.userFeedback.negative) * 100)}%
          </p>
          <p className="text-sm text-gray-500">Positive sentiment</p>
        </div>
      </div>
      
      {/* Migration Decision Recommendations */}
      <div className="bg-white rounded-lg shadow p-6">
        <h3 className="text-lg font-medium mb-4">Migration Recommendations</h3>
        
        <div className="space-y-4">
          {parseFloat(latencyImprovement) > 15 && (
            <div className="p-4 bg-green-50 border border-green-200 rounded">
              <p className="text-green-800">
                ✅ <strong>Proceed with migration:</strong> Saros shows {latencyImprovement}% latency improvement with {metrics.successRate.saros}% success rate.
              </p>
            </div>
          )}
          
          {metrics.successRate.saros < metrics.successRate.raydium && (
            <div className="p-4 bg-yellow-50 border border-yellow-200 rounded">
              <p className="text-yellow-800">
                ⚠️ <strong>Monitor reliability:</strong> Saros success rate ({metrics.successRate.saros}%) is lower than Raydium ({metrics.successRate.raydium}%).
              </p>
            </div>
          )}
          
          {parseFloat(sarosPercentage) < 25 && (
            <div className="p-4 bg-blue-50 border border-blue-200 rounded">
              <p className="text-blue-800">
                📈 <strong>Increase rollout:</strong> Current {sarosPercentage}% migration is performing well. Consider increasing to 25%.
              </p>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

Step 7: Complete the migration

Final Cutover Plan

Once your metrics show Saros performing better than Raydium:
// 1. Update environment variables
NEXT_PUBLIC_SAROS_ENABLED=true
NEXT_PUBLIC_SAROS_PERCENTAGE=100  # Full migration

// 2. Remove Raydium dependencies (in a separate commit for easy rollback)
npm uninstall @raydium-io/raydium-sdk @project-serum/serum

// 3. Clean up code
// Remove all Raydium-specific code and keep only Saros implementation
// Update all imports and function calls

// 4. Update documentation
// Update README, deployment guides, and team documentation

Post-Migration Cleanup

Create scripts/cleanup-migration.ts:
// Remove feature flags and migration code after successful migration
import fs from 'fs';
import path from 'path';

const cleanupMigrationCode = () => {
  console.log('🧹 Cleaning up migration artifacts...');
  
  // Remove feature flag references
  // Remove A/B testing components  
  // Consolidate duplicate functionality
  // Update imports throughout codebase
  
  console.log('✅ Migration cleanup complete!');
};

// Run after confirming migration success
cleanupMigrationCode();

Success validation checklist

Mark each item as complete:
  • Functionality Parity: All swap features work identically with Saros
  • Performance Improvement: Saros shows measurable latency or UX improvements
  • Reliability: Success rates are equal or better than Raydium
  • User Experience: No user complaints or confusion during migration
  • Monitoring: Analytics show healthy migration metrics
  • Rollback Plan: Tested ability to quickly revert if issues arise

Troubleshooting common issues

Cause: Saros has newer pools with potentially less variety than established Raydium pools.Solution:
  • Implement fallback to Raydium for unsupported pairs
  • Use Saros for supported pairs with better liquidity
  • Gradually expand as Saros pool variety increases
Cause: DLMM concentrated liquidity operations may have different computational requirements.Solution:
  • Compare traditional AMM vs DLMM costs
  • Use traditional Saros AMM for cost-sensitive operations
  • Factor improved capital efficiency into cost analysis
Cause: Different token account handling between protocols.Solution:
  • Implement per-pair protocol selection
  • Add comprehensive error handling with fallbacks
  • Log specific failures for Saros team investigation

Expected outcomes

After completing this migration:
  • Performance: 20-40% faster swap execution times
  • Features: Access to concentrated liquidity via DLMM pools
  • Developer Experience: Cleaner, more modern SDK with better TypeScript support
  • Competitive Advantage: Latest DeFi innovations and improved user experience

Next steps

Immediate (Week 1)

  • Monitor Performance: Track key metrics daily for first week
  • User Feedback: Collect feedback from power users
  • Optimization: Fine-tune based on real usage patterns

Short Term (Month 1)

Long Term (3+ Months)

  • Complete Platform Migration: Migrate all DeFi features to Saros ecosystem
  • Advanced Integrations: Explore farming, staking, and governance features
  • Business Development: Leverage improved capabilities for product differentiation

Migration completed successfully? You now have access to Saros’s concentrated liquidity and improved developer experience. Explore advanced patterns →