Skip to main content
LeoKit’s streaming quotes endpoint uses Server-Sent Events (SSE) to deliver real-time quote updates as they arrive from different protocols. This provides a better user experience than waiting for all quotes to resolve before displaying results.

Why Use Streaming?

Faster Time-to-First-Quote

Display the first quote in ~1-2 seconds instead of waiting 5-10 seconds for all protocols

Progressive UI Updates

Update your UI as each quote arrives, showing users better rates in real-time

Better UX

Users see immediate feedback instead of loading spinners

Lower Latency

Reduce perceived wait time with incremental updates

How It Works

  1. Client opens SSE connection with quote parameters
  2. Server immediately responds with init event containing quote_id
  3. Server sends quote events as each protocol responds
  4. Server sends final event with all quotes aggregated
  5. Server sends finished event and closes connection

Implementation

Basic Example

const eventSource = new EventSource(
  "https://api.leodex.io/leokit/streaming-quotes?" +
    new URLSearchParams({
      from_asset: "BTC.BTC",
      to_asset: "ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      amount: "100000000",
      destination: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
      api_key: "your_api_key_here",
    })
);

let quoteId = null;

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);

  switch (data.type) {
    case "init":
      quoteId = data.quote_id;
      console.log("Waiting for", data.total, "quotes...");
      break;

    case "quote":
      console.log("Received quote from", data.protocol);
      console.log("Expected output:", data.data.expected_amount_out);
      // Update UI with quote
      break;

    case "final":
      console.log("All quotes received:", data.quotes.length);
      console.log(
        "Best quote:",
        data.quotes.find((q) => q.data.flags?.includes("OPTIMAL"))
      );
      break;

    case "finished":
      eventSource.close();
      console.log("Stream completed");
      break;

    case "error":
      console.error("Error:", data.data);
      eventSource.close();
      break;
  }
};

eventSource.onerror = (error) => {
  console.error("SSE error:", error);
  eventSource.close();
};

Event Types

1. init Event

Sent immediately when the connection opens. Contains the quote_id you’ll use for the swap.
{
  "type": "init",
  "quote_id": "01936b4a-7c8e-7890-abcd-ef1234567890",
  "total": 3,
  "timestamp": "2026-01-15T12:34:56.789Z"
}
Fields:
  • quote_id - UUID to use for deposit/status endpoints
  • total - Expected number of quotes (one per protocol)
  • timestamp - When the quote request was received

2. quote Event

Sent each time a protocol returns a quote. May arrive in any order.
{
  "type": "quote",
  "protocol": "thorchain",
  "data": {
    "expected_amount_out": "9850000000",
    "total_swap_seconds": 180,
    "fees": {
      "network": [
        {
          "asset": "BTC.BTC",
          "amount": "10000",
          "type": "network"
        }
      ],
      "affiliate": [],
      "liquidity": "150000000"
    },
    "route": ["BTC.BTC", "THOR.RUNE", "ETH.USDC-0xA0b86991..."],
    "flags": ["OPTIMAL", "CHEAPEST"]
  },
  "timestamp": "2026-01-15T12:34:57.123Z"
}
Key Fields:
  • protocol - Which protocol provided this quote
  • data.expected_amount_out - Output amount (in base units)
  • data.flags - Array of flags like ["OPTIMAL", "FASTEST"]

3. final Event

Sent after all protocols have responded. Contains complete array of quotes sorted by best rate.
{
  "type": "final",
  "quotes": [
    {
      "protocol": "thorchain",
      "data": { ... }
    },
    {
      "protocol": "mayaprotocol",
      "data": { ... }
    }
  ],
  "quote_id": "01936b4a-7c8e-7890-abcd-ef1234567890",
  "timestamp": "2026-01-15T12:34:59.456Z"
}

4. finished Event

Signals the stream is complete. Close the connection after receiving this.
{
  "type": "finished"
}

5. error Event

Sent if an error occurs during quote generation.
{
  "type": "error",
  "data": {
    "code": "INSUFFICIENT_LIQUIDITY",
    "message": "No protocols can handle this swap amount",
    "status": 400
  }
}

React Integration

Here’s a React hook for streaming quotes:
import { useState, useEffect } from 'react';

function useStreamingQuotes(params) {
  const [quotes, setQuotes] = useState([]);
  const [quoteId, setQuoteId] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const eventSource = new EventSource(
      `https://api.leodex.io/leokit/streaming-quotes?${new URLSearchParams({
        ...params,
        api_key: 'your_api_key_here'
      })}`
    );

    eventSource.onmessage = (event) => {
      const data = JSON.parse(event.data);

      switch (data.type) {
        case 'init':
          setQuoteId(data.quote_id);
          break;

        case 'quote':
          // Add quote to array incrementally
          setQuotes(prev => [...prev, data]);
          break;

        case 'final':
          // Replace with sorted final array
          setQuotes(data.quotes);
          setLoading(false);
          break;

        case 'finished':
          eventSource.close();
          break;

        case 'error':
          setError(data.data);
          setLoading(false);
          eventSource.close();
          break;
      }
    };

    eventSource.onerror = () => {
      setError({ message: 'Connection failed' });
      setLoading(false);
      eventSource.close();
    };

    return () => eventSource.close();
  }, [params]);

  return { quotes, quoteId, loading, error };
}

// Usage in component
function SwapWidget() {
  const { quotes, quoteId, loading } = useStreamingQuotes({
    from_asset: 'BTC.BTC',
    to_asset: 'ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    amount: '100000000',
    destination: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
  });

  return (
    <div>
      {loading && <p>Loading quotes...</p>}
      {quotes.map(quote => (
        <QuoteCard key={quote.protocol} quote={quote} />
      ))}
    </div>
  );
}

Handling Disconnects

SSE connections can drop due to network issues. Implement automatic reconnection:
function createReconnectingEventSource(url, maxRetries = 3) {
  let retries = 0;
  let eventSource = null;

  function connect() {
    eventSource = new EventSource(url);

    eventSource.onmessage = (event) => {
      retries = 0; // Reset on successful message
      handleMessage(event);
    };

    eventSource.onerror = () => {
      eventSource.close();

      if (retries < maxRetries) {
        retries++;
        console.log(`Reconnecting... (attempt ${retries})`);
        setTimeout(connect, 1000 * retries); // Exponential backoff
      } else {
        console.error('Max retries reached');
      }
    };
  }

  connect();

  return {
    close: () => eventSource?.close()
  };
}

Best Practices

Always close connections - Call eventSource.close() when done to prevent memory leaks.
Handle all event types - Don’t assume you’ll only receive quote events. Handle error and finished too.
Save quote_id from init - You need this for the deposit endpoint. Don’t wait for the final event.
Show incremental updates - Display quotes as they arrive for better UX. Don’t wait for final.
Implement timeouts - Close the connection if no events arrive within 30 seconds.

SSE vs Regular Quote Endpoint

FeatureStreaming (/streaming-quotes)Regular (/quote)
Response time1-2s for first quote5-10s for all quotes
UpdatesIncrementalSingle response
ConnectionPersistent (SSE)One-time HTTP
Use caseInteractive UIsBatch processing

Browser Compatibility

EventSource is supported in all modern browsers:
  • ✅ Chrome 6+
  • ✅ Firefox 6+
  • ✅ Safari 5+
  • ✅ Edge 79+
  • ❌ IE 11 (use polyfill)
For IE11 support, use the EventSource polyfill:
import 'event-source-polyfill';

Troubleshooting

Cause: Invalid API key or malformed parametersSolution: Check the error event for details. Verify API key is correct.
Cause: No protocols support the requested swap routeSolution: Check /leokit/assets for supported assets. Verify asset identifiers.
Cause: Network issue or server timeoutSolution: Implement client-side timeout (30s recommended). Close and retry.
Cause: Protocols respond at different speedsSolution: This is normal. Sort quotes by expected_amount_out in your UI.

Next Steps