From 6a5bb67adc765adba7d143dcd27931e7edf7cc25 Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:47:28 -0800 Subject: [PATCH 1/9] refactor: Market discoverability improvements cp-7.64.0 (#25610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR addresses multiple market discovery UX issues for Perps: 1. Fix "See all perps" navigation: The button in the main perps tab now correctly navigates to the Market List search with "all" selected, instead of redirecting to perps home 2. Include all market categories in explore section: The explore section in wallet home and perps tab empty state no longer filters out commodities and forex markets 3. Add Commodities section to Perps Home: A new Commodities section is now displayed between Crypto and Stocks on the perps home screen 4. Temporarily disable market type badges: Stock/commodity badges on list items are commented out (can be quickly re-enabled if needed) ## **Changelog** CHANGELOG entry: Improvements to market discoverability ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2467 ## **Manual testing steps** ```gherkin Feature: Perps market discovery improvements Scenario: See all perps navigates to market list with all filter Given I am on the wallet home Perps tab with no positions When I tap "See all perps" Then I am on the Market List with "All" filter selected Scenario: Explore section includes all market categories Given I am on the wallet home Perps tab with no positions Then the explore section shows crypto, stocks, commodities, and forex markets Scenario: Commodities section appears between Crypto and Stocks Given I am on the Perps Home screen Then sections appear in order: Crypto, Commodities, Stocks, Forex Scenario: Market type badges are hidden Given I am viewing any market list Then no STOCK or COMMODITY badges appear on market rows ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- > [!NOTE] > **Medium Risk** > Medium risk: changes navigation targets and broadens market inclusion logic, which can alter user flows and market ordering across key Perps entry points, but does not touch trading/auth or core data handling. > > **Overview** > **Perps market discovery UX is broadened and re-routed.** The Perps tab “See all perps” CTA now navigates directly to `Routes.PERPS.MARKET_LIST` with `defaultMarketTypeFilter: 'all'` (instead of `PERPS_HOME`). > > **Explore and home market coverage is expanded.** `usePerpsTabExploreData` no longer filters to crypto/equity only, so the explore list includes all market types (tests updated accordingly), and `PerpsHomeView` adds a new Commodities section between Crypto and Stocks. > > **Badges are temporarily suppressed.** `PerpsMarketRowItem` defaults `showBadge` to `false`, effectively hiding stock/commodity badges unless explicitly enabled. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0516bfa2d9ce42ff158e7f04d4e90d7bd8139a90. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../Views/PerpsHomeView/PerpsHomeView.tsx | 10 ++++++++++ .../Perps/Views/PerpsTabView/PerpsTabView.tsx | 7 +++++-- .../PerpsMarketRowItem/PerpsMarketRowItem.tsx | 2 +- .../Perps/hooks/usePerpsTabExploreData.test.ts | 18 +++++++++--------- .../UI/Perps/hooks/usePerpsTabExploreData.ts | 11 ++--------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx b/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx index 235caac57d8..705d33626b0 100644 --- a/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx +++ b/app/components/UI/Perps/Views/PerpsHomeView/PerpsHomeView.tsx @@ -127,6 +127,7 @@ const PerpsHomeView = () => { orders, watchlistMarkets, perpsMarkets, // Crypto markets (renamed from trendingMarkets) + commoditiesMarkets, // Commodity markets stocksMarkets, // Equity markets only forexMarkets, recentActivity, @@ -503,6 +504,15 @@ const PerpsHomeView = () => { /> + {/* Commodities Markets List */} + + {/* Stocks Markets List */} { const handleSeeAllPerps = useCallback(() => { navigation.navigate(Routes.PERPS.ROOT, { - screen: Routes.PERPS.PERPS_HOME, - params: { source: PERPS_EVENT_VALUE.SOURCE.HOMESCREEN_TAB }, + screen: Routes.PERPS.MARKET_LIST, + params: { + defaultMarketTypeFilter: 'all', + source: PERPS_EVENT_VALUE.SOURCE.HOMESCREEN_TAB, + }, }); }, [navigation]); diff --git a/app/components/UI/Perps/components/PerpsMarketRowItem/PerpsMarketRowItem.tsx b/app/components/UI/Perps/components/PerpsMarketRowItem/PerpsMarketRowItem.tsx index 44a4e177c30..f40a27404f6 100644 --- a/app/components/UI/Perps/components/PerpsMarketRowItem/PerpsMarketRowItem.tsx +++ b/app/components/UI/Perps/components/PerpsMarketRowItem/PerpsMarketRowItem.tsx @@ -36,7 +36,7 @@ const PerpsMarketRowItem = ({ onPress, iconSize = HOME_SCREEN_CONFIG.DefaultIconSize, displayMetric = 'volume', - showBadge = true, + showBadge = false, // We can re-enable this if/when we decide to render the badges for stocks and commodities }: PerpsMarketRowItemProps) => { const { styles } = useStyles(styleSheet, {}); diff --git a/app/components/UI/Perps/hooks/usePerpsTabExploreData.test.ts b/app/components/UI/Perps/hooks/usePerpsTabExploreData.test.ts index d85f17b637c..2e5c6c6b580 100644 --- a/app/components/UI/Perps/hooks/usePerpsTabExploreData.test.ts +++ b/app/components/UI/Perps/hooks/usePerpsTabExploreData.test.ts @@ -189,7 +189,7 @@ describe('usePerpsTabExploreData', () => { expect(result.current.exploreMarkets[7].symbol).toBe('TOKEN7'); }); - it('filters out non-crypto/equity market types', () => { + it('includes all market types without filtering', () => { // Arrange const mixedMarkets: PerpsMarketDataWithVolumeNumber[] = [ { ...mockMarkets[0], marketType: undefined }, // crypto (no type) @@ -197,7 +197,7 @@ describe('usePerpsTabExploreData', () => { { ...mockMarkets[2], marketType: 'forex' as PerpsMarketData['marketType'], - }, // forex - should be filtered out + }, // forex ]; mockUsePerpsMarkets.mockReturnValue({ markets: mixedMarkets, @@ -212,13 +212,13 @@ describe('usePerpsTabExploreData', () => { usePerpsTabExploreData({ enabled: true }), ); - // Assert - forex should be filtered out - expect(result.current.exploreMarkets).toHaveLength(2); - expect( - result.current.exploreMarkets.every( - (m) => !m.marketType || m.marketType === 'equity', - ), - ).toBe(true); + // Assert - all market types are included (no filtering) + expect(result.current.exploreMarkets).toHaveLength(3); + expect(result.current.exploreMarkets.map((m) => m.symbol)).toEqual([ + 'BTC', + 'ETH', + 'SOL', + ]); }); it('returns empty watchlist when no symbols match', () => { diff --git a/app/components/UI/Perps/hooks/usePerpsTabExploreData.ts b/app/components/UI/Perps/hooks/usePerpsTabExploreData.ts index b6c631ae0ea..6cdd7657db8 100644 --- a/app/components/UI/Perps/hooks/usePerpsTabExploreData.ts +++ b/app/components/UI/Perps/hooks/usePerpsTabExploreData.ts @@ -42,18 +42,11 @@ export const usePerpsTabExploreData = ({ // Get watchlist symbols from Redux const watchlistSymbols = useSelector(selectPerpsWatchlistMarkets); - // Filter explore markets: crypto + equity, top 8 by volume + // Filter explore markets: all market types, top 8 by volume // Markets are already sorted by volume from usePerpsMarkets const exploreMarkets = useMemo(() => { if (!enabled) return []; - return markets - .filter( - (m) => - !m.marketType || - m.marketType === 'crypto' || - m.marketType === 'equity', - ) - .slice(0, EXPLORE_MARKETS_LIMIT); + return markets.slice(0, EXPLORE_MARKETS_LIMIT); }, [markets, enabled]); // Filter watchlist markets From 59d82157206ff69373237baa90802c8d612688eb Mon Sep 17 00:00:00 2001 From: George Gkasdrogkas Date: Wed, 4 Feb 2026 20:13:49 +0200 Subject: [PATCH 2/9] feat: bring back destination asset sync to new swaps asset picker (#25644) ## **Description** bring back destination asset sync to new asset picker. Original PR #23822 ## **Changelog** CHANGELOG entry: bring back destination asset sync to new swaps asset picker ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/SWAPS-3935 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- > [!NOTE] > **Medium Risk** > Updates bridge token-selection behavior to automatically adjust the destination token when the source token changes, which can affect swap/bridge state and user expectations but stays within UI selection logic. > > **Overview** > Restores destination-asset syncing in the Bridge asset picker by invoking `useAutoUpdateDestToken` whenever a new **source** token is selected (normal selection path), while leaving the swap-on-select-other-token behavior unchanged. > > Updates `useTokenSelection` unit tests to mock `useAutoUpdateDestToken` and assert the auto-update call occurs for source changes and does *not* occur when tokens are swapped. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b9f942d56fef84930cd3acf63d9a22cc5c0b4527. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../UI/Bridge/hooks/useTokenSelection.test.ts | 14 +++++++++++++- .../UI/Bridge/hooks/useTokenSelection.ts | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/components/UI/Bridge/hooks/useTokenSelection.test.ts b/app/components/UI/Bridge/hooks/useTokenSelection.test.ts index ce807f01bc4..c536f2d7996 100644 --- a/app/components/UI/Bridge/hooks/useTokenSelection.test.ts +++ b/app/components/UI/Bridge/hooks/useTokenSelection.test.ts @@ -30,6 +30,13 @@ jest.mock('./useIsNetworkEnabled', () => ({ useIsNetworkEnabled: jest.fn(() => true), })); +const mockAutoUpdateDestToken = jest.fn(); +jest.mock('./useAutoUpdateDestToken', () => ({ + useAutoUpdateDestToken: () => ({ + autoUpdateDestToken: mockAutoUpdateDestToken, + }), +})); + import { useSelector } from 'react-redux'; import { useIsNetworkEnabled } from './useIsNetworkEnabled'; const mockUseSelector = useSelector as jest.Mock; @@ -60,7 +67,7 @@ describe('useTokenSelection', () => { .mockReturnValueOnce(mockDestAmount); // selectDestAmount }); - it('dispatches setSourceToken when selecting new source token', async () => { + it('dispatches setSourceToken and calls autoUpdateDestToken when selecting new source token', async () => { const { result } = renderHook(() => useTokenSelection(TokenSelectorType.Source), ); @@ -74,6 +81,7 @@ describe('useTokenSelection', () => { }); expect(mockDispatch).toHaveBeenCalledWith(setSourceToken(newToken)); + expect(mockAutoUpdateDestToken).toHaveBeenCalledWith(newToken); expect(mockGoBack).toHaveBeenCalled(); }); @@ -88,6 +96,7 @@ describe('useTokenSelection', () => { expect(mockHandleSwitchTokens).toHaveBeenCalledWith(mockDestAmount); expect(mockHandleSwitchTokensInner).toHaveBeenCalled(); + expect(mockAutoUpdateDestToken).not.toHaveBeenCalled(); expect(mockGoBack).toHaveBeenCalled(); }); @@ -193,6 +202,7 @@ describe('useTokenSelection', () => { }); expect(mockDispatch).toHaveBeenCalledWith(setSourceToken(newToken)); + expect(mockAutoUpdateDestToken).toHaveBeenCalledWith(newToken); expect(mockGoBack).toHaveBeenCalled(); }); @@ -237,6 +247,7 @@ describe('useTokenSelection', () => { setSourceToken(sameAddressToken), ); expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockAutoUpdateDestToken).toHaveBeenCalledWith(sameAddressToken); expect(mockHandleSwitchTokens).not.toHaveBeenCalled(); }); @@ -260,6 +271,7 @@ describe('useTokenSelection', () => { expect(mockDispatch).toHaveBeenCalledWith(setSourceToken(sameChainToken)); expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockAutoUpdateDestToken).toHaveBeenCalledWith(sameChainToken); expect(mockHandleSwitchTokens).not.toHaveBeenCalled(); }); }); diff --git a/app/components/UI/Bridge/hooks/useTokenSelection.ts b/app/components/UI/Bridge/hooks/useTokenSelection.ts index 6419badb76e..6745f771628 100644 --- a/app/components/UI/Bridge/hooks/useTokenSelection.ts +++ b/app/components/UI/Bridge/hooks/useTokenSelection.ts @@ -12,6 +12,7 @@ import { import { BridgeToken, TokenSelectorType } from '../types'; import { useSwitchTokens } from './useSwitchTokens'; import { useIsNetworkEnabled } from './useIsNetworkEnabled'; +import { useAutoUpdateDestToken } from './useAutoUpdateDestToken'; /** * Hook to manage token selection logic for Bridge token selector @@ -26,6 +27,7 @@ export const useTokenSelection = (type: TokenSelectorType) => { const destAmount = useSelector(selectDestAmount); const { handleSwitchTokens } = useSwitchTokens(); const isDestNetworkEnabled = useIsNetworkEnabled(destToken?.chainId); + const { autoUpdateDestToken } = useAutoUpdateDestToken(); const handleTokenPress = useCallback( async (token: BridgeToken) => { @@ -59,6 +61,9 @@ export const useTokenSelection = (type: TokenSelectorType) => { dispatch(isSourcePicker ? setSourceToken(token) : setDestToken(token)); if (!isSourcePicker) { dispatch(setIsDestTokenManuallySet(true)); + } else { + // Auto-update dest token when source token changes + autoUpdateDestToken(token); } } @@ -73,6 +78,7 @@ export const useTokenSelection = (type: TokenSelectorType) => { navigation, handleSwitchTokens, isDestNetworkEnabled, + autoUpdateDestToken, ], ); From 61f1bad2f9ebc496343acb3ba4a5eff31cb44e48 Mon Sep 17 00:00:00 2001 From: Brian August Nguyen Date: Wed, 4 Feb 2026 10:26:27 -0800 Subject: [PATCH 3/9] chore: Reverted confirmation header change (#25594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR reverts the unintentional header change for the confirmation screens ## **Changelog** CHANGELOG entry: Fixed incorrect title for confirmation screens ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/91af13b2-5e99-41c9-a978-26c67986a6b3 ### **After** https://github.com/user-attachments/assets/aa47fbf6-b861-426b-a74b-e85c494c9723 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- > [!NOTE] > **Low Risk** > Low risk UI/navigation change that only affects confirmation screen header visibility; main risk is unintended header state for certain confirmation types due to `setOptions` dependency changes. > > **Overview** > Restores confirmation header behavior by **using React Navigation’s header only for full-screen confirmations** (`isFullScreenConfirmation`) and keeping it hidden for modal/bottom-sheet confirmations. > > Removes the in-view `HeaderCenter` from the full-screen confirmation layout, and updates tests to mock `useFullScreenConfirmation` and assert the new `navigation.setOptions({ headerShown, gestureEnabled })` behavior. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 406f6025ebe3483a6562a1bc2321776d08c32d45. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../confirm/confirm-component.test.tsx | 31 ++++++++++++++++--- .../components/confirm/confirm-component.tsx | 20 ++++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx b/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx index 3694a5511dc..8a82fcd1f51 100644 --- a/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx +++ b/app/components/Views/confirmations/components/confirm/confirm-component.test.tsx @@ -19,6 +19,7 @@ import { useTokensWithBalance } from '../../../../UI/Bridge/hooks/useTokensWithB import { useConfirmActions } from '../../hooks/useConfirmActions'; import { useParams } from '../../../../../util/navigation/navUtils'; import useConfirmationAlerts from '../../hooks/alerts/useConfirmationAlerts'; +import { useFullScreenConfirmation } from '../../hooks/ui/useFullScreenConfirmation'; jest.mock('../../hooks/useConfirmActions'); @@ -42,6 +43,7 @@ jest.mock('../../../../hooks/AssetPolling/AssetPollingProvider', () => ({ jest.mock('../../hooks/gas/useGasFeeToken'); jest.mock('../../hooks/tokens/useTokenWithBalance'); jest.mock('../../hooks/alerts/useConfirmationAlerts'); +jest.mock('../../hooks/ui/useFullScreenConfirmation'); jest.mock('../../../../hooks/useRefreshSmartTransactionsLiveness', () => ({ useRefreshSmartTransactionsLiveness: jest.fn(), })); @@ -171,6 +173,9 @@ describe('Confirm', () => { }); jest.mocked(useConfirmationAlerts).mockReturnValue([]); + jest.mocked(useFullScreenConfirmation).mockReturnValue({ + isFullScreenConfirmation: false, + }); }); afterEach(() => { @@ -188,6 +193,10 @@ describe('Confirm', () => { }); it('renders a flat confirmation for specified type(s): staking deposit', () => { + jest.mocked(useFullScreenConfirmation).mockReturnValue({ + isFullScreenConfirmation: true, + }); + const { getByTestId } = renderWithProvider(, { state: stakingDepositConfirmationState, }); @@ -195,6 +204,10 @@ describe('Confirm', () => { }); it('renders a flat confirmation for specified type(s): staking withdrawal', () => { + jest.mocked(useFullScreenConfirmation).mockReturnValue({ + isFullScreenConfirmation: true, + }); + const { getByTestId } = renderWithProvider(, { state: stakingWithdrawalConfirmationState, }); @@ -497,20 +510,28 @@ describe('Confirm', () => { expect(getByTestId('confirm-loader-default')).toBeDefined(); }); - it('sets navigation options with header hidden for modal confirmations', () => { + it('sets navigation options with header shown for full screen confirmations', () => { + jest.mocked(useFullScreenConfirmation).mockReturnValue({ + isFullScreenConfirmation: true, + }); + renderWithProvider(, { - state: typedSignV1ConfirmationState, + state: stakingDepositConfirmationState, }); expect(mockSetOptions).toHaveBeenCalledWith({ - headerShown: false, + headerShown: true, gestureEnabled: true, }); }); - it('sets navigation options with header hidden for full screen confirmations', () => { + it('sets navigation options with header hidden for non-full screen confirmations', () => { + jest.mocked(useFullScreenConfirmation).mockReturnValue({ + isFullScreenConfirmation: false, + }); + renderWithProvider(, { - state: stakingDepositConfirmationState, + state: typedSignV1ConfirmationState, }); expect(mockSetOptions).toHaveBeenCalledWith({ diff --git a/app/components/Views/confirmations/components/confirm/confirm-component.tsx b/app/components/Views/confirmations/components/confirm/confirm-component.tsx index 4a36c698367..e11f5650014 100755 --- a/app/components/Views/confirmations/components/confirm/confirm-component.tsx +++ b/app/components/Views/confirmations/components/confirm/confirm-component.tsx @@ -12,8 +12,6 @@ import { useNavigation } from '@react-navigation/native'; import { ConfirmationUIType } from '../../ConfirmationView.testIds'; import BottomSheet from '../../../../../component-library/components/BottomSheets/BottomSheet'; import { useStyles } from '../../../../../component-library/hooks'; -import HeaderCenter from '../../../../../component-library/components-temp/HeaderCenter'; -import { strings } from '../../../../../../locales/i18n'; import { UnstakeConfirmationViewProps } from '../../../../UI/Stake/Views/UnstakeConfirmationView/UnstakeConfirmationView.types'; import useConfirmationAlerts from '../../hooks/alerts/useConfirmationAlerts'; import useApprovalRequest from '../../hooks/useApprovalRequest'; @@ -126,14 +124,19 @@ export const Confirm = ({ useEffect(() => { if (approvalRequest) { - navigation.setOptions({ - // HeaderCenter is used for full screen confirmations, so we don't need React Navigation's header + const options = { headerShown: false, // If there is an approvalRequest, we need to allow the user to swipe to reject the confirmation gestureEnabled: true, - }); + }; + + if (isFullScreenConfirmation) { + // If the confirmation is full screen, we need to show the header + options.headerShown = true; + } + navigation.setOptions(options); } - }, [approvalRequest, navigation]); + }, [approvalRequest, isFullScreenConfirmation, navigation]); useEffect(() => { if (!approvalRequest) { @@ -162,11 +165,6 @@ export const Confirm = ({ style={[styles.flatContainer, fullscreenStyle]} testID={ConfirmationUIType.FLAT} > - ); From cb82e94ad02137fa857e6bee1c28c28c9a39aaf2 Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:28:20 -0800 Subject: [PATCH 4/9] fix: TAT-2323 Stop loss banner logic (#25556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR fixes the stop loss prompt banner to calculate the suggested trigger price correctly per the original specs. The banner was previously using a fixed -50% ROE calculation, but the design spec called for the midpoint between current price and liquidation price. **Stop Loss Price Calculation** - Changed from calculating a price at -50% ROE to using the midpoint between current (mark) price and liquidation price - The displayed ROE percentage now reflects the actual loss at that midpoint, rather than a hardcoded -50% **Position Age Requirement** - Increased from 1 minute to 2 minutes before showing the banner - Prevents the banner from appearing too soon after opening a position **Mark Price for Calculations** - Now uses the oracle mark price instead of the order book mid price - Reduces manipulation risk since mark price is sourced from external oracles rather than the exchange's order book **Safety Guard for Proximity** - If the suggested stop loss price is within 3% of the current mark price, the banner now shows "Add margin" instead of "Set stop loss" - Prevents accidental immediate fills if the stop loss would trigger right away The original implementation had a mismatch between the documentation in the spec (which said "midpoint") and the actual code (which used -50% ROE). The midpoint approach is better because it adapts to how close the position is to liquidation, rather than using a fixed percentage that might be inappropriate for the situation. ## **Changelog** CHANGELOG entry: Fixes incorrect stop lost banner price ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2323 ## **Manual testing steps** ```gherkin Feature: Stop Loss Prompt Banner Background: Given user has an open Perps position with isolated margin And position has no existing stop loss set And position has no take profit that would close before liquidation # Midpoint Price Calculation Scenario: Banner suggests stop loss at midpoint between current and liquidation price Given current mark price is $50,000 And liquidation price is $40,000 And position ROE is at or below -10% And position has been open for more than 2 minutes When the stop loss prompt banner appears Then suggested stop loss price should be $45,000 And suggested stop loss price should be the midpoint between current and liquidation # Position Age Requirement Scenario: Banner does not appear until position is 2 minutes old Given position ROE is -15% And position was opened less than 2 minutes ago When user views the position details Then stop loss prompt banner should NOT be displayed Scenario: Banner appears after position is 2 minutes old Given position ROE is -15% And position has been open for more than 2 minutes And ROE has been below -10% for at least 60 seconds When user views the position details Then stop loss prompt banner should be displayed And banner variant should be "stop_loss" # Safety Guard - Suggested SL Too Close to Current Price Scenario: Shows add margin variant when suggested SL is within 3% of current price Given current mark price is $50,000 And liquidation price is $49,000 And midpoint would be $49,500 (1% from current price) And position ROE is at or below -10% And position has been open for more than 2 minutes When the stop loss conditions are met Then banner variant should be "add_margin" And banner should NOT suggest setting a stop loss Scenario: Shows stop loss variant when suggested SL is more than 3% from current price Given current mark price is $50,000 And liquidation price is $40,000 And midpoint would be $45,000 (10% from current price) And position ROE is at or below -10% And position has been open for more than 2 minutes When the stop loss conditions are met Then banner variant should be "stop_loss" And banner should suggest setting stop loss at $45,000 # Near Liquidation - Add Margin Variant Scenario: Shows add margin variant when within 3% of liquidation Given current mark price is within 3% of liquidation price And position ROE is at or below -10% And position has been open for more than 2 minutes When user views the position details Then banner variant should be "add_margin" And banner should suggest adding margin # Suppression Conditions Scenario: Banner is suppressed for cross margin positions Given position uses cross margin And position ROE is -15% When user views the position details Then stop loss prompt banner should NOT be displayed Scenario: Banner is suppressed when stop loss already exists Given position has an existing stop loss set And position ROE is -15% When user views the position details Then stop loss prompt banner should NOT be displayed # One-Tap Flow Scenario: User sets stop loss via one-tap banner action Given stop loss prompt banner is displayed with variant "stop_loss" And suggested stop loss price is $45,000 When user taps "Set stop at $45,000" Then spinner should appear on the button And stop loss order should be placed at $45,000 And success toast should appear And banner should fade out ``` ## **Screenshots/Recordings** **BEFORE** Screenshot 2026-02-03 at 12 11 52 PM Screenshot 2026-02-03 at 12 12 53 PM Screenshot 2026-02-03 at 12 12 40 PM **AFTER** Screenshot 2026-02-03 at 12 00 38 PM Screenshot 2026-02-03 at 11 59 50 AM Screenshot 2026-02-03 at 11 59 26 AM Screenshot 2026-02-03 at 11 59 04 AM ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- > [!NOTE] > **Medium Risk** > Changes user-facing risk-management logic (price source, suggested stop-loss computation, and banner variant selection), which could affect when users are prompted to set orders or add margin if the calculations are wrong. > > **Overview** > **Stop-loss prompt banner logic is updated to match the intended spec.** The suggested stop-loss trigger price now uses the midpoint between *current mark price* and liquidation price, and the displayed ROE percent is computed from that midpoint (instead of a fixed configured ROE). > > **Banner timing and safety guards are tightened.** The minimum position age before showing any banner is increased to 2 minutes, the market details view prefers `markPrice` (oracle) for calculations with fallback to mid price, and the hook now falls back to `add_margin` when the suggested stop-loss is missing or within 3% of current price; tests were expanded/updated accordingly. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0877c376a646cb6485364ed27178ab3aba7bbd9c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../PerpsMarketDetailsView.tsx | 5 + .../UI/Perps/constants/perpsConfig.ts | 2 +- .../UI/Perps/hooks/useStopLossPrompt.test.ts | 273 ++++++++++++++++-- .../UI/Perps/hooks/useStopLossPrompt.ts | 87 ++++-- 4 files changed, 319 insertions(+), 48 deletions(-) diff --git a/app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.tsx b/app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.tsx index 7afa430cc67..f83357e61d8 100644 --- a/app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.tsx +++ b/app/components/UI/Perps/Views/PerpsMarketDetailsView/PerpsMarketDetailsView.tsx @@ -309,9 +309,14 @@ const PerpsMarketDetailsView: React.FC = () => { }); // Get current price for the symbol + // Use mark price (oracle price) for stop loss calculations to reduce manipulation risk + // Falls back to mid price if mark price unavailable const currentPrice = useMemo(() => { if (!market?.symbol) return 0; const priceData = livePrices[market.symbol]; + if (priceData?.markPrice) { + return parseFloat(priceData.markPrice); + } if (priceData?.price) { return parseFloat(priceData.price); } diff --git a/app/components/UI/Perps/constants/perpsConfig.ts b/app/components/UI/Perps/constants/perpsConfig.ts index 97f94350721..bde6a6931b5 100644 --- a/app/components/UI/Perps/constants/perpsConfig.ts +++ b/app/components/UI/Perps/constants/perpsConfig.ts @@ -558,7 +558,7 @@ export const STOP_LOSS_PROMPT_CONFIG = { // Minimum position age before showing any banner (milliseconds) // Prevents banner from appearing immediately after opening a position - PositionMinAgeMs: 60_000, // 60 seconds + PositionMinAgeMs: 120_000, // 2 minutes // Suggested stop loss ROE percentage // When suggesting a stop loss, calculate price at this ROE from entry diff --git a/app/components/UI/Perps/hooks/useStopLossPrompt.test.ts b/app/components/UI/Perps/hooks/useStopLossPrompt.test.ts index 24ff5ad4225..b8f76377aac 100644 --- a/app/components/UI/Perps/hooks/useStopLossPrompt.test.ts +++ b/app/components/UI/Perps/hooks/useStopLossPrompt.test.ts @@ -312,14 +312,21 @@ describe('useStopLossPrompt', () => { // Should NOT show immediately (position too new) expect(result.current.shouldShowBanner).toBe(false); - // Should still require full debounce period + // Should still require full debounce period AND position age + // Need to wait for max of both: RoeDebounceMs (60s) and PositionMinAgeMs (120s) + const requiredTime = + Math.max( + STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs, + STOP_LOSS_PROMPT_CONFIG.PositionMinAgeMs, + ) + 100; + act(() => { - jest.advanceTimersByTime(STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs - 100); + jest.advanceTimersByTime(requiredTime - 200); }); expect(result.current.shouldShowBanner).toBe(false); - // After full debounce, should show + // After full time passes, should show act(() => { jest.advanceTimersByTime(200); }); @@ -443,9 +450,16 @@ describe('useStopLossPrompt', () => { // Should NOT show immediately (no timestamp provided) expect(result.current.shouldShowBanner).toBe(false); - // Should require full debounce period + // Should require full debounce period AND position age + // Need to wait for max of both: RoeDebounceMs (60s) and PositionMinAgeMs (120s) + const requiredTime = + Math.max( + STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs, + STOP_LOSS_PROMPT_CONFIG.PositionMinAgeMs, + ) + 100; + act(() => { - jest.advanceTimersByTime(STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs + 100); + jest.advanceTimersByTime(requiredTime); }); expect(result.current.shouldShowBanner).toBe(true); @@ -530,11 +544,12 @@ describe('useStopLossPrompt', () => { }); describe('suggested stop loss calculations', () => { - it('calculates suggested stop loss price for long position', () => { + it('calculates suggested stop loss price as midpoint between current price and liquidation for long position', () => { const position = createMockPosition({ entryPrice: '50000', size: '1', // Long position leverage: { type: 'isolated', value: 10 }, + liquidationPrice: '45000', }); const { result } = renderHook(() => @@ -544,17 +559,17 @@ describe('useStopLossPrompt', () => { }), ); - // With -50% target ROE and 10x leverage: - // priceChange = (-0.50 * 50000) / 10 / 1 = -2500 - // slPrice = 50000 + (-2500) = 47500 - expect(result.current.suggestedStopLossPrice).toBe('47500'); + // Midpoint between current (48000) and liquidation (45000): + // midpoint = (48000 + 45000) / 2 = 46500 + expect(result.current.suggestedStopLossPrice).toBe('46500'); }); - it('calculates suggested stop loss price for short position', () => { + it('calculates suggested stop loss price as midpoint between current price and liquidation for short position', () => { const position = createMockPosition({ entryPrice: '50000', size: '-1', // Short position leverage: { type: 'isolated', value: 10 }, + liquidationPrice: '55000', }); const { result } = renderHook(() => @@ -564,17 +579,17 @@ describe('useStopLossPrompt', () => { }), ); - // With -50% target ROE and 10x leverage for short: - // priceChange = (-0.50 * 50000) / 10 / -1 = 2500 - // slPrice = 50000 + 2500 = 52500 - expect(result.current.suggestedStopLossPrice).toBe('52500'); + // Midpoint between current (52000) and liquidation (55000): + // midpoint = (52000 + 55000) / 2 = 53500 + expect(result.current.suggestedStopLossPrice).toBe('53500'); }); - it('returns target ROE as suggested stop loss percent', () => { + it('calculates ROE at the midpoint stop loss price for long position', () => { const position = createMockPosition({ entryPrice: '50000', size: '1', leverage: { type: 'isolated', value: 10 }, + liquidationPrice: '45000', }); const { result } = renderHook(() => @@ -584,10 +599,31 @@ describe('useStopLossPrompt', () => { }), ); - // Should return the configured target ROE (-50%), not the price change (-5%) - expect(result.current.suggestedStopLossPercent).toBe( - STOP_LOSS_PROMPT_CONFIG.SuggestedStopLossRoe, + // Midpoint SL price = 46500 + // ROE = (priceChange / entryPrice) * leverage * direction + // ROE = ((46500 - 50000) / 50000) * 10 * 1 * 100 = -70% + expect(result.current.suggestedStopLossPercent).toBe(-70); + }); + + it('calculates ROE at the midpoint stop loss price for short position', () => { + const position = createMockPosition({ + entryPrice: '50000', + size: '-1', + leverage: { type: 'isolated', value: 10 }, + liquidationPrice: '55000', + }); + + const { result } = renderHook(() => + useStopLossPrompt({ + position, + currentPrice: 52000, + }), ); + + // Midpoint SL price = 53500 + // ROE = (priceChange / entryPrice) * leverage * direction + // ROE = ((53500 - 50000) / 50000) * 10 * -1 * 100 = -70% + expect(result.current.suggestedStopLossPercent).toBe(-70); }); it('returns null for suggested price when no position', () => { @@ -601,6 +637,44 @@ describe('useStopLossPrompt', () => { expect(result.current.suggestedStopLossPrice).toBeNull(); expect(result.current.suggestedStopLossPercent).toBeNull(); }); + + it('returns null for suggested price when no liquidation price', () => { + const position = createMockPosition({ + entryPrice: '50000', + size: '1', + leverage: { type: 'isolated', value: 10 }, + liquidationPrice: undefined, + }); + + const { result } = renderHook(() => + useStopLossPrompt({ + position, + currentPrice: 48000, + }), + ); + + expect(result.current.suggestedStopLossPrice).toBeNull(); + expect(result.current.suggestedStopLossPercent).toBeNull(); + }); + + it('returns null for suggested price when current price is zero', () => { + const position = createMockPosition({ + entryPrice: '50000', + size: '1', + leverage: { type: 'isolated', value: 10 }, + liquidationPrice: '45000', + }); + + const { result } = renderHook(() => + useStopLossPrompt({ + position, + currentPrice: 0, + }), + ); + + expect(result.current.suggestedStopLossPrice).toBeNull(); + expect(result.current.suggestedStopLossPercent).toBeNull(); + }); }); describe('minimum loss threshold', () => { @@ -957,11 +1031,14 @@ describe('useStopLossPrompt', () => { ); expect(result.current.liquidationDistance).toBeNull(); + expect(result.current.suggestedStopLossPrice).toBeNull(); + expect(result.current.suggestedStopLossPercent).toBeNull(); }); - it('handles missing entry price', () => { + it('handles missing entry price - still calculates SL price but not percent', () => { const position = createMockPosition({ entryPrice: undefined as unknown as string, + liquidationPrice: '45000', }); const { result } = renderHook(() => @@ -971,7 +1048,11 @@ describe('useStopLossPrompt', () => { }), ); - expect(result.current.suggestedStopLossPrice).toBeNull(); + // SL price uses currentPrice and liquidationPrice (doesn't need entryPrice) + // Midpoint = (48000 + 45000) / 2 = 46500 + expect(result.current.suggestedStopLossPrice).toBe('46500'); + // But percent needs entryPrice to calculate ROE + expect(result.current.suggestedStopLossPercent).toBeNull(); }); it('prioritizes add_margin over stop_loss when both conditions met', () => { @@ -998,4 +1079,154 @@ describe('useStopLossPrompt', () => { expect(result.current.variant).toBe('add_margin'); }); }); + + describe('safety guard: suggested SL too close to current price', () => { + it('shows add_margin when suggested SL is within 3% of current price', () => { + // To test the safety guard, we need: + // 1. Liquidation distance >= 3% (to pass Priority 1) + // 2. Midpoint distance < 3% (to trigger safety guard) + // + // Current: 50000, Liquidation: 47500 → liquidationDistance = 5% + // Midpoint = (50000 + 47500) / 2 = 48750 → midpointDistance = 2.5% + const position = createMockPosition({ + entryPrice: '50000', + size: '1', + returnOnEquity: '-0.15', // Below threshold + liquidationPrice: '47500', // 5% from current (passes Priority 1) + leverage: { type: 'isolated', value: 10 }, + }); + + const { result } = renderHook(() => + useStopLossPrompt({ + position, + currentPrice: 50000, + }), + ); + + // Fast-forward past both position age and debounce requirements + const requiredTime = + Math.max( + STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs, + STOP_LOSS_PROMPT_CONFIG.PositionMinAgeMs, + ) + 100; + + act(() => { + jest.advanceTimersByTime(requiredTime); + }); + + // Midpoint = (50000 + 47500) / 2 = 48750 + // Distance from current = |50000 - 48750| / 50000 * 100 = 2.5% + // Since < 3%, safety guard triggers and shows add_margin instead of stop_loss + expect(result.current.suggestedStopLossPrice).toBe('48750'); + expect(result.current.variant).toBe('add_margin'); + }); + + it('shows stop_loss when suggested SL is more than 3% from current price', () => { + // Position where midpoint is safely away from current price + // Current: 50000, Liquidation: 40000 → Midpoint: 45000 → Distance: 10% from current + const position = createMockPosition({ + entryPrice: '50000', + size: '1', + returnOnEquity: '-0.15', // Below threshold + liquidationPrice: '40000', // Far from current + leverage: { type: 'isolated', value: 10 }, + }); + + const { result } = renderHook(() => + useStopLossPrompt({ + position, + currentPrice: 50000, + }), + ); + + // Fast-forward past both position age and debounce requirements + const requiredTime = + Math.max( + STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs, + STOP_LOSS_PROMPT_CONFIG.PositionMinAgeMs, + ) + 100; + + act(() => { + jest.advanceTimersByTime(requiredTime); + }); + + // Midpoint = (50000 + 40000) / 2 = 45000 + // Distance from current = |50000 - 45000| / 50000 * 100 = 10% + // Since > 3%, should show stop_loss + expect(result.current.suggestedStopLossPrice).toBe('45000'); + expect(result.current.variant).toBe('stop_loss'); + }); + + it('shows stop_loss when suggested SL is exactly at 3% threshold', () => { + // Position where midpoint is exactly at 3% from current + // Current: 50000, need midpoint at 48500 (3% away) + // midpoint = (current + liq) / 2, so liq = 2*midpoint - current = 2*48500 - 50000 = 47000 + const position = createMockPosition({ + entryPrice: '50000', + size: '1', + returnOnEquity: '-0.15', // Below threshold + liquidationPrice: '47000', + leverage: { type: 'isolated', value: 10 }, + }); + + const { result } = renderHook(() => + useStopLossPrompt({ + position, + currentPrice: 50000, + }), + ); + + // Fast-forward past both position age and debounce requirements + const requiredTime = + Math.max( + STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs, + STOP_LOSS_PROMPT_CONFIG.PositionMinAgeMs, + ) + 100; + + act(() => { + jest.advanceTimersByTime(requiredTime); + }); + + // Midpoint = (50000 + 47000) / 2 = 48500 + // Distance from current = |50000 - 48500| / 50000 * 100 = 3% + // Since safety guard uses < 3% (not <=), exactly 3% is NOT within threshold, so stop_loss + expect(result.current.suggestedStopLossPrice).toBe('48500'); + expect(result.current.variant).toBe('stop_loss'); + }); + + it('shows add_margin when suggestedStopLossPrice is null', () => { + // When liquidationPrice is missing, suggestedStopLossPrice will be null + // The hook should fall back to add_margin to avoid showing garbled banner text + const position = createMockPosition({ + entryPrice: '50000', + size: '1', + returnOnEquity: '-0.15', // Below threshold + liquidationPrice: undefined, // Missing - causes null suggestedStopLossPrice + leverage: { type: 'isolated', value: 10 }, + }); + + const { result } = renderHook(() => + useStopLossPrompt({ + position, + currentPrice: 50000, + }), + ); + + // Fast-forward past both position age and debounce requirements + const requiredTime = + Math.max( + STOP_LOSS_PROMPT_CONFIG.RoeDebounceMs, + STOP_LOSS_PROMPT_CONFIG.PositionMinAgeMs, + ) + 100; + + act(() => { + jest.advanceTimersByTime(requiredTime); + }); + + // Without a valid liquidation price, we can't calculate a stop loss price + expect(result.current.suggestedStopLossPrice).toBeNull(); + // Should show add_margin instead of stop_loss with null price + expect(result.current.variant).toBe('add_margin'); + }); + }); }); diff --git a/app/components/UI/Perps/hooks/useStopLossPrompt.ts b/app/components/UI/Perps/hooks/useStopLossPrompt.ts index 560c4f73719..80f8e6e276d 100644 --- a/app/components/UI/Perps/hooks/useStopLossPrompt.ts +++ b/app/components/UI/Perps/hooks/useStopLossPrompt.ts @@ -133,7 +133,7 @@ export const useStopLossPrompt = ({ // Reset hasBeenShownRef when position changes (from main) useEffect(() => { hasBeenShownRef.current = false; - }, [position?.symbol]); + }, [position?.symbol, position?.liquidationPrice, position?.entryPrice]); // Server timestamp bypass effect (from main) // If positionOpenedTimestamp shows position is >2 minutes old, bypass debounce AND position age check @@ -239,46 +239,36 @@ export const useStopLossPrompt = ({ return undefined; }, [enabled, roePercent, position, positionOpenedTimestamp, finishDebounce]); - // Calculate suggested stop loss price based on entry price and target ROE - // Formula: For a position, SL price at -50% ROE = entryPrice * (1 + targetROE/100/leverage) + // Calculate suggested stop loss price as midpoint between current price and liquidation price + // This provides a balanced protection point that limits losses while avoiding premature triggers const suggestedStopLossPrice = useMemo(() => { // Dev override: provide mock price for stop_loss variant without position if (__DEV__ && FORCE_BANNER_VARIANT === 'stop_loss' && !position) { return '45000'; // Mock price for display } - if (!position?.entryPrice) { + if (!position?.liquidationPrice || !currentPrice || currentPrice <= 0) { return null; } - const entryPrice = parseFloat(position.entryPrice); - const leverage = position.leverage?.value ?? 1; - const positionSize = parseFloat(position.size); + const liquidationPrice = parseFloat(position.liquidationPrice); - if (isNaN(entryPrice) || entryPrice <= 0 || leverage <= 0) { + if (isNaN(liquidationPrice) || liquidationPrice <= 0) { return null; } - // Target ROE is configurable (default -50%) - const targetRoeDecimal = STOP_LOSS_PROMPT_CONFIG.SuggestedStopLossRoe / 100; - - // Calculate price at target ROE - // ROE = (priceChange / entryPrice) * leverage * direction - // priceChange = ROE * entryPrice / leverage / direction - const isLong = positionSize >= 0; - const direction = isLong ? 1 : -1; - const priceChange = (targetRoeDecimal * entryPrice) / leverage / direction; - const slPrice = entryPrice + priceChange; + // Calculate midpoint between current price and liquidation price + const midpointPrice = (currentPrice + liquidationPrice) / 2; // Ensure SL price is positive and reasonable - if (slPrice <= 0) { + if (midpointPrice <= 0) { return null; } - return slPrice.toString(); - }, [position]); + return midpointPrice.toString(); + }, [position, currentPrice]); - // Return the target ROE percentage used to calculate the stop loss + // Calculate the ROE percentage at the suggested stop loss price // This represents the ROE the user will experience if the stop loss triggers const suggestedStopLossPercent = useMemo(() => { // Dev override: provide mock percentage for stop_loss variant without position @@ -286,15 +276,48 @@ export const useStopLossPrompt = ({ return STOP_LOSS_PROMPT_CONFIG.SuggestedStopLossRoe; } - // Return the configured target ROE if we have a valid stop loss price - if (!suggestedStopLossPrice) { + if (!suggestedStopLossPrice || !position?.entryPrice) { + return null; + } + + const entryPrice = parseFloat(position.entryPrice); + const slPrice = parseFloat(suggestedStopLossPrice); + const leverage = position.leverage?.value ?? 1; + const positionSize = parseFloat(position.size); + + if ( + isNaN(entryPrice) || + entryPrice <= 0 || + isNaN(slPrice) || + leverage <= 0 + ) { return null; } - // The stop loss price was calculated to achieve this specific ROE - return STOP_LOSS_PROMPT_CONFIG.SuggestedStopLossRoe; + // Calculate ROE at the suggested stop loss price + // ROE = (priceChange / entryPrice) * leverage * direction + const isLong = positionSize >= 0; + const direction = isLong ? 1 : -1; + const priceChange = slPrice - entryPrice; + const roe = (priceChange / entryPrice) * leverage * direction * 100; + + return roe; }, [suggestedStopLossPrice, position]); + // Safety guard: Check if suggested SL price is too close to current price + // If within 3% of mark price, we should show "add_margin" instead to avoid accidental immediate fills + const isSuggestedSlTooClose = useMemo(() => { + if (!suggestedStopLossPrice || !currentPrice || currentPrice <= 0) { + return false; + } + const slPrice = parseFloat(suggestedStopLossPrice); + if (isNaN(slPrice) || slPrice <= 0) { + return false; + } + const distance = (Math.abs(currentPrice - slPrice) / currentPrice) * 100; + return distance < STOP_LOSS_PROMPT_CONFIG.LiquidationDistanceThreshold; // Within 3% of current price + }, [suggestedStopLossPrice, currentPrice]); + // Determine if banner should show and which variant const { shouldShowBanner, variant } = useMemo((): { shouldShowBanner: boolean; @@ -358,7 +381,17 @@ export const useStopLossPrompt = ({ } // Priority 2: ROE below threshold with debounce → Stop loss variant + // But if suggested SL is too close to current price (within 3%), show add_margin instead if (roeDebounceComplete) { + // Guard: Don't show stop_loss variant if we can't calculate a valid suggested price + // This prevents displaying garbled banner text like "Set a stop loss at ( ROE)" + if (!suggestedStopLossPrice) { + return { shouldShowBanner: true, variant: 'add_margin' }; + } + if (isSuggestedSlTooClose) { + // Safety guard: SL price too close to current price, suggest adding margin instead + return { shouldShowBanner: true, variant: 'add_margin' }; + } return { shouldShowBanner: true, variant: 'stop_loss' }; } @@ -368,6 +401,8 @@ export const useStopLossPrompt = ({ position, liquidationDistance, roeDebounceComplete, + isSuggestedSlTooClose, + suggestedStopLossPrice, positionAgeCheckPassed, roePercent, ]); From 3456a1c413188299a4bb365be749062c1d945ed9 Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:39:20 -0800 Subject: [PATCH 5/9] fix: Multiple cleanups with historical perps orders (#24278) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR addresses multiple issues with historical Perps order display, focusing on accurate fill percentage calculation and proper price formatting for low-priced assets. **Problem 1: Inaccurate fill percentages for historical orders** HyperLiquid's historical orders API returns sz=0 for all completed (filled/canceled) orders, making it impossible to calculate accurate partial fill percentages. This caused canceled orders that were partially filled to incorrectly display 0% or 100% filled. **Solution 1: Reverse mapping from fills** Build a fillSizeByOrderId Map in usePerpsTransactionHistory that aggregates total filled size from actual fill data Pass this map to transformOrdersToTransactions to calculate accurate filled percentages For orders with fill data, compute percentage as (actualFilledSize / originalSize) * 100 Fallback to status-based logic when fill data unavailable **Problem 2: Loss of precision for low-priced assets** Assets like meme coins with prices below $0.01 were displaying "< $0.01" for limit prices in order transaction details, losing important precision for users. **Solution 2: Universal price formatting** Changed PerpsOrderTransactionView to use formatPerpsFiat with PRICE_RANGES_UNIVERSAL instead of formatPositiveFiat Now displays exact prices like "$0.00234" instead of "< $0.01" ## **Changelog** CHANGELOG entry: Fixed inaccurate fill percentages for historical Perps orders and improved price precision for low-priced assets ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2056 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- > [!NOTE] > **Medium Risk** > Touches perps transaction history aggregation and order/fee/price formatting, which can impact user-visible financial values and percentages. Logic is covered by expanded unit tests but may still have edge-case risk across order statuses and fill data quality. > > **Overview** > **Improves Perps order history accuracy and precision.** Historical orders now compute *filled %* using aggregated fill sizes (passed through `usePerpsTransactionHistory` into `transformOrdersToTransactions`) to avoid HyperLiquid’s `sz=0` limitation on completed orders, with clear fallbacks for filled/canceled/rejected/open and position-bound TP/SL edge cases. > > **Shows exact sub-cent values in order details.** `PerpsOrderTransactionView` switches limit price and fee formatting to `formatPerpsFiat` with `PRICE_RANGES_UNIVERSAL`, displaying precise values (e.g., `$0.00234`, `$0.005`) rather than `"< $0.01"`, and updates/expands tests accordingly. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit fd5795370a87474b989f96af748cf569bb18340e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../PerpsOrderTransactionView.test.tsx | 140 ++++++----- .../PerpsOrderTransactionView.tsx | 16 +- .../hooks/usePerpsTransactionHistory.test.ts | 7 +- .../Perps/hooks/usePerpsTransactionHistory.ts | 17 +- .../Perps/utils/transactionTransforms.test.ts | 226 +++++++++++++++++- .../UI/Perps/utils/transactionTransforms.ts | 48 +++- 6 files changed, 383 insertions(+), 71 deletions(-) diff --git a/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.test.tsx b/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.test.tsx index 2daf03338cd..290684d2321 100644 --- a/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.test.tsx +++ b/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.test.tsx @@ -113,7 +113,7 @@ describe('PerpsOrderTransactionView', () => { }); }); - it('should render order transaction details correctly', () => { + it('renders order transaction details correctly', () => { const { getByText, getByTestId } = render(); expect( @@ -131,18 +131,18 @@ describe('PerpsOrderTransactionView', () => { expect(getByText('100%')).toBeTruthy(); }); - it('should render fee breakdown correctly', () => { + it('renders fee breakdown correctly', () => { const { getByText } = render(); expect(getByText('MetaMask fee')).toBeTruthy(); expect(getByText('Hyperliquid fee')).toBeTruthy(); expect(getByText('Total fee')).toBeTruthy(); expect(getByText('$3')).toBeTruthy(); - expect(getByText('$7.50')).toBeTruthy(); - expect(getByText('$10.50')).toBeTruthy(); + expect(getByText('$7.5')).toBeTruthy(); // Trailing zero stripped + expect(getByText('$10.5')).toBeTruthy(); // Trailing zero stripped }); - it('should show zero fees when order is not filled', () => { + it('shows zero fees when order is not filled', () => { const unfilledTransaction = { ...mockTransaction, order: { @@ -162,7 +162,7 @@ describe('PerpsOrderTransactionView', () => { expect(zeroFees).toHaveLength(3); // All three fees should be $0 }); - it('should show "< $0.01" for fees less than 0.01', () => { + it('shows exact fee values for fees less than 0.01', () => { mockUsePerpsOrderFees.mockReturnValue({ totalFee: 0.005, protocolFee: 0.003, @@ -173,14 +173,16 @@ describe('PerpsOrderTransactionView', () => { error: null, }); - const { getAllByText } = render(); + const { getByText, queryByText } = render(); - // All three fees should show "< $0.01" since they're all less than 0.01 - const smallFeeLabels = getAllByText('< $0.01'); - expect(smallFeeLabels).toHaveLength(3); + // All three fees should show exact values, not "< $0.01" + expect(queryByText('< $0.01')).toBeNull(); + expect(getByText('$0.005')).toBeTruthy(); // Total fee + expect(getByText('$0.003')).toBeTruthy(); // Protocol fee + expect(getByText('$0.002')).toBeTruthy(); // MetaMask fee }); - it('should format fees normally when they are exactly 0.01', () => { + it('formats fees normally when they are exactly 0.01', () => { mockUsePerpsOrderFees.mockReturnValue({ totalFee: 0.03, protocolFee: 0.01, @@ -203,7 +205,7 @@ describe('PerpsOrderTransactionView', () => { expect(getByText('$0.03')).toBeTruthy(); // Total fee }); - it('should format fees normally when they are greater than 0.01', () => { + it('formats fees with exact values regardless of size', () => { mockUsePerpsOrderFees.mockReturnValue({ totalFee: 0.015, protocolFee: 0.012, @@ -214,16 +216,16 @@ describe('PerpsOrderTransactionView', () => { error: null, }); - const { getByText, getAllByText } = render(); + const { getByText, queryByText } = render(); - // Metamask fee is less than 0.01, should show "< $0.01" - expect(getAllByText('< $0.01')).toHaveLength(1); - // Protocol and total fees are >= 0.01, should be formatted normally - expect(getByText('$0.01')).toBeTruthy(); // Protocol fee formatted - expect(getByText('$0.02')).toBeTruthy(); // Total fee formatted (rounded) + // All fees should show exact values, not "< $0.01" + expect(queryByText('< $0.01')).toBeNull(); + expect(getByText('$0.003')).toBeTruthy(); // MetaMask fee (exact) + expect(getByText('$0.012')).toBeTruthy(); // Protocol fee (exact) + expect(getByText('$0.015')).toBeTruthy(); // Total fee (exact) }); - it('should handle mixed small and large fees correctly', () => { + it('handles mixed small and large fees correctly with exact values', () => { mockUsePerpsOrderFees.mockReturnValue({ totalFee: 0.025, protocolFee: 0.02, @@ -234,17 +236,16 @@ describe('PerpsOrderTransactionView', () => { error: null, }); - const { getByText, getAllByText } = render(); + const { getByText, queryByText } = render(); - // Metamask fee is less than 0.01 - const smallFeeLabels = getAllByText('< $0.01'); - expect(smallFeeLabels).toHaveLength(1); - // Protocol and total fees are >= 0.01, should be formatted + // All fees should show exact values + expect(queryByText('< $0.01')).toBeNull(); + expect(getByText('$0.005')).toBeTruthy(); // MetaMask fee (exact) expect(getByText('$0.02')).toBeTruthy(); // Protocol fee - expect(getByText('$0.03')).toBeTruthy(); // Total fee (rounded) + expect(getByText('$0.025')).toBeTruthy(); // Total fee (exact) }); - it('should handle edge case: fee just below 0.01 threshold', () => { + it('handles edge case: fee just below 0.01 threshold with exact values', () => { mockUsePerpsOrderFees.mockReturnValue({ totalFee: 0.029, protocolFee: 0.0099, @@ -255,15 +256,20 @@ describe('PerpsOrderTransactionView', () => { error: null, }); - const { getAllByText } = render(); + const { getByText, getAllByText, queryByText } = render( + , + ); - // Both metamask and protocol fees are just below 0.01 - const smallFeeLabels = getAllByText('< $0.01'); - expect(smallFeeLabels).toHaveLength(2); - // Total fee is >= 0.01, should be formatted + // All fees should show exact values, not "< $0.01" + expect(queryByText('< $0.01')).toBeNull(); + // Both metamask and protocol fees show exact value + const fee0099Labels = getAllByText('$0.0099'); + expect(fee0099Labels).toHaveLength(2); + // Total fee shows exact value + expect(getByText('$0.029')).toBeTruthy(); }); - it('should handle edge case: fee just above 0.01 threshold', () => { + it('handles edge case: fee just above 0.01 threshold with exact values', () => { mockUsePerpsOrderFees.mockReturnValue({ totalFee: 0.0201, protocolFee: 0.0101, @@ -274,19 +280,16 @@ describe('PerpsOrderTransactionView', () => { error: null, }); - const { queryByText, getAllByText, getByText } = render( - , - ); + const { queryByText, getByText } = render(); - // All fees are >= 0.01, should be formatted normally + // All fees should show exact values, not "< $0.01" expect(queryByText('< $0.01')).toBeNull(); - // Metamask fee and protocol fee (rounded) both show $0.01 - const fee01Labels = getAllByText('$0.01'); - expect(fee01Labels.length).toBeGreaterThanOrEqual(2); - expect(getByText('$0.02')).toBeTruthy(); // Total fee (rounded) + expect(getByText('$0.01')).toBeTruthy(); // MetaMask fee (exact) + expect(getByText('$0.0101')).toBeTruthy(); // Protocol fee (exact) + expect(getByText('$0.0201')).toBeTruthy(); // Total fee (exact) }); - it('should show "< $0.01" for all fees when all are below threshold', () => { + it('shows exact values for all fees when all are below 0.01', () => { mockUsePerpsOrderFees.mockReturnValue({ totalFee: 0.008, protocolFee: 0.005, @@ -297,14 +300,16 @@ describe('PerpsOrderTransactionView', () => { error: null, }); - const { getAllByText } = render(); + const { getByText, queryByText } = render(); - // All three fees are below 0.01 - const smallFeeLabels = getAllByText('< $0.01'); - expect(smallFeeLabels).toHaveLength(3); + // All three fees should show exact values, not "< $0.01" + expect(queryByText('< $0.01')).toBeNull(); + expect(getByText('$0.008')).toBeTruthy(); // Total fee + expect(getByText('$0.005')).toBeTruthy(); // Protocol fee + expect(getByText('$0.003')).toBeTruthy(); // MetaMask fee }); - it('should navigate to block explorer in browser tab when button is pressed', () => { + it('navigates to block explorer in browser tab when button is pressed', () => { const mockNavigate = jest.fn(); mockUseNavigation.mockReturnValue({ navigate: mockNavigate, @@ -326,7 +331,7 @@ describe('PerpsOrderTransactionView', () => { }); }); - it('should use testnet URL when network is testnet', () => { + it('uses testnet URL when network is testnet', () => { const mockNavigate = jest.fn(); mockUseNavigation.mockReturnValue({ navigate: mockNavigate, @@ -350,7 +355,7 @@ describe('PerpsOrderTransactionView', () => { }); }); - it('should not navigate to block explorer when no selected account', () => { + it('does not navigate to block explorer when no selected account', () => { const mockNavigate = jest.fn(); mockUseNavigation.mockReturnValue({ navigate: mockNavigate, @@ -373,7 +378,7 @@ describe('PerpsOrderTransactionView', () => { ); }); - it('should render error message when transaction is not found', () => { + it('renders error message when transaction is not found', () => { mockUseRoute.mockReturnValue({ params: { transaction: null }, }); @@ -383,14 +388,14 @@ describe('PerpsOrderTransactionView', () => { expect(getByText('Transaction not found')).toBeTruthy(); }); - it('should format date correctly', () => { + it('formats date correctly', () => { const { getByText } = render(); expect(getByText('Date')).toBeTruthy(); // The actual date format would depend on the formatTransactionDate utility }); - it('should handle different order types', () => { + it('handles different order types', () => { const marketOrderTransaction = { ...mockTransaction, order: { @@ -412,7 +417,7 @@ describe('PerpsOrderTransactionView', () => { }); }); - it('should handle missing order data gracefully', () => { + it('handles missing order data gracefully', () => { const transactionWithoutOrder = { ...mockTransaction, order: undefined, @@ -430,7 +435,34 @@ describe('PerpsOrderTransactionView', () => { }); }); - it('should call usePerpsOrderFees with correct parameters', () => { + it('displays exact price for low-priced assets like PUMP instead of "< $0.01"', () => { + // Arrange: Create a transaction with a very low limit price (typical for meme coins) + const lowPriceTransaction = { + ...mockTransaction, + asset: 'PUMP', + title: 'Long PUMP limit', + order: { + ...mockTransaction.order, + limitPrice: 0.00234, // Price below $0.01 + }, + }; + + mockUseRoute.mockReturnValue({ + params: { transaction: lowPriceTransaction }, + }); + + // Act + const { getByText, queryByText } = render(); + + // Assert: Should show the actual formatted price, not "< $0.01" + expect(getByText('Limit price')).toBeTruthy(); + // The price should be formatted with PRICE_RANGES_UNIVERSAL showing actual value + expect(queryByText('< $0.01')).toBeNull(); + // Should display the actual price with appropriate decimals (e.g., "$0.00234") + expect(getByText('$0.00234')).toBeTruthy(); + }); + + it('calls usePerpsOrderFees with correct parameters', () => { render(); expect(mockUsePerpsOrderFees).toHaveBeenCalledWith({ @@ -439,7 +471,7 @@ describe('PerpsOrderTransactionView', () => { }); }); - it('should set correct navigation options', () => { + it('sets correct navigation options', () => { const mockSetOptions = jest.fn(); mockUseNavigation.mockReturnValue({ setOptions: mockSetOptions, diff --git a/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.tsx b/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.tsx index b33d1938f55..83a7f513f49 100644 --- a/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.tsx +++ b/app/components/UI/Perps/Views/PerpsTransactionsView/PerpsOrderTransactionView.tsx @@ -28,8 +28,8 @@ import { PerpsNavigationParamList } from '../../types/navigation'; import { PerpsOrderTransactionRouteProp } from '../../types/transactionHistory'; import { formatPerpsFiat, - formatPositiveFiat, formatTransactionDate, + PRICE_RANGES_UNIVERSAL, } from '../../utils/formatUtils'; import { styleSheet } from './PerpsOrderTransactionView.styles'; @@ -96,7 +96,9 @@ const PerpsOrderTransactionView: React.FC = () => { }, { label: strings('perps.transactions.order.limit_price'), - value: formatPositiveFiat(transaction.order?.limitPrice ?? 0), + value: formatPerpsFiat(transaction.order?.limitPrice ?? 0, { + ranges: PRICE_RANGES_UNIVERSAL, + }), }, { label: strings('perps.transactions.order.filled'), @@ -106,20 +108,22 @@ const PerpsOrderTransactionView: React.FC = () => { const isFilled = transaction.order?.text === 'Filled'; - // Fee breakdown + // Fee breakdown - use PRICE_RANGES_UNIVERSAL to show exact values instead of "< $0.01" + const formatFee = (fee: number) => + formatPerpsFiat(fee, { ranges: PRICE_RANGES_UNIVERSAL }); const feeRows = [ { label: strings('perps.transactions.order.metamask_fee'), - value: formatPositiveFiat(isFilled ? metamaskFee : 0), + value: formatFee(isFilled ? metamaskFee : 0), }, { label: strings('perps.transactions.order.hyperliquid_fee'), - value: formatPositiveFiat(isFilled ? protocolFee : 0), + value: formatFee(isFilled ? protocolFee : 0), }, { label: strings('perps.transactions.order.total_fee'), - value: formatPositiveFiat(isFilled ? totalFee : 0), + value: formatFee(isFilled ? totalFee : 0), }, ]; diff --git a/app/components/UI/Perps/hooks/usePerpsTransactionHistory.test.ts b/app/components/UI/Perps/hooks/usePerpsTransactionHistory.test.ts index 23f96b04d10..de025ad912a 100644 --- a/app/components/UI/Perps/hooks/usePerpsTransactionHistory.test.ts +++ b/app/components/UI/Perps/hooks/usePerpsTransactionHistory.test.ts @@ -248,9 +248,14 @@ describe('usePerpsTransactionHistory', () => { endTime: undefined, }); - expect(mockTransformFillsToTransactions).toHaveBeenCalledWith(mockFills); + // Fills are enriched with detailedOrderType from matching orders + expect(mockTransformFillsToTransactions).toHaveBeenCalledWith([ + { ...mockFills[0], detailedOrderType: 'Market' }, + ]); + // Orders are passed with a fillSizeByOrderId Map for accurate filled percentage calculation expect(mockTransformOrdersToTransactions).toHaveBeenCalledWith( mockOrders, + expect.any(Map), ); expect(mockTransformFundingToTransactions).toHaveBeenCalledWith( mockFunding, diff --git a/app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts b/app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts index c399cda2661..01189c69591 100644 --- a/app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts +++ b/app/components/UI/Perps/hooks/usePerpsTransactionHistory.ts @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { BigNumber } from 'bignumber.js'; import Engine from '../../../../core/Engine'; import DevLogger from '../../../../core/SDKConnect/utils/DevLogger'; import type { CaipAccountId } from '@metamask/utils'; @@ -102,9 +103,23 @@ export const usePerpsTransactionHistory = ({ detailedOrderType: orderMap.get(fill.orderId)?.detailedOrderType, })); + // Build fill size map: orderId -> total filled size + // This allows accurate filled percentage calculation for historical orders, + // since HyperLiquid's historical orders API returns sz=0 for all completed orders + const fillSizeByOrderId = new Map(); + for (const fill of fills) { + if (fill.orderId) { + const current = fillSizeByOrderId.get(fill.orderId) || BigNumber(0); + fillSizeByOrderId.set(fill.orderId, current.plus(fill.size || '0')); + } + } + // Transform each data type to PerpsTransaction format const fillTransactions = transformFillsToTransactions(enrichedFills); - const orderTransactions = transformOrdersToTransactions(orders); + const orderTransactions = transformOrdersToTransactions( + orders, + fillSizeByOrderId, + ); const fundingTransactions = transformFundingToTransactions(funding); const userHistoryTransactions = transformUserHistoryToTransactions( userHistoryRef.current, diff --git a/app/components/UI/Perps/utils/transactionTransforms.test.ts b/app/components/UI/Perps/utils/transactionTransforms.test.ts index 5a9c1bdaa79..4f454a4e705 100644 --- a/app/components/UI/Perps/utils/transactionTransforms.test.ts +++ b/app/components/UI/Perps/utils/transactionTransforms.test.ts @@ -1,3 +1,4 @@ +import { BigNumber } from 'bignumber.js'; import { transformFillsToTransactions, transformOrdersToTransactions, @@ -942,7 +943,7 @@ describe('transactionTransforms', () => { timestamp: 1640995200000, }; - it('transforms filled order correctly', () => { + it('transforms filled order correctly (defaults to 100% without fill data)', () => { const result = transformOrdersToTransactions([mockOrder]); expect(result).toHaveLength(1); @@ -960,7 +961,7 @@ describe('transactionTransforms', () => { type: 'limit', size: '50000', limitPrice: '50000', - filled: '50%', + filled: '100%', // Filled status without fill data defaults to 100% }, }); }); @@ -1115,14 +1116,15 @@ describe('transactionTransforms', () => { expect(result[0].order.type).toBe('market'); }); - it('calculates filled percentage correctly', () => { - const partiallyFilledOrder = { + it('calculates filled percentage from size fields for open orders', () => { + const partiallyFilledOpenOrder = { ...mockOrder, + status: 'open' as const, // Open orders use size-based calculation size: '0.2', originalSize: '1', }; - const result = transformOrdersToTransactions([partiallyFilledOrder]); + const result = transformOrdersToTransactions([partiallyFilledOpenOrder]); if (!result[0]?.order) { return; @@ -1182,6 +1184,220 @@ describe('transactionTransforms', () => { expect(result[0].subtitle).toBe('0.5 BTC'); }); + + describe('fill-based percentage calculation', () => { + it('calculates accurate percentage from fill data map', () => { + // Arrange: Order with originalSize 1, actual fills totaling 0.3 (30%) + const order = { + ...mockOrder, + orderId: 'order-with-fills', + originalSize: '1', + size: '0', // HyperLiquid returns 0 for historical orders + status: 'canceled' as const, + }; + + const fillSizeByOrderId = new Map(); + fillSizeByOrderId.set('order-with-fills', BigNumber('0.3')); + + // Act + const result = transformOrdersToTransactions( + [order], + fillSizeByOrderId, + ); + + // Assert: Should show 30% based on actual fills, not 0% or 100% + expect(result[0].order?.filled).toBe('30%'); + }); + + it('shows 0% for canceled order without any fills', () => { + // Arrange: Canceled order with no fills in the map + const canceledOrder = { + ...mockOrder, + orderId: 'canceled-no-fills', + originalSize: '1', + size: '0', + status: 'canceled' as const, + }; + + // No entry in fill map for this order + const fillSizeByOrderId = new Map(); + + // Act + const result = transformOrdersToTransactions( + [canceledOrder], + fillSizeByOrderId, + ); + + // Assert: Should show 0% since canceled without fills + expect(result[0].order?.filled).toBe('0%'); + }); + + it('shows 100% for fully filled order from fill data', () => { + // Arrange: Order with originalSize 2, fills totaling 2 (100%) + const filledOrder = { + ...mockOrder, + orderId: 'fully-filled', + originalSize: '2', + size: '0', + status: 'filled' as const, + }; + + const fillSizeByOrderId = new Map(); + fillSizeByOrderId.set('fully-filled', BigNumber('2')); + + // Act + const result = transformOrdersToTransactions( + [filledOrder], + fillSizeByOrderId, + ); + + // Assert: Should show 100% + expect(result[0].order?.filled).toBe('100%'); + }); + + it('handles partial fill then cancel scenario correctly', () => { + // Arrange: Order for 10 units, 7 filled then canceled (70%) + const partialThenCanceled = { + ...mockOrder, + orderId: 'partial-canceled', + originalSize: '10', + size: '0', // HyperLiquid sets to 0 for all historical orders + status: 'canceled' as const, + }; + + const fillSizeByOrderId = new Map(); + fillSizeByOrderId.set('partial-canceled', BigNumber('7')); + + // Act + const result = transformOrdersToTransactions( + [partialThenCanceled], + fillSizeByOrderId, + ); + + // Assert: Should show 70% based on actual fills + expect(result[0].order?.filled).toBe('70%'); + }); + + it('rounds percentage to whole number', () => { + // Arrange: Order that would result in 33.333...% + const order = { + ...mockOrder, + orderId: 'fractional', + originalSize: '3', + size: '0', + status: 'canceled' as const, + }; + + const fillSizeByOrderId = new Map(); + fillSizeByOrderId.set('fractional', BigNumber('1')); + + // Act + const result = transformOrdersToTransactions( + [order], + fillSizeByOrderId, + ); + + // Assert: Should round to 33% + expect(result[0].order?.filled).toBe('33%'); + }); + + it('handles multiple fills for same order', () => { + // Arrange: The fill map should contain the SUM of all fills + // (this is done in usePerpsTransactionHistory, we just test that the function uses the sum) + const order = { + ...mockOrder, + orderId: 'multi-fill', + originalSize: '1', + size: '0', + status: 'filled' as const, + }; + + // Simulating sum of fills: 0.3 + 0.4 + 0.3 = 1.0 + const fillSizeByOrderId = new Map(); + fillSizeByOrderId.set('multi-fill', BigNumber('1')); + + // Act + const result = transformOrdersToTransactions( + [order], + fillSizeByOrderId, + ); + + // Assert: Should show 100% + expect(result[0].order?.filled).toBe('100%'); + }); + + it('falls back to status-based logic when fill map not provided', () => { + // Arrange: Filled order without fill map + const filledOrder = { + ...mockOrder, + status: 'filled' as const, + }; + + // Act: No fill map provided + const result = transformOrdersToTransactions([filledOrder]); + + // Assert: Should default to 100% for filled status + expect(result[0].order?.filled).toBe('100%'); + }); + + it('falls back to 0% for rejected order without fill data', () => { + // Arrange + const rejectedOrder = { + ...mockOrder, + status: 'rejected' as const, + }; + + const fillSizeByOrderId = new Map(); + + // Act + const result = transformOrdersToTransactions( + [rejectedOrder], + fillSizeByOrderId, + ); + + // Assert + expect(result[0].order?.filled).toBe('0%'); + }); + + it('returns 0% for position-bound TP/SL orders with zero size (not filled yet)', () => { + // Arrange: Position-bound TP/SL orders have size=0 and originalSize=0 + // because they're tied to the position size, not a fixed amount + const positionTpslOrder = { + ...mockOrder, + orderId: 'position-tpsl-order', + size: '0', // No fixed size - tied to position + originalSize: '0', // No fixed original size + status: 'open' as const, + isTrigger: true, + reduceOnly: true, + detailedOrderType: 'Take Profit Limit', + }; + + // Act: No fill map - test the fallback logic + const result = transformOrdersToTransactions([positionTpslOrder]); + + // Assert: Should show 0% (not triggered yet), NOT 100% + expect(result[0].order?.filled).toBe('0%'); + }); + + it('returns 100% for regular order with zero remaining size (fully filled)', () => { + // Arrange: Regular order that was fully filled + // originalSize > 0 but size = 0 means it was filled + const fullyFilledOrder = { + ...mockOrder, + orderId: 'fully-filled-regular', + size: '0', // All filled + originalSize: '1', // Started with 1 + status: 'open' as const, // Testing the size-based fallback + }; + + // Act: No fill map - test the fallback logic + const result = transformOrdersToTransactions([fullyFilledOrder]); + + // Assert: Should show 100% (nothing remaining) + expect(result[0].order?.filled).toBe('100%'); + }); + }); }); describe('transformFundingToTransactions', () => { diff --git a/app/components/UI/Perps/utils/transactionTransforms.ts b/app/components/UI/Perps/utils/transactionTransforms.ts index 13bcaaec36b..9dc7e82208a 100644 --- a/app/components/UI/Perps/utils/transactionTransforms.ts +++ b/app/components/UI/Perps/utils/transactionTransforms.ts @@ -376,10 +376,15 @@ export function transformFillsToTransactions( /** * Transform abstract Order objects to PerpsTransaction format * @param orders - Array of abstract Order objects + * @param fillSizeByOrderId - Optional map of orderId to total filled size (from actual fills). + * When provided, uses actual fill data to calculate accurate filled percentages. + * This is important because HyperLiquid's historical orders API returns sz=0 for all + * completed orders, making it impossible to calculate partial fill percentages without fill data. * @returns Array of PerpsTransaction objects */ export function transformOrdersToTransactions( orders: Order[], + fillSizeByOrderId?: Map, ): PerpsTransaction[] { return orders.map((order) => { const { @@ -428,15 +433,50 @@ export function transformOrdersToTransactions( : PerpsOrderTransactionStatus.Queued; } - // Calculate filled percentage from abstract types - const filledPercent = BigNumber(size).isEqualTo(0) - ? '100' - : BigNumber(originalSize) + // Calculate filled percentage - prefer actual fill data when available + let filledPercent: string; + const actualFilledSize = fillSizeByOrderId?.get(orderId); + + if (actualFilledSize !== undefined) { + // Use actual fill data for accurate percentage + const origSize = BigNumber(originalSize); + + if (origSize.isZero()) { + filledPercent = '0'; + } else { + filledPercent = actualFilledSize + .dividedBy(origSize) + .multipliedBy(100) + .toFixed(0); // Round to whole number + } + } else if (isCompleted || isTriggered) { + // Filled/triggered orders are 100% filled + filledPercent = '100'; + } else if (isCancelled || isRejected) { + // Canceled/rejected orders without fills = 0% filled + filledPercent = '0'; + } else { + // Open/pending orders - use the order's size fields + const sizeIsZero = BigNumber(size).isEqualTo(0); + const originalSizeIsZero = BigNumber(originalSize).isZero(); + + if (sizeIsZero && originalSizeIsZero) { + // Position-bound TP/SL orders have no fixed size (both are 0) + // They're not filled yet - they're just tied to the position size + filledPercent = '0'; + } else if (sizeIsZero) { + // Regular order with 0 remaining size = fully filled + filledPercent = '100'; + } else { + // Partially filled order + filledPercent = BigNumber(originalSize) .minus(size) .dividedBy(originalSize) .absoluteValue() .multipliedBy(100) .toString(); + } + } return { id: `${orderId}-${timestamp}`, From 720d4b74ea81a64035c942c22dc4990661c7c26c Mon Sep 17 00:00:00 2001 From: CW Date: Wed, 4 Feb 2026 10:54:12 -0800 Subject: [PATCH 6/9] chore: remove unused TestRails report results stage and related script (#25657) ## **Description** This commit removes the `report_results_stage` from the Bitrise configuration and deletes the associated `testrail.api.js` script, as they are no longer utilized in the CI/CD pipeline. This cleanup helps streamline the build process and reduces unnecessary code. ## **Changelog** CHANGELOG entry: ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMQA-1358 ## **Manual testing steps** ```gherkin Feature: Remove TestRails Integration Scenario: Release pipeline operates without TestRails Given the TestRails integration code has been removed When the release_e2e_pipeline runs Then it should execute build, test, and notify stages And it should not attempt to report results to TestRails And it should complete without errors ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- > [!NOTE] > **Low Risk** > Low risk cleanup limited to CI configuration; it only removes an unused TestRail-reporting stage/workflow and its Node script, with no impact to app runtime code. > > **Overview** > Removes the unused TestRail results reporting from Bitrise by deleting `report_results_stage` (and its `run_testrail_update_automated_test_results` workflow) and dropping it from `release_e2e_pipeline`. > > Deletes the associated `scripts/testrail/testrail.api.js` script that called the TestRail API to create a run and mark automated tests as passed. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 448975f855708f108ead810b9d8b863138d25169. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- bitrise.yml | 16 ------------ scripts/testrail/testrail.api.js | 45 -------------------------------- 2 files changed, 61 deletions(-) delete mode 100644 scripts/testrail/testrail.api.js diff --git a/bitrise.yml b/bitrise.yml index 83f6afa4c84..50c212289e4 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -87,7 +87,6 @@ pipelines: stages: - build_e2e_ios_android_stage: {} - run_release_e2e_ios_android_stage: {} - - report_results_stage: {} - notify: {} #PR_e2e_verfication (build ios & android), run iOS (smoke), emulator Android pr_smoke_e2e_pipeline: @@ -464,9 +463,6 @@ stages: workflows: - ios_run_regression_network_abstraction_tests_gns_disabled: {} - report_results_stage: - workflows: - - run_testrail_update_automated_test_results: {} # TODO: Remove this stage since it's not used anymore run_e2e_ios_android_stage: workflows: @@ -1080,18 +1076,6 @@ workflows: machine_type_id: elite-xl after_run: - android_e2e_build - ### Report automated test results to TestRail - run_testrail_update_automated_test_results: - before_run: - - code_setup - steps: - - script@1: - title: 'Add Automated Test Results to TestRail' - inputs: - - content: |- - #!/usr/bin/env bash - echo 'REPORT AUTOMATED TEST RESULTS TO TESTRAIL' - node ./scripts/testrail/testrail.api.js ### Separating workflows so they run concurrently during smoke runs run_tag_smoke_multichain_api_ios: diff --git a/scripts/testrail/testrail.api.js b/scripts/testrail/testrail.api.js deleted file mode 100644 index 8f5007295f9..00000000000 --- a/scripts/testrail/testrail.api.js +++ /dev/null @@ -1,45 +0,0 @@ -const axios = require('axios'); -require('dotenv').config({ path: './.js.env' }); - -const TESTRAIL_MM_API_URL = 'https://mmig.testrail.io/index.php?/api/v2'; -const TESTRAIL_PROJECT_ID = 4; -const AUTH_TOKEN = process.env.TESTRAIL_AUTH_TOKEN; -const automatedTestCasesEndpoint = `${TESTRAIL_MM_API_URL}/get_cases/${TESTRAIL_PROJECT_ID}&refs=@automated`; -const addTestRun = `${TESTRAIL_MM_API_URL}/add_run/${TESTRAIL_PROJECT_ID}`; -const getAutomatedTestRun = `${TESTRAIL_MM_API_URL}/get_tests/`; -const addResults = `${TESTRAIL_MM_API_URL}/add_results/`; - -axios.defaults.headers.common.Authorization = `Basic ${AUTH_TOKEN}`; -let runID; - -axios - .get(automatedTestCasesEndpoint) - .then((response) => { - const automatedcaseids = response.data.cases.map( - (automatedcase) => automatedcase.id, - ); - console.log(`test case id count: ${automatedcaseids.length}`); - return axios.post(addTestRun, { - name: 'Automated Test Run on bitrise release_e2e_pipline', - description: 'Automated test run on release branch', - include_all: false, - case_ids: automatedcaseids, - }); - }) - .then((response) => { - runID = response.data.id; - return axios.get(`${getAutomatedTestRun}${runID}`); - }) - .then((response) => { - const automatedResults = response.data.tests.map((test) => ({ - test_id: test.id, - status_id: 1, - comment: 'Passed on bitrise', - })); - return axios.post(`${addResults}${runID}`, { - results: automatedResults, - }); - }) - .catch((error) => { - console.error(`Error retrieving automated test cases: ${error}`); - }); From 51b59a73137289ef083c4e62024900068298c6b2 Mon Sep 17 00:00:00 2001 From: Matthew Grainger <46547583+Matt561@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:22:57 -0500 Subject: [PATCH 7/9] fix: MUSD-275 pay with row infinite loading state after changing musd conversion payment token (#25604) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR fixes an issue where the "Pay with" menu would become unresponsive after selecting a token during the mUSD conversion flow. Cause of bug: 1. Clicking an mUSD CTA multiple times in quick succession was creating more than one approval request. 2. When the user changes their payment token to one on another network, the mUSD same-chain enforcement kicks in to reject the previous and create a new transaction targeting the same chain as the new payment token. 3. The `pay-with-row` depends on the first approval request in the pending approvals array. When we'd reject the original tx, the duplicate (without payment token set) would be the next to be used. Step breakdown: 1. Default state (before doing anything) - pending approvals = [ ] // empty 3. Spam click the conversion CTA (e.g. Mainnet DAI) and created multiple transactions (e.g. 2) instead of the expected 1. - pending approvals = [ tx-1-mainnet-dai, tx-2-no-payment-token-set ] 4. Switch your payment token to one on another network (e.g. Linea USDC). Here we reject your old tx (tx-1-mainnet-dai) and create a new one (tx-3-linea-usdc). - pending approvals = [ tx-2-no-payment-token-set, tx-3-linea-usdc ] 5. The "Pay with" row is stuck in an infinite loading state because it always reads the first pending approval which in this case doesn't have a payment token set. I think this is why the rest of the screen is interactive except the "Pay with" row. Proposed Fixes - Post‑approval guard (existingPendingMusdConversion): Once the first musdConversion transaction is created, it shows up as a pending approval in Redux. Any subsequent CTA presses should reuse that existing pending conversion (and navigate back into its confirmation screen) instead of creating a second transaction. - Pre‑approval guard (inFlightInitiationPromises): There’s a short “race window” right after the user taps the CTA where we’ve started async initiation, but the approval hasn’t appeared in Redux yet. If the user spams the CTA in that window, the post‑approval guard can’t see anything to reuse yet—so we use a single-flight promise keyed by account+chain to ensure all concurrent calls await the same in-flight initiation and only one transaction gets created. ## **Changelog** CHANGELOG entry: prevent mUSD conversion initiation from creating duplicate transactions ## **Related issues** Fixes: [MUSD-275: During the convert flow, if a different stable coin is selected from the dropdown, the screen does not load. Unable to reproduce on android, only iOS once. After closing and re-opening the app, the issue is fixed.](https://consensyssoftware.atlassian.net/browse/MUSD-275) ## **Manual testing steps** ```gherkin Feature: Reliable mUSD conversion initiation Scenario: user re-enters an existing pending mUSD conversion Given user has a pending mUSD conversion approval for the selected account on the selected network When user taps the "Get 3% mUSD bonus" conversion CTA again Then user is navigated to the mUSD conversion confirmation And no additional conversion transaction is created Scenario: user taps the conversion CTA multiple times before the approval appears Given user is viewing a token that supports mUSD conversion on the selected network When user taps the "Get mUSD bonus" conversion CTA multiple times in rapid succession Then only one mUSD conversion transaction is created And the user is navigated to the mUSD conversion confirmation once ``` ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/c6af4fab-bc4e-4c0a-8f55-7a767df01368 ### **After** Spam clicking the mUSD conversion CTA doesn't break the "Pay with" menu https://github.com/user-attachments/assets/324cefa1-162d-4248-a1b8-2e9b907b4a4e ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- > [!NOTE] > **Medium Risk** > Changes mUSD conversion initiation control flow to reuse existing pending approvals and single-flight concurrent taps, which could affect transaction creation/navigation behavior if edge cases are missed. > > **Overview** > Prevents duplicate `musdConversion` transactions by (1) detecting an existing pending conversion for the same account+chain from `selectPendingApprovals`/`selectTransactionsByIds` and re-entering that flow, and (2) adding an in-memory single-flight map keyed by `address+chainId` to coalesce rapid taps before Redux reflects the pending approval. > > Updates `useMusdConversion` tests to cover concurrent initiation reuse and returning an existing pending conversion ID (including mixed-case address/chain handling), and wraps async hook calls in `act` to align with React state updates. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b4c0529022b888aa37b6c35892deb11843d81992. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../UI/Earn/hooks/useMusdConversion.test.ts | 175 ++++++++++++++++-- .../UI/Earn/hooks/useMusdConversion.ts | 171 +++++++++++++---- 2 files changed, 301 insertions(+), 45 deletions(-) diff --git a/app/components/UI/Earn/hooks/useMusdConversion.test.ts b/app/components/UI/Earn/hooks/useMusdConversion.test.ts index bd41b2be542..5108f4276dc 100644 --- a/app/components/UI/Earn/hooks/useMusdConversion.test.ts +++ b/app/components/UI/Earn/hooks/useMusdConversion.test.ts @@ -12,6 +12,8 @@ import { useSelector } from 'react-redux'; import { TransactionType } from '@metamask/transaction-controller'; import { selectMusdConversionEducationSeen } from '../../../../reducers/user'; import { trace, TraceName, TraceOperation } from '../../../../util/trace'; +import { RootState } from '../../../../reducers'; +import { selectSelectedInternalAccountByScope } from '../../../../selectors/multichainAccounts/accounts'; const mockTrace = trace as jest.MockedFunction; @@ -83,21 +85,44 @@ describe('useMusdConversion', () => { const setupUseSelectorMock = ({ selectedAccount = mockSelectedAccount, hasSeenConversionEducationScreen = true, + pendingApprovals = {}, + transactions = [], }: { selectedAccount?: typeof mockSelectedAccount | null; hasSeenConversionEducationScreen?: boolean; + pendingApprovals?: Record; + transactions?: { + id: string; + type?: TransactionType; + chainId?: Hex; + txParams?: { from?: string }; + }[]; } = {}) => { - const mockAccountSelector = jest.fn(() => selectedAccount); mockUseSelector.mockReset(); + const mockState = { + engine: { + backgroundState: { + ApprovalController: { + pendingApprovals, + }, + TransactionController: { + transactions, + }, + }, + }, + } as unknown as RootState; + mockUseSelector.mockImplementation((selector) => { if (selector === selectMusdConversionEducationSeen) { return hasSeenConversionEducationScreen; } - return mockAccountSelector; - }); + if (selector === selectSelectedInternalAccountByScope) { + return () => selectedAccount; + } - return { mockAccountSelector }; + return selector(mockState); + }); }; beforeEach(() => { @@ -141,7 +166,9 @@ describe('useMusdConversion', () => { const { result } = renderHook(() => useMusdConversion()); - await result.current.initiateConversion(mockConfig); + await act(async () => { + await result.current.initiateConversion(mockConfig); + }); expect(mockNavigation.navigate).toHaveBeenCalledWith(Routes.EARN.ROOT, { screen: Routes.FULL_SCREEN_CONFIRMATIONS.REDESIGNED_CONFIRMATIONS, @@ -155,6 +182,109 @@ describe('useMusdConversion', () => { }); }); + it('returns same transaction ID for concurrent initiations before approval exists', async () => { + setupUseSelectorMock(); + + mockNetworkController.findNetworkClientIdByChainId.mockReturnValue( + 'mainnet', + ); + + let resolveAddTransaction!: (value: { + transactionMeta: { id: string }; + }) => void; + const addTransactionPromise = new Promise<{ + transactionMeta: { id: string }; + }>((resolve) => { + resolveAddTransaction = resolve; + }); + mockTransactionController.addTransaction.mockReturnValue( + addTransactionPromise, + ); + + const { result } = renderHook(() => useMusdConversion()); + + let transactionIds!: [string | void, string | void]; + await act(async () => { + const firstCall = result.current.initiateConversion(mockConfig); + const secondCall = result.current.initiateConversion(mockConfig); + + resolveAddTransaction({ transactionMeta: { id: 'tx-123' } }); + + transactionIds = await Promise.all([firstCall, secondCall]); + }); + + expect(transactionIds).toEqual(['tx-123', 'tx-123']); + expect( + mockNetworkController.findNetworkClientIdByChainId, + ).toHaveBeenCalledTimes(1); + expect(mockTransactionController.addTransaction).toHaveBeenCalledTimes(1); + expect(mockNavigation.navigate).toHaveBeenCalledTimes(1); + }); + + it('returns existing pending musdConversion transaction ID for same account and chain', async () => { + setupUseSelectorMock({ + pendingApprovals: { + 'tx-existing': { id: 'tx-existing' }, + }, + transactions: [ + { + id: 'tx-existing', + type: TransactionType.musdConversion, + chainId: '0x1', + txParams: { from: mockSelectedAccount.address }, + }, + ], + }); + + const { result } = renderHook(() => useMusdConversion()); + + let transactionId!: string | void; + await act(async () => { + transactionId = await result.current.initiateConversion(mockConfig); + }); + + expect(transactionId).toBe('tx-existing'); + expect(mockNavigation.navigate).toHaveBeenCalledTimes(1); + expect( + mockNetworkController.findNetworkClientIdByChainId, + ).not.toHaveBeenCalled(); + expect(mockTransactionController.addTransaction).not.toHaveBeenCalled(); + }); + + it('returns existing pending musdConversion transaction ID with mixed-case chainId and from address', async () => { + setupUseSelectorMock({ + pendingApprovals: { + 'tx-existing': { id: 'tx-existing' }, + }, + transactions: [ + { + id: 'tx-existing', + type: TransactionType.musdConversion, + chainId: '0x1', + txParams: { + from: mockSelectedAccount.address.toUpperCase() as unknown as string, + }, + }, + ], + }); + + const { result } = renderHook(() => useMusdConversion()); + + let transactionId!: string | void; + await act(async () => { + transactionId = await result.current.initiateConversion({ + preferredPaymentToken: { + ...mockConfig.preferredPaymentToken, + chainId: '0x1' as Hex, + }, + }); + }); + + expect(transactionId).toBe('tx-existing'); + expect(mockNavigation.navigate).toHaveBeenCalledTimes(1); + expect(mockTransactionController.addTransaction).not.toHaveBeenCalled(); + }); + it('creates transaction with correct data structure', async () => { setupUseSelectorMock(); @@ -167,7 +297,9 @@ describe('useMusdConversion', () => { const { result } = renderHook(() => useMusdConversion()); - await result.current.initiateConversion(mockConfig); + await act(async () => { + await result.current.initiateConversion(mockConfig); + }); expect(mockTransactionController.addTransaction).toHaveBeenCalledWith( { @@ -242,7 +374,10 @@ describe('useMusdConversion', () => { const { result } = renderHook(() => useMusdConversion()); - const transactionId = await result.current.initiateConversion(mockConfig); + let transactionId!: string | void; + await act(async () => { + transactionId = await result.current.initiateConversion(mockConfig); + }); expect(transactionId).toBeUndefined(); expect(mockTransactionController.addTransaction).not.toHaveBeenCalled(); @@ -275,9 +410,12 @@ describe('useMusdConversion', () => { const { result } = renderHook(() => useMusdConversion()); - const transactionId = await result.current.initiateConversion({ - ...mockConfig, - skipEducationCheck: true, + let transactionId!: string | void; + await act(async () => { + transactionId = await result.current.initiateConversion({ + ...mockConfig, + skipEducationCheck: true, + }); }); expect(transactionId).toBe('tx-123'); @@ -334,7 +472,9 @@ describe('useMusdConversion', () => { navigationStack: 'CustomStack', }; - await result.current.initiateConversion(configWithCustomStack); + await act(async () => { + await result.current.initiateConversion(configWithCustomStack); + }); expect(mockNavigation.navigate).toHaveBeenCalledWith('CustomStack', { screen: Routes.FULL_SCREEN_CONFIRMATIONS.REDESIGNED_CONFIRMATIONS, @@ -360,7 +500,10 @@ describe('useMusdConversion', () => { const { result } = renderHook(() => useMusdConversion()); - const transactionId = await result.current.initiateConversion(mockConfig); + let transactionId!: string | void; + await act(async () => { + transactionId = await result.current.initiateConversion(mockConfig); + }); expect(transactionId).toBe('tx-123'); }); @@ -377,7 +520,9 @@ describe('useMusdConversion', () => { const { result } = renderHook(() => useMusdConversion()); - await result.current.initiateConversion(mockConfig); + await act(async () => { + await result.current.initiateConversion(mockConfig); + }); expect(mockTrace).toHaveBeenCalledWith({ name: TraceName.MusdConversionNavigation, @@ -409,7 +554,9 @@ describe('useMusdConversion', () => { const { result } = renderHook(() => useMusdConversion()); - await result.current.initiateConversion(mockConfig); + await act(async () => { + await result.current.initiateConversion(mockConfig); + }); expect(callOrder).toEqual(['trace', 'navigate']); }); diff --git a/app/components/UI/Earn/hooks/useMusdConversion.ts b/app/components/UI/Earn/hooks/useMusdConversion.ts index 3fafa97755c..2dbdbdce0c6 100644 --- a/app/components/UI/Earn/hooks/useMusdConversion.ts +++ b/app/components/UI/Earn/hooks/useMusdConversion.ts @@ -1,6 +1,11 @@ +import { + TransactionMeta, + TransactionType, +} from '@metamask/transaction-controller'; import { Hex } from '@metamask/utils'; -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; +import { isEqual } from 'lodash'; import Engine from '../../../../core/Engine'; import Logger from '../../../../util/Logger'; import { useNavigation } from '@react-navigation/native'; @@ -11,6 +16,60 @@ import { selectSelectedInternalAccountByScope } from '../../../../selectors/mult import { selectMusdConversionEducationSeen } from '../../../../reducers/user'; import { trace, TraceName, TraceOperation } from '../../../../util/trace'; import { createMusdConversionTransaction } from '../utils/musdConversionTransaction'; +import { selectPendingApprovals } from '../../../../selectors/approvalController'; +import { RootState } from '../../../../reducers'; +import { selectTransactionsByIds } from '../../../../selectors/transactionController'; + +/** + * Why do we have BOTH `existingPendingMusdConversion` AND `inFlightInitiationPromises`? + * + * These protect against two *different* duplication mechanisms: + * + * 1) `existingPendingMusdConversion` (post-approval creation / observable state): + * Once a `musdConversion` transaction is added, it becomes a pending approval in Redux. + * Subsequent CTA presses should **re-enter that existing flow** rather than creating a new tx. + * + * 2) `inFlightInitiationPromises` (pre-approval creation race window): + * There is a short window after the CTA press where we have started the async initiation + * but the pending approval is not yet observable in Redux. Rapid spam during that window + * can otherwise create multiple transactions before (1) can detect an existing pending tx. + */ +const inFlightInitiationPromises = new Map>(); + +function getInitiationKey(params: { selectedAddress: string; chainId: Hex }) { + const { selectedAddress, chainId } = params; + return `${selectedAddress.toLowerCase()}_${chainId.toLowerCase()}`; +} + +function findExistingPendingMusdConversion(params: { + pendingTransactionMetas: TransactionMeta[]; + selectedAddress: string; + preferredPaymentTokenChainId: Hex; +}) { + const { + pendingTransactionMetas, + selectedAddress, + preferredPaymentTokenChainId, + } = params; + + return pendingTransactionMetas.find((transactionMeta) => { + if (transactionMeta?.type !== TransactionType.musdConversion) { + return false; + } + + if ( + transactionMeta?.chainId.toLowerCase() !== + preferredPaymentTokenChainId.toLowerCase() + ) { + return false; + } + + return ( + transactionMeta?.txParams?.from?.toLowerCase() === + selectedAddress.toLowerCase() + ); + }); +} /** * Configuration for mUSD conversion @@ -57,6 +116,17 @@ export const useMusdConversion = () => { const [error, setError] = useState(null); const navigation = useNavigation(); + const pendingApprovals = useSelector(selectPendingApprovals, isEqual); + + const pendingApprovalIds = useMemo( + () => Object.keys(pendingApprovals ?? {}), + [pendingApprovals], + ); + + const pendingTransactionMetas = useSelector((state: RootState) => + selectTransactionsByIds(state, pendingApprovalIds), + ); + const selectedAccount = useSelector(selectSelectedInternalAccountByScope)( EVM_SCOPE, ); @@ -142,42 +212,80 @@ export const useMusdConversion = () => { throw new Error('No account selected'); } - const { NetworkController } = Engine.context; - const networkClientId = NetworkController.findNetworkClientIdByChainId( - preferredPaymentToken.chainId, + const existingPendingMusdConversion = findExistingPendingMusdConversion( + { + pendingTransactionMetas, + selectedAddress, + preferredPaymentTokenChainId: preferredPaymentToken.chainId, + }, ); - if (!networkClientId) { - throw new Error( - `Network client not found for chain ID: ${preferredPaymentToken.chainId}`, - ); - } - /** - * Navigate to the confirmation screen immediately for better UX, - * since there can be a delay between the user's button press and - * transaction creation in the background. + * Prevents the user from creating multiple transactions. + * Typically caused by the user quickly clicking the CTA multiple times in quick succession. */ - navigateToConversionScreen(config); + if (existingPendingMusdConversion?.id) { + navigateToConversionScreen(config); + return existingPendingMusdConversion.id; + } + + const initiationKey = getInitiationKey({ + selectedAddress, + chainId: preferredPaymentToken.chainId, + }); + + const inFlightInitiation = + inFlightInitiationPromises.get(initiationKey); + + if (inFlightInitiation) { + return await inFlightInitiation; + } + + const initiationPromise = (async () => { + const { NetworkController } = Engine.context; + const networkClientId = + NetworkController.findNetworkClientIdByChainId( + preferredPaymentToken.chainId, + ); + + if (!networkClientId) { + throw new Error( + `Network client not found for chain ID: ${preferredPaymentToken.chainId}`, + ); + } + + /** + * Navigate to the confirmation screen immediately for better UX, + * since there can be a delay between the user's button press and + * transaction creation in the background. + */ + navigateToConversionScreen(config); + + try { + const ZERO_HEX_VALUE = '0x0'; + const selectedAddressHex = selectedAddress as Hex; + + const { transactionId } = await createMusdConversionTransaction({ + chainId: preferredPaymentToken.chainId, + fromAddress: selectedAddressHex, + recipientAddress: selectedAddressHex, + amountHex: ZERO_HEX_VALUE, + networkClientId, + }); + + return transactionId; + } catch (err) { + // Prevent the user from being stuck on the confirmation screen without a transaction. + navigation.goBack(); + throw err; + } + })(); + inFlightInitiationPromises.set(initiationKey, initiationPromise); try { - const ZERO_HEX_VALUE = '0x0'; - - const selectedAddressHex = selectedAddress as Hex; - - const { transactionId } = await createMusdConversionTransaction({ - chainId: preferredPaymentToken.chainId, - fromAddress: selectedAddressHex, - recipientAddress: selectedAddressHex, - amountHex: ZERO_HEX_VALUE, - networkClientId, - }); - - return transactionId; - } catch (err) { - // Prevent the user from being stuck on the confirmation screen without a transaction. - navigation.goBack(); - throw err; + return await initiationPromise; + } finally { + inFlightInitiationPromises.delete(initiationKey); } } catch (err) { const errorMessage = @@ -199,6 +307,7 @@ export const useMusdConversion = () => { handleEducationRedirectIfNeeded, navigateToConversionScreen, navigation, + pendingTransactionMetas, selectedAddress, ], ); From 22f1621f60ee85790ab96c647ad28e3c948499d1 Mon Sep 17 00:00:00 2001 From: George Marshall Date: Wed, 4 Feb 2026 13:54:11 -0800 Subject: [PATCH 8/9] fix: improve perps tutorial animation alignment (#25664) ## **Description** Updated the perps onboarding carousel animations to remove empty space, improving the alignment and visual presentation in the carousel display. The animations now fit better within the carousel frame without unnecessary padding. ## **Changelog** CHANGELOG entry: Fixed perps tutorial animation alignment by removing empty space in carousel ## **Related issues** Fixes: N/A ## **Manual testing steps** ```gherkin Feature: Perps Onboarding Tutorial Scenario: user views perps onboarding carousel animations Given user opens the perps onboarding tutorial When user scrolls through the carousel Then animations should be properly aligned without empty space And dark mode animation should align correctly And light mode animation should align correctly ``` ## **Screenshots/Recordings** ### **Before** Animations had empty space causing misalignment in the carousel https://github.com/user-attachments/assets/95d0d833-3adb-41ff-b077-e4570064dce4 ### **After** Animations are properly aligned within the carousel frame https://github.com/user-attachments/assets/d5019d87-789f-44d5-b024-9e9d8d45bf8d ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- > [!NOTE] > **Low Risk** > The provided diff is empty (only shows `+++ /dev/null`), so there are no code or asset changes to review and minimal risk. > > **Overview** > No effective changes are shown in the provided diff (it only contains `+++ /dev/null`), so this PR appears to be a no-op from the perspective of the available patch. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 77dbb39f194611473a6f5aeb73b23c10b94a6ea8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../perps-onboarding-carousel-dark.riv | Bin 396242 -> 310457 bytes .../perps-onboarding-carousel-light.riv | Bin 388247 -> 301327 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/app/components/UI/Perps/animations/perps-onboarding-carousel-dark.riv b/app/components/UI/Perps/animations/perps-onboarding-carousel-dark.riv index 8265ba19aa5999d91b0dadb0d6782881d601382f..948bbcc2e73d44e7b645cddbdf53c7a5a170d898 100644 GIT binary patch delta 67567 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 V)RrQvx_xLZd9P_WTtVG?{{yk__>%wt delta 110002 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 AtN;K2 diff --git a/app/components/UI/Perps/animations/perps-onboarding-carousel-light.riv b/app/components/UI/Perps/animations/perps-onboarding-carousel-light.riv index c1a5f5d1bbcda5d93eb3f442eb89558dc3c32cd1..44ddf494009f8b5c656fe4b9e1d7717a6a62fed4 100644 GIT binary patch delta 73199 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 TZOJOMg1u(BRzWiu+#&xDv@)-{ delta 117278 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
  • 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 I+`zd12l`ETr2qf` From 7be712d23268f2ec506c3429ba22f51d58903ba9 Mon Sep 17 00:00:00 2001 From: Davide Brocchetto Date: Wed, 4 Feb 2026 15:03:29 -0800 Subject: [PATCH 9/9] test: Add custom slippage to swap smoke tests (#25667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Modifies the swap smoke E2E tests to include swap execution with custom slippage settings. Changes - Add SlippageModal page object and selectors to interact with slippage configuration - Add mock responses for swaps with custom slippage values - Update swap helpers to support setting custom slippage before submitting a swap https://github.com/user-attachments/assets/bc3baf0b-91ad-441c-9991-89c47ebcf889 ## **Changelog** CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [X] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [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](https://jsdoc.app/) format if applicable - [X] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [X] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [X] 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. --- > [!NOTE] > **Low Risk** > Test-only changes; main risk is increased E2E brittleness due to new UI interactions/selectors and slippage-specific mock URL matching. > > **Overview** > Extends swap E2E coverage to **configure and validate custom slippage** during swap submission. > > Adds a new `SlippageModal` page object + `SlippageModal.selectors` and enhances `QuoteView` with `verifySlippageDisplayed()` to assert the selected slippage percentage. > > Updates the swap helper `submitSwapUnifiedUI()` to accept an optional `slippage` override (defaulting to `2%`) and adjusts swap mocks/constants to return a dedicated quote when `slippage=3.5`, with the smoke test now executing an ETH→USDC swap using `3.5%` slippage. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 037b29bb107df87e1c8d8465bea962dcade869be. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- e2e/pages/swaps/QuoteView.ts | 20 ++++ e2e/pages/swaps/SlippageModal.ts | 107 ++++++++++++++++++ .../Bridge/SlippageModal.selectors.ts | 16 +++ tests/helpers/swap/constants.ts | 106 +++++++++++++++++ tests/helpers/swap/swap-mocks.ts | 13 ++- tests/helpers/swap/swap-unified-ui.ts | 19 ++++ tests/smoke/swap/swap-action-smoke.spec.ts | 5 +- 7 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 e2e/pages/swaps/SlippageModal.ts create mode 100644 e2e/selectors/Bridge/SlippageModal.selectors.ts diff --git a/e2e/pages/swaps/QuoteView.ts b/e2e/pages/swaps/QuoteView.ts index 9818f9f701d..5d49c08b9d5 100644 --- a/e2e/pages/swaps/QuoteView.ts +++ b/e2e/pages/swaps/QuoteView.ts @@ -1,6 +1,7 @@ import { waitFor } from 'detox'; import Matchers from '../../../tests/framework/Matchers'; import Gestures from '../../../tests/framework/Gestures'; +import Assertions from '../../../tests/framework/Assertions'; import { QuoteViewSelectorIDs, QuoteViewSelectorText, @@ -167,6 +168,25 @@ class QuoteView { elemDescription: 'Tap Max link to use maximum balance', }); } + + /** + * Gets the slippage display text element (e.g., "2.5%") + * @param value - The slippage value to match (e.g., "2.5" for "2.5%") + */ + slippageDisplayText(value: string): DetoxElement { + return Matchers.getElementByText(`${value}%`); + } + + /** + * Verifies that the slippage value is displayed correctly in the quote view + * @param value - The expected slippage value (e.g., "2.5" for 2.5%) + */ + async verifySlippageDisplayed(value: string): Promise { + await Assertions.expectElementToBeVisible(this.slippageDisplayText(value), { + timeout: 10000, + description: `Slippage should display ${value}%`, + }); + } } export default new QuoteView(); diff --git a/e2e/pages/swaps/SlippageModal.ts b/e2e/pages/swaps/SlippageModal.ts new file mode 100644 index 00000000000..28ef58487ca --- /dev/null +++ b/e2e/pages/swaps/SlippageModal.ts @@ -0,0 +1,107 @@ +import Matchers from '../../../tests/framework/Matchers'; +import Gestures from '../../../tests/framework/Gestures'; +import Assertions from '../../../tests/framework/Assertions'; +import { + SlippageModalSelectorIDs, + SlippageModalSelectorText, +} from '../../selectors/Bridge/SlippageModal.selectors'; + +class SlippageModal { + get editSlippageButton(): DetoxElement { + return Matchers.getElementByID( + SlippageModalSelectorIDs.EDIT_SLIPPAGE_BUTTON, + ); + } + + get customButton(): DetoxElement { + return Matchers.getElementByText(SlippageModalSelectorText.CUSTOM); + } + + get confirmButton(): DetoxElement { + return Matchers.getElementByText(SlippageModalSelectorText.CONFIRM); + } + + get inputStepperInput(): DetoxElement { + return Matchers.getElementByID( + SlippageModalSelectorIDs.INPUT_STEPPER_INPUT, + ); + } + + get keypadDeleteButton(): DetoxElement { + return Matchers.getElementByID( + SlippageModalSelectorIDs.KEYPAD_DELETE_BUTTON, + ); + } + + /** + * Opens the slippage modal by tapping the edit slippage button + */ + async openSlippageModal(): Promise { + await Assertions.expectElementToBeVisible(this.editSlippageButton, { + timeout: 30000, + description: 'Edit slippage button should be visible', + }); + await Gestures.waitAndTap(this.editSlippageButton, { + elemDescription: 'Tap edit slippage button', + }); + } + + /** + * Taps the custom slippage option to open the custom slippage modal + */ + async tapCustomOption(): Promise { + await Assertions.expectElementToBeVisible(this.customButton, { + description: 'Custom slippage option should be visible', + }); + await Gestures.waitAndTap(this.customButton, { + elemDescription: 'Tap custom slippage option', + }); + } + + /** + * Enters a custom slippage value using the keypad + * @param value - The slippage value to enter (e.g., "2.5" for 2.5%) + */ + async enterCustomSlippage(value: string): Promise { + // Wait for the custom slippage modal to be visible + await Assertions.expectElementToBeVisible(this.inputStepperInput, { + description: 'Custom slippage input should be visible', + }); + + // Clear the existing value first by long pressing delete + await Gestures.longPress(this.keypadDeleteButton, { + duration: 1000, + elemDescription: 'Long press delete to clear slippage input', + }); + + // Enter each character of the value using the keypad + for (const char of value) { + const button = Matchers.getElementByText(char); + await Gestures.waitAndTap(button, { + elemDescription: `Tap keypad button ${char}`, + }); + } + } + + /** + * Confirms the custom slippage selection + */ + async confirmCustomSlippage(): Promise { + await Gestures.waitAndTap(this.confirmButton, { + elemDescription: 'Confirm custom slippage', + }); + } + + /** + * Sets a custom slippage value end-to-end + * @param value - The slippage value to set (e.g., "2.5" for 2.5%) + */ + async setCustomSlippage(value: string): Promise { + await this.openSlippageModal(); + await this.tapCustomOption(); + await this.enterCustomSlippage(value); + await this.confirmCustomSlippage(); + } +} + +export default new SlippageModal(); diff --git a/e2e/selectors/Bridge/SlippageModal.selectors.ts b/e2e/selectors/Bridge/SlippageModal.selectors.ts new file mode 100644 index 00000000000..04d1aeb57ee --- /dev/null +++ b/e2e/selectors/Bridge/SlippageModal.selectors.ts @@ -0,0 +1,16 @@ +import enContent from '../../../locales/languages/en.json'; + +export const SlippageModalSelectorIDs = { + EDIT_SLIPPAGE_BUTTON: 'edit-slippage-button', + INPUT_STEPPER_MINUS_BUTTON: 'input-stepper-minus-button', + INPUT_STEPPER_PLUS_BUTTON: 'input-stepper-plus-button', + INPUT_STEPPER_INPUT: 'input-stepper-input', + KEYPAD_DELETE_BUTTON: 'keypad-delete-button', +}; + +export const SlippageModalSelectorText = { + CUSTOM: enContent.bridge.custom, + CONFIRM: enContent.bridge.confirm, + SUBMIT: enContent.bridge.submit, + CANCEL: enContent.bridge.cancel, +}; diff --git a/tests/helpers/swap/constants.ts b/tests/helpers/swap/constants.ts index af534acf037..df905cbda80 100644 --- a/tests/helpers/swap/constants.ts +++ b/tests/helpers/swap/constants.ts @@ -111,6 +111,112 @@ export const GET_QUOTE_ETH_USDC_RESPONSE = [ }, ]; +export const GET_QUOTE_ETH_USDC_RESPONSE_CUSTOM_SLIPPAGE = [ + { + quote: { + requestId: + '0x8af512b95468bcb5b521462d943b3a57e08db167b59bac65cd7665be701b4f6e', + bridgeId: '1inch', + srcChainId: 1, + destChainId: 1, + aggregator: '1inch', + aggregatorType: 'AGG', + srcAsset: { + address: '0x0000000000000000000000000000000000000000', + chainId: 1, + assetId: 'eip155:1/slip44:60', + symbol: 'ETH', + decimals: 18, + name: 'Ether', + coingeckoId: 'ethereum', + aggregators: [], + occurrences: 100, + iconUrl: + 'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/slip44/60.png', + metadata: {}, + }, + srcTokenAmount: '991250000000000000', + destAsset: { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + chainId: 1, + assetId: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + symbol: 'USDC', + decimals: 6, + name: 'USDC', + coingeckoId: 'usd-coin', + aggregators: [ + 'metamask', + 'oneInch', + 'liFi', + 'socket', + 'squid', + 'rango', + 'sonarwatch', + 'sushiSwap', + 'pmm', + 'bancor', + ], + occurrences: 10, + iconUrl: + 'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/erc20/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.png', + metadata: { + storage: { + balance: 9, + approval: 10, + }, + }, + }, + destTokenAmount: '2147920486', + minDestTokenAmount: '2072743268', + walletAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', + destWalletAddress: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', + feeData: { + metabridge: { + amount: '8750000000000000', + asset: { + address: '0x0000000000000000000000000000000000000000', + chainId: 1, + assetId: 'eip155:1/slip44:60', + symbol: 'ETH', + decimals: 18, + name: 'Ether', + coingeckoId: 'ethereum', + aggregators: [], + occurrences: 100, + iconUrl: + 'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/slip44/60.png', + metadata: {}, + }, + quoteBpsFee: 87.5, + baseBpsFee: 87.5, + }, + }, + bridges: ['1inch'], + protocols: ['1inch'], + steps: [], + slippage: 3.5, + gasSponsored: false, + gasIncluded7702: false, + priceData: { + totalFromAmountUsd: '2162.88', + totalToAmountUsd: '2147.121459579208', + priceImpact: '-0.0014770178826568753', + totalFeeAmountUsd: '18.925200000000004', + }, + }, + trade: { + chainId: 1, + to: '0x881D40237659C251811CEC9c364ef91dC08D300C', + from: '0x76cf1CdD1fcC252442b50D6e97207228aA4aefC3', + value: '0xde0b6b3a7640000', + data: '0x5f575529000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000136f6e65496e6368563646656544796e616d69630000000000000000000000000000000000000000000000000000000000000000000000000000000000000003600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000007b8b8d640000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000001f161421c8e000000000000000000000000000f326e4de8f66a0bdc0970b79e0924e33c79f19150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022807ed2379000000000000000000000000990636ecb3ff04d33d92e970d3d588bf5cd8d086000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000990636ecb3ff04d33d92e970d3d588bf5cd8d08600000000000000000000000074de5d4fcbf63e00296fd95d33236b97940166310000000000000000000000000000000000000000000000000dc1a09f859b2000000000000000000000000000000000000000000000000000000000007b8b8d640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d70000000000000000000000000000000000000000000000000000b900004e00a0744c8c09000000000000000000000000000000000000000090cbe4bdd538d6e9b379bff5fe72c3d67a521de500000000000000000000000000000000000000000000000000010e76033d760002a0000000000000000000000000000000000000000000000000000000007b8b8d6448c95033810000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f400000a0000111111125421ca6dc452d289314280a0f8842a650000000000000000007dcbea7c00000000000000000000000000000000000000000000000094', + gasLimit: 311852, + effectiveGas: 234172, + }, + estimatedProcessingTimeInSeconds: 0, + }, +]; + export const GET_QUOTE_ETH_DAI_RESPONSE = [ { quote: { diff --git a/tests/helpers/swap/swap-mocks.ts b/tests/helpers/swap/swap-mocks.ts index 342a6d6daf3..c4b40b7d4ea 100644 --- a/tests/helpers/swap/swap-mocks.ts +++ b/tests/helpers/swap/swap-mocks.ts @@ -6,6 +6,7 @@ import { } from '../../api-mocking/helpers/mockHelpers'; import { GET_QUOTE_ETH_USDC_RESPONSE, + GET_QUOTE_ETH_USDC_RESPONSE_CUSTOM_SLIPPAGE, GET_QUOTE_ETH_DAI_RESPONSE, GET_QUOTE_USDC_USDT_RESPONSE, GET_TOKENS_MAINNET_RESPONSE, @@ -29,14 +30,22 @@ export const testSpecificMock: TestSpecificMock = async ( responseCode: 200, }); - // Mock ETH->USDC + // Mock ETH->USDC with default 2% slippage await setupMockRequest(mockServer, { requestMethod: 'GET', - url: /getQuote.*destTokenAddress=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/i, + url: /getQuote.*destTokenAddress=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.*slippage=2/i, response: GET_QUOTE_ETH_USDC_RESPONSE, responseCode: 200, }); + // Mock ETH->USDC with 3.5% custom slippage + await setupMockRequest(mockServer, { + requestMethod: 'GET', + url: /getQuote.*destTokenAddress=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48.*slippage=3.5/i, + response: GET_QUOTE_ETH_USDC_RESPONSE_CUSTOM_SLIPPAGE, + responseCode: 200, + }); + // Mock ETH->DAI await setupMockRequest(mockServer, { requestMethod: 'GET', diff --git a/tests/helpers/swap/swap-unified-ui.ts b/tests/helpers/swap/swap-unified-ui.ts index 96297739449..7f2d91e813a 100644 --- a/tests/helpers/swap/swap-unified-ui.ts +++ b/tests/helpers/swap/swap-unified-ui.ts @@ -1,15 +1,23 @@ import TestHelpers from '../../../e2e/helpers'; import QuoteView from '../../../e2e/pages/swaps/QuoteView'; +import SlippageModal from '../../../e2e/pages/swaps/SlippageModal'; import Assertions from '../../framework/Assertions'; import ActivitiesView from '../../../e2e/pages/Transactions/ActivitiesView'; import { ActivitiesViewSelectorsText } from '../../../app/components/Views/ActivityView/ActivitiesView.testIds'; +interface SwapOptions { + /** Custom slippage percentage (e.g., "2.5" for 2.5%) */ + slippage?: string; +} + export async function submitSwapUnifiedUI( quantity: string, sourceTokenSymbol: string, destTokenSymbol: string, chainId: string, + options?: SwapOptions, ) { + const DEFAULT_SLIPPAGE_VALUE = '2'; await device.disableSynchronization(); await Assertions.expectElementToBeVisible(QuoteView.selectAmountLabel); await QuoteView.enterAmount(quantity); @@ -23,7 +31,18 @@ export async function submitSwapUnifiedUI( await Assertions.expectElementToBeVisible(QuoteView.networkFeeLabel, { timeout: 60000, }); + + // Set custom slippage if provided + if (options?.slippage) { + await SlippageModal.setCustomSlippage(options.slippage); + // Verify the slippage has been updated in the quote view + await QuoteView.verifySlippageDisplayed(options.slippage); + } else { + await QuoteView.verifySlippageDisplayed(DEFAULT_SLIPPAGE_VALUE); + } + await Assertions.expectElementToBeVisible(QuoteView.confirmSwap); + await QuoteView.tapConfirmSwap(); } diff --git a/tests/smoke/swap/swap-action-smoke.spec.ts b/tests/smoke/swap/swap-action-smoke.spec.ts index 4c672053ae5..72fd0c36922 100644 --- a/tests/smoke/swap/swap-action-smoke.spec.ts +++ b/tests/smoke/swap/swap-action-smoke.spec.ts @@ -34,7 +34,7 @@ describe(SmokeTrade('Swap from Actions'), (): void => { jest.setTimeout(120000); }); - it('should swap ETH to USDC', async (): Promise => { + it('swaps ETH to USDC with custom slippage', async (): Promise => { const quantity = '1'; const sourceTokenSymbol = 'ETH'; const destTokenSymbol = 'USDC'; @@ -90,12 +90,13 @@ describe(SmokeTrade('Swap from Actions'), (): void => { await prepareSwapsTestEnvironment(); await WalletView.tapWalletSwapButton(); - // Submit the Swap + // Submit swap with 3.5% custom slippage await submitSwapUnifiedUI( quantity, sourceTokenSymbol, destTokenSymbol, chainId, + { slippage: '3.5' }, ); // Check the swap activity completed