Skip to main content
Rainbow’s quote fetching system aggregates liquidity from multiple DEXs and aggregators to find the best swap rates.

Quote Architecture

Quotes are fetched reactively using shared values:
// From src/__swaps__/utils/quotes.ts
import { type Quote, type CrosschainQuote, type QuoteError } from '@rainbow-me/swaps';

// Type guards for quote responses
export function isQuoteError(data: unknown): data is QuoteError {
  return typeof data === 'object' && 
         data !== null && 
         'error' in data && 
         data.error === true;
}

export function isQuote(data: Quote | QuoteError | null): data is Quote {
  return typeof data === 'object' && 
         data !== null && 
         'sellAmount' in data && 
         'buyAmount' in data && 
         !isQuoteError(data);
}

export function isCrosschainQuote(
  data: Quote | CrosschainQuote | QuoteError | null
): data is CrosschainQuote {
  return typeof data === 'object' && 
         data !== null && 
         'swapType' in data && 
         data.swapType === SwapType.crossChain;
}

Quote Parameters

Building quote request:
// From src/__swaps__/utils/swaps.ts
type QuoteParams = {
  source?: string;              // Aggregator preference ('auto' | specific)
  chainId: ChainId;            // Source chain
  fromAddress: Address;        // User wallet
  sellTokenAddress: Address;   // Input token contract
  buyTokenAddress: Address;    // Output token contract
  sellAmount?: string;         // Input amount (if user typed input)
  buyAmount?: string;          // Output amount (if user typed output)
  slippage: number;            // Slippage in bips
  refuel?: boolean;            // Get gas on destination (cross-chain)
  toChainId?: ChainId;         // Destination chain (cross-chain)
  currency: NativeCurrencyKey; // Display currency (USD, EUR, etc.)
};

export const buildQuoteParams = ({
  currentAddress,
  inputAmount,
  outputAmount,
  inputAsset,
  outputAsset,
  lastTypedInput,
}: BuildQuoteParamsProps): QuoteParams | null => {
  const { source, slippage } = swapsStore.getState();
  
  if (!inputAsset || !outputAsset) return null;
  
  const isCrosschainSwap = inputAsset.chainId !== outputAsset.chainId;
  
  return {
    source: source === 'auto' ? undefined : source,
    chainId: inputAsset.chainId,
    fromAddress: currentAddress,
    sellTokenAddress: inputAsset.isNativeAsset 
      ? ETH_ADDRESS 
      : getAddress(inputAsset.address),
    buyTokenAddress: outputAsset.isNativeAsset 
      ? ETH_ADDRESS 
      : getAddress(outputAsset.address),
    // Only include amount for the field user typed
    sellAmount: lastTypedInput === 'inputAmount' || lastTypedInput === 'inputNativeValue'
      ? convertAmountToRawAmount(inputAmount.toString(), inputAsset.decimals)
      : undefined,
    buyAmount: lastTypedInput === 'outputAmount' || lastTypedInput === 'outputNativeValue'
      ? convertAmountToRawAmount(outputAmount.toString(), outputAsset.decimals)
      : undefined,
    slippage: Number(slippage),
    refuel: false,
    toChainId: isCrosschainSwap ? outputAsset.chainId : inputAsset.chainId,
    currency: store.getState().settings.nativeCurrency,
  };
};

Quote Sources

Rainbow aggregates quotes from multiple sources:

DEX Aggregators

Rainbow’s AggregatorFeatures:
  • Multi-DEX routing
  • MEV protection
  • Optimized gas
  • Best price guarantee

Source Selection

// User can choose preferred source
const swapSettings = useSwapSettings();

// Options:
// - 'auto': Rainbow selects best (default)
// - 'rainbow': Force Rainbow aggregator
// - '0x': Force 0x protocol
// - etc.

Quote Fetching Flow

1

Trigger Quote

Quote request triggered when:
  • User types amount
  • Asset selection changes
  • Slippage adjusted
  • Periodic refresh (12s)
2

Build Parameters

Construct quote params:
  • Extract current state
  • Validate inputs
  • Convert amounts to wei
  • Add user preferences
3

Fetch Quotes

Query aggregators in parallel:
const quotes = await Promise.allSettled([
  fetchRainbowQuote(params),
  fetch0xQuote(params),
  // More sources...
]);
4

Compare & Select

Choose best quote:
  • Compare output amounts
  • Account for gas costs
  • Consider price impact
  • Verify route safety
5

Update UI

Display quote to user:
  • Show exchange rate
  • Display estimated output
  • Show fees and gas
  • Enable swap button

Quote Validation

Quotes are validated before display:

Error Suppression

// From src/__swaps__/utils/quotes.ts
const SUPPRESSED_QUOTE_ERRORS = new Set([
  'error parsing sellAmount'
]);

export function shouldSuppressQuoteError(message: string): boolean {
  return SUPPRESSED_QUOTE_ERRORS.has(message);
}
Some errors are expected and don’t need user notification:
  • Parsing errors (user still typing)
  • Insufficient liquidity (amount too large)
  • Temporary network issues

Cross-Chain Validation

export function crosschainQuoteTargetsRecipient(
  quote: CrosschainQuote,
  recipient: string
): boolean {
  if (!recipient) return false;
  if (quote.routes.length === 0) return true;
  
  const normalized = recipient.toLowerCase();
  
  // Verify all routes target correct recipient
  return quote.routes.every(route => {
    const routeRecipientOk = 
      !route.recipient || 
      route.recipient.toLowerCase() === normalized;
    
    const txRecipientsOk = (route.userTxs ?? []).every(tx => 
      !tx.recipient || 
      tx.recipient.toLowerCase() === normalized
    );
    
    return routeRecipientOk && txRecipientsOk;
  });
}
For cross-chain swaps, Rainbow validates that all route steps send tokens to your wallet, not an intermediate address. This prevents loss of funds.

Quote Refresh

Quotes automatically refresh to stay current:

Refresh Intervals

// From swap provider
const QUOTE_REFRESH_INTERVAL = 12000; // 12 seconds

const quoteFetchingInterval = useAnimatedInterval({
  interval: QUOTE_REFRESH_INTERVAL,
  enabled: shouldFetchQuote,
  callback: () => {
    fetchQuote();
  },
});

Staleness Detection

const isQuoteStale = useSharedValue<number>(0);

// Quote marked stale when:
// - 12+ seconds since last fetch
// - User changed inputs
// - Price moved significantly

if (isQuoteStale.value > 12) {
  showWarning('Quote may be outdated - refreshing...');
  fetchQuote();
}

Quote Display

Quote information shown to user:

Exchange Rate

const exchangeRate = useDerivedValue(() => {
  if (!quote.value || isQuoteError(quote.value)) return null;
  
  const { sellAmount, buyAmount, sellTokenAsset, buyTokenAsset } = quote.value;
  
  // Calculate rate: 1 input token = X output tokens
  const rate = divWorklet(
    formatUnits(buyAmount, buyTokenAsset.decimals),
    formatUnits(sellAmount, sellTokenAsset.decimals)
  );
  
  return {
    rate,
    display: `1 ${sellTokenAsset.symbol} = ${rate} ${buyTokenAsset.symbol}`,
  };
});

Price Impact

const priceImpact = useDerivedValue(() => {
  if (!quote.value || !marketPrice.value) return null;
  
  const quotePrice = divWorklet(
    quote.value.buyAmount,
    quote.value.sellAmount
  );
  
  const impact = divWorklet(
    subWorklet(quotePrice, marketPrice.value),
    marketPrice.value
  );
  
  return mulWorklet(impact, 100); // Convert to percentage
});

Minimum Received

const minimumReceived = useDerivedValue(() => {
  if (!quote.value || isQuoteError(quote.value)) return null;
  
  const { buyAmount } = quote.value;
  const slippageBips = swapSettings.slippage;
  
  // Calculate minimum after slippage
  const slippageMultiplier = divWorklet(
    subWorklet(10000, slippageBips),
    10000
  );
  
  return mulWorklet(buyAmount, slippageMultiplier);
});

Quote Errors

Handling failed quote requests:
Error: Not enough liquidity for amountHandling:
  • Show warning to user
  • Suggest smaller amount
  • Display available liquidity
  • Offer to split trade
Error: Token pair not availableHandling:
  • Verify tokens exist on chain
  • Check multi-hop routes
  • Suggest alternative tokens
  • Try different chain
Error: Cannot reach quote APIHandling:
  • Retry with backoff
  • Try alternate aggregators
  • Show connection error
  • Cache last valid quote
Error: Amount parsing failedHandling:
  • Suppress error (user typing)
  • Validate on blur
  • Show format hint
  • Clear invalid input

Cross-Chain Quote Details

Additional data for bridge swaps:
interface CrosschainQuote extends Quote {
  swapType: SwapType.crossChain;
  
  routes: Array<{
    // Bridge information
    bridgeRoute: {
      bridge: string;        // Bridge protocol
      steps: BridgeStep[];  // Multi-step routes
      estimatedTime: number; // Seconds
    };
    
    // Transactions user must sign
    userTxs?: Array<{
      to: string;
      data: string;
      value: string;
      chainId: ChainId;
    }>;
    
    // Service time estimate
    serviceTime?: number;    // Seconds to complete
    
    // Recipient validation
    recipient?: string;
  }>;
}

Service Time Display

// From src/__swaps__/utils/swaps.ts
export const getCrossChainTimeEstimateWorklet = ({
  serviceTime,
}: {
  serviceTime?: number;
}) => {
  'worklet';
  
  let isLongWait = false;
  let timeEstimateDisplay;
  const timeEstimate = serviceTime;
  
  const minutes = Math.floor((timeEstimate || 0) / 60);
  const hours = Math.floor(minutes / 60);
  
  if (hours >= 1) {
    isLongWait = true;
    timeEstimateDisplay = `>${hours} ${hours === 1 ? 'hour' : 'hours'}`;
  } else if (minutes >= 1) {
    timeEstimateDisplay = `~${minutes} ${minutes === 1 ? 'min' : 'mins'}`;
  } else {
    timeEstimateDisplay = `~${timeEstimate} ${timeEstimate === 1 ? 'sec' : 'secs'}`;
  }
  
  return { isLongWait, timeEstimate, timeEstimateDisplay };
};
Cross-chain swaps can take from a few minutes to several hours depending on:
  • Bridge protocol used
  • Network congestion on both chains
  • Number of confirmations required
  • Type of bridge (fast vs. canonical)

Quote Optimization

Rainbow optimizes quote selection:

Best Quote Selection

const selectBestQuote = (quotes: Quote[]): Quote => {
  return quotes.reduce((best, current) => {
    // Calculate net output after gas
    const bestNet = subWorklet(
      best.buyAmount,
      mulWorklet(best.gas, best.gasPrice || '0')
    );
    
    const currentNet = subWorklet(
      current.buyAmount,
      mulWorklet(current.gas, current.gasPrice || '0')
    );
    
    // Choose quote with highest net output
    return greaterThanWorklet(currentNet, bestNet) ? current : best;
  });
};

Gas-Adjusted Returns

Quotes compared after gas costs:
  • Calculate gas cost in output token
  • Subtract from expected output
  • Compare net returns
  • Select highest net value

Performance Optimizations

Debounced Fetching

const debouncedFetchQuote = useMemo(
  () => debounce(fetchQuote, 300),
  []
);

// Debounce user input to avoid excessive API calls
useEffect(() => {
  if (inputAmount) {
    debouncedFetchQuote();
  }
}, [inputAmount]);

Parallel Requests

  • Query multiple aggregators simultaneously
  • Return first successful quote
  • Continue fetching for comparison
  • Cancel slow requests

Caching

  • Cache recent quotes
  • Reuse for similar amounts
  • Invalidate on price changes
  • Persist across app restarts

Swaps Overview

Complete swap functionality

Gas Estimation

How gas is calculated

Atomic Swaps

Advanced swap features

Build docs developers (and LLMs) love