Skip to content

Commit 2eacab6

Browse files
authored
fix: max button 7702 re-enable cp-7.69.0 (MetaMask#27373)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Fixes a bug where the max button was no longer showing on select networks. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: MetaMask#27381 ## **Manual testing steps** ```gherkin Feature: swap max button Scenario: user is using the swaps experience Given the chain they are on supports gasless When user selects tokens Then the max button should be visible ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the gating logic for showing the Bridge/Swap “Max” option for native assets, so regressions could hide/show the button on certain networks depending on `gasIncluded`/`gasIncluded7702` selector outputs. > > **Overview** > Updates `useShouldRenderMaxOption` to **stop depending on Smart Transactions/send-bundle support and quote sponsorship** and instead show the Max option for native assets when `selectGasIncludedQuoteParams` indicates `gasIncluded` or `gasIncluded7702`. > > Refactors the corresponding hook tests to mock the new selector shape and to cover gas-included vs 7702-enabled vs disabled scenarios (including non‑EVM native tokens), removing the old STX/send-bundle assertions. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e2b4a06. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 3ac4c6e commit 2eacab6

2 files changed

Lines changed: 46 additions & 98 deletions

File tree

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,17 @@
1-
import { useMemo } from 'react';
21
import { useSelector } from 'react-redux';
3-
import { selectShouldUseSmartTransaction } from '../../../../../selectors/smartTransactionsController';
4-
import { RootState } from '../../../../../reducers';
2+
import { selectGasIncludedQuoteParams } from '../../../../../selectors/bridge';
53
import { BridgeToken } from '../../types';
64
import { useTokenAddress } from '../useTokenAddress';
7-
import {
8-
formatChainIdToHex,
9-
isNativeAddress,
10-
isNonEvmChainId,
11-
} from '@metamask/bridge-controller';
5+
import { isNativeAddress } from '@metamask/bridge-controller';
126
import { BigNumber } from 'bignumber.js';
13-
import { useIsSendBundleSupported } from '../useIsSendBundleSupported';
147

158
export const useShouldRenderMaxOption = (
169
token?: BridgeToken,
1710
displayBalance?: string,
18-
isQuoteSponsored = false,
11+
_isQuoteSponsored = false,
1912
) => {
20-
const evmChainId = useMemo(() => {
21-
if (!token?.chainId || isNonEvmChainId(token.chainId)) {
22-
return undefined;
23-
}
24-
return formatChainIdToHex(token.chainId);
25-
}, [token?.chainId]);
26-
const isSendBundleSupported = useIsSendBundleSupported(evmChainId);
27-
const isGaslessSwapEnabled = useMemo(
28-
() => Boolean(isSendBundleSupported),
29-
[isSendBundleSupported],
30-
);
31-
const stxEnabled = useSelector((state: RootState) =>
32-
token?.chainId && !isNonEvmChainId(token.chainId)
33-
? selectShouldUseSmartTransaction(
34-
state,
35-
formatChainIdToHex(token.chainId),
36-
)
37-
: false,
13+
const { gasIncluded, gasIncluded7702 } = useSelector(
14+
selectGasIncludedQuoteParams,
3815
);
3916
const tokenAddress = useTokenAddress(token);
4017
const isNativeAsset = isNativeAddress(tokenAddress);
@@ -50,10 +27,5 @@ export const useShouldRenderMaxOption = (
5027
return true;
5128
}
5229

53-
// Show for EVM native tokens if gasless swap is enabled OR quote is sponsored
54-
// while smart transactions is enabled.
55-
// For non-EVM native tokens stxEnabled will be false evaluating the whole
56-
// expression to false. We do not know the fees beforehand so we cannot
57-
// max out the input amount.
58-
return (isGaslessSwapEnabled || isQuoteSponsored) && stxEnabled;
30+
return gasIncluded || gasIncluded7702;
5931
};
Lines changed: 40 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { renderHook } from '@testing-library/react-hooks';
22
import { CHAIN_IDS } from '@metamask/transaction-controller';
3-
import {
4-
formatChainIdToHex,
5-
isNativeAddress,
6-
isNonEvmChainId,
7-
} from '@metamask/bridge-controller';
3+
import { isNativeAddress } from '@metamask/bridge-controller';
84
import { useSelector } from 'react-redux';
95
import { useShouldRenderMaxOption } from '.';
106
import { BridgeToken } from '../../types';
117
import { useTokenAddress } from '../useTokenAddress';
12-
import { useIsSendBundleSupported } from '../useIsSendBundleSupported';
138

149
jest.mock('react-redux', () => ({
1510
useSelector: jest.fn(),
@@ -19,37 +14,21 @@ jest.mock('../useTokenAddress', () => ({
1914
useTokenAddress: jest.fn(),
2015
}));
2116

22-
jest.mock('../useIsSendBundleSupported', () => ({
23-
useIsSendBundleSupported: jest.fn(),
24-
}));
25-
2617
jest.mock('@metamask/bridge-controller', () => {
2718
const actual = jest.requireActual('@metamask/bridge-controller');
2819
return {
2920
...actual,
30-
formatChainIdToHex: jest.fn(),
3121
isNativeAddress: jest.fn(),
32-
isNonEvmChainId: jest.fn(),
3322
};
3423
});
3524

3625
const mockUseSelector = useSelector as jest.MockedFunction<typeof useSelector>;
3726
const mockUseTokenAddress = useTokenAddress as jest.MockedFunction<
3827
typeof useTokenAddress
3928
>;
40-
const mockUseIsSendBundleSupported =
41-
useIsSendBundleSupported as jest.MockedFunction<
42-
typeof useIsSendBundleSupported
43-
>;
44-
const mockFormatChainIdToHex = formatChainIdToHex as jest.MockedFunction<
45-
typeof formatChainIdToHex
46-
>;
4729
const mockIsNativeAddress = isNativeAddress as jest.MockedFunction<
4830
typeof isNativeAddress
4931
>;
50-
const mockIsNonEvmChainId = isNonEvmChainId as jest.MockedFunction<
51-
typeof isNonEvmChainId
52-
>;
5332

5433
const mockToken: BridgeToken = {
5534
address: '0x1234567890123456789012345678901234567890',
@@ -65,22 +44,23 @@ const nativeToken: BridgeToken = {
6544
chainId: CHAIN_IDS.MAINNET,
6645
};
6746

68-
const setSelectorValues = ({ stxEnabled = true }: { stxEnabled?: boolean }) => {
69-
mockUseSelector.mockImplementation(() => stxEnabled);
47+
const setSelectorValues = ({
48+
gasIncluded = false,
49+
gasIncluded7702 = false,
50+
}: {
51+
gasIncluded?: boolean;
52+
gasIncluded7702?: boolean;
53+
} = {}) => {
54+
mockUseSelector.mockImplementation(() => ({ gasIncluded, gasIncluded7702 }));
7055
};
7156

7257
describe('useShouldRenderMaxOption', () => {
7358
beforeEach(() => {
7459
jest.clearAllMocks();
7560

76-
setSelectorValues({ stxEnabled: true });
61+
setSelectorValues();
7762
mockUseTokenAddress.mockReturnValue(mockToken.address);
78-
mockUseIsSendBundleSupported.mockReturnValue(false);
79-
mockFormatChainIdToHex.mockImplementation(
80-
(chainId) => chainId as `0x${string}`,
81-
);
8263
mockIsNativeAddress.mockReturnValue(false);
83-
mockIsNonEvmChainId.mockReturnValue(false);
8464
});
8565

8666
it('returns false when token is undefined', () => {
@@ -109,11 +89,10 @@ describe('useShouldRenderMaxOption', () => {
10989
expect(result.current).toBe(true);
11090
});
11191

112-
it('returns true for native token when stx and sendBundle are enabled', () => {
113-
setSelectorValues({ stxEnabled: true });
92+
it('returns true for native token when gasIncluded is enabled', () => {
93+
setSelectorValues({ gasIncluded: true });
11494
mockUseTokenAddress.mockReturnValue(nativeToken.address);
11595
mockIsNativeAddress.mockReturnValue(true);
116-
mockUseIsSendBundleSupported.mockReturnValue(true);
11796

11897
const { result } = renderHook(() =>
11998
useShouldRenderMaxOption(nativeToken, '1.25'),
@@ -122,24 +101,28 @@ describe('useShouldRenderMaxOption', () => {
122101
expect(result.current).toBe(true);
123102
});
124103

125-
it('returns false for native token when sendBundle is disabled', () => {
126-
setSelectorValues({ stxEnabled: true });
104+
it('returns true for native token when 7702 is enabled', () => {
105+
setSelectorValues({
106+
gasIncluded: false,
107+
gasIncluded7702: true,
108+
});
127109
mockUseTokenAddress.mockReturnValue(nativeToken.address);
128110
mockIsNativeAddress.mockReturnValue(true);
129-
mockUseIsSendBundleSupported.mockReturnValue(false);
130111

131112
const { result } = renderHook(() =>
132113
useShouldRenderMaxOption(nativeToken, '1.25'),
133114
);
134115

135-
expect(result.current).toBe(false);
116+
expect(result.current).toBe(true);
136117
});
137118

138-
it('returns false for native token when stx is disabled even if sendBundle is enabled', () => {
139-
setSelectorValues({ stxEnabled: false });
119+
it('returns false for native token when gasIncluded and 7702 are both disabled', () => {
120+
setSelectorValues({
121+
gasIncluded: false,
122+
gasIncluded7702: false,
123+
});
140124
mockUseTokenAddress.mockReturnValue(nativeToken.address);
141125
mockIsNativeAddress.mockReturnValue(true);
142-
mockUseIsSendBundleSupported.mockReturnValue(true);
143126

144127
const { result } = renderHook(() =>
145128
useShouldRenderMaxOption(nativeToken, '1.25'),
@@ -148,60 +131,53 @@ describe('useShouldRenderMaxOption', () => {
148131
expect(result.current).toBe(false);
149132
});
150133

151-
it('returns true for sponsored native quote when stx is enabled', () => {
152-
setSelectorValues({ stxEnabled: true });
134+
it('returns false for sponsored native quote when gasIncluded paths are disabled', () => {
135+
setSelectorValues({
136+
gasIncluded: false,
137+
gasIncluded7702: false,
138+
});
153139
mockUseTokenAddress.mockReturnValue(nativeToken.address);
154140
mockIsNativeAddress.mockReturnValue(true);
155-
mockUseIsSendBundleSupported.mockReturnValue(false);
156141

157142
const { result } = renderHook(() =>
158143
useShouldRenderMaxOption(nativeToken, '1.25', true),
159144
);
160145

161-
expect(result.current).toBe(true);
146+
expect(result.current).toBe(false);
162147
});
163148

164-
it('returns false for sponsored native quote when stx is disabled', () => {
165-
setSelectorValues({ stxEnabled: false });
149+
it('returns true for sponsored native quote when 7702 path is enabled', () => {
150+
setSelectorValues({
151+
gasIncluded: false,
152+
gasIncluded7702: true,
153+
});
166154
mockUseTokenAddress.mockReturnValue(nativeToken.address);
167155
mockIsNativeAddress.mockReturnValue(true);
168156

169157
const { result } = renderHook(() =>
170158
useShouldRenderMaxOption(nativeToken, '1.25', true),
171159
);
172160

173-
expect(result.current).toBe(false);
174-
});
175-
176-
it('passes formatted EVM chain id to sendBundle hook', () => {
177-
const chainId = '0xa' as `0x${string}`;
178-
const formattedChainId = '0xa' as `0x${string}`;
179-
const token = { ...nativeToken, chainId };
180-
mockUseTokenAddress.mockReturnValue(token.address);
181-
mockIsNativeAddress.mockReturnValue(true);
182-
mockFormatChainIdToHex.mockReturnValue(formattedChainId);
183-
184-
renderHook(() => useShouldRenderMaxOption(token, '1.25'));
185-
186-
expect(mockUseIsSendBundleSupported).toHaveBeenCalledWith(formattedChainId);
161+
expect(result.current).toBe(true);
187162
});
188163

189-
it('passes undefined chain id to sendBundle hook for non-EVM token', () => {
164+
it('returns false for non-EVM native token when no gas-included path is enabled', () => {
190165
const solanaToken: BridgeToken = {
191166
...nativeToken,
192167
chainId:
193168
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' as `${string}:${string}`,
194169
};
170+
setSelectorValues({
171+
gasIncluded: false,
172+
gasIncluded7702: false,
173+
});
195174
mockUseTokenAddress.mockReturnValue(solanaToken.address);
196175
mockIsNativeAddress.mockReturnValue(true);
197-
mockIsNonEvmChainId.mockReturnValue(true);
198-
setSelectorValues({ stxEnabled: false });
199176

200177
const { result } = renderHook(() =>
201178
useShouldRenderMaxOption(solanaToken, '3'),
202179
);
203180

204-
expect(mockUseIsSendBundleSupported).toHaveBeenCalledWith(undefined);
205181
expect(result.current).toBe(false);
206182
});
207183
});

0 commit comments

Comments
 (0)