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
Client opens SSE connection with quote parameters
Server immediately responds with init event containing quote_id
Server sends quote events as each protocol responds
Server sends final event with all quotes aggregated
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.
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
Feature Streaming (/streaming-quotes) Regular (/quote) Response time 1-2s for first quote 5-10s for all quotes Updates Incremental Single response Connection Persistent (SSE) One-time HTTP Use case Interactive UIs Batch 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
Connection immediately closes
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