Skip to content

Commit 43130d6

Browse files
authored
fix(perps): BRENTOIL shows up in the 'explore crypto' section (#27699)
## **Description** BRENTOIL (Brent Crude Oil) and 14 other recently added HIP-3 assets appeared in the "Explore Crypto" section because they were missing from the `HIP3_ASSET_MARKET_TYPES` mapping in `hyperLiquidConfig.ts`. Without a `marketType`, the crypto filter (`!m.marketType`) incorrectly captured them. **Root cause:** `app/controllers/perps/constants/hyperLiquidConfig.ts:305` — 15 HIP-3 assets added to HyperLiquid's xyz DEX were not mapped in the app's asset classification config. **Fix:** (1) Added all 15 missing assets to `HIP3_ASSET_MARKET_TYPES` with correct categories. (2) Added `&& !m.isHip3` guard to the crypto filter in `usePerpsHomeData` as defense-in-depth against future unmapped HIP-3 assets. ## **Changelog** CHANGELOG entry: Fixed miscategorization of BRENTOIL and other non-crypto instruments appearing in the "Explore Crypto" section on Perps Home ## **Related issues** Fixes: [TAT-2672](https://consensyssoftware.atlassian.net/browse/TAT-2672) ## **Manual testing steps** ```gherkin Feature: Perps Home market categorization Scenario: BRENTOIL appears in Commodities, not Explore Crypto Given the user is on the Perps Home screen When the market data loads Then BRENTOIL should appear in the "Commodities" section And BRENTOIL should NOT appear in the "Explore Crypto" section And BTC, ETH, SOL remain in the "Explore Crypto" section ``` ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/60b1f75c-823e-4788-8173-dd58629a323c ### **After** https://github.com/user-attachments/assets/a2235b45-8a40-4f23-b0ca-aad8d12506aa ## **Validation Recipe** <details> <summary>Automated validation recipe (validate-recipe.sh)</summary> ```json { "pr": "27699", "title": "BRENTOIL excluded from Explore Crypto section", "jira": "TAT-2672", "acceptance_criteria": [ "BRENTOIL does not appear in the Explore Crypto section", "Other non-crypto instruments are excluded from Explore Crypto", "Filter logic enforced at data level, not manual exclusion", "BRENTOIL appears in Commodities section", "No crypto markets removed from Explore Crypto" ], "validate": { "static": ["yarn lint:tsc"], "runtime": { "pre_conditions": ["CDP connected", "Wallet unlocked on Wallet route"], "steps": [ { "id": "nav_perps", "description": "Navigate to perps home", "action": "navigate", "target": "Perps" }, { "id": "wait_load", "description": "Wait for market data to load", "action": "wait", "ms": 3000 }, { "id": "check_brentoil_not_in_crypto", "description": "BRENTOIL must not be in crypto markets (markets without marketType)", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets) { var cryptoMarkets = markets.filter(function(m) { return !m.marketType; }); var hasBrent = cryptoMarkets.some(function(m) { return (m.symbol || '').indexOf('BRENTOIL') >= 0; }); return JSON.stringify({hasBrentInCrypto: hasBrent, cryptoCount: cryptoMarkets.length}); })", "assert": { "operator": "eq", "field": "hasBrentInCrypto", "value": false } }, { "id": "check_brentoil_in_commodities", "description": "BRENTOIL must be in commodities section", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets) { var commodities = markets.filter(function(m) { return m.marketType === 'commodity'; }); var hasBrent = commodities.some(function(m) { return (m.symbol || '').indexOf('BRENTOIL') >= 0; }); return JSON.stringify({hasBrentInCommodities: hasBrent, commodityCount: commodities.length}); })", "assert": { "operator": "eq", "field": "hasBrentInCommodities", "value": true } }, { "id": "check_no_hip3_in_crypto", "description": "No HIP-3 markets should appear in crypto section", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets) { var hip3InCrypto = markets.filter(function(m) { return !m.marketType && m.isHip3; }); return JSON.stringify({hip3InCryptoCount: hip3InCrypto.length, symbols: hip3InCrypto.map(function(m) { return m.symbol; })}); })", "assert": { "operator": "eq", "field": "hip3InCryptoCount", "value": 0 } }, { "id": "check_crypto_markets_exist", "description": "Crypto markets still exist in Explore Crypto", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets) { var crypto = markets.filter(function(m) { return !m.marketType && !m.isHip3; }); var hasBTC = crypto.some(function(m) { return m.symbol === 'BTC'; }); var hasETH = crypto.some(function(m) { return m.symbol === 'ETH'; }); return JSON.stringify({cryptoCount: crypto.length, hasBTC: hasBTC, hasETH: hasETH}); })", "assert": { "operator": "eq", "field": "hasBTC", "value": true } }, { "id": "check_all_hip3_categorized", "description": "All HIP-3 markets have a marketType assigned", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets) { var uncategorized = markets.filter(function(m) { return m.isHip3 && !m.marketType; }); return JSON.stringify({uncategorizedCount: uncategorized.length, symbols: uncategorized.map(function(m) { return m.symbol; })}); })", "assert": { "operator": "eq", "field": "uncategorizedCount", "value": 0 } }, { "id": "check_no_errors", "description": "No errors in Metro logs", "action": "log_watch", "window_seconds": 10, "must_not_appear": ["TypeError", "undefined is not an object"] } ] } } } ``` </details> ## **Pre-merge author checklist** - [x] I've followed MetaMask Contributor Docs and Coding Standards - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using JSDoc format if applicable - [x] I've applied the right labels on the PR ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk UI/data-filtering change that only affects how perps markets are categorized and displayed; behavior is covered by new unit tests guarding HIP-3 filtering in both trending and search results. > > **Overview** > Prevents HIP-3 xyz DEX instruments (e.g., `xyz:BRENTOIL`) from being treated as crypto on Perps Home by **excluding HIP-3 markets** from the crypto `perpsMarkets` list (including search results). > > Expands `HIP3_ASSET_MARKET_TYPES` to classify newly added xyz assets across *equity/commodity/forex*, and adds unit tests to ensure HIP-3 markets don’t appear under **Explore Crypto** while correctly showing under their respective sections. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f35150d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 5a86bf1 commit 43130d6

3 files changed

Lines changed: 114 additions & 2 deletions

File tree

app/components/UI/Perps/hooks/usePerpsHomeData.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,102 @@ describe('usePerpsHomeData', () => {
486486
});
487487
});
488488

489+
describe('Market type filtering', () => {
490+
it('excludes HIP-3 markets from crypto (perpsMarkets) section', () => {
491+
const marketsWithHip3 = [
492+
...mockMarkets,
493+
createMockMarket({
494+
symbol: 'xyz:BRENTOIL',
495+
name: 'Brent Oil',
496+
isHip3: true,
497+
isNewMarket: true,
498+
}),
499+
createMockMarket({
500+
symbol: 'xyz:GOLD',
501+
name: 'Gold',
502+
marketType: 'commodity',
503+
isHip3: true,
504+
}),
505+
];
506+
507+
mockUsePerpsMarkets.mockReturnValue({
508+
markets: marketsWithHip3,
509+
isLoading: false,
510+
isRefreshing: false,
511+
error: null,
512+
refresh: mockRefreshMarkets,
513+
});
514+
515+
mockSortMarkets.mockImplementation(({ markets }) => markets);
516+
517+
const { result } = renderHook(() => usePerpsHomeData());
518+
519+
// Only non-HIP3 crypto markets should be in perpsMarkets
520+
expect(result.current.perpsMarkets).toHaveLength(3);
521+
expect(result.current.perpsMarkets.every((m) => !m.isHip3)).toBe(true);
522+
// BRENTOIL (unmapped HIP-3) must not appear in crypto
523+
expect(
524+
result.current.perpsMarkets.find((m) => m.symbol === 'xyz:BRENTOIL'),
525+
).toBeUndefined();
526+
});
527+
528+
it('includes HIP-3 commodity markets in commoditiesMarkets', () => {
529+
const marketsWithCommodity = [
530+
...mockMarkets,
531+
createMockMarket({
532+
symbol: 'xyz:BRENTOIL',
533+
name: 'Brent Oil',
534+
marketType: 'commodity',
535+
isHip3: true,
536+
}),
537+
];
538+
539+
mockUsePerpsMarkets.mockReturnValue({
540+
markets: marketsWithCommodity,
541+
isLoading: false,
542+
isRefreshing: false,
543+
error: null,
544+
refresh: mockRefreshMarkets,
545+
});
546+
547+
mockSortMarkets.mockImplementation(({ markets }) => markets);
548+
549+
const { result } = renderHook(() => usePerpsHomeData());
550+
551+
expect(result.current.commoditiesMarkets).toHaveLength(1);
552+
expect(result.current.commoditiesMarkets[0].symbol).toBe('xyz:BRENTOIL');
553+
});
554+
555+
it('excludes unmapped HIP-3 markets from search crypto results', () => {
556+
const marketsWithHip3 = [
557+
...mockMarkets,
558+
createMockMarket({
559+
symbol: 'xyz:BRENTOIL',
560+
name: 'Brent Oil',
561+
isHip3: true,
562+
isNewMarket: true,
563+
}),
564+
];
565+
566+
mockUsePerpsMarkets.mockReturnValue({
567+
markets: marketsWithHip3,
568+
isLoading: false,
569+
isRefreshing: false,
570+
error: null,
571+
refresh: mockRefreshMarkets,
572+
});
573+
574+
const { result } = renderHook(() =>
575+
usePerpsHomeData({ searchQuery: 'BRENT' }),
576+
);
577+
578+
// BRENTOIL should not appear in perpsMarkets (crypto) during search
579+
expect(
580+
result.current.perpsMarkets.find((m) => m.symbol === 'xyz:BRENTOIL'),
581+
).toBeUndefined();
582+
});
583+
});
584+
489585
describe('Trending markets sorting', () => {
490586
it('sorts markets using saved sort preference', () => {
491587
renderHook(() => usePerpsHomeData());

app/components/UI/Perps/hooks/usePerpsHomeData.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ export const usePerpsHomeData = ({
182182
const perpsMarkets = useMemo(
183183
() =>
184184
sortMarkets({
185-
markets: allMarkets.filter((m) => !m.marketType), // Crypto markets have no marketType
185+
markets: allMarkets.filter((m) => !m.marketType && !m.isHip3),
186186
sortBy,
187187
direction,
188188
}).slice(0, trendingLimit),
@@ -321,7 +321,7 @@ export const usePerpsHomeData = ({
321321
if (!searchQuery.trim()) {
322322
return perpsMarkets;
323323
}
324-
return filteredData.markets.filter((m) => !m.marketType);
324+
return filteredData.markets.filter((m) => !m.marketType && !m.isHip3);
325325
}, [searchQuery, perpsMarkets, filteredData.markets]);
326326

327327
const searchedStocksMarkets = useMemo(() => {

app/controllers/perps/constants/hyperLiquidConfig.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,19 @@ export const HIP3_ASSET_MARKET_TYPES: Record<
335335
'xyz:CRWV': 'equity',
336336
'xyz:SMSN': 'equity',
337337

338+
'xyz:GME': 'equity',
339+
'xyz:SOFTBANK': 'equity',
340+
'xyz:HYUNDAI': 'equity',
341+
'xyz:KIOXIA': 'equity',
342+
'xyz:HIMS': 'equity',
343+
'xyz:URNM': 'equity',
344+
'xyz:EWY': 'equity',
345+
'xyz:EWJ': 'equity',
346+
'xyz:SP500': 'equity',
347+
'xyz:JP225': 'equity',
348+
'xyz:KR200': 'equity',
349+
'xyz:VIX': 'equity',
350+
338351
// xyz DEX - Commodities
339352
'xyz:GOLD': 'commodity',
340353
'xyz:SILVER': 'commodity',
@@ -345,10 +358,13 @@ export const HIP3_ASSET_MARKET_TYPES: Record<
345358
'xyz:USAR': 'commodity',
346359
'xyz:NATGAS': 'commodity',
347360
'xyz:PLATINUM': 'commodity',
361+
'xyz:PALLADIUM': 'commodity',
362+
'xyz:BRENTOIL': 'commodity',
348363

349364
// xyz DEX - Forex
350365
'xyz:EUR': 'forex',
351366
'xyz:JPY': 'forex',
367+
'xyz:DXY': 'forex',
352368
} as const;
353369

354370
/**

0 commit comments

Comments
 (0)