@@ -2968,7 +2968,7 @@ describe('HyperLiquidProvider', () => {
29682968
29692969 describe ( 'Additional Error Handling and Edge Cases' , ( ) => {
29702970 describe ( 'ensureReady and buildAssetMapping' , ( ) => {
2971- it ( 'should handle meta fetch failure in buildAssetMapping' , async ( ) => {
2971+ it ( 'should handle meta fetch failure in buildAssetMapping when both endpoints fail ' , async ( ) => {
29722972 // Create a fresh provider to test buildAssetMapping
29732973 const freshProvider = new HyperLiquidProvider ( ) ;
29742974
@@ -3001,6 +3001,74 @@ describe('HyperLiquidProvider', () => {
30013001 expect ( result . error ) . toContain ( 'Network timeout' ) ;
30023002 } ) ;
30033003
3004+ it ( 'should fallback to meta() in buildAssetMapping when metaAndAssetCtxs returns null meta' , async ( ) => {
3005+ // Create a fresh provider to test buildAssetMapping fallback
3006+ const freshProvider = new HyperLiquidProvider ( ) ;
3007+
3008+ const mockMeta = {
3009+ universe : [
3010+ { name : 'BTC' , szDecimals : 3 , maxLeverage : 50 } ,
3011+ { name : 'ETH' , szDecimals : 4 , maxLeverage : 50 } ,
3012+ ] ,
3013+ } ;
3014+
3015+ const mockMetaFn = jest . fn ( ) . mockResolvedValue ( mockMeta ) ;
3016+
3017+ mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
3018+ createMockInfoClient ( {
3019+ // meta() returns valid data as fallback
3020+ meta : mockMetaFn ,
3021+ // metaAndAssetCtxs returns null meta (simulating partial failure)
3022+ metaAndAssetCtxs : jest . fn ( ) . mockResolvedValue ( [ null , [ ] ] ) ,
3023+ allMids : jest . fn ( ) . mockResolvedValue ( { BTC : '50000' , ETH : '3000' } ) ,
3024+ } ) ,
3025+ ) ;
3026+
3027+ MockedHyperLiquidClientService . mockImplementation (
3028+ ( ) => mockClientService ,
3029+ ) ;
3030+
3031+ // Trigger ensureReady -> buildAssetMapping by calling getMarkets
3032+ const markets = await freshProvider . getMarkets ( ) ;
3033+
3034+ // Should have used the meta() fallback
3035+ expect ( mockMetaFn ) . toHaveBeenCalled ( ) ;
3036+ // Should return the markets from the fallback
3037+ expect ( markets . length ) . toBeGreaterThan ( 0 ) ;
3038+ } ) ;
3039+
3040+ it ( 'should handle buildAssetMapping when meta() fallback also fails' , async ( ) => {
3041+ // Create a fresh provider to test buildAssetMapping
3042+ const freshProvider = new HyperLiquidProvider ( ) ;
3043+
3044+ mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
3045+ createMockInfoClient ( {
3046+ // meta() also fails
3047+ meta : jest . fn ( ) . mockRejectedValue ( new Error ( 'Fallback failed' ) ) ,
3048+ // metaAndAssetCtxs returns null meta
3049+ metaAndAssetCtxs : jest . fn ( ) . mockResolvedValue ( [ null , [ ] ] ) ,
3050+ } ) ,
3051+ ) ;
3052+
3053+ MockedHyperLiquidClientService . mockImplementation (
3054+ ( ) => mockClientService ,
3055+ ) ;
3056+
3057+ // Try to place an order which will trigger ensureReady -> buildAssetMapping
3058+ const orderParams : OrderParams = {
3059+ coin : 'BTC' ,
3060+ isBuy : true ,
3061+ size : '0.1' ,
3062+ orderType : 'market' ,
3063+ currentPrice : 50000 ,
3064+ } ;
3065+
3066+ const result = await freshProvider . placeOrder ( orderParams ) ;
3067+
3068+ // Should fail because asset mapping couldn't be built
3069+ expect ( result . success ) . toBe ( false ) ;
3070+ } ) ;
3071+
30043072 it ( 'should handle string response from meta endpoint' , async ( ) => {
30053073 // Test updatePositionTPSL with string meta response (invalid data type)
30063074 mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
@@ -3311,7 +3379,7 @@ describe('HyperLiquidProvider', () => {
33113379 } ) ;
33123380
33133381 describe ( 'getMarketDataWithPrices error scenarios' , ( ) => {
3314- it ( 'should handle missing perpsMeta' , async ( ) => {
3382+ it ( 'should handle missing perpsMeta when both metaAndAssetCtxs and meta() return null ' , async ( ) => {
33153383 mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
33163384 createMockInfoClient ( {
33173385 meta : jest . fn ( ) . mockResolvedValue ( null ) ,
@@ -3326,6 +3394,76 @@ describe('HyperLiquidProvider', () => {
33263394 ) ;
33273395 } ) ;
33283396
3397+ it ( 'should fallback to meta() when metaAndAssetCtxs returns null meta' , async ( ) => {
3398+ const mockMeta = {
3399+ universe : [ { name : 'BTC' , szDecimals : 3 , maxLeverage : 50 } ] ,
3400+ } ;
3401+
3402+ mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
3403+ createMockInfoClient ( {
3404+ // meta() returns valid data as fallback
3405+ meta : jest . fn ( ) . mockResolvedValue ( mockMeta ) ,
3406+ allMids : jest . fn ( ) . mockResolvedValue ( { BTC : '50000' } ) ,
3407+ predictedFundings : jest . fn ( ) . mockResolvedValue ( [ ] ) ,
3408+ // metaAndAssetCtxs returns null meta (simulating partial failure)
3409+ metaAndAssetCtxs : jest . fn ( ) . mockResolvedValue ( [ null , [ ] ] ) ,
3410+ } ) ,
3411+ ) ;
3412+
3413+ // Create fresh provider to avoid cached state from other tests
3414+ const freshProvider = new HyperLiquidProvider ( ) ;
3415+
3416+ // Should succeed by falling back to meta()
3417+ const result = await freshProvider . getMarketDataWithPrices ( ) ;
3418+ expect ( Array . isArray ( result ) ) . toBe ( true ) ;
3419+ expect ( result . length ) . toBeGreaterThan ( 0 ) ;
3420+ expect ( result [ 0 ] . symbol ) . toBe ( 'BTC' ) ;
3421+ } ) ;
3422+
3423+ it ( 'should fallback to meta() when metaAndAssetCtxs returns undefined meta' , async ( ) => {
3424+ const mockMeta = {
3425+ universe : [ { name : 'ETH' , szDecimals : 4 , maxLeverage : 50 } ] ,
3426+ } ;
3427+
3428+ mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
3429+ createMockInfoClient ( {
3430+ // meta() returns valid data as fallback
3431+ meta : jest . fn ( ) . mockResolvedValue ( mockMeta ) ,
3432+ allMids : jest . fn ( ) . mockResolvedValue ( { ETH : '3000' } ) ,
3433+ predictedFundings : jest . fn ( ) . mockResolvedValue ( [ ] ) ,
3434+ // metaAndAssetCtxs returns undefined (simulating malformed response)
3435+ metaAndAssetCtxs : jest . fn ( ) . mockResolvedValue ( [ undefined , [ ] ] ) ,
3436+ } ) ,
3437+ ) ;
3438+
3439+ // Create fresh provider to avoid cached state from other tests
3440+ const freshProvider = new HyperLiquidProvider ( ) ;
3441+
3442+ // Should succeed by falling back to meta()
3443+ const result = await freshProvider . getMarketDataWithPrices ( ) ;
3444+ expect ( Array . isArray ( result ) ) . toBe ( true ) ;
3445+ expect ( result . length ) . toBeGreaterThan ( 0 ) ;
3446+ expect ( result [ 0 ] . symbol ) . toBe ( 'ETH' ) ;
3447+ } ) ;
3448+
3449+ it ( 'should handle meta() fallback failure gracefully' , async ( ) => {
3450+ mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
3451+ createMockInfoClient ( {
3452+ // meta() also fails
3453+ meta : jest . fn ( ) . mockRejectedValue ( new Error ( 'Network timeout' ) ) ,
3454+ allMids : jest . fn ( ) . mockResolvedValue ( { BTC : '50000' } ) ,
3455+ predictedFundings : jest . fn ( ) . mockResolvedValue ( [ ] ) ,
3456+ // metaAndAssetCtxs returns null meta
3457+ metaAndAssetCtxs : jest . fn ( ) . mockResolvedValue ( [ null , [ ] ] ) ,
3458+ } ) ,
3459+ ) ;
3460+
3461+ // Should still throw the "no markets available" error
3462+ await expect ( provider . getMarketDataWithPrices ( ) ) . rejects . toThrow (
3463+ 'Failed to fetch market data - no markets available' ,
3464+ ) ;
3465+ } ) ;
3466+
33293467 it ( 'should handle missing allMids' , async ( ) => {
33303468 // Set up mock BEFORE creating fresh provider
33313469 mockClientService . getInfoClient = jest . fn ( ) . mockReturnValue (
0 commit comments