@@ -34,11 +34,74 @@ import { CrossChainSwapFactory } from '../factory'
3434import { CrossChainSwapAdapterRegistry , Quote } from '../registry'
3535import { NEAR_STABLE_COINS , SOLANA_STABLE_COINS , isCanonicalPair } from '../utils'
3636
37+ // Mapping from ChainId/NonEvmChain to aggregator API chain names
38+ const CHAIN_TO_API_NAME : Partial < Record < Chain , string > > = {
39+ [ NonEvmChain . Bitcoin ] : 'bitcoin' ,
40+ [ NonEvmChain . Near ] : 'near' ,
41+ [ NonEvmChain . Solana ] : 'solana' ,
42+ [ ChainId . MAINNET ] : 'ethereum' ,
43+ [ ChainId . BASE ] : 'base' ,
44+ [ ChainId . ARBITRUM ] : 'arbitrum' ,
45+ [ ChainId . OPTIMISM ] : 'optimism' ,
46+ [ ChainId . MATIC ] : 'polygon' ,
47+ [ ChainId . BSCMAINNET ] : 'bsc' ,
48+ [ ChainId . AVAXMAINNET ] : 'avalanche' ,
49+ [ ChainId . LINEA ] : 'linea' ,
50+ [ ChainId . SCROLL ] : 'scroll' ,
51+ [ ChainId . BLAST ] : 'blast' ,
52+ [ ChainId . ZKSYNC ] : 'zksync' ,
53+ [ ChainId . SONIC ] : 'sonic' ,
54+ [ ChainId . FANTOM ] : 'fantom' ,
55+ [ ChainId . BERA ] : 'bera' ,
56+ [ ChainId . MANTLE ] : 'mantle' ,
57+ [ ChainId . RONIN ] : 'ronin' ,
58+ [ ChainId . UNICHAIN ] : 'unichain' ,
59+ [ ChainId . HYPEREVM ] : 'hyperevm' ,
60+ }
61+
62+ // Reverse mapping from API chain names to ChainId/NonEvmChain
63+ export const API_NAME_TO_CHAIN : Record < string , Chain > = {
64+ bitcoin : NonEvmChain . Bitcoin ,
65+ near : NonEvmChain . Near ,
66+ solana : NonEvmChain . Solana ,
67+ ethereum : ChainId . MAINNET ,
68+ base : ChainId . BASE ,
69+ arbitrum : ChainId . ARBITRUM ,
70+ optimism : ChainId . OPTIMISM ,
71+ polygon : ChainId . MATIC ,
72+ bsc : ChainId . BSCMAINNET ,
73+ avalanche : ChainId . AVAXMAINNET ,
74+ linea : ChainId . LINEA ,
75+ scroll : ChainId . SCROLL ,
76+ blast : ChainId . BLAST ,
77+ zksync : ChainId . ZKSYNC ,
78+ sonic : ChainId . SONIC ,
79+ fantom : ChainId . FANTOM ,
80+ bera : ChainId . BERA ,
81+ mantle : ChainId . MANTLE ,
82+ ronin : ChainId . RONIN ,
83+ unichain : ChainId . UNICHAIN ,
84+ hyperevm : ChainId . HYPEREVM ,
85+ }
86+
87+ const getChainName = ( chain : Chain ) : string => {
88+ return CHAIN_TO_API_NAME [ chain ] || chain . toString ( )
89+ }
90+
91+ export const getChainIdFromName = ( chainName : string ) : Chain => {
92+ return API_NAME_TO_CHAIN [ chainName . toLowerCase ( ) ] || chainName
93+ }
94+
3795export const registry = new CrossChainSwapAdapterRegistry ( )
3896CrossChainSwapFactory . getAllAdapters ( ) . forEach ( adapter => {
3997 registry . registerAdapter ( adapter )
4098} )
4199
100+ console . log (
101+ 'Registered adapters:' ,
102+ registry . getAllAdapters ( ) . map ( a => a . getName ( ) ) ,
103+ )
104+
42105// Helper function to create a timeout promise
43106const createTimeoutPromise = ( ms : number ) => {
44107 return new Promise < never > ( ( _ , reject ) => {
@@ -525,8 +588,10 @@ export const CrossChainSwapRegistryProvider = ({ children }: { children: React.R
525588 allAdapters = registry . getAllAdapters ( )
526589 }
527590
528- // Create a modified version of getQuotes that can be cancelled
591+ // Build API URL with query parameters
529592 const quotes : Quote [ ] = [ ]
593+
594+ // Determine which adapters should be used (for filtering later)
530595 const adapters =
531596 params . fromChain === params . toChain &&
532597 isEvmChain ( params . fromChain ) &&
@@ -541,38 +606,169 @@ export const CrossChainSwapRegistryProvider = ({ children }: { children: React.R
541606 adapter . getSupportedChains ( ) . includes ( params . fromChain ) &&
542607 adapter . getSupportedChains ( ) . includes ( params . toChain ) ,
543608 )
544- // Map each adapter to a promise that can be cancelled
545- const quotePromises = adapters . map ( async adapter => {
546- try {
547- // Check for cancellation before starting
548- if ( signal . aborted ) throw new Error ( 'Cancelled' )
549-
550- // Race between the adapter quote and timeout
551- const quote = await Promise . race ( [ adapter . getQuote ( params ) , createTimeoutPromise ( 9_000 ) ] )
552-
553- // Check for cancellation after getting quote
554- if ( signal . aborted ) throw new Error ( 'Cancelled' )
555-
556- quotes . push ( { adapter, quote } )
557- const sortedQuotes = [ ...quotes ] . sort ( ( a , b ) => ( a . quote . outputAmount < b . quote . outputAmount ? 1 : - 1 ) )
558- setQuotes ( sortedQuotes )
559- setLoading ( false )
560- } catch ( err ) {
561- if ( err . message === 'Cancelled' || signal . aborted ) {
609+
610+ // Construct the API URL with parameters
611+ const fromToken = ( params . fromToken as any ) . isNative
612+ ? ZERO_ADDRESS
613+ : ( params . fromToken as any ) . wrapped ?. address ||
614+ ( params . fromToken as any ) . address ||
615+ ( params . fromToken as any ) . id ||
616+ ( params . fromToken as any ) . assetId
617+ const toToken = ( params . toToken as any ) . isNative
618+ ? ZERO_ADDRESS
619+ : ( params . toToken as any ) . wrapped ?. address ||
620+ ( params . toToken as any ) . address ||
621+ ( params . toToken as any ) . id ||
622+ ( params . toToken as any ) . assetId
623+ const fromTokenDecimals = params . fromToken . decimals
624+ const toTokenDecimals = params . toToken . decimals
625+
626+ const queryParams = new URLSearchParams ( {
627+ fromChain : getChainName ( params . fromChain ) ,
628+ fromToken,
629+ fromTokenDecimals : fromTokenDecimals . toString ( ) ,
630+ fromAddress : params . sender ,
631+ fromAmount : params . amount ,
632+ toChain : getChainName ( params . toChain ) ,
633+ toToken,
634+ toTokenDecimals : toTokenDecimals . toString ( ) ,
635+ toAddress : params . recipient ,
636+ fee : params . feeBps . toString ( ) ,
637+ integrator : 'kyberswap' ,
638+ stream : 'true' ,
639+ slippage : params . slippage . toString ( ) ,
640+ fromTokenUsd : ( params as any ) . tokenInUsd ?. toString ( ) || '0' ,
641+ toTokenUsd : ( params as any ) . tokenOutUsd ?. toString ( ) || '0' ,
642+ } )
643+
644+ const apiUrl = `https://pre-crosschain-aggregator.kyberengineering.io/api/v1/quotes?${ queryParams . toString ( ) } `
645+
646+ console . log ( 'Fetching quotes from streaming API:' , apiUrl )
647+
648+ try {
649+ // Check for cancellation before starting
650+ if ( signal . aborted ) throw new Error ( 'Cancelled' )
651+
652+ // Fetch with streaming
653+ const response = await fetch ( apiUrl , { signal } )
654+
655+ console . log ( 'Streaming API response status:' , response . status )
656+
657+ if ( ! response . ok ) {
658+ throw new Error ( `HTTP error! status: ${ response . status } ` )
659+ }
660+
661+ const reader = response . body ?. getReader ( )
662+ const decoder = new TextDecoder ( )
663+
664+ if ( ! reader ) {
665+ throw new Error ( 'No response body reader available' )
666+ }
667+
668+ let buffer = ''
669+
670+ // Read the stream
671+ while ( true ) {
672+ // Check for cancellation
673+ if ( signal . aborted ) {
674+ reader . cancel ( )
562675 throw new Error ( 'Cancelled' )
563676 }
564- console . error ( `Failed to get quote from ${ adapter . getName ( ) } :` , err )
677+
678+ const { done, value } = await reader . read ( )
679+
680+ if ( done ) break
681+
682+ buffer += decoder . decode ( value , { stream : true } )
683+
684+ // Process complete SSE messages
685+ const lines = buffer . split ( '\n' )
686+ buffer = lines . pop ( ) || '' // Keep incomplete line in buffer
687+
688+ for ( const line of lines ) {
689+ // Skip empty lines and event lines
690+ if ( ! line || line . startsWith ( 'event:' ) ) {
691+ continue
692+ }
693+
694+ if ( line . startsWith ( 'data:' ) ) {
695+ try {
696+ // Handle both "data:{json}" and "data: {json}" formats
697+ const jsonStr = line . startsWith ( 'data: ' ) ? line . slice ( 6 ) : line . slice ( 5 )
698+ const data = JSON . parse ( jsonStr )
699+
700+ // Skip complete messages
701+ if ( data . message === 'All quotes received' ) {
702+ console . log ( 'All quotes received from streaming API' )
703+ continue
704+ }
705+
706+ // Find the corresponding adapter
707+ console . log ( 'Processing quote from provider:' , data . provider )
708+ const adapter = registry . getAdapter ( data . provider )
709+
710+ if ( adapter ) {
711+ console . log ( 'Adapter found:' , adapter . getName ( ) , 'Output amount:' , data . outputAmount )
712+ // Convert the API response to NormalizedQuote format
713+ // Need to convert chain names back to Chain IDs for the adapters to work correctly
714+ const normalizedQuote = {
715+ quoteParams : {
716+ ...data . quoteParams ,
717+ fromChain : getChainIdFromName ( data . quoteParams . fromChain ) ,
718+ toChain : getChainIdFromName ( data . quoteParams . toChain ) ,
719+ } ,
720+ outputAmount : BigInt ( data . outputAmount ) ,
721+ formattedOutputAmount : data . formattedOutputAmount ,
722+ inputUsd : data . inputUsd ,
723+ outputUsd : data . outputUsd ,
724+ rate : data . rate ,
725+ timeEstimate : data . timeEstimate ,
726+ priceImpact : data . priceImpact ,
727+ gasFeeUsd : data . gasFeeUsd ,
728+ contractAddress : data . contractAddress ,
729+ rawQuote : data . rawQuote ,
730+ protocolFee : data . protocolFee ,
731+ protocolFeeString : data . protocolFeeString ,
732+ platformFeePercent : data . platformFeePercent ,
733+ }
734+
735+ quotes . push ( { adapter, quote : normalizedQuote } )
736+ console . log ( 'Total quotes so far:' , quotes . length )
737+ const sortedQuotes = [ ...quotes ] . sort ( ( a , b ) =>
738+ a . quote . outputAmount < b . quote . outputAmount ? 1 : - 1 ,
739+ )
740+ console . log ( 'Setting quotes state with' , sortedQuotes . length , 'quotes' )
741+ setQuotes ( sortedQuotes )
742+ setLoading ( false )
743+ } else {
744+ console . warn ( `Adapter not found in registry: ${ data . provider } ` )
745+ console . log (
746+ 'Available adapters:' ,
747+ registry . getAllAdapters ( ) . map ( a => a . getName ( ) ) ,
748+ )
749+ }
750+ } catch ( err ) {
751+ console . error ( 'Failed to parse SSE data:' , err )
752+ console . error ( 'Problematic line:' , line )
753+ console . error ( 'Line length:' , line . length )
754+ }
755+ }
756+ }
565757 }
566- } )
567758
568- await Promise . all ( quotePromises )
759+ if ( quotes . length === 0 ) {
760+ throw new Error ( 'No valid quotes found for the requested swap' )
761+ }
569762
570- if ( quotes . length === 0 ) {
571- throw new Error ( 'No valid quotes found for the requested swap' )
763+ quotes . sort ( ( a , b ) => ( a . quote . outputAmount < b . quote . outputAmount ? 1 : - 1 ) )
764+ return quotes
765+ } catch ( err ) {
766+ if ( ( err as Error ) . message === 'Cancelled' || signal . aborted ) {
767+ throw new Error ( 'Cancelled' )
768+ }
769+ console . error ( 'Failed to get quotes from streaming API:' , err )
770+ throw err
572771 }
573-
574- quotes . sort ( ( a , b ) => ( a . quote . outputAmount < b . quote . outputAmount ? 1 : - 1 ) )
575- return quotes
576772 }
577773
578774 const adaptedWallet = adaptSolanaWallet (
@@ -582,28 +778,32 @@ export const CrossChainSwapRegistryProvider = ({ children }: { children: React.R
582778 connection . sendTransaction as any ,
583779 )
584780
585- const q = await getQuotesWithCancellation ( {
586- feeBps,
587- tokenInUsd : tokenInUsd ,
588- tokenOutUsd : tokenOutUsd ,
589- fromChain : fromChainId ,
590- toChain : toChainId ,
591- fromToken : currencyIn ,
592- toToken : currencyOut ,
593- amount : inputAmount ,
594- slippage,
595- walletClient : fromChainId === 'solana' ? adaptedWallet : walletClient ?. data ,
596- sender,
597- recipient : receiver ,
598- nearTokens,
599- publicKey : btcPublicKey || '' ,
600- } ) . catch ( e => {
601- console . log ( e )
602- return [ ]
603- } )
604- setQuotes ( q )
605- setLoading ( false )
606- setAllLoading ( false )
781+ try {
782+ await getQuotesWithCancellation ( {
783+ feeBps,
784+ tokenInUsd : tokenInUsd ,
785+ tokenOutUsd : tokenOutUsd ,
786+ fromChain : fromChainId ,
787+ toChain : toChainId ,
788+ fromToken : currencyIn ,
789+ toToken : currencyOut ,
790+ amount : inputAmount ,
791+ slippage,
792+ walletClient : fromChainId === 'solana' ? adaptedWallet : walletClient ?. data ,
793+ sender,
794+ recipient : receiver ,
795+ nearTokens,
796+ publicKey : btcPublicKey || '' ,
797+ } )
798+ } catch ( e ) {
799+ console . error ( 'Error getting quotes:' , e )
800+ if ( ( e as Error ) . message !== 'Cancelled' ) {
801+ setQuotes ( [ ] )
802+ }
803+ } finally {
804+ setLoading ( false )
805+ setAllLoading ( false )
806+ }
607807 } , [
608808 sender ,
609809 receiver ,
0 commit comments