Skip to content

Commit 2006796

Browse files
authored
feat(card): disable swaps if selected address is different from priority token owner address (#21454)
<!-- 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** <!-- 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? --> Implements account address validation for the Card feature's "Add Funds" functionality. The feature now disables swaps when the priority token owner address doesn't match the user's selected EVM or Solana account address. ## **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: Disabled "Add Funds" button when priority token address doesn't match selected account on Card feature ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Card Add Funds Swap Validation Scenario: Add Funds button enabled when accounts match Given the priority token address matches the selected account When the Card Home loads Then the "Add Funds" button should be enabled Scenario: Add Funds button disabled when accounts don't match Given the priority token address doesn't match the selected account When the Card Home loads Then the "Add Funds" button should be disabled Scenario: EVM addresses compared case-insensitively Given an EVM account with address "0x1234...5678" And a priority token with address "0xABCD...5678" (different case) When comparing addresses Then they should match (case-insensitive) Scenario: Solana addresses compared case-sensitively Given a Solana account with address "SolanaAddress123" And a priority token with address "solanaaddress123" (different case) When comparing addresses Then they should NOT match (case-sensitive) ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/75b7c5f1-44ce-491d-a728-be8b58092e7a ## **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] > Disables the Card Home Add Funds action based on a new address-matching hook, adds disabled styling/tests, and improves SDK error logging for external wallet fetches. > > - **Card Home**: > - Integrates `useIsSwapEnabledForPriorityToken` to gate `Add Funds` by address match; sets `disabled` and applies `styles.halfWidthButtonDisabled` when not allowed. > - Updates `ButtonsSection` deps to include `isSwapEnabledForPriorityToken`. > - **New Hook**: `useIsSwapEnabledForPriorityToken` > - Returns true when not authenticated; otherwise requires priority token owner address to match selected account (EVM case-insensitive, Solana case-sensitive). > - **Styles**: Adds `halfWidthButtonDisabled` in `CardHome.styles.ts`. > - **Tests**: > - Adds `useIsSwapEnabledForPriorityToken.test.ts` covering auth and EVM/Solana comparisons. > - Extends `CardHome.test.tsx` to verify `Add Funds` enabled/disabled behavior and styling; updates snapshots. > - **SDK** (`CardSDK.ts`): Enhances error logging by attempting to parse and log JSON bodies when fetching external and priority wallet details fails. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5af32de. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 773d392 commit 2006796

File tree

7 files changed

+422
-2
lines changed

7 files changed

+422
-2
lines changed

app/components/UI/Card/Views/CardHome/CardHome.styles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ const createStyles = (theme: Theme) =>
9797
halfWidthButton: {
9898
width: '50%',
9999
},
100+
halfWidthButtonDisabled: {
101+
width: '50%',
102+
opacity: 0.5,
103+
},
100104
shouldBeHidden: {
101105
display: 'none',
102106
},

app/components/UI/Card/Views/CardHome/CardHome.test.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
selectCardholderAccounts,
6464
selectIsAuthenticatedCard,
6565
} from '../../../../../core/redux/slices/card';
66+
import { useIsSwapEnabledForPriorityToken } from '../../hooks/useIsSwapEnabledForPriorityToken';
6667

6768
const mockNavigate = jest.fn();
6869
const mockGoBack = jest.fn();
@@ -175,6 +176,10 @@ jest.mock('../../hooks/useOpenSwaps', () => ({
175176
useOpenSwaps: jest.fn(),
176177
}));
177178

179+
jest.mock('../../hooks/useIsSwapEnabledForPriorityToken', () => ({
180+
useIsSwapEnabledForPriorityToken: jest.fn(),
181+
}));
182+
178183
jest.mock('../../../../hooks/useMetrics', () => ({
179184
useMetrics: jest.fn(),
180185
MetaMetricsEvents: {
@@ -464,6 +469,8 @@ describe('CardHome Component', () => {
464469
error: null,
465470
});
466471

472+
(useIsSwapEnabledForPriorityToken as jest.Mock).mockReturnValue(true);
473+
467474
// Setup default selectors
468475
setupMockSelectors();
469476
});
@@ -1076,6 +1083,81 @@ describe('CardHome Component', () => {
10761083
);
10771084
});
10781085

1086+
describe('Swap Enabled for Priority Token', () => {
1087+
it('disables add funds button when swap is not enabled for priority token', () => {
1088+
// Given: swap is not enabled for the priority token
1089+
(useIsSwapEnabledForPriorityToken as jest.Mock).mockReturnValueOnce(
1090+
false,
1091+
);
1092+
1093+
// When: component renders
1094+
render();
1095+
1096+
// Then: add funds button should exist and be disabled
1097+
const addFundsButton = screen.getByTestId(
1098+
CardHomeSelectors.ADD_FUNDS_BUTTON,
1099+
);
1100+
expect(addFundsButton).toBeTruthy();
1101+
// Button should have disabled styling applied
1102+
expect(addFundsButton.props.disabled).toBe(true);
1103+
});
1104+
1105+
it('enables add funds button when swap is enabled for priority token', () => {
1106+
// Given: swap is enabled for the priority token
1107+
(useIsSwapEnabledForPriorityToken as jest.Mock).mockReturnValueOnce(true);
1108+
1109+
// When: component renders
1110+
render();
1111+
1112+
// Then: add funds button should be enabled
1113+
const addFundsButton = screen.getByTestId(
1114+
CardHomeSelectors.ADD_FUNDS_BUTTON,
1115+
);
1116+
expect(addFundsButton).toBeTruthy();
1117+
expect(addFundsButton.props.disabled).toBe(false);
1118+
});
1119+
1120+
it('applies disabled styling when swap is not enabled', () => {
1121+
// Given: swap is not enabled for the priority token
1122+
(useIsSwapEnabledForPriorityToken as jest.Mock).mockReturnValueOnce(
1123+
false,
1124+
);
1125+
1126+
// When: component renders
1127+
render();
1128+
1129+
// Then: button should have disabled prop set to true
1130+
const addFundsButton = screen.getByTestId(
1131+
CardHomeSelectors.ADD_FUNDS_BUTTON,
1132+
);
1133+
expect(addFundsButton).toBeTruthy();
1134+
expect(addFundsButton.props.disabled).toBe(true);
1135+
});
1136+
1137+
it('does not disable button when swap is enabled for priority token', async () => {
1138+
// Given: swap is enabled for the priority token
1139+
(useIsSwapEnabledForPriorityToken as jest.Mock).mockReturnValueOnce(true);
1140+
1141+
// When: component renders
1142+
render();
1143+
1144+
// Then: add funds button should be enabled and callable
1145+
const addFundsButton = screen.getByTestId(
1146+
CardHomeSelectors.ADD_FUNDS_BUTTON,
1147+
);
1148+
expect(addFundsButton.props.disabled).toBe(false);
1149+
1150+
mockTrackEvent.mockClear();
1151+
fireEvent.press(addFundsButton);
1152+
1153+
// When: user presses add funds button
1154+
// Then: should track event
1155+
await waitFor(() => {
1156+
expect(mockTrackEvent).toHaveBeenCalled();
1157+
});
1158+
});
1159+
});
1160+
10791161
describe('Baanx Login Features', () => {
10801162
it('shows change asset button when Baanx login is enabled', () => {
10811163
// Given: Baanx login is enabled (default)

app/components/UI/Card/Views/CardHome/CardHome.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import useIsBaanxLoginEnabled from '../../hooks/isBaanxLoginEnabled';
5656
import useCardDetails from '../../hooks/useCardDetails';
5757
import CardWarningBox from '../../components/CardWarningBox/CardWarningBox';
5858
import { selectIsAuthenticatedCard } from '../../../../../core/redux/slices/card';
59+
import { useIsSwapEnabledForPriorityToken } from '../../hooks/useIsSwapEnabledForPriorityToken';
5960

6061
/**
6162
* CardHome Component
@@ -105,6 +106,9 @@ const CardHome = () => {
105106
const { openSwaps } = useOpenSwaps({
106107
priorityToken,
107108
});
109+
const isSwapEnabledForPriorityToken = useIsSwapEnabledForPriorityToken(
110+
priorityToken?.walletAddress,
111+
);
108112

109113
const toggleIsBalanceAndAssetsHidden = useCallback(
110114
(value: boolean) => {
@@ -274,11 +278,16 @@ const CardHome = () => {
274278
<View style={styles.buttonsContainer}>
275279
<Button
276280
variant={ButtonVariants.Primary}
277-
style={styles.halfWidthButton}
281+
style={
282+
!isSwapEnabledForPriorityToken
283+
? styles.halfWidthButtonDisabled
284+
: styles.halfWidthButton
285+
}
278286
label={strings('card.card_home.add_funds')}
279287
size={ButtonSize.Lg}
280288
onPress={addFundsAction}
281289
width={ButtonWidthTypes.Full}
290+
disabled={!isSwapEnabledForPriorityToken}
282291
loading={isLoading}
283292
testID={CardHomeSelectors.ADD_FUNDS_BUTTON}
284293
/>
@@ -309,7 +318,12 @@ const CardHome = () => {
309318
);
310319
// eslint-disable-next-line react-compiler/react-compiler
311320
// eslint-disable-next-line react-hooks/exhaustive-deps
312-
}, [addFundsAction, priorityTokenWarning, isLoading]);
321+
}, [
322+
addFundsAction,
323+
priorityTokenWarning,
324+
isLoading,
325+
isSwapEnabledForPriorityToken,
326+
]);
313327

314328
const error = useMemo(
315329
() => priorityTokenError || cardDetailsError,

app/components/UI/Card/Views/CardHome/__snapshots__/CardHome.test.tsx.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ exports[`CardHome Component renders correctly and matches snapshot 1`] = `
448448
accessibilityRole="button"
449449
accessible={true}
450450
activeOpacity={1}
451+
disabled={false}
451452
loading={false}
452453
onPress={[Function]}
453454
onPressIn={[Function]}
@@ -1189,6 +1190,7 @@ exports[`CardHome Component renders correctly with privacy mode enabled 1`] = `
11891190
accessibilityRole="button"
11901191
accessible={true}
11911192
activeOpacity={1}
1193+
disabled={false}
11921194
loading={false}
11931195
onPress={[Function]}
11941196
onPressIn={[Function]}

0 commit comments

Comments
 (0)