@@ -317,6 +317,11 @@ export class HyperLiquidProvider implements PerpsProvider {
317317
318318 #cachedAllPerpDexs: ( { name : string } | null ) [ ] | null = null ;
319319
320+ // True once DEX discovery has succeeded with real data (not a fallback).
321+ // When false, #ensureReadyPromise is reset after each init so the next
322+ // caller retries DEX discovery instead of reusing a degraded mapping.
323+ #dexDiscoveryComplete = false ;
324+
320325 // Pending promise to deduplicate concurrent getValidatedDexs() calls
321326 #pendingValidatedDexsPromise: Promise < ( string | null ) [ ] > | null = null ;
322327
@@ -698,8 +703,8 @@ export class HyperLiquidProvider implements PerpsProvider {
698703 // Verify clients are properly initialized
699704 this . #clientService. ensureInitialized ( ) ;
700705
701- // Build asset mapping on first call only (flags are immutable)
702- if ( this . #symbolToAssetId. size === 0 ) {
706+ // Build asset mapping on first call, or retry if DEX discovery previously failed
707+ if ( this . #symbolToAssetId. size === 0 || ! this . #dexDiscoveryComplete ) {
703708 this . #deps. debugLogger . log (
704709 'HyperLiquidProvider: Building asset mapping' ,
705710 {
@@ -717,8 +722,15 @@ export class HyperLiquidProvider implements PerpsProvider {
717722 } ) ( ) ;
718723
719724 // Await initialization - keep the promise so subsequent calls resolve immediately
720- // The promise is only reset in disconnect() for clean reconnection
725+ // The promise is only reset in disconnect() for clean reconnection,
726+ // or when DEX discovery was degraded so the next caller retries.
721727 await this . #ensureReadyPromise;
728+ if ( ! this . #dexDiscoveryComplete) {
729+ // DEX discovery failed transiently — reset so next call retries.
730+ // Trading still works (main DEX mapping is populated), but HIP-3 markets
731+ // will be re-discovered on the next #ensureReady() call.
732+ this . #ensureReadyPromise = null ;
733+ }
722734 this . #deps. debugLogger . log ( '[ensureReady] Initialization complete' ) ;
723735 }
724736
@@ -1041,25 +1053,23 @@ export class HyperLiquidProvider implements PerpsProvider {
10411053 ensureError ( error , 'HyperLiquidProvider.fetchValidatedDexsInternal' ) ,
10421054 this . #getErrorContext( 'getValidatedDexs.perpDexs' ) ,
10431055 ) ;
1044- this . #cachedAllPerpDexs = [ null ] ;
1045- this . #cachedValidatedDexs = [ null ] ;
1046- return this . #cachedValidatedDexs;
1056+ // Do not cache — transient error, allow retry on next call
1057+ return [ null ] ;
10471058 }
10481059
1049- // Cache for buildAssetMapping() to avoid duplicate call
1050- this . #cachedAllPerpDexs = allDexs ;
1051-
10521060 // Validate API response
10531061 if ( ! allDexs || ! Array . isArray ( allDexs ) ) {
10541062 this . #deps. debugLogger . log (
10551063 'HyperLiquidProvider: Failed to fetch DEX list (invalid response), falling back to main DEX only' ,
10561064 { allDexs } ,
10571065 ) ;
1058- this . #cachedAllPerpDexs = [ null ] ;
1059- this . #cachedValidatedDexs = [ null ] ;
1060- return this . #cachedValidatedDexs;
1066+ // Do not cache — may be transient, allow retry on next call
1067+ return [ null ] ;
10611068 }
10621069
1070+ // Cache for buildAssetMapping() to avoid duplicate call
1071+ this . #cachedAllPerpDexs = allDexs ;
1072+
10631073 // Extract HIP-3 DEX names (filter out null which represents main DEX)
10641074 const availableHip3Dexs : string [ ] = [ ] ;
10651075 allDexs . forEach ( ( dex ) => {
@@ -1943,6 +1953,13 @@ export class HyperLiquidProvider implements PerpsProvider {
19431953 let dexsToMap : ( string | null ) [ ] ;
19441954 try {
19451955 dexsToMap = await this . #getValidatedDexs( ) ;
1956+ // Mark DEX discovery as complete only when we got real data (not a fallback).
1957+ // #cachedValidatedDexs is null when #fetchValidatedDexsInternal returned [null]
1958+ // without caching (transient failure) — in that case we stay incomplete so
1959+ // #ensureReady resets its promise and retries on the next call.
1960+ if ( this . #cachedValidatedDexs !== null ) {
1961+ this . #dexDiscoveryComplete = true ;
1962+ }
19461963 } catch ( dexError ) {
19471964 // If getValidatedDexs fails, fall back to main DEX only to keep the provider
19481965 // functional. Without this, a transient perpDexs() failure would permanently
@@ -7318,6 +7335,7 @@ export class HyperLiquidProvider implements PerpsProvider {
73187335 this . #cachedMetaByDex. clear ( ) ;
73197336 this . #cachedSpotMeta = null ;
73207337 this . #perpDexsCache = { data : null , timestamp : 0 } ;
7338+ this . #dexDiscoveryComplete = false ;
73217339
73227340 // Await pending initialization before clearing to prevent the IIFE from
73237341 // setting clientsInitialized = true after disconnect completes
0 commit comments